├── README.md ├── comparison-01.webp ├── fonts ├── osc.ttf ├── playlist-selected.ttf ├── stats mono.ttf ├── stats.ttf └── subs.ttf ├── input.conf ├── mpv.conf ├── preview.webp ├── preview2023-05-30.webp ├── script-opts ├── console.conf ├── detect_image.conf ├── image_positioning.conf ├── minimap.conf ├── osc.conf ├── playlistmanager.conf ├── ruler.conf ├── stats.conf ├── status_line.conf ├── thumbfast.conf └── youtube-quality.conf ├── scripts ├── Mac_Integration.lua ├── appendURL.lua ├── auto-profiles.lua ├── autocrop.lua ├── autoload.lua ├── cycle-commands.lua ├── detect-image.lua ├── equalizer.lua ├── freeze-window.lua ├── image-positioning.lua ├── minimap.lua ├── osc.lua ├── playlistmanager.lua ├── ruler.lua ├── seek-to.lua ├── stats.lua ├── status-line.lua ├── systemtime.lua ├── thumbfast.lua └── youtube-quality.lua └── shaders ├── Anime4K_Darken_HQ.glsl ├── Anime4K_Restore_CNN_Soft_VL.glsl ├── Anime4K_Thin_HQ.glsl ├── Anime4K_Upscale_CNN_x2_VL.glsl ├── CAS-scaled.glsl ├── CAS.glsl ├── FSR.glsl ├── FSRCNNX_x2_8-0-4-1.glsl ├── KrigBilateral.glsl ├── NVScaler.glsl ├── NVSharpen.glsl ├── SSimDownscaler.glsl ├── SSimSuperRes.glsl ├── adaptive-sharpen.glsl ├── adaptive-sharpen4k.glsl ├── adaptive-sharpen8k.glsl └── crt.glsl /README.md: -------------------------------------------------------------------------------- 1 | # Xzpyth mpv-config 2 | ## About 3 | My personal config for MPV player with [SSSR](https://gist.github.com/igv/2364ffa6e81540f29cb7ab4c9bc05b6b) shader. Next I use [Image viewer](https://github.com/occivink/mpv-image-viewer) with different upscale/dscale methods as it doesn't matter what FPS you will get and absolute perfection and sharpness was goal here 4 | This config uses **gpu-api vulkan** and **vo=gpu-next** for HDR support. For sources Below 720p (e.g. 480p 580p) I use [NVSharpen](https://gist.github.com/agyild/7e8951915b2bf24526a9343d951db214) as it's superior (in subjective experiments) here. 2023/05/31 I added [thumbfast](https://github.com/po5/thumbfast) 5 | ## Config 6 | For images i use separate methods involving SSSR downscaling and upscaling for hi-res and low-res images respectively 7 | ## Preview 8 | ![Alt text](preview.webp?raw=true "Screenshot of GUI") 9 | ![Alt text](comparison-01.webp?raw=true "Downscalers comparison") 10 | ## Keybindings 11 | - Shift + Enter = open playlist 12 | - c = 20LUFS audio normalisation 13 | - C = auto crop video 14 | - b = clear all shaders 15 | - U = switch between Anime4k or SSIM upscaler 16 | - H = switch between HDR modes (pictures too) 17 | - Y = switch between sharp modes (pictures too) 18 | - a / v / s = switch between audio / video / subtitle tracks 19 | -------------------------------------------------------------------------------- /comparison-01.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzpyth/mpv-config-with-upscalers/a8f22713e24b3641c9b77fe451f978479ae8856a/comparison-01.webp -------------------------------------------------------------------------------- /fonts/osc.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzpyth/mpv-config-with-upscalers/a8f22713e24b3641c9b77fe451f978479ae8856a/fonts/osc.ttf -------------------------------------------------------------------------------- /fonts/playlist-selected.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzpyth/mpv-config-with-upscalers/a8f22713e24b3641c9b77fe451f978479ae8856a/fonts/playlist-selected.ttf -------------------------------------------------------------------------------- /fonts/stats mono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzpyth/mpv-config-with-upscalers/a8f22713e24b3641c9b77fe451f978479ae8856a/fonts/stats mono.ttf -------------------------------------------------------------------------------- /fonts/stats.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzpyth/mpv-config-with-upscalers/a8f22713e24b3641c9b77fe451f978479ae8856a/fonts/stats.ttf -------------------------------------------------------------------------------- /fonts/subs.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzpyth/mpv-config-with-upscalers/a8f22713e24b3641c9b77fe451f978479ae8856a/fonts/subs.ttf -------------------------------------------------------------------------------- /input.conf: -------------------------------------------------------------------------------- 1 | WHEEL_UP add volume 2 2 | WHEEL_DOWN add volume -2 3 | UP add volume 2 4 | DOWN add volume -2 5 | AXIS_UP add volume 2 6 | AXIS_DOWN add volume -2 7 | Shift+RIGHT frame-step 8 | Shift+LEFT frame-back-step 9 | H show-text "gpu-next";script-message cycle-commands "apply-profile gpu-next" "apply-profile gpu" "apply-profile hdr-force" 10 | h apply-profile audio-out; show-text "audio-out" 11 | v cycle video 12 | - cycle deband 13 | a cycle audio 14 | s cycle sub 15 | I script-binding stats/display-stats-toggle 16 | i cycle interpolation 17 | t script-message-to seek_to toggle-seeker 18 | b apply-profile basic; show-text "Shaders cleared" 19 | U show-text "alternative upscale mode";script-message cycle-commands "apply-profile altup" "apply-profile ssim" 20 | Y show-text "Sharp mode";script-message cycle-commands "apply-profile sharp1" "apply-profile sharp0" 21 | y script-message show_time 22 | + add audio-delay 0.010 23 | _ add audio-delay -0.010 24 | F1 add sub-delay -0.1 25 | F2 add sub-delay +0.1 26 | F4 cycle-values video-aspect-override "16:9" "4:3" "2.35:1" "-1" 27 | 28 | #audio compressor 20LUFS# 29 | c af toggle "lavfi=[loudnorm=i=-20]"; 30 | 31 | 32 | Meta+v script-message-to Mac_Integration OpenFromClipboard 33 | TAB script-message-to Mac_Integration ShowFinder 34 | Ctrl+f script-message-to Mac_Integration ShowInFinder 35 | 36 | #imageviewer# 37 | 38 | 1 {image-viewer} change-list script-opts append image_positioning-drag_to_pan_margin=200 39 | 2 {image-viewer} change-list script-opts append ruler-exit_bindings=8 40 | 3 {image-viewer} change-list script-opts append ruler-line_color=FF 41 | 4 {image-viewer} change-list script-opts append ruler-scale=25 42 | 5 {image-viewer} change-list script-opts append ruler-max_size=20,20 43 | 44 | SPACE {image-viewer} repeatable playlist-next 45 | alt+SPACE {image-viewer} repeatable playlist-prev 46 | 47 | UP {image-viewer} ignore 48 | DOWN {image-viewer} ignore 49 | LEFT {image-viewer} repeatable playlist-prev 50 | RIGHT {image-viewer} repeatable playlist-next 51 | 52 | # simple reminder of default bindings 53 | #1 add contrast -1 54 | #2 add contrast 1 55 | #3 add brightness -1 56 | #4 add brightness 1 57 | #5 add gamma -1 58 | #6 add gamma 1 59 | #7 add saturation -1 60 | #8 add saturation 1 61 | 62 | # mouse-centric bindings 63 | MBTN_RIGHT {image-viewer} script-binding drag-to-pan 64 | MBTN_LEFT {image-viewer} script-binding pan-follows-cursor 65 | MBTN_LEFT_DBL {image-viewer} ignore 66 | WHEEL_UP {image-viewer} script-message cursor-centric-zoom 0.1 67 | WHEEL_DOWN {image-viewer} script-message cursor-centric-zoom -0.1 68 | 69 | # panning with the keyboard: 70 | # pan-image takes the following arguments 71 | # pan-image AXIS AMOUNT ZOOM_INVARIANT IMAGE_CONSTRAINED 72 | # ^ ^ ^ 73 | # x or y | | 74 | # | | 75 | # if yes, will pan by the same if yes, stops panning if the image 76 | # amount regardless of zoom would go outside of the window 77 | 78 | ctrl+down {image-viewer} repeatable script-message pan-image y -0.1 yes yes 79 | ctrl+up {image-viewer} repeatable script-message pan-image y +0.1 yes yes 80 | ctrl+right {image-viewer} repeatable script-message pan-image x -0.1 yes yes 81 | ctrl+left {image-viewer} repeatable script-message pan-image x +0.1 yes yes 82 | 83 | # now with more precision 84 | alt+down {image-viewer} repeatable script-message pan-image y -0.01 yes yes 85 | alt+up {image-viewer} repeatable script-message pan-image y +0.01 yes yes 86 | alt+right {image-viewer} repeatable script-message pan-image x -0.01 yes yes 87 | alt+left {image-viewer} repeatable script-message pan-image x +0.01 yes yes 88 | 89 | # replace at will with h,j,k,l if you prefer vim-style bindings 90 | 91 | # on a trackpad you may want to use these 92 | #WHEEL_UP repeatable script-message pan-image y -0.02 yes yes 93 | #WHEEL_DOWN repeatable script-message pan-image y +0.02 yes yes 94 | #WHEEL_LEFT repeatable script-message pan-image x -0.02 yes yes 95 | #WHEEL_RIGHT repeatable script-message pan-image x +0.02 yes yes 96 | 97 | # align the border of the image to the border of the window 98 | # align-border takes the following arguments: 99 | # align-border ALIGN_X ALIGN_Y 100 | # any value for ALIGN_* is accepted, -1 and 1 map to the border of the window 101 | ctrl+shift+right {image-viewer} script-message align-border -1 "" 102 | ctrl+shift+left {image-viewer} script-message align-border 1 "" 103 | ctrl+shift+down {image-viewer} script-message align-border "" -1 104 | ctrl+shift+up {image-viewer} script-message align-border "" 1 105 | 106 | # reset the image 107 | ctrl+0 {image-viewer} no-osd set video-pan-x 0; no-osd set video-pan-y 0; no-osd set video-zoom 0 108 | 109 | + {image-viewer} add video-zoom 0.5 110 | - {image-viewer} add video-zoom -0.5; script-message reset-pan-if-visible 111 | = {image-viewer} no-osd set video-zoom 0; script-message reset-pan-if-visible 112 | 113 | e {image-viewer} script-message equalizer-toggle 114 | alt+e {image-viewer} script-message equalizer-reset 115 | 116 | h {image-viewer} no-osd vf toggle hflip; show-text "Horizontal flip" 117 | v {image-viewer} no-osd vf toggle vflip; show-text "Vertical flip" 118 | 119 | r {image-viewer} script-message rotate-video 90; show-text "Clockwise rotation" 120 | R {image-viewer} script-message rotate-video -90; show-text "Counter-clockwise rotation" 121 | alt+r {image-viewer} no-osd set video-rotate 0; show-text "Reset rotation" 122 | 123 | d {image-viewer} script-message ruler 124 | 125 | # Toggling between pixel-exact reproduction and interpolation 126 | a {image-viewer} cycle-values scale nearest ewa_lanczossharp 127 | 128 | # Toggle color management on or off 129 | c {image-viewer} cycle icc-profile-auto 130 | 131 | # Screenshot of the window output 132 | S {image-viewer} screenshot window 133 | s {image-viewer} script-message status-line-toggle 134 | 135 | # Toggle aspect ratio information on and off 136 | A {image-viewer} cycle-values video-aspect-override "-1" "no" 137 | 138 | p {image-viewer} script-message force-print-filename 139 | 140 | # ADVANCED: you can define bindings that belong to a "section" (named "image-viewer" here) like so: 141 | #alt+SPACE {image-viewer} repeatable playlist-prev 142 | #SPACE {image-viewer} repeatable playlist-next 143 | # to load them conditionally with a command. See scripts-opts/image_viewer.conf for how you can do this 144 | 145 | 146 | -------------------------------------------------------------------------------- /mpv.conf: -------------------------------------------------------------------------------- 1 | # Renderer # 2 | vo=gpu-next 3 | target-colorspace-hint=yes 4 | --tone-mapping=spline 5 | profile=gpu-hq 6 | gpu-api=vulkan 7 | log-file=C:/.mpv/log.txt 8 | 9 | #glsl-shaders="~~/shaders/crt.glsl" #crt look thanks to haas can look good on some pre90s anime 10 | 11 | # GENERAL # 12 | #fs=yes #start in fullscreen 13 | ytdl=yes 14 | save-position-on-quit 15 | keep-open=yes 16 | autofit=45% 17 | geometry=50%:50% #start with window in the middle 18 | watch-later-directory=C:/.mpv/tmp #default directory for save-position-on-quit 19 | 20 | input-ipc-server=mpvpipe 21 | hr-seek-framedrop=no 22 | border=no 23 | msg-color=yes 24 | msg-module=yes 25 | 26 | # Screenshots # 27 | screenshot-template="%x\Screens\Screenshot-%F-T%wH.%wM.%wS.%wT-F%{estimated-frame-number}" 28 | screenshot-format=png # Set screenshot format 29 | screenshot-png-compression=4 # Range is 0 to 10. 0 being no compression. compute-time to size is log so 4 is best 30 | screenshot-tag-colorspace=yes 31 | screenshot-high-bit-depth=yes # Same output bitdepth as the video 32 | 33 | # OSC/OSD # 34 | 35 | osc=yes 36 | osd-bar=yes # Do not remove/comment if mpv_thumbnail_script_client_osc.lua is being used. 37 | osd-font='Public Sans Light' # Set a font for OSC 38 | osd-font-size=30 # Set a font size 39 | osd-color='#CCFFFFFF' # ARGB format 40 | osd-border-color='#DD322640' # ARGB format 41 | osd-bar-align-y=-1 # progress bar y alignment (-1 top, 0 centered, 1 bottom) 42 | osd-border-size=2 # size for osd text and progress bar 43 | osd-bar-h=1 # height of osd bar as a fractional percentage of your screen height 44 | osd-bar-w=60 # width of " " " 45 | 46 | # Subs # 47 | 48 | #blend-subtitles=no # 49 | sub-ass-vsfilter-blur-compat=yes # Backward compatibility for vsfilter fansubs 50 | sub-ass-scale-with-window=no # May have undesired effects with signs being misplaced. 51 | sub-fix-timing=yes 52 | sub-auto=fuzzy # external subs don't have to match the file name exactly to autoload 53 | #sub-gauss=0.6 # Some settings fixing VOB/PGS subtitles (creating blur & changing yellow subs to gray) 54 | sub-file-paths-append=ass # search for external subs in these relative subdirectories 55 | sub-file-paths-append=srt 56 | sub-file-paths-append=sub 57 | sub-file-paths-append=subs 58 | sub-file-paths-append=subtitles 59 | demuxer-mkv-subtitle-preroll=yes # try to correctly show embedded subs when seeking 60 | embeddedfonts=yes # use embedded fonts for SSA/ASS subs 61 | sub-fix-timing=no # do not try to fix gaps (which might make it worse in some cases). Enable if there are scenebleeds. 62 | 63 | # Subs - Forced # 64 | 65 | sub-font=Public Sans SemiBold 66 | sub-font-size=54 67 | sub-blur=0.3 68 | sub-border-color=0.0/0.0/0.0/1.0 69 | sub-border-size=3.4 70 | sub-color=0.95/0.95/0.95/1.00 71 | sub-margin-x=100 72 | sub-margin-y=50 73 | sub-shadow-color=0.0/0.0/0.0/0.33 74 | sub-shadow-offset=0 75 | 76 | # Audio # 77 | 78 | volume-max=150 # maximum volume in %, everything above 100 results in amplification 79 | #audio-exclusive=yes # bitstream 80 | audio-stream-silence # fix audio popping on random seek 81 | audio-file-auto=fuzzy # external audio doesn't has to match the file name exactly to autoload 82 | audio-pitch-correction=yes # automatically insert scaletempo when playing with higher speed 83 | 84 | # Languages # 85 | 86 | alang=jpn,jp,eng,en,enUS,en-US,de,ger 87 | slang=eng,en,und,de,ger,jp,jap 88 | 89 | # Video Profiles # 90 | 91 | dither=fruit 92 | scale=ewa_lanczos 93 | cscale=lanczos 94 | dscale=mitchell 95 | scale-antiring=0 96 | cscale-antiring=0 97 | correct-downscaling=yes 98 | linear-downscaling=no 99 | sigmoid-upscaling=yes 100 | 101 | # Debanding # 102 | 103 | deband=yes 104 | deband-iterations=2 105 | deband-threshold=40 #increase this number to increase deband strenght but lose some detail 106 | deband-range=16 107 | deband-grain=48 108 | dither-depth=auto 109 | 110 | # interpolation # 111 | 112 | interpolation=no #can be toggled with key i 113 | video-sync=display-resample 114 | tscale=oversample 115 | 116 | blend-subtitles=no 117 | 118 | # force audio-output (open cmd/terminal in mpv directory and type mpv --audio-device=help) # 119 | [audio-out] 120 | --audio-device='wasapi/{13f10bac-9ffd-448d-98ff-edea569ccf67}' 121 | 122 | #alternative upscale# 123 | [altup] 124 | vo=gpu 125 | scale=mitchell 126 | glsl-shaders-clr 127 | glsl-shaders="~~/shaders/Anime4K_Restore_CNN_Soft_VL.glsl" 128 | glsl-shaders-append="~~/shaders/Anime4K_Upscale_CNN_x2_VL.glsl" 129 | glsl-shaders-append="~~/shaders/Anime4K_Thin_HQ.glsl" 130 | glsl-shaders-append="~~/shaders/Anime4K_Darken_HQ.glsl" 131 | 132 | #altup toggle# 133 | [ssim] 134 | vo=gpu-next 135 | scale=lanczos 136 | glsl-shaders-clr 137 | glsl-shaders-append="~~/shaders/adaptive-sharpen4k.glsl" 138 | glsl-shaders-append="~~/shaders/SSimSuperRes.glsl" 139 | glsl-shaders-append="~~/shaders/SSimDownscaler.glsl" 140 | deband-grain=30 141 | 142 | #AUDIO PLAYER OSC# 143 | [audio] 144 | --script-opts=osc-visibility=always 145 | --save-position-on-quit=no 146 | glsl-shaders-clr 147 | scale=lanczos 148 | glsl-shaders-append="~~/shaders/SSimSuperRes.glsl" 149 | glsl-shaders-append="~~/shaders/SSimDownscaler.glsl" 150 | 151 | [extension.mp3] 152 | profile=audio 153 | [extension.m4a] 154 | profile=audio 155 | [extension.wav] 156 | profile=audio 157 | [extension.flac] 158 | profile=audio 159 | [extension.ogg] 160 | profile=audio 161 | [extension.opus] 162 | profile=audio 163 | [extension.cue] 164 | profile=audio 165 | [extension.m3a] 166 | profile=audio 167 | [extension.mkv] 168 | profile=mkv 169 | 170 | #[mkv] 171 | #cache=yes 172 | #demuxer-max-bytes=2000MiB 173 | 174 | [interlaced footage] 175 | profile-cond=p["video-frame-info/interlaced"] 176 | deinterlace=yes 177 | 178 | [low-res video] 179 | profile-desc=cond:(get('height', math.huge) < 720) and (get('estimated-frame-count', math.huge) > 2) 180 | scale=lanczos 181 | glsl-shaders-append="~~/shaders/NVSharpen.glsl" 182 | glsl-shaders-append="~~/shaders/KrigBilateral.glsl" 183 | deband-grain=60 184 | 185 | [720p video] 186 | profile-desc=cond:(get('height', math.huge) < 721) and (get('height', math.huge) > 719) and (get('estimated-frame-count', math.huge) > 2) 187 | scale=lanczos 188 | glsl-shaders-append="~~/shaders/SSimSuperRes.glsl" 189 | glsl-shaders-append="~~/shaders/KrigBilateral.glsl" 190 | deband-grain=100 191 | 192 | [hi-res video] 193 | profile-desc=cond:get('height', math.huge) > 720 and (get('estimated-frame-count', math.huge) > 2) or (get('estimated-frame-count', math.huge) ~= 0) 194 | scale=lanczos 195 | glsl-shaders-append="~~/shaders/SSimSuperRes.glsl" 196 | glsl-shaders-append="~~/shaders/adaptive-sharpen.glsl" 197 | glsl-shaders-append="~~/shaders/KrigBilateral.glsl" 198 | 199 | #image shaders# 200 | 201 | [hi-res-image] 202 | profile-desc=cond:(get('current-window-scale', math.huge) <= 1) and (get('estimated-frame-count', math.huge) == 1 or get('estimated-frame-count', math.huge) == 0) 203 | dscale=lanczos 204 | dscale-blur=0.8 205 | glsl-shaders-clr 206 | glsl-shaders="~~/shaders/SSimDownscaler.glsl" 207 | glsl-shaders="~~/shaders/KrigBilateral.glsl" 208 | 209 | [low-res-image] 210 | profile-desc=cond:(get('current-window-scale', math.huge) > 1) and (get('estimated-frame-count', math.huge) == 1 or get('estimated-frame-count', math.huge) == 0) 211 | glsl-shaders-clr 212 | scale=lanczos 213 | glsl-shaders-append="~~/shaders/NVSharpen.glsl" 214 | glsl-shaders-append="~~/shaders/KrigBilateral.glsl" 215 | glsl-shaders-append="~~/shaders/SSimDownscaler.glsl" 216 | 217 | [basic] 218 | glsl-shaders-clr #binded to button b in input config to clear shaders-for testing only 219 | scale=ewa_lanczossharp 220 | 221 | #[raw-mode] 222 | #profile-desc=cond:(get('height', math.huge)*get('width', math.huge)/(get('video-bitrate', math.huge)) < 80 ) 223 | 224 | #SDR switch# 225 | 226 | [gpu-next] 227 | vo=gpu-next 228 | --icc-profile-auto=no 229 | --inverse-tone-mapping=no 230 | --target-trc=auto 231 | [gpu] 232 | vo=gpu 233 | --icc-profile-auto=no 234 | --inverse-tone-mapping=no 235 | [hdr-force] 236 | vo=gpu-next 237 | target-colorspace-hint 238 | --icc-profile-auto=no 239 | --inverse-tone-mapping=no 240 | --target-trc=pq 241 | 242 | #Sharpen image 243 | 244 | [sharp1] 245 | glsl-shaders-clr 246 | scale=lanczos 247 | glsl-shaders-append="~~/shaders/adaptive-sharpen8k.glsl" 248 | glsl-shaders-append="~~/shaders/SSimSuperRes.glsl" 249 | glsl-shaders-append="~~/shaders/SSimDownscaler.glsl" 250 | deband-grain=60 251 | [sharp0] 252 | glsl-shaders-clr 253 | scale=lanczos 254 | dscale=lanczos 255 | glsl-shaders-append="~~/shaders/FSRCNNX_x2_8-0-4-1.glsl" 256 | glsl-shaders-append="~~/shaders/KrigBilateral.glsl" 257 | glsl-shaders-append="~~/shaders/SSimDownscaler.glsl" 258 | 259 | [protocol.file] 260 | network-timeout=0 261 | force-window=yes 262 | cache=yes 263 | demuxer-max-bytes=2000MiB 264 | demuxer-readahead-secs=300 265 | force-seekable=yes 266 | 267 | [protocol-network] 268 | network-timeout=5 269 | #force-window=immediate 270 | hls-bitrate=max 271 | cache=yes 272 | demuxer-max-bytes=2000MiB 273 | demuxer-readahead-secs=300 274 | 275 | [protocol.http] 276 | profile=protocol-network 277 | 278 | [protocol.https] 279 | profile=protocol-network 280 | 281 | [not-image] 282 | profile-cond=(get('estimated-frame-count', math.huge) >= 2) 283 | --script-opts=osc-visibility=auto 284 | 285 | [image] 286 | profile-cond=(get('estimated-frame-count', math.huge) < 2) 287 | --icc-profile-auto=no 288 | --script-opts=osc-visibility=never 289 | --save-position-on-quit=no 290 | #background=0.1 # dark grey background instead of pure black 291 | #mute=yes 292 | osc=no # the osc is mostly useful for videos 293 | 294 | sub-auto=no # don't try to autoload subtitles or audio files 295 | audio-file-auto=no # get rid of the useless V: 00:00:00 / 00:00:00 line 296 | image-display-duration=inf # don't slideshow by default 297 | loop-file=inf # loop files in case of webms or gifs 298 | loop-playlist=inf # and loop the whole playlist 299 | window-dragging=no # you need this if you plan to use drag-to-pan or pan-follows-cursor with MOUSE_LEFT 300 | deband=no 301 | 302 | [extension.png] 303 | video-aspect-override=no 304 | [extension.jpg] 305 | video-aspect-override=no 306 | [extension.jpeg] 307 | profile=extension.jpg 308 | [silent] 309 | msg-level=all=no 310 | 311 | 312 | -------------------------------------------------------------------------------- /preview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzpyth/mpv-config-with-upscalers/a8f22713e24b3641c9b77fe451f978479ae8856a/preview.webp -------------------------------------------------------------------------------- /preview2023-05-30.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzpyth/mpv-config-with-upscalers/a8f22713e24b3641c9b77fe451f978479ae8856a/preview2023-05-30.webp -------------------------------------------------------------------------------- /script-opts/console.conf: -------------------------------------------------------------------------------- 1 | font_size=9 -------------------------------------------------------------------------------- /script-opts/detect_image.conf: -------------------------------------------------------------------------------- 1 | # commands to execute when a file detected as an image (1 frame, no audio) is loaded or unloaded 2 | 3 | # an image was loaded, and the previous file was not an image (or there was no previous file) 4 | command_on_first_image_loaded=apply-profile image; enable-section image-viewer; script-message status-line-enable; script-message minimap-enable 5 | # an image was loaded (regardless of what the previous file was) 6 | command_on_image_loaded=no-osd set video-pan-x 0; script-message align-border "" -1 7 | # a non-image was loaded, and the previous file was an image 8 | command_on_non_image_loaded=disable-section image-viewer; no-osd set video-pan-x 0; no-osd set video-pan-y 0; no-osd set video-zoom 0; script-message status-line-disable;script-message minimap-disable 9 | 10 | # the purpose of these "hooks" is to let you change bindings, profiles, reset properties... 11 | # see https://mpv.io/manual/master/#list-of-input-commands for general command information 12 | # note that there is no such thing as "unloading a profile", to emulate this you must create an opposite profile and load that 13 | 14 | # example possible values: 15 | #command_on_first_image_loaded=apply-profile image; enable-section image-viewer; script-message status-line-enable 16 | #command_on_image_loaded=no-osd set video-pan-x 0; script-message align-border "" -1 17 | #command_on_non_image_loaded=disable-section image-viewer; no-osd set video-pan-x 0; no-osd set video-pan-y 0; no-osd set video-zoom 0; script-message status-line-disable 18 | -------------------------------------------------------------------------------- /script-opts/image_positioning.conf: -------------------------------------------------------------------------------- 1 | # size of the margins with drag-to-pan 2 | drag_to_pan_margin=50 3 | drag_to_pan_move_if_full_view=no 4 | 5 | # size of the margins with pan-follows-cursor 6 | pan_follows_cursor_margin=50 7 | 8 | # size of the margins with cursor-centric-zoom 9 | cursor_centric_zoom_margin=50 10 | # if the borders would show up, move the image 11 | # this makes it not exactly cursor-centric in some cases 12 | cursor_centric_zoom_auto_center=yes 13 | # allow zooming out if the image can already fully fit in the window 14 | cursor_centric_zoom_dezoom_if_full_view=no 15 | -------------------------------------------------------------------------------- /script-opts/minimap.conf: -------------------------------------------------------------------------------- 1 | # whether to show by default 2 | enabled=no 3 | # the position of the center of the minimap, in percentage of the window (x, y) 4 | center=92,92 5 | # the scale of the minimap (i.e. the view rectangle is scale / 100 times the size of the window) 6 | scale=12 7 | # the cutoff size of the minimap (i.e. the image rectangle is clipped if it falls outside of the this zone) 8 | max_size=16,16 9 | # opacity of the "image" (from 00=opaque to FF=transparent) 10 | image_opacity=88 11 | # color of the "image" (#BBGGRR where each component rages from 00 to FF) 12 | image_color=BBBBBB 13 | # opacity of the "view" 14 | view_opacity=BB 15 | view_color=222222 16 | # whether the view should be drawn above the image 17 | view_above_image=yes 18 | # whether to show the minimap if the current image is fully visible 19 | hide_when_full_image_in_view=yes 20 | -------------------------------------------------------------------------------- /script-opts/osc.conf: -------------------------------------------------------------------------------- 1 | [osc-conf] 2 | seekbarstyle=bar 3 | scalewindowed=3 4 | hidetimeout=1000 5 | fadeduration=200 6 | title=${media-title} • ${video-format} ${audio-codec-name} ▩ ${colormatrix} 7 | minmousemove=0 8 | timetotal=no 9 | visibility=auto 10 | boxalpha=80 11 | vidscale=no 12 | unicodeminus=yes 13 | scalefullscreen=3 14 | deadzonesize=0.6 15 | valign=0.99 16 | halign=0.0 17 | barmargin=0 18 | -------------------------------------------------------------------------------- /script-opts/playlistmanager.conf: -------------------------------------------------------------------------------- 1 | #### ------- Mpv-Playlistmanager configuration ------- #### 2 | 3 | #### ------- FUNCTIONAL ------- #### 4 | 5 | #navigation keybindings force override only while playlist is visible 6 | #if "no" then you can display the playlist by any of the navigation keys 7 | dynamic_binds=yes 8 | 9 | #dynamic keybind keys, they should not be re-bound in input.conf 10 | #to bind multiple keys separate them by a space 11 | key_moveup=UP 12 | key_movedown=DOWN 13 | key_selectfile=RIGHT LEFT 14 | key_unselectfile= 15 | key_playfile=ENTER 16 | key_removefile=BS 17 | key_closeplaylist=ESC 18 | 19 | #json format for replacing, check .lua for explanation 20 | #example json=[{"ext":{"all":true},"rules":[{"_":" "}]},{"ext":{"mp4":true,"mkv":true},"rules":[{"^(.+)%..+$":"%1"},{"%s*[%[%(].-[%]%)]%s*":""},{"(%w)%.(%w)":"%1 %2"}]},{"protocol":{"http":true,"https":true},"rules":[{"^%a+://w*%.?":""}]}] 21 | #empty for no replace 22 | filename_replace= 23 | 24 | #filetypes to search from directory 25 | loadfiles_filetypes=["jpg","jpeg","png","tif","tiff","gif","webp","svg","bmp","mp3","wav","ogm","flac","m4a","wma","ogg","opus","mkv","avi","mp4","ogv","webm","rmvb","flv","wmv","mpeg","mpg","m4v","3gp"] 26 | 27 | #loadfiles at startup if there is 0 or 1 items in playlist, if 0 uses worḱing dir for files 28 | #requires --idle=yes or --idle=once if 0 files in playlist 29 | loadfiles_on_start=no 30 | 31 | #sort playlist on mpv start 32 | sortplaylist_on_start=no 33 | 34 | #sort playlist when any files are added to playlist after initial load 35 | sortplaylist_on_file_add=no 36 | 37 | #yes: use alphanumerical sort comparison(nonpadded numbers in order), no: use normal lua string comparison 38 | alphanumsort=yes 39 | 40 | #linux | windows | auto 41 | system=auto 42 | 43 | #Use ~ for home directory. Leave as empty to use mpv/playlists 44 | playlist_savepath= 45 | 46 | #save playlist automatically after current file was unloaded 47 | save_playlist_on_file_end=no 48 | 49 | #2 shows playlist, 1 shows current file(filename strip applied), 0 shows nothing 50 | show_playlist_on_fileload=0 51 | 52 | #sync cursor when file is loaded from outside reasons(file-ending, playlist-next shortcut etc.) 53 | sync_cursor_on_load=yes 54 | 55 | #playlist open key will toggle visibility instead of refresh 56 | open_toggles=yes 57 | 58 | #allow the playlist cursor to loop from end to start and vice versa 59 | loop_cursor=yes 60 | 61 | #### ------- VISUAL ------- #### 62 | 63 | #prefer to display titles for following files: "all", "url", "none". Sorting still uses filename 64 | prefer_titles=url 65 | 66 | #call youtube-dl to resolve the titles of urls in the playlist 67 | resolve_titles=no 68 | 69 | #playlist timeout on inactivity, with high value on this open_toggles is good to be yes 70 | playlist_display_timeout=10 71 | 72 | #amount of entries to show before slicing. Optimal value depends on font/video size etc. 73 | showamount=20 74 | 75 | #font size scales by window, if no then needs larger font and padding sizes 76 | scale_playlist_by_window=yes 77 | #playlist ass style overrides 78 | #example {\fnUbuntu\fs10\b0\bord1} equals: font=Ubuntu, size=10, bold=no, border=1 79 | #read http://docs.aegisub.org/3.2/ASS_Tags/ for reference of tags 80 | #no values defaults to OSD settings in mpv.conf 81 | style_ass_tags={\fnDM Sans\fs8\bord0.8} 82 | #paddings for top left corner 83 | text_padding_x=10 84 | text_padding_y=30 85 | 86 | #set title of window with stripped name 87 | set_title_stripped=no 88 | title_prefix= 89 | title_suffix= - mpv 90 | 91 | #slice long filenames, and how many chars to show 92 | slice_longfilenames=no 93 | slice_longfilenames_amount=70 94 | 95 | #Playing header. One newline will be added after the string. 96 | #%mediatitle or %filename = title or name of playing file 97 | #%pos = position of playing file 98 | #%cursor = position of navigation 99 | #%plen = playlist lenght 100 | #%N = newline 101 | playlist_header=[%cursor/%plen] 102 | 103 | #Playlist file templates 104 | #%pos = position of file with leading zeros 105 | #%name = title or name of file 106 | #%N = newline 107 | #you can also use the ass tags mentioned above. For example: 108 | # selected_file={\c&HFF00FF&}➔ %name | to add a color for selected file. However, if you 109 | # use ass tags you need to reset them for every line (see https://github.com/jonniek/mpv-playlistmanager/issues/20) 110 | 111 | normal_file=○ {\fnPublic Sans}%name 112 | hovered_file=● {\fnPublic Sans Bold}%name 113 | selected_file=➔ {\fnPublic Sans}%name 114 | playing_file=▷ {\fnPublic Sans}%name 115 | playing_hovered_file=▶ {\fnPublic Sans Bold}%name 116 | playing_selected_file=➤ {\fnPublic Sans Bold}%name 117 | 118 | #what to show when playlist is truncated 119 | playlist_sliced_prefix=... 120 | playlist_sliced_suffix=... 121 | -------------------------------------------------------------------------------- /script-opts/ruler.conf: -------------------------------------------------------------------------------- 1 | # whether to show the length of the lines between the two points 2 | show_distance=yes 3 | # whether to show the coordinates of the two points 4 | show_coordinates=yes 5 | # the coordinate space of the text shown. Can be "image", "window", "both" 6 | coordinates_space=image 7 | # can be "degrees", "radians", "both", or "no" 8 | show_angles=degrees 9 | line_width=2 10 | dots_radius=3 11 | font_size=36 12 | # ranges from 00 (black) to FF (white) 13 | line_color=33 14 | # bindings used to set points. The binding to trigger ruler mode can also be used. Comma-separated list 15 | confirm_bindings=MBTN_LEFT,ENTER 16 | # bindings used to set points. The binding to trigger ruler mode can also be used. Comma-separated list 17 | exit_bindings=ESC 18 | # if yes, the first point will be immediately set at the cursor position when calling 'ruler' 19 | set_first_point_on_begin=no 20 | # if yes, the ruler overlay will be immediately cleared when setting the second point 21 | clear_on_second_point_set=no 22 | -------------------------------------------------------------------------------- /script-opts/stats.conf: -------------------------------------------------------------------------------- 1 | # MPV - stats.conf 2 | # deus0ww - 2020-01-21 3 | 4 | duration=10 5 | persistent_overlay=yes 6 | filter_params_max_length=0 7 | 8 | plot_perfdata=no 9 | plot_vsync_ratio=no 10 | plot_vsync_jitter=no 11 | 12 | font=Public Sans 13 | font_mono=JetBrains Mono 14 | font_size=5.5 15 | font_color=fafafa 16 | border_size=0.6 17 | border_color=000000 18 | alpha=11 19 | -------------------------------------------------------------------------------- /script-opts/status_line.conf: -------------------------------------------------------------------------------- 1 | # whether to show by default 2 | enabled=no 3 | # its font size 4 | size=36 5 | # distance of the text to the borders 6 | margin=10 7 | # the text to be expanded 8 | # see property expansion: https://mpv.io/manual/master/#property-expansion 9 | # \N can be used for line breaks 10 | # you can also use ass tags, see here: http://docs.aegisub.org/3.2/ASS_Tags/ 11 | text_top_left= 12 | text_top_right= 13 | text_bottom_left=${filename} [${playlist-pos-1}/${playlist-count}] 14 | text_bottom_right=[${dwidth:X}x${dheight:X}] 15 | -------------------------------------------------------------------------------- /script-opts/thumbfast.conf: -------------------------------------------------------------------------------- 1 | # Socket path (leave empty for auto) 2 | socket= 3 | 4 | # Thumbnail path (leave empty for auto) 5 | thumbnail= 6 | 7 | # Maximum thumbnail size in pixels (scaled down to fit) 8 | # Values are scaled when hidpi is enabled 9 | max_height=320 10 | max_width=320 11 | 12 | # Apply tone-mapping, no to disable 13 | tone_mapping=auto 14 | 15 | # Overlay id 16 | overlay_id=42 17 | 18 | # Spawn thumbnailer on file load for faster initial thumbnails 19 | spawn_first=no 20 | 21 | # Close thumbnailer process after an inactivity period in seconds, 0 to disable 22 | quit_after_inactivity=0 23 | 24 | # Enable on network playback 25 | network=no 26 | 27 | # Enable on audio playback 28 | audio=no 29 | 30 | # Enable hardware decoding 31 | hwdec=no 32 | 33 | # Windows only: use native Windows API to write to pipe (requires LuaJIT) 34 | direct_io=no 35 | 36 | # Custom path to the mpv executable 37 | mpv_path=mpv 38 | -------------------------------------------------------------------------------- /script-opts/youtube-quality.conf: -------------------------------------------------------------------------------- 1 | toggle_menu_binding=ctrl+f 2 | up_binding=UP 3 | down_binding=DOWN 4 | select_binding=ENTER 5 | 6 | selected_and_active=▶ - 7 | selected_and_inactive=● - 8 | unselected_and_active=▷ - 9 | unselected_and_inactive=○ - 10 | 11 | scale_playlist_by_window=no 12 | 13 | style_ass_tags={\\fnmonospace} 14 | 15 | text_padding_x=5 16 | text_padding_y=5 17 | 18 | menu_timeout=10 19 | 20 | fetch_formats=yes 21 | 22 | quality_strings=[ {"4320p" : "bestvideo[height<=?4320p]+bestaudio/best"}, {"2160p" : "bestvideo[height<=?2160]+bestaudio/best"}, {"1440p" : "bestvideo[height<=?1440]+bestaudio/best"}, {"1080p" : "bestvideo[height<=?1080]+bestaudio/best"}, {"720p" : "bestvideo[height<=?720]+bestaudio/best"}, {"480p" : "bestvideo[height<=?480]+bestaudio/best"}, {"360p" : "bestvideo[height<=?360]+bestaudio/best"}, {"240p" : "bestvideo[height<=?240]+bestaudio/best"}, {"144p" : "bestvideo[height<=?144]+bestaudio/best"} ] 23 | -------------------------------------------------------------------------------- /scripts/Mac_Integration.lua: -------------------------------------------------------------------------------- 1 | -- deus0ww - 2019-07-01 2 | 3 | local mp = require 'mp' 4 | local msg = require 'mp.msg' 5 | 6 | 7 | 8 | -- Show Finder 9 | mp.register_script_message('ShowFinder', function() 10 | mp.command_native({'run', 'open', '-a', 'Finder'}) 11 | end) 12 | 13 | 14 | 15 | -- Show File in Finder 16 | mp.register_script_message('ShowInFinder', function() 17 | local path = mp.get_property_native('path', '') 18 | msg.debug('Show in Finder:', path) 19 | if path == '' then return end 20 | local cmd = {'open'} 21 | if path:find('http://') ~= nil or path:find('https://') ~= nil then 22 | elseif path:find('edl://') ~= nil then 23 | cmd[#cmd+1] = '-R' 24 | path = path:gsub('edl://', ''):gsub(';/', '" /"') 25 | elseif path:find('file://') ~= nil then 26 | cmd[#cmd+1] = '-R' 27 | path = path:gsub('file://', '') 28 | else 29 | cmd[#cmd+1] = '-R' 30 | end 31 | cmd[#cmd+1] = path 32 | mp.command_native( {name='subprocess', args=cmd} ) 33 | end) 34 | 35 | 36 | 37 | -- Move to Trash -- Requires: https://github.com/ali-rantakari/trash 38 | mp.register_script_message('MoveToTrash', function() 39 | local demux_state = mp.get_property_native('demuxer-cache-state', {}) 40 | local demux_ranges = demux_state['seekable-ranges'] and #demux_state['seekable-ranges'] or 1 41 | if demux_ranges > 0 then 42 | mp.osd_message('Trashing not supported.') 43 | return 44 | end 45 | local path = mp.get_property_native('path', ''):gsub('edl://', ''):gsub(';/', '" /"') 46 | msg.debug('Moving to Trash:', path) 47 | if path and path ~= '' then 48 | mp.command_native({'run', 'trash', '-F', path}) 49 | mp.osd_message('Trashed.') 50 | else 51 | mp.osd_message('Trashing failed.') 52 | end 53 | end) 54 | 55 | 56 | 57 | -- Open From Clipboard - One URL per line 58 | mp.register_script_message('OpenFromClipboard', function() 59 | local osd_msg = 'Opening From Clipboard: ' 60 | 61 | local success, result = pcall(io.popen, 'pbpaste') 62 | if not success or not result then 63 | mp.osd_message(osd_msg .. 'n/a') 64 | return 65 | end 66 | local lines = {} 67 | for line in result:lines() do lines[#lines+1] = line end 68 | if #lines == 0 then 69 | mp.osd_message(osd_msg .. 'n/a') 70 | return 71 | end 72 | 73 | local mode = 'replace' 74 | for _, line in ipairs(lines) do 75 | msg.debug('loadfile', line, mode) 76 | mp.commandv('loadfile', line, mode) 77 | mode = 'append' 78 | end 79 | 80 | local msg = osd_msg 81 | if #lines > 0 then msg = msg .. '\n' .. lines[1] end 82 | if #lines > 1 then msg = msg .. (' ... and %d other URL(s).'):format(#lines-1) end 83 | mp.osd_message(msg, 6.0) 84 | end) 85 | -------------------------------------------------------------------------------- /scripts/appendURL.lua: -------------------------------------------------------------------------------- 1 | -- appendurl - Tsubajashi 2 | 3 | local platform = nil --set to 'linux', 'windows' or 'macos' to override automatic assign 4 | 5 | if not platform then 6 | local o = {} 7 | if mp.get_property_native('options/vo-mmcss-profile', o) ~= o then 8 | platform = 'windows' 9 | elseif mp.get_property_native('options/input-app-events', o) ~= o then 10 | platform = 'macos' 11 | else 12 | platform = 'linux' 13 | end 14 | end 15 | 16 | local utils = require 'mp.utils' 17 | local msg = require 'mp.msg' 18 | 19 | --main function 20 | function append(primaryselect) 21 | local clipboard = get_clipboard(primaryselect or false) 22 | if clipboard then 23 | mp.commandv("loadfile", clipboard, "append-play") 24 | mp.osd_message("URL appended: "..clipboard) 25 | msg.info("URL appended: "..clipboard) 26 | end 27 | end 28 | 29 | --handles the subprocess response table and return clipboard if it was a success 30 | function handleres(res, args, primary) 31 | if not res.error and res.status == 0 then 32 | return res.stdout 33 | else 34 | --if clipboard failed try primary selection 35 | if platform=='linux' and not primary then 36 | append(true) 37 | return nil 38 | end 39 | msg.error("There was an error getting "..platform.." clipboard: ") 40 | msg.error(" Status: "..(res.status or "")) 41 | msg.error(" Error: "..(res.error or "")) 42 | msg.error(" stdout: "..(res.stdout or "")) 43 | msg.error("args: "..utils.to_string(args)) 44 | return nil 45 | end 46 | end 47 | 48 | function get_clipboard(primary) 49 | if platform == 'linux' then 50 | local args = { 'xclip', '-selection', primary and 'primary' or 'clipboard', '-out' } 51 | return handleres(utils.subprocess({ args = args }), args, primary) 52 | elseif platform == 'windows' then 53 | local args = { 54 | 'powershell', '-NoProfile', '-Command', [[& { 55 | Trap { 56 | Write-Error -ErrorRecord $_ 57 | Exit 1 58 | } 59 | 60 | $clip = "" 61 | if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) { 62 | $clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText 63 | } else { 64 | Add-Type -AssemblyName PresentationCore 65 | $clip = [Windows.Clipboard]::GetText() 66 | } 67 | 68 | $clip = $clip -Replace "`r","" 69 | $u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip) 70 | [Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length) 71 | }]] 72 | } 73 | return handleres(utils.subprocess({ args = args }), args) 74 | elseif platform == 'macos' then 75 | local args = { 'pbpaste' } 76 | return handleres(utils.subprocess({ args = args }), args) 77 | end 78 | return nil 79 | end 80 | 81 | mp.add_key_binding("ctrl+v", "appendURL", append) -------------------------------------------------------------------------------- /scripts/auto-profiles.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Automatically apply profiles based on runtime conditions. 4 | At least mpv 0.21.0 is required. 5 | 6 | This script queries the list of loaded config profiles, and checks the 7 | "profile-desc" field of each profile. If it starts with "cond:", the script 8 | parses the string as Lua expression, and evaluates it. If the expression 9 | returns true, the profile is applied, if it returns false, it is ignored. 10 | 11 | Expressions can reference properties by accessing "p". For example, "p.pause" 12 | would return the current pause status. If the variable name contains any "_" 13 | characters, they are turned into "-". For example, "playback_time" would 14 | return the property "playback-time". (Although you can also just write 15 | p["playback-time"].) 16 | 17 | Note that if a property is not available, it will return nil, which can cause 18 | errors if used in expressions. These are printed and ignored, and the 19 | expression is considered to be false. You can also write e.g. 20 | get("playback-time", 0) instead of p.playback_time to default to 0. 21 | 22 | Whenever a property referenced by a profile condition changes, the condition 23 | is re-evaluated. If the return value of the condition changes from false or 24 | error to true, the profile is applied. 25 | 26 | Note that profiles cannot be "unapplied", so you may have to define inverse 27 | profiles with inverse conditions do undo a profile. 28 | 29 | Using profile-desc is just a hack - maybe it will be changed later. 30 | 31 | Supported --script-opts: 32 | 33 | auto-profiles: if set to "no", the script disables itself (but will still 34 | listen to property notifications etc. - if you set it to 35 | "yes" again, it will re-evaluate the current state) 36 | 37 | Example profiles: 38 | 39 | # the profile names aren't used (except for logging), but must not clash with 40 | # other profiles 41 | [test] 42 | profile-desc=cond:p.playback_time>10 43 | video-zoom=2 44 | 45 | # you would need this to actually "unapply" the "test" profile 46 | [test-revert] 47 | profile-desc=cond:p.playback_time<=10 48 | video-zoom=0 49 | 50 | --]] 51 | 52 | local utils = require 'mp.utils' 53 | local msg = require 'mp.msg' 54 | 55 | local profiles = {} 56 | local watched_properties = {} -- indexed by property name (used as a set) 57 | local cached_properties = {} -- property name -> last known raw value 58 | local properties_to_profiles = {} -- property name -> set of profiles using it 59 | local have_dirty_profiles = false -- at least one profile is marked dirty 60 | 61 | -- Used during evaluation of the profile condition, and should contain the 62 | -- profile the condition is evaluated for. 63 | local current_profile = nil 64 | 65 | local function evaluate(profile) 66 | msg.verbose("Re-evaluate auto profile " .. profile.name) 67 | 68 | current_profile = profile 69 | local status, res = pcall(profile.cond) 70 | current_profile = nil 71 | 72 | if not status then 73 | -- errors can be "normal", e.g. in case properties are unavailable 74 | msg.info("Error evaluating: " .. res) 75 | res = false 76 | elseif type(res) ~= "boolean" then 77 | msg.error("Profile '" .. profile.name .. "' did not return a boolean.") 78 | res = false 79 | end 80 | if res ~= profile.status and res == true then 81 | msg.info("Applying profile " .. profile.name) 82 | mp.commandv("apply-profile", profile.name) 83 | end 84 | profile.status = res 85 | profile.dirty = false 86 | end 87 | 88 | local function on_property_change(name, val) 89 | cached_properties[name] = val 90 | -- Mark all profiles reading this property as dirty, so they get re-evaluated 91 | -- the next time the script goes back to sleep. 92 | local dependent_profiles = properties_to_profiles[name] 93 | if dependent_profiles then 94 | for profile, _ in pairs(dependent_profiles) do 95 | assert(profile.cond) -- must be a profile table 96 | profile.dirty = true 97 | have_dirty_profiles = true 98 | end 99 | end 100 | end 101 | 102 | local function on_idle() 103 | if mp.get_opt("auto-profiles") == "no" then 104 | return 105 | end 106 | 107 | -- When events and property notifications stop, re-evaluate all dirty profiles. 108 | if have_dirty_profiles then 109 | for _, profile in ipairs(profiles) do 110 | if profile.dirty then 111 | evaluate(profile) 112 | end 113 | end 114 | end 115 | have_dirty_profiles = false 116 | end 117 | 118 | mp.register_idle(on_idle) 119 | 120 | local evil_meta_magic = { 121 | __index = function(table, key) 122 | -- interpret everything as property, unless it already exists as 123 | -- a non-nil global value 124 | local v = _G[key] 125 | if type(v) ~= "nil" then 126 | return v 127 | end 128 | -- Lua identifiers can't contain "-", so in order to match with mpv 129 | -- property conventions, replace "_" to "-" 130 | key = string.gsub(key, "_", "-") 131 | -- Normally, we use the cached value only (to reduce CPU usage I guess?) 132 | if not watched_properties[key] then 133 | watched_properties[key] = true 134 | mp.observe_property(key, "native", on_property_change) 135 | cached_properties[key] = mp.get_property_native(key) 136 | end 137 | -- The first time the property is read we need add it to the 138 | -- properties_to_profiles table, which will be used to mark the profile 139 | -- dirty if a property referenced by it changes. 140 | if current_profile then 141 | local map = properties_to_profiles[key] 142 | if not map then 143 | map = {} 144 | properties_to_profiles[key] = map 145 | end 146 | map[current_profile] = true 147 | end 148 | return cached_properties[key] 149 | end, 150 | } 151 | 152 | local evil_magic = {} 153 | setmetatable(evil_magic, evil_meta_magic) 154 | 155 | local function compile_cond(name, s) 156 | chunk, err = loadstring("return " .. s, "profile " .. name .. " condition") 157 | if not chunk then 158 | msg.error("Profile '" .. name .. "' condition: " .. err) 159 | return function() return false end 160 | end 161 | return chunk 162 | end 163 | 164 | for i, v in ipairs(mp.get_property_native("profile-list")) do 165 | local desc = v["profile-desc"] 166 | if desc and desc:sub(1, 5) == "cond:" then 167 | local profile = { 168 | name = v.name, 169 | cond = compile_cond(v.name, desc:sub(6)), 170 | properties = {}, 171 | status = nil, 172 | dirty = true, -- need re-evaluate 173 | } 174 | profiles[#profiles + 1] = profile 175 | have_dirty_profiles = true 176 | end 177 | end 178 | 179 | -- these definitions are for use by the condition expressions 180 | 181 | p = evil_magic 182 | 183 | function get(property_name, default) 184 | local val = p[property_name] 185 | if val == nil then 186 | val = default 187 | end 188 | return val 189 | end 190 | 191 | -- re-evaluate all profiles immediately 192 | on_idle() -------------------------------------------------------------------------------- /scripts/autocrop.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This script uses the lavfi cropdetect filter to automatically 3 | insert a crop filter with appropriate parameters for the 4 | currently playing video. 5 | 6 | It will automatically crop the video, when playback starts. 7 | 8 | Also It registers the key-binding "C" (shift+c). You can manually 9 | crop the video by pressing the "C" (shift+c) key. 10 | 11 | If the "C" key is pressed again, the crop filter is removed 12 | restoring playback to its original state. 13 | 14 | The workflow is as follows: First, it inserts the filter 15 | vf=lavfi=cropdetect. After (default is 1) 16 | seconds, it then inserts the filter vf=crop=w:h:x:y, where 17 | w,h,x,y are determined from the vf-metadata gathered by 18 | cropdetect. The cropdetect filter is removed immediately after 19 | the crop filter is inserted as it is no longer needed. 20 | 21 | Since the crop parameters are determined from the 1 second of 22 | video between inserting the cropdetect and crop filters, the "C" 23 | key should be pressed at a position in the video where the crop 24 | region is unambiguous (i.e., not a black frame, black background 25 | title card, or dark scene). 26 | 27 | The default options can be overridden by adding 28 | script-opts-append=autocrop-= into mpv.conf 29 | 30 | List of available parameters (For default values, see ): 31 | 32 | auto: bool - Whether to automatically apply crop at the start of 33 | playback. If you don't want to crop automatically, set it to 34 | false or add "script-opts-append=autocrop-auto=no" into 35 | mpv.conf. 36 | 37 | auto_delay: seconds - Delay before starting crop in auto mode. 38 | You can try to increase this value to avoid dark scene or 39 | fade in at beginning. Automatic cropping will not occur if 40 | the value is larger than the remaining playback time. 41 | 42 | detect_limit: number[0-255] - Black threshold for cropdetect. 43 | Smaller values will generally result in less cropping. 44 | See limit of https://ffmpeg.org/ffmpeg-filters.html#cropdetect 45 | 46 | detect_round: number[2^n] - The value which the width/height 47 | should be divisible by. Smaller values ​​have better detection 48 | accuracy. If you have problems with other filters, 49 | you can try to set it to 4 or 16. 50 | See round of https://ffmpeg.org/ffmpeg-filters.html#cropdetect 51 | 52 | detect_min_ratio: number[0.0-1.0] - The ratio of the minimum clip 53 | size to the original. If the picture is over cropped or under 54 | cropped, try adjusting this value. 55 | 56 | detect_seconds: seconds - How long to gather cropdetect data. 57 | Increasing this may be desirable to allow cropdetect more 58 | time to collect data. 59 | 60 | suppress_osd: bool - Whether the OSD shouldn't be used when filters 61 | are applied and removed. 62 | --]] 63 | 64 | require "mp.msg" 65 | require 'mp.options' 66 | 67 | local options = { 68 | auto = false, 69 | auto_delay = 4, 70 | detect_limit = "30/255", 71 | detect_round = 2, 72 | detect_min_ratio = 0.5, 73 | detect_seconds = 1, 74 | suppress_osd = false, 75 | } 76 | read_options(options) 77 | 78 | local label_prefix = mp.get_script_name() 79 | local labels = { 80 | crop = string.format("%s-crop", label_prefix), 81 | cropdetect = string.format("%s-cropdetect", label_prefix) 82 | } 83 | 84 | timers = { 85 | auto_delay = nil, 86 | detect_crop = nil 87 | } 88 | 89 | local command_prefix = options.suppress_osd and 'no-osd' or '' 90 | 91 | function is_filter_present(label) 92 | local filters = mp.get_property_native("vf") 93 | for index, filter in pairs(filters) do 94 | if filter["label"] == label then 95 | return true 96 | end 97 | end 98 | return false 99 | end 100 | 101 | function is_enough_time(seconds) 102 | 103 | -- Plus 1 second for deviation. 104 | local time_needed = seconds + 1 105 | local playtime_remaining = mp.get_property_native("playtime-remaining") 106 | 107 | return playtime_remaining and time_needed < playtime_remaining 108 | end 109 | 110 | function is_cropable() 111 | for _, track in pairs(mp.get_property_native('track-list')) do 112 | if track.type == 'video' and track.selected then 113 | return not track.albumart 114 | end 115 | end 116 | 117 | return false 118 | end 119 | 120 | function remove_filter(label) 121 | if is_filter_present(label) then 122 | mp.command(string.format('%s vf remove @%s', command_prefix, label)) 123 | return true 124 | end 125 | return false 126 | end 127 | 128 | function cleanup() 129 | 130 | -- Remove all existing filters. 131 | for key, value in pairs(labels) do 132 | remove_filter(value) 133 | end 134 | 135 | -- Kill all timers. 136 | for index, timer in pairs(timers) do 137 | if timer then 138 | timer:kill() 139 | timer = nil 140 | end 141 | end 142 | end 143 | 144 | function detect_crop() 145 | 146 | -- If it's not cropable, exit. 147 | if not is_cropable() then 148 | mp.msg.warn("autocrop only works for videos.") 149 | return 150 | end 151 | 152 | -- Verify if there is enough time to detect crop. 153 | local time_needed = options.detect_seconds 154 | 155 | if not is_enough_time(time_needed) then 156 | mp.msg.warn("Not enough time to detect crop.") 157 | return 158 | end 159 | 160 | -- Insert the cropdetect filter. 161 | local limit = options.detect_limit 162 | local round = options.detect_round 163 | 164 | mp.command( 165 | string.format( 166 | '%s vf pre @%s:cropdetect=limit=%s:round=%d:reset=0', 167 | command_prefix, labels.cropdetect, limit, round 168 | ) 169 | ) 170 | 171 | -- Wait to gather data. 172 | timers.detect_crop = mp.add_timeout(time_needed, detect_end) 173 | end 174 | 175 | function detect_end() 176 | 177 | -- Get the metadata and remove the cropdetect filter. 178 | local cropdetect_metadata = 179 | mp.get_property_native( 180 | string.format("vf-metadata/%s", 181 | labels.cropdetect 182 | ) 183 | ) 184 | remove_filter(labels.cropdetect) 185 | 186 | -- Remove the timer of detect crop. 187 | if timers.detect_crop then 188 | timers.detect_crop:kill() 189 | timers.detect_crop = nil 190 | end 191 | 192 | local meta = {} 193 | 194 | -- Verify the existence of metadata. 195 | if cropdetect_metadata then 196 | meta = { 197 | w = cropdetect_metadata["lavfi.cropdetect.w"], 198 | h = cropdetect_metadata["lavfi.cropdetect.h"], 199 | x = cropdetect_metadata["lavfi.cropdetect.x"], 200 | y = cropdetect_metadata["lavfi.cropdetect.y"], 201 | } 202 | else 203 | mp.msg.error("No crop data.") 204 | mp.msg.info("Was the cropdetect filter successfully inserted?") 205 | mp.msg.info("Does your version of ffmpeg/libav support AVFrame metadata?") 206 | return 207 | end 208 | 209 | -- Verify that the metadata meets the requirements and convert it. 210 | if meta.w and meta.h and meta.x and meta.y then 211 | local width = mp.get_property_native("width") 212 | local height = mp.get_property_native("height") 213 | 214 | meta = { 215 | w = tonumber(meta.w), 216 | h = tonumber(meta.h), 217 | x = tonumber(meta.x), 218 | y = tonumber(meta.y), 219 | min_w = width * options.detect_min_ratio, 220 | min_h = height * options.detect_min_ratio, 221 | max_w = width, 222 | max_h = height 223 | } 224 | else 225 | mp.msg.error("Got empty crop data.") 226 | mp.msg.info("You might need to increase detect_seconds.") 227 | return 228 | end 229 | 230 | apply_crop(meta) 231 | end 232 | 233 | function apply_crop(meta) 234 | 235 | -- Verify if it is necessary to crop. 236 | local is_effective = meta.x > 0 or meta.y > 0 237 | or meta.w < meta.max_w or meta.h < meta.max_h 238 | 239 | if not is_effective then 240 | mp.msg.info("No area detected for cropping.") 241 | return 242 | end 243 | 244 | -- Verify it is not over cropped. 245 | local is_excessive = meta.w < meta.min_w and meta.h < meta.min_h 246 | 247 | if is_excessive then 248 | mp.msg.info("The area to be cropped is too large.") 249 | mp.msg.info("You might need to decrease detect_min_ratio.") 250 | return 251 | end 252 | 253 | -- Remove existing crop. 254 | remove_filter(labels.crop) 255 | 256 | -- Apply crop. 257 | mp.command( 258 | string.format("%s vf pre @%s:lavfi-crop=w=%s:h=%s:x=%s:y=%s", 259 | command_prefix, labels.crop, meta.w, meta.h, meta.x, meta.y 260 | ) 261 | ) 262 | end 263 | 264 | function on_start() 265 | 266 | -- Clean up at the beginning. 267 | cleanup() 268 | 269 | -- If auto is not true, exit. 270 | if not options.auto then 271 | return 272 | end 273 | 274 | -- If it is the beginning, wait for detect_crop 275 | -- after auto_delay seconds, otherwise immediately. 276 | local playback_time = mp.get_property_native("playback-time") 277 | local is_delay_needed = playback_time 278 | and options.auto_delay > playback_time 279 | 280 | if is_delay_needed then 281 | 282 | -- Verify if there is enough time for autocrop. 283 | local time_needed = options.auto_delay + options.detect_seconds 284 | 285 | if not is_enough_time(time_needed) then 286 | mp.msg.warn("Not enough time for autocrop.") 287 | return 288 | end 289 | 290 | timers.auto_delay = mp.add_timeout(time_needed, 291 | function() 292 | detect_crop() 293 | 294 | -- Remove the timer of auto delay. 295 | timers.auto_delay:kill() 296 | timers.auto_delay = nil 297 | end 298 | ) 299 | else 300 | detect_crop() 301 | end 302 | end 303 | 304 | function on_toggle() 305 | 306 | -- If it is during auto_delay, kill the timer. 307 | if timers.auto_delay then 308 | timers.auto_delay:kill() 309 | timers.auto_delay = nil 310 | end 311 | 312 | -- Cropped => Remove it. 313 | if remove_filter(labels.crop) then 314 | return 315 | end 316 | 317 | -- Detecting => Leave it. 318 | if timers.detect_crop then 319 | mp.msg.warn("Already cropdetecting!") 320 | return 321 | end 322 | 323 | -- Neither => Do delectcrop. 324 | detect_crop() 325 | end 326 | 327 | mp.add_key_binding("C", "toggle_crop", on_toggle) 328 | mp.register_event("end-file", cleanup) 329 | mp.register_event("file-loaded", on_start) 330 | -------------------------------------------------------------------------------- /scripts/autoload.lua: -------------------------------------------------------------------------------- 1 | -- This script automatically loads playlist entries before and after the 2 | -- the currently played file. It does so by scanning the directory a file is 3 | -- located in when starting playback. It sorts the directory entries 4 | -- alphabetically, and adds entries before and after the current file to 5 | -- the internal playlist. (It stops if it would add an already existing 6 | -- playlist entry at the same position - this makes it "stable".) 7 | -- Add at most 5000 * 2 files when starting a file (before + after). 8 | 9 | --[[ 10 | To configure this script use file autoload.conf in directory script-opts (the "script-opts" 11 | directory must be in the mpv configuration directory, typically ~/.config/mpv/). 12 | 13 | Example configuration would be: 14 | 15 | disabled=no 16 | images=no 17 | videos=yes 18 | audio=yes 19 | ignore_hidden=yes 20 | 21 | --]] 22 | 23 | MAXENTRIES = 5000 24 | 25 | local msg = require 'mp.msg' 26 | local options = require 'mp.options' 27 | local utils = require 'mp.utils' 28 | 29 | o = { 30 | disabled = false, 31 | images = true, 32 | videos = true, 33 | audio = true, 34 | ignore_hidden = true 35 | } 36 | options.read_options(o) 37 | 38 | function Set (t) 39 | local set = {} 40 | for _, v in pairs(t) do set[v] = true end 41 | return set 42 | end 43 | 44 | function SetUnion (a,b) 45 | local res = {} 46 | for k in pairs(a) do res[k] = true end 47 | for k in pairs(b) do res[k] = true end 48 | return res 49 | end 50 | 51 | EXTENSIONS_VIDEO = Set { 52 | 'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp' 53 | } 54 | 55 | EXTENSIONS_AUDIO = Set { 56 | 'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus' 57 | } 58 | 59 | EXTENSIONS_IMAGES = Set { 60 | 'jpg', 'jpeg', 'png', 'tif', 'tiff', 'gif', 'webp', 'svg', 'bmp' 61 | } 62 | 63 | EXTENSIONS = Set {} 64 | if o.videos then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_VIDEO) end 65 | if o.audio then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_AUDIO) end 66 | if o.images then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_IMAGES) end 67 | 68 | function add_files_at(index, files) 69 | index = index - 1 70 | local oldcount = mp.get_property_number("playlist-count", 1) 71 | for i = 1, #files do 72 | mp.commandv("loadfile", files[i], "append") 73 | mp.commandv("playlist-move", oldcount + i - 1, index + i - 1) 74 | end 75 | end 76 | 77 | function get_extension(path) 78 | match = string.match(path, "%.([^%.]+)$" ) 79 | if match == nil then 80 | return "nomatch" 81 | else 82 | return match 83 | end 84 | end 85 | 86 | table.filter = function(t, iter) 87 | for i = #t, 1, -1 do 88 | if not iter(t[i]) then 89 | table.remove(t, i) 90 | end 91 | end 92 | end 93 | 94 | -- splitbynum and alnumcomp from alphanum.lua (C) Andre Bogus 95 | -- Released under the MIT License 96 | -- http://www.davekoelle.com/files/alphanum.lua 97 | 98 | -- split a string into a table of number and string values 99 | function splitbynum(s) 100 | local result = {} 101 | for x, y in (s or ""):gmatch("(%d*)(%D*)") do 102 | if x ~= "" then table.insert(result, tonumber(x)) end 103 | if y ~= "" then table.insert(result, y) end 104 | end 105 | return result 106 | end 107 | 108 | function clean_key(k) 109 | k = (' '..k..' '):gsub("%s+", " "):sub(2, -2):lower() 110 | return splitbynum(k) 111 | end 112 | 113 | -- compare two strings 114 | function alnumcomp(x, y) 115 | local xt, yt = clean_key(x), clean_key(y) 116 | for i = 1, math.min(#xt, #yt) do 117 | local xe, ye = xt[i], yt[i] 118 | if type(xe) == "string" then ye = tostring(ye) 119 | elseif type(ye) == "string" then xe = tostring(xe) end 120 | if xe ~= ye then return xe < ye end 121 | end 122 | return #xt < #yt 123 | end 124 | 125 | local autoloaded = nil 126 | 127 | function find_and_add_entries() 128 | local path = mp.get_property("path", "") 129 | local dir, filename = utils.split_path(path) 130 | msg.trace(("dir: %s, filename: %s"):format(dir, filename)) 131 | if o.disabled then 132 | msg.verbose("stopping: autoload disabled") 133 | return 134 | elseif #dir == 0 then 135 | msg.verbose("stopping: not a local path") 136 | return 137 | end 138 | 139 | local pl_count = mp.get_property_number("playlist-count", 1) 140 | -- check if this is a manually made playlist 141 | if (pl_count > 1 and autoloaded == nil) or 142 | (pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] == nil) then 143 | msg.verbose("stopping: manually made playlist") 144 | return 145 | else 146 | autoloaded = true 147 | end 148 | 149 | local pl = mp.get_property_native("playlist", {}) 150 | local pl_current = mp.get_property_number("playlist-pos-1", 1) 151 | msg.trace(("playlist-pos-1: %s, playlist: %s"):format(pl_current, 152 | utils.to_string(pl))) 153 | 154 | local files = utils.readdir(dir, "files") 155 | if files == nil then 156 | msg.verbose("no other files in directory") 157 | return 158 | end 159 | table.filter(files, function (v, k) 160 | -- The current file could be a hidden file, ignoring it doesn't load other 161 | -- files from the current directory. 162 | if (o.ignore_hidden and not (v == filename) and string.match(v, "^%.")) then 163 | return false 164 | end 165 | local ext = get_extension(v) 166 | if ext == nil then 167 | return false 168 | end 169 | return EXTENSIONS[string.lower(ext)] 170 | end) 171 | table.sort(files, alnumcomp) 172 | 173 | if dir == "." then 174 | dir = "" 175 | end 176 | 177 | -- Find the current pl entry (dir+"/"+filename) in the sorted dir list 178 | local current 179 | for i = 1, #files do 180 | if files[i] == filename then 181 | current = i 182 | break 183 | end 184 | end 185 | if current == nil then 186 | return 187 | end 188 | msg.trace("current file position in files: "..current) 189 | 190 | local append = {[-1] = {}, [1] = {}} 191 | for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1 192 | for i = 1, MAXENTRIES do 193 | local file = files[current + i * direction] 194 | local pl_e = pl[pl_current + i * direction] 195 | if file == nil or file[1] == "." then 196 | break 197 | end 198 | 199 | local filepath = dir .. file 200 | if pl_e then 201 | -- If there's a playlist entry, and it's the same file, stop. 202 | msg.trace(pl_e.filename.." == "..filepath.." ?") 203 | if pl_e.filename == filepath then 204 | break 205 | end 206 | end 207 | 208 | if direction == -1 then 209 | if pl_current == 1 then -- never add additional entries in the middle 210 | msg.info("Prepending " .. file) 211 | table.insert(append[-1], 1, filepath) 212 | end 213 | else 214 | msg.info("Adding " .. file) 215 | table.insert(append[1], filepath) 216 | end 217 | end 218 | end 219 | 220 | add_files_at(pl_current + 1, append[1]) 221 | add_files_at(pl_current, append[-1]) 222 | end 223 | 224 | mp.register_event("start-file", find_and_add_entries) 225 | -------------------------------------------------------------------------------- /scripts/cycle-commands.lua: -------------------------------------------------------------------------------- 1 | --[=====[ 2 | script to cycle commands with a keybind, accomplished through script messages 3 | available at: https://github.com/CogentRedTester/mpv-scripts 4 | 5 | syntax: 6 | script-message cycle-commands "command1" "command2" "command3" 7 | 8 | The syntax of each command is identical to the standard input.conf syntax, but each command must be within 9 | a pair of double quotes. 10 | 11 | Commands with mutiword arguments require you to send double quotes just like normal command syntax, however, 12 | you will need to escape the quotes with a backslash so that they are sent as part of the string. 13 | Semicolons also work exactly like they do normally, so you can easily send multiple commands each cycle. 14 | 15 | Here is an example of a standard input.conf entry: 16 | 17 | script-message cycle-commands "show-text one 1000 ; print-text two" "show-text \"three four\"" 18 | 19 | This would, on keypress one, print 'one' to the OSD for 1 second and 'two' to the console, 20 | and on keypress two 'three four' would be printed to the OSD. 21 | Notice how the quotation marks around 'three four' are escaped using backslashes. 22 | All other syntax details should be exactly the same as usual input commands. 23 | 24 | There are no limits to the number of commands, and the script message can be used as often as one wants, 25 | the script stores the current iteration for each unique cycle command, so there should be no overlap 26 | unless one binds the exact same command string (including spacing) 27 | ]=====]-- 28 | 29 | local mp = require 'mp' 30 | local msg = require 'mp.msg' 31 | 32 | --keeps track of the current position for a specific cycle 33 | local iterators = {} 34 | 35 | --main function to identify and run the cycles 36 | local function main(...) 37 | local commands = {...} 38 | 39 | --to identify the specific cycle we'll concatenate all the strings together to use as our table key 40 | local str = table.concat(commands, " | ") 41 | msg.trace('recieved:', str) 42 | 43 | if iterators[str] == nil then 44 | msg.debug('unknown cycle, creating iterator') 45 | iterators[str] = 1 46 | else 47 | iterators[str] = iterators[str] + 1 48 | if iterators[str] > #commands then iterators[str] = 1 end 49 | end 50 | 51 | --mp.command should run the commands exactly as if they were entered in input.conf. 52 | --This should provide universal support for all input.conf command syntax 53 | local cmd = commands[ iterators[str] ] 54 | msg.verbose('sending command:', cmd) 55 | mp.command(cmd) 56 | end 57 | 58 | mp.register_script_message('cycle-commands', main) 59 | -------------------------------------------------------------------------------- /scripts/detect-image.lua: -------------------------------------------------------------------------------- 1 | local opts = { 2 | command_on_first_image_loaded="", 3 | command_on_image_loaded="", 4 | command_on_non_image_loaded="", 5 | } 6 | local options = require 'mp.options' 7 | local msg = require 'mp.msg' 8 | 9 | options.read_options(opts, nil, function() end) 10 | 11 | function run_maybe(str) 12 | if str ~= "" then 13 | mp.command(str) 14 | end 15 | end 16 | 17 | local was_image = false 18 | 19 | function set_image(is_image) 20 | if is_image and not was_image then 21 | msg.info("First image detected") 22 | run_maybe(opts.command_on_first_image_loaded) 23 | end 24 | if is_image then 25 | msg.info("Image detected") 26 | run_maybe(opts.command_on_image_loaded) 27 | end 28 | if not is_image and was_image then 29 | msg.info("Non-image detected") 30 | run_maybe(opts.command_on_non_image_loaded) 31 | end 32 | was_image = is_image 33 | end 34 | 35 | local properties = {} 36 | 37 | function properties_changed() 38 | local dwidth = properties["dwidth"] 39 | local tracks = properties["track-list"] 40 | local path = properties["path"] 41 | local framecount = properties["estimated-frame-count"] 42 | 43 | if not path or path == "" then return end 44 | if not tracks or #tracks == 0 then return end 45 | local audio_tracks = 0 46 | for _, track in ipairs(tracks) do 47 | if track.type == "audio" then 48 | audio_tracks = audio_tracks + 1 49 | end 50 | end 51 | 52 | -- only do things when state is consistent 53 | if not framecount and audio_tracks > 0 then 54 | set_image(false) 55 | elseif framecount and dwidth and dwidth > 0 then 56 | -- png have 0 frames, jpg 1 ¯\_(ツ)_/¯ 57 | set_image((framecount == 0 or framecount == 1) and audio_tracks == 0) 58 | end 59 | end 60 | 61 | function observe(propname) 62 | mp.observe_property(propname, "native", function(_, val) 63 | if val ~= properties[propname] then 64 | properties[propname] = val 65 | msg.verbose("Property " .. propname .. " changed") 66 | properties_changed() 67 | end 68 | end) 69 | end 70 | observe("estimated-frame-count") 71 | observe("track-list") 72 | observe("dwidth") 73 | observe("path") 74 | -------------------------------------------------------------------------------- /scripts/equalizer.lua: -------------------------------------------------------------------------------- 1 | local opts = { 2 | bars = 'brightness,contrast,gamma,saturation,hue', 3 | draw_icons = true, 4 | } 5 | 6 | local msg = require 'mp.msg' 7 | local assdraw = require 'mp.assdraw' 8 | local options = require 'mp.options' 9 | local utils = require 'mp.utils' 10 | 11 | options.read_options(opts, nil, function(c) 12 | end) 13 | 14 | local enabled = false 15 | local active_bars = {} 16 | local bar_being_dragged = nil 17 | local stale = false 18 | 19 | function split_comma(input) 20 | local ret = {} 21 | for str in string.gmatch(input, "([^,]+)") do 22 | ret[#ret + 1] = str 23 | end 24 | return ret 25 | end 26 | 27 | function get_position_normalized(x, y, bar) 28 | return (x - bar.x) / bar.w, (y - bar.y) / bar.h 29 | end 30 | 31 | function handle_mouse_move() 32 | if not bar_being_dragged then return end 33 | local bar = bar_being_dragged 34 | local mx, my = mp.get_mouse_pos() 35 | local nx, _ = get_position_normalized(mx, my, bar) 36 | nx = math.max(0, math.min(nx, 1)) 37 | local val = math.floor(nx * (bar.max_value - bar.min_value) + bar.min_value + 0.5) 38 | mp.set_property_number(bar.property, val) 39 | -- the observe_property call will take care of setting the value 40 | end 41 | 42 | function handle_mouse_left(table) 43 | if table["event"] == "down" then 44 | local mx, my = mp.get_mouse_pos() 45 | for _, bar in ipairs(active_bars) do 46 | local nx, ny = get_position_normalized(mx, my, bar) 47 | if nx >= 0 and ny >= 0 and nx <= 1 and ny <= 1 then 48 | bar_being_dragged = bar 49 | local val = math.floor(nx * (bar.max_value - bar.min_value) + bar.min_value + 0.5) 50 | mp.set_property_number(bar.property, val) 51 | mp.add_forced_key_binding("mouse_move", "mouse_move", handle_mouse_move) 52 | break 53 | end 54 | end 55 | elseif table["event"] == "up" then 56 | mp.remove_key_binding("mouse_move") 57 | bar_being_dragged = nil 58 | end 59 | end 60 | 61 | function property_changed(prop, val) 62 | for _, bar in ipairs(active_bars) do 63 | if bar.property == prop then 64 | bar.value = val 65 | stale = true 66 | break 67 | end 68 | end 69 | end 70 | 71 | function idle_handler() 72 | if not stale then return end 73 | stale = false 74 | local a = assdraw.ass_new() 75 | a:new_event() 76 | a:append(string.format('{\\an0\\bord2\\shad0\\1a&00&\\1c&%s&}', '888888')) 77 | a:pos(0, 0) 78 | a:draw_start() 79 | for _, bar in ipairs(active_bars) do 80 | a:rect_cw(bar.x, bar.y, bar.x + bar.w, bar.y + bar.h) 81 | end 82 | a:new_event() 83 | a:append(string.format('{\\an0\\bord2\\shad0\\1a&00&\\1c&%s&}', 'dddddd')) 84 | a:pos(0, 0) 85 | a:draw_start() 86 | for _, bar in ipairs(active_bars) do 87 | if bar.value > bar.min_value then 88 | local val_norm = (bar.value - bar.min_value) / (bar.max_value - bar.min_value) 89 | a:rect_cw(bar.x, bar.y, bar.x + val_norm * bar.w, bar.y + bar.h) 90 | end 91 | end 92 | for _, bar in ipairs(active_bars) do 93 | a:new_event() 94 | a:append("{\\an6\\fs40\\bord2}") 95 | a:pos(bar.x - 8, bar.y + bar.h/2 - 2) 96 | a:append(bar.property:sub(1,1):upper() .. bar.property:sub(2,-1)) 97 | end 98 | local ww, wh = mp.get_osd_size() 99 | mp.set_osd_ass(ww, wh, a.text) 100 | end 101 | 102 | function fix_position() 103 | local ww, wh = mp.get_osd_size() 104 | for i, bar in ipairs(active_bars) do 105 | bar.x = ww / 5 106 | bar.y = wh / 2 + i * 50 107 | bar.w = ww - 2 * (ww / 5) 108 | bar.h = 30 109 | end 110 | end 111 | 112 | function dimensions_changed() 113 | stale = true 114 | fix_position() 115 | end 116 | 117 | function enable() 118 | if enabled then return end 119 | enabled = true 120 | mp.add_forced_key_binding("MBTN_LEFT", "mouse_left", handle_mouse_left, {complex=true}) 121 | for i, prop in ipairs(split_comma(opts.bars)) do 122 | local prop_info = mp.get_property_native("option-info/" .. prop) 123 | if not prop_info then 124 | msg.warn("Property \'" .. prop .. "\' does not exist") 125 | elseif not prop_info.type == 'Integer' then 126 | msg.warn("Property \'" .. prop .. "\' is not an integer") 127 | else 128 | mp.observe_property(prop, 'native', property_changed) 129 | active_bars[#active_bars + 1] = { 130 | property = prop, 131 | value = mp.get_property_number(prop), 132 | min_value = prop_info.min, 133 | max_value = prop_info.max, 134 | } 135 | end 136 | end 137 | stale = true 138 | fix_position() 139 | mp.observe_property("osd-dimensions", "native", dimensions_changed) 140 | mp.register_idle(idle_handler) 141 | end 142 | 143 | function disable() 144 | if not enabled then return end 145 | enabled = false 146 | active_bars = {} 147 | bar_being_dragged = nil 148 | mp.remove_key_binding("mouse_left") 149 | mp.remove_key_binding("mouse_move") 150 | mp.unobserve_property(property_changed) 151 | mp.unobserve_property(dimensions_changed) 152 | mp.unregister_idle(idle_handler) 153 | mp.set_osd_ass(1280, 720, "") 154 | end 155 | 156 | function toggle() 157 | if enabled then 158 | disable() 159 | else 160 | enable() 161 | end 162 | end 163 | 164 | function reset() 165 | for _, prop in ipairs(split_comma(opts.bars)) do 166 | local prop_info = mp.get_property_native("option-info/" .. prop) 167 | if prop_info and prop_info["default-value"] then 168 | mp.set_property(prop_info["name"], prop_info["default-value"]) 169 | end 170 | end 171 | end 172 | 173 | mp.add_key_binding(nil, "equalizer-enable", enable) 174 | mp.add_key_binding(nil, "equalizer-disable", disable) 175 | mp.add_key_binding(nil, "equalizer-toggle", toggle) 176 | mp.add_key_binding(nil, "equalizer-reset", reset) 177 | -------------------------------------------------------------------------------- /scripts/freeze-window.lua: -------------------------------------------------------------------------------- 1 | -- credit to TheAMM 2 | 3 | local size_changed = false 4 | 5 | mp.register_idle(function() 6 | if not size_changed then return end 7 | local ww, wh = mp.get_osd_size() 8 | if not ww or ww <= 0 or not wh or wh <= 0 then return end 9 | mp.set_property("geometry", string.format("%dx%d", ww, wh)) 10 | size_changed = false 11 | end) 12 | 13 | mp.observe_property("osd-width", "native", function() size_changed = true end) 14 | mp.observe_property("osd-height", "native", function() size_changed = true end) 15 | -------------------------------------------------------------------------------- /scripts/image-positioning.lua: -------------------------------------------------------------------------------- 1 | local opts = { 2 | drag_to_pan_margin = 50, 3 | drag_to_pan_move_if_full_view=false, 4 | 5 | pan_follows_cursor_margin = 50, 6 | 7 | cursor_centric_zoom_margin = 50, 8 | cursor_centric_zoom_auto_center = true, 9 | cursor_centric_zoom_dezoom_if_full_view = false, 10 | } 11 | local options = require 'mp.options' 12 | local msg = require 'mp.msg' 13 | local assdraw = require 'mp.assdraw' 14 | 15 | options.read_options(opts, nil, function() end) 16 | 17 | function clamp(value, low, high) 18 | if value <= low then 19 | return low 20 | elseif value >= high then 21 | return high 22 | else 23 | return value 24 | end 25 | end 26 | 27 | 28 | local cleanup = nil -- function set up by drag-to-pan/pan-follows cursor and must be called to clean lingering state 29 | 30 | function drag_to_pan_handler(table) 31 | if cleanup then 32 | cleanup() 33 | cleanup = nil 34 | end 35 | if table["event"] == "down" then 36 | local dim = mp.get_property_native("osd-dimensions") 37 | if not dim then return end 38 | local mouse_pos_origin, video_pan_origin = {}, {} 39 | local moved = false 40 | mouse_pos_origin[1], mouse_pos_origin[2] = mp.get_mouse_pos() 41 | video_pan_origin[1] = mp.get_property_number("video-pan-x") 42 | video_pan_origin[2] = mp.get_property_number("video-pan-y") 43 | local video_size = { dim.w - dim.ml - dim.mr, dim.h - dim.mt - dim.mb } 44 | local margin = opts.drag_to_pan_margin 45 | local move_up = true 46 | local move_lateral = true 47 | if not opts.drag_to_pan_move_if_full_view then 48 | if dim.ml >= 0 and dim.mr >= 0 then 49 | move_lateral = false 50 | end 51 | if dim.mt >= 0 and dim.mb >= 0 then 52 | move_up = false 53 | end 54 | end 55 | if not move_up and not move_lateral then return end 56 | local idle = function() 57 | if moved then 58 | local mX, mY = mp.get_mouse_pos() 59 | local pX = video_pan_origin[1] 60 | local pY = video_pan_origin[2] 61 | if move_lateral then 62 | pX = video_pan_origin[1] + (mX - mouse_pos_origin[1]) / video_size[1] 63 | if 2 * margin > dim.ml + dim.mr then 64 | pX = clamp(pX, 65 | (-margin + dim.w / 2) / video_size[1] - 0.5, 66 | (margin - dim.w / 2) / video_size[1] + 0.5) 67 | else 68 | pX = clamp(pX, 69 | (margin - dim.w / 2) / video_size[1] + 0.5, 70 | (-margin + dim.w / 2) / video_size[1] - 0.5) 71 | end 72 | end 73 | if move_up then 74 | pY = video_pan_origin[2] + (mY - mouse_pos_origin[2]) / video_size[2] 75 | if 2 * margin > dim.mt + dim.mb then 76 | pY = clamp(pY, 77 | (-margin + dim.h / 2) / video_size[2] - 0.5, 78 | (margin - dim.h / 2) / video_size[2] + 0.5) 79 | else 80 | pY = clamp(pY, 81 | (margin - dim.h / 2) / video_size[2] + 0.5, 82 | (-margin + dim.h / 2) / video_size[2] - 0.5) 83 | end 84 | end 85 | mp.command("no-osd set video-pan-x " .. clamp(pX, -3, 3) .. "; no-osd set video-pan-y " .. clamp(pY, -3, 3)) 86 | moved = false 87 | end 88 | end 89 | mp.register_idle(idle) 90 | mp.add_forced_key_binding("mouse_move", "image-viewer-mouse-move", function() moved = true end) 91 | cleanup = function() 92 | mp.remove_key_binding("image-viewer-mouse-move") 93 | mp.unregister_idle(idle) 94 | end 95 | end 96 | end 97 | 98 | function pan_follows_cursor_handler(table) 99 | if cleanup then 100 | cleanup() 101 | cleanup = nil 102 | end 103 | if table["event"] == "down" then 104 | local dim = mp.get_property_native("osd-dimensions") 105 | if not dim then return end 106 | local video_size = { dim.w - dim.ml - dim.mr, dim.h - dim.mt - dim.mb } 107 | local moved = true 108 | local idle = function() 109 | if moved then 110 | local mX, mY = mp.get_mouse_pos() 111 | local x = math.min(1, math.max(- 2 * mX / dim.w + 1, -1)) 112 | local y = math.min(1, math.max(- 2 * mY / dim.h + 1, -1)) 113 | local command = "" 114 | local margin = opts.pan_follows_cursor_margin 115 | if dim.ml + dim.mr < 0 then 116 | command = command .. "no-osd set video-pan-x " .. clamp(x * (2 * margin - dim.ml - dim.mr) / (2 * video_size[1]), -3, 3) .. ";" 117 | elseif mp.get_property_number("video-pan-x") ~= 0 then 118 | command = command .. "no-osd set video-pan-x " .. "0;" 119 | end 120 | if dim.mt + dim.mb < 0 then 121 | command = command .. "no-osd set video-pan-y " .. clamp(y * (2 * margin - dim.mt - dim.mb) / (2 * video_size[2]), -3, 3) .. ";" 122 | elseif mp.get_property_number("video-pan-y") ~= 0 then 123 | command = command .. "no-osd set video-pan-y " .. "0;" 124 | end 125 | if command ~= "" then 126 | mp.command(command) 127 | end 128 | moved = false 129 | end 130 | end 131 | mp.register_idle(idle) 132 | mp.add_forced_key_binding("mouse_move", "image-viewer-mouse-move", function() moved = true end) 133 | cleanup = function() 134 | mp.remove_key_binding("image-viewer-mouse-move") 135 | mp.unregister_idle(idle) 136 | end 137 | end 138 | end 139 | 140 | function cursor_centric_zoom_handler(amt) 141 | local zoom_inc = tonumber(amt) 142 | if not zoom_inc or zoom_inc == 0 then return end 143 | local dim = mp.get_property_native("osd-dimensions") 144 | if not dim then return end 145 | 146 | local margin = opts.cursor_centric_zoom_margin 147 | 148 | local video_size = { dim.w - dim.ml - dim.mr, dim.h - dim.mt - dim.mb } 149 | 150 | -- the size in pixels of the (in|de)crement 151 | local diff_width = (2 ^ zoom_inc - 1) * video_size[1] 152 | local diff_height = (2 ^ zoom_inc - 1) * video_size[2] 153 | if not opts.cursor_centric_zoom_dezoom_if_full_view and 154 | zoom_inc < 0 and 155 | video_size[1] + diff_width + 2 * margin <= dim.w and 156 | video_size[2] + diff_height + 2 * margin <= dim.h 157 | then 158 | -- the zoom decrement is too much, reduce it such that the full image is visible, no more, no less 159 | -- in addition, this should take care of trying too zoom out while everything is already visible 160 | local new_zoom_inc_x = math.log((dim.w - 2 * margin) / video_size[1]) / math.log(2) 161 | local new_zoom_inc_y = math.log((dim.h - 2 * margin) / video_size[2]) / math.log(2) 162 | local new_zoom_inc = math.min(0, math.min(new_zoom_inc_x, new_zoom_inc_y)) 163 | zoom_inc = new_zoom_inc 164 | diff_width = (2 ^ zoom_inc - 1) * video_size[1] 165 | diff_height = (2 ^ zoom_inc - 1) * video_size[2] 166 | end 167 | local new_width = video_size[1] + diff_width 168 | local new_height = video_size[2] + diff_height 169 | 170 | local mouse_pos_origin = {} 171 | mouse_pos_origin[1], mouse_pos_origin[2] = mp.get_mouse_pos() 172 | local new_pan_x, new_pan_y 173 | 174 | -- some additional constraints: 175 | -- if image can be fully visible (in either direction), set pan to 0 176 | -- if border would show on either side, then prefer adjusting the pan even if not cursor-centric 177 | local auto_c = opts.cursor_centric_zoom_auto_center 178 | if auto_c and video_size[1] + diff_width + 2 * margin <= dim.w then 179 | new_pan_x = 0 180 | else 181 | local pan_x = mp.get_property("video-pan-x") 182 | local rx = (dim.ml + video_size[1] / 2 - mouse_pos_origin[1]) / (video_size[1] / 2) 183 | new_pan_x = (pan_x * video_size[1] + rx * diff_width / 2) / new_width 184 | if auto_c then 185 | new_pan_x = clamp(new_pan_x, (dim.w - 2 * margin) / (2 * new_width) - 0.5, - (dim.w - 2 * margin) / (2 * new_width) + 0.5) 186 | end 187 | end 188 | 189 | if auto_c and video_size[2] + diff_height + 2 * margin <= dim.h then 190 | new_pan_y = 0 191 | else 192 | local pan_y = mp.get_property("video-pan-y") 193 | local ry = (dim.mt + video_size[2] / 2 - mouse_pos_origin[2]) / (video_size[2] / 2) 194 | new_pan_y = (pan_y * video_size[2] + ry * diff_height / 2) / new_height 195 | if auto_c then 196 | new_pan_y = clamp(new_pan_y, (dim.h - 2 * margin) / (2 * new_height) - 0.5, - (dim.h - 2 * margin) / (2 * new_height) + 0.5) 197 | end 198 | end 199 | 200 | local zoom_origin = mp.get_property("video-zoom") 201 | mp.command("no-osd set video-zoom " .. zoom_origin + zoom_inc .. "; no-osd set video-pan-x " .. clamp(new_pan_x, -3, 3) .. "; no-osd set video-pan-y " .. clamp(new_pan_y, -3, 3)) 202 | end 203 | 204 | function align_border(x, y) 205 | local dim = mp.get_property_native("osd-dimensions") 206 | if not dim then return end 207 | local video_size = { dim.w - dim.ml - dim.mr, dim.h - dim.mt - dim.mb } 208 | local x, y = tonumber(x), tonumber(y) 209 | local command = "" 210 | if x then 211 | command = command .. "no-osd set video-pan-x " .. clamp(- x * (dim.ml + dim.mr) / (2 * video_size[1]), -3, 3) .. ";" 212 | end 213 | if y then 214 | command = command .. "no-osd set video-pan-y " .. clamp(- y * (dim.mt + dim.mb) / (2 * video_size[2]), -3, 3) .. ";" 215 | end 216 | if command ~= "" then 217 | mp.command(command) 218 | end 219 | end 220 | 221 | function pan_image(axis, amount, zoom_invariant, image_constrained) 222 | amount = tonumber(amount) 223 | if not amount or amount == 0 or axis ~= "x" and axis ~= "y" then return end 224 | if zoom_invariant == "yes" then 225 | amount = amount / 2 ^ mp.get_property_number("video-zoom") 226 | end 227 | local prop = "video-pan-" .. axis 228 | local old_pan = mp.get_property_number(prop) 229 | if image_constrained == "yes" then 230 | local dim = mp.get_property_native("osd-dimensions") 231 | if not dim then return end 232 | local margin = 233 | (axis == "x" and amount > 0) and dim.ml 234 | or (axis == "x" and amount < 0) and dim.mr 235 | or (amount > 0) and dim.mt 236 | or (amount < 0) and dim.mb 237 | local vid_size = (axis == "x") and (dim.w - dim.ml - dim.mr) or (dim.h - dim.mt - dim.mb) 238 | local pixels_moved = math.abs(amount) * vid_size 239 | -- the margin is already visible, no point going further 240 | if margin >= 0 then 241 | return 242 | elseif margin + pixels_moved > 0 then 243 | amount = -(math.abs(amount) / amount) * margin / vid_size 244 | end 245 | end 246 | mp.set_property_number(prop, old_pan + amount) 247 | end 248 | 249 | function rotate_video(amt) 250 | local rot = mp.get_property_number("video-rotate") 251 | rot = (rot + amt) % 360 252 | mp.set_property_number("video-rotate", rot) 253 | end 254 | 255 | function reset_pan_if_visible() 256 | local dim = mp.get_property_native("osd-dimensions") 257 | if not dim then return end 258 | local command = "" 259 | if (dim.ml + dim.mr >= 0) then 260 | command = command .. "no-osd set video-pan-x 0" .. ";" 261 | end 262 | if (dim.mt + dim.mb >= 0) then 263 | command = command .. "no-osd set video-pan-y 0" .. ";" 264 | end 265 | if command ~= "" then 266 | mp.command(command) 267 | end 268 | end 269 | 270 | mp.add_key_binding(nil, "drag-to-pan", drag_to_pan_handler, {complex = true}) 271 | mp.add_key_binding(nil, "pan-follows-cursor", pan_follows_cursor_handler, {complex = true}) 272 | mp.add_key_binding(nil, "cursor-centric-zoom", cursor_centric_zoom_handler) 273 | mp.add_key_binding(nil, "align-border", align_border) 274 | mp.add_key_binding(nil, "pan-image", pan_image) 275 | mp.add_key_binding(nil, "rotate-video", rotate_video) 276 | mp.add_key_binding(nil, "reset-pan-if-visible", reset_pan_if_visible) 277 | mp.add_key_binding(nil, "force-print-filename", force_print_filename) 278 | -------------------------------------------------------------------------------- /scripts/minimap.lua: -------------------------------------------------------------------------------- 1 | local opts = { 2 | enabled = true, 3 | center = "92,92", 4 | scale = 12, 5 | max_size = "16,16", 6 | image_opacity = "88", 7 | image_color = "BBBBBB", 8 | view_opacity = "BB", 9 | view_color = "222222", 10 | view_above_image = true, 11 | hide_when_full_image_in_view = true, 12 | } 13 | 14 | local msg = require 'mp.msg' 15 | local assdraw = require 'mp.assdraw' 16 | local options = require 'mp.options' 17 | 18 | options.read_options(opts, nil, function(c) 19 | if c["enabled"] then 20 | if opts.enabled then 21 | enable() 22 | else 23 | disable() 24 | end 25 | end 26 | mark_stale() 27 | end) 28 | 29 | function split_comma(input) 30 | local ret = {} 31 | for str in string.gmatch(input, "([^,]+)") do 32 | ret[#ret + 1] = tonumber(str) 33 | end 34 | return ret 35 | end 36 | 37 | local active = false 38 | local refresh = true 39 | 40 | function draw_ass(ass) 41 | local ww, wh = mp.get_osd_size() 42 | mp.set_osd_ass(ww, wh, ass) 43 | end 44 | 45 | function mark_stale() 46 | refresh = true 47 | end 48 | 49 | function refresh_minimap() 50 | if not refresh then return end 51 | refresh = false 52 | 53 | local dim = mp.get_property_native("osd-dimensions") 54 | if not dim then 55 | draw_ass("") 56 | return 57 | end 58 | local ww, wh = dim.w, dim.h 59 | 60 | if not (ww > 0 and wh > 0) then return end 61 | if opts.hide_when_full_image_in_view then 62 | if dim.mt >= 0 and dim.mb >= 0 and dim.ml >= 0 and dim.mr >= 0 then 63 | draw_ass("") 64 | return 65 | end 66 | end 67 | 68 | local center = split_comma(opts.center) 69 | center[1] = center[1] * 0.01 * ww 70 | center[2] = center[2] * 0.01 * wh 71 | local cutoff = split_comma(opts.max_size) 72 | cutoff[1] = cutoff[1] * 0.01 * ww * 0.5 73 | cutoff[2] = cutoff[2] * 0.01 * wh * 0.5 74 | 75 | local a = assdraw.ass_new() 76 | local draw = function(x, y, w, h, opacity, color) 77 | a:new_event() 78 | a:pos(center[1], center[2]) 79 | a:append("{\\bord0}") 80 | a:append("{\\shad0}") 81 | a:append("{\\c&" .. color .. "&}") 82 | a:append("{\\2a&HFF}") 83 | a:append("{\\3a&HFF}") 84 | a:append("{\\4a&HFF}") 85 | a:append("{\\1a&H" .. opacity .. "}") 86 | w = w * 0.5 87 | h = h * 0.5 88 | a:draw_start() 89 | local rounded = {true,true,true,true} -- tl, tr, br, bl 90 | local x0,y0,x1,y1 = x-w, y-h, x+w, y+h 91 | if x0 < -cutoff[1] then 92 | x0 = -cutoff[1] 93 | rounded[4] = false 94 | rounded[1] = false 95 | end 96 | if y0 < -cutoff[2] then 97 | y0 = -cutoff[2] 98 | rounded[1] = false 99 | rounded[2] = false 100 | end 101 | if x1 > cutoff[1] then 102 | x1 = cutoff[1] 103 | rounded[2] = false 104 | rounded[3] = false 105 | end 106 | if y1 > cutoff[2] then 107 | y1 = cutoff[2] 108 | rounded[3] = false 109 | rounded[4] = false 110 | end 111 | 112 | local r = 3 113 | local c = 0.551915024494 * r 114 | if rounded[0] then 115 | a:move_to(x0 + r, y0) 116 | else 117 | a:move_to(x0,y0) 118 | end 119 | if rounded[1] then 120 | a:line_to(x1 - r, y0) 121 | a:bezier_curve(x1 - r + c, y0, x1, y0 + r - c, x1, y0 + r) 122 | else 123 | a:line_to(x1, y0) 124 | end 125 | if rounded[2] then 126 | a:line_to(x1, y1 - r) 127 | a:bezier_curve(x1, y1 - r + c, x1 - r + c, y1, x1 - r, y1) 128 | else 129 | a:line_to(x1, y1) 130 | end 131 | if rounded[3] then 132 | a:line_to(x0 + r, y1) 133 | a:bezier_curve(x0 + r - c, y1, x0, y1 - r + c, x0, y1 - r) 134 | else 135 | a:line_to(x0, y1) 136 | end 137 | if rounded[4] then 138 | a:line_to(x0, y0 + r) 139 | a:bezier_curve(x0, y0 + r - c, x0 + r - c, y0, x0 + r, y0) 140 | else 141 | a:line_to(x0, y0) 142 | end 143 | a:draw_stop() 144 | end 145 | local image = function() 146 | draw((dim.ml/2 - dim.mr/2) / opts.scale, 147 | (dim.mt/2 - dim.mb/2) / opts.scale, 148 | (ww - dim.ml - dim.mr) / opts.scale, 149 | (wh - dim.mt - dim.mb) / opts.scale, 150 | opts.image_opacity, 151 | opts.image_color) 152 | end 153 | local view = function() 154 | draw(0, 155 | 0, 156 | ww / opts.scale, 157 | wh / opts.scale, 158 | opts.view_opacity, 159 | opts.view_color) 160 | end 161 | if opts.view_above_image then 162 | image() 163 | view() 164 | else 165 | view() 166 | image() 167 | end 168 | draw_ass(a.text) 169 | end 170 | 171 | function enable() 172 | if active then return end 173 | active = true 174 | mp.observe_property("osd-dimensions", nil, mark_stale) 175 | mp.register_idle(refresh_minimap) 176 | mark_stale() 177 | end 178 | 179 | function disable() 180 | if not active then return end 181 | active = false 182 | mp.unobserve_property(mark_stale) 183 | mp.unregister_idle(refresh_minimap) 184 | draw_ass("") 185 | end 186 | 187 | function toggle() 188 | if active then 189 | disable() 190 | else 191 | enable() 192 | end 193 | end 194 | 195 | if opts.enabled then 196 | enable() 197 | end 198 | 199 | mp.add_key_binding(nil, "minimap-enable", enable) 200 | mp.add_key_binding(nil, "minimap-disable", disable) 201 | mp.add_key_binding(nil, "minimap-toggle", toggle) 202 | -------------------------------------------------------------------------------- /scripts/ruler.lua: -------------------------------------------------------------------------------- 1 | local opts = { 2 | show_distance = true, 3 | show_coordinates = true, 4 | coordinates_space = "image", 5 | show_angles = "degrees", 6 | line_width = 2, 7 | dots_radius = 3, 8 | font_size = 36, 9 | line_color = "33", 10 | confirm_bindings = "MBTN_LEFT,ENTER", 11 | exit_bindings = "ESC", 12 | set_first_point_on_begin = false, 13 | clear_on_second_point_set = false, 14 | } 15 | 16 | local options = require 'mp.options' 17 | local msg = require 'mp.msg' 18 | local assdraw = require 'mp.assdraw' 19 | 20 | local state = 0 -- {0,1,2,3} = {inactive,setting first point,setting second point,done} 21 | local first_point = nil -- in normalized video space coordinates 22 | local second_point = nil -- in normalized video space coordinates 23 | local video_dimensions_stale = false 24 | 25 | function split(input) 26 | local ret = {} 27 | for str in string.gmatch(input, "([^,]+)") do 28 | ret[#ret + 1] = str 29 | end 30 | return ret 31 | end 32 | 33 | local confirm_bindings = split(opts.confirm_bindings) 34 | local exit_bindings = split(opts.exit_bindings) 35 | 36 | options.read_options(opts, nil, function() 37 | if state ~= 0 then 38 | remove_bindings() 39 | end 40 | confirm_bindings = split(opts.confirm_bindings) 41 | exit_bindings = split(opts.exit_bindings) 42 | if state ~= 0 then 43 | add_bindings() 44 | mark_stale() 45 | end 46 | end) 47 | 48 | function draw_ass(ass) 49 | local ww, wh = mp.get_osd_size() 50 | mp.set_osd_ass(ww, wh, ass) 51 | end 52 | 53 | 54 | function cursor_video_space_normalized(dim) 55 | local mx, my = mp.get_mouse_pos() 56 | local ret = {} 57 | ret[1] = (mx - dim.ml) / (dim.w - dim.ml - dim.mr) 58 | ret[2] = (my - dim.mt) / (dim.h - dim.mt - dim.mb) 59 | return ret 60 | end 61 | 62 | function refresh() 63 | if not video_dimensions_stale then return end 64 | video_dimensions_stale = false 65 | 66 | local dim = mp.get_property_native("osd-dimensions") 67 | local out_params = mp.get_property_native("video-out-params") 68 | if not dim or not out_params then 69 | draw_ass("") 70 | return 71 | end 72 | local vid_width = out_params.dw 73 | local vid_height = out_params.dh 74 | 75 | function video_space_normalized_to_video(point) 76 | local ret = {} 77 | ret[1] = point[1] * vid_width 78 | ret[2] = point[2] * vid_height 79 | return ret 80 | end 81 | function video_space_normalized_to_screen(point) 82 | local ret = {} 83 | ret[1] = point[1] * (dim.w - dim.ml - dim.mr) + dim.ml 84 | ret[2] = point[2] * (dim.h - dim.mt - dim.mb) + dim.mt 85 | return ret 86 | end 87 | 88 | local line_start = {} 89 | local line_end = {} 90 | if second_point then 91 | line_start.image = video_space_normalized_to_video(first_point) 92 | line_start.screen = video_space_normalized_to_screen(first_point) 93 | line_end.image = video_space_normalized_to_video(second_point) 94 | line_end.screen = video_space_normalized_to_screen(second_point) 95 | elseif first_point then 96 | line_start.image = video_space_normalized_to_video(first_point) 97 | line_start.screen = video_space_normalized_to_screen(first_point) 98 | line_end.image = video_space_normalized_to_video(cursor_video_space_normalized(dim)) 99 | line_end.screen = {} 100 | line_end.screen[1], line_end.screen[2] = mp.get_mouse_pos() 101 | else 102 | local mx, my = mp.get_mouse_pos() 103 | line_start.image = video_space_normalized_to_video(cursor_video_space_normalized(dim)) 104 | line_start.screen = {} 105 | line_start.screen[1], line_start.screen[2] = mp.get_mouse_pos() 106 | line_end = line_start 107 | end 108 | local distinct = (math.abs(line_start.screen[1] - line_end.screen[1]) >= 1 109 | or math.abs(line_start.screen[2] - line_end.screen[2]) >= 1) 110 | 111 | local a = assdraw:ass_new() 112 | local draw_setup = function(bord) 113 | a:new_event() 114 | a:pos(0,0) 115 | a:append("{\\bord" .. bord .. "}") 116 | a:append("{\\shad0}") 117 | local r = opts.line_color 118 | a:append("{\\3c&H".. r .. r .. r .. "&}") 119 | a:append("{\\1a&HFF}") 120 | a:append("{\\2a&HFF}") 121 | a:append("{\\3a&H00}") 122 | a:append("{\\4a&HFF}") 123 | a:draw_start() 124 | end 125 | local dot = function(pos, size) 126 | draw_setup(size) 127 | a:move_to(pos[1], pos[2]-0.5) 128 | a:line_to(pos[1], pos[2]+0.5) 129 | end 130 | local line = function(from, to, size) 131 | draw_setup(size) 132 | a:move_to(from[1], from[2]) 133 | a:line_to(to[1], to[2]) 134 | end 135 | if distinct then 136 | dot(line_start.screen, opts.dots_radius) 137 | line(line_start.screen, line_end.screen, opts.line_width) 138 | dot(line_end.screen, opts.dots_radius) 139 | else 140 | dot(line_start.screen, opts.dots_radius) 141 | end 142 | 143 | local line_info = function() 144 | if not opts.show_distance then return end 145 | a:new_event() 146 | a:append("{\\fs36}{\\bord1}") 147 | a:pos((line_start.screen[1] + line_end.screen[1]) / 2, (line_start.screen[2] + line_end.screen[2]) / 2) 148 | local an = 1 149 | if line_start.image[1] < line_end.image[1] then an = an + 2 end 150 | if line_start.image[2] < line_end.image[2] then an = an + 6 end 151 | a:an(an) 152 | local image = math.sqrt(math.pow(line_start.image[1] - line_end.image[1], 2) + math.pow(line_start.image[2] - line_end.image[2], 2)) 153 | local screen = math.sqrt(math.pow(line_start.screen[1] - line_end.screen[1], 2) + math.pow(line_start.screen[2] - line_end.screen[2], 2)) 154 | if opts.coordinates_space == "both" then 155 | a:append(string.format("image: %.1f\\Nscreen: %.1f", image, screen)) 156 | elseif opts.coordinates_space == "image" then 157 | a:append(string.format("%.1f", image)) 158 | elseif opts.coordinates_space == "window" then 159 | a:append(string.format("%.1f", screen)) 160 | end 161 | end 162 | local dot_info = function(pos, opposite) 163 | if not opts.show_coordinates then return end 164 | a:new_event() 165 | a:append("{\\fs" .. opts.font_size .."}{\\bord1}") 166 | a:pos(pos.screen[1], pos.screen[2]) 167 | local an 168 | if distinct then 169 | an = 1 170 | if line_start.image[1] > line_end.image[1] then an = an + 2 end 171 | if line_start.image[2] < line_end.image[2] then an = an + 6 end 172 | else 173 | an = 7 174 | end 175 | if opposite then 176 | an = 9 + 1 - an 177 | end 178 | a:an(an) 179 | if opts.coordinates_space == "both" then 180 | a:append(string.format("image: %.1f, %.1f\\Nscreen: %i, %i", 181 | pos.image[1], pos.image[2], pos.screen[1], pos.screen[2])) 182 | elseif opts.coordinates_space == "image" then 183 | a:append(string.format("%.1f, %.1f", pos.image[1], pos.image[2])) 184 | elseif opts.coordinates_space == "window" then 185 | a:append(string.format("%i, %i", pos.screen[1], pos.screen[2])) 186 | end 187 | end 188 | dot_info(line_start, true) 189 | if distinct then 190 | line_info() 191 | dot_info(line_end, false) 192 | end 193 | if distinct and opts.show_angles ~= "no" then 194 | local dist = 50 195 | local pos_from_angle = function(mult, angle) 196 | return { 197 | line_start.screen[1] + mult * dist * math.cos(angle), 198 | line_start.screen[2] + mult * dist * math.sin(angle), 199 | } 200 | end 201 | local extended = { line_start.screen[1], line_start.screen[2] } 202 | if line_end.screen[1] > line_start.screen[1] then 203 | extended[1] = extended[1] + dist 204 | else 205 | extended[1] = extended[1] - dist 206 | end 207 | line(line_start.screen, extended, math.max(0, opts.line_width-0.5)) 208 | local angle = math.atan(math.abs(line_start.image[2] - line_end.image[2]) / math.abs(line_start.image[1] - line_end.image[1])) 209 | local fix_angle 210 | local an 211 | if line_end.image[2] < line_start.image[2] and line_end.image[1] > line_start.image[1] then 212 | -- upper-right 213 | an = 4 214 | fix_angle = function(angle) return - angle end 215 | elseif line_end.image[2] < line_start.image[2] then 216 | -- upper-left 217 | an = 6 218 | fix_angle = function(angle) return math.pi + angle end 219 | elseif line_end.image[1] < line_start.image[1] then 220 | -- bottom-left 221 | an = 6 222 | fix_angle = function(angle) return math.pi - angle end 223 | else 224 | -- bottom-right 225 | an = 4 226 | fix_angle = function(angle) return angle end 227 | end 228 | -- should implement this https://math.stackexchange.com/questions/873224/calculate-control-points-of-cubic-bezier-curve-approximating-a-part-of-a-circle 229 | local cp1 = pos_from_angle(1, fix_angle(angle*1/4)) 230 | local cp2 = pos_from_angle(1, fix_angle(angle*3/4)) 231 | local p2 = pos_from_angle(1, fix_angle(angle)) 232 | a:bezier_curve(cp1[1], cp1[2], cp2[1], cp2[2], p2[1], p2[2]) 233 | 234 | a:new_event() 235 | a:append("{\\fs" .. opts.font_size .."}{\\bord1}") 236 | local text_pos = pos_from_angle(1.1, fix_angle(angle*2/3)) -- you'd think /2 would make more sense, but *2/3 looks better 237 | a:pos(text_pos[1], text_pos[2]) 238 | a:an(an) 239 | if opts.show_angles == "both" then 240 | a:append(string.format("%.2f\\N%.1f°", angle, angle / math.pi * 180)) 241 | elseif opts.show_angles == "degrees" then 242 | a:append(string.format("%.1f°", angle / math.pi * 180)) 243 | elseif opts.show_angles == "radians" then 244 | a:append(string.format("%.2f", angle)) 245 | end 246 | end 247 | 248 | draw_ass(a.text) 249 | end 250 | 251 | function mark_stale() 252 | video_dimensions_stale = true 253 | end 254 | 255 | function add_bindings() 256 | mp.add_forced_key_binding("mouse_move", "ruler-mouse-move", mark_stale) 257 | for _, key in ipairs(confirm_bindings) do 258 | mp.add_forced_key_binding(key, "ruler-next-" .. key, next_step) 259 | end 260 | for _, key in ipairs(exit_bindings) do 261 | mp.add_forced_key_binding(key, "ruler-stop-" .. key, stop) 262 | end 263 | end 264 | 265 | function remove_bindings() 266 | for _, key in ipairs(confirm_bindings) do 267 | mp.remove_key_binding("ruler-next-" .. key) 268 | end 269 | for _, key in ipairs(exit_bindings) do 270 | mp.remove_key_binding("ruler-stop-" .. key) 271 | end 272 | mp.remove_key_binding("ruler-mouse-move") 273 | end 274 | 275 | function next_step() 276 | if state == 0 then 277 | state = 1 278 | mp.register_idle(refresh) 279 | mp.observe_property("osd-dimensions", nil, mark_stale) 280 | mark_stale() 281 | add_bindings() 282 | if opts.set_first_point_on_begin then 283 | next_step() 284 | end 285 | elseif state == 1 then 286 | local dim = mp.get_property_native("osd-dimensions") 287 | if not dim then return end 288 | state = 2 289 | first_point = cursor_video_space_normalized(dim) 290 | elseif state == 2 then 291 | local dim = mp.get_property_native("osd-dimensions") 292 | if not dim then return end 293 | state = 3 294 | second_point = cursor_video_space_normalized(dim) 295 | if opts.clear_on_second_point_set then 296 | next_step() 297 | end 298 | else 299 | stop() 300 | end 301 | end 302 | 303 | function stop() 304 | if state == 0 then return end 305 | mp.unregister_idle(refresh) 306 | mp.unobserve_property(mark_stale) 307 | remove_bindings() 308 | state = 0 309 | first_point = nil 310 | second_point = nil 311 | draw_ass("") 312 | end 313 | 314 | mp.add_key_binding(nil, "ruler", next_step) 315 | -------------------------------------------------------------------------------- /scripts/seek-to.lua: -------------------------------------------------------------------------------- 1 | local assdraw = require 'mp.assdraw' 2 | local active = false 3 | local cursor_position = 1 4 | local time_scale = {60*60*10, 60*60, 60*10, 60, 10, 1, 0.1, 0.01, 0.001} 5 | 6 | local ass_begin = mp.get_property("osd-ass-cc/0") 7 | local ass_end = mp.get_property("osd-ass-cc/1") 8 | 9 | local history = { {} } 10 | for i = 1, 9 do 11 | history[1][i] = 0 12 | end 13 | local history_position = 1 14 | 15 | local timer = nil 16 | local timer_duration = 3 17 | 18 | function show_seeker() 19 | local prepend_char = {'','',':','',':','','.','',''} 20 | local str = '' 21 | for i = 1, 9 do 22 | str = str .. prepend_char[i] 23 | if i == cursor_position then 24 | str = str .. '{\\b1}' .. history[history_position][i] .. '{\\r}' 25 | else 26 | str = str .. history[history_position][i] 27 | end 28 | end 29 | mp.osd_message("Seek to: " .. ass_begin .. str .. ass_end, timer_duration) 30 | end 31 | 32 | function copy_history_to_last() 33 | if history_position ~= #history then 34 | for i = 1, 9 do 35 | history[#history][i] = history[history_position][i] 36 | end 37 | history_position = #history 38 | end 39 | end 40 | 41 | function change_number(i) 42 | if (cursor_position == 3 or cursor_position == 5) and i >= 6 then 43 | return 44 | end 45 | if history[history_position][cursor_position] ~= i then 46 | copy_history_to_last() 47 | history[#history][cursor_position] = i 48 | end 49 | shift_cursor(false) 50 | end 51 | 52 | function shift_cursor(left) 53 | if left then 54 | cursor_position = math.max(1, cursor_position - 1) 55 | else 56 | cursor_position = math.min(cursor_position + 1, 9) 57 | end 58 | end 59 | 60 | function current_time_as_sec(time) 61 | local sec = 0 62 | for i = 1, 9 do 63 | sec = sec + time_scale[i] * time[i] 64 | end 65 | return sec 66 | end 67 | 68 | function time_equal(lhs, rhs) 69 | for i = 1, 9 do 70 | if lhs[i] ~= rhs[i] then 71 | return false 72 | end 73 | end 74 | return true 75 | end 76 | 77 | function seek_to() 78 | copy_history_to_last() 79 | mp.commandv("osd-bar", "seek", current_time_as_sec(history[history_position]), "absolute") 80 | if #history == 1 or not time_equal(history[history_position], history[#history - 1]) then 81 | history[#history + 1] = {} 82 | history_position = #history 83 | end 84 | for i = 1, 9 do 85 | history[#history][i] = 0 86 | end 87 | end 88 | 89 | function backspace() 90 | if cursor_position ~= 9 or current_time[9] == 0 then 91 | shift_cursor(true) 92 | end 93 | if history[history_position][cursor_position] ~= 0 then 94 | copy_history_to_last() 95 | history[#history][cursor_position] = 0 96 | end 97 | end 98 | 99 | function history_move(up) 100 | if up then 101 | history_position = math.max(1, history_position - 1) 102 | else 103 | history_position = math.min(history_position + 1, #history) 104 | end 105 | end 106 | 107 | local key_mappings = { 108 | LEFT = function() shift_cursor(true) show_seeker() end, 109 | RIGHT = function() shift_cursor(false) show_seeker() end, 110 | UP = function() history_move(true) show_seeker() end, 111 | DOWN = function() history_move(false) show_seeker() end, 112 | BS = function() backspace() show_seeker() end, 113 | ESC = function() set_inactive() end, 114 | ENTER = function() seek_to() set_inactive() end 115 | } 116 | for i = 0, 9 do 117 | local func = function() change_number(i) show_seeker() end 118 | key_mappings[string.format("KP%d", i)] = func 119 | key_mappings[string.format("%d", i)] = func 120 | end 121 | 122 | function set_active() 123 | if not mp.get_property("seekable") then return end 124 | local duration = mp.get_property_number("duration") 125 | if duration ~= nil then 126 | for i = 1, 9 do 127 | if duration > time_scale[i] then 128 | cursor_position = i 129 | break 130 | end 131 | end 132 | end 133 | for key, func in pairs(key_mappings) do 134 | mp.add_forced_key_binding(key, "seek-to-"..key, func) 135 | end 136 | show_seeker() 137 | timer = mp.add_periodic_timer(timer_duration, show_seeker) 138 | active = true 139 | end 140 | 141 | function set_inactive() 142 | mp.osd_message("") 143 | for key, _ in pairs(key_mappings) do 144 | mp.remove_key_binding("seek-to-"..key) 145 | end 146 | timer:kill() 147 | active = false 148 | end 149 | 150 | mp.add_key_binding(nil, "toggle-seeker", function() if active then set_inactive() else set_active() end end) 151 | -------------------------------------------------------------------------------- /scripts/status-line.lua: -------------------------------------------------------------------------------- 1 | local opts = { 2 | enabled = true, 3 | size = 36, 4 | margin = 10, 5 | text_top_left = "", 6 | text_top_right = "", 7 | text_bottom_left = "${filename} [${playlist-pos-1}/${playlist-count}]", 8 | text_bottom_right = "[${dwidth:X}x${dheight:X}]", 9 | } 10 | 11 | local msg = require 'mp.msg' 12 | local assdraw = require 'mp.assdraw' 13 | local options = require 'mp.options' 14 | 15 | options.read_options(opts, nil, function(c) 16 | if c["enabled"] then 17 | if opts.enabled then 18 | enable() 19 | else 20 | disable() 21 | end 22 | end 23 | if c["size"] or c["margin"] then 24 | mark_stale() 25 | end 26 | if c["text_top_left"] or 27 | c["text_top_right"] or 28 | c["text_bottom_left"] or 29 | c["text_bottom_right"] 30 | then 31 | observe_properties() 32 | mark_stale() 33 | end 34 | end) 35 | 36 | local stale = true 37 | local active = false 38 | 39 | function draw_ass(ass) 40 | local ww, wh = mp.get_osd_size() 41 | mp.set_osd_ass(ww, wh, ass) 42 | end 43 | 44 | function refresh() 45 | if not stale then return end 46 | stale = false 47 | local a = assdraw:ass_new() 48 | local draw_text = function(text, an, x, y) 49 | if text == "" then return end 50 | local expanded = mp.command_native({ "expand-text", text}) 51 | if not expanded then 52 | msg.error("Error expanding status-line") 53 | return 54 | end 55 | msg.verbose("Status-line changed to: " .. expanded) 56 | a:new_event() 57 | a:an(an) 58 | a:pos(x,y) 59 | a:append("{\\fs".. opts.size.. "}{\\bord1.0}") 60 | a:append(expanded) 61 | end 62 | local w,h = mp.get_osd_size() 63 | local m = opts.margin 64 | draw_text(opts.text_top_left, 7, m, m) 65 | draw_text(opts.text_top_right, 9, w-m, m) 66 | draw_text(opts.text_bottom_left, 1, m, h-m) 67 | draw_text(opts.text_bottom_right, 3, w-m, h-m) 68 | draw_ass(a.text) 69 | end 70 | 71 | function mark_stale() 72 | stale = true 73 | end 74 | 75 | function observe_properties() 76 | mp.unobserve_property(mark_stale) 77 | if not active then return end 78 | for _, str in ipairs({ 79 | opts.text_top_left, 80 | opts.text_top_right, 81 | opts.text_bottom_left, 82 | opts.text_bottom_right, 83 | }) do 84 | local start = 0 85 | while true do 86 | local s, e, cap = string.find(str, "%${[?!]?([%l%d-/]*)", start) 87 | if not s then break end 88 | msg.verbose("Observing property " .. cap) 89 | mp.observe_property(cap, nil, mark_stale) 90 | start = e 91 | end 92 | end 93 | mp.observe_property("osd-width", nil, mark_stale) 94 | mp.observe_property("osd-height", nil, mark_stale) 95 | end 96 | 97 | function enable() 98 | if active then return end 99 | active = true 100 | observe_properties() 101 | mp.register_idle(refresh) 102 | mark_stale() 103 | end 104 | 105 | 106 | function disable() 107 | if not active then return end 108 | active = false 109 | observe_properties() 110 | mp.unregister_idle(refresh) 111 | draw_ass("") 112 | end 113 | 114 | function toggle() 115 | if active then 116 | disable() 117 | else 118 | enable() 119 | end 120 | end 121 | 122 | if opts.enabled then 123 | enable() 124 | end 125 | 126 | mp.add_key_binding(nil, "status-line-enable", enable) 127 | mp.add_key_binding(nil, "status-line-disable", disable) 128 | mp.add_key_binding(nil, "status-line-toggle", toggle) 129 | 130 | -- TODO remove 131 | mp.add_key_binding(nil, "enable-status-line", function() 132 | msg.warn("This binding is deprecated, use 'status-line-enable' instead") 133 | enable() 134 | end) 135 | mp.add_key_binding(nil, "disable-status-line", function() 136 | msg.warn("This binding is deprecated, use 'status-line-disable' instead") 137 | disable() 138 | end) 139 | mp.add_key_binding(nil, "toggle-status-line", function() 140 | msg.warn("This binding is deprecated, use 'status-line-toggle' instead") 141 | toggle() 142 | end) 143 | -------------------------------------------------------------------------------- /scripts/systemtime.lua: -------------------------------------------------------------------------------- 1 | local messageShown = false 2 | local timer 3 | 4 | function updateSystemTime() 5 | local time = os.date("%H:%M:%S") 6 | mp.osd_message(time, 1) -- Display the message for 1 second 7 | 8 | if messageShown then 9 | timer:resume() -- Resume the timer to schedule the next update 10 | end 11 | end 12 | 13 | function toggleSystemTime() 14 | if messageShown then 15 | mp.osd_message("", 0) -- Clear the OSD message 16 | timer:kill() -- Kill the timer to stop the time updates 17 | messageShown = false 18 | else 19 | local time = os.date("%H:%M:%S") 20 | mp.osd_message(time, 999999) -- Display the initial time and set a long duration to keep it on top 21 | timer = mp.add_periodic_timer(1, updateSystemTime) -- Start the timer to update the time every second 22 | messageShown = true 23 | end 24 | end 25 | 26 | mp.register_script_message("show_time", toggleSystemTime) -------------------------------------------------------------------------------- /scripts/youtube-quality.lua: -------------------------------------------------------------------------------- 1 | local mp = require 'mp' 2 | local utils = require 'mp.utils' 3 | local msg = require 'mp.msg' 4 | local assdraw = require 'mp.assdraw' 5 | 6 | local opts = { 7 | toggle_menu_binding = "ctrl+f", 8 | up_binding = "UP", 9 | down_binding = "DOWN", 10 | select_binding = "ENTER", 11 | 12 | selected_and_active = "▶ - ", 13 | selected_and_inactive = "● - ", 14 | unselected_and_active = "▷ - ", 15 | unselected_and_inactive = "○ - ", 16 | 17 | scale_playlist_by_window=false, 18 | 19 | style_ass_tags = "{\\fnmonospace}", 20 | 21 | text_padding_x = 5, 22 | text_padding_y = 5, 23 | 24 | menu_timeout = 10, 25 | 26 | fetch_formats = true, 27 | 28 | quality_strings=[[ 29 | [ 30 | {"4320p" : "bestvideo[height<=?4320p]+bestaudio/best"}, 31 | {"2160p" : "bestvideo[height<=?2160]+bestaudio/best"}, 32 | {"1440p" : "bestvideo[height<=?1440]+bestaudio/best"}, 33 | {"1080p" : "bestvideo[height<=?1080]+bestaudio/best"}, 34 | {"720p" : "bestvideo[height<=?720]+bestaudio/best"}, 35 | {"480p" : "bestvideo[height<=?480]+bestaudio/best"}, 36 | {"360p" : "bestvideo[height<=?360]+bestaudio/best"} 37 | ] 38 | ]], 39 | } 40 | (require 'mp.options').read_options(opts, "youtube-quality") 41 | opts.quality_strings = utils.parse_json(opts.quality_strings) 42 | 43 | 44 | function show_menu() 45 | local selected = 1 46 | local active = 0 47 | local current_ytdl_format = mp.get_property("ytdl-format") 48 | msg.verbose("current ytdl-format: "..current_ytdl_format) 49 | local num_options = 0 50 | local options = {} 51 | 52 | 53 | if opts.fetch_formats then 54 | options, num_options = download_formats() 55 | end 56 | 57 | if next(options) == nil then 58 | for i,v in ipairs(opts.quality_strings) do 59 | num_options = num_options + 1 60 | for k,v2 in pairs(v) do 61 | options[i] = {label = k, format=v2} 62 | if v2 == current_ytdl_format then 63 | active = i 64 | selected = active 65 | end 66 | end 67 | end 68 | end 69 | 70 | for i,v in ipairs(options) do 71 | if v.format == current_ytdl_format then 72 | active = i 73 | selected = active 74 | break 75 | end 76 | end 77 | 78 | function selected_move(amt) 79 | selected = selected + amt 80 | if selected < 1 then selected = num_options 81 | elseif selected > num_options then selected = 1 end 82 | timeout:kill() 83 | timeout:resume() 84 | draw_menu() 85 | end 86 | function choose_prefix(i) 87 | if i == selected and i == active then return opts.selected_and_active 88 | elseif i == selected then return opts.selected_and_inactive end 89 | 90 | if i ~= selected and i == active then return opts.unselected_and_active 91 | elseif i ~= selected then return opts.unselected_and_inactive end 92 | return "> " 93 | end 94 | 95 | function draw_menu() 96 | local ass = assdraw.ass_new() 97 | 98 | ass:pos(opts.text_padding_x, opts.text_padding_y) 99 | ass:append(opts.style_ass_tags) 100 | 101 | for i,v in ipairs(options) do 102 | ass:append(choose_prefix(i)..v.label.."\\N") 103 | end 104 | 105 | local w, h = mp.get_osd_size() 106 | if opts.scale_playlist_by_window then w,h = 0, 0 end 107 | mp.set_osd_ass(w, h, ass.text) 108 | end 109 | 110 | function destroy() 111 | timeout:kill() 112 | mp.set_osd_ass(0,0,"") 113 | mp.remove_key_binding("move_up") 114 | mp.remove_key_binding("move_down") 115 | mp.remove_key_binding("select") 116 | mp.remove_key_binding("escape") 117 | end 118 | timeout = mp.add_periodic_timer(opts.menu_timeout, destroy) 119 | 120 | mp.add_forced_key_binding(opts.up_binding, "move_up", function() selected_move(-1) end, {repeatable=true}) 121 | mp.add_forced_key_binding(opts.down_binding, "move_down", function() selected_move(1) end, {repeatable=true}) 122 | mp.add_forced_key_binding(opts.select_binding, "select", function() 123 | destroy() 124 | mp.set_property("ytdl-format", options[selected].format) 125 | reload_resume() 126 | end) 127 | mp.add_forced_key_binding(opts.toggle_menu_binding, "escape", destroy) 128 | 129 | draw_menu() 130 | return 131 | end 132 | 133 | local ytdl = { 134 | path = "youtube-dl", 135 | searched = false, 136 | blacklisted = {} 137 | } 138 | 139 | format_cache={} 140 | function download_formats() 141 | local function exec(args) 142 | local ret = utils.subprocess({args = args}) 143 | return ret.status, ret.stdout, ret 144 | end 145 | 146 | local function table_size(t) 147 | s = 0 148 | for i,v in ipairs(t) do 149 | s = s+1 150 | end 151 | return s 152 | end 153 | 154 | local url = mp.get_property("path") 155 | 156 | url = string.gsub(url, "ytdl://", "") 157 | 158 | if format_cache[url] ~= nil then 159 | local res = format_cache[url] 160 | return res, table_size(res) 161 | end 162 | mp.osd_message("fetching available formats with youtube-dl...", 60) 163 | 164 | if not (ytdl.searched) then 165 | local ytdl_mcd = mp.find_config_file("youtube-dl") 166 | if not (ytdl_mcd == nil) then 167 | msg.verbose("found youtube-dl at: " .. ytdl_mcd) 168 | ytdl.path = ytdl_mcd 169 | end 170 | ytdl.searched = true 171 | end 172 | 173 | local command = {ytdl.path, "--no-warnings", "--no-playlist", "-J"} 174 | table.insert(command, url) 175 | local es, json, result = exec(command) 176 | 177 | if (es < 0) or (json == nil) or (json == "") then 178 | mp.osd_message("fetching formats failed...", 1) 179 | msg.error("failed to get format list: " .. err) 180 | return {}, 0 181 | end 182 | 183 | local json, err = utils.parse_json(json) 184 | 185 | if (json == nil) then 186 | mp.osd_message("fetching formats failed...", 1) 187 | msg.error("failed to parse JSON data: " .. err) 188 | return {}, 0 189 | end 190 | 191 | res = {} 192 | msg.verbose("youtube-dl succeeded!") 193 | for i,v in ipairs(json.formats) do 194 | if v.vcodec ~= "none" then 195 | local fps = v.fps and v.fps.."fps" or "" 196 | local resolution = string.format("%sx%s", v.width, v.height) 197 | local l = string.format("%-9s %-5s (%-4s / %s)", resolution, fps, v.ext, v.vcodec) 198 | local f = string.format("%s+bestaudio/best", v.format_id) 199 | table.insert(res, {label=l, format=f, width=v.width }) 200 | end 201 | end 202 | 203 | table.sort(res, function(a, b) return a.width > b.width end) 204 | 205 | mp.osd_message("", 0) 206 | format_cache[url] = res 207 | return res, table_size(res) 208 | end 209 | 210 | mp.add_forced_key_binding(opts.toggle_menu_binding, "quality-menu", show_menu) 211 | 212 | function reload_resume() 213 | function reload(path, time_pos) 214 | msg.debug("reload", path, time_pos) 215 | if time_pos == nil then 216 | mp.commandv("loadfile", path, "replace") 217 | else 218 | mp.commandv("loadfile", path, "replace", "start=+" .. time_pos) 219 | end 220 | end 221 | 222 | local path = mp.get_property("path") 223 | local time_pos = mp.get_property("time-pos") 224 | local reload_duration = mp.get_property_native("duration") 225 | 226 | local playlist_count = mp.get_property_number("playlist/count") 227 | local playlist_pos = mp.get_property_number("playlist-pos") 228 | local playlist = {} 229 | for i = 0, playlist_count-1 do 230 | playlist[i] = mp.get_property("playlist/" .. i .. "/filename") 231 | end 232 | 233 | if reload_duration and reload_duration > 0 then 234 | msg.info("reloading video from", time_pos, "second") 235 | reload(path, time_pos) 236 | else 237 | msg.info("reloading stream") 238 | reload(path, nil) 239 | end 240 | msg.info("file ", playlist_pos+1, " of ", playlist_count, "in playlist") 241 | for i = 0, playlist_pos-1 do 242 | mp.commandv("loadfile", playlist[i], "append") 243 | end 244 | mp.commandv("playlist-move", 0, playlist_pos+1) 245 | for i = playlist_pos+1, playlist_count-1 do 246 | mp.commandv("loadfile", playlist[i], "append") 247 | end 248 | end 249 | -------------------------------------------------------------------------------- /shaders/Anime4K_Darken_HQ.glsl: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2019-2021 bloc97 4 | // All rights reserved. 5 | 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | //!DESC Anime4K-v3.2-Darken-DoG-(HQ)-Luma 25 | //!HOOK MAIN 26 | //!BIND HOOKED 27 | //!SAVE LINELUMA 28 | //!COMPONENTS 1 29 | 30 | float get_luma(vec4 rgba) { 31 | return dot(vec4(0.299, 0.587, 0.114, 0.0), rgba); 32 | } 33 | 34 | vec4 hook() { 35 | return vec4(get_luma(HOOKED_tex(HOOKED_pos)), 0.0, 0.0, 0.0); 36 | } 37 | 38 | //!DESC Anime4K-v3.2-Darken-DoG-(HQ)-Difference-X 39 | //!HOOK MAIN 40 | //!BIND HOOKED 41 | //!BIND LINELUMA 42 | //!SAVE LINEKERNEL 43 | //!COMPONENTS 1 44 | 45 | #define SPATIAL_SIGMA (1.0 * float(HOOKED_size.y) / 1080.0) //Spatial window size, must be a positive real number. 46 | 47 | #define KERNELSIZE (max(int(ceil(SPATIAL_SIGMA * 2.0)), 1) * 2 + 1) //Kernel size, must be an positive odd integer. 48 | #define KERNELHALFSIZE (int(KERNELSIZE/2)) //Half of the kernel size without remainder. Must be equal to trunc(KERNELSIZE/2). 49 | #define KERNELLEN (KERNELSIZE * KERNELSIZE) //Total area of kernel. Must be equal to KERNELSIZE * KERNELSIZE. 50 | 51 | float gaussian(float x, float s, float m) { 52 | float scaled = (x - m) / s; 53 | return exp(-0.5 * scaled * scaled); 54 | } 55 | 56 | float comp_gaussian_x() { 57 | 58 | float g = 0.0; 59 | float gn = 0.0; 60 | 61 | for (int i=0; iRGB matrix has 1 for every row... (which is the case for BT.709) 195 | //Otherwise we would need to convert RGB to YUV, modify Y then convert back to RGB. 196 | return HOOKED_tex(HOOKED_pos) + (comp_gaussian_y() * STRENGTH); 197 | } 198 | 199 | -------------------------------------------------------------------------------- /shaders/Anime4K_Thin_HQ.glsl: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2019-2021 bloc97 4 | // All rights reserved. 5 | 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | //!DESC Anime4K-v3.2-Thin-(HQ)-Luma 25 | //!HOOK MAIN 26 | //!BIND HOOKED 27 | //!SAVE LINELUMA 28 | //!COMPONENTS 1 29 | 30 | float get_luma(vec4 rgba) { 31 | return dot(vec4(0.299, 0.587, 0.114, 0.0), rgba); 32 | } 33 | 34 | vec4 hook() { 35 | return vec4(get_luma(HOOKED_tex(HOOKED_pos)), 0.0, 0.0, 0.0); 36 | } 37 | 38 | //!DESC Anime4K-v3.2-Thin-(HQ)-Sobel-X 39 | //!HOOK MAIN 40 | //!BIND LINELUMA 41 | //!SAVE LINESOBEL 42 | //!COMPONENTS 2 43 | 44 | vec4 hook() { 45 | float l = LINELUMA_texOff(vec2(-1.0, 0.0)).x; 46 | float c = LINELUMA_tex(LINELUMA_pos).x; 47 | float r = LINELUMA_texOff(vec2(1.0, 0.0)).x; 48 | 49 | float xgrad = (-l + r); 50 | float ygrad = (l + c + c + r); 51 | 52 | return vec4(xgrad, ygrad, 0.0, 0.0); 53 | } 54 | 55 | 56 | //!DESC Anime4K-v3.2-Thin-(HQ)-Sobel-Y 57 | //!HOOK MAIN 58 | //!BIND LINESOBEL 59 | //!SAVE LINESOBEL 60 | //!COMPONENTS 1 61 | 62 | vec4 hook() { 63 | float tx = LINESOBEL_texOff(vec2(0.0, -1.0)).x; 64 | float cx = LINESOBEL_tex(LINESOBEL_pos).x; 65 | float bx = LINESOBEL_texOff(vec2(0.0, 1.0)).x; 66 | 67 | float ty = LINESOBEL_texOff(vec2(0.0, -1.0)).y; 68 | float by = LINESOBEL_texOff(vec2(0.0, 1.0)).y; 69 | 70 | float xgrad = (tx + cx + cx + bx) / 8.0; 71 | 72 | float ygrad = (-ty + by) / 8.0; 73 | 74 | //Computes the luminance's gradient 75 | float norm = sqrt(xgrad * xgrad + ygrad * ygrad); 76 | return vec4(pow(norm, 0.7)); 77 | } 78 | 79 | 80 | //!DESC Anime4K-v3.2-Thin-(HQ)-Gaussian-X 81 | //!HOOK MAIN 82 | //!BIND HOOKED 83 | //!BIND LINESOBEL 84 | //!SAVE LINESOBEL 85 | //!COMPONENTS 1 86 | 87 | #define SPATIAL_SIGMA (2.0 * float(HOOKED_size.y) / 1080.0) //Spatial window size, must be a positive real number. 88 | 89 | #define KERNELSIZE (max(int(ceil(SPATIAL_SIGMA * 2.0)), 1) * 2 + 1) //Kernel size, must be an positive odd integer. 90 | #define KERNELHALFSIZE (int(KERNELSIZE/2)) //Half of the kernel size without remainder. Must be equal to trunc(KERNELSIZE/2). 91 | #define KERNELLEN (KERNELSIZE * KERNELSIZE) //Total area of kernel. Must be equal to KERNELSIZE * KERNELSIZE. 92 | 93 | float gaussian(float x, float s, float m) { 94 | float scaled = (x - m) / s; 95 | return exp(-0.5 * scaled * scaled); 96 | } 97 | 98 | float comp_gaussian_x() { 99 | 100 | float g = 0.0; 101 | float gn = 0.0; 102 | 103 | for (int i=0; i 4). 23 | // Changed rcp + mul operations to div for better clarity when CAS_GO_SLOWER is set to 1, since the compiler should automatically 24 | // optimize those instructions anyway. 25 | // Made it directly operate on LUMA plane, since the original shader was operating on LUMA by deriving it from RGB. This should 26 | // cause a major increase in performance, especially on OpenGL 4.0+ renderers (4 texture lookups vs. 9) 27 | // Removed transparency preservation mechanism since the alpha channel is a separate source plan than LUMA 28 | // Added custom gamma curve support for relinearization 29 | // Removed final blending between the original and the sharpened pixels since it was redundant 30 | // 31 | // Notes 32 | // The filter is designed to run in linear light, and does have an optional relinerization and delinearization pass which 33 | // assumes BT.1886 content by default. Do not forget to change SOURCE_TRC and TARGET_TRC variables depending 34 | // on what kind of content the filter is running on. You might want to create seperate versions of the file with different 35 | // colorspace values, and apply them via autoprofiles. Note that running in non-linear light will result in oversharpening. 36 | // 37 | // By default the shader only runs on non-scaled content since it is designed for use without scaling, if the content is 38 | // scaled you should probably use CAS-scaled.glsl instead. However this behavior can be overriden by changing the WHEN 39 | // directives with "OUTPUT.w OUTPUT.h * LUMA.w LUMA.h * / 1.0 < !" which allows it to be used as a pre-upscale sharpener. 40 | 41 | //!HOOK LUMA 42 | //!BIND HOOKED 43 | //!DESC FidelityFX Sharpening (Relinearization) 44 | //!WHEN OUTPUT.w OUTPUT.h * LUMA.w LUMA.h * / 1.0 > ! OUTPUT.w OUTPUT.h * LUMA.w LUMA.h * / 1.0 < ! * 45 | 46 | // User variables - Relinearization 47 | // Compatibility 48 | #define SOURCE_TRC 4 // Is needed to convert from source colorspace to linear light. 0 = None (Skip conversion), 1 = Rec709, 2 = PQ, 3 = sRGB, 4 = BT.1886, 5 = HLG, 6 = Custom 49 | #define CUSTOM_GAMMA 2.2 // Custom power gamma curve to use if and when SOURCE_TRC is 6. 50 | 51 | // Shader code 52 | 53 | float From709(float rec709) { 54 | return max(min(rec709 / float(4.5), float(0.081)), pow((rec709 + float(0.099)) / float(1.099), float(1.0 / 0.45))); 55 | } 56 | 57 | float FromPq(float pq) { 58 | float p = pow(pq, float(0.0126833)); 59 | return (pow(clamp(p - float(0.835938), 0.0, 1.0) / (float(18.8516) - float(18.6875) * p), float(6.27739))); 60 | } 61 | 62 | float FromSrgb(float srgb) { 63 | return max(min(srgb / 12.92, float(0.04045)), pow((srgb + float(0.055)) / float(1.055), float(2.4))); 64 | } 65 | 66 | float FromHlg(float hlg) { 67 | const float a = 0.17883277; 68 | const float b = 0.28466892; 69 | const float c = 0.55991073; 70 | 71 | float linear; 72 | if (hlg >= 0.0 && hlg <= 0.5) { 73 | linear = pow(hlg, 2.0) / 3.0; 74 | } else { 75 | linear = (exp((hlg - c) / a) + b) / 12.0; 76 | } 77 | 78 | return linear; 79 | } 80 | 81 | vec4 hook() { 82 | vec4 col = HOOKED_tex(HOOKED_pos); 83 | col.r = clamp(col.r, 0.0, 1.0); 84 | #if (SOURCE_TRC == 1) 85 | col.r = From709(col.r); 86 | #elif (SOURCE_TRC == 2) 87 | col.r = FromPq(col.r); 88 | #elif (SOURCE_TRC == 3) 89 | col.r = FromSrgb(col.r); 90 | #elif (SOURCE_TRC == 4) 91 | col.r = pow(col.r, float(2.4)); 92 | #elif (SOURCE_TRC == 5) 93 | col.r = FromHlg(col.r); 94 | #elif (SOURCE_TRC == 6) 95 | col.r = pow(col.r, float(CUSTOM_GAMMA)); 96 | #endif 97 | return col; 98 | } 99 | 100 | //!HOOK LUMA 101 | //!BIND HOOKED 102 | //!DESC FidelityFX Sharpening 103 | //!WHEN OUTPUT.w OUTPUT.h * LUMA.w LUMA.h * / 1.0 > ! OUTPUT.w OUTPUT.h * LUMA.w LUMA.h * / 1.0 < ! * 104 | 105 | // User variables 106 | // Intensity 107 | #define SHARPENING 0.0 // Adjusts the range the shader adapts to high contrast (0 is not all the way off). Higher values = more high contrast sharpening. 0.0 to 1.0. 108 | 109 | // Performance 110 | #define CAS_BETTER_DIAGONALS 1 // If set to 0, drops certain math and texture lookup operations for better performance. 0 or 1. 111 | #define CAS_GO_SLOWER 0 // If set to 1, disables the use of optimized approximate transcendental functions which might slightly increase accuracy in exchange of performance. 0 or 1. 112 | 113 | // Compatibility 114 | #define TARGET_TRC 4 // Is needed to convert from source colorspace to target colorspace. 0 = None (Skip conversion), 1 = Rec709, 2 = PQ, 3 = sRGB, 4 = BT.1886, 5 = HLG, 6 = Custom 115 | #define CUSTOM_GAMMA 2.2 // Custom power gamma curve to use if and when TARGET_TRC is 6. 116 | 117 | // Shader code 118 | 119 | float To709(float linear) { 120 | return max(min(linear * float(4.5), float(0.018)), float(1.099) * pow(linear, float(0.45)) - float(0.099)); 121 | } 122 | 123 | float ToPq(float linear) { 124 | float p = pow(linear, float(0.159302)); 125 | return pow((float(0.835938) + float(18.8516) * p) / (float(1.0) + float(18.6875) * p), float(78.8438)); 126 | } 127 | 128 | float ToSrgb(float linear) { 129 | return max(min(linear * float(12.92), float(0.0031308)), float(1.055) * pow(linear, float(0.41666)) - float(0.055)); 130 | } 131 | 132 | float ToHlg(float linear) { 133 | const float a = 0.17883277; 134 | const float b = 0.28466892; 135 | const float c = 0.55991073; 136 | 137 | float hlg; 138 | if (linear <= 1.0 / 12.0) { 139 | hlg = sqrt(3.0 * linear); 140 | } else { 141 | hlg = a * log(12.0 * linear - b) + c; 142 | } 143 | 144 | return hlg; 145 | } 146 | 147 | #if (CAS_GO_SLOWER == 0) 148 | 149 | float APrxLoSqrtF1(float a) { 150 | return uintBitsToFloat((floatBitsToUint(a) >> uint(1)) + uint(0x1fbc4639)); 151 | } 152 | 153 | float APrxLoRcpF1(float a) { 154 | return uintBitsToFloat(uint(0x7ef07ebb) - floatBitsToUint(a)); 155 | } 156 | 157 | float APrxMedRcpF1(float a) { 158 | float b = uintBitsToFloat(uint(0x7ef19fff) - floatBitsToUint(a)); 159 | return b * (-b * a + float(2.0)); 160 | } 161 | 162 | #endif 163 | 164 | vec4 hook() 165 | { 166 | // fetch a 3x3 neighborhood around the pixel 'e', 167 | // a b c 168 | // d(e)f 169 | // g h i 170 | 171 | #if (defined(HOOKED_gather) && (__VERSION__ >= 400 || (GL_ES && __VERSION__ >= 310))) 172 | vec4 efhi = HOOKED_gather(vec2(HOOKED_pos + vec2(0.5) * HOOKED_pt), 0); 173 | 174 | float e = efhi.w; 175 | float f = efhi.z; 176 | float h = efhi.x; 177 | 178 | vec3 abd = HOOKED_gather(vec2(HOOKED_pos - vec2(0.5) * HOOKED_pt), 0).wzx; 179 | float b = abd.y; 180 | float d = abd.z; 181 | 182 | #if (CAS_BETTER_DIAGONALS == 1) 183 | float a = abd.x; 184 | float i = efhi.y; 185 | #endif 186 | #else 187 | float e = HOOKED_tex(HOOKED_pos).r; 188 | float f = HOOKED_texOff(vec2(1.0, 0.0)).r; 189 | float h = HOOKED_texOff(vec2(0.0, 1.0)).r; 190 | 191 | #if (CAS_BETTER_DIAGONALS == 1) 192 | float a = HOOKED_texOff(vec2(-1.0, -1.0)).r; 193 | float i = HOOKED_texOff(vec2(1.0, 1.0)).r; 194 | #endif 195 | 196 | float b = HOOKED_texOff(vec2( 0.0, -1.0)).r; 197 | float d = HOOKED_texOff(vec2(-1.0, 0.0)).r; 198 | #endif 199 | #if (CAS_BETTER_DIAGONALS == 1) 200 | float c = HOOKED_texOff(vec2( 1.0, -1.0)).r; 201 | float g = HOOKED_texOff(vec2(-1.0, 1.0)).r; 202 | #endif 203 | 204 | // Soft min and max. 205 | // a b c b 206 | // d e f * 0.5 + d e f * 0.5 207 | // g h i h 208 | // These are 2.0x bigger (factored out the extra multiply). 209 | 210 | float mnL = min(min(min(d, e), min(f, b)), h); 211 | float mxL = max(max(max(d, e), max(f, b)), h); 212 | #if (CAS_BETTER_DIAGONALS == 1) 213 | float mnL2 = min(mnL, min(min(a, c), min(g, i))); 214 | mnL += mnL2; 215 | 216 | float mxL2 = max(mxL, max(max(a, c), max(g, i))); 217 | mxL += mxL2; 218 | #endif 219 | 220 | // Smooth minimum distance to signal limit divided by smooth max. 221 | const float bdval = bool(CAS_BETTER_DIAGONALS) ? 2.0 : 1.0; 222 | #if (CAS_GO_SLOWER == 1) 223 | float ampL = clamp(min(mnL, bdval - mxL) / mxL, 0.0, 1.0); 224 | #else 225 | float ampL = clamp(min(mnL, bdval - mxL) * APrxLoRcpF1(mxL), 0.0, 1.0); 226 | #endif 227 | 228 | // Shaping amount of sharpening. 229 | #if (CAS_GO_SLOWER == 1) 230 | ampL = sqrt(ampL); 231 | #else 232 | ampL = APrxLoSqrtF1(ampL); 233 | #endif 234 | 235 | // Filter shape. 236 | // 0 w 0 237 | // w 1 w 238 | // 0 w 0 239 | 240 | const float peak = -(mix(8.0, 5.0, clamp(SHARPENING, 0.0, 1.0))); 241 | float wL = ampL / peak; 242 | 243 | // Filter. 244 | // Using green coef only 245 | float Weight = 1.0 + 4.0 * wL; 246 | vec4 pix = vec4(0.0, 0.0, 0.0, 1.0); 247 | pix.r = ((b + d + f + h) * wL) + e; 248 | #if (CAS_GO_SLOWER == 1) 249 | pix.r /= Weight; 250 | #else 251 | pix.r *= APrxMedRcpF1(Weight); 252 | #endif 253 | pix.r = clamp(pix.r, 0.0, 1.0); 254 | 255 | #if (TARGET_TRC == 1) 256 | pix.r = To709(pix.r); 257 | #elif (TARGET_TRC == 2) 258 | pix.r = ToPq(pix.r); 259 | #elif (TARGET_TRC == 3) 260 | pix.r = ToSrgb(pix.r); 261 | #elif (TARGET_TRC == 4) 262 | pix.r = pow(pix.r, float(1.0 / 2.4)); 263 | #elif (TARGET_TRC == 5) 264 | pix.r = ToHlg(pix.r); 265 | #elif (TARGET_TRC == 6) 266 | pix.r = pow(pix.r, float(1.0 / CUSTOM_GAMMA)); 267 | #endif 268 | 269 | return pix; 270 | } -------------------------------------------------------------------------------- /shaders/KrigBilateral.glsl: -------------------------------------------------------------------------------- 1 | // KrigBilateral by Shiandow 2 | // 3 | // This library is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU Lesser General Public 5 | // License as published by the Free Software Foundation; either 6 | // version 3.0 of the License, or (at your option) any later version. 7 | // 8 | // This library is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | // Lesser General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU Lesser General Public 14 | // License along with this library. 15 | 16 | //!HOOK CHROMA 17 | //!BIND HOOKED 18 | //!BIND LUMA 19 | //!SAVE LOWRES_Y 20 | //!WIDTH LUMA.w 21 | //!WHEN CHROMA.w LUMA.w < 22 | //!DESC KrigBilateral Downscaling Y pass 1 23 | 24 | #define offset vec2(0) 25 | 26 | #define axis 1 27 | 28 | #define Kernel(x) dot(vec3(0.42659, -0.49656, 0.076849), cos(vec3(0, 1, 2) * acos(-1.) * (x + 1.))) 29 | 30 | vec4 hook() { 31 | // Calculate bounds 32 | float low = ceil((LUMA_pos - CHROMA_pt) * LUMA_size - offset - 0.5)[axis]; 33 | float high = floor((LUMA_pos + CHROMA_pt) * LUMA_size - offset - 0.5)[axis]; 34 | 35 | float W = 0.0; 36 | vec4 avg = vec4(0); 37 | vec2 pos = LUMA_pos; 38 | 39 | for (float k = low; k <= high; k++) { 40 | pos[axis] = LUMA_pt[axis] * (k - offset[axis] + 0.5); 41 | float rel = (pos[axis] - LUMA_pos[axis])*CHROMA_size[axis]; 42 | float w = Kernel(rel); 43 | 44 | vec4 y = textureGrad(LUMA_raw, pos, vec2(0.0), vec2(0.0)).xxxx * LUMA_mul; 45 | y.y *= y.y; 46 | avg += w * y; 47 | W += w; 48 | } 49 | avg /= W; 50 | avg.y = abs(avg.y - avg.x * avg.x); 51 | return avg; 52 | } 53 | 54 | //!HOOK CHROMA 55 | //!BIND HOOKED 56 | //!BIND LOWRES_Y 57 | //!SAVE LOWRES_Y 58 | //!WHEN CHROMA.w LUMA.w < 59 | //!DESC KrigBilateral Downscaling Y pass 2 60 | 61 | #define offset vec2(0) 62 | 63 | #define axis 0 64 | 65 | #define Kernel(x) dot(vec3(0.42659, -0.49656, 0.076849), cos(vec3(0, 1, 2) * acos(-1.) * (x + 1.))) 66 | 67 | vec4 hook() { 68 | // Calculate bounds 69 | float low = ceil((LOWRES_Y_pos - CHROMA_pt) * LOWRES_Y_size - offset - 0.5)[axis]; 70 | float high = floor((LOWRES_Y_pos + CHROMA_pt) * LOWRES_Y_size - offset - 0.5)[axis]; 71 | 72 | float W = 0.0; 73 | vec4 avg = vec4(0); 74 | vec2 pos = LOWRES_Y_pos; 75 | 76 | for (float k = low; k <= high; k++) { 77 | pos[axis] = LOWRES_Y_pt[axis] * (k - offset[axis] + 0.5); 78 | float rel = (pos[axis] - LOWRES_Y_pos[axis])*CHROMA_size[axis]; 79 | float w = Kernel(rel); 80 | 81 | vec4 y = textureGrad(LOWRES_Y_raw, pos, vec2(0.0), vec2(0.0)).xxxx * LOWRES_Y_mul; 82 | y.y *= y.y; 83 | avg += w * y; 84 | W += w; 85 | } 86 | avg /= W; 87 | avg.y = abs(avg.y - avg.x * avg.x) + LOWRES_Y_texOff(0).y; 88 | return avg; 89 | } 90 | 91 | //!HOOK CHROMA 92 | //!BIND HOOKED 93 | //!BIND LUMA 94 | //!BIND LOWRES_Y 95 | //!WIDTH LUMA.w 96 | //!HEIGHT LUMA.h 97 | //!WHEN CHROMA.w LUMA.w < 98 | //!OFFSET ALIGN 99 | //!DESC KrigBilateral Upscaling UV 100 | 101 | #define sqr(x) dot(x,x) 102 | #define sigma_nsq 256.0/(255.0*255.0) 103 | 104 | #define N 8 105 | 106 | #define M(i,j) Mx[min(i,j)*N + max(i,j) - (min(i,j)*(min(i,j)+1))/2] 107 | 108 | #define C(i,j) (inversesqrt(1.0 + (X[i].y + X[j].y) / Var) * exp(-0.5 * (sqr(X[i].x - X[j].x) / (localVar + X[i].y + X[j].y) + sqr((coords[i] - coords[j]) / radius))) /*+ (X[i].x - y) * (X[j].x - y) / Var*/) // commented out part works well only on test patterns 109 | #define c(i) (inversesqrt(1.0 + X[i].y / Var) * exp(-0.5 * (sqr(X[i].x - y) / (localVar + X[i].y) + sqr((coords[i] - offset) / radius)))) 110 | 111 | #define getnsum(i) X[i] = vec4(LOWRES_Y_tex(LOWRES_Y_pt*(pos+coords[i]+vec2(0.5))).xy, \ 112 | CHROMA_tex(CHROMA_pt*(pos+coords[i]+vec2(0.5))).xy); \ 113 | w = clamp(1.5 - abs(coords[i]), 0.0, 1.0); \ 114 | total += w.x*w.y*vec4(X[i].x, X[i].x * X[i].x, X[i].y, 1.0); 115 | 116 | #define I3(f, n) f(n) f(n+1) f(n+2) 117 | #define I9(f, n) I3(f, n) I3(f, n+3) I3(f, n+6) 118 | 119 | vec4 hook() { 120 | vec2 pos = CHROMA_pos * HOOKED_size - vec2(0.5); 121 | vec2 offset = pos - round(pos); 122 | pos -= offset; 123 | 124 | vec2 coords[N+1]; 125 | vec4 X[N+1]; 126 | vec2 w; 127 | vec4 total = vec4(0); 128 | 129 | coords[0] = vec2(-1,-1); coords[1] = vec2(-1, 0); coords[2] = vec2(-1, 1); 130 | coords[3] = vec2( 0,-1); coords[4] = vec2( 0, 1); coords[5] = vec2( 1,-1); 131 | coords[6] = vec2( 1, 0); coords[7] = vec2( 1, 1); coords[8] = vec2( 0, 0); 132 | 133 | I9(getnsum, 0) 134 | 135 | total.xyz /= total.w; 136 | float localVar = abs(total.y - total.x * total.x) + sigma_nsq; 137 | float Var = localVar + total.z; 138 | float radius = 1.0; 139 | 140 | float y = LUMA_texOff(0).x; 141 | float Mx[(N*(N+1))/2]; 142 | float b[N]; 143 | vec2 interp = X[N].zw; 144 | 145 | b[0] = c(0) - c(N) - C(0,N) + C(N,N); M(0, 0) = C(0,0) - C(0,N) - C(0,N) + C(N,N); M(0, 1) = C(0,1) - C(1,N) - C(0,N) + C(N,N); M(0, 2) = C(0,2) - C(2,N) - C(0,N) + C(N,N); M(0, 3) = C(0,3) - C(3,N) - C(0,N) + C(N,N); M(0, 4) = C(0,4) - C(4,N) - C(0,N) + C(N,N); M(0, 5) = C(0,5) - C(5,N) - C(0,N) + C(N,N); M(0, 6) = C(0,6) - C(6,N) - C(0,N) + C(N,N); M(0, 7) = C(0,7) - C(7,N) - C(0,N) + C(N,N); 146 | b[1] = c(1) - c(N) - C(1,N) + C(N,N); M(1, 1) = C(1,1) - C(1,N) - C(1,N) + C(N,N); M(1, 2) = C(1,2) - C(2,N) - C(1,N) + C(N,N); M(1, 3) = C(1,3) - C(3,N) - C(1,N) + C(N,N); M(1, 4) = C(1,4) - C(4,N) - C(1,N) + C(N,N); M(1, 5) = C(1,5) - C(5,N) - C(1,N) + C(N,N); M(1, 6) = C(1,6) - C(6,N) - C(1,N) + C(N,N); M(1, 7) = C(1,7) - C(7,N) - C(1,N) + C(N,N); 147 | b[2] = c(2) - c(N) - C(2,N) + C(N,N); M(2, 2) = C(2,2) - C(2,N) - C(2,N) + C(N,N); M(2, 3) = C(2,3) - C(3,N) - C(2,N) + C(N,N); M(2, 4) = C(2,4) - C(4,N) - C(2,N) + C(N,N); M(2, 5) = C(2,5) - C(5,N) - C(2,N) + C(N,N); M(2, 6) = C(2,6) - C(6,N) - C(2,N) + C(N,N); M(2, 7) = C(2,7) - C(7,N) - C(2,N) + C(N,N); 148 | b[3] = c(3) - c(N) - C(3,N) + C(N,N); M(3, 3) = C(3,3) - C(3,N) - C(3,N) + C(N,N); M(3, 4) = C(3,4) - C(4,N) - C(3,N) + C(N,N); M(3, 5) = C(3,5) - C(5,N) - C(3,N) + C(N,N); M(3, 6) = C(3,6) - C(6,N) - C(3,N) + C(N,N); M(3, 7) = C(3,7) - C(7,N) - C(3,N) + C(N,N); 149 | b[4] = c(4) - c(N) - C(4,N) + C(N,N); M(4, 4) = C(4,4) - C(4,N) - C(4,N) + C(N,N); M(4, 5) = C(4,5) - C(5,N) - C(4,N) + C(N,N); M(4, 6) = C(4,6) - C(6,N) - C(4,N) + C(N,N); M(4, 7) = C(4,7) - C(7,N) - C(4,N) + C(N,N); 150 | b[5] = c(5) - c(N) - C(5,N) + C(N,N); M(5, 5) = C(5,5) - C(5,N) - C(5,N) + C(N,N); M(5, 6) = C(5,6) - C(6,N) - C(5,N) + C(N,N); M(5, 7) = C(5,7) - C(7,N) - C(5,N) + C(N,N); 151 | b[6] = c(6) - c(N) - C(6,N) + C(N,N); M(6, 6) = C(6,6) - C(6,N) - C(6,N) + C(N,N); M(6, 7) = C(6,7) - C(7,N) - C(6,N) + C(N,N); 152 | b[7] = c(7) - c(N) - C(7,N) + C(N,N); M(7, 7) = C(7,7) - C(7,N) - C(7,N) + C(N,N); 153 | 154 | b[1] -= b[0] * M(0, 1) / M(0, 0); M(1, 1) -= M(0, 1) * M(0, 1) / M(0, 0); M(1, 2) -= M(0, 2) * M(0, 1) / M(0, 0); M(1, 3) -= M(0, 3) * M(0, 1) / M(0, 0); M(1, 4) -= M(0, 4) * M(0, 1) / M(0, 0); M(1, 5) -= M(0, 5) * M(0, 1) / M(0, 0); M(1, 6) -= M(0, 6) * M(0, 1) / M(0, 0); M(1, 7) -= M(0, 7) * M(0, 1) / M(0, 0); 155 | b[2] -= b[0] * M(0, 2) / M(0, 0); M(2, 2) -= M(0, 2) * M(0, 2) / M(0, 0); M(2, 3) -= M(0, 3) * M(0, 2) / M(0, 0); M(2, 4) -= M(0, 4) * M(0, 2) / M(0, 0); M(2, 5) -= M(0, 5) * M(0, 2) / M(0, 0); M(2, 6) -= M(0, 6) * M(0, 2) / M(0, 0); M(2, 7) -= M(0, 7) * M(0, 2) / M(0, 0); 156 | b[3] -= b[0] * M(0, 3) / M(0, 0); M(3, 3) -= M(0, 3) * M(0, 3) / M(0, 0); M(3, 4) -= M(0, 4) * M(0, 3) / M(0, 0); M(3, 5) -= M(0, 5) * M(0, 3) / M(0, 0); M(3, 6) -= M(0, 6) * M(0, 3) / M(0, 0); M(3, 7) -= M(0, 7) * M(0, 3) / M(0, 0); 157 | b[4] -= b[0] * M(0, 4) / M(0, 0); M(4, 4) -= M(0, 4) * M(0, 4) / M(0, 0); M(4, 5) -= M(0, 5) * M(0, 4) / M(0, 0); M(4, 6) -= M(0, 6) * M(0, 4) / M(0, 0); M(4, 7) -= M(0, 7) * M(0, 4) / M(0, 0); 158 | b[5] -= b[0] * M(0, 5) / M(0, 0); M(5, 5) -= M(0, 5) * M(0, 5) / M(0, 0); M(5, 6) -= M(0, 6) * M(0, 5) / M(0, 0); M(5, 7) -= M(0, 7) * M(0, 5) / M(0, 0); 159 | b[6] -= b[0] * M(0, 6) / M(0, 0); M(6, 6) -= M(0, 6) * M(0, 6) / M(0, 0); M(6, 7) -= M(0, 7) * M(0, 6) / M(0, 0); 160 | b[7] -= b[0] * M(0, 7) / M(0, 0); M(7, 7) -= M(0, 7) * M(0, 7) / M(0, 0); 161 | 162 | b[2] -= b[1] * M(1, 2) / M(1, 1); M(2, 2) -= M(1, 2) * M(1, 2) / M(1, 1); M(2, 3) -= M(1, 3) * M(1, 2) / M(1, 1); M(2, 4) -= M(1, 4) * M(1, 2) / M(1, 1); M(2, 5) -= M(1, 5) * M(1, 2) / M(1, 1); M(2, 6) -= M(1, 6) * M(1, 2) / M(1, 1); M(2, 7) -= M(1, 7) * M(1, 2) / M(1, 1); 163 | b[3] -= b[1] * M(1, 3) / M(1, 1); M(3, 3) -= M(1, 3) * M(1, 3) / M(1, 1); M(3, 4) -= M(1, 4) * M(1, 3) / M(1, 1); M(3, 5) -= M(1, 5) * M(1, 3) / M(1, 1); M(3, 6) -= M(1, 6) * M(1, 3) / M(1, 1); M(3, 7) -= M(1, 7) * M(1, 3) / M(1, 1); 164 | b[4] -= b[1] * M(1, 4) / M(1, 1); M(4, 4) -= M(1, 4) * M(1, 4) / M(1, 1); M(4, 5) -= M(1, 5) * M(1, 4) / M(1, 1); M(4, 6) -= M(1, 6) * M(1, 4) / M(1, 1); M(4, 7) -= M(1, 7) * M(1, 4) / M(1, 1); 165 | b[5] -= b[1] * M(1, 5) / M(1, 1); M(5, 5) -= M(1, 5) * M(1, 5) / M(1, 1); M(5, 6) -= M(1, 6) * M(1, 5) / M(1, 1); M(5, 7) -= M(1, 7) * M(1, 5) / M(1, 1); 166 | b[6] -= b[1] * M(1, 6) / M(1, 1); M(6, 6) -= M(1, 6) * M(1, 6) / M(1, 1); M(6, 7) -= M(1, 7) * M(1, 6) / M(1, 1); 167 | b[7] -= b[1] * M(1, 7) / M(1, 1); M(7, 7) -= M(1, 7) * M(1, 7) / M(1, 1); 168 | 169 | b[3] -= b[2] * M(2, 3) / M(2, 2); M(3, 3) -= M(2, 3) * M(2, 3) / M(2, 2); M(3, 4) -= M(2, 4) * M(2, 3) / M(2, 2); M(3, 5) -= M(2, 5) * M(2, 3) / M(2, 2); M(3, 6) -= M(2, 6) * M(2, 3) / M(2, 2); M(3, 7) -= M(2, 7) * M(2, 3) / M(2, 2); 170 | b[4] -= b[2] * M(2, 4) / M(2, 2); M(4, 4) -= M(2, 4) * M(2, 4) / M(2, 2); M(4, 5) -= M(2, 5) * M(2, 4) / M(2, 2); M(4, 6) -= M(2, 6) * M(2, 4) / M(2, 2); M(4, 7) -= M(2, 7) * M(2, 4) / M(2, 2); 171 | b[5] -= b[2] * M(2, 5) / M(2, 2); M(5, 5) -= M(2, 5) * M(2, 5) / M(2, 2); M(5, 6) -= M(2, 6) * M(2, 5) / M(2, 2); M(5, 7) -= M(2, 7) * M(2, 5) / M(2, 2); 172 | b[6] -= b[2] * M(2, 6) / M(2, 2); M(6, 6) -= M(2, 6) * M(2, 6) / M(2, 2); M(6, 7) -= M(2, 7) * M(2, 6) / M(2, 2); 173 | b[7] -= b[2] * M(2, 7) / M(2, 2); M(7, 7) -= M(2, 7) * M(2, 7) / M(2, 2); 174 | 175 | b[4] -= b[3] * M(3, 4) / M(3, 3); M(4, 4) -= M(3, 4) * M(3, 4) / M(3, 3); M(4, 5) -= M(3, 5) * M(3, 4) / M(3, 3); M(4, 6) -= M(3, 6) * M(3, 4) / M(3, 3); M(4, 7) -= M(3, 7) * M(3, 4) / M(3, 3); 176 | b[5] -= b[3] * M(3, 5) / M(3, 3); M(5, 5) -= M(3, 5) * M(3, 5) / M(3, 3); M(5, 6) -= M(3, 6) * M(3, 5) / M(3, 3); M(5, 7) -= M(3, 7) * M(3, 5) / M(3, 3); 177 | b[6] -= b[3] * M(3, 6) / M(3, 3); M(6, 6) -= M(3, 6) * M(3, 6) / M(3, 3); M(6, 7) -= M(3, 7) * M(3, 6) / M(3, 3); 178 | b[7] -= b[3] * M(3, 7) / M(3, 3); M(7, 7) -= M(3, 7) * M(3, 7) / M(3, 3); 179 | 180 | b[5] -= b[4] * M(4, 5) / M(4, 4); M(5, 5) -= M(4, 5) * M(4, 5) / M(4, 4); M(5, 6) -= M(4, 6) * M(4, 5) / M(4, 4); M(5, 7) -= M(4, 7) * M(4, 5) / M(4, 4); 181 | b[6] -= b[4] * M(4, 6) / M(4, 4); M(6, 6) -= M(4, 6) * M(4, 6) / M(4, 4); M(6, 7) -= M(4, 7) * M(4, 6) / M(4, 4); 182 | b[7] -= b[4] * M(4, 7) / M(4, 4); M(7, 7) -= M(4, 7) * M(4, 7) / M(4, 4); 183 | 184 | b[6] -= b[5] * M(5, 6) / M(5, 5); M(6, 6) -= M(5, 6) * M(5, 6) / M(5, 5); M(6, 7) -= M(5, 7) * M(5, 6) / M(5, 5); 185 | b[7] -= b[5] * M(5, 7) / M(5, 5); M(7, 7) -= M(5, 7) * M(5, 7) / M(5, 5); 186 | 187 | b[7] -= b[6] * M(6, 7) / M(6, 6); M(7, 7) -= M(6, 7) * M(6, 7) / M(6, 6); 188 | 189 | b[7] /= M(7, 7); 190 | interp += b[7] * (X[7] - X[N]).zw; 191 | 192 | b[6] -= M(6, 7) * b[7]; b[6] /= M(6, 6); 193 | interp += b[6] * (X[6] - X[N]).zw; 194 | 195 | b[5] -= M(5, 6) * b[6]; b[5] -= M(5, 7) * b[7]; b[5] /= M(5, 5); 196 | interp += b[5] * (X[5] - X[N]).zw; 197 | 198 | b[4] -= M(4, 5) * b[5]; b[4] -= M(4, 6) * b[6]; b[4] -= M(4, 7) * b[7]; b[4] /= M(4, 4); 199 | interp += b[4] * (X[4] - X[N]).zw; 200 | 201 | b[3] -= M(3, 4) * b[4]; b[3] -= M(3, 5) * b[5]; b[3] -= M(3, 6) * b[6]; b[3] -= M(3, 7) * b[7]; b[3] /= M(3, 3); 202 | interp += b[3] * (X[3] - X[N]).zw; 203 | 204 | b[2] -= M(2, 3) * b[3]; b[2] -= M(2, 4) * b[4]; b[2] -= M(2, 5) * b[5]; b[2] -= M(2, 6) * b[6]; b[2] -= M(2, 7) * b[7]; b[2] /= M(2, 2); 205 | interp += b[2] * (X[2] - X[N]).zw; 206 | 207 | b[1] -= M(1, 2) * b[2]; b[1] -= M(1, 3) * b[3]; b[1] -= M(1, 4) * b[4]; b[1] -= M(1, 5) * b[5]; b[1] -= M(1, 6) * b[6]; b[1] -= M(1, 7) * b[7]; b[1] /= M(1, 1); 208 | interp += b[1] * (X[1] - X[N]).zw; 209 | 210 | b[0] -= M(0, 1) * b[1]; b[0] -= M(0, 2) * b[2]; b[0] -= M(0, 3) * b[3]; b[0] -= M(0, 4) * b[4]; b[0] -= M(0, 5) * b[5]; b[0] -= M(0, 6) * b[6]; b[0] -= M(0, 7) * b[7]; b[0] /= M(0, 0); 211 | interp += b[0] * (X[0] - X[N]).zw; 212 | 213 | return interp.xyxy; 214 | } 215 | -------------------------------------------------------------------------------- /shaders/NVSharpen.glsl: -------------------------------------------------------------------------------- 1 | // The MIT License(MIT) 2 | // 3 | // Copyright(c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files(the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of 9 | // the Software, and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions : 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR 18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | // NVIDIA Image Scaling v1.0.2 by NVIDIA 23 | // ported to mpv by agyild 24 | 25 | // Changelog 26 | // Made it directly operate on LUMA plane, since the original shader was operating 27 | // on LUMA by deriving it from RGB. 28 | 29 | //!HOOK LUMA 30 | //!BIND HOOKED 31 | //!DESC NVIDIA Image Sharpening v1.0.2 32 | //!COMPUTE 32 32 256 1 33 | //!WHEN OUTPUT.w OUTPUT.h * LUMA.w LUMA.h * / 1.0 > ! OUTPUT.w OUTPUT.h * LUMA.w LUMA.h * / 1.0 < ! * 34 | 35 | // User variables 36 | #define SHARPNESS 0.25 // Amount of sharpening. 0.0 to 1.0. 37 | #define NIS_THREAD_GROUP_SIZE 256 // May be set to 128 for better performance on NVIDIA hardware, otherwise set to 256. Don't forget to modify the COMPUTE directive accordingly as well (e.g., COMPUTE 32 32 128 1). 38 | #define NIS_HDR_MODE 0 // Must be set to 1 for content with PQ colorspace. 0 or 1. 39 | 40 | // Constant variables 41 | #define NIS_BLOCK_WIDTH 32 42 | #define NIS_BLOCK_HEIGHT 32 43 | #define kSupportSize 5 44 | #define kNumPixelsX (NIS_BLOCK_WIDTH + kSupportSize + 1) 45 | #define kNumPixelsY (NIS_BLOCK_HEIGHT + kSupportSize + 1) 46 | const float sharpen_slider = clamp(SHARPNESS, 0.0f, 1.0f) - 0.5f; 47 | const float MaxScale = (sharpen_slider >= 0.0f) ? 1.25f : 1.75f; 48 | const float MinScale = (sharpen_slider >= 0.0f) ? 1.25f : 1.0f; 49 | const float LimitScale = (sharpen_slider >= 0.0f) ? 1.25f : 1.0f; 50 | const float kDetectRatio = 2 * 1127.f / 1024.f; 51 | const float kDetectThres = (bool(NIS_HDR_MODE) ? 32.0f : 64.0f) / 1024.0f; 52 | const float kMinContrastRatio = bool(NIS_HDR_MODE) ? 1.5f : 2.0f; 53 | const float kMaxContrastRatio = bool(NIS_HDR_MODE) ? 5.0f : 10.0f; 54 | const float kSharpStartY = bool(NIS_HDR_MODE) ? 0.35f : 0.45f; 55 | const float kSharpEndY = bool(NIS_HDR_MODE) ? 0.55f : 0.9f; 56 | const float kSharpStrengthMin = max(0.0f, 0.4f + sharpen_slider * MinScale * (bool(NIS_HDR_MODE) ? 1.1f : 1.2)); 57 | const float kSharpStrengthMax = ((bool(NIS_HDR_MODE) ? 2.2f : 1.6f) + sharpen_slider * MaxScale * 1.8f); 58 | const float kSharpLimitMin = max((bool(NIS_HDR_MODE) ? 0.06f :0.1f), (bool(NIS_HDR_MODE) ? 0.1f : 0.14f) + sharpen_slider * LimitScale * (bool(NIS_HDR_MODE) ? 0.28f : 0.32f)); // 59 | const float kSharpLimitMax = ((bool(NIS_HDR_MODE) ? 0.6f : 0.5f) + sharpen_slider * LimitScale * 0.6f); 60 | const float kRatioNorm = 1.0f / (kMaxContrastRatio - kMinContrastRatio); 61 | const float kSharpScaleY = 1.0f / (kSharpEndY - kSharpStartY); 62 | const float kSharpStrengthScale = kSharpStrengthMax - kSharpStrengthMin; 63 | const float kSharpLimitScale = kSharpLimitMax - kSharpLimitMin; 64 | const float kContrastBoost = 1.0f; 65 | const float kEps = 1.0f / 255.0f; 66 | #define kSrcNormX HOOKED_pt.x 67 | #define kSrcNormY HOOKED_pt.y 68 | #define kDstNormX kSrcNormX 69 | #define kDstNormY kSrcNormY 70 | 71 | // HLSL to GLSL macros 72 | #define saturate(x) clamp(x, 0, 1) 73 | #define lerp(a, b, x) mix(a, b, x) 74 | 75 | // CS Shared variables 76 | shared float shPixelsY[kNumPixelsY][kNumPixelsX]; 77 | 78 | // Shader code 79 | 80 | vec4 GetEdgeMap(float p[5][5], int i, int j) { 81 | const float g_0 = abs(p[0 + i][0 + j] + p[0 + i][1 + j] + p[0 + i][2 + j] - p[2 + i][0 + j] - p[2 + i][1 + j] - p[2 + i][2 + j]); 82 | const float g_45 = abs(p[1 + i][0 + j] + p[0 + i][0 + j] + p[0 + i][1 + j] - p[2 + i][1 + j] - p[2 + i][2 + j] - p[1 + i][2 + j]); 83 | const float g_90 = abs(p[0 + i][0 + j] + p[1 + i][0 + j] + p[2 + i][0 + j] - p[0 + i][2 + j] - p[1 + i][2 + j] - p[2 + i][2 + j]); 84 | const float g_135 = abs(p[1 + i][0 + j] + p[2 + i][0 + j] + p[2 + i][1 + j] - p[0 + i][1 + j] - p[0 + i][2 + j] - p[1 + i][2 + j]); 85 | 86 | const float g_0_90_max = max(g_0, g_90); 87 | const float g_0_90_min = min(g_0, g_90); 88 | const float g_45_135_max = max(g_45, g_135); 89 | const float g_45_135_min = min(g_45, g_135); 90 | 91 | float e_0_90 = 0; 92 | float e_45_135 = 0; 93 | 94 | if (g_0_90_max + g_45_135_max == 0) 95 | { 96 | return vec4(0, 0, 0, 0); 97 | } 98 | 99 | e_0_90 = min(g_0_90_max / (g_0_90_max + g_45_135_max), 1.0f); 100 | e_45_135 = 1.0f - e_0_90; 101 | 102 | bool c_0_90 = (g_0_90_max > (g_0_90_min * kDetectRatio)) && (g_0_90_max > kDetectThres) && (g_0_90_max > g_45_135_min); 103 | bool c_45_135 = (g_45_135_max > (g_45_135_min * kDetectRatio)) && (g_45_135_max > kDetectThres) && (g_45_135_max > g_0_90_min); 104 | bool c_g_0_90 = g_0_90_max == g_0; 105 | bool c_g_45_135 = g_45_135_max == g_45; 106 | 107 | float f_e_0_90 = (c_0_90 && c_45_135) ? e_0_90 : 1.0f; 108 | float f_e_45_135 = (c_0_90 && c_45_135) ? e_45_135 : 1.0f; 109 | 110 | float weight_0 = (c_0_90 && c_g_0_90) ? f_e_0_90 : 0.0f; 111 | float weight_90 = (c_0_90 && !c_g_0_90) ? f_e_0_90 : 0.0f; 112 | float weight_45 = (c_45_135 && c_g_45_135) ? f_e_45_135 : 0.0f; 113 | float weight_135 = (c_45_135 && !c_g_45_135) ? f_e_45_135 : 0.0f; 114 | 115 | return vec4(weight_0, weight_90, weight_45, weight_135); 116 | } 117 | 118 | float CalcLTIFast(const float y[5]) { 119 | const float a_min = min(min(y[0], y[1]), y[2]); 120 | const float a_max = max(max(y[0], y[1]), y[2]); 121 | 122 | const float b_min = min(min(y[2], y[3]), y[4]); 123 | const float b_max = max(max(y[2], y[3]), y[4]); 124 | 125 | const float a_cont = a_max - a_min; 126 | const float b_cont = b_max - b_min; 127 | 128 | const float cont_ratio = max(a_cont, b_cont) / (min(a_cont, b_cont) + kEps); 129 | return (1.0f - saturate((cont_ratio - kMinContrastRatio) * kRatioNorm)) * kContrastBoost; 130 | } 131 | 132 | float EvalUSM(const float pxl[5], const float sharpnessStrength, const float sharpnessLimit) { 133 | // USM profile 134 | float y_usm = -0.6001f * pxl[1] + 1.2002f * pxl[2] - 0.6001f * pxl[3]; 135 | // boost USM profile 136 | y_usm *= sharpnessStrength; 137 | // clamp to the limit 138 | y_usm = min(sharpnessLimit, max(-sharpnessLimit, y_usm)); 139 | // reduce ringing 140 | y_usm *= CalcLTIFast(pxl); 141 | 142 | return y_usm; 143 | } 144 | 145 | vec4 GetDirUSM(const float p[5][5]) { 146 | // sharpness boost & limit are the same for all directions 147 | const float scaleY = 1.0f - saturate((p[2][2] - kSharpStartY) * kSharpScaleY); 148 | // scale the ramp to sharpen as a function of luma 149 | const float sharpnessStrength = scaleY * kSharpStrengthScale + kSharpStrengthMin; 150 | // scale the ramp to limit USM as a function of luma 151 | const float sharpnessLimit = (scaleY * kSharpLimitScale + kSharpLimitMin) * p[2][2]; 152 | 153 | vec4 rval; 154 | // 0 deg filter 155 | float interp0Deg[5]; 156 | { 157 | for (int i = 0; i < 5; ++i) 158 | { 159 | interp0Deg[i] = p[i][2]; 160 | } 161 | } 162 | 163 | rval.x = EvalUSM(interp0Deg, sharpnessStrength, sharpnessLimit); 164 | 165 | // 90 deg filter 166 | float interp90Deg[5]; 167 | { 168 | for (int i = 0; i < 5; ++i) 169 | { 170 | interp90Deg[i] = p[2][i]; 171 | } 172 | } 173 | 174 | rval.y = EvalUSM(interp90Deg, sharpnessStrength, sharpnessLimit); 175 | 176 | //45 deg filter 177 | float interp45Deg[5]; 178 | interp45Deg[0] = p[1][1]; 179 | interp45Deg[1] = lerp(p[2][1], p[1][2], 0.5f); 180 | interp45Deg[2] = p[2][2]; 181 | interp45Deg[3] = lerp(p[3][2], p[2][3], 0.5f); 182 | interp45Deg[4] = p[3][3]; 183 | 184 | rval.z = EvalUSM(interp45Deg, sharpnessStrength, sharpnessLimit); 185 | 186 | //135 deg filter 187 | float interp135Deg[5]; 188 | interp135Deg[0] = p[3][1]; 189 | interp135Deg[1] = lerp(p[3][2], p[2][1], 0.5f); 190 | interp135Deg[2] = p[2][2]; 191 | interp135Deg[3] = lerp(p[2][3], p[1][2], 0.5f); 192 | interp135Deg[4] = p[1][3]; 193 | 194 | rval.w = EvalUSM(interp135Deg, sharpnessStrength, sharpnessLimit); 195 | return rval; 196 | } 197 | 198 | void hook() { 199 | uvec2 blockIdx = gl_WorkGroupID.xy; 200 | uint threadIdx = gl_LocalInvocationID.x; 201 | 202 | const int dstBlockX = int(NIS_BLOCK_WIDTH * blockIdx.x); 203 | const int dstBlockY = int(NIS_BLOCK_HEIGHT * blockIdx.y); 204 | 205 | // fill in input luma tile in batches of 2x2 pixels 206 | // we use texture gather to get extra support necessary 207 | // to compute 2x2 edge map outputs too 208 | const float kShift = 0.5f - kSupportSize / 2; 209 | 210 | for (int i = int(threadIdx) * 2; i < kNumPixelsX * kNumPixelsY / 2; i += NIS_THREAD_GROUP_SIZE * 2) { 211 | uvec2 pos = uvec2(uint(i) % uint(kNumPixelsX), uint(i) / uint(kNumPixelsX) * 2); 212 | 213 | for (int dy = 0; dy < 2; dy++) { 214 | for (int dx = 0; dx < 2; dx++) { 215 | const float tx = (dstBlockX + pos.x + dx + kShift) * kSrcNormX; 216 | const float ty = (dstBlockY + pos.y + dy + kShift) * kSrcNormY; 217 | const float px = HOOKED_tex(vec2(tx, ty)).r; 218 | shPixelsY[pos.y + dy][pos.x + dx] = px; 219 | } 220 | } 221 | } 222 | 223 | groupMemoryBarrier(); 224 | barrier(); 225 | 226 | for (int k = int(threadIdx); k < NIS_BLOCK_WIDTH * NIS_BLOCK_HEIGHT; k += NIS_THREAD_GROUP_SIZE) 227 | { 228 | const ivec2 pos = ivec2(uint(k) % uint(NIS_BLOCK_WIDTH), uint(k) / uint(NIS_BLOCK_WIDTH)); 229 | 230 | // load 5x5 support to regs 231 | float p[5][5]; 232 | 233 | for (int i = 0; i < 5; ++i) 234 | { 235 | for (int j = 0; j < 5; ++j) 236 | { 237 | p[i][j] = shPixelsY[pos.y + i][pos.x + j]; 238 | } 239 | } 240 | 241 | // get directional filter bank output 242 | vec4 dirUSM = GetDirUSM(p); 243 | 244 | // generate weights for directional filters 245 | vec4 w = GetEdgeMap(p, kSupportSize / 2 - 1, kSupportSize / 2 - 1); 246 | 247 | // final USM is a weighted sum filter outputs 248 | const float usmY = (dirUSM.x * w.x + dirUSM.y * w.y + dirUSM.z * w.z + dirUSM.w * w.w); 249 | 250 | // do bilinear tap and correct luma texel so it produces new sharpened luma 251 | const int dstX = dstBlockX + pos.x; 252 | const int dstY = dstBlockY + pos.y; 253 | 254 | vec4 op = HOOKED_tex(vec2((dstX + 0.5f) * kDstNormX, (dstY + 0.5f) * kDstNormY)); 255 | op.x += usmY; 256 | 257 | imageStore(out_image, ivec2(dstX, dstY), op); 258 | } 259 | } -------------------------------------------------------------------------------- /shaders/SSimDownscaler.glsl: -------------------------------------------------------------------------------- 1 | // This library is free software; you can redistribute it and/or 2 | // modify it under the terms of the GNU Lesser General Public 3 | // License as published by the Free Software Foundation; either 4 | // version 3.0 of the License, or (at your option) any later version. 5 | // 6 | // This library is distributed in the hope that it will be useful, 7 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | // Lesser General Public License for more details. 10 | // 11 | // You should have received a copy of the GNU Lesser General Public 12 | // License along with this library. 13 | 14 | //!HOOK POSTKERNEL 15 | //!BIND HOOKED 16 | //!BIND PREKERNEL 17 | //!SAVE L2 18 | //!WIDTH NATIVE_CROPPED.w 19 | //!WHEN NATIVE_CROPPED.h POSTKERNEL.h > 20 | //!COMPONENTS 3 21 | //!DESC SSimDownscaler L2 pass 1 22 | 23 | #define axis 1 24 | 25 | #define offset vec2(0,0) 26 | 27 | #define MN(B,C,x) (x < 1.0 ? ((2.-1.5*B-(C))*x + (-3.+2.*B+C))*x*x + (1.-(B)/3.) : (((-(B)/6.-(C))*x + (B+5.*C))*x + (-2.*B-8.*C))*x+((4./3.)*B+4.*C)) 28 | #define Kernel(x) MN(.0, .5, abs(x)) 29 | #define taps 2.0 30 | 31 | vec4 hook() { 32 | vec2 base = PREKERNEL_pt * (PREKERNEL_pos * input_size + tex_offset); 33 | 34 | float low = ceil((PREKERNEL_pos - taps*POSTKERNEL_pt) * input_size - offset + tex_offset - 0.5)[axis]; 35 | float high = floor((PREKERNEL_pos + taps*POSTKERNEL_pt) * input_size - offset + tex_offset - 0.5)[axis]; 36 | 37 | float W = 0.0; 38 | vec4 avg = vec4(0); 39 | vec2 pos = base; 40 | 41 | for (float k = low; k <= high; k++) { 42 | pos[axis] = PREKERNEL_pt[axis] * (k - offset[axis] + 0.5); 43 | float rel = (pos[axis] - base[axis])*POSTKERNEL_size[axis]; 44 | float w = Kernel(rel); 45 | 46 | vec4 tex = textureLod(PREKERNEL_raw, pos, 0.0) * PREKERNEL_mul; 47 | avg += w * tex * tex; 48 | W += w; 49 | } 50 | avg /= W; 51 | 52 | return avg; 53 | } 54 | 55 | //!HOOK POSTKERNEL 56 | //!BIND HOOKED 57 | //!BIND L2 58 | //!SAVE L2 59 | //!WHEN NATIVE_CROPPED.w POSTKERNEL.w > 60 | //!COMPONENTS 3 61 | //!DESC SSimDownscaler L2 pass 2 62 | 63 | #define axis 0 64 | 65 | #define offset vec2(0,0) 66 | 67 | #define MN(B,C,x) (x < 1.0 ? ((2.-1.5*B-(C))*x + (-3.+2.*B+C))*x*x + (1.-(B)/3.) : (((-(B)/6.-(C))*x + (B+5.*C))*x + (-2.*B-8.*C))*x+((4./3.)*B+4.*C)) 68 | #define Kernel(x) MN(.0, .5, abs(x)) 69 | #define taps 2.0 70 | 71 | vec4 hook() { 72 | float low = ceil((L2_pos - taps*POSTKERNEL_pt) * L2_size - offset - 0.5)[axis]; 73 | float high = floor((L2_pos + taps*POSTKERNEL_pt) * L2_size - offset - 0.5)[axis]; 74 | 75 | float W = 0.0; 76 | vec4 avg = vec4(0); 77 | vec2 pos = L2_pos; 78 | 79 | for (float k = low; k <= high; k++) { 80 | pos[axis] = L2_pt[axis] * (k - offset[axis] + 0.5); 81 | float rel = (pos[axis] - L2_pos[axis])*POSTKERNEL_size[axis]; 82 | float w = Kernel(rel); 83 | 84 | avg += w * textureLod(L2_raw, pos, 0.0) * L2_mul; 85 | W += w; 86 | } 87 | avg /= W; 88 | 89 | return avg; 90 | } 91 | 92 | //!HOOK POSTKERNEL 93 | //!BIND HOOKED 94 | //!BIND L2 95 | //!SAVE MR 96 | //!WHEN NATIVE_CROPPED.h POSTKERNEL.h > 97 | //!COMPONENTS 4 98 | //!DESC SSimDownscaler mean & R 99 | 100 | #define oversharp 0.0 101 | 102 | #define sigma_nsq 10. / (255.*255.) 103 | #define locality 2.0 104 | 105 | #define offset vec2(0,0) 106 | 107 | #define Kernel(x) pow(1.0 / locality, abs(x)) 108 | #define taps 3.0 109 | 110 | #define Luma(rgb) ( dot(rgb, vec3(0.2126, 0.7152, 0.0722)) ) 111 | 112 | mat3x3 ScaleH(vec2 pos) { 113 | float low = ceil(-0.5*taps - offset)[0]; 114 | float high = floor(0.5*taps - offset)[0]; 115 | 116 | float W = 0.0; 117 | mat3x3 avg = mat3x3(0); 118 | 119 | for (float k = low; k <= high; k++) { 120 | pos[0] = HOOKED_pos[0] + HOOKED_pt[0] * k; 121 | float rel = k + offset[0]; 122 | float w = Kernel(rel); 123 | 124 | vec3 L = POSTKERNEL_tex(pos).rgb; 125 | avg += w * mat3x3(L, L*L, L2_tex(pos).rgb); 126 | W += w; 127 | } 128 | avg /= W; 129 | 130 | return avg; 131 | } 132 | 133 | vec4 hook() { 134 | vec2 pos = HOOKED_pos; 135 | 136 | float low = ceil(-0.5*taps - offset)[1]; 137 | float high = floor(0.5*taps - offset)[1]; 138 | 139 | float W = 0.0; 140 | mat3x3 avg = mat3x3(0); 141 | 142 | for (float k = low; k <= high; k++) { 143 | pos[1] = HOOKED_pos[1] + HOOKED_pt[1] * k; 144 | float rel = k + offset[1]; 145 | float w = Kernel(rel); 146 | 147 | avg += w * ScaleH(pos); 148 | W += w; 149 | } 150 | avg /= W; 151 | 152 | float Sl = Luma(max(avg[1] - avg[0] * avg[0], 0.)); 153 | float Sh = Luma(max(avg[2] - avg[0] * avg[0], 0.)); 154 | return vec4(avg[0], mix(sqrt((Sh + sigma_nsq) / (Sl + sigma_nsq)) * (1. + oversharp), clamp(Sh / Sl, 0., 1.), int(Sl > Sh))); 155 | } 156 | 157 | //!HOOK POSTKERNEL 158 | //!BIND HOOKED 159 | //!BIND MR 160 | //!WHEN NATIVE_CROPPED.h POSTKERNEL.h > 161 | //!DESC SSimDownscaler final pass 162 | 163 | #define locality 2.0 164 | 165 | #define offset vec2(0,0) 166 | 167 | #define Kernel(x) pow(1.0 / locality, abs(x)) 168 | #define taps 3.0 169 | 170 | #define Gamma(x) ( pow(x, vec3(1.0/2.0)) ) 171 | #define GammaInv(x) ( pow(clamp(x, 0.0, 1.0), vec3(2.0)) ) 172 | 173 | mat3x3 ScaleH(vec2 pos) { 174 | float low = ceil(-0.5*taps - offset)[0]; 175 | float high = floor(0.5*taps - offset)[0]; 176 | 177 | float W = 0.0; 178 | mat3x3 avg = mat3x3(0); 179 | 180 | for (float k = low; k <= high; k++) { 181 | pos[0] = HOOKED_pos[0] + HOOKED_pt[0] * k; 182 | float rel = k + offset[0]; 183 | float w = Kernel(rel); 184 | 185 | vec4 MR = MR_tex(pos); 186 | avg += w * mat3x3(MR.a*MR.rgb, MR.rgb, MR.aaa); 187 | W += w; 188 | } 189 | avg /= W; 190 | 191 | return avg; 192 | } 193 | 194 | vec4 hook() { 195 | vec2 pos = HOOKED_pos; 196 | 197 | float low = ceil(-0.5*taps - offset)[1]; 198 | float high = floor(0.5*taps - offset)[1]; 199 | 200 | float W = 0.0; 201 | mat3x3 avg = mat3x3(0); 202 | 203 | for (float k = low; k <= high; k++) { 204 | pos[1] = HOOKED_pos[1] + HOOKED_pt[1] * k; 205 | float rel = k + offset[1]; 206 | float w = Kernel(rel); 207 | 208 | avg += w * ScaleH(pos); 209 | W += w; 210 | } 211 | avg /= W; 212 | vec4 L = POSTKERNEL_texOff(0); 213 | return vec4(avg[1] + avg[2] * L.rgb - avg[0], L.a); 214 | } 215 | -------------------------------------------------------------------------------- /shaders/SSimSuperRes.glsl: -------------------------------------------------------------------------------- 1 | // SSimSuperRes by Shiandow 2 | // 3 | // This library is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU Lesser General Public 5 | // License as published by the Free Software Foundation; either 6 | // version 3.0 of the License, or (at your option) any later version. 7 | // 8 | // This library is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | // Lesser General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU Lesser General Public 14 | // License along with this library. 15 | 16 | //!HOOK POSTKERNEL 17 | //!BIND HOOKED 18 | //!SAVE LOWRES 19 | //!HEIGHT NATIVE_CROPPED.h 20 | //!WHEN NATIVE_CROPPED.h OUTPUT.h < 21 | //!COMPONENTS 4 22 | //!DESC SSSR Downscaling I 23 | 24 | #define axis 1 25 | 26 | #define offset vec2(0,0) 27 | 28 | #define MN(B,C,x) (x < 1.0 ? ((2.-1.5*B-(C))*x + (-3.+2.*B+C))*x*x + (1.-(B)/3.) : (((-(B)/6.-(C))*x + (B+5.*C))*x + (-2.*B-8.*C))*x+((4./3.)*B+4.*C)) 29 | #define Kernel(x) MN(0.334, 0.333, abs(x)) 30 | #define taps 2.0 31 | 32 | #define Luma(rgb) dot(rgb*rgb, vec3(0.2126, 0.7152, 0.0722)) 33 | 34 | vec4 hook() { 35 | float low = ceil((HOOKED_pos - taps/input_size) * HOOKED_size - offset - 0.5)[axis]; 36 | float high = floor((HOOKED_pos + taps/input_size) * HOOKED_size - offset - 0.5)[axis]; 37 | 38 | float W = 0.0; 39 | vec4 avg = vec4(0); 40 | vec2 pos = HOOKED_pos; 41 | vec4 tex; 42 | 43 | for (float k = low; k <= high; k++) { 44 | pos[axis] = HOOKED_pt[axis] * (k - offset[axis] + 0.5); 45 | float rel = (pos[axis] - HOOKED_pos[axis])*input_size[axis]; 46 | float w = Kernel(rel); 47 | 48 | tex.rgb = textureLod(HOOKED_raw, pos, 0.0).rgb * HOOKED_mul; 49 | tex.a = Luma(tex.rgb); 50 | avg += w * tex; 51 | W += w; 52 | } 53 | avg /= W; 54 | 55 | return vec4(avg.rgb, max(abs(avg.a - Luma(avg.rgb)), 5e-7)); 56 | } 57 | 58 | //!HOOK POSTKERNEL 59 | //!BIND LOWRES 60 | //!SAVE LOWRES 61 | //!WIDTH NATIVE_CROPPED.w 62 | //!HEIGHT NATIVE_CROPPED.h 63 | //!WHEN NATIVE_CROPPED.w OUTPUT.w < 64 | //!COMPONENTS 4 65 | //!DESC SSSR Downscaling II 66 | 67 | #define axis 0 68 | 69 | #define offset vec2(0,0) 70 | 71 | #define MN(B,C,x) (x < 1.0 ? ((2.-1.5*B-(C))*x + (-3.+2.*B+C))*x*x + (1.-(B)/3.) : (((-(B)/6.-(C))*x + (B+5.*C))*x + (-2.*B-8.*C))*x+((4./3.)*B+4.*C)) 72 | #define Kernel(x) MN(0.334, 0.333, abs(x)) 73 | #define taps 2.0 74 | 75 | #define Luma(rgb) dot(rgb*rgb, vec3(0.2126, 0.7152, 0.0722)) 76 | 77 | vec4 hook() { 78 | float low = ceil((LOWRES_pos - taps/input_size) * LOWRES_size - offset - 0.5)[axis]; 79 | float high = floor((LOWRES_pos + taps/input_size) * LOWRES_size - offset - 0.5)[axis]; 80 | 81 | float W = 0.0; 82 | vec4 avg = vec4(0); 83 | vec2 pos = LOWRES_pos; 84 | vec4 tex; 85 | 86 | for (float k = low; k <= high; k++) { 87 | pos[axis] = LOWRES_pt[axis] * (k - offset[axis] + 0.5); 88 | float rel = (pos[axis] - LOWRES_pos[axis])*input_size[axis]; 89 | float w = Kernel(rel); 90 | 91 | tex.rgb = textureLod(LOWRES_raw, pos, 0.0).rgb * LOWRES_mul; 92 | tex.a = Luma(tex.rgb); 93 | avg += w * tex; 94 | W += w; 95 | } 96 | avg /= W; 97 | 98 | return vec4(avg.rgb, max(abs(avg.a - Luma(avg.rgb)), 5e-7) + LOWRES_texOff(0).a); 99 | } 100 | 101 | //!HOOK POSTKERNEL 102 | //!BIND PREKERNEL 103 | //!BIND LOWRES 104 | //!SAVE var 105 | //!WIDTH NATIVE_CROPPED.w 106 | //!HEIGHT NATIVE_CROPPED.h 107 | //!WHEN NATIVE_CROPPED.h OUTPUT.h < 108 | //!COMPONENTS 2 109 | //!DESC SSSR var 110 | 111 | #define spread 1.0 / 4.0 112 | 113 | #define GetL(x,y) PREKERNEL_tex(PREKERNEL_pt * (PREKERNEL_pos * input_size + tex_offset + vec2(x,y))).rgb 114 | #define GetH(x,y) LOWRES_texOff(vec2(x,y)).rgb 115 | 116 | #define Luma(rgb) dot(rgb*rgb, vec3(0.2126, 0.7152, 0.0722)) 117 | #define diff(x,y) vec2(Luma((GetL(x,y) - meanL)), Luma((GetH(x,y) - meanH))) 118 | 119 | vec4 hook() { 120 | vec3 meanL = GetL(0,0); 121 | vec3 meanH = GetH(0,0); 122 | for (int X=-1; X<=1; X+=2) { 123 | meanL += GetL(X,0) * spread; 124 | meanH += GetH(X,0) * spread; 125 | } 126 | for (int Y=-1; Y<=1; Y+=2) { 127 | meanL += GetL(0,Y) * spread; 128 | meanH += GetH(0,Y) * spread; 129 | } 130 | meanL /= (1.0 + 4.0*spread); 131 | meanH /= (1.0 + 4.0*spread); 132 | 133 | vec2 var = diff(0,0); 134 | for (int X=-1; X<=1; X+=2) 135 | var += diff(X,0) * spread; 136 | 137 | for (int Y=-1; Y<=1; Y+=2) 138 | var += diff(0,Y) * spread; 139 | 140 | return vec4(max(var / (1.0 + 4.0*spread), vec2(1e-6)), 0, 0); 141 | } 142 | 143 | //!HOOK POSTKERNEL 144 | //!BIND HOOKED 145 | //!BIND PREKERNEL 146 | //!BIND LOWRES 147 | //!BIND var 148 | //!WHEN NATIVE_CROPPED.h OUTPUT.h < 149 | //!DESC SSSR final pass 150 | 151 | #define oversharp 0.5 152 | 153 | // -- Window Size -- 154 | #define taps 3.0 155 | #define even (taps - 2.0 * floor(taps / 2.0) == 0.0) 156 | #define minX int(1.0-ceil(taps/2.0)) 157 | #define maxX int(floor(taps/2.0)) 158 | 159 | #define Kernel(x) cos(acos(-1.0)*(x)/taps) // Hann kernel 160 | 161 | // -- Input processing -- 162 | #define var(x,y) var_tex(var_pt * (pos + vec2(x,y) + 0.5)).rg 163 | #define GetL(x,y) PREKERNEL_tex(PREKERNEL_pt * (pos + tex_offset + vec2(x,y) + 0.5)).rgb 164 | #define GetH(x,y) LOWRES_tex(LOWRES_pt * (pos + vec2(x,y) + 0.5)) 165 | 166 | #define Luma(rgb) dot(rgb*rgb, vec3(0.2126, 0.7152, 0.0722)) 167 | 168 | vec4 hook() { 169 | vec4 c0 = HOOKED_texOff(0); 170 | 171 | vec2 pos = HOOKED_pos * LOWRES_size - vec2(0.5); 172 | vec2 offset = pos - (even ? floor(pos) : round(pos)); 173 | pos -= offset; 174 | 175 | vec2 mVar = vec2(0.0); 176 | for (int X=-1; X<=1; X++) 177 | for (int Y=-1; Y<=1; Y++) { 178 | vec2 w = clamp(1.5 - abs(vec2(X,Y)), 0.0, 1.0); 179 | mVar += w.r * w.g * vec2(GetH(X,Y).a, 1.0); 180 | } 181 | mVar.r /= mVar.g; 182 | 183 | // Calculate faithfulness force 184 | float weightSum = 0.0; 185 | vec3 diff = vec3(0); 186 | 187 | for (int X = minX; X <= maxX; X++) 188 | for (int Y = minX; Y <= maxX; Y++) 189 | { 190 | float R = (-1.0 - oversharp) * sqrt(var(X,Y).r / (var(X,Y).g + mVar.r)); 191 | 192 | vec2 krnl = Kernel(vec2(X,Y) - offset); 193 | float weight = krnl.r * krnl.g / (Luma((c0.rgb - GetH(X,Y).rgb)) + GetH(X,Y).a); 194 | 195 | diff += weight * (GetL(X,Y) + GetH(X,Y).rgb * R + (-1.0 - R) * (c0.rgb)); 196 | weightSum += weight; 197 | } 198 | diff /= weightSum; 199 | 200 | c0.rgb = ((c0.rgb) + diff); 201 | 202 | return c0; 203 | } 204 | -------------------------------------------------------------------------------- /shaders/adaptive-sharpen.glsl: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2021, bacondither 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions 6 | // are met: 7 | // 1. Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer 9 | // in this position and unchanged. 10 | // 2. Redistributions in binary form must reproduce the above copyright 11 | // notice, this list of conditions and the following disclaimer in the 12 | // documentation and/or other materials provided with the distribution. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 15 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | // Adaptive sharpen - version 2021-10-17 26 | // Tuned for use post-resize 27 | 28 | //!HOOK OUTPUT 29 | //!BIND HOOKED 30 | //!DESC adaptive-sharpen 31 | 32 | //--------------------------------------- Settings ------------------------------------------------ 33 | 34 | #define curve_height 0.5 // Main control of sharpening strength [>0] 35 | // 0.3 <-> 2.0 is a reasonable range of values 36 | 37 | #define overshoot_ctrl false // Allow for higher overshoot if the current edge pixel 38 | // is surrounded by similar edge pixels 39 | 40 | // Defined values under this row are "optimal" DO NOT CHANGE IF YOU DO NOT KNOW WHAT YOU ARE DOING! 41 | 42 | #define curveslope 0.5 // Sharpening curve slope, high edge values 43 | 44 | #define L_compr_low 0.167 // Light compression, default (0.167=~6x) 45 | #define L_compr_high 0.334 // Light compression, surrounded by edges (0.334=~3x) 46 | 47 | #define D_compr_low 0.250 // Dark compression, default (0.250=4x) 48 | #define D_compr_high 0.500 // Dark compression, surrounded by edges (0.500=2x) 49 | 50 | #define scale_lim 0.1 // Abs max change before compression [>0.01] 51 | #define scale_cs 0.056 // Compression slope above scale_lim 52 | 53 | #define pm_p 1.0 // Power mean p-value [>0-1.0] 54 | //------------------------------------------------------------------------------------------------- 55 | 56 | #define max4(a,b,c,d) ( max(max(a, b), max(c, d)) ) 57 | 58 | // Soft if, fast linear approx 59 | #define soft_if(a,b,c) ( sat((a + b + c + 0.056/2.5)/(maxedge + 0.03/2.5) - 0.85) ) 60 | 61 | // Soft limit, modified tanh approx 62 | #define soft_lim(v,s) ( sat(abs(v/s)*(27.0 + pow(v/s, 2.0))/(27.0 + 9.0*pow(v/s, 2.0)))*s ) 63 | 64 | // Weighted power mean 65 | #define wpmean(a,b,w) ( pow(w*pow(abs(a), pm_p) + abs(1.0-w)*pow(abs(b), pm_p), (1.0/pm_p)) ) 66 | 67 | // Get destination pixel values 68 | #define get(x,y) ( HOOKED_texOff(vec2(x, y)).rgb ) 69 | #define sat(x) ( clamp(x, 0.0, 1.0) ) 70 | #define dxdy(val) ( length(fwidth(val)) ) // =~1/2.5 hq edge without c_comp 71 | 72 | #ifdef LUMA_tex 73 | #define CtL(RGB) RGB.x 74 | #else 75 | #define CtL(RGB) ( sqrt(dot(sat(RGB)*sat(RGB), vec3(0.2126, 0.7152, 0.0722))) ) 76 | #endif 77 | 78 | #define b_diff(pix) ( (blur-luma[pix])*(blur-luma[pix]) ) 79 | 80 | vec4 hook() { 81 | 82 | // [ c22 ] 83 | // [ c24, c9, c23 ] 84 | // [ c21, c1, c2, c3, c18 ] 85 | // [ c19, c10, c4, c0, c5, c11, c16 ] 86 | // [ c20, c6, c7, c8, c17 ] 87 | // [ c15, c12, c14 ] 88 | // [ c13 ] 89 | vec3 c[25] = vec3[](get( 0, 0), get(-1,-1), get( 0,-1), get( 1,-1), get(-1, 0), 90 | get( 1, 0), get(-1, 1), get( 0, 1), get( 1, 1), get( 0,-2), 91 | get(-2, 0), get( 2, 0), get( 0, 2), get( 0, 3), get( 1, 2), 92 | get(-1, 2), get( 3, 0), get( 2, 1), get( 2,-1), get(-3, 0), 93 | get(-2, 1), get(-2,-1), get( 0,-3), get( 1,-2), get(-1,-2)); 94 | 95 | float e[13] = float[](dxdy(c[0]), dxdy(c[1]), dxdy(c[2]), dxdy(c[3]), dxdy(c[4]), 96 | dxdy(c[5]), dxdy(c[6]), dxdy(c[7]), dxdy(c[8]), dxdy(c[9]), 97 | dxdy(c[10]), dxdy(c[11]), dxdy(c[12])); 98 | 99 | // RGB to luma 100 | float luma[25] = float[](CtL(c[0]), CtL(c[1]), CtL(c[2]), CtL(c[3]), CtL(c[4]), CtL(c[5]), CtL(c[6]), 101 | CtL(c[7]), CtL(c[8]), CtL(c[9]), CtL(c[10]), CtL(c[11]), CtL(c[12]), 102 | CtL(c[13]), CtL(c[14]), CtL(c[15]), CtL(c[16]), CtL(c[17]), CtL(c[18]), 103 | CtL(c[19]), CtL(c[20]), CtL(c[21]), CtL(c[22]), CtL(c[23]), CtL(c[24])); 104 | 105 | float c0_Y = luma[0]; 106 | 107 | // Blur, gauss 3x3 108 | float blur = (2.0 * (luma[2]+luma[4]+luma[5]+luma[7]) + (luma[1]+luma[3]+luma[6]+luma[8]) + 4.0 * luma[0]) / 16.0; 109 | 110 | // Contrast compression, center = 0.5 111 | float c_comp = sat(0.266666681f + 0.9*exp2(blur * blur * -7.4)); 112 | 113 | // Edge detection 114 | // Relative matrix weights 115 | // [ 1 ] 116 | // [ 4, 5, 4 ] 117 | // [ 1, 5, 6, 5, 1 ] 118 | // [ 4, 5, 4 ] 119 | // [ 1 ] 120 | float edge = ( 1.38*b_diff(0) 121 | + 1.15*(b_diff(2) + b_diff(4) + b_diff(5) + b_diff(7)) 122 | + 0.92*(b_diff(1) + b_diff(3) + b_diff(6) + b_diff(8)) 123 | + 0.23*(b_diff(9) + b_diff(10) + b_diff(11) + b_diff(12)) ) * c_comp; 124 | 125 | vec2 cs = vec2(L_compr_low, D_compr_low); 126 | 127 | if (overshoot_ctrl) { 128 | float maxedge = max4( max4(e[1],e[2],e[3],e[4]), max4(e[5],e[6],e[7],e[8]), 129 | max4(e[9],e[10],e[11],e[12]), e[0] ); 130 | 131 | // [ x ] 132 | // [ z, x, w ] 133 | // [ z, z, x, w, w ] 134 | // [ y, y, y, 0, y, y, y ] 135 | // [ w, w, x, z, z ] 136 | // [ w, x, z ] 137 | // [ x ] 138 | float sbe = soft_if(e[2],e[9], dxdy(c[22]))*soft_if(e[7],e[12],dxdy(c[13])) // x dir 139 | + soft_if(e[4],e[10],dxdy(c[19]))*soft_if(e[5],e[11],dxdy(c[16])) // y dir 140 | + soft_if(e[1],dxdy(c[24]),dxdy(c[21]))*soft_if(e[8],dxdy(c[14]),dxdy(c[17])) // z dir 141 | + soft_if(e[3],dxdy(c[23]),dxdy(c[18]))*soft_if(e[6],dxdy(c[20]),dxdy(c[15])); // w dir 142 | 143 | cs = mix(cs, vec2(L_compr_high, D_compr_high), sat(2.4002*sbe - 2.282)); 144 | } 145 | 146 | // Precalculated default squared kernel weights 147 | const vec3 w1 = vec3(0.5, 1.0, 1.41421356237); // 0.25, 1.0, 2.0 148 | const vec3 w2 = vec3(0.86602540378, 1.0, 0.54772255751); // 0.75, 1.0, 0.3 149 | 150 | // Transition to a concave kernel if the center edge val is above thr 151 | vec3 dW = pow(mix( w1, w2, sat(2.4*edge - 0.82)), vec3(2.0)); 152 | 153 | // Use lower weights for pixels in a more active area relative to center pixel area 154 | // This results in narrower and less visible overshoots around sharp edges 155 | float modif_e0 = 3.0 * e[0] + 0.02/2.5; 156 | 157 | float weights[12] = float[](( min(modif_e0/e[1], dW.y) ), 158 | ( dW.x ), 159 | ( min(modif_e0/e[3], dW.y) ), 160 | ( dW.x ), 161 | ( dW.x ), 162 | ( min(modif_e0/e[6], dW.y) ), 163 | ( dW.x ), 164 | ( min(modif_e0/e[8], dW.y) ), 165 | ( min(modif_e0/e[9], dW.z) ), 166 | ( min(modif_e0/e[10], dW.z) ), 167 | ( min(modif_e0/e[11], dW.z) ), 168 | ( min(modif_e0/e[12], dW.z) )); 169 | 170 | weights[0] = (max(max((weights[8] + weights[9])/4.0, weights[0]), 0.25) + weights[0])/2.0; 171 | weights[2] = (max(max((weights[8] + weights[10])/4.0, weights[2]), 0.25) + weights[2])/2.0; 172 | weights[5] = (max(max((weights[9] + weights[11])/4.0, weights[5]), 0.25) + weights[5])/2.0; 173 | weights[7] = (max(max((weights[10] + weights[11])/4.0, weights[7]), 0.25) + weights[7])/2.0; 174 | 175 | // Calculate the negative part of the laplace kernel and the low threshold weight 176 | float lowthrsum = 0.0; 177 | float weightsum = 0.0; 178 | float neg_laplace = 0.0; 179 | 180 | for (int pix = 0; pix < 12; ++pix) 181 | { 182 | float lowthr = sat((20.*4.5*c_comp*e[pix + 1] - 0.221)); 183 | 184 | neg_laplace += luma[pix+1] * luma[pix+1] * weights[pix] * lowthr; 185 | weightsum += weights[pix] * lowthr; 186 | lowthrsum += lowthr / 12.0; 187 | } 188 | 189 | neg_laplace = sqrt(neg_laplace / weightsum); 190 | 191 | // Compute sharpening magnitude function 192 | float sharpen_val = curve_height/(curve_height*curveslope*edge + 0.625); 193 | 194 | // Calculate sharpening diff and scale 195 | float sharpdiff = (c0_Y - neg_laplace)*(lowthrsum*sharpen_val + 0.01); 196 | 197 | // Calculate local near min & max, partial sort 198 | float temp; 199 | 200 | for (int i1 = 0; i1 < 24; i1 += 2) 201 | { 202 | temp = luma[i1]; 203 | luma[i1] = min(luma[i1], luma[i1+1]); 204 | luma[i1+1] = max(temp, luma[i1+1]); 205 | } 206 | 207 | for (int i2 = 24; i2 > 0; i2 -= 2) 208 | { 209 | temp = luma[0]; 210 | luma[0] = min(luma[0], luma[i2]); 211 | luma[i2] = max(temp, luma[i2]); 212 | 213 | temp = luma[24]; 214 | luma[24] = max(luma[24], luma[i2-1]); 215 | luma[i2-1] = min(temp, luma[i2-1]); 216 | } 217 | 218 | float min_dist = min(abs(luma[24] - c0_Y), abs(c0_Y - luma[0])); 219 | min_dist = min(min_dist, scale_lim*(1.0 - scale_cs) + min_dist*scale_cs); 220 | 221 | // Soft limited anti-ringing with tanh, wpmean to control compression slope 222 | sharpdiff = wpmean(max(sharpdiff, 0.0), soft_lim( max(sharpdiff, 0.0), min_dist ), cs.x ) 223 | - wpmean(min(sharpdiff, 0.0), soft_lim( min(sharpdiff, 0.0), min_dist ), cs.y ); 224 | 225 | float sharpdiff_lim = sat(c0_Y + sharpdiff) - c0_Y; 226 | /*float satmul = (c0_Y + max(sharpdiff_lim*0.9, sharpdiff_lim)*1.03 + 0.03)/(c0_Y + 0.03); 227 | vec3 res = c0_Y + sharpdiff_lim + (c[0] - c0_Y)*satmul; 228 | */ 229 | return vec4(sharpdiff_lim + c[0], HOOKED_texOff(0).a); 230 | } 231 | -------------------------------------------------------------------------------- /shaders/adaptive-sharpen4k.glsl: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2021, bacondither 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions 6 | // are met: 7 | // 1. Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer 9 | // in this position and unchanged. 10 | // 2. Redistributions in binary form must reproduce the above copyright 11 | // notice, this list of conditions and the following disclaimer in the 12 | // documentation and/or other materials provided with the distribution. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 15 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | // Adaptive sharpen - version 2021-10-17 26 | // Tuned for use post-resize 27 | 28 | //!HOOK OUTPUT 29 | //!BIND HOOKED 30 | //!DESC adaptive-sharpen 31 | 32 | //--------------------------------------- Settings ------------------------------------------------ 33 | 34 | #define curve_height 0.3 // Main control of sharpening strength [>0] 35 | // 0.3 <-> 2.0 is a reasonable range of values 36 | 37 | #define overshoot_ctrl false // Allow for higher overshoot if the current edge pixel 38 | // is surrounded by similar edge pixels 39 | 40 | // Defined values under this row are "optimal" DO NOT CHANGE IF YOU DO NOT KNOW WHAT YOU ARE DOING! 41 | 42 | #define curveslope 0.5 // Sharpening curve slope, high edge values 43 | 44 | #define L_compr_low 0.167 // Light compression, default (0.167=~6x) 45 | #define L_compr_high 0.334 // Light compression, surrounded by edges (0.334=~3x) 46 | 47 | #define D_compr_low 0.250 // Dark compression, default (0.250=4x) 48 | #define D_compr_high 0.500 // Dark compression, surrounded by edges (0.500=2x) 49 | 50 | #define scale_lim 0.1 // Abs max change before compression [>0.01] 51 | #define scale_cs 0.056 // Compression slope above scale_lim 52 | 53 | #define pm_p 1.0 // Power mean p-value [>0-1.0] 54 | //------------------------------------------------------------------------------------------------- 55 | 56 | #define max4(a,b,c,d) ( max(max(a, b), max(c, d)) ) 57 | 58 | // Soft if, fast linear approx 59 | #define soft_if(a,b,c) ( sat((a + b + c + 0.056/2.5)/(maxedge + 0.03/2.5) - 0.85) ) 60 | 61 | // Soft limit, modified tanh approx 62 | #define soft_lim(v,s) ( sat(abs(v/s)*(27.0 + pow(v/s, 2.0))/(27.0 + 9.0*pow(v/s, 2.0)))*s ) 63 | 64 | // Weighted power mean 65 | #define wpmean(a,b,w) ( pow(w*pow(abs(a), pm_p) + abs(1.0-w)*pow(abs(b), pm_p), (1.0/pm_p)) ) 66 | 67 | // Get destination pixel values 68 | #define get(x,y) ( HOOKED_texOff(vec2(x, y)).rgb ) 69 | #define sat(x) ( clamp(x, 0.0, 1.0) ) 70 | #define dxdy(val) ( length(fwidth(val)) ) // =~1/2.5 hq edge without c_comp 71 | 72 | #ifdef LUMA_tex 73 | #define CtL(RGB) RGB.x 74 | #else 75 | #define CtL(RGB) ( sqrt(dot(sat(RGB)*sat(RGB), vec3(0.2126, 0.7152, 0.0722))) ) 76 | #endif 77 | 78 | #define b_diff(pix) ( (blur-luma[pix])*(blur-luma[pix]) ) 79 | 80 | vec4 hook() { 81 | 82 | // [ c22 ] 83 | // [ c24, c9, c23 ] 84 | // [ c21, c1, c2, c3, c18 ] 85 | // [ c19, c10, c4, c0, c5, c11, c16 ] 86 | // [ c20, c6, c7, c8, c17 ] 87 | // [ c15, c12, c14 ] 88 | // [ c13 ] 89 | vec3 c[25] = vec3[](get( 0, 0), get(-1,-1), get( 0,-1), get( 1,-1), get(-1, 0), 90 | get( 1, 0), get(-1, 1), get( 0, 1), get( 1, 1), get( 0,-2), 91 | get(-2, 0), get( 2, 0), get( 0, 2), get( 0, 3), get( 1, 2), 92 | get(-1, 2), get( 3, 0), get( 2, 1), get( 2,-1), get(-3, 0), 93 | get(-2, 1), get(-2,-1), get( 0,-3), get( 1,-2), get(-1,-2)); 94 | 95 | float e[13] = float[](dxdy(c[0]), dxdy(c[1]), dxdy(c[2]), dxdy(c[3]), dxdy(c[4]), 96 | dxdy(c[5]), dxdy(c[6]), dxdy(c[7]), dxdy(c[8]), dxdy(c[9]), 97 | dxdy(c[10]), dxdy(c[11]), dxdy(c[12])); 98 | 99 | // RGB to luma 100 | float luma[25] = float[](CtL(c[0]), CtL(c[1]), CtL(c[2]), CtL(c[3]), CtL(c[4]), CtL(c[5]), CtL(c[6]), 101 | CtL(c[7]), CtL(c[8]), CtL(c[9]), CtL(c[10]), CtL(c[11]), CtL(c[12]), 102 | CtL(c[13]), CtL(c[14]), CtL(c[15]), CtL(c[16]), CtL(c[17]), CtL(c[18]), 103 | CtL(c[19]), CtL(c[20]), CtL(c[21]), CtL(c[22]), CtL(c[23]), CtL(c[24])); 104 | 105 | float c0_Y = luma[0]; 106 | 107 | // Blur, gauss 3x3 108 | float blur = (2.0 * (luma[2]+luma[4]+luma[5]+luma[7]) + (luma[1]+luma[3]+luma[6]+luma[8]) + 4.0 * luma[0]) / 16.0; 109 | 110 | // Contrast compression, center = 0.5 111 | float c_comp = sat(0.266666681f + 0.9*exp2(blur * blur * -7.4)); 112 | 113 | // Edge detection 114 | // Relative matrix weights 115 | // [ 1 ] 116 | // [ 4, 5, 4 ] 117 | // [ 1, 5, 6, 5, 1 ] 118 | // [ 4, 5, 4 ] 119 | // [ 1 ] 120 | float edge = ( 1.38*b_diff(0) 121 | + 1.15*(b_diff(2) + b_diff(4) + b_diff(5) + b_diff(7)) 122 | + 0.92*(b_diff(1) + b_diff(3) + b_diff(6) + b_diff(8)) 123 | + 0.23*(b_diff(9) + b_diff(10) + b_diff(11) + b_diff(12)) ) * c_comp; 124 | 125 | vec2 cs = vec2(L_compr_low, D_compr_low); 126 | 127 | if (overshoot_ctrl) { 128 | float maxedge = max4( max4(e[1],e[2],e[3],e[4]), max4(e[5],e[6],e[7],e[8]), 129 | max4(e[9],e[10],e[11],e[12]), e[0] ); 130 | 131 | // [ x ] 132 | // [ z, x, w ] 133 | // [ z, z, x, w, w ] 134 | // [ y, y, y, 0, y, y, y ] 135 | // [ w, w, x, z, z ] 136 | // [ w, x, z ] 137 | // [ x ] 138 | float sbe = soft_if(e[2],e[9], dxdy(c[22]))*soft_if(e[7],e[12],dxdy(c[13])) // x dir 139 | + soft_if(e[4],e[10],dxdy(c[19]))*soft_if(e[5],e[11],dxdy(c[16])) // y dir 140 | + soft_if(e[1],dxdy(c[24]),dxdy(c[21]))*soft_if(e[8],dxdy(c[14]),dxdy(c[17])) // z dir 141 | + soft_if(e[3],dxdy(c[23]),dxdy(c[18]))*soft_if(e[6],dxdy(c[20]),dxdy(c[15])); // w dir 142 | 143 | cs = mix(cs, vec2(L_compr_high, D_compr_high), sat(2.4002*sbe - 2.282)); 144 | } 145 | 146 | // Precalculated default squared kernel weights 147 | const vec3 w1 = vec3(0.5, 1.0, 1.41421356237); // 0.25, 1.0, 2.0 148 | const vec3 w2 = vec3(0.86602540378, 1.0, 0.54772255751); // 0.75, 1.0, 0.3 149 | 150 | // Transition to a concave kernel if the center edge val is above thr 151 | vec3 dW = pow(mix( w1, w2, sat(2.4*edge - 0.82)), vec3(2.0)); 152 | 153 | // Use lower weights for pixels in a more active area relative to center pixel area 154 | // This results in narrower and less visible overshoots around sharp edges 155 | float modif_e0 = 3.0 * e[0] + 0.02/2.5; 156 | 157 | float weights[12] = float[](( min(modif_e0/e[1], dW.y) ), 158 | ( dW.x ), 159 | ( min(modif_e0/e[3], dW.y) ), 160 | ( dW.x ), 161 | ( dW.x ), 162 | ( min(modif_e0/e[6], dW.y) ), 163 | ( dW.x ), 164 | ( min(modif_e0/e[8], dW.y) ), 165 | ( min(modif_e0/e[9], dW.z) ), 166 | ( min(modif_e0/e[10], dW.z) ), 167 | ( min(modif_e0/e[11], dW.z) ), 168 | ( min(modif_e0/e[12], dW.z) )); 169 | 170 | weights[0] = (max(max((weights[8] + weights[9])/4.0, weights[0]), 0.25) + weights[0])/2.0; 171 | weights[2] = (max(max((weights[8] + weights[10])/4.0, weights[2]), 0.25) + weights[2])/2.0; 172 | weights[5] = (max(max((weights[9] + weights[11])/4.0, weights[5]), 0.25) + weights[5])/2.0; 173 | weights[7] = (max(max((weights[10] + weights[11])/4.0, weights[7]), 0.25) + weights[7])/2.0; 174 | 175 | // Calculate the negative part of the laplace kernel and the low threshold weight 176 | float lowthrsum = 0.0; 177 | float weightsum = 0.0; 178 | float neg_laplace = 0.0; 179 | 180 | for (int pix = 0; pix < 12; ++pix) 181 | { 182 | float lowthr = sat((20.*4.5*c_comp*e[pix + 1] - 0.221)); 183 | 184 | neg_laplace += luma[pix+1] * luma[pix+1] * weights[pix] * lowthr; 185 | weightsum += weights[pix] * lowthr; 186 | lowthrsum += lowthr / 12.0; 187 | } 188 | 189 | neg_laplace = sqrt(neg_laplace / weightsum); 190 | 191 | // Compute sharpening magnitude function 192 | float sharpen_val = curve_height/(curve_height*curveslope*edge + 0.625); 193 | 194 | // Calculate sharpening diff and scale 195 | float sharpdiff = (c0_Y - neg_laplace)*(lowthrsum*sharpen_val + 0.01); 196 | 197 | // Calculate local near min & max, partial sort 198 | float temp; 199 | 200 | for (int i1 = 0; i1 < 24; i1 += 2) 201 | { 202 | temp = luma[i1]; 203 | luma[i1] = min(luma[i1], luma[i1+1]); 204 | luma[i1+1] = max(temp, luma[i1+1]); 205 | } 206 | 207 | for (int i2 = 24; i2 > 0; i2 -= 2) 208 | { 209 | temp = luma[0]; 210 | luma[0] = min(luma[0], luma[i2]); 211 | luma[i2] = max(temp, luma[i2]); 212 | 213 | temp = luma[24]; 214 | luma[24] = max(luma[24], luma[i2-1]); 215 | luma[i2-1] = min(temp, luma[i2-1]); 216 | } 217 | 218 | float min_dist = min(abs(luma[24] - c0_Y), abs(c0_Y - luma[0])); 219 | min_dist = min(min_dist, scale_lim*(1.0 - scale_cs) + min_dist*scale_cs); 220 | 221 | // Soft limited anti-ringing with tanh, wpmean to control compression slope 222 | sharpdiff = wpmean(max(sharpdiff, 0.0), soft_lim( max(sharpdiff, 0.0), min_dist ), cs.x ) 223 | - wpmean(min(sharpdiff, 0.0), soft_lim( min(sharpdiff, 0.0), min_dist ), cs.y ); 224 | 225 | float sharpdiff_lim = sat(c0_Y + sharpdiff) - c0_Y; 226 | /*float satmul = (c0_Y + max(sharpdiff_lim*0.9, sharpdiff_lim)*0.3 + 0.03)/(c0_Y + 0.03); 227 | vec3 res = c0_Y + sharpdiff_lim + (c[0] - c0_Y)*satmul; 228 | */ 229 | return vec4(sharpdiff_lim + c[0], HOOKED_texOff(0).a); 230 | } 231 | -------------------------------------------------------------------------------- /shaders/adaptive-sharpen8k.glsl: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2021, bacondither 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions 6 | // are met: 7 | // 1. Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer 9 | // in this position and unchanged. 10 | // 2. Redistributions in binary form must reproduce the above copyright 11 | // notice, this list of conditions and the following disclaimer in the 12 | // documentation and/or other materials provided with the distribution. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 15 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | // Adaptive sharpen - version 2021-10-17 26 | // Tuned for use post-resize 27 | 28 | //!HOOK OUTPUT 29 | //!BIND HOOKED 30 | //!DESC adaptive-sharpen 31 | 32 | //--------------------------------------- Settings ------------------------------------------------ 33 | 34 | #define curve_height 1.0 // Main control of sharpening strength [>0] 35 | // 0.3 <-> 2.0 is a reasonable range of values 36 | 37 | #define overshoot_ctrl false // Allow for higher overshoot if the current edge pixel 38 | // is surrounded by similar edge pixels 39 | 40 | // Defined values under this row are "optimal" DO NOT CHANGE IF YOU DO NOT KNOW WHAT YOU ARE DOING! 41 | 42 | #define curveslope 0.5 // Sharpening curve slope, high edge values 43 | 44 | #define L_compr_low 0.167 // Light compression, default (0.167=~6x) 45 | #define L_compr_high 0.334 // Light compression, surrounded by edges (0.334=~3x) 46 | 47 | #define D_compr_low 0.250 // Dark compression, default (0.250=4x) 48 | #define D_compr_high 0.500 // Dark compression, surrounded by edges (0.500=2x) 49 | 50 | #define scale_lim 0.1 // Abs max change before compression [>0.01] 51 | #define scale_cs 0.056 // Compression slope above scale_lim 52 | 53 | #define pm_p 1.0 // Power mean p-value [>0-1.0] 54 | //------------------------------------------------------------------------------------------------- 55 | 56 | #define max4(a,b,c,d) ( max(max(a, b), max(c, d)) ) 57 | 58 | // Soft if, fast linear approx 59 | #define soft_if(a,b,c) ( sat((a + b + c + 0.056/2.5)/(maxedge + 0.03/2.5) - 0.85) ) 60 | 61 | // Soft limit, modified tanh approx 62 | #define soft_lim(v,s) ( sat(abs(v/s)*(27.0 + pow(v/s, 2.0))/(27.0 + 9.0*pow(v/s, 2.0)))*s ) 63 | 64 | // Weighted power mean 65 | #define wpmean(a,b,w) ( pow(w*pow(abs(a), pm_p) + abs(1.0-w)*pow(abs(b), pm_p), (1.0/pm_p)) ) 66 | 67 | // Get destination pixel values 68 | #define get(x,y) ( HOOKED_texOff(vec2(x, y)).rgb ) 69 | #define sat(x) ( clamp(x, 0.0, 1.0) ) 70 | #define dxdy(val) ( length(fwidth(val)) ) // =~1/2.5 hq edge without c_comp 71 | 72 | #ifdef LUMA_tex 73 | #define CtL(RGB) RGB.x 74 | #else 75 | #define CtL(RGB) ( sqrt(dot(sat(RGB)*sat(RGB), vec3(0.2126, 0.7152, 0.0722))) ) 76 | #endif 77 | 78 | #define b_diff(pix) ( (blur-luma[pix])*(blur-luma[pix]) ) 79 | 80 | vec4 hook() { 81 | 82 | // [ c22 ] 83 | // [ c24, c9, c23 ] 84 | // [ c21, c1, c2, c3, c18 ] 85 | // [ c19, c10, c4, c0, c5, c11, c16 ] 86 | // [ c20, c6, c7, c8, c17 ] 87 | // [ c15, c12, c14 ] 88 | // [ c13 ] 89 | vec3 c[25] = vec3[](get( 0, 0), get(-1,-1), get( 0,-1), get( 1,-1), get(-1, 0), 90 | get( 1, 0), get(-1, 1), get( 0, 1), get( 1, 1), get( 0,-2), 91 | get(-2, 0), get( 2, 0), get( 0, 2), get( 0, 3), get( 1, 2), 92 | get(-1, 2), get( 3, 0), get( 2, 1), get( 2,-1), get(-3, 0), 93 | get(-2, 1), get(-2,-1), get( 0,-3), get( 1,-2), get(-1,-2)); 94 | 95 | float e[13] = float[](dxdy(c[0]), dxdy(c[1]), dxdy(c[2]), dxdy(c[3]), dxdy(c[4]), 96 | dxdy(c[5]), dxdy(c[6]), dxdy(c[7]), dxdy(c[8]), dxdy(c[9]), 97 | dxdy(c[10]), dxdy(c[11]), dxdy(c[12])); 98 | 99 | // RGB to luma 100 | float luma[25] = float[](CtL(c[0]), CtL(c[1]), CtL(c[2]), CtL(c[3]), CtL(c[4]), CtL(c[5]), CtL(c[6]), 101 | CtL(c[7]), CtL(c[8]), CtL(c[9]), CtL(c[10]), CtL(c[11]), CtL(c[12]), 102 | CtL(c[13]), CtL(c[14]), CtL(c[15]), CtL(c[16]), CtL(c[17]), CtL(c[18]), 103 | CtL(c[19]), CtL(c[20]), CtL(c[21]), CtL(c[22]), CtL(c[23]), CtL(c[24])); 104 | 105 | float c0_Y = luma[0]; 106 | 107 | // Blur, gauss 3x3 108 | float blur = (2.0 * (luma[2]+luma[4]+luma[5]+luma[7]) + (luma[1]+luma[3]+luma[6]+luma[8]) + 4.0 * luma[0]) / 16.0; 109 | 110 | // Contrast compression, center = 0.5 111 | float c_comp = sat(0.266666681f + 0.9*exp2(blur * blur * -7.4)); 112 | 113 | // Edge detection 114 | // Relative matrix weights 115 | // [ 1 ] 116 | // [ 4, 5, 4 ] 117 | // [ 1, 5, 6, 5, 1 ] 118 | // [ 4, 5, 4 ] 119 | // [ 1 ] 120 | float edge = ( 1.38*b_diff(0) 121 | + 1.15*(b_diff(2) + b_diff(4) + b_diff(5) + b_diff(7)) 122 | + 0.92*(b_diff(1) + b_diff(3) + b_diff(6) + b_diff(8)) 123 | + 0.23*(b_diff(9) + b_diff(10) + b_diff(11) + b_diff(12)) ) * c_comp; 124 | 125 | vec2 cs = vec2(L_compr_low, D_compr_low); 126 | 127 | if (overshoot_ctrl) { 128 | float maxedge = max4( max4(e[1],e[2],e[3],e[4]), max4(e[5],e[6],e[7],e[8]), 129 | max4(e[9],e[10],e[11],e[12]), e[0] ); 130 | 131 | // [ x ] 132 | // [ z, x, w ] 133 | // [ z, z, x, w, w ] 134 | // [ y, y, y, 0, y, y, y ] 135 | // [ w, w, x, z, z ] 136 | // [ w, x, z ] 137 | // [ x ] 138 | float sbe = soft_if(e[2],e[9], dxdy(c[22]))*soft_if(e[7],e[12],dxdy(c[13])) // x dir 139 | + soft_if(e[4],e[10],dxdy(c[19]))*soft_if(e[5],e[11],dxdy(c[16])) // y dir 140 | + soft_if(e[1],dxdy(c[24]),dxdy(c[21]))*soft_if(e[8],dxdy(c[14]),dxdy(c[17])) // z dir 141 | + soft_if(e[3],dxdy(c[23]),dxdy(c[18]))*soft_if(e[6],dxdy(c[20]),dxdy(c[15])); // w dir 142 | 143 | cs = mix(cs, vec2(L_compr_high, D_compr_high), sat(2.4002*sbe - 2.282)); 144 | } 145 | 146 | // Precalculated default squared kernel weights 147 | const vec3 w1 = vec3(0.5, 1.0, 1.41421356237); // 0.25, 1.0, 2.0 148 | const vec3 w2 = vec3(0.86602540378, 1.0, 0.54772255751); // 0.75, 1.0, 0.3 149 | 150 | // Transition to a concave kernel if the center edge val is above thr 151 | vec3 dW = pow(mix( w1, w2, sat(2.4*edge - 0.82)), vec3(2.0)); 152 | 153 | // Use lower weights for pixels in a more active area relative to center pixel area 154 | // This results in narrower and less visible overshoots around sharp edges 155 | float modif_e0 = 3.0 * e[0] + 0.02/2.5; 156 | 157 | float weights[12] = float[](( min(modif_e0/e[1], dW.y) ), 158 | ( dW.x ), 159 | ( min(modif_e0/e[3], dW.y) ), 160 | ( dW.x ), 161 | ( dW.x ), 162 | ( min(modif_e0/e[6], dW.y) ), 163 | ( dW.x ), 164 | ( min(modif_e0/e[8], dW.y) ), 165 | ( min(modif_e0/e[9], dW.z) ), 166 | ( min(modif_e0/e[10], dW.z) ), 167 | ( min(modif_e0/e[11], dW.z) ), 168 | ( min(modif_e0/e[12], dW.z) )); 169 | 170 | weights[0] = (max(max((weights[8] + weights[9])/4.0, weights[0]), 0.25) + weights[0])/2.0; 171 | weights[2] = (max(max((weights[8] + weights[10])/4.0, weights[2]), 0.25) + weights[2])/2.0; 172 | weights[5] = (max(max((weights[9] + weights[11])/4.0, weights[5]), 0.25) + weights[5])/2.0; 173 | weights[7] = (max(max((weights[10] + weights[11])/4.0, weights[7]), 0.25) + weights[7])/2.0; 174 | 175 | // Calculate the negative part of the laplace kernel and the low threshold weight 176 | float lowthrsum = 0.0; 177 | float weightsum = 0.0; 178 | float neg_laplace = 0.0; 179 | 180 | for (int pix = 0; pix < 12; ++pix) 181 | { 182 | float lowthr = sat((20.*4.5*c_comp*e[pix + 1] - 0.221)); 183 | 184 | neg_laplace += luma[pix+1] * luma[pix+1] * weights[pix] * lowthr; 185 | weightsum += weights[pix] * lowthr; 186 | lowthrsum += lowthr / 12.0; 187 | } 188 | 189 | neg_laplace = sqrt(neg_laplace / weightsum); 190 | 191 | // Compute sharpening magnitude function 192 | float sharpen_val = curve_height/(curve_height*curveslope*edge + 0.625); 193 | 194 | // Calculate sharpening diff and scale 195 | float sharpdiff = (c0_Y - neg_laplace)*(lowthrsum*sharpen_val + 0.01); 196 | 197 | // Calculate local near min & max, partial sort 198 | float temp; 199 | 200 | for (int i1 = 0; i1 < 24; i1 += 2) 201 | { 202 | temp = luma[i1]; 203 | luma[i1] = min(luma[i1], luma[i1+1]); 204 | luma[i1+1] = max(temp, luma[i1+1]); 205 | } 206 | 207 | for (int i2 = 24; i2 > 0; i2 -= 2) 208 | { 209 | temp = luma[0]; 210 | luma[0] = min(luma[0], luma[i2]); 211 | luma[i2] = max(temp, luma[i2]); 212 | 213 | temp = luma[24]; 214 | luma[24] = max(luma[24], luma[i2-1]); 215 | luma[i2-1] = min(temp, luma[i2-1]); 216 | } 217 | 218 | float min_dist = min(abs(luma[24] - c0_Y), abs(c0_Y - luma[0])); 219 | min_dist = min(min_dist, scale_lim*(1.0 - scale_cs) + min_dist*scale_cs); 220 | 221 | // Soft limited anti-ringing with tanh, wpmean to control compression slope 222 | sharpdiff = wpmean(max(sharpdiff, 0.0), soft_lim( max(sharpdiff, 0.0), min_dist ), cs.x ) 223 | - wpmean(min(sharpdiff, 0.0), soft_lim( min(sharpdiff, 0.0), min_dist ), cs.y ); 224 | 225 | float sharpdiff_lim = sat(c0_Y + sharpdiff) - c0_Y; 226 | /*float satmul = (c0_Y + max(sharpdiff_lim*0.9, sharpdiff_lim)*0.3 + 0.03)/(c0_Y + 0.03); 227 | vec3 res = c0_Y + sharpdiff_lim + (c[0] - c0_Y)*satmul; 228 | */ 229 | return vec4(sharpdiff_lim + c[0], HOOKED_texOff(0).a); 230 | } 231 | -------------------------------------------------------------------------------- /shaders/crt.glsl: -------------------------------------------------------------------------------- 1 | //!HOOK OUTPUT 2 | //!BIND OUTPUT 3 | //!BIND MAIN 4 | 5 | // Tunable parameters 6 | #define BRIGHT_BOOST 1.0 7 | #define DILATION 1.0 8 | #define GAMMA_INPUT 2.0 9 | #define GAMMA_OUTPUT 1.8 10 | #define MASK_SIZE 1.0 11 | #define MASK_STAGGER 0 12 | #define MASK_STRENGTH 0.3 13 | #define MASK_DOT_HEIGHT 1.0 14 | #define MASK_DOT_WIDTH 1 15 | #define SCANLINE_BEAM_WIDTH_MAX 1.5 16 | #define SCANLINE_BEAM_WIDTH_MIN 1.5 17 | #define SCANLINE_BRIGHT_MAX 0.65 18 | #define SCANLINE_BRIGHT_MIN 0.35 19 | #define SCANLINE_CUTOFF 400.0 20 | #define SCANLINE_STRENGTH 1.0 21 | #define SHARPNESS_H 0.5 22 | #define SHARPNESS_V 1.0 23 | 24 | #define PI 3.141592653589 25 | 26 | const vec2 sharp = vec2(SHARPNESS_H, SHARPNESS_V); 27 | 28 | vec4 hook() 29 | { 30 | vec2 fcoord = fract(MAIN_pos * MAIN_size - vec2(0.5)); 31 | vec2 base = MAIN_pos - MAIN_pt * fcoord; 32 | 33 | // Apply half-circle S-Curve to distance for sharper interpolation 34 | vec2 fstep = step(0.5, fcoord); 35 | vec2 fcurve = fcoord - fstep; 36 | fcurve = vec2(0.5) - sqrt(vec2(0.25) - fcurve * fcurve) * sign(vec2(0.5) - fcoord); 37 | fcoord = mix(fcoord, fcurve, sharp); 38 | 39 | vec4 color = MAIN_tex(base + MAIN_pt * fcoord); 40 | color.rgb = pow(color.rgb, vec3(GAMMA_INPUT / (DILATION + 1.0))); 41 | 42 | float luma = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)); 43 | float bright = (max(color.r, max(color.g, color.b)) + luma) / 2.0; 44 | float scan_bright = clamp(bright, SCANLINE_BRIGHT_MIN, SCANLINE_BRIGHT_MAX); 45 | float scan_beam = clamp(bright * SCANLINE_BEAM_WIDTH_MAX, SCANLINE_BEAM_WIDTH_MIN, SCANLINE_BEAM_WIDTH_MAX); 46 | float scan_weight = 1.0 - pow(cos(MAIN_pos.y * 2.0 * PI * MAIN_size.y) * 0.5 + 0.5, scan_beam) * SCANLINE_STRENGTH; 47 | 48 | if (MAIN_size.y >= SCANLINE_CUTOFF) 49 | scan_weight = 1.0; 50 | 51 | vec3 orig = color.rgb; 52 | color.rgb *= vec3(scan_weight); 53 | color.rgb = mix(color.rgb, orig, scan_bright); 54 | 55 | float mask = 1.0 - MASK_STRENGTH; 56 | ivec2 mod_fac = ivec2(MAIN_pos * OUTPUT_size / vec2(MASK_SIZE, MASK_DOT_HEIGHT * MASK_SIZE)); 57 | int dot_no = (mod_fac.x + (mod_fac.y % 2) * MASK_STAGGER) / MASK_DOT_WIDTH % 3; 58 | 59 | vec3 mask_weight = vec3(mask); 60 | mask_weight[dot_no] = 1.0; 61 | color.rgb *= mask_weight; 62 | 63 | color.rgb = pow(color.rgb, vec3(1.0 / GAMMA_OUTPUT)); 64 | color.rgb *= BRIGHT_BOOST; 65 | return color; 66 | } --------------------------------------------------------------------------------