diff --git a/NEWS.md b/NEWS.md index d8e6b3a91..961f8ab6d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -30,6 +30,9 @@ the simple chapter format or when generating chapters due to the `--generate-chapters` command line option. Part of the implementation of #2419. +* mkvpropedit: IETF BCP 47/RFC 5646 language tags: setting/deleting the track + language now acts on both the old language element as well as the + "LanguageIETF" track header element. Part of the implementation of #2419. ## Bug fixes diff --git a/src/common/property_element.cpp b/src/common/property_element.cpp index 4a02442a7..74453db0b 100644 --- a/src/common/property_element.cpp +++ b/src/common/property_element.cpp @@ -138,7 +138,8 @@ property_element_c::init_tables() { "If set to 0, the reference pseudo-cache system is not used.")); ELE("default-duration", KaxTrackDefaultDuration::ClassInfos, YT("Default duration"), YT("Number of nanoseconds (not scaled) per frame.")); ELE("name", KaxTrackName::ClassInfos, YT("Name"), YT("A human-readable track name.")); - ELE("language", KaxTrackLanguage::ClassInfos, YT("Language"), YT("Specifies the language of the track in the Matroska languages form.")); + ELE("language", KaxTrackLanguage::ClassInfos, YT("Language"), YT("Specifies the language of the track.")); + ELE("language-ietf", KaxLanguageIETF::ClassInfos, YT("Language (IETF BCP 47)"), YT("Specifies the language of the track in the form of a BCP 47 language tag.")); ELE("codec-id", KaxCodecID::ClassInfos, YT("Codec ID"), YT("An ID corresponding to the codec.")); ELE("codec-name", KaxCodecName::ClassInfos, YT("Codec name"), YT("A human-readable string specifying the codec.")); ELE("codec-delay", KaxCodecDelay::ClassInfos, YT("Codec-inherent delay"), YT("Delay built into the codec during decoding in ns.")); diff --git a/src/propedit/change.cpp b/src/propedit/change.cpp index 0182da4a7..3778f4b92 100644 --- a/src/propedit/change.cpp +++ b/src/propedit/change.cpp @@ -18,7 +18,7 @@ #include #include -#include "common/common_pch.h" +#include "common/bcp47.h" #include "common/date_time.h" #include "common/ebml.h" #include "common/iso639.h" @@ -349,7 +349,7 @@ change_c::get_semantic() { return find_ebml_semantic(libmatroska::KaxSegment::ClassInfos, m_property.m_callbacks->GlobalId); } -change_cptr +std::vector change_c::parse_spec(change_c::change_type_e type, const std::string &spec) { std::string name, value; @@ -368,14 +368,34 @@ change_c::parse_spec(change_c::change_type_e type, if (name.empty()) throw std::runtime_error(Y("missing property name")); - if ( mtx::included_in(type, ct_add, ct_set) - && (name == "language")) { - auto language_opt = mtx::iso639::look_up(value); - if (!language_opt) - throw std::runtime_error{fmt::format(("invalid ISO 639-2 language code '{0}'"), value)}; + if (mtx::included_in(name, "language", "language-ietf")) + return make_change_for_language(type, name, value); - value = language_opt->iso639_2_code; + return { std::make_shared(type, name, value) }; +} + +std::vector +change_c::make_change_for_language(change_c::change_type_e type, + std::string const &name, + std::string const &value) { + std::vector changes; + + if (type == ct_delete) { + if (name == "language") + changes.emplace_back(std::make_shared(ct_delete, "language", value)); + changes.emplace_back(std::make_shared(ct_delete, "language-ietf", value)); + + return changes; } - return std::make_shared(type, name, value); + auto language = mtx::bcp47::language_c::parse(value); + if (!language.is_valid()) + throw std::runtime_error{fmt::format(Y("invalid language tag '{0}': {1}"), value, language.get_error())}; + + if (language.has_valid_iso639_code() && (name == "language")) + changes.push_back(std::make_shared(type, "language", language.get_iso639_2_code())); + + changes.push_back(std::make_shared(type, "language-ietf", language.format())); + + return changes; } diff --git a/src/propedit/change.h b/src/propedit/change.h index f60a659c5..2c2ac5a55 100644 --- a/src/propedit/change.h +++ b/src/propedit/change.h @@ -53,7 +53,8 @@ public: void execute(EbmlMaster *master, EbmlMaster *sub_master); public: - static change_cptr parse_spec(change_type_e type, std::string const &spec); + static std::vector parse_spec(change_type_e type, std::string const &spec); + static std::vector make_change_for_language(change_type_e type, std::string const &name, std::string const &value); protected: void parse_ascii_string(); diff --git a/src/propedit/propedit_cli_parser.cpp b/src/propedit/propedit_cli_parser.cpp index 0a3ac5e20..e06582a78 100644 --- a/src/propedit/propedit_cli_parser.cpp +++ b/src/propedit/propedit_cli_parser.cpp @@ -186,6 +186,9 @@ propedit_cli_parser_c::list_property_names_for_table(const std::vector true + +test "language-ietf" do + result = [] + result += verify_languages("und", "und") + + propedit tmp, "--edit track:1 --set language=de-CH" + result += verify_languages("ger", "de-CH") + + propedit tmp, "--edit track:1 --set language-ietf=pt-BR" + result += verify_languages("ger", "pt-BR") + + propedit tmp, "--edit track:1 --delete language-ietf" + result += verify_languages("ger", nil) + + propedit tmp, "--edit track:1 --set language=es-MX" + result += verify_languages("spa", "es-MX") + + propedit tmp, "--edit track:1 --delete language" + result += verify_languages("eng", nil) + + result.join('+') +end