Compare commits
69 Commits
c938e82868
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
6eadea6f00
|
|||
|
05adde2559
|
|||
|
7579628faf
|
|||
|
5581723522
|
|||
|
b6390baec6
|
|||
|
21998241ba
|
|||
|
57f6a5db30
|
|||
|
5a6ac4bb3a
|
|||
|
5f08f09b2c
|
|||
|
9819a64f01
|
|||
|
73a305af0b
|
|||
|
9ac84aba18
|
|||
|
cfc6ac22e5
|
|||
|
e4afe79832
|
|||
|
ca0899d233
|
|||
|
deb591481e
|
|||
|
472899b680
|
|||
|
2f3ccc60fc
|
|||
|
02d3c00229
|
|||
|
f3a4afcf76
|
|||
|
087f16dfd0
|
|||
|
0b9f814eca
|
|||
|
efb9737e14
|
|||
|
56e477bff1
|
|||
|
04fabf6356
|
|||
|
d9747459d8
|
|||
|
33ea15228e
|
|||
|
1cd68861f5
|
|||
|
2c90b30993
|
|||
|
382c29ba42
|
|||
| 37b67be69b | |||
| 2ed3918aca | |||
| f0237be035 | |||
|
5260564aaf
|
|||
|
1c6904a8a5
|
|||
|
e0f195e0ee
|
|||
| 76ddb33630 | |||
|
7df9682378
|
|||
| 0c7677b6ab | |||
| 1f74dad64a | |||
| fc734fcffe | |||
| 3c8cc50e97 | |||
|
cbdca67613
|
|||
|
3f0308189d
|
|||
|
81471617f2
|
|||
|
|
c3555288a9 | ||
|
|
c2e9ae47e1 | ||
|
|
afa6c8e2c0 | ||
|
|
8ded1b4f0c | ||
|
|
9a193ac5c3 | ||
|
|
6e64e0c70f | ||
|
|
f596071c87 | ||
|
|
7904708a42 | ||
|
|
829141ccd9 | ||
|
|
0f23f6f7e6 | ||
|
|
7088ac357f | ||
|
|
57e17054b0 | ||
|
|
701ca424eb | ||
|
|
059283f217 | ||
|
|
f3a3f91da3 | ||
|
|
9aa407538e | ||
|
|
9e4077de96 | ||
| fccac9d3da | |||
| 65b60a75ea | |||
|
|
9e43e9f88d | ||
|
|
b82b16cf92 | ||
|
|
8fb0bdf1d0 | ||
|
|
7d55c2fb7a | ||
|
|
51ecbeab02 |
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
immersive-data
|
||||||
|
.ytdl_preload/
|
||||||
|
.ytdl-preload/
|
||||||
|
scripts/anilistUpdater/anilistToken.txt
|
||||||
|
.watch-later
|
||||||
|
state
|
||||||
32
.gitmodules
vendored
32
.gitmodules
vendored
@@ -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
2
.prettierrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
trailingComma: "es5"
|
||||||
|
singleQuote: true
|
||||||
1
ModernZ
1
ModernZ
Submodule ModernZ deleted from a03db11405
91
input.conf
91
input.conf
@@ -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
243
mac.conf
Normal 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
18
mpv-fonts.conf
Normal 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
Submodule mpv-youtube-queue deleted from 725ef8db36
277
mpv.conf
277
mpv.conf
@@ -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
BIN
mpv_websocket
Executable file
Binary file not shown.
BIN
mpv_websocket-mac
Executable file
BIN
mpv_websocket-mac
Executable file
Binary file not shown.
@@ -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
|
|
||||||
11
script-opts/anilistUpdater.conf
Normal file
11
script-opts/anilistUpdater.conf
Normal 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
107
script-opts/animecards.conf
Normal 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)
|
||||||
33
script-opts/autosubsync.conf
Normal file
33
script-opts/autosubsync.conf
Normal 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
|
||||||
22
script-opts/immersion-tracker.conf
Normal file
22
script-opts/immersion-tracker.conf
Normal 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
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
1
script-opts/stats.conf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
plot_tonemapping_lut=yes
|
||||||
331
script-opts/subs2srs.conf
Normal file
331
script-opts/subs2srs.conf
Normal 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
|
||||||
1
script-opts/ytdl_preload.conf
Normal file
1
script-opts/ytdl_preload.conf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
temp=/tmp/ytdl-preload
|
||||||
1
scripts/anilistUpdater/anilistUpdater.py
Symbolic link
1
scripts/anilistUpdater/anilistUpdater.py
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../submodules/mpv-anilist-updater/anilistUpdater/anilistUpdater.py
|
||||||
1
scripts/anilistUpdater/main.lua
Symbolic link
1
scripts/anilistUpdater/main.lua
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../submodules/mpv-anilist-updater/anilistUpdater/main.lua
|
||||||
1
scripts/animecards
Symbolic link
1
scripts/animecards
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../submodules/animecards/animecards
|
||||||
1
scripts/autosubsync-mpv
Symbolic link
1
scripts/autosubsync-mpv
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../submodules/autosubsync-mpv
|
||||||
1
scripts/immersion-tracker
Symbolic link
1
scripts/immersion-tracker
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../submodules/immersion-tracker
|
||||||
388
scripts/jimaku.js
Normal file
388
scripts/jimaku.js
Normal 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
|
||||||
|
);
|
||||||
@@ -1 +1 @@
|
|||||||
../ModernZ/modernz.lua
|
../submodules/ModernZ/modernz.lua
|
||||||
@@ -1 +1 @@
|
|||||||
../mpv-youtube-queue/mpv-youtube-queue.lua
|
../submodules/mpv-youtube-queue/mpv-youtube-queue.lua
|
||||||
83
scripts/run_websocket_server.lua
Normal file
83
scripts/run_websocket_server.lua
Normal 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
@@ -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)
|
|
||||||
@@ -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
1
scripts/subs2srs
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../submodules/mpvacious
|
||||||
@@ -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
1
scripts/thumbfast.lua
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../submodules/thumbfast/thumbfast.lua
|
||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
@@ -1 +1 @@
|
|||||||
../mpv-youtube-upnext/youtube-upnext.lua
|
../submodules/mpv-youtube-upnext/youtube-upnext.lua
|
||||||
@@ -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
1
scripts/ytdl-preload.lua
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../submodules/ytdl-preload/ytdl-preload.lua
|
||||||
1407
shaders/ArtCNN_C4F16.glsl
Normal file
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
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
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
3927
shaders/ArtCNN_C4F32_DS.glsl
Normal file
File diff suppressed because it is too large
Load Diff
1
submodules/ModernZ
Submodule
1
submodules/ModernZ
Submodule
Submodule submodules/ModernZ added at cd23007c69
1
submodules/animecards
Submodule
1
submodules/animecards
Submodule
Submodule submodules/animecards added at ced1d30630
1
submodules/autosubsync-mpv
Submodule
1
submodules/autosubsync-mpv
Submodule
Submodule submodules/autosubsync-mpv added at 125ac13d1b
1
submodules/immersion-tracker
Submodule
1
submodules/immersion-tracker
Submodule
Submodule submodules/immersion-tracker added at 2d8d139cb0
1
submodules/mpv-anilist-updater
Submodule
1
submodules/mpv-anilist-updater
Submodule
Submodule submodules/mpv-anilist-updater added at a5851c4c0e
1
submodules/mpv-youtube-queue
Submodule
1
submodules/mpv-youtube-queue
Submodule
Submodule submodules/mpv-youtube-queue added at 9d1b6d7eab
1
submodules/mpvacious
Submodule
1
submodules/mpvacious
Submodule
Submodule submodules/mpvacious added at 01c6adc825
1
submodules/thumbfast
Submodule
1
submodules/thumbfast
Submodule
Submodule submodules/thumbfast added at 9deb0733c4
1
submodules/ytdl-preload
Submodule
1
submodules/ytdl-preload
Submodule
Submodule submodules/ytdl-preload added at 39759ecdc3
Reference in New Issue
Block a user