Add Locale class to deal with differently specified languages/countries

Different media/tag formats specify languages and countries
differently. This change introduces a Locale class to keep track
of the format being used. So far there are no automatic conversions
implemented so it is entirely up to the user to pass valid values using
a format which matches the one required by the media/tag format.

This change also adds support for Matroska's IETF elements so at least the
raw value can be read, written and is preserved.
This commit is contained in:
Martchus 2020-12-13 18:37:15 +01:00
parent 4cc2dbd9e6
commit 6b469f1c26
23 changed files with 463 additions and 228 deletions

View File

@ -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

View File

@ -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:;

View File

@ -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

View File

@ -981,7 +981,7 @@ void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue
}
TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*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<char[]> &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<char[]> &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

View File

@ -1,44 +0,0 @@
#include "./language.h"
#include <unordered_map>
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

View File

@ -1,26 +0,0 @@
#ifndef TAG_PARSER_LANGUAGE_H
#define TAG_PARSER_LANGUAGE_H
#include "./global.h"
#include <c++utilities/conversion/stringbuilder.h>
#include <cstdint>
#include <string>
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

View File

@ -1,7 +1,7 @@
#ifndef TAG_PARSER_LOCALEAWARESTRING_H
#define TAG_PARSER_LOCALEAWARESTRING_H
#include "./global.h"
#include "./localehelper.h"
#include <string>
#include <vector>
@ -17,14 +17,11 @@ public:
explicit LocaleAwareString(std::string &&value);
~LocaleAwareString();
const std::vector<std::string> &languages() const;
std::vector<std::string> &languages();
const std::vector<std::string> &countries() const;
std::vector<std::string> &countries();
const Locale &locale() const;
Locale &locale();
private:
std::vector<std::string> m_languages;
std::vector<std::string> 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<std::string> &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<std::string> &LocaleAwareString::languages()
inline Locale &LocaleAwareString::locale()
{
return m_languages;
}
/*!
* \brief Returns associated countries.
*/
inline const std::vector<std::string> &LocaleAwareString::countries() const
{
return m_countries;
}
/*!
* \brief Returns associated countries.
*/
inline std::vector<std::string> &LocaleAwareString::countries()
{
return m_countries;
return m_locale;
}
} // namespace TagParser

143
localehelper.cpp Normal file
View File

@ -0,0 +1,143 @@
#include "./localehelper.h"
#include <c++utilities/conversion/stringconversion.h>
#include <unordered_map>
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<std::underlying_type_t<LocaleFormat>>(detail.format) >= static_cast<std::underlying_type_t<LocaleFormat>>(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::vector<LocaleDetail>, std::string>(*this, LocaleDetail(", "sv, LocaleFormat::Unknown), true);
}
} // namespace TagParser

128
localehelper.h Normal file
View File

@ -0,0 +1,128 @@
#ifndef TAG_PARSER_LANGUAGE_H
#define TAG_PARSER_LANGUAGE_H
#include "./global.h"
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include <vector>
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<LocaleDetail> {
explicit Locale() = default;
explicit Locale(std::initializer_list<LocaleDetail> details);
explicit Locale(std::string &&value, LocaleFormat format);
explicit Locale(std::string_view value, LocaleFormat format);
const LocaleDetail &abbreviatedName(LocaleFormat format) const;
template <typename LocaleFormat, typename... LocaleFormats>
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 <typename LocaleFormat, typename... LocaleFormats>
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<LocaleDetail> details)
: std::vector<LocaleDetail>(details)
{
}
/*!
* \brief Constructs a new locale with the specified \a value and \a format.
*/
inline Locale::Locale(std::string &&value, LocaleFormat format)
: std::vector<LocaleDetail>()
{
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<LocaleDetail>()
{
emplace_back(value, format);
}
} // namespace TagParser
#endif // TAG_PARSER_LANGUAGE_H

View File

@ -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);
}
}
}

View File

@ -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<ostream::char_type>(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<ostream::char_type>(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);

View File

@ -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<MatroskaTagFieldMaker> m_nestedMaker;
bool m_isBinary;
};
/*!

View File

@ -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

View File

@ -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;

View File

@ -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<string> MediaFileInfo::availableLanguages(MediaType type) const
unordered_set<string> 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;
}

View File

@ -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<char>::length(ex.what())) {
diag.emplace_back(DiagLevel::Critical, ex.what(), context);

View File

@ -1293,30 +1293,31 @@ void Mp4Track::makeMedia(Diagnostics &diag)
writer().writeUInt32BE(static_cast<std::uint32_t>(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<std::uint16_t>(langChar - 0x60) << (0xA - charIndex * 0x5);
codedLanguage |= static_cast<std::uint16_t>(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<char>(((tmp & 0x03E0) >> 0x5) + 0x60),
static_cast<char>(((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

View File

@ -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;

View File

@ -1,6 +1,7 @@
#ifndef TAG_PARSER_TAGVALUE_H
#define TAG_PARSER_TAGVALUE_H
#include "./localehelper.h"
#include "./positioninset.h"
#include <c++utilities/chrono/datetime.h>
@ -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<std::string, std::string> 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;
}
/*!

View File

@ -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

View File

@ -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<string>({ "eng" }), file.availableLanguages());
CPPUNIT_ASSERT_EQUAL(unordered_set<string>({}), file.availableLanguages(MediaType::Text));
CPPUNIT_ASSERT_EQUAL("ID: 2422994868, type: Video"s, file.tracks()[0]->label());

View File

@ -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);

View File

@ -84,7 +84,7 @@ void OverallTests::checkMp4Testfile2()
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned char>(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<unsigned char>(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<unsigned char>(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");
}