1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
-- slicing.lua — lossless video trimming via ffmpeg stream copy
--
-- Press the "set start" key once to mark A, then the "set end" key to mark B.
-- The marked [A, B] range is cut losslessly (-c copy) into a new file next to
-- the source, named "<name>_cut_<start>-<end>.<ext>". No re-encoding, so the
-- cut snaps to the nearest preceding keyframe (fast, but not frame-exact).
--
-- Default bindings (set in input.conf):
-- o script-binding slicing/set-start
-- O script-binding slicing/set-end
local mp = require "mp"
local msg = require "mp.msg"
local utils = require "mp.utils"
local start_time = nil
local function osd(text, dur)
mp.osd_message(text, dur or 3)
msg.info(text)
end
-- 12.345 -> "00:00:12.345" for filenames / logging
local function fmt(t)
local h = math.floor(t / 3600)
local m = math.floor((t % 3600) / 60)
local s = t % 60
return string.format("%02d:%02d:%06.3f", h, m, s)
end
-- whole-seconds token for filenames: 12.345 -> "12"
local function fmt_tag(t)
return string.format("%d", math.floor(t + 0.5))
end
local function set_start()
start_time = mp.get_property_number("time-pos")
if not start_time then return end
osd(("Cut start: %s"):format(fmt(start_time)))
end
local function set_end()
local end_time = mp.get_property_number("time-pos")
if not end_time then return end
if not start_time then
osd("Set the start point first (o)")
return
end
if end_time <= start_time then
osd("End point must be after the start point")
return
end
local path = mp.get_property("path")
if not path then
osd("No file is playing")
return
end
-- only local files can be stream-copied this way
if path:find("^%a[%w+.-]*://") then
osd("Cannot cut a stream (local files only)")
return
end
local dir, name = utils.split_path(path)
local stem, ext = name:match("^(.*)%.([^.]+)$")
if not stem then
stem, ext = name, "mkv"
end
local out = utils.join_path(dir, string.format(
"%s_cut_%s-%s.%s", stem, fmt_tag(start_time), fmt_tag(end_time), ext))
local duration = end_time - start_time
local args = {
"ffmpeg", "-y",
"-ss", string.format("%.3f", start_time),
"-i", path,
"-t", string.format("%.3f", duration),
"-map", "0",
"-c", "copy",
"-avoid_negative_ts", "make_zero",
out,
}
osd(("Cutting… %s → %s"):format(fmt(start_time), fmt(end_time)))
msg.info("ffmpeg: " .. table.concat(args, " "))
mp.command_native_async({
name = "subprocess",
args = args,
playback_only = false,
capture_stdout = true,
capture_stderr = true,
}, function(success, res, err)
if success and res and res.status == 0 then
local _, fname = utils.split_path(out)
osd(("Saved: %s"):format(fname))
start_time = nil
else
local detail = (res and res.stderr) or err or "unknown error"
osd("Cut failed (check console log)")
msg.error("ffmpeg failed: " .. tostring(detail))
end
end)
end
mp.add_key_binding(nil, "set-start", set_start)
mp.add_key_binding(nil, "set-end", set_end)
|