mirror of
https://gitlab.com/mbunkus/mkvtoolnix.git
synced 2024-12-24 11:54:01 +00:00
mkvmerge: implement chapter generation when appending/in fixed intervals
Implements mkvmerge's part of #1586.
This commit is contained in:
parent
89ca1e85ab
commit
ce7c31f49b
11
ChangeLog
11
ChangeLog
@ -1,3 +1,14 @@
|
||||
2016-03-01 Moritz Bunkus <moritz@bunkus.org>
|
||||
|
||||
* mkvmerge: new feature: added switches (»--generate-chapters« and
|
||||
»--generate-chapter-name-template«) for generating chapters while
|
||||
muxing. Two modes are currently supported: »when-appending« which
|
||||
creates one chapter at the beginning and an additional one each
|
||||
time a file is appended and »interval:…« which generates chapters
|
||||
in fixed intervals.
|
||||
|
||||
Implements mkvmerge's part of #1586.
|
||||
|
||||
2016-02-28 Moritz Bunkus <moritz@bunkus.org>
|
||||
|
||||
* MKVToolNix GUI: job queue enhancement: completed jobs will now
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "common/math.h"
|
||||
#include "common/strings/formatting.h"
|
||||
#include "common/tags/tags.h"
|
||||
#include "common/translation.h"
|
||||
#include "merge/cluster_helper.h"
|
||||
#include "merge/cues.h"
|
||||
#include "merge/libmatroska_extensions.h"
|
||||
@ -264,6 +265,8 @@ cluster_helper_c::add_packet(packet_cptr packet) {
|
||||
|
||||
if (g_video_packetizer == packet->source)
|
||||
++m->frame_field_number;
|
||||
|
||||
generate_chapters_if_necessary(packet);
|
||||
}
|
||||
|
||||
int64_t
|
||||
@ -694,4 +697,103 @@ cluster_helper_c::create_tags_for_track_statistics(KaxTags &tags,
|
||||
m->track_statistics.clear();
|
||||
}
|
||||
|
||||
void
|
||||
cluster_helper_c::enable_chapter_generation(chapter_generation_mode_e mode,
|
||||
std::string const &language) {
|
||||
m->chapter_generation_mode = mode;
|
||||
m->chapter_generation_language = !language.empty() ? language : "eng";
|
||||
}
|
||||
|
||||
chapter_generation_mode_e
|
||||
cluster_helper_c::get_chapter_generation_mode()
|
||||
const {
|
||||
return m->chapter_generation_mode;
|
||||
}
|
||||
|
||||
void
|
||||
cluster_helper_c::set_chapter_generation_interval(timestamp_c const &interval) {
|
||||
m->chapter_generation_interval = interval;
|
||||
}
|
||||
|
||||
void
|
||||
cluster_helper_c::set_chapter_generation_name_template(std::string const &name_template) {
|
||||
m->chapter_generation_name_template.override(name_template);
|
||||
}
|
||||
|
||||
void
|
||||
cluster_helper_c::verify_and_report_chapter_generation_parameters()
|
||||
const {
|
||||
if (chapter_generation_mode_e::none == m->chapter_generation_mode)
|
||||
return;
|
||||
|
||||
if (!m->chapter_generation_reference_track)
|
||||
mxerror(boost::format("Chapter generation is only possible if at least one video or audio track muxed.\n"));
|
||||
|
||||
mxinfo(boost::format("Using the track with the ID %1% from the file '%2%' as the reference for chapter generation.\n")
|
||||
% m->chapter_generation_reference_track->m_ti.m_id % m->chapter_generation_reference_track->m_ti.m_fname);
|
||||
}
|
||||
|
||||
void
|
||||
cluster_helper_c::register_new_packetizer(generic_packetizer_c &ptzr) {
|
||||
auto new_track_type = ptzr.get_track_type();
|
||||
|
||||
if (!g_video_packetizer && (track_video == new_track_type))
|
||||
g_video_packetizer = &ptzr;
|
||||
|
||||
auto current_ptzr_prio = !m->chapter_generation_reference_track ? 0
|
||||
: m->chapter_generation_reference_track->get_track_type() == track_video ? 100
|
||||
: m->chapter_generation_reference_track->get_track_type() == track_audio ? 80
|
||||
: 0;
|
||||
|
||||
auto new_ptzr_prio = new_track_type == track_video ? 100
|
||||
: new_track_type == track_audio ? 80
|
||||
: 0;
|
||||
|
||||
if (new_ptzr_prio > current_ptzr_prio)
|
||||
m->chapter_generation_reference_track = &ptzr;
|
||||
}
|
||||
|
||||
void
|
||||
cluster_helper_c::generate_chapters_if_necessary(packet_cptr const &packet) {
|
||||
if ((chapter_generation_mode_e::none == m->chapter_generation_mode) || !m->chapter_generation_reference_track)
|
||||
return;
|
||||
|
||||
auto successor = m->chapter_generation_reference_track->get_connected_successor();
|
||||
if (successor) {
|
||||
if (chapter_generation_mode_e::when_appending == m->chapter_generation_mode)
|
||||
m->chapter_generation_last_generated.reset();
|
||||
|
||||
while ((successor = m->chapter_generation_reference_track->get_connected_successor()))
|
||||
m->chapter_generation_reference_track = successor;
|
||||
}
|
||||
|
||||
auto ptzr = packet->source;
|
||||
if (ptzr != m->chapter_generation_reference_track)
|
||||
return;
|
||||
|
||||
if (chapter_generation_mode_e::when_appending == m->chapter_generation_mode) {
|
||||
if (packet->is_key_frame() && !m->chapter_generation_last_generated.valid())
|
||||
generate_one_chapter(timestamp_c::ns(packet->assigned_timecode));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (chapter_generation_mode_e::interval != m->chapter_generation_mode)
|
||||
return;
|
||||
|
||||
auto now = timestamp_c::ns(packet->assigned_timecode);
|
||||
|
||||
while (!m->chapter_generation_last_generated.valid() || (m->chapter_generation_last_generated <= now))
|
||||
generate_one_chapter(!m->chapter_generation_last_generated.valid() ? timestamp_c::ns(0) : m->chapter_generation_last_generated + m->chapter_generation_interval);
|
||||
}
|
||||
|
||||
void
|
||||
cluster_helper_c::generate_one_chapter(timestamp_c const ×tamp) {
|
||||
m->chapter_generation_number += 1;
|
||||
m->chapter_generation_last_generated = timestamp;
|
||||
auto name = format_chapter_name_template(m->chapter_generation_name_template.get_translated(), m->chapter_generation_number, timestamp);
|
||||
|
||||
add_chapter_atom(timestamp, name, m->chapter_generation_language);
|
||||
}
|
||||
|
||||
std::unique_ptr<cluster_helper_c> g_cluster_helper;
|
||||
|
@ -23,14 +23,22 @@
|
||||
#include <matroska/KaxCluster.h>
|
||||
|
||||
#include "common/split_point.h"
|
||||
#include "common/timestamp.h"
|
||||
#include "merge/libmatroska_extensions.h"
|
||||
|
||||
#define RND_TIMECODE_SCALE(a) (std::llround(static_cast<double>(a) / static_cast<double>(g_timecode_scale)) * static_cast<int64_t>(g_timecode_scale))
|
||||
|
||||
class generic_packetizer_c;
|
||||
class render_groups_c;
|
||||
class packet_t;
|
||||
using packet_cptr = std::shared_ptr<packet_t>;
|
||||
|
||||
enum class chapter_generation_mode_e {
|
||||
none,
|
||||
when_appending,
|
||||
interval,
|
||||
};
|
||||
|
||||
class cluster_helper_c {
|
||||
private:
|
||||
struct impl_t;
|
||||
@ -69,6 +77,14 @@ public:
|
||||
|
||||
void create_tags_for_track_statistics(KaxTags &tags, std::string const &writing_app, boost::posix_time::ptime const &writing_date);
|
||||
|
||||
void register_new_packetizer(generic_packetizer_c &ptzr);
|
||||
|
||||
void enable_chapter_generation(chapter_generation_mode_e mode, std::string const &language = "");
|
||||
chapter_generation_mode_e get_chapter_generation_mode() const;
|
||||
void set_chapter_generation_interval(timestamp_c const &interval);
|
||||
void set_chapter_generation_name_template(std::string const &name_template);
|
||||
void verify_and_report_chapter_generation_parameters() const;
|
||||
|
||||
private:
|
||||
void set_duration(render_groups_c *rg);
|
||||
bool must_duration_be_set(render_groups_c *rg, packet_cptr &new_packet);
|
||||
@ -76,6 +92,8 @@ private:
|
||||
void render_before_adding_if_necessary(packet_cptr &packet);
|
||||
void render_after_adding_if_necessary(packet_cptr &packet);
|
||||
void split_if_necessary(packet_cptr &packet);
|
||||
void generate_chapters_if_necessary(packet_cptr const &packet);
|
||||
void generate_one_chapter(timestamp_c const ×tamp);
|
||||
void split(packet_cptr &packet);
|
||||
|
||||
bool add_to_cues_maybe(packet_cptr &pack);
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "common/strings/formatting.h"
|
||||
#include "common/unique_numbers.h"
|
||||
#include "common/xml/ebml_tags_converter.h"
|
||||
#include "merge/cluster_helper.h"
|
||||
#include "merge/filelist.h"
|
||||
#include "merge/generic_packetizer.h"
|
||||
#include "merge/generic_reader.h"
|
||||
@ -305,14 +306,14 @@ generic_packetizer_c::set_track_type(int type,
|
||||
if (track_audio == type)
|
||||
m_reader->m_num_audio_tracks++;
|
||||
|
||||
else if (track_video == type) {
|
||||
else if (track_video == type)
|
||||
m_reader->m_num_video_tracks++;
|
||||
if (!g_video_packetizer)
|
||||
g_video_packetizer = this;
|
||||
|
||||
} else
|
||||
else
|
||||
m_reader->m_num_subtitle_tracks++;
|
||||
|
||||
g_cluster_helper->register_new_packetizer(*this);
|
||||
|
||||
if ( (TFA_AUTOMATIC == tfa_mode)
|
||||
&& (TFA_AUTOMATIC == m_timestamp_factory_application_mode))
|
||||
m_timestamp_factory_application_mode
|
||||
@ -1089,7 +1090,7 @@ generic_packetizer_c::connect(generic_packetizer_c *src,
|
||||
m_hcompression = src->m_hcompression;
|
||||
m_compressor = compressor_c::create(m_hcompression);
|
||||
m_last_cue_timecode = src->m_last_cue_timecode;
|
||||
m_timestamp_factory = src->m_timestamp_factory;
|
||||
m_timestamp_factory = src->m_timestamp_factory;
|
||||
m_correction_timecode_offset = 0;
|
||||
|
||||
if (-1 == append_timecode_offset)
|
||||
@ -1224,3 +1225,9 @@ generic_packetizer_c::before_file_finished() {
|
||||
void
|
||||
generic_packetizer_c::after_file_created() {
|
||||
}
|
||||
|
||||
generic_packetizer_c *
|
||||
generic_packetizer_c::get_connected_successor()
|
||||
const {
|
||||
return m_connected_successor;
|
||||
}
|
||||
|
@ -253,6 +253,8 @@ public:
|
||||
virtual void prevent_lacing();
|
||||
virtual bool is_lacing_prevented() const;
|
||||
|
||||
virtual generic_packetizer_c *get_connected_successor() const;
|
||||
|
||||
// Callbacks
|
||||
virtual void after_packet_rendered(packet_t const &packet);
|
||||
virtual void before_file_finished();
|
||||
|
@ -95,6 +95,12 @@ set_usage() {
|
||||
" entries to chapter names.\n");
|
||||
usage_text += Y(" --default-language <lng> Use this language for all tracks unless\n"
|
||||
" overridden with the --language option.\n");
|
||||
usage_text += Y(" --generate-chapters <mode>\n"
|
||||
" Automatically generate chapters according to\n"
|
||||
" the mode ('when-appending' or 'interval:<duration>').\n");
|
||||
usage_text += Y(" --generate-chapters-name-template <template>\n"
|
||||
" Template for newly generated chapter names\n"
|
||||
" (default: 'Chapter <NUM:2>').\n");
|
||||
usage_text += "\n";
|
||||
usage_text += Y(" Segment info handling:\n");
|
||||
usage_text += Y(" --segmentinfo <file> Read segment information from the file.\n");
|
||||
@ -439,11 +445,9 @@ identify(std::string &filename) {
|
||||
|
||||
It returns a number of nanoseconds.
|
||||
*/
|
||||
int64_t
|
||||
static int64_t
|
||||
parse_number_with_unit(const std::string &s,
|
||||
const std::string &subject,
|
||||
const std::string &argument,
|
||||
std::string display_s = "") {
|
||||
const std::string &argument) {
|
||||
boost::regex re1("(-?\\d+\\.?\\d*)(s|ms|us|ns|fps|p|i)?", boost::regex::perl | boost::regex::icase);
|
||||
boost::regex re2("(-?\\d+)/(-?\\d+)(s|ms|us|ns|fps|p|i)?", boost::regex::perl | boost::regex::icase);
|
||||
|
||||
@ -452,9 +456,6 @@ parse_number_with_unit(const std::string &s,
|
||||
double d_value = 0.0;
|
||||
bool is_fraction = false;
|
||||
|
||||
if (display_s.empty())
|
||||
display_s = s;
|
||||
|
||||
boost::smatch matches;
|
||||
if (boost::regex_match(s, matches, re1)) {
|
||||
parse_number(matches[1], d_value);
|
||||
@ -470,7 +471,7 @@ parse_number_with_unit(const std::string &s,
|
||||
is_fraction = true;
|
||||
|
||||
} else
|
||||
mxerror(boost::format(Y("'%1%' is not a valid %2% in '%3% %4%'.\n")) % s % subject % argument % display_s);
|
||||
mxerror(boost::format(Y("'%1%' is not recognized as a valid number format in '%2%'.\n")) % s % argument);
|
||||
|
||||
int64_t multiplier = 1000000000;
|
||||
balg::to_lower(unit);
|
||||
@ -496,7 +497,7 @@ parse_number_with_unit(const std::string &s,
|
||||
return (int64_t)(1000000000.0 / d_value);
|
||||
|
||||
} else if (unit != "s")
|
||||
mxerror(boost::format(Y("'%1%' does not contain a valid unit ('s', 'ms', 'us', 'ns', 'fps', 'p' or 'i') in '%2% %3%'.\n")) % s % argument % display_s);
|
||||
mxerror(boost::format(Y("'%1%' does not contain a valid unit ('s', 'ms', 'us', 'ns', 'fps', 'p' or 'i') in '%2%'.\n")) % s % argument);
|
||||
|
||||
if (is_fraction)
|
||||
return multiplier * n / d;
|
||||
@ -1351,7 +1352,7 @@ parse_arg_default_duration(const std::string &s,
|
||||
if (!parse_number(parts[0], id))
|
||||
mxerror(boost::format(Y("'%1%' is not a valid track ID in '--default-duration %2%'.\n")) % parts[0] % s);
|
||||
|
||||
ti.m_default_durations[id] = parse_number_with_unit(parts[1], "default duration", "--default-duration");
|
||||
ti.m_default_durations[id] = parse_number_with_unit(parts[1], (boost::format("--default-duration %1%") % s).str());
|
||||
}
|
||||
|
||||
/** \brief Parse the argument for \c --nalu-size-length
|
||||
@ -1636,6 +1637,31 @@ parse_arg_chapters(const std::string ¶m,
|
||||
g_kax_chapters = parse_chapters(g_chapter_file_name, 0, -1, 0, g_chapter_language.c_str(), g_chapter_charset.c_str(), false, nullptr, &g_tags_from_cue_chapters);
|
||||
}
|
||||
|
||||
static void
|
||||
parse_arg_generate_chapters(std::string const &arg) {
|
||||
auto parts = split(arg, ":", 2);
|
||||
|
||||
if (parts[0] == "when-appending") {
|
||||
g_cluster_helper->enable_chapter_generation(chapter_generation_mode_e::when_appending, g_chapter_language);
|
||||
g_chapter_language.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (parts[0] != "interval")
|
||||
mxerror(boost::format("Invalid chapter generation mode in '--generate-chapters %1%'.\n") % arg);
|
||||
|
||||
if (parts.size() < 2)
|
||||
parts.emplace_back("");
|
||||
|
||||
auto interval = int64_t{};
|
||||
if (!parse_timecode(parts[1], interval) || (interval < 0))
|
||||
mxerror(boost::format("The chapter generation interval must be a positive number in '--generate-chapters %1%'.\n") % arg);
|
||||
|
||||
g_cluster_helper->enable_chapter_generation(chapter_generation_mode_e::interval, g_chapter_language);
|
||||
g_cluster_helper->set_chapter_generation_interval(timestamp_c::ns(interval));
|
||||
g_chapter_language.clear();
|
||||
}
|
||||
|
||||
static void
|
||||
parse_arg_segmentinfo(const std::string ¶m,
|
||||
const std::string &arg) {
|
||||
@ -2154,6 +2180,20 @@ parse_args(std::vector<std::string> args) {
|
||||
|
||||
inputs_found = true;
|
||||
|
||||
} else if (this_arg == "--generate-chapters") {
|
||||
if (no_next_arg)
|
||||
mxerror(Y("'--generate-chapters' lacks the mode.\n"));
|
||||
|
||||
parse_arg_generate_chapters(next_arg);
|
||||
sit++;
|
||||
|
||||
} else if (this_arg == "--generate-chapters-name-template") {
|
||||
if (no_next_arg)
|
||||
mxerror(Y("'--generate-chapters-name-template' lacks the name template.\n"));
|
||||
|
||||
g_cluster_helper->set_chapter_generation_name_template(next_arg);
|
||||
sit++;
|
||||
|
||||
} else if (this_arg == "--segmentinfo") {
|
||||
if (no_next_arg)
|
||||
mxerror(Y("'--segmentinfo' lacks the file name.\n"));
|
||||
@ -2632,6 +2672,7 @@ main(int argc,
|
||||
check_track_id_validity();
|
||||
create_append_mappings_for_playlists();
|
||||
check_append_mapping();
|
||||
g_cluster_helper->verify_and_report_chapter_generation_parameters();
|
||||
calc_attachment_sizes();
|
||||
calc_max_chapter_size();
|
||||
}
|
||||
|
@ -48,6 +48,7 @@
|
||||
|
||||
#include "common/chapters/chapters.h"
|
||||
#include "common/command_line.h"
|
||||
#include "common/construct.h"
|
||||
#include "common/container.h"
|
||||
#include "common/date_time.h"
|
||||
#include "common/debugging.h"
|
||||
@ -69,6 +70,7 @@
|
||||
#include "merge/webm.h"
|
||||
|
||||
using namespace libmatroska;
|
||||
using namespace mtx::construct;
|
||||
|
||||
namespace libmatroska {
|
||||
|
||||
@ -168,6 +170,8 @@ static std::unique_ptr<EbmlVoid> s_kax_chapters_void;
|
||||
static int64_t s_max_chapter_size = 0;
|
||||
static std::unique_ptr<EbmlVoid> s_void_after_track_headers;
|
||||
|
||||
static std::vector<std::tuple<timestamp_c, std::string, std::string>> s_additional_chapter_atoms;
|
||||
|
||||
static mm_io_cptr s_out;
|
||||
|
||||
static bitvalue_c s_seguid_prev(128), s_seguid_current(128), s_seguid_next(128);
|
||||
@ -1273,7 +1277,7 @@ add_tags_from_cue_chapters() {
|
||||
*/
|
||||
static void
|
||||
render_chapter_void_placeholder() {
|
||||
if (0 >= s_max_chapter_size)
|
||||
if ((0 >= s_max_chapter_size) && (chapter_generation_mode_e::none == g_cluster_helper->get_chapter_generation_mode()))
|
||||
return;
|
||||
|
||||
if (outputting_webm()) {
|
||||
@ -1281,12 +1285,14 @@ render_chapter_void_placeholder() {
|
||||
|
||||
g_kax_chapters.reset();
|
||||
s_max_chapter_size = 0;
|
||||
g_cluster_helper->enable_chapter_generation(chapter_generation_mode_e::none);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto size = s_max_chapter_size + (chapter_generation_mode_e::none == g_cluster_helper->get_chapter_generation_mode() ? 100 : 1000);
|
||||
s_kax_chapters_void = std::make_unique<EbmlVoid>();
|
||||
s_kax_chapters_void->SetSize(s_max_chapter_size + 100);
|
||||
s_kax_chapters_void->SetSize(size);
|
||||
s_kax_chapters_void->Render(*s_out);
|
||||
}
|
||||
|
||||
@ -1392,6 +1398,38 @@ add_chapters_for_current_part() {
|
||||
sort_ebml_master(s_chapters_in_this_file.get());
|
||||
}
|
||||
|
||||
void
|
||||
add_chapter_atom(timestamp_c const &start_timestamp,
|
||||
std::string const &name,
|
||||
std::string const &language) {
|
||||
s_additional_chapter_atoms.emplace_back(start_timestamp, name, language);
|
||||
}
|
||||
|
||||
static void
|
||||
prepare_additional_chapter_atoms_for_rendering() {
|
||||
if (s_additional_chapter_atoms.empty())
|
||||
return;
|
||||
|
||||
if (!s_chapters_in_this_file)
|
||||
s_chapters_in_this_file = std::make_shared<KaxChapters>();
|
||||
|
||||
auto offset = timestamp_c::ns(g_no_linking ? g_cluster_helper->get_first_timecode_in_file() + g_cluster_helper->get_discarded_duration() : 0);
|
||||
auto &edition = GetChild<KaxEditionEntry>(*s_chapters_in_this_file);
|
||||
|
||||
if (!FindChild<KaxEditionUID>(edition))
|
||||
GetChild<KaxEditionUID>(edition).SetValue(create_unique_number(UNIQUE_EDITION_IDS));
|
||||
|
||||
for (auto const &additional_chapter : s_additional_chapter_atoms) {
|
||||
auto atom = cons<KaxChapterAtom>(new KaxChapterUID, create_unique_number(UNIQUE_CHAPTER_IDS),
|
||||
new KaxChapterTimeStart, (std::get<0>(additional_chapter) - offset).to_ns(),
|
||||
cons<KaxChapterDisplay>(new KaxChapterString, std::get<1>(additional_chapter),
|
||||
new KaxChapterLanguage, std::get<2>(additional_chapter)));
|
||||
edition.PushElement(*atom);
|
||||
}
|
||||
|
||||
s_additional_chapter_atoms.clear();
|
||||
}
|
||||
|
||||
static void
|
||||
render_chapters() {
|
||||
auto s_debug = debugging_option_c{"splitting_chapters"};
|
||||
@ -1401,12 +1439,23 @@ render_chapters() {
|
||||
% !!s_kax_chapters_void % (s_kax_chapters_void ? s_kax_chapters_void ->ElementSize() : 0)
|
||||
% !!s_chapters_in_this_file % (s_chapters_in_this_file ? s_chapters_in_this_file->ElementSize() : 0));
|
||||
|
||||
if (!s_kax_chapters_void)
|
||||
return;
|
||||
prepare_additional_chapter_atoms_for_rendering();
|
||||
|
||||
if (s_chapters_in_this_file)
|
||||
if (!s_chapters_in_this_file) {
|
||||
s_kax_chapters_void.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
fix_mandatory_elements(s_chapters_in_this_file.get());
|
||||
|
||||
if (s_kax_chapters_void && (s_kax_chapters_void->ElementSize() >= s_chapters_in_this_file->ElementSize()))
|
||||
s_kax_chapters_void->ReplaceWith(*s_chapters_in_this_file, *s_out, true, true);
|
||||
|
||||
else {
|
||||
s_out->setFilePointer(0, seek_end);
|
||||
s_chapters_in_this_file->Render(*s_out);
|
||||
}
|
||||
|
||||
s_kax_chapters_void.reset();
|
||||
}
|
||||
|
||||
|
@ -201,6 +201,7 @@ void main_loop();
|
||||
|
||||
void add_packetizer_globally(generic_packetizer_c *packetizer);
|
||||
void add_tags(KaxTag *tags);
|
||||
void add_chapter_atom(timestamp_c const &start_timestamp, std::string const &name, std::string const &language);
|
||||
|
||||
void create_next_output_file();
|
||||
void finish_file(bool last_file, bool create_new_file = false, bool previously_discarding = false);
|
||||
|
@ -51,6 +51,12 @@ public:
|
||||
|
||||
bool discarding{}, splitting_and_processed_fully{};
|
||||
|
||||
chapter_generation_mode_e chapter_generation_mode{chapter_generation_mode_e::none};
|
||||
translatable_string_c chapter_generation_name_template{YT("Chapter <NUM:2>")};
|
||||
timestamp_c chapter_generation_interval, chapter_generation_last_generated;
|
||||
generic_packetizer_c *chapter_generation_reference_track{};
|
||||
unsigned int chapter_generation_number{};
|
||||
std::string chapter_generation_language;
|
||||
|
||||
std::unordered_map<uint64_t, track_statistics_c> track_statistics;
|
||||
|
||||
|
@ -377,3 +377,5 @@ T_528handbrake_chapter_uids:f68bd8d577a3b4b2e8d83b7336bcc936:passed:20160119-173
|
||||
T_529aac_program_config_element_with_empty_comment_at_end:e3582257eebed3fada2bd75d8b2dfc20:passed:20160124-120513:0.014071844
|
||||
T_530avi_negative_video_height:6d5d6951adc7e2fc0743eade6e8bb6fd:passed:20160210-170826:0.014806634
|
||||
T_531simple_chapter_extraction:6b6dccb8f5a4ab488da2f174e0d9450c-6b6dccb8f5a4ab488da2f174e0d9450c-e790f2e02daab6faabf712ccf9877ac2-b832af714e46ad7514e669297320b032-b832af714e46ad7514e669297320b032-59afc62025a19921cfa64280acbbb61c:passed:20160225-223308:0.063513455
|
||||
T_532chapter_generation_when_appending:5680a0ca3f974d43ff7e4c7482812a4c-ea8f0f1e1246e42b6ba903b4eb33b9d4-88b2c0d2539b79b41f6cea5f8203bb89+ed0f627e76e018c68b2ed73d7c41133b+52fda80c406910138bcd95171052fa56+86a7d27b85289709141db725240a17dd+98c899d37d10d441cb1cfe9c33d6de06+cd7bd9aa38a1514838f86230e7ba9b5c+32c2cea8051c9f848e090aac1b761cb7+ok-926d7b0771fa08ea44710f260ef3b3bc:passed:20160301-194344:2.04755563
|
||||
T_533chapter_generation_interval:63486951fe0717eec1e93cb8fadaed92-5a57214bb210f51311edc6e8fca19f3e-cbbd43701884c34ae5d1efd9baf39a04+0e5962f224c28b3a7fc2a318fddd7ea9+74621b5528a14b9bee0aa6b8ac28693b+dfb83af32a6472c8e2d41238b89c7521+b60c9078501798a674db97c83fd99b14+e662cf60c4365aaeff7e6b088904739d+32c2cea8051c9f848e090aac1b761cb7+ok-c3b953881e1f7d35d58ab0bfe590d7ba:passed:20160301-194459:2.043516559
|
||||
|
19
tests/test-532chapter_generation_when_appending.rb
Executable file
19
tests/test-532chapter_generation_when_appending.rb
Executable file
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/ruby -w
|
||||
# coding: utf-8
|
||||
|
||||
file = "data/avi/v-h264-aac.avi"
|
||||
|
||||
# T_532chapter_generation_when_appending
|
||||
describe "mkvmerge / generate chapter »when-appending«"
|
||||
|
||||
def hash_results max
|
||||
( (1..max).collect { |i| hash_file(sprintf("%s-%02d", tmp, i)) } + [ File.exists?(sprintf("%s-%02d", tmp, max + 1)) ? 'bad' : 'ok' ]).join '+'
|
||||
end
|
||||
|
||||
test_merge "#{file} + #{file} + #{file}", :args => "--generate-chapters when-appending"
|
||||
test_merge "#{file} + #{file} + #{file}", :args => "--chapter-language ger --generate-chapters when-appending"
|
||||
test "creation and splitting" do
|
||||
merge "--chapter-language ger --generate-chapters when-appending --split 30s #{file} + #{file} + #{file}", :output => "#{tmp}-%02d"
|
||||
hash_results 7
|
||||
end
|
||||
test_merge "#{file}", :args => "--generate-chapters when-appending"
|
19
tests/test-533chapter_generation_interval.rb
Executable file
19
tests/test-533chapter_generation_interval.rb
Executable file
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/ruby -w
|
||||
# coding: utf-8
|
||||
|
||||
file = "data/avi/v-h264-aac.avi"
|
||||
|
||||
# T_533chapter_generation_interval
|
||||
describe "mkvmerge / generate chapter »interval«"
|
||||
|
||||
def hash_results max
|
||||
( (1..max).collect { |i| hash_file(sprintf("%s-%02d", tmp, i)) } + [ File.exists?(sprintf("%s-%02d", tmp, max + 1)) ? 'bad' : 'ok' ]).join '+'
|
||||
end
|
||||
|
||||
test_merge "#{file} + #{file} + #{file}", :args => "--generate-chapters interval:30s"
|
||||
test_merge "#{file} + #{file} + #{file}", :args => "--chapter-language ger --generate-chapters interval:30s"
|
||||
test "creation and splitting" do
|
||||
merge "--chapter-language ger --generate-chapters interval:30s --split 30s #{file} + #{file} + #{file}", :output => "#{tmp}-%02d"
|
||||
hash_results 7
|
||||
end
|
||||
test_merge "#{file}", :args => "--generate-chapters interval:30s"
|
Loading…
Reference in New Issue
Block a user