mirror of
https://github.com/sahinakkaya/dotfiles.git
synced 2024-12-22 23:29:36 +01:00
Add mpv's config
This commit is contained in:
parent
77936c9acd
commit
a3f3795ac6
180
.config/mpv/input.conf
Normal file
180
.config/mpv/input.conf
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
# mpv keybindings
|
||||||
|
#
|
||||||
|
# Location of user-defined bindings: ~/.config/mpv/input.conf
|
||||||
|
#
|
||||||
|
|
||||||
|
# Lines starting with # are comments. Use SHARP to assign the # key.
|
||||||
|
# Copy this file and uncomment and edit the bindings you want to change.
|
||||||
|
#
|
||||||
|
# List of commands and further details: DOCS/man/input.rst
|
||||||
|
# List of special keys: --input-keylist
|
||||||
|
# Keybindings testing mode: mpv --input-test --force-window --idle
|
||||||
|
#
|
||||||
|
# Use 'ignore' to unbind a key fully (e.g. 'ctrl+a ignore').
|
||||||
|
#
|
||||||
|
# Strings need to be quoted and escaped:
|
||||||
|
# KEY show-text "This is a single backslash: \\ and a quote: \" !"
|
||||||
|
#
|
||||||
|
# You can use modifier-key combinations like Shift+Left or Ctrl+Alt+x with
|
||||||
|
# the modifiers Shift, Ctrl, Alt and Meta (may not work on the terminal).
|
||||||
|
#
|
||||||
|
# The default keybindings are hardcoded into the mpv binary.
|
||||||
|
# You can disable them completely with: --no-input-default-bindings
|
||||||
|
|
||||||
|
# Developer note:
|
||||||
|
# On compilation, this file is baked into the mpv binary, and all lines are
|
||||||
|
# uncommented (unless '#' is followed by a space) - thus this file defines the
|
||||||
|
# default key bindings.
|
||||||
|
|
||||||
|
# If this is enabled, treat all the following bindings as default.
|
||||||
|
#default-bindings start
|
||||||
|
|
||||||
|
#MBTN_LEFT ignore # don't do anything
|
||||||
|
#MBTN_LEFT_DBL cycle fullscreen # toggle fullscreen
|
||||||
|
#MBTN_RIGHT cycle pause # toggle pause/playback mode
|
||||||
|
#MBTN_BACK playlist-prev # skip to the previous file
|
||||||
|
#MBTN_FORWARD playlist-next # skip to the next file
|
||||||
|
|
||||||
|
# Mouse wheels, touchpad or other input devices that have axes
|
||||||
|
# if the input devices supports precise scrolling it will also scale the
|
||||||
|
# numeric value accordingly
|
||||||
|
#WHEEL_UP seek 10 # seek 10 seconds forward
|
||||||
|
#WHEEL_DOWN seek -10 # seek 10 seconds backward
|
||||||
|
#WHEEL_LEFT add volume -2
|
||||||
|
#WHEEL_RIGHT add volume 2
|
||||||
|
|
||||||
|
## Seek units are in seconds, but note that these are limited by keyframes
|
||||||
|
#RIGHT seek 5 # seek 5 seconds forward
|
||||||
|
#LEFT seek -5 # seek 5 seconds backward
|
||||||
|
#UP seek 60 # seek 1 minute forward
|
||||||
|
#DOWN seek -60 # seek 1 minute backward
|
||||||
|
# Do smaller, always exact (non-keyframe-limited), seeks with shift.
|
||||||
|
# Don't show them on the OSD (no-osd).
|
||||||
|
#Shift+RIGHT no-osd seek 1 exact # seek exactly 1 second forward
|
||||||
|
#Shift+LEFT no-osd seek -1 exact # seek exactly 1 second backward
|
||||||
|
#Shift+UP no-osd seek 5 exact # seek exactly 5 seconds forward
|
||||||
|
#Shift+DOWN no-osd seek -5 exact # seek exactly 5 seconds backward
|
||||||
|
#Ctrl+LEFT no-osd sub-seek -1 # seek to the previous subtitle
|
||||||
|
#Ctrl+RIGHT no-osd sub-seek 1 # seek to the next subtitle
|
||||||
|
#Ctrl+Shift+LEFT sub-step -1 # change subtitle timing such that the previous subtitle is displayed
|
||||||
|
#Ctrl+Shift+RIGHT sub-step 1 # change subtitle timing such that the next subtitle is displayed
|
||||||
|
#Alt+left add video-pan-x 0.1 # move the video right
|
||||||
|
#Alt+right add video-pan-x -0.1 # move the video left
|
||||||
|
#Alt+up add video-pan-y 0.1 # move the video down
|
||||||
|
#Alt+down add video-pan-y -0.1 # move the video up
|
||||||
|
#Alt++ add video-zoom 0.1 # zoom in
|
||||||
|
#Alt+- add video-zoom -0.1 # zoom out
|
||||||
|
#Alt+BS set video-zoom 0 ; set video-pan-x 0 ; set video-pan-y 0 # reset zoom and pan settings
|
||||||
|
#PGUP add chapter 1 # seek to the next chapter
|
||||||
|
#PGDWN add chapter -1 # seek to the previous chapter
|
||||||
|
#Shift+PGUP seek 600 # seek 10 minutes forward
|
||||||
|
#Shift+PGDWN seek -600 # seek 10 minutes backward
|
||||||
|
#[ multiply speed 1/1.1 # decrease the playback speed
|
||||||
|
#] multiply speed 1.1 # increase the playback speed
|
||||||
|
< multiply speed 1/1.5
|
||||||
|
> multiply speed 1.5
|
||||||
|
#BS set speed 1.0 # reset the speed to normal
|
||||||
|
#Shift+BS revert-seek # undo the previous (or marked) seek
|
||||||
|
#Shift+Ctrl+BS revert-seek mark # mark the position for revert-seek
|
||||||
|
#q quit
|
||||||
|
#Q quit-watch-later # exit and remember the playback position
|
||||||
|
#q {encode} quit 4
|
||||||
|
#ESC set fullscreen no # leave fullscreen
|
||||||
|
#ESC {encode} quit 4
|
||||||
|
#p cycle pause # toggle pause/playback mode
|
||||||
|
#. frame-step # advance one frame and pause
|
||||||
|
#, frame-back-step # go back by one frame and pause
|
||||||
|
#SPACE cycle pause # toggle pause/playback mode
|
||||||
|
#> playlist-next # skip to the next file
|
||||||
|
#ENTER playlist-next # skip to the next file
|
||||||
|
#< playlist-prev # skip to the previous file
|
||||||
|
#O no-osd cycle-values osd-level 3 1 # toggle displaying the OSD on user interaction or always
|
||||||
|
#o show-progress # show playback progress
|
||||||
|
#P show-progress # show playback progress
|
||||||
|
#i script-binding stats/display-stats # display information and statistics
|
||||||
|
#I script-binding stats/display-stats-toggle # toggle displaying information and statistics
|
||||||
|
#` script-binding console/enable # open the console
|
||||||
|
#z add sub-delay -0.1 # shift subtitles 100 ms earlier
|
||||||
|
#Z add sub-delay +0.1 # delay subtitles by 100 ms
|
||||||
|
#x add sub-delay +0.1 # delay subtitles by 100 ms
|
||||||
|
#ctrl++ add audio-delay 0.100 # change audio/video sync by delaying the audio
|
||||||
|
#ctrl+- add audio-delay -0.100 # change audio/video sync by shifting the audio earlier
|
||||||
|
#Shift+g add sub-scale +0.1 # increase the subtitle font size
|
||||||
|
#Shift+f add sub-scale -0.1 # decrease the subtitle font size
|
||||||
|
#9 add volume -2
|
||||||
|
#/ add volume -2
|
||||||
|
#0 add volume 2
|
||||||
|
#* add volume 2
|
||||||
|
#m cycle mute # toggle mute
|
||||||
|
#1 add contrast -1
|
||||||
|
#2 add contrast 1
|
||||||
|
#3 add brightness -1
|
||||||
|
#4 add brightness 1
|
||||||
|
#5 add gamma -1
|
||||||
|
#6 add gamma 1
|
||||||
|
#7 add saturation -1
|
||||||
|
#8 add saturation 1
|
||||||
|
#Alt+0 set current-window-scale 0.5 # halve the window size
|
||||||
|
#Alt+1 set current-window-scale 1.0 # reset the window size
|
||||||
|
#Alt+2 set current-window-scale 2.0 # double the window size
|
||||||
|
#d cycle deinterlace # toggle the deinterlacing filter
|
||||||
|
#r add sub-pos -1 # move subtitles up
|
||||||
|
#R add sub-pos +1 # move subtitles down
|
||||||
|
#t add sub-pos +1 # move subtitles down
|
||||||
|
#v cycle sub-visibility # hide or show the subtitles
|
||||||
|
#Alt+v cycle secondary-sub-visibility # hide or show the secondary subtitles
|
||||||
|
#V cycle sub-ass-vsfilter-aspect-compat # toggle stretching SSA/ASS subtitles with anamorphic videos to match the historical renderer
|
||||||
|
#u cycle-values sub-ass-override "force" "no" # toggle overriding SSA/ASS subtitle styles with the normal styles
|
||||||
|
#j cycle sub # switch subtitle track
|
||||||
|
#J cycle sub down # switch subtitle track backwards
|
||||||
|
#SHARP cycle audio # switch audio track
|
||||||
|
#_ cycle video # switch video track
|
||||||
|
#T cycle ontop # toggle placing the video on top of other windows
|
||||||
|
#f cycle fullscreen # toggle fullscreen
|
||||||
|
#s screenshot # take a screenshot of the video in its original resolution with subtitles
|
||||||
|
#S screenshot video # take a screenshot of the video in its original resolution without subtitles
|
||||||
|
#Ctrl+s screenshot window # take a screenshot of the window with OSD and subtitles
|
||||||
|
#Alt+s screenshot each-frame # automatically screenshot every frame; issue this command again to stop taking screenshots
|
||||||
|
#w add panscan -0.1 # decrease panscan
|
||||||
|
#W add panscan +0.1 # shrink black bars by cropping the video
|
||||||
|
#e add panscan +0.1 # shrink black bars by cropping the video
|
||||||
|
#A cycle-values video-aspect-override "16:9" "4:3" "2.35:1" "-1" # cycle the video aspect ratio ("-1" is the container aspect)
|
||||||
|
#POWER quit
|
||||||
|
#PLAY cycle pause # toggle pause/playback mode
|
||||||
|
#PAUSE cycle pause # toggle pause/playback mode
|
||||||
|
#PLAYPAUSE cycle pause # toggle pause/playback mode
|
||||||
|
#PLAYONLY set pause no # unpause
|
||||||
|
#PAUSEONLY set pause yes # pause
|
||||||
|
#STOP quit
|
||||||
|
#FORWARD seek 60 # seek 1 minute forward
|
||||||
|
#REWIND seek -60 # seek 1 minute backward
|
||||||
|
#NEXT playlist-next # skip to the next file
|
||||||
|
#PREV playlist-prev # skip to the previous file
|
||||||
|
#VOLUME_UP add volume 2
|
||||||
|
#VOLUME_DOWN add volume -2
|
||||||
|
#MUTE cycle mute # toggle mute
|
||||||
|
#CLOSE_WIN quit
|
||||||
|
#CLOSE_WIN {encode} quit 4
|
||||||
|
#ctrl+w quit
|
||||||
|
#E cycle edition # switch edition
|
||||||
|
#l ab-loop # set/clear A-B loop points
|
||||||
|
#L cycle-values loop-file "inf" "no" # toggle infinite looping
|
||||||
|
#ctrl+c quit 4
|
||||||
|
#DEL script-binding osc/visibility # cycle OSC visibility between never, auto (mouse-move) and always
|
||||||
|
#ctrl+h cycle-values hwdec "auto" "no" # toggle hardware decoding
|
||||||
|
#F8 show-text ${playlist} # show the playlist
|
||||||
|
#F9 show-text ${track-list} # show the list of video, audio and sub tracks
|
||||||
|
|
||||||
|
#
|
||||||
|
# Legacy bindings (may or may not be removed in the future)
|
||||||
|
#
|
||||||
|
! add chapter -1 # seek to the previous chapter
|
||||||
|
@ add chapter 1 # seek to the next chapter
|
||||||
|
|
||||||
|
#
|
||||||
|
# Not assigned by default
|
||||||
|
# (not an exhaustive list of unbound commands)
|
||||||
|
#
|
||||||
|
|
||||||
|
# ? cycle sub-forced-only # toggle DVD forced subs
|
||||||
|
# ? stop # stop playback (quit or enter idle mode)
|
3
.config/mpv/mpv.conf
Normal file
3
.config/mpv/mpv.conf
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
script-opts=ytdl_hook-ytdl_path=/usr/bin/yt-dlp
|
||||||
|
ytdl-format=bestvideo[height<=?480]+bestaudio/best
|
||||||
|
|
212
.config/mpv/scripts/autload.lua
Normal file
212
.config/mpv/scripts/autload.lua
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
-- This script automatically loads playlist entries before and after the
|
||||||
|
-- the currently played file. It does so by scanning the directory a file is
|
||||||
|
-- located in when starting playback. It sorts the directory entries
|
||||||
|
-- alphabetically, and adds entries before and after the current file to
|
||||||
|
-- the internal playlist. (It stops if it would add an already existing
|
||||||
|
-- playlist entry at the same position - this makes it "stable".)
|
||||||
|
-- Add at most 5000 * 2 files when starting a file (before + after).
|
||||||
|
--[[
|
||||||
|
To configure this script use file autoload.conf in directory script-opts (the "script-opts"
|
||||||
|
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
|
||||||
|
|
||||||
|
Example configuration would be:
|
||||||
|
|
||||||
|
disabled=no
|
||||||
|
images=no
|
||||||
|
videos=yes
|
||||||
|
audio=yes
|
||||||
|
ignore_hidden=yes
|
||||||
|
|
||||||
|
--]] MAXENTRIES = 5000
|
||||||
|
|
||||||
|
local msg = require 'mp.msg'
|
||||||
|
local options = require 'mp.options'
|
||||||
|
local utils = require 'mp.utils'
|
||||||
|
|
||||||
|
o = {
|
||||||
|
disabled = false,
|
||||||
|
images = true,
|
||||||
|
videos = true,
|
||||||
|
audio = true,
|
||||||
|
ignore_hidden = true
|
||||||
|
}
|
||||||
|
options.read_options(o)
|
||||||
|
|
||||||
|
function Set(t)
|
||||||
|
local set = {}
|
||||||
|
for _, v in pairs(t) do set[v] = true end
|
||||||
|
return set
|
||||||
|
end
|
||||||
|
|
||||||
|
function SetUnion(a, b)
|
||||||
|
local res = {}
|
||||||
|
for k in pairs(a) do res[k] = true end
|
||||||
|
for k in pairs(b) do res[k] = true end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
EXTENSIONS_VIDEO = Set {
|
||||||
|
'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg',
|
||||||
|
'm4v', '3gp'
|
||||||
|
}
|
||||||
|
|
||||||
|
EXTENSIONS_AUDIO = Set {
|
||||||
|
'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus'
|
||||||
|
}
|
||||||
|
|
||||||
|
EXTENSIONS_IMAGES = Set {
|
||||||
|
'jpg', 'jpeg', 'png', 'tif', 'tiff', 'gif', 'webp', 'svg', 'bmp'
|
||||||
|
}
|
||||||
|
|
||||||
|
EXTENSIONS = Set {}
|
||||||
|
if o.videos then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_VIDEO) end
|
||||||
|
if o.audio then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_AUDIO) end
|
||||||
|
if o.images then EXTENSIONS = SetUnion(EXTENSIONS, EXTENSIONS_IMAGES) end
|
||||||
|
|
||||||
|
function add_files_at(index, files)
|
||||||
|
index = index - 1
|
||||||
|
local oldcount = mp.get_property_number('playlist-count', 1)
|
||||||
|
for i = 1, #files do
|
||||||
|
mp.commandv('loadfile', files[i], 'append')
|
||||||
|
mp.commandv('playlist-move', oldcount + i - 1, index + i - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_extension(path)
|
||||||
|
match = string.match(path, '%.([^%.]+)$')
|
||||||
|
if match == nil then
|
||||||
|
return 'nomatch'
|
||||||
|
else
|
||||||
|
return match
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.filter = function(t, iter)
|
||||||
|
for i = #t, 1, -1 do if not iter(t[i]) then table.remove(t, i) end end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- splitbynum and alnumcomp from alphanum.lua (C) Andre Bogus
|
||||||
|
-- Released under the MIT License
|
||||||
|
-- http://www.davekoelle.com/files/alphanum.lua
|
||||||
|
|
||||||
|
-- split a string into a table of number and string values
|
||||||
|
function splitbynum(s)
|
||||||
|
local result = {}
|
||||||
|
for x, y in (s or ''):gmatch('(%d*)(%D*)') do
|
||||||
|
if x ~= '' then table.insert(result, tonumber(x)) end
|
||||||
|
if y ~= '' then table.insert(result, y) end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function clean_key(k)
|
||||||
|
k = (' ' .. k .. ' '):gsub('%s+', ' '):sub(2, -2):lower()
|
||||||
|
return splitbynum(k)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- compare two strings
|
||||||
|
function alnumcomp(x, y)
|
||||||
|
local xt, yt = clean_key(x), clean_key(y)
|
||||||
|
for i = 1, math.min(#xt, #yt) do
|
||||||
|
local xe, ye = xt[i], yt[i]
|
||||||
|
if type(xe) == 'string' then
|
||||||
|
ye = tostring(ye)
|
||||||
|
elseif type(ye) == 'string' then
|
||||||
|
xe = tostring(xe)
|
||||||
|
end
|
||||||
|
if xe ~= ye then return xe < ye end
|
||||||
|
end
|
||||||
|
return #xt < #yt
|
||||||
|
end
|
||||||
|
|
||||||
|
local autoloaded = nil
|
||||||
|
|
||||||
|
function find_and_add_entries()
|
||||||
|
local path = mp.get_property('path', '')
|
||||||
|
local dir, filename = utils.split_path(path)
|
||||||
|
msg.trace(('dir: %s, filename: %s'):format(dir, filename))
|
||||||
|
if o.disabled then
|
||||||
|
msg.verbose('stopping: autoload disabled')
|
||||||
|
return
|
||||||
|
elseif #dir == 0 then
|
||||||
|
msg.verbose('stopping: not a local path')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local pl_count = mp.get_property_number('playlist-count', 1)
|
||||||
|
-- check if this is a manually made playlist
|
||||||
|
if (pl_count > 1 and autoloaded == nil) or
|
||||||
|
(pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] ==
|
||||||
|
nil) then
|
||||||
|
msg.verbose('stopping: manually made playlist')
|
||||||
|
return
|
||||||
|
else
|
||||||
|
autoloaded = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local pl = mp.get_property_native('playlist', {})
|
||||||
|
local pl_current = mp.get_property_number('playlist-pos-1', 1)
|
||||||
|
msg.trace(('playlist-pos-1: %s, playlist: %s'):format(pl_current,
|
||||||
|
utils.to_string(pl)))
|
||||||
|
|
||||||
|
local files = utils.readdir(dir, 'files')
|
||||||
|
if files == nil then
|
||||||
|
msg.verbose('no other files in directory')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
table.filter(files, function(v, k)
|
||||||
|
-- The current file could be a hidden file, ignoring it doesn't load other
|
||||||
|
-- files from the current directory.
|
||||||
|
if (o.ignore_hidden and not (v == filename) and string.match(v, '^%.')) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local ext = get_extension(v)
|
||||||
|
if ext == nil then return false end
|
||||||
|
return EXTENSIONS[string.lower(ext)]
|
||||||
|
end)
|
||||||
|
table.sort(files, alnumcomp)
|
||||||
|
|
||||||
|
if dir == '.' then dir = '' end
|
||||||
|
|
||||||
|
-- Find the current pl entry (dir+"/"+filename) in the sorted dir list
|
||||||
|
local current
|
||||||
|
for i = 1, #files do
|
||||||
|
if files[i] == filename then
|
||||||
|
current = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if current == nil then return end
|
||||||
|
msg.trace('current file position in files: ' .. current)
|
||||||
|
|
||||||
|
local append = {[-1] = {}, [1] = {}}
|
||||||
|
for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
|
||||||
|
for i = 1, MAXENTRIES do
|
||||||
|
local file = files[current + i * direction]
|
||||||
|
local pl_e = pl[pl_current + i * direction]
|
||||||
|
if file == nil or file[1] == '.' then break end
|
||||||
|
|
||||||
|
local filepath = dir .. file
|
||||||
|
if pl_e then
|
||||||
|
-- If there's a playlist entry, and it's the same file, stop.
|
||||||
|
msg.trace(pl_e.filename .. ' == ' .. filepath .. ' ?')
|
||||||
|
if pl_e.filename == filepath then break end
|
||||||
|
end
|
||||||
|
|
||||||
|
if direction == -1 then
|
||||||
|
if pl_current == 1 then -- never add additional entries in the middle
|
||||||
|
msg.info('Prepending ' .. file)
|
||||||
|
table.insert(append[-1], 1, filepath)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
msg.info('Adding ' .. file)
|
||||||
|
table.insert(append[1], filepath)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
add_files_at(pl_current + 1, append[1])
|
||||||
|
add_files_at(pl_current, append[-1])
|
||||||
|
end
|
||||||
|
|
||||||
|
mp.register_event('start-file', find_and_add_entries)
|
173
.config/mpv/scripts/mpv_chapters.js
Normal file
173
.config/mpv/scripts/mpv_chapters.js
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
//display chapter on osd and easily switch between chapters by click on title of chapter
|
||||||
|
mp.register_event("file-loaded", init);
|
||||||
|
mp.observe_property("chapter", "number", onChapterChange);
|
||||||
|
mp.observe_property("chapter-list/count", "number", init);
|
||||||
|
var options = {
|
||||||
|
font_size: 16,
|
||||||
|
font_color: "00FFFF",
|
||||||
|
border_size: 1.0,
|
||||||
|
border_color: "000000",
|
||||||
|
font_color_currentChapter: "C27F1B",
|
||||||
|
};
|
||||||
|
var playinfo = {
|
||||||
|
chapters: [], //array
|
||||||
|
chaptercount: "", // int
|
||||||
|
assinterface: [], //array(deprecated, use single assdraw instead)
|
||||||
|
currentChapter: "", //int
|
||||||
|
loaded:false,
|
||||||
|
};
|
||||||
|
var toggle_switch = false;
|
||||||
|
var assdraw = mp.create_osd_overlay("ass-events");
|
||||||
|
var autohidedelay = mp.get_property_number("cursor-autohide");
|
||||||
|
//function
|
||||||
|
function init() {
|
||||||
|
playinfo.chapters = getChapters();
|
||||||
|
playinfo.chaptercount = playinfo.chapters.length;
|
||||||
|
if(playinfo.chaptercount == 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (playinfo.chaptercount * options.font_size > 1000 / 1.5) {
|
||||||
|
options.font_size = options.font_size - 1;
|
||||||
|
}
|
||||||
|
drawChapterList();
|
||||||
|
mp.msg.info("initiated");
|
||||||
|
playinfo.loaded = true;
|
||||||
|
}
|
||||||
|
function getChapters() {
|
||||||
|
var chapterCount = mp.get_property("chapter-list/count");
|
||||||
|
if (chapterCount === 0) {
|
||||||
|
return ["null"];
|
||||||
|
} else {
|
||||||
|
var chaptersArray = [];
|
||||||
|
for (var index = 0; index < chapterCount; index++) {
|
||||||
|
var chapterTitle = mp.get_property_native(
|
||||||
|
"chapter-list/" + index + "/title"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (chapterTitle != undefined) {
|
||||||
|
chaptersArray.push(chapterTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chaptersArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawChapterList() {
|
||||||
|
var resY = 0;
|
||||||
|
var resX = 0;
|
||||||
|
var assdrawdata = "";
|
||||||
|
function setPos(str, _X, _Y) {
|
||||||
|
str = str + "{\\pos(" + _X + ", " + _Y + ")}";
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
function setborderSize(str) {
|
||||||
|
str = str + "{\\bord" + options.border_size + "}";
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
function setborderColor(str) {
|
||||||
|
str = str + "{\\3c&H" + options.border_color + "&}";
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
function setFontColor(str, index) {
|
||||||
|
var _color;
|
||||||
|
if (playinfo.currentChapter == index) {
|
||||||
|
_color = options.font_color_currentChapter;
|
||||||
|
} else {
|
||||||
|
_color = options.font_color;
|
||||||
|
}
|
||||||
|
str = str + "{\\c&H" + _color + "&}";
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
function setFont(str) {
|
||||||
|
str = str + "{\\fs" + options.font_size + "}";
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
function setEndofmodifiers(str) {
|
||||||
|
str = str + "{\\p0}";
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
function setEndofLine(str) {
|
||||||
|
str = str + "\n";
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
playinfo.chapters.forEach(function (element, index) {
|
||||||
|
assdrawdata = setPos(assdrawdata, resX, resY);
|
||||||
|
assdrawdata = setborderSize(assdrawdata);
|
||||||
|
assdrawdata = setborderColor(assdrawdata);
|
||||||
|
assdrawdata = setFontColor(assdrawdata, index);
|
||||||
|
assdrawdata = setFont(assdrawdata);
|
||||||
|
assdrawdata = setEndofmodifiers(assdrawdata);
|
||||||
|
assdrawdata = assdrawdata + element;
|
||||||
|
assdrawdata = setEndofLine(assdrawdata);
|
||||||
|
resY += options.font_size;
|
||||||
|
});
|
||||||
|
assdraw.data = assdrawdata
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleOverlay() {
|
||||||
|
if(!playinfo.loaded){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!toggle_switch) {
|
||||||
|
drawChapterList();
|
||||||
|
assdraw.update();
|
||||||
|
mp.set_property("cursor-autohide", "no");
|
||||||
|
toggle_switch = !toggle_switch;
|
||||||
|
} else {
|
||||||
|
assdraw.remove();
|
||||||
|
mp.set_property("cursor-autohide", autohidedelay);
|
||||||
|
toggle_switch = !toggle_switch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChapterChange() {
|
||||||
|
playinfo.currentChapter = mp.get_property_native("chapter");
|
||||||
|
if (playinfo.currentChapter != undefined) {
|
||||||
|
drawChapterList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((playinfo.currentChapter != undefined) & toggle_switch) {
|
||||||
|
assdraw.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function pos2chapter(x, y, overallscale) {
|
||||||
|
var vectical = y / (options.font_size * overallscale);
|
||||||
|
if(vectical > playinfo.chaptercount){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var intVectical = Math.floor(vectical);
|
||||||
|
var lengthofTitleClicked = playinfo.chapters[intVectical].length;
|
||||||
|
var lengthofTitleClicked_px =
|
||||||
|
(lengthofTitleClicked * options.font_size) / overallscale;
|
||||||
|
if (x < lengthofTitleClicked_px) {
|
||||||
|
return intVectical;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getOverallScale() {
|
||||||
|
return mp.get_osd_size().height / 720;
|
||||||
|
}
|
||||||
|
function onMBTN_LEFT() {
|
||||||
|
//get mouse position
|
||||||
|
if(!playinfo.loaded){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (toggle_switch) {
|
||||||
|
var overallscale = getOverallScale();
|
||||||
|
var pos = mp.get_mouse_pos();
|
||||||
|
var chapterClicked = pos2chapter(pos.x, pos.y, overallscale);
|
||||||
|
if (chapterClicked != null) {
|
||||||
|
mp.set_property_native("chapter", chapterClicked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mp.add_key_binding("TAB", "tab", function () {
|
||||||
|
toggleOverlay();
|
||||||
|
});
|
||||||
|
mp.add_key_binding("MBTN_LEFT", "mbtn_left", function () {
|
||||||
|
onMBTN_LEFT();
|
||||||
|
});
|
597
.config/mpv/scripts/sponsorblock.lua
Normal file
597
.config/mpv/scripts/sponsorblock.lua
Normal file
@ -0,0 +1,597 @@
|
|||||||
|
-- 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',
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
|
||||||
|
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', 'music_offtopic'
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
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)
|
3
.config/mpv/scripts/sponsorblock_shared/main.lua
Normal file
3
.config/mpv/scripts/sponsorblock_shared/main.lua
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- This is a dummy main.lua
|
||||||
|
-- required for mpv 0.33
|
||||||
|
-- do not delete
|
124
.config/mpv/scripts/sponsorblock_shared/sponsorblock.py
Normal file
124
.config/mpv/scripts/sponsorblock_shared/sponsorblock.py
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
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 as e:
|
||||||
|
print("database update failed", file=sys.stderr)
|
||||||
|
print(e, 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
|
1
.config/mpv/scripts/sponsorblock_shared/sponsorblock.txt
Normal file
1
.config/mpv/scripts/sponsorblock_shared/sponsorblock.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
VvdShPVADjWuTg9L44tkj5B1IqOS8Jfak5XX
|
Loading…
Reference in New Issue
Block a user