added mpv config
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# vim: set ft=python:
|
||||
|
||||
import vapoursynth as vs
|
||||
from vapoursynth import core
|
||||
clip = video_in
|
||||
|
||||
#You can change the desired framerate here
|
||||
dst_fps = display_fps
|
||||
|
||||
#BlockSize can be changed to 16 for better performance
|
||||
BlockSize=8
|
||||
|
||||
src_fps_num = int(container_fps * 1e8)
|
||||
src_fps_den = int(1e8)
|
||||
dst_fps_num = int(dst_fps * 1e4)
|
||||
dst_fps_den = int(1e4)
|
||||
|
||||
# Needed because clip FPS is missing
|
||||
clip = core.std.AssumeFPS(clip, fpsnum = src_fps_num, fpsden = src_fps_den)
|
||||
print("Reflowing from ",src_fps_num/src_fps_den," fps to ",dst_fps_num/dst_fps_den," fps.")
|
||||
|
||||
#Pel can be changed to 4 for better accuracy or 1 for better speed
|
||||
sup = core.mv.Super(clip, pel=2, hpad=BlockSize, vpad=BlockSize)
|
||||
bvec = core.mv.Analyse(sup, blksize=BlockSize, isb=True , chroma=True, search=3, searchparam=1)
|
||||
fvec = core.mv.Analyse(sup, blksize=BlockSize, isb=False, chroma=True, search=3, searchparam=1)
|
||||
clip = core.mv.BlockFPS(clip, sup, bvec, fvec, num=dst_fps_num, den=dst_fps_den, mode=3, thscd2=12)
|
||||
|
||||
clip.set_output()
|
||||
@@ -0,0 +1,78 @@
|
||||
# vim: set ft=python:
|
||||
|
||||
# see the README at https://gist.github.com/phiresky/4bfcfbbd05b3c2ed8645
|
||||
# source: https://github.com/mpv-player/mpv/issues/2149
|
||||
# source: https://github.com/mpv-player/mpv/issues/566
|
||||
# source: https://github.com/haasn/gentoo-conf/blob/nanodesu/home/nand/.mpv/filters/mvtools.vpy
|
||||
|
||||
import vapoursynth
|
||||
|
||||
core = vapoursynth.core
|
||||
# ref: http://avisynth.org.ru/mvtools/mvtools2.html#functions
|
||||
# default is 400, less means interpolation will only happen when it will work well
|
||||
ignore_threshold = 400
|
||||
# if n% of blocks change more than threshold then don't interpolate at all (default is 51%)
|
||||
scene_change_percentage = 51
|
||||
|
||||
dst_fps = 144
|
||||
# Interpolating to fps higher than 60 is too CPU-expensive, smoothmotion can handle the rest.
|
||||
while dst_fps > 60:
|
||||
dst_fps /= 2
|
||||
|
||||
if "video_in" in globals():
|
||||
# realtime
|
||||
clip = video_in
|
||||
# Needed because clip FPS is missing
|
||||
src_fps_num = int(container_fps * 1e8)
|
||||
src_fps_den = int(1e8)
|
||||
clip = core.std.AssumeFPS(clip, fpsnum=src_fps_num, fpsden=src_fps_den)
|
||||
else:
|
||||
# run with vspipe
|
||||
clip = core.ffms2.Source(source=in_filename)
|
||||
dst_fps = float(dst_fps)
|
||||
|
||||
# resolution in megapixels. 1080p ≈ 2MP, 720p ≈ 1MP
|
||||
mpix = clip.width * clip.height / 1000000
|
||||
|
||||
# Skip interpolation for >1080p or 60 Hz content due to performance
|
||||
if not (mpix > 2.5 or clip.fps_num / clip.fps_den > 59):
|
||||
analParams = {
|
||||
"overlap": 0,
|
||||
"search": 3,
|
||||
"truemotion": True,
|
||||
#'chrome': True,
|
||||
#'blksize':16,
|
||||
#'searchparam':1
|
||||
}
|
||||
blockParams = {
|
||||
"thscd1": ignore_threshold,
|
||||
"thscd2": int(scene_change_percentage * 255 / 100),
|
||||
"mode": 3,
|
||||
}
|
||||
|
||||
# default 1.5 or so
|
||||
if mpix > 4.5:
|
||||
# can't handle these on Full HD with Intel i5-2500k
|
||||
# see the description of these parameters in http://avisynth.org.ru/mvtools/mvtools2.html#functions
|
||||
analParams["search"] = 0
|
||||
blockParams["mode"] = 0
|
||||
quality = "low"
|
||||
else:
|
||||
quality = "high"
|
||||
|
||||
dst_fps_num = int(dst_fps * 1e4)
|
||||
dst_fps_den = int(1e4)
|
||||
print(
|
||||
"Reflowing from {} fps to {} fps (quality={})".format(
|
||||
clip.fps_num / clip.fps_den, dst_fps_num / dst_fps_den, quality
|
||||
)
|
||||
)
|
||||
|
||||
sup = core.mv.Super(clip, pel=2)
|
||||
bvec = core.mv.Analyse(sup, isb=True, **analParams)
|
||||
fvec = core.mv.Analyse(sup, isb=False, **analParams)
|
||||
clip = core.mv.BlockFPS(
|
||||
clip, sup, bvec, fvec, num=dst_fps_num, den=dst_fps_den, **blockParams
|
||||
)
|
||||
|
||||
clip.set_output()
|
||||
@@ -0,0 +1,45 @@
|
||||
# vim: set ft=python:
|
||||
|
||||
import vapoursynth as vs
|
||||
|
||||
core = vs.core
|
||||
core.std.LoadPlugin(
|
||||
path="/usr/lib/python3.14/site-packages/vapoursynth/plugins/mvtools.so"
|
||||
)
|
||||
clip = video_in
|
||||
|
||||
dst_fps = display_fps
|
||||
# Interpolating to fps higher than 60 is too CPU-expensive, smoothmotion can handle the rest.
|
||||
while dst_fps > 60:
|
||||
dst_fps = 60
|
||||
|
||||
dst_fps = 144
|
||||
|
||||
# Skip interpolation for > 4K or 60 Hz content due to performance
|
||||
if not (clip.width > 3840 or clip.height > 2160 or container_fps > 59):
|
||||
src_fps_num = int(container_fps * 1e8)
|
||||
src_fps_den = int(1e8)
|
||||
dst_fps_num = int(dst_fps * 1e4)
|
||||
dst_fps_den = int(1e4)
|
||||
# Needed because clip FPS is missing
|
||||
clip = core.std.AssumeFPS(clip, fpsnum=src_fps_num, fpsden=src_fps_den)
|
||||
print(
|
||||
"Reflowing from ",
|
||||
src_fps_num / src_fps_den,
|
||||
" fps to ",
|
||||
dst_fps_num / dst_fps_den,
|
||||
" fps.",
|
||||
)
|
||||
|
||||
sup = core.mv.Super(clip, pel=2, hpad=16, vpad=16)
|
||||
bvec = core.mv.Analyse(
|
||||
sup, blksize=16, isb=True, chroma=True, search=3, searchparam=1
|
||||
)
|
||||
fvec = core.mv.Analyse(
|
||||
sup, blksize=16, isb=False, chroma=True, search=3, searchparam=1
|
||||
)
|
||||
clip = core.mv.BlockFPS(
|
||||
clip, sup, bvec, fvec, num=dst_fps_num, den=dst_fps_den, mode=3, thscd2=12
|
||||
)
|
||||
|
||||
clip.set_output()
|
||||
@@ -0,0 +1,230 @@
|
||||
# mpv auto fullscreen fix:
|
||||
# windowrulev2 = suppressevent fullscreen,class:^(mpv.*)$
|
||||
# needs this in mpv's input.conf:
|
||||
# f run "/bin/sh" "-c" "hyprctl dispatch fullscreen toggle"; cycle fullscreen
|
||||
|
||||
|
||||
ü vf toggle format=yuv420p,vapoursynth="~/.config/mpv/filters/mvtools.vpy":4:4
|
||||
|
||||
### cool profiles function:
|
||||
# _ set profile FrameInterpolation #menu: Profiles > profile FrameInterpolation
|
||||
|
||||
|
||||
### SEEKING ###
|
||||
# Relative Seeking
|
||||
#Shift+RIGHT seek 1 exact
|
||||
#Shift+LEFT seek -1 exact
|
||||
#RIGHT seek 5 exact ; script-binding uosc/flash-timeline
|
||||
#LEFT seek -5 exact ; script-binding uosc/flash-timeline
|
||||
#Shift+DOWN seek 5 ; script-binding uosc/flash-timeline
|
||||
#Shift+UP seek -5 ; script-binding uosc/flash-timeline
|
||||
#DOWN seek 60 ; script-binding uosc/flash-timeline
|
||||
#UP seek -60 ; script-binding uosc/flash-timeline
|
||||
#Shift+PGDWN seek 600 ; script-binding uosc/flash-timeline
|
||||
#Shift+PGUP seek -600 ; script-binding uosc/flash-timeline
|
||||
|
||||
# Absolute seeking
|
||||
HOME osd-msg seek 0 absolute; set pause no
|
||||
END osd-msg seek 100 absolute-percent
|
||||
|
||||
Ctrl+1 osd-msg seek 10 absolute-percent;set pause no
|
||||
Ctrl+2 osd-msg seek 20 absolute-percent;set pause no
|
||||
Ctrl+3 osd-msg seek 30 absolute-percent;set pause no
|
||||
Ctrl+4 osd-msg seek 40 absolute-percent;set pause no
|
||||
Ctrl+5 osd-msg seek 50 absolute-percent;set pause no
|
||||
Ctrl+6 osd-msg seek 60 absolute-percent;set pause no
|
||||
Ctrl+7 osd-msg seek 70 absolute-percent;set pause no
|
||||
Ctrl+8 osd-msg seek 80 absolute-percent;set pause no
|
||||
Ctrl+9 osd-msg seek 90 absolute-percent;set pause no
|
||||
Ctrl+0 osd-msg seek 0 absolute; set pause no
|
||||
|
||||
|
||||
# generate thumbnails using the mpv_thumbnail_script
|
||||
# https://github.com/TheAMM/mpv_thumbnail_script#configuration
|
||||
alt+s script-binding generate-thumbnails
|
||||
|
||||
del script-message osc-visibility cycle
|
||||
|
||||
#DEL script-message cycle-cmd "script-message osc-visibility always" "set osd-level 1; script-message osc-visibility never"
|
||||
|
||||
|
||||
# File and chapter seeking
|
||||
#PGUP add chapter -1
|
||||
#PGDWN add chapter 1
|
||||
#Ctrl+PGUP playlist-prev
|
||||
#Ctrl+PGDWN playlist-next
|
||||
#Ctrl+z osd-msg-bar revert-seek
|
||||
|
||||
# Skip to previous/next subtitle (subject to some restrictions; see manpage)
|
||||
#Alt+LEFT no-osd sub-seek -1
|
||||
#Alt+RIGHT no-osd sub-seek 1
|
||||
|
||||
# Seek with ctrl+mouse wheel
|
||||
#Ctrl+AXIS_UP osd-msg-bar seek 2
|
||||
#Ctrl+AXIS_DOWN osd-msg-bar seek -2
|
||||
|
||||
|
||||
### UNSET BINDINGS TO BE REBOUND ###
|
||||
#r ignore
|
||||
#R ignore
|
||||
s ignore
|
||||
#e ignore
|
||||
g ignore
|
||||
#w ignore
|
||||
#z ignore
|
||||
#x ignore
|
||||
#r ignore
|
||||
#t ignore
|
||||
#T ignore
|
||||
#v ignore
|
||||
#V ignore
|
||||
#_ ignore
|
||||
#SHARP ignore
|
||||
|
||||
|
||||
### TOGGLE/CYCLE STUFF ###
|
||||
#z-c cycle-values audio-channels auto-safe mono
|
||||
#z-d cycle deband
|
||||
#z-D cycle deinterlace
|
||||
#z-y vf toggle yadif
|
||||
#z-i cycle interpolation
|
||||
#z-s cycle sub-visibility
|
||||
#z-a cycle mute
|
||||
#z-v cycle sub-ass-vsfilter-aspect-compat
|
||||
#z-T cycle ontop
|
||||
#z-r cycle-values video-aspect "16:9" "4:3" "2.35:1" "-1"
|
||||
#z-t cycle-values tscale "box" "oversample" "linear" "catmull_rom" "mitchell" "gaussian" "bicubic"
|
||||
#z-o cycle-values ass-style-override "yes" "force" "no"
|
||||
#z-w cycle force-window
|
||||
#z-f cycle osd-fractions
|
||||
#z-b cycle-values blend-subtitles "yes" "no" "video"
|
||||
|
||||
# GLSL Shaders
|
||||
#g-g change-list glsl-shaders clr ""
|
||||
#g-r change-list glsl-shaders toggle "/usr/share/mpv-prescalers/vulkan/ravu-r3.hook"
|
||||
#g-R change-list glsl-shaders toggle "/usr/share/mpv-prescalers/compute/ravu-r3.hook"
|
||||
#g-l change-list glsl-shaders toggle "~~/shaders/noise_static_luma.hook"
|
||||
#g-k change-list glsl-shaders toggle "~~/shaders/KrigBilateral.glsl"
|
||||
#g-f change-list glsl-shaders toggle "~~/shaders/kyoani_fog.glsl"
|
||||
|
||||
# tracks/editions
|
||||
# see uosc bindings at the end
|
||||
#e cycle edition
|
||||
#E cycle edition down
|
||||
|
||||
|
||||
### MISC ###
|
||||
|
||||
# Volume control (with uosc calls)
|
||||
#m cycle mute ; script-binding uosc/flash-volume
|
||||
#WHEEL_UP add volume 5 ; script-binding uosc/flash-volume
|
||||
#WHEEL_DOWN add volume -5 ; script-binding uosc/flash-volume
|
||||
#9 add volume -5 ; script-binding uosc/flash-volume
|
||||
#0 add volume 5 ; script-binding uosc/flash-volume
|
||||
|
||||
# Absolute window resizing
|
||||
Alt+1 set window-scale 0.5
|
||||
Alt+2 set window-scale 1.0
|
||||
Alt+3 set window-scale 1.5
|
||||
Alt+4 set window-scale 2.0
|
||||
#Alt+5 autofit something
|
||||
|
||||
# Relative window resizing
|
||||
#Alt+- multiply window-scale 1/1.25
|
||||
#Alt++ multiply window-scale 1.25
|
||||
|
||||
# Playback speed (with uosc calls)
|
||||
[ multiply speed 1/1.1 ; script-binding uosc/flash-speed
|
||||
] multiply speed 1.1 ; script-binding uosc/flash-speed
|
||||
{ multiply speed 0.5 ; script-binding uosc/flash-speed
|
||||
} multiply speed 2.0 ; script-binding uosc/flash-speed
|
||||
BS set speed 1 ; script-binding uosc/flash-speed
|
||||
|
||||
s multiply speed 1/1.1 ; script-binding uosc/flash-speed
|
||||
d multiply speed 1.1 ; script-binding uosc/flash-speed
|
||||
g set speed 1.6 ; script-binding uosc/flash-speed
|
||||
G set speed 1 ; script-binding uosc/flash-speed
|
||||
|
||||
# Subtitle adjustments
|
||||
#h add sub-delay -0.041708333333 # shift subs (by 1 frame, assuming 24fps)
|
||||
#l add sub-delay 0.041708333333
|
||||
#j add sub-pos 1
|
||||
#k add sub-pos -1
|
||||
|
||||
# Other
|
||||
#Ctrl+l cycle-values loop "inf" "no"
|
||||
#Ctrl+s screenshot subtitles
|
||||
#S screenshot video # rebind; s is used for subtitle selection
|
||||
#P print-text "$filename $time-pos $estimated-frame-number"
|
||||
#F10 show_text ${chapter-list}
|
||||
#c show_text ${chapter-list}
|
||||
|
||||
#<key> script-message osc-chapterlist <optional-duration-in-seconds>
|
||||
F10 script-message osc-chapterlist 2
|
||||
c script-message osc-chapterlist 2
|
||||
|
||||
|
||||
# Do stuff with ${path}
|
||||
#F run firefox "${path}"; show-text "Opening in FireFox"
|
||||
#C run zsh -c "echo -n '${path}' | xclip -sel c"; show-text "Copied path to clipboard"
|
||||
|
||||
# Copy stuff to clipboard
|
||||
y-y run zsh -c "echo -n '${path}'| xclip -sel c"; show-text "Copied path to clipboard"
|
||||
y-p run zsh -c "echo -n '${path}'| xclip -sel c"; show-text "Copied path to clipboard"
|
||||
y-t run zsh -c "echo -n '${time-pos}' | xclip -sel c"; show-text "Copied timestamp to clipboard"
|
||||
y-T run zsh -c "echo -n '${media-title} [${time-pos}/${duration}]' | xclip -sel c"; show-text "Copied title+timestamp to clipboard"
|
||||
# time-code with fractions
|
||||
#y-f run python -c "import pyperclip; timeform = r\"${time-pos}\"; timemill = r\"${=time-pos}\"; time = timeform+timemill[-7:-3]; pyperclip.copy(time)"; show-text "Copied timestamp to clipboard"
|
||||
|
||||
# Open help and config files
|
||||
F1 run xdg-open https://mpv.io/manual/stable/
|
||||
F2 run nvim ~/.config/mpv/mpv.conf
|
||||
F3 run nvim ~/.config/mpv/input.conf
|
||||
F4 run nvim ~/.config/mpv/default-input.conf
|
||||
|
||||
### SCRIPTS ###
|
||||
#r script-message-to reload reload_resume
|
||||
#Alt+w script-message-to webm display-webm-encoder
|
||||
#Alt+r script-message-to cycle_video_rotate Cycle_Video_Rotate 90
|
||||
#z-p script-message-to pitchcontrol toggle
|
||||
#Alt+c script-message-to crop start-crop
|
||||
#Ctrl+v script-message-to appendURL appendURL
|
||||
#G script-binding seek_to/toggle-seeker
|
||||
|
||||
#Alt+g script-binding sponsorblock/set_segment
|
||||
#Alt+G script-binding sponsorblock/submit_segment
|
||||
#Alt+h script-binding sponsorblock/upvote
|
||||
#Alt+H script-binding sponsorblock/downvote
|
||||
|
||||
### uosc ###
|
||||
#tab script-binding uosc/peek-timeline
|
||||
#MBTN_MID script-binding uosc/menu
|
||||
#menu script-binding uosc/menu
|
||||
|
||||
#p script-binding uosc/playlist #! Navigate playlist
|
||||
#c script-binding uosc/chapters #! Navigate chapters
|
||||
#v script-binding uosc/video #! Tracks > Select video
|
||||
#Ctrl+a script-binding uosc/audio #! Tracks > Select audio
|
||||
#Ctl+s script-binding uosc/subtitles #! Tracks > Select subtitles
|
||||
# script-binding uosc/load-subtitles #! Tracks > Load subtitles
|
||||
# script-binding uosc/navigate-directory #! Utils > Navigate directory
|
||||
# script-binding uosc/show-in-directory #! Utils > Show in directory
|
||||
#q quit #! Quit
|
||||
|
||||
## Cheat sheet for bindings bound by scripts directly ##
|
||||
# Ctrl+f change ytdl quality
|
||||
# Ctrl+r reload in-place / also bound to "r" above
|
||||
#
|
||||
## Playlist manager ##
|
||||
# Shift+ enter open/close
|
||||
# p save playlist to $XDG_CACHE_DIR/mpv
|
||||
# Ctrl+p sort playlist
|
||||
# Backspace remove current item
|
||||
# RIGHT select item to move in the playlist (using up/down)
|
||||
# ü vf toggle format=yuv420p,vapoursynth=~~/motioninterpolation.vpy:4:4
|
||||
# ü cycle interpolation
|
||||
# Ü cycle-values tscale "oversample" "linear" "catmull_rom" "mitchell"
|
||||
|
||||
|
||||
# Alt+ü vf toggle format=yuv420p,vapoursynth="~/.config/mpv/filters/motioninterpolation.vpy":4:4
|
||||
# Alt+ü vf toggle format=yuv420p,vapoursynth="~/.config/mpv/filters/mvtools.vpy":4:4
|
||||
@@ -0,0 +1,117 @@
|
||||
# profile=svp
|
||||
# profile=smoothmotion
|
||||
|
||||
# written by fennek182
|
||||
# version 1
|
||||
# last change: Wed 2026-01-21
|
||||
|
||||
#[smoothmotion]
|
||||
# see 2.1.2 High quality configurations
|
||||
# https://wiki.archlinux.org/title/mpv#High-quality-configurations
|
||||
#profile=high-quality
|
||||
#video-sync=display-resample
|
||||
#interpolation
|
||||
#tscale=oversample
|
||||
|
||||
# vf=vapoursynth="~/.config/mpv/filters/mvtools.vpy":buffered-frames=4:concurrent-frames=4
|
||||
|
||||
# video-sync=display-resample
|
||||
# interpolation = yes
|
||||
# tscale = box
|
||||
# tscale-clamp = 0.0
|
||||
# tscale-radius = 1.0
|
||||
# tscale-window = sphinx
|
||||
|
||||
# ========== PROFILES ==========
|
||||
# [svp]
|
||||
# input-ipc-server=/tmp/mpvsocket
|
||||
|
||||
hr-seek-framedrop=no
|
||||
# watch-later-options-remove=vf
|
||||
hwdec=auto-copy
|
||||
hwdec-codecs=all
|
||||
|
||||
# [FrameInterpolation]
|
||||
# vf=vapoursynth="~/.config/mpv/filters/mvtools.vpy"
|
||||
# profile-restore = copy
|
||||
# ============================================
|
||||
|
||||
resume-playback=no
|
||||
volume-max=200
|
||||
cache=yes
|
||||
cache=auto
|
||||
keep-open
|
||||
# set max cache size to 20 Gigabytes
|
||||
demuxer-max-bytes=21474836480
|
||||
|
||||
# ============== YT-DLP ==============
|
||||
# limit yt-dlp to 1440p60
|
||||
# ytdl-format=bestvideo[height<=?1440]+bestaudio/best
|
||||
ytdl-format=bestvideo+bestaudio/best
|
||||
# ============== YT-DLP ==============
|
||||
|
||||
# open window immediately (not waiting for yt-dlp)
|
||||
force-window=immediate
|
||||
|
||||
# # for stupid fucking braindead audbile drm bs
|
||||
demuxer-lavf-o=activation_bytes=162dfd19
|
||||
|
||||
# Can fix stuttering in some cases, in other cases probably causes it. Try it if you experience stuttering.
|
||||
# opengl-early-flush=yes
|
||||
|
||||
# if using mpv_thumbnail_script
|
||||
# https://github.com/TheAMM/mpv_thumbnail_script
|
||||
# then mpv's built-in osc must be disabled
|
||||
# osc=no
|
||||
|
||||
# =============== TEST HDR 2026-01-21 ==============
|
||||
# Renderer:
|
||||
# gpu-next requires the latest (unreleased) git version of libplacebo and the latest (unreleased) git version of mpv.
|
||||
# It's still in testing/fixing/development stages and isn't recommended unless people want to test unfinished, bleeding-edge code.
|
||||
# The main benefits of GPU-Next are that it has much more efficient rendering code, and that it supports HDR tonemapping, including HDR/DV, and including HDR/DV to SDR (useful for Linux users since Linux lacks HDR support).
|
||||
# differences gpu/gpu-next: https://github.com/mpv-player/mpv/wiki/GPU-Next-vs-GPU
|
||||
|
||||
# vo=gpu-next
|
||||
# target-colorspace-hint=yes
|
||||
|
||||
# get apis by "mpv --gpu-api=help":
|
||||
# gpu-api=vulkan
|
||||
# gpu-context=waylandvk
|
||||
# =============== TEST HDR 2026-01-21 ==============
|
||||
|
||||
|
||||
# ----------- old - delete
|
||||
# --keepaspect-window (the default) will lock the window size to the video aspect.
|
||||
# --no-keepaspect-window disables this behavior, and will instead add black bars if window aspect and video aspect mismatch.
|
||||
# Whether this actually works depends on the VO backend. (Ignored in fullscreen mode.)
|
||||
# no-keepaspect-window
|
||||
|
||||
# target-colorspace-hint=yes
|
||||
# --tone-mapping=spline
|
||||
# profile=gpu-hq
|
||||
|
||||
# always open a new instance of mpv instead of replacing currently playing video
|
||||
#process-instance=multi
|
||||
|
||||
# use yt-dlp instead of youtube-dl since it's slow now (Oct 21)
|
||||
# script-opts=ytdl_hook-ytdl_path=/usr/bin/yt-dlp
|
||||
|
||||
# catppuccin colors:
|
||||
# Main mpv options
|
||||
# osd-color='#cdd6f4'
|
||||
# osd-shadow-color='#1e1e2e'
|
||||
|
||||
# ; # Stats script options
|
||||
# ; # Options are on separate lines for clarity
|
||||
# ; # Colors are in #BBGGRR format
|
||||
# ; script-opts-append=stats-border_color=251818
|
||||
# ; script-opts-append=stats-font_color=f4d6cd
|
||||
# ; script-opts-append=stats-plot_bg_border_color=d5e294
|
||||
# ; script-opts-append=stats-plot_bg_color=251818
|
||||
# ; script-opts-append=stats-plot_color=d5e294
|
||||
# ;
|
||||
# ; # External script options
|
||||
# ; # It is fine to leave these here even if one does not use these scripts because they are just ignored unless a script uses them
|
||||
# ;
|
||||
# ; # UOSC options
|
||||
# ; script-opts-append=uosc-color=foreground=94e2d5,foreground_text=313244,background=1e1e2e,background_text=cdd6f4,curtain=181825,success=a6e3a1,error=f38ba8
|
||||
Binary file not shown.
@@ -0,0 +1,36 @@
|
||||
-- mpvSockets, one socket per instance, removes socket on exit
|
||||
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
local function get_temp_path()
|
||||
local directory_seperator = package.config:match("([^\n]*)\n?")
|
||||
local example_temp_file_path = os.tmpname()
|
||||
|
||||
-- remove generated temp file
|
||||
pcall(os.remove, example_temp_file_path)
|
||||
|
||||
local seperator_idx = example_temp_file_path:reverse():find(directory_seperator)
|
||||
local temp_path_length = #example_temp_file_path - seperator_idx
|
||||
|
||||
return example_temp_file_path:sub(1, temp_path_length)
|
||||
end
|
||||
|
||||
tempDir = get_temp_path()
|
||||
|
||||
function join_paths(...)
|
||||
local arg={...}
|
||||
path = ""
|
||||
for i,v in ipairs(arg) do
|
||||
path = utils.join_path(path, tostring(v))
|
||||
end
|
||||
return path;
|
||||
end
|
||||
|
||||
ppid = utils.getpid()
|
||||
os.execute("mkdir " .. join_paths(tempDir, "mpvSockets") .. " 2>/dev/null")
|
||||
mp.set_property("options/input-ipc-server", join_paths(tempDir, "mpvSockets", ppid))
|
||||
|
||||
function shutdown_handler()
|
||||
os.remove(join_paths(tempDir, "mpvSockets", ppid))
|
||||
end
|
||||
mp.register_event("shutdown", shutdown_handler)
|
||||
@@ -0,0 +1,568 @@
|
||||
-- sponsorblock.lua
|
||||
--
|
||||
-- This script skips sponsored segments of YouTube videos
|
||||
-- using data from https://github.com/ajayyy/SponsorBlock
|
||||
|
||||
local ON_WINDOWS = package.config:sub(1,1) ~= "/"
|
||||
|
||||
local options = {
|
||||
server_address = "https://sponsor.ajay.app",
|
||||
|
||||
python_path = ON_WINDOWS and "python" or "python3",
|
||||
|
||||
-- Categories to fetch
|
||||
categories = "sponsor,intro,outro,interaction,selfpromo,filler",
|
||||
|
||||
-- Categories to skip automatically
|
||||
skip_categories = "sponsor,intro,outro,interaction,selfpromo",
|
||||
|
||||
-- If true, sponsored segments will only be skipped once
|
||||
skip_once = true,
|
||||
|
||||
-- Note that sponsored segments may ocasionally be inaccurate if this is turned off
|
||||
-- see https://blog.ajay.app/voting-and-pseudo-randomness-or-sponsorblock-or-youtube-sponsorship-segment-blocker
|
||||
local_database = false,
|
||||
|
||||
-- Update database on first run, does nothing if local_database is false
|
||||
auto_update = true,
|
||||
|
||||
-- How long to wait between local database updates
|
||||
-- Format: "X[d,h,m]", leave blank to update on every mpv run
|
||||
auto_update_interval = "6h",
|
||||
|
||||
-- User ID used to submit sponsored segments, leave blank for random
|
||||
user_id = "",
|
||||
|
||||
-- Name to display on the stats page https://sponsor.ajay.app/stats/ leave blank to keep current name
|
||||
display_name = "",
|
||||
|
||||
-- Tell the server when a skip happens
|
||||
report_views = true,
|
||||
|
||||
-- Auto upvote skipped sponsors
|
||||
auto_upvote = false,
|
||||
|
||||
-- Use sponsor times from server if they're more up to date than our local database
|
||||
server_fallback = true,
|
||||
|
||||
-- Create chapters at sponsor boundaries for OSC display and manual skipping
|
||||
make_chapters = true,
|
||||
|
||||
-- Minimum duration for sponsors (in seconds), segments under that threshold will be ignored
|
||||
min_duration = 1,
|
||||
|
||||
-- Fade audio for smoother transitions
|
||||
audio_fade = false,
|
||||
|
||||
-- Audio fade step, applied once every 100ms until cap is reached
|
||||
audio_fade_step = 10,
|
||||
|
||||
-- Audio fade cap
|
||||
audio_fade_cap = 0,
|
||||
|
||||
-- Fast forward through sponsors instead of skipping
|
||||
fast_forward = false,
|
||||
|
||||
-- Playback speed modifier when fast forwarding, applied once every second until cap is reached
|
||||
fast_forward_increase = .2,
|
||||
|
||||
-- Playback speed cap
|
||||
fast_forward_cap = 2,
|
||||
|
||||
-- Length of the sha256 prefix (3-32) when querying server, 0 to disable
|
||||
sha256_length = 4,
|
||||
|
||||
-- Pattern for video id in local files, ignored if blank
|
||||
-- Recommended value for base youtube-dl is "-([%w-_]+)%.[mw][kpe][v4b]m?$"
|
||||
local_pattern = "",
|
||||
|
||||
-- Legacy option, use skip_categories instead
|
||||
skip = true
|
||||
}
|
||||
|
||||
mp.options = require "mp.options"
|
||||
mp.options.read_options(options, "sponsorblock")
|
||||
|
||||
local legacy = mp.command_native_async == nil
|
||||
--[[
|
||||
if legacy then
|
||||
options.local_database = false
|
||||
end
|
||||
--]]
|
||||
options.local_database = false
|
||||
|
||||
local utils = require "mp.utils"
|
||||
scripts_dir = mp.find_config_file("scripts")
|
||||
|
||||
local sponsorblock = utils.join_path(scripts_dir, "sponsorblock_shared/sponsorblock.py")
|
||||
local uid_path = utils.join_path(scripts_dir, "sponsorblock_shared/sponsorblock.txt")
|
||||
local database_file = options.local_database and utils.join_path(scripts_dir, "sponsorblock_shared/sponsorblock.db") or ""
|
||||
local youtube_id = nil
|
||||
local ranges = {}
|
||||
local init = false
|
||||
local segment = {a = 0, b = 0, progress = 0, first = true}
|
||||
local retrying = false
|
||||
local last_skip = {uuid = "", dir = nil}
|
||||
local speed_timer = nil
|
||||
local fade_timer = nil
|
||||
local fade_dir = nil
|
||||
local volume_before = mp.get_property_number("volume")
|
||||
local categories = {}
|
||||
local all_categories = {"sponsor", "intro", "outro", "interaction", "selfpromo", "preview", "music_offtopic", "filler"}
|
||||
local chapter_cache = {}
|
||||
|
||||
for category in string.gmatch(options.skip_categories, "([^,]+)") do
|
||||
categories[category] = true
|
||||
end
|
||||
|
||||
function file_exists(name)
|
||||
local f = io.open(name,"r")
|
||||
if f ~= nil then io.close(f) return true else return false end
|
||||
end
|
||||
|
||||
function t_count(t)
|
||||
local count = 0
|
||||
for _ in pairs(t) do count = count + 1 end
|
||||
return count
|
||||
end
|
||||
|
||||
function time_sort(a, b)
|
||||
if a.time == b.time then
|
||||
return string.match(a.title, "segment end")
|
||||
end
|
||||
return a.time < b.time
|
||||
end
|
||||
|
||||
function parse_update_interval()
|
||||
local s = options.auto_update_interval
|
||||
if s == "" then return 0 end -- Interval Disabled
|
||||
|
||||
local num, mod = s:match "^(%d+)([hdm])$"
|
||||
|
||||
if num == nil or mod == nil then
|
||||
mp.osd_message("[sponsorblock] auto_update_interval " .. s .. " is invalid", 5)
|
||||
return nil
|
||||
end
|
||||
|
||||
local time_table = {
|
||||
m = 60,
|
||||
h = 60 * 60,
|
||||
d = 60 * 60 * 24,
|
||||
}
|
||||
|
||||
return num * time_table[mod]
|
||||
end
|
||||
|
||||
function clean_chapters()
|
||||
local chapters = mp.get_property_native("chapter-list")
|
||||
local new_chapters = {}
|
||||
for _, chapter in pairs(chapters) do
|
||||
if chapter.title ~= "Preview segment start" and chapter.title ~= "Preview segment end" then
|
||||
table.insert(new_chapters, chapter)
|
||||
end
|
||||
end
|
||||
mp.set_property_native("chapter-list", new_chapters)
|
||||
end
|
||||
|
||||
function create_chapter(chapter_title, chapter_time)
|
||||
local chapters = mp.get_property_native("chapter-list")
|
||||
local duration = mp.get_property_native("duration")
|
||||
table.insert(chapters, {title=chapter_title, time=(duration == nil or duration > chapter_time) and chapter_time or duration - .001})
|
||||
table.sort(chapters, time_sort)
|
||||
mp.set_property_native("chapter-list", chapters)
|
||||
end
|
||||
|
||||
function process(uuid, t, new_ranges)
|
||||
start_time = tonumber(string.match(t, "[^,]+"))
|
||||
end_time = tonumber(string.sub(string.match(t, ",[^,]+"), 2))
|
||||
for o_uuid, o_t in pairs(ranges) do
|
||||
if (start_time >= o_t.start_time and start_time <= o_t.end_time) or (o_t.start_time >= start_time and o_t.start_time <= end_time) then
|
||||
new_ranges[o_uuid] = o_t
|
||||
return
|
||||
end
|
||||
end
|
||||
category = string.match(t, "[^,]+$")
|
||||
if categories[category] and end_time - start_time >= options.min_duration then
|
||||
new_ranges[uuid] = {
|
||||
start_time = start_time,
|
||||
end_time = end_time,
|
||||
category = category,
|
||||
skipped = false
|
||||
}
|
||||
end
|
||||
if options.make_chapters and not chapter_cache[uuid] then
|
||||
chapter_cache[uuid] = true
|
||||
local category_title = (category:gsub("^%l", string.upper):gsub("_", " "))
|
||||
create_chapter(category_title .. " segment start (" .. string.sub(uuid, 1, 6) .. ")", start_time)
|
||||
create_chapter(category_title .. " segment end (" .. string.sub(uuid, 1, 6) .. ")", end_time)
|
||||
end
|
||||
end
|
||||
|
||||
function getranges(_, exists, db, more)
|
||||
if type(exists) == "table" and exists["status"] == "1" then
|
||||
if options.server_fallback then
|
||||
mp.add_timeout(0, function() getranges(true, true, "") end)
|
||||
else
|
||||
return mp.osd_message("[sponsorblock] database update failed, gave up")
|
||||
end
|
||||
end
|
||||
if db ~= "" and db ~= database_file then db = database_file end
|
||||
if exists ~= true and not file_exists(db) then
|
||||
if not retrying then
|
||||
mp.osd_message("[sponsorblock] database update failed, retrying...")
|
||||
retrying = true
|
||||
end
|
||||
return update()
|
||||
end
|
||||
if retrying then
|
||||
mp.osd_message("[sponsorblock] database update succeeded")
|
||||
retrying = false
|
||||
end
|
||||
local sponsors
|
||||
local args = {
|
||||
options.python_path,
|
||||
sponsorblock,
|
||||
"ranges",
|
||||
db,
|
||||
options.server_address,
|
||||
youtube_id,
|
||||
options.categories,
|
||||
tostring(options.sha256_length)
|
||||
}
|
||||
if not legacy then
|
||||
sponsors = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = args})
|
||||
else
|
||||
sponsors = utils.subprocess({args = args})
|
||||
end
|
||||
mp.msg.debug("Got: " .. string.gsub(sponsors.stdout, "[\n\r]", ""))
|
||||
if not string.match(sponsors.stdout, "^%s*(.*%S)") then return end
|
||||
if string.match(sponsors.stdout, "error") then return getranges(true, true) end
|
||||
local new_ranges = {}
|
||||
local r_count = 0
|
||||
if more then r_count = -1 end
|
||||
for t in string.gmatch(sponsors.stdout, "[^:%s]+") do
|
||||
uuid = string.match(t, "([^,]+),[^,]+$")
|
||||
if ranges[uuid] then
|
||||
new_ranges[uuid] = ranges[uuid]
|
||||
else
|
||||
process(uuid, t, new_ranges)
|
||||
end
|
||||
r_count = r_count + 1
|
||||
end
|
||||
local c_count = t_count(ranges)
|
||||
if c_count == 0 or r_count >= c_count then
|
||||
ranges = new_ranges
|
||||
end
|
||||
end
|
||||
|
||||
function fast_forward()
|
||||
if options.fast_forward and options.fast_forward == true then
|
||||
speed_timer = nil
|
||||
mp.set_property("speed", 1)
|
||||
end
|
||||
local last_speed = mp.get_property_number("speed")
|
||||
local new_speed = math.min(last_speed + options.fast_forward_increase, options.fast_forward_cap)
|
||||
if new_speed <= last_speed then return end
|
||||
mp.set_property("speed", new_speed)
|
||||
end
|
||||
|
||||
function fade_audio(step)
|
||||
local last_volume = mp.get_property_number("volume")
|
||||
local new_volume = math.max(options.audio_fade_cap, math.min(last_volume + step, volume_before))
|
||||
if new_volume == last_volume then
|
||||
if step >= 0 then fade_dir = nil end
|
||||
if fade_timer ~= nil then fade_timer:kill() end
|
||||
fade_timer = nil
|
||||
return
|
||||
end
|
||||
mp.set_property("volume", new_volume)
|
||||
end
|
||||
|
||||
function skip_ads(name, pos)
|
||||
if pos == nil then return end
|
||||
local sponsor_ahead = false
|
||||
for uuid, t in pairs(ranges) do
|
||||
if (options.fast_forward == uuid or not options.skip_once or not t.skipped) and t.start_time <= pos and t.end_time > pos then
|
||||
if options.fast_forward == uuid then return end
|
||||
if options.fast_forward == false then
|
||||
mp.osd_message("[sponsorblock] " .. t.category .. " skipped")
|
||||
mp.set_property("time-pos", t.end_time)
|
||||
else
|
||||
mp.osd_message("[sponsorblock] skipping " .. t.category)
|
||||
end
|
||||
t.skipped = true
|
||||
last_skip = {uuid = uuid, dir = nil}
|
||||
if options.report_views or options.auto_upvote then
|
||||
local args = {
|
||||
options.python_path,
|
||||
sponsorblock,
|
||||
"stats",
|
||||
database_file,
|
||||
options.server_address,
|
||||
youtube_id,
|
||||
uuid,
|
||||
options.report_views and "1" or "",
|
||||
uid_path,
|
||||
options.user_id,
|
||||
options.auto_upvote and "1" or ""
|
||||
}
|
||||
if not legacy then
|
||||
mp.command_native_async({name = "subprocess", playback_only = false, args = args}, function () end)
|
||||
else
|
||||
utils.subprocess_detached({args = args})
|
||||
end
|
||||
end
|
||||
if options.fast_forward ~= false then
|
||||
options.fast_forward = uuid
|
||||
if speed_timer ~= nil then speed_timer:kill() end
|
||||
speed_timer = mp.add_periodic_timer(1, fast_forward)
|
||||
end
|
||||
return
|
||||
elseif (not options.skip_once or not t.skipped) and t.start_time <= pos + 1 and t.end_time > pos + 1 then
|
||||
sponsor_ahead = true
|
||||
end
|
||||
end
|
||||
if options.audio_fade then
|
||||
if sponsor_ahead then
|
||||
if fade_dir ~= false then
|
||||
if fade_dir == nil then volume_before = mp.get_property_number("volume") end
|
||||
if fade_timer ~= nil then fade_timer:kill() end
|
||||
fade_dir = false
|
||||
fade_timer = mp.add_periodic_timer(.1, function() fade_audio(-options.audio_fade_step) end)
|
||||
end
|
||||
elseif fade_dir == false then
|
||||
fade_dir = true
|
||||
if fade_timer ~= nil then fade_timer:kill() end
|
||||
fade_timer = mp.add_periodic_timer(.1, function() fade_audio(options.audio_fade_step) end)
|
||||
end
|
||||
end
|
||||
if options.fast_forward and options.fast_forward ~= true then
|
||||
options.fast_forward = true
|
||||
speed_timer:kill()
|
||||
speed_timer = nil
|
||||
mp.set_property("speed", 1)
|
||||
end
|
||||
end
|
||||
|
||||
function vote(dir)
|
||||
if last_skip.uuid == "" then return mp.osd_message("[sponsorblock] no sponsors skipped, can't submit vote") end
|
||||
local updown = dir == "1" and "up" or "down"
|
||||
if last_skip.dir == dir then return mp.osd_message("[sponsorblock] " .. updown .. "vote already submitted") end
|
||||
last_skip.dir = dir
|
||||
local args = {
|
||||
options.python_path,
|
||||
sponsorblock,
|
||||
"stats",
|
||||
database_file,
|
||||
options.server_address,
|
||||
youtube_id,
|
||||
last_skip.uuid,
|
||||
"",
|
||||
uid_path,
|
||||
options.user_id,
|
||||
dir
|
||||
}
|
||||
if not legacy then
|
||||
mp.command_native_async({name = "subprocess", playback_only = false, args = args}, function () end)
|
||||
else
|
||||
utils.subprocess({args = args})
|
||||
end
|
||||
mp.osd_message("[sponsorblock] " .. updown .. "vote submitted")
|
||||
end
|
||||
|
||||
function update()
|
||||
mp.command_native_async({name = "subprocess", playback_only = false, args = {
|
||||
options.python_path,
|
||||
sponsorblock,
|
||||
"update",
|
||||
database_file,
|
||||
options.server_address
|
||||
}}, getranges)
|
||||
end
|
||||
|
||||
function file_loaded()
|
||||
local initialized = init
|
||||
ranges = {}
|
||||
segment = {a = 0, b = 0, progress = 0, first = true}
|
||||
last_skip = {uuid = "", dir = nil}
|
||||
chapter_cache = {}
|
||||
local video_path = mp.get_property("path", "")
|
||||
mp.msg.debug("Path: " .. video_path)
|
||||
local video_referer = string.match(mp.get_property("http-header-fields", ""), "Referer:([^,]+)") or ""
|
||||
mp.msg.debug("Referer: " .. video_referer)
|
||||
|
||||
local urls = {
|
||||
"https?://youtu%.be/([%w-_]+).*",
|
||||
"https?://w?w?w?%.?youtube%.com/v/([%w-_]+).*",
|
||||
"/watch.*[?&]v=([%w-_]+).*",
|
||||
"/embed/([%w-_]+).*"
|
||||
}
|
||||
youtube_id = nil
|
||||
for i, url in ipairs(urls) do
|
||||
youtube_id = youtube_id or string.match(video_path, url) or string.match(video_referer, url)
|
||||
if youtube_id then break end
|
||||
end
|
||||
youtube_id = youtube_id or string.match(video_path, options.local_pattern)
|
||||
|
||||
if not youtube_id or string.len(youtube_id) < 11 or (local_pattern and string.len(youtube_id) ~= 11) then return end
|
||||
youtube_id = string.sub(youtube_id, 1, 11)
|
||||
mp.msg.debug("Found YouTube ID: " .. youtube_id)
|
||||
init = true
|
||||
if not options.local_database then
|
||||
getranges(true, true)
|
||||
else
|
||||
local exists = file_exists(database_file)
|
||||
if exists and options.server_fallback then
|
||||
getranges(true, true)
|
||||
mp.add_timeout(0, function() getranges(true, true, "", true) end)
|
||||
elseif exists then
|
||||
getranges(true, true)
|
||||
elseif options.server_fallback then
|
||||
mp.add_timeout(0, function() getranges(true, true, "") end)
|
||||
end
|
||||
end
|
||||
if initialized then return end
|
||||
if options.skip then
|
||||
mp.observe_property("time-pos", "native", skip_ads)
|
||||
end
|
||||
if options.display_name ~= "" then
|
||||
local args = {
|
||||
options.python_path,
|
||||
sponsorblock,
|
||||
"username",
|
||||
database_file,
|
||||
options.server_address,
|
||||
youtube_id,
|
||||
"",
|
||||
"",
|
||||
uid_path,
|
||||
options.user_id,
|
||||
options.display_name
|
||||
}
|
||||
if not legacy then
|
||||
mp.command_native_async({name = "subprocess", playback_only = false, args = args}, function () end)
|
||||
else
|
||||
utils.subprocess_detached({args = args})
|
||||
end
|
||||
end
|
||||
if not options.local_database or (not options.auto_update and file_exists(database_file)) then return end
|
||||
|
||||
if file_exists(database_file) then
|
||||
local db_info = utils.file_info(database_file)
|
||||
local cur_time = os.time(os.date("*t"))
|
||||
local upd_interval = parse_update_interval()
|
||||
if upd_interval == nil or os.difftime(cur_time, db_info.mtime) < upd_interval then return end
|
||||
end
|
||||
|
||||
update()
|
||||
end
|
||||
|
||||
function set_segment()
|
||||
if not youtube_id then return end
|
||||
local pos = mp.get_property_number("time-pos")
|
||||
if pos == nil then return end
|
||||
if segment.progress > 1 then
|
||||
segment.progress = segment.progress - 2
|
||||
end
|
||||
if segment.progress == 1 then
|
||||
segment.progress = 0
|
||||
segment.b = pos
|
||||
mp.osd_message("[sponsorblock] segment boundary B set, press again for boundary A", 3)
|
||||
else
|
||||
segment.progress = 1
|
||||
segment.a = pos
|
||||
mp.osd_message("[sponsorblock] segment boundary A set, press again for boundary B", 3)
|
||||
end
|
||||
if options.make_chapters and not segment.first then
|
||||
local start_time = math.min(segment.a, segment.b)
|
||||
local end_time = math.max(segment.a, segment.b)
|
||||
if end_time - start_time ~= 0 and end_time ~= 0 then
|
||||
clean_chapters()
|
||||
create_chapter("Preview segment start", start_time)
|
||||
create_chapter("Preview segment end", end_time)
|
||||
end
|
||||
end
|
||||
segment.first = false
|
||||
end
|
||||
|
||||
function select_category(selected)
|
||||
for category in string.gmatch(options.categories, "([^,]+)") do
|
||||
mp.remove_key_binding("select_category_"..category)
|
||||
mp.remove_key_binding("kp_select_category_"..category)
|
||||
end
|
||||
submit_segment(selected)
|
||||
end
|
||||
|
||||
function submit_segment(category)
|
||||
if not youtube_id then return end
|
||||
local start_time = math.min(segment.a, segment.b)
|
||||
local end_time = math.max(segment.a, segment.b)
|
||||
if end_time - start_time == 0 or end_time == 0 then
|
||||
mp.osd_message("[sponsorblock] empty segment, not submitting")
|
||||
elseif segment.progress <= 1 then
|
||||
segment.progress = segment.progress + 2
|
||||
local category_list = ""
|
||||
for category_id, category in pairs(all_categories) do
|
||||
local category_title = (category:gsub("^%l", string.upper):gsub("_", " "))
|
||||
category_list = category_list .. category_id .. ": " .. category_title .. "\n"
|
||||
mp.add_forced_key_binding(tostring(category_id), "select_category_"..category, function() select_category(category) end)
|
||||
mp.add_forced_key_binding("KP"..tostring(category_id), "kp_select_category_"..category, function() select_category(category) end)
|
||||
end
|
||||
mp.osd_message(string.format("[sponsorblock] press a number to select category for segment: %.2d:%.2d:%.2d to %.2d:%.2d:%.2d\n\n" .. category_list .. "\nyou can press Shift+G again for default (Sponsor) or hide this message with g", math.floor(start_time/(60*60)), math.floor(start_time/60%60), math.floor(start_time%60), math.floor(end_time/(60*60)), math.floor(end_time/60%60), math.floor(end_time%60)), 30)
|
||||
else
|
||||
mp.osd_message("[sponsorblock] submitting segment...", 30)
|
||||
local submit
|
||||
local args = {
|
||||
options.python_path,
|
||||
sponsorblock,
|
||||
"submit",
|
||||
database_file,
|
||||
options.server_address,
|
||||
youtube_id,
|
||||
tostring(start_time),
|
||||
tostring(end_time),
|
||||
uid_path,
|
||||
options.user_id,
|
||||
category or "sponsor"
|
||||
}
|
||||
if not legacy then
|
||||
submit = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = args})
|
||||
else
|
||||
submit = utils.subprocess({args = args})
|
||||
end
|
||||
if string.match(submit.stdout, "success") then
|
||||
segment = {a = 0, b = 0, progress = 0, first = true}
|
||||
mp.osd_message("[sponsorblock] segment submitted")
|
||||
if options.make_chapters then
|
||||
clean_chapters()
|
||||
create_chapter("Submitted segment start", start_time)
|
||||
create_chapter("Submitted segment end", end_time)
|
||||
end
|
||||
elseif string.match(submit.stdout, "error") then
|
||||
mp.osd_message("[sponsorblock] segment submission failed, server may be down. try again", 5)
|
||||
elseif string.match(submit.stdout, "502") then
|
||||
mp.osd_message("[sponsorblock] segment submission failed, server is down. try again", 5)
|
||||
elseif string.match(submit.stdout, "400") then
|
||||
mp.osd_message("[sponsorblock] segment submission failed, impossible inputs", 5)
|
||||
segment = {a = 0, b = 0, progress = 0, first = true}
|
||||
elseif string.match(submit.stdout, "429") then
|
||||
mp.osd_message("[sponsorblock] segment submission failed, rate limited. try again", 5)
|
||||
elseif string.match(submit.stdout, "409") then
|
||||
mp.osd_message("[sponsorblock] segment already submitted", 3)
|
||||
segment = {a = 0, b = 0, progress = 0, first = true}
|
||||
else
|
||||
mp.osd_message("[sponsorblock] segment submission failed", 5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mp.register_event("file-loaded", file_loaded)
|
||||
mp.add_key_binding("g", "set_segment", set_segment)
|
||||
mp.add_key_binding("G", "submit_segment", submit_segment)
|
||||
mp.add_key_binding("h", "upvote_segment", function() return vote("1") end)
|
||||
mp.add_key_binding("H", "downvote_segment", function() return vote("0") end)
|
||||
-- Bindings below are for backwards compatibility and could be removed at any time
|
||||
mp.add_key_binding(nil, "sponsorblock_set_segment", set_segment)
|
||||
mp.add_key_binding(nil, "sponsorblock_submit_segment", submit_segment)
|
||||
mp.add_key_binding(nil, "sponsorblock_upvote", function() return vote("1") end)
|
||||
mp.add_key_binding(nil, "sponsorblock_downvote", function() return vote("0") end)
|
||||
@@ -0,0 +1,3 @@
|
||||
-- This is a dummy main.lua
|
||||
-- required for mpv 0.33
|
||||
-- do not delete
|
||||
@@ -0,0 +1,122 @@
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import hashlib
|
||||
import sqlite3
|
||||
import random
|
||||
import string
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
if sys.argv[1] in ["submit", "stats", "username"]:
|
||||
if not sys.argv[8]:
|
||||
if os.path.isfile(sys.argv[7]):
|
||||
with open(sys.argv[7]) as f:
|
||||
uid = f.read()
|
||||
else:
|
||||
uid = "".join(random.choices(string.ascii_letters + string.digits, k=36))
|
||||
with open(sys.argv[7], "w") as f:
|
||||
f.write(uid)
|
||||
else:
|
||||
uid = sys.argv[8]
|
||||
|
||||
opener = urllib.request.build_opener()
|
||||
opener.addheaders = [("User-Agent", "mpv_sponsorblock/1.0 (https://github.com/po5/mpv_sponsorblock)")]
|
||||
urllib.request.install_opener(opener)
|
||||
|
||||
if sys.argv[1] == "ranges" and (not sys.argv[2] or not os.path.isfile(sys.argv[2])):
|
||||
sha = None
|
||||
if 3 <= int(sys.argv[6]) <= 32:
|
||||
sha = hashlib.sha256(sys.argv[4].encode()).hexdigest()[:int(sys.argv[6])]
|
||||
times = []
|
||||
try:
|
||||
response = urllib.request.urlopen(sys.argv[3] + "/api/skipSegments" + ("/" + sha + "?" if sha else "?videoID=" + sys.argv[4] + "&") + urllib.parse.urlencode([("categories", json.dumps(sys.argv[5].split(",")))]))
|
||||
segments = json.load(response)
|
||||
for segment in segments:
|
||||
if sha and sys.argv[4] != segment["videoID"]:
|
||||
continue
|
||||
if sha:
|
||||
for s in segment["segments"]:
|
||||
times.append(str(s["segment"][0]) + "," + str(s["segment"][1]) + "," + s["UUID"] + "," + s["category"])
|
||||
else:
|
||||
times.append(str(segment["segment"][0]) + "," + str(segment["segment"][1]) + "," + segment["UUID"] + "," + segment["category"])
|
||||
print(":".join(times))
|
||||
except (TimeoutError, urllib.error.URLError) as e:
|
||||
print("error")
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 404:
|
||||
print("")
|
||||
else:
|
||||
print("error")
|
||||
elif sys.argv[1] == "ranges":
|
||||
conn = sqlite3.connect(sys.argv[2])
|
||||
conn.row_factory = sqlite3.Row
|
||||
c = conn.cursor()
|
||||
times = []
|
||||
for category in sys.argv[5].split(","):
|
||||
c.execute("SELECT startTime, endTime, votes, UUID, category FROM sponsorTimes WHERE videoID = ? AND shadowHidden = 0 AND votes > -1 AND category = ?", (sys.argv[4], category))
|
||||
sponsors = c.fetchall()
|
||||
best = list(sponsors)
|
||||
dealtwith = []
|
||||
similar = []
|
||||
for sponsor_a in sponsors:
|
||||
for sponsor_b in sponsors:
|
||||
if sponsor_a is not sponsor_b and sponsor_a["startTime"] >= sponsor_b["startTime"] and sponsor_a["startTime"] <= sponsor_b["endTime"]:
|
||||
similar.append([sponsor_a, sponsor_b])
|
||||
if sponsor_a in best:
|
||||
best.remove(sponsor_a)
|
||||
if sponsor_b in best:
|
||||
best.remove(sponsor_b)
|
||||
for sponsors_a in similar:
|
||||
if sponsors_a in dealtwith:
|
||||
continue
|
||||
group = set(sponsors_a)
|
||||
for sponsors_b in similar:
|
||||
if sponsors_b[0] in group or sponsors_b[1] in group:
|
||||
group.add(sponsors_b[0])
|
||||
group.add(sponsors_b[1])
|
||||
dealtwith.append(sponsors_b)
|
||||
best.append(max(group, key=lambda x:x["votes"]))
|
||||
for time in best:
|
||||
times.append(str(time["startTime"]) + "," + str(time["endTime"]) + "," + time["UUID"] + "," + time["category"])
|
||||
print(":".join(times))
|
||||
elif sys.argv[1] == "update":
|
||||
try:
|
||||
urllib.request.urlretrieve(sys.argv[3] + "/database.db", sys.argv[2] + ".tmp")
|
||||
os.replace(sys.argv[2] + ".tmp", sys.argv[2])
|
||||
except PermissionError:
|
||||
print("database update failed, file currently in use", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except ConnectionResetError:
|
||||
print("database update failed, connection reset", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except TimeoutError:
|
||||
print("database update failed, timed out", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except urllib.error.URLError:
|
||||
print("database update failed", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
elif sys.argv[1] == "submit":
|
||||
try:
|
||||
req = urllib.request.Request(sys.argv[3] + "/api/skipSegments", data=json.dumps({"videoID": sys.argv[4], "segments": [{"segment": [float(sys.argv[5]), float(sys.argv[6])], "category": sys.argv[9]}], "userID": uid}).encode(), headers={"Content-Type": "application/json"})
|
||||
response = urllib.request.urlopen(req)
|
||||
print("success")
|
||||
except urllib.error.HTTPError as e:
|
||||
print(e.code)
|
||||
except:
|
||||
print("error")
|
||||
elif sys.argv[1] == "stats":
|
||||
try:
|
||||
if sys.argv[6]:
|
||||
urllib.request.urlopen(sys.argv[3] + "/api/viewedVideoSponsorTime?UUID=" + sys.argv[5])
|
||||
if sys.argv[9]:
|
||||
urllib.request.urlopen(sys.argv[3] + "/api/voteOnSponsorTime?UUID=" + sys.argv[5] + "&userID=" + uid + "&type=" + sys.argv[9])
|
||||
except:
|
||||
pass
|
||||
elif sys.argv[1] == "username":
|
||||
try:
|
||||
data = urllib.parse.urlencode({"userID": uid, "userName": sys.argv[9]}).encode()
|
||||
req = urllib.request.Request(sys.argv[3] + "/api/setUsername", data=data)
|
||||
urllib.request.urlopen(req)
|
||||
except:
|
||||
pass
|
||||
@@ -0,0 +1 @@
|
||||
iUa1tBJrcHEXyi4YFO5OjUaXy57xRu9QSbNp
|
||||
Reference in New Issue
Block a user