Add Dolby EDR metadata support (GH #12).
[libdcp.git] / src / cpl.cc
index f3cebe3713d9aebcd4c82407d7467b1d13fae859..5ff86fda7baff36b2e397a9c7dc934f7e1d7740c 100644 (file)
@@ -41,6 +41,8 @@
 #include "compose.hpp"
 #include "cpl.h"
 #include "dcp_assert.h"
+#include "equality_options.h"
+#include "filesystem.h"
 #include "local_time.h"
 #include "metadata.h"
 #include "raw_convert.h"
@@ -107,7 +109,7 @@ CPL::CPL (boost::filesystem::path file)
        , _content_kind (ContentKind::FEATURE)
 {
        cxml::Document f ("CompositionPlaylist");
-       f.read_file (file);
+       f.read_file(dcp::filesystem::fix_long_path(file));
 
        if (f.namespace_uri() == cpl_interop_ns) {
                _standard = Standard::INTEROP;
@@ -182,7 +184,7 @@ CPL::set (std::vector<std::shared_ptr<Reel>> reels)
 
 
 void
-CPL::write_xml (boost::filesystem::path file, shared_ptr<const CertificateChain> signer) const
+CPL::write_xml(boost::filesystem::path file, shared_ptr<const CertificateChain> signer, bool include_mca_subdescriptors) const
 {
        xmlpp::Document doc;
        xmlpp::Element* root;
@@ -227,7 +229,7 @@ CPL::write_xml (boost::filesystem::path file, shared_ptr<const CertificateChain>
        for (auto i: _reels) {
                auto asset_list = i->write_to_cpl (reel_list, _standard);
                if (first && _standard == Standard::SMPTE) {
-                       maybe_write_composition_metadata_asset (asset_list);
+                       maybe_write_composition_metadata_asset(asset_list, include_mca_subdescriptors);
                        first = false;
                }
        }
@@ -238,7 +240,7 @@ CPL::write_xml (boost::filesystem::path file, shared_ptr<const CertificateChain>
                signer->sign (root, _standard);
        }
 
-       doc.write_to_file_formatted (file.string(), "UTF-8");
+       doc.write_to_file_formatted(dcp::filesystem::fix_long_path(file).string(), "UTF-8");
 
        set_file (file);
 }
@@ -288,7 +290,16 @@ CPL::read_composition_metadata_asset (cxml::ConstNodePtr node)
                _luminance = Luminance (lum);
        }
 
-       _main_sound_configuration = node->optional_string_child("MainSoundConfiguration");
+       if (auto msc = node->optional_string_child("MainSoundConfiguration")) {
+               try {
+                       _main_sound_configuration = MainSoundConfiguration(*msc);
+               } catch (MainSoundConfigurationError& e) {
+                       /* With Interop DCPs this node may not make any sense, but that's OK */
+                       if (_standard == dcp::Standard::SMPTE) {
+                               throw e;
+                       }
+               }
+       }
 
        auto sr = node->optional_string_child("MainSoundSampleRate");
        if (sr) {
@@ -334,21 +345,32 @@ CPL::read_composition_metadata_asset (cxml::ConstNodePtr node)
        }
 
        auto eml = node->optional_node_child ("ExtensionMetadataList");
-       if (eml) {
+
+       auto extension_metadata = [eml](string scope, string name, string property) -> boost::optional<std::string> {
+               if (!eml) {
+                       return {};
+               }
+
                for (auto i: eml->node_children("ExtensionMetadata")) {
-                       auto name = i->optional_string_child("Name");
-                       if (name && *name == "Sign Language Video") {
+                       auto xml_scope = i->optional_string_attribute("scope");
+                       auto xml_name = i->optional_string_child("Name");
+                       if (xml_scope && *xml_scope == scope && xml_name && *xml_name == name) {
                                auto property_list = i->node_child("PropertyList");
                                for (auto j: property_list->node_children("Property")) {
-                                       auto name = j->optional_string_child("Name");
-                                       auto value = j->optional_string_child("Value");
-                                       if (name && value && *name == "Language Tag") {
-                                               _sign_language_video_language = *value;
+                                       auto property_name = j->optional_string_child("Name");
+                                       auto property_value = j->optional_string_child("Value");
+                                       if (property_name && property_value && *property_name == property) {
+                                               return property_value;
                                        }
                                }
                        }
                }
-       }
+
+               return {};
+       };
+
+       _sign_language_video_language = extension_metadata("http://isdcf.com/2017/10/SignLanguageVideo", "Sign Language Video", "Language Tag");
+       _dolby_edr_image_transfer_function = extension_metadata("http://www.dolby.com/schemas/2014/EDR-Metadata", "Dolby EDR", "image transfer function");
 }
 
 
@@ -385,6 +407,7 @@ CPL::write_mca_subdescriptors(xmlpp::Element* parent, shared_ptr<const SoundAsse
                        sf->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
                }
 
+               /* Find the MCA subdescriptors in the MXF so that we can also write them here */
                list<ASDCP::MXF::InterchangeObject*> channels;
                auto r = reader->reader()->OP1aHeader().GetMDObjectsByType(
                        asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
@@ -393,9 +416,6 @@ CPL::write_mca_subdescriptors(xmlpp::Element* parent, shared_ptr<const SoundAsse
 
                for (auto i: channels) {
                        auto channel = reinterpret_cast<ASDCP::MXF::AudioChannelLabelSubDescriptor*>(i);
-                       if (static_cast<int>(channel->MCAChannelID) > asset->channels()) {
-                               continue;
-                       }
                        auto ch = mca_subs->add_child("AudioChannelLabelSubDescriptor", "r0");
                        channel->InstanceUID.EncodeString(buffer, sizeof(buffer));
                        ch->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
@@ -430,7 +450,7 @@ CPL::write_mca_subdescriptors(xmlpp::Element* parent, shared_ptr<const SoundAsse
  *  is missing this method will do nothing.
  */
 void
-CPL::maybe_write_composition_metadata_asset (xmlpp::Element* node) const
+CPL::maybe_write_composition_metadata_asset(xmlpp::Element* node, bool include_mca_subdescriptors) const
 {
        if (
                !_main_sound_configuration ||
@@ -494,7 +514,9 @@ CPL::maybe_write_composition_metadata_asset (xmlpp::Element* node) const
                _luminance->as_xml (meta, "meta");
        }
 
-       meta->add_child("MainSoundConfiguration", "meta")->add_child_text(*_main_sound_configuration);
+       if (_main_sound_configuration) {
+               meta->add_child("MainSoundConfiguration", "meta")->add_child_text(_main_sound_configuration->to_string());
+       }
        meta->add_child("MainSoundSampleRate", "meta")->add_child_text(raw_convert<string>(*_main_sound_sample_rate) + " 1");
 
        auto stored = meta->add_child("MainPictureStoredArea", "meta");
@@ -547,9 +569,13 @@ CPL::maybe_write_composition_metadata_asset (xmlpp::Element* node) const
                add_extension_metadata ("http://isdcf.com/2017/10/SignLanguageVideo", "Sign Language Video", "Language Tag", *_sign_language_video_language);
        }
 
+       if (_dolby_edr_image_transfer_function) {
+               add_extension_metadata("http://www.dolby.com/schemas/2014/EDR-Metadata", "Dolby EDR", "image transfer function", *_dolby_edr_image_transfer_function);
+       }
+
        if (_reels.front()->main_sound()) {
                auto asset = _reels.front()->main_sound()->asset();
-               if (asset) {
+               if (asset && include_mca_subdescriptors) {
                        write_mca_subdescriptors(meta, asset);
                }
        }
@@ -599,7 +625,7 @@ CPL::reel_file_assets () const
 
 
 bool
-CPL::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
+CPL::equals(shared_ptr<const Asset> other, EqualityOptions const& opt, NoteHandler note) const
 {
        auto other_cpl = dynamic_pointer_cast<const CPL>(other);
        if (!other_cpl) {