diff --git a/abstracttrack.h b/abstracttrack.h index e5233e4..7d8f91b 100644 --- a/abstracttrack.h +++ b/abstracttrack.h @@ -472,7 +472,7 @@ inline byte AbstractTrack::channelConfig() const } /*! - * \brief Returns the number of samples if known; otherwise returns 0. + * \brief Returns the number of samples/frames if known; otherwise returns 0. */ inline uint64 AbstractTrack::sampleCount() const { diff --git a/matroska/matroskacontainer.cpp b/matroska/matroskacontainer.cpp index 8ec951d..0fb30b6 100644 --- a/matroska/matroskacontainer.cpp +++ b/matroska/matroskacontainer.cpp @@ -610,6 +610,21 @@ void MatroskaContainer::parseSegmentInfo() } } +/*! + * \brief Reads track-specific statistics from tags. + * \remarks Tags and tracks must have been parsed before calling this method. + * \sa MatroskaTrack::readStatisticsFromTags() + */ +void MatroskaContainer::readTrackStatisticsFromTags() +{ + if(tracks().empty() || tags().empty()) { + return; + } + for(const auto &track : tracks()) { + track->readStatisticsFromTags(tags()); + } +} + void MatroskaContainer::internalParseTags() { static const string context("parsing tags of Matroska container"); @@ -622,8 +637,8 @@ void MatroskaContainer::internalParseTags() case MatroskaIds::Tag: m_tags.emplace_back(make_unique()); try { - m_tags.back()->parse(*subElement); - } catch(NoDataFoundException &) { + m_tags.back()->parse(*subElement); + } catch(const NoDataFoundException &) { m_tags.pop_back(); } catch(const Failure &) { addNotification(NotificationType::Critical, argsToString("Unable to parse tag ", m_tags.size(), '.'), context); @@ -638,9 +653,11 @@ void MatroskaContainer::internalParseTags() } } catch(const Failure &) { addNotification(NotificationType::Critical, "Element structure seems to be invalid.", context); + readTrackStatisticsFromTags(); throw; } } + readTrackStatisticsFromTags(); } void MatroskaContainer::internalParseTracks() @@ -672,9 +689,11 @@ void MatroskaContainer::internalParseTracks() } } catch(const Failure &) { addNotification(NotificationType::Critical, "Element structure seems to be invalid.", context); + readTrackStatisticsFromTags(); throw; } } + readTrackStatisticsFromTags(); } void MatroskaContainer::internalParseChapters() diff --git a/matroska/matroskacontainer.h b/matroska/matroskacontainer.h index 54fedb9..2594aeb 100644 --- a/matroska/matroskacontainer.h +++ b/matroska/matroskacontainer.h @@ -60,7 +60,7 @@ protected: private: void parseSegmentInfo(); - void fetchEditionEntryElements(); + void readTrackStatisticsFromTags(); uint64 m_maxIdLength; uint64 m_maxSizeLength; diff --git a/matroska/matroskatagid.h b/matroska/matroskatagid.h index 7c91f7e..4da1e46 100644 --- a/matroska/matroskatagid.h +++ b/matroska/matroskatagid.h @@ -337,6 +337,35 @@ inline TAG_PARSER_EXPORT const char *termsOfUse() { return "TERMS_OF_USE"; } +/*! + * \brief Encapsulates track-specific Matroska tag IDs written by mkvmerge 7.0.0 or newer. + * \sa https://github.com/mbunkus/mkvtoolnix/wiki/Automatic-tag-generation + */ +namespace TrackSpecific { +inline TAG_PARSER_EXPORT const char *numberOfBytes() { + return "NUMBER_OF_BYTES"; +} +inline TAG_PARSER_EXPORT const char *numberOfFrames() { + return "NUMBER_OF_FRAMES"; +} +inline TAG_PARSER_EXPORT const char *duration() { + return "DURATION"; +} +/// \brief The track's bit rate in bits per second. +inline TAG_PARSER_EXPORT const char *bitrate() { + return "BPS"; +} +inline TAG_PARSER_EXPORT const char *writingApp() { + return "_STATISTICS_WRITING_APP"; +} +inline TAG_PARSER_EXPORT const char *writingDate() { + return "_STATISTICS_WRITING_DATE_UTC"; +} +inline TAG_PARSER_EXPORT const char *statisticsTags() { + return "_STATISTICS_TAGS"; +} +} + } /*! diff --git a/matroska/matroskatrack.cpp b/matroska/matroskatrack.cpp index f1c13f4..aaf999d 100644 --- a/matroska/matroskatrack.cpp +++ b/matroska/matroskatrack.cpp @@ -2,6 +2,7 @@ #include "./matroskatrack.h" #include "./matroskacontainer.h" #include "./matroskaid.h" +#include "./matroskatag.h" #include "../avi/bitmapinfoheader.h" @@ -201,6 +202,81 @@ MediaFormat MatroskaTrack::codecIdToMediaFormat(const string &codecId) return fmt; } +/// \cond + +template +void MatroskaTrack::assignPropertyFromTagValue(const std::unique_ptr &tag, const char *fieldId, PropertyType &property, const ConversionFunction &conversionFunction) +{ + const TagValue &value = tag->value(fieldId); + if(!value.isEmpty()) { + try { + property = conversionFunction(value); + } catch(const ConversionException &) { + string message; + try { + message = argsToString("Ignoring invalid value \"", value.toString(TagTextEncoding::Utf8), "\" of \"", fieldId, '\"', '.'); + } catch(const ConversionException &) { + message = argsToString("Ignoring invalid value of \"", fieldId, '\"', '.'); + } + addNotification(NotificationType::Warning, message, argsToString("reading track statatistic from \"", tag->toString(), '\"')); + } + } +} + +template>...> +NumberType tagValueToNumber(const TagValue &tagValue) +{ + // optimization for Latin1/UTF-8 strings + if(tagValue.type() == TagDataType::Text) { + switch(tagValue.dataEncoding()) { + case TagTextEncoding::Latin1: + case TagTextEncoding::Utf8: + return bufferToNumber(tagValue.dataPointer(), tagValue.dataSize()); + default: + ; + } + } + // generic conversion + return stringToNumber(tagValue.toString(TagTextEncoding::Utf8)); +} + +template>...> +NumberType tagValueToBitrate(const TagValue &tagValue) +{ + return stringToNumber(tagValue.toString(TagTextEncoding::Utf8)) / 1000; +} + +/// \endcond + +/*! + * \brief Reads track-specific statistics from the specified \a tags. + * \remarks + * - Those statistics are generated might be generated by some Muxers, eg. mkvmerge 7.0.0 or newer. + * - Only tags targeting the track are considered. Hence the track ID must have been determined + * before (either by calling parseHeader() or setId()). + * \sa https://github.com/mbunkus/mkvtoolnix/wiki/Automatic-tag-generation for list of track-specific + * tag fields written by mkvmerge + */ +void MatroskaTrack::readStatisticsFromTags(const std::vector > &tags) +{ + using namespace std::placeholders; + using namespace MatroskaTagIds::TrackSpecific; + for(const auto &tag : tags) { + const TagTarget &target = tag->target(); + if(find(target.tracks().cbegin(), target.tracks().cend(), id()) == target.tracks().cend()) { + continue; + } + assignPropertyFromTagValue(tag, numberOfBytes(), m_size, &tagValueToNumber); + assignPropertyFromTagValue(tag, numberOfFrames(), m_sampleCount, &tagValueToNumber); + assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::duration(), m_duration, bind(&TagValue::toTimeSpan, _1)); + assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::bitrate(), m_bitrate, &tagValueToBitrate); + assignPropertyFromTagValue(tag, writingDate(), m_modificationTime, bind(&TagValue::toDateTime, _1)); + if(m_creationTime.isNull()) { + m_creationTime = m_modificationTime; + } + } +} + void MatroskaTrack::internalParseHeader() { static const string context("parsing header of Matroska track"); diff --git a/matroska/matroskatrack.h b/matroska/matroskatrack.h index c8f4f5d..c8443fc 100644 --- a/matroska/matroskatrack.h +++ b/matroska/matroskatrack.h @@ -8,6 +8,7 @@ namespace Media { class EbmlElement; class MatroskaContainer; class MatroskaTrack; +class MatroskaTag; class TAG_PARSER_EXPORT MatroskaTrackHeaderMaker { @@ -55,6 +56,7 @@ public: TrackType type() const; static MediaFormat codecIdToMediaFormat(const std::string &codecId); + void readStatisticsFromTags(const std::vector > &tags); MatroskaTrackHeaderMaker prepareMakingHeader() const; void makeHeader(std::ostream &stream) const; @@ -62,6 +64,9 @@ protected: void internalParseHeader(); private: + template + void assignPropertyFromTagValue(const std::unique_ptr &tag, const char *fieldId, PropertyType &integer, const ConversionFunction &conversionFunction); + EbmlElement *m_trackElement; };