Guest User

drcbox

a guest
May 21st, 2024
116
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 8.13 KB | None | 0 0
  1. --[[
  2. mpv dynaudnorm filter with visual feedback.
  3.  
  4. Copyright 2016 Avi Halachmi ( https://github.com/avih )
  5. Copyright 2020 Paul B Mahol
  6. License: public domain
  7.  
  8. Needs mpv with very recent FFmpeg build.
  9.  
  10. Default config:
  11. - Enter/exit drcbox keys mode: ctrl+n
  12. - Toggle dynaudnorm without changing its values: ctrl+N (ctrl+shift+n)
  13. - Reset dynaudnorm values: alt+ctrl+n
  14. --]]
  15. -- ------ config -------
  16. local start_keys_enabled = false  -- if true then choose the up/down keys wisely
  17. local key_toggle_bindings = 'ctrl+n'  -- enable/disable drcbox key bindings
  18. local key_toggle_drcbox = 'n'  -- enable/disable drcbox
  19. local key_reset_drcbox = 'alt+ctrl+n'
  20. local altboundary = true -- don't reset loudness gain after seeking video  (tweak by SearchDownload)
  21. local dynamic_update = true -- reapply filter after changing options for updating params (after 0.5s timeout)
  22.  
  23. local options = {
  24.   {keys = {'2', '1'}, option = {'framelen',     1, 10, 8000,  250,  250 } },
  25.   {keys = {'4', '3'}, option = {'gausssize',    2,  3,  301,   31,   31 } },
  26.   {keys = {'6', '5'}, option = {'peak',      0.01,  0,    1, 0.95, 0.95 } },
  27.   {keys = {'8', '7'}, option = {'maxgain',      1,  1,  100,   10,   10 } },
  28.   {keys = {'0', '9'}, option = {'targetrms', 0.01,  0,    1,  0.9,  0.9 } },
  29.   {keys = {'=', '-'}, option = {'compress',   0.1,  0,   30,    0,    0 } },
  30.   {keys = {'w', 'q'}, option = {'coupling',     1,  0,    1,    1,    1 } },
  31.   {keys = {'r', 'e'}, option = {'correctdc',    1,  0,    1,    0,    0 } },
  32.  
  33. }
  34.  
  35.  
  36. local function get_cmd_full()
  37.   f = options[1].option[5]
  38.   g = options[2].option[5]
  39.   p = options[3].option[5]
  40.   m = options[4].option[5]
  41.   r = options[5].option[5]
  42.   s = options[6].option[5]
  43.   n = options[7].option[5]
  44.   c = options[8].option[5]
  45.   ab = ""
  46.   if altboundary then ab = ":b=1" end
  47.   return 'no-osd af toggle @dynaudnorm:lavfi=[dynaudnorm=f='..f..':g='..g..':p='..p..':m='..m..':r='..r..':n='..n..':c='..c..':s='..s..ab..']'
  48. end
  49.  
  50. local function get_cmd(option)
  51.   return 'no-osd af-command dynaudnorm '.. option[1] ..' '.. option[5]
  52. end
  53.  
  54. -- these two vars are used globally
  55. local bindings_enabled = start_keys_enabled
  56. local drcbox_enabled = false  -- but af is not touched before the dynaudnorm is modified
  57.  
  58. -- ------ OSD handling -------
  59. local function ass(x)
  60.   return x
  61. end
  62.  
  63. local function fsize(s)  -- 100 is the normal font size
  64.   return ass('{\\fscx' .. s .. '\\fscy' .. s ..'}')
  65. end
  66.  
  67. local function color(c)  -- c is RRGGBB
  68.   return ass('{\\1c&H' .. ss(c, 5, 7) .. ss(c, 3, 5) .. ss(c, 1, 3) .. '&}')
  69. end
  70.  
  71. function iff(cc, a, b) if cc then return a else return b end end
  72. function ss(s, from, to) return s:sub(from, to - 1) end
  73.  
  74. local function cnorm() return color('ffffff') end  -- white
  75. local function cdis()  return color('909090') end  -- grey
  76. local function ceq()   return iff(drcbox_enabled, color('ffff90'), cdis()) end  -- yellow-ish
  77. local function ckeys() return iff(bindings_enabled, color('90FF90'), cdis()) end  -- green-ish
  78.  
  79. local DUR_DEFAULT = 1.5 -- seconds
  80. local osd_timer = nil
  81. local prev_max = 130
  82. -- duration: seconds, or default if missing/nil, or infinite if 0 (or negative)
  83. local function ass_osd(msg, duration)  -- empty or missing msg -> just clears the OSD
  84.   duration = duration or DUR_DEFAULT
  85.   if not msg or msg == '' then
  86.     msg = '{}'  -- the API ignores empty string, but '{}' works to clean it up
  87.     duration = 0
  88.   end
  89.   mp.set_osd_ass(0, 0, msg)
  90.   if osd_timer then
  91.     osd_timer:kill()
  92.     osd_timer = nil
  93.   end
  94.   if duration > 0 then
  95.     osd_timer = mp.add_timeout(duration, ass_osd)  -- ass_osd() clears without a timer
  96.   end
  97. end
  98.  
  99. function round(num, numDecimalPlaces)
  100.   return tonumber(string.format("%." .. (numDecimalPlaces or 0) .. "f", num))
  101. end
  102.  
  103. -- some visual messing about
  104. local function updateOSD()
  105.   local msg1 = fsize(70) .. 'DynAudNorm: ' .. ceq() .. iff(drcbox_enabled, 'On', 'Off')
  106.             .. ' [' .. key_toggle_drcbox .. ']' .. cnorm()
  107.   local msg2 = fsize(70)
  108.             .. 'Key-bindings: ' .. ckeys() .. iff(bindings_enabled, 'On', 'Off')
  109.             .. ' [' .. key_toggle_bindings .. ']' .. cnorm()
  110.   local msg3 = ''
  111.  
  112.   for i = 1, #options do
  113.     local option = options[i].option[1]
  114.     local value = round(options[i].option[5], 2)
  115.     local default = options[i].option[6]
  116.     local info =
  117.       ceq() .. fsize(50) .. option .. ' ' .. fsize(100)
  118.       .. iff(value ~= default and drcbox_enabled, '', cdis()) .. value .. ceq()
  119.       .. fsize(50) .. ckeys() .. ' [' .. options[i].keys[2] .. '/' .. options[i].keys[1] .. ']'
  120.       .. ceq() .. fsize(100) .. cnorm()
  121.  
  122.      msg3 = msg3 .. '   ' .. info
  123.   end
  124.  
  125.   local nlb = '\n' .. ass('{\\an1}')  -- new line and "align bottom for next"
  126.   local msg = ass('{\\an1}') .. msg3 .. nlb .. msg2 .. nlb .. msg1
  127.   local duration = iff(start_keys_enabled, iff(bindings_enabled and drcbox_enabled, 5, nil)
  128.                                          , iff(bindings_enabled, 0, nil))
  129.   ass_osd(msg, duration)
  130. end
  131.  
  132.  
  133. local function update_key_binding(enable, key, name, fn)
  134.   if enable then
  135.     mp.add_forced_key_binding(key, name, fn, 'repeatable')
  136.   else
  137.     mp.remove_key_binding(name)
  138.   end
  139. end
  140.  
  141. local function updateAF()
  142.   mp.command(get_cmd_full())
  143. end
  144.  
  145. local function updateAF_options()
  146.   if not drcbox_enabled then return end
  147.   for i = 1, #options do
  148.     local o = options[i].option
  149.     mp.command(get_cmd(o))
  150.   end
  151. end
  152. local timer2 = mp.add_timeout(0.5, function() updateAF(); updateAF() end)
  153. timer2:kill()
  154. local function getBind(option, delta)
  155.   return function()  -- onKey
  156.     option[5] = option[5] + delta
  157.     if option[5] > option[4] then
  158.       option[5] = option[4]
  159.     end
  160.     if option[5] < option[3] then
  161.       option[5] = option[3]
  162.     end
  163.     if dynamic_update then
  164.       timer2:kill()
  165.       timer2:resume()
  166.     else
  167.       updateAF_options() --very slow and buggy
  168.     end
  169.     updateOSD()
  170.   end
  171. end
  172.  
  173. local function toggle_bindings(explicit, no_osd)
  174.   bindings_enabled = iff(explicit ~= nil, explicit, not bindings_enabled)
  175.   for i = 1, #options do
  176.     local keys = options[i].keys
  177.     local option = options[i].option[1]
  178.     local delta = options[i].option[2]
  179.     update_key_binding(bindings_enabled, keys[1], 'eq' .. keys[1], getBind(options[i].option,  delta)) -- up
  180.     update_key_binding(bindings_enabled, keys[2], 'eq' .. keys[2], getBind(options[i].option, -delta)) -- down
  181.   end
  182.   if not no_osd then updateOSD() end
  183. end
  184.  
  185. local function toggle_drcbox()
  186.   drcbox_enabled = not drcbox_enabled
  187.   if drcbox_enabled then
  188.     prev_max = mp.get_property("volume-max")
  189.     mp.set_property("volume-max", 100)
  190.     if mp.get_property_native("volume") > 100 then mp.set_property("volume", 100) end
  191.   else
  192.     mp.set_property("volume-max", prev_max)
  193.   end
  194.   updateAF()
  195.   updateOSD()
  196. end
  197.  
  198. local function reset_drcbox()
  199.   for i = 1, #options do
  200.     options[i].option[5] = options[i].option[6]
  201.   end
  202.   updateAF_options()
  203.   updateOSD()
  204. end
  205.  
  206. mp.register_script_message("toggle_normalize", toggle_drcbox)
  207. mp.add_forced_key_binding(key_toggle_bindings, toggle_bindings)
  208. mp.add_forced_key_binding(key_reset_drcbox, reset_drcbox)
  209. if bindings_enabled then toggle_bindings(true, true) end
  210.  
  211.  
  212. mp.register_event("file-loaded", function()
  213.     if drcbox_enabled and mp.get_property("af") and not string.find(mp.get_property("af"), "@dynaudnorm") then
  214.         mp.add_timeout(0.5, updateAF)
  215.     end
  216.     if drcbox_enabled == false and mp.get_property("af") and string.find(mp.get_property("af"), "@dynaudnorm") then
  217.         drcbox_enabled = true
  218.         prev_max = mp.get_property("volume-max")
  219.         mp.set_property("volume-max", 100)
  220.         if mp.get_property_native("volume") > 100 then mp.set_property("volume", 100) end
  221.         local filter = string.sub(string.match(mp.get_property("af"), "dynaudnorm=[^,]*"), 12)
  222.        
  223.         local function update(n, param)
  224.             local val = string.match(filter, param .. "=[%d%.]*")
  225.             if val then options[n].option[5] = tonumber(string.sub(val, 3)) end
  226.         end
  227.         update(1, "f")
  228.         update(2, "g")
  229.         update(3, "p")
  230.         update(4, "m")
  231.         update(5, "r")
  232.         update(6, "s")
  233.         update(7, "n")
  234.         update(8, "c")
  235.     end
  236. end)
Advertisement
Add Comment
Please, Sign In to add comment