Compare commits

...

69 Commits

Author SHA1 Message Date
6eadea6f00 update to fork 2025-09-08 14:36:43 -07:00
05adde2559 update link 2025-09-08 14:06:22 -07:00
7579628faf update input.conf 2025-09-08 14:04:55 -07:00
5581723522 revert back to fork 2025-09-08 14:04:39 -07:00
b6390baec6 update submodule 2025-09-07 15:40:24 -07:00
21998241ba update mpvacious submodule and add keybinds to input.conf 2025-09-03 20:37:53 -07:00
57f6a5db30 update 2025-09-03 20:20:32 -07:00
5a6ac4bb3a Remove mpvacious submodule 2025-09-03 20:14:37 -07:00
5f08f09b2c add ytdl-preload script-opts file 2025-08-19 00:24:59 -07:00
9819a64f01 add immersion tracker 2025-08-19 00:16:22 -07:00
73a305af0b update mpv config 2025-08-17 17:31:36 -07:00
9ac84aba18 update submodule paths 2025-08-17 17:31:31 -07:00
cfc6ac22e5 Stage new submodule locations 2025-08-17 16:59:41 -07:00
e4afe79832 Move submodules to submodules directory 2025-08-17 16:57:56 -07:00
ca0899d233 enable watch history 2025-05-27 00:30:17 -07:00
deb591481e update configs 2025-05-27 00:30:00 -07:00
472899b680 update mac conf 2025-05-26 04:32:13 -07:00
2f3ccc60fc update mac conf 2025-05-26 04:19:01 -07:00
02d3c00229 update anilist to submodule 2025-05-26 02:19:45 -07:00
f3a4afcf76 update 2025-05-26 01:32:12 -07:00
087f16dfd0 update subs2srs 2025-05-24 21:23:18 -07:00
0b9f814eca add back mpvacious 2025-05-02 01:07:28 -07:00
efb9737e14 remove mpvacious 2025-05-01 19:38:05 -07:00
56e477bff1 update modernz 2025-04-28 23:48:50 -07:00
04fabf6356 add other shaders 2025-04-12 01:18:10 -07:00
d9747459d8 update config 2025-04-10 17:44:19 -07:00
33ea15228e update 2025-04-08 23:46:52 -07:00
1cd68861f5 add prettier file 2025-04-06 23:02:51 -07:00
2c90b30993 add jimaku script and update subsync 2025-04-06 23:02:06 -07:00
382c29ba42 update to alass 2025-04-06 22:25:05 -07:00
37b67be69b update 2025-04-05 19:47:37 -07:00
2ed3918aca update config 2025-04-05 19:46:45 -07:00
f0237be035 update script to use virtual environment 2025-04-04 03:43:22 -07:00
5260564aaf add mpvacious 2025-04-04 00:23:45 -07:00
1c6904a8a5 Merge branch 'master' of gitea.suda.codes:sudacode/mpv 2025-04-03 21:31:33 -07:00
e0f195e0ee update colors 2025-04-03 21:31:29 -07:00
76ddb33630 add laptop 2025-03-27 22:01:13 -07:00
7df9682378 replace immersive with animecard 2025-03-27 21:11:03 -07:00
0c7677b6ab update immersive 2025-03-25 19:06:26 -07:00
1f74dad64a remove c file 2025-03-18 03:29:34 -07:00
fc734fcffe Merge branch 'master' of gitea.suda.codes:sudacode/mpv 2025-03-18 03:26:35 -07:00
3c8cc50e97 add mpris 2025-03-18 03:26:26 -07:00
cbdca67613 update config and add fontconfig 2025-03-17 00:15:16 -07:00
3f0308189d update 2025-03-14 18:02:01 -07:00
81471617f2 add mpv anilist 2025-03-11 01:57:43 -07:00
ksyasuda
c3555288a9 update immersive config 2025-03-10 17:57:26 -07:00
ksyasuda
c2e9ae47e1 update 2025-03-10 02:07:25 -07:00
ksyasuda
afa6c8e2c0 update 2025-03-07 21:07:39 -08:00
ksyasuda
8ded1b4f0c update 2025-03-06 02:00:38 -08:00
ksyasuda
9a193ac5c3 update 2025-03-04 22:00:59 -08:00
ksyasuda
6e64e0c70f add immersive 2025-03-04 02:25:17 -08:00
ksyasuda
f596071c87 update 2025-03-03 23:41:05 -08:00
ksyasuda
7904708a42 add mpv_websocket 2025-03-03 22:33:17 -08:00
ksyasuda
829141ccd9 update 2025-02-25 01:52:56 -08:00
ksyasuda
0f23f6f7e6 update 2025-02-25 01:52:46 -08:00
ksyasuda
7088ac357f update 2025-02-20 13:53:57 -08:00
ksyasuda
57e17054b0 update 2025-02-20 13:52:58 -08:00
ksyasuda
701ca424eb update 2025-02-20 13:08:45 -08:00
ksyasuda
059283f217 update 2025-02-15 14:01:30 -08:00
ksyasuda
f3a3f91da3 update 2025-02-15 13:57:15 -08:00
ksyasuda
9aa407538e Merge branch 'master' of gitea.suda.codes:sudacode/mpv 2025-02-15 13:57:01 -08:00
ksyasuda
9e4077de96 update 2025-02-15 13:56:58 -08:00
fccac9d3da Merge branch 'master' of gitea.suda.codes:sudacode/mpv 2025-02-15 13:51:45 -08:00
65b60a75ea add yt-dlp preload submodule 2025-02-15 13:50:30 -08:00
ksyasuda
9e43e9f88d update 2025-02-15 01:34:02 -08:00
ksyasuda
b82b16cf92 update 2025-02-14 20:18:32 -08:00
ksyasuda
8fb0bdf1d0 update 2025-01-20 16:23:33 -08:00
ksyasuda
7d55c2fb7a update 2025-01-20 16:17:22 -08:00
ksyasuda
51ecbeab02 remove youtube-search 2025-01-20 16:01:00 -08:00
54 changed files with 12756 additions and 3912 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
immersive-data
.ytdl_preload/
.ytdl-preload/
scripts/anilistUpdater/anilistToken.txt
.watch-later
state

32
.gitmodules vendored
View File

@@ -1,13 +1,31 @@
[submodule "mpv-youtube-upnext"] [submodule "mpv-youtube-upnext"]
path = mpv-youtube-upnext path = submodules/mpv-youtube-upnext
url = https://github.com/ksyasuda/mpv-youtube-upnext url = https://github.com/ksyasuda/mpv-youtube-upnext
[submodule "mpv-osc-modern"]
path = mpv-osc-modern
url = https://github.com/ksyasuda/mpv-osc-modern
branch = with.thumbfast
[submodule "mpv-youtube-queue"] [submodule "mpv-youtube-queue"]
path = mpv-youtube-queue path = submodules/mpv-youtube-queue
url = https://github.com/ksyasuda/mpv-youtube-queue url = https://github.com/ksyasuda/mpv-youtube-queue
[submodule "ModernZ"] [submodule "ModernZ"]
path = ModernZ path = submodules/ModernZ
url = git@github.com:Samillion/ModernZ.git url = git@github.com:Samillion/ModernZ.git
[submodule "ytdl-preload"]
path = submodules/ytdl-preload
url = git@gist.github.com:17d90e3deeb35b5f75e55adb19098f58.git
[submodule "mpv-anilist-updater"]
path = submodules/mpv-anilist-updater
url = git@github.com:AzuredBlue/mpv-anilist-updater.git
[submodule "thumbfast"]
path = submodules/thumbfast
url = git@github.com:po5/thumbfast.git
[submodule "submodules/autosubsync-mpv"]
path = submodules/autosubsync-mpv
url = git@github.com:joaquintorres/autosubsync-mpv.git
[submodule "submodules/immersion-tracker"]
path = submodules/immersion-tracker
url = git@gitea.suda.codes:sudacode/immersion-tracker.git
[submodule "submodules/mpvacious"]
path = submodules/mpvacious
url = git@github.com:ksyasuda/mpvacious.git
[submodule "submodules/animecards"]
path = submodules/animecards
url = git@github.com:ksyasuda/Anacreon-Script

2
.prettierrc Normal file
View File

@@ -0,0 +1,2 @@
trailingComma: "es5"
singleQuote: true

Submodule ModernZ deleted from a03db11405

View File

@@ -189,12 +189,93 @@
# ? cycle program # cycle transport stream programs # ? cycle program # cycle transport stream programs
# ? stop # stop playback (quit or enter idle mode) # ? stop # stop playback (quit or enter idle mode)
# n cycle_values af loudnorm=I=-30 loudnorm=I=-15 anull # n cycle_values af loudnorm=I=-30 loudnorm=I=-15 anull
CTRL+1 no-osd change-list glsl-shaders set "~~/shaders/FSR.glsl"; show-text "FSR" CTRL+1 no-osd change-list glsl-shaders set "~/.config/mpv/shaders/FSR.glsl"; show-text "FSR"
CTRL+2 no-osd change-list glsl-shaders set "~~/shaders/NVScaler.glsl"; show-text "NIS" CTRL+2 no-osd change-list glsl-shaders set "~/.config/mpv/shaders/NVScaler.glsl"; show-text "NIS"
CTRL+3 no-osd change-list glsl-shaders set "~~/shaders/CAS-scaled.glsl"; show-text "CAS" CTRL+3 no-osd change-list glsl-shaders set "~/.config/mpv/shaders/CAS-scaled.glsl"; show-text "CAS"
CTRL+4 no-osd change-list glsl-shaders set "~~/shaders/FSRCNNX.glsl"; show-text "FSRCNNX" CTRL+4 no-osd change-list glsl-shaders set "~/.config/mpv/shaders/FSRCNNX.glsl"; show-text "FSRCNNX"
CTRL+5 no-osd change-list glsl-shaders set "~/.config/mpv/shaders/ArtCNN_C4F32.glsl"; show-text "ArtCNN"
CTRL+6 no-osd change-list glsl-shaders set "~/.config/mpv/shaders/ArtCNN_C4F16_DS.glsl"; show-text "ArtCNN"
CTRL+7 no-osd change-list glsl-shaders set "~/.config/mpv/shaders/ArtCNN_C4F16.glsl"; show-text "ArtCNN"
CTRL+0 no-osd change-list glsl-shaders clr ""; show-text "GLSL shaders cleared" CTRL+0 no-osd change-list glsl-shaders clr ""; show-text "GLSL shaders cleared"
ctrl+K cycle-values keep-open "yes" "no" ctrl+K cycle-values keep-open "yes" "no"
ctrl+r script-binding reload-scripts ctrl+r script-binding reload-scripts
ctrl+s script_binding autosubsync-menu
# {{{ sponsorblock
ctrl+g script-binding sponsorblock/set_segment
ctrl+G script-binding sponsorblock/submit_segment
ctrl+< script-binding sponsorblock/upvote_setment
ctrl+> script-binding sponsorblock/downvote_segment
#}}}
# {{{ anilist
ctrl+A script-binding update_anilist
ctrl+B script-binding launch_anilist
ctrl+E script-binding open_folder
ctrl+V script-binding mpvacious-secondary-sid-toggle
# }}}
# {{{ mpvacious (subs2srs)
a script-binding mpvacious-menu-open
ctrl+g ignore # script-binding mpvacious-animated-snapshot-toggle
ctrl+N script-binding mpvacious-export-note
ctrl+b ignore # script-binding mpvacious-update-selected-note
ctrl+B ignore # script-binding mpvacious-overwrite-selected-note
ctrl+m ignore # script-binding mpvacious-update-last-note
ctrl+M ignore # script-binding mpvacious-overwrite-last-note
G script-binding mpvacious-quick-card-menu-open
alt+g script-binding mpvacious-quick-card-sel-menu-open
ctrl+c script-binding mpvacious-copy-primary-sub-to-clipboard
ctrl+C script-binding mpvacious-copy-secondary-sub-to-clipboard
ctrl+= script-binding mpvacious-autocopy-toggle
H script-binding mpvacious-sub-seek-back
L script-binding mpvacious-sub-seek-forward
alt+h script-binding mpvacious-sub-seek-back-pause
alt+l script-binding mpvacious-sub-seek-forward-pause
ctrl+h script-binding mpvacious-sub-rewind
ctrl+H script-binding mpvacious-sub-replay
ctrl+L script-binding mpvacious-sub-play-up-to-next
ctrl+V script-binding mpvacious-secondary-sid-toggle
ctrl+K script-binding mpvacious-secondary-sid-prev
ctrl+J script-binding mpvacious-secondary-sid-next
# }}}
# {{ animecards
ctrl+v script-binding animecards/update-anki-card
# }}
# {{{ mpv-youtube-queue
ctrl+a script-binding mpv_youtube_queue/add_to_queue
ctrl+n script-binding mpv_youtube_queue/play_next_in_queue
ctrl+p script-binding mpv_youtube_queue/play_previous_in_queue
ctrl+q script-binding mpv_youtube_queue/print_queue
ctrl+k script-binding mpv_youtube_queue/move_cursor_up
ctrl+j script-binding mpv_youtube_queue/move_cursor_down
ctrl+ENTER script-binding mpv_youtube_queue/play_selected_video
ctrl+o script-binding mpv_youtube_queue/open_video_in_browser
ctrl+P script-binding mpv_youtube_queue/print_current_video
ctrl+O script-binding mpv_youtube_queue/open_channel_in_browser
ctrl+d script-binding mpv_youtube_queue/download_current_video
ctrl+D script-binding mpv_youtube_queue/download_selected_video
ctrl+m script-binding mpv_youtube_queue/move_video
ctrl+x script-binding mpv_youtube_queue/delete_video
#}}}
#{{{ MPV SELECT
g ignore
g-a script-binding select/select-aid
g-b script-binding select/select-binding
g-c script-binding select/select-chapter
g-e script-binding select/select-edition
g-h script-binding select/select-watch-history
g-l script-binding select/select-subtitle-line
g-m script-binding select/menu
g-p script-binding select/select-playlist
g-r script-binding select/show-properties
g-s script-binding select/select-sid
g-S script-binding select/select-secondary-sid
g-t script-binding select/select-track
g-v script-binding select/select-vid
g-w script-binding select/select-watch-later
# }}}

243
mac.conf Normal file
View File

@@ -0,0 +1,243 @@
#
# Example mpv configuration file
#
# Warning:
#
# The commented example options usually do _not_ set the default values. Call
# mpv with --list-options to see the default values for most options. There is
# no builtin or example mpv.conf with all the defaults.
#
#
# Configuration files are read system-wide from /usr/local/etc/mpv.conf
# and per-user from ~~/mpv.conf, where per-user settings override
# system-wide settings, all of which are overridden by the command line.
#
# Configuration file settings and the command line options use the same
# underlying mechanisms. Most options can be put into the configuration file
# by dropping the preceding '--'. See the man page for a complete list of
# options.
#
# Lines starting with '#' are comments and are ignored.
#
# See the CONFIGURATION FILES section in the man page
# for a detailed description of the syntax.
#
# Profiles should be placed at the bottom of the configuration file to ensure
# that settings wanted as defaults are not restricted to specific profiles.
##################
# video settings #
##################
# Start in fullscreen mode by default.
#fs=yes
# force starting with centered window
# geometry=50%:50%
# don't allow a new window to have a size larger than 90% of the screen size
#autofit-larger=90%x90%
# Do not close the window on exit.
#keep-open=yes
# Do not wait with showing the video window until it has loaded. (This will
# resize the window once video is loaded. Also always shows a window with
# audio.)
#force-window=immediate
# Disable the On Screen Controller (OSC).
# osc=no
# Keep the player window on top of all other windows.
# window=scale=1.0
# Specify high quality video rendering preset (for --vo=gpu only)
# Can cause performance problems with some drivers and GPUs.
# profile=gpu-hq
# Force video to lock on the display's refresh rate, and change video and audio
# speed to some degree to ensure synchronous playback - can cause problems
# with some drivers and desktop environments.
#video-sync=display-resample
# Enable hardware decoding if available. Often, this does not work with all
# video outputs, but should work well with default settings on most systems.
# If performance or energy usage is an issue, forcing the vdpau or vaapi VOs
# may or may not help.
# discourged by mpv devs and not likely to make significant difference
# hwdec=auto-copy
# hwdec-codecs=all
##################
# audio settings #
##################
# Specify default audio device. You can list devices with: --audio-device=help
# The option takes the device string (the stuff between the '...').
#audio-device=alsa/default
# Do not filter audio to keep pitch when changing playback speed.
#audio-pitch-correction=no
# Output 5.1 audio natively, and upmix/downmix audio with a different format.
#audio-channels=5.1
# Disable any automatic remix, _if_ the audio output accepts the audio format.
# of the currently played file. See caveats mentioned in the manpage.
# (The default is "auto-safe", see manpage.)
#audio-channels=auto
##################
# other settings #
##################
# Pretend to be a web browser. Might fix playback with some streaming sites,
# but also will break with shoutcast streams.
# user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
# user-agent="Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36"
# user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/37.0.2062.94 Chrome/37.0.2062.94 Safari/537.36"
# user-agent="Chromium/37.0.2062.94 Chrome/37.0.2062.94 Safari/537.36"
# cache settings
#
# Use a large seekable RAM cache even for local input.
cache=yes
#
# Use extra large RAM cache (needs cache=yes to make it useful).
demuxer-max-bytes=500M
demuxer-max-back-bytes=100M
#
# Disable the behavior that the player will pause if the cache goes below a
# certain fill size.
cache-pause=no
#
# Store cache payload on the hard disk instead of in RAM. (This may negatively
# impact performance unless used for slow input such as network.)
#cache-dir=~/.cache/
#cache-on-disk=yes
# Display English subtitles if available.
#slang=en
# Play Finnish audio if available, fall back to English otherwise.
#alang=fi,en
# Change subtitle encoding. For Arabic subtitles use 'cp1256'.
# If the file seems to be valid UTF-8, prefer UTF-8.
# (You can add '+' in front of the codepage to force it.)
#sub-codepage=cp1256
# You can also include other configuration files.
#include=/path/to/the/file/you/want/to/include
############
# Profiles #
############
# The options declared as part of profiles override global default settings,
# but only take effect when the profile is active.
# The following profile can be enabled on the command line with: --profile=eye-cancer
#[eye-cancer]
#sharpen=5
sub-font="JetBrainsMono Nerd Font"
sub-font-size=45
# osd-font="Fluent System Icons"
border=no
geometry=50%
volume=50
# speed-step=0.05
# audio-spdif=ac3,eac3,dts-hd,truehd
# glsl-shaders="~~/shaders/Anime4K_Clamp_Highlights.glsl:~~/shaders/Anime4K_Restore_CNN_VL.glsl:~~/shaders/Anime4K_Upscale_CNN_x2_VL.glsl:~~/shaders/Anime4K_AutoDownscalePre_x2.glsl:~~/shaders/Anime4K_AutoDownscalePre_x4.glsl:~~/shaders/Anime4K_Upscale_CNN_x2_M.glsl"
glsl-shaders="~~/shaders/ArtCNN_C4F16.glsl"
# glsl-shaders="~~/shaders/FSR.glsl"
# Can fix stuttering in some cases, in other cases probably causes it. Try it if you experience stuttering.
opengl-early-flush=no
video-sync=display-resample
osc=no
no-border
ytdl-raw-options=sub-langs=en.*,write-auto-subs=
ytdl-format=bestvideo+bestaudio/best
# get subtitles for videos automatically
sub-auto=fuzzy
slang=en,eng
# CATPPUCCIN MACHIATTO
# Main mpv options
background-color='#24273a'
osd-back-color='#181926'
osd-border-color='#181926'
osd-color='#cad3f5'
osd-shadow-color='#24273a'
# Stats script options
# Options are on separate lines for clarity
# Colors are in #BBGGRR format
script-opts-append=stats-border_color=30201e
script-opts-append=stats-font_color=f5d3ca
script-opts-append=stats-plot_bg_border_color=f8bdb7
script-opts-append=stats-plot_bg_color=30201e
script-opts-append=stats-plot_color=f8bdb7
# profile=svp
profile=gpu-hq
# GPU OPTIONS
vo=gpu-next
# hwdec=nvdec-copy
hwdec=videotoolbox
scale=bicubic
dscale=bicubic
cscale=bicubic
tscale=oversample
interpolation=yes
interpolation-preserve=no
input-ipc-server=/tmp/mpvsocket
# ao=pule,pipewire
# ao=pipewire,pulse
ontop=yes
ao=coreaudio
save-position-on-quit
watch-later-dir="~~.watch-later"
resume-playback=yes
save-watch-history
watch-history-path="~~state/watch_history.jsonl"
vd-lavc-threads=0
gpu-api=vulkan
gpu-context=macvk
opengl-pbo=yes
[svp]
input-ipc-server=/tmp/mpvsocket # Receives input from SVP
hr-seek-framedrop=no # Fixes audio desync
resume-playback=no # Not compatible with SVP
[Idle]
profile-cond=p["idle-active"]
profile-restore=copy-equal
title=' '
keepaspect=no
[immersion]
cookies=yes
cookies-file=/Volumes/sudacode/japanese/cookies.Japanese.txt
ytdl-raw-options=mark-watched=,write-auto-subs=,sub-langs=ja.*
ytdl-raw-options-append=cookies=/Volumes/sudacode/japanese/cookies.Japanese.txt
ytdl-raw-options-append=sponsorblock-mark=all
ytdl-raw-options-append=sponsorblock-remove=sponsor
ytdl-format=bestvideo+bestaudio/best
# get subtitles for videos automatically
sub-auto=fuzzy
slang=ja,jpn
alang=ja,jpn
vlang=ja,jpn

