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 &current_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 &current_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 &timestamp) {
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