diff options
| author | Carl Hetherington <cth@carlh.net> | 2020-08-27 22:59:12 +0200 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2020-09-21 21:57:18 +0200 |
| commit | b933775fc54e0b51ad3777d72bf2866f0c04bacc (patch) | |
| tree | c13133d7cfe9722f3b2eeaa52a8686e029172377 /src | |
| parent | 1ae2755dbbaa30b6240dfd304c289253a577b424 (diff) | |
Support CPL metadata.
Diffstat (limited to 'src')
| -rw-r--r-- | src/cpl.cc | 235 | ||||
| -rw-r--r-- | src/cpl.h | 131 | ||||
| -rw-r--r-- | src/exceptions.cc | 24 | ||||
| -rw-r--r-- | src/exceptions.h | 22 | ||||
| -rw-r--r-- | src/language_tag.cc | 113 | ||||
| -rw-r--r-- | src/language_tag.h | 68 | ||||
| -rw-r--r-- | src/reel.cc | 6 | ||||
| -rw-r--r-- | src/reel.h | 4 | ||||
| -rw-r--r-- | src/reel_subtitle_asset.cc | 11 | ||||
| -rw-r--r-- | src/reel_subtitle_asset.h | 11 | ||||
| -rw-r--r-- | src/types.cc | 287 | ||||
| -rw-r--r-- | src/types.h | 87 |
12 files changed, 962 insertions, 37 deletions
@@ -45,8 +45,10 @@ #include "local_time.h" #include "dcp_assert.h" #include "compose.hpp" +#include "raw_convert.h" #include <libxml/parser.h> #include <libxml++/libxml++.h> +#include <boost/algorithm/string.hpp> #include <boost/foreach.hpp> using std::string; @@ -54,14 +56,18 @@ using std::list; using std::pair; using std::make_pair; using std::cout; +using std::set; using std::vector; using boost::shared_ptr; using boost::optional; using boost::dynamic_pointer_cast; using namespace dcp; + static string const cpl_interop_ns = "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#"; static string const cpl_smpte_ns = "http://www.smpte-ra.org/schemas/429-7/2006/CPL"; +static string const cpl_metadata_ns = "http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata"; + CPL::CPL (string annotation_text, ContentKind content_kind) /* default _content_title_text to annotation_text */ @@ -122,6 +128,19 @@ CPL::CPL (boost::filesystem::path file) } _reels = type_grand_children<Reel> (f, "ReelList", "Reel"); + cxml::ConstNodePtr reel_list = f.node_child ("ReelList"); + if (reel_list) { + list<cxml::NodePtr> reels = reel_list->node_children("Reel"); + if (!reels.empty()) { + cxml::ConstNodePtr asset_list = reels.front()->node_child("AssetList"); + cxml::ConstNodePtr metadata = asset_list->optional_node_child("CompositionMetadataAsset"); + if (metadata) { + read_composition_metadata_asset (metadata); + } + } + } + + f.ignore_child ("Issuer"); f.ignore_child ("Signer"); f.ignore_child ("Signature"); @@ -139,6 +158,7 @@ CPL::add (boost::shared_ptr<Reel> reel) } /** Write an CompositonPlaylist XML file. + * * @param file Filename to write. * @param standard INTEROP or SMPTE. * @param signer Signer to sign the CPL, or 0 to add no signature. @@ -171,8 +191,13 @@ CPL::write_xml (boost::filesystem::path file, Standard standard, shared_ptr<cons xmlpp::Element* reel_list = root->add_child ("ReelList"); + bool first = true; BOOST_FOREACH (shared_ptr<Reel> i, _reels) { - i->write_to_cpl (reel_list, standard); + xmlpp::Element* asset_list = i->write_to_cpl (reel_list, standard); + if (first && standard == dcp::SMPTE) { + maybe_write_composition_metadata_asset (asset_list); + first = false; + } } indent (root, 0); @@ -186,6 +211,191 @@ CPL::write_xml (boost::filesystem::path file, Standard standard, shared_ptr<cons set_file (file); } + +void +CPL::read_composition_metadata_asset (cxml::ConstNodePtr node) +{ + cxml::ConstNodePtr fctt = node->node_child("FullContentTitleText"); + _full_content_title_text = fctt->content(); + _full_content_title_text_language = fctt->optional_string_attribute("language"); + + _release_territory = node->optional_string_child("ReleaseTerritory"); + + cxml::ConstNodePtr vn = node->optional_node_child("VersionNumber"); + if (vn) { + _version_number = raw_convert<int>(vn->content()); + /* I decided to check for this number being non-negative on being set, and in the verifier, but not here */ + optional<string> vn_status = vn->optional_string_attribute("status"); + if (vn_status) { + _status = string_to_status (*vn_status); + } + } + + _chain = node->optional_string_child("Chain"); + _distributor = node->optional_string_child("Distributor"); + _facility = node->optional_string_child("Facility"); + + cxml::ConstNodePtr acv = node->optional_node_child("AlternateContentVersionList"); + if (acv) { + BOOST_FOREACH (cxml::ConstNodePtr i, acv->node_children("ContentVersion")) { + _content_versions.push_back (ContentVersion(i)); + } + } + + cxml::ConstNodePtr lum = node->optional_node_child("Luminance"); + if (lum) { + _luminance = Luminance (lum); + } + + _main_sound_configuration = node->string_child("MainSoundConfiguration"); + + string sr = node->string_child("MainSoundSampleRate"); + vector<string> sr_bits; + boost::split (sr_bits, sr, boost::is_any_of(" ")); + DCP_ASSERT (sr_bits.size() == 2); + _main_sound_sample_rate = raw_convert<int>(sr_bits[0]); + + _main_picture_stored_area = dcp::Size ( + node->node_child("MainPictureStoredArea")->number_child<int>("Width"), + node->node_child("MainPictureStoredArea")->number_child<int>("Height") + ); + + _main_picture_active_area = dcp::Size ( + node->node_child("MainPictureActiveArea")->number_child<int>("Width"), + node->node_child("MainPictureActiveArea")->number_child<int>("Height") + ); + + optional<string> sll = node->optional_string_child("MainSubtitleLanguageList"); + if (sll) { + vector<string> sll_split; + boost::split (sll_split, *sll, boost::is_any_of(" ")); + DCP_ASSERT (!sll_split.empty()); + + /* If the first language on SubtitleLanguageList is the same as the language of the first subtitle we'll ignore it */ + size_t first = 0; + if (!_reels.empty()) { + shared_ptr<dcp::ReelSubtitleAsset> sub = _reels.front()->main_subtitle(); + if (sub) { + optional<dcp::LanguageTag> lang = sub->language(); + if (lang && lang->to_string() == sll_split[0]) { + first = 1; + } + } + } + + for (size_t i = first; i < sll_split.size(); ++i) { + _additional_subtitle_languages.push_back (sll_split[i]); + } + } +} + + +/** Write a CompositionMetadataAsset node as a child of @param node provided + * the required metadata is stored in the object. If any required metadata + * is missing this method will do nothing. + */ +void +CPL::maybe_write_composition_metadata_asset (xmlpp::Element* node) const +{ + if ( + !_main_sound_configuration || + !_main_sound_sample_rate || + !_main_picture_stored_area || + !_main_picture_active_area || + _reels.empty() || + !_reels.front()->main_picture()) { + return; + } + + xmlpp::Element* meta = node->add_child("meta:CompositionMetadataAsset"); + meta->set_namespace_declaration (cpl_metadata_ns, "meta"); + + meta->add_child("Id")->add_child_text("urn:uuid:" + make_uuid()); + + shared_ptr<dcp::ReelPictureAsset> mp = _reels.front()->main_picture(); + meta->add_child("EditRate")->add_child_text(mp->edit_rate().as_string()); + meta->add_child("IntrinsicDuration")->add_child_text(raw_convert<string>(mp->intrinsic_duration())); + + xmlpp::Element* fctt = meta->add_child("FullContentTitleText", "meta"); + if (_full_content_title_text) { + fctt->add_child_text (*_full_content_title_text); + } + if (_full_content_title_text_language) { + fctt->set_attribute("language", *_full_content_title_text_language); + } + + if (_release_territory) { + meta->add_child("ReleaseTerritory", "meta")->add_child_text(*_release_territory); + } + + if (_version_number) { + xmlpp::Element* vn = meta->add_child("VersionNumber", "meta"); + vn->add_child_text(raw_convert<string>(*_version_number)); + if (_status) { + vn->set_attribute("status", status_to_string(*_status)); + } + } + + if (_chain) { + meta->add_child("Chain", "meta")->add_child_text(*_chain); + } + + if (_distributor) { + meta->add_child("Distributor", "meta")->add_child_text(*_distributor); + } + + if (_facility) { + meta->add_child("Facility", "meta")->add_child_text(*_facility); + } + + if (_content_versions.size() > 1) { + xmlpp::Element* vc = meta->add_child("AlternateContentVersionList", "meta"); + for (size_t i = 1; i < _content_versions.size(); ++i) { + _content_versions[i].as_xml (vc); + } + } + + if (_luminance) { + _luminance->as_xml (meta, "meta"); + } + + meta->add_child("MainSoundConfiguration", "meta")->add_child_text(*_main_sound_configuration); + meta->add_child("MainSoundSampleRate", "meta")->add_child_text(raw_convert<string>(*_main_sound_sample_rate) + " 1"); + + xmlpp::Element* stored = meta->add_child("MainPictureStoredArea", "meta"); + stored->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->width)); + stored->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->height)); + + xmlpp::Element* active = meta->add_child("MainPictureActiveArea", "meta"); + active->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->width)); + active->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->height)); + + optional<dcp::LanguageTag> first_subtitle_language; + BOOST_FOREACH (shared_ptr<const Reel> i, _reels) { + if (i->main_subtitle()) { + first_subtitle_language = i->main_subtitle()->language(); + if (first_subtitle_language) { + break; + } + } + } + + if (first_subtitle_language || !_additional_subtitle_languages.empty()) { + string lang; + if (first_subtitle_language) { + lang = first_subtitle_language->to_string(); + } + BOOST_FOREACH (dcp::LanguageTag const& i, _additional_subtitle_languages) { + if (!lang.empty()) { + lang += " "; + } + lang += i.to_string(); + } + meta->add_child("MainSubtitleLanguageList")->add_child_text(lang); + } +} + + list<shared_ptr<ReelMXF> > CPL::reel_mxfs () { @@ -337,6 +547,19 @@ CPL::duration () const } return d; } + + +void +CPL::set_version_number (int v) +{ + if (v < 0) { + throw BadSettingError ("CPL version number cannot be negative"); + } + + _version_number = v; +} + + void CPL::set_content_versions (vector<ContentVersion> v) { @@ -357,3 +580,13 @@ CPL::content_version () const DCP_ASSERT (!_content_versions.empty()); return _content_versions[0]; } + + +void +CPL::set_additional_subtitle_languages (vector<dcp::LanguageTag> const& langs) +{ + _additional_subtitle_languages.clear (); + BOOST_FOREACH (dcp::LanguageTag const& i, langs) { + _additional_subtitle_languages.push_back (i.to_string()); + } +} @@ -44,6 +44,7 @@ #include "asset.h" #include "certificate.h" #include "key.h" +#include "language_tag.h" #include "types.h" #include <boost/filesystem.hpp> #include <boost/function.hpp> @@ -160,6 +161,114 @@ public: _ratings = r; } + boost::optional<std::string> full_content_title_text () const { + return _full_content_title_text; + } + + void set_full_content_title_text (std::string t) { + _full_content_title_text = t; + } + + boost::optional<std::string> full_content_title_text_language () const { + return _full_content_title_text_language; + } + + void set_full_content_title_text_language (dcp::LanguageTag l) { + _full_content_title_text_language = l.to_string(); + } + + boost::optional<std::string> release_territory () const { + return _release_territory; + } + + void set_release_territory (dcp::LanguageTag::RegionSubtag t) { + _release_territory = t.subtag(); + } + + boost::optional<int> version_number () const { + return _version_number; + } + + void set_version_number (int v); + + boost::optional<Status> status () const { + return _status; + } + + void set_status (Status s) { + _status = s; + } + + boost::optional<std::string> chain () const { + return _chain; + } + + void set_chain (std::string c) { + _chain = c; + } + + boost::optional<std::string> distributor () const { + return _distributor; + } + + void set_distributor (std::string d) { + _distributor = d; + } + + boost::optional<std::string> facility () const { + return _facility; + } + + void set_facility (std::string f) { + _facility = f; + } + + boost::optional<Luminance> luminance () const { + return _luminance; + } + + void set_luminance (Luminance l) { + _luminance = l; + } + + boost::optional<std::string> main_sound_configuration () const { + return _main_sound_configuration; + } + + void set_main_sound_configuration (std::string c) { + _main_sound_configuration = c; + } + + boost::optional<int> main_sound_sample_rate () const { + return _main_sound_sample_rate; + } + + void set_main_sound_sample_rate (int r) { + _main_sound_sample_rate = r; + } + + boost::optional<dcp::Size> main_picture_stored_area () const { + return _main_picture_stored_area; + } + + void set_main_picture_stored_area (dcp::Size s) { + _main_picture_stored_area = s; + } + + boost::optional<dcp::Size> main_picture_active_area () const { + return _main_picture_active_area; + } + + void set_main_picture_active_area (dcp::Size s) { + _main_picture_active_area = s; + } + + std::vector<std::string> additional_subtitle_languages () const { + return _additional_subtitle_languages; + } + + void set_additional_subtitle_languages (std::vector<dcp::LanguageTag> const& lang); + boost::optional<Standard> standard () const { return _standard; } @@ -171,6 +280,9 @@ protected: std::string pkl_type (Standard standard) const; private: + void maybe_write_composition_metadata_asset (xmlpp::Element* node) const; + void read_composition_metadata_asset (cxml::ConstNodePtr node); + std::string _issuer; std::string _creator; std::string _issue_date; @@ -179,6 +291,25 @@ private: ContentKind _content_kind; ///< <ContentKind> std::vector<ContentVersion> _content_versions; std::vector<Rating> _ratings; + /** Human-readable name of the composition, without any metadata (i.e. no -FTR-EN-XX- etc.) */ + boost::optional<std::string> _full_content_title_text; + boost::optional<std::string> _full_content_title_text_language; + /** This is stored and returned as a string so that we can tolerate non-RFC-5646 strings, + * but must be set as a dcp::LanguageTag to try to ensure that we create compliant output. + */ + boost::optional<std::string> _release_territory; + boost::optional<int> _version_number; + boost::optional<Status> _status; + boost::optional<std::string> _chain; + boost::optional<std::string> _distributor; + boost::optional<std::string> _facility; + boost::optional<Luminance> _luminance; + boost::optional<std::string> _main_sound_configuration; + boost::optional<int> _main_sound_sample_rate; + boost::optional<dcp::Size> _main_picture_stored_area; + boost::optional<dcp::Size> _main_picture_active_area; + /* See note for _release_territory above */ + std::vector<std::string> _additional_subtitle_languages; std::list<boost::shared_ptr<Reel> > _reels; diff --git a/src/exceptions.cc b/src/exceptions.cc index 70320b52..16676e20 100644 --- a/src/exceptions.cc +++ b/src/exceptions.cc @@ -137,6 +137,30 @@ CombineError::CombineError (string message) : runtime_error (message) {} + LanguageTagError::LanguageTagError (std::string message) : runtime_error (message) {} + + +BadSettingError::BadSettingError (std::string message) + : runtime_error (message) +{ + +} + + +DuplicateIdError::DuplicateIdError (std::string message) + : runtime_error (message) +{ + +} + + +MainSoundConfigurationError::MainSoundConfigurationError (std::string s) + : runtime_error (String::compose("Could not parse MainSoundConfiguration %1", s)) +{ + +} + +>>>>>>> Support CPL metadata. diff --git a/src/exceptions.h b/src/exceptions.h index 79ee9fca..12624175 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -254,6 +254,28 @@ public: LanguageTagError (std::string message); }; + +class BadSettingError : public std::runtime_error +{ +public: + BadSettingError (std::string message); +}; + + +class DuplicateIdError : public std::runtime_error +{ +public: + DuplicateIdError (std::string message); +}; + + +class MainSoundConfigurationError : public std::runtime_error +{ +public: + MainSoundConfigurationError (std::string s); +}; + + } #endif diff --git a/src/language_tag.cc b/src/language_tag.cc index b64dab72..4ac90507 100644 --- a/src/language_tag.cc +++ b/src/language_tag.cc @@ -41,6 +41,9 @@ #include <string> +using std::make_pair; +using std::ostream; +using std::pair; using std::string; using std::vector; using boost::optional; @@ -64,31 +67,10 @@ find_in_list (LanguageTag::SubtagData* list, int length, string subtag) } -static -optional<LanguageTag::SubtagData> -find_in_list (LanguageTag::SubtagType type, string subtag) -{ - switch (type) { - case dcp::LanguageTag::LANGUAGE: - return find_in_list(language_list, sizeof(language_list) / sizeof(LanguageTag::SubtagData), subtag); - case dcp::LanguageTag::SCRIPT: - return find_in_list(script_list, sizeof(script_list) / sizeof(LanguageTag::SubtagData), subtag); - case dcp::LanguageTag::REGION: - return find_in_list(region_list, sizeof(region_list) / sizeof(LanguageTag::SubtagData), subtag); - case dcp::LanguageTag::VARIANT: - return find_in_list(variant_list, sizeof(variant_list) / sizeof(LanguageTag::SubtagData), subtag); - case dcp::LanguageTag::EXTLANG: - return find_in_list(extlang_list, sizeof(extlang_list) / sizeof(LanguageTag::SubtagData), subtag); - } - - return optional<LanguageTag::SubtagData>(); -} - - LanguageTag::Subtag::Subtag (string subtag, SubtagType type) : _subtag (subtag) { - if (!find_in_list(type, subtag)) { + if (!get_subtag_data(type, subtag)) { throw LanguageTagError(String::compose("Unknown %1 string %2", subtag_type_name(type), subtag)); } } @@ -268,29 +250,29 @@ LanguageTag::description () const string d; BOOST_FOREACH (VariantSubtag const& i, _variants) { - optional<SubtagData> variant = find_in_list (VARIANT, i.subtag()); + optional<SubtagData> variant = get_subtag_data (VARIANT, i.subtag()); DCP_ASSERT (variant); d += variant->description + " dialect of "; } - optional<SubtagData> language = find_in_list (LANGUAGE, _language->subtag()); + optional<SubtagData> language = get_subtag_data (LANGUAGE, _language->subtag()); DCP_ASSERT (language); d += language->description; if (_script) { - optional<SubtagData> script = find_in_list (SCRIPT, _script->subtag()); + optional<SubtagData> script = get_subtag_data (SCRIPT, _script->subtag()); DCP_ASSERT (script); d += " written using the " + script->description + " script"; } if (_region) { - optional<SubtagData> region = find_in_list (REGION, _region->subtag()); + optional<SubtagData> region = get_subtag_data (REGION, _region->subtag()); DCP_ASSERT (region); d += " for " + region->description; } BOOST_FOREACH (ExtlangSubtag const& i, _extlangs) { - optional<SubtagData> extlang = find_in_list (EXTLANG, i.subtag()); + optional<SubtagData> extlang = get_subtag_data (EXTLANG, i.subtag()); DCP_ASSERT (extlang); d += ", " + extlang->description; } @@ -381,3 +363,80 @@ dcp::LanguageTag::ExtlangSubtag::operator< (ExtlangSubtag const & other) const { return subtag() < other.subtag(); } + + +bool +dcp::operator== (dcp::LanguageTag const& a, dcp::LanguageTag const& b) +{ + return a.to_string() == b.to_string(); +} + + +ostream& +dcp::operator<< (ostream& os, dcp::LanguageTag const& tag) +{ + os << tag.to_string(); + return os; +} + + +vector<pair<LanguageTag::SubtagType, LanguageTag::SubtagData> > +LanguageTag::subtags () const +{ + vector<pair<SubtagType, SubtagData> > s; + + if (_language) { + s.push_back (make_pair(LANGUAGE, *get_subtag_data(LANGUAGE, _language->subtag()))); + } + + if (_script) { + s.push_back (make_pair(SCRIPT, *get_subtag_data(SCRIPT, _script->subtag()))); + } + + if (_region) { + s.push_back (make_pair(REGION, *get_subtag_data(REGION, _region->subtag()))); + } + + BOOST_FOREACH (VariantSubtag const& i, _variants) { + s.push_back (make_pair(VARIANT, *get_subtag_data(VARIANT, i.subtag()))); + } + + BOOST_FOREACH (ExtlangSubtag const& i, _extlangs) { + s.push_back (make_pair(EXTLANG, *get_subtag_data(EXTLANG, i.subtag()))); + } + + return s; +} + + +optional<LanguageTag::SubtagData> +LanguageTag::get_subtag_data (LanguageTag::SubtagType type, string subtag) +{ + switch (type) { + case dcp::LanguageTag::LANGUAGE: + return find_in_list(language_list, sizeof(language_list) / sizeof(LanguageTag::SubtagData), subtag); + case dcp::LanguageTag::SCRIPT: + return find_in_list(script_list, sizeof(script_list) / sizeof(LanguageTag::SubtagData), subtag); + case dcp::LanguageTag::REGION: + return find_in_list(region_list, sizeof(region_list) / sizeof(LanguageTag::SubtagData), subtag); + case dcp::LanguageTag::VARIANT: + return find_in_list(variant_list, sizeof(variant_list) / sizeof(LanguageTag::SubtagData), subtag); + case dcp::LanguageTag::EXTLANG: + return find_in_list(extlang_list, sizeof(extlang_list) / sizeof(LanguageTag::SubtagData), subtag); + } + + return optional<LanguageTag::SubtagData>(); +} + + +optional<string> +LanguageTag::get_subtag_description (LanguageTag::SubtagType type, string subtag) +{ + optional<SubtagData> data = get_subtag_data (type, subtag); + if (!data) { + return optional<string>(); + } + + return data->description; +} + diff --git a/src/language_tag.h b/src/language_tag.h index 8aa8a723..74b4a8a7 100644 --- a/src/language_tag.h +++ b/src/language_tag.h @@ -82,6 +82,12 @@ public: return _subtag; } + virtual SubtagType type () const = 0; + + bool operator== (Subtag const& other) { + return _subtag == other._subtag; + } + protected: Subtag (std::string subtag, SubtagType type); @@ -96,6 +102,10 @@ public: : Subtag(subtag, LANGUAGE) {} LanguageSubtag (char const* subtag) : Subtag(subtag, LANGUAGE) {} + + SubtagType type () const { + return LANGUAGE; + } }; class ScriptSubtag : public Subtag @@ -105,6 +115,10 @@ public: : Subtag(subtag, SCRIPT) {} ScriptSubtag (char const* subtag) : Subtag(subtag, SCRIPT) {} + + SubtagType type () const { + return SCRIPT; + } }; class RegionSubtag : public Subtag @@ -114,6 +128,10 @@ public: : Subtag(subtag, REGION) {} RegionSubtag (char const* subtag) : Subtag(subtag, REGION) {} + + SubtagType type () const { + return REGION; + } }; class VariantSubtag : public Subtag @@ -124,6 +142,10 @@ public: VariantSubtag (char const* subtag) : Subtag(subtag, VARIANT) {} + SubtagType type () const { + return VARIANT; + } + bool operator== (VariantSubtag const& other) const; bool operator< (VariantSubtag const& other) const; }; @@ -137,6 +159,10 @@ public: ExtlangSubtag (char const* subtag) : Subtag(subtag, EXTLANG) {} + SubtagType type () const { + return EXTLANG; + } + bool operator== (ExtlangSubtag const& other) const; bool operator< (ExtlangSubtag const& other) const; }; @@ -144,18 +170,58 @@ public: LanguageTag () {} LanguageTag (std::string tag); + boost::optional<LanguageSubtag> language() { + return _language; + } + void set_language (LanguageSubtag language); + + boost::optional<ScriptSubtag> script() const { + return _script; + } + void set_script (ScriptSubtag script); + + boost::optional<RegionSubtag> region() const { + return _region; + } + void set_region (RegionSubtag region); + + std::vector<VariantSubtag> variants() const { + return _variants; + } + void set_variants (std::vector<VariantSubtag> variants); void add_variant (VariantSubtag variant); + + std::vector<ExtlangSubtag> extlangs() const { + return _extlangs; + } + void set_extlangs (std::vector<ExtlangSubtag> extlangs); void add_extlang (ExtlangSubtag extlang); + std::vector<std::pair<SubtagType, SubtagData> > subtags () const; + static std::vector<SubtagData> get_all (SubtagType type); static std::string subtag_type_name (SubtagType type); + static boost::optional<std::string> get_subtag_description (SubtagType, std::string subtag); + static boost::optional<SubtagData > get_subtag_data (SubtagType, std::string subtag); + + template <class T> + static boost::optional<std::string> get_subtag_description (T s) { + return get_subtag_description (s.type(), s.subtag()); + } + + template <class T> + static boost::optional<SubtagData> get_subtag_data (T s) { + return get_subtag_data (s.type(), s.subtag()); + } + private: + boost::optional<LanguageSubtag> _language; boost::optional<ScriptSubtag> _script; boost::optional<RegionSubtag> _region; @@ -163,6 +229,8 @@ private: std::vector<ExtlangSubtag> _extlangs; }; +extern bool operator==(dcp::LanguageTag const& a, dcp::LanguageTag const& b); +extern std::ostream& operator<<(std::ostream& os, dcp::LanguageTag const& tag); } diff --git a/src/reel.cc b/src/reel.cc index 453a3163..d3e2a850 100644 --- a/src/reel.cc +++ b/src/reel.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2014-2017 Carl Hetherington <cth@carlh.net> + Copyright (C) 2014-2020 Carl Hetherington <cth@carlh.net> This file is part of libdcp. @@ -115,7 +115,7 @@ Reel::Reel (boost::shared_ptr<const cxml::Node> node) node->done (); } -void +xmlpp::Element * Reel::write_to_cpl (xmlpp::Element* node, Standard standard) const { xmlpp::Element* reel = node->add_child ("Reel"); @@ -151,6 +151,8 @@ Reel::write_to_cpl (xmlpp::Element* node, Standard standard) const if (_atmos) { _atmos->write_to_cpl (asset_list, standard); } + + return asset_list; } bool @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net> This file is part of libdcp. @@ -113,7 +113,7 @@ public: std::list<boost::shared_ptr<ReelAsset> > assets () const; - void write_to_cpl (xmlpp::Element* node, Standard standard) const; + xmlpp::Element* write_to_cpl (xmlpp::Element* node, Standard standard) const; bool encrypted () const; diff --git a/src/reel_subtitle_asset.cc b/src/reel_subtitle_asset.cc index 4fa9fd0a..f7df36f8 100644 --- a/src/reel_subtitle_asset.cc +++ b/src/reel_subtitle_asset.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net> This file is part of libdcp. @@ -35,6 +35,7 @@ * @brief ReelSubtitleAsset class. */ +#include "language_tag.h" #include "subtitle_asset.h" #include "reel_subtitle_asset.h" #include "smpte_subtitle_asset.h" @@ -57,7 +58,10 @@ ReelSubtitleAsset::ReelSubtitleAsset (boost::shared_ptr<const cxml::Node> node) : ReelAsset (node) , ReelMXF (node) { - node->ignore_child ("Language"); + optional<string> const language = node->optional_string_child("Language"); + if (language) { + _language = dcp::LanguageTag(*language); + } node->done (); } @@ -78,6 +82,9 @@ ReelSubtitleAsset::write_to_cpl (xmlpp::Node* node, Standard standard) const { xmlpp::Node* asset = write_to_cpl_asset (node, standard, hash()); write_to_cpl_mxf (asset); + if (_language) { + asset->add_child("Language")->add_child_text(_language->to_string()); + } return asset; } diff --git a/src/reel_subtitle_asset.h b/src/reel_subtitle_asset.h index 4cc03cf1..123478cf 100644 --- a/src/reel_subtitle_asset.h +++ b/src/reel_subtitle_asset.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net> + Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net> This file is part of libdcp. @@ -38,6 +38,7 @@ #ifndef LIBDCP_REEL_SUBTITLE_ASSET_H #define LIBDCP_REEL_SUBTITLE_ASSET_H +#include "language_tag.h" #include "reel_asset.h" #include "reel_mxf.h" #include "subtitle_asset.h" @@ -62,9 +63,17 @@ public: return asset_of_type<SubtitleAsset> (); } + void set_language (dcp::LanguageTag language); + + boost::optional<dcp::LanguageTag> language () const { + return _language; + } + private: std::string key_type () const; std::string cpl_node_name (Standard standard) const; + + boost::optional<dcp::LanguageTag> _language; }; } diff --git a/src/types.cc b/src/types.cc index 746b6f6a..9d67ee67 100644 --- a/src/types.cc +++ b/src/types.cc @@ -38,11 +38,16 @@ #include "dcp_assert.h" #include <libxml++/libxml++.h> #include <boost/algorithm/string.hpp> +#include <boost/foreach.hpp> +#include <string> #include <vector> +#include <cmath> #include <cstdio> #include <iomanip> -using namespace std; +using std::string; +using std::ostream; +using std::vector; using namespace dcp; using namespace boost; @@ -464,6 +469,28 @@ dcp::operator<< (ostream& s, Rating const & r) } +ContentVersion::ContentVersion () + : id ("urn:uuid:" + make_uuid()) +{ + +} + + +ContentVersion::ContentVersion (cxml::ConstNodePtr node) +{ + id = node->string_child("Id"); + label_text = node->string_child("LabelText"); +} + + +ContentVersion::ContentVersion (string label_text_) + : id ("urn:uuid:" + make_uuid()) + , label_text (label_text_) +{ + +} + + void ContentVersion::as_xml (xmlpp::Element* parent) const { @@ -472,3 +499,261 @@ ContentVersion::as_xml (xmlpp::Element* parent) const cv->add_child("LabelText")->add_child_text(label_text); } + +Luminance::Luminance (cxml::ConstNodePtr node) +{ + _unit = string_to_unit (node->string_attribute("units")); + _value = raw_convert<float> (node->content()); +} + + +Luminance::Luminance (float value, Unit unit) + : _unit (unit) +{ + set_value (value); +} + + +void +Luminance::set_value (float v) +{ + if (v < 0) { + throw dcp::MiscError (String::compose("Invalid luminance value %1", v)); + } + + _value = v; +} + + +void +Luminance::as_xml (xmlpp::Element* parent, string ns) const +{ + xmlpp::Element* lum = parent->add_child("Luminance", ns); + lum->set_attribute("units", unit_to_string(_unit)); + lum->add_child_text(raw_convert<string>(_value, 3)); +} + + +string +Luminance::unit_to_string (Unit u) +{ + switch (u) { + case CANDELA_PER_SQUARE_METRE: + return "candela-per-square-metre"; + case FOOT_LAMBERT: + return "foot-lambert"; + default: + DCP_ASSERT (false); + } + + return ""; +} + + +Luminance::Unit +Luminance::string_to_unit (string u) +{ + if (u == "candela-per-square-metre") { + return Unit::CANDELA_PER_SQUARE_METRE; + } else if (u == "foot-lambert") { + return Unit::FOOT_LAMBERT; + } + + throw XMLError (String::compose("Invalid luminance unit %1", u)); +} + + +bool +dcp::operator== (Luminance const& a, Luminance const& b) +{ + return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit(); +} + + +MainSoundConfiguration::MainSoundConfiguration (string s) +{ + vector<string> parts; + boost::split (parts, s, boost::is_any_of("/")); + if (parts.size() != 2) { + throw MainSoundConfigurationError (s); + } + + if (parts[0] == "51") { + _field = FIVE_POINT_ONE; + } else if (parts[0] == "71") { + _field = SEVEN_POINT_ONE; + } else { + throw MainSoundConfigurationError (s); + } + + vector<string> channels; + boost::split (channels, parts[1], boost::is_any_of(",")); + + if (channels.size() > 16) { + throw MainSoundConfigurationError (s); + } + + BOOST_FOREACH (string i, channels) { + if (i == "-") { + _channels.push_back(optional<Channel>()); + } else if (i == "L") { + _channels.push_back(LEFT); + } else if (i == "R") { + _channels.push_back(RIGHT); + } else if (i == "C") { + _channels.push_back(CENTRE); + } else if (i == "LFE") { + _channels.push_back(LFE); + } else if (i == "Ls" || i == "Lss") { + _channels.push_back(LS); + } else if (i == "Rs" || i == "Rss") { + _channels.push_back(RS); + } else if (i == "HI") { + _channels.push_back(HI); + } else if (i == "VIN") { + _channels.push_back(VI); + } else if (i == "Lrs") { + _channels.push_back(BSL); + } else if (i == "Rrs") { + _channels.push_back(BSR); + } else if (i == "DBOX") { + _channels.push_back(MOTION_DATA); + } else if (i == "Sync") { + _channels.push_back(SYNC_SIGNAL); + } else if (i == "Sign") { + _channels.push_back(SIGN_LANGUAGE); + } else { + throw MainSoundConfigurationError (s); + } + } +} + + +MainSoundConfiguration::MainSoundConfiguration (Field field, int channels) + : _field (field) +{ + _channels.resize (channels); +} + + +string +MainSoundConfiguration::to_string () const +{ + string c; + if (_field == FIVE_POINT_ONE) { + c = "51/"; + } else { + c = "71/"; + } + + BOOST_FOREACH (optional<Channel> i, _channels) { + if (!i) { + c += "-,"; + } else { + switch (*i) { + case LEFT: + c += "L,"; + break; + case RIGHT: + c += "R,"; + break; + case CENTRE: + c += "C,"; + break; + case LFE: + c += "LFE,"; + break; + case LS: + c += (_field == FIVE_POINT_ONE ? "Ls," : "Lss,"); + break; + case RS: + c += (_field == FIVE_POINT_ONE ? "Rs," : "Rss,"); + break; + case HI: + c += "HI,"; + break; + case VI: + c += "VIN,"; + break; + case LC: + case RC: + c += "-,"; + break; + case BSL: + c += "Lrs,"; + break; + case BSR: + c += "Rrs,"; + break; + case MOTION_DATA: + c += "DBOX,"; + break; + case SYNC_SIGNAL: + c += "Sync,"; + break; + case SIGN_LANGUAGE: + /* XXX: not sure what this should be */ + c += "Sign,"; + break; + default: + c += "-,"; + break; + } + } + } + + if (c.length() > 0) { + c = c.substr(0, c.length() - 1); + } + + return c; +} + + +optional<Channel> +MainSoundConfiguration::mapping (int index) const +{ + DCP_ASSERT (static_cast<size_t>(index) < _channels.size()); + return _channels[index]; +} + + +void +MainSoundConfiguration::set_mapping (int index, Channel c) +{ + DCP_ASSERT (static_cast<size_t>(index) < _channels.size()); + _channels[index] = c; +} + + +string +dcp::status_to_string (Status s) +{ + switch (s) { + case FINAL: + return "final"; + case TEMP: + return "temp"; + case PRE: + return "pre"; + default: + DCP_ASSERT (false); + } + +} + + +Status +dcp::string_to_status (string s) +{ + if (s == "final") { + return FINAL; + } else if (s == "temp") { + return TEMP; + } else if (s == "pre") { + return PRE; + } + + DCP_ASSERT (false); +} + diff --git a/src/types.h b/src/types.h index f8a9f45e..9a4b3e13 100644 --- a/src/types.h +++ b/src/types.h @@ -328,10 +328,26 @@ extern bool operator== (Rating const & a, Rating const & b); extern std::ostream& operator<< (std::ostream& s, Rating const & r); +enum Status +{ + FINAL, ///< final version + TEMP, ///< temporary version (picture/sound unfinished) + PRE ///< pre-release (picture/sound finished) +}; + + +extern std::string status_to_string (Status s); +extern Status string_to_status (std::string s); + + class ContentVersion { public: - ContentVersion () {} + ContentVersion (); + + explicit ContentVersion (cxml::ConstNodePtr node); + + explicit ContentVersion (std::string label_text_); ContentVersion (std::string id_, std::string label_text_) : id (id_) @@ -344,6 +360,75 @@ public: std::string label_text; }; + +class Luminance +{ +public: + enum Unit { + CANDELA_PER_SQUARE_METRE, + FOOT_LAMBERT + }; + + Luminance (cxml::ConstNodePtr node); + + Luminance (float value, Unit unit); + + void set_value (float v); + void set_unit (Unit u) { + _unit = u; + } + + float value () const { + return _value; + } + + Unit unit () const { + return _unit; + } + + void as_xml (xmlpp::Element* parent, std::string ns) const; + + static std::string unit_to_string (Unit u); + static Unit string_to_unit (std::string u); + +private: + float _value; + Unit _unit; +}; + +bool operator== (Luminance const& a, Luminance const& b); + + +class MainSoundConfiguration +{ +public: + enum Field { + FIVE_POINT_ONE, + SEVEN_POINT_ONE, + }; + + MainSoundConfiguration (std::string); + MainSoundConfiguration (Field field_, int channels); + + Field field () const { + return _field; + } + + int channels () const { + return _channels.size(); + } + + boost::optional<Channel> mapping (int index) const; + void set_mapping (int index, Channel channel); + + std::string to_string () const; + +private: + Field _field; + std::vector<boost::optional<Channel> > _channels; +}; + + } #endif |