18
mpv-fonts.conf Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<dir>/home/sudacode/.config/mpv/fonts</dir>
<include ignore_missing="yes">/etc/fonts/fonts.conf</include>
<include ignore_missing="yes">/etc/fonts/conf.d</include>
<alias>
<family>sans-serif</family>
<prefer>
<family>JetBrainsMono Nerd Font</family>
<family>Fluent System Icons</family>
<family>Noto Sans CJK JP</family>
<family>Noto Sans</family>
<family>Open Sans</family>
</prefer>
</alias>
</fontconfig>

Submodule mpv-osc-modern deleted from 139d4dced8

277
mpv.conf
View File

@@ -1,190 +1,113 @@
# user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
# Example mpv configuration file
#
# Warning:
#
# The commented example options usually do _not_ set the default values. Call
# mpv with --list-options to see the default values for most options. There is
# no builtin or example mpv.conf with all the defaults.
#
#
# Configuration files are read system-wide from /usr/local/etc/mpv.conf
# and per-user from ~/.config/mpv/mpv.conf, where per-user settings override
# system-wide settings, all of which are overridden by the command line.
#
# Configuration file settings and the command line options use the same
# underlying mechanisms. Most options can be put into the configuration file
# by dropping the preceding '--'. See the man page for a complete list of
# options.
#
# Lines starting with '#' are comments and are ignored.
#
# See the CONFIGURATION FILES section in the man page
# for a detailed description of the syntax.
#
# Profiles should be placed at the bottom of the configuration file to ensure
# that settings wanted as defaults are not restricted to specific profiles.
##################
# video settings #
##################
# Start in fullscreen mode by default.
#fs=yes
# force starting with centered window
# geometry=50%:50%
# don't allow a new window to have a size larger than 90% of the screen size
#autofit-larger=90%x90%
# Do not close the window on exit.
#keep-open=yes
# Do not wait with showing the video window until it has loaded. (This will
# resize the window once video is loaded. Also always shows a window with
# audio.)
#force-window=immediate
# Disable the On Screen Controller (OSC).
# osc=no
# Keep the player window on top of all other windows.
#ontop=yes
# window=scale=1.0
# Specify high quality video rendering preset (for --vo=gpu only)
# Can cause performance problems with some drivers and GPUs.
# profile=gpu-hq
# Force video to lock on the display's refresh rate, and change video and audio
# speed to some degree to ensure synchronous playback - can cause problems
# with some drivers and desktop environments.
#video-sync=display-resample
# Enable hardware decoding if available. Often, this does not work with all
# video outputs, but should work well with default settings on most systems.
# If performance or energy usage is an issue, forcing the vdpau or vaapi VOs
# may or may not help.
# discourged by mpv devs and not likely to make significant difference
# hwdec=auto-copy
# hwdec-codecs=all
##################
# audio settings #
##################
# Specify default audio device. You can list devices with: --audio-device=help
# The option takes the device string (the stuff between the '...').
#audio-device=alsa/default
# Do not filter audio to keep pitch when changing playback speed.
#audio-pitch-correction=no
# Output 5.1 audio natively, and upmix/downmix audio with a different format.
#audio-channels=5.1
# Disable any automatic remix, _if_ the audio output accepts the audio format.
# of the currently played file. See caveats mentioned in the manpage.
# (The default is "auto-safe", see manpage.)
#audio-channels=auto
##################
# other settings #
##################
# Pretend to be a web browser. Might fix playback with some streaming sites,
# but also will break with shoutcast streams.
user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
# user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
# user-agent="Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36"
# user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/37.0.2062.94 Chrome/37.0.2062.94 Safari/537.36"
# user-agent="Chromium/37.0.2062.94 Chrome/37.0.2062.94 Safari/537.36"
# cache settings
#
# Use a large seekable RAM cache even for local input.
cache=yes cache=yes
# demuxer-max-bytes=500M
# Use extra large RAM cache (needs cache=yes to make it useful). demuxer-max-back-bytes=100M
#demuxer-max-bytes=500M cache-pause=no
#demuxer-max-back-bytes=100M ontop=yes
# sub-font="JetBrainsMono Nerd Font"
# Disable the behavior that the player will pause if the cache goes below a sub-font-size=45
# certain fill size. # osd-font="Fluent System Icons"
#cache-pause=no border=no
# # geometry=50%
# Store cache payload on the hard disk instead of in RAM. (This may negatively autofit=50%
# impact performance unless used for slow input such as network.) volume=75
#cache-dir=~/.cache/ audio-spdif=ac3,dts-hd,truehd
#cache-on-disk=yes # glsl-shaders="~~/shaders/Anime4K_Clamp_Highlights.glsl:~~/shaders/Anime4K_Restore_CNN_VL.glsl:~~/shaders/Anime4K_Upscale_CNN_x2_VL.glsl:~~/shaders/Anime4K_AutoDownscalePre_x2.glsl:~~/shaders/Anime4K_AutoDownscalePre_x4.glsl:~~/shaders/Anime4K_Upscale_CNN_x2_M.glsl"
# glsl-shaders="~~/shaders/FSRCNNX.glsl:~~/shaders/FSR.glsl:~~/shaders/NVScaler.glsl:~~/shaders/CAS-scaled.glsl"
# Display English subtitles if available. # glsl-shaders="~~/shaders/ArtCNN_C4F32_DS.glsl"
#slang=en glsl-shaders="~~/shaders/ArtCNN_C4F32.glsl"
# Play Finnish audio if available, fall back to English otherwise.
#alang=fi,en
# Change subtitle encoding. For Arabic subtitles use 'cp1256'.
# If the file seems to be valid UTF-8, prefer UTF-8.
# (You can add '+' in front of the codepage to force it.)
#sub-codepage=cp1256
# You can also include other configuration files.
#include=/path/to/the/file/you/want/to/include
############
# Profiles #
############
# The options declared as part of profiles override global default settings,
# but only take effect when the profile is active.
# The following profile can be enabled on the command line with: --profile=eye-cancer
#[eye-cancer]
#sharpen=5
profile=svp
sub-font=JetBrainsMono Nerd Font
sub-font-size=50
osd-font=JetBrainsMono Nerd Font
geometry=50%
volume=50
# speed-step=0.05
audio-spdif=ac3,eac3,dts-hd,truehd
# Can fix stuttering in some cases, in other cases probably causes it. Try it if you experience stuttering. # Can fix stuttering in some cases, in other cases probably causes it. Try it if you experience stuttering.
opengl-early-flush=no opengl-early-flush=no
video-sync=display-resample
osc=no osc=no
no-border
ytdl-raw-options=ignore-config=,sub-lang=en,write-auto-sub= ytdl-raw-options=sub-langs=en.*,write-auto-subs=
ytdl-format=bestvideo+bestaudio/best ytdl-format=bestvideo+bestaudio/best
# get subtitles for videos automatically # get subtitles for videos automatically
# sub-auto=fuzzy sub-auto=fuzzy
slang=en slang=en,eng
# disable subtitles by default = no
sid=no
# keep-open
input-ipc-server=/tmp/mpvsocket
video-sync=display-resample
glsl-shaders="~~/shaders/FSRCNNX.glsl:~~/shaders/NVScaler.glsl:~~/shaders/CAS-scaled.glsl"
# profile=svp
profile=high-quality
blend-subtitles=video
# GPU OPTIONS # GPU OPTIONS
vo=gpu-next vo=gpu-next
hwdec=nvdec hwdec=nvdec
scale=bicubic gpu-api=vulkan
dscale=bicubic scale=ewa_lanczossharp
dscale=catmull_rom
cscale=bicubic cscale=bicubic
tscale=oversample tscale=oversample
interpolation=yes interpolation=yes
interpolation-preserve=no interpolation-preserve=no
# fruit: 8-Bit/8-Bit+FRC display
# ordered: true 10-Bit/12-Bit display
# error-diffusion: high-end GPUs
dither=error-diffusion
dither-depth=auto
error-diffusion=sierra-lite # uncomment if not 'error-diffusion'
###### Antiring
scale-antiring=0.5
dscale-antiring=0.5
cscale-antiring=0.5
# laptop
# vo=gpu
# gpu-api=opengl
# gpu-context=wayland
# profile=opengl-hq
gpu-context=waylandvk
gpu-api=vulkan
vulkan-swap-mode=mailbox
swapchain-depth=2
vulkan-async-compute=no
vd-lavc-threads=0
opengl-pbo=yes
vd-lavc-film-grain=gpu
input-ipc-server=/tmp/mpvsocket
# ao=pule,pipewire
ao=pipewire,pulse
deband=yes
deband-iterations=4
deband-threshold=35
deband-range=16
deband-grain=4
subs-with-matching-audio=no
sub-fix-timing=yes
sub-ass-override=scale
#Some settings fixing VOB/PGS subtitles (creating blur & changing yellow subs to gray)
sub-gauss=1.0
sub-gray=yes
###### High-quality screenshots
screenshot-format=webp
screenshot-webp-lossless=yes
screenshot-high-bit-depth=yes
screenshot-sw=no
screenshot-directory="/truenas/sudacode/pictures/mpv"
screenshot-template="%f-%wH.%wM.%wS.%wT-#%#00n"
save-position-on-quit
watch-later-dir="~~/.watch-later"
resume-playback=yes
save-watch-history
watch-history-path="~~/state/watch_history.jsonl"
target-colorspace-hint=yes
[hdr]
target-colorspace-hint=yes
gpu-api=vulkan
gpu-context=waylandvk
[svp] [svp]
input-ipc-server=/tmp/mpvsocket # Receives input from SVP input-ipc-server=/tmp/mpvsocket # Receives input from SVP
@@ -196,3 +119,21 @@ profile-cond=p["idle-active"]
profile-restore=copy-equal profile-restore=copy-equal
title=' ' title=' '
keepaspect=no keepaspect=no
[immersion]
cookies=yes
cookies-file=/truenas/sudacode/japanese/cookies.Japanese.txt
ytdl-raw-options=mark-watched=,write-auto-subs=,sub-langs=ja.*
ytdl-raw-options-append=cookies=/truenas/sudacode/japanese/cookies.Japanese.txt
ytdl-raw-options-append=sponsorblock-mark=all
ytdl-raw-options-append=sponsorblock-remove=sponsor
ytdl-format=bestvideo+bestaudio/best
# get subtitles for videos automatically
sub-auto=fuzzy
slang=ja,jpn,JA,JPN,ja.hi,ja.*
alang=ja,jpn
vlang=ja,jpn
sub-font="Noto Sans CJK JP"
sub-border-size=1
sub-shadow-color=0.0/0.0/0.0/0.50
sub-shadow-offset=2

BIN
mpv_websocket Executable file

Binary file not shown.

BIN
mpv_websocket-mac Executable file

Binary file not shown.

View File

@@ -1,126 +0,0 @@
--[[
This is a module designed to interface with mpv-user-input
https://github.com/CogentRedTester/mpv-user-input
Loading this script as a module will return a table with two functions to format
requests to get and cancel user-input requests. See the README for details.
Alternatively, developers can just paste these functions directly into their script,
however this is not recommended as there is no guarantee that the formatting of
these requests will remain the same for future versions of user-input.
]]
local API_VERSION = "0.1.0"
local mp = require 'mp'
local msg = require "mp.msg"
local utils = require 'mp.utils'
local mod = {}
local name = mp.get_script_name()
local counter = 1
local function pack(...)
local t = {...}
t.n = select("#", ...)
return t
end
local request_mt = {}
-- ensures the option tables are correctly formatted based on the input
local function format_options(options, response_string)
return {
response = response_string,
version = API_VERSION,
id = name..'/'..(options.id or ""),
source = name,
request_text = ("[%s] %s"):format(options.source or name, options.request_text or options.text or "requesting user input:"),
default_input = options.default_input,
cursor_pos = tonumber(options.cursor_pos),
queueable = options.queueable and true,
replace = options.replace and true
}
end
-- cancels the request
function request_mt:cancel()
assert(self.uid, "request object missing UID")
mp.commandv("script-message-to", "user_input", "cancel-user-input/uid", self.uid)
end
-- updates the options for the request
function request_mt:update(options)
assert(self.uid, "request object missing UID")
options = utils.format_json( format_options(options) )
mp.commandv("script-message-to", "user_input", "update-user-input/uid", self.uid, options)
end
-- sends a request to ask the user for input using formatted options provided
-- creates a script message to recieve the response and call fn
function mod.get_user_input(fn, options, ...)
options = options or {}
local response_string = name.."/__user_input_request/"..counter
counter = counter + 1
local request = {
uid = response_string,
passthrough_args = pack(...),
callback = fn,
pending = true
}
-- create a callback for user-input to respond to
mp.register_script_message(response_string, function(response)
mp.unregister_script_message(response_string)
request.pending = false
response = utils.parse_json(response)
request.callback(response.line, response.err, unpack(request.passthrough_args, 1, request.passthrough_args.n))
end)
-- send the input command
options = utils.format_json( format_options(options, response_string) )
mp.commandv("script-message-to", "user_input", "request-user-input", options)
return setmetatable(request, { __index = request_mt })
end
-- runs the request synchronously using coroutines
-- takes the option table and an optional coroutine resume function
function mod.get_user_input_co(options, co_resume)
local co, main = coroutine.running()
assert(not main and co, "get_user_input_co must be run from within a coroutine")
local uid = {}
local request = mod.get_user_input(function(line, err)
if co_resume then
co_resume(uid, line, err)
else
local success, er = coroutine.resume(co, uid, line, err)
if not success then
msg.warn(debug.traceback(co))
msg.error(er)
end
end
end, options)
-- if the uid was not sent then the coroutine was resumed by the user.
-- we will treat this as a cancellation request
local success, line, err = coroutine.yield(request)
if success ~= uid then
request:cancel()
request.callback = function() end
return nil, "cancelled"
end
return line, err
end
-- sends a request to cancel all input requests with the given id
function mod.cancel_user_input(id)
id = name .. '/' .. (id or "")
mp.commandv("script-message-to", "user_input", "cancel-user-input/id", id)
end
return mod

View File

@@ -0,0 +1,11 @@
# Use 'yes' or 'no' for boolean options below
# Example for multiple directories (comma or semicolon separated):
# DIRECTORIES=D:/Torrents,D:/Anime
# or
# DIRECTORIES=D:/Torrents;D:/Anime
DIRECTORIES=/truenas/jellyfin/anime
UPDATE_PERCENTAGE=85
SET_COMPLETED_TO_REWATCHING_ON_FIRST_EPISODE=no
UPDATE_PROGRESS_WHEN_REWATCHING=yes
SET_TO_COMPLETED_AFTER_LAST_EPISODE_CURRENT=yes
SET_TO_COMPLETED_AFTER_LAST_EPISODE_REWATCHING=yes

107
script-opts/animecards.conf Normal file
View File

@@ -0,0 +1,107 @@
# =================================================
# Anacreon Script (Animecards) Config for MPV
# =================================================
# ⚠ WARNING:
# Use only yes/no (not true/false) in boolean options
# =================================================
# Anki Field Names
# These must match the field names in your Anki note type
# =================================================
FRONT_FIELD=Expression
SENTENCE_FIELD=Sentence
IMAGE_FIELD=Picture
SENTENCE_AUDIO_FIELD=SentenceAudio
# =================================================
# Behavior Settings
# =================================================
# [Not recommended] Copy subtitles to clipboard enabled by default? (yes/no)
# The more modern and recommended alternative is to use the websocket.
ENABLE_SUBS_TO_CLIP=no
# Ask before overwriting existing cards? (yes/no)
# Recommended to set yes unless you know what you are doing
ASK_TO_OVERWRITE=yes
# [Dangerous] Max cards that can be overwritten at once (-1 = unlimited)
OVERWRITE_LIMIT=8
# Keep bold formatting added by yomitan? (yes/no)
HIGHLIGHT_WORD=no
# Use MPV's built-in clipboard API (requires v0.40+)? (yes/no)
# Alternative clipboard method that may reduce latency on Windows.
# Supported on macOS and Wayland as well. Not supported on X11.
USE_MPV_CLIPBOARD_API=no
# ==========================================================
# Audio Settings
# ==========================================================
# [Optional] Padding and fade settings in seconds. (0 = disable)
# Padding grabs extra audio around your selected subs.
# Fade does a volume fade in/out effect.
AUDIO_CLIP_PADDING=0.75
AUDIO_CLIP_FADE=0.2
# Always create mono audio? (yes/no)
AUDIO_MONO=yes
# [Optional] Use MPV's current volume for card audio? (yes/no)
USE_MPV_VOLUME=no
# [Optional] Play sentence audio after updating the card? (yes/no)
AUTOPLAY_AUDIO=no
# =================================================
# Image Settings
# =================================================
# Format of screenshots: png, jpg or webp
# ⚠ Use png or jpg for iOS/Mac compatibility
# (webp won't display on iOS/Mac)
IMAGE_FORMAT=png
# Resize image to this height (in pixels).
# Preserves aspect ratio. (0 = keep original resolution)
IMAGE_HEIGHT=480
# [JPG only] Quality: from 0 (worst) to 100 (best)
JPG_QUALITY=88
# =================================================
# Animated Image Settings
# =================================================
# Enable animated export for the selected subtitle segment (yes/no)
ANIMATED_IMAGE_ENABLED=yes
# Animated format: webp or avif
ANIMATED_IMAGE_FORMAT=avif
# Resize animated image to this height (in pixels). (0 = keep original)
ANIMATED_IMAGE_HEIGHT=480
# Frames per second for animated export (1-30)
ANIMATED_IMAGE_FPS=24
# Quality 0-100 (mapped to CRF for avif)
ANIMATED_IMAGE_QUALITY=69
# =================================================
# Misc Info - Extra metadata for cards
# =================================================
# [Optional] Save extra metadata (filename, timestamp, etc.) to a field
WRITE_MISCINFO=yes
# Field to store the extra info (only used if WRITE_MISCINFO=yes)
MISCINFO_FIELD=MiscInfo
# Pattern for the Misc Info content:
# %f = filename (without extension)
# %F = filename (with extension)
# %t = timestamp (HH:MM:SS)
# %T = timestamp with milliseconds (HH:MM:SS:MLS)
# <br> = Next line tag
# Examples:
# MISCINFO_PATTERN %f (%t)
# MISCINFO_PATTERN=File: %F<br>Timestamp: %T
MISCINFO_PATTERN=[Anacreon Script] %f (%t)

View File

@@ -0,0 +1,33 @@
# Absolute paths to the executables, if needed:
# 1. ffmpeg
ffmpeg_path=/usr/bin/ffmpeg
# 2. ffsubsync
ffsubsync_path=/usr/bin/ffsubsync
# 3. alass
alass_path=/usr/bin/alass
# Preferred retiming tool. Allowed options: 'ffsubsync', 'alass', 'ask'.
# If set to 'ask', the add-on will ask to choose the tool every time:
# 1. Preferred tool for syncing to audio.
# audio_subsync_tool=ask
audio_subsync_tool=ffsubsync
# audio_subsync_tool=alass
# 2. Preferred tool for syncing to another subtitle.
# altsub_subsync_tool=ask
# altsub_subsync_tool=ffsubsync
altsub_subsync_tool=alass
# Unload old subs (yes,no)
# After retiming, tell mpv to forget the original subtitle track.
unload_old_sub=yes
# unload_old_sub=no
# Overwrite the original subtitle file.
# Replace the old subtitle file with the retimed file.
# overwrite_old_sub=yes
overwrite_old_sub=no

View File

@@ -0,0 +1,22 @@
start_tracking_key=ctrl+t
data_dir=/home/sudacode/.config/mpv/scripts/immersion-tracker/data
csv_file=/truenas/sudacode/japanese/immersion_tracker.csv
session_file=/home/sudacode/.config/mpv/scripts/immersion-tracker/data/current_session.json
min_session_duration=30
save_interval=10
enable_debug_logging=no
backup_sessions=no
max_backup_files=10
use_title=yes
use_filename=no
custom_prefix=[Immersion]
max_title_length = 100
export_csv=yes
export_json=no
export_html=no
backup_csv=yes
show_session_start=yes
show_session_end=yes
show_progress_milestones=no
milestone_percentages=25507590

View File

