#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;
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 */
}
_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");
}
/** 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.
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);
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 ()
{
}
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)
{
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());
+ }
+}
#include "asset.h"
#include "certificate.h"
#include "key.h"
+#include "language_tag.h"
#include "types.h"
#include <boost/filesystem.hpp>
#include <boost/function.hpp>
_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;
}
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;
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;
: 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.
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
#include <string>
+using std::make_pair;
+using std::ostream;
+using std::pair;
using std::string;
using std::vector;
using boost::optional;
}
-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));
}
}
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;
}
{
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;
+}
+
return _subtag;
}
+ virtual SubtagType type () const = 0;
+
+ bool operator== (Subtag const& other) {
+ return _subtag == other._subtag;
+ }
+
protected:
Subtag (std::string subtag, SubtagType type);
: Subtag(subtag, LANGUAGE) {}
LanguageSubtag (char const* subtag)
: Subtag(subtag, LANGUAGE) {}
+
+ SubtagType type () const {
+ return LANGUAGE;
+ }
};
class ScriptSubtag : public Subtag
: Subtag(subtag, SCRIPT) {}
ScriptSubtag (char const* subtag)
: Subtag(subtag, SCRIPT) {}
+
+ SubtagType type () const {
+ return SCRIPT;
+ }
};
class RegionSubtag : public Subtag
: Subtag(subtag, REGION) {}
RegionSubtag (char const* subtag)
: Subtag(subtag, REGION) {}
+
+ SubtagType type () const {
+ return REGION;
+ }
};
class VariantSubtag : public Subtag
VariantSubtag (char const* subtag)
: Subtag(subtag, VARIANT) {}
+ SubtagType type () const {
+ return VARIANT;
+ }
+
bool operator== (VariantSubtag const& other) const;
bool operator< (VariantSubtag const& other) const;
};
ExtlangSubtag (char const* subtag)
: Subtag(subtag, EXTLANG) {}
+ SubtagType type () const {
+ return EXTLANG;
+ }
+
bool operator== (ExtlangSubtag const& other) const;
bool operator< (ExtlangSubtag const& other) const;
};
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;
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);
}
/*
- Copyright (C) 2014-2017 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2014-2020 Carl Hetherington <cth@carlh.net>
This file is part of libdcp.
node->done ();
}
-void
+xmlpp::Element *
Reel::write_to_cpl (xmlpp::Element* node, Standard standard) const
{
xmlpp::Element* reel = node->add_child ("Reel");
if (_atmos) {
_atmos->write_to_cpl (asset_list, standard);
}
+
+ return asset_list;
}
bool
/*
- Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
This file is part of libdcp.
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;
/*
- Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
This file is part of libdcp.
* @brief ReelSubtitleAsset class.
*/
+#include "language_tag.h"
#include "subtitle_asset.h"
#include "reel_subtitle_asset.h"
#include "smpte_subtitle_asset.h"
: 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 ();
}
{
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;
}
/*
- Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
This file is part of libdcp.
#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"
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;
};
}
#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;
}
+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
{
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);
+}
+
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_)
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
--- /dev/null
+/*
+ Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+ This file is part of libdcp.
+
+ libdcp is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ libdcp is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with libdcp. If not, see <http://www.gnu.org/licenses/>.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of portions of this program with the
+ OpenSSL library under certain conditions as described in each
+ individual source file, and distribute linked combinations
+ including the two.
+
+ You must obey the GNU General Public License in all respects
+ for all of the code used other than OpenSSL. If you modify
+ file(s) with this exception, you may extend this exception to your
+ version of the file(s), but you are not obligated to do so. If you
+ do not wish to do so, delete this exception statement from your
+ version. If you delete this exception statement from all source
+ files in the program, then also delete it here.
+*/
+
+
+#include "certificate_chain.h"
+#include "cpl.h"
+#include "exceptions.h"
+#include "language_tag.h"
+#include "reel.h"
+#include "reel_subtitle_asset.h"
+#include "test.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/test/unit_test.hpp>
+
+
+using std::list;
+using std::string;
+using std::vector;
+using boost::shared_ptr;
+
+
+BOOST_AUTO_TEST_CASE (cpl_metadata_bad_values_test)
+{
+ dcp::CPL cpl("", dcp::FEATURE);
+ BOOST_CHECK_THROW (cpl.set_version_number(-1), dcp::BadSettingError);
+
+ vector<dcp::ContentVersion> cv;
+ cv.push_back (dcp::ContentVersion("same-id", "version 1"));
+ cv.push_back (dcp::ContentVersion("same-id", "version 2"));
+ BOOST_CHECK_THROW (cpl.set_content_versions(cv), dcp::DuplicateIdError);
+}
+
+
+BOOST_AUTO_TEST_CASE (main_sound_configuration_test1)
+{
+ dcp::MainSoundConfiguration msc("51/L,R,C,LFE,-,-");
+ BOOST_CHECK_EQUAL (msc.to_string(), "51/L,R,C,LFE,-,-");
+ BOOST_CHECK_EQUAL (msc.channels(), 6);
+ BOOST_CHECK_EQUAL (msc.field(), dcp::MainSoundConfiguration::FIVE_POINT_ONE);
+ BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::LEFT);
+ BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::RIGHT);
+ BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::CENTRE);
+ BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::LFE);
+ BOOST_CHECK (!msc.mapping(4));
+ BOOST_CHECK (!msc.mapping(5));
+}
+
+
+BOOST_AUTO_TEST_CASE (main_sound_configuration_test2)
+{
+ dcp::MainSoundConfiguration msc("71/L,R,C,LFE,-,-");
+ BOOST_CHECK_EQUAL (msc.to_string(), "71/L,R,C,LFE,-,-");
+ BOOST_CHECK_EQUAL (msc.channels(), 6);
+ BOOST_CHECK_EQUAL (msc.field(), dcp::MainSoundConfiguration::SEVEN_POINT_ONE);
+ BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::LEFT);
+ BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::RIGHT);
+ BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::CENTRE);
+ BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::LFE);
+ BOOST_CHECK (!msc.mapping(4));
+ BOOST_CHECK (!msc.mapping(5));
+}
+
+
+BOOST_AUTO_TEST_CASE (main_sound_configuration_test3)
+{
+ dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss");
+ BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss");
+ BOOST_CHECK_EQUAL (msc.channels(), 6);
+ BOOST_CHECK_EQUAL (msc.field(), dcp::MainSoundConfiguration::SEVEN_POINT_ONE);
+ BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::LEFT);
+ BOOST_CHECK (!msc.mapping(1));
+ BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::CENTRE);
+ BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::LFE);
+ BOOST_CHECK_EQUAL (msc.mapping(4).get(), dcp::LS);
+ BOOST_CHECK_EQUAL (msc.mapping(5).get(), dcp::RS);
+}
+
+
+BOOST_AUTO_TEST_CASE (main_sound_configuration_test4)
+{
+ dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss,-,-,-,-,-,-,-,-,-");
+ BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss,-,-,-,-,-,-,-,-,-");
+ BOOST_CHECK_EQUAL (msc.channels(), 15);
+ BOOST_CHECK_EQUAL (msc.field(), dcp::MainSoundConfiguration::SEVEN_POINT_ONE);
+ BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::LEFT);
+ BOOST_CHECK (!msc.mapping(1));
+ BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::CENTRE);
+ BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::LFE);
+ BOOST_CHECK_EQUAL (msc.mapping(4).get(), dcp::LS);
+ BOOST_CHECK_EQUAL (msc.mapping(5).get(), dcp::RS);
+ for (int i = 6; i < 15; ++i) {
+ BOOST_CHECK (!msc.mapping(i));
+ }
+}
+
+
+BOOST_AUTO_TEST_CASE (main_sound_configuration_test5)
+{
+ dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss,HI,VIN,-,-,Lrs,Rrs,DBOX,Sync,Sign");
+ BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss,HI,VIN,-,-,Lrs,Rrs,DBOX,Sync,Sign");
+ BOOST_CHECK_EQUAL (msc.channels(), 15);
+ BOOST_CHECK_EQUAL (msc.field(), dcp::MainSoundConfiguration::SEVEN_POINT_ONE);
+ BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::LEFT);
+ BOOST_CHECK (!msc.mapping(1));
+ BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::CENTRE);
+ BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::LFE);
+ BOOST_CHECK_EQUAL (msc.mapping(4).get(), dcp::LS);
+ BOOST_CHECK_EQUAL (msc.mapping(5).get(), dcp::RS);
+ BOOST_CHECK_EQUAL (msc.mapping(6).get(), dcp::HI);
+ BOOST_CHECK_EQUAL (msc.mapping(7).get(), dcp::VI);
+ BOOST_CHECK (!msc.mapping(8));
+ BOOST_CHECK (!msc.mapping(9));
+ BOOST_CHECK_EQUAL (msc.mapping(10).get(), dcp::BSL);
+ BOOST_CHECK_EQUAL (msc.mapping(11).get(), dcp::BSR);
+ BOOST_CHECK_EQUAL (msc.mapping(12).get(), dcp::MOTION_DATA);
+ BOOST_CHECK_EQUAL (msc.mapping(13).get(), dcp::SYNC_SIGNAL);
+ BOOST_CHECK_EQUAL (msc.mapping(14).get(), dcp::SIGN_LANGUAGE);
+}
+
+
+BOOST_AUTO_TEST_CASE (luminance_test1)
+{
+ BOOST_CHECK_NO_THROW (dcp::Luminance(4, dcp::Luminance::CANDELA_PER_SQUARE_METRE));
+ BOOST_CHECK_THROW (dcp::Luminance(-4, dcp::Luminance::CANDELA_PER_SQUARE_METRE), dcp::MiscError);
+}
+
+
+BOOST_AUTO_TEST_CASE (luminance_test2)
+{
+ shared_ptr<cxml::Document> doc (new cxml::Document("Luminance"));
+
+ doc->read_string (
+ "<Luminance units=\"candela-per-square-metre\">4.5</Luminance>"
+ );
+
+ dcp::Luminance lum (doc);
+ BOOST_CHECK (lum.unit() == dcp::Luminance::CANDELA_PER_SQUARE_METRE);
+ BOOST_CHECK_CLOSE (lum.value(), 4.5, 0.1);
+}
+
+
+BOOST_AUTO_TEST_CASE (luminance_test3)
+{
+ shared_ptr<cxml::Document> doc (new cxml::Document("Luminance"));
+
+ doc->read_string (
+ "<Luminance units=\"candela-per-square-motre\">4.5</Luminance>"
+ );
+
+ BOOST_CHECK_THROW (new dcp::Luminance(doc), dcp::XMLError);
+}
+
+
+BOOST_AUTO_TEST_CASE (luminance_test4)
+{
+ shared_ptr<cxml::Document> doc (new cxml::Document("Luminance"));
+
+ doc->read_string (
+ "<Luminance units=\"candela-per-square-metre\">-4.5</Luminance>"
+ );
+
+ /* We tolerate out-of-range values when reading from XML */
+ dcp::Luminance lum (doc);
+ BOOST_CHECK (lum.unit() == dcp::Luminance::CANDELA_PER_SQUARE_METRE);
+ BOOST_CHECK_CLOSE (lum.value(), -4.5, 0.1);
+}
+
+
+/** A test where most CPL metadata is present */
+BOOST_AUTO_TEST_CASE (cpl_metadata_read_test1)
+{
+ dcp::CPL cpl("test/ref/cpl_metadata_test1.xml");
+
+ BOOST_CHECK_EQUAL (cpl.full_content_title_text().get(), "full-content-title");
+ BOOST_CHECK (cpl.full_content_title_text_language().get() == "de");
+ BOOST_CHECK (cpl.release_territory().get() == "ES");
+ BOOST_CHECK_EQUAL (cpl.version_number().get(), 2);
+ BOOST_CHECK_EQUAL (cpl.status().get(), dcp::FINAL);
+ BOOST_CHECK_EQUAL (cpl.chain().get(), "the-chain");
+ BOOST_CHECK_EQUAL (cpl.distributor().get(), "the-distributor");
+ BOOST_CHECK_EQUAL (cpl.facility().get(), "the-facility");
+ BOOST_CHECK (cpl.luminance() == dcp::Luminance(4.5, dcp::Luminance::FOOT_LAMBERT));
+
+ dcp::MainSoundConfiguration msc(cpl.main_sound_configuration().get());
+ BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::LEFT);
+ BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::RIGHT);
+ BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::CENTRE);
+ BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::LFE);
+ BOOST_CHECK (!msc.mapping(4));
+ BOOST_CHECK (!msc.mapping(5));
+ BOOST_CHECK (!msc.mapping(6));
+ BOOST_CHECK (!msc.mapping(7));
+ BOOST_CHECK (!msc.mapping(8));
+ BOOST_CHECK (!msc.mapping(9));
+ BOOST_CHECK (!msc.mapping(10));
+ BOOST_CHECK (!msc.mapping(11));
+ BOOST_CHECK (!msc.mapping(12));
+ BOOST_CHECK_EQUAL (msc.mapping(13).get(), dcp::SYNC_SIGNAL);
+
+ BOOST_CHECK_EQUAL (cpl.main_sound_sample_rate().get(), 48000);
+ BOOST_CHECK (cpl.main_picture_stored_area().get() == dcp::Size(1998, 1080));
+ BOOST_CHECK (cpl.main_picture_active_area().get() == dcp::Size(1440, 1080));
+
+ list<shared_ptr<dcp::Reel> > reels = cpl.reels ();
+ BOOST_REQUIRE_EQUAL (reels.size(), 1);
+ BOOST_CHECK_EQUAL (reels.front()->main_subtitle()->language().get(), dcp::LanguageTag("de-DE"));
+
+ vector<string> asl = cpl.additional_subtitle_languages();
+ BOOST_REQUIRE_EQUAL (asl.size(), 2);
+ BOOST_CHECK_EQUAL (asl[0], "en-US");
+ BOOST_CHECK_EQUAL (asl[1], "fr-ZA");
+
+ BOOST_CHECK (cpl.additional_subtitle_languages() == asl);
+}
+
+
+/** A test where most CPL metadata is present */
+BOOST_AUTO_TEST_CASE (cpl_metadata_write_test1)
+{
+ RNGFixer fix;
+
+ dcp::CPL cpl("", dcp::FEATURE);
+ cpl.set_issue_date ("2020-08-28T13:35:06+02:00");
+
+ vector<dcp::ContentVersion> cv;
+ cv.push_back (dcp::ContentVersion("some-id", "version 1"));
+ cv.push_back (dcp::ContentVersion("another-id", "version 2"));
+ cpl.set_content_versions (cv);
+
+ cpl.set_full_content_title_text ("full-content-title");
+ cpl.set_full_content_title_text_language (dcp::LanguageTag("de"));
+ cpl.set_release_territory (dcp::LanguageTag::RegionSubtag("ES"));
+ cpl.set_version_number (2);
+ cpl.set_status (dcp::FINAL);
+ cpl.set_chain ("the-chain");
+ cpl.set_distributor ("the-distributor");
+ cpl.set_facility ("the-facility");
+ cpl.set_luminance (dcp::Luminance(4.5, dcp::Luminance::FOOT_LAMBERT));
+
+ dcp::MainSoundConfiguration msc(dcp::MainSoundConfiguration::SEVEN_POINT_ONE, 16);
+ msc.set_mapping (0, dcp::LEFT);
+ msc.set_mapping (1, dcp::RIGHT);
+ msc.set_mapping (2, dcp::CENTRE);
+ msc.set_mapping (3, dcp::LFE);
+ msc.set_mapping (13, dcp::SYNC_SIGNAL);
+ cpl.set_main_sound_configuration (msc.to_string());
+
+ cpl.set_main_sound_sample_rate (48000);
+ cpl.set_main_picture_stored_area (dcp::Size(1998, 1080));
+ cpl.set_main_picture_active_area (dcp::Size(1440, 1080));
+
+ shared_ptr<cxml::Document> doc (new cxml::Document("MainSubtitle"));
+
+ doc->read_string (
+ "<MainSubtitle>"
+ "<Id>urn:uuid:8bca1489-aab1-9259-a4fd-8150abc1de12</Id>"
+ "<AnnotationText>Goodbye world!</AnnotationText>"
+ "<EditRate>25 1</EditRate>"
+ "<IntrinsicDuration>1870</IntrinsicDuration>"
+ "<EntryPoint>0</EntryPoint>"
+ "<Duration>525</Duration>"
+ "<KeyId>urn:uuid:540cbf10-ab14-0233-ab1f-fb31501cabfa</KeyId>"
+ "<Hash>3EABjX9BB1CAWhLUtHhrGSyLgOY=</Hash>"
+ "<Language>de-DE</Language>"
+ "</MainSubtitle>"
+ );
+
+ shared_ptr<dcp::Reel> reel(new dcp::Reel());
+ reel->add (black_picture_asset("build/test/cpl_metadata_write_test1"));
+ reel->add (shared_ptr<dcp::ReelSubtitleAsset>(new dcp::ReelSubtitleAsset(doc)));
+ cpl.add (reel);
+
+ vector<dcp::LanguageTag> lt;
+ lt.push_back(dcp::LanguageTag("en-US"));
+ lt.push_back(dcp::LanguageTag("fr-ZA"));
+ cpl.set_additional_subtitle_languages (lt);
+
+ cpl.write_xml ("build/test/cpl_metadata_write_test1.xml", dcp::SMPTE, shared_ptr<dcp::CertificateChain>());
+ check_xml (
+ dcp::file_to_string("test/ref/cpl_metadata_test1.xml"),
+ dcp::file_to_string("build/test/cpl_metadata_write_test1.xml"),
+ list<string>()
+ );
+}
+
+
+/** A test where most CPL metadata is present */
+BOOST_AUTO_TEST_CASE (cpl_metadata_roundtrip_test_1)
+{
+ dcp::CPL cpl ("test/ref/cpl_metadata_test1.xml");
+ cpl.write_xml ("build/test/cpl_metadata_roundtrip_test1.xml", dcp::SMPTE, shared_ptr<dcp::CertificateChain>());
+ list<string> ignore;
+ ignore.push_back ("Id");
+ check_xml (
+ dcp::file_to_string("test/ref/cpl_metadata_test1.xml"),
+ dcp::file_to_string("build/test/cpl_metadata_roundtrip_test1.xml"),
+ ignore
+ );
+}
+
+
+/** A test where only a bare minimum of CPL metadata is present */
+BOOST_AUTO_TEST_CASE (cpl_metadata_write_test2)
+{
+ RNGFixer fix;
+
+ dcp::CPL cpl("", dcp::FEATURE);
+ cpl.set_issue_date ("2020-08-28T13:35:06+02:00");
+ cpl.set_content_version (dcp::ContentVersion("id", "version"));
+
+ dcp::MainSoundConfiguration msc(dcp::MainSoundConfiguration::SEVEN_POINT_ONE, 16);
+ msc.set_mapping (0, dcp::LEFT);
+ msc.set_mapping (1, dcp::RIGHT);
+ msc.set_mapping (2, dcp::CENTRE);
+ msc.set_mapping (3, dcp::LFE);
+ msc.set_mapping (13, dcp::SYNC_SIGNAL);
+ cpl.set_main_sound_configuration (msc.to_string());
+
+ cpl.set_main_sound_sample_rate (48000);
+ cpl.set_main_picture_stored_area (dcp::Size(1998, 1080));
+ cpl.set_main_picture_active_area (dcp::Size(1440, 1080));
+
+ shared_ptr<dcp::Reel> reel(new dcp::Reel());
+ reel->add (black_picture_asset("build/test/cpl_metadata_write_test1"));
+ cpl.add (reel);
+
+ cpl.write_xml ("build/test/cpl_metadata_write_test2.xml", dcp::SMPTE, shared_ptr<dcp::CertificateChain>());
+ check_xml (
+ dcp::file_to_string("test/ref/cpl_metadata_test2.xml"),
+ dcp::file_to_string("build/test/cpl_metadata_write_test2.xml"),
+ list<string>()
+ );
+}
+
+
+/** A test where only a bare minimum of CPL metadata is present */
+BOOST_AUTO_TEST_CASE (cpl_metadata_read_test2)
+{
+ dcp::CPL cpl("test/ref/cpl_metadata_test2.xml");
+
+ BOOST_CHECK_EQUAL (cpl.full_content_title_text().get(), "");
+ BOOST_CHECK (!cpl.full_content_title_text_language());
+ BOOST_CHECK (!cpl.release_territory());
+ BOOST_CHECK (!cpl.version_number());
+ BOOST_CHECK (!cpl.status());
+ BOOST_CHECK (!cpl.chain());
+ BOOST_CHECK (!cpl.distributor());
+ BOOST_CHECK (!cpl.facility());
+ BOOST_CHECK (!cpl.luminance());
+
+ dcp::MainSoundConfiguration msc(cpl.main_sound_configuration().get());
+ BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::LEFT);
+ BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::RIGHT);
+ BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::CENTRE);
+ BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::LFE);
+ BOOST_CHECK (!msc.mapping(4));
+ BOOST_CHECK (!msc.mapping(5));
+ BOOST_CHECK (!msc.mapping(6));
+ BOOST_CHECK (!msc.mapping(7));
+ BOOST_CHECK (!msc.mapping(8));
+ BOOST_CHECK (!msc.mapping(9));
+ BOOST_CHECK (!msc.mapping(10));
+ BOOST_CHECK (!msc.mapping(11));
+ BOOST_CHECK (!msc.mapping(12));
+ BOOST_CHECK_EQUAL (msc.mapping(13).get(), dcp::SYNC_SIGNAL);
+
+ BOOST_CHECK_EQUAL (cpl.main_sound_sample_rate().get(), 48000);
+ BOOST_CHECK (cpl.main_picture_stored_area().get() == dcp::Size(1998, 1080));
+ BOOST_CHECK (cpl.main_picture_active_area().get() == dcp::Size(1440, 1080));
+
+ list<shared_ptr<dcp::Reel> > reels = cpl.reels ();
+ BOOST_REQUIRE_EQUAL (reels.size(), 1);
+}
+
+
+/** A test where only a bare minimum of CPL metadata is present */
+BOOST_AUTO_TEST_CASE (cpl_metadata_roundtrip_test_2)
+{
+ dcp::CPL cpl ("test/ref/cpl_metadata_test2.xml");
+ cpl.write_xml ("build/test/cpl_metadata_roundtrip_test2.xml", dcp::SMPTE, shared_ptr<dcp::CertificateChain>());
+ list<string> ignore;
+ ignore.push_back ("Id");
+ check_xml (
+ dcp::file_to_string("test/ref/cpl_metadata_test2.xml"),
+ dcp::file_to_string("build/test/cpl_metadata_roundtrip_test2.xml"),
+ ignore
+ );
+}
+
/*
- Copyright (C) 2015-2019 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2015-2020 Carl Hetherington <cth@carlh.net>
This file is part of libdcp.
*/
#include "reel_mono_picture_asset.h"
+#include "reel_subtitle_asset.h"
#include <libcxml/cxml.h>
#include <boost/test/unit_test.hpp>
+#include "test.h"
+
+using std::string;
+using boost::optional;
using boost::shared_ptr;
+
/** Test the XML constructor of ReelPictureAsset */
BOOST_AUTO_TEST_CASE (reel_picture_asset_test)
{
doc->read_string (
"<MainPicture>"
"<Id>urn:uuid:06ac1ca7-9c46-4107-8864-a6448e24b04b</Id>"
- "<AnnotationText></AnnotationText>"
+ "<AnnotationText>Hello world!</AnnotationText>"
"<EditRate>24 1</EditRate>"
"<IntrinsicDuration>187048</IntrinsicDuration>"
- "<EntryPoint>0</EntryPoint>"
- "<Duration>187048</Duration>"
+ "<EntryPoint>42</EntryPoint>"
+ "<Duration>9444</Duration>"
"<Hash>6EQX4NjG8vxIWhLUtHhrGSyLgOY=</Hash>"
"<FrameRate>24 1</FrameRate>"
"<ScreenAspectRatio>2048 858</ScreenAspectRatio>"
);
dcp::ReelMonoPictureAsset pa (doc);
- BOOST_CHECK_EQUAL (pa.screen_aspect_ratio(), dcp::Fraction (2048, 858));
+ BOOST_CHECK_EQUAL (pa.id(), "06ac1ca7-9c46-4107-8864-a6448e24b04b");
+ BOOST_CHECK_EQUAL (pa.annotation_text(), "Hello world!");
+ BOOST_CHECK_EQUAL (pa.edit_rate(), dcp::Fraction(24, 1));
+ BOOST_CHECK_EQUAL (pa.intrinsic_duration(), 187048);
+ BOOST_CHECK_EQUAL (pa.entry_point().get(), 42L);
+ BOOST_CHECK_EQUAL (pa.duration().get(), 9444L);
+ BOOST_CHECK_EQUAL (pa.hash().get(), string("6EQX4NjG8vxIWhLUtHhrGSyLgOY="));
+ BOOST_CHECK_EQUAL (pa.frame_rate(), dcp::Fraction(24, 1));
+ BOOST_CHECK_EQUAL (pa.screen_aspect_ratio(), dcp::Fraction(2048, 858));
+}
+
+
+/** Test the XML constructor of ReelSubtitleAsset */
+BOOST_AUTO_TEST_CASE (reel_subtitle_asset_test)
+{
+ shared_ptr<cxml::Document> doc (new cxml::Document("MainSubtitle"));
+
+ doc->read_string (
+ "<MainSubtitle>"
+ "<Id>urn:uuid:8bca1489-aab1-9259-a4fd-8150abc1de12</Id>"
+ "<AnnotationText>Goodbye world!</AnnotationText>"
+ "<EditRate>25 1</EditRate>"
+ "<IntrinsicDuration>1870</IntrinsicDuration>"
+ "<EntryPoint>0</EntryPoint>"
+ "<Duration>525</Duration>"
+ "<KeyId>urn:uuid:540cbf10-ab14-0233-ab1f-fb31501cabfa</KeyId>"
+ "<Hash>3EABjX9BB1CAWhLUtHhrGSyLgOY=</Hash>"
+ "<Language>de-DE</Language>"
+ "</MainSubtitle>"
+ );
+
+ dcp::ReelSubtitleAsset ps (doc);
+ BOOST_CHECK_EQUAL (ps.id(), "8bca1489-aab1-9259-a4fd-8150abc1de12");
+ BOOST_CHECK_EQUAL (ps.annotation_text(), "Goodbye world!");
+ BOOST_CHECK_EQUAL (ps.edit_rate(), dcp::Fraction(25, 1));
+ BOOST_CHECK_EQUAL (ps.intrinsic_duration(), 1870);
+ BOOST_CHECK_EQUAL (ps.entry_point().get(), 0L);
+ BOOST_CHECK_EQUAL (ps.duration().get(), 525L);
+ BOOST_CHECK_EQUAL (ps.hash().get(), string("3EABjX9BB1CAWhLUtHhrGSyLgOY="));
+ BOOST_REQUIRE (ps.language());
+ BOOST_CHECK_EQUAL (ps.language()->to_string(), "de-DE");
}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<CompositionPlaylist xmlns="http://www.smpte-ra.org/schemas/429-7/2006/CPL">
+ <Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b</Id>
+ <AnnotationText></AnnotationText>
+ <IssueDate>2020-08-28T13:35:06+02:00</IssueDate>
+ <Issuer>libdcp1.6.4devel</Issuer>
+ <Creator>libdcp1.6.4devel</Creator>
+ <ContentTitleText></ContentTitleText>
+ <ContentKind>feature</ContentKind>
+ <ContentVersion>
+ <Id>some-id</Id>
+ <LabelText>version 1</LabelText>
+ </ContentVersion>
+ <RatingList/>
+ <ReelList>
+ <Reel>
+ <Id>urn:uuid:8b92bcee-62fc-4a33-a51a-816e9611ce85</Id>
+ <AssetList>
+ <MainPicture>
+ <Id>urn:uuid:46c3eb45-15e5-47d6-8684-d8641e4dc516</Id>
+ <AnnotationText></AnnotationText>
+ <EditRate>24 1</EditRate>
+ <IntrinsicDuration>24</IntrinsicDuration>
+ <EntryPoint>0</EntryPoint>
+ <Duration>24</Duration>
+ <Hash>BM4qh04HOSGF5vop4mhJBE7C4M0=</Hash>
+ <FrameRate>24 1</FrameRate>
+ <ScreenAspectRatio>1998 1080</ScreenAspectRatio>
+ </MainPicture>
+ <MainSubtitle>
+ <Id>urn:uuid:8bca1489-aab1-9259-a4fd-8150abc1de12</Id>
+ <AnnotationText>Goodbye world!</AnnotationText>
+ <EditRate>25 1</EditRate>
+ <IntrinsicDuration>1870</IntrinsicDuration>
+ <EntryPoint>0</EntryPoint>
+ <Duration>525</Duration>
+ <KeyId>urn:uuid:540cbf10-ab14-0233-ab1f-fb31501cabfa</KeyId>
+ <Hash>3EABjX9BB1CAWhLUtHhrGSyLgOY=</Hash>
+ <Language>de-DE</Language>
+ </MainSubtitle>
+ <meta:CompositionMetadataAsset xmlns:meta="http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata">
+ <Id>urn:uuid:77e1fb48-ce0c-4d29-bf88-8c3bfec8013a</Id>
+ <EditRate>24 1</EditRate>
+ <IntrinsicDuration>24</IntrinsicDuration>
+ <meta:FullContentTitleText language="de">full-content-title</meta:FullContentTitleText>
+ <meta:ReleaseTerritory>ES</meta:ReleaseTerritory>
+ <meta:VersionNumber status="final">2</meta:VersionNumber>
+ <meta:Chain>the-chain</meta:Chain>
+ <meta:Distributor>the-distributor</meta:Distributor>
+ <meta:Facility>the-facility</meta:Facility>
+ <meta:AlternateContentVersionList>
+ <ContentVersion>
+ <Id>another-id</Id>
+ <LabelText>version 2</LabelText>
+ </ContentVersion>
+ </meta:AlternateContentVersionList>
+ <meta:Luminance units="foot-lambert">4.5</meta:Luminance>
+ <meta:MainSoundConfiguration>71/L,R,C,LFE,-,-,-,-,-,-,-,-,-,Sync,-,-</meta:MainSoundConfiguration>
+ <meta:MainSoundSampleRate>48000 1</meta:MainSoundSampleRate>
+ <meta:MainPictureStoredArea>
+ <meta:Width>1998</meta:Width>
+ <meta:Height>1080</meta:Height>
+ </meta:MainPictureStoredArea>
+ <meta:MainPictureActiveArea>
+ <meta:Width>1440</meta:Width>
+ <meta:Height>1080</meta:Height>
+ </meta:MainPictureActiveArea>
+ <MainSubtitleLanguageList>de-DE en-US fr-ZA</MainSubtitleLanguageList>
+ </meta:CompositionMetadataAsset>
+ </AssetList>
+ </Reel>
+ </ReelList>
+</CompositionPlaylist>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<CompositionPlaylist xmlns="http://www.smpte-ra.org/schemas/429-7/2006/CPL">
+ <Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b</Id>
+ <AnnotationText></AnnotationText>
+ <IssueDate>2020-08-28T13:35:06+02:00</IssueDate>
+ <Issuer>libdcp1.6.4devel</Issuer>
+ <Creator>libdcp1.6.4devel</Creator>
+ <ContentTitleText></ContentTitleText>
+ <ContentKind>feature</ContentKind>
+ <ContentVersion>
+ <Id>id</Id>
+ <LabelText>version</LabelText>
+ </ContentVersion>
+ <RatingList/>
+ <ReelList>
+ <Reel>
+ <Id>urn:uuid:8b92bcee-62fc-4a33-a51a-816e9611ce85</Id>
+ <AssetList>
+ <MainPicture>
+ <Id>urn:uuid:46c3eb45-15e5-47d6-8684-d8641e4dc516</Id>
+ <AnnotationText></AnnotationText>
+ <EditRate>24 1</EditRate>
+ <IntrinsicDuration>24</IntrinsicDuration>
+ <EntryPoint>0</EntryPoint>
+ <Duration>24</Duration>
+ <Hash>BM4qh04HOSGF5vop4mhJBE7C4M0=</Hash>
+ <FrameRate>24 1</FrameRate>
+ <ScreenAspectRatio>1998 1080</ScreenAspectRatio>
+ </MainPicture>
+ <meta:CompositionMetadataAsset xmlns:meta="http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata">
+ <Id>urn:uuid:77e1fb48-ce0c-4d29-bf88-8c3bfec8013a</Id>
+ <EditRate>24 1</EditRate>
+ <IntrinsicDuration>24</IntrinsicDuration>
+ <meta:FullContentTitleText/>
+ <meta:MainSoundConfiguration>71/L,R,C,LFE,-,-,-,-,-,-,-,-,-,Sync,-,-</meta:MainSoundConfiguration>
+ <meta:MainSoundSampleRate>48000 1</meta:MainSoundSampleRate>
+ <meta:MainPictureStoredArea>
+ <meta:Width>1998</meta:Width>
+ <meta:Height>1080</meta:Height>
+ </meta:MainPictureStoredArea>
+ <meta:MainPictureActiveArea>
+ <meta:Width>1440</meta:Width>
+ <meta:Height>1080</meta:Height>
+ </meta:MainPictureActiveArea>
+ </meta:CompositionMetadataAsset>
+ </AssetList>
+ </Reel>
+ </ReelList>
+</CompositionPlaylist>
#include "sound_asset.h"
#include "sound_asset_writer.h"
#include "smpte_subtitle_asset.h"
+#include "mono_picture_asset.h"
+#include "openjpeg_image.h"
+#include "j2k.h"
+#include "picture_asset_writer.h"
+#include "reel_mono_picture_asset.h"
+#include "reel_asset.h"
#include "test.h"
#include "util.h"
#include <asdcp/KM_util.h>
using boost::shared_ptr;
using boost::optional;
+
boost::filesystem::path private_test;
boost::filesystem::path xsd_test = "build/test/xsd with spaces";
+
struct TestConfig
{
TestConfig()
}
+shared_ptr<dcp::OpenJPEGImage>
+black_image ()
+{
+ shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(dcp::Size(1998, 1080)));
+ int const pixels = 1998 * 1080;
+ for (int i = 0; i < 3; ++i) {
+ memset (image->data(i), 0, pixels * sizeof(int));
+ }
+ return image;
+}
+
+
+shared_ptr<dcp::ReelAsset>
+black_picture_asset (boost::filesystem::path dir, int frames)
+{
+ shared_ptr<dcp::OpenJPEGImage> image = black_image ();
+ dcp::Data frame = dcp::compress_j2k (image, 100000000, 24, false, false);
+ BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
+
+ shared_ptr<dcp::MonoPictureAsset> asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE));
+ boost::filesystem::create_directories (dir);
+ shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
+ for (int i = 0; i < frames; ++i) {
+ writer->write (frame.data().get(), frame.size());
+ }
+ writer->finalize ();
+
+ return shared_ptr<dcp::ReelAsset>(new dcp::ReelMonoPictureAsset(asset, 0));
+}
+
+
BOOST_GLOBAL_FIXTURE (TestConfig);
#include "reel.h"
#include "reel_subtitle_asset.h"
#include "subtitle.h"
+#include "reel_asset.h"
#include <boost/filesystem.hpp>
+#include <boost/optional.hpp>
namespace xmlpp {
class Element;
extern boost::shared_ptr<dcp::DCP> make_simple_with_smpte_subs (boost::filesystem::path path);
extern boost::shared_ptr<dcp::DCP> make_simple_with_interop_ccaps (boost::filesystem::path path);
extern boost::shared_ptr<dcp::DCP> make_simple_with_smpte_ccaps (boost::filesystem::path path);
+extern boost::shared_ptr<dcp::OpenJPEGImage> black_image ();
+extern boost::shared_ptr<dcp::ReelAsset> black_picture_asset (boost::filesystem::path dir, int frames = 24);
/** Creating an object of this class will make asdcplib's random number generation
* (more) predictable.
RNGFixer ();
~RNGFixer ();
};
-
}
-static
-shared_ptr<dcp::OpenJPEGImage>
-black_image ()
-{
- shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(dcp::Size(1998, 1080)));
- int const pixels = 1998 * 1080;
- for (int i = 0; i < 3; ++i) {
- memset (image->data(i), 0, pixels * sizeof(int));
- }
- return image;
-}
-
-
static
void
dcp_from_frame (dcp::Data const& frame, boost::filesystem::path dir)
colour_test.cc
colour_conversion_test.cc
combine_test.cc
+ cpl_metadata_test.cc
cpl_sar_test.cc
cpl_ratings_test.cc
dcp_font_test.cc