├── 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 | 
9 | 
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 | }
--------------------------------------------------------------------------------