Skip to content

Commit

Permalink
Merge pull request #7 from idolactivities/feature/remove_split_filter
Browse files Browse the repository at this point in the history
Remove split filter to reduce RAM usage
  • Loading branch information
lae authored Nov 23, 2020
2 parents 090a172 + 85dc332 commit 72332c9
Showing 1 changed file with 61 additions and 116 deletions.
177 changes: 61 additions & 116 deletions clipper/clipper-linux.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,40 +63,40 @@ function id_colorspace(video)
end
end

function encode_cmd(video, ss, to, options, filter, output, logfile)
local command = {
('%q'):format(FFMPEG), ('-ss %s -to %s -i %q -copyts'):format(ss, to, video), options,
('-filter_complex %q -map "[vo]" -map "[ao]"'):format(filter),
('-color_primaries %s -color_trc %s -colorspace %s'):format(id_colorspace(video)), ('%q'):format(output),
('2> %q'):format(logfile)
}
function build_encode_cmd(video, segment_inputs, subs_path, hardsub, options, output, logfile)
local command = {('%q'):format(FFMPEG)}

-- add a input video read offset if our video FPS is 50+ to fix off-by-one
-- frame errors in 60FPS videos (haven't tested on 50FPS videos though, I
-- just picked a number close to 17)
local frame_ms = aegisub.ms_from_frame(1) - aegisub.ms_from_frame(0)
if frame_ms <= 20 then table.insert(command, 2, ('-itsoffset -%0.3f'):format(frame_ms / 1000)) end
local itsoffset = ''
if frame_ms <= 20 then itsoffset = ('-itsoffset -%0.3f'):format(frame_ms / 1000) end

