Compare commits
36 Commits
76ddb33630
...
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
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@ immersive-data
|
|||||||
.ytdl_preload/
|
.ytdl_preload/
|
||||||
.ytdl-preload/
|
.ytdl-preload/
|
||||||
scripts/anilistUpdater/anilistToken.txt
|
scripts/anilistUpdater/anilistToken.txt
|
||||||
|
.watch-later
|
||||||
|
state
|
||||||
|
|||||||
33
.gitmodules
vendored
33
.gitmodules
vendored
@@ -1,18 +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-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"]
|
[submodule "ytdl-preload"]
|
||||||
path = ytdl-preload
|
path = submodules/ytdl-preload
|
||||||
url = git@gist.github.com:17d90e3deeb35b5f75e55adb19098f58.git
|
url = git@gist.github.com:17d90e3deeb35b5f75e55adb19098f58.git
|
||||||
[submodule "autosubsync-mpv"]
|
[submodule "mpv-anilist-updater"]
|
||||||
path = autosubsync-mpv
|
path = submodules/mpv-anilist-updater
|
||||||
url = git@github.com:Ajatt-Tools/autosubsync-mpv.git
|
url = git@github.com:AzuredBlue/mpv-anilist-updater.git
|
||||||
[submodule "scripts/autosubsync-mpv"]
|
[submodule "thumbfast"]
|
||||||
path = scripts/autosubsync-mpv
|
path = submodules/thumbfast
|
||||||
url = git@github.com:Ajatt-Tools/autosubsync-mpv.git
|
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 2d5537aa72
78
input.conf
78
input.conf
@@ -193,19 +193,89 @@ CTRL+1 no-osd change-list glsl-shaders set "~/.config/mpv/shaders/FSR.glsl"; sho
|
|||||||
CTRL+2 no-osd change-list glsl-shaders set "~/.config/mpv/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 "~/.config/mpv/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 "~/.config/mpv/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/FSRCNNX.glsl:~/.config/mpv/shaders/CAS-scaled.glsl:~/.config/mpv/shaders/NVScaler.glsl"; show-text "ALL"
|
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
|
ctrl+s script_binding autosubsync-menu
|
||||||
|
|
||||||
|
# {{{ sponsorblock
|
||||||
ctrl+g script-binding sponsorblock/set_segment
|
ctrl+g script-binding sponsorblock/set_segment
|
||||||
ctrl+G script-binding sponsorblock/submit_segment
|
ctrl+G script-binding sponsorblock/submit_segment
|
||||||
ctrl+h script-binding sponsorablock/upvote_setment
|
ctrl+< script-binding sponsorblock/upvote_setment
|
||||||
ctrl+H script-binding sponsorablock/downvote_segment
|
ctrl+> script-binding sponsorblock/downvote_segment
|
||||||
|
#}}}
|
||||||
|
|
||||||
|
# {{{ anilist
|
||||||
ctrl+A script-binding update_anilist
|
ctrl+A script-binding update_anilist
|
||||||
ctrl+B script-binding launch_anilist
|
ctrl+B script-binding launch_anilist
|
||||||
ctrl+E script-binding open_folder
|
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
|
||||||
242
mpv.conf
242
mpv.conf
@@ -1,162 +1,21 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
#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; 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
|
||||||
#
|
|
||||||
# Use extra large RAM cache (needs cache=yes to make it useful).
|
|
||||||
demuxer-max-bytes=500M
|
demuxer-max-bytes=500M
|
||||||
demuxer-max-back-bytes=100M
|
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
|
cache-pause=no
|
||||||
#
|
ontop=yes
|
||||||
# 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="JetBrainsMono Nerd Font"
|
||||||
sub-font-size=45
|
sub-font-size=45
|
||||||
# osd-font="Fluent System Icons"
|
# osd-font="Fluent System Icons"
|
||||||
border=no
|
border=no
|
||||||
|
# geometry=50%
|
||||||
geometry=50%
|
autofit=50%
|
||||||
|
volume=75
|
||||||
volume=50
|
audio-spdif=ac3,dts-hd,truehd
|
||||||
# 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/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"
|
# glsl-shaders="~~/shaders/FSRCNNX.glsl:~~/shaders/FSR.glsl:~~/shaders/NVScaler.glsl:~~/shaders/CAS-scaled.glsl"
|
||||||
# glsl-shaders="~~/shaders/FSR.glsl"
|
# glsl-shaders="~~/shaders/ArtCNN_C4F32_DS.glsl"
|
||||||
|
glsl-shaders="~~/shaders/ArtCNN_C4F32.glsl"
|
||||||
|
|
||||||
# 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
|
||||||
@@ -172,46 +31,79 @@ ytdl-format=bestvideo+bestaudio/best
|
|||||||
sub-auto=fuzzy
|
sub-auto=fuzzy
|
||||||
slang=en,eng
|
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=svp
|
||||||
profile=gpu-hq
|
profile=high-quality
|
||||||
|
blend-subtitles=video
|
||||||
# GPU OPTIONS
|
# GPU OPTIONS
|
||||||
vo=gpu-next
|
vo=gpu-next
|
||||||
hwdec=nvdec-copy
|
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
|
# laptop
|
||||||
# vo=gpu
|
# vo=gpu
|
||||||
# gpu-api=opengl
|
# gpu-api=opengl
|
||||||
# gpu-context=wayland
|
# gpu-context=wayland
|
||||||
# profile=opengl-hq
|
# 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
|
input-ipc-server=/tmp/mpvsocket
|
||||||
# ao=pule,pipewire
|
# ao=pule,pipewire
|
||||||
ao=pipewire,pulse
|
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]
|
[hdr]
|
||||||
target-colorspace-hint=yes
|
target-colorspace-hint=yes
|
||||||
gpu-api=vulkan
|
gpu-api=vulkan
|
||||||
@@ -230,14 +122,18 @@ keepaspect=no
|
|||||||
|
|
||||||
[immersion]
|
[immersion]
|
||||||
cookies=yes
|
cookies=yes
|
||||||
cookies-file=/truenas/sudacode/japanese/cookies.txt
|
cookies-file=/truenas/sudacode/japanese/cookies.Japanese.txt
|
||||||
ytdl-raw-options=mark-watched=,write-auto-subs=,sub-langs=ja.*
|
ytdl-raw-options=mark-watched=,write-auto-subs=,sub-langs=ja.*
|
||||||
ytdl-raw-options-append=cookies=/truenas/sudacode/japanese/cookies.txt
|
ytdl-raw-options-append=cookies=/truenas/sudacode/japanese/cookies.Japanese.txt
|
||||||
ytdl-raw-options-append=sponsorblock-mark=all
|
ytdl-raw-options-append=sponsorblock-mark=all
|
||||||
ytdl-raw-options-append=sponsorblock-remove=sponsor
|
ytdl-raw-options-append=sponsorblock-remove=sponsor
|
||||||
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=ja,jpn
|
slang=ja,jpn,JA,JPN,ja.hi,ja.*
|
||||||
alang=ja,jpn
|
alang=ja,jpn
|
||||||
vlang=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-mac
Executable file
BIN
mpv_websocket-mac
Executable file
Binary file not shown.
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)
|
||||||
@@ -19,13 +19,13 @@ audio_subsync_tool=ffsubsync
|
|||||||
|
|
||||||
# 2. Preferred tool for syncing to another subtitle.
|
# 2. Preferred tool for syncing to another subtitle.
|
||||||
# altsub_subsync_tool=ask
|
# altsub_subsync_tool=ask
|
||||||
altsub_subsync_tool=ffsubsync
|
# altsub_subsync_tool=ffsubsync
|
||||||
# altsub_subsync_tool=alass
|
altsub_subsync_tool=alass
|
||||||
|
|
||||||
# Unload old subs (yes,no)
|
# Unload old subs (yes,no)
|
||||||
# After retiming, tell mpv to forget the original subtitle track.
|
# After retiming, tell mpv to forget the original subtitle track.
|
||||||
# unload_old_sub=yes
|
unload_old_sub=yes
|
||||||
unload_old_sub=no
|
# unload_old_sub=no
|
||||||
|
|
||||||
# Overwrite the original subtitle file.
|
# Overwrite the original subtitle file.
|
||||||
# Replace the old subtitle file with the retimed file.
|
# Replace the old subtitle file with the retimed file.
|
||||||
|
|||||||
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
|
||||||
|
|
||||||
@@ -175,47 +175,47 @@ 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=#F45C5B
|
windowcontrols_close_hover=#ED8796
|
||||||
# color of maximize window controls on hover
|
# color of maximize window controls on hover
|
||||||
windowcontrols_max_hover=#F8BC3A
|
windowcontrols_max_hover=#EED49F
|
||||||
# color of minimize window controls on hover
|
# color of minimize window controls on hover
|
||||||
windowcontrols_min_hover=#43CB44
|
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
|
||||||
|
|||||||
@@ -30,6 +30,6 @@ menu_timeout=5
|
|||||||
show_errors=yes
|
show_errors=yes
|
||||||
ytdlp_file_format=mp4
|
ytdlp_file_format=mp4
|
||||||
ytdlp_output_template=%(uploader)s/%(title)s.%(ext)s
|
ytdlp_output_template=%(uploader)s/%(title)s.%(ext)s
|
||||||
use_history_db=no
|
use_history_db=yes
|
||||||
backend_host=http://localhost
|
backend_host=http://localhost
|
||||||
backend_port=42069
|
backend_port=42069
|
||||||
|
|||||||
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,568 +0,0 @@
|
|||||||
import ast
|
|
||||||
import hashlib
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import webbrowser
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from guessit import guessit
|
|
||||||
|
|
||||||
|
|
||||||
class AniListUpdater:
|
|
||||||
ANILIST_API_URL = "https://graphql.anilist.co"
|
|
||||||
TOKEN_PATH = os.path.join(os.path.dirname(__file__), "anilistToken.txt")
|
|
||||||
OPTIONS = "--excludes country --excludes language --type episode"
|
|
||||||
CACHE_REFRESH_RATE = 24 * 60 * 60
|
|
||||||
|
|
||||||
# Load token and user id
|
|
||||||
def __init__(self):
|
|
||||||
self.access_token = (
|
|
||||||
self.load_access_token()
|
|
||||||
) # Replace token here if you don't use the .txt
|
|
||||||
self.user_id = self.get_user_id()
|
|
||||||
|
|
||||||
# Load token from anilistToken.txt
|
|
||||||
def load_access_token(self):
|
|
||||||
try:
|
|
||||||
with open(self.TOKEN_PATH, "r") as file:
|
|
||||||
content = file.read().strip()
|
|
||||||
if ":" in content:
|
|
||||||
token = content.split(":", 1)[1].splitlines()[0]
|
|
||||||
return token
|
|
||||||
|
|
||||||
return content
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error reading access token: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Load user id from file, if not then make api request and save it.
|
|
||||||
def get_user_id(self):
|
|
||||||
try:
|
|
||||||
with open(self.TOKEN_PATH, "r") as file:
|
|
||||||
content = file.read().strip()
|
|
||||||
if ":" in content:
|
|
||||||
return int(content.split(":")[0])
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error reading user ID: {e}")
|
|
||||||
|
|
||||||
query = """
|
|
||||||
query {
|
|
||||||
Viewer {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
response = self.make_api_request(query, None, self.access_token)
|
|
||||||
if response and "data" in response:
|
|
||||||
user_id = response["data"]["Viewer"]["id"]
|
|
||||||
self.save_user_id(user_id)
|
|
||||||
return user_id
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Cache user id
|
|
||||||
def save_user_id(self, user_id):
|
|
||||||
try:
|
|
||||||
with open(self.TOKEN_PATH, "r+") as file:
|
|
||||||
content = file.read()
|
|
||||||
file.seek(0)
|
|
||||||
file.write(f"{user_id}:{content}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error saving user ID: {e}")
|
|
||||||
|
|
||||||
def cache_to_file(self, path, guessed_name, result):
|
|
||||||
try:
|
|
||||||
with open(self.TOKEN_PATH, "a") as file:
|
|
||||||
# Epoch Time, hash of the path, guessed name, result
|
|
||||||
file.write(
|
|
||||||
f"\n{time.time()};;{self.hash_path(os.path.dirname(path))};;{guessed_name};;{result}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error trying to cache {result}: {e}")
|
|
||||||
|
|
||||||
def hash_path(self, path):
|
|
||||||
return hashlib.sha256(path.encode("utf-8")).hexdigest()
|
|
||||||
|
|
||||||
def check_and_clean_cache(self, path, guessed_name):
|
|
||||||
try:
|
|
||||||
valid_lines = []
|
|
||||||
unique = set()
|
|
||||||
path = self.hash_path(os.path.dirname(path))
|
|
||||||
cached_result = (None, None)
|
|
||||||
|
|
||||||
with open(self.TOKEN_PATH, "r+") as file:
|
|
||||||
orig_lines = file.readlines()
|
|
||||||
|
|
||||||
for line in orig_lines:
|
|
||||||
if line.strip():
|
|
||||||
if ";;" in line:
|
|
||||||
epoch, dir_path, guess, result = line.strip().split(";;")
|
|
||||||
|
|
||||||
if (
|
|
||||||
time.time() - float(epoch) < self.CACHE_REFRESH_RATE
|
|
||||||
and (dir_path, guess) not in unique
|
|
||||||
):
|
|
||||||
unique.add((dir_path, guess))
|
|
||||||
valid_lines.append(line)
|
|
||||||
|
|
||||||
if dir_path == path and guess == guessed_name:
|
|
||||||
cached_result = (result, len(valid_lines) - 1)
|
|
||||||
else:
|
|
||||||
valid_lines.append(line)
|
|
||||||
|
|
||||||
if valid_lines != orig_lines:
|
|
||||||
with open(self.TOKEN_PATH, "w") as file:
|
|
||||||
file.writelines(valid_lines)
|
|
||||||
|
|
||||||
return cached_result
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error trying to read cache file: {e}")
|
|
||||||
|
|
||||||
def update_cache(self, path, guessed_name, result, index):
|
|
||||||
try:
|
|
||||||
with open(self.TOKEN_PATH, "r") as file:
|
|
||||||
lines = file.readlines()
|
|
||||||
|
|
||||||
if 0 <= index < len(lines):
|
|
||||||
# Update the line at the given index with the new cache data
|
|
||||||
updated_line = (
|
|
||||||
f"{time.time()};;{self.hash_path(os.path.dirname(path))};;{guessed_name};;{result}\n"
|
|
||||||
if result is not None
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
lines[index] = updated_line
|
|
||||||
|
|
||||||
# Write the updated lines back to the file
|
|
||||||
with open(self.TOKEN_PATH, "w") as file:
|
|
||||||
file.writelines(lines)
|
|
||||||
|
|
||||||
else:
|
|
||||||
print(f"Invalid index {index} for updating cache.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error trying to update cache file: {e}")
|
|
||||||
|
|
||||||
# Function to make an api request to AniList's api
|
|
||||||
def make_api_request(self, query, variables=None, access_token=None):
|
|
||||||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
|
||||||
|
|
||||||
if access_token:
|
|
||||||
headers["Authorization"] = f"Bearer {access_token}"
|
|
||||||
|
|
||||||
response = requests.post(
|
|
||||||
self.ANILIST_API_URL,
|
|
||||||
json={"query": query, "variables": variables},
|
|
||||||
headers=headers,
|
|
||||||
)
|
|
||||||
# print(f"Made an API Query with: Query: {query}\nVariables: {variables} ")
|
|
||||||
if response.status_code == 200:
|
|
||||||
return response.json()
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
f"API request failed: {response.status_code} - {response.text}\nQuery: {query}\nVariables: {variables}"
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def season_order(season):
|
|
||||||
return {"WINTER": 1, "SPRING": 2, "SUMMER": 3, "FALL": 4}.get(season, 5)
|
|
||||||
|
|
||||||
def filter_valid_seasons(self, seasons):
|
|
||||||
# Filter only to those whose format is TV and duration > 21 OR those who have no duration and are releasing.
|
|
||||||
# This is due to newly added anime having duration as null
|
|
||||||
seasons = [
|
|
||||||
season
|
|
||||||
for season in seasons
|
|
||||||
if (
|
|
||||||
(season["duration"] is None and season["status"] == "RELEASING")
|
|
||||||
or (season["duration"] is not None and season["duration"] > 21)
|
|
||||||
)
|
|
||||||
and season["format"] == "TV"
|
|
||||||
]
|
|
||||||
# One of the problems with this filter is needing the format to be 'TV'
|
|
||||||
# But if accepted any format, it would also include many ONA's which arent included in absolute numbering.
|
|
||||||
|
|
||||||
# Sort them based on release date
|
|
||||||
seasons = sorted(
|
|
||||||
seasons,
|
|
||||||
key=lambda x: (
|
|
||||||
x["seasonYear"] if x["seasonYear"] else float("inf"),
|
|
||||||
self.season_order(x["season"] if x["season"] else float("inf")),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return seasons
|
|
||||||
|
|
||||||
# Finds the season and episode of an anime with absolute numbering
|
|
||||||
def find_season_and_episode(self, seasons, absolute_episode):
|
|
||||||
accumulated_episodes = 0
|
|
||||||
for season in seasons:
|
|
||||||
season_episodes = (
|
|
||||||
season.get("episodes", 12) if season.get("episodes") else 12
|
|
||||||
)
|
|
||||||
|
|
||||||
if accumulated_episodes + season_episodes >= absolute_episode:
|
|
||||||
return (
|
|
||||||
season.get("id"),
|
|
||||||
season.get("title", {}).get("romaji"),
|
|
||||||
(
|
|
||||||
season.get("mediaListEntry", {}).get("progress")
|
|
||||||
if season.get("mediaListEntry")
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
season.get("episodes"),
|
|
||||||
absolute_episode - accumulated_episodes,
|
|
||||||
)
|
|
||||||
accumulated_episodes += season_episodes
|
|
||||||
return (None, None, None, None, None)
|
|
||||||
|
|
||||||
def handle_filename(self, filename):
|
|
||||||
file_info = self.parse_filename(filename)
|
|
||||||
cached_result, line_index = self.check_and_clean_cache(
|
|
||||||
filename, file_info.get("name")
|
|
||||||
)
|
|
||||||
# str -> tuple
|
|
||||||
cached_result = ast.literal_eval(cached_result) if cached_result else None
|
|
||||||
|
|
||||||
# True if:
|
|
||||||
# Is not cached
|
|
||||||
# Tries to update and current episode is not the next one.
|
|
||||||
# It is not in your watching/planning list.
|
|
||||||
# This means that for shows with absolute numbering, if it updates, it will always call the API
|
|
||||||
# Since it needs to convert from absolute to relative.
|
|
||||||
if cached_result is None or (
|
|
||||||
cached_result
|
|
||||||
and (file_info.get("episode") != cached_result[2] + 1)
|
|
||||||
and sys.argv[2] != "launch"
|
|
||||||
):
|
|
||||||
result = self.get_anime_info_and_progress(
|
|
||||||
file_info.get("name"), file_info.get("episode"), file_info.get("year")
|
|
||||||
)
|
|
||||||
result = self.update_episode_count(
|
|
||||||
result
|
|
||||||
) # Returns either the same, or the updated result
|
|
||||||
|
|
||||||
# If it returned a result and the progress isnt None, then put it in cache, since it wasn't.
|
|
||||||
if result and result[2] is not None:
|
|
||||||
if line_index is not None:
|
|
||||||
print(f"Updating cache to: {result}")
|
|
||||||
self.update_cache(
|
|
||||||
filename, file_info.get("name"), result, line_index
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
print(f"Not found in cache! Adding to file... {result}")
|
|
||||||
self.cache_to_file(filename, file_info.get("name"), result)
|
|
||||||
|
|
||||||
# True for opening AniList and updating next episode.
|
|
||||||
else:
|
|
||||||
print(f"Found in cache! {cached_result}")
|
|
||||||
# Change to the episode that needs to be updated
|
|
||||||
cached_result = cached_result[:4] + (file_info.get("episode"),)
|
|
||||||
result = self.update_episode_count(cached_result)
|
|
||||||
|
|
||||||
# If it's different, update in cache as well.
|
|
||||||
if cached_result != result and result:
|
|
||||||
print(f"Updating cache to: {result}")
|
|
||||||
self.update_cache(filename, file_info.get("name"), result, line_index)
|
|
||||||
|
|
||||||
# If it either errored or couldn't update, retry without cache.
|
|
||||||
if not result:
|
|
||||||
print(f"Failed to update through cache, retrying without.")
|
|
||||||
# Deleting from the cache
|
|
||||||
self.update_cache(filename, file_info.get("name"), None, line_index)
|
|
||||||
# Retrying
|
|
||||||
self.handle_filename(filename)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
# Hardcoded exceptions to fix detection
|
|
||||||
# Easier than just renaming my files 1 by 1 on Qbit
|
|
||||||
# Every exception I find will be added here
|
|
||||||
def fix_filename(self, path_parts):
|
|
||||||
guess = guessit(
|
|
||||||
path_parts[-1], self.OPTIONS
|
|
||||||
) # Simply easier for fixing the filename if we have what it is detecting.
|
|
||||||
|
|
||||||
path_parts[-1] = os.path.splitext(path_parts[-1])[0]
|
|
||||||
pattern = r'[\\\/:!\*\?"<>\|\._-]'
|
|
||||||
|
|
||||||
title_depth = -1
|
|
||||||
|
|
||||||
# Fix from folders if the everything is not in the filename
|
|
||||||
if "title" not in guess:
|
|
||||||
# Depth=2
|
|
||||||
for depth in range(2, min(4, len(path_parts))):
|
|
||||||
folder_guess = guessit(path_parts[-depth], self.OPTIONS)
|
|
||||||
if "title" in folder_guess:
|
|
||||||
guess["title"] = folder_guess["title"]
|
|
||||||
title_depth = -depth
|
|
||||||
break
|
|
||||||
|
|
||||||
if "title" not in guess:
|
|
||||||
print(
|
|
||||||
f"Couldn't find title in filename '{path_parts[-1]}'! Guess result: {guess}"
|
|
||||||
)
|
|
||||||
return path_parts
|
|
||||||
|
|
||||||
# Only clean up titles for some series
|
|
||||||
cleanup_titles = ["Ranma", "Chi", "Bleach"]
|
|
||||||
if any(title in guess["title"] for title in cleanup_titles):
|
|
||||||
path_parts[title_depth] = re.sub(pattern, " ", path_parts[title_depth])
|
|
||||||
path_parts[title_depth] = " ".join(path_parts[title_depth].split())
|
|
||||||
|
|
||||||
if "Centimeters per Second" == guess["title"] and 5 == guess.get("episode", 0):
|
|
||||||
path_parts[title_depth] = path_parts[title_depth].replace(" 5 ", " Five ")
|
|
||||||
# For some reason AniList has this film in 3 parts.
|
|
||||||
path_parts[title_depth] = path_parts[title_depth].replace(
|
|
||||||
"per Second", "per Second 3"
|
|
||||||
)
|
|
||||||
|
|
||||||
return path_parts
|
|
||||||
|
|
||||||
# Parse the file name using guessit
|
|
||||||
def parse_filename(self, filepath):
|
|
||||||
path_parts = self.fix_filename(filepath.replace("\\", "/").split("/"))
|
|
||||||
filename = path_parts[-1]
|
|
||||||
name, season, part, year = "", "", "", ""
|
|
||||||
episode = 1
|
|
||||||
# First, try to guess from the filename
|
|
||||||
guess = guessit(filename, self.OPTIONS)
|
|
||||||
print(f"File name guess: {filename} -> {dict(guess)}")
|
|
||||||
|
|
||||||
# Episode guess from the title.
|
|
||||||
# Usually, releases are formated [Release Group] Title - S01EX
|
|
||||||
|
|
||||||
# If the episode index is 0, that would mean that the episode is before the title in the filename
|
|
||||||
# Which is a horrible way of formatting it, so assume its wrong
|
|
||||||
|
|
||||||
# If its 1, then the title is probably 0, so its okay. (Unless season is 0)
|
|
||||||
# Really? What is the format "S1E1 - {title}"? That's almost psycopathic.
|
|
||||||
|
|
||||||
# If its >2, theres probably a Release Group and Title / Season / Part, so its good
|
|
||||||
|
|
||||||
episode = guess.get("episode", None)
|
|
||||||
season = guess.get("season", "")
|
|
||||||
part = str(guess.get("part", ""))
|
|
||||||
year = str(guess.get("year", ""))
|
|
||||||
|
|
||||||
# Quick fixes assuming season before episode
|
|
||||||
# 'episode_title': '02' in 'S2 02'
|
|
||||||
if guess.get("episode_title", "").isdigit() and episode is None:
|
|
||||||
print(
|
|
||||||
f'Detected episode in episode_title. Episode: {int(guess.get("episode_title"))}'
|
|
||||||
)
|
|
||||||
episode = int(guess.get("episode_title"))
|
|
||||||
|
|
||||||
# 'episode': [86, 13] (EIGHTY-SIX), [1, 2, 3] (RANMA) lol.
|
|
||||||
if isinstance(episode, list):
|
|
||||||
print(f"Detected multiple episodes: {episode}. Picking last one.")
|
|
||||||
episode = episode[-1]
|
|
||||||
|
|
||||||
# 'season': [2, 3] in "S2 03"
|
|
||||||
if isinstance(season, list):
|
|
||||||
print(f"Detected multiple seasons: {season}. Picking first one as season.")
|
|
||||||
if episode is None:
|
|
||||||
print(
|
|
||||||
"Episode still not detected. Picking last position of the season list."
|
|
||||||
)
|
|
||||||
episode = season[-1]
|
|
||||||
|
|
||||||
season = season[0]
|
|
||||||
|
|
||||||
episode = episode or 1
|
|
||||||
season = str(season)
|
|
||||||
|
|
||||||
keys = list(guess.keys())
|
|
||||||
episode_index = keys.index("episode") if "episode" in guess else 1
|
|
||||||
season_index = keys.index("season") if "season" in guess else -1
|
|
||||||
title_in_filename = "title" in guess and (
|
|
||||||
episode_index > 0 and (season_index > 0 or season_index == -1)
|
|
||||||
)
|
|
||||||
|
|
||||||
# If the title is not in the filename or episode index is 0, try the folder name
|
|
||||||
# If the episode index > 0 and season index > 0, its safe to assume that the title is in the file name
|
|
||||||
|
|
||||||
if title_in_filename:
|
|
||||||
name = guess["title"]
|
|
||||||
else:
|
|
||||||
# If it isnt in the name of the file, try to guess using the name of the folder it is stored in
|
|
||||||
|
|
||||||
# Depth=2 folders
|
|
||||||
for depth in [2, 3]:
|
|
||||||
folder_guess = (
|
|
||||||
guessit(path_parts[-depth], self.OPTIONS)
|
|
||||||
if len(path_parts) > depth - 1
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
if folder_guess != "":
|
|
||||||
print(
|
|
||||||
f'{depth-1}{"st" if depth-1==1 else "nd"} Folder guess:\n{path_parts[-depth]} -> {dict(folder_guess)}'
|
|
||||||
)
|
|
||||||
|
|
||||||
name = str(folder_guess.get("title", ""))
|
|
||||||
season = season or str(folder_guess.get("season", ""))
|
|
||||||
part = part or str(folder_guess.get("part", ""))
|
|
||||||
year = year or str(folder_guess.get("year", ""))
|
|
||||||
|
|
||||||
if name != "":
|
|
||||||
break # If we got the name, its probable we already got season and part from the way folders are usually structured
|
|
||||||
|
|
||||||
# Add season and part if there are
|
|
||||||
if season and (int(season) > 1 or part):
|
|
||||||
name += f" Season {season}"
|
|
||||||
|
|
||||||
if part:
|
|
||||||
name += f" Part {part}"
|
|
||||||
|
|
||||||
print("Guessed name: " + name)
|
|
||||||
return {
|
|
||||||
"name": name,
|
|
||||||
"episode": episode,
|
|
||||||
"year": year,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_anime_info_and_progress(self, name, file_progress, year):
|
|
||||||
query = """
|
|
||||||
query($search: String, $year: FuzzyDateInt, $page: Int) {
|
|
||||||
Page(page: $page) {
|
|
||||||
media (search: $search, type: ANIME, startDate_greater: $year) {
|
|
||||||
id
|
|
||||||
title { romaji }
|
|
||||||
season
|
|
||||||
seasonYear
|
|
||||||
episodes
|
|
||||||
duration
|
|
||||||
format
|
|
||||||
status
|
|
||||||
mediaListEntry {
|
|
||||||
status
|
|
||||||
progress
|
|
||||||
media {
|
|
||||||
episodes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
variables = {"search": name, "year": year or 1, "page": 1}
|
|
||||||
|
|
||||||
response = self.make_api_request(query, variables, self.access_token)
|
|
||||||
if response and "data" in response:
|
|
||||||
seasons = response["data"]["Page"]["media"]
|
|
||||||
# This is the first element, which is the same as Media(search: $search)
|
|
||||||
|
|
||||||
if len(seasons) == 0:
|
|
||||||
raise Exception(f"Couldn't find an anime from this title! ({name})")
|
|
||||||
|
|
||||||
anime_data = (
|
|
||||||
seasons[0]["id"],
|
|
||||||
seasons[0]["title"]["romaji"],
|
|
||||||
(
|
|
||||||
seasons[0]["mediaListEntry"]["progress"]
|
|
||||||
if seasons[0]["mediaListEntry"] is not None
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
seasons[0]["episodes"],
|
|
||||||
file_progress,
|
|
||||||
)
|
|
||||||
# If the episode in the file name is larger than the total amount of episodes
|
|
||||||
# Then they are using absolute numbering format for episodes (looking at you SubsPlease)
|
|
||||||
# Try to guess season and episode.
|
|
||||||
if (
|
|
||||||
seasons[0]["episodes"] is not None
|
|
||||||
and file_progress > seasons[0]["episodes"]
|
|
||||||
):
|
|
||||||
seasons = self.filter_valid_seasons(seasons)
|
|
||||||
print(
|
|
||||||
"Related shows:",
|
|
||||||
", ".join(season["title"]["romaji"] for season in seasons),
|
|
||||||
)
|
|
||||||
|
|
||||||
anime_data = self.find_season_and_episode(seasons, file_progress)
|
|
||||||
|
|
||||||
print(
|
|
||||||
f"Final guessed anime: {next(season for season in seasons if season['id'] == anime_data[0])}"
|
|
||||||
) # Print data of the show
|
|
||||||
print(
|
|
||||||
f"Absolute episode {file_progress} corresponds to Anime: {anime_data[1]}, Episode: {anime_data[-1]}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
print(f"Final guessed anime: {seasons[0]}") # Print data of the show
|
|
||||||
return anime_data
|
|
||||||
return (None, None, None, None)
|
|
||||||
|
|
||||||
# Update the anime based on file progress
|
|
||||||
def update_episode_count(self, result):
|
|
||||||
if result is None:
|
|
||||||
raise Exception("Parameter in update_episode_count is null.")
|
|
||||||
|
|
||||||
anime_id, anime_name, current_progress, total_episodes, file_progress = result
|
|
||||||
|
|
||||||
# Only launch anilist
|
|
||||||
if sys.argv[2] == "launch":
|
|
||||||
print(
|
|
||||||
f'Opening AniList for "{anime_name}": https://anilist.co/anime/{anime_id}'
|
|
||||||
)
|
|
||||||
webbrowser.open_new_tab(f"https://anilist.co/anime/{anime_id}")
|
|
||||||
return result
|
|
||||||
|
|
||||||
if current_progress is None:
|
|
||||||
raise Exception(
|
|
||||||
"Failed to get current episode count. Is it on your watching/planning list?"
|
|
||||||
)
|
|
||||||
|
|
||||||
# If its lower than the current progress, dont update.
|
|
||||||
if file_progress <= current_progress:
|
|
||||||
raise Exception(
|
|
||||||
f"Episode was not new. Not updating ({file_progress} <= {current_progress})"
|
|
||||||
)
|
|
||||||
|
|
||||||
query = """
|
|
||||||
mutation ($mediaId: Int, $progress: Int, $status: MediaListStatus) {
|
|
||||||
SaveMediaListEntry (mediaId: $mediaId, progress: $progress, status: $status) {
|
|
||||||
status
|
|
||||||
id
|
|
||||||
progress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
variables = {"mediaId": anime_id, "progress": file_progress}
|
|
||||||
|
|
||||||
# Handle changing "Planned to watch" animes to "Watching"
|
|
||||||
if file_progress != total_episodes:
|
|
||||||
variables["status"] = (
|
|
||||||
"CURRENT" # Set to "CURRENT" if it isn't the final episode.
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self.make_api_request(query, variables, self.access_token)
|
|
||||||
if response and "data" in response:
|
|
||||||
updated_progress = response["data"]["SaveMediaListEntry"]["progress"]
|
|
||||||
print(
|
|
||||||
f"Episode count updated successfully! New progress: {updated_progress}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
anime_id,
|
|
||||||
anime_name,
|
|
||||||
updated_progress,
|
|
||||||
total_episodes,
|
|
||||||
file_progress,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
print("Failed to update episode count.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
try:
|
|
||||||
updater = AniListUpdater()
|
|
||||||
updater.handle_filename(sys.argv[1])
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERROR: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
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,129 +0,0 @@
|
|||||||
local utils = require("mp.utils")
|
|
||||||
|
|
||||||
-- The directory the script will work on
|
|
||||||
-- Leaving it blank will make it work on every video you watch with mpv
|
|
||||||
-- You can still update manually via Ctrl+A
|
|
||||||
-- Setting a directory will only work if the path of the video contains this directory
|
|
||||||
DIRECTORY = ""
|
|
||||||
|
|
||||||
-- Example: DIRECTORY = "D:/Torrents" or "D:/Anime"
|
|
||||||
|
|
||||||
function callback(success, result, error)
|
|
||||||
if result.status == 0 then
|
|
||||||
mp.osd_message("Updated anime correctly.", 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_python_command()
|
|
||||||
local os_name = package.config:sub(1, 1)
|
|
||||||
if os_name == "\\" then
|
|
||||||
-- Windows
|
|
||||||
return "python"
|
|
||||||
else
|
|
||||||
-- Linux
|
|
||||||
return "python3"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local python_command = get_python_command()
|
|
||||||
|
|
||||||
-- Make sure it doesnt trigger twice in 1 video
|
|
||||||
local triggered = false
|
|
||||||
|
|
||||||
-- Function to check if we've reached 85% of the video
|
|
||||||
function check_progress()
|
|
||||||
if triggered then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local percent_pos = mp.get_property_number("percent-pos")
|
|
||||||
|
|
||||||
if percent_pos then
|
|
||||||
if percent_pos >= 85 then
|
|
||||||
update_anilist("update")
|
|
||||||
triggered = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Function to launch the .py script
|
|
||||||
function update_anilist(action)
|
|
||||||
if action == "launch" then
|
|
||||||
mp.osd_message("Launching AniList", 2)
|
|
||||||
end
|
|
||||||
local script_dir = debug.getinfo(1).source:match("@?(.*/)")
|
|
||||||
local directory = mp.get_property("working-directory")
|
|
||||||
-- It seems like in Linux working-directory sometimes returns it without a "/" at the end
|
|
||||||
directory = (directory:sub(-1) == "/" or directory:sub(-1) == "\\") and directory or directory .. "/"
|
|
||||||
-- For some reason, "path" sometimes returns the absolute path, sometimes it doesn't.
|
|
||||||
local file_path = mp.get_property("path")
|
|
||||||
local path = utils.join_path(directory, file_path)
|
|
||||||
|
|
||||||
local table = {}
|
|
||||||
table.name = "subprocess"
|
|
||||||
table.args = { python_command, script_dir .. "anilistUpdater.py", path, action }
|
|
||||||
local cmd = mp.command_native_async(table, callback)
|
|
||||||
end
|
|
||||||
|
|
||||||
mp.observe_property("percent-pos", "number", check_progress)
|
|
||||||
|
|
||||||
-- Reset triggered
|
|
||||||
mp.register_event("file-loaded", function()
|
|
||||||
triggered = false
|
|
||||||
if DIRECTORY ~= "" then
|
|
||||||
local directory = mp.get_property("working-directory")
|
|
||||||
directory = (directory:sub(-1) == "/" or directory:sub(-1) == "\\") and directory or directory .. "/"
|
|
||||||
local file_path = mp.get_property("path")
|
|
||||||
local path = utils.join_path(directory, file_path)
|
|
||||||
path = path:gsub("\\", "/")
|
|
||||||
|
|
||||||
if string.find(path, DIRECTORY) ~= 1 then
|
|
||||||
mp.unobserve_property(check_progress)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Keybinds, modify as you please
|
|
||||||
mp.add_key_binding("ctrl+a", "update_anilist", function()
|
|
||||||
update_anilist("update")
|
|
||||||
end)
|
|
||||||
|
|
||||||
mp.add_key_binding("ctrl+b", "launch_anilist", function()
|
|
||||||
update_anilist("launch")
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Open the folder that the video is
|
|
||||||
function open_folder()
|
|
||||||
local path = mp.get_property("path")
|
|
||||||
local directory
|
|
||||||
|
|
||||||
if not path then
|
|
||||||
mp.msg.warn("No file is currently playing.")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if path:find("\\") then
|
|
||||||
directory = path:match("(.*)\\")
|
|
||||||
elseif path:find("\\\\") then
|
|
||||||
directory = path:match("(.*)\\\\")
|
|
||||||
else
|
|
||||||
directory = mp.get_property("working-directory")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Use the system command to open the folder in File Explorer
|
|
||||||
local args
|
|
||||||
if package.config:sub(1, 1) == "\\" then
|
|
||||||
-- Windows
|
|
||||||
args = { "explorer", directory }
|
|
||||||
elseif os.getenv("XDG_CURRENT_DESKTOP") or os.getenv("WAYLAND_DISPLAY") or os.getenv("DISPLAY") then
|
|
||||||
-- Linux (assume a desktop environment like GNOME, KDE, etc.)
|
|
||||||
args = { "xdg-open", directory }
|
|
||||||
elseif package.config:sub(1, 1) == "/" then
|
|
||||||
-- macOS
|
|
||||||
args = { "open", directory }
|
|
||||||
end
|
|
||||||
|
|
||||||
mp.command_native({ name = "subprocess", args = args, detach = true })
|
|
||||||
end
|
|
||||||
|
|
||||||
mp.add_key_binding("ctrl+d", "open_folder", open_folder)
|
|
||||||
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,496 +0,0 @@
|
|||||||
------------- Instructions -------------
|
|
||||||
-- -- Video Demonstration: https://www.youtube.com/watch?v=M4t7HYS73ZQ
|
|
||||||
-- IF USING WEBSOCKET (RECOMMENDED)
|
|
||||||
-- -- Install the mpv_webscoket extension: https://github.com/kuroahna/mpv_websocket
|
|
||||||
-- -- Open a LOCAL copy of https://github.com/Renji-XD/texthooker-ui
|
|
||||||
-- -- Configure the script (if you're not using the Lapis note format)
|
|
||||||
-- IF USING CLIPBOARD INSERTER (NOT RECOMMENDED)
|
|
||||||
-- -- Install the clipboard inserter plugin: https://github.com/laplus-sadness/lap-clipboard-inserter
|
|
||||||
-- -- Open the texthooker UI, enable the plugin and enable clipboard pasting: https://github.com/Renji-XD/texthooker-ui
|
|
||||||
-- BOTH
|
|
||||||
-- -- Wait for an unknown word and create the card with Yomichan.
|
|
||||||
-- -- Select all the subtitle lines you wish to add to the card and copy with Ctrl + c.
|
|
||||||
-- -- Press Ctrl + v in MPV to add the lines, their Audio and the currently paused image to the back of the card.
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
------------- Credits -------------
|
|
||||||
-- Credits and copyright go to Anacreon DJT: https://anacreondjt.gitlab.io/
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
------------- Original Credits (Outdated) -------------
|
|
||||||
-- This script was made by users of 4chan's Daily Japanese Thread (DJT) on /jp/
|
|
||||||
-- More information can be found here http://animecards.site/
|
|
||||||
-- Message @Anacreon with bug reports and feature requests on Discord (https://animecards.site/discord/) or 4chan (https://boards.4channel.org/jp/#s=djt)
|
|
||||||
--
|
|
||||||
-- If you like this work please consider subscribing on Patreon!
|
|
||||||
-- https://www.patreon.com/Quizmaster
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
local utils = require("mp.utils")
|
|
||||||
local msg = require("mp.msg")
|
|
||||||
|
|
||||||
------------- User Config -------------
|
|
||||||
-- Set these to match your field names in Anki
|
|
||||||
local FRONT_FIELD = "Expression"
|
|
||||||
local SENTENCE_AUDIO_FIELD = "SentenceAudio"
|
|
||||||
local SENTENCE_FIELD = "Sentence"
|
|
||||||
local IMAGE_FIELD = "Picture"
|
|
||||||
-- Optional padding and fade settings in seconds.
|
|
||||||
-- Padding grabs extra audio around your selected subs.
|
|
||||||
-- Fade does a volume fade effect at the beginning and end of the resulting audio.
|
|
||||||
local AUDIO_CLIP_FADE = 0.2
|
|
||||||
local AUDIO_CLIP_PADDING = 0.75
|
|
||||||
-- Optional play sentence audio automatically after card update
|
|
||||||
local AUTOPLAY_AUDIO = false
|
|
||||||
-- Optional screenshot image format. Valid options: "webp" or "png"
|
|
||||||
-- Change to "png" if you plan to view cards on iOS or Mac.
|
|
||||||
local IMAGE_FORMAT = "png"
|
|
||||||
-- Optional set to true if you want your volume in mpv to affect Anki card volume.
|
|
||||||
local USE_MPV_VOLUME = false
|
|
||||||
-- Set to true if you want writing to clipboard to be enabled by default.
|
|
||||||
-- The more modern and recommended alternative is to use the websocket.
|
|
||||||
local ENABLE_SUBS_TO_CLIP = false
|
|
||||||
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
------------- Internal Variables -------------
|
|
||||||
local subs = {}
|
|
||||||
local debug_mode = false
|
|
||||||
local use_powershell_clipboard = nil
|
|
||||||
local prefix = ""
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
------------- Setup -------------
|
|
||||||
if unpack ~= nil then
|
|
||||||
table.unpack = unpack
|
|
||||||
end
|
|
||||||
|
|
||||||
local o = {}
|
|
||||||
-- Possible platforms: windows, linux, macos
|
|
||||||
local platform = mp.get_property_native("platform")
|
|
||||||
if platform == "darwin" then
|
|
||||||
platform = "macos"
|
|
||||||
end
|
|
||||||
|
|
||||||
local display_server
|
|
||||||
if os.getenv("WAYLAND_DISPLAY") then
|
|
||||||
display_server = "wayland"
|
|
||||||
elseif platform == "linux" then
|
|
||||||
display_server = "xorg"
|
|
||||||
else
|
|
||||||
display_server = ""
|
|
||||||
end
|
|
||||||
|
|
||||||
local function dlog(...)
|
|
||||||
if debug_mode then
|
|
||||||
print(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function verfiy_libmp3lame()
|
|
||||||
local encoderlist = mp.get_property("encoder-list")
|
|
||||||
if not encoderlist or not string.find(encoderlist, "libmp3lame") then
|
|
||||||
mp.osd_message(
|
|
||||||
"Error: libmp3lame encoder not found. Audio export will not work.\nPlease use a build of mpv with libmp3lame support.",
|
|
||||||
10
|
|
||||||
)
|
|
||||||
msg.error("Error: libmp3lame encoder not found. MP3 audio export will not work.")
|
|
||||||
else
|
|
||||||
dlog("libmp3lame encoder found.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
mp.register_event("file-loaded", verfiy_libmp3lame)
|
|
||||||
|
|
||||||
dlog("Detected Platform: " .. platform)
|
|
||||||
dlog("Detected display server: " .. display_server)
|
|
||||||
|
|
||||||
---------------------------------------
|
|
||||||
-- Handle requests to AnkiConnect
|
|
||||||
local function anki_connect(action, params)
|
|
||||||
local request = utils.format_json({ action = action, params = params, version = 6 })
|
|
||||||
local args = { "curl", "-s", "localhost:8765", "-X", "POST", "-d", request }
|
|
||||||
|
|
||||||
dlog("AnkiConnect request: " .. request)
|
|
||||||
|
|
||||||
local result = utils.subprocess({ args = args, cancellable = false, capture_stderr = true })
|
|
||||||
|
|
||||||
if result.status ~= 0 then
|
|
||||||
msg.error("Curl command failed with status: " .. tostring(result.status))
|
|
||||||
msg.error("Stderr: " .. (result.stderr or "none"))
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
if not result.stdout or result.stdout == "" then
|
|
||||||
msg.error("Empty response from AnkiConnect")
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
dlog("AnkiConnect response: " .. result.stdout)
|
|
||||||
|
|
||||||
local success, parsed_result = pcall(function()
|
|
||||||
return utils.parse_json(result.stdout)
|
|
||||||
end)
|
|
||||||
if not success or not parsed_result then
|
|
||||||
msg.error("Failed to parse JSON response: " .. (result.stdout or "empty"))
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return parsed_result
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Get media directory path from AnkiConnect
|
|
||||||
local function set_media_dir()
|
|
||||||
local media_dir_response = anki_connect("getMediaDirPath")
|
|
||||||
if not media_dir_response then
|
|
||||||
msg.error("Failed to communicate with AnkiConnect. Is Anki running and do you have AnkiConnect installed?")
|
|
||||||
mp.osd_message(
|
|
||||||
"Error: Failed to communicate with AnkiConnect. Is Anki running and do you have AnkiConnect installed?",
|
|
||||||
5
|
|
||||||
)
|
|
||||||
return
|
|
||||||
elseif media_dir_response["error"] then
|
|
||||||
msg.error("AnkiConnect error: " .. tostring(media_dir_response["error"]))
|
|
||||||
mp.osd_message("AnkiConnect error: " .. tostring(media_dir_response["error"]), 5)
|
|
||||||
return
|
|
||||||
elseif media_dir_response["result"] then
|
|
||||||
prefix = media_dir_response["result"]
|
|
||||||
dlog("Got media directory path from AnkiConnect: " .. prefix)
|
|
||||||
else
|
|
||||||
msg.error("Unexpected response format from AnkiConnect")
|
|
||||||
mp.osd_message("Error: Unexpected response from AnkiConnect", 5)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function clean(s)
|
|
||||||
for _, ws in ipairs({
|
|
||||||
"%s",
|
|
||||||
" ",
|
|
||||||
"",
|
|
||||||
" ",
|
|
||||||
" ",
|
|
||||||
" ",
|
|
||||||
" ",
|
|
||||||
" ",
|
|
||||||
" ",
|
|
||||||
" ",
|
|
||||||
" ",
|
|
||||||
" ",
|
|
||||||
" ",
|
|
||||||
" ",
|
|
||||||
"",
|
|
||||||
" ",
|
|
||||||
" ",
|
|
||||||
" ",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
}) do
|
|
||||||
s = s:gsub(ws .. "+", "")
|
|
||||||
end
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_name(s, e)
|
|
||||||
return mp.get_property("filename"):gsub("%W", "") .. tostring(s) .. tostring(e)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_clipboard()
|
|
||||||
local res
|
|
||||||
if platform == "windows" then
|
|
||||||
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)
|
|
||||||
}]],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
elseif platform == "macos" then
|
|
||||||
return io.popen("LANG=en_US.UTF-8 pbpaste"):read("*a")
|
|
||||||
else -- platform == 'linux'
|
|
||||||
if display_server == "wayland" then
|
|
||||||
res = utils.subprocess({ args = {
|
|
||||||
"wl-paste",
|
|
||||||
} })
|
|
||||||
else -- display_server == 'xorg'
|
|
||||||
res = utils.subprocess({ args = {
|
|
||||||
"xclip",
|
|
||||||
"-selection",
|
|
||||||
"clipboard",
|
|
||||||
"-out",
|
|
||||||
} })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not res.error then
|
|
||||||
return res.stdout
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function powershell_set_clipboard(text)
|
|
||||||
utils.subprocess({
|
|
||||||
args = {
|
|
||||||
"powershell",
|
|
||||||
"-NoProfile",
|
|
||||||
"-Command",
|
|
||||||
[[Set-Clipboard -Value @"]] .. "\n" .. text .. "\n" .. [["@]],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
local function cmd_set_clipboard(text)
|
|
||||||
local cmd = "echo " .. text .. " | clip"
|
|
||||||
mp.command("run cmd /D /C " .. cmd)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function determine_clip_type()
|
|
||||||
powershell_set_clipboard([[Anacreon様]])
|
|
||||||
use_powershell_clipboard = get_clipboard() == [[Anacreon様]]
|
|
||||||
end
|
|
||||||
|
|
||||||
local function linux_set_clipboard(text)
|
|
||||||
if display_server == "wayland" then
|
|
||||||
os.execute("wl-copy <<EOF\n" .. text .. "\nEOF\n")
|
|
||||||
else -- display_server == 'xorg'
|
|
||||||
os.execute("xclip -selection clipboard <<EOF\n" .. text .. "\nEOF\n")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function macos_set_clipboard(text)
|
|
||||||
os.execute("export LANG=en_US.UTF-8; cat <<EOF | pbcopy\n" .. text .. "\nEOF\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function record_sub(_, text)
|
|
||||||
if text and mp.get_property_number("sub-start") and mp.get_property_number("sub-end") then
|
|
||||||
local sub_delay = mp.get_property_native("sub-delay")
|
|
||||||
local audio_delay = mp.get_property_native("audio-delay")
|
|
||||||
local newtext = clean(text)
|
|
||||||
if newtext == "" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
subs[newtext] = {
|
|
||||||
mp.get_property_number("sub-start") + sub_delay - audio_delay,
|
|
||||||
mp.get_property_number("sub-end") + sub_delay - audio_delay,
|
|
||||||
}
|
|
||||||
dlog(string.format("%s -> %s : %s", subs[newtext][1], subs[newtext][2], newtext))
|
|
||||||
if ENABLE_SUBS_TO_CLIP then
|
|
||||||
-- Remove newlines from text before sending it to clipboard.
|
|
||||||
-- This way pressing control+v without copying from texthooker page
|
|
||||||
-- will always give last line.
|
|
||||||
text = string.gsub(text, "[\n\r]+", " ")
|
|
||||||
if platform == "windows" then
|
|
||||||
if use_powershell_clipboard == nil then
|
|
||||||
determine_clip_type()
|
|
||||||
end
|
|
||||||
if use_powershell_clipboard then
|
|
||||||
powershell_set_clipboard(text)
|
|
||||||
else
|
|
||||||
cmd_set_clipboard(text)
|
|
||||||
end
|
|
||||||
elseif platform == "macos" then
|
|
||||||
macos_set_clipboard(text)
|
|
||||||
else
|
|
||||||
linux_set_clipboard(text)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function create_audio(s, e)
|
|
||||||
if s == nil or e == nil then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local name = get_name(s, e)
|
|
||||||
local destination = utils.join_path(prefix, name .. ".mp3")
|
|
||||||
s = s - AUDIO_CLIP_PADDING
|
|
||||||
local t = e - s + AUDIO_CLIP_PADDING
|
|
||||||
local source = mp.get_property("path")
|
|
||||||
local aid = mp.get_property("aid")
|
|
||||||
|
|
||||||
local tracks_count = mp.get_property_number("track-list/count")
|
|
||||||
for i = 1, tracks_count do
|
|
||||||
local track_type = mp.get_property(string.format("track-list/%d/type", i))
|
|
||||||
local track_selected = mp.get_property(string.format("track-list/%d/selected", i))
|
|
||||||
if track_type == "audio" and track_selected == "yes" then
|
|
||||||
if mp.get_property(string.format("track-list/%d/external-filename", i), o) ~= o then
|
|
||||||
source = mp.get_property(string.format("track-list/%d/external-filename", i))
|
|
||||||
aid = "auto"
|
|
||||||
end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local cmd = {
|
|
||||||
"run",
|
|
||||||
"mpv",
|
|
||||||
source,
|
|
||||||
"--loop-file=no",
|
|
||||||
"--video=no",
|
|
||||||
"--no-ocopy-metadata",
|
|
||||||
"--no-sub",
|
|
||||||
"--audio-channels=1",
|
|
||||||
string.format("--start=%.3f", s),
|
|
||||||
string.format("--length=%.3f", t),
|
|
||||||
string.format("--aid=%s", aid),
|
|
||||||
string.format("--volume=%s", USE_MPV_VOLUME and mp.get_property("volume") or "100"),
|
|
||||||
string.format("--af-append=afade=t=in:curve=ipar:st=%.3f:d=%.3f", s, AUDIO_CLIP_FADE),
|
|
||||||
string.format("--af-append=afade=t=out:curve=ipar:st=%.3f:d=%.3f", s + t - AUDIO_CLIP_FADE, AUDIO_CLIP_FADE),
|
|
||||||
string.format("-o=%s", destination),
|
|
||||||
}
|
|
||||||
mp.commandv(table.unpack(cmd))
|
|
||||||
dlog(utils.to_string(cmd))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function create_screenshot(s, e)
|
|
||||||
local source = mp.get_property("path")
|
|
||||||
local img = utils.join_path(prefix, get_name(s, e) .. "." .. IMAGE_FORMAT)
|
|
||||||
|
|
||||||
local cmd = {
|
|
||||||
"run",
|
|
||||||
"mpv",
|
|
||||||
source,
|
|
||||||
"--loop-file=no",
|
|
||||||
"--audio=no",
|
|
||||||
"--no-ocopy-metadata",
|
|
||||||
"--no-sub",
|
|
||||||
"--frames=1",
|
|
||||||
}
|
|
||||||
if IMAGE_FORMAT == "webp" then
|
|
||||||
table.insert(cmd, "--ovc=libwebp")
|
|
||||||
table.insert(cmd, "--ovcopts-add=lossless=0")
|
|
||||||
table.insert(cmd, "--ovcopts-add=compression_level=6")
|
|
||||||
table.insert(cmd, "--ovcopts-add=preset=drawing")
|
|
||||||
elseif IMAGE_FORMAT == "png" then
|
|
||||||
table.insert(cmd, "--vf-add=format=rgb24")
|
|
||||||
end
|
|
||||||
table.insert(cmd, "--vf-add=scale=480*iw*sar/ih:480")
|
|
||||||
table.insert(cmd, string.format("--start=%.3f", mp.get_property_number("time-pos")))
|
|
||||||
table.insert(cmd, string.format("-o=%s", img))
|
|
||||||
mp.commandv(table.unpack(cmd))
|
|
||||||
dlog(utils.to_string(cmd))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function add_to_last_added(ifield, afield, tfield)
|
|
||||||
local added_notes = anki_connect("findNotes", { query = "added:1" })["result"]
|
|
||||||
table.sort(added_notes)
|
|
||||||
local noteid = added_notes[#added_notes]
|
|
||||||
local note = anki_connect("notesInfo", { notes = { noteid } })
|
|
||||||
|
|
||||||
if note ~= nil then
|
|
||||||
local word = note["result"][1]["fields"][FRONT_FIELD]["value"]
|
|
||||||
local new_fields = {
|
|
||||||
[SENTENCE_AUDIO_FIELD] = afield,
|
|
||||||
[SENTENCE_FIELD] = tfield,
|
|
||||||
[IMAGE_FIELD] = ifield,
|
|
||||||
}
|
|
||||||
|
|
||||||
anki_connect("updateNoteFields", {
|
|
||||||
note = {
|
|
||||||
id = noteid,
|
|
||||||
fields = new_fields,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
mp.osd_message("Updated note: " .. word, 3)
|
|
||||||
msg.info("Updated note: " .. word)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_extract()
|
|
||||||
local lines = get_clipboard()
|
|
||||||
local e = 0
|
|
||||||
local s = 0
|
|
||||||
for line in lines:gmatch("[^\r\n]+") do
|
|
||||||
line = clean(line)
|
|
||||||
dlog(line)
|
|
||||||
if subs[line] ~= nil then
|
|
||||||
if subs[line][1] ~= nil and subs[line][2] ~= nil then
|
|
||||||
if s == 0 then
|
|
||||||
s = subs[line][1]
|
|
||||||
else
|
|
||||||
s = math.min(s, subs[line][1])
|
|
||||||
end
|
|
||||||
e = math.max(e, subs[line][2])
|
|
||||||
end
|
|
||||||
else
|
|
||||||
mp.osd_message("ERR! Line not found: " .. line, 3)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
dlog(string.format("s=%d, e=%d", s, e))
|
|
||||||
if e ~= 0 then
|
|
||||||
create_screenshot(s, e)
|
|
||||||
create_audio(s, e)
|
|
||||||
local ifield = "<img src=" .. get_name(s, e) .. "." .. IMAGE_FORMAT .. ">"
|
|
||||||
local afield = "[sound:" .. get_name(s, e) .. ".mp3]"
|
|
||||||
local tfield = string.gsub(string.gsub(lines, "\n+", "<br />"), "\r", "")
|
|
||||||
add_to_last_added(ifield, afield, tfield)
|
|
||||||
if AUTOPLAY_AUDIO then
|
|
||||||
local name = get_name(s, e)
|
|
||||||
local audio = utils.join_path(prefix, name .. ".mp3")
|
|
||||||
local cmd = { "run", "mpv", audio, "--loop-file=no", "--load-scripts=no" }
|
|
||||||
mp.commandv(table.unpack(cmd))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function ex()
|
|
||||||
if not prefix or prefix == "" then
|
|
||||||
set_media_dir()
|
|
||||||
end
|
|
||||||
|
|
||||||
if debug_mode then
|
|
||||||
get_extract()
|
|
||||||
else
|
|
||||||
pcall(get_extract)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function rec(...)
|
|
||||||
if debug_mode then
|
|
||||||
record_sub(...)
|
|
||||||
else
|
|
||||||
pcall(record_sub, ...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function toggle_sub_to_clipboard()
|
|
||||||
ENABLE_SUBS_TO_CLIP = not ENABLE_SUBS_TO_CLIP
|
|
||||||
mp.osd_message("Clipboard inserter " .. (ENABLE_SUBS_TO_CLIP and "activated" or "deactived"), 3)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function toggle_debug_mode()
|
|
||||||
debug_mode = not debug_mode
|
|
||||||
mp.osd_message("Debug mode " .. (debug_mode and "activated" or "deactived"), 3)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function clear_subs(_)
|
|
||||||
subs = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
mp.observe_property("sub-text", "string", rec)
|
|
||||||
mp.observe_property("filename", "string", clear_subs)
|
|
||||||
|
|
||||||
mp.add_key_binding("ctrl+v", "update-anki-card", ex)
|
|
||||||
mp.add_key_binding("ctrl+t", "toggle-clipboard-insertion", toggle_sub_to_clipboard)
|
|
||||||
mp.add_key_binding("ctrl+d", "toggle-debug-mode", toggle_debug_mode)
|
|
||||||
mp.add_key_binding("ctrl+V", ex)
|
|
||||||
mp.add_key_binding("ctrl+T", toggle_sub_to_clipboard)
|
|
||||||
mp.add_key_binding("ctrl+D", toggle_debug_mode)
|
|
||||||
Submodule scripts/autosubsync-mpv deleted from 125ac13d1b
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
|
||||||
@@ -8,75 +8,75 @@ local platform = mp.get_property_native("platform")
|
|||||||
local config_file_path = mp.find_config_file("mpv.conf")
|
local config_file_path = mp.find_config_file("mpv.conf")
|
||||||
local config_folder_path, config_file = utils.split_path(config_file_path)
|
local config_folder_path, config_file = utils.split_path(config_file_path)
|
||||||
local mpv_websocket_path =
|
local mpv_websocket_path =
|
||||||
utils.join_path(config_folder_path, platform == "windows" and "mpv_websocket.exe" or "mpv_websocket")
|
utils.join_path(config_folder_path, platform == "windows" and "mpv_websocket.exe" or "mpv_websocket")
|
||||||
local initialised_websocket
|
local initialised_websocket
|
||||||
|
|
||||||
local _, err = utils.file_info(config_file_path)
|
local _, err = utils.file_info(config_file_path)
|
||||||
if err then
|
if err then
|
||||||
error("failed to open mpv config file `" .. config_file_path .. "`")
|
error("failed to open mpv config file `" .. config_file_path .. "`")
|
||||||
end
|
end
|
||||||
|
|
||||||
local _, err = utils.file_info(mpv_websocket_path)
|
local _, err = utils.file_info(mpv_websocket_path)
|
||||||
if err then
|
if err then
|
||||||
error("failed to open mpv_websocket")
|
error("failed to open mpv_websocket")
|
||||||
end
|
end
|
||||||
|
|
||||||
local function find_mpv_socket(config_file_path)
|
local function find_mpv_socket(config_file_path)
|
||||||
local file = io.open(config_file_path, "r")
|
local file = io.open(config_file_path, "r")
|
||||||
if file == nil then
|
if file == nil then
|
||||||
error("failed to read mpv config file `" .. config_file_path .. "`")
|
error("failed to read mpv config file `" .. config_file_path .. "`")
|
||||||
end
|
end
|
||||||
|
|
||||||
local mpv_socket
|
local mpv_socket
|
||||||
for line in file:lines() do
|
for line in file:lines() do
|
||||||
mpv_socket = line:match("^input%-ipc%-server%s*=%s*(%g+)%s*")
|
mpv_socket = line:match("^input%-ipc%-server%s*=%s*(%g+)%s*")
|
||||||
if mpv_socket then
|
if mpv_socket then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
file:close()
|
file:close()
|
||||||
|
|
||||||
if not mpv_socket then
|
if not mpv_socket then
|
||||||
error("input-ipc-server option does not exist in `" .. config_file_path .. "`")
|
error("input-ipc-server option does not exist in `" .. config_file_path .. "`")
|
||||||
end
|
end
|
||||||
|
|
||||||
return mpv_socket
|
return mpv_socket
|
||||||
end
|
end
|
||||||
|
|
||||||
local mpv_socket = find_mpv_socket(config_file_path)
|
local mpv_socket = find_mpv_socket(config_file_path)
|
||||||
if platform == "windows" then
|
if platform == "windows" then
|
||||||
mpv_socket = "\\\\.\\pipe" .. mpv_socket:gsub("/", "\\")
|
mpv_socket = "\\\\.\\pipe" .. mpv_socket:gsub("/", "\\")
|
||||||
end
|
end
|
||||||
|
|
||||||
local function start_websocket()
|
local function start_websocket()
|
||||||
initialised_websocket = mp.command_native_async({
|
initialised_websocket = mp.command_native_async({
|
||||||
name = "subprocess",
|
name = "subprocess",
|
||||||
playback_only = false,
|
playback_only = false,
|
||||||
capture_stdout = true,
|
capture_stdout = true,
|
||||||
capture_stderr = true,
|
capture_stderr = true,
|
||||||
args = {
|
args = {
|
||||||
mpv_websocket_path,
|
mpv_websocket_path,
|
||||||
"-m",
|
"-m",
|
||||||
mpv_socket,
|
mpv_socket,
|
||||||
"-w",
|
"-w",
|
||||||
"6677",
|
"6677",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
local function end_websocket()
|
local function end_websocket()
|
||||||
mp.abort_async_command(initialised_websocket)
|
mp.abort_async_command(initialised_websocket)
|
||||||
initialised_websocket = nil
|
initialised_websocket = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function toggle_websocket()
|
local function toggle_websocket()
|
||||||
local paused = mp.get_property_bool("pause")
|
local paused = mp.get_property_bool("pause")
|
||||||
if initialised_websocket and paused then
|
if initialised_websocket and paused then
|
||||||
end_websocket()
|
end_websocket()
|
||||||
elseif not initialised_websocket and not paused then
|
elseif not initialised_websocket and not paused then
|
||||||
start_websocket()
|
start_websocket()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
mp.register_script_message("togglewebsocket", toggle_websocket)
|
mp.register_script_message("togglewebsocket", toggle_websocket)
|
||||||
|
|||||||
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 +1 @@
|
|||||||
../mpv-youtube-upnext/youtube-upnext.lua
|
../submodules/mpv-youtube-upnext/youtube-upnext.lua
|
||||||
@@ -1 +1 @@
|
|||||||
../ytdl-preload/ytdl-preload.lua
|
../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/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
Reference in New Issue
Block a user