File: acompressor.lua

package info (click to toggle)
mpv 0.40.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 12,676 kB
  • sloc: ansic: 152,062; python: 1,228; sh: 646; javascript: 612; cpp: 461; objc: 302; pascal: 49; xml: 29; makefile: 19
file content (158 lines) | stat: -rw-r--r-- 4,875 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
-- This script adds control to the dynamic range compression ffmpeg
-- filter including key bindings for adjusting parameters.
--
-- See https://ffmpeg.org/ffmpeg-filters.html#acompressor for explanation
-- of the parameters.

local mp = require 'mp'
local options = require 'mp.options'

local o = {
    default_enable = false,
    show_osd = true,
    osd_timeout = 4000,
    filter_label = mp.get_script_name(),

    key_toggle = 'n',
    key_increase_threshold = 'F1',
    key_decrease_threshold = 'Shift+F1',
    key_increase_ratio = 'F2',
    key_decrease_ratio = 'Shift+F2',
    key_increase_knee = 'F3',
    key_decrease_knee = 'Shift+F3',
    key_increase_makeup = 'F4',
    key_decrease_makeup = 'Shift+F4',
    key_increase_attack = 'F5',
    key_decrease_attack = 'Shift+F5',
    key_increase_release = 'F6',
    key_decrease_release = 'Shift+F6',

    default_threshold = -25.0,
    default_ratio = 3.0,
    default_knee = 2.0,
    default_makeup = 8.0,
    default_attack = 20.0,
    default_release = 250.0,

    step_threshold = -2.5,
    step_ratio = 1.0,
    step_knee = 1.0,
    step_makeup = 1.0,
    step_attack = 10.0,
    step_release = 10.0,
}
options.read_options(o)

local params = {
    { name = 'attack',    min=0.01, max=2000, hide_default=true,  dB=''   },
    { name = 'release',   min=0.01, max=9000, hide_default=true,  dB=''   },
    { name = 'threshold', min= -30, max=   0, hide_default=false, dB='dB' },
    { name = 'ratio',     min=   1, max=  20, hide_default=false, dB=''   },
    { name = 'knee',      min=   1, max=  10, hide_default=true,  dB='dB' },
    { name = 'makeup',    min=   0, max=  24, hide_default=false, dB='dB' },
}

local function parse_value(value)
    -- Using nil here because tonumber differs between lua 5.1 and 5.2 when
    -- parsing fractions in combination with explicit base argument set to 10.
    -- And we can't omit it because gsub returns 2 values which would get
    -- unpacked and cause more problems. Gotta love scripting languages.
    return tonumber(value:gsub('dB$', ''), nil)
end

local function format_value(value, dB)
    return string.format('%g%s', value, dB)
end

local function show_osd(filter)
    if not o.show_osd then
        return
    end

    if not filter.enabled then
        mp.commandv('show-text', 'Dynamic range compressor: disabled', o.osd_timeout)
        return
    end

    local pretty = {}
    for _,param in ipairs(params) do
        local value = parse_value(filter.params[param.name])
        if not (param.hide_default and value == o['default_' .. param.name]) then
            pretty[#pretty+1] = string.format('%s: %g%s', param.name:gsub("^%l", string.upper),
                                              value, param.dB)
        end
    end

    if #pretty == 0 then
        pretty = ''
    else
        pretty = '\n(' .. table.concat(pretty, ', ') .. ')'
    end

    mp.commandv('show-text', 'Dynamic range compressor: enabled' .. pretty, o.osd_timeout)
end

local function get_filter()
    local af = mp.get_property_native('af', {})

    for i = 1, #af do
        if af[i].label == o.filter_label then
            return af, i
        end
    end

    af[#af+1] = {
        name = 'acompressor',
        label = o.filter_label,
        enabled = false,
        params = {},
    }

    for _,param in pairs(params) do
        af[#af].params[param.name] = format_value(o['default_' .. param.name], param.dB)
    end

    return af, #af
end

local function toggle_acompressor()
    local af, i = get_filter()
    af[i].enabled = not af[i].enabled
    mp.set_property_native('af', af)
    show_osd(af[i])
end

local function update_param(name, increment)
    for _,param in pairs(params) do
        if param.name == string.lower(name) then
            local af, i = get_filter()
            local value = parse_value(af[i].params[param.name])
            value = math.max(param.min, math.min(value + increment, param.max))
            af[i].params[param.name] = format_value(value, param.dB)
            af[i].enabled = true
            mp.set_property_native('af', af)
            show_osd(af[i])
            return
        end
    end

    mp.msg.error('Unknown parameter "' .. name .. '"')
end

mp.add_key_binding(o.key_toggle, "toggle-acompressor", toggle_acompressor)
mp.register_script_message('update-param', update_param)

for _,param in pairs(params) do
    for direction,step in pairs({increase=1, decrease=-1}) do
        mp.add_key_binding(o['key_' .. direction .. '_' .. param.name],
                           'acompressor-' .. direction .. '-' .. param.name,
                           function() update_param(param.name, step*o['step_' .. param.name]); end,
                           { repeatable = true })
    end
end

if o.default_enable then
    local af, i = get_filter()
    af[i].enabled = true
    mp.set_property_native('af', af)
end