-- mpv-lossless-cut MODIFIED - REVERSED MODE
-- Markierte Bereiche werden ENTFERNT (nicht behalten)
mp.msg = require("mp.msg")
mp.utils = require("mp.utils")
mp.options = require("mp.options")
local options = {
output_dir = ".",
}
mp.options.read_options(options, "mpv-lossless-cut")
local cuts = {}
local os_name = package.config:sub(1, 1) == "\\" and "windows"
or (io.popen("uname"):read("*a"):match("Darwin") and "mac" or "linux")
local function log(message)
mp.msg.info(message)
mp.osd_message(message, 4)
end
local function fmt_time(secs)
local h = math.floor(secs / 3600)
local m = math.floor((secs % 3600) / 60)
local s = secs % 60
return string.format('%02d:%02d:%05.2f', h, m, s)
end
local function fmt_time_raw(secs)
return string.format("%.6f", secs)
end
local function write_timestamp_files()
local outdir = mp.get_property("working-directory")
if not outdir or outdir == "" then return end
local file = io.open(join_paths(outdir, "cuts.txt"), "w")
if file then
for _, c in ipairs(cuts) do
file:write(fmt_time_raw(c.start))
if c["end"] then
file:write(" " .. fmt_time_raw(c["end"]))
end
file:write("\n")
end
file:close()
mp.msg.info("Timestamps gespeichert: cuts.txt")
end
end
function join_paths(path1, path2)
if not path1 or path1 == "" then return path2 or "" end
if not path2 or path2 == "" then return path1 end
local sep = os_name == "windows" and "\\" or "/"
path1 = path1:gsub("[/\\]", sep):gsub(sep .. "+$", "")
if path2:match("^[A-Za-z]:") or path2:match("^" .. sep) then return path2 end
return path1 .. sep .. path2
end
local function ensure_dir(dir)
local args = os_name == "windows" and { "cmd", "/c", "mkdir", dir } or { "mkdir", "-p", dir }
return mp.utils.subprocess({ args = args, cancellable = false }).status == 0
end
local function run_ffmpeg(args)
local base = { "ffmpeg", "-nostdin", "-loglevel", "error", "-y" }
for _, a in ipairs(args) do table.insert(base, a) end
print("FFMPEG: " .. table.concat(base, " "))
return mp.utils.subprocess({ args = base, cancellable = false }).status == 0
end
local cut_overlay = nil
local overlay_visible = true
local function update_overlay()
if not overlay_visible then
if cut_overlay then
cut_overlay:remove()
cut_overlay = nil
end
return
end
if #cuts == 0 then
if cut_overlay then
cut_overlay:remove()
cut_overlay = nil
end
return
end
local dur = mp.get_property_number("duration")
if not dur or dur == 0 then return end
table.sort(cuts, function(a, b) return a.start < b.start end)
local tag = "{\\an9}{\\fs16}{\\cFFFFFF}{\\3c&H000000&}"
local out = {}
table.insert(out, tag .. "========= CUTS =========")
local total = 0
for i, c in ipairs(cuts) do
local d = (c["end"] or c.start) - c.start
total = total + d
local line = tag .. "[" .. i .. "] " .. fmt_time(c.start)
if c["end"] then
line = line .. " -> " .. fmt_time(c["end"])
else
line = line .. " -> "
end
table.insert(out, line)
end
table.insert(out, tag .. "---------------")
table.insert(out, tag .. fmt_time(dur) .. " - " .. string.format("%.0fs", total) .. " = " .. fmt_time(dur - total))
local data = table.concat(out, "\\N")
if not cut_overlay then
cut_overlay = mp.create_osd_overlay("ass-events")
end
if cut_overlay then
cut_overlay.data = data
cut_overlay:update()
end
end
mp.add_periodic_timer(5, update_overlay)
local function show_cuts()
local dur = mp.get_property_number("duration") or 0
table.sort(cuts, function(a, b) return a.start < b.start end)
local msg = "=== CUTS (ENTFERNE) ===\n"
msg = msg .. "Video: " .. fmt_time(dur) .. "\n"
if #cuts == 0 then
msg = msg .. "Keine Cuts"
else
local total = 0
for i, c in ipairs(cuts) do
local d = c["end"] - c.start
total = total + d
msg = msg .. i .. ": " .. fmt_time(c.start) .. " -> " .. fmt_time(c["end"]) .. "\n"
end
msg = msg .. "Entferne: " .. string.format("%.1fs", total) .. "\n"
msg = msg .. "Ergebnis: " .. fmt_time(dur - total)
end
mp.osd_message(msg, 5)
end
local function cut_start()
local t = mp.get_property_number("time-pos")
if t then
table.insert(cuts, {start = t})
log("[START: " .. fmt_time(t) .. "]")
update_overlay()
write_timestamp_files()
end
end
local function cut_end()
if #cuts == 0 or cuts[#cuts]["end"] then
log("Kein Start gesetzt!")
return
end
cuts[#cuts]["end"] = mp.get_property_number("time-pos")
log("CUT: " .. fmt_time(cuts[#cuts].start) .. " - " .. fmt_time(cuts[#cuts]["end"]))
update_overlay()
write_timestamp_files()
end
local function cut_delete()
if #cuts > 0 then
local c = table.remove(cuts)
log("Geloescht: " .. fmt_time(c.start))
else
log("Nichts zu loeschen")
end
update_overlay()
write_timestamp_files()
end
local function cut_render()
if #cuts == 0 or not cuts[#cuts]["end"] then
log("Keine gueltigen Cuts!")
return
end
local input = mp.get_property("path")
local filename = mp.get_property("filename")
local dur = mp.get_property_number("duration") or 0
local outdir = mp.get_property("working-directory")
log("=== ENTFERNE MARKIERTE BEREICHE ===")
log("Input: " .. input)
table.sort(cuts, function(a, b) return a.start < b.start end)
-- Segmente berechnen die BEHALTEN werden
local keep = {}
local last = 0
for _, c in ipairs(cuts) do
if c.start > last then
table.insert(keep, {start = last, ["end"] = c.start})
log("Behalte: " .. fmt_time(last) .. " - " .. fmt_time(c.start))
end
last = c["end"]
end
if last < dur then
table.insert(keep, {start = last, ["end"] = dur})
log("Behalte: " .. fmt_time(last) .. " - " .. fmt_time(dur))
end
log("Segmente: " .. #keep)
if #keep == 0 then
log("Nichts zu behalten!")
return
end
local tmp = os.getenv("HOME") .. "/mpv_rev_" .. os.time()
ensure_dir(tmp)
-- Segmente exportieren
for i, k in ipairs(keep) do
local seg_file = tmp .. "/seg_" .. i .. ".mp4"
local duration = k["end"] - k.start
mp.osd_message(string.format("Segment %d/%d...", i, #keep), 999)
run_ffmpeg({
"-i", input,
"-ss", tostring(k.start),
"-t", tostring(duration),
"-c:v", "libx264", "-preset", "fast", "-crf", "18",
"-c:a", "aac", "-b:a", "192k",
"-movflags", "+faststart",
seg_file
})
end
-- Concat
local list_file = tmp .. "/list.txt"
local f = io.open(list_file, "w")
if f then
for i = 1, #keep do
f:write("file '" .. tmp .. "/seg_" .. i .. ".mp4'\n")
end
f:close()
end
local filename_noext = filename:match("^(.-)%.[^%.]+$") or filename
local out_file = join_paths(outdir, filename_noext .. "_cut.mp4")
mp.osd_message("Verbinde...", 999)
run_ffmpeg({
"-f", "concat", "-safe", "0", "-i", list_file,
"-c", "copy", out_file
})
os.execute("rm -rf " .. tmp)
log("Fertig: " .. out_file)
end
-- Key bindings
mp.add_key_binding("c", "cut_start", cut_start)
mp.add_key_binding("x", "cut_end", cut_end)
mp.add_key_binding("p", "cut_show", show_cuts)
mp.add_key_binding("d", "cut_delete", cut_delete)
mp.add_key_binding("z", "cut_render", cut_render)
mp.set_property("keep-open", "yes")
log("C=Start, X=Ende, P=Show, D=Delete, Z=Export")
mp.register_event("file-loaded", function()
cuts = {}
update_overlay()
end)
|