diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a23941..c1ad691 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,7 @@ set(HEADER_FILES id3/id3v2tag.h ivf/ivfframe.h ivf/ivfstream.h - language.h + localehelper.h localeawarestring.h margin.h matroska/ebmlelement.h @@ -122,7 +122,7 @@ set(SRC_FILES id3/id3v2tag.cpp ivf/ivfframe.cpp ivf/ivfstream.cpp - language.cpp + localehelper.cpp localeawarestring.cpp matroska/ebmlelement.cpp matroska/matroskaattachment.cpp diff --git a/abstracttrack.cpp b/abstracttrack.cpp index 23aaf8f..37d3999 100644 --- a/abstracttrack.cpp +++ b/abstracttrack.cpp @@ -1,6 +1,5 @@ #include "./abstracttrack.h" #include "./exceptions.h" -#include "./language.h" #include "./mediaformat.h" #include "./mp4/mp4ids.h" @@ -130,8 +129,8 @@ string AbstractTrack::label() const if (!name().empty()) { ss << ", name: \"" << name() << "\""; } - if (isLanguageDefined(language())) { - ss << ", language: " << languageNameFromIsoWithFallback(language()) << ""; + if (const auto &language = locale().fullOrSomeAbbreviatedName(); !language.empty()) { + ss << ", language: " << language << ""; } return ss.str(); } @@ -173,13 +172,13 @@ string AbstractTrack::makeDescription(bool verbose) const case MediaType::Audio: case MediaType::Text: if (channelCount()) { - if (!language().empty() && language() != "und") { - return argsToString(formatName, '-', channelCount(), "ch-", language()); + if (const auto &localeName = locale().someAbbreviatedName(); !localeName.empty()) { + return argsToString(formatName, '-', channelCount(), "ch-", localeName); } else { return argsToString(formatName, '-', channelCount(), 'c', 'h'); } - } else if (!language().empty() && language() != "und") { - additionalInfoRef = language().data(); + } else if (const auto &localeName = locale().someAbbreviatedName(); !localeName.empty()) { + additionalInfoRef = localeName.data(); } break; default:; diff --git a/abstracttrack.h b/abstracttrack.h index b461d14..13fe524 100644 --- a/abstracttrack.h +++ b/abstracttrack.h @@ -3,6 +3,7 @@ #include "./aspectratio.h" #include "./diagnostics.h" +#include "./localehelper.h" #include "./margin.h" #include "./mediaformat.h" #include "./size.h" @@ -95,8 +96,8 @@ public: double maxBitrate() const; const CppUtilities::DateTime &creationTime() const; const CppUtilities::DateTime &modificationTime() const; - const std::string &language() const; - void setLanguage(const std::string &language); + const Locale &locale() const; + void setLocale(const Locale &locale); std::uint32_t samplingFrequency() const; std::uint32_t extensionSamplingFrequency() const; std::uint16_t bitsPerSample() const; @@ -160,7 +161,7 @@ protected: double m_maxBitrate; CppUtilities::DateTime m_creationTime; CppUtilities::DateTime m_modificationTime; - std::string m_language; + Locale m_locale; std::uint32_t m_samplingFrequency; std::uint32_t m_extensionSamplingFrequency; std::uint16_t m_bitsPerSample; @@ -435,22 +436,22 @@ inline const CppUtilities::DateTime &AbstractTrack::modificationTime() const } /*! - * \brief Returns the language of the track if known; otherwise returns an empty string. + * \brief Returns the locale of the track if known; otherwise returns an empty locale. * - * The format of the language denotation depends on the particular implementation. + * The format of the locale depends on the particular format/implementation. */ -inline const std::string &AbstractTrack::language() const +inline const Locale &AbstractTrack::locale() const { - return m_language; + return m_locale; } /*! - * \brief Sets the language of the track. - * \remarks Whether the new value is applied when saving changes depends on the implementation. + * \brief Sets the locale of the track. + * \remarks Whether the new value is applied when saving changes depends on the format/implementation. */ -inline void AbstractTrack::setLanguage(const std::string &language) +inline void AbstractTrack::setLocale(const Locale &locale) { - m_language = language; + m_locale = locale; } /*! @@ -621,11 +622,11 @@ inline std::uint32_t AbstractTrack::timeScale() const } /*! - * \brief Returns true if the track is denoted as enabled; otherwise returns false. + * \brief Returns true if the track is marked as enabled; otherwise returns false. */ inline bool AbstractTrack::isEnabled() const { - return m_enabled; + return m_flags & TrackFlags::Enabled; } /*! @@ -634,15 +635,15 @@ inline bool AbstractTrack::isEnabled() const */ inline void AbstractTrack::setEnabled(bool enabled) { - m_enabled = enabled; + CppUtilities::modFlagEnum(m_flags, TrackFlags::Enabled, enabled); } /*! - * \brief Returns true if the track is denoted as default; otherwise returns false. + * \brief Returns true if the track is marked as default; otherwise returns false. */ inline bool AbstractTrack::isDefault() const { - return m_default; + return m_flags & TrackFlags::Default; } /*! @@ -651,15 +652,15 @@ inline bool AbstractTrack::isDefault() const */ inline void AbstractTrack::setDefault(bool isDefault) { - m_default = isDefault; + CppUtilities::modFlagEnum(m_flags, TrackFlags::Default, isDefault); } /*! - * \brief Returns true if the track is denoted as forced; otherwise returns false. + * \brief Returns true if the track is marked as forced; otherwise returns false. */ inline bool AbstractTrack::isForced() const { - return m_forced; + return m_flags & TrackFlags::Forced; } /*! @@ -668,7 +669,7 @@ inline bool AbstractTrack::isForced() const */ inline void AbstractTrack::setForced(bool forced) { - m_forced = forced; + CppUtilities::modFlagEnum(m_flags, TrackFlags::Forced, forced); } /*! @@ -676,15 +677,15 @@ inline void AbstractTrack::setForced(bool forced) */ inline bool AbstractTrack::hasLacing() const { - return m_lacing; + return m_flags & TrackFlags::Lacing; } /*! - * \brief Returns true if the track is denoted as encrypted; otherwise returns false. + * \brief Returns true if the track is marked as encrypted; otherwise returns false. */ inline bool AbstractTrack::isEncrypted() const { - return m_encrypted; + return m_flags & TrackFlags::Encrypted; } /*! @@ -708,7 +709,7 @@ inline const Margin &AbstractTrack::cropping() const */ inline bool AbstractTrack::isHeaderValid() const { - return m_headerValid; + return m_flags & TrackFlags::HeaderValid; } } // namespace TagParser diff --git a/id3/id3v2frame.cpp b/id3/id3v2frame.cpp index 9f85d5e..4ef5396 100644 --- a/id3/id3v2frame.cpp +++ b/id3/id3v2frame.cpp @@ -981,7 +981,7 @@ void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue } TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast(*buffer), diag); if (*(++buffer)) { - tagValue.setLanguage(string(buffer, 3)); + tagValue.setLocale(Locale(std::string(buffer, 3), LocaleFormat::ISO_639_2_B)); // does standard say whether T or B? } auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true, diag); tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding); @@ -1163,8 +1163,8 @@ void Id3v2Frame::makeComment(unique_ptr &buffer, std::uint32_t &bufferSi diag.emplace_back(DiagLevel::Critical, "Data encoding and description encoding aren't equal.", context); throw InvalidDataException(); } - const string &lng = comment.language(); - if (lng.length() > 3) { + const string &language = comment.locale().abbreviatedName(LocaleFormat::ISO_639_2_B, LocaleFormat::ISO_639_2_T, LocaleFormat::Unknown); + if (language.length() > 3) { diag.emplace_back(DiagLevel::Critical, "The language must be 3 bytes long (ISO-639-2).", context); throw InvalidDataException(); } @@ -1198,7 +1198,7 @@ void Id3v2Frame::makeComment(unique_ptr &buffer, std::uint32_t &bufferSi // write language for (unsigned int i = 0; i < 3; ++i) { - *(++offset) = (lng.length() > i) ? lng[i] : 0x00; + *(++offset) = (language.length() > i) ? language[i] : 0x00; } // write description diff --git a/language.cpp b/language.cpp deleted file mode 100644 index 24fea30..0000000 --- a/language.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "./language.h" - -#include - -namespace TagParser { - -/// \cond -static const auto &languageMapping() -{ -#include "resources/languages.h" - return languages; -} -/// \endcond - -/*! - * \brief Returns the language name for the specified ISO-639-2 code (bibliographic, 639-2/B). - * \remarks If \a isoCode is unknown an empty string is returned. - */ -const std::string &languageNameFromIso(const std::string &isoCode) -{ - const auto &mapping = languageMapping(); - const auto i = mapping.find(isoCode); - if (i == mapping.cend()) { - static const std::string empty; - return empty; - } - return i->second; -} - -/*! - * \brief Returns the language name for the specified ISO-639-2 code (bibliographic, 639-2/B). - * \remarks If \a isoCode is unknown the \a isoCode itself is returned. - */ -const std::string &languageNameFromIsoWithFallback(const std::string &isoCode) -{ - const auto &mapping = languageMapping(); - const auto i = mapping.find(isoCode); - if (i == mapping.cend()) { - return isoCode; - } - return i->second; -} - -} // namespace TagParser diff --git a/language.h b/language.h deleted file mode 100644 index 07569e2..0000000 --- a/language.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef TAG_PARSER_LANGUAGE_H -#define TAG_PARSER_LANGUAGE_H - -#include "./global.h" - -#include - -#include -#include - -namespace TagParser { - -/*! - * \brief Returns whether \a languageSpecification is not empty or undefined. - */ -inline bool isLanguageDefined(const std::string &languageSpecification) -{ - return !languageSpecification.empty() && languageSpecification != "und"; -} - -TAG_PARSER_EXPORT const std::string &languageNameFromIso(const std::string &isoCode); -TAG_PARSER_EXPORT const std::string &languageNameFromIsoWithFallback(const std::string &isoCode); - -} // namespace TagParser - -#endif // TAG_PARSER_LANGUAGE_H diff --git a/localeawarestring.h b/localeawarestring.h index 3f23f0d..09e151d 100644 --- a/localeawarestring.h +++ b/localeawarestring.h @@ -1,7 +1,7 @@ #ifndef TAG_PARSER_LOCALEAWARESTRING_H #define TAG_PARSER_LOCALEAWARESTRING_H -#include "./global.h" +#include "./localehelper.h" #include #include @@ -17,14 +17,11 @@ public: explicit LocaleAwareString(std::string &&value); ~LocaleAwareString(); - const std::vector &languages() const; - std::vector &languages(); - const std::vector &countries() const; - std::vector &countries(); + const Locale &locale() const; + Locale &locale(); private: - std::vector m_languages; - std::vector m_countries; + Locale m_locale; }; /*! @@ -51,35 +48,19 @@ inline LocaleAwareString::~LocaleAwareString() } /*! - * \brief Returns associated languages. + * \brief Returns the associated locale. */ -inline const std::vector &LocaleAwareString::languages() const +inline const Locale &LocaleAwareString::locale() const { - return m_languages; + return m_locale; } /*! - * \brief Returns associated languages. + * \brief Returns the associated locale. */ -inline std::vector &LocaleAwareString::languages() +inline Locale &LocaleAwareString::locale() { - return m_languages; -} - -/*! - * \brief Returns associated countries. - */ -inline const std::vector &LocaleAwareString::countries() const -{ - return m_countries; -} - -/*! - * \brief Returns associated countries. - */ -inline std::vector &LocaleAwareString::countries() -{ - return m_countries; + return m_locale; } } // namespace TagParser diff --git a/localehelper.cpp b/localehelper.cpp new file mode 100644 index 0000000..16cd04e --- /dev/null +++ b/localehelper.cpp @@ -0,0 +1,143 @@ +#include "./localehelper.h" + +#include + +#include + +using namespace std::literals; + +namespace TagParser { + +/// \cond +static const auto &languageMapping() +{ +#include "resources/languages.h" + return languages; +} +/// \endcond + +/*! + * \brief Returns the language name for the specified ISO-639-2 code (bibliographic, 639-2/B). + * \remarks If \a isoCode is unknown an empty string is returned. + */ +const std::string &languageNameFromIso(const std::string &isoCode) +{ + const auto &mapping = languageMapping(); + const auto i = mapping.find(isoCode); + if (i == mapping.cend()) { + static const std::string empty; + return empty; + } + return i->second; +} + +/*! + * \brief Returns the language name for the specified ISO-639-2 code (bibliographic, 639-2/B). + * \remarks If \a isoCode is unknown the \a isoCode itself is returned. + */ +const std::string &languageNameFromIsoWithFallback(const std::string &isoCode) +{ + const auto &mapping = languageMapping(); + const auto i = mapping.find(isoCode); + if (i == mapping.cend()) { + return isoCode; + } + return i->second; +} + +/*! + * \brief Returns an empty LocaleDetail. + */ +const LocaleDetail &LocaleDetail::getEmpty() +{ + static const auto empty = LocaleDetail(); + return empty; +} + +/*! + * \brief Returns the abbreviated name of the specified \a format. + * + * This function returns the \a format if present. Otherwise it returns an empty string. This might be + * improved in the future, e.g. to convert to the specified \a format. It would also be possible to combine + * multiple details. For instance, if BCP-47 is required but only an ISO language and a domain name country + * are present, both could be combined and returned as BCP-47 abbreviation. + */ +const LocaleDetail &Locale::abbreviatedName(LocaleFormat format) const +{ + for (const auto &detail : *this) { + if (!detail.empty() && detail.format == format && isLanguageDefined(detail)) { + return detail; + } + } + return LocaleDetail::getEmpty(); +} + +/*! + * \brief Returns *some* abbreviated name, *preferrably* of the specified \a preferredFormat. + * + * This function returns the \a preferredFormat if present. Otherwise it returns the most relevant + * detail. This might be improved in the future, e.g. to convert to the \a preferredFormat. It would + * also be possible to combine multiple details. For instance, if BCP-47 is preferred but only an ISO + * language and a domain name country are present, both could be combined and returned as BCP-47 abbreviation. + * + * Returns an empty string if no details are present. + * + * \remarks This function is intended to be used for display purposes when the exact format doesn't matter and + * you just want to show the "best" abbreviation specified within the file. + */ +const LocaleDetail &Locale::someAbbreviatedName(LocaleFormat preferredFormat) const +{ + auto format = LocaleFormat::Unknown; + const LocaleDetail *mostRelevantDetail = nullptr; + for (const auto &detail : *this) { + if (!detail.empty() + && static_cast>(detail.format) >= static_cast>(format)) { + if (detail.format == preferredFormat) { + return detail; + } + format = detail.format; + mostRelevantDetail = &detail; + } + } + if (!mostRelevantDetail || !isLanguageDefined(*mostRelevantDetail)) { + return LocaleDetail::getEmpty(); + } + return *mostRelevantDetail; +} + +/*! + * \brief Returns the full name of the locale, e.g. Germany for the ISO code "ger" or an empty string if the + * full name is not known. + * \remarks So far the full name is only known for ISO-639-2/B codes. + */ +const std::string &TagParser::Locale::fullName() const +{ + for (const auto &detail : *this) { + if (detail.format == LocaleFormat::ISO_639_2_B || detail.format == LocaleFormat::ISO_639_2_T) { + return languageNameFromIso(detail); + } + } + return LocaleDetail::getEmpty(); +} + +/*! + * \brief Returns the full name if possible and otherwise falls back to the abbreviated name. + * \remarks This function is intended to be used for display purposes. + */ +const std::string &Locale::fullOrSomeAbbreviatedName() const +{ + if (const auto &name = fullName(); !name.empty()) { + return name; + } + return someAbbreviatedName(); +} + +/*! + * \brief Returns all details as comma-separated string. + */ +std::string Locale::toString() const +{ + return CppUtilities::joinStrings, std::string>(*this, LocaleDetail(", "sv, LocaleFormat::Unknown), true); +} + +} // namespace TagParser diff --git a/localehelper.h b/localehelper.h new file mode 100644 index 0000000..9acb1c0 --- /dev/null +++ b/localehelper.h @@ -0,0 +1,128 @@ +#ifndef TAG_PARSER_LANGUAGE_H +#define TAG_PARSER_LANGUAGE_H + +#include "./global.h" + +#include +#include +#include +#include +#include + +namespace TagParser { + +/*! + * \brief Returns whether an ISO-639-2 \a languageSpecification is not empty or undefined. + */ +inline bool isLanguageDefined(const std::string &languageSpecification) +{ + return !languageSpecification.empty() && languageSpecification != "und" && languageSpecification != "XXX"; +} + +TAG_PARSER_EXPORT const std::string &languageNameFromIso(const std::string &isoCode); +TAG_PARSER_EXPORT const std::string &languageNameFromIsoWithFallback(const std::string &isoCode); + +/// \brief The LocaleFormat enum class specifies the format used by a LocaleDetail. +enum class LocaleFormat : std::uint64_t { + Unknown, /**< the format is unknown */ + DomainCountry, /**< a country as used by [Internet domains](https://www.iana.org/domains/root/db) (e.g. "de" for Germany or "at" for Austria) */ + ISO_639_1, /**< a language specified via ISO-639-1 code (e.g. "de" for German) */ + ISO_639_2_T, /**< a language specified via ISO-639-2/T code (terminological, e.g. "deu" for German) */ + ISO_639_2_B, /**< a language specified via ISO-639-2/B code (bibliographic, e.g. "ger" for German) */ + BCP_47, /**< a language and/or country according to [BCP 47](https://tools.ietf.org/html/bcp47) using + the [IANA Language Subtag Registry](https://www.iana.com/assignments/language-subtag-registry/language-subtag-registry) + (e.g. "de_DE" for the language/country German/Germany or "de_AT" for German/Austria) */ +}; + +/// \brief The LocaleDetail struct specifies a language and/or country. +struct TAG_PARSER_EXPORT LocaleDetail : public std::string { + explicit LocaleDetail(); + explicit LocaleDetail(std::string_view value, LocaleFormat format); + explicit LocaleDetail(std::string &&value, LocaleFormat format); + LocaleFormat format = LocaleFormat::Unknown; + static const LocaleDetail &getEmpty(); +}; + +/*! + * \brief Constructs an empty LocaleDetail. + */ +inline LocaleDetail::LocaleDetail() + : format(LocaleFormat::Unknown) +{ +} + +/*! + * \brief Constructs a new LocaleDetail making a copy of \a value. + */ +inline LocaleDetail::LocaleDetail(std::string_view value, LocaleFormat format) + : std::string(value) + , format(format) +{ +} + +/*! + * \brief Constructs a new LocaleDetail moving the specified \a value. + */ +inline LocaleDetail::LocaleDetail(std::string &&value, LocaleFormat format) + : std::string(std::move(value)) + , format(format) +{ +} + +/// \brief The Locale struct specifies a language and/or a country using one or more LocaleDetail objects. +struct TAG_PARSER_EXPORT Locale : public std::vector { + explicit Locale() = default; + explicit Locale(std::initializer_list details); + explicit Locale(std::string &&value, LocaleFormat format); + explicit Locale(std::string_view value, LocaleFormat format); + const LocaleDetail &abbreviatedName(LocaleFormat format) const; + template + const LocaleDetail &abbreviatedName(LocaleFormat format, LocaleFormats... moreFormats) const; + const LocaleDetail &someAbbreviatedName(LocaleFormat preferredFormat = LocaleFormat::BCP_47) const; + const std::string &fullName() const; + const std::string &fullOrSomeAbbreviatedName() const; + std::string toString() const; +}; + +/*! + * \brief Returns the abbreviated name of the specified \a format; if not present, checks \a moreFormats. + */ +template +inline const LocaleDetail &Locale::abbreviatedName(LocaleFormat format, LocaleFormats... moreFormats) const +{ + if (const auto &detail = abbreviatedName(format); !detail.empty()) { + return detail; + } else { + return abbreviatedName(moreFormats...); + } +} + +/*! + * \brief Constructs a new locale with the specified \a details. + */ +inline Locale::Locale(std::initializer_list details) + : std::vector(details) +{ +} + +/*! + * \brief Constructs a new locale with the specified \a value and \a format. + */ +inline Locale::Locale(std::string &&value, LocaleFormat format) + : std::vector() +{ + emplace_back(std::move(value), format); +} + +/*! + * \brief Constructs a new locale with the specified \a value and \a format. + */ +inline Locale::Locale(std::string_view value, LocaleFormat format) + : std::vector() +{ + emplace_back(value, format); +} + +} // namespace TagParser + +#endif // TAG_PARSER_LANGUAGE_H diff --git a/matroska/matroskachapter.cpp b/matroska/matroskachapter.cpp index 8e8145c..e1a23a7 100644 --- a/matroska/matroskachapter.cpp +++ b/matroska/matroskachapter.cpp @@ -99,15 +99,13 @@ void MatroskaChapter::internalParse(Diagnostics &diag) } break; case MatroskaIds::ChapLanguage: - m_names.back().languages().emplace_back(chapterDisplayElement->readString()); + m_names.back().locale().emplace_back(chapterDisplayElement->readString(), LocaleFormat::ISO_639_2_B); break; case MatroskaIds::ChapLanguageIETF: - diag.emplace_back(DiagLevel::Warning, - "\"ChapterDisplay\"-element contains a \"ChapLanguageIETF\"-element which is not supported yet. It will be ignored.", - context); + m_names.back().locale().emplace_back(chapterDisplayElement->readString(), LocaleFormat::BCP_47); break; case MatroskaIds::ChapCountry: - m_names.back().countries().emplace_back(chapterDisplayElement->readString()); + m_names.back().locale().emplace_back(chapterDisplayElement->readString(), LocaleFormat::DomainCountry); break; } } @@ -124,8 +122,8 @@ void MatroskaChapter::internalParse(Diagnostics &diag) } // "eng" is default language for (LocaleAwareString &name : m_names) { - if (name.languages().empty()) { - name.languages().emplace_back("eng"); + if (name.locale().empty()) { + name.locale().emplace_back("eng"sv, LocaleFormat::ISO_639_2_B); } } } diff --git a/matroska/matroskatagfield.cpp b/matroska/matroskatagfield.cpp index ef8f90b..a78f2f2 100644 --- a/matroska/matroskatagfield.cpp +++ b/matroska/matroskatagfield.cpp @@ -87,13 +87,13 @@ void MatroskaTagField::reparse(EbmlElement &simpleTagElement, Diagnostics &diag, } break; case MatroskaIds::TagLanguage: - if (!tagLanguageFound && !tagLanguageIETFFound) { + if (!tagLanguageFound) { tagLanguageFound = true; - string lng = child->readString(); - if (lng != "und") { - value().setLanguage(lng); + auto language = child->readString(); + if (language != "und") { + value().locale().emplace_back(std::move(language), LocaleFormat::ISO_639_2_B); } - } else if (tagLanguageFound) { + } else { diag.emplace_back(DiagLevel::Warning, "\"SimpleTag\"-element contains multiple \"TagLanguage\"-elements. Surplus \"TagLanguage\"-elements will be ignored.", context); } @@ -101,10 +101,7 @@ void MatroskaTagField::reparse(EbmlElement &simpleTagElement, Diagnostics &diag, case MatroskaIds::TagLanguageIETF: if (!tagLanguageIETFFound) { tagLanguageIETFFound = true; - diag.emplace_back(DiagLevel::Warning, - "\"SimpleTag\"-element contains a \"TagLanguageIETF\"-element. That's not supported at this point. The element will be dropped " - "when applying changes.", - context); + value().locale().emplace_back(child->readString(), LocaleFormat::BCP_47); } else { diag.emplace_back(DiagLevel::Warning, "\"SimpleTag\"-element contains multiple \"TagLanguageIETF\"-elements. Surplus \"TagLanguageIETF\"-elements will be ignored.", @@ -192,6 +189,8 @@ void MatroskaTagField::make(ostream &stream, Diagnostics &diag) */ MatroskaTagFieldMaker::MatroskaTagFieldMaker(MatroskaTagField &field, Diagnostics &diag) : m_field(field) + , m_language(m_field.value().locale().abbreviatedName(LocaleFormat::ISO_639_2_B, LocaleFormat::Unknown)) + , m_languageIETF(m_field.value().locale().abbreviatedName(LocaleFormat::BCP_47)) , m_isBinary(false) { try { @@ -203,23 +202,29 @@ MatroskaTagFieldMaker::MatroskaTagFieldMaker(MatroskaTagField &field, Diagnostic "making Matroska \"SimpleTag\" element."); m_isBinary = true; } - size_t languageSize = m_field.value().language().size(); - if (!languageSize) { - languageSize = 3; // if there's no language set, the 3 byte long value "und" is used - } + + // compute size of the mandatory "TagLanguage" 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 = 2 + EbmlElement::calculateSizeDenotationLength(languageSize) + languageSize; + // compute size of the optional "TagLanguageIETF" element + const auto languageIETFElementSize + = m_languageIETF.empty() ? 0 : (2 + EbmlElement::calculateSizeDenotationLength(m_languageIETF.size()) + m_languageIETF.size()); + + // compute "SimpleTag" element size m_simpleTagSize = // "TagName" element +2 + EbmlElement::calculateSizeDenotationLength(m_field.id().size()) + m_field.id().size() - // "TagLanguage" element - + 2 + EbmlElement::calculateSizeDenotationLength(languageSize) - + languageSize + // language elements + + languageElementSize + + languageIETFElementSize // "TagDefault" element + 2 + 1 + 1 // "TagString" element + 2 + EbmlElement::calculateSizeDenotationLength(m_stringValue.size()) + m_stringValue.size(); - // nested tags + + // compute size of nested tags for (auto &nestedField : field.nestedFields()) { m_nestedMaker.emplace_back(nestedField.prepareMaking(diag)); m_simpleTagSize += m_nestedMaker.back().m_totalSize; @@ -238,30 +243,37 @@ void MatroskaTagFieldMaker::make(ostream &stream) const { BinaryWriter writer(&stream); char buff[8]; - // write header of "SimpleTag" element + // write "SimpleTag" element writer.writeUInt16BE(MatroskaIds::SimpleTag); std::uint8_t sizeDenotationLen = EbmlElement::makeSizeDenotation(m_simpleTagSize, buff); stream.write(buff, sizeDenotationLen); - // write header of "TagName" element + // write "TagName" element writer.writeUInt16BE(MatroskaIds::TagName); sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.id().size(), buff); stream.write(buff, sizeDenotationLen); stream.write(m_field.id().c_str(), m_field.id().size()); - // write header of "TagLanguage" element + // write "TagLanguage" element writer.writeUInt16BE(MatroskaIds::TagLanguage); - if (m_field.value().language().empty()) { + if (m_language.empty()) { stream.put(static_cast(0x80 | 3)); stream.write("und", 3); } else { - sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().language().size(), buff); + sizeDenotationLen = EbmlElement::makeSizeDenotation(m_language.size(), buff); stream.write(buff, sizeDenotationLen); - stream.write(m_field.value().language().c_str(), m_field.value().language().size()); + stream.write(m_language.data(), m_language.size()); } - // write header of "TagDefault" element + // write "TagLanguageIETF" element + if (!m_languageIETF.empty()) { + writer.writeUInt16BE(MatroskaIds::TagLanguageIETF); + sizeDenotationLen = EbmlElement::makeSizeDenotation(m_languageIETF.size(), buff); + stream.write(buff, sizeDenotationLen); + stream.write(m_languageIETF.data(), m_languageIETF.size()); + } + // write "TagDefault" element writer.writeUInt16BE(MatroskaIds::TagDefault); stream.put(static_cast(0x80 | 1)); stream.put(m_field.isDefault() ? 1 : 0); - // write header of "TagString"/"TagBinary" element + // write "TagString"/"TagBinary" element if (m_isBinary) { writer.writeUInt16BE(MatroskaIds::TagBinary); sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().dataSize(), buff); diff --git a/matroska/matroskatagfield.h b/matroska/matroskatagfield.h index 86d252b..80d31ab 100644 --- a/matroska/matroskatagfield.h +++ b/matroska/matroskatagfield.h @@ -39,11 +39,13 @@ private: MatroskaTagFieldMaker(MatroskaTagField &field, Diagnostics &diag); MatroskaTagField &m_field; - bool m_isBinary; std::string m_stringValue; + const std::string &m_language; + const std::string &m_languageIETF; std::uint64_t m_simpleTagSize; std::uint64_t m_totalSize; std::vector m_nestedMaker; + bool m_isBinary; }; /*! diff --git a/matroska/matroskatrack.cpp b/matroska/matroskatrack.cpp index 2d07104..e74edb7 100644 --- a/matroska/matroskatrack.cpp +++ b/matroska/matroskatrack.cpp @@ -409,11 +409,10 @@ void MatroskaTrack::internalParseHeader(Diagnostics &diag) m_name = trackInfoElement->readString(); break; case MatroskaIds::TrackLanguage: - m_language = trackInfoElement->readString(); + m_locale.emplace_back(trackInfoElement->readString(), LocaleFormat::ISO_639_2_B); break; case MatroskaIds::TrackLanguageIETF: - diag.emplace_back(DiagLevel::Warning, - "\"TrackEntry\"-element contains a \"LanguageIETF\"-element which is not supported yet. It will be ignored.", context); + m_locale.emplace_back(trackInfoElement->readString(), LocaleFormat::BCP_47); break; case MatroskaIds::CodecID: m_format = codecIdToMediaFormat(m_formatId = trackInfoElement->readString()); @@ -539,8 +538,8 @@ void MatroskaTrack::internalParseHeader(Diagnostics &diag) } // set English if no language has been specified (it is default value of MatroskaIds::TrackLanguage) - if (m_language.empty()) { - m_language = "eng"; + if (m_locale.empty()) { + m_locale.emplace_back("eng"sv, LocaleFormat::ISO_639_2_B); } } @@ -557,6 +556,8 @@ void MatroskaTrack::internalParseHeader(Diagnostics &diag) */ 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); @@ -570,9 +571,14 @@ MatroskaTrackHeaderMaker::MatroskaTrackHeaderMaker(const MatroskaTrack &track, D if (!m_track.name().empty()) { m_dataSize += 2 + EbmlElement::calculateSizeDenotationLength(m_track.name().size()) + m_track.name().size(); } - if (!m_track.language().empty()) { - m_dataSize += 3 + EbmlElement::calculateSizeDenotationLength(m_track.language().size()) + m_track.language().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()) { @@ -581,6 +587,7 @@ MatroskaTrackHeaderMaker::MatroskaTrackHeaderMaker(const MatroskaTrack &track, D case MatroskaIds::TrackUID: case MatroskaIds::TrackName: case MatroskaIds::TrackLanguage: + case MatroskaIds::TrackLanguageIETF: case MatroskaIds::TrackFlagEnabled: case MatroskaIds::TrackFlagDefault: case MatroskaIds::TrackFlagForced: @@ -619,8 +626,9 @@ void MatroskaTrackHeaderMaker::make(ostream &stream) const if (!m_track.name().empty()) { EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackName, m_track.name()); } - if (!m_track.language().empty()) { - EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackLanguage, m_track.language()); + 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 diff --git a/matroska/matroskatrack.h b/matroska/matroskatrack.h index 861cc62..afd1204 100644 --- a/matroska/matroskatrack.h +++ b/matroska/matroskatrack.h @@ -22,6 +22,8 @@ private: MatroskaTrackHeaderMaker(const MatroskaTrack &track, Diagnostics &diag); const MatroskaTrack &m_track; + const std::string &m_language; + const std::string &m_languageIETF; std::uint64_t m_dataSize; std::uint64_t m_requiredSize; std::uint8_t m_sizeDenotationLength; diff --git a/mediafileinfo.cpp b/mediafileinfo.cpp index fd6269c..2630291 100644 --- a/mediafileinfo.cpp +++ b/mediafileinfo.cpp @@ -3,7 +3,7 @@ #include "./backuphelper.h" #include "./diagnostics.h" #include "./exceptions.h" -#include "./language.h" +#include "./locale.h" #include "./progressfeedback.h" #include "./signature.h" #include "./tag.h" @@ -914,13 +914,18 @@ unordered_set MediaFileInfo::availableLanguages(MediaType type) const unordered_set res; if (m_container) { for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) { - const AbstractTrack *track = m_container->track(i); - if ((type == MediaType::Unknown || track->mediaType() == type) && isLanguageDefined(track->language())) { - res.emplace(track->language()); + const AbstractTrack *const track = m_container->track(i); + if (type != MediaType::Unknown && track->mediaType() != type) { + continue; + } + if (const auto &language = track->locale().someAbbreviatedName(); !language.empty()) { + res.emplace(language); } } - } else if (m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type) && isLanguageDefined(m_singleTrack->language())) { - res.emplace(m_singleTrack->language()); + } else if (m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type)) { + if (const auto &language = m_singleTrack->locale().someAbbreviatedName(); !language.empty()) { + res.emplace(language); + } } return res; } diff --git a/mp4/mp4tagfield.cpp b/mp4/mp4tagfield.cpp index 218d061..834e985 100644 --- a/mp4/mp4tagfield.cpp +++ b/mp4/mp4tagfield.cpp @@ -541,7 +541,7 @@ Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field, Diagnostics &diag) } } } - } catch (ConversionException &ex) { + } catch (const ConversionException &ex) { // it was not possible to perform required conversions if (char_traits::length(ex.what())) { diag.emplace_back(DiagLevel::Critical, ex.what(), context); diff --git a/mp4/mp4track.cpp b/mp4/mp4track.cpp index 826b323..f7ea73d 100644 --- a/mp4/mp4track.cpp +++ b/mp4/mp4track.cpp @@ -1293,30 +1293,31 @@ void Mp4Track::makeMedia(Diagnostics &diag) writer().writeUInt32BE(static_cast(duration)); } // convert and write language - std::uint16_t language = 0; + const std::string &language = m_locale.abbreviatedName(LocaleFormat::ISO_639_2_T, LocaleFormat::Unknown); + std::uint16_t codedLanguage = 0; for (size_t charIndex = 0; charIndex != 3; ++charIndex) { - const char langChar = charIndex < m_language.size() ? m_language[charIndex] : 0; + const char langChar = charIndex < language.size() ? language[charIndex] : 0; if (langChar >= 'a' && langChar <= 'z') { - language |= static_cast(langChar - 0x60) << (0xA - charIndex * 0x5); + codedLanguage |= static_cast(langChar - 0x60) << (0xA - charIndex * 0x5); continue; } // handle invalid characters - if (m_language.empty()) { + if (language.empty()) { // preserve empty language field - language = 0; + codedLanguage = 0; break; } - diag.emplace_back(DiagLevel::Warning, "Assigned language \"" % m_language + "\" is of an invalid format. Setting language to undefined.", - "making mdhd atom"); - language = 0x55C4; // und(efined) + diag.emplace_back( + DiagLevel::Warning, "Assigned language \"" % language + "\" is of an invalid format. Setting language to undefined.", "making mdhd atom"); + codedLanguage = 0x55C4; // und(efined) break; } - if (m_language.size() > 3) { + if (language.size() > 3) { diag.emplace_back( - DiagLevel::Warning, "Assigned language \"" % m_language + "\" is longer than 3 byte and hence will be truncated.", "making mdhd atom"); + DiagLevel::Warning, "Assigned language \"" % language + "\" is longer than 3 byte and hence will be truncated.", "making mdhd atom"); } - writer().writeUInt16BE(language); + writer().writeUInt16BE(codedLanguage); writer().writeUInt16BE(0); // pre defined // write hdlr atom writer().writeUInt32BE(33 + m_name.size()); // size @@ -1588,9 +1589,9 @@ void Mp4Track::internalParseHeader(Diagnostics &diag) static_cast(((tmp & 0x03E0) >> 0x5) + 0x60), static_cast(((tmp & 0x001F) >> 0x0) + 0x60), }; - m_language = string(buff, 3); + m_locale.emplace_back(std::string(buff, 3), LocaleFormat::ISO_639_2_T); } else { - m_language.clear(); + m_locale.clear(); } // read hdlr atom diff --git a/tagvalue.cpp b/tagvalue.cpp index 837f7de..6e34c6d 100644 --- a/tagvalue.cpp +++ b/tagvalue.cpp @@ -97,7 +97,7 @@ TagValue::TagValue(const TagValue &other) : m_size(other.m_size) , m_desc(other.m_desc) , m_mimeType(other.m_mimeType) - , m_language(other.m_language) + , m_locale(other.m_locale) , m_type(other.m_type) , m_encoding(other.m_encoding) , m_descEncoding(other.m_descEncoding) @@ -121,7 +121,7 @@ TagValue &TagValue::operator=(const TagValue &other) m_type = other.m_type; m_desc = other.m_desc; m_mimeType = other.m_mimeType; - m_language = other.m_language; + m_locale = other.m_locale; m_flags = other.m_flags; m_encoding = other.m_encoding; m_descEncoding = other.m_descEncoding; @@ -183,7 +183,7 @@ bool TagValue::compareTo(const TagValue &other, TagValueComparisionFlags options // check whether meta-data is equal (except description) if (!(options & TagValueComparisionFlags::IgnoreMetaData)) { // check meta-data which always uses UTF-8 (everything but description) - if (m_mimeType != other.m_mimeType || m_language != other.m_language || m_flags != other.m_flags) { + if (m_mimeType != other.m_mimeType || m_locale != other.m_locale || m_flags != other.m_flags) { return false; } @@ -307,7 +307,7 @@ void TagValue::clearMetadata() { m_desc.clear(); m_mimeType.clear(); - m_language.clear(); + m_locale.clear(); m_flags = TagValueFlags::None; m_encoding = TagTextEncoding::Latin1; m_descEncoding = TagTextEncoding::Latin1; diff --git a/tagvalue.h b/tagvalue.h index b633685..38b5cf9 100644 --- a/tagvalue.h +++ b/tagvalue.h @@ -1,6 +1,7 @@ #ifndef TAG_PARSER_TAGVALUE_H #define TAG_PARSER_TAGVALUE_H +#include "./localehelper.h" #include "./positioninset.h" #include @@ -143,8 +144,9 @@ public: void setDescription(const std::string &value, TagTextEncoding encoding = TagTextEncoding::Latin1); const std::string &mimeType() const; void setMimeType(const std::string &mimeType); - const std::string &language() const; - void setLanguage(const std::string &language); + const Locale &locale() const; + Locale &locale(); + void setLocale(const Locale &locale); TagValueFlags flags() const; void setFlags(TagValueFlags flags); bool isLabeledAsReadonly() const; @@ -188,7 +190,7 @@ private: std::size_t m_size; std::string m_desc; std::string m_mimeType; - std::string m_language; + Locale m_locale; std::unordered_map m_nativeData; TagDataType m_type; TagTextEncoding m_encoding; @@ -597,31 +599,45 @@ inline void TagValue::setMimeType(const std::string &mimeType) } /*! - * \brief Returns the language. + * \brief Returns the locale. * \remarks * - Whether this additional meta-data is available and can be used depends on the tag format. It will * be ignored by the implementation of the tag format if not supported. - * - The format of the language is specific to the tag format. The implementation of the tag format might + * - The format of the locale is specific to the tag format. The implementation of the tag format might * store the value without further validation. - * \sa setLanguage() + * \sa setLocale() */ -inline const std::string &TagValue::language() const +inline const Locale &TagValue::locale() const { - return m_language; + return m_locale; } /*! - * \brief Sets the language. + * \brief Returns the locale. * \remarks * - Whether this additional meta-data is available and can be used depends on the tag format. It will * be ignored by the implementation of the tag format if not supported. - * - The format of the language is specific to the tag format. The implementation of the tag format might + * - The format of the locale is specific to the tag format. The implementation of the tag format might * store the value without further validation. - * \sa language() + * \sa setLocale() */ -inline void TagValue::setLanguage(const std::string &language) +inline Locale &TagValue::locale() { - m_language = language; + return m_locale; +} + +/*! + * \brief Sets the setLocale. + * \remarks + * - Whether this additional meta-data is available and can be used depends on the tag format. It will + * be ignored by the implementation of the tag format if not supported. + * - The format of the locale is specific to the tag format. The implementation of the tag format might + * store the value without further validation. + * \sa locale() + */ +inline void TagValue::setLocale(const Locale &locale) +{ + m_locale = locale; } /*! diff --git a/tests/helper.h b/tests/helper.h index 2d9539c..a7ac05d 100644 --- a/tests/helper.h +++ b/tests/helper.h @@ -2,6 +2,7 @@ #define TAGPARSER_TEST_HELPER #include "../diagnostics.h" +#include "../localehelper.h" #include "../size.h" #include "../tagvalue.h" @@ -47,6 +48,14 @@ inline std::ostream &operator<<(std::ostream &os, const TagParser::DiagMessage & return os << diagMessage.levelName() << ':' << ' ' << diagMessage.message() << ' ' << '(' << diagMessage.context() << ')'; } +/*! + * \brief Prints a Locale to enable using it in CPPUNIT_ASSERT_EQUAL. + */ +inline std::ostream &operator<<(std::ostream &os, const TagParser::Locale &locale) +{ + return os << locale.toString(); +} + } // namespace CppUtilities #endif // TAGPARSER_TEST_HELPER diff --git a/tests/mediafileinfo.cpp b/tests/mediafileinfo.cpp index 06bfd91..8c57208 100644 --- a/tests/mediafileinfo.cpp +++ b/tests/mediafileinfo.cpp @@ -182,7 +182,7 @@ void MediaFileInfoTests::testFullParseAndFurtherProperties() CPPUNIT_ASSERT_EQUAL(DiagLevel::Critical, diag.level()); // track info / available languages - file.tracks().back()->setLanguage("eng"); + file.tracks().back()->setLocale(Locale("eng"sv, LocaleFormat::ISO_639_2_B)); CPPUNIT_ASSERT_EQUAL(unordered_set({ "eng" }), file.availableLanguages()); CPPUNIT_ASSERT_EQUAL(unordered_set({}), file.availableLanguages(MediaType::Text)); CPPUNIT_ASSERT_EQUAL("ID: 2422994868, type: Video"s, file.tracks()[0]->label()); diff --git a/tests/overallmkv.cpp b/tests/overallmkv.cpp index f5c21c6..ce4e970 100644 --- a/tests/overallmkv.cpp +++ b/tests/overallmkv.cpp @@ -186,14 +186,14 @@ void OverallTests::checkMkvTestfile4() switch (m_tagStatus) { case TagStatus::Original: case TagStatus::Removed: - CPPUNIT_ASSERT_EQUAL("und"s, track->language()); + CPPUNIT_ASSERT_EQUAL(Locale("und"sv, LocaleFormat::ISO_639_2_B), track->locale()); CPPUNIT_ASSERT_EQUAL(string(), track->name()); CPPUNIT_ASSERT(track->isEnabled()); CPPUNIT_ASSERT(!track->isForced()); CPPUNIT_ASSERT(!track->isDefault()); break; case TagStatus::TestMetaDataPresent: - CPPUNIT_ASSERT_EQUAL("ger"s, track->language()); + CPPUNIT_ASSERT_EQUAL(Locale("ger"sv, LocaleFormat::ISO_639_2_B), track->locale()); CPPUNIT_ASSERT_EQUAL("the name"s, track->name()); CPPUNIT_ASSERT(track->isEnabled()); CPPUNIT_ASSERT(track->isForced()); @@ -245,7 +245,7 @@ void OverallTests::checkMkvTestfile5() case 3554194305: CPPUNIT_ASSERT_EQUAL(MediaType::Text, track->mediaType()); CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::TextSubtitle, track->format().general); - CPPUNIT_ASSERT_EQUAL("ger"s, track->language()); + CPPUNIT_ASSERT_EQUAL(Locale("ger"sv, LocaleFormat::ISO_639_2_B), track->locale()); break; default:; } @@ -599,7 +599,7 @@ void OverallTests::setMkvTestMetaData() // also change language, name, forced and default of track "3171450505" to German MatroskaTrack *track = container->trackById(3171450505); CPPUNIT_ASSERT(track); - track->setLanguage("ger"); + track->setLocale(Locale("ger"sv, LocaleFormat::ISO_639_2_B)); track->setName("the name"); track->setDefault(true); track->setEnabled(true); diff --git a/tests/overallmp4.cpp b/tests/overallmp4.cpp index 1850aaf..c3f1333 100644 --- a/tests/overallmp4.cpp +++ b/tests/overallmp4.cpp @@ -84,7 +84,7 @@ void OverallTests::checkMp4Testfile2() CPPUNIT_ASSERT_EQUAL(static_cast(SubFormats::AacMpeg4LowComplexityProfile), track->format().sub); CPPUNIT_ASSERT(!(track->format().extension & ExtensionFormats::SpectralBandReplication)); CPPUNIT_ASSERT(!(track->format().extension & ExtensionFormats::ParametricStereo)); - CPPUNIT_ASSERT_EQUAL("eng"s, track->language()); + CPPUNIT_ASSERT_EQUAL(Locale("eng"sv, LocaleFormat::ISO_639_2_T), track->locale()); CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year()); CPPUNIT_ASSERT_EQUAL(48000u, track->samplingFrequency()); CPPUNIT_ASSERT_EQUAL(static_cast(Mpeg4ChannelConfigs::FrontLeftFrontRight), track->channelConfig()); @@ -92,13 +92,13 @@ void OverallTests::checkMp4Testfile2() case 3: CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType()); CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Ac3, track->format().general); - CPPUNIT_ASSERT_EQUAL("eng"s, track->language()); + CPPUNIT_ASSERT_EQUAL(Locale("eng"sv, LocaleFormat::ISO_639_2_T), track->locale()); CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year()); break; case 4: CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType()); CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::DtsHd, track->format().general); - CPPUNIT_ASSERT_EQUAL("eng"s, track->language()); + CPPUNIT_ASSERT_EQUAL(Locale("eng"sv, LocaleFormat::ISO_639_2_T), track->locale()); CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year()); break; case 6: @@ -296,7 +296,7 @@ void OverallTests::checkMp4Testfile6() CPPUNIT_ASSERT_EQUAL(static_cast(SubFormats::AacMpeg4LowComplexityProfile), track->format().sub); CPPUNIT_ASSERT(!(track->format().extension & ExtensionFormats::SpectralBandReplication)); CPPUNIT_ASSERT(!(track->format().extension & ExtensionFormats::ParametricStereo)); - CPPUNIT_ASSERT_EQUAL("ger"s, track->language()); + CPPUNIT_ASSERT_EQUAL(Locale("ger"sv, LocaleFormat::ISO_639_2_T), track->locale()); CPPUNIT_ASSERT_EQUAL("test"s, track->name()); CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year()); CPPUNIT_ASSERT_EQUAL(48000u, track->samplingFrequency()); @@ -305,13 +305,13 @@ void OverallTests::checkMp4Testfile6() case 3: CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType()); CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Ac3, track->format().general); - CPPUNIT_ASSERT_EQUAL("eng"s, track->language()); + CPPUNIT_ASSERT_EQUAL(Locale("eng"sv, LocaleFormat::ISO_639_2_T), track->locale()); CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year()); break; case 4: CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType()); CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::DtsHd, track->format().general); - CPPUNIT_ASSERT_EQUAL("eng"s, track->language()); + CPPUNIT_ASSERT_EQUAL(Locale("eng"sv, LocaleFormat::ISO_639_2_T), track->locale()); CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year()); break; case 5: @@ -513,7 +513,7 @@ void OverallTests::alterMp4Tracks() container->addTrack(track); CPPUNIT_ASSERT_EQUAL(6_st, container->trackCount()); auto &secondTrack = container->tracks()[1]; - secondTrack->setLanguage("ger"); + secondTrack->setLocale(Locale("ger"sv, LocaleFormat::ISO_639_2_T)); secondTrack->setName("test"); }