Tag Parser  7.0.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
matroskatrack.cpp
Go to the documentation of this file.
1 #include "./matroskatrack.h"
2 #include "./ebmlelement.h"
3 #include "./matroskacontainer.h"
4 #include "./matroskaid.h"
5 #include "./matroskatag.h"
6 
7 #include "../avi/bitmapinfoheader.h"
8 
9 #include "../wav/waveaudiostream.h"
10 
11 #include "../avc/avcconfiguration.h"
12 
13 #include "../mp4/mp4ids.h"
14 #include "../mp4/mp4track.h"
15 
16 #include "../exceptions.h"
17 #include "../mediaformat.h"
18 
19 #include <c++utilities/conversion/stringconversion.h>
20 
21 using namespace std;
22 using namespace ConversionUtilities;
23 
24 namespace TagParser {
25 
37 MatroskaTrack::MatroskaTrack(EbmlElement &trackElement)
38  : AbstractTrack(trackElement.stream(), trackElement.startOffset())
39  , m_trackElement(&trackElement)
40 {
41 }
42 
47 {
48 }
49 
51 {
53 }
54 
59 {
60  auto parts = splitString<vector<string>>(codecId, "/", EmptyPartsTreat::Keep, 3);
61  parts.resize(3);
62  const auto &part1 = parts[0], &part2 = parts[1], &part3 = parts[2];
63  MediaFormat fmt;
64  if (part1 == "V_MS" && part2 == "VFW" && part3 == "FOURCC") {
66  } else if (part1 == "V_UNCOMPRESSED") {
68  } else if (part1 == "V_MPEG4") {
70  if (part2 == "ISO") {
71  if (part3 == "SP") {
73  } else if (part3 == "ASP") {
75  } else if (part3 == "AVC") {
77  }
78  } else if (part2 == "MS" && part3 == "V3") {
80  }
81  } else if (part1 == "V_MPEG1") {
83  } else if (part1 == "V_MPEG2") {
85  } else if (part1 == "V_REAL") {
87  } else if (part1 == "V_QUICKTIME") {
89  } else if (part1 == "V_THEORA") {
91  } else if (part1 == "V_PRORES") {
93  } else if (part1 == "V_VP8") {
95  } else if (part1 == "V_VP9") {
97  } else if (part1 == "A_MPEG") {
99  if (part2 == "L1") {
101  } else if (part2 == "L2") {
103  } else if (part2 == "L3") {
105  }
106  } else if (part1 == "V_MPEGH" && part2 == "ISO" && part3 == "HEVC") {
108  } else if (part1 == "A_PCM") {
110  if (part2 == "INT") {
111  if (part3 == "BIG") {
113  } else if (part3 == "LIT") {
115  }
116  } else if (part2 == "FLOAT" && part3 == "IEEE") {
118  }
119  } else if (part1 == "A_MPC") {
121  } else if (part1 == "A_AC3") {
123  } else if (part1 == "A_ALAC") {
125  } else if (part1 == "A_DTS") {
127  if (part2 == "EXPRESS") {
129  } else if (part2 == "LOSSLESS") {
131  }
132  } else if (part1 == "A_VORBIS") {
134  } else if (part1 == "A_FLAC") {
136  } else if (part1 == "A_OPUS") {
138  } else if (part1 == "A_REAL") {
140  } else if (part1 == "A_MS" && part2 == "ACM") {
142  } else if (part1 == "A_AAC") {
144  if (part2 == "MPEG2") {
145  if (part3 == "MAIN") {
147  } else if (part3 == "LC") {
149  } else if (part3 == "SBR") {
152  } else if (part3 == "SSR") {
154  }
155  } else if (part2 == "MPEG4") {
156  if (part3 == "MAIN") {
158  } else if (part3 == "LC") {
160  } else if (part3 == "SBR") {
163  } else if (part3 == "SSR") {
165  } else if (part3 == "LTP") {
167  }
168  }
169  } else if (part1 == "A_QUICKTIME") {
171  } else if (part1 == "A_TTA1") {
173  } else if (part1 == "A_WAVPACK4") {
175  } else if (part1 == "S_TEXT") {
177  if (part2 == "UTF8") {
179  } else if (part2 == "SSA") {
181  } else if (part2 == "ASS") {
183  } else if (part2 == "USF") {
185  } else if (part2 == "WEBVTT") {
187  }
188  } else if (part1 == "S_IMAGE") {
190  if (part2 == "BMP") {
192  }
193  } else if (part1 == "S_VOBSUB") {
195  } else if (part1 == "S_KATE") {
197  } else if (part1 == "B_VOBBTN") {
199  } else if (part1 == "S_DVBSUB") {
201  } else if (part1 == "V_MSWMV") {
203  }
204  return fmt;
205 }
206 
208 
209 template <typename PropertyType, typename ConversionFunction>
210 void MatroskaTrack::assignPropertyFromTagValue(const std::unique_ptr<MatroskaTag> &tag, const char *fieldId, PropertyType &property,
211  const ConversionFunction &conversionFunction, Diagnostics &diag)
212 {
213  const TagValue &value = tag->value(fieldId);
214  if (!value.isEmpty()) {
215  try {
216  property = conversionFunction(value);
217  } catch (const ConversionException &) {
218  string message;
219  try {
220  message = argsToString("Ignoring invalid value \"", value.toString(TagTextEncoding::Utf8), "\" of \"", fieldId, '\"', '.');
221  } catch (const ConversionException &) {
222  message = argsToString("Ignoring invalid value of \"", fieldId, '\"', '.');
223  }
224  diag.emplace_back(DiagLevel::Warning, message, argsToString("reading track statatistic from \"", tag->toString(), '\"'));
225  }
226  }
227 }
228 
229 template <typename NumberType, Traits::EnableIf<std::is_integral<NumberType>>...> NumberType tagValueToNumber(const TagValue &tagValue)
230 {
231  // optimization for Latin1/UTF-8 strings
232  if (tagValue.type() == TagDataType::Text) {
233  switch (tagValue.dataEncoding()) {
236  return bufferToNumber<NumberType>(tagValue.dataPointer(), tagValue.dataSize());
237  default:;
238  }
239  }
240  // generic conversion
241  return stringToNumber<NumberType>(tagValue.toString(TagTextEncoding::Utf8));
242 }
243 
244 template <typename NumberType, Traits::EnableIf<std::is_floating_point<NumberType>>...> NumberType tagValueToBitrate(const TagValue &tagValue)
245 {
246  return stringToNumber<NumberType>(tagValue.toString(TagTextEncoding::Utf8)) / 1000;
247 }
248 
250 
260 void MatroskaTrack::readStatisticsFromTags(const std::vector<std::unique_ptr<MatroskaTag>> &tags, Diagnostics &diag)
261 {
262  using namespace std::placeholders;
263  using namespace MatroskaTagIds::TrackSpecific;
264  for (const auto &tag : tags) {
265  const TagTarget &target = tag->target();
266  if (find(target.tracks().cbegin(), target.tracks().cend(), id()) == target.tracks().cend()) {
267  continue;
268  }
269  assignPropertyFromTagValue(tag, numberOfBytes(), m_size, &tagValueToNumber<uint64>, diag);
270  assignPropertyFromTagValue(tag, numberOfFrames(), m_sampleCount, &tagValueToNumber<uint64>, diag);
271  assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::duration(), m_duration, bind(&TagValue::toTimeSpan, _1), diag);
272  assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::bitrate(), m_bitrate, &tagValueToBitrate<double>, diag);
273  assignPropertyFromTagValue(tag, writingDate(), m_modificationTime, bind(&TagValue::toDateTime, _1), diag);
274  if (m_creationTime.isNull()) {
276  }
277  }
278 }
279 
281 {
282  static const string context("parsing header of Matroska track");
283  try {
284  m_trackElement->parse(diag);
285  } catch (const Failure &) {
286  diag.emplace_back(DiagLevel::Critical, "Unable to parse track element.", context);
287  throw;
288  }
289  // read information about the track from the childs of the track entry element
290  for (EbmlElement *trackInfoElement = m_trackElement->firstChild(), *subElement = nullptr; trackInfoElement;
291  trackInfoElement = trackInfoElement->nextSibling()) {
292  try {
293  trackInfoElement->parse(diag);
294  } catch (const Failure &) {
295  diag.emplace_back(DiagLevel::Critical, "Unable to parse track information element.", context);
296  break;
297  }
298  uint32 defaultDuration = 0;
299  switch (trackInfoElement->id()) {
301  switch (trackInfoElement->readUInteger()) {
304  break;
307  break;
310  break;
313  break;
316  break;
317  default:
319  }
320  break;
322  for (subElement = trackInfoElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
323  try {
324  subElement->parse(diag);
325  } catch (const Failure &) {
326  diag.emplace_back(DiagLevel::Critical, "Unable to parse video track element.", context);
327  break;
328  }
329  switch (subElement->id()) {
331  m_displaySize.setWidth(subElement->readUInteger());
332  break;
334  m_displaySize.setHeight(subElement->readUInteger());
335  break;
337  m_pixelSize.setWidth(subElement->readUInteger());
338  break;
340  m_pixelSize.setHeight(subElement->readUInteger());
341  break;
343  m_cropping.setTop(subElement->readUInteger());
344  break;
346  m_cropping.setLeft(subElement->readUInteger());
347  break;
349  m_cropping.setBottom(subElement->readUInteger());
350  break;
352  m_cropping.setRight(subElement->readUInteger());
353  break;
355  m_fps = subElement->readFloat();
356  break;
358  m_interlaced = subElement->readUInteger();
359  break;
361  m_colorSpace = subElement->readUInteger();
362  break;
363  default:;
364  }
365  }
366  break;
368  for (subElement = trackInfoElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
369  try {
370  subElement->parse(diag);
371  } catch (const Failure &) {
372  diag.emplace_back(DiagLevel::Critical, "Unable to parse audio track element.", context);
373  break;
374  }
375  switch (subElement->id()) {
377  m_bitsPerSample = subElement->readUInteger();
378  break;
380  m_channelCount = subElement->readUInteger();
381  break;
383  if (!m_samplingFrequency) {
384  m_samplingFrequency = subElement->readFloat();
385  }
386  break;
389  m_extensionSamplingFrequency = subElement->readFloat();
390  }
391  break;
392  default:;
393  }
394  }
395  break;
397  m_trackNumber = trackInfoElement->readUInteger();
398  break;
400  m_id = trackInfoElement->readUInteger();
401  break;
403  m_name = trackInfoElement->readString();
404  break;
406  m_language = trackInfoElement->readString();
407  break;
409  m_format = codecIdToMediaFormat(m_formatId = trackInfoElement->readString());
410  break;
412  m_formatName = trackInfoElement->readString();
413  break;
415  break; // TODO
417  m_enabled = trackInfoElement->readUInteger();
418  break;
420  m_default = trackInfoElement->readUInteger();
421  break;
423  m_forced = trackInfoElement->readUInteger();
424  break;
426  m_lacing = trackInfoElement->readUInteger();
427  break;
429  defaultDuration = trackInfoElement->readUInteger();
430  break;
431  default:;
432  }
433  switch (m_mediaType) {
434  case MediaType::Video:
435  if (!m_fps && defaultDuration) {
436  m_fps = 1000000000.0 / defaultDuration;
437  }
438  break;
439  default:;
440  }
441  }
442 
443  // read further information from the CodecPrivate element for some codecs
444  switch (m_format.general) {
445  EbmlElement *codecPrivateElement;
447  if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
448  // parse bitmap info header to determine actual format
449  if (codecPrivateElement->dataSize() >= 0x28) {
450  m_istream->seekg(codecPrivateElement->dataOffset());
451  BitmapInfoHeader bitmapInfoHeader;
452  bitmapInfoHeader.parse(reader());
453  m_formatId.reserve(m_formatId.size() + 7);
454  m_formatId += " \"";
455  m_formatId += interpretIntegerAsString(bitmapInfoHeader.compression);
456  m_formatId += "\"";
458  } else {
459  diag.emplace_back(DiagLevel::Critical, "BITMAPINFOHEADER structure (in \"CodecPrivate\"-element) is truncated.", context);
460  }
461  }
462  break;
464  if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
465  // parse WAVE header to determine actual format
466  if (codecPrivateElement->dataSize() >= 16) {
467  m_istream->seekg(codecPrivateElement->dataOffset());
468  WaveFormatHeader waveFormatHeader;
469  waveFormatHeader.parse(reader());
470  WaveAudioStream::addInfo(waveFormatHeader, *this);
471  } else {
472  diag.emplace_back(DiagLevel::Critical, "BITMAPINFOHEADER structure (in \"CodecPrivate\"-element) is truncated.", context);
473  }
474  }
475  break;
477  if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
478  auto audioSpecificConfig
479  = Mp4Track::parseAudioSpecificConfig(*m_istream, codecPrivateElement->dataOffset(), codecPrivateElement->dataSize(), diag);
481  audioSpecificConfig->audioObjectType, audioSpecificConfig->sbrPresent, audioSpecificConfig->psPresent);
482  if (audioSpecificConfig->sampleFrequencyIndex == 0xF) {
483  //m_samplingFrequency = audioSpecificConfig->sampleFrequency;
484  } else if (audioSpecificConfig->sampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
485  //m_samplingFrequency = mpeg4SamplingFrequencyTable[audioSpecificConfig->sampleFrequencyIndex];
486  } else {
487  diag.emplace_back(DiagLevel::Warning, "Audio specific config has invalid sample frequency index.", context);
488  }
489  if (audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
490  //m_extensionSamplingFrequency = audioSpecificConfig->extensionSampleFrequency;
491  } else if (audioSpecificConfig->extensionSampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
492  //m_extensionSamplingFrequency = mpeg4SamplingFrequencyTable[audioSpecificConfig->extensionSampleFrequencyIndex];
493  } else {
494  diag.emplace_back(DiagLevel::Warning, "Audio specific config has invalid extension sample frequency index.", context);
495  }
496  m_channelConfig = audioSpecificConfig->channelConfiguration;
497  m_extensionChannelConfig = audioSpecificConfig->extensionChannelConfiguration;
498  }
499  break;
501  if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
502  auto avcConfig = make_unique<TagParser::AvcConfiguration>();
503  try {
504  m_istream->seekg(codecPrivateElement->dataOffset());
505  avcConfig->parse(m_reader, codecPrivateElement->dataSize());
506  Mp4Track::addInfo(*avcConfig, *this);
507  } catch (const TruncatedDataException &) {
508  diag.emplace_back(DiagLevel::Critical, "AVC configuration is truncated.", context);
509  } catch (const Failure &) {
510  diag.emplace_back(DiagLevel::Critical, "AVC configuration is invalid.", context);
511  }
512  }
513  break;
514  default:;
515  }
516 
517  // parse format name for unknown formats
519  if (startsWith<string>(m_formatId, "V_") || startsWith<string>(m_formatId, "A_") || startsWith<string>(m_formatId, "S_")) {
520  m_formatName = m_formatId.substr(2);
521  } else {
523  }
524  m_formatName.append(" (unknown)");
525  }
526 
527  // use pixel size as display size if display size not specified
528  if (!m_displaySize.width()) {
530  }
531  if (!m_displaySize.height()) {
533  }
534 
535  // set English if no language has been specified (it is default value of MatroskaIds::TrackLanguage)
536  if (m_language.empty()) {
537  m_language = "eng";
538  }
539 }
540 
552 MatroskaTrackHeaderMaker::MatroskaTrackHeaderMaker(const MatroskaTrack &track, Diagnostics &diag)
553  : m_track(track)
554  , m_dataSize(0)
555 {
556  VAR_UNUSED(diag);
557 
558  // calculate size for recognized elements
559  m_dataSize += 2 + 1 + EbmlElement::calculateUIntegerLength(m_track.id());
560  m_dataSize += 1 + 1 + EbmlElement::calculateUIntegerLength(m_track.trackNumber());
561  m_dataSize += 1 + 1 + EbmlElement::calculateUIntegerLength(m_track.isEnabled());
562  m_dataSize += 1 + 1 + EbmlElement::calculateUIntegerLength(m_track.isDefault());
563  m_dataSize += 2 + 1 + EbmlElement::calculateUIntegerLength(m_track.isForced());
564  if (!m_track.name().empty()) {
565  m_dataSize += 2 + EbmlElement::calculateSizeDenotationLength(m_track.name().size()) + m_track.name().size();
566  }
567  if (!m_track.language().empty()) {
568  m_dataSize += 3 + EbmlElement::calculateSizeDenotationLength(m_track.language().size()) + m_track.language().size();
569  }
570 
571  // calculate size for other elements
572  for (EbmlElement *trackInfoElement = m_track.m_trackElement->firstChild(); trackInfoElement; trackInfoElement = trackInfoElement->nextSibling()) {
573  switch (trackInfoElement->id()) {
581  // skip recognized elements
582  break;
583  default:
584  trackInfoElement->makeBuffer();
585  m_dataSize += trackInfoElement->totalSize();
586  }
587  }
588  m_sizeDenotationLength = EbmlElement::calculateSizeDenotationLength(m_dataSize);
589  m_requiredSize = 1 + m_sizeDenotationLength + m_dataSize;
590 }
591 
599 void MatroskaTrackHeaderMaker::make(ostream &stream) const
600 {
601  // make ID and size
602  char buffer[9];
603  *buffer = static_cast<char>(MatroskaIds::TrackEntry);
604  EbmlElement::makeSizeDenotation(m_dataSize, buffer + 1);
605  stream.write(buffer, 1 + m_sizeDenotationLength);
606 
607  // make recognized elements
613  if (!m_track.name().empty()) {
615  }
616  if (!m_track.language().empty()) {
618  }
619 
620  // make other elements
621  for (EbmlElement *trackInfoElement = m_track.m_trackElement->firstChild(); trackInfoElement; trackInfoElement = trackInfoElement->nextSibling()) {
622  switch (trackInfoElement->id()) {
630  // skip recognized elements
631  break;
632  default:
633  trackInfoElement->copyBuffer(stream);
634  }
635  }
636 }
637 
638 } // namespace TagParser
const std::string & language() const
Returns the language of the track if known; otherwise returns an empty string.
void setBottom(uint32 bottom)
Sets the bottom margin to bottom.
Definition: margin.h:91
bool isDefault() const
Returns true if the track is denoted as default; otherwise returns false.
IoUtilities::BinaryReader m_reader
const std::string name() const
Returns the track name if known; otherwise returns an empty string.
uint32 trackNumber() const
Returns the track number if known; otherwise returns 0.
static void makeSimpleElement(std::ostream &stream, IdentifierType id, uint64 content)
Makes a simple EBML element.
void setWidth(uint32 value)
Sets the width.
Definition: size.h:75
TAG_PARSER_EXPORT const char * numberOfBytes()
ImplementationType * firstChild()
Returns the first child of the element.
static MediaFormat codecIdToMediaFormat(const std::string &codecId)
Returns the MediaFormat for the specified Matroska codec ID.
static byte calculateSizeDenotationLength(uint64 size)
Returns the length of the size denotation for the specified size in byte.
static void addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track)
Adds the information from the specified avcConfig to the specified track.
Definition: mp4track.cpp:1016
TAG_PARSER_EXPORT const char * writingDate()
static void addInfo(const WaveFormatHeader &waveHeader, AbstractTrack &track)
Adds the information from the specified waveHeader to the specified track.
TAG_PARSER_EXPORT const char * bitrate()
The track&#39;s bit rate in bits per second.
DataSizeType dataSize() const
Returns the data size of the element in byte.
STL namespace.
GeneralMediaFormat general
Definition: mediaformat.h:256
IoUtilities::BinaryReader & reader()
Returns a binary reader for the associated stream.
void setLeft(uint32 left)
Sets the left margin to left.
Definition: margin.h:75
void parse(IoUtilities::BinaryReader &reader)
Parses the WAVE "fmt " header segment using the specified reader.
static byte calculateUIntegerLength(uint64 integer)
Returns the length of the specified unsigned integer in byte.
static std::unique_ptr< Mpeg4AudioSpecificConfig > parseAudioSpecificConfig(std::istream &stream, uint64 startOffset, uint64 size, Diagnostics &diag)
Parses the audio specific configuration for the track.
Definition: mp4track.cpp:651
~MatroskaTrack() override
Destroys the track.
void setRight(uint32 right)
Sets the right margin to right.
Definition: margin.h:107
TAG_PARSER_EXPORT const char * duration()
void setHeight(uint32 value)
Sets the height.
Definition: size.h:83
TAG_PARSER_EXPORT MediaFormat idToMediaFormat(byte mpeg4AudioObjectId, bool sbrPresent=false, bool psPresent=false)
Definition: mp4ids.cpp:352
bool isEmpty() const
Returns an indication whether an value is assigned.
Definition: tagvalue.h:367
uint64 dataOffset() const
Returns the data offset of the element in the related stream.
ChronoUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:263
constexpr uint32 height() const
Returns the height.
Definition: size.h:67
void internalParseHeader(Diagnostics &diag) override
This method is internally called to parse header information.
ChronoUtilities::TimeSpan m_duration
void setTop(uint32 top)
Sets the top margin to top.
Definition: margin.h:59
static byte makeSizeDenotation(uint64 size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
TAG_PARSER_EXPORT MediaFormat fourccToMediaFormat(uint32 fourccId)
Definition: mp4ids.cpp:46
std::istream * m_istream
bool isEnabled() const
Returns true if the track is denoted as enabled; otherwise returns false.
uint64 id() const
Returns the track ID if known; otherwise returns 0.
ChronoUtilities::DateTime toDateTime() const
Converts the value of the current TagValue object to its equivalent DateTime representation.
Definition: tagvalue.cpp:291
ChronoUtilities::DateTime m_creationTime
unsigned char extension
Definition: mediaformat.h:258
void parse(IoUtilities::BinaryReader &reader)
Parses the BITMAPINFOHEADER structure using the specified reader.
const IdContainerType & tracks() const
Returns the tracks.
Definition: tagtarget.h:104
TAG_PARSER_EXPORT const char * numberOfFrames()
TrackType type() const override
Returns the type of the track if known; otherwise returns TrackType::Unspecified. ...
bool isForced() const
Returns true if the track is denoted as forced; otherwise returns false.
std::string toString(TagTextEncoding encoding=TagTextEncoding::Unspecified) const
Converts the value of the current TagValue object to its equivalent std::string representation.
Definition: tagvalue.h:343
ChronoUtilities::DateTime m_modificationTime
constexpr uint32 width() const
Returns the width.
Definition: size.h:59
void readStatisticsFromTags(const std::vector< std::unique_ptr< MatroskaTag >> &tags, Diagnostics &diag)
Reads track-specific statistics from the specified tags.
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
TrackType
Specifies the track type.
Definition: abstracttrack.h:28
uint32 mpeg4SamplingFrequencyTable[13]
Definition: mp4ids.cpp:408
void make(std::ostream &stream) const
Saves the header for the track (specified when constructing the object) to the specified stream (make...