@@ -25,7 +25,9 @@ hidetimeout=1500
# if seeking should reset the hidetimeout # if seeking should reset the hidetimeout
seek_resets_hidetimeout=yes seek_resets_hidetimeout=yes
# fade-out duration (in ms), set to 0 for no fade # fade-out duration (in ms), set to 0 for no fade
fadeduration=250 fadeduration=200
# whether to enable fade-in effect
fadein=no
# minimum mouse movement (in pixels) required to show OSC # minimum mouse movement (in pixels) required to show OSC
minmousemove=0 minmousemove=0
# show OSC only when hovering at the bottom # show OSC only when hovering at the bottom
@@ -34,8 +36,12 @@ bottomhover=yes
bottomhover_zone=130 bottomhover_zone=130
# show OSC when seeking # show OSC when seeking
osc_on_seek=no osc_on_seek=no
# show OSC on start of every file
osc_on_start=no
# pause video while seeking with mouse move (on button hold) # pause video while seeking with mouse move (on button hold)
mouse_seek_pause=yes mouse_seek_pause=yes
# force show seekbar tooltip on mouse drag, even if not hovering seekbar
force_seek_tooltip=no
# scale osc with the video # scale osc with the video
vidscale=auto vidscale=auto
@@ -96,9 +102,9 @@ raise_subtitle_amount=125
# show the jump backward and forward buttons # show the jump backward and forward buttons
jump_buttons=yes jump_buttons=yes
# change the jump amount in seconds # change the jump amount in seconds
jump_amount=10 jump_amount=1
# change the jump amount in seconds when right-clicking jump buttons and shift-clicking chapter skip buttons # change the jump amount in seconds when right-clicking jump buttons and shift-clicking chapter skip buttons
jump_more_amount=60 jump_more_amount=5
# show different icon when jump_amount is set to 5, 10, or 30 # show different icon when jump_amount is set to 5, 10, or 30
jump_icon_number=yes jump_icon_number=yes
# seek mode for jump buttons # seek mode for jump buttons
@@ -138,6 +144,10 @@ ontop_button=yes
loop_button=no loop_button=no
# show speed control button # show speed control button
speed_button=no speed_button=no
# speed change amount per click
speed_button_click=1
# speed change amount on scroll
speed_button_scroll=0.25
# show info button # show info button
info_button=yes info_button=yes
# show fullscreen toggle button # show fullscreen toggle button
@@ -165,52 +175,54 @@ zoom_out_min=-1
# Colors and style # Colors and style
# accent color of the OSC and title bar # accent color of the OSC and title bar
osc_color=#000000 osc_color=#181926
# color of the title in borderless/fullscreen mode # color of the title in borderless/fullscreen mode
window_title_color=#FFFFFF window_title_color=#A5ADCB
# color of the window controls (close, minimize, maximize) in borderless/fullscreen mode # color of the window controls (close, minimize, maximize) in borderless/fullscreen mode
window_controls_color=#FFFFFF window_controls_color=#A5ADCB
# color of close window control on hover # color of close window control on hover
windowcontrols_close_hover=#E81123 windowcontrols_close_hover=#ED8796
# color of min/max window controls on hover # color of maximize window controls on hover
windowcontrols_minmax_hover=#FFD700 windowcontrols_max_hover=#EED49F
# color of minimize window controls on hover
windowcontrols_min_hover=#A6DA95
# color of the title (above seekbar) # color of the title (above seekbar)
title_color=#FFFFFF title_color=#CAD3F5
# color of the cache information # color of the cache information
cache_info_color=#FFFFFF cache_info_color=#CAD3F5
# color of the seekbar progress and handle # color of the seekbar progress and handle
seekbarfg_color=#FB8C00 seekbarfg_color=#C6A0F6
# color of the remaining seekbar # color of the remaining seekbar
seekbarbg_color=#94754F seekbarbg_color=#B7BDF8
# color of the cache ranges on the seekbar # color of the cache ranges on the seekbar
seekbar_cache_color=#918F8E seekbar_cache_color=#A5ADCB
# match volume bar color with seekbar color (ignores side_buttons_color) # match volume bar color with seekbar color (ignores side_buttons_color)
volumebar_match_seek_color=no volumebar_match_seek_color=no
# color of the timestamps (below seekbar) # color of the timestamps (below seekbar)
time_color=#FFFFFF time_color=#CAD3F5
# color of the chapter title next to timestamp (below seekbar) # color of the chapter title next to timestamp (below seekbar)
chapter_title_color=#FFFFFF chapter_title_color=#A5ADCB
# color of the side buttons (audio, subtitles, playlist, etc.) # color of the side buttons (audio, subtitles, playlist, etc.)
side_buttons_color=#FFFFFF side_buttons_color=#A5ADCB
# color of the middle buttons (skip, jump, chapter, etc.) # color of the middle buttons (skip, jump, chapter, etc.)
middle_buttons_color=#FFFFFF middle_buttons_color=#A5ADCB
# color of the play/pause button # color of the play/pause button
playpause_color=#FFFFFF playpause_color=#A5ADCB
# color of the element when held down (pressed) # color of the element when held down (pressed)
held_element_color=#999999 held_element_color=#999999
# color of a hovered button when hover_effect includes "color" # color of a hovered button when hover_effect includes "color"
hover_effect_color=#FB8C00 hover_effect_color=#C6A0F6
# color of the border for thumbnails (with thumbfast) # color of the border for thumbnails (with thumbfast)
thumbnail_border_color=#111111 thumbnail_border_color=#181926
# color of the border outline for thumbnails # color of the border outline for thumbnails
thumbnail_border_outline=#404040 thumbnail_border_outline=#363A4F
# alpha of the OSC background box # alpha of the OSC background box
fade_alpha=130 fade_alpha=130
# blur strength for the OSC alpha fade. caution: high values can take a lot of CPU time to render # blur strength for the OSC alpha fade. caution: high values can take a lot of CPU time to render
fade_blur_strength=100 fade_blur_strength=100
# alpha of the window title bar (0 to disable) # alpha of the window title bar (0 to disable)
window_fade_alpha=75 window_fade_alpha=100
# blur strength for the window title bar. caution: high values can take a lot of CPU time to render # blur strength for the window title bar. caution: high values can take a lot of CPU time to render
window_fade_blur_strength=100 window_fade_blur_strength=100
# width of the thumbnail border (for thumbfast) # width of the thumbnail border (for thumbfast)
@@ -267,6 +279,8 @@ persistentbuffer=no
# Miscellaneous settings # Miscellaneous settings
# only used at init to set visibility_mode(...) # only used at init to set visibility_mode(...)
visibility=auto visibility=auto
# visibility modes to cycle through, modes are separated by _
visibility_modes=never_auto_always
# minimum interval between OSC redraws (in seconds) # minimum interval between OSC redraws (in seconds)
tick_delay=0.03 tick_delay=0.03
# use display FPS as the minimum redraw interval # use display FPS as the minimum redraw interval
@@ -290,7 +304,7 @@ tooltip_height_offset=2
# if tooltip contains many characters, it is moved to the left by offset # if tooltip contains many characters, it is moved to the left by offset
tooltip_left_offset=5 tooltip_left_offset=5
# portrait window width trigger to move some elements # portrait window width trigger to move some elements
portrait_window_trigger=930 portrait_window_trigger=1000
# hide volume bar trigger window width # hide volume bar trigger window width
hide_volume_bar_trigger=1150 hide_volume_bar_trigger=1150
# osc height offset if title above seekbar is disabled # osc height offset if title above seekbar is disabled
@@ -359,3 +373,6 @@ playlist_next_mbtn_right_command=script-binding select/select-playlist; script-m
# fullscreen button mouse actions # fullscreen button mouse actions
fullscreen_mbtn_left_command=cycle fullscreen fullscreen_mbtn_left_command=cycle fullscreen
fullscreen_mbtn_right_command=cycle window-maximized fullscreen_mbtn_right_command=cycle window-maximized
# info button mouse actions
info_mbtn_left_command=script-binding stats/display-page-1-toggle

View File

@@ -16,12 +16,12 @@ save_queue=ctrl+s
save_full_queue=ctrl+S save_full_queue=ctrl+S
remove_from_queue=ctrl+x remove_from_queue=ctrl+x
play_selected_video=ctrl+ENTER play_selected_video=ctrl+ENTER
browser=firefox browser=zen-browser
clipboard_command=xclip -o clipboard_command=wl-paste
cursor_icon=➤ cursor_icon=➤
display_limit=10 display_limit=10
download_directory=~/videos/YouTube download_directory=~/videos/YouTube
download_quality=720p download_quality=1080p
downloader=curl downloader=curl
font_name=JetBrainsMono NF font_name=JetBrainsMono NF
font_size=12 font_size=12

1
script-opts/stats.conf Normal file
View File

@@ -0,0 +1 @@
plot_tonemapping_lut=yes

331
script-opts/subs2srs.conf Normal file
View File

@@ -0,0 +1,331 @@
###
### Main mpvacious configuration file.
### Save this file to ~/.config/mpv/script-opts/subs2srs.conf
###
##
## General settings
##
# Anki deck for new cards. Subdecks are supported.
deck_name=Minecraft
# Model names are listed in `Tools -> Manage note types` menu in Anki.
# If you don't have a model for Japanese, get it from
# https://tatsumoto.neocities.org/blog/setting-up-anki.html#import-an-example-mining-deck
model_name=Lapis
# Field names as they appear in the selected note type.
# If you set `audio_field` or `image_field` empty,
# the corresponding media file will not be created.
audio_field=SentenceAudio
image_field=Picture
sentence_field=Expression
secondary_field=SelectionText
# The tag(s) added to new notes. Spaces separate multiple tags.
# Leave nothing after `=` to disable tagging completely.
# The following substitutions are supported:
# %n - the name of the video
# %t - timestamp
# %d - episode number (if none, returns nothing)
# %e - SUBS2SRS_TAGS environment variable (if you have it set)
# note_tag=subs2srs
note_tag=%n-E%d %t
#note_tag=
# Size and name of the font used in the menu
menu_font_size=24
menu_font_name=Noto Serif CJK JP
# AnkiConnect server address
# The default address for a server on the same device is http://127.0.0.1:8765.
# If Anki is running and AnkiConnect is installed, opening this URL should
# open a page showing the current version of AnkiConnect.
# Change this if you have changed webBindAddress in AnkiConnect's settings.
ankiconnect_url=127.0.0.1:8765
##
## Toggleables.
## Possible values: `yes` or `no`.
# Use FFmpeg encoder instead of mpv encoder
# If mpvacious encounters problems creating audio and images for Anki cards,
# setting this to `yes` should fix them.
#
# You need to install ffmpeg and add it to the PATH first.
# https://wiki.archlinux.org/title/FFmpeg
# https://www.ffmpeg.org/download.html
#
# FFmpeg encoder is unable to create audio and images from remote content (like YouTube videos).
use_ffmpeg=yes
# Automatically create the deck for new cards (see deck_name option)
create_deck=no
# Allow making notes with the same sentence field.
allow_duplicates=no
# When mpv starts, automatically copy subs to the clipboard as they appear on screen.
# This option can be also toggled in the addon's OSD menu.
autoclip=no
# Possible options:
# "disabled" - autocopy is disabled
# "clipboard" - copy to the system clipboard (e.g. uses xclip)
# "goldendict" - send the subtitle string to goldendict (goldendict-ng).
# "custom_command" - run any custom command specified in `autoclip_custom_args`.
autoclip_method=custom_command
# Command to run when autoclip is enabled and set to "custom_command".
# If empty, nothing will be done.
# If set, calls the external program.
# The following substitutions are supported:
# %MPV_PRIMARY% - primary subtitle line
# %MPV_SECONDARY% - secondary subtitle line
autoclip_custom_args=wl-copy %MPV_PRIMARY%
# Remove all spaces from the primary subtitle text.
# Set this to "yes" for languages without spaces like Japanese.
# However, if mpvacious detects any latin characters in the string, spaces will not be removed.
nuke_spaces=yes
# if set to `yes`, the volume of the outputted audio file
# depends on the volume of the player at the time of export
tie_volumes=no
# This is used when selecting cards in Anki to update, it wont let you
# overwrite more than the value specified below
# Just remember that having multiple cards with the same sentence
# and the same audio recording is usually bad practice.
card_overwrite_safeguard = 1
# Remove text in parentheses and leading/trailing spaces or
# newlines that may interfere with Rikaitan before copying
# subtitles to the clipboard
clipboard_trim_enabled=yes
# Add media to fields before or after existing data
append_media=yes
# Remove text in brackets before substituting %n into tag
tag_nuke_brackets=yes
# Remove text in parentheses before substituting %n into tag
tag_nuke_parentheses=no
# Remove the episode number before substituting %n into tag
tag_del_episode_num=yes
# Remove everything after the episode number before substituting %n into tag
# Does nothing if the previous option tag_del_episode_num is disabled.
tag_del_after_episode_num=yes
# Convert filename to lowercase for tagging.
tag_filename_lowercase=no
# Lets you disable anki browser manipulation by mpvacious.
disable_gui_browse=no
# Play audio clip automatically in background
# after note creation (or note update) to ensure that the audio is correctly cut.
preview_audio=yes
# When selecting subtitle lines, print them on the screen.
show_selected_text=yes
# For convenience, read config file from disk before a card is made.
# Useful if you change your config often since you won't have to restart mpv every time,
# but reading from disk takes some time.
reload_config_before_card_creation=yes
##
## Image settings
##
# Snapshot format.
# Do not switch to `jpg` unless your computer doesn't support `webp` or `avif`.
snapshot_format=avif
# snapshot_format=webp
#snapshot_format=jpg
# Quality of produced image files. 0 = lowest, 100=highest.
snapshot_quality=88
# Image dimensions
# If either (but not both) of the width or height parameters is -2,
# the value will be calculated preserving the aspect-ratio.
snapshot_width=-2
snapshot_height=400
# Screenshot (yes, no)
# Usually not required.
# When making Anki cards, create a screenshot (by calling 'screenshot-to-file') instead of a snapshot.
# If set to yes, image dimensions and quality cannot be controlled due to mpv limitations.
# 'snapshot_format' is still respected.
# When using this, a custom sync server is recommended, e.g. https://github.com/ankicommunity/anki-sync-server
screenshot=yes
# The exact image template used when exporting to Anki's image field.
# Adding data-editor-shrink="true" makes the image smaller by default within the Anki viewer
# on versions 2.1.53+ (equivalent of double-clicking on the image).
# You likely would not want to change this unless you know what you are doing.
image_template=<img alt="snapshot" src="%s">
#image_template=<img alt="snapshot" data-editor-shrink="true" src="%s">
# Similar to image_template but with audio.
# Normally, the user doesn't need to change this setting,
# but it may be needed for audio files to be playable on AnkiWeb.
audio_template=[sound:%s]
#audio_template=<audio controls="" src="%s"></audio>
##
## Animated snapshots
## Animated snapshots will capture the video from the start to the end times selected when using mpvacious.
##
# If enabled, generates animated snapshots (something like GIFs) instead of static snapshots.
animated_snapshot_enabled=yes
# Animated snapshot format. Like "snapshot_format" but for animated images. Can be either avif or webp.
animated_snapshot_format=avif
# animated_snapshot_format=webp
# Number of frame per seconds, a value between 0 and 30 (30 included)
# Higher values will increase both quality and file size, lower values will do the opposite
animated_snapshot_fps=24
# Animated snapshot dimensions
# If either (but not both) of the width or height parameters is -2,
# the value will be calculated preserving the aspect-ratio.
animated_snapshot_width=-2
animated_snapshot_height=400
# Quality of the produced animation, 0 = lowest, 100 = highest
animated_snapshot_quality=69
##
## Audio settings
##
# Audio format.
# Opus is the recommended format.
audio_format=opus
# audio_format=mp3
# Container for opus files.
# It may be required to use a different container for Opus.
# This is the case on certain computers or devices
# which are running proprietary operating systems, e.g. AnkiMobile. Using them is discouraged.
# ・ Ogg/Opus play everywhere except AnkiWeb in Safari and AnkiMobile.
# ・ M4A (iOS 17.2 and probably even earlier) and WEBM (since iOS 17.4) play everywhere.
# ・ Opus in CAF can be used with older iOS. CAF plays only on Anki Desktop, Safari and AnkiMobile.
# ・ (iOS Lockdown Mode disables Opus support completely,
# though you may try to add an exception for AnkiMobile.)
# opus_container=ogg
#opus_container=opus
opus_container=m4a
# opus_container=webm
#opus_container=caf
# Sane values are 16k-32k for opus, 64k-128k for mp3.
audio_bitrate=32k
# Set a pad to the dialog timings. 0.5 = half a second.
# Pads are never applied to manually set timings.
audio_padding=0.0
#audio_padding=0.5
##
## Forvo support (Rikaitan users only)
##
# yes - fetch audio from Forvo if Rikaitan couldn't find the audio (default)
# always - always fetch audio from Forvo and replace the audio added by Rikaitan
# no - never use Forvo
use_forvo=yes
# Vocab field should be equal to {expression} field in Rikaitan
vocab_field=Expression
# Vocab Audio field should be equal to {audio} field in Rikaitan
vocab_audio_field=ExpressionAudio
##
## Misc info
## Various context information that can be written on your cards in a specified field.
##
# yes to enable or no to disable.
miscinfo_enable=yes
# Field name
miscinfo_field=ExtraInfo
# Format string used to fill the misc info field.
# It supports the same substitutions as `note_tag`. HTML is supported.
miscinfo_format=%n EP%d (%t)
#miscinfo_format=From <b>mpvacious</b> %n at %t.
##
## Secondary subtitles
## Mpvacious can try automatically loading secondary subtitles that will appear at the top.
## For example, you may want to load English subs alongside Japanese subs.
##
## Secondary subtitles should be present in the container.
## But if you manually set secondary sid from the command line, mpvacious won't change it.
##
# Load secondary subtitle track automatically when a file is opened.
secondary_sub_auto_load=yes
# Language of secondary subs. This is your native language or a language you know well.
# If you leave this parameter empty, secondary subs will NOT be automatically loaded.
secondary_sub_lang=eng,en
#secondary_sub_lang=
# Hover area.
# Proportion of the top part of the mpv window where the secondary subtitles are visible when hovered over.
# Possible values: from 0.0 to 1.0
secondary_sub_area=0.15
# Visibility state
# Can be set to: 'auto', 'never', 'always'.
# If set to 'never' or 'always', secondary_sub_area has no effect.
# If set to 'auto', visibility behaves according to the value of secondary_sub_area.
# Default binding to cycle this value: Ctrl+v.
secondary_sub_visibility=auto
# Perform two-pass loudness normalization.
# Parameter explanation can be found e.g. at:
# https://auphonic.com/blog/2013/01/07/loudness-targets-mobile-audio-podcasts-radio-tv/
# https://auphonic.com/blog/2019/08/19/dynamic-range-processing/
# MAKE SURE TO REMOVE loudnorm FROM CUSTOM ARGS BEFORE ENABLING.
loudnorm=no
loudnorm_target=-16
loudnorm_range=11
loudnorm_peak=-1.5
##
## Custom audio encoding arguments
## These arguments are added to the command line.
## `mpv` and `ffmpeg` accept slightly different parameters.
## Feel free to experiment for yourself, but be careful or media creation might stop working.
##
# loudnorm IN CUSTOM ARGS IS LEFT FOR BACKWARD COMPATIBILITY.
# MAKE SURE TO REMOVE ALL MENTIONS OF loudnorm FROM CUSTOM ARGS
# (E.G. SET TO EMPTY STRINGS) BEFORE ENABLING TWO-PASS loudnorm.
# ENABLING loudnorm BOTH THROUGH THE SWITCH AND THROUGH CUSTOM ARGS
# CAN LEAD TO UNPREDICTABLE RESULTS.
# Ffmpeg
ffmpeg_audio_args=-af loudnorm=I=-16:TP=-1.5:LRA=11:dual_mono=true
#ffmpeg_audio_args=
#ffmpeg_audio_args=-af silenceremove=1:0:-50dB
# mpv
# mpv accepts each filter as a separate argument, e.g. --af-append=1 --af-append=2
mpv_audio_args=--af-append=loudnorm=I=-16:TP=-1.5:LRA=11:dual_mono=true
#mpv_audio_args=
#mpv_audio_args=--af-append=silenceremove=1:0:-50dB

View File

@@ -0,0 +1 @@
temp=/tmp/ytdl-preload

View File

@@ -0,0 +1 @@
../../submodules/mpv-anilist-updater/anilistUpdater/anilistUpdater.py

View File

@@ -0,0 +1 @@
../../submodules/mpv-anilist-updater/anilistUpdater/main.lua

1
scripts/animecards Symbolic link
View File

@@ -0,0 +1 @@
../submodules/animecards/animecards

1
scripts/autosubsync-mpv Symbolic link
View File

@@ -0,0 +1 @@
../submodules/autosubsync-mpv

1
scripts/immersion-tracker Symbolic link
View File

@@ -0,0 +1 @@
../submodules/immersion-tracker

388
scripts/jimaku.js Normal file
View File

