Tag Parser  7.0.3
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>> * = nullptr> 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>> * = nullptr>
245 NumberType tagValueToBitrate(const TagValue &tagValue)
246 {
247  return stringToNumber<NumberType>(tagValue.toString(TagTextEncoding::Utf8)) / 1000;
248 }
249 
251 
261 void MatroskaTrack::readStatisticsFromTags(const std::vector<std::unique_ptr<MatroskaTag>> &tags, Diagnostics &diag)
262 {
263  using namespace std::placeholders;
264  using namespace MatroskaTagIds::TrackSpecific;
265  for (const auto &tag : tags) {
266  const TagTarget &target = tag->target();
267  if (find(target.tracks().cbegin(), target.tracks().cend(), id()) == target.tracks().cend()) {
268  continue;
269  }
270  assignPropertyFromTagValue(tag, numberOfBytes(), m_size, &tagValueToNumber<uint64>, diag);
271  assignPropertyFromTagValue(tag, numberOfFrames(), m_sampleCount, &tagValueToNumber<uint64>, diag);
272  assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::duration(), m_duration, bind(&TagValue::toTimeSpan, _1), diag);
273  assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::bitrate(), m_bitrate, &tagValueToBitrate<double>, diag);
274  assignPropertyFromTagValue(tag, writingDate(), m_modificationTime, bind(&TagValue::toDateTime, _1), diag);
275  if (m_creationTime.isNull()) {
277  }
278  }
279 }
280 
282 {
283  static const string context("parsing header of Matroska track");
284  try {
285  m_trackElement->parse(diag);
286  } catch (const Failure &) {
287  diag.emplace_back(DiagLevel::Critical, "Unable to parse track element.", context);
288  throw;
289  }
290  // read information about the track from the childs of the track entry element
291  for (EbmlElement *trackInfoElement = m_trackElement->firstChild(), *subElement = nullptr; trackInfoElement;
292  trackInfoElement = trackInfoElement->nextSibling()) {
293  try {
294  trackInfoElement->parse(diag);
295  } catch (const Failure &) {
296  diag.emplace_back(DiagLevel::Critical, "Unable to parse track information element.", context);
297  break;
298  }
299  uint32 defaultDuration = 0;
300  switch (trackInfoElement->id()) {
302  switch (trackInfoElement->readUInteger()) {
305  break;
308  break;
311  break;
314  break;
317  break;
318  default:
320  }
321  break;
323  for (subElement = trackInfoElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
324  try {
325  subElement->parse(diag);
326  } catch (const Failure &) {
327  diag.emplace_back(DiagLevel::Critical, "Unable to parse video track element.", context);
328  break;
329  }
330  switch (subElement->id()) {
332  m_displaySize.setWidth(subElement->readUInteger());
333  break;
335  m_displaySize.setHeight(subElement->readUInteger());
336  break;
338  m_pixelSize.setWidth(subElement->readUInteger());
339  break;
341  m_pixelSize.setHeight(subElement->readUInteger());
342  break;
344  m_cropping.setTop(subElement->readUInteger());
345  break;
347  m_cropping.setLeft(subElement->readUInteger());
348  break;
350  m_cropping.setBottom(subElement->readUInteger());
351  break;
353  m_cropping.setRight(subElement->readUInteger());
354  break;
356  m_fps = subElement->readFloat();
357  break;
359  m_interlaced = subElement->readUInteger();
360  break;
362  m_colorSpace = subElement->readUInteger();
363  break;
364  default:;
365  }
366  }
367  break;
369  for (subElement = trackInfoElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
370  try {
371  subElement->parse(diag);
372  } catch (const Failure &) {
373  diag.emplace_back(DiagLevel::Critical, "Unable to parse audio track element.", context);
374  break;
375  }
376  switch (subElement->id()) {
378  m_bitsPerSample = subElement->readUInteger();
379  break;
381  m_channelCount = subElement->readUInteger();
382  break;
384  if (!m_samplingFrequency) {
385  m_samplingFrequency = subElement->readFloat();
386  }
387  break;
390  m_extensionSamplingFrequency = subElement->readFloat();
391  }
392  break;
393  default:;
394  }
395  }
396  break;
398  m_trackNumber = trackInfoElement->readUInteger();
399  break;
401  m_id = trackInfoElement->readUInteger();
402  break;
404  m_name = trackInfoElement->readString();
405  break;
407  m_language = trackInfoElement->readString();
408  break;
410  m_format = codecIdToMediaFormat(m_formatId = trackInfoElement->readString());
411  break;
413  m_formatName = trackInfoElement->readString();
414  break;
416  break; // TODO
418  m_enabled = trackInfoElement->readUInteger();
419  break;
421  m_default = trackInfoElement->readUInteger();
422  break;
424  m_forced = trackInfoElement->readUInteger();
425  break;
427  m_lacing = trackInfoElement->readUInteger();
428  break;
430  defaultDuration = trackInfoElement->readUInteger();
431  break;
432  default:;
433  }
434  switch (m_mediaType) {
435  case MediaType::Video:
436  if (!m_fps && defaultDuration) {
437  m_fps = 1000000000.0 / defaultDuration;
438  }
439  break;
440  default:;
441  }
442  }
443 
444  // read further information from the CodecPrivate element for some codecs
445  switch (m_format.general) {
446  EbmlElement *codecPrivateElement;
448  if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
449  // parse bitmap info header to determine actual format
450  if (codecPrivateElement->dataSize() >= 0x28) {
451  m_istream->seekg(codecPrivateElement->dataOffset());
452  BitmapInfoHeader bitmapInfoHeader;
453  bitmapInfoHeader.parse(reader());
454  m_formatId.reserve(m_formatId.size() + 7);
455  m_formatId += " \"";
456  m_formatId += interpretIntegerAsString(bitmapInfoHeader.compression);
457  m_formatId += "\"";
459  } else {
460  diag.emplace_back(DiagLevel::Critical, "BITMAPINFOHEADER structure (in \"CodecPrivate\"-element) is truncated.", context);
461  }
462  }
463  break;
465  if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
466  // parse WAVE header to determine actual format
467  if (codecPrivateElement->dataSize() >= 16) {
468  m_istream->seekg(codecPrivateElement->dataOffset());
469  WaveFormatHeader waveFormatHeader;
470  waveFormatHeader.parse(reader());
471  WaveAudioStream::addInfo(waveFormatHeader, *this);
472  } else {
473  diag.emplace_back(DiagLevel::Critical, "BITMAPINFOHEADER structure (in \"CodecPrivate\"-element) is truncated.", context);
474  }
475  }
476  break;
478  if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
479  auto audioSpecificConfig
480  = Mp4Track::parseAudioSpecificConfig(*m_istream, codecPrivateElement->dataOffset(), codecPrivateElement->dataSize(), diag);
482  audioSpecificConfig->audioObjectType, audioSpecificConfig->sbrPresent, audioSpecificConfig->psPresent);
483  if (audioSpecificConfig->sampleFrequencyIndex == 0xF) {
484  //m_samplingFrequency = audioSpecificConfig->sampleFrequency;
485  } else if (audioSpecificConfig->sampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
486  //m_samplingFrequency = mpeg4SamplingFrequencyTable[audioSpecificConfig->sampleFrequencyIndex];
487  } else {
488  diag.emplace_back(DiagLevel::Warning, "Audio specific config has invalid sample frequency index.", context);
489  }
490  if (audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
491  //m_extensionSamplingFrequency = audioSpecificConfig->extensionSampleFrequency;
492  } else if (audioSpecificConfig->extensionSampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
493  //m_extensionSamplingFrequency = mpeg4SamplingFrequencyTable[audioSpecificConfig->extensionSampleFrequencyIndex];
494  } else {
495  diag.emplace_back(DiagLevel::Warning, "Audio specific config has invalid extension sample frequency index.", context);
496  }
497  m_channelConfig = audioSpecificConfig->channelConfiguration;
498  m_extensionChannelConfig = audioSpecificConfig->extensionChannelConfiguration;
499  }
500  break;
502  if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
503  auto avcConfig = make_unique<TagParser::AvcConfiguration>();
504  try {
505  m_istream->seekg(codecPrivateElement->dataOffset());
506  avcConfig->parse(m_reader, codecPrivateElement->dataSize());
507  Mp4Track::addInfo(*avcConfig, *this);
508  } catch (const TruncatedDataException &) {
509  diag.emplace_back(DiagLevel::Critical, "AVC configuration is truncated.", context);
510  } catch (const Failure &) {
511  diag.emplace_back(DiagLevel::Critical, "AVC configuration is invalid.", context);
512  }
513  }
514  break;
515  default:;
516  }
517 
518  // parse format name for unknown formats
520  if (startsWith<string>(m_formatId, "V_") || startsWith<string>(m_formatId, "A_") || startsWith<string>(m_formatId, "S_")) {
521  m_formatName = m_formatId.substr(2);
522  } else {
524  }
525  m_formatName.append(" (unknown)");
526  }
527 
528  // use pixel size as display size if display size not specified
529  if (!m_displaySize.width()) {
531  }
532  if (!m_displaySize.height()) {
534  }
535 
536  // set English if no language has been specified (it is default value of MatroskaIds::TrackLanguage)
537  if (m_language.empty()) {
538  m_language = "eng";
539  }
540 }
541 
553 MatroskaTrackHeaderMaker::MatroskaTrackHeaderMaker(const MatroskaTrack &track, Diagnostics &diag)
554  : m_track(track)
555  , m_dataSize(0)
556 {
557  VAR_UNUSED(diag);
558 
559  // calculate size for recognized elements
560  m_dataSize += 2 + 1 + EbmlElement::calculateUIntegerLength(m_track.id());
561  m_dataSize += 1 + 1 + EbmlElement::calculateUIntegerLength(m_track.trackNumber());
562  m_dataSize += 1 + 1 + EbmlElement::calculateUIntegerLength(m_track.isEnabled());
563  m_dataSize += 1 + 1 + EbmlElement::calculateUIntegerLength(m_track.isDefault());
564  m_dataSize += 2 + 1 + EbmlElement::calculateUIntegerLength(m_track.isForced());
565  if (!m_track.name().empty()) {
566  m_dataSize += 2 + EbmlElement::calculateSizeDenotationLength(m_track.name().size()) + m_track.name().size();
567  }
568  if (!m_track.language().empty()) {
569  m_dataSize += 3 + EbmlElement::calculateSizeDenotationLength(m_track.language().size()) + m_track.language().size();
570  }
571 
572  // calculate size for other elements
573  for (EbmlElement *trackInfoElement = m_track.m_trackElement->firstChild(); trackInfoElement; trackInfoElement = trackInfoElement->nextSibling()) {
574  switch (trackInfoElement->id()) {
582  // skip recognized elements
583  break;
584  default:
585  trackInfoElement->makeBuffer();
586  m_dataSize += trackInfoElement->totalSize();
587  }
588  }
589  m_sizeDenotationLength = EbmlElement::calculateSizeDenotationLength(m_dataSize);
590  m_requiredSize = 1 + m_sizeDenotationLength + m_dataSize;
591 }
592 
600 void MatroskaTrackHeaderMaker::make(ostream &stream) const
601 {
602  // make ID and size
603  char buffer[9];
604  *buffer = static_cast<char>(MatroskaIds::TrackEntry);
605  EbmlElement::makeSizeDenotation(m_dataSize, buffer + 1);
606  stream.write(buffer, 1 + m_sizeDenotationLength);
607 
608  // make recognized elements
614  if (!m_track.name().empty()) {
616  }
617  if (!m_track.language().empty()) {
619  }
620 
621  // make other elements
622  for (EbmlElement *trackInfoElement = m_track.m_trackElement->firstChild(); trackInfoElement; trackInfoElement = trackInfoElement->nextSibling()) {
623  switch (trackInfoElement->id()) {
631  // skip recognized elements
632  break;
633  default:
634  trackInfoElement->copyBuffer(stream);
635  }
636  }
637 }
638 
639 } // 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...