Skip to content

Commit f637706

Browse files
committed
add slicing_copy.lua
1 parent 20228b3 commit f637706

File tree

2 files changed

+233
-0
lines changed

2 files changed

+233
-0
lines changed

Diff for: README.md

+6
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ This script skips to the next silence in the file. The intended use for this is
186186

187187
Modified from [detuur-mpv-scripts/skiptosilence.lua](https://github.com/Eisa01/detuur-mpv-scripts/blob/master/skiptosilence.lua)
188188

189+
## slicing_copy.lua
190+
191+
This script is for mpv to cut fragments of the video.. Requires that FFmpeg be installed.
192+
193+
Modified from [snylonue/mpv_slicing_copy](https://github.com/snylonue/mpv_slicing_copy)
194+
189195
## sponsorblock_minimal.lua
190196

191197
This script skip/mute sponsored segments of YouTube and bilibili videos

Diff for: slicing_copy.lua

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
local msg = require "mp.msg"
2+
local utils = require "mp.utils"
3+
local options = require "mp.options"
4+
5+
local cut_pos = nil
6+
local copy_audio = true
7+
local ext_map = {
8+
["mpegts"] = "ts",
9+
}
10+
local o = {
11+
ffmpeg_path = "ffmpeg",
12+
target_dir = "~~/cutfragments",
13+
overwrite = false, -- whether to overwrite exist files
14+
vcodec = "copy",
15+
acodec = "copy",
16+
debug = false,
17+
}
18+
19+
options.read_options(o)
20+
21+
Command = {}
22+
23+
local function is_protocol(path)
24+
return type(path) == 'string' and (path:find('^%a[%w.+-]-://') ~= nil or path:find('^%a[%w.+-]-:%?') ~= nil)
25+
end
26+
27+
function Command:new(name)
28+
local o = {}
29+
setmetatable(o, self)
30+
self.__index = self
31+
o.name = ""
32+
o.args = { "" }
33+
if name then
34+
o.name = name
35+
o.args[1] = name
36+
end
37+
return o
38+
end
39+
function Command:arg(...)
40+
for _, v in ipairs({...}) do
41+
self.args[#self.args + 1] = v
42+
end
43+
return self
44+
end
45+
function Command:as_str()
46+
return table.concat(self.args, " ")
47+
end
48+
function Command:run()
49+
local res, err = mp.command_native({
50+
name = "subprocess",
51+
args = self.args,
52+
capture_stdout = true,
53+
capture_stderr = true,
54+
})
55+
return res, err
56+
end
57+
58+
local function file_format()
59+
local fmt = mp.get_property("file-format")
60+
if not fmt:find(',') then
61+
return fmt
62+
end
63+
local path = mp.get_property('path')
64+
if is_protocol(path) then
65+
return nil
66+
end
67+
local filename = mp.get_property('filename')
68+
return filename:match('%.([^.]+)$')
69+
end
70+
71+
local function get_ext()
72+
local fmt = file_format()
73+
if fmt and ext_map[fmt] ~= nil then
74+
return ext_map[fmt]
75+
else
76+
return fmt
77+
end
78+
end
79+
80+
local function timestamp(duration)
81+
local hours = math.floor(duration / 3600)
82+
local minutes = math.floor(duration % 3600 / 60)
83+
local seconds = duration % 60
84+
return string.format("%02d:%02d:%06.3f", hours, minutes, seconds)
85+
end
86+
87+
local function osd(str)
88+
return mp.osd_message(str, 3)
89+
end
90+
91+
local function info(s)
92+
msg.info(s)
93+
osd(s)
94+
end
95+
96+
local function get_outname(path, shift, endpos)
97+
local name = mp.get_property("filename/no-ext")
98+
if is_protocol(path) then
99+
name = mp.get_property("media-title")
100+
end
101+
local ext = get_ext() or "mkv"
102+
name = string.format("%s_%s-%s.%s", name, timestamp(shift), timestamp(endpos), ext)
103+
return name:gsub(":", "-")
104+
end
105+
106+
local function cut(shift, endpos)
107+
local duration = endpos - shift
108+
local path = mp.get_property("path")
109+
local inpath = mp.get_property("stream-open-filename")
110+
local outpath = utils.join_path(
111+
o.target_dir,
112+
get_outname(path, shift, endpos)
113+
)
114+
115+
local cache = mp.get_property_native("cache")
116+
local cache_state = mp.get_property_native("demuxer-cache-state")
117+
local cache_ranges = cache_state and cache_state["seekable-ranges"] or {}
118+
if path and is_protocol(path) or cache == "auto" and #cache_ranges > 0 then
119+
local pid = mp.get_property_native('pid')
120+
local temp_path = os.getenv("TEMP") or "/tmp/"
121+
local temp_video_file = utils.join_path(temp_path, "mpv_dump_" .. pid .. ".mkv")
122+
mp.commandv("dump-cache", shift, endpos, temp_video_file)
123+
shift = 0
124+
inpath = temp_video_file
125+
end
126+
127+
local cmds = Command:new(o.ffmpeg_path)
128+
:arg("-v", "warning")
129+
:arg(o.overwrite and "-y" or "-n")
130+
:arg("-stats")
131+
cmds:arg("-ss", tostring(shift))
132+
cmds:arg("-accurate_seek")
133+
cmds:arg("-i", inpath)
134+
cmds:arg("-t", tostring(duration))
135+
cmds:arg("-c:v", o.vcodec)
136+
cmds:arg("-c:a", o.acodec)
137+
cmds:arg("-c:s", "copy")
138+
cmds:arg("-map", string.format("v:%s?", math.max(mp.get_property_number("current-tracks/video/id", 0) - 1, 0)))
139+
cmds:arg("-map", string.format("a:%s?", math.max(mp.get_property_number("current-tracks/audio/id", 0) - 1, 0)))
140+
cmds:arg("-map", string.format("s:%s?", math.max(mp.get_property_number("current-tracks/sub/id", 0) - 1, 0)))
141+
cmds:arg(not copy_audio and "-an" or nil)
142+
cmds:arg("-avoid_negative_ts", "make_zero")
143+
cmds:arg("-async", "1")
144+
cmds:arg(outpath)
145+
msg.info("Run commands: " .. cmds:as_str())
146+
local screenx, screeny, aspect = mp.get_osd_size()
147+
mp.set_osd_ass(screenx, screeny, "{\\an9}● ")
148+
local res, err = cmds:run()
149+
mp.set_osd_ass(screenx, screeny, "")
150+
if err then
151+
msg.error(utils.to_string(err))
152+
mp.osd_message("Failed. Refer console for details.")
153+
elseif res.status ~= 0 then
154+
if res.stderr ~= "" or res.stdout ~= "" then
155+
msg.info("stderr: " .. (res.stderr:gsub("^%s*(.-)%s*$", "%1"))) -- trim stderr
156+
msg.info("stdout: " .. (res.stdout:gsub("^%s*(.-)%s*$", "%1"))) -- trim stdout
157+
mp.osd_message("Failed. Refer console for details.")
158+
end
159+
elseif res.status == 0 then
160+
if o.debug and (res.stderr ~= "" or res.stdout ~= "") then
161+
msg.info("stderr: " .. (res.stderr:gsub("^%s*(.-)%s*$", "%1"))) -- trim stderr
162+
msg.info("stdout: " .. (res.stdout:gsub("^%s*(.-)%s*$", "%1"))) -- trim stdout
163+
end
164+
msg.info("Trim file successfully created: " .. outpath)
165+
mp.add_timeout(1, function()
166+
mp.osd_message("Trim file successfully created!")
167+
end)
168+
end
169+
end
170+
171+
local function toggle_mark()
172+
local pos, err = mp.get_property_number("time-pos")
173+
if not pos then
174+
osd("Failed to get timestamp")
175+
msg.error("Failed to get timestamp: " .. err)
176+
return
177+
end
178+
if cut_pos then
179+
local shift, endpos = cut_pos, pos
180+
if shift > endpos then
181+
shift, endpos = endpos, shift
182+
elseif shift == endpos then
183+
osd("Cut fragment is empty")
184+
return
185+
end
186+
cut_pos = nil
187+
info(string.format("Cut fragment: %s-%s", timestamp(shift), timestamp(endpos)))
188+
cut(shift, endpos)
189+
else
190+
cut_pos = pos
191+
info(string.format("Marked %s as start position", timestamp(pos)))
192+
end
193+
end
194+
195+
local function toggle_audio()
196+
copy_audio = not copy_audio
197+
info("Audio capturing is " .. (copy_audio and "enabled" or "disabled"))
198+
end
199+
200+
local function clear_toggle_mark()
201+
cut_pos = nil
202+
info("Cleared cut fragment")
203+
end
204+
205+
o.target_dir = o.target_dir:gsub('"', "")
206+
local file, _ = utils.file_info(mp.command_native({ "expand-path", o.target_dir }))
207+
if not file then
208+
--create target_dir if it doesn't exist
209+
local savepath = mp.command_native({ "expand-path", o.target_dir })
210+
local is_windows = package.config:sub(1, 1) == "\\"
211+
local windows_args = { 'powershell', '-NoProfile', '-Command', 'mkdir', string.format("\"%s\"", savepath) }
212+
local unix_args = { 'mkdir', '-p', savepath }
213+
local args = is_windows and windows_args or unix_args
214+
local res = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = args})
215+
if res.status ~= 0 then
216+
msg.error("Failed to create target_dir save directory "..savepath..". Error: "..(res.error or "unknown"))
217+
return
218+
end
219+
elseif not file.is_dir then
220+
osd("target_dir is a file")
221+
msg.warn(string.format("target_dir `%s` is a file", o.target_dir))
222+
end
223+
o.target_dir = mp.command_native({ "expand-path", o.target_dir })
224+
225+
mp.add_key_binding("c", "slicing_mark", toggle_mark)
226+
mp.add_key_binding("a", "slicing_audio", toggle_audio)
227+
mp.add_key_binding("C", "clear_slicing_mark", clear_toggle_mark)

0 commit comments

Comments
 (0)