*/
/** @file src/asset.cc
- * @brief Parent class for assets of DCPs.
+ * @brief Asset class.
*/
-#include <iostream>
-#include <boost/filesystem.hpp>
-#include <boost/function.hpp>
-#include <libxml++/nodes/element.h>
-#include "AS_DCP.h"
-#include "KM_util.h"
++#include "raw_convert.h"
#include "asset.h"
#include "util.h"
-#include "metadata.h"
+#include "exceptions.h"
#include "compose.hpp"
-#include "raw_convert.h"
+#include <libxml++/libxml++.h>
- #include <boost/lexical_cast.hpp>
using std::string;
- using boost::lexical_cast;
-using boost::shared_ptr;
-using namespace libdcp;
-
-Asset::Asset (boost::filesystem::path directory, boost::filesystem::path file_name)
- : _directory (directory)
- , _file_name (file_name)
- , _uuid (make_uuid ())
- , _edit_rate (0)
- , _entry_point (0)
- , _intrinsic_duration (0)
- , _duration (0)
+using boost::function;
+using boost::optional;
+using namespace dcp;
+
+/** Create an Asset with a randomly-generated ID */
+Asset::Asset ()
{
- if (_file_name.empty ()) {
- _file_name = _uuid + ".xml";
- }
+
+}
+
+/** Create an Asset from a given file. The ID will
+ * be extracted from the file.
+ * @param file File name.
+ */
+Asset::Asset (boost::filesystem::path file)
+ : _file (file)
+{
+
+}
+
+/** Create an Asset with a specified ID.
+ * @param id ID to use.
+ */
+Asset::Asset (string id)
+ : Object (id)
+{
+
}
void
-Asset::write_to_pkl (xmlpp::Node* node, bool interop) const
+Asset::write_to_pkl (xmlpp::Node* node, Standard standard) const
{
+ assert (!_file.empty ());
+
xmlpp::Node* asset = node->add_child ("Asset");
- asset->add_child("Id")->add_child_text ("urn:uuid:" + _uuid);
- asset->add_child("AnnotationText")->add_child_text (_file_name.string ());
- asset->add_child("Hash")->add_child_text (digest ());
- asset->add_child("Size")->add_child_text (raw_convert<string> (boost::filesystem::file_size(path())));
- if (interop) {
- asset->add_child("Type")->add_child_text (String::compose ("application/x-smpte-mxf;asdcpKind=%1", asdcp_kind ()));
- } else {
- asset->add_child("Type")->add_child_text ("application/mxf");
- }
+ asset->add_child("Id")->add_child_text ("urn:uuid:" + _id);
+ asset->add_child("AnnotationText")->add_child_text (_id);
+ asset->add_child("Hash")->add_child_text (hash ());
- asset->add_child("Size")->add_child_text (lexical_cast<string> (boost::filesystem::file_size (_file)));
++ asset->add_child("Size")->add_child_text (raw_convert<string> (boost::filesystem::file_size (_file)));
+ asset->add_child("Type")->add_child_text (pkl_type (standard));
}
void
-Asset::write_to_assetmap (xmlpp::Node* node) const
+Asset::write_to_assetmap (xmlpp::Node* node, boost::filesystem::path root) const
{
+ assert (!_file.empty ());
+
xmlpp::Node* asset = node->add_child ("Asset");
- asset->add_child("Id")->add_child_text ("urn:uuid:" + _uuid);
+ asset->add_child("Id")->add_child_text ("urn:uuid:" + _id);
xmlpp::Node* chunk_list = asset->add_child ("ChunkList");
xmlpp::Node* chunk = chunk_list->add_child ("Chunk");
- chunk->add_child("Path")->add_child_text (_file_name.string ());
+ optional<boost::filesystem::path> path = relative_to_root (root, _file);
+ if (!path) {
+ throw MiscError (String::compose ("Asset %1 is not within the directory %2", _file, root));
+ }
+ chunk->add_child("Path")->add_child_text (path.get().string ());
chunk->add_child("VolumeIndex")->add_child_text ("1");
chunk->add_child("Offset")->add_child_text ("0");
- chunk->add_child("Length")->add_child_text (lexical_cast<string> (boost::filesystem::file_size (_file)));
- chunk->add_child("Length")->add_child_text (raw_convert<string> (boost::filesystem::file_size(path())));
-}
-
-boost::filesystem::path
-Asset::path () const
-{
- boost::filesystem::path p;
- p /= _directory;
- p /= _file_name;
- return p;
++ chunk->add_child("Length")->add_child_text (raw_convert<string> (boost::filesystem::file_size (_file)));
}
string
*/
-#include <libxml/parser.h>
++#include "raw_convert.h"
#include "cpl.h"
-#include "parse/cpl.h"
#include "util.h"
-#include "mono_picture_asset.h"
-#include "stereo_picture_asset.h"
-#include "sound_asset.h"
-#include "subtitle_asset.h"
-#include "parse/asset_map.h"
+#include "mono_picture_mxf.h"
+#include "stereo_picture_mxf.h"
+#include "sound_mxf.h"
+#include "subtitle_content.h"
#include "reel.h"
#include "metadata.h"
#include "signer.h"
using std::pair;
using std::make_pair;
using boost::shared_ptr;
--using boost::lexical_cast;
using boost::optional;
-using namespace libdcp;
+using namespace dcp;
-CPL::CPL (boost::filesystem::path directory, string name, ContentKind content_kind, int length, int frames_per_second)
- : _directory (directory)
- , _name (name)
+CPL::CPL (string annotation_text, ContentKind content_kind)
+ : _annotation_text (annotation_text)
+ /* default _content_title_text to _annotation_text */
+ , _content_title_text (annotation_text)
, _content_kind (content_kind)
- , _length (length)
- , _fps (frames_per_second)
+ , _content_version_id ("urn:uuid:" + make_uuid ())
{
- _id = make_uuid ();
+ /* default _content_version_id to and _content_version_label to
+ a random ID and the current time.
+ */
+ _content_version_id = "urn:uuid:" + make_uuid() + LocalTime().as_string ();
+ _content_version_label_text = _content_version_id;
}
-/** Construct a CPL object from a XML file.
- * @param directory The directory containing this CPL's DCP.
- * @param file The CPL XML filename.
- * @param asset_maps AssetMaps to look for assets in.
- * @param require_mxfs true to throw an exception if a required MXF file does not exist.
- */
-CPL::CPL (boost::filesystem::path directory, string file, list<PathAssetMap> asset_maps, bool require_mxfs)
- : _directory (directory)
+/** Construct a CPL object from a XML file */
+CPL::CPL (boost::filesystem::path file)
+ : Asset (file)
, _content_kind (FEATURE)
- , _length (0)
- , _fps (0)
{
- /* Read the XML */
- shared_ptr<parse::CPL> cpl;
- try {
- cpl.reset (new parse::CPL (file));
- } catch (FileError& e) {
- boost::throw_exception (FileError ("could not load CPL file", file, e.number ()));
- }
-
- /* Now cherry-pick the required bits into our own data structure */
-
- _name = cpl->annotation_text;
- _content_kind = cpl->content_kind;
-
- /* Trim urn:uuid: off the front */
- _id = cpl->id.substr (9);
-
- for (list<shared_ptr<parse::Reel> >::iterator i = cpl->reels.begin(); i != cpl->reels.end(); ++i) {
-
- shared_ptr<parse::Picture> p;
-
- if ((*i)->asset_list->main_picture) {
- p = (*i)->asset_list->main_picture;
- } else {
- p = (*i)->asset_list->main_stereoscopic_picture;
- }
-
- _fps = p->edit_rate.numerator;
- _length += p->duration;
-
- shared_ptr<PictureAsset> picture;
- shared_ptr<SoundAsset> sound;
- shared_ptr<SubtitleAsset> subtitle;
-
- /* Some rather twisted logic to decide if we are 3D or not;
- some DCPs give a MainStereoscopicPicture to indicate 3D, others
- just have a FrameRate twice the EditRate and apparently
- expect you to divine the fact that they are hence 3D.
- */
-
- if (!(*i)->asset_list->main_stereoscopic_picture && p->edit_rate == p->frame_rate) {
+ cxml::Document f ("CompositionPlaylist");
+ f.read_file (file);
- try {
- pair<string, shared_ptr<const parse::AssetMapAsset> > asset = asset_from_id (asset_maps, p->id);
-
- picture.reset (new MonoPictureAsset (asset.first, asset.second->chunks.front()->path));
-
- picture->read ();
- picture->set_edit_rate (_fps);
- picture->set_entry_point (p->entry_point);
- picture->set_duration (p->duration);
- if (p->key_id.length() > 9) {
- /* Trim urn:uuid: */
- picture->set_key_id (p->key_id.substr (9));
- }
- } catch (MXFFileError) {
- if (require_mxfs) {
- throw;
- }
- }
-
- } else {
- try {
- pair<string, shared_ptr<const parse::AssetMapAsset> > asset = asset_from_id (asset_maps, p->id);
-
- picture.reset (new StereoPictureAsset (asset.first, asset.second->chunks.front()->path));
-
- picture->read ();
- picture->set_edit_rate (_fps);
- picture->set_entry_point (p->entry_point);
- picture->set_duration (p->duration);
- if (p->key_id.length() > 9) {
- /* Trim urn:uuid: */
- picture->set_key_id (p->key_id.substr (9));
- }
-
- } catch (MXFFileError) {
- if (require_mxfs) {
- throw;
- }
- }
-
- }
-
- if ((*i)->asset_list->main_sound) {
-
- try {
- pair<string, shared_ptr<const parse::AssetMapAsset> > asset = asset_from_id (asset_maps, (*i)->asset_list->main_sound->id);
-
- sound.reset (new SoundAsset (asset.first, asset.second->chunks.front()->path));
- shared_ptr<parse::MainSound> s = (*i)->asset_list->main_sound;
-
- sound->read ();
- sound->set_entry_point (s->entry_point);
- sound->set_duration (s->duration);
- if (s->key_id.length() > 9) {
- /* Trim urn:uuid: */
- sound->set_key_id (s->key_id.substr (9));
- }
- } catch (MXFFileError) {
- if (require_mxfs) {
- throw;
- }
- }
- }
-
- if ((*i)->asset_list->main_subtitle) {
-
- pair<string, shared_ptr<const parse::AssetMapAsset> > asset = asset_from_id (asset_maps, (*i)->asset_list->main_subtitle->id);
+ _id = f.string_child ("Id");
+ if (_id.length() > 9) {
+ _id = _id.substr (9);
+ }
+ _annotation_text = f.optional_string_child ("AnnotationText").get_value_or ("");
+ _metadata.issuer = f.optional_string_child ("Issuer").get_value_or ("");
+ _metadata.creator = f.optional_string_child ("Creator").get_value_or ("");
+ _metadata.issue_date = f.string_child ("IssueDate");
+ _content_title_text = f.string_child ("ContentTitleText");
+ _content_kind = content_kind_from_string (f.string_child ("ContentKind"));
+ shared_ptr<cxml::Node> content_version = f.optional_node_child ("ContentVersion");
+ if (content_version) {
+ _content_version_id = content_version->optional_string_child ("Id").get_value_or ("");
+ _content_version_label_text = content_version->string_child ("LabelText");
+ content_version->done ();
+ }
+ f.ignore_child ("RatingList");
+ _reels = type_grand_children<Reel> (f, "ReelList", "Reel");
- subtitle.reset (new SubtitleAsset (asset.first, asset.second->chunks.front()->path));
+ f.ignore_child ("Issuer");
+ f.ignore_child ("Signer");
+ f.ignore_child ("Signature");
- subtitle->set_entry_point ((*i)->asset_list->main_subtitle->entry_point);
- subtitle->set_duration ((*i)->asset_list->main_subtitle->duration);
- }
-
- _reels.push_back (shared_ptr<Reel> (new Reel (picture, sound, subtitle)));
- }
+ f.done ();
}
+/** Add a reel to this CPL.
+ * @param reel Reel to add.
+ */
void
-CPL::add_reel (shared_ptr<Reel> reel)
+CPL::add (boost::shared_ptr<Reel> reel)
{
_reels.push_back (reel);
}
*/
/** @file src/dcp.cc
- * @brief A class to create a DCP.
+ * @brief DCP class.
*/
-#include <sstream>
-#include <iomanip>
-#include <cassert>
-#include <iostream>
-#include <boost/filesystem.hpp>
-#include <boost/algorithm/string.hpp>
-#include <libxml++/libxml++.h>
-#include <xmlsec/xmldsig.h>
-#include <xmlsec/app.h>
++#include "raw_convert.h"
#include "dcp.h"
-#include "asset.h"
-#include "sound_asset.h"
-#include "picture_asset.h"
-#include "subtitle_asset.h"
+#include "sound_mxf.h"
+#include "picture_mxf.h"
+#include "subtitle_content.h"
+#include "mono_picture_mxf.h"
+#include "stereo_picture_mxf.h"
#include "util.h"
#include "metadata.h"
#include "exceptions.h"
#include "reel.h"
#include "cpl.h"
#include "signer.h"
-#include "kdm.h"
-#include "raw_convert.h"
+#include "compose.hpp"
+#include "AS_DCP.h"
+#include "decrypted_kdm.h"
+#include "decrypted_kdm_key.h"
+#include <xmlsec/xmldsig.h>
+#include <xmlsec/app.h>
+#include <libxml++/libxml++.h>
+#include <boost/filesystem.hpp>
+#include <boost/algorithm/string.hpp>
- #include <boost/lexical_cast.hpp>
+#include <sstream>
+#include <iomanip>
+#include <cassert>
+#include <iostream>
using std::string;
using std::list;
using std::stringstream;
using std::ostream;
-using std::copy;
-using std::back_inserter;
using std::make_pair;
+using std::map;
+using std::cout;
+using std::exception;
using boost::shared_ptr;
- using boost::lexical_cast;
-using namespace libdcp;
+using boost::algorithm::starts_with;
+using namespace dcp;
DCP::DCP (boost::filesystem::path directory)
: _directory (directory)
chunk->add_child("Path")->add_child_text (pkl_uuid + "_pkl.xml");
chunk->add_child("VolumeIndex")->add_child_text ("1");
chunk->add_child("Offset")->add_child_text ("0");
- chunk->add_child("Length")->add_child_text (lexical_cast<string> (pkl_length));
+ chunk->add_child("Length")->add_child_text (raw_convert<string> (pkl_length));
- for (list<shared_ptr<CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) {
- (*i)->write_to_assetmap (asset_list);
- }
-
- list<shared_ptr<const Asset> > a = assets ();
- for (list<shared_ptr<const Asset> >::const_iterator i = a.begin(); i != a.end(); ++i) {
- (*i)->write_to_assetmap (asset_list);
+ for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
+ (*i)->write_to_assetmap (asset_list, _directory);
}
/* This must not be the _formatted version otherwise signature digests will be wrong */
*/
/** @file src/dcp_time.cc
- * @brief A representation of time within a DCP.
+ * @brief Time class.
*/
- #include <boost/lexical_cast.hpp>
++#include "raw_convert.h"
+#include "dcp_time.h"
+#include "exceptions.h"
+#include <boost/algorithm/string.hpp>
#include <iostream>
#include <vector>
-#include <boost/algorithm/string.hpp>
#include <cmath>
-#include "dcp_time.h"
-#include "exceptions.h"
-#include "raw_convert.h"
using namespace std;
using namespace boost;
--- /dev/null
+/*
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
++#include "types.h"
++#include "raw_convert.h"
+#include "font.h"
+#include "xml.h"
+#include "text.h"
+#include <libcxml/cxml.h>
+
+using std::string;
+using std::list;
+using boost::shared_ptr;
+using boost::optional;
+using namespace dcp;
+
+Font::Font (boost::shared_ptr<const cxml::Node> node)
+{
+ text = node->content ();
+
+ id = node->optional_string_attribute ("Id").get_value_or ("");
+ size = node->optional_number_attribute<int64_t> ("Size").get_value_or (0);
+ italic = node->optional_bool_attribute ("Italic");
+ optional<string> c = node->optional_string_attribute ("Color");
+ if (c) {
+ color = Color (c.get ());
+ }
+ optional<string> const e = node->optional_string_attribute ("Effect");
+ if (e) {
+ effect = string_to_effect (e.get ());
+ }
+ c = node->optional_string_attribute ( "EffectColor");
+ if (c) {
+ effect_color = Color (c.get ());
+ }
+ subtitle_nodes = type_children<Subtitle> (node, "Subtitle");
+ font_nodes = type_children<Font> (node, "Font");
+ text_nodes = type_children<Text> (node, "Text");
+}
+
+Font::Font (std::list<boost::shared_ptr<Font> > const & font_nodes)
+ : size (0)
+ , italic (false)
+ , color ("FFFFFFFF")
+ , effect_color ("FFFFFFFF")
+{
+ for (list<shared_ptr<Font> >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) {
+ if (!(*i)->id.empty ()) {
+ id = (*i)->id;
+ }
+ if ((*i)->size != 0) {
+ size = (*i)->size;
+ }
+ if ((*i)->italic) {
+ italic = (*i)->italic.get ();
+ }
+ if ((*i)->color) {
+ color = (*i)->color.get ();
+ }
+ if ((*i)->effect) {
+ effect = (*i)->effect.get ();
+ }
+ if ((*i)->effect_color) {
+ effect_color = (*i)->effect_color.get ();
+ }
+ }
+}
--- /dev/null
+/*
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/asset.cc
+ * @brief Parent class for assets of DCPs made up of MXF files.
+ */
+
++#include "raw_convert.h"
+#include "AS_DCP.h"
+#include "KM_prng.h"
+#include "KM_util.h"
+#include "mxf.h"
+#include "util.h"
+#include "metadata.h"
+#include "exceptions.h"
+#include "compose.hpp"
+#include <libxml++/nodes/element.h>
+#include <boost/filesystem.hpp>
+#include <iostream>
+
+using std::string;
+using std::list;
+using std::pair;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using namespace dcp;
+
+MXF::MXF (Fraction edit_rate)
+ : Content (edit_rate)
+ , _encryption_context (0)
+ , _decryption_context (0)
+{
+
+}
+
+MXF::MXF (boost::filesystem::path file)
+ : Content (file)
+ , _encryption_context (0)
+ , _decryption_context (0)
+{
+
+}
+
+MXF::~MXF ()
+{
+ delete _encryption_context;
+ delete _decryption_context;
+}
+
+void
+MXF::fill_writer_info (ASDCP::WriterInfo* writer_info, Standard standard)
+{
+ writer_info->ProductVersion = _metadata.product_version;
+ writer_info->CompanyName = _metadata.company_name;
+ writer_info->ProductName = _metadata.product_name.c_str();
+
+ if (standard == INTEROP) {
+ writer_info->LabelSetType = ASDCP::LS_MXF_INTEROP;
+ } else {
+ writer_info->LabelSetType = ASDCP::LS_MXF_SMPTE;
+ }
+ unsigned int c;
+ Kumu::hex2bin (_id.c_str(), writer_info->AssetUUID, Kumu::UUID_Length, &c);
+ assert (c == Kumu::UUID_Length);
+
+ if (_key) {
+ Kumu::GenRandomUUID (writer_info->ContextID);
+ writer_info->EncryptedEssence = true;
+
+ unsigned int c;
+ Kumu::hex2bin (_key_id.c_str(), writer_info->CryptographicKeyID, Kumu::UUID_Length, &c);
+ assert (c == Kumu::UUID_Length);
+ }
+}
+
+bool
+MXF::equals (shared_ptr<const Content> other, EqualityOptions opt, boost::function<void (NoteType, string)> note) const
+{
+ if (!Content::equals (other, opt, note)) {
+ return false;
+ }
+
+ shared_ptr<const MXF> other_mxf = dynamic_pointer_cast<const MXF> (other);
+ if (!other_mxf) {
+ note (ERROR, "comparing an MXF asset with a non-MXF asset");
+ return false;
+ }
+
+ if (_file != other_mxf->file ()) {
+ note (ERROR, "MXF names differ");
+ if (!opt.mxf_names_can_differ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/** Set the (private) key that will be used to encrypt or decrypt this MXF's content.
+ * This is the top-secret key that is distributed (itself encrypted) to cinemas
+ * via Key Delivery Messages (KDMs).
+ * @param key Key to use.
+ */
+void
+MXF::set_key (Key key)
+{
+ _key = key;
+
+ if (_key_id.empty ()) {
+ /* No key ID so far; we now need one */
+ _key_id = make_uuid ();
+ }
+
+ _decryption_context = new ASDCP::AESDecContext;
+ if (ASDCP_FAILURE (_decryption_context->InitKey (_key->value ()))) {
+ throw MiscError ("could not set up decryption context");
+ }
+
+ _encryption_context = new ASDCP::AESEncContext;
+ if (ASDCP_FAILURE (_encryption_context->InitKey (_key->value ()))) {
+ throw MiscError ("could not set up encryption context");
+ }
+
+ uint8_t cbc_buffer[ASDCP::CBC_BLOCK_SIZE];
+
+ Kumu::FortunaRNG rng;
+ if (ASDCP_FAILURE (_encryption_context->SetIVec (rng.FillRandom (cbc_buffer, ASDCP::CBC_BLOCK_SIZE)))) {
+ throw MiscError ("could not set up CBC initialization vector");
+ }
+}
+
+void
+MXF::read_writer_info (ASDCP::WriterInfo const & info)
+{
+ char buffer[64];
+ Kumu::bin2UUIDhex (info.AssetUUID, ASDCP::UUIDlen, buffer, sizeof (buffer));
+ _id = buffer;
+}
+
+string
+MXF::pkl_type (Standard standard) const
+{
+ switch (standard) {
+ case INTEROP:
+ return String::compose ("application/x-smpte-mxf;asdcpKind=%1", asdcp_kind ());
+ case SMPTE:
+ return "application/mxf";
+ default:
+ assert (false);
+ }
+}
--- /dev/null
-namespace libdcp {
+ /*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+
+ #include <sstream>
+
++namespace dcp {
+
+ /** A sort-of version of boost::lexical_cast that does uses the "C"
+ * locale (i.e. no thousands separators and a . for the decimal separator).
+ */
+ template <typename P, typename Q>
+ P
+ raw_convert (Q v)
+ {
+ std::stringstream s;
+ s.imbue (std::locale::classic ());
+ s << v;
+ P r;
+ s >> r;
+ return r;
+ }
+
+ };
--- /dev/null
- using boost::lexical_cast;
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
++#include "raw_convert.h"
+#include "reel_asset.h"
+#include "content.h"
+#include "compose.hpp"
+#include <libcxml/cxml.h>
+
+using std::pair;
+using std::string;
+using std::make_pair;
+using boost::shared_ptr;
- a->add_child("IntrinsicDuration")->add_child_text (lexical_cast<string> (_intrinsic_duration));
- a->add_child("EntryPoint")->add_child_text (lexical_cast<string> (_entry_point));
- a->add_child("Duration")->add_child_text (lexical_cast<string> (_duration));
+using namespace dcp;
+
+ReelAsset::ReelAsset ()
+ : Object (make_uuid ())
+ , _content (_id)
+ , _edit_rate (Fraction (24, 1))
+ , _intrinsic_duration (0)
+ , _entry_point (0)
+ , _duration (0)
+{
+
+}
+
+/** Construct a ReelAsset.
+ * @param content Content that this asset refers to.
+ * @param entry_point Entry point to use in that content.
+ */
+ReelAsset::ReelAsset (boost::shared_ptr<Content> content, int64_t entry_point)
+ : Object (content->id ())
+ , _content (content)
+ , _edit_rate (content->edit_rate ())
+ , _intrinsic_duration (content->intrinsic_duration ())
+ , _entry_point (entry_point)
+ , _duration (_intrinsic_duration - _entry_point)
+ , _hash (make_digest (content->file (), 0))
+{
+ /* default _annotation_text to the leaf name of our file */
+ _annotation_text = content->file().leaf().string ();
+}
+
+ReelAsset::ReelAsset (boost::shared_ptr<const cxml::Node> node)
+ : Object (node->string_child ("Id"))
+ , _content (_id)
+ , _annotation_text (node->optional_string_child ("AnnotationText").get_value_or (""))
+ , _edit_rate (Fraction (node->string_child ("EditRate")))
+ , _intrinsic_duration (node->number_child<int64_t> ("IntrinsicDuration"))
+ , _entry_point (node->number_child<int64_t> ("EntryPoint"))
+ , _duration (node->number_child<int64_t> ("Duration"))
+ , _hash (node->optional_string_child ("Hash").get_value_or (""))
+ , _key_id (node->optional_string_child ("KeyId").get_value_or (""))
+{
+ if (_id.length() > 9) {
+ _id = _id.substr (9);
+ _content.set_id (_id);
+ }
+
+ if (_key_id.length() > 9) {
+ _key_id = _key_id.substr (9);
+ }
+}
+
+void
+ReelAsset::write_to_cpl (xmlpp::Node* node, Standard) const
+{
+ pair<string, string> const attr = cpl_node_attribute ();
+ xmlpp::Element* a = node->add_child (cpl_node_name ());
+ if (!attr.first.empty ()) {
+ a->set_attribute (attr.first, attr.second);
+ }
+ a->add_child("Id")->add_child_text ("urn:uuid:" + _id);
+ a->add_child("AnnotationText")->add_child_text (_annotation_text);
+ a->add_child("EditRate")->add_child_text (String::compose ("%1 %2", _edit_rate.numerator, _edit_rate.denominator));
++ a->add_child("IntrinsicDuration")->add_child_text (raw_convert<string> (_intrinsic_duration));
++ a->add_child("EntryPoint")->add_child_text (raw_convert<string> (_entry_point));
++ a->add_child("Duration")->add_child_text (raw_convert<string> (_duration));
+ if (!_key_id.empty ()) {
+ a->add_child("KeyId")->add_child_text ("urn:uuid:" + _key_id);
+ }
+}
+
+pair<string, string>
+ReelAsset::cpl_node_attribute () const
+{
+ return make_pair ("", "");
+}
--- /dev/null
+/*
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subtitle.h"
+#include "xml.h"
+#include "font.h"
+#include "text.h"
+#include <libcxml/cxml.h>
++#include <boost/lexical_cast.hpp>
+
+using std::string;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using namespace dcp;
+
+Subtitle::Subtitle (boost::shared_ptr<const cxml::Node> node)
+{
+ in = Time (node->string_attribute ("TimeIn"));
+ out = Time (node->string_attribute ("TimeOut"));
+ font_nodes = type_children<Font> (node, "Font");
+ text_nodes = type_children<Text> (node, "Text");
+ fade_up_time = fade_time (node, "FadeUpTime");
+ fade_down_time = fade_time (node, "FadeDownTime");
+}
+
+Time
+Subtitle::fade_time (shared_ptr<const cxml::Node> node, string name)
+{
+ string const u = node->optional_string_attribute (name).get_value_or ("");
+ Time t;
+
+ if (u.empty ()) {
+ t = Time (0, 0, 0, 20);
+ } else if (u.find (":") != string::npos) {
+ t = Time (u);
+ } else {
+ t = Time (0, 0, 0, lexical_cast<int> (u));
+ }
+
+ if (t > Time (0, 0, 8, 0)) {
+ t = Time (0, 0, 8, 0);
+ }
+
+ return t;
+}
--- /dev/null
- #include <boost/lexical_cast.hpp>
+/*
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
++#include "raw_convert.h"
+#include "subtitle_content.h"
+#include "util.h"
+#include "xml.h"
+#include "font.h"
+#include "text.h"
+#include "load_font.h"
+#include "subtitle_string.h"
+#include "AS_DCP.h"
+#include "KM_util.h"
+#include <libxml++/nodes/element.h>
- using boost::lexical_cast;
+#include <boost/algorithm/string.hpp>
+#include <fstream>
+
+using std::string;
+using std::list;
+using std::ostream;
+using std::ofstream;
+using std::stringstream;
+using std::cout;
+using boost::shared_ptr;
- root->add_child("ReelNumber")->add_child_text (lexical_cast<string> (_reel_number));
+using boost::optional;
+using namespace dcp;
+
+SubtitleContent::SubtitleContent (boost::filesystem::path file, bool mxf)
+ : Content (file)
+ , _need_sort (false)
+{
+ shared_ptr<cxml::Document> xml;
+
+ if (mxf) {
+ ASDCP::TimedText::MXFReader reader;
+ Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
+ if (ASDCP_FAILURE (r)) {
+ boost::throw_exception (MXFFileError ("could not open MXF file for reading", file, r));
+ }
+
+ string s;
+ reader.ReadTimedTextResource (s, 0, 0);
+ xml.reset (new cxml::Document ("SubtitleReel"));
+ stringstream t;
+ t << s;
+ xml->read_stream (t);
+
+ ASDCP::WriterInfo info;
+ reader.FillWriterInfo (info);
+
+ char buffer[64];
+ Kumu::bin2UUIDhex (info.AssetUUID, ASDCP::UUIDlen, buffer, sizeof (buffer));
+ _id = buffer;
+
+ } else {
+ xml.reset (new cxml::Document ("DCSubtitle"));
+ xml->read_file (file);
+ _id = xml->string_child ("SubtitleID");
+ }
+
+ /* XXX: hacks aplenty in here; probably need separate parsers for DCSubtitle and SubtitleReel */
+
+ _movie_title = xml->optional_string_child ("MovieTitle");
+ _reel_number = xml->string_child ("ReelNumber");
+ _language = xml->string_child ("Language");
+
+ xml->ignore_child ("LoadFont");
+
+ list<shared_ptr<dcp::Font> > font_nodes = type_children<dcp::Font> (xml, "Font");
+ _load_font_nodes = type_children<dcp::LoadFont> (xml, "LoadFont");
+
+ /* Now make Subtitle objects to represent the raw XML nodes
+ in a sane way.
+ */
+
+ shared_ptr<cxml::Node> subtitle_list = xml->optional_node_child ("SubtitleList");
+ if (subtitle_list) {
+ list<shared_ptr<dcp::Font> > font = type_children<dcp::Font> (subtitle_list, "Font");
+ copy (font.begin(), font.end(), back_inserter (font_nodes));
+ }
+
+ ParseState parse_state;
+ examine_font_nodes (xml, font_nodes, parse_state);
+}
+
+SubtitleContent::SubtitleContent (Fraction edit_rate, string movie_title, string language)
+ : Content (edit_rate)
+ , _movie_title (movie_title)
+ , _reel_number ("1")
+ , _language (language)
+ , _need_sort (false)
+{
+
+}
+
+void
+SubtitleContent::examine_font_nodes (
+ shared_ptr<const cxml::Node> xml,
+ list<shared_ptr<dcp::Font> > const & font_nodes,
+ ParseState& parse_state
+ )
+{
+ for (list<shared_ptr<dcp::Font> >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) {
+
+ parse_state.font_nodes.push_back (*i);
+ maybe_add_subtitle ((*i)->text, parse_state);
+
+ for (list<shared_ptr<dcp::Subtitle> >::iterator j = (*i)->subtitle_nodes.begin(); j != (*i)->subtitle_nodes.end(); ++j) {
+ parse_state.subtitle_nodes.push_back (*j);
+ examine_text_nodes (xml, (*j)->text_nodes, parse_state);
+ examine_font_nodes (xml, (*j)->font_nodes, parse_state);
+ parse_state.subtitle_nodes.pop_back ();
+ }
+
+ examine_font_nodes (xml, (*i)->font_nodes, parse_state);
+ examine_text_nodes (xml, (*i)->text_nodes, parse_state);
+
+ parse_state.font_nodes.pop_back ();
+ }
+}
+
+void
+SubtitleContent::examine_text_nodes (
+ shared_ptr<const cxml::Node> xml,
+ list<shared_ptr<dcp::Text> > const & text_nodes,
+ ParseState& parse_state
+ )
+{
+ for (list<shared_ptr<dcp::Text> >::const_iterator i = text_nodes.begin(); i != text_nodes.end(); ++i) {
+ parse_state.text_nodes.push_back (*i);
+ maybe_add_subtitle ((*i)->text, parse_state);
+ examine_font_nodes (xml, (*i)->font_nodes, parse_state);
+ parse_state.text_nodes.pop_back ();
+ }
+}
+
+void
+SubtitleContent::maybe_add_subtitle (string text, ParseState const & parse_state)
+{
+ if (empty_or_white_space (text)) {
+ return;
+ }
+
+ if (parse_state.text_nodes.empty() || parse_state.subtitle_nodes.empty ()) {
+ return;
+ }
+
+ assert (!parse_state.text_nodes.empty ());
+ assert (!parse_state.subtitle_nodes.empty ());
+
+ dcp::Font effective_font (parse_state.font_nodes);
+ dcp::Text effective_text (*parse_state.text_nodes.back ());
+ dcp::Subtitle effective_subtitle (*parse_state.subtitle_nodes.back ());
+
+ _subtitles.push_back (
+ shared_ptr<SubtitleString> (
+ new SubtitleString (
+ font_id_to_name (effective_font.id),
+ effective_font.italic.get(),
+ effective_font.color.get(),
+ effective_font.size,
+ effective_subtitle.in,
+ effective_subtitle.out,
+ effective_text.v_position,
+ effective_text.v_align,
+ text,
+ effective_font.effect ? effective_font.effect.get() : NONE,
+ effective_font.effect_color.get(),
+ effective_subtitle.fade_up_time,
+ effective_subtitle.fade_down_time
+ )
+ )
+ );
+}
+
+list<shared_ptr<SubtitleString> >
+SubtitleContent::subtitles_at (Time t) const
+{
+ list<shared_ptr<SubtitleString> > s;
+ for (list<shared_ptr<SubtitleString> >::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
+ if ((*i)->in() <= t && t <= (*i)->out ()) {
+ s.push_back (*i);
+ }
+ }
+
+ return s;
+}
+
+std::string
+SubtitleContent::font_id_to_name (string id) const
+{
+ list<shared_ptr<dcp::LoadFont> >::const_iterator i = _load_font_nodes.begin();
+ while (i != _load_font_nodes.end() && (*i)->id != id) {
+ ++i;
+ }
+
+ if (i == _load_font_nodes.end ()) {
+ return "";
+ }
+
+ if ((*i)->uri && (*i)->uri.get() == "arial.ttf") {
+ return "Arial";
+ }
+
+ return "";
+}
+
+void
+SubtitleContent::add (shared_ptr<SubtitleString> s)
+{
+ _subtitles.push_back (s);
+ _need_sort = true;
+}
+
+struct SubtitleSorter {
+ bool operator() (shared_ptr<SubtitleString> a, shared_ptr<SubtitleString> b) {
+ if (a->in() != b->in()) {
+ return a->in() < b->in();
+ }
+ return a->v_position() < b->v_position();
+ }
+};
+
+void
+SubtitleContent::write_xml () const
+{
+ FILE* f = fopen_boost (file (), "r");
+ Glib::ustring const s = xml_as_string ();
+ fwrite (s.c_str(), 1, s.length(), f);
+ fclose (f);
+}
+
+Glib::ustring
+SubtitleContent::xml_as_string () const
+{
+ xmlpp::Document doc;
+ xmlpp::Element* root = doc.create_root_node ("DCSubtitle");
+ root->set_attribute ("Version", "1.0");
+
+ root->add_child("SubtitleID")->add_child_text (_id);
+ if (_movie_title) {
+ root->add_child("MovieTitle")->add_child_text (_movie_title.get ());
+ }
- font->set_attribute ("Size", lexical_cast<string> (size));
++ root->add_child("ReelNumber")->add_child_text (raw_convert<string> (_reel_number));
+ root->add_child("Language")->add_child_text (_language);
+
+ if (_load_font_nodes.size() > 1) {
+ boost::throw_exception (MiscError ("multiple LoadFont nodes not supported"));
+ }
+
+ if (!_load_font_nodes.empty ()) {
+ xmlpp::Element* load_font = root->add_child("LoadFont");
+ load_font->set_attribute ("Id", _load_font_nodes.front()->id);
+ if (_load_font_nodes.front()->uri) {
+ load_font->set_attribute ("URI", _load_font_nodes.front()->uri.get ());
+ }
+ }
+
+ list<shared_ptr<SubtitleString> > sorted = _subtitles;
+ if (_need_sort) {
+ sorted.sort (SubtitleSorter ());
+ }
+
+ /* XXX: multiple fonts not supported */
+ /* XXX: script, underlined, weight not supported */
+
+ bool italic = false;
+ Color color;
+ int size = 0;
+ Effect effect = NONE;
+ Color effect_color;
+ int spot_number = 1;
+ Time last_in;
+ Time last_out;
+ Time last_fade_up_time;
+ Time last_fade_down_time;
+
+ xmlpp::Element* font = 0;
+ xmlpp::Element* subtitle = 0;
+
+ for (list<shared_ptr<SubtitleString> >::iterator i = sorted.begin(); i != sorted.end(); ++i) {
+
+ /* We will start a new <Font>...</Font> whenever some font property changes.
+ I suppose we should really make an optimal hierarchy of <Font> tags, but
+ that seems hard.
+ */
+
+ bool const font_changed =
+ italic != (*i)->italic() ||
+ color != (*i)->color() ||
+ size != (*i)->size() ||
+ effect != (*i)->effect() ||
+ effect_color != (*i)->effect_color();
+
+ if (font_changed) {
+ italic = (*i)->italic ();
+ color = (*i)->color ();
+ size = (*i)->size ();
+ effect = (*i)->effect ();
+ effect_color = (*i)->effect_color ();
+ }
+
+ if (!font || font_changed) {
+ font = root->add_child ("Font");
+ string id = "theFontId";
+ if (!_load_font_nodes.empty()) {
+ id = _load_font_nodes.front()->id;
+ }
+ font->set_attribute ("Id", id);
+ font->set_attribute ("Italic", italic ? "yes" : "no");
+ font->set_attribute ("Color", color.to_argb_string());
- subtitle->set_attribute ("SpotNumber", lexical_cast<string> (spot_number++));
++ font->set_attribute ("Size", raw_convert<string> (size));
+ font->set_attribute ("Effect", effect_to_string (effect));
+ font->set_attribute ("EffectColor", effect_color.to_argb_string());
+ font->set_attribute ("Script", "normal");
+ font->set_attribute ("Underlined", "no");
+ font->set_attribute ("Weight", "normal");
+ }
+
+ if (!subtitle || font_changed ||
+ (last_in != (*i)->in() ||
+ last_out != (*i)->out() ||
+ last_fade_up_time != (*i)->fade_up_time() ||
+ last_fade_down_time != (*i)->fade_down_time()
+ )) {
+
+ subtitle = font->add_child ("Subtitle");
- subtitle->set_attribute ("FadeUpTime", lexical_cast<string> ((*i)->fade_up_time().to_ticks()));
- subtitle->set_attribute ("FadeDownTime", lexical_cast<string> ((*i)->fade_down_time().to_ticks()));
++ subtitle->set_attribute ("SpotNumber", raw_convert<string> (spot_number++));
+ subtitle->set_attribute ("TimeIn", (*i)->in().to_string());
+ subtitle->set_attribute ("TimeOut", (*i)->out().to_string());
- text->set_attribute ("VPosition", lexical_cast<string> ((*i)->v_position()));
++ subtitle->set_attribute ("FadeUpTime", raw_convert<string> ((*i)->fade_up_time().to_ticks()));
++ subtitle->set_attribute ("FadeDownTime", raw_convert<string> ((*i)->fade_down_time().to_ticks()));
+
+ last_in = (*i)->in ();
+ last_out = (*i)->out ();
+ last_fade_up_time = (*i)->fade_up_time ();
+ last_fade_down_time = (*i)->fade_down_time ();
+ }
+
+ xmlpp::Element* text = subtitle->add_child ("Text");
+ text->set_attribute ("VAlign", valign_to_string ((*i)->v_align()));
++ text->set_attribute ("VPosition", raw_convert<string> ((*i)->v_position()));
+ text->add_child_text ((*i)->text());
+ }
+
+ return doc.write_to_string_formatted ("UTF-8");
+}
+
*/
- #include <boost/lexical_cast.hpp>
++#include "raw_convert.h"
+#include "types.h"
+#include "exceptions.h"
+#include <boost/algorithm/string.hpp>
#include <vector>
#include <cstdio>
#include <iomanip>
exceptions.h
gamma_lut.h
image.h
- kdm.h
key.h
- lut.h
+ local_time.h
lut_cache.h
metadata.h
- mono_picture_asset.h
+ mono_picture_mxf.h
mono_picture_frame.h
- mxf_asset.h
- picture_asset.h
- picture_asset_writer.h
+ mxf.h
+ mxf_writer.h
+ object.h
+ picture_mxf.h
+ picture_mxf_writer.h
+ raw_convert.h
rgb_xyz.h
- rec709_linearised_gamma_lut.h
reel.h
+ reel_asset.h
+ reel_mono_picture_asset.h
+ reel_picture_asset.h
+ reel_sound_asset.h
+ reel_stereo_picture_asset.h
+ ref.h
argb_frame.h
signer.h
signer_chain.h