291 lines
7.5 KiB
Lua
291 lines
7.5 KiB
Lua
-- NOTE TO FUTURE SELF:
|
|
-- To view debug logs when launching through Finder,
|
|
-- use the ` key in mpv instead of viewing ~/Library/Logs/mpv.log.
|
|
|
|
mp.msg.info("MPV-CUT LOADED")
|
|
|
|
utils = require "mp.utils"
|
|
|
|
local function print(s)
|
|
mp.msg.info(s)
|
|
mp.osd_message(s)
|
|
end
|
|
|
|
local function table_to_str(o)
|
|
if type(o) == 'table' then
|
|
local s = ''
|
|
for k,v in pairs(o) do
|
|
if type(k) ~= 'number' then k = '"'..k..'"' end
|
|
s = s .. '['..k..'] = ' .. table_to_str(v) .. '\n'
|
|
end
|
|
return s
|
|
else
|
|
return tostring(o)
|
|
end
|
|
end
|
|
|
|
local result = mp.command_native({ name = "subprocess", args = {"ffmpeg"}, playback_only = false, capture_stdout = true, capture_stderr = true })
|
|
mp.msg.info("Your PATH: " .. os.getenv('PATH'))
|
|
if result.status ~= 1 then
|
|
mp.osd_message("FFmpeg failed to run, please press ` for debug info", 5)
|
|
mp.msg.error("FFmpeg failed to run:\n" .. table_to_str(result))
|
|
mp.msg.error("`which ffmpeg` output:\n" .. table_to_str(mp.command_native({ name = "subprocess", args = {"which", "ffmpeg"}, playback_only = false, capture_stdout = true, capture_stderr = true })))
|
|
end
|
|
|
|
local function to_hms(seconds)
|
|
local ms = math.floor((seconds - math.floor(seconds)) * 1000)
|
|
local secs = math.floor(seconds)
|
|
local mins = math.floor(secs / 60)
|
|
secs = secs % 60
|
|
local hours = math.floor(mins / 60)
|
|
mins = mins % 60
|
|
return string.format("%02d-%02d-%02d-%03d", hours, mins, secs, ms)
|
|
end
|
|
|
|
local function next_table_key(t, current)
|
|
local keys = {}
|
|
for k in pairs(t) do
|
|
keys[#keys + 1] = k
|
|
end
|
|
table.sort(keys)
|
|
for i = 1, #keys do
|
|
if keys[i] == current then
|
|
return keys[(i % #keys) + 1]
|
|
end
|
|
end
|
|
return keys[1]
|
|
end
|
|
|
|
ACTIONS = {}
|
|
|
|
ACTIONS.COPY = function(d)
|
|
local args = {
|
|
"ffmpeg",
|
|
"-nostdin", "-y",
|
|
"-loglevel", "error",
|
|
"-ss", d.start_time,
|
|
"-t", d.duration,
|
|
"-i", d.inpath,
|
|
"-c", "copy",
|
|
"-map", "0",
|
|
"-dn",
|
|
"-avoid_negative_ts", "make_zero",
|
|
utils.join_path(d.indir, "COPY_" .. d.channel .. "_" .. d.infile_noext .. "_FROM_" .. d.start_time_hms .. "_TO_" .. d.end_time_hms .. d.ext)
|
|
}
|
|
mp.command_native_async({
|
|
name = "subprocess",
|
|
args = args,
|
|
playback_only = false,
|
|
}, function() print("Done") end)
|
|
end
|
|
|
|
ACTIONS.ENCODE = function(d)
|
|
local args = {
|
|
"ffmpeg",
|
|
"-nostdin", "-y",
|
|
"-loglevel", "error",
|
|
"-ss", d.start_time,
|
|
"-t", d.duration,
|
|
"-i", d.inpath,
|
|
"-pix_fmt", "yuv420p",
|
|
"-crf", "16",
|
|
"-preset", "superfast",
|
|
utils.join_path(d.indir, "ENCODE_" .. d.channel .. "_" .. d.infile_noext .. "_FROM_" .. d.start_time_hms .. "_TO_" .. d.end_time_hms .. d.ext)
|
|
}
|
|
mp.command_native_async({
|
|
name = "subprocess",
|
|
args = args,
|
|
playback_only = false,
|
|
}, function() print("Done") end)
|
|
end
|
|
|
|
ACTIONS.LIST = function(d)
|
|
local inpath = mp.get_property("path")
|
|
local outpath = inpath .. ".list"
|
|
local file = io.open(outpath, "a")
|
|
if not file then print("Error writing to cut list") return end
|
|
local filesize = file:seek("end")
|
|
local s = "\n" .. d.channel
|
|
.. ":" .. d.start_time
|
|
.. ":" .. d.end_time
|
|
file:write(s)
|
|
local delta = file:seek("end") - filesize
|
|
io.close(file)
|
|
print("Δ " .. delta)
|
|
end
|
|
|
|
ACTION = "COPY"
|
|
|
|
CHANNEL = 1
|
|
|
|
CHANNEL_NAMES = {}
|
|
|
|
KEY_CUT = "c"
|
|
KEY_CANCEL_CUT = "C"
|
|
KEY_CYCLE_ACTION = "a"
|
|
KEY_BOOKMARK_ADD = "i"
|
|
KEY_CHANNEL_INC = "="
|
|
KEY_CHANNEL_DEC = "-"
|
|
|
|
home_config = mp.command_native({"expand-path", "~/.config/mpv-cut/config.lua"})
|
|
if pcall(require, "config") then
|
|
mp.msg.info("Loaded config file from script dir")
|
|
elseif pcall(dofile, home_config) then
|
|
mp.msg.info("Loaded config file from " .. home_config)
|
|
else
|
|
mp.msg.info("No config loaded")
|
|
end
|
|
|
|
for i, v in ipairs(CHANNEL_NAMES) do
|
|
CHANNEL_NAMES[i] = string.gsub(v, ":", "-")
|
|
end
|
|
|
|
if not ACTIONS[ACTION] then ACTION = next_table_key(ACTIONS, nil) end
|
|
|
|
START_TIME = nil
|
|
|
|
local function get_current_channel_name()
|
|
return CHANNEL_NAMES[CHANNEL] or tostring(CHANNEL)
|
|
end
|
|
|
|
local function get_data()
|
|
local d = {}
|
|
d.inpath = mp.get_property("path")
|
|
d.indir = utils.split_path(d.inpath)
|
|
d.infile = mp.get_property("filename")
|
|
d.infile_noext = mp.get_property("filename/no-ext")
|
|
d.ext = mp.get_property("filename"):match("^.+(%..+)$") or ".mp4"
|
|
d.channel = get_current_channel_name()
|
|
return d
|
|
end
|
|
|
|
local function get_times(start_time, end_time)
|
|
local d = {}
|
|
d.start_time = tostring(start_time)
|
|
d.end_time = tostring(end_time)
|
|
d.duration = tostring(end_time - start_time)
|
|
d.start_time_hms = tostring(to_hms(start_time))
|
|
d.end_time_hms = tostring(to_hms(end_time))
|
|
d.duration_hms = tostring(to_hms(end_time - start_time))
|
|
return d
|
|
end
|
|
|
|
text_overlay = mp.create_osd_overlay("ass-events")
|
|
text_overlay.hidden = true
|
|
text_overlay:update()
|
|
|
|
local function text_overlay_off()
|
|
-- https://github.com/mpv-player/mpv/issues/10227
|
|
text_overlay:update()
|
|
text_overlay.hidden = true
|
|
text_overlay:update()
|
|
end
|
|
|
|
local function text_overlay_on()
|
|
local channel = get_current_channel_name()
|
|
text_overlay.data = string.format("%s in %s from %s", ACTION, channel, START_TIME)
|
|
text_overlay.hidden = false
|
|
text_overlay:update()
|
|
end
|
|
|
|
local function print_or_update_text_overlay(content)
|
|
if START_TIME then text_overlay_on() else print(content) end
|
|
end
|
|
|
|
local function cycle_action()
|
|
ACTION = next_table_key(ACTIONS, ACTION)
|
|
print_or_update_text_overlay("ACTION: " .. ACTION)
|
|
end
|
|
|
|
local function cut(start_time, end_time)
|
|
local d = get_data()
|
|
local t = get_times(start_time, end_time)
|
|
for k, v in pairs(t) do d[k] = v end
|
|
mp.msg.info(ACTION)
|
|
mp.msg.info(table_to_str(d))
|
|
ACTIONS[ACTION](d)
|
|
end
|
|
|
|
local function put_time()
|
|
local time = mp.get_property_number("time-pos")
|
|
if not START_TIME then
|
|
START_TIME = time
|
|
text_overlay_on()
|
|
return
|
|
end
|
|
text_overlay_off()
|
|
if time > START_TIME then
|
|
cut(START_TIME, time)
|
|
START_TIME = nil
|
|
else
|
|
print("INVALID")
|
|
START_TIME = nil
|
|
end
|
|
end
|
|
|
|
local function cancel_cut()
|
|
text_overlay_off()
|
|
START_TIME = nil
|
|
print("CANCELLED CUT")
|
|
end
|
|
|
|
local function get_bookmark_file_path()
|
|
local d = get_data()
|
|
mp.msg.info(table_to_str(d))
|
|
local outfile = string.format("%s_%s.book", d.channel, d.infile)
|
|
return utils.join_path(d.indir, outfile)
|
|
end
|
|
|
|
local function bookmarks_load()
|
|
local inpath = get_bookmark_file_path()
|
|
local file = io.open(inpath, "r")
|
|
if not file then return end
|
|
local arr = {}
|
|
for line in file:lines() do
|
|
if tonumber(line) then
|
|
table.insert(arr, {
|
|
time = tonumber(line),
|
|
title = "chapter_" .. line
|
|
})
|
|
end
|
|
end
|
|
file:close()
|
|
table.sort(arr, function(a, b) return a.time < b.time end)
|
|
mp.set_property_native("chapter-list", arr)
|
|
end
|
|
|
|
local function bookmark_add()
|
|
local d = get_data()
|
|
local outpath = get_bookmark_file_path()
|
|
local file = io.open(outpath, "a")
|
|
if not file then print("Failed to open bookmark file for writing") return end
|
|
local out_string = mp.get_property_number("time-pos") .. "\n"
|
|
local filesize = file:seek("end")
|
|
file:write(out_string)
|
|
local delta = file:seek("end") - filesize
|
|
io.close(file)
|
|
bookmarks_load()
|
|
print(string.format("Δ %s, %s", delta, d.channel))
|
|
end
|
|
|
|
local function channel_inc()
|
|
CHANNEL = CHANNEL + 1
|
|
bookmarks_load()
|
|
print_or_update_text_overlay(get_current_channel_name())
|
|
end
|
|
|
|
local function channel_dec()
|
|
if CHANNEL >= 2 then CHANNEL = CHANNEL - 1 end
|
|
bookmarks_load()
|
|
print_or_update_text_overlay(get_current_channel_name())
|
|
end
|
|
|
|
mp.add_key_binding(KEY_CUT, "cut", put_time)
|
|
mp.add_key_binding(KEY_CANCEL_CUT, "cancel_cut", cancel_cut)
|
|
mp.add_key_binding(KEY_BOOKMARK_ADD, "bookmark_add", bookmark_add)
|
|
mp.add_key_binding(KEY_CHANNEL_INC, "channel_inc", channel_inc)
|
|
mp.add_key_binding(KEY_CHANNEL_DEC, "channel_dec", channel_dec)
|
|
mp.add_key_binding(KEY_CYCLE_ACTION, "cycle_action", cycle_action)
|
|
|
|
mp.register_event('file-loaded', bookmarks_load)
|