Coverage Report

Created: 2026-09-14 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/sequences/track.cc
Line
Count
Source
1
/*
2
 * HEIF image base codec.
3
 * Copyright (c) 2024 Dirk Farin <dirk.farin@gmail.com>
4
 *
5
 * This file is part of libheif.
6
 *
7
 * libheif is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Lesser General Public License as
9
 * published by the Free Software Foundation, either version 3 of
10
 * the License, or (at your option) any later version.
11
 *
12
 * libheif is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public License
18
 * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
#include <cstring>
22
#include "track.h"
23
#include "context.h"
24
#include "sequences/seq_boxes.h"
25
#include "sequences/chunk.h"
26
#include "sequences/track_visual.h"
27
#include "sequences/track_metadata.h"
28
#include "api_structs.h"
29
#include <algorithm>
30
#include <limits>
31
#include <memory>
32
#include <utility>
33
34
35
TrackOptions& TrackOptions::operator=(const TrackOptions& src)
36
0
{
37
0
  if (&src == this) {
38
0
    return *this;
39
0
  }
40
41
0
  this->track_timescale = src.track_timescale;
42
0
  this->write_sample_aux_infos_interleaved = src.write_sample_aux_infos_interleaved;
43
0
  this->with_sample_tai_timestamps = src.with_sample_tai_timestamps;
44
45
0
  if (src.tai_clock_info) {
46
0
    this->tai_clock_info = heif_tai_clock_info_alloc();
47
0
    heif_tai_clock_info_copy(this->tai_clock_info, src.tai_clock_info);
48
0
  }
49
0
  else {
50
0
    this->tai_clock_info = nullptr;
51
0
  }
52
53
0
  this->with_sample_content_ids = src.with_sample_content_ids;
54
0
  this->gimi_track_content_id = src.gimi_track_content_id;
55
56
0
  return *this;
57
0
}
58
59
60
SampleAuxInfoHelper::SampleAuxInfoHelper(bool interleaved)
61
0
  : m_interleaved(interleaved)
62
0
{
63
0
  m_saiz = std::make_shared<Box_saiz>();
64
0
  m_saio = std::make_shared<Box_saio>();
65
0
}
66
67
68
void SampleAuxInfoHelper::set_aux_info_type(uint32_t aux_info_type, uint32_t aux_info_type_parameter)
69
0
{
70
0
  m_saiz->set_aux_info_type(aux_info_type, aux_info_type_parameter);
71
0
  m_saio->set_aux_info_type(aux_info_type, aux_info_type_parameter);
72
0
}
73
74
Error SampleAuxInfoHelper::add_sample_info(const std::vector<uint8_t>& data)
75
0
{
76
0
  if (data.size() > 0xFF) {
77
0
    return {
78
0
      heif_error_Encoding_error,
79
0
      heif_suberror_Unspecified,
80
0
      "Encoded sample auxiliary information exceeds maximum size"
81
0
    };
82
0
  }
83
84
0
  m_saiz->add_sample_size(static_cast<uint8_t>(data.size()));
85
86
0
  m_data.insert(m_data.end(), data.begin(), data.end());
87
88
0
  return Error::Ok;
89
0
}
90
91
void SampleAuxInfoHelper::add_nonpresent_sample()
92
0
{
93
0
  m_saiz->add_nonpresent_sample();
94
0
}
95
96
97
void SampleAuxInfoHelper::write_interleaved(const std::shared_ptr<HeifFile>& file)
98
0
{
99
0
  if (m_interleaved && !m_data.empty()) {
100
    // TODO: I think this does not work because the image data does not know that there is SAI in-between
101
0
    uint64_t pos = file->append_mdat_data(m_data);
102
0
    m_saio->add_chunk_offset(pos);
103
104
0
    m_data.clear();
105
0
  }
106
0
}
107
108
void SampleAuxInfoHelper::write_all(const std::shared_ptr<Box>& parent, const std::shared_ptr<HeifFile>& file)
109
0
{
110
0
  parent->append_child_box(m_saiz);
111
0
  parent->append_child_box(m_saio);
112
113
0
  if (!m_data.empty()) {
114
0
    uint64_t pos = file->append_mdat_data(m_data);
115
0
    m_saio->add_chunk_offset(pos);
116
0
  }
117
0
}
118
119
120
SampleAuxInfoReader::SampleAuxInfoReader(const std::shared_ptr<Box_saiz>& saiz,
121
                                         const std::shared_ptr<Box_saio>& saio,
122
                                         const std::vector<std::shared_ptr<Chunk>>& chunks)
123
0
{
124
0
  m_saiz = saiz;
125
0
  m_saio = saio;
126
127
0
  bool oneChunk = (saio->get_num_chunks() == 1);
128
129
0
  uint32_t current_chunk = 0;
130
0
  uint64_t offset = saio->get_chunk_offset(0);
131
0
  uint32_t nSamples = saiz->get_num_samples();
132
133
0
  m_contiguous_and_constant_size = (oneChunk && m_saiz->have_samples_constant_size());
134
135
0
  if (m_contiguous_and_constant_size) {
136
0
    m_singleChunk_offset = offset;
137
0
  }
138
0
  else {
139
0
    m_sample_offsets.resize(nSamples);
140
141
0
    for (uint32_t i = 0; i < nSamples; i++) {
142
0
      if (!oneChunk && i > chunks[current_chunk]->last_sample_number()) {
143
0
        current_chunk++;
144
0
        if (current_chunk >= chunks.size()) {
145
0
          break;
146
0
        }
147
0
        offset = saio->get_chunk_offset(current_chunk);
148
0
      }
149
150
0
      m_sample_offsets[i] = offset;
151
0
      offset += saiz->get_sample_size(i);
152
0
    }
153
0
  }
154
0
}
155
156
157
heif_sample_aux_info_type SampleAuxInfoReader::get_type() const
158
0
{
159
0
  heif_sample_aux_info_type type;
160
0
  type.type = m_saiz->get_aux_info_type();
161
0
  type.parameter = m_saiz->get_aux_info_type_parameter();
162
0
  return type;
163
0
}
164
165
166
Result<std::vector<uint8_t> > SampleAuxInfoReader::get_sample_info(const HeifFile* file, uint32_t sample_idx)
167
0
{
168
0
  uint64_t offset;
169
0
  uint8_t size;
170
171
0
  if (m_contiguous_and_constant_size) {
172
0
    size = m_saiz->get_sample_size(0);
173
0
    offset = m_singleChunk_offset + uint64_t{sample_idx} * size;
174
0
  }
175
0
  else {
176
0
    size = m_saiz->get_sample_size(sample_idx);
177
0
    if (size > 0) {
178
0
      if (sample_idx >= m_sample_offsets.size()) {
179
0
        return {};
180
0
      }
181
182
0
      offset = m_sample_offsets[sample_idx];
183
0
    }
184
0
  }
185
186
0
  std::vector<uint8_t> data;
187
188
0
  if (size > 0) {
189
0
    Error err = file->append_data_from_file_range(data, offset, size);
190
0
    if (err) {
191
0
      return err;
192
0
    }
193
0
  }
194
195
0
  return data;
196
0
}
197
198
199
std::shared_ptr<HeifFile> Track::get_file() const
200
0
{
201
0
  return m_heif_context->get_heif_file();
202
0
}
203
204
205
Track::Track(HeifContext* ctx)
206
0
{
207
0
  m_heif_context = ctx;
208
0
}
209
210
211
Error Track::load(const std::shared_ptr<Box_trak>& trak_box)
212
0
{
213
0
  m_trak = trak_box;
214
215
0
  auto tkhd = trak_box->get_child_box<Box_tkhd>();
216
0
  if (!tkhd) {
217
0
    return {
218
0
      heif_error_Invalid_input,
219
0
      heif_suberror_Unspecified,
220
0
      "Track has no 'tkhd' box."
221
0
    };
222
0
  }
223
224
0
  m_tkhd = tkhd;
225
226
0
  m_id = tkhd->get_track_id();
227
228
0
  auto edts = trak_box->get_child_box<Box_edts>();
229
0
  if (edts) {
230
0
    m_elst = edts->get_child_box<Box_elst>();
231
0
  }
232
233
0
  auto mdia = trak_box->get_child_box<Box_mdia>();
234
0
  if (!mdia) {
235
0
    return {
236
0
      heif_error_Invalid_input,
237
0
      heif_suberror_Unspecified,
238
0
      "Track has no 'mdia' box."
239
0
    };
240
0
  }
241
242
0
  m_tref = trak_box->get_child_box<Box_tref>();
243
244
0
  auto hdlr = mdia->get_child_box<Box_hdlr>();
245
0
  if (!hdlr) {
246
0
    return {
247
0
      heif_error_Invalid_input,
248
0
      heif_suberror_Unspecified,
249
0
      "Track has no 'hdlr' box."
250
0
    };
251
0
  }
252
253
0
  m_handler_type = hdlr->get_handler_type();
254
255
0
  m_minf = mdia->get_child_box<Box_minf>();
256
0
  if (!m_minf) {
257
0
    return {
258
0
      heif_error_Invalid_input,
259
0
      heif_suberror_Unspecified,
260
0
      "Track has no 'minf' box."
261
0
    };
262
0
  }
263
264
0
  m_mdhd = mdia->get_child_box<Box_mdhd>();
265
0
  if (!m_mdhd) {
266
0
    return {
267
0
      heif_error_Invalid_input,
268
0
      heif_suberror_Unspecified,
269
0
      "Track has no 'mdhd' box."
270
0
    };
271
0
  }
272
273
0
  auto stbl = m_minf->get_child_box<Box_stbl>();
274
0
  if (!stbl) {
275
0
    return {
276
0
      heif_error_Invalid_input,
277
0
      heif_suberror_Unspecified,
278
0
      "Track has no 'stbl' box."
279
0
    };
280
0
  }
281
282
0
  m_stsd = stbl->get_child_box<Box_stsd>();
283
0
  if (!m_stsd) {
284
0
    return {
285
0
      heif_error_Invalid_input,
286
0
      heif_suberror_Unspecified,
287
0
      "Track has no 'stsd' box."
288
0
    };
289
0
  }
290
291
0
  m_stsc = stbl->get_child_box<Box_stsc>();
292
0
  if (!m_stsc) {
293
0
    return {
294
0
      heif_error_Invalid_input,
295
0
      heif_suberror_Unspecified,
296
0
      "Track has no 'stsc' box."
297
0
    };
298
0
  }
299
300
0
  m_stco = stbl->get_child_box<Box_stco>();
301
0
  if (!m_stco) {
302
0
    return {
303
0
      heif_error_Invalid_input,
304
0
      heif_suberror_Unspecified,
305
0
      "Track has no 'stco' box."
306
0
    };
307
0
  }
308
309
0
  m_stsz = stbl->get_child_box<Box_stsz>();
310
0
  if (!m_stsz) {
311
0
    return {
312
0
      heif_error_Invalid_input,
313
0
      heif_suberror_Unspecified,
314
0
      "Track has no 'stsz' box."
315
0
    };
316
0
  }
317
318
  // Enforce the sequence-frame limit before allocating any per-chunk state below.
319
  // Box_stsz::parse already applies this when parsing from a file, but check again
320
  // here so the invariant holds even if m_stsz was built another way.
321
0
  const auto* limits = m_heif_context->get_security_limits();
322
0
  if (limits->max_sequence_frames > 0 &&
323
0
      m_stsz->num_samples() > limits->max_sequence_frames) {
324
0
    return {
325
0
      heif_error_Memory_allocation_error,
326
0
      heif_suberror_Security_limit_exceeded,
327
0
      "Security limit for maximum number of sequence frames exceeded"
328
0
    };
329
0
  }
330
331
0
  m_stts = stbl->get_child_box<Box_stts>();
332
0
  if (!m_stts) {
333
0
    return {
334
0
      heif_error_Invalid_input,
335
0
      heif_suberror_Unspecified,
336
0
      "Track has no 'stts' box."
337
0
    };
338
0
  }
339
340
0
  m_ctts = stbl->get_child_box<Box_ctts>();
341
342
  // --- check that number of samples in various boxes are consistent
343
344
0
  if (m_stts->get_number_of_samples() != m_stsz->num_samples()) {
345
0
    return {
346
0
      heif_error_Invalid_input,
347
0
      heif_suberror_Unspecified,
348
0
      "Number of samples in 'stts' and 'stsz' is inconsistent."
349
0
    };
350
0
  }
351
352
0
  if (m_ctts && m_ctts->get_number_of_samples() != m_stsz->num_samples()) {
353
0
    return {
354
0
      heif_error_Invalid_input,
355
0
      heif_suberror_Unspecified,
356
0
      "Number of samples in 'ctts' and 'stsz' is inconsistent."
357
0
    };
358
0
  }
359
360
0
  const std::vector<uint32_t>& chunk_offsets = m_stco->get_offsets();
361
0
  assert(chunk_offsets.size() <= (size_t) std::numeric_limits<uint32_t>::max()); // There cannot be more than uint32_t chunks.
362
363
  // Account the per-chunk sample-range tables (Chunk::m_sample_ranges) against
364
  // max_total_memory before building any chunk. Their combined size across all
365
  // chunks is num_samples * sizeof(SampleFileRange); a small track can declare
366
  // millions of samples in one chunk, so without this the tables are untracked
367
  // and multiple tracks can exhaust memory undetected (GHSA-xw34-mjcp-jqh8, V2/V3).
368
0
  if (auto err = m_chunk_sample_ranges_memory.alloc(m_stsz->num_samples(),
369
0
                                                    Chunk::sample_range_entry_size(),
370
0
                                                    limits, "the sequence chunk sample-range tables")) {
371
0
    return err;
372
0
  }
373
374
0
  uint32_t current_sample_idx = 0;
375
0
  int32_t previous_sample_description_index = -1;
376
377
0
  for (size_t chunk_idx = 0; chunk_idx < chunk_offsets.size(); chunk_idx++) {
378
0
    auto* s2c = m_stsc->get_chunk(static_cast<uint32_t>(chunk_idx + 1));
379
0
    if (!s2c) {
380
0
      return {
381
0
        heif_error_Invalid_input,
382
0
        heif_suberror_Unspecified,
383
0
        "'stco' box references a non-existing chunk."
384
0
      };
385
0
    }
386
387
0
    Box_stsc::SampleToChunk sampleToChunk = *s2c;
388
389
0
    auto sample_description = m_stsd->get_sample_entry(sampleToChunk.sample_description_index - 1);
390
0
    if (!sample_description) {
391
0
      return {
392
0
        heif_error_Invalid_input,
393
0
        heif_suberror_Unspecified,
394
0
        "Track references a non-existing sample description."
395
0
      };
396
0
    }
397
398
0
    if (auto auxi = sample_description->get_child_box<Box_auxi>()) {
399
0
      m_auxiliary_info_type = auxi->get_aux_track_type_urn();
400
0
    }
401
402
0
    if (m_first_taic == nullptr) {
403
0
      auto taic = sample_description->get_child_box<Box_taic>();
404
0
      if (taic) {
405
0
        m_first_taic = taic;
406
0
      }
407
0
    }
408
409
0
    if (static_cast<uint64_t>(current_sample_idx) + sampleToChunk.samples_per_chunk > m_stsz->num_samples()) {
410
0
      return {
411
0
        heif_error_Invalid_input,
412
0
        heif_suberror_Unspecified,
413
0
        "Number of samples in 'stsc' box exceeds sample sizes in 'stsz' box."
414
0
      };
415
0
    }
416
417
0
    auto chunk = Chunk::create(m_heif_context, m_id,
418
0
                               current_sample_idx, sampleToChunk.samples_per_chunk,
419
0
                               m_stco->get_offsets()[chunk_idx],
420
0
                               m_stsz);
421
422
0
    if (!chunk) {
423
0
      return {
424
0
        heif_error_Invalid_input,
425
0
        heif_suberror_Unspecified,
426
0
        "Chunk file offset overflows 64-bit range."
427
0
      };
428
0
    }
429
430
0
    if (auto visualSampleDescription = std::dynamic_pointer_cast<const Box_VisualSampleEntry>(sample_description)) {
431
0
      if (chunk_idx > 0 && (int32_t) sampleToChunk.sample_description_index == previous_sample_description_index) {
432
        // reuse decoder from previous chunk if it uses the sample sample_description_index
433
0
        chunk->set_decoder(m_chunks[chunk_idx - 1]->get_decoder());
434
0
      }
435
0
      else {
436
        // use a new decoder
437
0
        auto decoder = Decoder::alloc_for_sequence_sample_description_box(visualSampleDescription);
438
0
        if (!decoder) {
439
0
          return {
440
0
            heif_error_Invalid_input,
441
0
            heif_suberror_Unspecified,
442
0
            "Sample description has unsupported codec or is missing the codec configuration box."
443
0
          };
444
0
        }
445
0
        chunk->set_decoder(decoder);
446
0
      }
447
0
    }
448
449
0
    m_chunks.push_back(chunk);
450
451
0
    current_sample_idx += sampleToChunk.samples_per_chunk;
452
0
    previous_sample_description_index = sampleToChunk.sample_description_index;
453
0
  }
454
455
0
  if (current_sample_idx != m_stsz->num_samples()) {
456
0
    return {
457
0
      heif_error_Invalid_input,
458
0
      heif_suberror_Unspecified,
459
0
      "Number of samples covered by 'stsc' does not match 'stsz'/'stts'."
460
0
    };
461
0
  }
462
463
  // --- read sample auxiliary information boxes
464
465
0
  std::vector<std::shared_ptr<Box_saiz> > saiz_boxes = stbl->get_child_boxes<Box_saiz>();
466
0
  std::vector<std::shared_ptr<Box_saio> > saio_boxes = stbl->get_child_boxes<Box_saio>();
467
468
0
  if (saio_boxes.size() != saiz_boxes.size()) {
469
0
    return Error{
470
0
      heif_error_Invalid_input,
471
0
      heif_suberror_Unspecified,
472
0
      "Boxes 'saiz' and `saio` must come in pairs."
473
0
    };
474
0
  }
475
476
0
  for (const auto& saiz : saiz_boxes) {
477
0
    uint32_t aux_info_type = saiz->get_aux_info_type();
478
0
    uint32_t aux_info_type_parameter = saiz->get_aux_info_type_parameter();
479
480
    // find the corresponding saio box
481
482
0
    std::shared_ptr<Box_saio> saio;
483
0
    for (const auto& candidate : saio_boxes) {
484
0
      if (candidate->get_aux_info_type() == aux_info_type &&
485
0
          candidate->get_aux_info_type_parameter() == aux_info_type_parameter) {
486
0
        saio = candidate;
487
0
        break;
488
0
      }
489
0
    }
490
491
0
    if (saio) {
492
0
      if (saio->get_num_chunks() != 1 &&
493
0
          saio->get_num_chunks() != m_stco->get_number_of_chunks()) {
494
0
        return Error{
495
0
          heif_error_Invalid_input,
496
0
          heif_suberror_Unspecified,
497
0
          "Invalid number of chunks in 'saio' box."
498
0
        };
499
0
      }
500
501
0
      if (saio->get_num_chunks() != 1 && m_chunks.empty() && saiz->get_num_samples() > 0) {
502
0
        return Error{
503
0
          heif_error_Invalid_input,
504
0
          heif_suberror_Unspecified,
505
0
          "'saiz' box references samples but no chunks exist."
506
0
        };
507
0
      }
508
509
0
      if (saiz->get_num_samples() > m_stsz->num_samples()) {
510
0
        return Error{
511
0
          heif_error_Invalid_input,
512
0
          heif_suberror_Unspecified,
513
0
          "Number of samples in 'saiz' box exceeds actual number of samples."
514
0
        };
515
0
      }
516
517
0
      if (aux_info_type == fourcc("suid")) {
518
0
        m_aux_reader_content_ids = std::make_unique<SampleAuxInfoReader>(saiz, saio, m_chunks);
519
0
      }
520
521
0
      if (aux_info_type == fourcc("stai")) {
522
0
        m_aux_reader_tai_timestamps = std::make_unique<SampleAuxInfoReader>(saiz, saio, m_chunks);
523
0
      }
524
0
    }
525
0
    else {
526
0
      return Error{
527
0
        heif_error_Invalid_input,
528
0
        heif_suberror_Unspecified,
529
0
        "'saiz' box without matching 'saio' box."
530
0
      };
531
0
    }
532
0
  }
533
534
  // --- read track properties
535
536
0
  if (auto meta = trak_box->get_child_box<Box_meta>()) {
537
0
    auto iloc = meta->get_child_box<Box_iloc>();
538
0
    auto idat = meta->get_child_box<Box_idat>();
539
540
0
    auto iinf = meta->get_child_box<Box_iinf>();
541
0
    if (iinf) {
542
0
      auto infe_boxes = iinf->get_child_boxes<Box_infe>();
543
0
      for (const auto& box : infe_boxes) {
544
0
        if (box->get_item_type_4cc() == fourcc("uri ") &&
545
0
            box->get_item_uri_type() == "urn:uuid:15beb8e4-944d-5fc6-a3dd-cb5a7e655c73") {
546
0
          heif_item_id id = box->get_item_ID();
547
548
0
          if (!iloc) {
549
0
            return {
550
0
              heif_error_Invalid_input,
551
0
              heif_suberror_Unspecified,
552
0
              "Track meta references item content-id but file has no 'iloc' box."
553
0
            };
554
0
          }
555
556
0
          std::vector<uint8_t> data;
557
0
          Error err = iloc->read_data(id, m_heif_context->get_heif_file()->get_reader(), idat, &data, m_heif_context->get_security_limits());
558
0
          if (err) {
559
0
            return err;
560
0
          }
561
562
0
          Result<std::string> contentIdResult = vector_to_string(data);
563
0
          if (!contentIdResult) {
564
0
            return contentIdResult.error();
565
0
          }
566
567
0
          m_track_info.gimi_track_content_id = *contentIdResult;
568
0
        }
569
0
      }
570
0
    }
571
0
  }
572
573
574
  // --- initialize track tables
575
576
0
  Error err = init_sample_timing_table();
577
0
  if (err) {
578
0
    return err;
579
0
  }
580
581
0
  return {};
582
0
}
583
584
585
Track::Track(HeifContext* ctx, uint32_t track_id, const TrackOptions* options, uint32_t handler_type)
586
0
{
587
0
  m_heif_context = ctx;
588
589
0
  m_moov = ctx->get_heif_file()->get_moov_box();
590
0
  assert(m_moov);
591
592
  // --- find next free track ID
593
594
0
  if (track_id == 0) {
595
0
    auto idResult = ctx->get_id_creator().get_new_id(IDCreator::Namespace::track);
596
    // Track constructor cannot return errors; assert on overflow (extremely unlikely).
597
0
    assert(idResult);
598
0
    track_id = *idResult;
599
600
0
    auto mvhd = m_moov->get_child_box<Box_mvhd>();
601
0
    mvhd->set_next_track_id(track_id + 1);
602
603
0
    m_id = track_id;
604
0
  }
605
606
0
  m_trak = std::make_shared<Box_trak>();
607
0
  m_moov->append_child_box(m_trak);
608
609
0
  m_tkhd = std::make_shared<Box_tkhd>();
610
0
  m_trak->append_child_box(m_tkhd);
611
0
  m_tkhd->set_track_id(track_id);
612
613
0
  auto mdia = std::make_shared<Box_mdia>();
614
0
  m_trak->append_child_box(mdia);
615
616
0
  m_mdhd = std::make_shared<Box_mdhd>();
617
0
  m_mdhd->set_timescale(options ? options->track_timescale : 90000);
618
0
  mdia->append_child_box(m_mdhd);
619
620
0
  m_hdlr = std::make_shared<Box_hdlr>();
621
0
  mdia->append_child_box(m_hdlr);
622
0
  m_hdlr->set_handler_type(handler_type);
623
624
0
  m_minf = std::make_shared<Box_minf>();
625
0
  mdia->append_child_box(m_minf);
626
627
  // add (unused) 'dinf'
628
629
0
  auto dinf = std::make_shared<Box_dinf>();
630
0
  auto dref = std::make_shared<Box_dref>();
631
0
  auto url = std::make_shared<Box_url>();
632
0
  m_minf->append_child_box(dinf);
633
0
  dinf->append_child_box(dref);
634
0
  dref->append_child_box(url);
635
636
  // vmhd is added in Track_Visual
637
638
0
  m_stbl = std::make_shared<Box_stbl>();
639
0
  m_minf->append_child_box(m_stbl);
640
641
0
  m_stsd = std::make_shared<Box_stsd>();
642
0
  m_stbl->append_child_box(m_stsd);
643
644
0
  m_stts = std::make_shared<Box_stts>();
645
0
  m_stbl->append_child_box(m_stts);
646
647
0
  m_ctts = std::make_shared<Box_ctts>();
648
  // The ctts box will be added in finalize_track(), but only there is frame-reordering.
649
650
0
  m_stsc = std::make_shared<Box_stsc>();
651
0
  m_stbl->append_child_box(m_stsc);
652
653
0
  m_stsz = std::make_shared<Box_stsz>();
654
0
  m_stbl->append_child_box(m_stsz);
655
656
0
  m_stco = std::make_shared<Box_stco>();
657
0
  m_stbl->append_child_box(m_stco);
658
659
0
  m_stss = std::make_shared<Box_stss>();
660
0
  m_stbl->append_child_box(m_stss);
661
662
0
  if (options) {
663
0
    m_track_info = *options;
664
665
0
    if (m_track_info.with_sample_tai_timestamps != heif_sample_aux_info_presence_none) {
666
0
      m_aux_helper_tai_timestamps = std::make_unique<SampleAuxInfoHelper>(m_track_info.write_sample_aux_infos_interleaved);
667
0
      m_aux_helper_tai_timestamps->set_aux_info_type(fourcc("stai"));
668
0
    }
669
670
0
    if (m_track_info.with_sample_content_ids != heif_sample_aux_info_presence_none) {
671
0
      m_aux_helper_content_ids = std::make_unique<SampleAuxInfoHelper>(m_track_info.write_sample_aux_infos_interleaved);
672
0
      m_aux_helper_content_ids->set_aux_info_type(fourcc("suid"));
673
0
    }
674
675
0
    if (!options->gimi_track_content_id.empty()) {
676
0
      auto hdlr_box = std::make_shared<Box_hdlr>();
677
0
      hdlr_box->set_handler_type(fourcc("meta"));
678
679
0
      auto uuid_box = std::make_shared<Box_infe>();
680
0
      uuid_box->set_item_type_4cc(fourcc("uri "));
681
0
      uuid_box->set_item_uri_type("urn:uuid:15beb8e4-944d-5fc6-a3dd-cb5a7e655c73");
682
0
      uuid_box->set_item_ID(1);
683
684
0
      auto iinf_box = std::make_shared<Box_iinf>();
685
0
      iinf_box->append_child_box(uuid_box);
686
687
0
      std::vector<uint8_t> track_uuid_vector;
688
0
      track_uuid_vector.insert(track_uuid_vector.begin(),
689
0
                               options->gimi_track_content_id.c_str(),
690
0
                               options->gimi_track_content_id.c_str() + options->gimi_track_content_id.length() + 1);
691
692
0
      auto iloc_box = std::make_shared<Box_iloc>();
693
0
      iloc_box->append_data(1, track_uuid_vector, 1);
694
695
0
      auto meta_box = std::make_shared<Box_meta>();
696
0
      meta_box->append_child_box(hdlr_box);
697
0
      meta_box->append_child_box(iinf_box);
698
0
      meta_box->append_child_box(iloc_box);
699
700
0
      m_trak->append_child_box(meta_box);
701
0
    }
702
0
  }
703
0
}
704
705
706
Result<std::shared_ptr<Track> > Track::alloc_track(HeifContext* ctx, const std::shared_ptr<Box_trak>& trak)
707
7
{
708
7
  auto mdia = trak->get_child_box<Box_mdia>();
709
7
  if (!mdia) {
710
7
    return Error{
711
7
      heif_error_Invalid_input,
712
7
      heif_suberror_Unspecified,
713
7
      "Track has no 'mdia' box."
714
7
    };
715
7
  }
716
717
0
  auto hdlr = mdia->get_child_box<Box_hdlr>();
718
0
  if (!hdlr) {
719
0
    return Error{
720
0
      heif_error_Invalid_input,
721
0
      heif_suberror_Unspecified,
722
0
      "Track has no 'hdlr' box."
723
0
    };
724
0
  }
725
726
0
  std::shared_ptr<Track> track;
727
728
0
  switch (hdlr->get_handler_type()) {
729
0
    case fourcc("pict"):
730
0
    case fourcc("vide"):
731
0
    case fourcc("auxv"):
732
0
      track = std::make_shared<Track_Visual>(ctx);
733
0
      break;
734
0
    case fourcc("meta"):
735
0
      track = std::make_shared<Track_Metadata>(ctx);
736
0
      break;
737
0
    default: {
738
0
      std::stringstream sstr;
739
0
      sstr << "Track with unsupported handler type '" << fourcc_to_string(hdlr->get_handler_type()) << "'.";
740
0
      return Error{
741
0
        heif_error_Unsupported_feature,
742
0
        heif_suberror_Unsupported_track_type,
743
0
        sstr.str()
744
0
      };
745
0
    }
746
0
  }
747
748
0
  assert(track);
749
0
  Error loadError = track->load(trak);
750
0
  if (loadError) {
751
0
    return loadError;
752
0
  }
753
754
0
  return {track};
755
0
}
756
757
758
bool Track::is_visual_track() const
759
0
{
760
0
  return (m_handler_type == fourcc("pict") ||
761
0
          m_handler_type == fourcc("vide"));
762
0
}
763
764
765
static const char* cAuxType_alpha_miaf = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha";
766
static const char* cAuxType_alpha_hevc = "urn:mpeg:hevc:2015:auxid:1";
767
static const char* cAuxType_alpha_avc = "urn:mpeg:avc:2015:auxid:1";
768
769
heif_auxiliary_track_info_type Track::get_auxiliary_info_type() const
770
0
{
771
0
  if (m_auxiliary_info_type == cAuxType_alpha_miaf ||
772
0
      m_auxiliary_info_type == cAuxType_alpha_hevc ||
773
0
      m_auxiliary_info_type == cAuxType_alpha_avc) {
774
0
    return heif_auxiliary_track_info_type_alpha;
775
0
  }
776
0
  else {
777
0
    return heif_auxiliary_track_info_type_unknown;
778
0
  }
779
0
}
780
781
const char* get_track_auxiliary_info_type(heif_compression_format format)
782
0
{
783
0
  switch (format) {
784
0
    case heif_compression_HEVC:
785
0
      return cAuxType_alpha_hevc;
786
0
    case heif_compression_AVC:
787
0
      return cAuxType_alpha_avc;
788
0
    case heif_compression_AV1:
789
0
      return cAuxType_alpha_miaf;
790
0
    default:
791
0
      return cAuxType_alpha_miaf; // TODO: is this correct for all remaining compression types ?
792
0
  }
793
0
}
794
795
// TODO: is this correct or should we set the aux_info_type depending on the compression format?
796
void Track::set_auxiliary_info_type(heif_auxiliary_track_info_type type)
797
0
{
798
0
  switch (type) {
799
0
    case heif_auxiliary_track_info_type_alpha:
800
0
      m_auxiliary_info_type = cAuxType_alpha_miaf;
801
0
      break;
802
0
    default:
803
0
      m_auxiliary_info_type.clear();
804
0
      break;
805
0
  }
806
0
}
807
808
809
uint32_t Track::get_first_cluster_sample_entry_type() const
810
0
{
811
0
  if (m_stsd->get_num_sample_entries() == 0) {
812
0
    return 0; // TODO: error ? Or can we assume at this point that there is at least one sample entry?
813
0
  }
814
815
0
  return m_stsd->get_sample_entry(0)->get_short_type();
816
0
}
817
818
819
Result<std::string> Track::get_first_cluster_urim_uri() const
820
0
{
821
0
  if (m_stsd->get_num_sample_entries() == 0) {
822
0
    return Error{
823
0
      heif_error_Invalid_input,
824
0
      heif_suberror_Unspecified,
825
0
      "This track has no sample entries."
826
0
    };
827
0
  }
828
829
0
  std::shared_ptr<const Box> sampleEntry = m_stsd->get_sample_entry(0);
830
0
  auto urim = std::dynamic_pointer_cast<const Box_URIMetaSampleEntry>(sampleEntry);
831
0
  if (!urim) {
832
0
    return Error{
833
0
      heif_error_Usage_error,
834
0
      heif_suberror_Unspecified,
835
0
      "This cluster is no 'urim' sample entry."
836
0
    };
837
0
  }
838
839
0
  std::shared_ptr<const Box_uri> uri = urim->get_child_box<const Box_uri>();
840
0
  if (!uri) {
841
0
    return Error{
842
0
      heif_error_Invalid_input,
843
0
      heif_suberror_Unspecified,
844
0
      "The 'urim' box has no 'uri' child box."
845
0
    };
846
0
  }
847
848
0
  return uri->get_uri();
849
0
}
850
851
852
bool Track::end_of_sequence_reached() const
853
0
{
854
  //return (m_next_sample_to_be_processed > m_chunks.back()->last_sample_number());
855
0
  return m_next_sample_to_be_output >= m_num_output_samples;
856
0
}
857
858
859
Error Track::finalize_track()
860
0
{
861
  // --- write active chunk data
862
863
0
  size_t data_start = m_heif_context->get_heif_file()->append_mdat_data(m_chunk_data);
864
0
  m_chunk_data.clear();
865
866
  // first sample in chunk? -> write chunk offset
867
868
0
  if (true) {
869
    // m_stsc->last_chunk_empty()) {
870
    // TODO: we will have to call this at the end of a chunk to dump the current SAI queue
871
872
    // if auxiliary data is interleaved, write it between the chunks
873
0
    if (m_aux_helper_tai_timestamps) m_aux_helper_tai_timestamps->write_interleaved(get_file());
874
0
    if (m_aux_helper_content_ids) m_aux_helper_content_ids->write_interleaved(get_file());
875
876
    // TODO
877
0
    assert(data_start < 0xFF000000); // add some headroom for header data
878
0
    m_stco->add_chunk_offset(static_cast<uint32_t>(data_start));
879
0
  }
880
881
882
  // --- write rest of data
883
884
0
  if (m_aux_helper_tai_timestamps) m_aux_helper_tai_timestamps->write_all(m_stbl, get_file());
885
0
  if (m_aux_helper_content_ids) m_aux_helper_content_ids->write_all(m_stbl, get_file());
886
887
0
  uint64_t duration = m_stts->get_total_duration(true);
888
0
  m_mdhd->set_duration(duration);
889
890
0
  m_stss->set_total_number_of_samples(m_stsz->num_samples());
891
892
  // only add ctts box if we use frame-reordering
893
0
  if (!m_ctts->is_constant_offset()) {
894
0
    m_stbl->append_child_box(m_ctts);
895
0
  }
896
897
0
  return {};
898
0
}
899
900
901
uint64_t Track::get_duration_in_media_units() const
902
0
{
903
0
  return m_mdhd->get_duration();
904
0
}
905
906
907
uint32_t Track::get_timescale() const
908
0
{
909
0
  return m_mdhd->get_timescale();
910
0
}
911
912
913
void Track::set_track_duration_in_movie_units(uint64_t total_duration, uint64_t segment_duration)
914
0
{
915
0
  m_tkhd->set_duration(total_duration);
916
917
0
  if (m_elst) {
918
0
    Box_elst::Entry entry;
919
0
    entry.segment_duration = segment_duration;
920
921
0
    m_elst->add_entry(entry);
922
0
  }
923
0
}
924
925
926
void Track::enable_edit_list_repeat_mode(bool enable)
927
0
{
928
0
  if (!m_elst) {
929
0
    if (!enable) {
930
0
      return;
931
0
    }
932
933
0
    auto edts = std::make_shared<Box_edts>();
934
0
    m_trak->append_child_box(edts);
935
936
0
    m_elst = std::make_shared<Box_elst>();
937
0
    edts->append_child_box(m_elst);
938
939
0
    m_elst->enable_repeat_mode(enable);
940
0
  }
941
0
}
942
943
944
void Track::add_chunk(heif_compression_format format)
945
0
{
946
0
  auto chunk = std::make_shared<Chunk>(m_heif_context, m_id, format);
947
0
  m_chunks.push_back(chunk);
948
949
0
  int chunkIdx = (uint32_t) m_chunks.size();
950
0
  m_stsc->add_chunk(chunkIdx);
951
0
}
952
953
void Track::set_sample_description_box(const std::shared_ptr<Box>& sample_description_box)
954
0
{
955
  // --- add 'taic' when we store timestamps as sample auxiliary information
956
957
0
  if (m_track_info.with_sample_tai_timestamps != heif_sample_aux_info_presence_none) {
958
0
    auto taic = std::make_shared<Box_taic>();
959
0
    taic->set_from_tai_clock_info(m_track_info.tai_clock_info);
960
0
    sample_description_box->append_child_box(taic);
961
0
  }
962
963
0
  m_stsd->add_sample_entry(sample_description_box);
964
0
}
965
966
967
Error Track::write_sample_data(const std::vector<uint8_t>& raw_data, uint32_t sample_duration,
968
                               int32_t composition_time_offset,
969
                               bool is_sync_sample,
970
                               const heif_tai_timestamp_packet* tai, const std::optional<std::string>& gimi_contentID)
971
0
{
972
0
  m_chunk_data.insert(m_chunk_data.end(), raw_data.begin(), raw_data.end());
973
974
0
  m_stsc->increase_samples_in_chunk(1);
975
976
0
  m_stsz->append_sample_size((uint32_t) raw_data.size());
977
978
0
  if (is_sync_sample) {
979
0
    m_stss->add_sync_sample(m_next_sample_to_be_output + 1);
980
0
  }
981
982
0
  if (sample_duration == 0) {
983
0
    return {
984
0
      heif_error_Usage_error,
985
0
      heif_suberror_Unspecified,
986
0
      "Sample duration may not be 0"
987
0
    };
988
0
  }
989
990
0
  m_stts->append_sample_duration(sample_duration);
991
0
  m_ctts->append_sample_offset(composition_time_offset);
992
993
994
  // --- sample timestamp
995
996
0
  if (m_track_info.with_sample_tai_timestamps != heif_sample_aux_info_presence_none) {
997
0
    if (tai) {
998
0
      std::vector<uint8_t> tai_data = Box_itai::encode_tai_to_bitstream(tai);
999
0
      auto err = m_aux_helper_tai_timestamps->add_sample_info(tai_data);
1000
0
      if (err) {
1001
0
        return err;
1002
0
      }
1003
0
    }
1004
0
    else if (m_track_info.with_sample_tai_timestamps == heif_sample_aux_info_presence_optional) {
1005
0
      m_aux_helper_tai_timestamps->add_nonpresent_sample();
1006
0
    }
1007
0
    else {
1008
0
      return {
1009
0
        heif_error_Encoding_error,
1010
0
        heif_suberror_Unspecified,
1011
0
        "Mandatory TAI timestamp missing"
1012
0
      };
1013
0
    }
1014
0
  }
1015
1016
  // --- sample content id
1017
1018
0
  if (m_track_info.with_sample_content_ids != heif_sample_aux_info_presence_none) {
1019
0
    if (gimi_contentID) {
1020
0
      const auto& id = *gimi_contentID;
1021
0
      const char* id_str = id.c_str();
1022
0
      std::vector<uint8_t> id_vector;
1023
0
      id_vector.insert(id_vector.begin(), id_str, id_str + id.length() + 1);
1024
0
      auto err = m_aux_helper_content_ids->add_sample_info(id_vector);
1025
0
      if (err) {
1026
0
        return err;
1027
0
      }
1028
0
    }
1029
0
    else if (m_track_info.with_sample_content_ids == heif_sample_aux_info_presence_optional) {
1030
0
      m_aux_helper_content_ids->add_nonpresent_sample();
1031
0
    }
1032
0
    else {
1033
0
      return {
1034
0
        heif_error_Encoding_error,
1035
0
        heif_suberror_Unspecified,
1036
0
        "Mandatory ContentID missing"
1037
0
      };
1038
0
    }
1039
0
  }
1040
1041
0
  m_next_sample_to_be_output++;
1042
1043
0
  return Error::Ok;
1044
0
}
1045
1046
1047
void Track::add_reference_to_track(uint32_t referenceType, uint32_t to_track_id)
1048
0
{
1049
0
  if (!m_tref) {
1050
0
    m_tref = std::make_shared<Box_tref>();
1051
0
    m_trak->append_child_box(m_tref);
1052
0
  }
1053
1054
0
  m_tref->add_references(to_track_id, referenceType);
1055
0
}
1056
1057
1058
Error Track::init_sample_timing_table()
1059
0
{
1060
0
  m_num_samples = m_stsz->num_samples();
1061
1062
0
  const auto* limits = m_heif_context->get_security_limits();
1063
1064
  // Account the presentation timeline against max_total_memory before allocating
1065
  // it. m_num_samples is bounded by max_sequence_frames, but a single small track
1066
  // can still declare millions of samples, so this ~48-bytes/sample vector must be
1067
  // tracked to bound multi-track accumulation (GHSA-xw34-mjcp-jqh8, V2/V3). The
1068
  // media timeline built below is moved into m_presentation_timeline (not copied),
1069
  // so only one such buffer ever exists and this single reservation covers it.
1070
0
  if (auto err = m_presentation_timeline_memory.alloc(m_num_samples, sizeof(SampleTiming),
1071
0
                                                      limits, "the sequence presentation timeline")) {
1072
0
    return err;
1073
0
  }
1074
1075
  // --- build media timeline
1076
1077
0
  std::vector<SampleTiming> media_timeline;
1078
0
  media_timeline.reserve(m_num_samples);
1079
1080
0
  uint64_t current_decoding_time = 0;
1081
0
  uint32_t current_chunk = 0;
1082
0
  uint32_t current_sample_in_chunk_idx = 0;
1083
1084
0
  for (uint32_t i = 0; i < m_num_samples; i++) {
1085
0
    SampleTiming timing;
1086
0
    timing.sampleIdx = i;
1087
0
    timing.sampleInChunkIdx = current_sample_in_chunk_idx;
1088
0
    timing.media_decoding_time = current_decoding_time;
1089
    // O(log entries) per lookup (Box_stts uses a prefix-sum binary search), so
1090
    // building the whole table is O(samples * log entries) rather than the former
1091
    // O(samples * entries) file-open CPU-DoS (GHSA-xw34-mjcp-jqh8, variant V1).
1092
0
    timing.sample_duration_media_time = m_stts->get_sample_duration(i);
1093
0
    current_decoding_time += timing.sample_duration_media_time;
1094
0
    current_sample_in_chunk_idx++;
1095
1096
0
    while (current_chunk < m_chunks.size() &&
1097
0
           i > m_chunks[current_chunk]->last_sample_number()) {
1098
0
      current_chunk++;
1099
0
      current_sample_in_chunk_idx = 0;
1100
0
    }
1101
1102
0
    if (current_chunk >= m_chunks.size()) {
1103
0
      return {
1104
0
        heif_error_Invalid_input,
1105
0
        heif_suberror_Unspecified,
1106
0
        "Sample not covered by any chunk."
1107
0
      };
1108
0
    }
1109
1110
0
    timing.chunkIdx = current_chunk;
1111
1112
0
    media_timeline.push_back(timing);
1113
0
  }
1114
1115
  // --- build presentation timeline from editlist
1116
1117
0
  bool fallback = false;
1118
1119
0
  if (m_heif_context->get_sequence_timescale() != get_timescale()) {
1120
0
    fallback = true;
1121
0
  }
1122
0
  else if (m_elst &&
1123
0
           m_elst->num_entries() == 1 &&
1124
0
           m_elst->get_entry(0).media_time == 0 &&
1125
0
           m_elst->get_entry(0).segment_duration == m_mdhd->get_duration() &&
1126
0
           m_elst->is_repeat_mode()) {
1127
1128
0
    uint64_t duration_media_units = get_duration_in_media_units();
1129
0
    if (duration_media_units == 0) {
1130
0
      return {
1131
0
        heif_error_Invalid_input,
1132
0
        heif_suberror_Unspecified,
1133
0
        "Track duration is zero."
1134
0
      };
1135
0
    }
1136
1137
0
    uint64_t multiplier = m_heif_context->get_sequence_duration() / duration_media_units;
1138
1139
    // Logical number of output samples = physical samples x repeat multiplier.
1140
    // Compute this saturating instead of wrapping: with the indefinite-duration
1141
    // sentinel (mvhd.duration = UINT64_MAX) the multiplier is enormous, and a plain
1142
    // multiply could overflow uint64_t and wrap to a small, misleading value.
1143
0
    uint64_t timeline_size = media_timeline.size();
1144
0
    uint64_t total_output_samples;
1145
0
    if (timeline_size != 0 && multiplier > std::numeric_limits<uint64_t>::max() / timeline_size) {
1146
0
      total_output_samples = std::numeric_limits<uint64_t>::max();
1147
0
    }
1148
0
    else {
1149
0
      total_output_samples = multiplier * timeline_size;
1150
0
    }
1151
1152
    // The decode loop (Track_Visual::decode_next_image_sample), the raw-data path
1153
    // (get_next_sample_raw_data) and end_of_sequence_reached() all count emitted
1154
    // samples with a uint32_t (m_next_sample_to_be_output). If m_num_output_samples
1155
    // exceeds UINT32_MAX, that counter can never reach it and decoding never
1156
    // terminates (GHSA-xw34-mjcp-jqh8, variants V4/V5). The physical-sample limit
1157
    // (max_sequence_frames) is also never applied to this repeat-amplified logical
1158
    // count (variant V6). Bound the number of samples the loop will emit by the
1159
    // sequence-frame limit (or UINT32_MAX when the limit is disabled), so a
1160
    // repeat/indefinite edit list cannot inflate a single physical sample into an
1161
    // effectively infinite decode. The reported repetition count below still signals
1162
    // "infinite", so a caller wanting true unbounded playback can loop the media
1163
    // itself via heif_decoding_options::ignore_sequence_editlist.
1164
0
    uint64_t output_sample_cap = (limits->max_sequence_frames > 0)
1165
0
                                 ? limits->max_sequence_frames
1166
0
                                 : std::numeric_limits<uint32_t>::max();
1167
1168
0
    m_num_output_samples = std::min(total_output_samples, output_sample_cap);
1169
1170
0
    if (m_heif_context->is_sequence_duration_indefinite()) {
1171
      // mvhd carries the all-1s sentinel -> editlist repeats forever.
1172
0
      m_num_repetitions = std::numeric_limits<uint32_t>::max();
1173
0
    }
1174
0
    else if (multiplier >= std::numeric_limits<uint32_t>::max()) {
1175
      // Doesn't fit in the API's uint32_t; treat as effectively infinite.
1176
0
      m_num_repetitions = std::numeric_limits<uint32_t>::max();
1177
0
    }
1178
0
    else {
1179
0
      m_num_repetitions = static_cast<uint32_t>(multiplier);
1180
0
    }
1181
0
  }
1182
0
  else {
1183
0
    fallback = true;
1184
0
  }
1185
1186
  // Fallback: just play the media timeline
1187
0
  if (fallback) {
1188
0
    m_num_output_samples = media_timeline.size();
1189
    // No editlist box at all: the media plays exactly once.
1190
    // Editlist box present but its pattern isn't one libheif interprets: report
1191
    // 0 so callers know they should not rely on a repetition count.
1192
0
    m_num_repetitions = m_elst ? 0 : 1;
1193
0
  }
1194
1195
  // Both branches above use media_timeline unchanged as the presentation timeline.
1196
  // Move (do not copy) it in so only one such buffer is ever allocated, matching
1197
  // the single reservation made at the top of this function.
1198
0
  m_presentation_timeline = std::move(media_timeline);
1199
1200
0
  return {};
1201
0
}
1202
1203
1204
Result<heif_raw_sequence_sample*> Track::get_next_sample_raw_data(const heif_decoding_options* options)
1205
0
{
1206
0
  uint64_t num_output_samples = m_num_output_samples;
1207
0
  if (options && options->ignore_sequence_editlist) {
1208
0
    num_output_samples = m_num_samples;
1209
0
  }
1210
1211
0
  if (m_next_sample_to_be_output >= num_output_samples) {
1212
0
    return Error{
1213
0
      heif_error_End_of_sequence,
1214
0
      heif_suberror_Unspecified,
1215
0
      "End of sequence"
1216
0
    };
1217
0
  }
1218
1219
0
  const auto& sampleTiming = m_presentation_timeline[m_next_sample_to_be_output % m_presentation_timeline.size()];
1220
0
  uint32_t sample_idx = sampleTiming.sampleIdx;
1221
0
  uint32_t chunk_idx = sampleTiming.chunkIdx;
1222
1223
0
  const std::shared_ptr<Chunk>& chunk = m_chunks[chunk_idx];
1224
1225
0
  DataExtent extent = chunk->get_data_extent_for_sample(sample_idx);
1226
0
  auto readResult = extent.read_data();
1227
0
  if (!readResult) {
1228
0
    return readResult.error();
1229
0
  }
1230
1231
  // Keep the sample in a unique_ptr until the single success exit below. Several
1232
  // error returns follow (sample auxiliary info driven by file bytes), and the
1233
  // Result<T*> error variant cannot carry the pointer back to the caller, so a raw
1234
  // 'new' here leaked the sample together with its full payload copy on every one
1235
  // of them (GHSA-4rv4-953r-p24q).
1236
0
  auto sample = std::make_unique<heif_raw_sequence_sample>();
1237
1238
  // Move the payload out of the function-local extent instead of copying it. The
1239
  // extent is destroyed when this function returns anyway, and moving halves the
1240
  // peak memory needed per sample.
1241
0
  sample->data = std::move(**readResult);
1242
1243
  // read sample duration
1244
1245
0
  if (m_stts) {
1246
0
    sample->duration = m_stts->get_sample_duration(sample_idx);
1247
0
  }
1248
1249
  // --- read sample auxiliary data
1250
1251
0
  if (m_aux_reader_content_ids) {
1252
0
    auto readResult = m_aux_reader_content_ids->get_sample_info(get_file().get(), sample_idx);
1253
0
    if (!readResult) {
1254
0
      return readResult.error();
1255
0
    }
1256
1257
0
    if (!readResult->empty()) {
1258
0
      Result<std::string> convResult = vector_to_string(*readResult);
1259
0
      if (!convResult) {
1260
0
        return convResult.error();
1261
0
      }
1262
1263
0
      sample->gimi_sample_content_id = *convResult;
1264
0
    }
1265
0
  }
1266
1267
0
  if (m_aux_reader_tai_timestamps) {
1268
0
    auto readResult = m_aux_reader_tai_timestamps->get_sample_info(get_file().get(), sample_idx);
1269
0
    if (!readResult) {
1270
0
      return readResult.error();
1271
0
    }
1272
1273
0
    if (!readResult->empty()) {
1274
0
      auto resultTai = Box_itai::decode_tai_from_vector(*readResult);
1275
0
      if (!resultTai) {
1276
0
        return resultTai.error();
1277
0
      }
1278
1279
0
      sample->timestamp = heif_tai_timestamp_packet_alloc();
1280
0
      heif_tai_timestamp_packet_copy(sample->timestamp, &*resultTai);
1281
0
    }
1282
0
  }
1283
1284
0
  m_next_sample_to_be_output++;
1285
1286
0
  return sample.release();
1287
0
}
1288
1289
1290
std::vector<heif_sample_aux_info_type> Track::get_sample_aux_info_types() const
1291
0
{
1292
0
  std::vector<heif_sample_aux_info_type> types;
1293
1294
0
  if (m_aux_reader_tai_timestamps) types.emplace_back(m_aux_reader_tai_timestamps->get_type());
1295
0
  if (m_aux_reader_content_ids) types.emplace_back(m_aux_reader_content_ids->get_type());
1296
1297
0
  return types;
1298
0
}