-
-
Save avih/41acff712abd32e1f436235388c8b523 to your computer and use it in GitHub Desktop.
| --[[ | |
| mpv 5-bands equalizer with visual feedback. | |
| Copyright 2016 Avi Halachmi ( https://github.com/avih ) | |
| License: public domain | |
| Default config: | |
| - Enter/exit equilizer keys mode: ctrl+e | |
| - Equalizer keys: 2/w control bass ... 6/y control treble, and middles in between | |
| - Toggle equalizer without changing its values: ctrl+E (ctrl+shift+e) | |
| - Reset equalizer values: alt+ctrl+e | |
| - See ffmpeg filter description below the config section | |
| --]] | |
| -- ------ config ------- | |
| local start_keys_enabled = false -- if true then choose the up/down keys wisely | |
| local key_toggle_bindings = 'ctrl+e' -- enable/disable equalizer key bindings | |
| local key_toggle_equalizer = 'ctrl+E' -- enable/disable equalizer | |
| local key_reset_equalizer = 'alt+ctrl+e' -- sets all bands to gain 0 | |
| -- reduce clicks (update the filter chain inplace). requires ffmpeg >= 4.0 | |
| local inplace = true | |
| -- configure the equalizer keys, bands, and initial gain value for each band | |
| local bands = { | |
| -- octave is x2. e.g. two octaves range around f is from f/2 to f*2 | |
| -- {up down} | |
| {keys = {'2', 'w'}, filter = {'equalizer=f=64:width_type=o:w=3.3:g=', 0}}, -- 20-200 | |
| {keys = {'3', 'e'}, filter = {'equalizer=f=400:width_type=o:w=2.0:g=', 0}}, -- 200-800 | |
| {keys = {'4', 'r'}, filter = {'equalizer=f=1250:width_type=o:w=1.3:g=', 0}}, -- 800-2k | |
| {keys = {'5', 't'}, filter = {'equalizer=f=2830:width_type=o:w=1.0:g=', 0}}, -- 2k-4k | |
| {keys = {'6', 'y'}, filter = {'equalizer=f=5600:width_type=o:w=1.0:g=', 0}}, -- 4k-8k | |
| --{keys = {'7', 'u'}, filter = {'equalizer=f=12500:width_type=o:w=1.3:g=', 0}} -- - 20k | |
| } | |
| --[[ | |
| https://ffmpeg.org/ffmpeg-filters.html#equalizer | |
| Apply a two-pole peaking equalisation (EQ) filter. With this filter, the signal-level | |
| at and around a selected frequency can be increased or decreased, whilst (unlike | |
| bandpass and bandreject filters) that at all other frequencies is unchanged. | |
| In order to produce complex equalisation curves, this filter can be given several | |
| times, each with a different central frequency. | |
| The filter accepts the following options: | |
| frequency, f: Set the filter's central frequency in Hz. | |
| width_type: Set method to specify band-width of filter. | |
| h Hz | |
| q Q-Factor | |
| o octave | |
| s slope | |
| width, w: Specify the band-width of a filter in width_type units. | |
| gain, g: Set the required gain or attenuation in dB. Beware of clipping when | |
| using a positive gain. | |
| --]] | |
| -- ------- utils -------- | |
| function iff(cc, a, b) if cc then return a else return b end end | |
| function ss(s, from, to) return s:sub(from, to - 1) end | |
| --[[-- utils | |
| local mp_msg = require 'mp.msg' | |
| function midwidth(min, max) -- range --> middle freq and width in octaves | |
| local wo = math.log(max / min) / math.log(2) | |
| mp_msg.info(min, max / (2 ^ (wo / 2)) .. ' <' .. wo .. '>', max) | |
| end | |
| function range(f, wo) -- middle freq and width in octaves --> range | |
| local h = 2 ^ (wo / 2) | |
| mp_msg.info(f / h, '' .. f .. ' <' .. wo .. '>' , f * h) | |
| end | |
| --]] | |
| -- return the filter as numbers {frequency, gain} | |
| local function filter_data(filter) | |
| return { tonumber(ss(filter[1], 13, filter[1]:find(':', 14, true))), filter[2] } | |
| end | |
| -- the mpv command string for adding the filter (only used when gain != 0) | |
| local function get_cmd(filter) | |
| return 'no-osd af add lavfi=[' .. filter[1] .. filter[2] .. ']' | |
| end | |
| -- setup named filter equalizer<band> | |
| local function get_cmd_band_inplace_setup(filter, band, reset) | |
| local v = reset and 0 or filter[2] | |
| return 'no-osd af add @equalizer'.. band ..':lavfi=[' .. filter[1] .. v .. ']' | |
| end | |
| -- update gain of named filter equalizer<band> inplace | |
| local function get_cmd_band_inplace(filter, band, reset) | |
| local v = reset and 0 or filter[2] | |
| return 'no-osd af-command equalizer'.. band ..' g '.. v | |
| end | |
| -- these two vars are used globally | |
| local bindings_enabled = start_keys_enabled | |
| local eq_enabled = true -- but af is not touched before the equalizer is modified | |
| local inplace_init = false | |
| -- ------ OSD handling ------- | |
| local function ass(x) | |
| -- local gpo = mp.get_property_osd | |
| -- return gpo('osd-ass-cc/0') .. x .. gpo('osd-ass-cc/1') | |
| -- seemingly it's impossible to enable ass escaping with mp.set_osd_ass, | |
| -- so we're already in ass mode, and no need to unescape first. | |
| return x | |
| end | |
| local function fsize(s) -- 100 is the normal font size | |
| return ass('{\\fscx' .. s .. '\\fscy' .. s ..'}') | |
| end | |
| local function color(c) -- c is RRGGBB | |
| return ass('{\\1c&H' .. ss(c, 5, 7) .. ss(c, 3, 5) .. ss(c, 1, 3) .. '&}') | |
| end | |
| local function cnorm() return color('ffffff') end -- white | |
| local function cdis() return color('909090') end -- grey | |
| local function ceq() return iff(eq_enabled, color('ffff90'), cdis()) end -- yellow-ish | |
| local function ckeys() return iff(bindings_enabled, color('90FF90'), cdis()) end -- green-ish | |
| local DUR_DEFAULT = 1.5 -- seconds | |
| local osd_timer = nil | |
| -- duration: seconds, or default if missing/nil, or infinite if 0 (or negative) | |
| local function ass_osd(msg, duration) -- empty or missing msg -> just clears the OSD | |
| duration = duration or DUR_DEFAULT | |
| if not msg or msg == '' then | |
| msg = '{}' -- the API ignores empty string, but '{}' works to clean it up | |
| duration = 0 | |
| end | |
| mp.set_osd_ass(0, 0, msg) | |
| if osd_timer then | |
| osd_timer:kill() | |
| osd_timer = nil | |
| end | |
| if duration > 0 then | |
| osd_timer = mp.add_timeout(duration, ass_osd) -- ass_osd() clears without a timer | |
| end | |
| end | |
| -- some visual messing about | |
| local function updateOSD() | |
| local msg1 = fsize(70) .. 'Equalizer: ' .. ceq() .. iff(eq_enabled, 'On', 'Off') | |
| .. ' [' .. key_toggle_equalizer .. ']' .. cnorm() | |
| local msg2 = fsize(70) | |
| .. 'Key-bindings: ' .. ckeys() .. iff(bindings_enabled, 'On', 'Off') | |
| .. ' [' .. key_toggle_bindings .. ']' .. cnorm() | |
| local msg3 = '' | |
| for i = 1, #bands do | |
| local data = filter_data(bands[i].filter) | |
| local info = | |
| ceq() .. fsize(50) .. data[1] .. ' hz ' .. fsize(100) | |
| .. iff(data[2] ~= 0 and eq_enabled, '', cdis()) .. data[2] .. ceq() | |
| .. fsize(50) .. ckeys() .. ' [' .. bands[i].keys[1] .. '/' .. bands[i].keys[2] .. ']' | |
| .. ceq() .. fsize(100) .. cnorm() | |
| msg3 = msg3 .. iff(i > 1, ' ', '') .. info | |
| end | |
| local nlb = '\n' .. ass('{\\an1}') -- new line and "align bottom for next" | |
| local msg = ass('{\\an1}') .. msg3 .. nlb .. msg2 .. nlb .. msg1 | |
| local duration = iff(start_keys_enabled, iff(bindings_enabled and eq_enabled, 5, nil) | |
| , iff(bindings_enabled, 0, nil)) | |
| ass_osd(msg, duration) | |
| end | |
| -- ------- actual functionality ------ | |
| local function updateAF_simple() -- setup an audio filter chain which applies the equalizer | |
| mp.command('no-osd af clr ""') -- af clr must have two double-quotes | |
| if not eq_enabled then return end | |
| for i = 1, #bands do | |
| local f = bands[i].filter | |
| if f[2] ~= 0 then -- insert filters only were the gain is non default | |
| mp.command(get_cmd(f)) | |
| end | |
| end | |
| end | |
| -- update gains of the whole equalizer inplace, also setup on first time | |
| local function updateAF_inplace() | |
| for i = 1, #bands do | |
| local f = bands[i].filter | |
| if not inplace_init then | |
| mp.command(get_cmd_band_inplace_setup(f, i, not eq_enabled)) | |
| end | |
| mp.command(get_cmd_band_inplace(f, i, not eq_enabled)) | |
| end | |
| inplace_init = true | |
| end | |
| if inplace then | |
| updateAF = updateAF_inplace | |
| else | |
| updateAF = updateAF_simple | |
| end | |
| local function getBind(filter, delta) | |
| return function() -- onKey | |
| filter[2] = filter[2] + delta | |
| updateAF() | |
| updateOSD() | |
| end | |
| end | |
| local function update_key_binding(enable, key, name, fn) | |
| if enable then | |
| mp.add_forced_key_binding(key, name, fn, 'repeatable') | |
| else | |
| mp.remove_key_binding(name) | |
| end | |
| end | |
| local function toggle_bindings(explicit, no_osd) | |
| bindings_enabled = iff(explicit ~= nil, explicit, not bindings_enabled) | |
| for i = 1, #bands do | |
| local k = bands[i].keys | |
| local f = bands[i].filter | |
| update_key_binding(bindings_enabled, k[1], 'eq' .. k[1], getBind(f, 1)) -- up | |
| update_key_binding(bindings_enabled, k[2], 'eq' .. k[2], getBind(f, -1)) -- down | |
| end | |
| if not no_osd then updateOSD() end | |
| end | |
| local function toggle_equalizer() | |
| eq_enabled = not eq_enabled | |
| updateAF() | |
| updateOSD() | |
| end | |
| local function reset_equalizer() | |
| for i = 1, #bands do | |
| bands[i].filter[2] = 0 | |
| end | |
| updateAF() | |
| updateOSD() | |
| end | |
| mp.add_forced_key_binding(key_toggle_equalizer, toggle_equalizer) | |
| mp.add_forced_key_binding(key_toggle_bindings, toggle_bindings) | |
| mp.add_forced_key_binding(key_reset_equalizer, reset_equalizer) | |
| if bindings_enabled then toggle_bindings(true, true) end | |
| if inplace then | |
| -- inplace changes (using af-command LABEL ...) don't recreate the filter | |
| -- chain and can reduce perceptible discontinuities, however, they also | |
| -- don't seem to persist across playlist items (probably reset on re-init). | |
| -- this hook persists the current values between files, by replacing the | |
| -- filters with new ones with the current values (not sub-values inplace). | |
| mp.add_hook("on_before_start_file", 50, function() | |
| inplace_init = false -- ensure the filters are recreated if exist | |
| updateAF() | |
| end) | |
| end | |
| -- init: setup the equalizer if the initial gain is not 0 | |
| for i = 1, #bands do | |
| if bands[i].filter[2] ~= 0 then | |
| updateAF() | |
| break | |
| end | |
| end |
Hi everyone, is it working now?
If it is not like some above comments, is there any way to make the audio bass boosted in mpv player?
Can you add some presets to it ? Because there's some nice presets you can find from EasyEffects: https://github.com/JackHack96/EasyEffects-Presets
Having presets for Classic Music, Rock, Commentary would improve experience of using mpv a lot.
Some presets for experimenting:
https://gist.github.com/kra3/9781800
https://github.com/schollz/Winamp-Original-Presets/tree/master/resources
There's some essential presets like Rock, Live, Classic.. that exists in almost all music players.
Can you add some presets to it ?
I could, but I don't intend to.
Do feel free to enhance it to include present, and you can post here a link to your version.
I also tried that, for some reason it's not working for me :S
Anyway, here where I live is late, so I'll try some more stuff tomorrow, thanks