tagparser/matroska/matroskatrack.cpp

659 lines
29 KiB
C++

#include "./matroskatrack.h"
#include "./ebmlelement.h"
#include "./matroskacontainer.h"
#include "./matroskaid.h"
#include "./matroskatag.h"
#include "../avi/bitmapinfoheader.h"
#include "../wav/waveaudiostream.h"
#include "../avc/avcconfiguration.h"
#include "../mp4/mp4ids.h"
#include "../mp4/mp4track.h"
#include "../exceptions.h"
#include "../mediaformat.h"
#include <c++utilities/conversion/stringconversion.h>
using namespace std;
using namespace CppUtilities;
namespace TagParser {
/*!
* \class TagParser::MatroskaTrack
* \brief Implementation of TagParser::AbstractTrack for the Matroska container.
*/
/*!
* \brief Constructs a new track for the specified \a trackElement.
*
* Each track element (ID: MatroskaId::TrackEntry) holds header information
* for one track in the Matroska file.
*/
MatroskaTrack::MatroskaTrack(EbmlElement &trackElement)
: AbstractTrack(trackElement.stream(), trackElement.startOffset())
, m_trackElement(&trackElement)
{
}
/*!
* \brief Destroys the track.
*/
MatroskaTrack::~MatroskaTrack()
{
}
TrackType MatroskaTrack::type() const
{
return TrackType::MatroskaTrack;
}
/*!
* \brief Returns the MediaFormat for the specified Matroska codec ID.
* \todo Use an std::unordered_map here.
*/
MediaFormat MatroskaTrack::codecIdToMediaFormat(const string &codecId)
{
auto parts = splitString<vector<string>>(codecId, "/", EmptyPartsTreat::Keep, 3);
parts.resize(3);
const auto &part1 = parts[0], &part2 = parts[1], &part3 = parts[2];
MediaFormat fmt;
if (part1 == "V_MS" && part2 == "VFW" && part3 == "FOURCC") {
fmt.general = GeneralMediaFormat::MicrosoftVideoCodecManager;
} else if (part1 == "V_UNCOMPRESSED") {
fmt.general = GeneralMediaFormat::UncompressedVideoFrames;
} else if (part1 == "V_MPEG4") {
fmt.general = GeneralMediaFormat::Mpeg4Video;
if (part2 == "ISO") {
if (part3 == "SP") {
fmt.sub = SubFormats::Mpeg4SimpleProfile1;
} else if (part3 == "ASP") {
fmt.sub = SubFormats::Mpeg4AdvancedSimpleProfile1;
} else if (part3 == "AVC") {
fmt.general = GeneralMediaFormat::Avc;
}
} else if (part2 == "MS" && part3 == "V3") {
fmt.sub = SubFormats::Mpeg4SimpleProfile1;
}
} else if (part1 == "V_MPEG1") {
fmt.general = GeneralMediaFormat::Mpeg1Video;
} else if (part1 == "V_MPEG2") {
fmt.general = GeneralMediaFormat::Mpeg2Video;
} else if (part1 == "V_REAL") {
fmt.general = GeneralMediaFormat::RealVideo;
} else if (part1 == "V_QUICKTIME") {
fmt.general = GeneralMediaFormat::QuicktimeVideo;
} else if (part1 == "V_THEORA") {
fmt.general = GeneralMediaFormat::Theora;
} else if (part1 == "V_PRORES") {
fmt.general = GeneralMediaFormat::ProRes;
} else if (part1 == "V_VP8") {
fmt.general = GeneralMediaFormat::Vp8;
} else if (part1 == "V_VP9") {
fmt.general = GeneralMediaFormat::Vp9;
} else if (part1 == "V_AV1") {
fmt.general = GeneralMediaFormat::Av1;
} else if (part1 == "A_MPEG") {
fmt.general = GeneralMediaFormat::Mpeg1Audio;
if (part2 == "L1") {
fmt.sub = SubFormats::Mpeg1Layer1;
} else if (part2 == "L2") {
fmt.sub = SubFormats::Mpeg1Layer2;
} else if (part2 == "L3") {
fmt.sub = SubFormats::Mpeg1Layer3;
}
} else if (part1 == "V_MPEGH" && part2 == "ISO" && part3 == "HEVC") {
fmt.general = GeneralMediaFormat::Hevc;
} else if (part1 == "A_PCM") {
fmt.general = GeneralMediaFormat::Pcm;
if (part2 == "INT") {
if (part3 == "BIG") {
fmt.sub = SubFormats::PcmIntBe;
} else if (part3 == "LIT") {
fmt.sub = SubFormats::PcmIntLe;
}
} else if (part2 == "FLOAT" && part3 == "IEEE") {
fmt.sub = SubFormats::PcmFloatIeee;
}
} else if (part1 == "A_MPC") {
fmt.general = GeneralMediaFormat::Mpc;
} else if (part1 == "A_AC3") {
fmt.general = GeneralMediaFormat::Ac3;
} else if (part1 == "A_EAC3") {
fmt.general = GeneralMediaFormat::EAc3;
} else if (part1 == "A_ALAC") {
fmt.general = GeneralMediaFormat::Alac;
} else if (part1 == "A_DTS") {
fmt.general = GeneralMediaFormat::Dts;
if (part2 == "EXPRESS") {
fmt.sub = SubFormats::DtsExpress;
} else if (part2 == "LOSSLESS") {
fmt.sub = SubFormats::DtsLossless;
}
} else if (part1 == "A_VORBIS") {
fmt.general = GeneralMediaFormat::Vorbis;
} else if (part1 == "A_FLAC") {
fmt.general = GeneralMediaFormat::Flac;
} else if (part1 == "A_OPUS") {
fmt.general = GeneralMediaFormat::Opus;
} else if (part1 == "A_REAL") {
fmt.general = GeneralMediaFormat::RealAudio;
} else if (part1 == "A_MS" && part2 == "ACM") {
fmt.general = GeneralMediaFormat::MicrosoftAudioCodecManager;
} else if (part1 == "A_AAC") {
fmt.general = GeneralMediaFormat::Aac;
if (part2 == "MPEG2") {
if (part3 == "MAIN") {
fmt.sub = SubFormats::AacMpeg2MainProfile;
} else if (part3 == "LC") {
fmt.sub = SubFormats::AacMpeg2LowComplexityProfile;
} else if (part3 == "SBR") {
fmt.sub = SubFormats::AacMpeg2LowComplexityProfile;
fmt.extension = ExtensionFormats::SpectralBandReplication;
} else if (part3 == "SSR") {
fmt.sub = SubFormats::AacMpeg2ScalableSamplingRateProfile;
}
} else if (part2 == "MPEG4") {
if (part3 == "MAIN") {
fmt.sub = SubFormats::AacMpeg4MainProfile;
} else if (part3 == "LC") {
fmt.sub = SubFormats::AacMpeg4LowComplexityProfile;
} else if (part3 == "SBR") {
fmt.sub = SubFormats::AacMpeg4LowComplexityProfile;
fmt.extension = ExtensionFormats::SpectralBandReplication;
} else if (part3 == "SSR") {
fmt.sub = SubFormats::AacMpeg4ScalableSamplingRateProfile;
} else if (part3 == "LTP") {
fmt.sub = SubFormats::AacMpeg4LongTermPrediction;
}
}
} else if (part1 == "A_QUICKTIME") {
fmt.general = GeneralMediaFormat::QuicktimeAudio;
} else if (part1 == "A_TTA1") {
fmt.general = GeneralMediaFormat::Tta;
} else if (part1 == "A_WAVPACK4") {
fmt.general = GeneralMediaFormat::WavPack;
} else if (part1 == "S_TEXT") {
fmt.general = GeneralMediaFormat::TextSubtitle;
if (part2 == "UTF8") {
fmt.sub = SubFormats::PlainUtf8Subtitle;
} else if (part2 == "SSA") {
fmt.sub = SubFormats::SubStationAlpha;
} else if (part2 == "ASS") {
fmt.sub = SubFormats::AdvancedSubStationAlpha;
} else if (part2 == "USF") {
fmt.sub = SubFormats::UniversalSubtitleFormat;
} else if (part2 == "WEBVTT") {
fmt.sub = SubFormats::WebVideoTextTracksFormat;
}
} else if (part1 == "S_IMAGE") {
fmt.general = GeneralMediaFormat::ImageSubtitle;
if (part2 == "BMP") {
fmt.sub = SubFormats::ImgSubBmp;
}
} else if (part1 == "S_VOBSUB") {
fmt.general = GeneralMediaFormat::VobSub;
} else if (part1 == "S_KATE") {
fmt.general = GeneralMediaFormat::OggKate;
} else if (part1 == "B_VOBBTN") {
fmt.general = GeneralMediaFormat::VobBtn;
} else if (part1 == "S_DVBSUB") {
fmt.general = GeneralMediaFormat::DvbSub;
} else if (part1 == "V_MSWMV") {
fmt.general = GeneralMediaFormat::Vc1;
}
return fmt;
}
/// \cond
template <typename PropertyType, typename ConversionFunction>
void MatroskaTrack::assignPropertyFromTagValue(const std::unique_ptr<MatroskaTag> &tag, std::string_view fieldId, PropertyType &property,
const ConversionFunction &conversionFunction, Diagnostics &diag)
{
const TagValue &value = tag->value(std::string(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, '\"', '.');
}
diag.emplace_back(DiagLevel::Warning, message, argsToString("reading track statatistic from \"", tag->toString(), '\"'));
}
}
}
template <typename NumberType, Traits::EnableIf<std::is_integral<NumberType>> * = nullptr> 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<NumberType>(tagValue.dataPointer(), tagValue.dataSize());
default:;
}
}
// generic conversion
return stringToNumber<NumberType>(tagValue.toString(TagTextEncoding::Utf8));
}
template <typename NumberType, Traits::EnableIf<std::is_floating_point<NumberType>> * = nullptr>
NumberType tagValueToBitrate(const TagValue &tagValue)
{
return stringToNumber<NumberType>(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<std::unique_ptr<MatroskaTag>> &tags, Diagnostics &diag)
{
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<std::uint64_t>, diag);
assignPropertyFromTagValue(tag, numberOfFrames(), m_sampleCount, &tagValueToNumber<std::uint64_t>, diag);
assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::duration(), m_duration, bind(&TagValue::toTimeSpan, _1), diag);
assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::bitrate(), m_bitrate, &tagValueToBitrate<double>, diag);
assignPropertyFromTagValue(tag, writingDate(), m_modificationTime, bind(&TagValue::toDateTime, _1), diag);
if (m_creationTime.isNull()) {
m_creationTime = m_modificationTime;
}
}
}
void MatroskaTrack::internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
{
CPP_UTILITIES_UNUSED(progress)
static const string context("parsing header of Matroska track");
try {
m_trackElement->parse(diag);
} catch (const Failure &) {
diag.emplace_back(DiagLevel::Critical, "Unable to parse track element.", context);
throw;
}
// read information about the track from the children of the track entry element
auto hasIsoLanguage = false;
m_flags = TrackFlags::Default | TrackFlags::Enabled;
for (EbmlElement *trackInfoElement = m_trackElement->firstChild(), *subElement = nullptr; trackInfoElement;
trackInfoElement = trackInfoElement->nextSibling()) {
try {
trackInfoElement->parse(diag);
} catch (const Failure &) {
diag.emplace_back(DiagLevel::Critical, "Unable to parse track information element.", context);
break;
}
std::uint64_t defaultDuration = 0;
switch (trackInfoElement->id()) {
case MatroskaIds::TrackType:
switch (trackInfoElement->readUInteger()) {
case MatroskaTrackType::Video:
m_mediaType = MediaType::Video;
break;
case MatroskaTrackType::Audio:
m_mediaType = MediaType::Audio;
break;
case MatroskaTrackType::Subtitle:
m_mediaType = MediaType::Text;
break;
case MatroskaTrackType::Buttons:
m_mediaType = MediaType::Buttons;
break;
case MatroskaTrackType::Control:
m_mediaType = MediaType::Control;
break;
default:
m_mediaType = MediaType::Unknown;
}
break;
case MatroskaIds::TrackVideo:
for (subElement = trackInfoElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
try {
subElement->parse(diag);
} catch (const Failure &) {
diag.emplace_back(DiagLevel::Critical, "Unable to parse video track element.", context);
break;
}
switch (subElement->id()) {
case MatroskaIds::DisplayWidth:
m_displaySize.setWidth(static_cast<std::uint32_t>(subElement->readUInteger()));
break;
case MatroskaIds::DisplayHeight:
m_displaySize.setHeight(static_cast<std::uint32_t>(subElement->readUInteger()));
break;
case MatroskaIds::PixelWidth:
m_pixelSize.setWidth(static_cast<std::uint32_t>(subElement->readUInteger()));
break;
case MatroskaIds::PixelHeight:
m_pixelSize.setHeight(static_cast<std::uint32_t>(subElement->readUInteger()));
break;
case MatroskaIds::PixelCropTop:
m_cropping.setTop(static_cast<std::uint32_t>(subElement->readUInteger()));
break;
case MatroskaIds::PixelCropLeft:
m_cropping.setLeft(static_cast<std::uint32_t>(subElement->readUInteger()));
break;
case MatroskaIds::PixelCropBottom:
m_cropping.setBottom(static_cast<std::uint32_t>(subElement->readUInteger()));
break;
case MatroskaIds::PixelCropRight:
m_cropping.setRight(static_cast<std::uint32_t>(subElement->readUInteger()));
break;
case MatroskaIds::FrameRate:
m_fps = static_cast<std::uint32_t>(subElement->readFloat());
break;
case MatroskaIds::FlagInterlaced:
modFlagEnum(m_flags, TrackFlags::Interlaced, subElement->readUInteger());
break;
case MatroskaIds::ColorSpace:
m_colorSpace = static_cast<std::uint32_t>(subElement->readUInteger());
break;
default:;
}
}
break;
case MatroskaIds::TrackAudio:
for (subElement = trackInfoElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
try {
subElement->parse(diag);
} catch (const Failure &) {
diag.emplace_back(DiagLevel::Critical, "Unable to parse audio track element.", context);
break;
}
switch (subElement->id()) {
case MatroskaIds::BitDepth:
m_bitsPerSample = static_cast<std::uint16_t>(subElement->readUInteger());
break;
case MatroskaIds::Channels:
m_channelCount = static_cast<std::uint16_t>(subElement->readUInteger());
break;
case MatroskaIds::SamplingFrequency:
if (!m_samplingFrequency) {
m_samplingFrequency = static_cast<std::uint32_t>(subElement->readFloat());
}
break;
case MatroskaIds::OutputSamplingFrequency:
if (!m_extensionSamplingFrequency) {
m_extensionSamplingFrequency = static_cast<std::uint32_t>(subElement->readFloat());
}
break;
default:;
}
}
break;
case MatroskaIds::TrackNumber:
m_trackNumber = static_cast<std::uint32_t>(trackInfoElement->readUInteger());
break;
case MatroskaIds::TrackUID:
m_id = trackInfoElement->readUInteger();
break;
case MatroskaIds::TrackName:
m_name = trackInfoElement->readString();
break;
case MatroskaIds::TrackLanguage:
m_locale.emplace_back(trackInfoElement->readString(), LocaleFormat::ISO_639_2_B);
hasIsoLanguage = true;
break;
case MatroskaIds::TrackLanguageIETF:
m_locale.emplace_back(trackInfoElement->readString(), LocaleFormat::BCP_47);
break;
case MatroskaIds::CodecID:
m_format = codecIdToMediaFormat(m_formatId = trackInfoElement->readString());
break;
case MatroskaIds::CodecName:
m_formatName = trackInfoElement->readString();
break;
case MatroskaIds::CodecDelay:
break; // TODO
case MatroskaIds::TrackFlagEnabled:
modFlagEnum(m_flags, TrackFlags::Enabled, trackInfoElement->readUInteger());
break;
case MatroskaIds::TrackFlagDefault:
modFlagEnum(m_flags, TrackFlags::Default, trackInfoElement->readUInteger());
break;
case MatroskaIds::TrackFlagForced:
modFlagEnum(m_flags, TrackFlags::Forced, trackInfoElement->readUInteger());
break;
case MatroskaIds::TrackFlagLacing:
modFlagEnum(m_flags, TrackFlags::Lacing, trackInfoElement->readUInteger());
break;
case MatroskaIds::DefaultDuration:
defaultDuration = trackInfoElement->readUInteger();
break;
default:;
}
switch (m_mediaType) {
case MediaType::Video:
if (!m_fps && defaultDuration) {
m_fps = static_cast<std::uint32_t>(1000000000.0 / static_cast<double>(defaultDuration));
}
break;
default:;
}
}
// read further information from the CodecPrivate element for some codecs
EbmlElement *codecPrivateElement;
switch (m_format.general) {
case GeneralMediaFormat::MicrosoftVideoCodecManager:
if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
// parse bitmap info header to determine actual format
if (codecPrivateElement->dataSize() >= 0x28) {
m_istream->seekg(static_cast<streamoff>(codecPrivateElement->dataOffset()));
BitmapInfoHeader bitmapInfoHeader;
bitmapInfoHeader.parse(reader());
m_formatId.reserve(m_formatId.size() + 7);
m_formatId += " \"";
m_formatId += interpretIntegerAsString(bitmapInfoHeader.compression);
m_formatId += "\"";
m_format += FourccIds::fourccToMediaFormat(bitmapInfoHeader.compression);
} else {
diag.emplace_back(DiagLevel::Critical, "BITMAPINFOHEADER structure (in \"CodecPrivate\"-element) is truncated.", context);
}
}
break;
case GeneralMediaFormat::MicrosoftAudioCodecManager:
if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
// parse WAVE header to determine actual format
m_istream->seekg(static_cast<streamoff>(codecPrivateElement->dataOffset()));
WaveFormatHeader waveFormatHeader;
waveFormatHeader.parse(reader(), codecPrivateElement->dataSize(), diag);
WaveAudioStream::addInfo(waveFormatHeader, *this);
}
break;
case GeneralMediaFormat::Aac:
if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
auto audioSpecificConfig
= Mp4Track::parseAudioSpecificConfig(*m_istream, codecPrivateElement->dataOffset(), codecPrivateElement->dataSize(), diag);
m_format += Mpeg4AudioObjectIds::idToMediaFormat(
audioSpecificConfig->audioObjectType, audioSpecificConfig->sbrPresent, audioSpecificConfig->psPresent);
if (audioSpecificConfig->sampleFrequencyIndex == 0xF) {
//m_samplingFrequency = audioSpecificConfig->sampleFrequency;
} else if (audioSpecificConfig->sampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
//m_samplingFrequency = mpeg4SamplingFrequencyTable[audioSpecificConfig->sampleFrequencyIndex];
} else {
diag.emplace_back(DiagLevel::Warning, "Audio specific config has invalid sample frequency index.", context);
}
if (audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
//m_extensionSamplingFrequency = audioSpecificConfig->extensionSampleFrequency;
} else if (audioSpecificConfig->extensionSampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
//m_extensionSamplingFrequency = mpeg4SamplingFrequencyTable[audioSpecificConfig->extensionSampleFrequencyIndex];
} else {
diag.emplace_back(DiagLevel::Warning, "Audio specific config has invalid extension sample frequency index.", context);
}
m_channelConfig = audioSpecificConfig->channelConfiguration;
m_extensionChannelConfig = audioSpecificConfig->extensionChannelConfiguration;
}
break;
case GeneralMediaFormat::Avc:
if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
auto avcConfig = make_unique<TagParser::AvcConfiguration>();
try {
m_istream->seekg(static_cast<streamoff>(codecPrivateElement->dataOffset()));
avcConfig->parse(m_reader, codecPrivateElement->dataSize(), diag);
Mp4Track::addInfo(*avcConfig, *this);
} catch (const TruncatedDataException &) {
diag.emplace_back(DiagLevel::Critical, "AVC configuration is truncated.", context);
} catch (const Failure &) {
diag.emplace_back(DiagLevel::Critical, "AVC configuration is invalid.", context);
}
}
break;
default:;
}
// parse format name for unknown formats
if (m_format.general == GeneralMediaFormat::Unknown && m_formatName.empty()) {
if (startsWith<string>(m_formatId, "V_") || startsWith<string>(m_formatId, "A_") || startsWith<string>(m_formatId, "S_")) {
m_formatName = m_formatId.substr(2);
} else {
m_formatName = m_formatId;
}
m_formatName.append(" (unknown)");
}
// use pixel size as display size if display size not specified
if (!m_displaySize.width()) {
m_displaySize.setWidth(m_pixelSize.width());
}
if (!m_displaySize.height()) {
m_displaySize.setHeight(m_pixelSize.height());
}
// set English if no ISO language has been specified (it is the default value of MatroskaIds::TrackLanguage)
if (!hasIsoLanguage) {
m_locale.emplace_back("eng"sv, LocaleFormat::ISO_639_2_B);
}
}
/*!
* \class TagParser::MatroskaTrackHeaderMaker
* \brief The MatroskaTrackHeaderMaker class helps writing Matroska "TrackEntry"-elements storing track header information.
*
* An instance can be obtained using the MatroskaTrack::prepareMakingHeader() method.
*/
/*!
* \brief Prepares making the header for the specified \a track.
* \sa See MatroskaTrack::prepareMakingHeader() for more information.
*/
MatroskaTrackHeaderMaker::MatroskaTrackHeaderMaker(const MatroskaTrack &track, Diagnostics &diag)
: m_track(track)
, m_language(m_track.locale().abbreviatedName(LocaleFormat::ISO_639_2_B, LocaleFormat::Unknown))
, m_languageIETF(m_track.locale().abbreviatedName(LocaleFormat::BCP_47))
, m_dataSize(0)
{
CPP_UTILITIES_UNUSED(diag);
// calculate size for recognized elements
m_dataSize += 2u + 1u + EbmlElement::calculateUIntegerLength(m_track.id());
m_dataSize += 1u + 1u + EbmlElement::calculateUIntegerLength(m_track.trackNumber());
m_dataSize += 1u + 1u + EbmlElement::calculateUIntegerLength(m_track.isEnabled());
m_dataSize += 1u + 1u + EbmlElement::calculateUIntegerLength(m_track.isDefault());
m_dataSize += 2u + 1u + EbmlElement::calculateUIntegerLength(m_track.isForced());
if (!m_track.name().empty()) {
m_dataSize += 2u + EbmlElement::calculateSizeDenotationLength(m_track.name().size()) + m_track.name().size();
}
// compute size of the mandatory "Language" element (if there's no language set, the 3 byte long value "und" is used)
const auto languageSize = m_language.empty() ? 3 : m_language.size();
const auto languageElementSize = 3 + EbmlElement::calculateSizeDenotationLength(languageSize) + languageSize;
// compute size of the optional "LanguageIETF" element
const auto languageIETFElementSize
= m_languageIETF.empty() ? 0 : (3 + EbmlElement::calculateSizeDenotationLength(m_languageIETF.size()) + m_languageIETF.size());
m_dataSize += languageElementSize + languageIETFElementSize;
// calculate size for other elements
for (EbmlElement *trackInfoElement = m_track.m_trackElement->firstChild(); trackInfoElement; trackInfoElement = trackInfoElement->nextSibling()) {
switch (trackInfoElement->id()) {
case MatroskaIds::TrackNumber:
case MatroskaIds::TrackUID:
case MatroskaIds::TrackName:
case MatroskaIds::TrackLanguage:
case MatroskaIds::TrackLanguageIETF:
case MatroskaIds::TrackFlagEnabled:
case MatroskaIds::TrackFlagDefault:
case MatroskaIds::TrackFlagForced:
// skip recognized elements
break;
default:
trackInfoElement->makeBuffer();
m_dataSize += trackInfoElement->totalSize();
}
}
m_sizeDenotationLength = EbmlElement::calculateSizeDenotationLength(m_dataSize);
m_requiredSize = 1u + m_sizeDenotationLength + m_dataSize;
}
/*!
* \brief Saves the header for the track (specified when constructing the object) to the
* specified \a stream (makes a "TrackEntry"-element).
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws Assumes the data is already validated and thus does NOT
* throw TagParser::Failure or a derived exception.
*/
void MatroskaTrackHeaderMaker::make(ostream &stream) const
{
// make ID and size
char buffer[9];
*buffer = static_cast<char>(MatroskaIds::TrackEntry);
EbmlElement::makeSizeDenotation(m_dataSize, buffer + 1);
stream.write(buffer, 1 + m_sizeDenotationLength);
// make recognized elements
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackUID, m_track.id());
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackNumber, m_track.trackNumber());
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackFlagEnabled, m_track.isEnabled());
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackFlagDefault, m_track.isDefault());
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackFlagForced, m_track.isForced());
if (!m_track.name().empty()) {
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackName, m_track.name());
}
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackLanguage, m_language.empty() ? "und" : m_language);
if (!m_languageIETF.empty()) {
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackLanguageIETF, m_languageIETF);
}
// make other elements
for (EbmlElement *trackInfoElement = m_track.m_trackElement->firstChild(); trackInfoElement; trackInfoElement = trackInfoElement->nextSibling()) {
switch (trackInfoElement->id()) {
case MatroskaIds::TrackNumber:
case MatroskaIds::TrackUID:
case MatroskaIds::TrackName:
case MatroskaIds::TrackLanguage:
case MatroskaIds::TrackLanguageIETF:
case MatroskaIds::TrackFlagEnabled:
case MatroskaIds::TrackFlagDefault:
case MatroskaIds::TrackFlagForced:
// skip recognized elements
break;
default:
trackInfoElement->copyBuffer(stream);
}
}
}
} // namespace TagParser