#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 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>(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 void MatroskaTrack::assignPropertyFromTagValue(const std::unique_ptr &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 > * = 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(tagValue.dataPointer(), tagValue.dataSize()); default:; } } // generic conversion return stringToNumber(tagValue.toString(TagTextEncoding::Utf8)); } template > * = nullptr> 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, 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, diag); assignPropertyFromTagValue(tag, numberOfFrames(), m_sampleCount, &tagValueToNumber, diag); assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::duration(), m_duration, bind(&TagValue::toTimeSpan, _1), diag); assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::bitrate(), m_bitrate, &tagValueToBitrate, 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 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::uint32_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(subElement->readUInteger()); break; case MatroskaIds::DisplayHeight: m_displaySize.setHeight(subElement->readUInteger()); break; case MatroskaIds::PixelWidth: m_pixelSize.setWidth(subElement->readUInteger()); break; case MatroskaIds::PixelHeight: m_pixelSize.setHeight(subElement->readUInteger()); break; case MatroskaIds::PixelCropTop: m_cropping.setTop(subElement->readUInteger()); break; case MatroskaIds::PixelCropLeft: m_cropping.setLeft(subElement->readUInteger()); break; case MatroskaIds::PixelCropBottom: m_cropping.setBottom(subElement->readUInteger()); break; case MatroskaIds::PixelCropRight: m_cropping.setRight(subElement->readUInteger()); break; case MatroskaIds::FrameRate: m_fps = subElement->readFloat(); break; case MatroskaIds::FlagInterlaced: modFlagEnum(m_flags, TrackFlags::Interlaced, subElement->readUInteger()); break; case MatroskaIds::ColorSpace: m_colorSpace = 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 = subElement->readUInteger(); break; case MatroskaIds::Channels: m_channelCount = subElement->readUInteger(); break; case MatroskaIds::SamplingFrequency: if (!m_samplingFrequency) { m_samplingFrequency = subElement->readFloat(); } break; case MatroskaIds::OutputSamplingFrequency: if (!m_extensionSamplingFrequency) { m_extensionSamplingFrequency = subElement->readFloat(); } break; default:; } } break; case MatroskaIds::TrackNumber: m_trackNumber = 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); 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 = 1000000000.0 / 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(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(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(); try { m_istream->seekg(static_cast(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(m_formatId, "V_") || startsWith(m_formatId, "A_") || startsWith(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 language has been specified (it is default value of MatroskaIds::TrackLanguage) if (m_locale.empty()) { 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 += 2 + 1 + EbmlElement::calculateUIntegerLength(m_track.id()); m_dataSize += 1 + 1 + EbmlElement::calculateUIntegerLength(m_track.trackNumber()); m_dataSize += 1 + 1 + EbmlElement::calculateUIntegerLength(m_track.isEnabled()); m_dataSize += 1 + 1 + EbmlElement::calculateUIntegerLength(m_track.isDefault()); m_dataSize += 2 + 1 + EbmlElement::calculateUIntegerLength(m_track.isForced()); if (!m_track.name().empty()) { m_dataSize += 2 + 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 = 1 + 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(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::TrackFlagEnabled: case MatroskaIds::TrackFlagDefault: case MatroskaIds::TrackFlagForced: // skip recognized elements break; default: trackInfoElement->copyBuffer(stream); } } } } // namespace TagParser