mirror of
https://gitlab.com/mbunkus/mkvtoolnix.git
synced 2024-12-24 11:54:01 +00:00
mkvmerge: splitting by parts based on frame/field numbers
Implements #819.
This commit is contained in:
parent
1e0b64418c
commit
5a152fccd5
@ -1,3 +1,9 @@
|
||||
2013-01-14 Moritz Bunkus <moritz@bunkus.org>
|
||||
|
||||
* mkvmerge: new feature: Implemented splitting by parts based on
|
||||
frame/field numbers ("--split parts-frames:" in
|
||||
mkvmerge). Implements #819.
|
||||
|
||||
2013-01-13 Moritz Bunkus <moritz@bunkus.org>
|
||||
|
||||
* mkvmerge: bug fix: Re-writing the track headers after they'd
|
||||
|
@ -33,12 +33,14 @@ split_point_t::str()
|
||||
const {
|
||||
return (boost::format("<%1% %2% once:%3% discard:%4% create_file:%5%>")
|
||||
% format_timecode(m_point)
|
||||
% ( split_point_t::SPT_DURATION == m_type ? "duration"
|
||||
: split_point_t::SPT_SIZE == m_type ? "size"
|
||||
: split_point_t::SPT_TIMECODE == m_type ? "timecode"
|
||||
: split_point_t::SPT_CHAPTER == m_type ? "chapter"
|
||||
: split_point_t::SPT_PARTS == m_type ? "part"
|
||||
: "unknown")
|
||||
% ( split_point_t::SPT_DURATION == m_type ? "duration"
|
||||
: split_point_t::SPT_SIZE == m_type ? "size"
|
||||
: split_point_t::SPT_TIMECODE == m_type ? "timecode"
|
||||
: split_point_t::SPT_CHAPTER == m_type ? "chapter"
|
||||
: split_point_t::SPT_PARTS == m_type ? "part"
|
||||
: split_point_t::SPT_PARTS_FRAME_FIELD == m_type ? "part(frame/field)"
|
||||
: split_point_t::SPT_FRAME_FIELD == m_type ? "frame/field"
|
||||
: "unknown")
|
||||
% m_use_once % m_discard % m_create_new_file).str();
|
||||
}
|
||||
|
||||
@ -170,7 +172,8 @@ cluster_helper_c::split_if_necessary(packet_cptr &packet) {
|
||||
&& (packet->assigned_timecode >= m_current_split_point->m_point))
|
||||
split_now = true;
|
||||
|
||||
else if ( (split_point_t::SPT_FRAME_FIELD == m_current_split_point->m_type)
|
||||
else if ( ( (split_point_t::SPT_FRAME_FIELD == m_current_split_point->m_type)
|
||||
|| (split_point_t::SPT_PARTS_FRAME_FIELD == m_current_split_point->m_type))
|
||||
&& (m_frame_field_number >= m_current_split_point->m_point))
|
||||
split_now = true;
|
||||
|
||||
@ -196,8 +199,9 @@ cluster_helper_c::split(packet_cptr &packet) {
|
||||
|
||||
if (m_current_split_point->m_use_once) {
|
||||
if ( m_current_split_point->m_discard
|
||||
&& (split_point_t::SPT_PARTS == m_current_split_point->m_type)
|
||||
&& (m_split_points.end() == (m_current_split_point + 1))) {
|
||||
&& ( (split_point_t::SPT_PARTS == m_current_split_point->m_type)
|
||||
|| (split_point_t::SPT_PARTS_FRAME_FIELD == m_current_split_point->m_type))
|
||||
&& (m_split_points.end() == (m_current_split_point + 1))) {
|
||||
mxdebug_if(m_debug_splitting, boost::format("Splitting: Last part in 'parts:' splitting mode finished\n"));
|
||||
m_splitting_and_processed_fully = true;
|
||||
}
|
||||
@ -685,7 +689,8 @@ cluster_helper_c::split_mode_produces_many_files()
|
||||
if (!splitting())
|
||||
return false;
|
||||
|
||||
if (m_split_points.front().m_type != split_point_t::SPT_PARTS)
|
||||
if ( (split_point_t::SPT_PARTS != m_split_points.front().m_type)
|
||||
&& (split_point_t::SPT_PARTS_FRAME_FIELD != m_split_points.front().m_type))
|
||||
return true;
|
||||
|
||||
bool first = true;
|
||||
|
@ -48,6 +48,7 @@ struct split_point_t {
|
||||
SPT_TIMECODE,
|
||||
SPT_CHAPTER,
|
||||
SPT_PARTS,
|
||||
SPT_PARTS_FRAME_FIELD,
|
||||
SPT_FRAME_FIELD,
|
||||
};
|
||||
|
||||
|
@ -121,6 +121,9 @@ set_usage() {
|
||||
" Keep ranges of timecodes start-end, either in\n"
|
||||
" separate files or append to previous range's file\n"
|
||||
" if prefixed with '+'.\n");
|
||||
usage_text += Y(" --split parts-frames:start1-end1[,[+]start2-end2,...]\n"
|
||||
" Same as 'parts:', but 'startN'/'endN' are frame/\n"
|
||||
" field numbers instead of timecodes.\n");
|
||||
usage_text += Y(" --split frames:A[,B...]\n"
|
||||
" Create a new file after each frame/field A, B\n"
|
||||
" etc.\n");
|
||||
@ -900,12 +903,16 @@ parse_arg_split_chapters(std::string const &arg) {
|
||||
}
|
||||
|
||||
static void
|
||||
parse_arg_split_parts(const std::string &arg) {
|
||||
parse_arg_split_parts(const std::string &arg,
|
||||
bool frames_fields) {
|
||||
std::string s = arg;
|
||||
|
||||
if (balg::istarts_with(s, "parts:"))
|
||||
s.erase(0, 6);
|
||||
|
||||
else if (balg::istarts_with(s, "parts-frames:"))
|
||||
s.erase(0, 13);
|
||||
|
||||
if (s.empty())
|
||||
mxerror(boost::format(Y("Missing start/end specifications for '--split' in '--split %1%'.\n")) % arg);
|
||||
|
||||
@ -925,35 +932,52 @@ parse_arg_split_parts(const std::string &arg) {
|
||||
int64_t start;
|
||||
if (pair[0].empty())
|
||||
start = split_points.empty() ? 0 : std::get<1>(split_points.back());
|
||||
else if (!parse_timecode(pair[0], start))
|
||||
|
||||
else if (!frames_fields && !parse_timecode(pair[0], start))
|
||||
mxerror(boost::format(Y("Invalid start time for '--split' in '--split %1%' (current part: %2%). Additional error message: %3%.\n")) % arg % part_spec % timecode_parser_error);
|
||||
|
||||
else if (frames_fields && (!parse_number(pair[0], start) || (0 > start)))
|
||||
mxerror(boost::format(Y("Invalid start frame/field number for '--split' in '--split %1%' (current part: %2%).\n")) % arg % part_spec);
|
||||
|
||||
int64_t end;
|
||||
if (pair[1].empty())
|
||||
end = std::numeric_limits<int64_t>::max();
|
||||
else if (!parse_timecode(pair[1], end))
|
||||
|
||||
else if (!frames_fields && !parse_timecode(pair[1], end))
|
||||
mxerror(boost::format(Y("Invalid end time for '--split' in '--split %1%' (current part: %2%). Additional error message: %3%.\n")) % arg % part_spec % timecode_parser_error);
|
||||
|
||||
if (end <= start)
|
||||
mxerror(boost::format(Y("Invalid end time for '--split' in '--split %1%' (current part: %2%). The end time must be bigger than the start time.\n")) % arg % part_spec);
|
||||
else if (frames_fields && (!parse_number(pair[1], end) || (0 > end)))
|
||||
mxerror(boost::format(Y("Invalid end frame for '--split' in '--split %1%' (current part: %2%).\n")) % arg % part_spec);
|
||||
|
||||
if (!split_points.empty() && (start < std::get<1>(split_points.back())))
|
||||
mxerror(boost::format(Y("Invalid start time for '--split' in '--split %1%' (current part: %2%). The start time must be bigger than or equal to the previous part's end time.\n")) % arg % part_spec);
|
||||
if (end <= start) {
|
||||
if (frames_fields)
|
||||
mxerror(boost::format(Y("Invalid end frame/field number for '--split' in '--split %1%' (current part: %2%). The end number must be bigger than the start number.\n")) % arg % part_spec);
|
||||
else
|
||||
mxerror(boost::format(Y("Invalid end time for '--split' in '--split %1%' (current part: %2%). The end time must be bigger than the start time.\n")) % arg % part_spec);
|
||||
}
|
||||
|
||||
if (!split_points.empty() && (start < std::get<1>(split_points.back()))) {
|
||||
if (frames_fields)
|
||||
mxerror(boost::format(Y("Invalid start frame/field number for '--split' in '--split %1%' (current part: %2%). The start number must be bigger than or equal to the previous part's end number.\n")) % arg % part_spec);
|
||||
else
|
||||
mxerror(boost::format(Y("Invalid start time for '--split' in '--split %1%' (current part: %2%). The start time must be bigger than or equal to the previous part's end time.\n")) % arg % part_spec);
|
||||
}
|
||||
|
||||
split_points.push_back(std::make_tuple(start, end, create_new_file));
|
||||
}
|
||||
|
||||
auto sp_type = frames_fields ? split_point_t::SPT_PARTS_FRAME_FIELD : split_point_t::SPT_PARTS;
|
||||
int64_t previous_end = 0;
|
||||
|
||||
for (auto &split_point : split_points) {
|
||||
if (previous_end < std::get<0>(split_point))
|
||||
g_cluster_helper->add_split_point(split_point_t{ previous_end, split_point_t::SPT_PARTS, true, true, std::get<2>(split_point) });
|
||||
g_cluster_helper->add_split_point(split_point_t{ std::get<0>(split_point), split_point_t::SPT_PARTS, true, false, std::get<2>(split_point) });
|
||||
g_cluster_helper->add_split_point(split_point_t{ previous_end, sp_type, true, true, std::get<2>(split_point) });
|
||||
g_cluster_helper->add_split_point(split_point_t{ std::get<0>(split_point), sp_type, true, false, std::get<2>(split_point) });
|
||||
previous_end = std::get<1>(split_point);
|
||||
}
|
||||
|
||||
if (std::get<1>(split_points.back()) < std::numeric_limits<int64_t>::max())
|
||||
g_cluster_helper->add_split_point(split_point_t{ std::get<1>(split_points.back()), split_point_t::SPT_PARTS, true, true });
|
||||
g_cluster_helper->add_split_point(split_point_t{ std::get<1>(split_points.back()), sp_type, true, true });
|
||||
}
|
||||
|
||||
/** \brief Parse the size format to \c --split
|
||||
@ -1028,7 +1052,10 @@ parse_arg_split(const std::string &arg) {
|
||||
parse_arg_split_timecodes(arg);
|
||||
|
||||
else if (balg::istarts_with(s, "parts:"))
|
||||
parse_arg_split_parts(arg);
|
||||
parse_arg_split_parts(arg, false);
|
||||
|
||||
else if (balg::istarts_with(s, "parts-frames:"))
|
||||
parse_arg_split_parts(arg, true);
|
||||
|
||||
else if (balg::istarts_with(s, "frames:"))
|
||||
parse_arg_split_frames(arg);
|
||||
|
@ -1147,7 +1147,8 @@ mmg_dialog::update_command_line() {
|
||||
: 2 == idx ? wxT("duration:")
|
||||
: 3 == idx ? wxT("timecodes:")
|
||||
: 4 == idx ? wxT("parts:")
|
||||
: 5 == idx ? wxT("frames:")
|
||||
: 5 == idx ? wxT("parts-frames:")
|
||||
: 6 == idx ? wxT("frames:")
|
||||
: wxT("chapters:");
|
||||
|
||||
clargs.Add(wxT("--split"));
|
||||
|
@ -248,6 +248,22 @@ tab_global::translate_split_args() {
|
||||
new_tool_tip = format_tooltip(wxU(join(" ", help)));
|
||||
|
||||
} else if (5 == mode) {
|
||||
st_split_args->SetLabel(Z("Parts:"));
|
||||
std::vector<std::string> help = {
|
||||
Y("A comma-separated list of frame/field number ranges of content to keep."),
|
||||
Y("Each range consists of a start and end frame/field number with a '-' in the middle, e.g. '157-238'."),
|
||||
Y("The numbering starts at 1."),
|
||||
Y("This mode considers only the first video track that is output."),
|
||||
Y("If no video track is output no splitting will occur."),
|
||||
Y("The numbers given with this argument are interpreted based on the number of Matroska blocks that are output."),
|
||||
Y("A single Matroska block contains either a full frame (for progressive content) or a single field (for interlaced content)."),
|
||||
Y("mkvmerge does not distinguish between those two and simply counts the number of blocks."),
|
||||
Y("If a start number is left out then the previous range's end number is used, or the start of the file if there was no previous range."),
|
||||
Y("If a range's start number is prefixed with '+' then its content will be written to the same file as the previous range. Otherwise a new file will be created for this range."),
|
||||
};
|
||||
new_tool_tip = format_tooltip(wxU(join(" ", help)));
|
||||
|
||||
} else if (6 == mode) {
|
||||
st_split_args->SetLabel(Z("Frames/fields:"));
|
||||
std::vector<std::string> help = {
|
||||
Y("A comma-separated list of frame/field numbers after which to split."),
|
||||
@ -260,7 +276,7 @@ tab_global::translate_split_args() {
|
||||
};
|
||||
new_tool_tip = format_tooltip(wxU(join(" ", help)));
|
||||
|
||||
} else if (6 == mode) {
|
||||
} else if (7 == mode) {
|
||||
st_split_args->SetLabel(Z("Chapter numbers:"));
|
||||
std::vector<std::string> help = {
|
||||
Y("Either the word 'all' which selects all chapters or a comma-separated list of chapter numbers after which to split."),
|
||||
@ -298,7 +314,8 @@ tab_global::translate_ui() {
|
||||
cob_split_mode->Append(Z("split after size"));
|
||||
cob_split_mode->Append(Z("split after duration"));
|
||||
cob_split_mode->Append(Z("split after timecodes"));
|
||||
cob_split_mode->Append(Z("split by parts"));
|
||||
cob_split_mode->Append(Z("split by parts (timecode-based)"));
|
||||
cob_split_mode->Append(Z("split by parts (frame/field-number-based)"));
|
||||
cob_split_mode->Append(Z("split after frame/field numbers"));
|
||||
cob_split_mode->Append(Z("split before chapters"));
|
||||
cob_split_mode->SetSelection(split_mode);
|
||||
@ -438,13 +455,14 @@ tab_global::load(wxConfigBase *cfg,
|
||||
wxString split_mode;
|
||||
cfg->Read(wxT("split_mode"), &split_mode, wxT("none"));
|
||||
|
||||
split_mode_idx = split_mode == wxT("size") ? 1
|
||||
: split_mode == wxT("duration") ? 2
|
||||
: split_mode == wxT("timecodes") ? 3
|
||||
: split_mode == wxT("parts") ? 4
|
||||
: split_mode == wxT("frames") ? 5
|
||||
: split_mode == wxT("chapters") ? 6
|
||||
: 0;
|
||||
split_mode_idx = split_mode == wxT("size") ? 1
|
||||
: split_mode == wxT("duration") ? 2
|
||||
: split_mode == wxT("timecodes") ? 3
|
||||
: split_mode == wxT("parts") ? 4
|
||||
: split_mode == wxT("parts-frames") ? 5
|
||||
: split_mode == wxT("frames") ? 6
|
||||
: split_mode == wxT("chapters") ? 7
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -515,8 +533,9 @@ tab_global::save(wxConfigBase *cfg) {
|
||||
: 2 == split_mode_idx ? wxT("duration")
|
||||
: 3 == split_mode_idx ? wxT("timecodes")
|
||||
: 4 == split_mode_idx ? wxT("parts")
|
||||
: 5 == split_mode_idx ? wxT("frames")
|
||||
: 6 == split_mode_idx ? wxT("chapters")
|
||||
: 5 == split_mode_idx ? wxT("parts-frames")
|
||||
: 6 == split_mode_idx ? wxT("frames")
|
||||
: 7 == split_mode_idx ? wxT("chapters")
|
||||
: wxT("none");
|
||||
cfg->Write(wxT("split_mode"), value);
|
||||
cfg->Write(wxT("split_args"), cob_split_args->GetValue());
|
||||
|
@ -230,3 +230,4 @@ T_381X_alac:bf27f11ef2cc46c8490fe6ecfbc9d47c-ed3dd47220947f8130974268f0af281d:pa
|
||||
T_382split_chapters:352a6c27ffc26565b45c8d3c014a18aa+be25f966d8d05465491385a420cd1f9b+ok-e09092b9b0f3e8b370f1a4b741fe8539+03badbdab502b87e0f323e486e0cf40c+ok-0ec4a1de0fb74fc5924439245dd48267+4fb8cfcbefde6256464d2c2f5422e926+19d72aca3ea4267fdbb0c685f3a807a5+6a1598fcf8286bf6a18d9a5b0da44ea7+1b098adad89cf106aa7a7ff7ed85faba+8d7d5c1cb47a9f1bbdbbd34336251947+1018f6b30b3d610f27482408a1d2e209+e7db2fc928d431accfc426e2a716e4a9+5e26b821afb4f7091d71164dfb3106f3+82c85375d465486a288a144ed88343c2+eacf9cdf018cd1e334da2450929d9497+ec2af2541adbee9e24d8058f5d15ed70+a0a7c8ed361294e1326130c3fccc3e36+ce0bb503534f00e454f51ebc883afa4f+8d2ed3e7a632d9c6ac948f157c3753b3+d653a17ba7d0448549e3019e2da03d1e+8db5db08e53b7e72ecd64689fa2fdf50+ok-de4f62f696ad97032645248dc401d59f+3946ed32564f5a269935835734396341+74886de816bc98c7ac2ba7d48288e7ca+6ad323bcf3b392129c1e8ea9d3cbf3d7+7f220ae5a4fe9b1c837f9078a0cf3648+4db726777694397bd9a606f3120c8a4b+e1c0df9d1716b406cab866ddf5518ce3+2439a94e258047a36141a77d94876002+ac69a10174e80fabeb412231f11b3f53+70b9f25ff48423fb3adad372edc9abd8+23e771db8b2505c16a8c9eac42ea1119+774089c52b37fd31bc7858762051de75+2a3f668f947f0add929d887ae7caf032+96e857a44a6674d4a985cf21f9053c35+96e62109ad173b219422765def88759f+2df1b1666e689581dd412c8b092fe568+83e5e7ed87bc4010c12b4afe3f3da858+ok:passed:20121227-202501:1.612054564
|
||||
T_383mp4_text_track_subtitles:76381141faab5737c5001b042d4b6235:passed:20121228-231519:0.072762138
|
||||
T_384vobsub_in_mp4:62fd55de5e9ce07925d710395971a7ae:passed:20130112-201109:0.568759516
|
||||
T_385split_parts_frames:14add8af7d016c048f0828708af04a86+c787a3d20fe2c26f7e0e0311f955a281+ok-e1001084ce3512e9dc8940d4c9ade87d+ok-d1c052f274338865a10c8ac6bbd1d1a3+ok-a4401edb1f7c56909267babaaa0e252b+ok-5eaaeb9e142363b1d3abff097b8a93c6+ok:passed:20130114-224502:1.316951981
|
||||
|
39
tests/test-385split_parts_frames.rb
Normal file
39
tests/test-385split_parts_frames.rb
Normal file
@ -0,0 +1,39 @@
|
||||
#!/usr/bin/ruby -w
|
||||
|
||||
# T_385split_parts_frames
|
||||
describe "mkvmerge / --split parts-frames:..."
|
||||
|
||||
test "data/avi/v-h264-aac.avi two output files" do
|
||||
merge "--split parts-frames:480-720,-960 data/avi/v-h264-aac.avi", :output => "#{tmp}-%02d"
|
||||
result = [ hash_file("#{tmp}-01"), hash_file("#{tmp}-02"), File.exist?("#{tmp}-03") ? "bad" : "ok" ].join('+')
|
||||
unlink_tmp_files
|
||||
result
|
||||
end
|
||||
|
||||
test "data/avi/v-h264-aac.avi one output file" do
|
||||
merge "--split parts-frames:480-720,+960-1198 data/avi/v-h264-aac.avi", :output => "#{tmp}-%02d"
|
||||
result = [ hash_file("#{tmp}-%02d"), File.exist?("#{tmp}-02") ? fail("second file should not exist") : "ok" ].join('+')
|
||||
unlink_tmp_files
|
||||
result
|
||||
end
|
||||
|
||||
test "data/avi/v-h264-aac.avi one output file, starting at 0" do
|
||||
merge "--split parts-frames:-720,+960-1198 data/avi/v-h264-aac.avi", :output => "#{tmp}-%02d"
|
||||
result = [ hash_file("#{tmp}-%02d"), File.exist?("#{tmp}-02") ? fail("second file should not exist") : "ok" ].join('+')
|
||||
unlink_tmp_files
|
||||
result
|
||||
end
|
||||
|
||||
test "data/avi/v-h264-aac.avi three parts one output file, starting at 0" do
|
||||
merge "--split parts-frames:-240,+480-720,+960-1198 data/avi/v-h264-aac.avi", :output => "#{tmp}-%02d"
|
||||
result = [ hash_file("#{tmp}-%02d"), File.exist?("#{tmp}-02") ? fail("second file should not exist") : "ok" ].join('+')
|
||||
unlink_tmp_files
|
||||
result
|
||||
end
|
||||
|
||||
test "data/avi/v-h264-aac.avi three parts one output file, starting at 240" do
|
||||
merge "--split parts-frames:240-480,+720-960,+1198-1440 data/avi/v-h264-aac.avi", :output => "#{tmp}-%02d"
|
||||
result = [ hash_file("#{tmp}-%02d"), File.exist?("#{tmp}-02") ? fail("second file should not exist") : "ok" ].join('+')
|
||||
unlink_tmp_files
|
||||
result
|
||||
end
|
Loading…
Reference in New Issue
Block a user