-- identify earliest and latest points in the clip so that we can limit
-- reading the input file to just the section we need (++execution speed)
for _, segments in ipairs(segment_inputs) do
local seek_start = math.floor(segments[1][1] / 1000)
local seek_end = math.ceil(segments[#segments][2] / 1000)
table.insert(command, itsoffset)
table.insert(command, ('-ss %s -to %s -i %q -copyts'):format(seek_start, seek_end, video))
end

table.insert(command, options)
local filter = filter_complex(segment_inputs, subs_path, hardsub)
table.insert(command, ('-filter_complex %q -map "[vo]" -map "[ao]"'):format(filter))
table.insert(command, ('-color_primaries %s -color_trc %s -colorspace %s'):format(id_colorspace(video)))
table.insert(command, ('%q'):format(output))
table.insert(command, ('2> %q'):format(logfile))

return table.concat(command, ' ')
end

function build_segments(sub, sel, explicit)
-- default to true if unspecified, for explicitly defined segments
local explicit = explicit == nil and true or explicit

function build_segments(sub, sel)
local line_timings = {}
if explicit then
for _, si in ipairs(sel) do
line = sub[si]
if line.end_time > line.start_time then
table.insert(line_timings, {line.start_time, line.end_time})
end
end
else
for _, si in ipairs(sel) do
line = sub[si]
if not line.comment and line.end_time > line.start_time then
table.insert(line_timings, {line.start_time, line.end_time})
end
end
for _, si in ipairs(sel) do
line = sub[si]
if line.end_time > line.start_time then table.insert(line_timings, {line.start_time, line.end_time}) end
end

-- used for merging segments right after one another
Expand All @@ -106,48 +106,24 @@ function build_segments(sub, sel, explicit)
-- list of timings for each segment input with the first line timing
local segment_inputs = {}
local segments = {{line_timings[1][1], line_timings[1][2]}}
if explicit then
for i = 2, #line_timings do
local previous_end = segments[#segments][2]
if line_timings[i][1] >= previous_end and line_timings[i][1] - previous_end <= frame_duration then
-- merge lines if they're right after one another
segments[#segments][2] = line_timings[i][2]
elseif line_timings[i][1] > previous_end then
-- add a timing within the current segment since it falls after
-- earlier defined segments
table.insert(segments, {line_timings[i][1], line_timings[i][2]})
else
-- if a line goes backwards, save the current segment input and
-- initialize a new list of segments
table.insert(segment_inputs, segments)
segments = {{line_timings[i][1], line_timings[i][2]}}
end
end
-- save our last segment input once all line timings have been read
table.insert(segment_inputs, segments)
else
for i = 2, #line_timings do
local previous_end = segments[#segments][2]
if line_timings[i][1] >= previous_end and line_timings[i][1] - previous_end <= 500 then
-- merge lines with less than 500ms gap
segments[#segments][2] = line_timings[i][2]
-- elseif tlines[i][2] <= previous_end then
-- skip since the line overlaps with an earlier line
-- need to identify whether or not selected line is within current segment
elseif line_timings[i][1] > previous_end then
-- add a timing within the current segment since it falls after
-- earlier defined segments
table.insert(segments, {line_timings[i][1], line_timings[i][2]})
else
-- if a line goes backwards, save the current segment input and
-- initialize a new list of segments
table.insert(segment_inputs, segments)
segments = {{line_timings[i][1], line_timings[i][2]}}
end
for i = 2, #line_timings do
local previous_end = segments[#segments][2]
if line_timings[i][1] >= previous_end and line_timings[i][1] - previous_end <= frame_duration then
-- merge lines if they're right after one another
segments[#segments][2] = line_timings[i][2]
elseif line_timings[i][1] > previous_end and line_timings[i][1] - previous_end <= 60000 then
-- add a timing within the current segment since it falls after
-- earlier defined segments, but within a minute of the next
table.insert(segments, {line_timings[i][1], line_timings[i][2]})
else
-- if a line goes backwards, or is a minute away, save the
-- current segment input and initialize a new list of segments
table.insert(segment_inputs, segments)
segments = {{line_timings[i][1], line_timings[i][2]}}
end
-- save our last segment input once all line timings have been read
table.insert(segment_inputs, segments)
end
-- save our last segment input once all line timings have been read
table.insert(segment_inputs, segments)

return segment_inputs
end
Expand Down Expand Up @@ -197,46 +173,18 @@ end

function filter_complex(inputs, subtitle_file, hardsub)
local input_ids = {}
for i = 1, #inputs do table.insert(input_ids, ("%03d"):format(i)) end
for i = 1, #inputs do table.insert(input_ids, ("%03d"):format(i - 1)) end

-- contains all of the filters needed to pass through filter_complex
local filters = {}

-- initial filter for the input video
local vin_filter = {
"[0:v]", -- input video
"format=pix_fmts=rgb32," -- convert input video to raw before hardsubbing
}

-- apply ASS subtitle filter for hardsub
if hardsub then table.insert(vin_filter, ("ass='%s',"):format(subtitle_file)) end

table.insert(vin_filter, ("split=%d"):format(#inputs)) -- duplicate input video into several

-- specify video outputs for the split filter above
for _, id in ipairs(input_ids) do table.insert(vin_filter, ("[v%s]"):format(id)) end
table.insert(filters, table.concat(vin_filter))

-- initial filter for the input audio
local ain_filter = {
"[0:a]", -- input audio
("asplit=%d"):format(#inputs) -- duplicate input audio into several
}
-- specify audio outputs for the split filter above
for _, id in ipairs(input_ids) do table.insert(ain_filter, ("[a%s]"):format(id)) end
table.insert(filters, table.concat(ain_filter))

-- start building out inputs list for the final concat filter
local trimmed_vins, trimmed_ains = '', ''
-- create a/v filters for trimming segments
for i = 1, #inputs do
local id = input_ids[i]
local segments = inputs[i]

-- specify inputs for the concat filter to apply at the end
trimmed_vins = trimmed_vins .. ("[v%st]"):format(id)
trimmed_ains = trimmed_ains .. ("[a%st]"):format(id)

-- build expression to pass to the select/aselect filters
local selects, selects_sep = '', ''
for _, segment in ipairs(segments) do
Expand All @@ -246,22 +194,30 @@ function filter_complex(inputs, subtitle_file, hardsub)
selects_sep = '+'
end

-- specify inputs for the concat filter to apply at the end
trimmed_vins = trimmed_vins .. ("[v%st]"):format(id)
trimmed_ains = trimmed_ains .. ("[a%st]"):format(id)

-- https://ffmpeg.org/ffmpeg-filters.html#select_002c-aselect
local vsel_filter = {
("[v%s]"):format(id), -- video segment id

local v_filter = {
("[%s:v]"):format(i - 1), -- input video
"format=pix_fmts=rgb32,", -- convert input video to raw before hardsubbing
("select='%s',"):format(selects), -- the filter that trims the input
"setpts=N/FRAME_RATE/TB", -- constructs correct timestamps for output
("[v%st]"):format(id) -- trimmed video output for current segment to concat later
}
table.insert(filters, table.concat(vsel_filter))
-- apply ASS subtitle filter for hardsub
if hardsub then table.insert(v_filter, 3, ("ass='%s',"):format(subtitle_file)) end
table.insert(filters, table.concat(v_filter))

local asel_filter = {
("[a%s]"):format(id), -- audio segment id
local a_filter = {
("[%s:a]"):format(i - 1), -- input audio
("aselect='%s',"):format(selects), -- the filter that trims the input
"asetpts=N/SR/TB", -- constructs correct timestamps for output
("[a%st]"):format(id) -- trimmed audio output for current segment to concat later
}
table.insert(filters, table.concat(asel_filter))
table.insert(filters, table.concat(a_filter))
end

vconcat_filter = {
Expand Down Expand Up @@ -315,7 +271,7 @@ function macro_export_subtitle(subs, sel, _)

if file_exists(output_path) then confirm_overwrite(output_path) end

local segment_inputs = build_segments(subs, sel, true)
local segment_inputs = build_segments(subs, sel)
local subs_adjusted = retime_subtitles(subs, segment_inputs)
save_subtitles(subs_adjusted, output_path)
end
Expand Down Expand Up @@ -356,21 +312,10 @@ function macro_clipper(subs, sel, _)
end
local logfile_path = output_path .. '_encode.log'

local segment_inputs = build_segments(subs, sel, true)
local filter = filter_complex(segment_inputs, subs_path, hardsub)

-- identify earliest and latest points in the clip so that we can limit
-- reading the input file to just the section we need (++execution speed)
local seek_start = math.floor(segment_inputs[1][1][1] / 1000)
local seek_end = seek_start
for _, segments in ipairs(segment_inputs) do
local segment_start = math.floor(segments[1][1] / 1000)
local segment_end = math.ceil(segments[#segments][2] / 1000)
if seek_start > segment_start then seek_start = segment_start end
if seek_end < segment_end then seek_end = segment_end end
end
local segment_inputs = build_segments(subs, sel)

local encode_cmd = encode_cmd(video_path, seek_start, seek_end, options, filter, output_path, logfile_path)
local encode_cmd = build_encode_cmd(video_path, segment_inputs, subs_path, hardsub, options, output_path,
logfile_path)
aegisub.debug.out(encode_cmd .. ('\n\nFor command output, please see the log file at %q\n\n'):format(logfile_path))
res = os.execute(encode_cmd)

Expand Down

0 comments on commit 72332c9

Please sign in to comment.