@@ -0,0 +1,388 @@
// Go to https://jimaku.cc/login and create a new account.
// Then go to https://jimaku.cc/account and click the `Generate` button to create a new API key
// Click the `Copy` button and paste it below
var API_KEY = "";
// Configuration options
var CONFIG = {
// Filter the response to only have the specified episode
prompt_episode: true,
// Subtitle suffix (e.g., ".JA" for Japanese subtitles)
subtitle_suffix: ".JA",
// Preferred subtitle format (order matters, first is most preferred)
preferred_formats: ["ass", "srt", "vtt"],
// Automatically load the subtitle after download
auto_load: true,
// Default subtitle delay in seconds (can be positive or negative)
default_delay: 0,
// Default subtitle font size
default_font_size: 16,
// Automatically rename the subtitle file after download
auto_rename: true,
// Automatically run autosubsync-mpv after downloading the subtitle
run_auto_subsync: true
};
// Keybindings
// var MANUAL_SEARCH_KEY = "g";
var FILENAME_AUTO_SEARCH_KEY = "ctrl+J";
var PARENT_FOLDER_AUTO_SEARCH_KEY = "n";
function api(url, extraArgs) {
var baseArgs = [
"curl",
"-s",
"--url",
url,
"--header",
"Authorization: " + API_KEY
];
var args = Array.prototype.concat.apply(baseArgs, extraArgs);
var res = mp.command_native({
name: "subprocess",
playback_only: false,
capture_stdout: true,
capture_stderr: true,
args: args
});
if (res.stdout) return JSON.parse(res.stdout);
}
function downloadSub(sub) {
return api(sub.url, ["--output", sub.name]);
}
function showMessage(message, persist) {
var ass_start = mp.get_property_osd("osd-ass-cc/0");
var ass_stop = mp.get_property_osd("osd-ass-cc/1");
mp.osd_message(
ass_start + "{\\fs16}" + message + ass_stop,
persist ? 999 : 2
);
}
// The timeout is neccessary due to a weird bug in mpv
function inputGet(args) {
mp.input.terminate();
setTimeout(function () {
mp.input.get(args);
}, 1);
}
// The timeout is neccessary due to a weird bug in mpv
function inputSelect(args) {
mp.input.terminate();
setTimeout(function () {
mp.input.select(args);
}, 1);
}
// Taken from mpv-subversive
// https://github.com/nairyosangha/mpv-subversive/blob/master/backend/backend.lua#L146
function sanitize(text) {
var subPatterns = [
/\.[a-zA-Z]+$/, // extension
/\./g,
/-/g,
/_/g,
/\[[^\]]+\]/g, // [] bracket
/\([^\)]+\)/g, // () bracket
/720[pP]/g,
/480[pP]/g,
/1080[pP]/g,
/[xX]26[45]/g,
/[bB]lu[-]?[rR]ay/g,
/^[\s]*/,
/[\s]*$/,
/1920x1080/g,
/1920X1080/g,
/Hi10P/g,
/FLAC/g,
/AAC/g
];
var result = text;
subPatterns.forEach(function (subPattern) {
var newResult = result.replace(subPattern, " ");
if (newResult.length > 0) {
result = newResult;
}
});
return result;
}
// Adapted from mpv-subversive
// https://github.com/nairyosangha/mpv-subversive/blob/master/backend/backend.lua#L164
function extractTitle(text) {
var matchers = [
{ regex: /^([\w\s\d]+)[Ss]\d+[Ee]?\d+/, group: 1 },
{ regex: /^([\w\s\d]+)-[\s]*\d+[\s]*[^\w]*$/, group: 1 },
{ regex: /^([\w\s\d]+)[Ee]?[Pp]?[\s]+\d+$/, group: 1 },
{ regex: /^([\w\s\d]+)[\s]\d+.*$/, group: 1 },
{ regex: /^\d+[\s]*(.+)$/, group: 1 }
];
for (var i = 0; i < matchers.length; i++) {
var matcher = matchers[i];
var match = text.match(matcher.regex);
if (match) {
return match[matcher.group].trim();
}
}
return text;
}
function getNames(results) {
return results.map(function (item) {
return item.name;
});
}
function runAutoSubSyncMPV() {
try {
mp.command_native(["script-binding", "autosubsync-menu"]);
} catch (e) {
showMessage("autosubsync-mpv not installed");
return;
}
}
function selectSub(selectedSub) {
showMessage("Downloading: " + selectedSub.name);
try {
downloadSub(selectedSub);
// Get current video filename without extension
var videoPath = mp.get_property("path");
if (!videoPath) {
throw new Error("No video file is currently playing");
}
var videoName = videoPath.substring(0, videoPath.lastIndexOf("."));
// Get subtitle extension
var subExt = selectedSub.name.substring(selectedSub.name.lastIndexOf("."));
var newSubName = selectedSub.name;
if (CONFIG.auto_rename) {
// Create new subtitle filename
newSubName = videoName + CONFIG.subtitle_suffix + subExt;
// Rename the downloaded subtitle file
var renameResult = mp.command_native({
name: "subprocess",
playback_only: false,
args: ["mv", selectedSub.name, newSubName]
});
if (renameResult.error) {
throw new Error(
"Failed to rename subtitle file: " + renameResult.error
);
}
showMessage(newSubName + " downloaded and renamed");
} else {
showMessage(newSubName + " downloaded");
}
if (CONFIG.auto_load) {
mp.commandv("sub_add", newSubName);
showMessage(newSubName + " added");
// Apply subtitle settings if configured
if (CONFIG.default_delay !== 0) {
mp.commandv("sub_delay", CONFIG.default_delay);
}
if (CONFIG.default_font_size !== 16) {
mp.commandv("sub_font_size", CONFIG.default_font_size);
}
}
if (CONFIG.run_auto_subsync) {
runAutoSubSyncMPV();
}
mp.set_property("pause", "no");
} catch (error) {
showMessage("Error: " + error.message, true);
mp.set_property("pause", "no");
}
}
function sortByPreferredFormat(files) {
return files.sort(function (a, b) {
var extA = a.name.substring(a.name.lastIndexOf(".") + 1).toLowerCase();
var extB = b.name.substring(b.name.lastIndexOf(".") + 1).toLowerCase();
var indexA = CONFIG.preferred_formats.indexOf(extA);
var indexB = CONFIG.preferred_formats.indexOf(extB);
if (indexA === -1) return 1;
if (indexB === -1) return -1;
return indexA - indexB;
});
}
function selectEpisode(anime, episode) {
mp.input.terminate();
var episodeResults;
if (episode) {
showMessage("Fetching subs for: " + anime.name + " episode " + episode);
episodeResults = api(
"https://jimaku.cc/api/entries/" + anime.id + "/files?episode=" + episode
);
} else {
showMessage("Fetching all subs for: " + anime.name);
episodeResults = api(
"https://jimaku.cc/api/entries/" + anime.id + "/files"
);
}
if (episodeResults.error) {
showMessage("Error: " + animeResults.error);
return;
}
if (episodeResults.length === 0) {
showMessage("No results found");
return;
}
// Sort results by preferred format
episodeResults = sortByPreferredFormat(episodeResults);
if (episodeResults.length === 1) {
var selectedEpisode = episodeResults[0];
selectSub(selectedEpisode);
return;
}
var items = getNames(episodeResults);
inputSelect({
prompt: "Select episode: ",
items: items,
submit: function (id) {
var selectedEpisode = episodeResults[id - 1];
selectSub(selectedEpisode);
}
});
}
function onAnimeSelected(anime) {
if (CONFIG.prompt_episode) {
inputGet({
prompt: "Episode (leave blank for all): ",
submit: function (episode) {
selectEpisode(anime, episode);
}
});
} else {
selectEpisode(anime);
}
}
function search(searchTerm, isAuto) {
mp.input.terminate();
showMessage('Searching for: "' + searchTerm + '"');
var animeResults = api(
encodeURI(
"https://jimaku.cc/api/entries/search?anime=true&query=" + searchTerm
)
);
if (animeResults.error) {
showMessage("Error: " + animeResults.error);
return;
}
if (animeResults.length === 0) {
showMessage("No results found");
if (isAuto) {
manualSearch(searchTerm);
}
return;
}
if (animeResults.length === 1) {
var selectedAnime = animeResults[0];
onAnimeSelected(selectedAnime);
return;
}
var items = getNames(animeResults);
inputSelect({
prompt: "Select anime: ",
items: items,
submit: function (id) {
var selectedAnime = animeResults[id - 1];
showMessage(selectedAnime.name, true);
onAnimeSelected(selectedAnime);
}
});
}
function manualSearch(defaultText) {
inputGet({
prompt: "Search term: ",
submit: search,
default_text: defaultText
});
mp.set_property("pause", "yes");
showMessage("Manual Jimaku Search", true);
}
function autoSearch() {
var filename = mp.get_property("filename");
var sanitizedFilename = sanitize(filename);
var currentAnime = extractTitle(sanitizedFilename);
mp.set_property("pause", "yes");
search(currentAnime, true);
}
function autoSearchParentFolder() {
var path = mp.get_property("stream-open-filename");
var pathSplit = path.split(path.indexOf("/") >= 0 ? "/" : "\\");
var filename =
pathSplit.length === 1 ? pathSplit[0] : pathSplit[pathSplit.length - 2];
var sanitizedFilename = sanitize(filename);
var currentAnime = extractTitle(sanitizedFilename);
mp.set_property("pause", "yes");
search(currentAnime, true);
}
// mp.add_key_binding(MANUAL_SEARCH_KEY, "jimaku-manual-search", manualSearch);
mp.add_key_binding(
FILENAME_AUTO_SEARCH_KEY,
"jimaku-filename-auto-search",
autoSearch
);
mp.add_key_binding(
PARENT_FOLDER_AUTO_SEARCH_KEY,
"jimaku-parent-folder-auto-search",
autoSearchParentFolder
);

View File

@@ -1 +1 @@
../ModernZ/modernz.lua ../submodules/ModernZ/modernz.lua

View File

@@ -1 +1 @@
../mpv-youtube-queue/mpv-youtube-queue.lua ../submodules/mpv-youtube-queue/mpv-youtube-queue.lua

View File

@@ -0,0 +1,83 @@
-- mpv_websocket
-- https://github.com/kuroahna/mpv_websocket
local utils = require("mp.utils")
local platform = mp.get_property_native("platform")
local config_file_path = mp.find_config_file("mpv.conf")
local config_folder_path, config_file = utils.split_path(config_file_path)
local mpv_websocket_path =
utils.join_path(config_folder_path, platform == "windows" and "mpv_websocket.exe" or "mpv_websocket")
local initialised_websocket
local _, err = utils.file_info(config_file_path)
if err then
error("failed to open mpv config file `" .. config_file_path .. "`")
end
local _, err = utils.file_info(mpv_websocket_path)
if err then
error("failed to open mpv_websocket")
end
local function find_mpv_socket(config_file_path)
local file = io.open(config_file_path, "r")
if file == nil then
error("failed to read mpv config file `" .. config_file_path .. "`")
end
local mpv_socket
for line in file:lines() do
mpv_socket = line:match("^input%-ipc%-server%s*=%s*(%g+)%s*")
if mpv_socket then
break
end
end
file:close()
if not mpv_socket then
error("input-ipc-server option does not exist in `" .. config_file_path .. "`")
end
return mpv_socket
end
local mpv_socket = find_mpv_socket(config_file_path)
if platform == "windows" then
mpv_socket = "\\\\.\\pipe" .. mpv_socket:gsub("/", "\\")
end
local function start_websocket()
initialised_websocket = mp.command_native_async({
name = "subprocess",
playback_only = false,
capture_stdout = true,
capture_stderr = true,
args = {
mpv_websocket_path,
"-m",
mpv_socket,
"-w",
"6677",
},
})
end
local function end_websocket()
mp.abort_async_command(initialised_websocket)
initialised_websocket = nil
end
local function toggle_websocket()
local paused = mp.get_property_bool("pause")
if initialised_websocket and paused then
end_websocket()
elseif not initialised_websocket and not paused then
start_websocket()
end
end
mp.register_script_message("togglewebsocket", toggle_websocket)
start_websocket()

File diff suppressed because it is too large Load Diff

View File

