#include "./matroskatag.h" #include "./ebmlelement.h" #include "../diagnostics.h" #include #include #include using namespace std; using namespace CppUtilities; namespace TagParser { /*! * \class TagParser::MatroskaTag * \brief Implementation of TagParser::Tag for the Matroska container. */ MatroskaTag::IdentifierType MatroskaTag::internallyGetFieldId(KnownField field) const { using namespace MatroskaTagIds; switch (field) { case KnownField::Artist: return std::string(artist()); case KnownField::Album: return std::string(album()); case KnownField::Comment: return std::string(comment()); case KnownField::RecordDate: return std::string(dateRecorded()); case KnownField::ReleaseDate: return std::string(dateRelease()); case KnownField::Title: return std::string(title()); case KnownField::Genre: return std::string(genre()); case KnownField::PartNumber: return std::string(partNumber()); case KnownField::TotalParts: return std::string(totalParts()); case KnownField::Encoder: return std::string(encoder()); case KnownField::EncoderSettings: return std::string(encoderSettings()); case KnownField::Bpm: return std::string(bpm()); case KnownField::Bps: return std::string(bps()); case KnownField::Rating: return std::string(rating()); case KnownField::Description: return std::string(description()); case KnownField::Lyrics: return std::string(lyrics()); case KnownField::RecordLabel: return std::string(label()); case KnownField::Performers: return std::string(actor()); case KnownField::Lyricist: return std::string(lyricist()); case KnownField::Composer: return std::string(composer()); case KnownField::Length: return std::string(duration()); case KnownField::Language: return std::string(language()); case KnownField::AlbumArtist: return std::string(accompaniment()); case KnownField::Subtitle: return std::string(subtitle()); case KnownField::LeadPerformer: return std::string(leadPerformer()); case KnownField::Arranger: return std::string(arranger()); case KnownField::Conductor: return std::string(conductor()); case KnownField::Director: return std::string(director()); case KnownField::AssistantDirector: return std::string(assistantDirector()); case KnownField::DirectorOfPhotography: return std::string(directorOfPhotography()); case KnownField::SoundEngineer: return std::string(soundEngineer()); case KnownField::ArtDirector: return std::string(artDirector()); case KnownField::ProductionDesigner: return std::string(productionDesigner()); case KnownField::Choregrapher: return std::string(choregrapher()); case KnownField::CostumeDesigner: return std::string(costumeDesigner()); case KnownField::Actor: return std::string(actor()); case KnownField::Character: return std::string(character()); case KnownField::WrittenBy: return std::string(writtenBy()); case KnownField::ScreenplayBy: return std::string(screenplayBy()); case KnownField::EditedBy: return std::string(editedBy()); case KnownField::Producer: return std::string(producer()); case KnownField::Coproducer: return std::string(coproducer()); case KnownField::ExecutiveProducer: return std::string(executiveProducer()); case KnownField::DistributedBy: return std::string(distributedBy()); case KnownField::MasteredBy: return std::string(masteredBy()); case KnownField::EncodedBy: return std::string(encodedBy()); case KnownField::MixedBy: return std::string(mixedBy()); case KnownField::RemixedBy: return std::string(remixedBy()); case KnownField::ProductionStudio: return std::string(productionStudio()); case KnownField::ThanksTo: return std::string(thanksTo()); case KnownField::Publisher: return std::string(publisher()); case KnownField::Mood: return std::string(mood()); case KnownField::OriginalMediaType: return std::string(originalMediaType()); case KnownField::ContentType: return std::string(contentType()); case KnownField::Subject: return std::string(subject()); case KnownField::Keywords: return std::string(keywords()); case KnownField::Summary: return std::string(summary()); case KnownField::Synopsis: return std::string(synopsis()); case KnownField::InitialKey: return std::string(initialKey()); case KnownField::Period: return std::string(period()); case KnownField::LawRating: return std::string(lawRating()); case KnownField::EncodingDate: return std::string(dateEncoded()); case KnownField::TaggingDate: return std::string(dateTagged()); case KnownField::DigitalizationDate: return std::string(dateDigitized()); case KnownField::WritingDate: return std::string(dateWritten()); case KnownField::PurchasingDate: return std::string(datePurchased()); case KnownField::RecordingLocation: return std::string(recordingLocation()); case KnownField::CompositionLocation: return std::string(compositionLocation()); case KnownField::ComposerNationality: return std::string(composerNationality()); case KnownField::PlayCounter: return std::string(playCounter()); case KnownField::Measure: return std::string(measure()); case KnownField::Tuning: return std::string(tuning()); case KnownField::ISRC: return std::string(isrc()); case KnownField::MCDI: return std::string(mcdi()); case KnownField::ISBN: return std::string(isbn()); case KnownField::Barcode: return std::string(barcode()); case KnownField::CatalogNumber: return std::string(catalogNumber()); case KnownField::LabelCode: return std::string(labelCode()); case KnownField::LCCN: return std::string(lccn()); case KnownField::IMDB: return std::string(imdb()); case KnownField::TMDB: return std::string(tmdb()); case KnownField::TVDB: return std::string(tvdb()); case KnownField::PurchaseItem: return std::string(purchaseItem()); case KnownField::PurchaseInfo: return std::string(purchaseInfo()); case KnownField::PurchaseOwner: return std::string(purchaseOwner()); case KnownField::PurchasePrice: return std::string(purchasePrice()); case KnownField::PurchaseCurrency: return std::string(purchaseCurrency()); case KnownField::Copyright: return std::string(copyright()); case KnownField::ProductionCopyright: return std::string(productionCopyright()); case KnownField::License: return std::string(license()); case KnownField::TermsOfUse: return std::string(termsOfUse()); default: return std::string(); } } KnownField MatroskaTag::internallyGetKnownField(const IdentifierType &id) const { using namespace MatroskaTagIds; static const std::unordered_map fieldMap({ { artist(), KnownField::Artist }, { album(), KnownField::Album }, { comment(), KnownField::Comment }, { dateRecorded(), KnownField::RecordDate }, { dateRelease(), KnownField::ReleaseDate }, { title(), KnownField::Title }, { partNumber(), KnownField::PartNumber }, { totalParts(), KnownField::TotalParts }, { encoder(), KnownField::Encoder }, { encoderSettings(), KnownField::EncoderSettings }, { bpm(), KnownField::Bpm }, { bps(), KnownField::Bps }, { rating(), KnownField::Rating }, { description(), KnownField::Description }, { lyrics(), KnownField::Lyrics }, { label(), KnownField::RecordLabel }, { actor(), KnownField::Performers }, { lyricist(), KnownField::Lyricist }, { composer(), KnownField::Composer }, { duration(), KnownField::Length }, { language(), KnownField::Language }, { accompaniment(), KnownField::AlbumArtist }, { subtitle(), KnownField::Subtitle }, { leadPerformer(), KnownField::LeadPerformer }, { arranger(), KnownField::Arranger }, { conductor(), KnownField::Conductor }, { director(), KnownField::Director }, { assistantDirector(), KnownField::AssistantDirector }, { directorOfPhotography(), KnownField::DirectorOfPhotography }, { soundEngineer(), KnownField::SoundEngineer }, { artDirector(), KnownField::ArtDirector }, { productionDesigner(), KnownField::ProductionDesigner }, { choregrapher(), KnownField::Choregrapher }, { costumeDesigner(), KnownField::CostumeDesigner }, { actor(), KnownField::Actor }, { character(), KnownField::Character }, { writtenBy(), KnownField::WrittenBy }, { screenplayBy(), KnownField::ScreenplayBy }, { editedBy(), KnownField::EditedBy }, { producer(), KnownField::Producer }, { coproducer(), KnownField::Coproducer }, { executiveProducer(), KnownField::ExecutiveProducer }, { distributedBy(), KnownField::DistributedBy }, { masteredBy(), KnownField::MasteredBy }, { encodedBy(), KnownField::EncodedBy }, { mixedBy(), KnownField::MixedBy }, { remixedBy(), KnownField::RemixedBy }, { productionStudio(), KnownField::ProductionStudio }, { thanksTo(), KnownField::ThanksTo }, { publisher(), KnownField::Publisher }, { mood(), KnownField::Mood }, { originalMediaType(), KnownField::OriginalMediaType }, { contentType(), KnownField::ContentType }, { subject(), KnownField::Subject }, { keywords(), KnownField::Keywords }, { summary(), KnownField::Summary }, { synopsis(), KnownField::Synopsis }, { initialKey(), KnownField::InitialKey }, { period(), KnownField::Period }, { lawRating(), KnownField::LawRating }, { dateEncoded(), KnownField::EncodingDate }, { dateTagged(), KnownField::TaggingDate }, { dateDigitized(), KnownField::DigitalizationDate }, { dateWritten(), KnownField::WritingDate }, { datePurchased(), KnownField::PurchasingDate }, { recordingLocation(), KnownField::RecordingLocation }, { compositionLocation(), KnownField::CompositionLocation }, { composerNationality(), KnownField::ComposerNationality }, { playCounter(), KnownField::PlayCounter }, { measure(), KnownField::Measure }, { tuning(), KnownField::Tuning }, { isrc(), KnownField::ISRC }, { mcdi(), KnownField::MCDI }, { isbn(), KnownField::ISBN }, { barcode(), KnownField::Barcode }, { catalogNumber(), KnownField::CatalogNumber }, { labelCode(), KnownField::LabelCode }, { lccn(), KnownField::LCCN }, { imdb(), KnownField::IMDB }, { tmdb(), KnownField::TMDB }, { tvdb(), KnownField::TVDB }, { purchaseItem(), KnownField::PurchaseItem }, { purchaseInfo(), KnownField::PurchaseInfo }, { purchaseOwner(), KnownField::PurchaseOwner }, { purchasePrice(), KnownField::PurchasePrice }, { purchaseCurrency(), KnownField::PurchaseCurrency }, { copyright(), KnownField::Copyright }, { productionCopyright(), KnownField::ProductionCopyright }, { license(), KnownField::License }, { termsOfUse(), KnownField::TermsOfUse }, }); const auto knownField(fieldMap.find(id)); return knownField != fieldMap.cend() ? knownField->second : KnownField::Invalid; } /*! * \brief Parses tag information from the specified \a tagElement. * * \throws Throws std::ios_base::failure when an IO error occurs. * \throws Throws TagParser::Failure or a derived exception when a parsing * error occurs. */ void MatroskaTag::parse(EbmlElement &tagElement, Diagnostics &diag) { parse2(tagElement, MatroskaTagFlags::None, diag); } /*! * \brief Parses tag information from the specified \a tagElement. * * \throws Throws std::ios_base::failure when an IO error occurs. * \throws Throws TagParser::Failure or a derived exception when a parsing * error occurs. */ void MatroskaTag::parse2(EbmlElement &tagElement, MatroskaTagFlags flags, Diagnostics &diag) { static const string context("parsing Matroska tag"); m_size = tagElement.totalSize(); tagElement.parse(diag); if (tagElement.totalSize() > numeric_limits::max()) { // FIXME: Support this? Likely not very useful in practise. diag.emplace_back(DiagLevel::Critical, "Matroska tag is too big.", context); throw NotImplementedException(); } const auto normalize = flags & MatroskaTagFlags::NormalizeKnownFieldIds; for (EbmlElement *child = tagElement.firstChild(); child; child = child->nextSibling()) { child->parse(diag); switch (child->id()) { case MatroskaIds::SimpleTag: try { auto field = MatroskaTagField(); field.reparse(*child, diag, true); if (normalize) { auto normalizedId = field.id(); MatroskaTagField::normalizeId(normalizedId); if (internallyGetKnownField(normalizedId) != KnownField::Invalid) { field.id() = std::move(normalizedId); } } fields().emplace(field.id(), std::move(field)); } catch (const Failure &) { // message will be added to diag anyways } break; case MatroskaIds::Targets: parseTargets(*child, diag); break; } } } /*! * \brief Parses the specified \a targetsElement. * * \throws Throws std::ios_base::failure when an IO error occurs. * \throws Throws TagParser::Failure or a derived exception when a parsing * error occurs. */ void MatroskaTag::parseTargets(EbmlElement &targetsElement, Diagnostics &diag) { static const string context("parsing targets of Matroska tag"); m_target.clear(); bool targetTypeValueFound = false; bool targetTypeFound = false; targetsElement.parse(diag); for (EbmlElement *child = targetsElement.firstChild(); child; child = child->nextSibling()) { try { child->parse(diag); } catch (const Failure &) { diag.emplace_back(DiagLevel::Critical, "Unable to parse children of Targets element.", context); break; } switch (child->id()) { case MatroskaIds::TargetTypeValue: if (!targetTypeValueFound) { m_target.setLevel(child->readUInteger()); targetTypeValueFound = true; } else { diag.emplace_back( DiagLevel::Warning, "Targets element contains multiple TargetTypeValue elements. Surplus elements will be ignored.", context); } break; case MatroskaIds::TargetType: if (!targetTypeFound) { m_target.setLevelName(child->readString()); targetTypeFound = true; } else { diag.emplace_back( DiagLevel::Warning, "Targets element contains multiple TargetType elements. Surplus elements will be ignored.", context); } break; case MatroskaIds::TagTrackUID: m_target.tracks().emplace_back(child->readUInteger()); break; case MatroskaIds::TagEditionUID: m_target.editions().emplace_back(child->readUInteger()); break; case MatroskaIds::TagChapterUID: m_target.chapters().emplace_back(child->readUInteger()); break; case MatroskaIds::TagAttachmentUID: m_target.attachments().emplace_back(child->readUInteger()); break; default: diag.emplace_back(DiagLevel::Warning, "Targets element contains unknown element. It will be ignored.", context); } } if (!m_target.level()) { m_target.setLevel(50); // default level } } /*! * \class TagParser::MatroskaTagMaker * \brief The MatroskaTagMaker class helps writing Matroska "Tag"-elements storing tag information. * * An instance can be obtained using the MatroskaTag::prepareMaking() method. */ /*! * \brief Prepares making the specified \a tag. * \sa See MatroskaTag::prepareMaking() for more information. */ MatroskaTagMaker::MatroskaTagMaker(MatroskaTag &tag, Diagnostics &diag) : m_tag(tag) { // calculate size of "Targets" element m_targetsSize = 0; // NOT including ID and size if (m_tag.target().level() != 50) { // size of "TargetTypeValue" m_targetsSize += 2u + 1u + EbmlElement::calculateUIntegerLength(m_tag.target().level()); } if (!m_tag.target().levelName().empty()) { // size of "TargetType" m_targetsSize += 2u + EbmlElement::calculateSizeDenotationLength(m_tag.target().levelName().size()) + m_tag.target().levelName().size(); } for (const auto &v : initializer_list>{ m_tag.target().tracks(), m_tag.target().editions(), m_tag.target().chapters(), m_tag.target().attachments() }) { for (auto uid : v) { // size of UID denotation m_targetsSize += 2u + 1u + EbmlElement::calculateUIntegerLength(uid); } } m_tagSize = 2u + EbmlElement::calculateSizeDenotationLength(m_targetsSize) + m_targetsSize; // calculate size of "SimpleTag" elements m_maker.reserve(m_tag.fields().size()); m_simpleTagsSize = 0; // including ID and size for (auto &pair : m_tag.fields()) { if (pair.second.value().isNull()) { continue; } try { m_maker.emplace_back(pair.second.prepareMaking(diag)); m_simpleTagsSize += m_maker.back().requiredSize(); } catch (const Failure &) { } } m_tagSize += m_simpleTagsSize; m_totalSize = 2u + EbmlElement::calculateSizeDenotationLength(m_tagSize) + m_tagSize; } /*! * \brief Saves the tag (specified when constructing the object) to the * specified \a stream (makes a "Tag"-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 MatroskaTagMaker::make(ostream &stream) const { // write header char buff[11]; BE::getBytes(static_cast(MatroskaIds::Tag), buff); stream.write(buff, 2); // ID std::uint8_t len = EbmlElement::makeSizeDenotation(m_tagSize, buff); stream.write(buff, len); // size // write "Targets" element BE::getBytes(static_cast(MatroskaIds::Targets), buff); stream.write(buff, 2); len = EbmlElement::makeSizeDenotation(m_targetsSize, buff); stream.write(buff, len); const TagTarget &t = m_tag.target(); if (t.level() != 50) { // write "TargetTypeValue" BE::getBytes(static_cast(MatroskaIds::TargetTypeValue), buff); stream.write(buff, 2); len = EbmlElement::makeUInteger(t.level(), buff); stream.put(static_cast(0x80 | len)); stream.write(buff, len); } if (!t.levelName().empty()) { // write "TargetType" BE::getBytes(static_cast(MatroskaIds::TargetType), buff); stream.write(buff, 2); len = EbmlElement::makeSizeDenotation(t.levelName().size(), buff); stream.write(buff, len); stream.write(t.levelName().c_str(), static_cast(t.levelName().size())); } // write UIDs using p = pair>; for (const auto &pair : initializer_list

{ p(MatroskaIds::TagTrackUID, t.tracks()), p(MatroskaIds::TagEditionUID, t.editions()), p(MatroskaIds::TagChapterUID, t.chapters()), p(MatroskaIds::TagAttachmentUID, t.attachments()) }) { if (!pair.second.empty()) { BE::getBytes(pair.first, buff); for (auto uid : pair.second) { len = EbmlElement::makeUInteger(uid, buff + 3); *(buff + 2) = static_cast(0x80 | len); stream.write(buff, 3 + len); } } } // write "SimpleTag" elements using maker objects prepared previously for (const auto &maker : m_maker) { maker.make(stream); } } } // namespace TagParser