618 lines
22 KiB
Lua
618 lines
22 KiB
Lua
--[[
|
|
* chapter-make-read.lua v.2025-03-01
|
|
*
|
|
* AUTHORS: dyphire
|
|
* License: MIT
|
|
* link: https://github.com/dyphire/mpv-scripts
|
|
--]]
|
|
|
|
--[[
|
|
Copyright (c) 2023 dyphire <qimoge@gmail.com>
|
|
|
|
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.
|
|
--]]
|
|
|
|
-- Implementation read and automatically load the namesake external chapter file.
|
|
-- The external chapter files should conform to the following formats.
|
|
-- Note: The Timestamps should use the 12-bit format of 'hh:mm:ss.sss'.
|
|
-- Note: The file encoding should be UTF-8 and the linebreak should be Unix(LF).
|
|
-- Note: The script also supports reading OGM format and MediaInfo format in addition to the following formats.
|
|
--[[
|
|
00:00:00.000 A part
|
|
00:00:40.312 OP
|
|
00:02:00.873 B part
|
|
00:10:44.269 C part
|
|
00:22:40.146 ED
|
|
--]]
|
|
|
|
-- This script also supports manually load/refresh,marks,edits,remove and creates external chapter files, usage:
|
|
-- Note: It can also be used to export the existing chapter information of the playback file.
|
|
-- add bindings to input.conf:
|
|
-- key script-message-to chapter_make_read load_chapter
|
|
-- key script-message-to chapter_make_read create_chapter
|
|
-- key script-message-to chapter_make_read edit_chapter
|
|
-- key script-message-to chapter_make_read remove_chapter
|
|
-- key script-message-to chapter_make_read write_chapter chp
|
|
-- key script-message-to chapter_make_read write_chapter ogm
|
|
|
|
local msg = require 'mp.msg'
|
|
local utils = require 'mp.utils'
|
|
local options = require "mp.options"
|
|
|
|
local o = {
|
|
autoload = true,
|
|
autosave = false,
|
|
force_overwrite = false,
|
|
-- Specifies the extension of the external chapter file.
|
|
chapter_file_ext = ".chp",
|
|
-- Select whether the external chapter file needs to match the extension of the source file.
|
|
basename_with_ext = true,
|
|
-- Specifies the subpath of the same directory as the playback file as the external chapter file path.
|
|
-- Note: The external chapter file is read from the subdirectory first.
|
|
-- If the file does not exist, it will next be read from the same directory as the playback file.
|
|
external_chapter_subpath = "chapters",
|
|
-- save all chapter files in a single global directory
|
|
global_chapters = false,
|
|
global_chapters_dir = "~~/chapters",
|
|
-- hash works only in global_chapters_dir
|
|
hash = false,
|
|
-- ask for title or leave it empty
|
|
ask_for_title = true,
|
|
-- placeholder when asking for title of a new chapter
|
|
placeholder_title = "Chapter ",
|
|
-- pause the playback when asking for chapter title
|
|
pause_on_input = true,
|
|
}
|
|
|
|
options.read_options(o)
|
|
|
|
local input_loaded, input = pcall(require, "mp.input")
|
|
-- Requires: https://github.com/CogentRedTester/mpv-user-input
|
|
local user_input_loaded, user_input = pcall(require, "user-input-module")
|
|
|
|
local path = nil
|
|
local dir = nil
|
|
local fname = nil
|
|
local chapter_fullpath = nil
|
|
local all_chapters = {}
|
|
local chapter_count = 0
|
|
local chapters_modified = false
|
|
local paused = false
|
|
local protocol = false
|
|
|
|
local function is_protocol(path)
|
|
return type(path) == 'string' and (path:find('^%a[%w.+-]-://') ~= nil or path:find('^%a[%w.+-]-:%?') ~= nil)
|
|
end
|
|
|
|
function url_decode(str)
|
|
local function hex_to_char(x)
|
|
return string.char(tonumber(x, 16))
|
|
end
|
|
|
|
if str ~= nil then
|
|
str = str:gsub('^%a[%a%d-_]+://', '')
|
|
:gsub('^%a[%a%d-_]+:\\?', '')
|
|
:gsub('%%(%x%x)', hex_to_char)
|
|
if str:find('://localhost:?') then
|
|
str = str:gsub('^.*/', '')
|
|
end
|
|
str = str:gsub('[\\/:%?]*', '')
|
|
return str
|
|
end
|
|
end
|
|
|
|
--create global_chapters_dir if it doesn't exist
|
|
local global_chapters_dir = mp.command_native({ "expand-path", o.global_chapters_dir })
|
|
if global_chapters_dir and global_chapters_dir ~= '' then
|
|
local meta = utils.file_info(global_chapters_dir)
|
|
if not meta or not meta.is_dir then
|
|
local is_windows = package.config:sub(1, 1) == "\\"
|
|
local windows_args = { 'powershell', '-NoProfile', '-Command', 'mkdir', string.format("\"%s\"", global_chapters_dir) }
|
|
local unix_args = { 'mkdir', '-p', global_chapters_dir }
|
|
local args = is_windows and windows_args or unix_args
|
|
local res = mp.command_native({ name = "subprocess", capture_stdout = true, playback_only = false, args = args })
|
|
if res.status ~= 0 then
|
|
msg.error("Failed to create global_chapters_dir save directory " .. global_chapters_dir ..
|
|
". Error: " .. (res.error or "unknown"))
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
local function read_chapter(func)
|
|
local meta = utils.file_info(chapter_fullpath)
|
|
if not meta or not meta.is_file then return end
|
|
local f = io.open(chapter_fullpath, "r")
|
|
if not f then return end
|
|
local contents = {}
|
|
for line in f:lines() do
|
|
table.insert(contents, (func(line)))
|
|
end
|
|
f:close()
|
|
return contents
|
|
end
|
|
|
|
local function read_chapter_table()
|
|
local line_pos = 0
|
|
return read_chapter(function(line)
|
|
local h, m, s, t, n, l
|
|
local thin_space = string.char(0xE2, 0x80, 0x89)
|
|
local line = line:gsub(thin_space, " ")
|
|
if line:match("^%d+:%d+:%d+") ~= nil then
|
|
h, m, s = line:match("^(%d+):(%d+):(%d+[,%.]?%d+)")
|
|
s = s:gsub(',', '.')
|
|
t = h * 3600 + m * 60 + s
|
|
if line:match("^%d+:%d+:%d+[,%.]?%d+[,%s].*") ~= nil then
|
|
n = line:match("^%d+:%d+:%d+[,%.]?%d+[,%s](.*)")
|
|
n = n:gsub(":%s%a?%a?:", "")
|
|
:gsub("^%s*(.-)%s*$", "%1")
|
|
end
|
|
l = line
|
|
line_pos = line_pos + 1
|
|
elseif line:match("^%d+:%d+[,%.]?%d+[,%s].*") ~= nil then
|
|
m, s = line:match("^(%d+):(%d+[,%.]?%d+)")
|
|
s = s:gsub(',', '.')
|
|
t = m * 60 + s
|
|
if line:match("^%d+:%d+[,%.]?%d+[,%s].*") ~= nil then
|
|
n = line:match("^%d+:%d+[,%.]?%d+[,%s](.*)")
|
|
n = n:gsub(":%s%a?%a?:", "")
|
|
:gsub("^%s*(.-)%s*$", "%1")
|
|
end
|
|
l = line
|
|
line_pos = line_pos + 1
|
|
elseif line:match("^CHAPTER%d+=%d+:%d+:%d+") ~= nil then
|
|
h, m, s = line:match("^CHAPTER%d+=(%d+):(%d+):(%d+[,%.]?%d+)")
|
|
s = s:gsub(',', '.')
|
|
t = h * 3600 + m * 60 + s
|
|
l = line
|
|
line_pos = line_pos + 1
|
|
elseif line:match("^CHAPTER%d+NAME=.*") ~= nil then
|
|
n = line:gsub("^CHAPTER%d+NAME=", "")
|
|
n = n:gsub("^%s*(.-)%s*$", "%1")
|
|
l = line
|
|
line_pos = line_pos + 1
|
|
else
|
|
return
|
|
end
|
|
return { found_title = n, found_time = t, found_line = l }
|
|
end)
|
|
end
|
|
|
|
local function refresh_globals()
|
|
path = mp.get_property("path")
|
|
if path then
|
|
protocol = is_protocol(path)
|
|
dir = utils.split_path(path)
|
|
end
|
|
|
|
if protocol then
|
|
fname = url_decode(mp.get_property("media-title"))
|
|
elseif o.basename_with_ext then
|
|
fname = mp.get_property("filename")
|
|
else
|
|
fname = mp.get_property("filename/no-ext")
|
|
end
|
|
|
|
all_chapters = mp.get_property_native("chapter-list")
|
|
chapter_count = mp.get_property_number("chapter-list/count")
|
|
end
|
|
|
|
local function format_time(seconds)
|
|
local result = ""
|
|
local hours, mins, secs, msecs
|
|
if seconds <= 0 then
|
|
return "00:00:00.000";
|
|
else
|
|
hours = string.format("%02.f", math.floor(seconds / 3600))
|
|
mins = string.format("%02.f", math.floor(seconds / 60 - (hours * 60)))
|
|
secs = string.format("%02.f", math.floor(seconds - hours * 60 * 60 - mins * 60))
|
|
msecs = string.format("%03.f", seconds * 1000 - hours * 60 * 60 * 1000 - mins * 60 * 1000 - secs * 1000)
|
|
result = hours .. ":" .. mins .. ":" .. secs .. "." .. msecs
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- for unix use only
|
|
-- returns a table of command path and varargs, or nil if command was not found
|
|
local 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
|
|
|
|
-- returns md5 hash of the full path of the current media file
|
|
local function hash(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
|
|
local is_unix = package.config:sub(1,1) == "/"
|
|
if is_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
|
|
|
|
local function get_chapter_filename(path)
|
|
name = hash(path)
|
|
if name == nil then
|
|
msg.warn("hash function failed, fallback to filename")
|
|
name = fname
|
|
end
|
|
return name
|
|
end
|
|
|
|
local function mark_chapter(force_overwrite)
|
|
refresh_globals()
|
|
if not path then return end
|
|
|
|
local chapter_index = 0
|
|
local chapters_time = {}
|
|
local chapters_title = {}
|
|
local fpath = dir
|
|
if protocol then
|
|
fpath = global_chapters_dir
|
|
if o.hash then fname = get_chapter_filename(path) end
|
|
elseif o.external_chapter_subpath ~= '' then
|
|
fpath = utils.join_path(dir, o.external_chapter_subpath)
|
|
local meta = utils.file_info(fpath)
|
|
if not meta or not meta.is_dir then
|
|
fpath = dir
|
|
end
|
|
end
|
|
|
|
if o.global_chapters and global_chapters_dir and global_chapters_dir ~= '' and not protocol then
|
|
fpath = global_chapters_dir
|
|
local meta = utils.file_info(fpath)
|
|
if meta and meta.is_dir then
|
|
if o.hash then
|
|
fname = get_chapter_filename(path)
|
|
end
|
|
end
|
|
end
|
|
|
|
local chapter_filename = fname .. o.chapter_file_ext
|
|
chapter_fullpath = utils.join_path(fpath, chapter_filename)
|
|
local fmeta = utils.file_info(chapter_fullpath)
|
|
if (not fmeta or not fmeta.is_file) and fpath ~= dir and not protocol then
|
|
if o.basename_with_ext then
|
|
fname = mp.get_property("filename")
|
|
else
|
|
fname = mp.get_property("filename/no-ext")
|
|
end
|
|
chapter_filename = fname .. o.chapter_file_ext
|
|
chapter_fullpath = utils.join_path(dir, chapter_filename)
|
|
end
|
|
local list_contents = read_chapter_table()
|
|
|
|
if not list_contents then return end
|
|
for i = 1, #list_contents do
|
|
local chapter_time = tonumber(list_contents[i].found_time)
|
|
if chapter_time ~= nil and chapter_time >= 0 then
|
|
table.insert(chapters_time, chapter_time)
|
|
end
|
|
if list_contents[i].found_title ~= nil then
|
|
table.insert(chapters_title, list_contents[i].found_title)
|
|
end
|
|
end
|
|
if not chapters_time[1] then return end
|
|
|
|
table.sort(chapters_time, function(a, b) return a < b end)
|
|
|
|
if force_overwrite then all_chapters = {} 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)
|
|
msg.info("load external chapter file successful: " .. chapter_filename)
|
|
end
|
|
|
|
local function change_chapter_list(chapter_tltle, chapter_index)
|
|
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 = chapter_tltle
|
|
mp.set_property_native("chapter-list", chapter_list)
|
|
end
|
|
|
|
local function change_title_callback(user_input, err, chapter_index)
|
|
if user_input == nil or err ~= nil then
|
|
if paused then return elseif o.pause_on_input then mp.set_property_native("pause", false) end
|
|
msg.warn("no chapter title provided:", err)
|
|
return
|
|
end
|
|
change_chapter_list(user_input, chapter_index)
|
|
if paused then return elseif o.pause_on_input then mp.set_property_native("pause", false) end
|
|
chapters_modified = true
|
|
end
|
|
|
|
local function input_title(default_input, cursor_pos, chapter_index)
|
|
input.get({
|
|
prompt = 'Chapter title:',
|
|
default_text = default_input,
|
|
cursor_position = cursor_pos,
|
|
submit = function(text)
|
|
input.terminate()
|
|
change_chapter_list(text, chapter_index)
|
|
end,
|
|
closed = function()
|
|
if paused then return elseif o.pause_on_input then mp.set_property_native("pause", false) end
|
|
end
|
|
})
|
|
end
|
|
|
|
local function input_choice(title, chapter_index)
|
|
if not input_loaded and not user_input_loaded then
|
|
msg.error("no mpv-user-input, can't get user input, install: https://github.com/CogentRedTester/mpv-user-input")
|
|
return
|
|
end
|
|
|
|
if input_loaded then
|
|
input_title(title, #title + 1, chapter_index)
|
|
elseif user_input_loaded then
|
|
-- ask user for chapter title
|
|
-- (+1 because mpv indexes from 0, lua from 1)
|
|
user_input.get_user_input(change_title_callback, {
|
|
request_text = "Chapter title:",
|
|
default_input = title,
|
|
cursor_pos = #title + 1,
|
|
}, chapter_index)
|
|
end
|
|
end
|
|
|
|
local function create_chapter()
|
|
refresh_globals()
|
|
if not path then return end
|
|
|
|
local time_pos = mp.get_property_number("time-pos")
|
|
local time_pos_osd = mp.get_property_osd("time-pos/full")
|
|
local current_chapter = mp.get_property_number("chapter")
|
|
mp.osd_message(time_pos_osd, 1)
|
|
|
|
if chapter_count == 0 then
|
|
all_chapters[1] = {
|
|
title = o.placeholder_title .. "01",
|
|
time = time_pos
|
|
}
|
|
-- We just set it to zero here so when we add 1 later it ends up as 1
|
|
-- otherwise it's probably "nil"
|
|
current_chapter = 0
|
|
-- note that mpv will treat the beginning of the file as all_chapters[0] when using pageup/pagedown
|
|
-- so we don't actually have to worry if the file doesn't start with a chapter
|
|
else
|
|
-- to insert a chapter we have to increase the index on all subsequent chapters
|
|
-- otherwise we'll end up with duplicate chapter IDs which will confuse mpv
|
|
-- +2 looks weird, but remember mpv indexes at 0 and lua indexes at 1
|
|
-- adding two will turn "current chapter" from mpv notation into "next chapter" from lua's notation
|
|
-- count down because these areas of memory overlap
|
|
for i = chapter_count, current_chapter + 2, -1 do
|
|
all_chapters[i + 1] = all_chapters[i]
|
|
end
|
|
all_chapters[current_chapter + 2] = {
|
|
title = o.placeholder_title .. string.format("%02.f", current_chapter + 2),
|
|
time = time_pos
|
|
}
|
|
end
|
|
mp.set_property_native("chapter-list", all_chapters)
|
|
mp.set_property_number("chapter", current_chapter + 1)
|
|
chapters_modified = true
|
|
|
|
if o.ask_for_title then
|
|
local chapter_index = mp.get_property_number("chapter") + 1
|
|
local title = o.placeholder_title .. string.format("%02.f", chapter_index)
|
|
|
|
input_choice(title, chapter_index)
|
|
|
|
if o.pause_on_input then
|
|
paused = mp.get_property_native("pause")
|
|
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
|
|
|
|
local function edit_chapter()
|
|
local chapter_index = mp.get_property_number("chapter") + 1
|
|
local chapter_list = mp.get_property_native("chapter-list")
|
|
local title = chapter_list[chapter_index + 1].title
|
|
if chapter_index == nil or chapter_index == -1 then
|
|
msg.verbose("no chapter selected, nothing to edit")
|
|
return
|
|
end
|
|
|
|
input_choice(title, chapter_index)
|
|
|
|
if o.pause_on_input then
|
|
paused = mp.get_property_native("pause")
|
|
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
|
|
|
|
local 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")
|
|
-- +1 because mpv indexes from 0, lua from 1
|
|
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
|
|
|
|
local function write_chapter(format, force_write)
|
|
refresh_globals()
|
|
if not path or chapter_count == 0 or (not chapters_modified and not force_write) then
|
|
msg.debug("nothing to write")
|
|
return
|
|
end
|
|
|
|
if o.global_chapters then dir = global_chapters_dir end
|
|
if o.hash and o.global_chapters then fname = get_chapter_filename(path) end
|
|
local out_path = utils.join_path(dir, fname .. o.chapter_file_ext)
|
|
local chapters = ""
|
|
local next_chapter = nil
|
|
for i = 1, chapter_count, 1 do
|
|
local current_chapter = all_chapters[i]
|
|
local time_pos = format_time(current_chapter.time)
|
|
if format == "ogm" then
|
|
next_chapter = "CHAPTER" .. string.format("%02.f", i) .. "=" .. time_pos .. "\n" ..
|
|
"CHAPTER" .. string.format("%02.f", i) .. "NAME=" .. current_chapter.title .. "\n"
|
|
elseif format == "chp" then
|
|
next_chapter = time_pos .. " " .. current_chapter.title .. "\n"
|
|
else
|
|
msg.warn("please specify the correct chapter format: chp/ogm.")
|
|
return
|
|
end
|
|
if i == 1 and (o.global_chapters or protocol) then
|
|
chapters = "# " .. path .. "\n\n" .. next_chapter
|
|
else
|
|
chapters = chapters .. next_chapter
|
|
end
|
|
end
|
|
|
|
local file = io.open(out_path, "w")
|
|
if file == nil then
|
|
dir = global_chapters_dir
|
|
fname = url_decode(mp.get_property("media-title"))
|
|
if o.hash then fname = get_chapter_filename(path) end
|
|
out_path = utils.join_path(dir, fname .. o.chapter_file_ext)
|
|
file = io.open(out_path, "w")
|
|
end
|
|
if file == nil then
|
|
mp.error("Could not open chapter file for writing.")
|
|
return
|
|
end
|
|
file:write(chapters)
|
|
file:close()
|
|
if not o.autosave then
|
|
mp.osd_message("Export chapter file to: " .. out_path, 3)
|
|
end
|
|
msg.info("Export chapter file to: " .. out_path)
|
|
end
|
|
|
|
-- HOOKS -----------------------------------------------------------------------
|
|
|
|
if o.autoload then
|
|
mp.add_hook("on_preloaded", 50, function()
|
|
if o.force_overwrite then
|
|
mark_chapter(true)
|
|
else
|
|
mark_chapter(false)
|
|
end
|
|
end)
|
|
end
|
|
|
|
if o.autosave then
|
|
mp.add_hook("on_unload", 50, function()
|
|
write_chapter("chp", false)
|
|
end)
|
|
end
|
|
|
|
if user_input_loaded and not input_loaded then
|
|
mp.add_hook("on_unload", 50, function() user_input.cancel_user_input() end)
|
|
end
|
|
|
|
mp.register_script_message("load_chapter", function() mark_chapter(true) end)
|
|
mp.register_script_message("create_chapter", create_chapter, { repeatable = true })
|
|
mp.register_script_message("remove_chapter", remove_chapter)
|
|
mp.register_script_message("edit_chapter", edit_chapter)
|
|
mp.register_script_message("write_chapter", function(format)
|
|
write_chapter(format, true)
|
|
end) |