@@ -1,754 +0,0 @@
-- Display some stats.
--
-- Please consult the readme for information about usage and configuration:
-- https://github.com/Argon-/mpv-stats
--
-- Please note: not every property is always available and therefore not always
-- visible.
local mp = require 'mp'
local options = require 'mp.options'
local utils = require 'mp.utils'
-- Options
local o = {
-- Default key bindings
key_oneshot = "i",
key_toggle = "I",
key_page_1 = "1",
key_page_2 = "2",
key_page_3 = "3",
duration = 4,
redraw_delay = 1, -- acts as duration in the toggling case
ass_formatting = true,
persistent_overlay = false, -- whether the stats can be overwritten by other output
print_perfdata_passes = false, -- when true, print the full information about all passes
filter_params_max_length = 100, -- a filter list longer than this many characters will be shown one filter per line instead
debug = false,
-- Graph options and style
plot_perfdata = true,
plot_vsync_ratio = true,
plot_vsync_jitter = true,
skip_frames = 5,
global_max = true,
flush_graph_data = true, -- clear data buffers when toggling
plot_bg_border_color = "0000FF",
plot_bg_color = "262626",
plot_color = "FFFFFF",
-- Text style
font = "Source Sans Pro",
font_mono = "Source Sans Pro", -- monospaced digits are sufficient
font_size = 8,
font_color = "FFFFFF",
border_size = 0.8,
border_color = "262626",
shadow_x_offset = 0.0,
shadow_y_offset = 0.0,
shadow_color = "000000",
alpha = "11",
-- Custom header for ASS tags to style the text output.
-- Specifying this will ignore the text style values above and just
-- use this string instead.
custom_header = "",
-- Text formatting
-- With ASS
ass_nl = "\\N",
ass_indent = "\\h\\h\\h\\h\\h",
ass_prefix_sep = "\\h\\h",
ass_b1 = "{\\b1}",
ass_b0 = "{\\b0}",
ass_it1 = "{\\i1}",
ass_it0 = "{\\i0}",
-- Without ASS
no_ass_nl = "\n",
no_ass_indent = "\t",
no_ass_prefix_sep = " ",
no_ass_b1 = "\027[1m",
no_ass_b0 = "\027[0m",
no_ass_it1 = "\027[3m",
no_ass_it0 = "\027[0m",
}
options.read_options(o)
local format = string.format
local max = math.max
local min = math.min
-- Function used to record performance data
local recorder = nil
-- Timer used for redrawing (toggling) and clearing the screen (oneshot)
local display_timer = nil
-- Current page and <page key>:<page function> mappings
local curr_page = o.key_page_1
local pages = {}
-- Save these sequences locally as we'll need them a lot
local ass_start = mp.get_property_osd("osd-ass-cc/0")
local ass_stop = mp.get_property_osd("osd-ass-cc/1")
-- Ring buffers for the values used to construct a graph.
-- .pos denotes the current position, .len the buffer length
-- .max is the max value in the corresponding buffer
local vsratio_buf, vsjitter_buf
local function init_buffers()
vsratio_buf = {0, pos = 1, len = 50, max = 0}
vsjitter_buf = {0, pos = 1, len = 50, max = 0}
end
-- Save all properties known to this version of mpv
local property_list = {}
for p in string.gmatch(mp.get_property("property-list"), "([^,]+)") do property_list[p] = true end
-- Mapping of properties to their deprecated names
local property_aliases = {
["decoder-frame-drop-count"] = "drop-frame-count",
["frame-drop-count"] = "vo-drop-frame-count",
["container-fps"] = "fps",
}
-- Return deprecated name for the given property
local function compat(p)
while not property_list[p] and property_aliases[p] do
p = property_aliases[p]
end
return p
end
local function set_ASS(b)
if not o.use_ass or o.persistent_overlay then
return ""
end
return b and ass_start or ass_stop
end
local function no_ASS(t)
return set_ASS(false) .. t .. set_ASS(true)
end
local function b(t)
return o.b1 .. t .. o.b0
end
local function it(t)
return o.it1 .. t .. o.it0
end
local function text_style()
if not o.use_ass then
return ""
end
if o.custom_header and o.custom_header ~= "" then
return set_ASS(true) .. o.custom_header
else
return format("%s{\\r}{\\an7}{\\fs%d}{\\fn%s}{\\bord%f}{\\3c&H%s&}" ..
"{\\1c&H%s&}{\\alpha&H%s&}{\\xshad%f}{\\yshad%f}{\\4c&H%s&}",
set_ASS(true), o.font_size, o.font, o.border_size,
o.border_color, o.font_color, o.alpha, o.shadow_x_offset,
o.shadow_y_offset, o.shadow_color)
end
end
local function has_vo_window()
return mp.get_property("vo-configured") == "yes"
end
local function has_ansi()
local is_windows = type(package) == 'table'
and type(package.config) == 'string'
and package.config:sub(1, 1) == '\\'
if is_windows then
return os.getenv("ANSICON")
end
return true
end
-- Generate a graph from the given values.
-- Returns an ASS formatted vector drawing as string.
--
-- values: Array/table of numbers representing the data. Used like a ring buffer
-- it will get iterated backwards `len` times starting at position `i`.
-- i : Index of the latest data value in `values`.
-- len : The length/amount of numbers in `values`.
-- v_max : The maximum number in `values`. It is used to scale all data
-- values to a range of 0 to `v_max`.
-- v_avg : The average number in `values`. It is used to try and center graphs
-- if possible. May be left as nil
-- scale : A value that will be multiplied with all data values.
-- x_tics: Horizontal width multiplier for the steps
local function generate_graph(values, i, len, v_max, v_avg, scale, x_tics)
-- Check if at least one value exists
if not values[i] then
return ""
end
local x_max = (len - 1) * x_tics
local y_offset = o.border_size
local y_max = o.font_size * 0.66
local x = 0
-- try and center the graph if possible, but avoid going above `scale`
if v_avg then
scale = min(scale, v_max / (2 * v_avg))
end
local s = {format("m 0 0 n %f %f l ", x, y_max - (y_max * values[i] / v_max * scale))}
i = ((i - 2) % len) + 1
for p = 1, len - 1 do
if values[i] then
x = x - x_tics
s[#s+1] = format("%f %f ", x, y_max - (y_max * values[i] / v_max * scale))
end
i = ((i - 2) % len) + 1
end
s[#s+1] = format("%f %f %f %f", x, y_max, 0, y_max)
local bg_box = format("{\\bord0.5}{\\3c&H%s&}{\\1c&H%s&}m 0 %f l %f %f %f 0 0 0",
o.plot_bg_border_color, o.plot_bg_color, y_max, x_max, y_max, x_max)
return format("%s{\\r}{\\pbo%f}{\\shad0}{\\alpha&H00}{\\p1}%s{\\p0}{\\bord0}{\\1c&H%s}{\\p1}%s{\\p0}%s",
o.prefix_sep, y_offset, bg_box, o.plot_color, table.concat(s), text_style())
end
local function append(s, str, attr)
if not str then
return false
end
attr.prefix_sep = attr.prefix_sep or o.prefix_sep
attr.indent = attr.indent or o.indent
attr.nl = attr.nl or o.nl
attr.suffix = attr.suffix or ""
attr.prefix = attr.prefix or ""
attr.no_prefix_markup = attr.no_prefix_markup or false
attr.prefix = attr.no_prefix_markup and attr.prefix or b(attr.prefix)
s[#s+1] = format("%s%s%s%s%s%s", attr.nl, attr.indent,
attr.prefix, attr.prefix_sep, no_ASS(str), attr.suffix)
return true
end
-- Format and append a property.
-- A property whose value is either `nil` or empty (hereafter called "invalid")
-- is skipped and not appended.
-- Returns `false` in case nothing was appended, otherwise `true`.
--
-- s : Table containing strings.
-- prop : The property to query and format (based on its OSD representation).
-- attr : Optional table to overwrite certain (formatting) attributes for
-- this property.
-- exclude: Optional table containing keys which are considered invalid values
-- for this property. Specifying this will replace empty string as
-- default invalid value (nil is always invalid).
local function append_property(s, prop, attr, excluded)
excluded = excluded or {[""] = true}
local ret = mp.get_property_osd(prop)
if not ret or excluded[ret] then
if o.debug then
print("No value for property: " .. prop)
end
return false
end
return append(s, ret, attr)
end
local function append_perfdata(s, dedicated_page)
local vo_p = mp.get_property_native("vo-passes")
if not vo_p then
return
end
local ds = mp.get_property_bool("display-sync-active", false)
local target_fps = ds and mp.get_property_number("display-fps", 0)
or mp.get_property_number(compat("container-fps"), 0)
if target_fps > 0 then target_fps = 1 / target_fps * 1e9 end
-- Sums of all last/avg/peak values
local last_s, avg_s, peak_s = {}, {}, {}
for frame, data in pairs(vo_p) do
last_s[frame], avg_s[frame], peak_s[frame] = 0, 0, 0
for _, pass in ipairs(data) do
last_s[frame] = last_s[frame] + pass["last"]
avg_s[frame] = avg_s[frame] + pass["avg"]
peak_s[frame] = peak_s[frame] + pass["peak"]
end
end
-- Pretty print measured time
local function pp(i)
-- rescale to microseconds for a saner display
return format("%05d", i / 1000)
end
-- Format n/m with a font weight based on the ratio
local function p(n, m)
local i = 0
if m > 0 then
i = tonumber(n) / m
end
-- Calculate font weight. 100 is minimum, 400 is normal, 700 bold, 900 is max
local w = (700 * math.sqrt(i)) + 200
return format("{\\b%d}%02d%%{\\b0}", w, i * 100)
end
s[#s+1] = format("%s%s%s%s{\\fs%s}%s{\\fs%s}",
dedicated_page and "" or o.nl, dedicated_page and "" or o.indent,
b("Frame Timings:"), o.prefix_sep, o.font_size * 0.66,
"(last/average/peak μs)", o.font_size)
for frame, data in pairs(vo_p) do
local f = "%s%s%s{\\fn%s}%s / %s / %s %s%s{\\fn%s}%s%s%s"
if dedicated_page then
s[#s+1] = format("%s%s%s:", o.nl, o.indent,
b(frame:gsub("^%l", string.upper)))
for _, pass in ipairs(data) do
s[#s+1] = format(f, o.nl, o.indent, o.indent,
o.font_mono, pp(pass["last"]),
pp(pass["avg"]), pp(pass["peak"]),
o.prefix_sep .. o.prefix_sep, p(pass["last"], last_s[frame]),
o.font, o.prefix_sep, o.prefix_sep, pass["desc"])
if o.plot_perfdata and o.use_ass then
s[#s+1] = generate_graph(pass["samples"], pass["count"],
pass["count"], pass["peak"],
pass["avg"], 0.9, 0.25)
end
end
-- Print sum of timing values as "Total"
s[#s+1] = format(f, o.nl, o.indent, o.indent,
o.font_mono, pp(last_s[frame]),
pp(avg_s[frame]), pp(peak_s[frame]), "", "", o.font,
o.prefix_sep, o.prefix_sep, b("Total"))
else
-- for the simplified view, we just print the sum of each pass
s[#s+1] = format(f, o.nl, o.indent, o.indent, o.font_mono,
pp(last_s[frame]), pp(avg_s[frame]), pp(peak_s[frame]),
"", "", o.font, o.prefix_sep, o.prefix_sep,
frame:gsub("^%l", string.upper))
end
end
end
local function append_display_sync(s)
if not mp.get_property_bool("display-sync-active", false) then
return
end
local vspeed = append_property(s, "video-speed-correction", {prefix="DS:"})
if vspeed then
append_property(s, "audio-speed-correction",
{prefix="/", nl="", indent=" ", prefix_sep=" ", no_prefix_markup=true})
else
append_property(s, "audio-speed-correction",
{prefix="DS:" .. o.prefix_sep .. " - / ", prefix_sep=""})
end
append_property(s, "mistimed-frame-count", {prefix="Mistimed:", nl=""})
append_property(s, "vo-delayed-frame-count", {prefix="Delayed:", nl=""})
-- As we need to plot some graphs we print jitter and ratio on their own lines
if not display_timer.oneshot and (o.plot_vsync_ratio or o.plot_vsync_jitter) and o.use_ass then
local ratio_graph = ""
local jitter_graph = ""
if o.plot_vsync_ratio then
ratio_graph = generate_graph(vsratio_buf, vsratio_buf.pos, vsratio_buf.len, vsratio_buf.max, nil, 0.8, 1)
end
if o.plot_vsync_jitter then
jitter_graph = generate_graph(vsjitter_buf, vsjitter_buf.pos, vsjitter_buf.len, vsjitter_buf.max, nil, 0.8, 1)
end
append_property(s, "vsync-ratio", {prefix="VSync Ratio:", suffix=o.prefix_sep .. ratio_graph})
append_property(s, "vsync-jitter", {prefix="VSync Jitter:", suffix=o.prefix_sep .. jitter_graph})
else
-- Since no graph is needed we can print ratio/jitter on the same line and save some space
local vratio = append_property(s, "vsync-ratio", {prefix="VSync Ratio:"})
append_property(s, "vsync-jitter", {prefix="VSync Jitter:", nl="" or o.nl})
end
end
local function append_filters(s, prop, prefix)
local length = 0
local filters = {}
for _,f in ipairs(mp.get_property_native(prop, {})) do
local n = f.name
if f.enabled ~= nil and not f.enabled then
n = n .. " (disabled)"
end
local p = {}
for key,value in pairs(f.params) do
p[#p+1] = key .. "=" .. value
end
if #p > 0 then
p = " [" .. table.concat(p, " ") .. "]"
else
p = ""
end
length = length + n:len() + p:len()
filters[#filters+1] = no_ASS(n) .. it(no_ASS(p))
end
if #filters > 0 then
local ret
if length < o.filter_params_max_length then
ret = table.concat(filters, ", ")
else
local sep = o.nl .. o.indent .. o.indent
ret = sep .. table.concat(filters, sep)
end
s[#s+1] = o.nl .. o.indent .. b(prefix) .. o.prefix_sep .. ret
end
end
local function add_header(s)
s[#s+1] = text_style()
end
local function add_file(s)
append(s, "", {prefix="File:", nl="", indent=""})
append_property(s, "filename", {prefix_sep="", nl="", indent=""})
if not (mp.get_property_osd("filename") == mp.get_property_osd("media-title")) then
append_property(s, "media-title", {prefix="Title:"})
end
local ch_index = mp.get_property_number("chapter")
if ch_index and ch_index >= 0 then
append_property(s, "chapter-list/" .. tostring(ch_index) .. "/title", {prefix="Chapter:"})
append_property(s, "chapter-list/count",
{prefix="(" .. tostring(ch_index + 1) .. "/", suffix=")", nl="",
indent=" ", prefix_sep=" ", no_prefix_markup=true})
end
local demuxer_cache = mp.get_property_native("demuxer-cache-state", {})
if demuxer_cache["fw-bytes"] then
demuxer_cache = demuxer_cache["fw-bytes"] -- returns bytes
else
demuxer_cache = 0
end
local demuxer_secs = mp.get_property_number("demuxer-cache-duration", 0)
local stream_cache = mp.get_property_number("cache-used", 0) * 1024 -- returns KiB
if stream_cache + demuxer_cache + demuxer_secs > 0 then
append(s, utils.format_bytes_humanized(stream_cache + demuxer_cache), {prefix="Total Cache:"})
append(s, utils.format_bytes_humanized(demuxer_cache), {prefix="(Demuxer:",
suffix=",", nl="", no_prefix_markup=true, indent=o.prefix_sep})
append(s, format("%.1f", demuxer_secs), {suffix=" sec)", nl="", indent="",
no_prefix_markup=true})
local speed = mp.get_property_number("cache-speed", 0)
if speed > 0 then
append(s, utils.format_bytes_humanized(speed) .. "/s", {prefix="Speed:", nl="",
indent=o.prefix_sep, no_prefix_markup=true})
end
end
append_property(s, "file-size", {prefix="Size:"})
end
local function add_video(s)
local r = mp.get_property_native("video-params")
-- in case of e.g. lavi-complex there can be no input video, only output
if not r then
r = mp.get_property_native("video-out-params")
end
if not r then
return
end
append(s, "", {prefix=o.nl .. o.nl .. "Video:", nl="", indent=""})
if append_property(s, "video-codec", {prefix_sep="", nl="", indent=""}) then
append_property(s, "hwdec-current", {prefix="(hwdec:", nl="", indent=" ",
no_prefix_markup=true, suffix=")"}, {no=true, [""]=true})
end
append_property(s, "avsync", {prefix="A-V:"})
if append_property(s, compat("decoder-frame-drop-count"),
{prefix="Dropped Frames:", suffix=" (decoder)"}) then
append_property(s, compat("frame-drop-count"), {suffix=" (output)", nl="", indent=""})
end
if append_property(s, "display-fps", {prefix="Display FPS:", suffix=" (specified)"}) then
append_property(s, "estimated-display-fps",
{suffix=" (estimated)", nl="", indent=""})
else
append_property(s, "estimated-display-fps",
{prefix="Display FPS:", suffix=" (estimated)"})
end
if append_property(s, compat("container-fps"), {prefix="FPS:", suffix=" (specified)"}) then
append_property(s, "estimated-vf-fps",
{suffix=" (estimated)", nl="", indent=""})
else
append_property(s, "estimated-vf-fps",
{prefix="FPS:", suffix=" (estimated)"})
end
append_display_sync(s)
append_perfdata(s, o.print_perfdata_passes)
if append(s, r["w"], {prefix="Native Resolution:"}) then
append(s, r["h"], {prefix="x", nl="", indent=" ", prefix_sep=" ", no_prefix_markup=true})
end
append_property(s, "window-scale", {prefix="Window Scale:"})
append(s, format("%.2f", r["aspect"]), {prefix="Aspect Ratio:"})
append(s, r["pixelformat"], {prefix="Pixel Format:"})
-- Group these together to save vertical space
local prim = append(s, r["primaries"], {prefix="Primaries:"})
local cmat = append(s, r["colormatrix"], {prefix="Colormatrix:", nl=prim and "" or o.nl})
append(s, r["colorlevels"], {prefix="Levels:", nl=cmat and "" or o.nl})
-- Append HDR metadata conditionally (only when present and interesting)
local hdrpeak = r["sig-peak"] or 0
local hdrinfo = ""
if hdrpeak > 1 then
hdrinfo = " (HDR peak: " .. format("%.2f", hdrpeak) .. ")"
end
append(s, r["gamma"], {prefix="Gamma:", suffix=hdrinfo})
append_property(s, "packet-video-bitrate", {prefix="Bitrate:", suffix=" kbps"})
append_filters(s, "vf", "Filters:")
end
local function add_audio(s)
local r = mp.get_property_native("audio-params")
-- in case of e.g. lavi-complex there can be no input audio, only output
if not r then
r = mp.get_property_native("audio-out-params")
end
if not r then
return
end
append(s, "", {prefix=o.nl .. o.nl .. "Audio:", nl="", indent=""})
append_property(s, "audio-codec", {prefix_sep="", nl="", indent=""})
append(s, r["format"], {prefix="Format:"})
append(s, r["samplerate"], {prefix="Sample Rate:", suffix=" Hz"})
append(s, r["channel-count"], {prefix="Channels:"})
append_property(s, "packet-audio-bitrate", {prefix="Bitrate:", suffix=" kbps"})
append_filters(s, "af", "Filters:")
end
-- Determine whether ASS formatting shall/can be used and set formatting sequences
local function eval_ass_formatting()
o.use_ass = o.ass_formatting and has_vo_window()
if o.use_ass then
o.nl = o.ass_nl
o.indent = o.ass_indent
o.prefix_sep = o.ass_prefix_sep
o.b1 = o.ass_b1
o.b0 = o.ass_b0
o.it1 = o.ass_it1
o.it0 = o.ass_it0
else
o.nl = o.no_ass_nl
o.indent = o.no_ass_indent
o.prefix_sep = o.no_ass_prefix_sep
if not has_ansi() then
o.b1 = ""
o.b0 = ""
o.it1 = ""
o.it0 = ""
else
o.b1 = o.no_ass_b1
o.b0 = o.no_ass_b0
o.it1 = o.no_ass_it1
o.it0 = o.no_ass_it0
end
end
end
-- Returns an ASS string with "normal" stats
local function default_stats()
local stats = {}
eval_ass_formatting()
add_header(stats)
add_file(stats)
add_video(stats)
add_audio(stats)
return table.concat(stats)
end
-- Returns an ASS string with extended VO stats
local function vo_stats()
local stats = {}
eval_ass_formatting()
add_header(stats)
append_perfdata(stats, true)
return table.concat(stats)
end
-- Returns an ASS string with stats about filters/profiles/shaders
local function filter_stats()
return "coming soon"
end
-- Current page and <page key>:<page function> mapping
curr_page = o.key_page_1
pages = {
[o.key_page_1] = { f = default_stats, desc = "Default" },
[o.key_page_2] = { f = vo_stats, desc = "Extended Frame Timings" },
--[o.key_page_3] = { f = filter_stats, desc = "Dummy" },
}
-- Returns a function to record vsratio/jitter with the specified `skip` value
local function record_data(skip)
init_buffers()
skip = max(skip, 0)
local i = skip
return function()
if i < skip then
i = i + 1
return
else
i = 0
end
if o.plot_vsync_jitter then
local r = mp.get_property_number("vsync-jitter", nil)
if r then
vsjitter_buf.pos = (vsjitter_buf.pos % vsjitter_buf.len) + 1
vsjitter_buf[vsjitter_buf.pos] = r
vsjitter_buf.max = max(vsjitter_buf.max, r)
end
end
if o.plot_vsync_ratio then
local r = mp.get_property_number("vsync-ratio", nil)
if r then
vsratio_buf.pos = (vsratio_buf.pos % vsratio_buf.len) + 1
vsratio_buf[vsratio_buf.pos] = r
vsratio_buf.max = max(vsratio_buf.max, r)
end
end
end
end
-- Call the function for `page` and print it to OSD
local function print_page(page)
if o.persistent_overlay then
mp.set_osd_ass(0, 0, pages[page].f())
else
mp.osd_message(pages[page].f(), display_timer.oneshot and o.duration or o.redraw_delay + 1)
end
end
local function clear_screen()
if o.persistent_overlay then mp.set_osd_ass(0, 0, "") else mp.osd_message("", 0) end
end
-- Add keybindings for every page
local function add_page_bindings()
local function a(k)
return function()
curr_page = k
print_page(k)
if display_timer.oneshot then display_timer:kill() ; display_timer:resume() end
end
end
for k, _ in pairs(pages) do
mp.add_forced_key_binding(k, k, a(k), {repeatable=true})
end
end
-- Remove keybindings for every page
local function remove_page_bindings()
for k, _ in pairs(pages) do
mp.remove_key_binding(k)
end
end
local function process_key_binding(oneshot)
-- Stats are already being displayed
if display_timer:is_enabled() then
-- Previous and current keys were oneshot -> restart timer
if display_timer.oneshot and oneshot then
display_timer:kill()
print_page(curr_page)
display_timer:resume()
-- Previous and current keys were toggling -> end toggling
elseif not display_timer.oneshot and not oneshot then
display_timer:kill()
clear_screen()
remove_page_bindings()
if recorder then
mp.unregister_event(recorder)
recorder = nil
end
end
-- No stats are being displayed yet
else
if not oneshot and (o.plot_vsync_jitter or o.plot_vsync_ratio) then
recorder = record_data(o.skip_frames)
mp.register_event("tick", recorder)
end
display_timer:kill()
display_timer.oneshot = oneshot
display_timer.timeout = oneshot and o.duration or o.redraw_delay
add_page_bindings()
print_page(curr_page)
display_timer:resume()
end
end
-- Create the timer used for redrawing (toggling) or clearing the screen (oneshot)
-- The duration here is not important and always set in process_key_binding()
display_timer = mp.add_periodic_timer(o.duration,
function()
if display_timer.oneshot then
display_timer:kill() ; clear_screen() ; remove_page_bindings()
else
print_page(curr_page)
end
end)
display_timer:kill()
-- Single invocation key binding
mp.add_key_binding(o.key_oneshot, "display-stats", function() process_key_binding(true) end,
{repeatable=true})
-- Toggling key binding
mp.add_key_binding(o.key_toggle, "display-stats-toggle", function() process_key_binding(false) end,
{repeatable=false})
-- Single invocation bindings without key, can be used in input.conf to create
-- bindings for a specific page: "e script-binding stats/display-page-2"
for k, _ in pairs(pages) do
mp.add_key_binding(nil, "display-page-" .. k, function() process_key_binding(true) end,
{repeatable=true})
end
-- Reprint stats immediately when VO was reconfigured, only when toggled
mp.register_event("video-reconfig",
function()
if display_timer:is_enabled() then
print_page(curr_page)
end
end)

View File

@@ -1,92 +0,0 @@
-- Rebuild the terminal status line as a lua script
-- Be aware that this will require more cpu power!
-- Also, this is based on a rather old version of the
-- builtin mpv status line.
-- Add a string to the status line
function atsl(s)
newStatus = newStatus .. s
end
function update_status_line()
-- Reset the status line
newStatus = ""
if mp.get_property_bool("pause") then
atsl("(Paused) ")
elseif mp.get_property_bool("paused-for-cache") then
atsl("(Buffering) ")
end
if mp.get_property("aid") ~= "no" then
atsl("A")
end
if mp.get_property("vid") ~= "no" then
atsl("V")
end
atsl(": ")
atsl(mp.get_property_osd("time-pos"))
atsl(" / ");
atsl(mp.get_property_osd("duration"));
atsl(" (")
atsl(mp.get_property_osd("percent-pos", -1))
atsl("%)")
local r = mp.get_property_number("speed", -1)
if r ~= 1 then
atsl(string.format(" x%4.2f", r))
end
r = mp.get_property_number("avsync", nil)
if r ~= nil then
atsl(string.format(" A-V: %f", r))
end
r = mp.get_property("total-avsync-change", 0)
if math.abs(r) > 0.05 then
atsl(string.format(" ct:%7.3f", r))
end
r = mp.get_property_number("decoder-drop-frame-count", -1)
if r > 0 then
atsl(" Late: ")
atsl(r)
end
r = mp.get_property_osd("video-bitrate")
if r ~= nil and r ~= "" then
atsl(" Vb: ")
atsl(r)
end
r = mp.get_property_osd("audio-bitrate")
if r ~= nil and r ~= "" then
atsl(" Ab: ")
atsl(r)
end
r = mp.get_property_number("cache", 0)
if r > 0 then
atsl(string.format(" Cache: %d%% ", r))
end
-- Set the new status line
mp.set_property("options/term-status-msg", newStatus)
end
timer = mp.add_periodic_timer(1, update_status_line)
function on_pause_change(name, value)
if value == false then
timer:resume()
else
timer:stop()
end
mp.add_timeout(0.1, update_status_line)
end
mp.observe_property("pause", "bool", on_pause_change)
mp.register_event("seek", update_status_line)

1
scripts/subs2srs Symbolic link
View File

@@ -0,0 +1 @@
../submodules/mpvacious

View File

@@ -1,921 +0,0 @@
-- thumbfast.lua
--
-- High-performance on-the-fly thumbnailer
--
-- Built for easy integration in third-party UIs.
local options = {
-- Socket path (leave empty for auto)
socket = "",
-- Thumbnail path (leave empty for auto)
thumbnail = "",
-- Maximum thumbnail size in pixels (scaled down to fit)
-- Values are scaled when hidpi is enabled
max_height = 200,
max_width = 200,
-- Apply tone-mapping, no to disable
tone_mapping = "auto",
-- Overlay id
overlay_id = 42,
-- Spawn thumbnailer on file load for faster initial thumbnails
spawn_first = false,
-- Close thumbnailer process after an inactivity period in seconds, 0 to disable
quit_after_inactivity = 0,
-- Enable on network playback
network = false,
-- Enable on audio playback
audio = false,
-- Enable hardware decoding
hwdec = false,
-- Windows only: use native Windows API to write to pipe (requires LuaJIT)
direct_io = false,
-- Custom path to the mpv executable
mpv_path = "mpv"
}
mp.utils = require "mp.utils"
mp.options = require "mp.options"
mp.options.read_options(options, "thumbfast")
local properties = {}
local pre_0_30_0 = mp.command_native_async == nil
local pre_0_33_0 = true
function subprocess(args, async, callback)
callback = callback or function() end
if not pre_0_30_0 then
if async then
return mp.command_native_async({name = "subprocess", playback_only = true, args = args}, callback)
else
return mp.command_native({name = "subprocess", playback_only = false, capture_stdout = true, args = args})
end
else
if async then
return mp.utils.subprocess_detached({args = args}, callback)
else
return mp.utils.subprocess({args = args})
end
end
end
local winapi = {}
if options.direct_io then
local ffi_loaded, ffi = pcall(require, "ffi")
if ffi_loaded then
winapi = {
ffi = ffi,
C = ffi.C,
bit = require("bit"),
socket_wc = "",
-- WinAPI constants
CP_UTF8 = 65001,
GENERIC_WRITE = 0x40000000,
OPEN_EXISTING = 3,
FILE_FLAG_WRITE_THROUGH = 0x80000000,
FILE_FLAG_NO_BUFFERING = 0x20000000,
PIPE_NOWAIT = ffi.new("unsigned long[1]", 0x00000001),
INVALID_HANDLE_VALUE = ffi.cast("void*", -1),
-- don't care about how many bytes WriteFile wrote, so allocate something to store the result once
_lpNumberOfBytesWritten = ffi.new("unsigned long[1]"),
}
-- cache flags used in run() to avoid bor() call
winapi._createfile_pipe_flags = winapi.bit.bor(winapi.FILE_FLAG_WRITE_THROUGH, winapi.FILE_FLAG_NO_BUFFERING)
ffi.cdef[[
void* __stdcall CreateFileW(const wchar_t *lpFileName, unsigned long dwDesiredAccess, unsigned long dwShareMode, void *lpSecurityAttributes, unsigned long dwCreationDisposition, unsigned long dwFlagsAndAttributes, void *hTemplateFile);
bool __stdcall WriteFile(void *hFile, const void *lpBuffer, unsigned long nNumberOfBytesToWrite, unsigned long *lpNumberOfBytesWritten, void *lpOverlapped);
bool __stdcall CloseHandle(void *hObject);
bool __stdcall SetNamedPipeHandleState(void *hNamedPipe, unsigned long *lpMode, unsigned long *lpMaxCollectionCount, unsigned long *lpCollectDataTimeout);
int __stdcall MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar);
]]
winapi.MultiByteToWideChar = function(MultiByteStr)
if MultiByteStr then
local utf16_len = winapi.C.MultiByteToWideChar(winapi.CP_UTF8, 0, MultiByteStr, -1, nil, 0)
if utf16_len > 0 then
local utf16_str = winapi.ffi.new("wchar_t[?]", utf16_len)
if winapi.C.MultiByteToWideChar(winapi.CP_UTF8, 0, MultiByteStr, -1, utf16_str, utf16_len) > 0 then
return utf16_str
end
end
end
return ""
end
else
options.direct_io = false
end
end
local file = nil
local file_bytes = 0
local spawned = false
local disabled = false
local force_disabled = false
local spawn_waiting = false
local spawn_working = false
local script_written = false
local dirty = false
local x = nil
local y = nil
local last_x = x
local last_y = y
local last_seek_time = nil
local effective_w = options.max_width
local effective_h = options.max_height
local real_w = nil
local real_h = nil
local last_real_w = nil
local last_real_h = nil
local script_name = nil
local show_thumbnail = false
local filters_reset = {["lavfi-crop"]=true, ["crop"]=true}
local filters_runtime = {["hflip"]=true, ["vflip"]=true}
local filters_all = {["hflip"]=true, ["vflip"]=true, ["lavfi-crop"]=true, ["crop"]=true}
local tone_mappings = {["none"]=true, ["clip"]=true, ["linear"]=true, ["gamma"]=true, ["reinhard"]=true, ["hable"]=true, ["mobius"]=true}
local last_tone_mapping = nil
local last_vf_reset = ""
local last_vf_runtime = ""
local last_rotate = 0
local par = ""
local last_par = ""
local last_has_vid = 0
local has_vid = 0
local file_timer = nil
local file_check_period = 1/60
local allow_fast_seek = true
local client_script = [=[
#!/usr/bin/env bash
MPV_IPC_FD=0; MPV_IPC_PATH="%s"
trap "kill 0" EXIT
while [[ $# -ne 0 ]]; do case $1 in --mpv-ipc-fd=*) MPV_IPC_FD=${1/--mpv-ipc-fd=/} ;; esac; shift; done
if echo "print-text thumbfast" >&"$MPV_IPC_FD"; then echo -n > "$MPV_IPC_PATH"; tail -f "$MPV_IPC_PATH" >&"$MPV_IPC_FD" & while read -r -u "$MPV_IPC_FD" 2>/dev/null; do :; done; fi
]=]
local function get_os()
local raw_os_name = ""
if jit and jit.os and jit.arch then
raw_os_name = jit.os
else
if package.config:sub(1,1) == "\\" then
-- Windows
local env_OS = os.getenv("OS")
if env_OS then
raw_os_name = env_OS
end
else
raw_os_name = subprocess({"uname", "-s"}).stdout
end
end
raw_os_name = (raw_os_name):lower()
local os_patterns = {
["windows"] = "windows",
["linux"] = "linux",
["osx"] = "darwin",
["mac"] = "darwin",
["darwin"] = "darwin",
["^mingw"] = "windows",
["^cygwin"] = "windows",
["bsd$"] = "darwin",
["sunos"] = "darwin"
}
-- Default to linux
local str_os_name = "linux"
for pattern, name in pairs(os_patterns) do
if raw_os_name:match(pattern) then
str_os_name = name
break
end
end
return str_os_name
end
local os_name = mp.get_property("platform") or get_os()
local path_separator = os_name == "windows" and "\\" or "/"
if options.socket == "" then
if os_name == "windows" then
options.socket = "thumbfast"
else
options.socket = "/tmp/thumbfast"
end
end
if options.thumbnail == "" then
if os_name == "windows" then
options.thumbnail = os.getenv("TEMP").."\\thumbfast.out"
else
options.thumbnail = "/tmp/thumbfast.out"
end
end
local unique = mp.utils.getpid()
options.socket = options.socket .. unique
options.thumbnail = options.thumbnail .. unique
if options.direct_io then
if os_name == "windows" then
winapi.socket_wc = winapi.MultiByteToWideChar("\\\\.\\pipe\\" .. options.socket)
end
if winapi.socket_wc == "" then
options.direct_io = false
end
end
local mpv_path = options.mpv_path
if mpv_path == "mpv" and os_name == "darwin" and unique then
-- TODO: look into ~~osxbundle/
mpv_path = string.gsub(subprocess({"ps", "-o", "comm=", "-p", tostring(unique)}).stdout, "[\n\r]", "")
if mpv_path ~= "mpv" then
mpv_path = string.gsub(mpv_path, "/mpv%-bundle$", "/mpv")
local mpv_bin = mp.utils.file_info("/usr/local/mpv")
if mpv_bin and mpv_bin.is_file then
mpv_path = "/usr/local/mpv"
else
local mpv_app = mp.utils.file_info("/Applications/mpv.app/Contents/MacOS/mpv")
if mpv_app and mpv_app.is_file then
mp.msg.warn("symlink mpv to fix Dock icons: `sudo ln -s /Applications/mpv.app/Contents/MacOS/mpv /usr/local/mpv`")
else
mp.msg.warn("drag to your Applications folder and symlink mpv to fix Dock icons: `sudo ln -s /Applications/mpv.app/Contents/MacOS/mpv /usr/local/mpv`")
end
end
end
end
local function vo_tone_mapping()
local passes = mp.get_property_native("vo-passes")
if passes and passes["fresh"] then
for k, v in pairs(passes["fresh"]) do
for k2, v2 in pairs(v) do
if k2 == "desc" and v2 then
local tone_mapping = string.match(v2, "([0-9a-z.-]+) tone map")
if tone_mapping then
return tone_mapping
end
end
end
end
end
end
local function vf_string(filters, full)
local vf = ""
local vf_table = properties["vf"]
if vf_table and #vf_table > 0 then
for i = #vf_table, 1, -1 do
if filters[vf_table[i].name] then
local args = ""
for key, value in pairs(vf_table[i].params) do
if args ~= "" then
args = args .. ":"
end
args = args .. key .. "=" .. value
end
vf = vf .. vf_table[i].name .. "=" .. args .. ","
end
end
end
if (full and options.tone_mapping ~= "no") or options.tone_mapping == "auto" then
if properties["video-params"] and properties["video-params"]["primaries"] == "bt.2020" then
local tone_mapping = options.tone_mapping
if tone_mapping == "auto" then
tone_mapping = last_tone_mapping or properties["tone-mapping"]
if tone_mapping == "auto" and properties["current-vo"] == "gpu-next" then
tone_mapping = vo_tone_mapping()
end
end
if not tone_mappings[tone_mapping] then
tone_mapping = "hable"
end
last_tone_mapping = tone_mapping
vf = vf .. "zscale=transfer=linear,format=gbrpf32le,tonemap="..tone_mapping..",zscale=transfer=bt709,"
end
end
if full then
vf = vf.."scale=w="..effective_w..":h="..effective_h..par..",pad=w="..effective_w..":h="..effective_h..":x=-1:y=-1,format=bgra"
end
return vf
end
local function calc_dimensions()
local width = properties["video-out-params"] and properties["video-out-params"]["dw"]
local height = properties["video-out-params"] and properties["video-out-params"]["dh"]
if not width or not height then return end
local scale = properties["display-hidpi-scale"] or 1
if width / height > options.max_width / options.max_height then
effective_w = math.floor(options.max_width * scale + 0.5)
effective_h = math.floor(height / width * effective_w + 0.5)
else
effective_h = math.floor(options.max_height * scale + 0.5)
effective_w = math.floor(width / height * effective_h + 0.5)
end
local v_par = properties["video-out-params"] and properties["video-out-params"]["par"] or 1
if v_par == 1 then
par = ":force_original_aspect_ratio=decrease"
else
par = ""
end
end
local info_timer = nil
local function info(w, h)
local rotate = properties["video-params"] and properties["video-params"]["rotate"]
local image = properties["current-tracks"] and properties["current-tracks"]["video"] and properties["current-tracks"]["video"]["image"]
local albumart = image and properties["current-tracks"]["video"]["albumart"]
disabled = (w or 0) == 0 or (h or 0) == 0 or
has_vid == 0 or
(properties["demuxer-via-network"] and not options.network) or
(albumart and not options.audio) or
(image and not albumart) or
force_disabled
if info_timer then
info_timer:kill()
info_timer = nil
elseif has_vid == 0 or (rotate == nil and not disabled) then
info_timer = mp.add_timeout(0.05, function() info(w, h) end)
end
local json, err = mp.utils.format_json({width=w, height=h, disabled=disabled, available=true, socket=options.socket, thumbnail=options.thumbnail, overlay_id=options.overlay_id})
if pre_0_30_0 then
mp.command_native({"script-message", "thumbfast-info", json})
else
mp.command_native_async({"script-message", "thumbfast-info", json}, function() end)
end
end
local function remove_thumbnail_files()
if file then
file:close()
file = nil
file_bytes = 0
end
os.remove(options.thumbnail)
os.remove(options.thumbnail..".bgra")
end
local activity_timer
local function spawn(time)
if disabled then return end
local path = properties["path"]
if path == nil then return end
if options.quit_after_inactivity > 0 then
if show_thumbnail or activity_timer:is_enabled() then
activity_timer:kill()
end
activity_timer:resume()
end
local open_filename = properties["stream-open-filename"]
local ytdl = open_filename and properties["demuxer-via-network"] and path ~= open_filename
if ytdl then
path = open_filename
end
remove_thumbnail_files()
local vid = properties["vid"]
has_vid = vid or 0
local args = {
mpv_path, "--no-config", "--msg-level=all=no", "--idle", "--pause", "--keep-open=always", "--really-quiet", "--no-terminal",
"--load-scripts=no", "--osc=no", "--ytdl=no", "--load-stats-overlay=no", "--load-osd-console=no", "--load-auto-profiles=no",
"--edition="..(properties["edition"] or "auto"), "--vid="..(vid or "auto"), "--no-sub", "--no-audio",
"--start="..time, allow_fast_seek and "--hr-seek=no" or "--hr-seek=yes",
"--ytdl-format=worst", "--demuxer-readahead-secs=0", "--demuxer-max-bytes=128KiB",
"--vd-lavc-skiploopfilter=all", "--vd-lavc-software-fallback=1", "--vd-lavc-fast", "--vd-lavc-threads=2", "--hwdec="..(options.hwdec and "auto" or "no"),
"--vf="..vf_string(filters_all, true),
"--sws-scaler=fast-bilinear",
"--video-rotate="..last_rotate,
"--ovc=rawvideo", "--of=image2", "--ofopts=update=1", "--o="..options.thumbnail
}
if not pre_0_30_0 then
table.insert(args, "--sws-allow-zimg=no")
end
if os_name == "darwin" and properties["macos-app-activation-policy"] then
table.insert(args, "--macos-app-activation-policy=accessory")
end
if os_name == "windows" or pre_0_33_0 then
table.insert(args, "--input-ipc-server="..options.socket)
elseif not script_written then
local client_script_path = options.socket..".run"
local script = io.open(client_script_path, "w+")
if script == nil then
mp.msg.error("client script write failed")
return
else
script_written = true
script:write(string.format(client_script, options.socket))
script:close()
subprocess({"chmod", "+x", client_script_path}, true)
table.insert(args, "--scripts="..client_script_path)
end
else
local client_script_path = options.socket..".run"
table.insert(args, "--scripts="..client_script_path)
end
table.insert(args, "--")
table.insert(args, path)
spawned = true
spawn_waiting = true
subprocess(args, true,
function(success, result)
if spawn_waiting and (success == false or (result.status ~= 0 and result.status ~= -2)) then
spawned = false
spawn_waiting = false
options.tone_mapping = "no"
mp.msg.error("mpv subprocess create failed")
if not spawn_working then -- notify users of required configuration
if options.mpv_path == "mpv" then
if properties["current-vo"] == "libmpv" then
if options.mpv_path == mpv_path then -- attempt to locate ImPlay
mpv_path = "ImPlay"
spawn(time)
else -- ImPlay not in path
if os_name ~= "darwin" then
force_disabled = true
info(real_w or effective_w, real_h or effective_h)
end
mp.commandv("show-text", "thumbfast: ERROR! cannot create mpv subprocess", 5000)
mp.commandv("script-message-to", "implay", "show-message", "thumbfast initial setup", "Set mpv_path=PATH_TO_ImPlay in thumbfast config:\n" .. string.gsub(mp.command_native({"expand-path", "~~/script-opts/thumbfast.conf"}), "[/\\]", path_separator).."\nand restart ImPlay")
end
else
mp.commandv("show-text", "thumbfast: ERROR! cannot create mpv subprocess", 5000)
if os_name == "windows" then
mp.commandv("script-message-to", "mpvnet", "show-text", "thumbfast: ERROR! install standalone mpv, see README", 5000, 20)
mp.commandv("script-message", "mpv.net", "show-text", "thumbfast: ERROR! install standalone mpv, see README", 5000, 20)
end
end
else
mp.commandv("show-text", "thumbfast: ERROR! cannot create mpv subprocess", 5000)
-- found ImPlay but not defined in config
mp.commandv("script-message-to", "implay", "show-message", "thumbfast", "Set mpv_path=PATH_TO_ImPlay in thumbfast config:\n" .. string.gsub(mp.command_native({"expand-path", "~~/script-opts/thumbfast.conf"}), "[/\\]", path_separator).."\nand restart ImPlay")
end
end
elseif success == true and (result.status == 0 or result.status == -2) then
if not spawn_working and properties["current-vo"] == "libmpv" and options.mpv_path ~= mpv_path then
mp.commandv("script-message-to", "implay", "show-message", "thumbfast initial setup", "Set mpv_path=ImPlay in thumbfast config:\n" .. string.gsub(mp.command_native({"expand-path", "~~/script-opts/thumbfast.conf"}), "[/\\]", path_separator).."\nand restart ImPlay")
end
spawn_working = true
spawn_waiting = false
end
end
)
end
local function run(command)
if not spawned then return end
if options.direct_io then
local hPipe = winapi.C.CreateFileW(winapi.socket_wc, winapi.GENERIC_WRITE, 0, nil, winapi.OPEN_EXISTING, winapi._createfile_pipe_flags, nil)
if hPipe ~= winapi.INVALID_HANDLE_VALUE then
local buf = command .. "\n"
winapi.C.SetNamedPipeHandleState(hPipe, winapi.PIPE_NOWAIT, nil, nil)
winapi.C.WriteFile(hPipe, buf, #buf + 1, winapi._lpNumberOfBytesWritten, nil)
winapi.C.CloseHandle(hPipe)
end
return
end
local command_n = command.."\n"
if os_name == "windows" then
if file and file_bytes + #command_n >= 4096 then
file:close()
file = nil
file_bytes = 0
end
if not file then
file = io.open("\\\\.\\pipe\\"..options.socket, "r+b")
end
elseif pre_0_33_0 then
subprocess({"/usr/bin/env", "sh", "-c", "echo '" .. command .. "' | socat - " .. options.socket})
return
elseif not file then
file = io.open(options.socket, "r+")
end
if file then
file_bytes = file:seek("end")
file:write(command_n)
file:flush()
end
end
local function draw(w, h, script)
if not w or not show_thumbnail then return end
if x ~= nil then
if pre_0_30_0 then
mp.command_native({"overlay-add", options.overlay_id, x, y, options.thumbnail..".bgra", 0, "bgra", w, h, (4*w)})
else
mp.command_native_async({"overlay-add", options.overlay_id, x, y, options.thumbnail..".bgra", 0, "bgra", w, h, (4*w)}, function() end)
end
elseif script then
local json, err = mp.utils.format_json({width=w, height=h, x=x, y=y, socket=options.socket, thumbnail=options.thumbnail, overlay_id=options.overlay_id})
mp.commandv("script-message-to", script, "thumbfast-render", json)
end
end
local function real_res(req_w, req_h, filesize)
local count = filesize / 4
local diff = (req_w * req_h) - count
if (properties["video-params"] and properties["video-params"]["rotate"] or 0) % 180 == 90 then
req_w, req_h = req_h, req_w
end
if diff == 0 then
return req_w, req_h
else
local threshold = 5 -- throw out results that change too much
local long_side, short_side = req_w, req_h
if req_h > req_w then
long_side, short_side = req_h, req_w
end
for a = short_side, short_side - threshold, -1 do
if count % a == 0 then
local b = count / a
if long_side - b < threshold then
if req_h < req_w then return b, a else return a, b end
end
end
end
return nil
end
end
local function move_file(from, to)
if os_name == "windows" then
os.remove(to)
end
-- move the file because it can get overwritten while overlay-add is reading it, and crash the player
os.rename(from, to)
end
local function seek(fast)
if last_seek_time then
run("async seek " .. last_seek_time .. (fast and " absolute+keyframes" or " absolute+exact"))
end
end
local seek_period = 3/60
local seek_period_counter = 0
local seek_timer
seek_timer = mp.add_periodic_timer(seek_period, function()
if seek_period_counter == 0 then
seek(allow_fast_seek)
seek_period_counter = 1
else
if seek_period_counter == 2 then
if allow_fast_seek then
seek_timer:kill()
seek()
end
else seek_period_counter = seek_period_counter + 1 end
end
end)
seek_timer:kill()
local function request_seek()
if seek_timer:is_enabled() then
seek_period_counter = 0
else
seek_timer:resume()
seek(allow_fast_seek)
seek_period_counter = 1
end
end
local function check_new_thumb()
-- the slave might start writing to the file after checking existance and
-- validity but before actually moving the file, so move to a temporary
-- location before validity check to make sure everything stays consistant
-- and valid thumbnails don't get overwritten by invalid ones
local tmp = options.thumbnail..".tmp"
move_file(options.thumbnail, tmp)
local finfo = mp.utils.file_info(tmp)
if not finfo then return false end
spawn_waiting = false
local w, h = real_res(effective_w, effective_h, finfo.size)
if w then -- only accept valid thumbnails
move_file(tmp, options.thumbnail..".bgra")
real_w, real_h = w, h
if real_w and (real_w ~= last_real_w or real_h ~= last_real_h) then
last_real_w, last_real_h = real_w, real_h
info(real_w, real_h)
end
if not show_thumbnail then
file_timer:kill()
end
return true
end
return false
end
file_timer = mp.add_periodic_timer(file_check_period, function()
if check_new_thumb() then
draw(real_w, real_h, script_name)
end
end)
file_timer:kill()
local function clear()
file_timer:kill()
seek_timer:kill()
if options.quit_after_inactivity > 0 then
if show_thumbnail or activity_timer:is_enabled() then
activity_timer:kill()
end
activity_timer:resume()
end
last_seek_time = nil
show_thumbnail = false
last_x = nil
last_y = nil
if script_name then return end
if pre_0_30_0 then
mp.command_native({"overlay-remove", options.overlay_id})
else
mp.command_native_async({"overlay-remove", options.overlay_id}, function() end)
end
end
local function quit()
activity_timer:kill()
if show_thumbnail then
activity_timer:resume()
return
end
run("quit")
spawned = false
real_w, real_h = nil, nil
clear()
end
activity_timer = mp.add_timeout(options.quit_after_inactivity, quit)
activity_timer:kill()
local function thumb(time, r_x, r_y, script)
if disabled then return end
time = tonumber(time)
if time == nil then return end
if r_x == "" or r_y == "" then
x, y = nil, nil
else
x, y = math.floor(r_x + 0.5), math.floor(r_y + 0.5)
end
script_name = script
if last_x ~= x or last_y ~= y or not show_thumbnail then
show_thumbnail = true
last_x = x
last_y = y
draw(real_w, real_h, script)
end
if options.quit_after_inactivity > 0 then
if show_thumbnail or activity_timer:is_enabled() then
activity_timer:kill()
end
activity_timer:resume()
end
if time == last_seek_time then return end
last_seek_time = time
if not spawned then spawn(time) end
request_seek()
if not file_timer:is_enabled() then file_timer:resume() end
end
local function watch_changes()
if not dirty or not properties["video-out-params"] then return end
dirty = false
local old_w = effective_w
local old_h = effective_h
calc_dimensions()
local vf_reset = vf_string(filters_reset)
local rotate = properties["video-rotate"] or 0
local resized = old_w ~= effective_w or
old_h ~= effective_h or
last_vf_reset ~= vf_reset or
(last_rotate % 180) ~= (rotate % 180) or
par ~= last_par
if resized then
last_rotate = rotate
info(effective_w, effective_h)
elseif last_has_vid ~= has_vid and has_vid ~= 0 then
info(effective_w, effective_h)
end
if spawned then
if resized then
-- mpv doesn't allow us to change output size
local seek_time = last_seek_time
run("quit")
clear()
spawned = false
spawn(seek_time or mp.get_property_number("time-pos", 0))
file_timer:resume()
else
if rotate ~= last_rotate then
run("set video-rotate "..rotate)
end
local vf_runtime = vf_string(filters_runtime)
if vf_runtime ~= last_vf_runtime then
run("vf set "..vf_string(filters_all, true))
last_vf_runtime = vf_runtime
end
end
else
last_vf_runtime = vf_string(filters_runtime)
end
last_vf_reset = vf_reset
last_rotate = rotate
last_par = par
last_has_vid = has_vid
if not spawned and not disabled and options.spawn_first and resized then
spawn(mp.get_property_number("time-pos", 0))
file_timer:resume()
end
end
local function update_property(name, value)
properties[name] = value
end
local function update_property_dirty(name, value)
properties[name] = value
dirty = true
if name == "tone-mapping" then
last_tone_mapping = nil
end
end
local function update_tracklist(name, value)
-- current-tracks shim
for _, track in ipairs(value) do
if track.type == "video" and track.selected then
properties["current-tracks/video/image"] = track.image
properties["current-tracks/video/albumart"] = track.albumart
return
end
end
end
local function sync_changes(prop, val)
update_property(prop, val)
if val == nil then return end
if type(val) == "boolean" then
if prop == "vid" then
has_vid = 0
last_has_vid = 0
info(effective_w, effective_h)
clear()
return
end
val = val and "yes" or "no"
end
if prop == "vid" then
has_vid = 1
end
if not spawned then return end
run("set "..prop.." "..val)
dirty = true
end
local function file_load()
clear()
spawned = false
real_w, real_h = nil, nil
last_real_w, last_real_h = nil, nil
last_tone_mapping = nil
last_seek_time = nil
if info_timer then
info_timer:kill()
info_timer = nil
end
calc_dimensions()
info(effective_w, effective_h)
end
local function shutdown()
run("quit")
remove_thumbnail_files()
if os_name ~= "windows" then
os.remove(options.socket)
os.remove(options.socket..".run")
end
end
local function on_duration(prop, val)
allow_fast_seek = (val or 30) >= 30
end
mp.observe_property("current-tracks", "native", function(name, value)
if pre_0_33_0 then
mp.unobserve_property(update_tracklist)
pre_0_33_0 = false
end
update_property(name, value)
end)
mp.observe_property("track-list", "native", update_tracklist)
mp.observe_property("display-hidpi-scale", "native", update_property_dirty)
mp.observe_property("video-out-params", "native", update_property_dirty)
mp.observe_property("video-params", "native", update_property_dirty)
mp.observe_property("vf", "native", update_property_dirty)
mp.observe_property("tone-mapping", "native", update_property_dirty)
mp.observe_property("demuxer-via-network", "native", update_property)
mp.observe_property("stream-open-filename", "native", update_property)
mp.observe_property("macos-app-activation-policy", "native", update_property)
mp.observe_property("current-vo", "native", update_property)
mp.observe_property("video-rotate", "native", update_property)
mp.observe_property("path", "native", update_property)
mp.observe_property("vid", "native", sync_changes)
mp.observe_property("edition", "native", sync_changes)
mp.observe_property("duration", "native", on_duration)
mp.register_script_message("thumb", thumb)
mp.register_script_message("clear", clear)
mp.register_event("file-loaded", file_load)
mp.register_event("shutdown", shutdown)
mp.register_idle(watch_changes)

1
scripts/thumbfast.lua Symbolic link
View File

@@ -0,0 +1 @@
../submodules/thumbfast/thumbfast.lua

View File

@@ -1,749 +0,0 @@
local mp = require 'mp'
local msg = require 'mp.msg'
local utils = require 'mp.utils'
local options = require 'mp.options'
-- Default options
local opts = {
-- All drawing is scaled by this value, including the text borders and the
-- cursor. Change it if you have a high-DPI display.
scale = 1,
-- Set the font used for the REPL and the console. This probably doesn't
-- have to be a monospaced font.
font = "",
-- Set the font size used for the REPL and the console. This will be
-- multiplied by "scale."
font_size = 16
}
options.read_options(opts, "user_input")
local API_VERSION = "0.1.0"
local API_MAJOR_MINOR = API_VERSION:match("%d+%.%d+")
local co = nil
local queue = {}
local active_ids = {}
local histories = {}
local request = nil
local line = ''
--[[
The below code is a modified implementation of text input from mpv's console.lua:
https://github.com/mpv-player/mpv/blob/7ca14d646c7e405f3fb1e44600e2a67fc4607238/player/lua/console.lua
Modifications:
removed support for log messages, sending commands, tab complete, help commands
removed update timer
Changed esc key to call handle_esc function
handle_esc and handle_enter now resume the main coroutine with a response table
made history specific to request ids
localised all functions - reordered some to fit
keybindings use new names
]]
--
------------------------------START ORIGINAL MPV CODE-----------------------------------
----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------
-- Copyright (C) 2019 the mpv developers
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
-- copyright notice and this permission notice appear in all copies.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
local assdraw = require 'mp.assdraw'
local function detect_platform()
local o = {}
-- Kind of a dumb way of detecting the platform but whatever
if mp.get_property_native('options/vo-mmcss-profile', o) ~= o then
return 'windows'
elseif mp.get_property_native('options/macos-force-dedicated-gpu', o) ~= o then
return 'macos'
elseif os.getenv('WAYLAND_DISPLAY') then
return 'wayland'
end
return 'x11'
end
-- Pick a better default font for Windows and macOS
local platform = detect_platform()
if platform == 'windows' then
opts.font = 'Consolas'
elseif platform == 'macos' then
opts.font = 'Menlo'
else
opts.font = 'monospace'
end
local repl_active = false
local insert_mode = false
local cursor = 1
local key_bindings = {}
local global_margin_y = 0
-- Escape a string for verbatim display on the OSD
local function ass_escape(str)
-- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if
-- it isn't followed by a recognised character, so add a zero-width
-- non-breaking space
str = str:gsub('\\', '\\\239\187\191')
str = str:gsub('{', '\\{')
str = str:gsub('}', '\\}')
-- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of
-- consecutive newlines
str = str:gsub('\n', '\239\187\191\\N')
-- Turn leading spaces into hard spaces to prevent ASS from stripping them
str = str:gsub('\\N ', '\\N\\h')
str = str:gsub('^ ', '\\h')
return str
end
-- Render the REPL and console as an ASS OSD
local function update()
local dpi_scale = mp.get_property_native("display-hidpi-scale", 1.0)
dpi_scale = dpi_scale * opts.scale
local screenx, screeny, aspect = mp.get_osd_size()
screenx = screenx / dpi_scale
screeny = screeny / dpi_scale
-- Clear the OSD if the REPL is not active
if not repl_active then
mp.set_osd_ass(screenx, screeny, '')
return
end
local ass = assdraw.ass_new()
local style = '{\\r' .. '\\1a&H00&\\3a&H00&\\4a&H99&' ..
'\\1c&Heeeeee&\\3c&H111111&\\4c&H000000&' .. '\\fn' ..
opts.font .. '\\fs' .. opts.font_size ..
'\\bord1\\xshad0\\yshad1\\fsp0\\q1}'
local queue_style = '{\\r' .. '\\1a&H00&\\3a&H00&\\4a&H99&' ..
'\\1c&Heeeeee&\\3c&H111111&\\4c&H000000&' .. '\\fn' ..
opts.font .. '\\fs' .. opts.font_size ..
'\\c&H66ccff&' ..
'\\bord1\\xshad0\\yshad1\\fsp0\\q1}'
-- Create the cursor glyph as an ASS drawing. ASS will draw the cursor
-- inline with the surrounding text, but it sets the advance to the width
-- of the drawing. So the cursor doesn't affect layout too much, make it as
-- thin as possible and make it appear to be 1px wide by giving it 0.5px
-- horizontal borders.
local cheight = opts.font_size * 8
local cglyph = '{\\r' .. '\\1a&H44&\\3a&H44&\\4a&H99&' ..
'\\1c&Heeeeee&\\3c&Heeeeee&\\4c&H000000&' ..
'\\xbord0.5\\ybord0\\xshad0\\yshad1\\p4\\pbo24}' ..
'm 0 0 l 1 0 l 1 ' .. cheight .. ' l 0 ' .. cheight ..
'{\\p0}'
local before_cur = ass_escape(line:sub(1, cursor - 1))
local after_cur = ass_escape(line:sub(cursor))
ass:new_event()
ass:an(1)
ass:pos(2, screeny - 2 - global_margin_y * screeny)
if (#queue == 2) then
ass:append(queue_style ..
string.format("There is 1 more request queued\\N"))
elseif (#queue > 2) then
ass:append(queue_style ..
string.format("There are %d more requests queued\\N",
#queue - 1))
end
ass:append(style .. request.text .. '\\N')
ass:append('> ' .. before_cur)
ass:append(cglyph)
ass:append(style .. after_cur)
-- Redraw the cursor with the REPL text invisible. This will make the
-- cursor appear in front of the text.
ass:new_event()
ass:an(1)
ass:pos(2, screeny - 2)
ass:append(style .. '{\\alpha&HFF&}> ' .. before_cur)
ass:append(cglyph)
ass:append(style .. '{\\alpha&HFF&}' .. after_cur)
mp.set_osd_ass(screenx, screeny, ass.text)
end
-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
local function next_utf8(str, pos)
if pos > str:len() then return pos end
repeat
pos = pos + 1
until pos > str:len() or str:byte(pos) < 0x80 or
str:byte(pos) > 0xbf
return pos
end
-- As above, but finds the previous UTF-8 charcter in 'str' before 'pos'
local function prev_utf8(str, pos)
if pos <= 1 then return pos end
repeat
pos = pos - 1
until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) >
0xbf
return pos
end
-- Insert a character at the current cursor position (any_unicode)
local function handle_char_input(c)
if insert_mode then
line = line:sub(1, cursor - 1) .. c .. line:sub(next_utf8(line, cursor))
else
line = line:sub(1, cursor - 1) .. c .. line:sub(cursor)
end
cursor = cursor + #c
update()
end
-- Remove the character behind the cursor (Backspace)
local function handle_backspace()
if cursor <= 1 then return end
local prev = prev_utf8(line, cursor)
line = line:sub(1, prev - 1) .. line:sub(cursor)
cursor = prev
update()
end
-- Remove the character in front of the cursor (Del)
local function handle_del()
if cursor > line:len() then return end
line = line:sub(1, cursor - 1) .. line:sub(next_utf8(line, cursor))
update()
end
-- Toggle insert mode (Ins)
local function handle_ins() insert_mode = not insert_mode end
-- Move the cursor to the next character (Right)
local function next_char(amount)
cursor = next_utf8(line, cursor)
update()
end
-- Move the cursor to the previous character (Left)
local function prev_char(amount)
cursor = prev_utf8(line, cursor)
update()
end
-- Clear the current line (Ctrl+C)
local function clear()
line = ''
cursor = 1
insert_mode = false
request.history.pos = #request.history.list + 1
update()
end
-- Close the REPL if the current line is empty, otherwise do nothing (Ctrl+D)
local function maybe_exit()
if line == '' then
else
handle_del()
end
end
local function handle_esc() coroutine.resume(co, { line = nil, err = "exited" }) end
-- Run the current command and clear the line (Enter)
local function handle_enter()
if request.history.list[#request.history.list] ~= line and line ~= "" then
request.history.list[#request.history.list + 1] = line
end
coroutine.resume(co, { line = line })
end
-- Go to the specified position in the command history
local function go_history(new_pos)
local old_pos = request.history.pos
request.history.pos = new_pos
-- Restrict the position to a legal value
if request.history.pos > #request.history.list + 1 then
request.history.pos = #request.history.list + 1
elseif request.history.pos < 1 then
request.history.pos = 1
end
-- Do nothing if the history position didn't actually change
if request.history.pos == old_pos then return end
-- If the user was editing a non-history line, save it as the last history
-- entry. This makes it much less frustrating to accidentally hit Up/Down
-- while editing a line.
if old_pos == #request.history.list + 1 and line ~= '' and
request.history.list[#request.history.list] ~= line then
request.history.list[#request.history.list + 1] = line
end
-- Now show the history line (or a blank line for #history + 1)
if request.history.pos <= #request.history.list then
line = request.history.list[request.history.pos]
else
line = ''
end
cursor = line:len() + 1
insert_mode = false
update()
end
-- Go to the specified relative position in the command history (Up, Down)
local function move_history(amount) go_history(request.history.pos + amount) end
-- Go to the first command in the command history (PgUp)
local function handle_pgup() go_history(1) end
-- Stop browsing history and start editing a blank line (PgDown)
local function handle_pgdown() go_history(#request.history.list + 1) end
-- Move to the start of the current word, or if already at the start, the start
-- of the previous word. (Ctrl+Left)
local function prev_word()
-- This is basically the same as next_word() but backwards, so reverse the
-- string in order to do a "backwards" find. This wouldn't be as annoying
-- to do if Lua didn't insist on 1-based indexing.
cursor = line:len() -
select(2, line:reverse()
:find('%s*[^%s]*', line:len() - cursor + 2)) + 1
update()
end
-- Move to the end of the current word, or if already at the end, the end of
-- the next word. (Ctrl+Right)
local function next_word()
cursor = select(2, line:find('%s*[^%s]*', cursor)) + 1
update()
end
-- Move the cursor to the beginning of the line (HOME)
local function go_home()
cursor = 1
update()
end
-- Move the cursor to the end of the line (END)
local function go_end()
cursor = line:len() + 1
update()
end
-- Delete from the cursor to the beginning of the word (Ctrl+Backspace)
local function del_word()
local before_cur = line:sub(1, cursor - 1)
local after_cur = line:sub(cursor)
before_cur = before_cur:gsub('[^%s]+%s*$', '', 1)
line = before_cur .. after_cur
cursor = before_cur:len() + 1
update()
end
-- Delete from the cursor to the end of the word (Ctrl+Del)
local function del_next_word()
if cursor > line:len() then return end
local before_cur = line:sub(1, cursor - 1)
local after_cur = line:sub(cursor)
after_cur = after_cur:gsub('^%s*[^%s]+', '', 1)
line = before_cur .. after_cur
update()
end
-- Delete from the cursor to the end of the line (Ctrl+K)
local function del_to_eol()
line = line:sub(1, cursor - 1)
update()
end
-- Delete from the cursor back to the start of the line (Ctrl+U)
local function del_to_start()
line = line:sub(cursor)
cursor = 1
update()
end
-- Returns a string of UTF-8 text from the clipboard (or the primary selection)
local function get_clipboard(clip)
if platform == 'x11' then
local res = utils.subprocess({
args = {
'xclip', '-selection', clip and 'clipboard' or 'primary', '-out'
},
playback_only = false
})
if not res.error then return res.stdout end
elseif platform == 'wayland' then
local res = utils.subprocess({
args = { 'wl-paste', clip and '-n' or '-np' },
playback_only = false
})
if not res.error then return res.stdout end
elseif platform == 'windows' then
local res = utils.subprocess({
args = {
'powershell', '-NoProfile', '-Command', [[& {
Trap {
Write-Error -ErrorRecord $_
Exit 1
}
$clip = ""
if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) {
$clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText
} else {
Add-Type -AssemblyName PresentationCore
$clip = [Windows.Clipboard]::GetText()
}
$clip = $clip -Replace "`r",""
$u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip)
[Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
}]]
},
playback_only = false
})
if not res.error then return res.stdout end
elseif platform == 'macos' then
local res =
utils.subprocess({ args = { 'pbpaste' }, playback_only = false })
if not res.error then return res.stdout end
end
return ''
end
-- Paste text from the window-system's clipboard. 'clip' determines whether the
-- clipboard or the primary selection buffer is used (on X11 and Wayland only.)
local function paste(clip)
local text = get_clipboard(clip)
local before_cur = line:sub(1, cursor - 1)
local after_cur = line:sub(cursor)
line = before_cur .. text .. after_cur
cursor = cursor + text:len()
update()
end
-- List of input bindings. This is a weird mashup between common GUI text-input
-- bindings and readline bindings.
local function get_bindings()
local bindings = {
{ 'esc', handle_esc }, { 'enter', handle_enter },
{ 'kp_enter', handle_enter },
{ 'shift+enter', function() handle_char_input('\n') end },
{ 'ctrl+j', handle_enter }, { 'ctrl+m', handle_enter },
{ 'bs', handle_backspace }, { 'shift+bs', handle_backspace },
{ 'ctrl+h', handle_backspace }, { 'del', handle_del },
{ 'shift+del', handle_del }, { 'ins', handle_ins },
{ 'shift+ins', function() paste(false) end },
{ 'mbtn_mid', function() paste(false) end },
{ 'left', function() prev_char() end },
{ 'ctrl+b', function() prev_char() end },
{ 'right', function() next_char() end },
{ 'ctrl+f', function() next_char() end },
{ 'up', function() move_history(-1) end },
{ 'ctrl+p', function() move_history(-1) end },
{ 'wheel_up', function() move_history(-1) end },
{ 'down', function() move_history(1) end },
{ 'ctrl+n', function() move_history(1) end },
{ 'wheel_down', function() move_history(1) end },
{ 'wheel_left', function() end }, { 'wheel_right', function() end },
{ 'ctrl+left', prev_word }, { 'alt+b', prev_word },
{ 'ctrl+right', next_word }, { 'alt+f', next_word }, { 'ctrl+a', go_home },
{ 'home', go_home }, { 'ctrl+e', go_end }, { 'end', go_end },
{ 'pgup', handle_pgup }, { 'pgdwn', handle_pgdown }, { 'ctrl+c', clear },
{ 'ctrl+d', maybe_exit }, { 'ctrl+k', del_to_eol },
{ 'ctrl+u', del_to_start }, { 'ctrl+v', function() paste(true) end },
{ 'meta+v', function() paste(true) end }, { 'ctrl+bs', del_word },
{ 'ctrl+w', del_word }, { 'ctrl+del', del_next_word },
{ 'alt+d', del_next_word },
{ 'kp_dec', function() handle_char_input('.') end }
}
for i = 0, 9 do
bindings[#bindings + 1] = {
'kp' .. i, function() handle_char_input('' .. i) end
}
end
return bindings
end
local function text_input(info)
if info.key_text and
(info.event == "press" or info.event == "down" or info.event == "repeat") then
handle_char_input(info.key_text)
end
end
local function define_key_bindings()
if #key_bindings > 0 then return end
for _, bind in ipairs(get_bindings()) do
-- Generate arbitrary name for removing the bindings later.
local name = "_userinput_" .. bind[1]
key_bindings[#key_bindings + 1] = name
mp.add_forced_key_binding(bind[1], name, bind[2], { repeatable = true })
end
mp.add_forced_key_binding("any_unicode", "_userinput_text", text_input,
{ repeatable = true, complex = true })
key_bindings[#key_bindings + 1] = "_userinput_text"
end
local function undefine_key_bindings()
for _, name in ipairs(key_bindings) do mp.remove_key_binding(name) end
key_bindings = {}
end
-- Set the REPL visibility ("enable", Esc)
local function set_active(active)
if active == repl_active then return end
if active then
repl_active = true
insert_mode = false
define_key_bindings()
else
clear()
repl_active = false
undefine_key_bindings()
collectgarbage()
end
update()
end
utils.shared_script_property_observe("osc-margins", function(_, val)
if val then
-- formatted as "%f,%f,%f,%f" with left, right, top, bottom, each
-- value being the border size as ratio of the window size (0.0-1.0)
local vals = {}
for v in string.gmatch(val, "[^,]+") do
vals[#vals + 1] = tonumber(v)
end
global_margin_y = vals[4] -- bottom
else
global_margin_y = 0
end
update()
end)
-- Redraw the REPL when the OSD size changes. This is needed because the
-- PlayRes of the OSD will need to be adjusted.
mp.observe_property('osd-width', 'native', update)
mp.observe_property('osd-height', 'native', update)
mp.observe_property('display-hidpi-scale', 'native', update)
----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------
-------------------------------END ORIGINAL MPV CODE------------------------------------
--[[
sends a response to the original script in the form of a json string
it is expected that all requests get a response, if the input is nil then err should say why
current error codes are:
exited the user closed the input instead of pressing Enter
already_queued a request with the specified id was already in the queue
cancelled a script cancelled the request
replace replaced by another request
]]
local function send_response(res)
if res.source then
mp.commandv("script-message-to", res.source, res.response,
(utils.format_json(res)))
else
mp.commandv("script-message", res.response, (utils.format_json(res)))
end
end
-- push new request onto the queue
-- if a request with the same id already exists and the queueable flag is not enabled then
-- a nil result will be returned to the function
function push_request(req)
if active_ids[req.id] then
if req.replace then
for i, q_req in ipairs(queue) do
if q_req.id == req.id then
send_response {
err = "replaced",
response = q_req.response,
source = q_req.source
}
queue[i] = req
if i == 1 then request = req end
end
end
update()
return
end
if not req.queueable then
send_response {
err = "already_queued",
response = req.response,
source = req.source
}
return
end
end
table.insert(queue, req)
active_ids[req.id] = (active_ids[req.id] or 0) + 1
if #queue == 1 then coroutine.resume(co) end
update()
end
-- safely removes an item from the queue and updates the set of active requests
function remove_request(index)
local req = table.remove(queue, index)
active_ids[req.id] = active_ids[req.id] - 1
if active_ids[req.id] == 0 then active_ids[req.id] = nil end
return req
end
-- an infinite loop that moves through the request queue
-- uses a coroutine to handle asynchronous operations
local function driver()
while (true) do
while queue[1] do
request = queue[1]
line = request.default_input
cursor = request.cursor_pos
if repl_active then
update()
else
set_active(true)
end
res = coroutine.yield()
if res then
res.source, res.response = request.source, request.response
send_response(res)
remove_request(1)
end
end
set_active(false)
coroutine.yield()
end
end
co = coroutine.create(driver)
-- cancels any input request that returns true for the given predicate function
local function cancel_input_request(pred)
for i = #queue, 1, -1 do
if pred(i) then
req = remove_request(i)
send_response {
err = "cancelled",
response = req.response,
source = req.source
}
-- if we're removing the first item then that means the coroutine is waiting for a response
-- we will need to tell the coroutine to resume, upon which it will move to the next request
-- if there is something in the buffer then save it to the history before erasing it
if i == 1 then
local old_line = line
if old_line ~= "" then
table.insert(histories[req.id].list, old_line)
end
clear()
coroutine.resume(co)
end
end
end
end
mp.register_script_message("cancel-user-input/uid", function(uid)
cancel_input_request(function(i) return queue[i].response == uid end)
end)
-- removes all requests with the specified id from the queue
mp.register_script_message("cancel-user-input/id", function(id)
cancel_input_request(function(i) return queue[i].id == id end)
end)
-- ensures a request has the correct fields and is correctly formatted
local function format_request_fields(req)
assert(req.version, "input requests require an API version string")
if not string.find(req.version, API_MAJOR_MINOR, 1, true) then
error(
("input request has invalid version: expected %s.x, got %s"):format(
API_MAJOR_MINOR, req.version))
end
assert(req.response, "input requests require a response string")
assert(req.id, "input requests require an id string")
req.text = ass_escape(req.request_text or "")
req.default_input = req.default_input or ""
req.cursor_pos = tonumber(req.cursor_pos) or 1
req.id = req.id or "mpv"
if req.cursor_pos ~= 1 then
if req.cursor_pos < 1 then
req.cursor_pos = 1
elseif req.cursor_pos > #req.default_input then
req.cursor_pos = #req.default_input + 1
end
end
if not histories[req.id] then histories[req.id] = { pos = 1, list = {} } end
req.history = histories[req.id]
return req
end
-- updates the fields of a specific request
mp.register_script_message("update-user-input/uid", function(uid, req_opts)
req_opts = utils.parse_json(req_opts)
req_opts.response = uid
for i, req in ipairs(queue) do
if req.response == uid then
local success, result = pcall(format_request_fields, req_opts)
if not success then return msg.error(result) end
queue[i] = result
if i == 1 then request = queue[1] end
update()
return
end
end
end)
-- the function that parses the input requests
local function input_request(req)
req = format_request_fields(req)
push_request(req)
end
-- script message to recieve input requests, get-user-input.lua acts as an interface to call this script message
mp.register_script_message("request-user-input", function(req)
msg.debug(req)
req = utils.parse_json(req)
local success, err = pcall(input_request, req)
if not success then
send_response { err = err, response = req.response, source = req.source }
msg.error(err)
end
end)

View File

@@ -1,373 +0,0 @@
--[[
This script allows users to search and open youtube results from within mpv.
Available at: https://github.com/CogentRedTester/mpv-scripts
Users can open the search page with Y, and use Y again to open a search.
Alternatively, Ctrl+y can be used at any time to open a search.
Esc can be used to close the page.
Enter will open the selected item, Shift+Enter will append the item to the playlist.
This script requires that my other scripts `scroll-list` and `user-input` be installed.
scroll-list.lua and user-input-module.lua must be in the ~~/script-modules/ directory,
while user-input.lua should be loaded by mpv normally.
https://github.com/CogentRedTester/mpv-scroll-list
https://github.com/CogentRedTester/mpv-user-input
This script also requires a youtube API key to be entered.
The API key must be passed to the `API_key` script-opt.
A personal API key is free and can be created from:
https://console.developers.google.com/apis/api/youtube.googleapis.com/
The script also requires that curl be in the system path.
An alternative to using the official youtube API is to use Invidious.
This script has experimental support for Invidious searches using the 'invidious',
'API_path', and 'frontend' options. API_path refers to the url of the API the
script uses, Invidious API paths are usually in the form:
https://domain.name/api/v1/
The frontend option is the url to actualy try to load videos from. This
can probably be the same as the above url:
https://domain.name
Since the url syntax seems to be identical between Youtube and Invidious,
it should be possible to mix these options, a.k.a. using the Google
API to get videos from an Invidious frontend, or to use an Invidious
API to get videos from Youtube.
The 'invidious' option tells the script that the API_path is for an
Invidious path. This is to support other possible API options in the future.
]]
--
local mp = require "mp"
local msg = require "mp.msg"
local utils = require "mp.utils"
local opts = require "mp.options"
package.path = mp.command_native({ "expand-path", "~~/script-modules/?.lua;" }) ..
package.path
local ui = require "user-input-module"
local list = require "scroll-list"
local o = {
API_key = "",
-- number of search results to show in the list
num_results = 40,
-- the url to send API calls to
API_path = "https://www.googleapis.com/youtube/v3/",
-- attempt this API if the default fails
fallback_API_path = "",
-- the url to load videos from
frontend = "https://www.youtube.com",
-- use invidious API calls
invidious = false,
-- whether the fallback uses invidious as well
fallback_invidious = false
}
opts.read_options(o)
-- ensure the URL options are properly formatted
local function format_options()
if o.API_path:sub(-1) ~= "/" then o.API_path = o.API_path .. "/" end
if o.fallback_API_path:sub(-1) ~= "/" then
o.fallback_API_path = o.fallback_API_path .. "/"
end
if o.frontend:sub(-1) == "/" then o.frontend = o.frontend:sub(1, -2) end
end
format_options()
list.header =
("%s Search: \\N-------------------------------------------------"):format(
o.invidious and "Invidious" or "Youtube")
list.num_entries = 17
list.list_style = [[{\fs10}\N{\q2\fs25\c&Hffffff&}]]
list.empty_text = "enter search query"
local ass_escape = list.ass_escape
-- encodes a string so that it uses url percent encoding
-- this function is based on code taken from here: https://rosettacode.org/wiki/URL_encoding#Lua
local function encode_string(str)
if type(str) ~= "string" then return str end
local output, t = str:gsub("[^%w]", function(char)
return string.format("%%%X", string.byte(char))
end)
return output
end
-- convert HTML character codes to the correct characters
local function html_decode(str)
if type(str) ~= "string" then return str end
return str:gsub("&(#?)(%w-);", function(is_ascii, code)
if is_ascii == "#" then return string.char(tonumber(code)) end
if code == "amp" then return "&" end
if code == "quot" then return '"' end
if code == "apos" then return "'" end
if code == "lt" then return "<" end
if code == "gt" then return ">" end
return nil
end)
end
-- creates a formatted results table from an invidious API call
function format_invidious_results(response)
if not response then return nil end
local results = {}
for i, item in ipairs(response) do
if i > o.num_results then break end
local t = {}
table.insert(results, t)
t.title = html_decode(item.title)
t.channelTitle = html_decode(item.author)
if item.type == "video" then
t.type = "video"
t.id = item.videoId
elseif item.type == "playlist" then
t.type = "playlist"
t.id = item.playlistId
elseif item.type == "channel" then
t.type = "channel"
t.id = item.authorId
t.title = t.channelTitle
end
end
return results
end
-- creates a formatted results table from a youtube API call
function format_youtube_results(response)
if not response or not response.items then return nil end
local results = {}
for _, item in ipairs(response.items) do
local t = {}
table.insert(results, t)
t.title = html_decode(item.snippet.title)
t.channelTitle = html_decode(item.snippet.channelTitle)
if item.id.kind == "youtube#video" then
t.type = "video"
t.id = item.id.videoId
elseif item.id.kind == "youtube#playlist" then
t.type = "playlist"
t.id = item.id.playlistId
elseif item.id.kind == "youtube#channel" then
t.type = "channel"
t.id = item.id.channelId
end
end
return results
end
-- sends an API request
local function send_request(type, queries, API_path)
local url = (API_path or o.API_path) .. type
url = url .. "?"
for key, value in pairs(queries) do
msg.verbose(key, value)
url = url .. "&" .. key .. "=" .. encode_string(value)
end
msg.debug(url)
local request = mp.command_native({
name = "subprocess",
capture_stdout = true,
capture_stderr = true,
playback_only = false,
args = { "curl", url }
})
local response = utils.parse_json(request.stdout)
msg.trace(utils.to_string(request))
if request.status ~= 0 then
msg.error(request.stderr)
return nil
end
if not response then
msg.error("Could not parse response:")
msg.error(request.stdout)
return nil
end
if response.error then
msg.error(request.stdout)
return nil
end
return response
end
-- sends a search API request - handles Google/Invidious API differences
local function search_request(queries, API_path, invidious)
list.header =
("%s Search: %s\\N-------------------------------------------------"):format(
invidious and "Invidious" or "Youtube", ass_escape(queries.q, true))
list.list = {}
list.empty_text = "~"
list:update()
local results = {}
-- we need to modify the returned results so that the rest of the script can read it
if invidious then
-- Invidious searches are done with pages rather than a max result number
local page = 1
while #results < o.num_results do
queries.page = page
local response = send_request("search", queries, API_path)
response = format_invidious_results(response)
if not response then
msg.warn("Search did not return a results list");
return
end
if #response == 0 then break end
for _, item in ipairs(response) do
table.insert(results, item)
end
page = page + 1
end
else
local response = send_request("search", queries, API_path)
results = format_youtube_results(response)
end
-- print error messages to console if the API request fails
if not results then
msg.warn("Search did not return a results list")
return
end
list.empty_text = "no results"
return results
end
local function insert_video(item)
list:insert({
ass = ("%s {\\c&aaaaaa&}%s"):format(ass_escape(item.title),
ass_escape(item.channelTitle)),
url = ("%s/watch?v=%s"):format(o.frontend, item.id)
})
end
local function insert_playlist(item)
list:insert({
ass = ("🖿 %s {\\c&aaaaaa&}%s"):format(ass_escape(item.title),
ass_escape(item.channelTitle)),
url = ("%s/playlist?list=%s"):format(o.frontend, item.id)
})
end
local function insert_channel(item)
list:insert({
ass = ("👤 %s"):format(ass_escape(item.title)),
url = ("%s/channel/%s"):format(o.frontend, item.id)
})
end
local function reset_list()
list.selected = 1
list:clear()
end
-- creates the search request queries depending on what API we're using
local function get_search_queries(query, invidious)
if invidious then
return { q = query, type = "all", page = 1 }
else
return {
key = o.API_key,
q = query,
part = "id,snippet",
maxResults = o.num_results
}
end
end
local function search(query)
local response = search_request(get_search_queries(query, o.invidious),
o.API_path, o.invidious)
if not response and o.fallback_API_path ~= "/" then
msg.info("search failed - attempting fallback")
response = search_request(
get_search_queries(query, o.fallback_invidious),
o.fallback_API_path, o.fallback_invidious)
end
if not response then return end
reset_list()
for _, item in ipairs(response) do
if item.type == "video" then
insert_video(item)
elseif item.type == "playlist" then
insert_playlist(item)
elseif item.type == "channel" then
insert_channel(item)
end
end
list:update()
list:open()
end
local function play_result(flag)
if not list[list.selected] then return end
local url = list[list.selected].url
mp.msg.info("URL: " .. url)
mp.msg.info("Flag: " .. flag)
if flag == "new_window" then
mp.commandv("run", "mpv", url);
return
end
mp.commandv("loadfile", url, flag)
if flag == "replace" then
list:close()
else
mp.commandv("script-message", "add_to_youtube_queue",
list[list.selected].url, 1)
end
end
table.insert(list.keybinds,
{ "ENTER", "play", function() play_result("replace") end, {} })
table.insert(list.keybinds, {
"Ctrl+Shift", "play_append", function() play_result("append-play") end, {}
})
table.insert(list.keybinds, {
"Ctrl+Shift+ENTER", "play_new_window",
function() play_result("new_window") end, {}
})
local function open_search_input()
ui.get_user_input(function(input)
if not input then return end
search(input)
end, { request_text = "Enter Query:" })
end
mp.add_key_binding("Ctrl+y", "yt", open_search_input)
mp.add_key_binding("Y", "youtube-search", function()
if not list.hidden then
open_search_input()
else
list:open()
if #list.list == 0 then open_search_input() end
end
end)

View File

@@ -1 +1 @@
../mpv-youtube-upnext/youtube-upnext.lua ../submodules/mpv-youtube-upnext/youtube-upnext.lua

View File

@@ -1,237 +0,0 @@
local cachePath = "/tmp/ytdl-preload"
local nextIndex
local caught = true
local pop = false
local ytdl = "yt-dlp"
local utils = require 'mp.utils'
local chapter_list = {}
local json = ""
local function exists(file)
local ok, err, code = os.rename(file, file)
if not ok then
if code == 13 then -- Permission denied, but it exists
return true
end
end
return ok, err
end
-- from ytdl_hook
local function time_to_secs(time_string)
local ret
local a, b, c = time_string:match("(%d+):(%d%d?):(%d%d)")
if a ~= nil then
ret = (a * 3600 + b * 60 + c)
else
a, b = time_string:match("(%d%d?):(%d%d)")
if a ~= nil then ret = (a * 60 + b) end
end
return ret
end
local function extract_chapters(data, video_length)
local ret = {}
for line in data:gmatch("[^\r\n]+") do
local time = time_to_secs(line)
if time and (time < video_length) then
table.insert(ret, { time = time, title = line })
end
end
table.sort(ret, function(a, b) return a.time < b.time end)
return ret
end
local function chapters()
if json.chapters then
for i = 1, #json.chapters do
local chapter = json.chapters[i]
local title = chapter.title or ""
if title == "" then
title = string.format('Chapter %02d', i)
end
table.insert(chapter_list,
{ time = chapter.start_time, title = title })
end
elseif not (json.description == nil) and not (json.duration == nil) then
chapter_list = extract_chapters(json.description, json.duration)
end
end
-- end ytdl_hook
local title = ""
local function listener(event)
if not caught and event.prefix == mp.get_script_name() then
local destination = string.match(event.text,
"%[download%] Destination: (.+).mkv") or
string.match(event.text,
"%[download%] (.+).mkv has already been downloaded")
if destination and
string.find(destination, string.gsub(cachePath, '~/', '')) then
_, title = utils.split_path(destination)
local audio = ""
if exists(destination .. ".mka") then
audio = "audio-file=" .. destination .. '.mka,'
end
mp.commandv("loadfile", destination .. ".mkv", "append",
audio .. 'force-media-title="' ..
title:gsub("-" .. ("[%w_-]"):rep(11) .. "$", "") ..
'",demuxer-max-back-bytes=1MiB,demuxer-max-bytes=3MiB,ytdl=no') -- ,sub-file="..destination..".en.vtt") --in case they are not set up to autoload
mp.commandv("playlist_move", mp.get_property("playlist-count") - 1,
nextIndex)
mp.commandv("playlist_remove", nextIndex + 1)
mp.unregister_event(listener)
caught = true
title = ""
pop = true
end
end
end
-- from ytdl_hook
mp.add_hook("on_preloaded", 10, function()
if string.find(mp.get_property("path"), cachePath) then
chapters()
if next(chapter_list) ~= nil then
mp.set_property_native("chapter-list", chapter_list)
chapter_list = {}
json = ""
end
end
end)
-- end ytdl_hook
local function DL()
-- mp.add_timeout(1, function()
if tonumber(mp.get_property("playlist-pos-1")) > 0 and
mp.get_property("playlist-pos-1") ~= mp.get_property("playlist-count") then
nextIndex = tonumber(mp.get_property("playlist-pos")) + 1
local nextFile = mp.get_property(
"playlist/" .. tostring(nextIndex) .. "/filename")
if nextFile and caught and nextFile:find("://", 0, false) then
caught = false
mp.enable_messages("info")
mp.register_event("log-message", listener)
local ytFormat = mp.get_property("ytdl-format")
local fVideo = string.match(ytFormat, '(.+)%+.+//?') or 'bestvideo'
local fAudio = string.match(ytFormat, '.+%+(.+)//?') or 'bestaudio'
json = mp.command_native({
name = "subprocess",
args = { ytdl, "--dump-single-json", nextFile },
capture_stdout = true,
capture_stderr = true
})
if json then
json = json.stdout
if json:find("audio only") then
mp.command_native_async({
name = "subprocess",
args = {
ytdl, "-q", "-f", fAudio, "--restrict-filenames",
"--no-playlist", "--sub-lang", "en", "--write-sub",
"--no-part", "-o",
cachePath .. "/%(title)s-%(id)s.mka", nextFile
},
playback_only = false
}, function() end)
else
if fVideo:find("bestvideo") then
fVideo = fVideo:gsub("bestvideo", "best")
end
end
json = utils.parse_json(json)
end
mp.command_native_async({
name = "subprocess",
-- args = {ytdl, "-f", fVideo..'/best', "--restrict-filenames", "--no-part", "-N","2","-o", cachePath.."/%(title)s-%(id)s.mkv", nextFile},
args = {
ytdl, "-f", fVideo .. '/best', "--restrict-filenames",
"--no-playlist", "--no-part", "-o",
cachePath .. "/%(title)s-%(id)s.mkv", nextFile
},
playback_only = false
}, function() end)
end
end
-- end)
end
local function clearCache()
if pop == true then
if package.config:sub(1, 1) ~= '/' then
os.execute('rd /s/q "' .. cachePath .. '"')
else
os.execute('rm -rd ' .. cachePath)
end
print('clear')
mp.command("quit")
end
end
local skipInitial
mp.observe_property("playlist-count", "number", function()
if skipInitial then
DL()
else
skipInitial = true
end
end)
-- from ytdl_hook
local platform_is_windows = (package.config:sub(1, 1) == "\\")
local o = {
exclude = "",
try_ytdl_first = false,
use_manifests = false,
all_formats = false,
force_all_formats = true,
ytdl_path = ""
}
local paths_to_search = { "yt-dlp", "yt-dlp_x86", "youtube-dl" }
local options = require 'mp.options'
options.read_options(o, "ytdl_hook")
local separator = platform_is_windows and ";" or ":"
if o.ytdl_path:match("[^" .. separator .. "]") then
paths_to_search = {}
for path in o.ytdl_path:gmatch("[^" .. separator .. "]+") do
table.insert(paths_to_search, path)
end
end
local function exec(args)
local ret = mp.command_native({
name = "subprocess",
args = args,
capture_stdout = true,
capture_stderr = true
})
return ret.status, ret.stdout, ret, ret.killed_by_us
end
local msg = require 'mp.msg'
local command = {}
for _, path in pairs(paths_to_search) do
-- search for youtube-dl in mpv's config dir
local exesuf = platform_is_windows and ".exe" or ""
local ytdl_cmd = mp.find_config_file(path .. exesuf)
if ytdl_cmd then
msg.verbose("Found youtube-dl at: " .. ytdl_cmd)
ytdl = ytdl_cmd
break
else
msg.verbose("No youtube-dl found with path " .. path .. exesuf ..
" in config directories")
-- search in PATH
command[1] = path
es, json, result, aborted = exec(command)
if result.error_string == "init" then
msg.verbose("youtube-dl with path " .. path .. exesuf ..
" not found in PATH or not enough permissions")
else
msg.verbose("Found youtube-dl with path " .. path .. exesuf ..
" in PATH")
ytdl = path
break
end
end
end
-- end ytdl_hook
mp.register_event("start-file", DL)
mp.register_event("shutdown", clearCache)

1
scripts/ytdl-preload.lua Symbolic link
View File

@@ -0,0 +1 @@
../submodules/ytdl-preload/ytdl-preload.lua

1407
shaders/ArtCNN_C4F16.glsl Normal file

File diff suppressed because it is too large Load Diff

1407
shaders/ArtCNN_C4F16_DS.glsl Normal file

File diff suppressed because it is too large Load Diff

3927
shaders/ArtCNN_C4F32.glsl Normal file

File diff suppressed because it is too large Load Diff

3927
shaders/ArtCNN_C4F32_DS.glsl Normal file

File diff suppressed because it is too large Load Diff

1
submodules/ModernZ Submodule

Submodule submodules/ModernZ added at cd23007c69

1
submodules/animecards Submodule

Submodule submodules/animecards added at ced1d30630

1
submodules/mpvacious Submodule

Submodule submodules/mpvacious added at 01c6adc825

1
submodules/thumbfast Submodule

Submodule submodules/thumbfast added at 9deb0733c4