1 /*
2 mkvmerge -- utility for splicing together matroska files
3 from component media subtypes
4
5 Distributed under the GPL v2
6 see the file COPYING for details
7 or visit https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
8
9 The cluster helper groups frames into blocks groups and those
10 into clusters, sets the durations, renders the clusters etc.
11
12 Written by Moritz Bunkus <moritz@bunkus.org>.
13 */
14
15 #include "common/common_pch.h"
16
17 #include "common/at_scope_exit.h"
18 #include "common/doc_type_version_handler.h"
19 #include "common/ebml.h"
20 #include "common/hacks.h"
21 #include "common/strings/formatting.h"
22 #include "common/tags/tags.h"
23 #include "common/translation.h"
24 #include "merge/cluster_helper.h"
25 #include "merge/cues.h"
26 #include "merge/generic_packetizer.h"
27 #include "merge/generic_reader.h"
28 #include "merge/libmatroska_extensions.h"
29 #include "merge/output_control.h"
30 #include "merge/packet_extensions.h"
31 #include "merge/private/cluster_helper.h"
32
33 #include <matroska/KaxBlock.h>
34 #include <matroska/KaxBlockData.h>
35 #include <matroska/KaxCuesData.h>
36 #include <matroska/KaxSeekHead.h>
37
38 using namespace libmatroska;
39
40 debugging_option_c render_groups_c::ms_gap_detection{"cluster_helper_gap_detection"};
41
~impl_t()42 cluster_helper_c::impl_t::~impl_t() {
43 }
44
cluster_helper_c()45 cluster_helper_c::cluster_helper_c()
46 : m{new cluster_helper_c::impl_t{}}
47 {
48 }
49
~cluster_helper_c()50 cluster_helper_c::~cluster_helper_c() {
51 }
52
53 mm_io_c *
get_output()54 cluster_helper_c::get_output() {
55 return m->out;
56 }
57
58 KaxCluster *
get_cluster()59 cluster_helper_c::get_cluster() {
60 return m->cluster.get();
61 }
62
63 int64_t
get_first_timestamp_in_file() const64 cluster_helper_c::get_first_timestamp_in_file()
65 const {
66 return m->first_timestamp_in_file;
67 }
68
69 int64_t
get_first_timestamp_in_part() const70 cluster_helper_c::get_first_timestamp_in_part()
71 const {
72 return m->first_timestamp_in_part;
73 }
74
75 int64_t
get_max_timestamp_in_file() const76 cluster_helper_c::get_max_timestamp_in_file()
77 const {
78 return m->max_timestamp_in_file;
79 }
80
81 int
get_packet_count() const82 cluster_helper_c::get_packet_count()
83 const {
84 return m->packets.size();
85 }
86
87 bool
splitting() const88 cluster_helper_c::splitting()
89 const {
90 if (g_splitting_by_all_chapters || !g_splitting_by_chapter_numbers.empty())
91 return true;
92
93 return !m->split_points.empty();
94 }
95
96 bool
discarding() const97 cluster_helper_c::discarding()
98 const {
99 return splitting() && m->discarding;
100 }
101
102 bool
is_splitting_and_processed_fully() const103 cluster_helper_c::is_splitting_and_processed_fully()
104 const {
105 return m->splitting_and_processed_fully;
106 }
107
108 void
render_before_adding_if_necessary(packet_cptr & packet)109 cluster_helper_c::render_before_adding_if_necessary(packet_cptr &packet) {
110 int64_t timestamp = get_timestamp();
111 int64_t timestamp_delay = ( (packet->assigned_timestamp > m->max_timestamp_in_cluster)
112 || (-1 == m->max_timestamp_in_cluster)) ? packet->assigned_timestamp : m->max_timestamp_in_cluster;
113 timestamp_delay -= ( (-1 == m->min_timestamp_in_cluster)
114 || (packet->assigned_timestamp < m->min_timestamp_in_cluster)) ? packet->assigned_timestamp : m->min_timestamp_in_cluster;
115 timestamp_delay = (int64_t)(timestamp_delay / g_timestamp_scale);
116
117 mxdebug_if(m->debug_packets,
118 fmt::format("cluster_helper_c::add_packet(): new packet { source {0}/{1} "
119 "timestamp: {2} duration: {3} bref: {4} fref: {5} assigned_timestamp: {6} timestamp_delay: {7} }\n",
120 packet->source->m_ti.m_id, packet->source->m_ti.m_fname, packet->timestamp, packet->duration,
121 packet->bref, packet->fref, packet->assigned_timestamp, mtx::string::format_timestamp(timestamp_delay)));
122
123 bool is_video_keyframe = (packet->source == g_video_packetizer) && packet->is_key_frame();
124 bool do_render = (std::numeric_limits<int16_t>::max() < timestamp_delay)
125 || (std::numeric_limits<int16_t>::min() > timestamp_delay)
126 || ( (std::max<int64_t>(0, m->min_timestamp_in_cluster) > m->previous_cluster_ts)
127 && (packet->assigned_timestamp > m->min_timestamp_in_cluster)
128 && (!g_video_packetizer || !is_video_keyframe || m->first_video_keyframe_seen)
129 && ( (packet->gap_following && !m->packets.empty())
130 || ((packet->assigned_timestamp - timestamp) > g_max_ns_per_cluster)
131 || is_video_keyframe));
132
133 if (is_video_keyframe)
134 m->first_video_keyframe_seen = true;
135
136 mxdebug_if(m->debug_rendering,
137 fmt::format("render check cur_ts {8} min_ts_ic {0} prev_cl_ts {1} test {2} is_vid_and_key {3} tc_delay {4} gap_following_and_not_empty {5} cur_ts>min_ts_ic {7} first_video_key_seen {9} do_render {6}\n",
138 m->min_timestamp_in_cluster, m->previous_cluster_ts, std::max<int64_t>(0, m->min_timestamp_in_cluster) > m->previous_cluster_ts, is_video_keyframe,
139 timestamp_delay, packet->gap_following && !m->packets.empty(), do_render, packet->assigned_timestamp > m->min_timestamp_in_cluster, packet->assigned_timestamp, m->first_video_keyframe_seen));
140
141 if (!do_render)
142 return;
143
144 render();
145 prepare_new_cluster();
146 }
147
148 void
render_after_adding_if_necessary(packet_cptr & packet)149 cluster_helper_c::render_after_adding_if_necessary(packet_cptr &packet) {
150 // Render the cluster if it is full (according to my many criteria).
151 auto timestamp = get_timestamp();
152 if ( ((packet->assigned_timestamp - timestamp) > g_max_ns_per_cluster)
153 || (m->packets.size() > static_cast<size_t>(g_max_blocks_per_cluster))
154 || (get_cluster_content_size() > 1500000)) {
155 render();
156 prepare_new_cluster();
157 }
158 }
159
160 void
split_if_necessary(packet_cptr & packet)161 cluster_helper_c::split_if_necessary(packet_cptr &packet) {
162 if ( !splitting()
163 || (m->current_split_point_idx >= m->split_points.size())
164 || (g_file_num > g_split_max_num_files)
165 || !packet->is_key_frame()
166 || ( (packet->source->get_track_type() != track_video)
167 && g_video_packetizer))
168 return;
169
170 auto ¤t_split_point = m->split_points[m->current_split_point_idx];
171 bool split_now = false;
172
173 // Maybe we want to start a new file now.
174 if (split_point_c::size == current_split_point.m_type) {
175 int64_t additional_size = 0;
176
177 if (!m->packets.empty())
178 // Cluster + Cluster timestamp: roughly 21 bytes. Add all frame sizes & their overheaders, too.
179 additional_size = 21 + std::accumulate(m->packets.begin(), m->packets.end(), 0, [](size_t size, const packet_cptr &p) { return size + p->data->get_size() + (p->is_key_frame() ? 10 : p->is_p_frame() ? 13 : 16); });
180
181 additional_size += 18 * m->num_cue_elements;
182
183 mxdebug_if(m->debug_splitting,
184 fmt::format("cluster_helper split decision: header_overhead: {0}, additional_size: {1}, bytes_in_file: {2}, sum: {3}\n",
185 m->header_overhead, additional_size, m->bytes_in_file, m->header_overhead + additional_size + m->bytes_in_file));
186 if ((m->header_overhead + additional_size + m->bytes_in_file) >= current_split_point.m_point)
187 split_now = true;
188
189 } else if ( (split_point_c::duration == current_split_point.m_type)
190 && (0 <= m->first_timestamp_in_file)
191 && (timestamp_c::ns(packet->assigned_timestamp - m->first_timestamp_in_file - current_split_point.m_point) > timestamp_c::ms(-1)))
192 split_now = true;
193
194 else if ( ( (split_point_c::timestamp == current_split_point.m_type)
195 || (split_point_c::parts == current_split_point.m_type))
196 && (timestamp_c::ns(packet->assigned_timestamp - current_split_point.m_point) > timestamp_c::ms(-1)))
197 split_now = true;
198
199 else if ( ( (split_point_c::frame_field == current_split_point.m_type)
200 || (split_point_c::parts_frame_field == current_split_point.m_type))
201 && (m->frame_field_number >= current_split_point.m_point))
202 split_now = true;
203
204 if (!split_now)
205 return;
206
207 split(packet);
208 }
209
210 void
split(packet_cptr & packet)211 cluster_helper_c::split(packet_cptr &packet) {
212 render();
213
214 m->num_cue_elements = 0;
215
216 auto ¤t_split_point = m->split_points[m->current_split_point_idx];
217 bool create_new_file = current_split_point.m_create_new_file;
218 bool previously_discarding = m->discarding;
219 auto generate_chapter = false;
220
221 mxdebug_if(m->debug_splitting, fmt::format("Splitting: splitpoint {0} reached before timestamp {1}, create new? {2}.\n", current_split_point.str(), mtx::string::format_timestamp(packet->assigned_timestamp), create_new_file));
222
223 finish_file(false, create_new_file, previously_discarding);
224
225 if (current_split_point.m_use_once) {
226 if ( !current_split_point.m_discard
227 && (chapter_generation_mode_e::when_appending == m->chapter_generation_mode)
228 && ( (split_point_c::parts == current_split_point.m_type)
229 || (split_point_c::parts_frame_field == current_split_point.m_type)))
230 generate_chapter = true;
231
232 if ( current_split_point.m_discard
233 && ( (split_point_c::parts == current_split_point.m_type)
234 || (split_point_c::parts_frame_field == current_split_point.m_type))
235 && ((m->current_split_point_idx + 1) >= m->split_points.size())) {
236 mxdebug_if(m->debug_splitting, fmt::format("Splitting: Last part in 'parts:' splitting mode finished\n"));
237 m->splitting_and_processed_fully = true;
238 }
239
240 m->discarding = current_split_point.m_discard;
241 ++m->current_split_point_idx;
242 }
243
244 if (create_new_file) {
245 create_next_output_file();
246 if (g_no_linking) {
247 m->previous_cluster_ts = -1;
248 m->timestamp_offset = g_video_packetizer ? m->max_video_timestamp_rendered : packet->assigned_timestamp;
249 }
250
251 m->bytes_in_file = 0;
252 m->first_timestamp_in_file = -1;
253 m->max_timestamp_in_file = -1;
254 m->min_timestamp_in_file.reset();
255 }
256
257 m->first_timestamp_in_part = -1;
258
259 handle_discarded_duration(create_new_file, previously_discarding);
260
261 if (generate_chapter)
262 generate_one_chapter(timestamp_c::ns(packet->assigned_timestamp - std::max<int64_t>(0, m->timestamp_offset) - m->discarded_duration));
263
264 prepare_new_cluster();
265 }
266
267 void
add_packet(packet_cptr packet)268 cluster_helper_c::add_packet(packet_cptr packet) {
269 if (!m->cluster)
270 prepare_new_cluster();
271
272 packet->normalize_timestamps();
273 render_before_adding_if_necessary(packet);
274 split_if_necessary(packet);
275
276 m->packets.push_back(packet);
277 m->cluster_content_size += packet->data->get_size();
278
279 if (packet->assigned_timestamp > m->max_timestamp_in_cluster)
280 m->max_timestamp_in_cluster = packet->assigned_timestamp;
281
282 if ((-1 == m->min_timestamp_in_cluster) || (packet->assigned_timestamp < m->min_timestamp_in_cluster))
283 m->min_timestamp_in_cluster = packet->assigned_timestamp;
284
285 render_after_adding_if_necessary(packet);
286
287 if (g_video_packetizer == packet->source)
288 ++m->frame_field_number;
289
290 generate_chapters_if_necessary(packet);
291 }
292
293 int64_t
get_timestamp()294 cluster_helper_c::get_timestamp() {
295 return m->packets.empty() ? 0 : m->packets.front()->assigned_timestamp;
296 }
297
298 void
prepare_new_cluster()299 cluster_helper_c::prepare_new_cluster() {
300 m->cluster.reset(new kax_cluster_c);
301 m->cluster_content_size = 0;
302 m->packets.clear();
303
304 m->cluster->SetParent(*g_kax_segment);
305 m->cluster->SetPreviousTimecode(std::max<int64_t>(0, m->previous_cluster_ts), static_cast<int64_t>(g_timestamp_scale));
306 }
307
308 int
get_cluster_content_size()309 cluster_helper_c::get_cluster_content_size() {
310 return m->cluster_content_size;
311 }
312
313 void
set_output(mm_io_c * out)314 cluster_helper_c::set_output(mm_io_c *out) {
315 m->out = out;
316 }
317
318 void
set_duration(render_groups_c * rg)319 cluster_helper_c::set_duration(render_groups_c *rg) {
320 if (rg->m_durations.empty())
321 return;
322
323 kax_block_blob_c *group = rg->m_groups.back().get();
324 int64_t def_duration = rg->m_source->get_track_default_duration();
325 int64_t block_duration = 0;
326
327 size_t i;
328 for (i = 0; rg->m_durations.size() > i; ++i)
329 block_duration += rg->m_durations[i];
330 mxdebug_if(m->debug_duration,
331 fmt::format("cluster_helper::set_duration: block_duration {0} rounded duration {1} def_duration {2} use_durations {3} rg->m_duration_mandatory {4}\n",
332 block_duration, round_timestamp_scale(block_duration), def_duration, g_use_durations ? 1 : 0, rg->m_duration_mandatory ? 1 : 0));
333
334 if (rg->m_duration_mandatory) {
335 if ( (0 == block_duration)
336 || ( (0 < block_duration)
337 && (round_timestamp_scale(block_duration) != round_timestamp_scale(static_cast<int64_t>(rg->m_durations.size()) * def_duration)))) {
338 auto rounding_error = rg->m_source->get_track_type() == track_subtitle ? rg->m_first_timestamp_rounding_error.value_or(0) : 0;
339 group->set_block_duration(round_timestamp_scale(block_duration + rounding_error));
340 }
341
342 } else if ( ( g_use_durations
343 || (0 < def_duration))
344 && (0 < block_duration)
345 && (round_timestamp_scale(block_duration) != round_timestamp_scale(rg->m_durations.size() * def_duration)))
346 group->set_block_duration(round_timestamp_scale(block_duration));
347 }
348
349 bool
must_duration_be_set(render_groups_c * rg,packet_cptr & new_packet)350 cluster_helper_c::must_duration_be_set(render_groups_c *rg,
351 packet_cptr &new_packet) {
352 size_t i;
353 int64_t block_duration = 0;
354 int64_t def_duration = new_packet->source->get_track_default_duration();
355 int64_t group_size = rg ? rg->m_durations.size() : 0;
356
357 if (rg)
358 for (i = 0; rg->m_durations.size() > i; ++i)
359 block_duration += rg->m_durations[i];
360
361 block_duration += new_packet->get_duration();
362
363 mxdebug_if(m->debug_duration, fmt::format("must_duration_be_set: rg 0x{0} block_duration {1} group_size {2} rg->duration_mandatory {3} new_packet->duration_mandatory {4}\n",
364 static_cast<void *>(rg), block_duration, group_size, !rg ? "—" : rg->m_duration_mandatory ? "1" : "0", new_packet->duration_mandatory));
365
366 if ((rg && rg->m_duration_mandatory) || new_packet->duration_mandatory) {
367 if ( ( (0 == block_duration)
368 && ((0 != group_size) || (new_packet->duration_mandatory && new_packet->has_duration())))
369 || ( (0 < block_duration)
370 && (round_timestamp_scale(block_duration) != round_timestamp_scale((group_size + 1) * def_duration)))) {
371 // if (!rg)
372 // mxinfo(fmt::format("YOYO 1 np mand {0} blodu {1} defdur {2} bloduRND {3} defdurRND {4} groupsize {5} ts {6}\n", new_packet->duration_mandatory, block_duration, (group_size + 1) * def_duration,
373 // round_timestamp_scale(block_duration), round_timestamp_scale((group_size + 1) * def_duration), group_size, mtx::string::format_timestamp(new_packet->assigned_timestamp)));
374 return true;
375 }
376
377 } else if ( ( g_use_durations
378 || (0 < def_duration))
379 && (0 < block_duration)
380 && (round_timestamp_scale(block_duration) != round_timestamp_scale((group_size + 1) * def_duration))) {
381 // if (!rg)
382 // mxinfo(fmt::format("YOYO 1 np blodu {0} defdur {1} bloduRND {2} defdurRND {3} ts {4}\n", block_duration, def_duration, round_timestamp_scale(block_duration), round_timestamp_scale((group_size + 1) * def_duration),
383 // mtx::string::format_timestamp(new_packet->assigned_timestamp)));
384 return true;
385 }
386
387 return false;
388 }
389
390 /*
391 <+Asylum> The chicken and the egg are lying in bed next to each
392 other after a good hard shag, the chicken is smoking a
393 cigarette, the egg says "Well that answers that bloody
394 question doesn't it"
395 */
396
397 int
render()398 cluster_helper_c::render() {
399 std::vector<render_groups_cptr> render_groups;
400 kax_cues_with_cleanup_c cues;
401 cues.SetGlobalTimecodeScale(g_timestamp_scale);
402
403 bool use_simpleblock = !mtx::hacks::is_engaged(mtx::hacks::NO_SIMPLE_BLOCKS);
404
405 LacingType lacing_type = mtx::hacks::is_engaged(mtx::hacks::LACING_XIPH) ? LACING_XIPH : mtx::hacks::is_engaged(mtx::hacks::LACING_EBML) ? LACING_EBML : LACING_AUTO;
406
407 int64_t min_cl_timestamp = std::numeric_limits<int64_t>::max();
408 int64_t max_cl_timestamp = 0;
409
410 int elements_in_cluster = 0;
411 bool added_to_cues = false;
412
413 // Splitpoint stuff
414 if ((-1 == m->header_overhead) && splitting())
415 m->header_overhead = m->out->getFilePointer() + g_tags_size;
416
417 // Make sure that we don't have negative/wrapped around timestamps in the output file.
418 // Can happend when we're splitting; so adjust timestamp_offset accordingly.
419 m->timestamp_offset = std::accumulate(m->packets.begin(), m->packets.end(), m->timestamp_offset, [](int64_t a, const packet_cptr &p) { return std::min(a, p->assigned_timestamp); });
420 int64_t timestamp_offset = m->timestamp_offset + get_discarded_duration();
421
422 for (auto &pack : m->packets) {
423 generic_packetizer_c *source = pack->source;
424 bool has_codec_state = !!pack->codec_state;
425
426 if (g_video_packetizer == source)
427 m->max_video_timestamp_rendered = std::max(pack->assigned_timestamp + pack->get_duration(), m->max_video_timestamp_rendered);
428
429 if (discarding()) {
430 if (-1 == m->first_discarded_timestamp)
431 m->first_discarded_timestamp = pack->assigned_timestamp;
432 m->last_discarded_timestamp_and_duration = std::max(m->last_discarded_timestamp_and_duration, pack->assigned_timestamp + pack->get_duration());
433 continue;
434 }
435
436 if (source->contains_gap())
437 m->cluster->SetSilentTrackUsed();
438
439 render_groups_c *render_group = nullptr;
440 for (auto &rg : render_groups)
441 if (rg->m_source == source) {
442 render_group = rg.get();
443 break;
444 }
445
446 if (!render_group) {
447 render_groups.push_back(render_groups_cptr(new render_groups_c(source)));
448 render_group = render_groups.back().get();
449 }
450
451 min_cl_timestamp = std::min(pack->assigned_timestamp, min_cl_timestamp);
452 max_cl_timestamp = std::max(pack->assigned_timestamp, max_cl_timestamp);
453
454 KaxTrackEntry &track_entry = static_cast<KaxTrackEntry &>(*source->get_track_entry());
455
456 kax_block_blob_c *previous_block_group = !render_group->m_groups.empty() ? render_group->m_groups.back().get() : nullptr;
457 kax_block_blob_c *new_block_group = previous_block_group;
458
459 auto require_new_render_group = !render_group->m_more_data
460 || !pack->is_key_frame()
461 || has_codec_state
462 || pack->has_discard_padding()
463 || render_group->m_has_discard_padding
464 || render_group->follows_gap(*pack)
465 || must_duration_be_set(nullptr, pack)
466 || source->is_lacing_prevented();
467
468 if (require_new_render_group) {
469 set_duration(render_group);
470 render_group->m_durations.clear();
471 render_group->m_duration_mandatory = false;
472 render_group->m_first_timestamp_rounding_error.reset();
473
474 BlockBlobType this_block_blob_type
475 = !use_simpleblock ? BLOCK_BLOB_NO_SIMPLE
476 : must_duration_be_set(render_group, pack) ? BLOCK_BLOB_NO_SIMPLE
477 : !pack->data_adds.empty() ? BLOCK_BLOB_NO_SIMPLE
478 : has_codec_state ? BLOCK_BLOB_NO_SIMPLE
479 : pack->has_discard_padding() ? BLOCK_BLOB_NO_SIMPLE
480 : BLOCK_BLOB_ALWAYS_SIMPLE;
481
482 render_group->m_groups.push_back(kax_block_blob_cptr(new kax_block_blob_c(this_block_blob_type)));
483 new_block_group = render_group->m_groups.back().get();
484 m->cluster->AddBlockBlob(new_block_group);
485 new_block_group->SetParent(*m->cluster);
486
487 added_to_cues = false;
488 }
489
490 if (!render_group->m_first_timestamp_rounding_error)
491 render_group->m_first_timestamp_rounding_error = pack->unmodified_assigned_timestamp - pack->assigned_timestamp;
492
493 for (auto const &extension : pack->extensions)
494 if (packet_extension_c::BEFORE_ADDING_TO_CLUSTER_CB == extension->get_type())
495 static_cast<before_adding_to_cluster_cb_packet_extension_c *>(extension.get())->get_callback()(pack, timestamp_offset);
496
497 auto data_buffer = new DataBuffer(static_cast<binary *>(pack->data->get_buffer()), pack->data->get_size());
498
499 // Now put the packet into the cluster.
500 render_group->m_more_data = new_block_group->add_frame_auto(track_entry, pack->assigned_timestamp - timestamp_offset, *data_buffer, lacing_type,
501 pack->has_bref() ? pack->bref - timestamp_offset : -1,
502 pack->has_fref() ? pack->fref - timestamp_offset : -1,
503 pack->key_flag, pack->discardable_flag);
504
505 if (has_codec_state) {
506 KaxBlockGroup &bgroup = (KaxBlockGroup &)*new_block_group;
507 KaxCodecState *cstate = new KaxCodecState;
508 bgroup.PushElement(*cstate);
509 cstate->CopyBuffer(pack->codec_state->get_buffer(), pack->codec_state->get_size());
510 }
511
512 if (-1 == m->first_timestamp_in_file)
513 m->first_timestamp_in_file = pack->assigned_timestamp;
514 if (-1 == m->first_timestamp_in_part)
515 m->first_timestamp_in_part = pack->assigned_timestamp;
516
517 m->min_timestamp_in_file = std::min(timestamp_c::ns(pack->assigned_timestamp), m->min_timestamp_in_file.value_or_max());
518 m->max_timestamp_in_file = std::max(pack->assigned_timestamp, m->max_timestamp_in_file);
519 m->max_timestamp_and_duration = std::max(pack->assigned_timestamp + pack->get_duration(), m->max_timestamp_and_duration);
520
521 if (!pack->is_key_frame() || !track_entry.LacingEnabled())
522 render_group->m_more_data = false;
523
524 if (pack->has_duration())
525 render_group->m_durations.push_back(pack->get_unmodified_duration());
526 render_group->m_duration_mandatory |= pack->duration_mandatory;
527 render_group->m_expected_next_timestamp = pack->assigned_timestamp + pack->get_duration();
528
529 cues_c::get().set_duration_for_id_timestamp(source->get_track_num(), pack->assigned_timestamp - timestamp_offset, pack->get_duration());
530
531 if (new_block_group) {
532 // Set the reference priority if it was wanted.
533 if ((0 < pack->ref_priority) && new_block_group->replace_simple_by_group())
534 GetChild<KaxReferencePriority>(*new_block_group).SetValue(pack->ref_priority);
535
536 // Handle BlockAdditions if needed
537 if (!pack->data_adds.empty() && new_block_group->ReplaceSimpleByGroup()) {
538 KaxBlockAdditions &additions = AddEmptyChild<KaxBlockAdditions>(*new_block_group);
539
540 size_t data_add_idx;
541 for (data_add_idx = 0; pack->data_adds.size() > data_add_idx; ++data_add_idx) {
542 auto &block_more = AddEmptyChild<KaxBlockMore>(additions);
543 GetChild<KaxBlockAddID >(block_more).SetValue(data_add_idx + 1);
544 GetChild<KaxBlockAdditional>(block_more).CopyBuffer((binary *)pack->data_adds[data_add_idx]->get_buffer(), pack->data_adds[data_add_idx]->get_size());
545 }
546 }
547
548 if (pack->has_discard_padding()) {
549 GetChild<KaxDiscardPadding>(*new_block_group).SetValue(pack->discard_padding.to_ns());
550 render_group->m_has_discard_padding = true;
551 }
552 }
553
554 elements_in_cluster++;
555
556 if (!new_block_group)
557 new_block_group = previous_block_group;
558
559 else if (g_write_cues && (!added_to_cues || has_codec_state)) {
560 added_to_cues = add_to_cues_maybe(pack);
561 if (added_to_cues)
562 cues.AddBlockBlob(*new_block_group);
563 }
564
565 pack->group = new_block_group;
566
567 pack->account(m->track_statistics[ source->get_uid() ], timestamp_offset);
568
569 source->after_packet_rendered(*pack);
570 }
571
572 mtx::at_scope_exit_c cleanup([this]() {
573 m->cluster->delete_non_blocks();
574 });
575
576 if (!discarding()) {
577 if (0 < elements_in_cluster) {
578 for (auto &rg : render_groups)
579 set_duration(rg.get());
580
581 m->cluster->SetPreviousTimecode(min_cl_timestamp - timestamp_offset - 1, (int64_t)g_timestamp_scale);
582 m->cluster->set_min_timestamp(min_cl_timestamp - timestamp_offset);
583 m->cluster->set_max_timestamp(max_cl_timestamp - timestamp_offset);
584
585 m->cluster->Render(*m->out, cues);
586 g_doc_type_version_handler->account(*m->cluster);
587 m->bytes_in_file += m->cluster->ElementSize();
588
589 if (g_kax_sh_cues)
590 g_kax_sh_cues->IndexThis(*m->cluster, *g_kax_segment);
591
592 m->previous_cluster_ts = m->cluster->GlobalTimecode();
593
594 cues_c::get().postprocess_cues(cues, *m->cluster);
595
596 } else
597 m->previous_cluster_ts = -1;
598 }
599
600 m->min_timestamp_in_cluster = -1;
601 m->max_timestamp_in_cluster = -1;
602
603 return 1;
604 }
605
606 bool
add_to_cues_maybe(packet_cptr & pack)607 cluster_helper_c::add_to_cues_maybe(packet_cptr &pack) {
608 auto &source = *pack->source;
609 auto strategy = source.get_cue_creation();
610
611 // Update the cues (index table) either if cue entries for I frames were requested and this is an I frame...
612 bool add = (CUE_STRATEGY_IFRAMES == strategy) && pack->is_key_frame();
613
614 // ... or if a codec state change is present ...
615 add = add || !!pack->codec_state;
616
617 // ... or if the user requested entries for all frames ...
618 add = add || (CUE_STRATEGY_ALL == strategy);
619
620 // ... or if this is a key frame for an audio track, there is no
621 // video track and the last cue entry was created more than 0.5s
622 // ago.
623 add = add || ( (CUE_STRATEGY_SPARSE == strategy)
624 && (track_audio == source.get_track_type())
625 && !g_video_packetizer
626 && pack->is_key_frame()
627 && ( (0 > source.get_last_cue_timestamp())
628 || ((pack->assigned_timestamp - source.get_last_cue_timestamp()) >= 500'000'000)));
629
630 if (!add)
631 return false;
632
633 source.set_last_cue_timestamp(pack->assigned_timestamp);
634
635 ++m->num_cue_elements;
636 g_cue_writing_requested = 1;
637
638 return true;
639 }
640
641 int64_t
get_duration() const642 cluster_helper_c::get_duration()
643 const {
644 auto result = m->max_timestamp_and_duration - m->min_timestamp_in_file.to_ns(0) - m->discarded_duration;
645 mxdebug_if(m->debug_duration,
646 fmt::format("cluster_helper_c::get_duration(): max_tc_and_dur {0} - min_tc_in_file {1} - discarded_duration {2} = {3} ; first_tc_in_file = {4}\n",
647 m->max_timestamp_and_duration, m->min_timestamp_in_file.to_ns(0), m->discarded_duration, result, m->first_timestamp_in_file));
648 return result;
649 }
650
651 int64_t
get_discarded_duration() const652 cluster_helper_c::get_discarded_duration()
653 const {
654 return m->discarded_duration;
655 }
656
657 void
handle_discarded_duration(bool create_new_file,bool previously_discarding)658 cluster_helper_c::handle_discarded_duration(bool create_new_file,
659 bool previously_discarding) {
660 m->previous_discarded_duration = m->discarded_duration;
661
662 if (create_new_file) { // || (!previously_discarding && m->discarding)) {
663 mxdebug_if(m->debug_splitting,
664 fmt::format("RESETTING discarded duration of {0}, create_new_file {1} previously_discarding {2} m->discarding {3}\n",
665 m->discarded_duration, create_new_file, previously_discarding, m->discarding));
666 m->discarded_duration = 0;
667
668 } else if (previously_discarding && !m->discarding) {
669 auto diff = m->last_discarded_timestamp_and_duration - std::max<int64_t>(m->first_discarded_timestamp, 0);
670 m->discarded_duration += diff;
671
672 mxdebug_if(m->debug_splitting,
673 fmt::format("ADDING to discarded duration TC at {0} / {1} diff {2} new total {3} create_new_file {4} previously_discarding {5} m->discarding {6}\n",
674 m->first_discarded_timestamp, m->last_discarded_timestamp_and_duration, diff, m->discarded_duration,
675 create_new_file, previously_discarding, m->discarding));
676 } else
677 mxdebug_if(m->debug_splitting,
678 fmt::format("KEEPING discarded duration at {0}, create_new_file {1} previously_discarding {2} m->discarding {3}\n",
679 m->discarded_duration, create_new_file, previously_discarding, m->discarding));
680
681 m->first_discarded_timestamp = -1;
682 m->last_discarded_timestamp_and_duration = 0;
683 }
684
685 void
add_split_point(const split_point_c & split_point)686 cluster_helper_c::add_split_point(const split_point_c &split_point) {
687 m->split_points.push_back(split_point);
688
689 if (m->split_points.size() != 1)
690 return;
691
692 m->discarding = m->split_points[0].m_discard;
693
694 if (0 == m->split_points[0].m_point)
695 ++m->current_split_point_idx;
696 }
697
698 bool
split_mode_produces_many_files() const699 cluster_helper_c::split_mode_produces_many_files()
700 const {
701 if (g_splitting_by_all_chapters || !g_splitting_by_chapter_numbers.empty())
702 return true;
703
704 if (!splitting())
705 return false;
706
707 if ( (split_point_c::parts != m->split_points.front().m_type)
708 && (split_point_c::parts_frame_field != m->split_points.front().m_type))
709 return true;
710
711 bool first = true;
712 for (auto &split_point : m->split_points)
713 if (!split_point.m_discard && split_point.m_create_new_file) {
714 if (!first)
715 return true;
716 first = false;
717 }
718
719 return false;
720 }
721
722 void
discard_queued_packets()723 cluster_helper_c::discard_queued_packets() {
724 m->packets.clear();
725 }
726
727 void
dump_split_points() const728 cluster_helper_c::dump_split_points()
729 const {
730 mxdebug_if(m->debug_splitting,
731 fmt::format("Split points:{0}\n",
732 std::accumulate(m->split_points.begin(), m->split_points.end(), ""s, [](std::string const &accu, split_point_c const &point) { return accu + " " + point.str(); })));
733 }
734
735 void
create_tags_for_track_statistics(KaxTags & tags,std::string const & writing_app,QDateTime const & writing_date)736 cluster_helper_c::create_tags_for_track_statistics(KaxTags &tags,
737 std::string const &writing_app,
738 QDateTime const &writing_date) {
739 std::optional<QDateTime> actual_writing_date;
740 if (g_write_date)
741 actual_writing_date = writing_date;
742
743 for (auto const &ptzr : g_packetizers) {
744 auto track_uid = ptzr.packetizer->get_uid();
745
746 m->track_statistics[track_uid]
747 .set_track_uid(track_uid)
748 .set_source_id(ptzr.packetizer->get_source_id())
749 .create_tags(tags, writing_app, actual_writing_date);
750 }
751
752 m->track_statistics.clear();
753 }
754
755 void
enable_chapter_generation(chapter_generation_mode_e mode,mtx::bcp47::language_c const & language)756 cluster_helper_c::enable_chapter_generation(chapter_generation_mode_e mode,
757 mtx::bcp47::language_c const &language) {
758 m->chapter_generation_mode = mode;
759 m->chapter_generation_language = language.is_valid() ? language : mtx::bcp47::language_c::parse("eng");
760 }
761
762 chapter_generation_mode_e
get_chapter_generation_mode() const763 cluster_helper_c::get_chapter_generation_mode()
764 const {
765 return m->chapter_generation_mode;
766 }
767
768 void
set_chapter_generation_interval(timestamp_c const & interval)769 cluster_helper_c::set_chapter_generation_interval(timestamp_c const &interval) {
770 m->chapter_generation_interval = interval;
771 }
772
773 void
verify_and_report_chapter_generation_parameters() const774 cluster_helper_c::verify_and_report_chapter_generation_parameters()
775 const {
776 if (chapter_generation_mode_e::none == m->chapter_generation_mode)
777 return;
778
779 if (!m->chapter_generation_reference_track)
780 mxerror(Y("Chapter generation is only possible if at least one video or audio track is copied.\n"));
781
782 mxinfo(fmt::format(Y("Using the track with the ID {0} from the file '{1}' as the reference for chapter generation.\n"),
783 m->chapter_generation_reference_track->m_ti.m_id, m->chapter_generation_reference_track->m_ti.m_fname));
784 }
785
786 void
register_new_packetizer(generic_packetizer_c & ptzr)787 cluster_helper_c::register_new_packetizer(generic_packetizer_c &ptzr) {
788 auto new_track_type = ptzr.get_track_type();
789
790 if (!g_video_packetizer && (track_video == new_track_type))
791 g_video_packetizer = &ptzr;
792
793 auto current_ptzr_prio = !m->chapter_generation_reference_track ? 0
794 : m->chapter_generation_reference_track->get_track_type() == track_video ? 100
795 : m->chapter_generation_reference_track->get_track_type() == track_audio ? 80
796 : 0;
797
798 auto new_ptzr_prio = new_track_type == track_video ? 100
799 : new_track_type == track_audio ? 80
800 : 0;
801
802 if (new_ptzr_prio > current_ptzr_prio)
803 m->chapter_generation_reference_track = &ptzr;
804 }
805
806 void
generate_chapters_if_necessary(packet_cptr const & packet)807 cluster_helper_c::generate_chapters_if_necessary(packet_cptr const &packet) {
808 if ((chapter_generation_mode_e::none == m->chapter_generation_mode) || !m->chapter_generation_reference_track)
809 return;
810
811 auto successor = m->chapter_generation_reference_track->get_connected_successor();
812 if (successor) {
813 if (chapter_generation_mode_e::when_appending == m->chapter_generation_mode)
814 m->chapter_generation_last_generated.reset();
815
816 while ((successor = m->chapter_generation_reference_track->get_connected_successor()))
817 m->chapter_generation_reference_track = successor;
818 }
819
820 auto ptzr = packet->source;
821 if (ptzr != m->chapter_generation_reference_track)
822 return;
823
824 if (chapter_generation_mode_e::when_appending == m->chapter_generation_mode) {
825 if (packet->is_key_frame() && !m->chapter_generation_last_generated.valid())
826 generate_one_chapter(timestamp_c::ns(packet->assigned_timestamp));
827
828 return;
829 }
830
831 if (chapter_generation_mode_e::interval != m->chapter_generation_mode)
832 return;
833
834 auto now = timestamp_c::ns(packet->assigned_timestamp);
835
836 while (true) {
837 if (!m->chapter_generation_last_generated.valid())
838 generate_one_chapter(timestamp_c::ns(0));
839
840 auto next_chapter = m->chapter_generation_last_generated + m->chapter_generation_interval;
841 if (next_chapter > now)
842 break;
843
844 generate_one_chapter(next_chapter);
845 }
846 }
847
848 void
generate_one_chapter(timestamp_c const & timestamp)849 cluster_helper_c::generate_one_chapter(timestamp_c const ×tamp) {
850 auto appended_file_name = chapter_generation_mode_e::when_appending == m->chapter_generation_mode ? m->chapter_generation_reference_track->m_reader->m_ti.m_fname : std::string{};
851 m->chapter_generation_number += 1;
852 m->chapter_generation_last_generated = timestamp;
853 auto name = mtx::chapters::format_name_template(mtx::chapters::g_chapter_generation_name_template.get_translated(), m->chapter_generation_number, timestamp, appended_file_name);
854
855 add_chapter_atom(timestamp, name, m->chapter_generation_language);
856 }
857
858 std::unique_ptr<cluster_helper_c> g_cluster_helper;
859