uhhh
This commit is contained in:
parent
4c05318733
commit
4faefaac6b
1
base/.config/mpv/script-opts/sponsorblock.conf
Normal file
1
base/.config/mpv/script-opts/sponsorblock.conf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
skip_categories=sponsor,intro
|
569
base/.config/mpv/scripts/sponsorblock.lua
Normal file
569
base/.config/mpv/scripts/sponsorblock.lua
Normal file
|
@ -0,0 +1,569 @@
|
||||||
|
-- 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",
|
||||||
|
|
||||||
|
-- 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 = {
|
||||||
|
"ytdl://([%w-_]+).*",
|
||||||
|
"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)
|
3
base/.config/mpv/scripts/sponsorblock_shared/main.lua
Normal file
3
base/.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
|
122
base/.config/mpv/scripts/sponsorblock_shared/sponsorblock.py
Normal file
122
base/.config/mpv/scripts/sponsorblock_shared/sponsorblock.py
Normal file
|
@ -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 @@
|
||||||
|
HwYQ8h92cinV3Wi2YAnfTRbjQwVoi9mPfWds
|
|
@ -3,3 +3,5 @@
|
||||||
email = snow@datagirl.xyz
|
email = snow@datagirl.xyz
|
||||||
[init]
|
[init]
|
||||||
defaultBranch = trunk
|
defaultBranch = trunk
|
||||||
|
[core]
|
||||||
|
autocrlf = input
|
||||||
|
|
76
base/.mkshrc
76
base/.mkshrc
|
@ -2,14 +2,18 @@
|
||||||
|
|
||||||
if [ -f /etc/shrc ]; then
|
if [ -f /etc/shrc ]; then
|
||||||
. /etc/shrc
|
. /etc/shrc
|
||||||
|
elif [ -f /opt/shrc ]; then
|
||||||
|
. /opt/shrc
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export EDITOR=nvim
|
export EDITOR=nvim
|
||||||
|
export KANIDM_URL="https://idm.qalico.net"
|
||||||
|
export KANIDM_USER="$USER"
|
||||||
|
|
||||||
|
# opt out of .NET telemetry
|
||||||
|
export DOTNET_CLI_TELEMETRY_OPTOUT=true
|
||||||
|
|
||||||
## Extra paths
|
## Extra paths
|
||||||
if [ -f "$HOME/.cargo/env" ] ; then
|
|
||||||
. $HOME/.cargo/env
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Python packages go here
|
# Python packages go here
|
||||||
if [ -d "$HOME/.local/bin" ] ; then
|
if [ -d "$HOME/.local/bin" ] ; then
|
||||||
|
@ -20,6 +24,7 @@ fi
|
||||||
if [ -d "/opt/homebrew" ] ; then
|
if [ -d "/opt/homebrew" ] ; then
|
||||||
PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/sbin:/usr/sbin:$PATH"
|
PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/sbin:/usr/sbin:$PATH"
|
||||||
HOMEBREW_CASK_OPTS="--appdir=~/Applications"
|
HOMEBREW_CASK_OPTS="--appdir=~/Applications"
|
||||||
|
PATH="/opt/homebrew/opt/ruby/bin:$PATH"
|
||||||
export HOMEBREW_CASK_OPTS
|
export HOMEBREW_CASK_OPTS
|
||||||
# x86 brew hack
|
# x86 brew hack
|
||||||
if [ -d "$HOME/.x86_64/homebrew" ] ; then
|
if [ -d "$HOME/.x86_64/homebrew" ] ; then
|
||||||
|
@ -29,11 +34,20 @@ if [ -d "/opt/homebrew" ] ; then
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -f "$HOME/.cargo/env" ] ; then
|
||||||
|
PATH="$HOME/.cargo/bin:$PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
# Flutter
|
# Flutter
|
||||||
if [ -d "$HOME/.opt/flutter" ] ; then
|
if [ -d "$HOME/.opt/flutter" ] ; then
|
||||||
PATH="$HOME/.opt/flutter/bin:$PATH"
|
PATH="$HOME/.opt/flutter/bin:$PATH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Perl local lib
|
||||||
|
if [ -d "$HOME/perl5/lib/perl5" ] ; then
|
||||||
|
eval $(perl -I ~/perl5/lib/perl5 -Mlocal::lib)
|
||||||
|
fi
|
||||||
|
|
||||||
# Android SDK (for VMs)
|
# Android SDK (for VMs)
|
||||||
if [ -d "$HOME/Library/Android/sdk" ] ; then
|
if [ -d "$HOME/Library/Android/sdk" ] ; then
|
||||||
export ANDROID_SDK_ROOT=$HOME/Library/Android/sdk
|
export ANDROID_SDK_ROOT=$HOME/Library/Android/sdk
|
||||||
|
@ -48,8 +62,8 @@ if [ "$TERM" = "screen" ] ; then
|
||||||
export TERM
|
export TERM
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export LANG=
|
export LANG=en_US.UTF-8
|
||||||
export LC_ALL=en_US.UTF-8
|
export LC_ALL=
|
||||||
|
|
||||||
## Ripped from a custom /etc/shrc, not very useful here
|
## Ripped from a custom /etc/shrc, not very useful here
|
||||||
if [ -z "$UID" ] ; then
|
if [ -z "$UID" ] ; then
|
||||||
|
@ -77,7 +91,7 @@ getrv() {
|
||||||
# so we can use it here.
|
# so we can use it here.
|
||||||
rv=${1:-$?}
|
rv=${1:-$?}
|
||||||
if [ "$rv" -ne "0" -a "$rv" -ne "130" ] ; then
|
if [ "$rv" -ne "0" -a "$rv" -ne "130" ] ; then
|
||||||
printf "$rv | "
|
printf "${BRL}33m${BRR}%d${BRL}0m${BRR} » " "$rv"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,9 +106,16 @@ get_ps1() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$SSH_TTY" ] ; then
|
if [ -n "$SSH_TTY" ] ; then
|
||||||
printf "${BRL}33m${BRR}[SSH] "
|
printf "${BRL}32m${BRR}SSH » "
|
||||||
fi
|
fi
|
||||||
printf "${USER_COLOR}${HOST%%.*}:${BRL}39m${BRR}";
|
|
||||||
|
if _branch="$(git branch --show-current 2>/dev/null)" ; then
|
||||||
|
[ -z "${_branch}" ] && _branch="$(git rev-parse --short HEAD 2>/dev/null)"
|
||||||
|
printf "${BRL}97m${BRR}%s » " "${_branch}"
|
||||||
|
unset _branch
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "${USER_COLOR}${HOST%%.*} ${BRL}39m${BRR}";
|
||||||
if [ -n "$BASH_VERSION" ] ; then
|
if [ -n "$BASH_VERSION" ] ; then
|
||||||
printf "\w"
|
printf "\w"
|
||||||
elif [[ "${PWD#$HOME}" != "$PWD" ]] ; then
|
elif [[ "${PWD#$HOME}" != "$PWD" ]] ; then
|
||||||
|
@ -102,7 +123,7 @@ get_ps1() {
|
||||||
else
|
else
|
||||||
printf "$PWD"
|
printf "$PWD"
|
||||||
fi
|
fi
|
||||||
printf "${USER_COLOR} ${PROMPT}${BRL}0m${BRR} "
|
printf "${USER_COLOR} \n${BRL}0m${BRR}${PROMPT} "
|
||||||
}
|
}
|
||||||
|
|
||||||
case "$-" in *i*)
|
case "$-" in *i*)
|
||||||
|
@ -131,6 +152,9 @@ case "$-" in *i*)
|
||||||
# Hack to bubble up profile, see bin/tmux-shim
|
# Hack to bubble up profile, see bin/tmux-shim
|
||||||
alias tmux="$HOME/bin/tmux-shim"
|
alias tmux="$HOME/bin/tmux-shim"
|
||||||
|
|
||||||
|
# Alias dotfiles
|
||||||
|
alias dotfiles='git --git-dir=$HOME/.dotfiles.git/ --work-tree=$HOME'
|
||||||
|
|
||||||
# Set bash PS1
|
# Set bash PS1
|
||||||
if [ -n "$BASH_VERSION" ] ; then
|
if [ -n "$BASH_VERSION" ] ; then
|
||||||
PS1="$(get_ps1)"
|
PS1="$(get_ps1)"
|
||||||
|
@ -148,6 +172,39 @@ case "$-" in *i*)
|
||||||
echo "$@" | bc
|
echo "$@" | bc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
## Bitwarden auto-unlock
|
||||||
|
if command -v bw >/dev/null ; then
|
||||||
|
if ! command -v jq >/dev/null ; then
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
** NOTE: jq isn't installed, default bw will be used
|
||||||
|
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
bw() {
|
||||||
|
status="$(command bw status | jq -r .status)"
|
||||||
|
if [ "$status" != "unlocked" ] ; then
|
||||||
|
export BW_SESSION="$(command bw unlock --raw)"
|
||||||
|
[ -z "$BW_SESSION" ] && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
command bw "$@"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
influx() {
|
||||||
|
real_influx="$(whereis -q influx)"
|
||||||
|
if [ -n "$real_influx" ] ; then
|
||||||
|
if [ -z "$INFLUX_TOKEN" ] && command -v bw >/dev/null ; then
|
||||||
|
export INFLUX_TOKEN="$(bw get password influx_token)"
|
||||||
|
fi
|
||||||
|
"$real_influx" "$@"
|
||||||
|
else
|
||||||
|
echo "influx not installed; not continuing"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
nconv()
|
nconv()
|
||||||
{
|
{
|
||||||
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
|
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
|
||||||
|
@ -159,3 +216,4 @@ case "$-" in *i*)
|
||||||
}
|
}
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|
137
base/.ssh/config
137
base/.ssh/config
|
@ -1,53 +1,84 @@
|
||||||
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
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
if [ "$1" = "-n" ] ; then
|
||||||
|
template='[:alnum:]'
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
template='[:alnum:]+=\-_'
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
if [ -z "$1" ] ; then
|
if [ -z "$1" ] ; then
|
||||||
echo "usage: $0 [length]" >&2
|
echo "usage: $0 [-n] length" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
LC_ALL=C tr -dc '[:alnum:]' </dev/urandom | \
|
LC_ALL=C tr -dc "$template" </dev/urandom | \
|
||||||
fold -w$1 | head -n1
|
fold -w$1 | head -n1
|
||||||
|
|
Loading…
Reference in a new issue