From 607891801fbf2051a0c9062d15a445dd238c1e1b Mon Sep 17 00:00:00 2001 From: ryukamish Date: Sat, 11 Apr 2026 16:06:45 +0530 Subject: add: mako and mpv configs --- mpv/scripts/SmartSkip.lua | 1383 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1383 insertions(+) create mode 100644 mpv/scripts/SmartSkip.lua (limited to 'mpv/scripts/SmartSkip.lua') diff --git a/mpv/scripts/SmartSkip.lua b/mpv/scripts/SmartSkip.lua new file mode 100644 index 0000000..a80241a --- /dev/null +++ b/mpv/scripts/SmartSkip.lua @@ -0,0 +1,1383 @@ +-- Copyright (c) 2025, Eisa AlAwadhi +-- License: BSD 2-Clause License +-- Creator: Eisa AlAwadhi +-- Project: SmartSkip +-- Version: 1.3.4 +-- Date: 14-May-2025 + +-- Related forked projects: +-- https://github.com/detuur/mpv-scripts/blob/master/skiptosilence.lua +-- https://github.com/mar04/chapters_for_mpv +-- https://github.com/po5/chapterskip/blob/master/chapterskip.lua + +local o = { + -----Silence Skip Settings----- + silence_audio_level = -40, + silence_duration = 0.65, + ignore_silence_duration=5, + min_skip_duration = 0, + max_skip_duration = 180, + keybind_twice_cancel_skip = true, + silence_skip_to_end = "playlist-next", + add_chapter_on_skip = true, + force_mute_on_skip = false, + -----Smart Skip Settings----- + last_chapter_skip_behavior=[[ [ ["no-chapters", "silence-skip"], ["internal-chapters", "playlist-next"], ["external-chapters", "silence-skip"] ] ]], + smart_next_proceed_countdown = true, + smart_prev_cancel_countdown = true, + -----Chapters Settings----- + external_chapters_autoload = true, + modified_chapters_autosave=[[ ["no-chapters", "external-chapters"] ]], + global_chapters = true, + global_chapters_path = "/:dir%mpvconf%/chapters", + hash_global_chapters = true, + add_chapter_ask_title = false, + add_chapter_pause_for_input = false, + add_chapter_placeholder_title = "Chapter ", + -----Auto-Skip Settings----- + autoskip_chapter = true, + autoskip_countdown = 3, + autoskip_countdown_bulk = false, + autoskip_countdown_graceful = false, + skip_once = false, + categories=[[ [ ["internal-chapters", "prologue>Prologue/^Intro; opening>^OP/ OP$/^Opening; ending>^ED/ ED$/^Ending/Credits Start; preview>PV/ PV$/^Preview/Preview Start"], ["external-chapters", "idx->0/2"] ] ]], + skip=[[ [ ["internal-chapters", "toggle;toggle_idx;opening;ending;preview"], ["external-chapters", "toggle;toggle_idx"] ] ]], + invert_autoskip_exclusion = false, + autoskip_exclusion=[[ + [""] + ]], + -----OSD Messages Settings----- + osd_duration = 2500, + silence_skip_osd = "osd-msg-bar", + chapter_osd = "osd-msg-bar", + autoskip_osd = "osd-msg-bar", + playlist_osd = true, + osd_msg = true, + -----Keybind Settings----- + toggle_autoskip_keybind=[[ ["ctrl+."] ]], + toggle_category_autoskip_keybind=[[ ["alt+."] ]], + cancel_autoskip_countdown_keybind=[[ ["esc", "n"] ]], + proceed_autoskip_countdown_keybind=[[ ["enter", "y"] ]], + add_chapter_keybind=[[ ["n"] ]], + remove_chapter_keybind=[[ ["alt+n"] ]], + edit_chapter_keybind=[[ [""] ]], + write_chapters_keybind=[[ ["ctrl+n"] ]], + bake_chapters_keybind=[[ [""] ]], + chapter_next_keybind=[[ ["ctrl+right"] ]], + chapter_prev_keybind=[[ ["ctrl+left"] ]], + smart_next_keybind=[[ [">"] ]], + smart_prev_keybind=[[ ["<"] ]], + silence_skip_keybind=[[ ["?"] ]], + cancel_silence_skip_keybind=[[ ["esc"] ]], +} + +local mp = require 'mp' +local msg = require 'mp.msg' +local utils = require 'mp.utils' +local options = require 'mp.options' +options.read_options(o) + +if o.add_chapter_on_skip ~= false and o.add_chapter_on_skip ~= true then o.add_chapter_on_skip = utils.parse_json(o.add_chapter_on_skip) end +if o.modified_chapters_autosave ~= false and o.modified_chapters_autosave ~= true then o.modified_chapters_autosave = utils.parse_json(o.modified_chapters_autosave) end +o.last_chapter_skip_behavior = utils.parse_json(o.last_chapter_skip_behavior) +if utils.parse_json(o.skip) ~= nil then o.skip = utils.parse_json(o.skip) end +if utils.parse_json(o.categories) ~= nil then o.categories = utils.parse_json(o.categories) end +if o.skip_once ~= false and o.skip_once ~= true then o.skip_once = utils.parse_json(o.skip_once) end + +if o.global_chapters_path:match('/:dir%%mpvconf%%') then + o.global_chapters_path = o.global_chapters_path:gsub('/:dir%%mpvconf%%', mp.find_config_file('.')) +elseif o.global_chapters_path:match('/:dir%%script%%') then + o.global_chapters_path = o.global_chapters_path:gsub('/:dir%%script%%', debug.getinfo(1).source:match('@?(.*/)')) +elseif o.global_chapters_path:match('/:var%%(.*)%%') then + local os_variable = o.global_chapters_path:match('/:var%%(.*)%%') + o.global_chapters_path = o.global_chapters_path:gsub('/:var%%(.*)%%', os.getenv(os_variable)) +end + +o.toggle_autoskip_keybind = utils.parse_json(o.toggle_autoskip_keybind) +o.cancel_autoskip_countdown_keybind = utils.parse_json(o.cancel_autoskip_countdown_keybind) +o.proceed_autoskip_countdown_keybind = utils.parse_json(o.proceed_autoskip_countdown_keybind) +o.toggle_category_autoskip_keybind = utils.parse_json(o.toggle_category_autoskip_keybind) +o.add_chapter_keybind = utils.parse_json(o.add_chapter_keybind) +o.remove_chapter_keybind = utils.parse_json(o.remove_chapter_keybind) +o.write_chapters_keybind = utils.parse_json(o.write_chapters_keybind) +o.edit_chapter_keybind = utils.parse_json(o.edit_chapter_keybind) +o.bake_chapters_keybind = utils.parse_json(o.bake_chapters_keybind) +o.chapter_prev_keybind = utils.parse_json(o.chapter_prev_keybind) +o.chapter_next_keybind = utils.parse_json(o.chapter_next_keybind) +o.smart_prev_keybind = utils.parse_json(o.smart_prev_keybind) +o.smart_next_keybind = utils.parse_json(o.smart_next_keybind) +o.silence_skip_keybind = utils.parse_json(o.silence_skip_keybind) +o.cancel_silence_skip_keybind = utils.parse_json(o.cancel_silence_skip_keybind) +o.autoskip_exclusion = utils.parse_json(o.autoskip_exclusion) --1.3.4# option autoskip_exclusion parsing + +package.path = mp.command_native({"expand-path", "~~/script-modules/?.lua;"}) .. package.path +local user_input_module, input = pcall(require, "user-input-module") + +mp.set_property('user-data/smartskip/silence-skip', 'no') + +if o.osd_duration == -1 then o.osd_duration = (mp.get_property_number('osd-duration') or 1000) end +local speed_state = 1 +local pause_state = false +local mute_state = false +local sub_state = nil +local secondary_sub_state = nil +local vid_state = nil +local skip_flag = false +local window_state = nil +local force_silence_skip = false +local initial_skip_time = 0 +local initial_chapter_count = 0 +local chapter_state = 'no-chapters' +local file_length = 0 +local keep_open_state = "yes" +if mp.get_property("config") ~= "no" then keep_open_state = mp.get_property("keep-open") end +local osd_duration_default = (mp.get_property_number('osd-duration') or 1000) +local geometry_default = mp.get_property_native("geometry") +local autoskip_chapter = o.autoskip_chapter +local playlist_osd = false +local autoskip_playlist_osd = false +local g_playlist_pos = 0 +local g_opt_categories = o.categories +local g_opt_skip_once = false +o.autoskip_countdown = math.floor(o.autoskip_countdown) +local g_autoskip_countdown = o.autoskip_countdown +local g_autoskip_countdown_flag = false +local g_protocols = {'https?:', 'magnet:', 'rtmps?:', 'smb:', 'ftps?:', 'sftp:'} --1.3.4# default global protocols needed to check for protocols / website exclusion +local g_filepath = '' --1.3.4# define initial global variable for filepath +local g_autoskip_disabled = false --1.3.4# set global variable for autoskip disabled to be used for autoskip exclusion_check function +local categories = { + toggle = "", + toggle_idx = "", +} + +-- utility functions -- +function has_value(tab, val, array2d) + if not tab then return msg.error('check value passed') end + if not val then return msg.error('check value passed') end + if not array2d then + for index, value in ipairs(tab) do + if string.lower(value) == string.lower(val) then + return true + end + end + end + if array2d then + for i=1, #tab do + if tab[i] and string.lower(tab[i][array2d]) == string.lower(val) then + return true + end + end + end + + return false +end + +function esc_string(str) + return str:gsub("([%p])", "%%%1") +end + +function esc_lua_pattern(str) --1.3.4# helper function to escape pattern needed for exclusion_check function + return str:gsub("([%^%$%(%)%%%.%[%]%+%-%?])", "%%%1") +end + +function starts_protocol(tab, val) --1.3.4# helper function to check if file is protocol needed for exclusion_check function + for index, value in ipairs(tab) do + if (val:find(value) == 1) then + return true + end + end + return false +end + +function prompt_msg(text, duration, osd) + if not text then return end + msg.info(text) + + if not o.osd_msg then return end + if osd == "no-osd" or osd == "osd-bar" then return end + + if not duration then duration = o.osd_duration end + mp.commandv("show-text", text, duration) +end + +function prompt_progress(osd, duration) + if not o.osd_msg or not osd then return end + if osd == "no-osd" then return end + + if not duration then duration = o.osd_duration end + mp.set_property("osd-duration", o.osd_duration) + mp.commandv(osd, "show-progress") + mp.add_timeout(0.07, function () mp.set_property('osd-duration', osd_duration_default) end) +end + +function bind_keys(keys, name, func, opts) + if not keys then + mp.add_forced_key_binding(keys, name, func, opts) + return + end + + for i = 1, #keys do + if i == 1 then + mp.add_forced_key_binding(keys[i], name, func, opts) + else + mp.add_forced_key_binding(keys[i], name .. i, func, opts) + end + end +end + +function unbind_keys(keys, name) + if not keys then + mp.remove_key_binding(name) + return + end + + for i = 1, #keys do + if i == 1 then + mp.remove_key_binding(name) + else + mp.remove_key_binding(name .. i) + end + end +end + +function exclusion_check() + if g_filepath == "" then return false end + if o.autoskip_exclusion == nil then msg.warn('ignoring autoskip_exclusion option due to an inappropriate value') return false end + if not o.autoskip_exclusion[1] or #o.autoskip_exclusion == 1 and o.autoskip_exclusion[1] == "" then return false end + + local invertable_return = {true, false} + local exclusion_msg = 'Auto-Skip disabled because of exclusion' + if o.invert_autoskip_exclusion then + invertable_return = {false, true} + exclusion_msg = 'Auto-Skip enabled because of inclusion' + end + + if has_value(o.autoskip_exclusion, g_filepath, nil) then + msg.info(exclusion_msg) + return invertable_return[1] + elseif not starts_protocol(g_protocols, g_filepath) then + if has_value(o.autoskip_exclusion, g_filepath:match('^(.-)([^\\/]-)%.([^\\/%.]-)%.?$'), nil) or + has_value(o.autoskip_exclusion, g_filepath:match('^(.-)([^\\/]-)%.([^\\/%.]-)%.?$'):gsub('\\$', ''), nil) then + msg.info(exclusion_msg) + return invertable_return[1] + elseif has_value(o.autoskip_exclusion, g_filepath:match('%.([^%.]+)$'), nil) or + has_value(o.autoskip_exclusion, "."..g_filepath:match('%.([^%.]+)$'), nil) then + msg.info(exclusion_msg) + return invertable_return[1] + else + for i=1, #o.autoskip_exclusion do + local esc_autoskip_exclusion = esc_lua_pattern(o.autoskip_exclusion[i]) + if string.lower(g_filepath):match(string.lower(esc_autoskip_exclusion)) and o.autoskip_exclusion[i]:sub(-2) == '\\*' and string.lower(o.autoskip_exclusion[i]:sub(1, -2)) ~= string.lower(g_filepath):match("(.*[\\/])") then + msg.info(exclusion_msg) + return invertable_return[1] + end + end + end + elseif starts_protocol(g_protocols, g_filepath) then + if has_value(o.autoskip_exclusion, g_filepath:match('(.-)(:)'), nil) or + has_value(o.autoskip_exclusion, g_filepath:match('(.-:)'), nil) or + has_value(o.autoskip_exclusion, g_filepath:match('(.-:/?/?)'), nil) then + msg.info(exclusion_msg) + return invertable_return[1] + elseif g_filepath:find('https?://') == 1 then + local difchk_1, difchk_2 = g_filepath:match("(https?://)w?w?w?%.?([%w%.%:]*)") + local different_check_temp = difchk_1..difchk_2 + local different_checks = {different_check_temp, g_filepath:match("https?://w?w?w?%.?([%w%.%:]*)"), g_filepath:match("https?://([%w%.%:]*)"), g_filepath:match("(https?://[%w%.%:]*)") } + for i = 1, #different_checks do + if different_checks[i] and has_value(o.autoskip_exclusion, different_checks[i], nil) + or different_checks[i]..'/' and has_value(o.autoskip_exclusion, different_checks[i]..'/', nil) then + msg.info(exclusion_msg) + return invertable_return[1] + end + end + end + end + + return invertable_return[2] +end + +-- skip-silence utility functions -- +function restoreProp(timepos,pause) + if not timepos then timepos = mp.get_property_number("time-pos") end + if not pause then pause = pause_state end + + mp.set_property("vid", vid_state) + mp.set_property("force-window", window_state) + mp.set_property_bool("mute", mute_state) + mp.set_property("speed", speed_state) + mp.unobserve_property(foundSilence) + mp.command("no-osd af remove @skiptosilence") + mp.set_property_bool("pause", pause) + mp.set_property_number("time-pos", timepos) + mp.set_property("sub-visibility", sub_state) + mp.set_property("secondary-sub-visibility", secondary_sub_state) + unbind_keys(o.cancel_silence_skip_keybind, 'cancel-silence-skip') + mp.set_property('user-data/smartskip/silence-skip', 'no') + timer:kill() + skip_flag = false +end + +function handleMinMaxDuration(timepos) + if not skip_flag then return end + if not timepos then timepos = mp.get_property_number("time-pos") end + + skip_duration = timepos - initial_skip_time + if o.min_skip_duration > 0 and skip_duration <= o.min_skip_duration then + restoreProp(initial_skip_time) + prompt_msg('Skipping Cancelled\nSilence less than minimum', o.osd_duration, o.silence_skip_osd) + return true + end + if o.max_skip_duration > 0 and skip_duration >= o.max_skip_duration then + restoreProp(initial_skip_time) + prompt_msg('Skipping Cancelled\nSilence is more than configured maximum', o.osd_duration, o.silence_skip_osd) + return true + end + return false +end + +function setKeepOpenState() + if o.silence_skip_to_end == "playlist-next" then + mp.set_property("keep-open", "yes") + else + mp.set_property("keep-open", "always") + end +end + +function eofHandler(name, val) + if val and skip_flag then + if o.silence_skip_to_end == 'playlist-next' then + restoreProp((mp.get_property_native('duration') or 0)) + if mp.get_property_native('playlist-playing-pos')+1 == mp.get_property_native('playlist-count') then + prompt_msg('Skipped to end at ' .. mp.get_property_osd('duration'), o.osd_duration, o.silence_skip_osd) + else + mp.commandv("playlist-next") + end + elseif o.silence_skip_to_end == 'cancel' then + prompt_msg('Skipping Cancelled\nSilence not detected', o.osd_duration, o.silence_skip_osd) + restoreProp(initial_skip_time) + elseif o.silence_skip_to_end == 'pause' then + prompt_msg('Skipped to end at ' .. mp.get_property_osd('duration'), o.osd_duration, o.silence_skip_osd) + restoreProp((mp.get_property_native('duration') or 0), true) + end + end +end + +-- smart-skip main code -- +function smartNext() + if g_autoskip_countdown_flag and o.smart_next_proceed_countdown then proceed_autoskip(true) return end + local next_action = "silence-skip" + local chapters_count = (mp.get_property_number('chapters') or 0) + local chapter = (mp.get_property_number('chapter') or 0) + local current_playlist = (mp.get_property_native('playlist-playing-pos')+1 or 0) + local total_playlist = (mp.get_property_native('playlist-count') or 0) + + if chapter+2 <= chapters_count then + next_action = 'chapter-next' + elseif chapter+2 > chapters_count and (initial_chapter_count == 0 or chapters_count == 0 or force_silence_skip) then + if chapters_count == 0 then force_silence_skip = true end + next_action = 'silence-skip' + elseif chapter+1 >= chapters_count then + for i = 1, #o.last_chapter_skip_behavior do + if o.last_chapter_skip_behavior[i] and o.last_chapter_skip_behavior[i][1] == chapter_state then + next_action = o.last_chapter_skip_behavior[i][2] + break + end + end + end + + if next_action == 'playlist-next' and current_playlist == total_playlist then + next_action = 'chapter-next' + end + + if next_action == 'silence-skip' then + silenceSkip() + end + if next_action == 'chapter-next' then + mp.set_property('osd-duration', o.osd_duration) + mp.commandv(o.chapter_osd, 'add', 'chapter', 1) + mp.add_timeout(0.07, function () mp.set_property('osd-duration', osd_duration_default) end) + end + if next_action == 'playlist-next' then + mp.command('playlist_next') + end +end + +function smartPrev() + if skip_flag then restoreProp(initial_skip_time) return end + if g_autoskip_countdown_flag and o.smart_prev_cancel_countdown then kill_chapterskip_countdown('osd') return end + local chapters_count = (mp.get_property_number('chapters') or 0) + local chapter = (mp.get_property_number('chapter') or 0) + local timepos = (mp.get_property_native("time-pos") or 0) + + if chapter-1 < 0 and timepos > 1 and chapters_count == 0 then + mp.commandv('seek', 0, 'absolute', 'exact') + prompt_progress(o.chapter_osd, o.osd_duration) + elseif chapter-1 < 0 and timepos < 1 then + mp.command('playlist_prev') + elseif chapter-1 <= chapters_count then + mp.set_property('osd-duration', o.osd_duration) + mp.commandv(o.chapter_osd, 'add', 'chapter', -1) + mp.add_timeout(0.07, function () mp.set_property('osd-duration', osd_duration_default) end) + end +end + +-- chapter-next/prev main code -- +function chapterSeek(direction) + if skip_flag and direction == -1 then restoreProp(initial_skip_time) return end + + local chapters_count = (mp.get_property_number('chapters') or 0) + local chapter = (mp.get_property_number('chapter') or 0) + local timepos = (mp.get_property_native("time-pos") or 0) + + if chapter+direction < 0 and timepos > 1 and chapters_count == 0 then + mp.commandv('seek', 0, 'absolute', 'exact') + prompt_progress(o.chapter_osd, o.osd_duration) + elseif chapter+direction < 0 and timepos < 1 then + mp.command('playlist_prev') + elseif chapter+direction >= chapters_count then + mp.command('playlist_next') + else + mp.set_property('osd-duration', o.osd_duration) + mp.commandv(o.chapter_osd, 'add', 'chapter', direction) + mp.add_timeout(0.07, function () mp.set_property('osd-duration', osd_duration_default) end) + end +end + +-- silence skip main code -- +function silenceSkip(action) + bind_keys(o.cancel_silence_skip_keybind, "cancel-silence-skip", function() restoreProp(initial_skip_time) return end) + if skip_flag then if o.keybind_twice_cancel_skip then restoreProp(initial_skip_time) end return end + initial_skip_time = (mp.get_property_native("time-pos") or 0) + if math.floor(initial_skip_time) == math.floor(mp.get_property_native('duration') or 0) then return end + mp.set_property('user-data/smartskip/silence-skip', 'yes') + kill_chapterskip_countdown() + + local width = mp.get_property_native("osd-width") + local height = mp.get_property_native("osd-height") + local window_maximized = mp.get_property_native("window-maximized") + + window_state = mp.get_property("force-window") + vid_state = mp.get_property("vid") + sub_state = mp.get_property("sub-visibility") + secondary_sub_state = mp.get_property("secondary-sub-visibility") + pause_state = mp.get_property_native("pause") + speed_state = mp.get_property_native("speed") + + if not window_maximized then mp.set_property_native("geometry", ("%dx%d"):format(width, height)) end + + if o.silence_skip_osd ~= 'no-osd' then mp.commandv(o.silence_skip_osd, "show-progress") end + + mp.command( + "no-osd af add @skiptosilence:lavfi=[silencedetect=noise=" .. + o.silence_audio_level .. "dB:d=" .. o.silence_duration .. "]" + ) + + mp.observe_property("af-metadata/skiptosilence", "string", foundSilence) + mp.set_property("sub-visibility", "no") + mp.set_property("secondary-sub-visibility", "no") + mp.set_property("force-window", "yes") + mp.set_property("vid", "no") + mute_state = mp.get_property_native("mute") + if o.force_mute_on_skip then + mp.set_property_bool("mute", true) + end + mp.set_property_bool("pause", false) + mp.set_property("speed", 100) + setKeepOpenState() + skip_flag = true + + timer = mp.add_periodic_timer(0.5, function() + local video_time = (mp.get_property_native("time-pos") or 0) + handleMinMaxDuration(video_time) + if skip_flag and o.silence_skip_osd ~= 'no-osd' then mp.commandv(o.silence_skip_osd, "show-progress") end + end) +end + +function foundSilence(name, value) + if value == "{}" or value == nil then + return + end + + timecode = tonumber(string.match(value, "%d+%.?%d+")) + if timecode == nil or timecode < initial_skip_time + o.ignore_silence_duration then + return + end + + if handleMinMaxDuration(timecode) then return end + + restoreProp(timecode) + + mp.add_timeout(0.05, function() prompt_msg('Skipped to silence 🕒 ' .. mp.get_property_osd("time-pos"), o.osd_duration, o.silence_skip_osd) end) + if o.add_chapter_on_skip == true or has_value(o.add_chapter_on_skip, chapter_state) then + mp.add_timeout(0.05, add_chapter) + end + skip_flag = false +end + +-- modified fork of chapters_for_mpv -- +--[[ +Copyright (c) 2023 Mariusz Libera + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--]] + + +-- to debug run mpv with arg: --msg-level=SmartSkip=debug +-- to test o.run mpv with arg: --script-opts=SmartSkip-OPTION=VALUE + + + +local chapters_modified = false + +msg.debug("options:", utils.to_string(options)) + + +-- CHAPTER MANIPULATION -------------------------------------------------------- + + +function change_title_callback(user_input, err, chapter_index) + if user_input == nil or err ~= nil then + msg.warn("no chapter title provided:", err) + return + end + + local chapter_list = mp.get_property_native("chapter-list") + + if chapter_index > mp.get_property_number("chapter-list/count") then + msg.warn("can't set chapter title") + return + end + + chapter_list[chapter_index].title = user_input + + mp.set_property_native("chapter-list", chapter_list) + chapters_modified = true +end + + +function edit_chapter() + local mpv_chapter_index = mp.get_property_number("chapter") + local chapter_list = mp.get_property_native("chapter-list") + + if mpv_chapter_index == nil or mpv_chapter_index == -1 then + msg.verbose("no chapter selected, nothing to edit") + return + end + + if not user_input_module then + msg.error("no mpv-user-input, can't get user input, install: https://github.com/CogentRedTester/mpv-user-input") + return + end + input.get_user_input(change_title_callback, { + request_text = "title of the chapter:", + default_input = chapter_list[mpv_chapter_index + 1].title, + cursor_pos = #(chapter_list[mpv_chapter_index + 1].title) + 1, + }, mpv_chapter_index + 1) + + if o.add_chapter_pause_for_input then + mp.set_property_bool("pause", true) + mp.osd_message(" ", 0.1) + end +end + + +function add_chapter(timepos) + if not timepos then timepos = mp.get_property_number("time-pos") end + local chapter_list = mp.get_property_native("chapter-list") + + if #chapter_list > 0 then + for i = 1, #chapter_list do + if math.floor(chapter_list[i].time) == math.floor(timepos) then + msg.debug("failed to add chapter, chapter exists in same position") + return + end + end + end + + local chapter_index = (mp.get_property_number("chapter") or -1) + 2 + + table.insert(chapter_list, chapter_index, {title = "", time = timepos}) + + msg.debug("inserting new chapter at ", chapter_index, " chapter_", " time: ", timepos) + + mp.set_property_native("chapter-list", chapter_list) + chapters_modified = true + + if o.add_chapter_ask_title then + if not user_input_module then + msg.error("no mpv-user-input, can't get user input, install: https://github.com/CogentRedTester/mpv-user-input") + return + end + -- ask user for chapter title + input.get_user_input(change_title_callback, { + request_text = "title of the chapter:", + default_input = o.placeholder_title .. chapter_index, + cursor_pos = #(o.placeholder_title .. chapter_index) + 1, + }, chapter_index) + + if o.add_chapter_pause_for_input then + mp.set_property_bool("pause", true) + -- FIXME: for whatever reason osd gets hidden when we pause the + -- playback like that, workaround to make input prompt appear + -- right away without requiring mouse or keyboard action + mp.osd_message(" ", 0.1) + end + end +end + + +function remove_chapter() + local chapter_count = mp.get_property_number("chapter-list/count") + + if chapter_count < 1 then + msg.verbose("no chapters to remove") + return + end + + local chapter_list = mp.get_property_native("chapter-list") + local current_chapter = mp.get_property_number("chapter") + 1 + + table.remove(chapter_list, current_chapter) + msg.debug("removing chapter", current_chapter) + + mp.set_property_native("chapter-list", chapter_list) + chapters_modified = true +end + + +-- UTILITY FUNCTIONS ----------------------------------------------------------- + + +function detect_os() + if package.config:sub(1,1) == "/" then + return "unix" + else + return "windows" + end +end + + +-- for unix use only +-- returns a table of command path and varargs, or nil if command was not found +function command_exists(command, ...) + msg.debug("looking for command:", command) + -- msg.debug("args:", ) + local process = mp.command_native({ + name = "subprocess", + capture_stdout = true, + capture_stderr = true, + playback_only = false, + args = {"sh", "-c", "command -v -- " .. command} + }) + + if process.status == 0 then + local command_path = process.stdout:gsub("\n", "") + msg.debug("command found:", command_path) + return {command_path, ...} + else + msg.debug("command not found:", command) + return nil + end +end + +function mkdir(path) + local args = nil + + if detect_os() == "unix" then + args = {"mkdir", "-p", "--", path} + else + args = {"powershell", "-NoProfile", "-Command", "mkdir", path} + end + + local process = mp.command_native({ + name = 'subprocess', + playback_only = false, + capture_stdout = true, + capture_stderr = true, + args = args, + }) + + if process.status == 0 then + msg.debug("mkdir success:", path) + return true + else + msg.error("mkdir failure:", path) + return false + end +end + + +-- returns md5 hash of the full path of the current media file +function hash() + local path = mp.get_property("path") + if path == nil then + msg.debug("something is wrong with the path, can't get full_path, can't hash it") + return + end + + msg.debug("hashing:", path) + + local cmd = { + name = 'subprocess', + capture_stdout = true, + playback_only = false, + } + local args = nil + + if detect_os() == "unix" then + local md5 = command_exists("md5sum") or command_exists("md5") or command_exists("openssl", "md5 | cut -d ' ' -f 2") + if md5 == nil then + msg.warn("no md5 command found, can't generate hash") + return + end + md5 = table.concat(md5, " ") + cmd["stdin_data"] = path + args = {"sh", "-c", md5 .. " | cut -d ' ' -f 1 | tr '[:lower:]' '[:upper:]'" } + else --windows + -- https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-filehash?view=powershell-7.3 + local hash_command ="$s = [System.IO.MemoryStream]::new(); $w = [System.IO.StreamWriter]::new($s); $w.write(\"" .. path .. "\"); $w.Flush(); $s.Position = 0; Get-FileHash -Algorithm MD5 -InputStream $s | Select-Object -ExpandProperty Hash" + args = {"powershell", "-NoProfile", "-Command", hash_command} + end + cmd["args"] = args + msg.debug("hash cmd:", utils.to_string(cmd)) + local process = mp.command_native(cmd) + + if process.status == 0 then + local hash = process.stdout:gsub("%s+", "") + msg.debug("hash:", hash) + return hash + else + msg.warn("hash function failed") + return + end +end + +function construct_ffmetadata() + local path = mp.get_property("path") + if path == nil then + msg.debug("something is wrong with the path, can't get full_path") + return + end + + local chapter_count = mp.get_property_number("chapter-list/count") + local all_chapters = mp.get_property_native("chapter-list") + + local ffmetadata = ";FFMETADATA1\n;file=" .. path + + for i, c in ipairs(all_chapters) do + local c_title = c.title + local c_start = c.time * 1000000000 + local c_end + + if i < chapter_count then + c_end = all_chapters[i+1].time * 1000000000 + else + c_end = (mp.get_property_number("duration") or c.time) * 1000000000 + end + + msg.debug(i, "c_title", c_title, "c_start:", c_start, "c_end", c_end) + + ffmetadata = ffmetadata .. "\n[CHAPTER]\nSTART=" .. c_start .. "\nEND=" .. c_end .. "\ntitle=" .. c_title + end + + return ffmetadata +end + + +-- FILE IO --------------------------------------------------------------------- + + +-- args: +-- osd - if true, display an osd message +-- force -- if true write chapters file even if there are no changes +-- on success returns path of the chapters file, nil on failure +function write_chapters(...) + local chapters_count = (mp.get_property_number('chapters') or 0) + local osd, force = ... + if not force and not chapters_modified then + msg.debug("nothing to write") + return + end + if initial_chapter_count == 0 and chapters_count == 0 then return end + + + -- figure out the directory + local chapters_dir + if o.global_chapters then + local dir = utils.file_info(o.global_chapters_path) + if dir then + if dir.is_dir then + msg.debug("o.global_chapters_path exists:", o.global_chapters_path) + chapters_dir = o.global_chapters_path + else + msg.error("o.global_chapters_path is not a directory") + return + end + else + msg.verbose("o.global_chapters_path doesn't exists:", o.global_chapters_path) + if mkdir(o.global_chapters_path) then + chapters_dir = o.global_chapters_path + else + return + end + end + else + chapters_dir = utils.split_path(mp.get_property("path")) + end + + -- and the name + local name = mp.get_property("filename") + if o.hash_global_chapters and o.global_chapters then + name = hash() + if name == nil then + msg.warn("hash function failed, fallback to filename") + name = mp.get_property("filename") + end + end + + local chapters_file_path = utils.join_path(chapters_dir, name .. ".ffmetadata") + --1.09#HERE I SHOULD ADD SOME SORT OF DELETE FUNCTION IN CASE CHAPTER COUNT IS 0 + msg.debug("opening for writing:", chapters_file_path) + local chapters_file = io.open(chapters_file_path, "w") + if chapters_file == nil then + msg.error("could not open chapter file for writing") + return + end + + local success, error = chapters_file:write(construct_ffmetadata()) + chapters_file:close() + + if success then + if osd then + prompt_msg('Chapters written to:' .. chapters_file_path) + end + return chapters_file_path + else + msg.error("error writing chapters file:", error) + return + end +end + +function load_chapters() + local path = mp.get_property("path") + local expected_chapters_file = utils.join_path(utils.split_path(path), mp.get_property("filename") .. ".ffmetadata") + + msg.debug("looking for:", expected_chapters_file) + + local file = utils.file_info(expected_chapters_file) + + if file then + msg.debug("found in the local directory, loading..") + mp.set_property("file-local-options/chapters-file", expected_chapters_file) + chapter_state = 'external-chapters' + return + end + + if not o.global_chapters then + msg.debug("not in local, global chapters not enabled, aborting search") + return + end + + msg.debug("looking in the global directory") + + if o.hash_global_chapters then + local hashed_path = hash() + if hashed_path then + expected_chapters_file = utils.join_path(o.global_chapters_path, hashed_path .. ".ffmetadata") + else + msg.debug("hash function failed, fallback to path") + expected_chapters_file = utils.join_path(o.global_chapters_path, mp.get_property("filename") .. ".ffmetadata") + end + else + expected_chapters_file = utils.join_path(o.global_chapters_path, mp.get_property("filename") .. ".ffmetadata") + end + + msg.debug("looking for:", expected_chapters_file) + + file = utils.file_info(expected_chapters_file) + + if file then + msg.debug("found in the global directory, loading..") + mp.set_property("file-local-options/chapters-file", expected_chapters_file) + chapter_state = 'external-chapters' + return + end + + msg.debug("chapters file not found") +end + + +function bake_chapters() + if mp.get_property_number("chapter-list/count") == 0 then + msg.verbose("no chapters present") + return + end + + local chapters_file_path = write_chapters(false, true) + if not chapters_file_path then + msg.error("no chapters file") + return + end + + local filename = mp.get_property("filename") + local output_name + + -- extract file extension + local reverse_dot_index = filename:reverse():find(".", 1, true) + if reverse_dot_index == nil then + msg.warning("file has no extension, fallback to .mkv") + output_name = filename .. ".chapters.mkv" + else + local dot_index = #filename + 1 - reverse_dot_index + local ext = filename:sub(dot_index + 1) + msg.debug("ext:", ext) + if ext ~= "mkv" and ext ~= "mp4" and ext ~= "webm" then + msg.debug("fallback to .mkv") + ext = "mkv" + end + output_name = filename:sub(1, dot_index) .. "chapters." .. ext + end + + local file_path = mp.get_property("path") + local output_path = utils.join_path(utils.split_path(file_path), output_name) + + local args = {"ffmpeg", "-y", "-i", file_path, "-i", chapters_file_path, "-map_metadata", "1", "-codec", "copy", output_path} + + msg.debug("args:", utils.to_string(args)) + + local process = mp.command_native({ + name = 'subprocess', + playback_only = false, + capture_stdout = true, + capture_stderr = true, + args = args + }) + + if process.status == 0 then + prompt_msg('file written to ' .. output_path) + else + msg.error("failed to write file:\n", process.stderr) + end +end + +--modified fork of chapterskip.lua-- + +function matches(i, title) + local opt_skip = o.skip + if type(o.skip) == 'table' then + for i=1, #o.skip do + if o.skip[i] and o.skip[i][1] == chapter_state then + opt_skip = o.skip[i][2] + break + end + end + end + + for category in string.gmatch(opt_skip, " *([^;]*[^; ]) *") do + if categories[category:lower()] then + if category:lower() == "idx-" or category:lower() == "toggle_idx" then + for pattern in string.gmatch(categories[category:lower()], "([^/]+)") do + if tonumber(pattern) == i then + return true + end + end + else + if title then + for pattern in string.gmatch(categories[category:lower()], "([^/]+)") do + if string.match(title, pattern) then + return true + end + end + end + end + end + end +end + +local skipped = {} +local parsed = {} + +function prep_chapterskip_var() + if chapter_state == 'no-chapters' then return end + g_opt_categories = o.categories + + g_opt_skip_once = false + if o.skip_once == true or o.skip_once == false then + g_opt_skip_once = o.skip_once + elseif has_value(o.skip_once, chapter_state) then + g_opt_skip_once = true; + end + + if type(o.categories) == 'table' then + for i=1, #o.categories do + if o.categories[i] and o.categories[i][1] == chapter_state then + g_opt_categories = o.categories[i][2] + break + end + end + end + + for category in string.gmatch(g_opt_categories, "([^;]+)") do + local name, patterns = string.match(category, " *([^+>]*[^+> ]) *[+>](.*)") + if name then + categories[name:lower()] = patterns + elseif not parsed[category] then + mp.msg.warn("Improper category definition: " .. category) + end + parsed[category] = true + end +end + +function start_chapterskip_countdown(text, duration, osd) + g_autoskip_countdown_flag = true + g_autoskip_countdown = g_autoskip_countdown - 1 + + if o.autoskip_countdown_graceful and (g_autoskip_countdown <= 0) then kill_chapterskip_countdown(); mp.osd_message('',0) return end + + if (g_autoskip_countdown < 0) then kill_chapterskip_countdown(); mp.osd_message('',0) return end + + text = text:gsub("%%countdown%%", g_autoskip_countdown) + prompt_msg(text, duration, osd) +end + +function kill_chapterskip_countdown(action) + if not g_autoskip_countdown_flag then return end + if action == 'osd' then + prompt_msg('○ Auto-Skip Cancelled', o.osd_duration, o.autoskip_osd) + end + if g_autoskip_timer ~= nil then + g_autoskip_timer:kill() + end + unbind_keys(o.cancel_autoskip_countdown_keybind, 'cancel-autoskip-countdown') + unbind_keys(o.proceed_autoskip_countdown_keybind, 'proceed-autoskip-countdown') + g_autoskip_countdown = o.autoskip_countdown + g_autoskip_countdown_flag = false +end + +function chapterskip(_, current, countdown) + if g_autoskip_disabled then return end --1.3.4# do not autoskip if g_autoskip_disabled is in disabled state + if skip_flag then return end + if chapter_state == 'no-chapters' then return end + if not autoskip_chapter then return end + if not current then return end + if g_autoskip_countdown_flag then kill_chapterskip_countdown('osd') end + if not countdown then countdown = o.autoskip_countdown end + + local chapters = mp.get_property_native("chapter-list") + local skip = false + local consecutive_i = 0 + + for i=0, #chapters do + if (not g_opt_skip_once or not skipped[i]) and i == 0 and chapters[i+1] and matches(i, chapters[i+1].title) then + if i == current + 1 or skip == i - 1 then + if skip then + skipped[skip] = true + end + skip = i + consecutive_i = consecutive_i+1 + end + elseif (not g_opt_skip_once or not skipped[i]) and chapters[i] and matches(i, chapters[i].title) then + if i == current + 1 or skip == i - 1 then + if skip then + skipped[skip] = true + end + skip = i + consecutive_i = consecutive_i+1 + end + elseif skip and countdown <= 0 then + if o.autoskip_osd == 'osd-bar' or o.autoskip_osd == 'osd-msg-bar' then prompt_progress('osd-bar', o.osd_duration) end + + if consecutive_i > 1 then + local autoskip_osd_string = '' + for j=consecutive_i, 1, -1 do + local chapter_title = '' + if chapters[i-j] then chapter_title = chapters[i-j].title end + autoskip_osd_string=(autoskip_osd_string..'\n ➤ Chapter ('..i-j..') '..chapter_title) + end + prompt_msg('● Auto-Skip'..autoskip_osd_string, o.osd_duration, o.autoskip_osd) + else + prompt_msg('➤ Auto-Skip: Chapter '.. mp.command_native({'expand-text', '${chapter}'}), o.osd_duration, o.autoskip_osd) + end + mp.commandv('no-osd', 'set', 'chapter', i-1) + skipped[skip] = true + return + elseif skip and countdown > 0 then + g_autoskip_countdown_flag = true + bind_keys(o.cancel_autoskip_countdown_keybind, "cancel-autoskip-countdown", function() kill_chapterskip_countdown('osd') return end) + + local autoskip_osd_string = '' + local autoskip_graceful_osd = '' + if o.autoskip_countdown_graceful then autoskip_graceful_osd = 'Press Keybind to:\n' end + + if consecutive_i > 1 and o.autoskip_countdown_bulk then + local autoskip_osd_string = '' + for j=consecutive_i, 1, -1 do + local chapter_title = '' + if chapters[i-j] then chapter_title = chapters[i-j].title end + autoskip_osd_string=(autoskip_osd_string..'\n ▷ Chapter ('..i-j..') '..chapter_title) + end + prompt_msg(autoskip_graceful_osd..'○ Auto-Skip'..' in "'..o.autoskip_countdown..'"'..autoskip_osd_string, 2000, o.autoskip_osd) + g_autoskip_timer = mp.add_periodic_timer(1, function () + start_chapterskip_countdown(autoskip_graceful_osd..'○ Auto-Skip'..' in "%countdown%"'..autoskip_osd_string, 2000, o.autoskip_osd) + end) + else + prompt_msg(autoskip_graceful_osd..'▷ Auto-Skip in "'..o.autoskip_countdown..'": Chapter '.. mp.command_native({'expand-text', '${chapter}'}), 2000, o.autoskip_osd) + g_autoskip_timer = mp.add_periodic_timer(1, function () + start_chapterskip_countdown(autoskip_graceful_osd..'▷ Auto-Skip in "%countdown%": Chapter '.. mp.command_native({'expand-text', '${chapter}'}), 2000, o.autoskip_osd) + end) + end + + function proceed_autoskip(force) + if not g_autoskip_countdown_flag then kill_chapterskip_countdown() return end + if g_autoskip_countdown > 1 and not force then return end + + if o.autoskip_osd == 'osd-bar' or o.autoskip_osd == 'osd-msg-bar' then prompt_progress('osd-bar', o.osd_duration) end + + if consecutive_i > 1 and o.autoskip_countdown_bulk then + local autoskip_osd_string = '' + for j=consecutive_i, 1, -1 do + local chapter_title = '' + if chapters[i-j] then chapter_title = chapters[i-j].title end + autoskip_osd_string=(autoskip_osd_string..'\n ➤ Chapter ('..i-j..') '..chapter_title) + end + prompt_msg('● Auto-Skip'..autoskip_osd_string, o.osd_duration, o.autoskip_osd) + else + prompt_msg('➤ Auto-Skip: Chapter '.. mp.command_native({'expand-text', '${chapter}'}), o.osd_duration, o.autoskip_osd) + end + + if consecutive_i > 1 and o.autoskip_countdown_bulk then + mp.commandv('no-osd', 'set', 'chapter', i-1) + else + mp.commandv('no-osd', 'add', 'chapter', 1) + end + skipped[skip] = true + kill_chapterskip_countdown() + end + bind_keys(o.proceed_autoskip_countdown_keybind, "proceed-autoskip-countdown", function() proceed_autoskip(true) return end) + if o.autoskip_countdown_graceful then return end + mp.add_timeout(countdown, proceed_autoskip) + return + end + end + if skip and countdown <= 0 then + if mp.get_property_native("playlist-count") == mp.get_property_native("playlist-pos-1") then + return mp.set_property("time-pos", mp.get_property_native("duration")) + end + mp.commandv("playlist-next") + if o.autoskip_osd ~= 'no-osd' or o.autoskip_osd ~= 'osd-bar' then autoskip_playlist_osd = true end + elseif skip and countdown > 0 then + g_autoskip_countdown_flag = true + bind_keys(o.cancel_autoskip_countdown_keybind, "cancel-autoskip-countdown", function() kill_chapterskip_countdown('osd') return end) + local autoskip_graceful_osd = '' + if o.autoskip_countdown_graceful then autoskip_graceful_osd = 'Press Keybind to:\n' end + if consecutive_i > 1 and o.autoskip_countdown_bulk then + local i = (mp.get_property_number('chapters')+1 or 0) + local autoskip_osd_string = '' + for j=consecutive_i, 1, -1 do + local chapter_title = '' + if chapters[i-j] then chapter_title = chapters[i-j].title end + autoskip_osd_string=(autoskip_osd_string..'\n ▷ Chapter ('..i-j..') '..chapter_title) + end + prompt_msg(autoskip_graceful_osd..'○ Auto-Skip'..' in "'..o.autoskip_countdown..'"'..autoskip_osd_string, 2000, o.autoskip_osd) + g_autoskip_timer = mp.add_periodic_timer(1, function () + start_chapterskip_countdown(autoskip_graceful_osd..'○ Auto-Skip'..' in "%countdown%"'..autoskip_osd_string, 2000, o.autoskip_osd) + end) + else + prompt_msg(autoskip_graceful_osd..'▷ Auto-Skip in "'..o.autoskip_countdown..'": Chapter '.. mp.command_native({'expand-text', '${chapter}'}), 2000, o.autoskip_osd) + g_autoskip_timer = mp.add_periodic_timer(1, function () + start_chapterskip_countdown(autoskip_graceful_osd..'▷ Auto-Skip in "%countdown%": Chapter '.. mp.command_native({'expand-text', '${chapter}'}), 2000, o.autoskip_osd) + end) + end + function proceed_autoskip(force) + if not g_autoskip_countdown_flag then return end + if g_autoskip_countdown > 1 and not force then return end + + if o.autoskip_osd == 'osd-bar' or o.autoskip_osd == 'osd-msg-bar' then prompt_progress('osd-bar', o.osd_duration) end + + if consecutive_i > 1 and o.autoskip_countdown_bulk then + if mp.get_property_native("playlist-count") == mp.get_property_native("playlist-pos-1") then + return mp.set_property("time-pos", mp.get_property_native("duration")) + end + mp.commandv("playlist-next") + else + local current_chapter = (mp.get_property_number("chapter") + 1 or 0) + local chapters_count = (mp.get_property_number('chapters') or 0) + + if current_chapter == chapters_count then + if mp.get_property_native("playlist-count") == mp.get_property_native("playlist-pos-1") then + return mp.set_property("time-pos", mp.get_property_native("duration")) + end + mp.commandv("playlist-next") + else + mp.commandv('no-osd', 'add', 'chapter', 1) + end + end + if o.autoskip_osd ~= 'no-osd' or o.autoskip_osd ~= 'osd-bar' then autoskip_playlist_osd = true end + kill_chapterskip_countdown() + end + bind_keys(o.proceed_autoskip_countdown_keybind, "proceed-autoskip-countdown", function() proceed_autoskip(true) return end) + if o.autoskip_countdown_graceful then return end + mp.add_timeout(countdown, proceed_autoskip) + end +end + +function toggle_autoskip() + if autoskip_chapter == true then + prompt_msg('○ Auto-Skip Disabled', o.osd_duration, o.autoskip_osd) + autoskip_chapter = false + if g_autoskip_countdown_flag then kill_chapterskip_countdown() end + elseif autoskip_chapter == false then + prompt_msg('● Auto-Skip Enabled', o.osd_duration, o.autoskip_osd) + autoskip_chapter = true + end +end + +function toggle_category_autoskip() + if chapter_state == 'no-chapters' then return end + if not mp.get_property_number("chapter") then return end + local chapters = mp.get_property_native("chapter-list") + local current_chapter = (mp.get_property_number("chapter") + 1 or 0) + + local chapter_title = tostring(current_chapter) + if current_chapter > 0 and chapters[current_chapter].title and chapters[current_chapter].title ~= '' then + chapter_title = chapters[current_chapter].title + end + + local found_i = 0 + if matches(current_chapter, chapter_title) then + for category in string.gmatch(g_opt_categories, "([^;]+)") do + local name, patterns = string.match(category, " *([^+>]*[^+> ]) *[+>](.*)") + + for pattern in string.gmatch(patterns, "([^/]+)") do + if string.match(chapter_title:lower(), pattern:lower()) then + g_opt_categories = g_opt_categories:gsub(esc_string(pattern)..'/?', "") + found_i = found_i + 1 + end + end + end + + for category in string.gmatch(g_opt_categories, "([^;]+)") do + local name, patterns = string.match(category, " *([^+>]*[^+> ]) *[+>](.*)") + if name then + categories[name:lower()] = patterns + elseif not parsed[category] then + mp.msg.warn("Improper category definition: " .. category) + end + parsed[category] = true + end + + if type(o.categories) == 'table' then + for i=1, #o.categories do + if o.categories[i] and o.categories[i][1] == chapter_state then + o.categories[i][2] = g_opt_categories + break + end + end + else + o.categories = g_opt_categories + end + end + if current_chapter > 0 and chapters[current_chapter].title and chapters[current_chapter].title ~= '' then + if found_i > 0 or string.match(categories.toggle, esc_string(chapter_title)) then + prompt_msg('○ Removed from Auto-Skip\n ▷ Chapter: '..chapter_title, o.osd_duration, o.autoskip_osd) + categories.toggle = categories.toggle:gsub(esc_string("^"..chapter_title.."/"), "") + if g_autoskip_countdown_flag then kill_chapterskip_countdown() end + else + prompt_msg('● Added to Auto-Skip\n ➔ Chapter: '..chapter_title, o.osd_duration, o.autoskip_osd) + categories.toggle = categories.toggle.."^"..chapter_title.."/" + end + else + if found_i > 0 or string.match(categories.toggle_idx, esc_string(chapter_title)) then + prompt_msg('○ Removed from Auto-Skip\n ▷ Chapter: '..chapter_title, o.osd_duration, o.autoskip_osd) + categories.toggle_idx = categories.toggle_idx:gsub(esc_string(chapter_title.."/"), "") + if g_autoskip_countdown_flag then kill_chapterskip_countdown() end + else + prompt_msg('● Added to Auto-Skip\n ➔ Chapter: '..chapter_title, o.osd_duration, o.autoskip_osd) + categories.toggle_idx = categories.toggle_idx..chapter_title.."/" + end + end +end + +-- HOOKS -------------------------------------------------------------------- +if user_input_module then mp.add_hook("on_unload", 50, function () input.cancel_user_input() end) end -- chapters.lua +mp.observe_property("chapter", "number", chapterskip) -- chapterskip.lua + +-- smart skip events / properties / hooks -- + +mp.register_event('file-loaded', function() + file_length = (mp.get_property_native('duration') or 0) + g_filepath = (mp.get_property('path') or '') --1.3.4# get filepath needed for exclusion_check function + if exclusion_check() then g_autoskip_disabled = true end --1.3.4# disable / enable autoskip based on exclusion_check + + if o.playlist_osd and g_playlist_pos > 0 then playlist_osd = true end + if playlist_osd and not autoskip_playlist_osd then + prompt_msg('['..mp.command_native({'expand-text', '${playlist-pos-1}'})..'/'..mp.command_native({'expand-text', '${playlist-count}'})..'] '..mp.command_native({'expand-text', '${filename}'})) + end + if autoskip_playlist_osd then + prompt_msg('➤ Auto-Skip\n['..mp.command_native({'expand-text', '${playlist-pos-1}'})..'/'..mp.command_native({'expand-text', '${playlist-count}'})..'] '..mp.command_native({'expand-text', '${filename}'}), o.osd_duration, o.autoskip_osd) + end + playlist_osd = false + autoskip_playlist_osd = false + force_silence_skip = false + skipped = {} + initial_chapter_count = mp.get_property_number("chapter-list/count") + if initial_chapter_count > 0 and chapter_state ~= 'external-chapters' then chapter_state = 'internal-chapters' end + prep_chapterskip_var() +end) + +mp.add_hook("on_load", 50, function() + if o.external_chapters_autoload then load_chapters() end +end) + +mp.observe_property('pause', 'bool', function(name, value) + if value and skip_flag then + restoreProp(initial_skip_time, true) + end + if g_autoskip_countdown_flag then kill_chapterskip_countdown('osd') end +end) + +mp.add_hook('on_unload', 9, function() + if geometry_default == "" then mp.set_property("geometry","") end + if o.modified_chapters_autosave == true or has_value(o.modified_chapters_autosave, chapter_state) then write_chapters(false) end + mp.set_property("keep-open", keep_open_state) + chapter_state = 'no-chapters' + g_playlist_pos = (mp.get_property_native('playlist-playing-pos')+1 or 0) + kill_chapterskip_countdown() +end) + +mp.register_event('seek', function() + if g_autoskip_countdown_flag then kill_chapterskip_countdown('osd') end +end) + +mp.observe_property('eof-reached', 'bool', eofHandler) + +-- BINDINGS -------------------------------------------------------------------- + +bind_keys(o.toggle_autoskip_keybind, "toggle-autoskip", toggle_autoskip) +bind_keys(o.toggle_category_autoskip_keybind, "toggle-category-autoskip", toggle_category_autoskip) +bind_keys(o.add_chapter_keybind, "add-chapter", add_chapter) +bind_keys(o.remove_chapter_keybind, "remove-chapter", remove_chapter) +bind_keys(o.write_chapters_keybind, "write-chapters", function () write_chapters(true) end) +bind_keys(o.edit_chapter_keybind, "edit-chapter", edit_chapter) +bind_keys(o.bake_chapters_keybind, "bake-chapters", bake_chapters) +bind_keys(o.chapter_prev_keybind, "chapter-prev", function() chapterSeek(-1) end) +bind_keys(o.chapter_next_keybind, "chapter-next", function() chapterSeek(1) end) +bind_keys(o.smart_prev_keybind, "smart-prev", smartPrev) +bind_keys(o.smart_next_keybind, "smart-next", smartNext) +bind_keys(o.silence_skip_keybind, "silence-skip", silenceSkip) -- cgit v1.3