├── .gitignore ├── .gitmodules ├── input.conf ├── lua-modules └── auto-profiles-functions.lua ├── mpv.conf ├── script-opts ├── blacklist_extensions.conf ├── osc.conf └── stats.conf ├── scripts ├── auto-audio-device.lua ├── auto-profiles.lua ├── auto-save-state.lua ├── autodeint.lua ├── betterchapters.lua └── blacklist-extensions.lua └── vs-scripts ├── conv-tests.py ├── decimate.py ├── f3kdb.py ├── mi-offline.py ├── motion-interpolation-brokenfps.py └── motion-interpolation.py /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | fonts/ 3 | watch_later/ 4 | vs-plugins/ 5 | icc-cache/ 6 | mpv-livetweet.lua 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "scripts/mpv-scripts"] 2 | path = scripts/mpv-scripts.disable 3 | url = git@github.com:Argon-/mpv-scripts.git 4 | [submodule "shaders/AdaptiveSharpen"] 5 | path = shaders/AdaptiveSharpen 6 | url = git@gist.github.com:8a77e4eb8276753b54bb94c1c50c317e.git 7 | [submodule "shaders/mpv-prescalers"] 8 | path = shaders/mpv-prescalers 9 | url = https://github.com/bjin/mpv-prescalers 10 | [submodule "shaders/SSimDownscaler"] 11 | path = shaders/SSimDownscaler 12 | url = git@gist.github.com:36508af3ffc84410fe39761d6969be10.git 13 | [submodule "shaders/SSimSuperRes"] 14 | path = shaders/SSimSuperRes 15 | url = git@gist.github.com:2364ffa6e81540f29cb7ab4c9bc05b6b.git 16 | [submodule "shaders/FineSharp"] 17 | path = shaders/FineSharp 18 | url = git@gist.github.com:a9a21ad1f6dd7d0b4452.git 19 | -------------------------------------------------------------------------------- /input.conf: -------------------------------------------------------------------------------- 1 | # vim: syntax=config 2 | 3 | 4 | # Mouse 5 | 6 | MOUSE_BTN0 ignore 7 | MOUSE_BTN0_DBL cycle fullscreen 8 | MOUSE_BTN2 cycle pause 9 | #MOUSE_BTN3 ignore 10 | #MOUSE_BTN4 ignore 11 | #MOUSE_BTN5 ignore 12 | #MOUSE_BTN6 ignore 13 | 14 | 15 | 16 | # Trackpad 17 | 18 | #AXIS_UP ignore 19 | #AXIS_DOWN ignore 20 | AXIS_LEFT ignore 21 | AXIS_RIGHT ignore 22 | 23 | 24 | 25 | # Arrow/navigation keys 26 | 27 | RIGHT osd-msg-bar seek +5 relative+keyframes 28 | LEFT osd-msg-bar seek -5 relative+keyframes 29 | SHIFT+RIGHT osd-msg-bar seek +1 relative+exact 30 | SHIFT+LEFT osd-msg-bar seek -1 relative+exact 31 | CTRL+RIGHT frame-step ; show-text "Frame: ${estimated-frame-number} / ${estimated-frame-count}" 32 | CTRL+LEFT frame-back-step ; show-text "Frame: ${estimated-frame-number} / ${estimated-frame-count}" 33 | 34 | UP osd-msg-bar seek +30 relative+keyframes 35 | DOWN osd-msg-bar seek -30 relative+keyframes 36 | SHIFT+UP osd-msg-bar seek +120 relative+keyframes 37 | SHIFT+DOWN osd-msg-bar seek -120 relative+keyframes 38 | 39 | PGUP osd-msg-bar seek +600 relative+keyframes 40 | PGDWN osd-msg-bar seek -600 relative+keyframes 41 | 42 | ALT+RIGHT sub-seek +1 ; show-text "Sub Seek +1" 43 | ALT+LEFT sub-seek -1 ; show-text "Sub Seek -1" 44 | 45 | #ALT+RIGHT add video-pan-x -0.01 46 | #ALT+LEFT add video-pan-x +0.01 47 | #ALT+UP add video-pan-y +0.01 48 | #ALT+DOWN add video-pan-y -0.01 49 | 50 | #META+RIGHT add video-zoom +0.05 51 | #META+LEFT add video-zoom -0.05 52 | #META+UP add video-zoom +0.05 53 | #META+DOWN add video-zoom -0.05 54 | 55 | 56 | 57 | # ` [1] [2] [3] [4] [5] [6] [7] [8] [9] [0] - = 58 | # ~ [!] @ # $ % ^ & * ( ) _ + 59 | 60 | 1 add contrast -1 ; show-text "Contrast: ${contrast}" 61 | 2 add contrast +1 ; show-text "Contrast: ${contrast}" 62 | 3 add brightness -1 ; show-text "Brightness: ${brightness}" 63 | 4 add brightness +1 ; show-text "Brightness: ${brightness}" 64 | 5 add gamma -1 ; show-text "Gamma: ${gamma}" 65 | 6 add gamma +1 ; show-text "Gamma: ${gamma}" 66 | 7 add saturation -1 ; show-text "Saturation: ${saturation}" 67 | 8 add saturation +1 ; show-text "Saturation: ${saturation}" 68 | 69 | 9 add volume -2 ; show-text "Volume: ${volume}" 70 | 0 add volume +2 ; show-text "Volume: ${volume}" 71 | 72 | ! cycle ontop 73 | 74 | CTRL+1 show-text "Shaders: ${glsl-shaders}" 75 | CTRL+2 change-list glsl-shaders toggle "~/.mpv/shaders/AdaptiveSharpen/adaptive-sharpen.glsl" 76 | CTRL+3 change-list glsl-shaders toggle "~/.mpv/shaders/SSimSuperRes/SSimSuperRes.glsl" 77 | CTRL+4 change-list glsl-shaders toggle "~/.mpv/shaders/SSimDownscaler/SSimDownscaler.glsl" 78 | CTRL+5 change-list glsl-shaders toggle "~/.mpv/shaders/KrigBilateral/KrigBilateral.glsl" 79 | #CTRL+0 set glsl-shaders "" 80 | 81 | ` ignore 82 | ~ ignore 83 | # ignore 84 | $ ignore 85 | % ignore 86 | ^ ignore 87 | & ignore 88 | * ignore 89 | § ignore 90 | ± ignore 91 | 92 | 93 | 94 | # [q] [w] [e] [r] [t] [y] [u] [i] [o] [p] [ ] 95 | # [Q] [W] E R [T] [Y] [U] [I] O [P] { } 96 | 97 | Q quit 98 | q script-binding auto_save_state/quit-watch-later-conditional 99 | 100 | w script-message osc-playlist 101 | W playlist-shuffle 102 | e playlist-prev ; show-text "${playlist-pos-1}/${playlist-count}" 103 | E ignore 104 | 105 | r playlist-next ; show-text "${playlist-pos-1}/${playlist-count}" 106 | R ignore 107 | 108 | t cycle-values sub-use-margins "yes" "no" 109 | T cycle-values ass-force-margins "yes" "no" # does not work with :blend-subtitles 110 | CTRL+t cycle-values blend-subtitles "yes" "video" "no" 111 | 112 | y cycle-values stretch-image-subs-to-screen "yes" "no" 113 | Y cycle-values stretch-dvd-subs "yes" "no" 114 | 115 | u cycle-values hwdec "auto" "no" 116 | U cycle-values vf "format=colorlevels=full" "format=colorlevels=auto" "format=colorlevels=limited" 117 | 118 | i script-binding stats/display-stats 119 | I script-binding stats/display-stats-toggle 120 | o cycle-values osd-level 3 1 121 | O ignore 122 | p cycle-values video-rotate 90 180 270 0 123 | P cycle-values video-aspect "16:9" "4:3" "2.35:1" "16:10" 124 | 125 | [ ignore 126 | ] ignore 127 | { ignore 128 | } ignore 129 | 130 | 131 | 132 | # [a] [s] [d] [f] [g] [h] [j] [k] [l] 133 | # [A] [S] [D] [F] [G] [H] [J] [K] [L] 134 | 135 | a cycle audio # switch audio streams 136 | #A cycle-values af "lavfi=[dynaudnorm=f=200:g=5:r=0.1]" "" 137 | A cycle-values af "lavfi=[dynaudnorm=compress=25.0:gausssize=53]" "" 138 | CTRL+a script-binding auto_audio_device/toggle-switching # toggle automatic audio device switching 139 | 140 | s cycle sub # cycle through subtitles 141 | S cycle sub-visibility 142 | CTRL+s cycle secondary-sid 143 | 144 | d cycle-values window-scale "1.5" "2.0" "3.0" "0.5" "1.0" ; show-text "Scale: ${window-scale}" 145 | D cycle edition 146 | CTRL+d cycle video 147 | 148 | f cycle fullscreen ; show-text "Scale: ${window-scale}" 149 | F vf clr "" ; show-text "Filters cleared" 150 | 151 | g cycle-values video-sync display-resample audio ; cycle-values interpolation yes no ; show-text "Interpolation: ${interpolation} (${tscale})" 152 | G cycle-values tscale "linear" "catmull_rom" "mitchell" "bicubic" "oversample" ; show-text "Interpolation: ${interpolation} (${tscale})" 153 | CTRL+g cycle-values interpolation no yes ; show-text "Interpolation: ${interpolation} (${tscale})" 154 | ALT+g vf toggle format=yuv420p,vapoursynth=~~/vs-scripts/motion-interpolation.py:4 155 | 156 | h cycle deinterlace 157 | H script-binding autodeint 158 | 159 | j cycle deband 160 | J vf toggle "lavfi=[hqdn3d=2.0]" 161 | 162 | k vf toggle vapoursynth=~~/vs-scripts/decimate.py # fix 24FPS videos encoded at 30FPS 163 | K ignore 164 | 165 | l cycle-values loop-file yes no ; show-text "${?=loop-file==inf:Looping enabled (file)}${?=loop-file==no:Looping disabled (file)}" 166 | L cycle-values loop-playlist yes no ; show-text "${?=loop-playlist==inf:Looping enabled}${?=loop-playlist==no:Looping disabled}" 167 | CTRL+l ab-loop 168 | 169 | 170 | 171 | # [z] [x] [c] [v] [b] [n] [m] [,] [.] 172 | # [Z] X C V [B] [N] [M] [<] [>] 173 | 174 | z script-binding betterchapters/chapterplaylist-next #; show-text "${?chapter:Chapter: ${chapter}}" 175 | Z script-binding betterchapters/chapterplaylist-prev #; show-text "${?chapter:Chapter: ${chapter}}" 176 | 177 | x script-message osc-chapterlist 178 | X ignore 179 | c script-message osc-playlist 180 | C ignore 181 | v script-message osc-tracklist 182 | V ignore 183 | 184 | b add speed +0.05 185 | B add speed -0.05 186 | CTRL+b set speed 1.0 187 | 188 | n add audio-delay +0.10 189 | N add audio-delay -0.10 190 | CTRL+n set sub-delay 0 191 | 192 | m add sub-delay +0.10 193 | M add sub-delay -0.10 194 | CTRL+m set sub-delay 0 195 | 196 | , add sub-scale -0.05 # decrease subtitle font size 197 | < add sub-scale +0.05 # increase subtitle font size 198 | . add sub-pos -1 # move subtitles up 199 | > add sub-pos +1 # move subtitles down 200 | 201 | # Adjust timing so that next/prev subtitle is displayed now 202 | / sub-step +1 ; show-text "Sub Step +1 (timing adjustment)" 203 | ? sub-step -1 ; show-text "Sub Step -1 (timing adjustment)" 204 | 205 | 206 | 207 | # [esc] [space] [backspace] 208 | # [tab] [enter] 209 | 210 | 211 | ESC cycle fullscreen 212 | SPACE cycle pause 213 | IDEOGRAPHIC_SPACE cycle pause 214 | TAB cycle mute 215 | ENTER show-progress 216 | 217 | BS revert-seek 218 | SHIFT+BS set speed 1.0 ; set gamma 0 ; set brightness 0 ; set contrast 0 ; set saturation 0 ; set hue 0 ; show-text "Speed/Gamma/Brightness/Contrast/Saturation/Hue resetted" 219 | ALT+BS set video-pan-x 0 ; set video-pan-y 0 ; show-text "Pan resetted" 220 | META+BS set video-zoom 0 ; show-text "Zoom resetted" 221 | 222 | 223 | 224 | # [F1] [F2] F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 225 | 226 | F1 script-binding console/enable 227 | 228 | 229 | 230 | # Numpad 231 | 232 | KP0 ignore 233 | KP1 ignore 234 | KP2 ignore 235 | KP3 ignore 236 | KP4 ignore 237 | KP5 ignore 238 | KP6 ignore 239 | KP7 ignore 240 | KP8 ignore 241 | KP9 ignore 242 | KP_DEC ignore 243 | KP_ENTER ignore 244 | 245 | 246 | 247 | # Media Keys 248 | 249 | POWER script-binding auto_save_state/quit-watch-later-conditional 250 | MENU show-progress 251 | PLAY cycle pause 252 | PAUSE cycle pause 253 | PLAYPAUSE cycle pause 254 | STOP script-binding auto_save_state/quit-watch-later-conditional 255 | FORWARD osd-msg-bar seek +5 relative keyframes 256 | REWIND osd-msg-bar seek -5 relative keyframes 257 | NEXT script-binding betterchapters/chapterplaylist-next #; show-text "${?chapter:Chapter: ${chapter}}" 258 | PREV script-binding betterchapters/chapterplaylist-prev #; show-text "${?chapter:Chapter: ${chapter}}" 259 | VOLUME_UP add volume +2 ; show-text "Volume: ${volume}" 260 | VOLUME_DOWN add volume -2 ; show-text "Volume: ${volume}" 261 | MUTE cycle mute 262 | CLOSE_WIN quit 263 | -------------------------------------------------------------------------------- /lua-modules/auto-profiles-functions.lua: -------------------------------------------------------------------------------- 1 | local utils = require 'mp.utils' 2 | local msg = require 'mp.msg' 3 | 4 | -- Quality levels 5 | local HIGH = "High Quality" 6 | local MID = "Mid Quality" 7 | local LOW = "Low Quality" 8 | -- Platform 9 | local is_linux, is_osx, is_windows = false, false, false 10 | local exec_cache = {} 11 | 12 | 13 | local function exec(process, force_exec) 14 | local key = table.concat(process, " ") 15 | if force_exec or exec_cache[key] == nil or exec_cache[key].error then 16 | local p_ret = utils.subprocess({args = process, playback_only = false}) 17 | exec_cache[key] = p_ret 18 | if p_ret.error and p_ret.error == "init" then 19 | msg.error("executable not found: " .. key) 20 | end 21 | return p_ret 22 | else 23 | return exec_cache[key] 24 | end 25 | end 26 | 27 | 28 | local function get_platform() 29 | local is_linux = false 30 | local is_osx = false 31 | local is_windows = type(package) == 'table' and type(package.config) == 'string' and string.sub(package.config, 1, 1) == '\\' 32 | if not is_windows then 33 | uname = exec({"uname"}) 34 | is_linux = uname.stdout == "Linux\n" 35 | is_osx = uname.stdout == "Darwin\n" 36 | end 37 | return is_linux, is_osx, is_windows 38 | end 39 | 40 | 41 | function on_battery() 42 | if is_osx then 43 | local bat = exec({"/usr/bin/pmset", "-g", "batt"}, true) 44 | return string.match(bat.stdout, "Now drawing from 'Battery Power'") ~= nil 45 | elseif is_linux then 46 | local res = exec({"/bin/cat", "/sys/class/power_supply/AC/online"}, true) 47 | return res.stdout == "0\n" 48 | elseif is_windows then 49 | msg.warn("on_battery() not implemented on windows. PRs welcome") 50 | end 51 | msg.warn("assuming AC power") 52 | return false 53 | end 54 | 55 | 56 | -- This is a crude attempt to figure out if a (beefy) dedicated GPU is present. 57 | -- Can't identify the actually used GPU but works when we assume that an existing 58 | -- dedicated GPU can/will be used in case we are drawing power from AC. 59 | -- function dedicated_gpu() 60 | -- if is_osx then 61 | -- local r = exec({"system_profiler", "SPDisplaysDataType"}) 62 | -- return string.find(r.stdout, "Chipset Model: AMD Radeon") ~= nil or string.find(r.stdout, "Chipset Model: NVIDIA GeForce") ~= nil 63 | -- -- Untested 64 | -- elseif is_linux then 65 | -- local r = exec({"lshw", "-C", 'display'}) 66 | -- r.stdout = string.lower(r.stdout) 67 | -- return string.find(r.stdout, "amd") ~= nil or string.find(r.stdout, "nvidia") ~= nil 68 | -- elseif is_windows then 69 | -- msg.warn("dedicated_gpu() not implemented on windows. PRs welcome") 70 | -- end 71 | -- msg.warn("assuming dedicated GPU") 72 | -- return true 73 | -- end 74 | 75 | 76 | local function determine_level(width, height, fps) 77 | if on_battery() then 78 | -- if it's high fps (>61) or both UHD resolution and kind of high fps 79 | if fps > 61 or (width > 3841 and fps > 31) then 80 | return LOW 81 | end 82 | return MID 83 | else 84 | -- if it's high fps (>61) or both UHD resolution and high fps 85 | if fps > 61 or (width > 3841 and fps > 61) then 86 | return MID 87 | end 88 | return HIGH 89 | end 90 | 91 | msg.error("could not determine profile") 92 | msg.warn("assuming LOW") 93 | return LOW 94 | end 95 | 96 | 97 | local function is_level(level) 98 | return function(width, height, fps) 99 | local l = determine_level(width, height, fps) 100 | return l == level 101 | end 102 | end 103 | 104 | 105 | is_high = is_level(HIGH) 106 | is_mid = is_level(MID) 107 | is_low = is_level(LOW) 108 | 109 | is_linux, is_osx, is_windows = get_platform() 110 | -------------------------------------------------------------------------------- /mpv.conf: -------------------------------------------------------------------------------- 1 | # vim: syntax=config 2 | 3 | 4 | ########### 5 | # General # 6 | ########### 7 | 8 | input-ipc-server=/tmp/mpvsocket # listen for IPC on this socket 9 | load-auto-profiles=no # use local copy with modifications 10 | 11 | no-border # no window title bar 12 | msg-module # prepend module name to log messages 13 | msg-color # color log messages on terminal 14 | term-osd-bar # display a progress bar on the terminal 15 | use-filedir-conf # look for additional config files in the directory of the opened file 16 | pause # no autoplay 17 | keep-open=always # keep the player open when a file's end is reached 18 | autofit-larger=100%x95% # resize window in case it's larger than W%xH% of the screen 19 | cursor-autohide-fs-only # don't autohide the cursor in window mode, only fullscreen 20 | cursor-autohide=1000 # autohide the curser after 1s 21 | prefetch-playlist=yes 22 | force-seekable=yes 23 | hls-bitrate=max # use max quality for HLS streams 24 | 25 | screenshot-format=png 26 | screenshot-png-compression=8 27 | screenshot-template='~/Desktop/%F (%P) %n' 28 | watch-later-directory='~/.mpv/watch_later' 29 | write-filename-in-watch-later-config 30 | watch-later-options-remove=fullscreen 31 | 32 | [ytdl-desktop] 33 | profile-cond=on_battery() 34 | ytdl-format=bestvideo[height<=?2160]+bestaudio/best 35 | 36 | [ytdl-laptop] 37 | profile-cond=not on_battery() 38 | ytdl-format=bestvideo[height<=?2160][fps<=?30]+bestaudio/best 39 | 40 | [default] 41 | 42 | 43 | ######### 44 | # Cache # 45 | ######### 46 | 47 | #cache=yes 48 | #demuxer-max-bytes=400MiB 49 | #demuxer-max-back-bytes=150MiB 50 | 51 | 52 | ############# 53 | # OSD / OSC # 54 | ############# 55 | 56 | osd-level=1 # enable osd and display --osd-status-msg on interaction 57 | osd-duration=2500 # hide the osd after x ms 58 | osd-status-msg='${time-pos} / ${duration}${?percent-pos: (${percent-pos}%)}${?frame-drop-count:${!frame-drop-count==0: Dropped: ${frame-drop-count}}}\n${?chapter:Chapter: ${chapter}}' 59 | 60 | osd-font='Source Sans 3' 61 | osd-font-size=32 62 | osd-color='#CCFFFFFF' # ARGB format 63 | osd-border-color='#DD322640' # ARGB format 64 | #osd-shadow-offset=1 # pixel width for osd text and progress bar 65 | osd-bar-align-y=0 # progress bar y alignment (-1 top, 0 centered, 1 bottom) 66 | osd-border-size=2 # size for osd text and progress bar 67 | osd-bar-h=2 # height of osd bar as a fractional percentage of your screen height 68 | osd-bar-w=60 # width of " " " 69 | 70 | 71 | ############# 72 | # Subtitles # 73 | ############# 74 | 75 | demuxer-mkv-subtitle-preroll=yes # try to show embedded subs when seeking even when no index information is present 76 | demuxer-mkv-subtitle-preroll-secs=2 77 | sub-filter-sdh=yes 78 | 79 | sub-auto=fuzzy # external subs don't have to match the file name exactly to autoload 80 | sub-file-paths-append=ass # search for external subs in these relative subdirectories 81 | sub-file-paths-append=srt 82 | sub-file-paths-append=sub 83 | sub-file-paths-append=subs 84 | sub-file-paths-append=subtitles 85 | 86 | embeddedfonts=yes # use embedded fonts for SSA/ASS subs 87 | sub-fix-timing=no # do not try to fix gaps (which might make it worse in some cases) 88 | sub-ass-style-overrides=Kerning=yes # allows you to override style parameters of ASS scripts 89 | sub-use-margins 90 | sub-ass-force-margins 91 | 92 | # the following options only apply to subtitles without own styling (i.e. not ASS but e.g. SRT) 93 | sub-font='Source Sans 3' 94 | sub-font-size=36 95 | sub-color='#FFFFFFFF' 96 | sub-border-color='#FF262626' 97 | sub-border-size=3.2 98 | sub-shadow-offset=1 99 | sub-shadow-color='#33000000' 100 | sub-spacing=0.5 101 | 102 | 103 | ############# 104 | # Languages # 105 | ############# 106 | 107 | slang=enm,en,eng,de,deu,ger # automatically select these subtitles (decreasing priority) 108 | alang=ja,jp,jpn,en,eng,de,deu,ger # automatically select these audio tracks (decreasing priority) 109 | 110 | 111 | ######### 112 | # Audio # 113 | ######### 114 | 115 | audio-file-auto=fuzzy # external audio doesn't has to match the file name exactly to autoload 116 | audio-pitch-correction=yes # automatically insert scaletempo when playing with higher speed 117 | volume-max=200 # maximum volume in %, everything above 100 results in amplification 118 | volume=100 # default volume, 100 = unchanged 119 | 120 | # run `mpv --audio-device=help` to get the name of a potential multi-channel HDMI audio device (if any) 121 | # and use it in the conditions below 122 | #[hdmi-audio] 123 | #profile-cond=get('audio-device') == 'coreaudio/4DD90482-0000-0000-011C-0103806C3D78' 124 | #audio-spdif=ac3,eac3,dts-hd,truehd 125 | #audio-channels=7.1(wide-side),5.1(side),3.1,2.1,stereo 126 | ##ad-lavc-downmix=no 127 | 128 | #[hdmi-audio-inverted] 129 | #profile-cond=get('audio-device') ~= 'coreaudio/4DD90482-0000-0000-011C-0103806C3D78' 130 | #audio-spdif= 131 | #audio-channels=auto-safe 132 | ##ad-lavc-downmix=no 133 | 134 | [default] 135 | 136 | ################ 137 | # Video Output # 138 | ################ 139 | 140 | # Active VO options are set conditionally and require my slightly modified 141 | # `scripts/auto-profiles.lua` as well as `scripts/auto-profiles-functions.lua`. 142 | # More information about auto-profiles in general: https://github.com/wiiaboo/mpv-scripts/blob/master/auto-profiles.lua 143 | # on_battery() and dedicated_gpu() are my own functions in `scripts/auto-profiles-functions.lua` 144 | # and might need to be adjusted for your system/setup. 145 | 146 | # Defaults for all profiles 147 | tscale=oversample # [sharp] oversample <-> linear (triangle) <-> catmull_rom <-> mitchell <-> gaussian <-> bicubic [smooth] 148 | #opengl-early-flush=no 149 | #opengl-pbo=no # "yes" is currently bugged: https://github.com/mpv-player/mpv/issues/4988 150 | icc-profile-auto 151 | #hwdec=no 152 | 153 | [high-quality] 154 | profile-cond=is_high(get('width', 0), get('height', 0), get('estimated-vf-fps', 0)) 155 | scale=ewa_lanczossharp 156 | cscale=spline36 157 | dscale=catmull_rom 158 | scale-antiring=0 159 | cscale-antiring=0 160 | dither-depth=auto 161 | sigmoid-upscaling=yes 162 | deband=yes 163 | glsl-shaders-append='~/.mpv/shaders/mpv-prescalers/gather/ravu-lite-ar-r4.hook' 164 | glsl-shaders-append='~/.mpv/shaders/mpv-prescalers/gather/ravu-lite-ar-r4.hook' 165 | glsl-shaders-append='~/.mpv/shaders/mpv-prescalers/gather/ravu-lite-ar-r4.hook' 166 | 167 | [mid-quality] 168 | profile-cond=is_mid(get('width', 0), get('height', 0), get('estimated-vf-fps', 0)) 169 | scale=spline36 170 | cscale=bicubic 171 | dscale=catmull_rom 172 | scale-antiring=1.0 173 | cscale-antiring=1.0 174 | dither-depth=auto 175 | sigmoid-upscaling=yes 176 | deband=yes 177 | glsl-shaders-set="" 178 | 179 | [low-quality] 180 | profile-cond=is_low(get('width', 0), get('height', 0), get('estimated-vf-fps', 0)) 181 | scale=bicubic 182 | cscale=bicubic 183 | dscale=bicubic 184 | scale-antiring=0 185 | cscale-antiring=0 186 | dither-depth=no 187 | sigmoid-upscaling=no 188 | deband=no 189 | glsl-shaders-set="" 190 | 191 | 192 | ################################### 193 | # Protocol Specific Configuration # 194 | ################################### 195 | 196 | [protocol.https] 197 | cache=yes 198 | 199 | [protocol.http] 200 | cache=yes 201 | -------------------------------------------------------------------------------- /script-opts/blacklist_extensions.conf: -------------------------------------------------------------------------------- 1 | # only one of blacklist, whitelist should be defined at a time 2 | 3 | # only allow video and image formats 4 | #whitelist=mkv,webm,png,jpg,mp4,avi,jpeg,flv,m4v 5 | 6 | # alternatively, blacklist formats commonly found near videos 7 | blacklist=srt,ass,mks,txt,conf,mka 8 | 9 | remove_files_without_extension=yes 10 | 11 | # if the script should be applied only at the beginning, or anytime the playlist changes 12 | oneshot=yes 13 | -------------------------------------------------------------------------------- /script-opts/osc.conf: -------------------------------------------------------------------------------- 1 | layout=bottombar 2 | seekbarstyle=diamond 3 | seekbarhandlesize=1 4 | deadzonesize=0.98 5 | -------------------------------------------------------------------------------- /script-opts/stats.conf: -------------------------------------------------------------------------------- 1 | font=Source Sans 3 2 | font_mono=Cascadia Mono 3 | -------------------------------------------------------------------------------- /scripts/auto-audio-device.lua: -------------------------------------------------------------------------------- 1 | local mp = require 'mp' 2 | 3 | 4 | local auto_change = true 5 | local device_description = {} 6 | local av_map = { 7 | ["default"] = "auto", 8 | } 9 | -- local av_map = { 10 | -- ["DELL U2520D.*"] = "coreaudio/AppleUSBAudioEngine:FiiO:DigiHug USB Audio:114000:3", -- FiiO USB DAC-E10 11 | -- ["SONY TV [*]00.*"] = "coreaudio/4DD90482-0000-0000-011C-0103806C3D78_0C80D000", -- HDMI 12 | -- ["Built[-]in Retina Display.*"] = "coreaudio/BuiltInSpeakerDevice", -- Built-in 2024 MBP 13 | -- ["default"] = "auto", 14 | -- } 15 | 16 | -- local av_map_patterns = { 17 | -- ["DELL U2520D.*"] = "coreaudio/AppleUSBAudioEngine[:]FiiO[:]DigiHug USB Audio.*", -- FiiO USB DAC-E10 18 | -- ["SONY TV [*]00.*"] = "coreaudio/4DD90482[-]0000[-]0000[-]011C[-]0103806C3D78.*", -- HDMI 19 | -- ["Built[-]in Retina Display.*"] = "coreaudio/BuiltInSpeakerDevice", -- Built-in 2024 MBP 20 | -- ["default"] = "auto", 21 | -- } 22 | local av_map_patterns = { 23 | ["DELL U2520D.*"] = "FiiO USB DAC[-]E10", 24 | ["SONY TV [*]00.*"] = "SONY TV [*]00", 25 | ["Built[-]in Retina Display.*"] = "MacBook Pro Speakers", 26 | ["default"] = "auto", 27 | } 28 | 29 | 30 | function create_av_map(audio_devices) 31 | if not audio_devices then 32 | return 33 | end 34 | 35 | local new_av_map = { 36 | ["default"] = "auto", 37 | } 38 | 39 | --print("Creating new av_map") 40 | --print("Audio devices: ") 41 | for _, device in ipairs(audio_devices) do 42 | --print(" > " .. device.name .. " | " .. device.description) 43 | for display, audio_patt in pairs(av_map_patterns) do 44 | --if string.match(device.name, audio_patt) then 45 | if string.match(device.name, "coreaudio/.*") and string.match(device.description, audio_patt) then 46 | new_av_map[display] = device.name 47 | device_description[device.name] = device.description 48 | --print(" Matched: " .. display .. " -> " .. device.name .. " | " .. device.description) 49 | end 50 | end 51 | end 52 | 53 | print("New av_map:") 54 | for k, v in pairs(new_av_map) do 55 | print(" > " .. k .. " -> " .. v) 56 | end 57 | av_map = new_av_map 58 | end 59 | 60 | 61 | function set_audio_device(obs_display) 62 | if not auto_change then 63 | return 64 | end 65 | 66 | local display = obs_display or mp.get_property_native("display-names") 67 | if display == nil then 68 | return 69 | end 70 | 71 | if not display or not display[1] then 72 | print("Unknown display return value: " .. tostring(display)) 73 | return 74 | end 75 | print("Display: " .. display[1]) 76 | 77 | -- iterate av_map and search for the first entry that matches display 78 | local new_adev = nil 79 | for k, v in pairs(av_map) do 80 | if string.match(display[1], k) then 81 | new_adev = v 82 | break 83 | end 84 | end 85 | 86 | if new_adev == nil then 87 | new_adev = av_map["default"] 88 | print("No audio match found for display: " .. tostring(display[1])) 89 | end 90 | --local new_adev = av_map[display[1]] or av_map["default"] 91 | local current_adev = mp.get_property("audio-device", av_map["default"]) 92 | if new_adev ~= current_adev then 93 | mp.osd_message("Audio device: " .. (device_description[new_adev] or new_adev)) 94 | mp.set_property("audio-device", new_adev) 95 | end 96 | end 97 | 98 | mp.observe_property("audio-device-list", "native", function(name, value) create_av_map(value) end) 99 | mp.observe_property("display-names", "native", function(name, value) set_audio_device(value) end) 100 | --mp.add_key_binding("", "set-audio-device", function() set_audio_device(nil) end) 101 | mp.add_key_binding("", "toggle-switching", function() 102 | if auto_change then 103 | set_audio_device({"default"}) 104 | mp.osd_message("Audio device: auto (forced)") 105 | auto_change = false -- set after call to set_audio_device otherwise it won't do anything 106 | else 107 | auto_change = true 108 | set_audio_device(nil) 109 | end 110 | end) 111 | -------------------------------------------------------------------------------- /scripts/auto-profiles.lua: -------------------------------------------------------------------------------- 1 | -- Note: anything global is accessible by profile condition expressions. 2 | 3 | local lua_modules = mp.find_config_file('lua-modules') 4 | if lua_modules then 5 | package.path = package.path .. ';' .. lua_modules .. '/?.lua' 6 | end 7 | 8 | local f = require 'auto-profiles-functions' 9 | local msg = require 'mp.msg' 10 | 11 | local profiles = {} 12 | local watched_properties = {} -- indexed by property name (used as a set) 13 | local cached_properties = {} -- property name -> last known raw value 14 | local properties_to_profiles = {} -- property name -> set of profiles using it 15 | local have_dirty_profiles = false -- at least one profile is marked dirty 16 | local pending_hooks = {} -- as set (keys only, meaningless values) 17 | 18 | -- Used during evaluation of the profile condition, and should contain the 19 | -- profile the condition is evaluated for. 20 | local current_profile = nil 21 | 22 | -- Cached set of all top-level mpv properities. Only used for extra validation. 23 | local property_set = {} 24 | for _, property in pairs(mp.get_property_native("property-list")) do 25 | property_set[property] = true 26 | end 27 | 28 | local function evaluate(profile) 29 | msg.verbose("Re-evaluating auto profile " .. profile.name) 30 | 31 | current_profile = profile 32 | local status, res = pcall(profile.cond) 33 | current_profile = nil 34 | 35 | if not status then 36 | -- errors can be "normal", e.g. in case properties are unavailable 37 | msg.verbose("Profile condition error on evaluating: " .. res) 38 | res = false 39 | end 40 | res = not not res 41 | if res ~= profile.status then 42 | if res == true then 43 | msg.info("Applying auto profile: " .. profile.name) 44 | mp.commandv("apply-profile", profile.name) 45 | elseif profile.status == true and profile.has_restore_opt then 46 | msg.info("Restoring profile: " .. profile.name) 47 | mp.commandv("apply-profile", profile.name, "restore") 48 | end 49 | end 50 | profile.status = res 51 | profile.dirty = false 52 | end 53 | 54 | local function on_property_change(name, val) 55 | cached_properties[name] = val 56 | -- Mark all profiles reading this property as dirty, so they get re-evaluated 57 | -- the next time the script goes back to sleep. 58 | local dependent_profiles = properties_to_profiles[name] 59 | if dependent_profiles then 60 | for profile, _ in pairs(dependent_profiles) do 61 | assert(profile.cond) -- must be a profile table 62 | profile.dirty = true 63 | have_dirty_profiles = true 64 | end 65 | end 66 | end 67 | 68 | local function on_idle() 69 | -- When events and property notifications stop, re-evaluate all dirty profiles. 70 | if have_dirty_profiles then 71 | for _, profile in ipairs(profiles) do 72 | if profile.dirty then 73 | evaluate(profile) 74 | end 75 | end 76 | end 77 | have_dirty_profiles = false 78 | -- Release all hooks (the point was to wait until an idle event) 79 | while true do 80 | local h = next(pending_hooks) 81 | if not h then 82 | break 83 | end 84 | pending_hooks[h] = nil 85 | h:cont() 86 | end 87 | end 88 | 89 | local function on_hook(h) 90 | h:defer() 91 | pending_hooks[h] = true 92 | end 93 | 94 | function get(name, default) 95 | -- Normally, we use the cached value only 96 | if not watched_properties[name] then 97 | watched_properties[name] = true 98 | local res, err = mp.get_property_native(name) 99 | -- Property has to not exist and the toplevel of property in the name must also 100 | -- not have an existing match in the property set for this to be considered an error. 101 | -- This allows things like user-data/test to still work. 102 | if err == "property not found" and property_set[name:match("^([^/]+)")] == nil then 103 | msg.error("Property '" .. name .. "' was not found.") 104 | return default 105 | end 106 | cached_properties[name] = res 107 | mp.observe_property(name, "native", on_property_change) 108 | end 109 | -- The first time the property is read we need add it to the 110 | -- properties_to_profiles table, which will be used to mark the profile 111 | -- dirty if a property referenced by it changes. 112 | if current_profile then 113 | local map = properties_to_profiles[name] 114 | if not map then 115 | map = {} 116 | properties_to_profiles[name] = map 117 | end 118 | map[current_profile] = true 119 | end 120 | local val = cached_properties[name] 121 | if val == nil then 122 | val = default 123 | end 124 | return val 125 | end 126 | 127 | local function magic_get(name) 128 | -- Lua identifiers can't contain "-", so in order to match with mpv 129 | -- property conventions, replace "_" to "-" 130 | name = string.gsub(name, "_", "-") 131 | return get(name, nil) 132 | end 133 | 134 | local evil_magic = {} 135 | setmetatable(evil_magic, { 136 | __index = function(_, key) 137 | -- interpret everything as property, unless it already exists as 138 | -- a non-nil global value 139 | local v = _G[key] 140 | if type(v) ~= "nil" then 141 | return v 142 | end 143 | return magic_get(key) 144 | end, 145 | }) 146 | 147 | p = {} 148 | setmetatable(p, { 149 | __index = function(_, key) 150 | return magic_get(key) 151 | end, 152 | }) 153 | 154 | local function compile_cond(name, s) 155 | local code, chunkname = "return " .. s, "profile " .. name .. " condition" 156 | local chunk, err 157 | -- luacheck: push 158 | -- luacheck: ignore setfenv loadstring 159 | if setfenv then -- lua 5.1 160 | chunk, err = loadstring(code, chunkname) 161 | if chunk then 162 | setfenv(chunk, evil_magic) 163 | end 164 | else -- lua 5.2 165 | chunk, err = load(code, chunkname, "t", evil_magic) 166 | end 167 | -- luacheck: pop 168 | if not chunk then 169 | msg.error("Profile '" .. name .. "' condition: " .. err) 170 | chunk = function() return false end 171 | end 172 | return chunk 173 | end 174 | 175 | local function load_profiles(profiles_property) 176 | for _, v in ipairs(profiles_property) do 177 | local cond = v["profile-cond"] 178 | if cond and #cond > 0 then 179 | local profile = { 180 | name = v.name, 181 | cond = compile_cond(v.name, cond), 182 | properties = {}, 183 | status = nil, 184 | dirty = true, -- need re-evaluate 185 | has_restore_opt = v["profile-restore"] and v["profile-restore"] ~= "default" 186 | } 187 | profiles[#profiles + 1] = profile 188 | have_dirty_profiles = true 189 | end 190 | end 191 | end 192 | 193 | mp.observe_property("profile-list", "native", function (_, profiles_property) 194 | profiles = {} 195 | watched_properties = {} 196 | cached_properties = {} 197 | properties_to_profiles = {} 198 | mp.unobserve_property(on_property_change) 199 | 200 | load_profiles(profiles_property) 201 | 202 | if #profiles < 1 and mp.get_property("load-auto-profiles") == "auto" then 203 | exit() 204 | return 205 | end 206 | 207 | on_idle() -- re-evaluate all profiles immediately 208 | end) 209 | 210 | mp.register_idle(on_idle) 211 | for _, name in ipairs({"on_load", "on_preloaded", "on_before_start_file"}) do 212 | mp.add_hook(name, 50, on_hook) 213 | end -------------------------------------------------------------------------------- /scripts/auto-save-state.lua: -------------------------------------------------------------------------------- 1 | -- Save watch-later conditionally. 2 | -- a) Always for playlists (so mpv remembers the position within this playlist) 3 | -- b) Never for files shorter than `min_length` seconds 4 | -- c) When the current playback position is > `thresh_start` and < `thresh_end` 5 | 6 | 7 | local opts = require 'mp.options' 8 | local o = { 9 | min_length = 600, 10 | thresh_end = 180, 11 | thresh_start = 60, 12 | } 13 | opts.read_options(o) 14 | 15 | 16 | -- Return true when multiple files are being played 17 | function check_playlist() 18 | local pcount, err = mp.get_property_number("playlist-count") 19 | if not pcount then 20 | print("error: " .. err) 21 | pcount = 1 22 | end 23 | 24 | return pcount > 1 25 | end 26 | 27 | 28 | -- Return true when the current playback time is not too close to the start or end 29 | -- Always return false for short files, no matter the playback time 30 | function check_time() 31 | local duration = mp.get_property_number("duration", 9999) 32 | if duration < o.min_length then 33 | return false 34 | end 35 | 36 | local remaining, err = mp.get_property_number("time-remaining") 37 | if not remaining then 38 | print("error: " .. err) 39 | remaining = -math.huge 40 | end 41 | local pos, err = mp.get_property_number("time-pos") 42 | if not pos then 43 | print("error: " .. err) 44 | pos = -math.huge 45 | end 46 | 47 | return pos > o.thresh_start and remaining > o.thresh_end 48 | end 49 | 50 | 51 | mp.add_key_binding("q", "quit-watch-later-conditional", 52 | function() 53 | --mp.set_property_bool("options/save-position-on-quit", check_playlist() or check_time()) 54 | if check_playlist() or check_time() then 55 | mp.command("quit-watch-later") 56 | else 57 | mp.command("quit") 58 | end 59 | end) 60 | -------------------------------------------------------------------------------- /scripts/autodeint.lua: -------------------------------------------------------------------------------- 1 | -- From: https://github.com/mpv-player/mpv/tree/master/TOOLS/lua 2 | -- (with slight modifications) 3 | -- 4 | -- This script uses the lavfi idet filter to automatically insert the 5 | -- appropriate deinterlacing filter based on a short section of the 6 | -- currently playing video. 7 | -- 8 | -- It registers the key-binding ctrl+d, which when pressed, inserts the filters 9 | -- ``vf=lavfi=idet,pullup,vf=lavfi=idet``. After 4 seconds, it removes these 10 | -- filters and decides whether the content is progressive, interlaced, or 11 | -- telecined and the interlacing field dominance. 12 | -- 13 | -- Based on this information, it may set mpv's ``deinterlace`` property (which 14 | -- usually inserts the yadif filter), or insert the ``pullup`` filter if the 15 | -- content is telecined. It also sets mpv's ``field-dominance`` property. 16 | -- 17 | -- OPTIONS: 18 | -- The default detection time may be overridden by adding 19 | -- 20 | -- --script-opts=autodeint.detect_seconds= 21 | -- 22 | -- to mpv's arguments. This may be desirable to allow idet more 23 | -- time to collect data. 24 | -- 25 | -- To see counts of the various types of frames for each detection phase, 26 | -- the verbosity can be increased with 27 | -- 28 | -- --msg-level autodeint=v 29 | -- 30 | -- This script requires a recent version of ffmpeg for which the idet 31 | -- filter adds the required metadata. 32 | 33 | require "mp.msg" 34 | 35 | script_name = mp.get_script_name() 36 | detect_label = string.format("%s-detect", script_name) 37 | pullup_label = string.format("%s", script_name) 38 | ivtc_detect_label = string.format("%s-ivtc-detect", script_name) 39 | 40 | -- number of seconds to gather cropdetect data 41 | detect_seconds = tonumber(mp.get_opt(string.format("%s.detect_seconds", script_name))) 42 | if not detect_seconds then 43 | detect_seconds = 4 44 | end 45 | 46 | function del_filter_if_present(label) 47 | -- necessary because mp.command('vf del @label:filter') raises an 48 | -- error if the filter doesn't exist 49 | local vfs = mp.get_property_native("vf") 50 | 51 | for i,vf in pairs(vfs) do 52 | if vf["label"] == label then 53 | table.remove(vfs, i) 54 | mp.set_property_native("vf", vfs) 55 | return true 56 | end 57 | end 58 | return false 59 | end 60 | 61 | function start_detect() 62 | -- exit if detection is already in progress 63 | if timer then 64 | mp.msg.warn("already detecting!") 65 | mp.osd_message("autodeint: already detecting!") 66 | return 67 | end 68 | 69 | mp.set_property("deinterlace","no") 70 | del_filter_if_present(pullup_label) 71 | 72 | -- insert the detection filter 73 | local cmd = string.format('vf add @%s:lavfi=graph="idet",@%s:pullup,@%s:lavfi=graph="idet"', 74 | detect_label, pullup_label, ivtc_detect_label) 75 | if not mp.command(cmd) then 76 | mp.msg.error("failed to insert detection filters") 77 | mp.osd_message("autodeint: failed to insert detection filters") 78 | return 79 | end 80 | 81 | -- wait to gather data 82 | mp.osd_message("autodeint: starting detection") 83 | timer = mp.add_timeout(detect_seconds, select_filter) 84 | end 85 | 86 | function stop_detect() 87 | del_filter_if_present(detect_label) 88 | del_filter_if_present(ivtc_detect_label) 89 | timer = nil 90 | end 91 | 92 | progressive, interlaced_tff, interlaced_bff, interlaced = 0, 1, 2, 3, 4 93 | 94 | function judge(label) 95 | -- get the metadata 96 | local result = mp.get_property_native(string.format("vf-metadata/%s", label)) 97 | -- filter might have been removed by third party 98 | if not result or next(result) == nil then 99 | return progressive 100 | end 101 | num_tff = tonumber(result["lavfi.idet.multiple.tff"]) 102 | num_bff = tonumber(result["lavfi.idet.multiple.bff"]) 103 | num_progressive = tonumber(result["lavfi.idet.multiple.progressive"]) 104 | num_undetermined = tonumber(result["lavfi.idet.multiple.undetermined"]) 105 | num_interlaced = num_tff + num_bff 106 | num_determined = num_interlaced + num_progressive 107 | 108 | mp.msg.verbose(label .. " progressive = "..num_progressive) 109 | mp.msg.verbose(label .. " interlaced-tff = "..num_tff) 110 | mp.msg.verbose(label .. " interlaced-bff = "..num_bff) 111 | mp.msg.verbose(label .. " undetermined = "..num_undetermined) 112 | 113 | if num_determined < num_undetermined then 114 | mp.msg.warn("majority undetermined frames") 115 | end 116 | if num_progressive > 20*num_interlaced then 117 | return progressive 118 | elseif num_tff > 10*num_bff then 119 | return interlaced_tff 120 | elseif num_bff > 10*num_tff then 121 | return interlaced_bff 122 | else 123 | return interlaced 124 | end 125 | end 126 | 127 | function select_filter() 128 | -- handle the first detection filter results 129 | verdict = judge(detect_label) 130 | if verdict == progressive then 131 | mp.msg.info("progressive: doing nothing") 132 | mp.osd_message("autodeint: no deinterlacing required") 133 | stop_detect() 134 | return 135 | elseif verdict == interlaced_tff then 136 | mp.set_property("field-dominance", "top") 137 | elseif verdict == interlaced_bff then 138 | mp.set_property("field-dominance", "bottom") 139 | elseif verdict == interlaced then 140 | mp.set_property("field-dominance", "auto") 141 | end 142 | 143 | -- handle the ivtc detection filter results 144 | verdict = judge(ivtc_detect_label) 145 | if verdict == progressive then 146 | mp.msg.info(string.format("telecinied with %s field dominance: using pullup", mp.get_property("field-dominance"))) 147 | mp.osd_message("autodeint: using pullup") 148 | stop_detect() 149 | else 150 | mp.msg.info(string.format("interlaced with %s field dominance: setting deinterlace property", mp.get_property("field-dominance"))) 151 | del_filter_if_present(pullup_label) 152 | mp.osd_message(string.format("autodeint: setting deinterlace property (%s)", mp.get_property("field-dominance"))) 153 | mp.set_property("deinterlace", "yes") 154 | stop_detect() 155 | end 156 | end 157 | 158 | mp.add_key_binding("ctrl+d", script_name, start_detect) 159 | -------------------------------------------------------------------------------- /scripts/betterchapters.lua: -------------------------------------------------------------------------------- 1 | -- From: https://github.com/mpv-player/mpv/issues/4738#issuecomment-321298846 2 | 3 | function chapter_seek(direction) 4 | local chapters = mp.get_property_number("chapters") 5 | if chapters == nil then chapters = 0 end 6 | local chapter = mp.get_property_number("chapter") 7 | if chapter == nil then chapter = 0 end 8 | if chapter+direction < 0 then 9 | mp.command("playlist_prev") 10 | mp.commandv("script-message", "osc-playlist") 11 | elseif chapter+direction >= chapters then 12 | mp.command("playlist_next") 13 | mp.commandv("script-message", "osc-playlist") 14 | else 15 | mp.commandv("add", "chapter", direction) 16 | mp.commandv("script-message", "osc-chapterlist") 17 | end 18 | end 19 | 20 | mp.add_key_binding(nil, "chapterplaylist-next", function() chapter_seek(1) end) 21 | mp.add_key_binding(nil, "chapterplaylist-prev", function() chapter_seek(-1) end) -------------------------------------------------------------------------------- /scripts/blacklist-extensions.lua: -------------------------------------------------------------------------------- 1 | -- From: https://github.com/occivink/mpv-scripts 2 | 3 | opts = { 4 | blacklist="", 5 | whitelist="", 6 | remove_files_without_extension = false, 7 | oneshot = true, 8 | } 9 | (require 'mp.options').read_options(opts) 10 | local msg = require 'mp.msg' 11 | 12 | function split(input) 13 | local ret = {} 14 | for str in string.gmatch(input, "([^,]+)") do 15 | ret[#ret + 1] = str 16 | end 17 | return ret 18 | end 19 | 20 | opts.blacklist = split(opts.blacklist) 21 | opts.whitelist = split(opts.whitelist) 22 | 23 | local exclude 24 | if #opts.whitelist > 0 then 25 | exclude = function(extension) 26 | for _, ext in pairs(opts.whitelist) do 27 | if extension == ext then 28 | return false 29 | end 30 | end 31 | return true 32 | end 33 | elseif #opts.blacklist > 0 then 34 | exclude = function(extension) 35 | for _, ext in pairs(opts.blacklist) do 36 | if extension == ext then 37 | return true 38 | end 39 | end 40 | return false 41 | end 42 | else 43 | return 44 | end 45 | 46 | function should_remove(filename) 47 | if string.find(filename, "://") then 48 | return false 49 | end 50 | local extension = string.match(filename, "%.([^./]+)$") 51 | if not extension and opts.remove_file_without_extension then 52 | return true 53 | end 54 | if extension and exclude(string.lower(extension)) then 55 | return true 56 | end 57 | return false 58 | end 59 | 60 | function process(playlist_count) 61 | if playlist_count < 2 then return end 62 | if opts.oneshot then 63 | mp.unobserve_property(observe) 64 | end 65 | local playlist = mp.get_property_native("playlist") 66 | local removed = 0 67 | for i = #playlist, 1, -1 do 68 | if should_remove(playlist[i].filename) then 69 | mp.commandv("playlist-remove", i-1) 70 | removed = removed + 1 71 | end 72 | end 73 | if removed == #playlist then 74 | msg.warn("Removed eveything from the playlist") 75 | end 76 | end 77 | 78 | function observe(k,v) process(v) end 79 | 80 | mp.observe_property("playlist-count", "number", observe) 81 | -------------------------------------------------------------------------------- /vs-scripts/conv-tests.py: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | import numpy as np 3 | 4 | core = vs.get_core() 5 | clip = video_in 6 | 7 | 8 | def mat(dim=3, mid=1, rest=0): 9 | a = np.ones((dim, dim), np.int16) * rest 10 | a[len(a)//2,len(a)//2] = mid 11 | return a 12 | 13 | def mat5x5(mid=1, rest=0): 14 | return mat(5, mid, rest) 15 | 16 | def mat3x3(mid=1, rest=0): 17 | return mat(3, mid, rest) 18 | 19 | def mean(val=1, dim=3): 20 | return mat(dim, val, val) 21 | 22 | def gaussian(): 23 | return np.matrix([[1, 2, 1], 24 | [2, 4, 2], 25 | [1, 2, 1]]) 26 | 27 | def sharpen(m=5): 28 | return np.matrix([[ 0, -1, 0], 29 | [-1, m, -1], 30 | [ 0, -1, 0]]) 31 | 32 | def sharpen5x5(): 33 | return np.matrix([[1, 4, 6, 4, 1], 34 | [4, 16, 24, 16, 4], 35 | [6, 24, -476, 24, 6], 36 | [4, 16, 24, 16, 4], 37 | [1, 4, 6, 4, 1]]) 38 | 39 | def outline(m=8): 40 | return np.matrix([[-1, -1, -1], 41 | [-1, m, -1], 42 | [-1, -1, -1]]) 43 | 44 | def emboss1(m=0): 45 | return np.matrix([[-1, -1, 0], 46 | [-1, m, 1], 47 | [ 0, 1, 1]]) 48 | 49 | def emboss2(m=0): 50 | return np.matrix([[-2, -1, 0], 51 | [-1, m, 1], 52 | [ 0, 1, 2]]) 53 | 54 | def test(m=0, r=0): 55 | return np.matrix([[ 4, 2, 4], 56 | [ 2, 1, 2], 57 | [ 4, 2, 4]]) 58 | 59 | 60 | class Conv: 61 | def __init__(self, planes=[0, 1, 2], mult=0, mat=None, bias=0): 62 | self._p = planes 63 | self._mult = mult 64 | self._mat = mat 65 | self._bias = bias 66 | @property 67 | def matrix(self): 68 | return [i for e in self._mat.tolist() for i in e] 69 | @matrix.setter 70 | def matrix(self, mat): 71 | self._mat = mat 72 | @property 73 | def mult(self): 74 | return self._mult 75 | def div(self): # 0 = VS is using 1/sum(mat) 76 | return 1/self._mult if self._mult != 0 else 0 77 | @mult.setter 78 | def mult(self, mult): 79 | self._mult = mult 80 | @property 81 | def planes(self): 82 | return self._p 83 | @planes.setter 84 | def planes(self, p): 85 | self._p = p 86 | @property 87 | def bias(self): 88 | return self._bias 89 | @bias.setter 90 | def bias(self, b): 91 | self._bias = b 92 | 93 | 94 | convs = [] 95 | #convs.append(Conv(planes=[0], mult=1/sharpen5x5().sum(), mat=sharpen5x5())) 96 | #convs.append(Conv(planes=[1,2], mat=sharpen5x5())) 97 | #convs.append(Conv(planes=[0], mat=sharpen5x5())) 98 | #convs.append(Conv(planes=[1,2], mat=outline(9))) 99 | #convs.append(Conv(planes=[0], mat=emboss1(1))) 100 | convs.append(Conv(planes=[0], mat=test())) 101 | 102 | 103 | 104 | for c in convs: 105 | clip = core.std.Convolution(clip, matrix=c.matrix, bias=c.bias, divisor=c.div(), planes=c.planes, saturate=True, mode="s") 106 | 107 | clip.set_output() 108 | -------------------------------------------------------------------------------- /vs-scripts/decimate.py: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | core = vs.get_core() 3 | 4 | clip = video_in 5 | clip = core.vivtc.VDecimate(clip) 6 | clip = core.std.AssumeFPS(clip, fpsnum=24000, fpsden=1001) 7 | 8 | clip.set_output() 9 | -------------------------------------------------------------------------------- /vs-scripts/f3kdb.py: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | core = vs.get_core() 3 | 4 | clip = video_in 5 | clip = core.std.Trim(clip, first=0, length=500000) 6 | clip = core.f3kdb.Deband(clip, grainy=16, grainc=16, output_depth=16, dynamic_grain=True) 7 | #clip = core.resize.Bicubic(clip, format=vs.YUV420P8) 8 | clip.set_output() 9 | -------------------------------------------------------------------------------- /vs-scripts/mi-offline.py: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | core = vs.get_core() 3 | core.std.LoadPlugin("/Users/Julian/.mpv/vs-plugins/ffms2/libffms2.dylib") 4 | 5 | clip = core.ffms2.Source(source='/Users/Julian/Documents/DLs/Test-Videos/test-pulldown.mkv') 6 | display_fps = 60 7 | container_fps = 24000/1001 8 | 9 | 10 | 11 | # skip motion interpolation completely for content exceeding the limits below 12 | max_width = 1920 13 | max_height = 1200 14 | max_fps = 60 15 | # use BlockFPS instead of FlowFPS for content exceeding the limits below 16 | max_flow_width = 1280 17 | max_flow_height = 720 18 | # a block is considered as changed when 8*8*x > th_block|flow_diff with 8*8 19 | # being the size of a block (scaled internally) and x the difference of each 20 | # pixel within this block, default 400 21 | th_block_diff = 8*8*7 22 | th_flow_diff = 8*8*7 23 | # (threshold/255)% blocks have to change to consider this a scene change 24 | # (= no motion compensation), default 130 25 | th_block_changed = 14 26 | th_flow_changed = 14 27 | # size of blocks the analyse step is performed on 28 | blocksize = 2**4 29 | 30 | 31 | 32 | # assume display_fps to be bogus when not in a certain range 33 | target_num = 60 if display_fps < 23.9 or display_fps > 300 else display_fps 34 | while (target_num > max_fps): 35 | target_num /= 2 36 | target_num = int(target_num * 1e6) 37 | target_den = int(1e6) 38 | source_num = int(container_fps * 1e6) 39 | source_den = int(1e6) 40 | 41 | #clip = video_in 42 | 43 | if not (clip.width > max_width or clip.height > max_height or container_fps > max_fps): 44 | clip = core.std.AssumeFPS(clip, fpsnum=source_num, fpsden=source_den) 45 | sup = core.mv.Super(clip, pel=2, hpad=blocksize, vpad=blocksize) 46 | bv = core.mv.Analyse(sup, blksize=blocksize, isb=True , chroma=True, search=3, searchparam=2) 47 | fv = core.mv.Analyse(sup, blksize=blocksize, isb=False, chroma=True, search=3, searchparam=2) 48 | 49 | use_block = clip.width > max_flow_width or clip.height > max_flow_height 50 | if use_block: 51 | clip = core.mv.BlockFPS(clip, sup, bv, fv, num=target_num, den=target_den, 52 | mode=3, thscd1=th_block_diff, thscd2=th_block_changed) 53 | else: 54 | clip = core.mv.FlowFPS(clip, sup, bv, fv, num=target_num, den=target_den, 55 | mask=0, thscd1=th_flow_diff, thscd2=th_flow_changed) 56 | print('motion-interpolation: {0} -> {1} FPS | {3} | {2} Hz' 57 | .format(source_num / source_den, target_num / target_den, 58 | display_fps, use_block and "block" or "flow")) 59 | else: 60 | print('motion-interpolation: skipping {0}x{1} {2} FPS video' 61 | .format(clip.width, clip.height, container_fps)) 62 | 63 | clip.set_output() 64 | -------------------------------------------------------------------------------- /vs-scripts/motion-interpolation-brokenfps.py: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | core = vs.get_core() 3 | container_fps = 24000/1001 # assume fixed FPS (for broken files) 4 | 5 | # skip motion interpolation completely for content exceeding the limits below 6 | max_width = 1920 7 | max_height = 1200 8 | max_fps = 60 9 | # use BlockFPS instead of FlowFPS for content exceeding the limits below 10 | max_flow_width = 1280 11 | max_flow_height = 720 12 | # a block is considered as changed when 8*8*x > th_block|flow_diff with 8*8 13 | # being the size of a block (scaled internally) and x the difference of each 14 | # pixel within this block, default 400 15 | th_block_diff = 8*8*7 16 | th_flow_diff = 8*8*7 17 | # (threshold/255)% blocks have to change to consider this a scene change 18 | # (= no motion compensation), default 130 19 | th_block_changed = 14 20 | th_flow_changed = 14 21 | # size of blocks the analyse step is performed on 22 | blocksize = 2**4 23 | # motion estimation accuracy, precision to 1/acc pixel 24 | acc = 1 25 | # search algorithm and argument 26 | search_alg = 3 27 | search_arg = 2 28 | # processing mask mode (FlowFPS) 29 | msk = 1 30 | 31 | 32 | 33 | # assume display_fps to be bogus when not in a certain range 34 | target_num = 60 if display_fps < 23.9 or display_fps > 300 else display_fps 35 | while (target_num > max_fps): 36 | target_num /= 2 37 | target_num = int(target_num * 1e6) 38 | target_den = int(1e6) 39 | source_num = int(container_fps * 1e6) 40 | source_den = int(1e6) 41 | 42 | clip = video_in 43 | 44 | if not (clip.width > max_width or clip.height > max_height or container_fps > max_fps): 45 | clip = core.std.AssumeFPS(clip, fpsnum=source_num, fpsden=source_den) 46 | sup = core.mv.Super(clip, pel=acc, hpad=blocksize, vpad=blocksize) 47 | bv = core.mv.Analyse(sup, blksize=blocksize, isb=True , chroma=True, search=search_alg, searchparam=search_arg) 48 | fv = core.mv.Analyse(sup, blksize=blocksize, isb=False, chroma=True, search=search_alg, searchparam=search_arg) 49 | 50 | use_block = clip.width > max_flow_width or clip.height > max_flow_height 51 | if use_block: 52 | clip = core.mv.BlockFPS(clip, sup, bv, fv, num=target_num, den=target_den, 53 | mode=3, thscd1=th_block_diff, thscd2=th_block_changed) 54 | else: 55 | clip = core.mv.FlowFPS(clip, sup, bv, fv, num=target_num, den=target_den, 56 | mask=msk, thscd1=th_flow_diff, thscd2=th_flow_changed) 57 | print('motion-interpolation: {0} -> {1} FPS | {3} | {2} Hz' 58 | .format(source_num / source_den, target_num / target_den, 59 | display_fps, use_block and "block" or "flow")) 60 | else: 61 | print('motion-interpolation: skipping {0}x{1} {2} FPS video' 62 | .format(clip.width, clip.height, container_fps)) 63 | 64 | clip.set_output() 65 | -------------------------------------------------------------------------------- /vs-scripts/motion-interpolation.py: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | core = vs.get_core() 3 | 4 | 5 | # skip motion interpolation completely for content exceeding the limits below 6 | max_width = 1920 7 | max_height = 1200 8 | max_fps = 61 9 | # use BlockFPS instead of FlowFPS for content exceeding the limits below 10 | max_flow_width = 1280 11 | max_flow_height = 720 12 | # a block is considered as changed when 8*8*x > th_block|flow_diff with 8*8 13 | # being the size of a block (scaled internally) and x the difference of each 14 | # pixel within this block, default 400 15 | th_block_diff = 8*8*7 16 | th_flow_diff = 8*8*7 17 | # (threshold/255)% blocks have to change to consider this a scene change 18 | # (= no motion compensation), default 130 19 | th_block_changed = 14 20 | th_flow_changed = 14 21 | # size of blocks the analyse step is performed on 22 | blocksize = 2**4 23 | # motion estimation accuracy, precision to 1/acc pixel 24 | acc = 1 25 | # search algorithm and argument 26 | search_alg = 3 27 | search_arg = 2 28 | # processing mask mode (FlowFPS) 29 | msk = 1 30 | 31 | 32 | # assume display_fps to be bogus when not in a certain range 33 | target_num = 60 if display_fps < 23.9 or display_fps > 300 else display_fps 34 | while (target_num > max_fps): 35 | target_num /= 2 36 | target_num = int(target_num * 1e6) 37 | target_den = int(1e6) 38 | source_num = int(container_fps * 1e6) 39 | source_den = int(1e6) 40 | 41 | clip = video_in 42 | 43 | if not (clip.width > max_width or clip.height > max_height or container_fps > max_fps): 44 | clip = core.std.AssumeFPS(clip, fpsnum=source_num, fpsden=source_den) 45 | sup = core.mv.Super(clip, pel=acc, hpad=blocksize, vpad=blocksize) 46 | bv = core.mv.Analyse(sup, blksize=blocksize, isb=True , chroma=True, search=search_alg, searchparam=search_arg) 47 | fv = core.mv.Analyse(sup, blksize=blocksize, isb=False, chroma=True, search=search_alg, searchparam=search_arg) 48 | 49 | use_block = clip.width > max_flow_width or clip.height > max_flow_height 50 | if use_block: 51 | clip = core.mv.BlockFPS(clip, sup, bv, fv, num=target_num, den=target_den, 52 | mode=3, thscd1=th_block_diff, thscd2=th_block_changed) 53 | else: 54 | clip = core.mv.FlowFPS(clip, sup, bv, fv, num=target_num, den=target_den, 55 | mask=msk, thscd1=th_flow_diff, thscd2=th_flow_changed) 56 | print('motion-interpolation: {0} -> {1} FPS ({3}) {2} Hz' 57 | .format(source_num / source_den, target_num / target_den, 58 | display_fps, use_block and "block" or "flow")) 59 | else: 60 | print('motion-interpolation: skipping {0}x{1} {2} FPS video' 61 | .format(clip.width, clip.height, container_fps)) 62 | 63 | clip.set_output() 64 | --------------------------------------------------------------------------------