-- sponsorblock_minimal.lua v 0.5.1 -- -- This script skip/mute sponsored segments of YouTube and bilibili videos -- using data from https://github.com/ajayyy/SponsorBlock -- and https://github.com/hanydd/BilibiliSponsorBlock local opt = require 'mp.options' local utils = require 'mp.utils' local options = { youtube_sponsor_server = "https://sponsor.ajay.app/api/skipSegments", bilibili_sponsor_server = "https://bsbsb.top/api/skipSegments", -- Categories to fetch -- Perform skip/mute/mark chapter based on the 'actionType' returned categories = '"sponsor"', } opt.read_options(options) local ranges = nil local video_id = nil local sponsor_server = nil local cache = {} local mute = false local ON = false local function getranges(url) local res = mp.command_native{ name = "subprocess", capture_stdout = true, playback_only = false, args = { "curl", "-L", "-s", "-g", "-H", "origin: mpv-script/sponsorblock_minimal", "-H", "x-ext-version: 0.5.1", url } } if res.status ~= 0 then return nil end return utils.parse_json(res.stdout) end local function make_chapter(ranges) local chapters_time = {} local chapters_title = {} local chapter_index = 0 local all_chapters = mp.get_property_native("chapter-list") for _, v in pairs(ranges) do table.insert(chapters_time, v.segment[1]) table.insert(chapters_title, v.category) table.insert(chapters_time, v.segment[2]) table.insert(chapters_title, "normal") end for i = 1, #chapters_time do chapter_index = chapter_index + 1 all_chapters[chapter_index] = { title = chapters_title[i] or ("Chapter " .. string.format("%02.f", chapter_index)), time = chapters_time[i] } end table.sort(all_chapters, function(a, b) return a['time'] < b['time'] end) mp.set_property_native("chapter-list", all_chapters) end local function skip_ads(_, pos) if pos ~= nil and ranges ~= nil then for _, v in pairs(ranges) do if v.actionType == "skip" and v.segment[1] <= pos and v.segment[2] > pos then --this message may sometimes be wrong --it only seems to be a visual thing though local time = math.floor(v.segment[2] - pos) mp.osd_message(string.format("[sponsorblock] skipping forward %ds", time)) --need to do the +0.01 otherwise mpv will start spamming skip sometimes mp.set_property("time-pos", v.segment[2] + 0.01) elseif v.actionType == "mute" then if v.segment[1] <= pos and v.segment[2] >= pos then cache[v.segment[2]] = nil mp.set_property_bool("mute", true) elseif pos > v.segment[2] and not cache[v.segment[2]] and mute ~= false then cache[v.segment[2]] = true mp.set_property_bool("mute", false) end end end end end local function file_loaded() cache = {} local video_path = mp.get_property("path", "") local video_referer = mp.get_property("http-header-fields", ""):match("[Rr]eferer:%s*([^,\r\n]+)") or "" local purl = mp.get_property("metadata/by-key/PURL", "") local bilibili = video_path:match("bilibili.com/video") or video_referer:match("bilibili.com/video") or false mute = mp.get_property_bool("mute") local urls = { "ytdl://youtu%.be/([%w-_]+).*", "ytdl://w?w?w?%.?youtube%.com/v/([%w-_]+).*", "ytdl://w?w?w?%.?bilibili%.com/video/([%w-_]+).*", "https?://youtu%.be/([%w-_]+).*", "https?://w?w?w?%.?youtube%.com/v/([%w-_]+).*", "https?://w?w?w?%.?bilibili%.com/video/([%w-_]+).*", "/watch.*[?&]v=([%w-_]+).*", "/embed/([%w-_]+).*", "^ytdl://([%w-_]+)$", "-([%w-_]+)%." } for _, url in ipairs(urls) do video_id = video_id or video_path:match(url) or video_referer:match(url) or purl:match(url) end if not video_id or string.len(video_id) < 11 then return end if bilibili then sponsor_server = options.bilibili_sponsor_server video_id = string.sub(video_id, 1, 12) else sponsor_server = options.youtube_sponsor_server video_id = string.sub(video_id, 1, 11) end local url = ("%s?videoID=%s&categories=[%s]"):format(sponsor_server, video_id, options.categories) ranges = getranges(url) if ranges ~= nil then make_chapter(ranges) ON = true mp.observe_property("time-pos", "native", skip_ads) end end local function end_file() if not ON then return end mp.unobserve_property(skip_ads) video_id = nil cache = nil ranges = nil ON = false end mp.register_event("file-loaded", file_loaded) mp.register_event("end-file", end_file)