mpv: Replace sponsorblock with minimal version
Use a more minimal version of sponsorblock which neither caches the results locally, nor allows many of the fancy things that the python plugin allows. On the other hand, it is lua-only and does not depend on python, and fulfills the same basic functionality: skipping sponsorship segments. You can toggle the plugin on and off by pressing `b`. Additionally, it also works on files played locally, if those carry a yt identifier in their filename.
This commit is contained in:
parent
c751df31ad
commit
4ade691441
5 changed files with 91 additions and 626 deletions
|
@ -1,506 +0,0 @@
|
||||||
-- 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",
|
|
||||||
|
|
||||||
-- Whether or not to automatically skip sponsors
|
|
||||||
skip = true,
|
|
||||||
|
|
||||||
-- 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 = true,
|
|
||||||
|
|
||||||
-- Update database on first run, does nothing if local_database is false
|
|
||||||
auto_update = true,
|
|
||||||
|
|
||||||
-- 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 = true,
|
|
||||||
|
|
||||||
-- 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 with skip=false
|
|
||||||
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,
|
|
||||||
|
|
||||||
-- Pattern for video id in local files, ignored if blank
|
|
||||||
-- Recommended value for base youtube-dl is "-([%a%d%-_]+)%.[mw][kpe][v4b][m]?$"
|
|
||||||
local_pattern = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
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"
|
|
||||||
if mp.get_script_directory == nil then
|
|
||||||
scripts_dir = mp.find_config_file("scripts/sponsorblock")
|
|
||||||
else
|
|
||||||
scripts_dir = mp.get_script_directory()
|
|
||||||
end
|
|
||||||
local sponsorblock = utils.join_path(scripts_dir, "shared/sponsorblock.py")
|
|
||||||
local uid_path = utils.join_path(scripts_dir, "shared/sponsorblock.txt")
|
|
||||||
local database_file = options.local_database and
|
|
||||||
utils.join_path(scripts_dir, "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")
|
|
||||||
|
|
||||||
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) return a.time < b.time 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 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
|
|
||||||
}
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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
|
|
||||||
goto continue
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if end_time - start_time >= options.min_duration then
|
|
||||||
new_ranges[uuid] = {
|
|
||||||
start_time = start_time,
|
|
||||||
end_time = end_time,
|
|
||||||
skipped = false
|
|
||||||
}
|
|
||||||
end
|
|
||||||
if options.make_chapters then
|
|
||||||
create_chapter("Sponsor start (" .. string.sub(uuid, 1, 6) ..
|
|
||||||
")", start_time)
|
|
||||||
create_chapter("Sponsor end (" .. string.sub(uuid, 1, 6) .. ")",
|
|
||||||
end_time)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
::continue::
|
|
||||||
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()
|
|
||||||
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] sponsor skipped")
|
|
||||||
mp.set_property("time-pos", t.end_time)
|
|
||||||
else
|
|
||||||
mp.osd_message("[sponsorblock] skipping sponsor")
|
|
||||||
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
|
|
||||||
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()
|
|
||||||
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}
|
|
||||||
local video_path = mp.get_property("path")
|
|
||||||
local youtube_id1 = string.match(video_path,
|
|
||||||
"https?://youtu%.be/([%a%d%-_]+).*")
|
|
||||||
local youtube_id2 = string.match(video_path,
|
|
||||||
"https?://w?w?w?%.?youtube%.com/v/([%a%d%-_]+).*")
|
|
||||||
local youtube_id3 = string.match(video_path, "/watch%?v=([%a%d%-_]+).*")
|
|
||||||
local youtube_id4 = string.match(video_path, "/embed/([%a%d%-_]+).*")
|
|
||||||
local local_pattern = nil
|
|
||||||
if options.local_pattern ~= "" then
|
|
||||||
local_pattern = string.match(video_path, options.local_pattern)
|
|
||||||
end
|
|
||||||
youtube_id = youtube_id1 or youtube_id2 or youtube_id3 or youtube_id4 or
|
|
||||||
local_pattern
|
|
||||||
if not youtube_id then return end
|
|
||||||
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
|
|
||||||
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 submit_segment()
|
|
||||||
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
|
|
||||||
mp.osd_message(string.format(
|
|
||||||
"[sponsorblock] press Shift+G again to confirm: %.2d:%.2d:%.2d to %.2d:%.2d:%.2d",
|
|
||||||
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)), 5)
|
|
||||||
segment.progress = segment.progress + 2
|
|
||||||
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
|
|
||||||
}
|
|
||||||
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("e", "sponsorblock_set_segment", set_segment)
|
|
||||||
mp.add_key_binding("E", "sponsorblock_submit_segment", submit_segment)
|
|
||||||
mp.add_key_binding("w", "sponsorblock_upvote", function() return vote("1") end)
|
|
||||||
mp.add_key_binding("W", "sponsorblock_downvote", function() return vote("0") end)
|
|
|
@ -1,110 +0,0 @@
|
||||||
import urllib.request
|
|
||||||
import urllib.parse
|
|
||||||
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])):
|
|
||||||
times = []
|
|
||||||
try:
|
|
||||||
response = urllib.request.urlopen(sys.argv[3] + "/api/getVideoSponsorTimes?videoID=" + sys.argv[4])
|
|
||||||
data = json.load(response)
|
|
||||||
for i, time in enumerate(data["sponsorTimes"]):
|
|
||||||
times.append(str(time[0]) + "," + str(time[1]) + "," + data["UUIDs"][i])
|
|
||||||
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()
|
|
||||||
c.execute("SELECT startTime, endTime, votes, UUID FROM sponsorTimes WHERE videoID = ? AND shadowHidden = 0 AND votes > -1 AND category = 'sponsor'", (sys.argv[4],))
|
|
||||||
times = []
|
|
||||||
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"])
|
|
||||||
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)
|
|
||||||
exit(1)
|
|
||||||
except ConnectionResetError:
|
|
||||||
print("database update failed, connection reset", file=sys.stderr)
|
|
||||||
exit(1)
|
|
||||||
except TimeoutError:
|
|
||||||
print("database update failed, timed out", file=sys.stderr)
|
|
||||||
exit(1)
|
|
||||||
except urllib.error.URLError:
|
|
||||||
print("database update failed", file=sys.stderr)
|
|
||||||
exit(1)
|
|
||||||
elif sys.argv[1] == "submit":
|
|
||||||
try:
|
|
||||||
response = urllib.request.urlopen(sys.argv[3] + "/api/postVideoSponsorTimes?videoID=" + sys.argv[4] + "&startTime=" + sys.argv[5] + "&endTime=" + sys.argv[6] + "&userID=" + uid)
|
|
||||||
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,3 +0,0 @@
|
||||||
if mp.get_script_directory == nil then
|
|
||||||
dofile(mp.find_config_file("scripts/sponsorblock/main.lua"))
|
|
||||||
end
|
|
88
mpv/.config/mpv/scripts/sponsorblock_minimal.lua
Normal file
88
mpv/.config/mpv/scripts/sponsorblock_minimal.lua
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
-- sponsorblock_minimal.lua
|
||||||
|
--
|
||||||
|
-- This script skips sponsored segments of YouTube videos
|
||||||
|
-- using data from https://github.com/ajayyy/SponsorBlock
|
||||||
|
--
|
||||||
|
-- original from https://codeberg.org/jouni/mpv_sponsorblock_minimal
|
||||||
|
-- adapted for local playback skipping and some refactoring by me
|
||||||
|
|
||||||
|
local options = {
|
||||||
|
API = "https://sponsor.ajay.app/api/skipSegments",
|
||||||
|
|
||||||
|
-- Categories to fetch and skip
|
||||||
|
categories = '"sponsor","intro","outro","interaction","selfpromo"'
|
||||||
|
}
|
||||||
|
|
||||||
|
local function getranges()
|
||||||
|
local args = {
|
||||||
|
"curl",
|
||||||
|
"-s",
|
||||||
|
"-d",
|
||||||
|
"videoID="..youtube_id,
|
||||||
|
"-d",
|
||||||
|
"categories=["..options.categories.."]",
|
||||||
|
"-G",
|
||||||
|
options.API}
|
||||||
|
local sponsors = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = args})
|
||||||
|
|
||||||
|
if string.match(sponsors.stdout,"%[(.-)%]") then
|
||||||
|
ranges = {}
|
||||||
|
for i in string.gmatch(string.sub(sponsors.stdout,2,-2),"%[(.-)%]") do
|
||||||
|
k,v = string.match(i,"(%d+.?%d*),(%d+.?%d*)")
|
||||||
|
ranges[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local function skip_ads(name,pos)
|
||||||
|
if pos ~= nil then
|
||||||
|
for k,v in pairs(ranges) do
|
||||||
|
if tonumber(k) <= pos and tonumber(v) > pos then
|
||||||
|
--this message may sometimes be wrong
|
||||||
|
--it only seems to be a visual thing though
|
||||||
|
mp.osd_message("[sponsorblock] skipping forward "..math.floor(tonumber(v)-mp.get_property("time-pos")).."s")
|
||||||
|
--need to do the +0.01 otherwise mpv will start spamming skip sometimes
|
||||||
|
--example: https://www.youtube.com/watch?v=4ypMJzeNooo
|
||||||
|
mp.set_property("time-pos",tonumber(v)+0.01)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local function file_loaded()
|
||||||
|
local video_path = mp.get_property("path")
|
||||||
|
local youtube_id1 = string.match(video_path, "https?://youtu%.be/([%w-_]+).*")
|
||||||
|
local youtube_id2 = string.match(video_path, "https?://w?w?w?%.?youtube%.com/v/([%w-_]+).*")
|
||||||
|
local youtube_id3 = string.match(video_path, "/watch.*[?&]v=([%w-_]+).*")
|
||||||
|
local youtube_id4 = string.match(video_path, "/embed/([%w-_]+).*")
|
||||||
|
local localytfile = string.match(video_path, "-([%a%d%-_]+)%.[mw][kpe][v4b][m]?$")
|
||||||
|
youtube_id = youtube_id1 or youtube_id2 or youtube_id3 or youtube_id4 or localytfile
|
||||||
|
if not youtube_id or string.len(youtube_id) < 11 then return end
|
||||||
|
youtube_id = string.sub(youtube_id, 1, 11)
|
||||||
|
|
||||||
|
getranges()
|
||||||
|
if ranges then
|
||||||
|
ON = true
|
||||||
|
mp.add_key_binding("b","sponsorblock",toggle)
|
||||||
|
mp.observe_property("time-pos", "native", skip_ads)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local function toggle()
|
||||||
|
if ON then
|
||||||
|
mp.unobserve_property(skip_ads)
|
||||||
|
mp.osd_message("[sponsorblock] off")
|
||||||
|
ON = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
mp.observe_property("time-pos", "native", skip_ads)
|
||||||
|
mp.osd_message("[sponsorblock] on")
|
||||||
|
ON = true
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
mp.register_event("file-loaded", file_loaded)
|
|
@ -49,12 +49,8 @@ It degrades gracefully, and simply keeps running in higher quality if the file i
|
||||||
|
|
||||||
## sponsorblock
|
## sponsorblock
|
||||||
|
|
||||||
The [mpv-sponsorblock](https://github.com/po5/mpv_sponsorblock) script is included to enable automatically skipping many sponsorship segments integrated within youtube videos.
|
The [minimal mpv-sponsorblock](https://codeberg.org/jouni/mpv_sponsorblock_minimal) script is included to enable automatically skipping many sponsorship segments integrated within youtube videos.
|
||||||
|
|
||||||
This works mostly fully automated, it checks the database and marks affected segments in red on your timeline, which it automatically skips over.
|
This works mostly fully automated, it checks the database and finds affected segments, which it then automatically skips over.
|
||||||
|
|
||||||
If you are watching a video with sponsorship segments that have not yet been marked, you can add your own segment with `e`
|
To toggle sponsorblock on or off just hit `b`, it will confirm the choice via osd.
|
||||||
(once at the beginning of the segment, once at the end) and when that is done send it off to the database with `E`.
|
|
||||||
|
|
||||||
If you watch a video and it contains a well-functioning skip, simply hit `w` to upvote the previous segment.
|
|
||||||
If a segment did not work well, skipped over other content or similar issues, hit `W` to downvote it.
|
|
||||||
|
|
Loading…
Reference in a new issue