-
-
Save bitingsock/5e9714efff963c9689b0671d68f195ad to your computer and use it in GitHub Desktop.
--New child with start with the current 'aid' and 'audio-device' of the parent | |
-- non-Windows environments require the use of the 'socat' package | |
local platform_is_windows = (package.config:sub(1, 1) == "\\") | |
local options = require 'mp.options' | |
local o = { | |
pipe_template = platform_is_windows and "\\\\.\\pipe\\mpvDupedAudio" or "~/mpvDupedAudio", --windows format | |
new_child_key = 'Ctrl+A', --Start a new child: 'Ctrl+Shift+a' | |
cycle_child_control_key = 'Ctrl+Alt+A', --Cycle child control: 'Ctrl+Shift+Alt+a' | |
cycle_child_aid_key = 'Ctrl+Alt+a', --Cycle 'aid' of child: 'Ctrl+Alt+a' | |
child_increase_volume_key = 'Ctrl+Alt+WHEEL_UP', --Change volume of child: 'Ctrl+Alt+wheel' | |
child_decrease_volume_key = 'Ctrl+Alt+WHEEL_DOWN', --Change volume of child: 'Ctrl+Alt+wheel' | |
} | |
options.read_options(o) | |
local piper = platform_is_windows and " >" or " | socat - " | |
local mpvexe = "mpv" .. (platform_is_windows and ".com" or "") | |
local pipeInstance = {} | |
local control = 0 | |
local function sync(prop, newVal) | |
local timestamp = mp.get_property("audio-pts") or mp.get_property("time-pos") | |
for k, v in pairs(pipeInstance) do | |
if v then | |
os.execute("echo no-osd set time-pos " .. timestamp .. piper .. o.pipe_template .. k) | |
if prop == "speed" then os.execute("echo no-osd set speed " .. newVal .. piper .. o.pipe_template .. k) end | |
if prop == "pause" then os.execute("echo no-osd set pause " .. newVal .. piper .. o.pipe_template .. k) end | |
end | |
end | |
end | |
local function cycle_aid() | |
os.execute("echo cycle aid" .. piper .. o.pipe_template .. control) | |
end | |
local function switchControl() | |
local lastInstance = 0 | |
for k, v in ipairs(pipeInstance) do | |
if v then | |
lastInstance = k | |
end | |
end | |
if lastInstance == 0 then | |
control = 0 | |
return | |
end | |
if control >= lastInstance then control = 0 end | |
for k, v in ipairs(pipeInstance) do | |
if k > control and v then | |
control = k | |
return | |
end | |
end | |
end | |
local controlling = false | |
local function quit() | |
os.execute("echo quit" .. piper .. o.pipe_template .. control) | |
pipeInstance[control] = false | |
switchControl() | |
print(control) | |
if control == 0 then | |
print(">>> Removed last child <<<") | |
mp.remove_key_binding("quit") | |
mp.remove_key_binding("cycle_aid") | |
mp.remove_key_binding("volumeUP") | |
mp.remove_key_binding("volumeDOWN") | |
mp.unobserve_property(cycle_pause) | |
mp.unobserve_property(syncSpeed) | |
mp.unregister_event(sync) | |
controlling = false | |
else | |
print(">>> Now controlling child: " .. control .. " <<<") | |
end | |
end | |
local function start_child() | |
table.insert(pipeInstance, true) | |
control = #pipeInstance | |
print(">>> Now controlling child: " .. control .. " <<<") | |
mp.commandv("set", "msg-level", mp.get_script_name() .. "=warn") | |
if controlling == false then | |
mp.register_event("playback-restart", sync) | |
mp.add_forced_key_binding("q", "quit", quit) | |
mp.add_forced_key_binding(o.cycle_child_aid_key, "cycle_aid", cycle_aid) | |
mp.add_forced_key_binding(o.child_increase_volume_key, "volumeUP", function() os.execute("echo add volume 5" .. piper .. o.pipe_template .. control) end) | |
mp.add_forced_key_binding(o.child_decrease_volume_key, "volumeDOWN", function() os.execute("echo add volume -5" .. piper .. o.pipe_template .. control) end) | |
mp.add_timeout(0.25, function() | |
mp.observe_property("pause", "string", sync) | |
mp.observe_property("speed", "string", sync) | |
end) | |
controlling = true | |
end | |
mp.command_native_async({ | |
name = "subprocess", | |
args = { | |
mpvexe, | |
mp.get_property("path"), | |
"--speed=" .. mp.get_property("speed"), | |
"--pause=" .. mp.get_property("pause"), | |
"--no-video", | |
"--no-sub", | |
"--osd-level=1", | |
"--aid=" .. mp.get_property("aid"), | |
"--audio-device=" .. mp.get_property("audio-device"), | |
"--input-ipc-server=" .. o.pipe_template .. control, | |
"--start=" .. (mp.get_property("audio-pts") or mp.get_property("time-pos")) | |
}, | |
playback_only = false | |
}, function() end) | |
mp.add_timeout(0.25, function() | |
mp.commandv("set", "msg-level", mp.get_script_name() .. "=status") | |
end) | |
end | |
mp.add_forced_key_binding(o.cycle_child_control_key, "switchControl", function() | |
switchControl() | |
if control == 0 then | |
print(">>> No active children <<<") | |
else | |
print(">>> Now controlling child: " .. control .. " <<<") | |
end | |
end) | |
mp.add_forced_key_binding(o.new_child_key, "start", start_child) | |
mp.register_event("shutdown", function() | |
for k, _ in pairs(pipeInstance) do | |
os.execute("echo quit" .. piper .. o.pipe_template .. k) | |
end | |
end) |
Something in some DEs is probably eating any ALT combo.
The alt hotkeys work for me in Ubuntu Desktop. Sync also works after installing socat. Thanks a lot!
@bitingsock thank you for this awesome script. I noticed you used the following code to change the volume of the child:
os.execute("echo add volume 5" .. piper .. o.pipe_template .. control)
Just wondering if you know of a more direct approach, one that doesn't use the command line? I'm on windows and this results in my screen constantly flickering in fullscreen whenever I change the volume.
I am on windows and do not exhibit that behavior. IPC is the most direct way to control other processes. there surely is a different windows specific way to modify wasapi apps volume but I would prefer to address the real problem. Do you use a third party terminal emulator or something? Can you show a -v
log with these "flickers"? Arbitrary calls to the shell should not create a console window, normally.
Cant seem to get the child process to spawn, my hope was the main process would run the tv speakers via and the 2nd i could send to the virtual speakers in asiolinkpro which allow you to split off the individual dolby channels and send them to any speaker in your asio driver.
asiolinkprod river works in mpv splits the channels into 8 assigned to multiple soundcards with ease but mpvs bad handling of sound occasionally overloads the asio driver no matter how its normalised, requiring a quick driver reset several times per movie.
Potplayer does dual audio devices but the sync is not very tight between the 2
@Raidfire-SDR can you verify the child process is not spawning? Try adding a --log-file=temp.log
to the child arguments around line 100 and see if it dumps anything useful.
Hi, I got this working, my usage is a little different to the initial intention and I would like to modify the script so it will always run with a default config, I would like your input on how I would go about doing the following:
On runtime a child is spawned with both audio devices hardcoded.
I have managed successfully to run mpv on my default soundcard (10 channel pro studio rack) this gives me software control of the device so i can mute the front left, right and center and just get the sub and rear channels, spawn a child then using a profile swap this to an spdif stream i have everything sent to the tv and mute out the rear and sub, works beautifully, much better latency than voicemeeter, would just like to hard code it now so it all happens at runtime without the use of shortcuts.
I tried to disable the need for CTRL+A to spawn a child but i can't get it working, another angle would be, is there anyway to echo the command via script messaging from the main conf file, tried several iterations and couldn't get that running either.
I did however manage to set a static sound card for the child, so at least I'm 1 keypress away from full dolby at runtime. As a side note how easy would it be to access the individual channel gains for dolby that are exposed in mpv , directly in the lua script?
@Raidfire-SDR
I would probably do something like this at the very end of the script:
local function onstart()
if mp.get_property("vid") ~= "no" then
start_child()
mp.unregister_event(onstart)
end
end
mp.register_event("file-loaded", onstart)
and change line 104 "--audio-device=" .. mp.get_property("audio-device"),
to be the audio device that you want for the child.
The problem with starting the child at runtime is the info the child needs (media file path for example) is not populated yet, so we have to at least wait until playback actually starts.
Thanks that gave me a good starting point, after a little playing so i could see what was doing what in your script i found a nice way to handle the latency, tunable per your own system but seem solid once done, the 0.4 value is equivalent to a delay of 400ms (lua uses seconds mpv uses ms, actual behavior is the child is playing 500ms up front of the parent to negate the latency). also I couldn't get anything but null from audio-pts so i removed it and went with just time-pos, i believe there is a macro request to update the resolution of audio-pts and time-pos in the mpv - lua hooks, so latency should become less of an issue when we can get sub 100ms sync resolution in mpv, currently the closest you can pull 2 streams with ipc is 100ms, this seemed like a viable workaround. Failure to reset local controlling to false yielded some weird results!
local function onstart()
if mp.get_property("vid") ~= "no" then
start_child()
mp.unregister_event(onstart)
delay()
end
end
function delay()
os.execute("echo add audio-delay 0.4" .. piper .. o.pipe_template .. control)
end
mp.register_event("file-loaded", onstart)
seems this is controling the parent not the child, what am i doing wrong? the goal is to hardcode an audio delay to the child process
If you just need it hardcoded and not tunable you should just put it as a runtime option in the start_child function (line 93):
mp.command_native_async({
name = "subprocess",
args = {
mpvexe,
mp.get_property("path"),
"--speed=" .. mp.get_property("speed"),
"--pause=" .. mp.get_property("pause"),
"--no-video",
"--no-sub",
"--osd-level=1",
"--aid=" .. mp.get_property("aid"),
"--audio-device=" .. mp.get_property("audio-device"),
"--input-ipc-server=" .. o.pipe_template .. control,
"--start=" .. (mp.get_property("audio-pts") or mp.get_property("time-pos") or "0"),
"--audio-delay=0.4"
},
playback_only = false
}, function() end)
Works well need to fine tune the latency but almost there.
line 104 of lua hardcoded child process audio device
line 107 of lua latency tuning value
mpv conf - parent audio device and spdif passthru
https://github.com/Raidfire-SDR/V2-Videophile-MPV-Configs/blob/main/scripts/audio-dupe-dolby.lua
For Dolby to work well, use your device hosting the front speakers and center as the parent device set in mpv.conf, this ensures mpv's main a/v sync is locked to the voice tracks keeping the dubbing smooth set your back speaker device in lua and use a dolby test file to fine tune the latency value.
I am on windows and do not exhibit that behavior. IPC is the most direct way to control other processes. there surely is a different windows specific way to modify wasapi apps volume but I would prefer to address the real problem. Do you use a third party terminal emulator or something? Can you show a
-v
log with these "flickers"? Arbitrary calls to the shell should not create a console window, normally.
I have this problem, constant flickering of a cmd window on all commands passing through.
(Windows 10)
But more importantly i have something like a 3 second lag between the primary and secondary processes... I can't figure this out for the life of me. I even tried controlling the timing offset of the secondary process but it's not moving around (plus no matter what, i dont think a negative offset would even compute, if the secondary one is a dependant stream to the primary, right? IDK tbh.)
Oh and natural Q for Quit just flickers harder instead of quitting anything.
If i hardcode delay by adding
"--audio-delay=-3.6"
at line 107
it syncs perfectly at launch, but will go back to desync if i ever pause or skip.
Maybe useful to reveal that all mpv things like runtime keybinds to change delays, volume change, etc, are allowed from within the child process window, could help someone figure out that they can tune things on the fly... But ofc it is a less than ideal solution, just a workaround. (uuhh wait, this SEEMS to work, it shows my delay is correct at launch, but after a pause/skip, tweaking the delay actually doesn't change anything other than the HUD/UI values. Sigh sorry for the messy comment.)
As for the flickering issues (which I haven't been able to reproduce) you can try specifying the command to spawn in a minimized window to see if it helps. as a test, try replacing line 85:
mp.add_forced_key_binding(o.child_increase_volume_key, "volumeUP", function() os.execute("echo add volume 5" .. piper .. o.pipe_template .. control) end)
with
mp.add_forced_key_binding(o.child_increase_volume_key, "volumeUP", function() os.execute('START /MIN CMD.EXE /C "echo add volume 5' .. piper .. o.pipe_template .. control .. '"') end)
then see if volume up (default: Ctrl+Alt+WHEEL_UP) produces the issue.
as for the delay issue, I can only assume your systems are under heavy load. I ran some stressed scenarios and it was a bit out of step but nowhere near 3 seconds. Unfortunately, the only real fix without dependencies would only result in 1 second latency at best as lua's os.time() only produces seconds.
Another possibility is Windows Defender (or some other AV) is analyzing system calls from mpv and delaying it.
Just confirmed it working on debian(mate). Seeking and pausing is synced. Something in some DEs is probably eating any ALT combo.
I should make a note, but make sure you have socat installed. I would like to not need this but writing to the pipes directly proves challenging, if anyone knows a more native way to do it let me know. I saw netcat too but it is also not installed be default on my live debian 12 disc.