diff options
| author | Carl Hetherington <cth@carlh.net> | 2013-06-18 15:07:41 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2013-06-18 15:07:41 +0100 |
| commit | c2bac22380bea453665a24c6f39200a977771daf (patch) | |
| tree | a464d94724318b81484eb3011fa0a946205550c3 /src | |
| parent | 564f68cb2c258e61c1e70950c9d036859ea8619a (diff) | |
| parent | 59617eb2230e47b59882c4f9ca6092ce05f53cf1 (diff) | |
Merge master.
Diffstat (limited to 'src')
| -rw-r--r-- | src/argb_frame.cc | 14 | ||||
| -rw-r--r-- | src/argb_frame.h | 14 | ||||
| -rw-r--r-- | src/asset.cc | 58 | ||||
| -rw-r--r-- | src/asset.h | 61 | ||||
| -rw-r--r-- | src/cpl.cc | 460 | ||||
| -rw-r--r-- | src/cpl.h | 111 | ||||
| -rw-r--r-- | src/cpl_file.cc | 148 | ||||
| -rw-r--r-- | src/dcp.cc | 558 | ||||
| -rw-r--r-- | src/dcp.h | 102 | ||||
| -rw-r--r-- | src/dcp_time.cc | 2 | ||||
| -rw-r--r-- | src/gamma_lut.cc | 16 | ||||
| -rw-r--r-- | src/gamma_lut.h | 13 | ||||
| -rw-r--r-- | src/lut.h (renamed from src/test_mode.h) | 46 | ||||
| -rw-r--r-- | src/lut_cache.h | 28 | ||||
| -rw-r--r-- | src/metadata.cc | 30 | ||||
| -rw-r--r-- | src/metadata.h | 29 | ||||
| -rw-r--r-- | src/mxf_asset.cc | 54 | ||||
| -rw-r--r-- | src/mxf_asset.h | 47 | ||||
| -rw-r--r-- | src/parse/asset_map.cc (renamed from src/asset_map.cc) | 40 | ||||
| -rw-r--r-- | src/parse/asset_map.h (renamed from src/asset_map.h) | 16 | ||||
| -rw-r--r-- | src/parse/cpl.cc | 147 | ||||
| -rw-r--r-- | src/parse/cpl.h (renamed from src/cpl_file.h) | 52 | ||||
| -rw-r--r-- | src/parse/pkl.cc (renamed from src/pkl_file.cc) | 36 | ||||
| -rw-r--r-- | src/parse/pkl.h (renamed from src/pkl_file.h) | 16 | ||||
| -rw-r--r-- | src/parse/subtitle.cc | 135 | ||||
| -rw-r--r-- | src/parse/subtitle.h | 93 | ||||
| -rw-r--r-- | src/picture_asset.cc | 323 | ||||
| -rw-r--r-- | src/picture_asset.h | 182 | ||||
| -rw-r--r-- | src/picture_frame.cc | 55 | ||||
| -rw-r--r-- | src/picture_frame.h | 16 | ||||
| -rw-r--r-- | src/reel.cc | 24 | ||||
| -rw-r--r-- | src/reel.h | 4 | ||||
| -rw-r--r-- | src/sound_asset.cc | 223 | ||||
| -rw-r--r-- | src/sound_asset.h | 75 | ||||
| -rw-r--r-- | src/sound_frame.cc | 4 | ||||
| -rw-r--r-- | src/subtitle_asset.cc | 251 | ||||
| -rw-r--r-- | src/subtitle_asset.h | 91 | ||||
| -rw-r--r-- | src/test_mode.cc | 45 | ||||
| -rw-r--r-- | src/types.cc | 12 | ||||
| -rw-r--r-- | src/types.h | 15 | ||||
| -rw-r--r-- | src/util.cc | 64 | ||||
| -rw-r--r-- | src/util.h | 28 | ||||
| -rw-r--r-- | src/wscript | 24 | ||||
| -rw-r--r-- | src/xml.cc | 263 | ||||
| -rw-r--r-- | src/xml.h | 156 |
45 files changed, 2316 insertions, 1865 deletions
diff --git a/src/argb_frame.cc b/src/argb_frame.cc index 8e54e3b4..a48f80bb 100644 --- a/src/argb_frame.cc +++ b/src/argb_frame.cc @@ -21,16 +21,14 @@ using namespace libdcp; -/** Construct an empty ARGBFrame with a given width and height and with +/** Construct an empty ARGBFrame of a given size and with * undefined contents. - * @param width Width in pixels. - * @param height Height in pixels. + * @param size Size in pixels. */ -ARGBFrame::ARGBFrame (int width, int height) - : _width (width) - , _height (height) +ARGBFrame::ARGBFrame (Size size) + : _size (size) { - _data = new uint8_t[width * height * 4]; + _data = new uint8_t[_size.width * _size.height * 4]; } @@ -43,5 +41,5 @@ ARGBFrame::~ARGBFrame () int ARGBFrame::stride () const { - return _width * 4; + return _size.width * 4; } diff --git a/src/argb_frame.h b/src/argb_frame.h index c5c35768..a9946bb0 100644 --- a/src/argb_frame.h +++ b/src/argb_frame.h @@ -22,6 +22,7 @@ */ #include <stdint.h> +#include "util.h" namespace libdcp { @@ -44,7 +45,7 @@ namespace libdcp class ARGBFrame { public: - ARGBFrame (int width, int height); + ARGBFrame (Size size); ~ARGBFrame (); uint8_t* data () const { @@ -54,17 +55,12 @@ public: /** Length of one picture row in bytes */ int stride () const; - int width () const { - return _width; - } - - int height () const { - return _height; + Size size () const { + return _size; } private: - int _width; - int _height; + Size _size; uint8_t* _data; }; diff --git a/src/asset.cc b/src/asset.cc index fa6947d1..c566a1e5 100644 --- a/src/asset.cc +++ b/src/asset.cc @@ -25,6 +25,7 @@ #include <fstream> #include <boost/filesystem.hpp> #include <boost/lexical_cast.hpp> +#include <boost/function.hpp> #include <libxml++/nodes/element.h> #include "AS_DCP.h" #include "KM_util.h" @@ -36,10 +37,14 @@ using namespace std; using namespace boost; using namespace libdcp; -Asset::Asset (string directory, string file_name) +Asset::Asset (string directory, string file_name, int edit_rate, int intrinsic_duration) : _directory (directory) , _file_name (file_name) , _uuid (make_uuid ()) + , _edit_rate (edit_rate) + , _entry_point (0) + , _intrinsic_duration (intrinsic_duration) + , _duration (intrinsic_duration) { if (_file_name.empty ()) { _file_name = _uuid + ".xml"; @@ -47,30 +52,27 @@ Asset::Asset (string directory, string file_name) } void -Asset::write_to_pkl (xmlpp::Element* p) const +Asset::write_to_pkl (xmlpp::Node* node) const { - xmlpp::Element* asset = p->add_child("Asset"); - asset->add_child("Id")->add_child_text("urn:uuid:" + _uuid); + 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); - asset->add_child("Hash")->add_child_text (digest()); - asset->add_child("Size")->add_child_text (boost::lexical_cast<string> (filesystem::file_size(path()))); + asset->add_child("Hash")->add_child_text (digest ()); + asset->add_child("Size")->add_child_text (lexical_cast<string> (filesystem::file_size(path()))); asset->add_child("Type")->add_child_text ("application/mxf"); } void -Asset::write_to_assetmap (ostream& s) const +Asset::write_to_assetmap (xmlpp::Node* node) const { - s << " <Asset>\n" - << " <Id>urn:uuid:" << _uuid << "</Id>\n" - << " <ChunkList>\n" - << " <Chunk>\n" - << " <Path>" << _file_name << "</Path>\n" - << " <VolumeIndex>1</VolumeIndex>\n" - << " <Offset>0</Offset>\n" - << " <Length>" << filesystem::file_size(path()) << "</Length>\n" - << " </Chunk>\n" - << " </ChunkList>\n" - << " </Asset>\n"; + xmlpp::Node* asset = node->add_child ("Asset"); + asset->add_child("Id")->add_child_text ("urn:uuid:" + _uuid); + 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); + 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> (filesystem::file_size(path()))); } filesystem::path @@ -92,3 +94,23 @@ Asset::digest () const return _digest; } +bool +Asset::equals (shared_ptr<const Asset> other, EqualityOptions, boost::function<void (NoteType, string)> note) const +{ + if (_edit_rate != other->_edit_rate) { + note (ERROR, "MXF edit rates differ"); + return false; + } + + if (_intrinsic_duration != other->_intrinsic_duration) { + note (ERROR, "MXF intrinsic durations differ"); + return false; + } + + if (_duration != other->_duration) { + note (ERROR, "MXF durations differ"); + return false; + } + + return true; +} diff --git a/src/asset.h b/src/asset.h index 5436e05c..3dffa9ab 100644 --- a/src/asset.h +++ b/src/asset.h @@ -27,6 +27,8 @@ #include <string> #include <list> #include <boost/filesystem.hpp> +#include <boost/function.hpp> +#include <libxml++/libxml++.h> #include "types.h" namespace ASDCP { @@ -51,37 +53,72 @@ public: * @param directory Directory where our XML or MXF file is. * @param file_name Name of our file within directory, or empty to make one up based on UUID. */ - Asset (std::string directory, std::string file_name = ""); + Asset (std::string directory, std::string file_name = "", int edit_rate = 0, int intrinsic_duration = 0); virtual ~Asset() {} /** Write details of the asset to a CPL AssetList node. * @param p Parent node. */ - virtual void write_to_cpl (xmlpp::Element* p) const = 0; + virtual void write_to_cpl (xmlpp::Node *) const = 0; /** Write details of the asset to a PKL AssetList node. * @param p Parent node. */ - void write_to_pkl (xmlpp::Element* p) const; + void write_to_pkl (xmlpp::Node *) const; /** Write details of the asset to a ASSETMAP stream. * @param s Stream. */ - void write_to_assetmap (std::ostream& s) const; + void write_to_assetmap (xmlpp::Node *) const; std::string uuid () const { return _uuid; } - virtual bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, std::list<std::string>& notes) const = 0; + boost::filesystem::path path () const; + + void set_directory (std::string d) { + _directory = d; + } + + void set_file_name (std::string f) { + _file_name = f; + } + + int entry_point () const { + return _entry_point; + } + + int duration () const { + return _duration; + } + + int intrinsic_duration () const { + return _intrinsic_duration; + } + + int edit_rate () const { + return _edit_rate; + } + + void set_entry_point (int e) { + _entry_point = e; + } + + void set_duration (int d) { + _duration = d; + } + + void set_intrinsic_duration (int d) { + _intrinsic_duration = d; + } + + virtual bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, std::string)>) const; protected: - friend class PictureAsset; - friend class SoundAsset; std::string digest () const; - boost::filesystem::path path () const; /** Directory that our MXF or XML file is in */ std::string _directory; @@ -89,6 +126,14 @@ protected: std::string _file_name; /** Our UUID */ std::string _uuid; + /** The edit rate; this is normally equal to the number of video frames per second */ + int _edit_rate; + /** Start point to present in frames */ + int _entry_point; + /** Total length in frames */ + int _intrinsic_duration; + /** Length to present in frames */ + int _duration; private: /** Digest of our MXF or XML file */ diff --git a/src/cpl.cc b/src/cpl.cc new file mode 100644 index 00000000..1ca64f88 --- /dev/null +++ b/src/cpl.cc @@ -0,0 +1,460 @@ +/* + Copyright (C) 2012 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 <fstream> +#include "cpl.h" +#include "parse/cpl.h" +#include "util.h" +#include "picture_asset.h" +#include "sound_asset.h" +#include "subtitle_asset.h" +#include "parse/asset_map.h" +#include "reel.h" +#include "metadata.h" +#include "encryption.h" + +using std::string; +using std::stringstream; +using std::ofstream; +using std::ostream; +using std::list; +using boost::shared_ptr; +using boost::lexical_cast; +using namespace libdcp; + +CPL::CPL (string directory, string name, ContentKind content_kind, int length, int frames_per_second) + : _directory (directory) + , _name (name) + , _content_kind (content_kind) + , _length (length) + , _fps (frames_per_second) +{ + _uuid = make_uuid (); +} + +/** 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_map The corresponding asset map. + * @param require_mxfs true to throw an exception if a required MXF file does not exist. + */ +CPL::CPL (string directory, string file, shared_ptr<const libdcp::parse::AssetMap> asset_map, bool require_mxfs) + : _directory (directory) + , _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)); + } + + /* Now cherry-pick the required bits into our own data structure */ + + _name = cpl->annotation_text; + _content_kind = cpl->content_kind; + + for (list<shared_ptr<libdcp::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) { + + try { + picture.reset (new MonoPictureAsset ( + _directory, + asset_map->asset_from_id (p->id)->chunks.front()->path + ) + ); + + picture->set_entry_point (p->entry_point); + picture->set_duration (p->duration); + } catch (MXFFileError) { + if (require_mxfs) { + throw; + } + } + + } else { + try { + picture.reset (new StereoPictureAsset ( + _directory, + asset_map->asset_from_id (p->id)->chunks.front()->path, + _fps, + p->duration + ) + ); + + picture->set_entry_point (p->entry_point); + picture->set_duration (p->duration); + + } catch (MXFFileError) { + if (require_mxfs) { + throw; + } + } + + } + + if ((*i)->asset_list->main_sound) { + + try { + sound.reset (new SoundAsset ( + _directory, + asset_map->asset_from_id ((*i)->asset_list->main_sound->id)->chunks.front()->path + ) + ); + + sound->set_entry_point ((*i)->asset_list->main_sound->entry_point); + sound->set_duration ((*i)->asset_list->main_sound->duration); + } catch (MXFFileError) { + if (require_mxfs) { + throw; + } + } + } + + if ((*i)->asset_list->main_subtitle) { + + subtitle.reset (new SubtitleAsset ( + _directory, + asset_map->asset_from_id ((*i)->asset_list->main_subtitle->id)->chunks.front()->path + ) + ); + + 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))); + } +} + +void +CPL::add_reel (shared_ptr<const Reel> reel) +{ + _reels.push_back (reel); +} + +void +CPL::write_xml (shared_ptr<Encryption> crypt, XMLMetadata const & metadata) const +{ + boost::filesystem::path p; + p /= _directory; + stringstream s; + s << _uuid << "_cpl.xml"; + p /= s.str(); + + xmlpp::Document doc; + xmlpp::Element* root = doc.create_root_node ("CompositionPlaylist", "http://www.smpte-ra.org/schemas/429-7/2006/CPL"); + + if (crypt) { + root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig"); + } + + root->add_child("Id")->add_child_text ("urn:uuid:" + _uuid); + root->add_child("AnnotationText")->add_child_text (_name); + root->add_child("IssueDate")->add_child_text (metadata.issue_date); + root->add_child("Creator")->add_child_text (metadata.creator); + root->add_child("ContentTitleText")->add_child_text (_name); + root->add_child("ContentKind")->add_child_text (content_kind_to_string (_content_kind)); + { + xmlpp::Node* cv = root->add_child ("ContentVersion"); + cv->add_child ("Id")->add_child_text ("urn:uri:" + _uuid + "_" + metadata.issue_date); + cv->add_child ("LabelText")->add_child_text (_uuid + "_" + metadata.issue_date); + } + root->add_child("RatingList"); + + xmlpp::Node* reel_list = root->add_child ("ReelList"); + + for (list<shared_ptr<const Reel> >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) { + (*i)->write_to_cpl (reel_list); + } + + if (crypt) { + sign (root, crypt->certificates, crypt->signer_key); + } + + doc.write_to_file_formatted (p.string (), "UTF-8"); + + _digest = make_digest (p.string ()); + _length = boost::filesystem::file_size (p.string ()); +} + +void +CPL::write_to_pkl (xmlpp::Node* node) const +{ + xmlpp::Node* asset = node->add_child ("Asset"); + asset->add_child("Id")->add_child_text ("urn:uuid:" + _uuid); + asset->add_child("Hash")->add_child_text (_digest); + asset->add_child("Size")->add_child_text (lexical_cast<string> (_length)); + asset->add_child("Type")->add_child_text ("text/xml"); +} + +list<shared_ptr<const Asset> > +CPL::assets () const +{ + list<shared_ptr<const Asset> > a; + for (list<shared_ptr<const Reel> >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) { + if ((*i)->main_picture ()) { + a.push_back ((*i)->main_picture ()); + } + if ((*i)->main_sound ()) { + a.push_back ((*i)->main_sound ()); + } + if ((*i)->main_subtitle ()) { + a.push_back ((*i)->main_subtitle ()); + } + } + + return a; +} + +void +CPL::write_to_assetmap (xmlpp::Node* node) const +{ + xmlpp::Node* asset = node->add_child ("Asset"); + asset->add_child("Id")->add_child_text ("urn:uuid:" + _uuid); + xmlpp::Node* chunk_list = asset->add_child ("ChunkList"); + xmlpp::Node* chunk = chunk_list->add_child ("Chunk"); + chunk->add_child("Path")->add_child_text (_uuid + "_cpl.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> (_length)); +} + + + +bool +CPL::equals (CPL const & other, EqualityOptions opt, boost::function<void (NoteType, string)> note) const +{ + if (_name != other._name && !opt.cpl_names_can_differ) { + stringstream s; + s << "names differ: " << _name << " vs " << other._name << "\n"; + note (ERROR, s.str ()); + return false; + } + + if (_content_kind != other._content_kind) { + note (ERROR, "content kinds differ"); + return false; + } + + if (_fps != other._fps) { + note (ERROR, "frames per second differ"); + return false; + } + + if (_length != other._length) { + note (ERROR, "lengths differ"); + return false; + } + + if (_reels.size() != other._reels.size()) { + note (ERROR, "reel counts differ"); + return false; + } + + list<shared_ptr<const Reel> >::const_iterator a = _reels.begin (); + list<shared_ptr<const Reel> >::const_iterator b = other._reels.begin (); + + while (a != _reels.end ()) { + if (!(*a)->equals (*b, opt, note)) { + return false; + } + ++a; + ++b; + } + + return true; +} + +shared_ptr<xmlpp::Document> +CPL::make_kdm ( + CertificateChain const & certificates, + string const & signer_key, + shared_ptr<const Certificate> recipient_cert, + boost::posix_time::ptime from, + boost::posix_time::ptime until + ) const +{ + assert (recipient_cert); + + shared_ptr<xmlpp::Document> doc (new xmlpp::Document); + xmlpp::Element* root = doc->create_root_node ("DCinemaSecurityMessage"); + root->set_namespace_declaration ("http://www.smpte-ra.org/schemas/430-3/2006/ETM", ""); + root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds"); + root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc"); + + { + xmlpp::Element* authenticated_public = root->add_child("AuthenticatedPublic"); + authenticated_public->set_attribute("Id", "ID_AuthenticatedPublic"); + xmlAddID (0, doc->cobj(), (const xmlChar *) "ID_AuthenticatedPublic", authenticated_public->get_attribute("Id")->cobj()); + + authenticated_public->add_child("MessageId")->add_child_text("urn:uuid:" + make_uuid()); + authenticated_public->add_child("MessageType")->add_child_text("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type"); + authenticated_public->add_child("AnnotationText")->add_child_text(Metadata::instance()->product_name); + authenticated_public->add_child("IssueDate")->add_child_text(Metadata::instance()->issue_date); + + { + xmlpp::Element* signer = authenticated_public->add_child("Signer"); + signer->add_child("X509IssuerName", "ds")->add_child_text ( + Certificate::name_for_xml (recipient_cert->issuer()) + ); + signer->add_child("X509SerialNumber", "ds")->add_child_text ( + recipient_cert->serial() + ); + } + + { + xmlpp::Element* required_extensions = authenticated_public->add_child("RequiredExtensions"); + + { + xmlpp::Element* kdm_required_extensions = required_extensions->add_child("KDMRequiredExtensions"); + kdm_required_extensions->set_namespace_declaration ("http://www.smpte-ra.org/schemas/430-1/2006/KDM"); + { + xmlpp::Element* recipient = kdm_required_extensions->add_child("Recipient"); + { + xmlpp::Element* serial_element = recipient->add_child("X509IssuerSerial"); + serial_element->add_child("X509IssuerName", "ds")->add_child_text ( + Certificate::name_for_xml (recipient_cert->issuer()) + ); + serial_element->add_child("X509SerialNumber", "ds")->add_child_text ( + recipient_cert->serial() + ); + } + + recipient->add_child("X509SubjectName")->add_child_text (Certificate::name_for_xml (recipient_cert->subject())); + } + + kdm_required_extensions->add_child("CompositionPlaylistId")->add_child_text("urn:uuid:" + _uuid); + kdm_required_extensions->add_child("ContentTitleText")->add_child_text(_name); + kdm_required_extensions->add_child("ContentAuthenticator")->add_child_text(certificates.leaf()->thumbprint()); + kdm_required_extensions->add_child("ContentKeysNotValidBefore")->add_child_text("XXX"); + kdm_required_extensions->add_child("ContentKeysNotValidAfter")->add_child_text("XXX"); + + { + xmlpp::Element* authorized_device_info = kdm_required_extensions->add_child("AuthorizedDeviceInfo"); + authorized_device_info->add_child("DeviceListIdentifier")->add_child_text("urn:uuid:" + make_uuid()); + authorized_device_info->add_child("DeviceListDescription")->add_child_text(recipient_cert->subject()); + { + xmlpp::Element* device_list = authorized_device_info->add_child("DeviceList"); + device_list->add_child("CertificateThumbprint")->add_child_text(recipient_cert->thumbprint()); + } + } + + { + xmlpp::Element* key_id_list = kdm_required_extensions->add_child("KeyIdList"); + list<shared_ptr<const Asset> > a = assets(); + for (list<shared_ptr<const Asset> >::iterator i = a.begin(); i != a.end(); ++i) { + /* XXX: non-MXF assets? */ + shared_ptr<const MXFAsset> mxf = boost::dynamic_pointer_cast<const MXFAsset> (*i); + if (mxf) { + mxf->add_typed_key_id (key_id_list); + } + } + } + + { + xmlpp::Element* forensic_mark_flag_list = kdm_required_extensions->add_child("ForensicMarkFlagList"); + forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ( + "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable" + ); + forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ( + "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable" + ); + } + } + } + + authenticated_public->add_child("NonCriticalExtensions"); + } + + { + xmlpp::Element* authenticated_private = root->add_child("AuthenticatedPrivate"); + authenticated_private->set_attribute ("Id", "ID_AuthenticatedPrivate"); + xmlAddID (0, doc->cobj(), (const xmlChar *) "ID_AuthenticatedPrivate", authenticated_private->get_attribute("Id")->cobj()); + { + xmlpp::Element* encrypted_key = authenticated_private->add_child ("EncryptedKey", "enc"); + { + xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc"); + encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"); + encryption_method->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1"); + } + + xmlpp::Element* cipher_data = authenticated_private->add_child ("CipherData", "enc"); + cipher_data->add_child("CipherValue", "enc")->add_child_text("XXX"); + } + } + + /* XXX: x2 one for each mxf? */ + + { + xmlpp::Element* signature = root->add_child("Signature", "ds"); + + { + xmlpp::Element* signed_info = signature->add_child("SignedInfo", "ds"); + signed_info->add_child("CanonicalizationMethod", "ds")->set_attribute( + "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" + ); + signed_info->add_child("SignatureMethod", "ds")->set_attribute( + "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" + ); + { + xmlpp::Element* reference = signed_info->add_child("Reference", "ds"); + reference->set_attribute("URI", "#ID_AuthenticatedPublic"); + reference->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256"); + reference->add_child("DigestValue", "ds"); + } + + { + xmlpp::Element* reference = signed_info->add_child("Reference", "ds"); + reference->set_attribute("URI", "#ID_AuthenticatedPrivate"); + reference->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256"); + reference->add_child("DigestValue", "ds"); + } + } + + add_signature_value (signature, certificates, signer_key, "ds"); + } + + return doc; +} diff --git a/src/cpl.h b/src/cpl.h new file mode 100644 index 00000000..f9814337 --- /dev/null +++ b/src/cpl.h @@ -0,0 +1,111 @@ +/* + Copyright (C) 2012 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 <list> +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <libxml++/libxml++.h> +#include "types.h" +#include "certificates.h" + +namespace libdcp { + +namespace parse { + class AssetMap; +} + +class Asset; +class Reel; +class XMLMetadata; + class Encryption; + +/** @brief A CPL within a DCP */ +class CPL +{ +public: + CPL (std::string directory, std::string name, ContentKind content_kind, int length, int frames_per_second); + CPL (std::string directory, std::string file, boost::shared_ptr<const parse::AssetMap> asset_map, bool require_mxfs = true); + + void add_reel (boost::shared_ptr<const Reel> reel); + + /** @return the length in frames */ + int length () const { + return _length; + } + + /** @return the type of the content, used by media servers + * to categorise things (e.g. feature, trailer, etc.) + */ + ContentKind content_kind () const { + return _content_kind; + } + + std::list<boost::shared_ptr<const Reel> > reels () const { + return _reels; + } + + /** @return the CPL's name, as will be presented on projector + * media servers and theatre management systems. + */ + std::string name () const { + return _name; + } + + /** @return the number of frames per second */ + int frames_per_second () const { + return _fps; + } + + std::list<boost::shared_ptr<const Asset> > assets () const; + + bool equals (CPL const & other, EqualityOptions options, boost::function<void (NoteType, std::string)> note) const; + + void write_xml (XMLMetadata const &, boost::shared_ptr<Encryption>) const; + void write_to_assetmap (xmlpp::Node *) const; + void write_to_pkl (xmlpp::Node *) const; + + boost::shared_ptr<xmlpp::Document> make_kdm ( + CertificateChain const &, + std::string const &, + boost::shared_ptr<const Certificate>, + boost::posix_time::ptime from, + boost::posix_time::ptime until + ) const; + +private: + std::string _directory; + /** the name of the DCP */ + std::string _name; + /** the content kind of the CPL */ + ContentKind _content_kind; + /** length in frames */ + mutable int _length; + /** frames per second */ + int _fps; + /** reels */ + std::list<boost::shared_ptr<const Reel> > _reels; + + /** our UUID */ + std::string _uuid; + /** a SHA1 digest of our XML */ + mutable std::string _digest; +}; + +} diff --git a/src/cpl_file.cc b/src/cpl_file.cc deleted file mode 100644 index 6a17d721..00000000 --- a/src/cpl_file.cc +++ /dev/null @@ -1,148 +0,0 @@ -/* - Copyright (C) 2012 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/cpl_file.cc - * @brief Classes used to parse a CPL. - */ - -#include <iostream> -#include "cpl_file.h" - -using namespace std; -using namespace libdcp; - -CPLFile::CPLFile (string file) - : XMLFile (file, "CompositionPlaylist") -{ - id = string_child ("Id"); - annotation_text = optional_string_child ("AnnotationText"); - issue_date = string_child ("IssueDate"); - creator = optional_string_child ("Creator"); - content_title_text = string_child ("ContentTitleText"); - content_kind = kind_child ("ContentKind"); - content_version = optional_type_child<ContentVersion> ("ContentVersion"); - ignore_child ("RatingList"); - reels = type_grand_children<CPLReel> ("ReelList", "Reel"); - - ignore_child ("Issuer"); - ignore_child ("Signer"); - ignore_child ("Signature"); - - done (); -} - -ContentVersion::ContentVersion (xmlpp::Node const * node) - : XMLNode (node) -{ - id = optional_string_child ("Id"); - label_text = string_child ("LabelText"); - done (); -} - -CPLReel::CPLReel (xmlpp::Node const * node) - : XMLNode (node) -{ - id = string_child ("Id"); - asset_list = type_child<CPLAssetList> ("AssetList"); - - ignore_child ("AnnotationText"); - done (); -} - -CPLAssetList::CPLAssetList (xmlpp::Node const * node) - : XMLNode (node) -{ - main_picture = optional_type_child<MainPicture> ("MainPicture"); - main_stereoscopic_picture = optional_type_child<MainStereoscopicPicture> ("MainStereoscopicPicture"); - main_sound = optional_type_child<MainSound> ("MainSound"); - main_subtitle = optional_type_child<MainSubtitle> ("MainSubtitle"); - - done (); -} - -MainPicture::MainPicture (xmlpp::Node const * node) - : Picture (node) -{ - -} - -MainStereoscopicPicture::MainStereoscopicPicture (xmlpp::Node const * node) - : Picture (node) -{ - -} - -Picture::Picture (xmlpp::Node const * node) - : XMLNode (node) -{ - id = string_child ("Id"); - annotation_text = optional_string_child ("AnnotationText"); - edit_rate = fraction_child ("EditRate"); - intrinsic_duration = int64_child ("IntrinsicDuration"); - entry_point = int64_child ("EntryPoint"); - duration = int64_child ("Duration"); - frame_rate = fraction_child ("FrameRate"); - try { - screen_aspect_ratio = fraction_child ("ScreenAspectRatio"); - } catch (XMLError& e) { - /* Maybe it's not a fraction */ - } - try { - float f = float_child ("ScreenAspectRatio"); - screen_aspect_ratio = Fraction (f * 1000, 1000); - } catch (bad_cast& e) { - - } - - ignore_child ("Hash"); - - done (); -} - -MainSound::MainSound (xmlpp::Node const * node) - : XMLNode (node) -{ - id = string_child ("Id"); - annotation_text = optional_string_child ("AnnotationText"); - edit_rate = fraction_child ("EditRate"); - intrinsic_duration = int64_child ("IntrinsicDuration"); - entry_point = int64_child ("EntryPoint"); - duration = int64_child ("Duration"); - - ignore_child ("Hash"); - ignore_child ("Language"); - - done (); -} - -MainSubtitle::MainSubtitle (xmlpp::Node const * node) - : XMLNode (node) -{ - id = string_child ("Id"); - annotation_text = optional_string_child ("AnnotationText"); - edit_rate = fraction_child ("EditRate"); - intrinsic_duration = int64_child ("IntrinsicDuration"); - entry_point = int64_child ("EntryPoint"); - duration = int64_child ("Duration"); - - ignore_child ("Hash"); - ignore_child ("Language"); - - done (); -} @@ -29,6 +29,7 @@ #include <boost/filesystem.hpp> #include <boost/lexical_cast.hpp> #include <boost/algorithm/string.hpp> +#include <boost/lexical_cast.hpp> #include <libxml++/libxml++.h> #include <xmlsec/xmldsig.h> #include <xmlsec/app.h> @@ -40,10 +41,11 @@ #include "util.h" #include "metadata.h" #include "exceptions.h" -#include "cpl_file.h" -#include "pkl_file.h" -#include "asset_map.h" +#include "parse/pkl.h" +#include "parse/asset_map.h" #include "reel.h" +#include "cpl.h" +#include "encryption.h" using std::string; using std::list; @@ -51,6 +53,7 @@ using std::stringstream; using std::ofstream; using std::ostream; using boost::shared_ptr; +using boost::lexical_cast; using namespace libdcp; DCP::DCP (string directory) @@ -60,21 +63,21 @@ DCP::DCP (string directory) } void -DCP::write_xml (shared_ptr<Encryption> crypt) const +DCP::write_xml (XMLMetadata const & metadata, shared_ptr<Encryption> crypt) const { for (list<shared_ptr<const CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) { - (*i)->write_xml (crypt); + (*i)->write_xml (metadata, crypt); } string pkl_uuid = make_uuid (); - string pkl_path = write_pkl (pkl_uuid, crypt); + string pkl_path = write_pkl (pkl_uuid, metadata, crypt); write_volindex (); - write_assetmap (pkl_uuid, boost::filesystem::file_size (pkl_path)); + write_assetmap (pkl_uuid, boost::filesystem::file_size (pkl_path), metadata); } std::string -DCP::write_pkl (string pkl_uuid, shared_ptr<Encryption> crypt) const +DCP::write_pkl (string pkl_uuid, XMLMetadata const & metadata, shared_ptr<Encryption> crypt) const { assert (!_cpls.empty ()); @@ -93,28 +96,25 @@ DCP::write_pkl (string pkl_uuid, shared_ptr<Encryption> crypt) const pkl->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid); /* XXX: this is a bit of a hack */ pkl->add_child("AnnotationText")->add_child_text(_cpls.front()->name()); - pkl->add_child("IssueDate")->add_child_text (Metadata::instance()->issue_date); - pkl->add_child("Issuer")->add_child_text (Metadata::instance()->issuer); - pkl->add_child("Creator")->add_child_text (Metadata::instance()->creator); - - { - xmlpp::Element* asset_list = pkl->add_child("AssetList"); - 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_pkl (asset_list); - } + pkl->add_child("IssueDate")->add_child_text (metadata.issue_date); + pkl->add_child("Issuer")->add_child_text (metadata.issuer); + pkl->add_child("Creator")->add_child_text (metadata.creator); - for (list<shared_ptr<const CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) { - (*i)->write_to_pkl (asset_list); - } + xmlpp::Element* asset_list = pkl->add_child("AssetList"); + 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_pkl (asset_list); + } + + for (list<shared_ptr<const CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) { + (*i)->write_to_pkl (asset_list); } if (crypt) { sign (pkl, crypt->certificates, crypt->signer_key); } - doc.write_to_file_formatted (p.string(), "UTF-8"); - + doc.write_to_file_formatted (p.string (), "UTF-8"); return p.string (); } @@ -124,55 +124,50 @@ DCP::write_volindex () const boost::filesystem::path p; p /= _directory; p /= "VOLINDEX.xml"; - ofstream vi (p.string().c_str()); - vi << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - << "<VolumeIndex xmlns=\"http://www.smpte-ra.org/schemas/429-9/2007/AM\">\n" - << " <Index>1</Index>\n" - << "</VolumeIndex>\n"; + xmlpp::Document doc; + xmlpp::Element* root = doc.create_root_node ("VolumeIndex", "http://www.smpte-ra.org/schemas/429-9/2007/AM"); + root->add_child("Index")->add_child_text ("1"); + doc.write_to_file_formatted (p.string (), "UTF-8"); } void -DCP::write_assetmap (string pkl_uuid, int pkl_length) const +DCP::write_assetmap (string pkl_uuid, int pkl_length, XMLMetadata const & metadata) const { boost::filesystem::path p; p /= _directory; p /= "ASSETMAP.xml"; - ofstream am (p.string().c_str()); - - am << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - << "<AssetMap xmlns=\"http://www.smpte-ra.org/schemas/429-9/2007/AM\">\n" - << " <Id>urn:uuid:" << make_uuid() << "</Id>\n" - << " <Creator>" << Metadata::instance()->creator << "</Creator>\n" - << " <VolumeCount>1</VolumeCount>\n" - << " <IssueDate>" << Metadata::instance()->issue_date << "</IssueDate>\n" - << " <Issuer>" << Metadata::instance()->issuer << "</Issuer>\n" - << " <AssetList>\n"; - - am << " <Asset>\n" - << " <Id>urn:uuid:" << pkl_uuid << "</Id>\n" - << " <PackingList>true</PackingList>\n" - << " <ChunkList>\n" - << " <Chunk>\n" - << " <Path>" << pkl_uuid << "_pkl.xml</Path>\n" - << " <VolumeIndex>1</VolumeIndex>\n" - << " <Offset>0</Offset>\n" - << " <Length>" << pkl_length << "</Length>\n" - << " </Chunk>\n" - << " </ChunkList>\n" - << " </Asset>\n"; + + xmlpp::Document doc; + xmlpp::Element* root = doc.create_root_node ("AssetMap", "http://www.smpte-ra.org/schemas/429-9/2007/AM"); + + root->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid()); + root->add_child("Creator")->add_child_text (metadata.creator); + root->add_child("VolumeCount")->add_child_text ("1"); + root->add_child("IssueDate")->add_child_text (metadata.issue_date); + root->add_child("Issuer")->add_child_text (metadata.issuer); + xmlpp::Node* asset_list = root->add_child ("AssetList"); + + xmlpp::Node* asset = asset_list->add_child ("Asset"); + asset->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid); + asset->add_child("PackingList")->add_child_text ("true"); + xmlpp::Node* chunk_list = asset->add_child ("ChunkList"); + xmlpp::Node* chunk = chunk_list->add_child ("Chunk"); + 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)); for (list<shared_ptr<const CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) { - (*i)->write_to_assetmap (am); + (*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 (am); + (*i)->write_to_assetmap (asset_list); } - am << " </AssetList>\n" - << "</AssetMap>\n"; + doc.write_to_file_formatted (p.string (), "UTF-8"); } @@ -181,29 +176,29 @@ DCP::read (bool require_mxfs) { Files files; - shared_ptr<AssetMap> asset_map; + shared_ptr<parse::AssetMap> asset_map; try { boost::filesystem::path p = _directory; p /= "ASSETMAP"; if (boost::filesystem::exists (p)) { - asset_map.reset (new AssetMap (p.string ())); + asset_map.reset (new libdcp::parse::AssetMap (p.string ())); } else { p = _directory; p /= "ASSETMAP.xml"; if (boost::filesystem::exists (p)) { - asset_map.reset (new AssetMap (p.string ())); + asset_map.reset (new libdcp::parse::AssetMap (p.string ())); } else { - throw DCPReadError ("could not find AssetMap file"); + boost::throw_exception (DCPReadError ("could not find AssetMap file")); } } } catch (FileError& e) { - throw FileError ("could not load AssetMap file", files.asset_map); + boost::throw_exception (FileError ("could not load AssetMap file", files.asset_map)); } - for (list<shared_ptr<AssetMapAsset> >::const_iterator i = asset_map->assets.begin(); i != asset_map->assets.end(); ++i) { + for (list<shared_ptr<libdcp::parse::AssetMapAsset> >::const_iterator i = asset_map->assets.begin(); i != asset_map->assets.end(); ++i) { if ((*i)->chunks.size() != 1) { - throw XMLError ("unsupported asset chunk count"); + boost::throw_exception (XMLError ("unsupported asset chunk count")); } boost::filesystem::path t = _directory; @@ -230,24 +225,24 @@ DCP::read (bool require_mxfs) if (files.pkl.empty ()) { files.pkl = t.string(); } else { - throw DCPReadError ("duplicate PKLs found"); + boost::throw_exception (DCPReadError ("duplicate PKLs found")); } } } if (files.cpls.empty ()) { - throw FileError ("no CPL files found", ""); + boost::throw_exception (FileError ("no CPL files found", "")); } if (files.pkl.empty ()) { - throw FileError ("no PKL file found", ""); + boost::throw_exception (FileError ("no PKL file found", "")); } - shared_ptr<PKLFile> pkl; + shared_ptr<parse::PKL> pkl; try { - pkl.reset (new PKLFile (files.pkl)); + pkl.reset (new parse::PKL (files.pkl)); } catch (FileError& e) { - throw FileError ("could not load PKL file", files.pkl); + boost::throw_exception (FileError ("could not load PKL file", files.pkl)); } /* Cross-check */ @@ -259,10 +254,10 @@ DCP::read (bool require_mxfs) } bool -DCP::equals (DCP const & other, EqualityOptions opt, list<string>& notes) const +DCP::equals (DCP const & other, EqualityOptions opt, boost::function<void (NoteType, string)> note) const { if (_cpls.size() != other._cpls.size()) { - notes.push_back ("CPL counts differ"); + note (ERROR, "CPL counts differ"); return false; } @@ -270,7 +265,7 @@ DCP::equals (DCP const & other, EqualityOptions opt, list<string>& notes) const list<shared_ptr<const CPL> >::const_iterator b = other._cpls.begin (); while (a != _cpls.end ()) { - if (!(*a)->equals (*b->get(), opt, notes)) { + if (!(*a)->equals (*b->get(), opt, note)) { return false; } ++a; @@ -280,7 +275,6 @@ DCP::equals (DCP const & other, EqualityOptions opt, list<string>& notes) const return true; } - void DCP::add_cpl (shared_ptr<CPL> cpl) { @@ -308,421 +302,3 @@ DCP::assets () const a.unique (); return a; } - -CPL::CPL (string directory, string name, ContentKind content_kind, int length, int frames_per_second) - : _directory (directory) - , _name (name) - , _content_kind (content_kind) - , _length (length) - , _fps (frames_per_second) -{ - _uuid = make_uuid (); -} - -/** 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_map The corresponding asset map. - * @param require_mxfs true to throw an exception if a required MXF file does not exist. - */ -CPL::CPL (string directory, string file, shared_ptr<const AssetMap> asset_map, bool require_mxfs) - : _directory (directory) - , _content_kind (FEATURE) - , _length (0) - , _fps (0) -{ - /* Read the XML */ - shared_ptr<CPLFile> cpl; - try { - cpl.reset (new CPLFile (file)); - } catch (FileError& e) { - throw FileError ("could not load CPL file", file); - } - - /* Now cherry-pick the required bits into our own data structure */ - - _name = cpl->annotation_text; - _content_kind = cpl->content_kind; - - for (list<shared_ptr<CPLReel> >::iterator i = cpl->reels.begin(); i != cpl->reels.end(); ++i) { - - shared_ptr<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) { - - try { - picture.reset (new MonoPictureAsset ( - _directory, - asset_map->asset_from_id (p->id)->chunks.front()->path, - _fps, - (*i)->asset_list->main_picture->entry_point, - (*i)->asset_list->main_picture->duration - ) - ); - } catch (MXFFileError) { - if (require_mxfs) { - throw; - } - } - - } else { - - try { - picture.reset (new StereoPictureAsset ( - _directory, - asset_map->asset_from_id (p->id)->chunks.front()->path, - _fps, - p->entry_point, - p->duration - ) - ); - } catch (MXFFileError) { - if (require_mxfs) { - throw; - } - } - - } - - if ((*i)->asset_list->main_sound) { - - try { - sound.reset (new SoundAsset ( - _directory, - asset_map->asset_from_id ((*i)->asset_list->main_sound->id)->chunks.front()->path, - _fps, - (*i)->asset_list->main_sound->entry_point, - (*i)->asset_list->main_sound->duration - ) - ); - } catch (MXFFileError) { - if (require_mxfs) { - throw; - } - } - } - - if ((*i)->asset_list->main_subtitle) { - - subtitle.reset (new SubtitleAsset ( - _directory, - asset_map->asset_from_id ((*i)->asset_list->main_subtitle->id)->chunks.front()->path - ) - ); - } - - _reels.push_back (shared_ptr<Reel> (new Reel (picture, sound, subtitle))); - } -} - -void -CPL::add_reel (shared_ptr<const Reel> reel) -{ - _reels.push_back (reel); -} - -void -CPL::write_xml (shared_ptr<Encryption> crypt) const -{ - boost::filesystem::path p; - p /= _directory; - stringstream s; - s << _uuid << "_cpl.xml"; - p /= s.str(); - - xmlpp::Document doc; - xmlpp::Element* cpl = doc.create_root_node("CompositionPlaylist", "http://www.smpte-ra.org/schemas/429-7/2006/CPL"); - - if (crypt) { - cpl->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig"); - } - - cpl->add_child("Id")->add_child_text ("urn:uuid:" + _uuid); - cpl->add_child("AnnotationText")->add_child_text (_name); - cpl->add_child("IssueDate")->add_child_text (Metadata::instance()->issue_date); - cpl->add_child("Creator")->add_child_text (Metadata::instance()->creator); - cpl->add_child("ContentTitleText")->add_child_text (_name); - cpl->add_child("ContentKind")->add_child_text (content_kind_to_string (_content_kind)); - - { - xmlpp::Element* cv = cpl->add_child ("ContentVersion"); - cv->add_child("Id")->add_child_text ("urn:uri:" + _uuid + "_" + Metadata::instance()->issue_date); - cv->add_child("LabelText")->add_child_text (_uuid + "_" + Metadata::instance()->issue_date); - } - - cpl->add_child("RatingList"); - - xmlpp::Element* reel_list = cpl->add_child("ReelList"); - for (list<shared_ptr<const Reel> >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) { - (*i)->write_to_cpl (reel_list); - } - - if (crypt) { - sign (cpl, crypt->certificates, crypt->signer_key); - } - - doc.write_to_file_formatted (p.string(), "UTF-8"); - - _digest = make_digest (p.string ()); - _length = boost::filesystem::file_size (p.string ()); -} - -void -CPL::write_to_pkl (xmlpp::Element* p) const -{ - xmlpp::Element* asset = p->add_child("Asset"); - asset->add_child("Id")->add_child_text("urn:uuid:" + _uuid); - asset->add_child("Hash")->add_child_text(_digest); - asset->add_child("Size")->add_child_text(boost::lexical_cast<string> (_length)); - asset->add_child("Type")->add_child_text("text/xml"); -} - -list<shared_ptr<const Asset> > -CPL::assets () const -{ - list<shared_ptr<const Asset> > a; - for (list<shared_ptr<const Reel> >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) { - if ((*i)->main_picture ()) { - a.push_back ((*i)->main_picture ()); - } - if ((*i)->main_sound ()) { - a.push_back ((*i)->main_sound ()); - } - if ((*i)->main_subtitle ()) { - a.push_back ((*i)->main_subtitle ()); - } - } - - return a; -} - -void -CPL::write_to_assetmap (ostream& s) const -{ - s << " <Asset>\n" - << " <Id>urn:uuid:" << _uuid << "</Id>\n" - << " <ChunkList>\n" - << " <Chunk>\n" - << " <Path>" << _uuid << "_cpl.xml</Path>\n" - << " <VolumeIndex>1</VolumeIndex>\n" - << " <Offset>0</Offset>\n" - << " <Length>" << _length << "</Length>\n" - << " </Chunk>\n" - << " </ChunkList>\n" - << " </Asset>\n"; -} - - - -bool -CPL::equals (CPL const & other, EqualityOptions opt, list<string>& notes) const -{ - if (_name != other._name) { - notes.push_back ("names differ"); - return false; - } - - if (_content_kind != other._content_kind) { - notes.push_back ("content kinds differ"); - return false; - } - - if (_fps != other._fps) { - notes.push_back ("frames per second differ"); - return false; - } - - if (_length != other._length) { - notes.push_back ("lengths differ"); - return false; - } - - if (_reels.size() != other._reels.size()) { - notes.push_back ("reel counts differ"); - return false; - } - - list<shared_ptr<const Reel> >::const_iterator a = _reels.begin (); - list<shared_ptr<const Reel> >::const_iterator b = other._reels.begin (); - - while (a != _reels.end ()) { - if (!(*a)->equals (*b, opt, notes)) { - return false; - } - ++a; - ++b; - } - - return true; -} - -shared_ptr<xmlpp::Document> -CPL::make_kdm ( - CertificateChain const & certificates, - string const & signer_key, - shared_ptr<const Certificate> recipient_cert, - boost::posix_time::ptime from, - boost::posix_time::ptime until - ) const -{ - assert (recipient_cert); - - shared_ptr<xmlpp::Document> doc (new xmlpp::Document); - xmlpp::Element* root = doc->create_root_node ("DCinemaSecurityMessage"); - root->set_namespace_declaration ("http://www.smpte-ra.org/schemas/430-3/2006/ETM", ""); - root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds"); - root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc"); - - { - xmlpp::Element* authenticated_public = root->add_child("AuthenticatedPublic"); - authenticated_public->set_attribute("Id", "ID_AuthenticatedPublic"); - xmlAddID (0, doc->cobj(), (const xmlChar *) "ID_AuthenticatedPublic", authenticated_public->get_attribute("Id")->cobj()); - - authenticated_public->add_child("MessageId")->add_child_text("urn:uuid:" + make_uuid()); - authenticated_public->add_child("MessageType")->add_child_text("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type"); - authenticated_public->add_child("AnnotationText")->add_child_text(Metadata::instance()->product_name); - authenticated_public->add_child("IssueDate")->add_child_text(Metadata::instance()->issue_date); - - { - xmlpp::Element* signer = authenticated_public->add_child("Signer"); - signer->add_child("X509IssuerName", "ds")->add_child_text ( - Certificate::name_for_xml (recipient_cert->issuer()) - ); - signer->add_child("X509SerialNumber", "ds")->add_child_text ( - recipient_cert->serial() - ); - } - - { - xmlpp::Element* required_extensions = authenticated_public->add_child("RequiredExtensions"); - - { - xmlpp::Element* kdm_required_extensions = required_extensions->add_child("KDMRequiredExtensions"); - kdm_required_extensions->set_namespace_declaration ("http://www.smpte-ra.org/schemas/430-1/2006/KDM"); - { - xmlpp::Element* recipient = kdm_required_extensions->add_child("Recipient"); - { - xmlpp::Element* serial_element = recipient->add_child("X509IssuerSerial"); - serial_element->add_child("X509IssuerName", "ds")->add_child_text ( - Certificate::name_for_xml (recipient_cert->issuer()) - ); - serial_element->add_child("X509SerialNumber", "ds")->add_child_text ( - recipient_cert->serial() - ); - } - - recipient->add_child("X509SubjectName")->add_child_text (Certificate::name_for_xml (recipient_cert->subject())); - } - - kdm_required_extensions->add_child("CompositionPlaylistId")->add_child_text("urn:uuid:" + _uuid); - kdm_required_extensions->add_child("ContentTitleText")->add_child_text(_name); - kdm_required_extensions->add_child("ContentAuthenticator")->add_child_text(certificates.leaf()->thumbprint()); - kdm_required_extensions->add_child("ContentKeysNotValidBefore")->add_child_text("XXX"); - kdm_required_extensions->add_child("ContentKeysNotValidAfter")->add_child_text("XXX"); - - { - xmlpp::Element* authorized_device_info = kdm_required_extensions->add_child("AuthorizedDeviceInfo"); - authorized_device_info->add_child("DeviceListIdentifier")->add_child_text("urn:uuid:" + make_uuid()); - authorized_device_info->add_child("DeviceListDescription")->add_child_text(recipient_cert->subject()); - { - xmlpp::Element* device_list = authorized_device_info->add_child("DeviceList"); - device_list->add_child("CertificateThumbprint")->add_child_text(recipient_cert->thumbprint()); - } - } - - { - xmlpp::Element* key_id_list = kdm_required_extensions->add_child("KeyIdList"); - list<shared_ptr<const Asset> > a = assets(); - for (list<shared_ptr<const Asset> >::iterator i = a.begin(); i != a.end(); ++i) { - /* XXX: non-MXF assets? */ - shared_ptr<const MXFAsset> mxf = boost::dynamic_pointer_cast<const MXFAsset> (*i); - if (mxf) { - mxf->add_typed_key_id (key_id_list); - } - } - } - - { - xmlpp::Element* forensic_mark_flag_list = kdm_required_extensions->add_child("ForensicMarkFlagList"); - forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ( - "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable" - ); - forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ( - "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable" - ); - } - } - } - - authenticated_public->add_child("NonCriticalExtensions"); - } - - { - xmlpp::Element* authenticated_private = root->add_child("AuthenticatedPrivate"); - authenticated_private->set_attribute ("Id", "ID_AuthenticatedPrivate"); - xmlAddID (0, doc->cobj(), (const xmlChar *) "ID_AuthenticatedPrivate", authenticated_private->get_attribute("Id")->cobj()); - { - xmlpp::Element* encrypted_key = authenticated_private->add_child ("EncryptedKey", "enc"); - { - xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc"); - encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"); - encryption_method->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1"); - } - - xmlpp::Element* cipher_data = authenticated_private->add_child ("CipherData", "enc"); - cipher_data->add_child("CipherValue", "enc")->add_child_text("XXX"); - } - } - - /* XXX: x2 one for each mxf? */ - - { - xmlpp::Element* signature = root->add_child("Signature", "ds"); - - { - xmlpp::Element* signed_info = signature->add_child("SignedInfo", "ds"); - signed_info->add_child("CanonicalizationMethod", "ds")->set_attribute( - "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" - ); - signed_info->add_child("SignatureMethod", "ds")->set_attribute( - "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" - ); - { - xmlpp::Element* reference = signed_info->add_child("Reference", "ds"); - reference->set_attribute("URI", "#ID_AuthenticatedPublic"); - reference->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256"); - reference->add_child("DigestValue", "ds"); - } - - { - xmlpp::Element* reference = signed_info->add_child("Reference", "ds"); - reference->set_attribute("URI", "#ID_AuthenticatedPrivate"); - reference->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256"); - reference->add_child("DigestValue", "ds"); - } - } - - add_signature_value (signature, certificates, signer_key, "ds"); - } - - return doc; -} @@ -28,7 +28,6 @@ #include <vector> #include <boost/shared_ptr.hpp> #include <boost/signals2.hpp> -#include <boost/date_time/posix_time/posix_time.hpp> #include "types.h" #include "certificates.h" @@ -46,93 +45,11 @@ class PictureAsset; class SoundAsset; class SubtitleAsset; class Reel; -class AssetMap; +class CPL; +class XMLMetadata; +class Encryption; -class Encryption -{ -public: - Encryption (CertificateChain c, std::string const & k) - : certificates (c) - , signer_key (k) - {} - - CertificateChain certificates; - std::string signer_key; -}; - -/** @brief A CPL within a DCP */ -class CPL -{ -public: - CPL (std::string directory, std::string name, ContentKind content_kind, int length, int frames_per_second); - CPL (std::string directory, std::string file, boost::shared_ptr<const AssetMap> asset_map, bool require_mxfs = true); - - void add_reel (boost::shared_ptr<const Reel> reel); - - /** @return the length in frames */ - int length () const { - return _length; - } - - /** @return the type of the content, used by media servers - * to categorise things (e.g. feature, trailer, etc.) - */ - ContentKind content_kind () const { - return _content_kind; - } - - std::list<boost::shared_ptr<const Reel> > reels () const { - return _reels; - } - - /** @return the CPL's name, as will be presented on projector - * media servers and theatre management systems. - */ - std::string name () const { - return _name; - } - - /** @return the number of frames per second */ - int frames_per_second () const { - return _fps; - } - - std::list<boost::shared_ptr<const Asset> > assets () const; - - bool equals (CPL const & other, EqualityOptions options, std::list<std::string>& notes) const; - - void write_xml (boost::shared_ptr<Encryption>) const; - void write_to_assetmap (std::ostream& s) const; - void write_to_pkl (xmlpp::Element* p) const; - - boost::shared_ptr<xmlpp::Document> make_kdm ( - CertificateChain const &, - std::string const &, - boost::shared_ptr<const Certificate>, - boost::posix_time::ptime from, - boost::posix_time::ptime until - ) const; - -private: - std::string _directory; - /** the name of the DCP */ - std::string _name; - /** the content kind of the CPL */ - ContentKind _content_kind; - /** length in frames */ - mutable int _length; - /** frames per second */ - int _fps; - /** reels */ - std::list<boost::shared_ptr<const Reel> > _reels; - - /** our UUID */ - std::string _uuid; - /** a SHA1 digest of our XML */ - mutable std::string _digest; -}; - -/** @class DCP dcp.h libdcp/dcp.h +/** @class DCP * @brief A class to create or read a DCP. */ @@ -160,15 +77,14 @@ public: /** Write the required XML files to the directory that was * passed into the constructor. */ - void write_xml (boost::shared_ptr<Encryption> crypt = boost::shared_ptr<Encryption> ()) const; + void write_xml (XMLMetadata const &, boost::shared_ptr<Encryption> crypt = boost::shared_ptr<Encryption> ()) const; /** Compare this DCP with another, according to various options. * @param other DCP to compare this one to. - * @param options Options to define just what "equality" means. - * @param notes Filled in with notes about differences. + * @param options Options to define what "equality" means. * @return true if the DCPs are equal according to EqualityOptions, otherwise false. */ - bool equals (DCP const & other, EqualityOptions options, std::list<std::string>& notes) const; + bool equals (DCP const & other, EqualityOptions options, boost::function<void (NoteType, std::string)> note) const; /** Add a CPL to this DCP. * @param cpl CPL to add. @@ -190,7 +106,7 @@ private: /** Write the PKL file. * @param pkl_uuid UUID to use. */ - std::string write_pkl (std::string pkl_uuid, boost::shared_ptr<Encryption>) const; + std::string write_pkl (std::string pkl_uuid, XMLMetadata const &, boost::shared_ptr<Encryption>) const; /** Write the VOLINDEX file */ void write_volindex () const; @@ -199,7 +115,7 @@ private: * @param pkl_uuid UUID of our PKL. * @param pkl_length Length of our PKL in bytes. */ - void write_assetmap (std::string pkl_uuid, int pkl_length) const; + void write_assetmap (std::string pkl_uuid, int pkl_length, XMLMetadata const &) const; /** @return Assets in all this CPLs in this DCP */ std::list<boost::shared_ptr<const Asset> > assets () const; diff --git a/src/dcp_time.cc b/src/dcp_time.cc index 7c7c5298..7d3111d2 100644 --- a/src/dcp_time.cc +++ b/src/dcp_time.cc @@ -64,7 +64,7 @@ Time::Time (string time) vector<string> b; split (b, time, is_any_of (":")); if (b.size() != 4) { - throw DCPReadError ("unrecognised time specification"); + boost::throw_exception (DCPReadError ("unrecognised time specification")); } h = lexical_cast<int> (b[0]); diff --git a/src/gamma_lut.cc b/src/gamma_lut.cc new file mode 100644 index 00000000..acc80af0 --- /dev/null +++ b/src/gamma_lut.cc @@ -0,0 +1,16 @@ +#include <cmath> +#include "gamma_lut.h" +#include "lut_cache.h" + +using namespace libdcp; + +LUTCache<GammaLUT> GammaLUT::cache; + +GammaLUT::GammaLUT(int bits, float gamma) + : LUT<float> (bits, gamma) +{ + int const bit_length = pow(2, bits); + for (int i = 0; i < bit_length; ++i) { + _lut[i] = pow(float(i) / (bit_length - 1), gamma); + } +} diff --git a/src/gamma_lut.h b/src/gamma_lut.h new file mode 100644 index 00000000..e41cd21f --- /dev/null +++ b/src/gamma_lut.h @@ -0,0 +1,13 @@ +#include "lut.h" +#include "lut_cache.h" + +namespace libdcp { + +class GammaLUT : public LUT<float> +{ +public: + GammaLUT (int bit_length, float gamma); + static LUTCache<GammaLUT> cache; +}; + +} diff --git a/src/test_mode.h b/src/lut.h index b2e671d5..bdb5f37f 100644 --- a/src/test_mode.h +++ b/src/lut.h @@ -17,13 +17,47 @@ */ -/** @file src/test_mode.h - * @brief A method to enable test mode for libdcp. - */ +#ifndef LIBDCP_LUT_H +#define LIBDCP_LUT_H -namespace libdcp -{ +#include <cmath> + +namespace libdcp { -extern void enable_test_mode (); +template<typename T> +class LUT +{ +public: + LUT(int bit_depth, float gamma) + : _lut(0) + , _bit_depth (bit_depth) + , _gamma (gamma) + { + _lut = new T[int(std::pow(2.0f, _bit_depth))]; + } + + virtual ~LUT() { + delete[] _lut; + } + + T const * lut() const { + return _lut; + } + + int bit_depth () const { + return _bit_depth; + } + + float gamma () const { + return _gamma; + } + +protected: + T* _lut; + int _bit_depth; + float _gamma; +}; } + +#endif diff --git a/src/lut_cache.h b/src/lut_cache.h new file mode 100644 index 00000000..b60ee109 --- /dev/null +++ b/src/lut_cache.h @@ -0,0 +1,28 @@ +#ifndef LIBDCP_LUT_CACHE_H +#define LIBDCP_LUT_CACHE_H + +#include <list> +#include <boost/shared_ptr.hpp> + +template<class T> +class LUTCache +{ +public: + boost::shared_ptr<T> get (int bit_depth, float gamma) + { + for (typename std::list<boost::shared_ptr<T> >::iterator i = _cache.begin(); i != _cache.end(); ++i) { + if ((*i)->bit_depth() == bit_depth && (*i)->gamma() == gamma) { + return *i; + } + } + + boost::shared_ptr<T> lut (new T (bit_depth, gamma)); + _cache.push_back (lut); + return lut; + } + +private: + std::list<boost::shared_ptr<T> > _cache; +}; + +#endif diff --git a/src/metadata.cc b/src/metadata.cc index 7e900e50..2967ac1d 100644 --- a/src/metadata.cc +++ b/src/metadata.cc @@ -27,16 +27,25 @@ using namespace std; using namespace libdcp; -Metadata* Metadata::_instance = 0; - -/** Construct a Metadata object with some default values */ -Metadata::Metadata () +MXFMetadata::MXFMetadata () : company_name ("libdcp") , product_name ("libdcp") , product_version (LIBDCP_VERSION) - , issuer ("libdcp" LIBDCP_VERSION) +{ + +} + + +XMLMetadata::XMLMetadata () + : issuer ("libdcp" LIBDCP_VERSION) , creator ("libdcp" LIBDCP_VERSION) { + set_issue_date_now (); +} + +void +XMLMetadata::set_issue_date_now () +{ char buffer[64]; time_t now; time (&now); @@ -44,15 +53,4 @@ Metadata::Metadata () strftime (buffer, 64, "%Y-%m-%dT%I:%M:%S+00:00", tm); issue_date = string (buffer); } - -/** @return Singleton Metadata instance */ -Metadata * -Metadata::instance () -{ - if (_instance == 0) { - _instance = new Metadata; - } - - return _instance; -} diff --git a/src/metadata.h b/src/metadata.h index 1610491e..7336766d 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -17,6 +17,9 @@ */ +#ifndef LIBDCP_METADATA_H +#define LIBDCP_METADATA_H + /** @file src/metadata.h * @brief Metadata for writing to the DCP. */ @@ -26,28 +29,28 @@ namespace libdcp { -/** @brief A class to hold various metadata that will be written - * to the DCP. - * - * The values are initialised, and can be modified if desired. - */ -class Metadata +class MXFMetadata { public: - static Metadata* instance (); + MXFMetadata (); std::string company_name; std::string product_name; std::string product_version; +}; + +class XMLMetadata +{ +public: + XMLMetadata (); + + void set_issue_date_now (); + std::string issuer; std::string creator; std::string issue_date; - -private: - Metadata (); - - /** Singleton instance of Metadata */ - static Metadata* _instance; }; } + +#endif diff --git a/src/mxf_asset.cc b/src/mxf_asset.cc index 6ba42d75..d52ab2cc 100644 --- a/src/mxf_asset.cc +++ b/src/mxf_asset.cc @@ -39,12 +39,18 @@ using boost::shared_ptr; using boost::dynamic_pointer_cast; using namespace libdcp; -MXFAsset::MXFAsset (string directory, string file_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length, bool encrypted) +MXFAsset::MXFAsset (string directory, string file_name) : Asset (directory, file_name) + , _progress (0) + , _encrypted (false) + , _encryption_context (0) +{ + +} + +MXFAsset::MXFAsset (string directory, string file_name, boost::signals2::signal<void (float)>* progress, int edit_rate, int intrinsic_duration, bool encrypted) + : Asset (directory, file_name, edit_rate, intrinsic_duration) , _progress (progress) - , _fps (fps) - , _entry_point (entry_point) - , _length (length) , _encrypted (encrypted) , _encryption_context (0) { @@ -76,15 +82,15 @@ MXFAsset::~MXFAsset () } void -MXFAsset::fill_writer_info (ASDCP::WriterInfo* writer_info) const +MXFAsset::fill_writer_info (ASDCP::WriterInfo* writer_info, string uuid, MXFMetadata const & metadata) { - writer_info->ProductVersion = Metadata::instance()->product_version; - writer_info->CompanyName = Metadata::instance()->company_name; - writer_info->ProductName = Metadata::instance()->product_name.c_str(); + writer_info->ProductVersion = metadata.product_version; + writer_info->CompanyName = metadata.company_name; + writer_info->ProductName = metadata.product_name.c_str(); writer_info->LabelSetType = ASDCP::LS_MXF_SMPTE; unsigned int c; - Kumu::hex2bin (_uuid.c_str(), writer_info->AssetUUID, Kumu::UUID_Length, &c); + Kumu::hex2bin (uuid.c_str(), writer_info->AssetUUID, Kumu::UUID_Length, &c); assert (c == Kumu::UUID_Length); if (_encrypted) { @@ -98,38 +104,28 @@ MXFAsset::fill_writer_info (ASDCP::WriterInfo* writer_info) const } bool -MXFAsset::equals (shared_ptr<const Asset> other, EqualityOptions, list<string>& notes) const +MXFAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, string)> note) const { + if (!Asset::equals (other, opt, note)) { + return false; + } + shared_ptr<const MXFAsset> other_mxf = dynamic_pointer_cast<const MXFAsset> (other); if (!other_mxf) { - notes.push_back ("comparing an MXF asset with a non-MXF asset"); + note (ERROR, "comparing an MXF asset with a non-MXF asset"); return false; } if (_file_name != other_mxf->_file_name) { - notes.push_back ("MXF names differ"); - return false; - } - - if (_fps != other_mxf->_fps) { - notes.push_back ("MXF frames per second differ"); - return false; + note (ERROR, "MXF names differ"); + if (!opt.mxf_names_can_differ) { + return false; + } } - if (_length != other_mxf->_length) { - notes.push_back ("MXF lengths differ"); - return false; - } - return true; } -int -MXFAsset::length () const -{ - return _length; -} - void MXFAsset::add_typed_key_id (xmlpp::Element* parent) const { diff --git a/src/mxf_asset.h b/src/mxf_asset.h index 93fa9013..a23a052d 100644 --- a/src/mxf_asset.h +++ b/src/mxf_asset.h @@ -30,45 +30,48 @@ namespace ASDCP { namespace libdcp { +class MXFMetadata; + /** @brief Parent class for assets which have MXF files */ class MXFAsset : public Asset { public: /** Construct an MXFAsset. + * This class will not write anything to disk in this constructor, but subclasses may. + * + * @param directory Directory where MXF file is. + * @param file_name Name of MXF file. + */ + MXFAsset (std::string directory, std::string file_name); + + /** Construct an MXFAsset. + * This class will not write anything to disk in this constructor, but subclasses may. + * * @param directory Directory where MXF file is. * @param file_name Name of MXF file. - * @param progress Signal to inform of progress. - * @param fps Frames per second. - * @param entry_point The entry point of this MXF; ie the first frame that should be used. - * @param length Length in frames. - * @param encrypted true if the MXF should be encrypted. + * @param progress Signal to use to inform of progress, or 0. + * @param edit_rate Edit rate in frames per second (usually equal to the video frame rate). + * @param intrinsic_duration Duration of the whole asset in frames. */ - MXFAsset ( - std::string directory, std::string file_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length, bool encrypted - ); + MXFAsset (std::string directory, std::string file_name, boost::signals2::signal<void (float)>* progress, int edit_rate, int intrinsic_duration, bool encrypted); ~MXFAsset (); - virtual bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, std::list<std::string>& notes) const; - - int length () const; - void add_typed_key_id (xmlpp::Element *) const; + virtual bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, std::string)> note) const; -protected: - virtual std::string key_type () const = 0; - /** Fill in a ADSCP::WriteInfo struct. * @param w struct to fill in. + * @param uuid uuid to use. */ - void fill_writer_info (ASDCP::WriterInfo* w) const; + void fill_writer_info (ASDCP::WriterInfo* w, std::string uuid, MXFMetadata const & metadata); - /** Signal to emit to report progress */ + void add_typed_key_id (xmlpp::Element *) const; + +protected: + virtual std::string key_type () const = 0; + + /** Signal to emit to report progress, or 0 */ boost::signals2::signal<void (float)>* _progress; - /** Frames per second */ - int _fps; - int _entry_point; - /** Length in frames */ - int _length; bool _encrypted; ASDCP::AESEncContext* _encryption_context; std::string _key_value; diff --git a/src/asset_map.cc b/src/parse/asset_map.cc index 8fcc515f..aedc931e 100644 --- a/src/asset_map.cc +++ b/src/parse/asset_map.cc @@ -23,36 +23,36 @@ #include <boost/algorithm/string.hpp> #include "asset_map.h" -#include "util.h" +#include "../util.h" +#include "../xml.h" using std::string; using std::list; using boost::shared_ptr; -using namespace libdcp; +using namespace libdcp::parse; AssetMap::AssetMap (string file) - : XMLFile (file, "AssetMap") { - id = string_child ("Id"); - creator = string_child ("Creator"); - volume_count = int64_child ("VolumeCount"); - issue_date = string_child ("IssueDate"); - issuer = string_child ("Issuer"); - assets = type_grand_children<AssetMapAsset> ("AssetList", "Asset"); + cxml::File f (file, "AssetMap"); + + id = f.string_child ("Id"); + creator = f.string_child ("Creator"); + volume_count = f.number_child<int64_t> ("VolumeCount"); + issue_date = f.string_child ("IssueDate"); + issuer = f.string_child ("Issuer"); + assets = type_grand_children<AssetMapAsset> (f, "AssetList", "Asset"); } -AssetMapAsset::AssetMapAsset (xmlpp::Node const * node) - : XMLNode (node) +AssetMapAsset::AssetMapAsset (shared_ptr<const cxml::Node> node) { - id = string_child ("Id"); - packing_list = optional_string_child ("PackingList"); - chunks = type_grand_children<Chunk> ("ChunkList", "Chunk"); + id = node->string_child ("Id"); + packing_list = node->optional_string_child ("PackingList").get_value_or (""); + chunks = type_grand_children<Chunk> (node, "ChunkList", "Chunk"); } -Chunk::Chunk (xmlpp::Node const * node) - : XMLNode (node) +Chunk::Chunk (shared_ptr<const cxml::Node> node) { - path = string_child ("Path"); + path = node->string_child ("Path"); string const prefix = "file://"; @@ -60,9 +60,9 @@ Chunk::Chunk (xmlpp::Node const * node) path = path.substr (prefix.length()); } - volume_index = optional_int64_child ("VolumeIndex"); - offset = optional_int64_child ("Offset"); - length = optional_int64_child ("Length"); + volume_index = node->optional_number_child<int64_t> ("VolumeIndex").get_value_or (0); + offset = node->optional_number_child<int64_t> ("Offset").get_value_or (0); + length = node->optional_number_child<int64_t> ("Length").get_value_or (0); } shared_ptr<AssetMapAsset> diff --git a/src/asset_map.h b/src/parse/asset_map.h index 8cf89b4b..af3e8918 100644 --- a/src/asset_map.h +++ b/src/parse/asset_map.h @@ -23,18 +23,20 @@ #include <stdint.h> #include <boost/shared_ptr.hpp> -#include "xml.h" +#include <libcxml/cxml.h> namespace libdcp { +namespace parse { + /** @class Chunk * @brief A simple parser for and representation of a \<Chunk\> node within an asset map. */ -class Chunk : public XMLNode +class Chunk { public: Chunk (); - Chunk (xmlpp::Node const * node); + Chunk (boost::shared_ptr<const cxml::Node> node); std::string path; int64_t volume_index; @@ -45,11 +47,11 @@ public: /** @class AssetMapAsset * @brief A simple parser for and representation of an \<AssetMap\> node within an asset map. */ -class AssetMapAsset : public XMLNode +class AssetMapAsset { public: AssetMapAsset (); - AssetMapAsset (xmlpp::Node const * node); + AssetMapAsset (boost::shared_ptr<const cxml::Node> node); std::string id; std::string packing_list; @@ -59,7 +61,7 @@ public: /** @class AssetMap * @brief A simple parser for and representation of an asset map file. */ -class AssetMap : public XMLFile +class AssetMap { public: AssetMap (std::string file); @@ -75,3 +77,5 @@ public: }; } + +} diff --git a/src/parse/cpl.cc b/src/parse/cpl.cc new file mode 100644 index 00000000..c4cf4374 --- /dev/null +++ b/src/parse/cpl.cc @@ -0,0 +1,147 @@ +/* + Copyright (C) 2012 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/cpl_file.cc + * @brief Classes used to parse a CPL. + */ + +#include <iostream> +#include "cpl.h" +#include "../xml.h" +#include "../util.h" + +using std::string; +using std::bad_cast; +using boost::shared_ptr; +using namespace libdcp::parse; + +CPL::CPL (string file) +{ + cxml::File f (file, "CompositionPlaylist"); + + id = f.string_child ("Id"); + annotation_text = f.optional_string_child ("AnnotationText").get_value_or (""); + issue_date = f.string_child ("IssueDate"); + creator = f.optional_string_child ("Creator").get_value_or (""); + content_title_text = f.string_child ("ContentTitleText"); + content_kind = content_kind_from_string (f.string_child ("ContentKind")); + content_version = optional_type_child<ContentVersion> (f, "ContentVersion"); + f.ignore_child ("RatingList"); + reels = type_grand_children<Reel> (f, "ReelList", "Reel"); + + f.ignore_child ("Issuer"); + f.ignore_child ("Signer"); + f.ignore_child ("Signature"); + + f.done (); +} + +ContentVersion::ContentVersion (shared_ptr<const cxml::Node> node) +{ + id = node->optional_string_child ("Id").get_value_or (""); + label_text = node->string_child ("LabelText"); + node->done (); +} + +Reel::Reel (shared_ptr<const cxml::Node> node) +{ + id = node->string_child ("Id"); + asset_list = type_child<CPLAssetList> (node, "AssetList"); + + node->ignore_child ("AnnotationText"); + node->done (); +} + +CPLAssetList::CPLAssetList (shared_ptr<const cxml::Node> node) +{ + main_picture = optional_type_child<MainPicture> (node, "MainPicture"); + main_stereoscopic_picture = optional_type_child<MainStereoscopicPicture> (node, "MainStereoscopicPicture"); + main_sound = optional_type_child<MainSound> (node, "MainSound"); + main_subtitle = optional_type_child<MainSubtitle> (node, "MainSubtitle"); + + node->done (); +} + +MainPicture::MainPicture (shared_ptr<const cxml::Node> node) + : Picture (node) +{ + +} + +MainStereoscopicPicture::MainStereoscopicPicture (shared_ptr<const cxml::Node> node) + : Picture (node) +{ + +} + +Picture::Picture (shared_ptr<const cxml::Node> node) +{ + id = node->string_child ("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"); + frame_rate = Fraction (node->string_child ("FrameRate")); + try { + screen_aspect_ratio = Fraction (node->string_child ("ScreenAspectRatio")); + } catch (XMLError& e) { + /* Maybe it's not a fraction */ + } + try { + float f = node->number_child<float> ("ScreenAspectRatio"); + screen_aspect_ratio = Fraction (f * 1000, 1000); + } catch (bad_cast& e) { + + } + + node->ignore_child ("Hash"); + + node->done (); +} + +MainSound::MainSound (shared_ptr<const cxml::Node> node) +{ + id = node->string_child ("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"); + + node->ignore_child ("Hash"); + node->ignore_child ("Language"); + + node->done (); +} + +MainSubtitle::MainSubtitle (shared_ptr<const cxml::Node> node) +{ + id = node->string_child ("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"); + + node->ignore_child ("Hash"); + node->ignore_child ("Language"); + + node->done (); +} diff --git a/src/cpl_file.h b/src/parse/cpl.h index 67b38a0d..434a244b 100644 --- a/src/cpl_file.h +++ b/src/parse/cpl.h @@ -17,28 +17,34 @@ */ -/** @file src/cpl_file.h +/** @file src/parse/cpl.h * @brief Classes used to parse a CPL. */ #include <stdint.h> #include <boost/shared_ptr.hpp> -#include "xml.h" +#include <libcxml/cxml.h> +#include "../types.h" namespace libdcp { -/** @brief A simple parser for and representation of a CPL \<Picture\> node */ -class Picture : public XMLNode +namespace parse { + +/** @brief A simple representation of a CPL \<Picture\> node */ +class Picture { public: Picture () {} - Picture (xmlpp::Node const * node); + Picture (boost::shared_ptr<const cxml::Node> node); std::string id; std::string annotation_text; Fraction edit_rate; + /** Duration of the whole thing */ int64_t intrinsic_duration; + /** Start point in frames */ int64_t entry_point; + /** Duration that will actually play */ int64_t duration; Fraction frame_rate; Fraction screen_aspect_ratio; @@ -50,7 +56,7 @@ class MainPicture : public Picture { public: MainPicture () {} - MainPicture (xmlpp::Node const * node); + MainPicture (boost::shared_ptr<const cxml::Node> node); }; /** @brief A simple parser for and representation of a CPL \<MainStereoscopicPicture\> node */ @@ -58,15 +64,15 @@ class MainStereoscopicPicture : public Picture { public: MainStereoscopicPicture () {} - MainStereoscopicPicture (xmlpp::Node const * node); + MainStereoscopicPicture (boost::shared_ptr<const cxml::Node> node); }; /** @brief A simple parser for and representation of a CPL \<MainSound\> node */ -class MainSound : public XMLNode +class MainSound { public: MainSound () {} - MainSound (xmlpp::Node const * node); + MainSound (boost::shared_ptr<const cxml::Node> node); std::string id; std::string annotation_text; @@ -77,11 +83,11 @@ public: }; /** @brief A simple parser for and representation of a CPL \<MainSubtitle\> node */ -class MainSubtitle : public XMLNode +class MainSubtitle { public: MainSubtitle () {} - MainSubtitle (xmlpp::Node const * node); + MainSubtitle (boost::shared_ptr<const cxml::Node> node); std::string id; std::string annotation_text; @@ -92,11 +98,11 @@ public: }; /** @brief A simple parser for and representation of a CPL \<AssetList\> node */ -class CPLAssetList : public XMLNode +class CPLAssetList { public: CPLAssetList () {} - CPLAssetList (xmlpp::Node const * node); + CPLAssetList (boost::shared_ptr<const cxml::Node> node); boost::shared_ptr<MainPicture> main_picture; boost::shared_ptr<MainStereoscopicPicture> main_stereoscopic_picture; @@ -105,11 +111,11 @@ public: }; /** @brief A simple parser for and representation of a CPL \<Reel\> node */ -class CPLReel : public XMLNode +class Reel { public: - CPLReel () {} - CPLReel (xmlpp::Node const * node); + Reel () {} + Reel (boost::shared_ptr<const cxml::Node> node); std::string id; boost::shared_ptr<CPLAssetList> asset_list; @@ -117,27 +123,27 @@ public: /** @brief A simple parser for and representation of a CPL \<ContentVersion\> node */ -class ContentVersion : public XMLNode +class ContentVersion { public: ContentVersion () {} - ContentVersion (xmlpp::Node const * node); + ContentVersion (boost::shared_ptr<const cxml::Node> node); std::string id; std::string label_text; }; -/** @class CPLFile +/** @class CPL * @brief Class to parse a CPL * * This class is used to parse XML CPL files. It is rarely necessary * for the caller to use it outside libdcp. */ -class CPLFile : public XMLFile +class CPL { public: /** Parse a CPL XML file into our member variables */ - CPLFile (std::string file); + CPL (std::string file); std::string id; std::string annotation_text; @@ -146,8 +152,10 @@ public: std::string content_title_text; ContentKind content_kind; boost::shared_ptr<ContentVersion> content_version; - std::list<boost::shared_ptr<CPLReel> > reels; + std::list<boost::shared_ptr<Reel> > reels; }; } +} + diff --git a/src/pkl_file.cc b/src/parse/pkl.cc index 21763f27..d790cfe4 100644 --- a/src/pkl_file.cc +++ b/src/parse/pkl.cc @@ -22,30 +22,30 @@ */ #include <iostream> -#include "pkl_file.h" +#include "pkl.h" using namespace std; using namespace boost; -using namespace libdcp; +using namespace libdcp::parse; -PKLFile::PKLFile (string file) - : XMLFile (file, "PackingList") +PKL::PKL (string file) { - id = string_child ("Id"); - annotation_text = string_child ("AnnotationText"); - issue_date = string_child ("IssueDate"); - issuer = string_child ("Issuer"); - creator = string_child ("Creator"); - assets = type_grand_children<PKLAsset> ("AssetList", "Asset"); + cxml::File f (file, "PackingList"); + + id = f.string_child ("Id"); + annotation_text = f.optional_string_child ("AnnotationText").get_value_or (""); + issue_date = f.string_child ("IssueDate"); + issuer = f.string_child ("Issuer"); + creator = f.string_child ("Creator"); + assets = type_grand_children<PKLAsset> (f, "AssetList", "Asset"); } -PKLAsset::PKLAsset (xmlpp::Node const * node) - : XMLNode (node) +PKLAsset::PKLAsset (boost::shared_ptr<const cxml::Node> node) { - id = string_child ("Id"); - annotation_text = optional_string_child ("AnnotationText"); - hash = string_child ("Hash"); - size = int64_child ("Size"); - type = string_child ("Type"); - original_file_name = optional_string_child ("OriginalFileName"); + id = node->string_child ("Id"); + annotation_text = node->optional_string_child ("AnnotationText").get_value_or (""); + hash = node->string_child ("Hash"); + size = node->number_child<int64_t> ("Size"); + type = node->string_child ("Type"); + original_file_name = node->optional_string_child ("OriginalFileName").get_value_or (""); } diff --git a/src/pkl_file.h b/src/parse/pkl.h index b64da5da..13d87fa1 100644 --- a/src/pkl_file.h +++ b/src/parse/pkl.h @@ -17,20 +17,22 @@ */ -/** @file src/pkl_file.h +/** @file src/parse/pkl.h * @brief Classes used to parse a PKL */ #include <boost/shared_ptr.hpp> -#include "xml.h" +#include "../xml.h" namespace libdcp { -class PKLAsset : public XMLNode +namespace parse { + +class PKLAsset { public: PKLAsset () {} - PKLAsset (xmlpp::Node const * node); + PKLAsset (boost::shared_ptr<const cxml::Node>); std::string id; std::string annotation_text; @@ -40,10 +42,10 @@ public: std::string original_file_name; }; -class PKLFile : public XMLFile +class PKL { public: - PKLFile (std::string file); + PKL (std::string file); std::string id; std::string annotation_text; @@ -54,3 +56,5 @@ public: }; } + +} diff --git a/src/parse/subtitle.cc b/src/parse/subtitle.cc new file mode 100644 index 00000000..612af716 --- /dev/null +++ b/src/parse/subtitle.cc @@ -0,0 +1,135 @@ +/* + Copyright (C) 2012-2013 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 <boost/shared_ptr.hpp> +#include <boost/optional.hpp> +#include "subtitle.h" +#include "../types.h" + +using std::string; +using std::list; +using boost::shared_ptr; +using boost::optional; +using boost::lexical_cast; +using namespace libdcp; +using namespace libdcp::parse; + +Font::Font (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 (list<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 (); + } + } +} + +LoadFont::LoadFont (shared_ptr<const cxml::Node> node) +{ + id = node->string_attribute ("Id"); + uri = node->string_attribute ("URI"); +} + + +Subtitle::Subtitle (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; +} + +Text::Text (shared_ptr<const cxml::Node> node) + : v_align (CENTER) +{ + text = node->content (); + v_position = node->number_attribute<float> ("VPosition"); + optional<string> v = node->optional_string_attribute ("VAlign"); + if (v) { + v_align = string_to_valign (v.get ()); + } + + font_nodes = type_children<Font> (node, "Font"); +} + diff --git a/src/parse/subtitle.h b/src/parse/subtitle.h new file mode 100644 index 00000000..34321545 --- /dev/null +++ b/src/parse/subtitle.h @@ -0,0 +1,93 @@ +/* + Copyright (C) 2012 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 "../xml.h" +#include "../dcp_time.h" +#include "../types.h" + +namespace libdcp +{ + +namespace parse +{ + +class Font; + +class Text +{ +public: + Text () {} + Text (boost::shared_ptr<const cxml::Node> node); + + float v_position; + VAlign v_align; + std::string text; + std::list<boost::shared_ptr<Font> > font_nodes; +}; + +class Subtitle +{ +public: + Subtitle () {} + Subtitle (boost::shared_ptr<const cxml::Node> node); + + Time in; + Time out; + Time fade_up_time; + Time fade_down_time; + std::list<boost::shared_ptr<Font> > font_nodes; + std::list<boost::shared_ptr<Text> > text_nodes; + +private: + Time fade_time (boost::shared_ptr<const cxml::Node>, std::string name); +}; + +class Font +{ +public: + Font () {} + Font (boost::shared_ptr<const cxml::Node> node); + Font (std::list<boost::shared_ptr<Font> > const & font_nodes); + + std::string text; + std::string id; + int size; + boost::optional<bool> italic; + boost::optional<Color> color; + boost::optional<Effect> effect; + boost::optional<Color> effect_color; + + std::list<boost::shared_ptr<Subtitle> > subtitle_nodes; + std::list<boost::shared_ptr<Font> > font_nodes; + std::list<boost::shared_ptr<Text> > text_nodes; +}; + +class LoadFont +{ +public: + LoadFont () {} + LoadFont (boost::shared_ptr<const cxml::Node> node); + + std::string id; + std::string uri; +}; + +} + +} diff --git a/src/picture_asset.cc b/src/picture_asset.cc index f783bb39..c6a95c74 100644 --- a/src/picture_asset.cc +++ b/src/picture_asset.cc @@ -43,62 +43,69 @@ using std::list; using std::vector; using std::max; using std::stringstream; +using std::pair; +using std::make_pair; +using std::istream; +using std::cout; using boost::shared_ptr; using boost::dynamic_pointer_cast; using boost::lexical_cast; using namespace libdcp; -PictureAsset::PictureAsset (string directory, string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length, bool encrypted) - : MXFAsset (directory, mxf_name, progress, fps, entry_point, length, encrypted) - , _width (0) - , _height (0) +PictureAsset::PictureAsset (string directory, string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, bool encrypted, Size size) + : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted) + , _size (size) +{ + +} + +PictureAsset::PictureAsset (string directory, string mxf_name) + : MXFAsset (directory, mxf_name) { } void -PictureAsset::write_to_cpl (xmlpp::Element* parent) const +PictureAsset::write_to_cpl (xmlpp::Node* node) const { - xmlpp::Element* main_picture = parent->add_child("MainPicture"); - main_picture->add_child("Id")->add_child_text("urn:uuid:" + _uuid); - main_picture->add_child("AnnotationText")->add_child_text(_file_name); - main_picture->add_child("EditRate")->add_child_text(boost::lexical_cast<string> (_fps) + " 1"); - main_picture->add_child("IntrinsicDuration")->add_child_text(boost::lexical_cast<string> (_length)); - main_picture->add_child("EntryPoint")->add_child_text("0"); - main_picture->add_child("Duration")->add_child_text(boost::lexical_cast<string> (_length)); + xmlpp::Node* mp = node->add_child ("MainPicture"); + mp->add_child ("Id")->add_child_text ("urn:uuid:" + _uuid); + mp->add_child ("AnnotationText")->add_child_text (_file_name); + mp->add_child ("EditRate")->add_child_text (lexical_cast<string> (_edit_rate) + " 1"); + mp->add_child ("IntrinsicDuration")->add_child_text (lexical_cast<string> (_intrinsic_duration)); + mp->add_child ("EntryPoint")->add_child_text (lexical_cast<string> (_entry_point)); + mp->add_child ("Duration")->add_child_text (lexical_cast<string> (_duration)); if (_encrypted) { - main_picture->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id); + mp->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id); } - main_picture->add_child("FrameRate")->add_child_text(boost::lexical_cast<string> (_fps) + " 1"); - stringstream sar; - sar << _width << " " << _height; - main_picture->add_child("ScreenAspectRatio")->add_child_text(sar.str()); + mp->add_child ("FrameRate")->add_child_text (lexical_cast<string> (_edit_rate) + " 1"); + mp->add_child ("ScreenAspectRatio")->add_child_text (lexical_cast<string> (_size.width) + " " + lexical_cast<string> (_size.height)); } bool -PictureAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, list<string>& notes) const +PictureAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, string)> note) const { - if (!MXFAsset::equals (other, opt, notes)) { + if (!MXFAsset::equals (other, opt, note)) { return false; } ASDCP::JP2K::MXFReader reader_A; if (ASDCP_FAILURE (reader_A.OpenRead (path().string().c_str()))) { - throw MXFFileError ("could not open MXF file for reading", path().string()); + boost::throw_exception (MXFFileError ("could not open MXF file for reading", path().string())); } ASDCP::JP2K::MXFReader reader_B; if (ASDCP_FAILURE (reader_B.OpenRead (other->path().string().c_str()))) { - throw MXFFileError ("could not open MXF file for reading", path().string()); + boost::throw_exception (MXFFileError ("could not open MXF file for reading", path().string())); } ASDCP::JP2K::PictureDescriptor desc_A; if (ASDCP_FAILURE (reader_A.FillPictureDescriptor (desc_A))) { - throw DCPReadError ("could not read video MXF information"); + boost::throw_exception (DCPReadError ("could not read video MXF information")); } ASDCP::JP2K::PictureDescriptor desc_B; if (ASDCP_FAILURE (reader_B.FillPictureDescriptor (desc_B))) { - throw DCPReadError ("could not read video MXF information"); + boost::throw_exception (DCPReadError ("could not read video MXF information")); } if ( @@ -122,7 +129,7 @@ PictureAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, list<s // desc_A.QuantizationDefault != desc_B.QuantizationDefault ) { - notes.push_back ("video MXF picture descriptors differ"); + note (ERROR, "video MXF picture descriptors differ"); return false; } @@ -142,15 +149,14 @@ MonoPictureAsset::MonoPictureAsset ( string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, - int length, - int width, - int height, - bool encrypted) - : PictureAsset (directory, mxf_name, progress, fps, 0, length, encrypted) + int intrinsic_duration, + bool encrypted, + Size size, + MXFMetadata const & metadata + ) + : PictureAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted, size) { - _width = width; - _height = height; - construct (get_path); + construct (get_path, metadata); } MonoPictureAsset::MonoPictureAsset ( @@ -159,74 +165,82 @@ MonoPictureAsset::MonoPictureAsset ( string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, - int length, - int width, - int height, - bool encrypted) - : PictureAsset (directory, mxf_name, progress, fps, 0, length, encrypted) + int intrinsic_duration, + bool encrypted, + Size size, + MXFMetadata const & metadata + ) + : PictureAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted, size) +{ + construct (boost::bind (&MonoPictureAsset::path_from_list, this, _1, files), metadata); +} + +MonoPictureAsset::MonoPictureAsset (string directory, string mxf_name, int fps, Size size) + : PictureAsset (directory, mxf_name, 0, fps, 0, false, size) { - _width = width; - _height = height; - construct (boost::bind (&MonoPictureAsset::path_from_list, this, _1, files)); + } -MonoPictureAsset::MonoPictureAsset (string directory, string mxf_name, int fps, int entry_point, int length) - : PictureAsset (directory, mxf_name, 0, fps, entry_point, length, false) +MonoPictureAsset::MonoPictureAsset (string directory, string mxf_name) + : PictureAsset (directory, mxf_name) { ASDCP::JP2K::MXFReader reader; if (ASDCP_FAILURE (reader.OpenRead (path().string().c_str()))) { - throw MXFFileError ("could not open MXF file for reading", path().string()); + boost::throw_exception (MXFFileError ("could not open MXF file for reading", path().string())); } ASDCP::JP2K::PictureDescriptor desc; if (ASDCP_FAILURE (reader.FillPictureDescriptor (desc))) { - throw DCPReadError ("could not read video MXF information"); + boost::throw_exception (DCPReadError ("could not read video MXF information")); } - _width = desc.StoredWidth; - _height = desc.StoredHeight; + _size.width = desc.StoredWidth; + _size.height = desc.StoredHeight; + _edit_rate = desc.EditRate.Numerator; + assert (desc.EditRate.Denominator == 1); + _intrinsic_duration = desc.ContainerDuration; } void -MonoPictureAsset::construct (boost::function<string (int)> get_path) +MonoPictureAsset::construct (boost::function<string (int)> get_path, MXFMetadata const & metadata) { ASDCP::JP2K::CodestreamParser j2k_parser; ASDCP::JP2K::FrameBuffer frame_buffer (4 * Kumu::Megabyte); if (ASDCP_FAILURE (j2k_parser.OpenReadFrame (get_path(0).c_str(), frame_buffer))) { - throw FileError ("could not open JPEG2000 file for reading", get_path (0)); + boost::throw_exception (FileError ("could not open JPEG2000 file for reading", get_path (0))); } ASDCP::JP2K::PictureDescriptor picture_desc; j2k_parser.FillPictureDescriptor (picture_desc); - picture_desc.EditRate = ASDCP::Rational (_fps, 1); + picture_desc.EditRate = ASDCP::Rational (_edit_rate, 1); ASDCP::WriterInfo writer_info; - fill_writer_info (&writer_info); - + fill_writer_info (&writer_info, _uuid, metadata); + ASDCP::JP2K::MXFWriter mxf_writer; - if (ASDCP_FAILURE (mxf_writer.OpenWrite (path().string().c_str(), writer_info, picture_desc))) { - throw MXFFileError ("could not open MXF file for writing", path().string()); + if (ASDCP_FAILURE (mxf_writer.OpenWrite (path().string().c_str(), writer_info, picture_desc, 16384, false))) { + boost::throw_exception (MXFFileError ("could not open MXF file for writing", path().string())); } - for (int i = 0; i < _length; ++i) { + for (int i = 0; i < _intrinsic_duration; ++i) { string const path = get_path (i); if (ASDCP_FAILURE (j2k_parser.OpenReadFrame (path.c_str(), frame_buffer))) { - throw FileError ("could not open JPEG2000 file for reading", path); + boost::throw_exception (FileError ("could not open JPEG2000 file for reading", path)); } if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, _encryption_context, 0))) { - throw MiscError ("error in writing video MXF"); + boost::throw_exception (MXFFileError ("error in writing video MXF", this->path().string())); } if (_progress) { - (*_progress) (0.5 * float (i) / _length); + (*_progress) (0.5 * float (i) / _intrinsic_duration); } } if (ASDCP_FAILURE (mxf_writer.Finalize())) { - throw MiscError ("error in finalising video MXF"); + boost::throw_exception (MXFFileError ("error in finalising video MXF", path().string())); } } @@ -239,28 +253,29 @@ MonoPictureAsset::path_from_list (int f, vector<string> const & files) const shared_ptr<const MonoPictureFrame> MonoPictureAsset::get_frame (int n) const { - return shared_ptr<const MonoPictureFrame> (new MonoPictureFrame (path().string(), n + _entry_point)); + return shared_ptr<const MonoPictureFrame> (new MonoPictureFrame (path().string(), n)); } bool -MonoPictureAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, list<string>& notes) const +MonoPictureAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, string)> note) const { - if (!PictureAsset::equals (other, opt, notes)) { + if (!PictureAsset::equals (other, opt, note)) { return false; } shared_ptr<const MonoPictureAsset> other_picture = dynamic_pointer_cast<const MonoPictureAsset> (other); assert (other_picture); - for (int i = 0; i < _length; ++i) { + for (int i = 0; i < _intrinsic_duration; ++i) { + note (PROGRESS, "Comparing video frame " + lexical_cast<string> (i) + " of " + lexical_cast<string> (_intrinsic_duration)); shared_ptr<const MonoPictureFrame> frame_A = get_frame (i); shared_ptr<const MonoPictureFrame> frame_B = other_picture->get_frame (i); if (!frame_buffer_equals ( - i, opt, notes, - frame_A->j2k_frame()->RoData(), frame_A->j2k_frame()->Size(), - frame_B->j2k_frame()->RoData(), frame_B->j2k_frame()->Size() + i, opt, note, + frame_A->j2k_data(), frame_A->j2k_size(), + frame_B->j2k_data(), frame_B->j2k_size() )) { return false; } @@ -270,31 +285,31 @@ MonoPictureAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, li } bool -StereoPictureAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, list<string>& notes) const +StereoPictureAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, string)> note) const { - if (!PictureAsset::equals (other, opt, notes)) { + if (!PictureAsset::equals (other, opt, note)) { return false; } shared_ptr<const StereoPictureAsset> other_picture = dynamic_pointer_cast<const StereoPictureAsset> (other); assert (other_picture); - for (int i = 0; i < _length; ++i) { + for (int i = 0; i < _intrinsic_duration; ++i) { shared_ptr<const StereoPictureFrame> frame_A = get_frame (i); shared_ptr<const StereoPictureFrame> frame_B = other_picture->get_frame (i); if (!frame_buffer_equals ( - i, opt, notes, - frame_A->j2k_frame()->Left.RoData(), frame_A->j2k_frame()->Left.Size(), - frame_B->j2k_frame()->Left.RoData(), frame_B->j2k_frame()->Left.Size() + i, opt, note, + frame_A->left_j2k_data(), frame_A->left_j2k_size(), + frame_B->left_j2k_data(), frame_B->left_j2k_size() )) { return false; } if (!frame_buffer_equals ( - i, opt, notes, - frame_A->j2k_frame()->Right.RoData(), frame_A->j2k_frame()->Right.Size(), - frame_B->j2k_frame()->Right.RoData(), frame_B->j2k_frame()->Right.Size() + i, opt, note, + frame_A->right_j2k_data(), frame_A->right_j2k_size(), + frame_B->right_j2k_data(), frame_B->right_j2k_size() )) { return false; } @@ -305,10 +320,12 @@ StereoPictureAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, bool PictureAsset::frame_buffer_equals ( - int frame, EqualityOptions opt, list<string>& notes, uint8_t const * data_A, unsigned int size_A, uint8_t const * data_B, unsigned int size_B + int frame, EqualityOptions opt, boost::function<void (NoteType, string)> note, + uint8_t const * data_A, unsigned int size_A, uint8_t const * data_B, unsigned int size_B ) const { if (size_A == size_B && memcmp (data_A, data_B, size_A) == 0) { + note (NOTE, "J2K identical"); /* Easy result; the J2K data is identical */ return true; } @@ -320,7 +337,7 @@ PictureAsset::frame_buffer_equals ( /* Compare them */ if (image_A->numcomps != image_B->numcomps) { - notes.push_back ("image component counts for frame " + lexical_cast<string>(frame) + " differ"); + note (ERROR, "image component counts for frame " + lexical_cast<string>(frame) + " differ"); return false; } @@ -331,7 +348,7 @@ PictureAsset::frame_buffer_equals ( for (int c = 0; c < image_A->numcomps; ++c) { if (image_A->comps[c].w != image_B->comps[c].w || image_A->comps[c].h != image_B->comps[c].h) { - notes.push_back ("image sizes for frame " + lexical_cast<string>(frame) + " differ"); + note (ERROR, "image sizes for frame " + lexical_cast<string>(frame) + " differ"); return false; } @@ -357,11 +374,18 @@ PictureAsset::frame_buffer_equals ( double const std_dev = sqrt (double (total_squared_deviation) / abs_diffs.size()); - if (mean > opt.max_mean_pixel_error || std_dev > opt.max_std_dev_pixel_error) { - notes.push_back ("mean or standard deviation out of range for " + lexical_cast<string>(frame)); + note (NOTE, "mean difference " + lexical_cast<string> (mean) + ", deviation " + lexical_cast<string> (std_dev)); + + if (mean > opt.max_mean_pixel_error) { + note (ERROR, "mean " + lexical_cast<string>(mean) + " out of range " + lexical_cast<string>(opt.max_mean_pixel_error) + " in frame " + lexical_cast<string>(frame)); return false; } - + + if (std_dev > opt.max_std_dev_pixel_error) { + note (ERROR, "standard deviation " + lexical_cast<string>(std_dev) + " out of range " + lexical_cast<string>(opt.max_std_dev_pixel_error) + " in frame " + lexical_cast<string>(frame)); + return false; + } + opj_image_destroy (image_A); opj_image_destroy (image_B); @@ -369,27 +393,152 @@ PictureAsset::frame_buffer_equals ( } -StereoPictureAsset::StereoPictureAsset (string directory, string mxf_name, int fps, int entry_point, int length) - : PictureAsset (directory, mxf_name, 0, fps, entry_point, length, false) +StereoPictureAsset::StereoPictureAsset (string directory, string mxf_name, int fps, int intrinsic_duration) + : PictureAsset (directory, mxf_name, 0, fps, intrinsic_duration, false, Size (0, 0)) { ASDCP::JP2K::MXFSReader reader; if (ASDCP_FAILURE (reader.OpenRead (path().string().c_str()))) { - throw MXFFileError ("could not open MXF file for reading", path().string()); + boost::throw_exception (MXFFileError ("could not open MXF file for reading", path().string())); } ASDCP::JP2K::PictureDescriptor desc; if (ASDCP_FAILURE (reader.FillPictureDescriptor (desc))) { - throw DCPReadError ("could not read video MXF information"); + boost::throw_exception (DCPReadError ("could not read video MXF information")); } - _width = desc.StoredWidth; - _height = desc.StoredHeight; + _size.width = desc.StoredWidth; + _size.height = desc.StoredHeight; } shared_ptr<const StereoPictureFrame> StereoPictureAsset::get_frame (int n) const { - return shared_ptr<const StereoPictureFrame> (new StereoPictureFrame (path().string(), n + _entry_point)); + return shared_ptr<const StereoPictureFrame> (new StereoPictureFrame (path().string(), n)); +} + +shared_ptr<MonoPictureAssetWriter> +MonoPictureAsset::start_write (bool overwrite, MXFMetadata const & metadata) +{ + /* XXX: can't we use shared_ptr here? */ + return shared_ptr<MonoPictureAssetWriter> (new MonoPictureAssetWriter (this, overwrite, metadata)); +} + +FrameInfo::FrameInfo (istream& s) +{ + s >> offset >> size >> hash; +} + +void +FrameInfo::write (ostream& s) +{ + s << offset << " " << size << " " << hash; +} + +struct MonoPictureAssetWriter::ASDCPState +{ + ASDCPState() + : frame_buffer (4 * Kumu::Megabyte) + {} + + ASDCP::JP2K::CodestreamParser j2k_parser; + ASDCP::JP2K::FrameBuffer frame_buffer; + ASDCP::JP2K::MXFWriter mxf_writer; + ASDCP::WriterInfo writer_info; + ASDCP::JP2K::PictureDescriptor picture_descriptor; +}; + + +/** @param a Asset to write to. `a' must not be deleted while + * this writer class still exists, or bad things will happen. + */ +MonoPictureAssetWriter::MonoPictureAssetWriter (MonoPictureAsset* a, bool overwrite, MXFMetadata const & m) + : _state (new MonoPictureAssetWriter::ASDCPState) + , _asset (a) + , _frames_written (0) + , _started (false) + , _finalized (false) + , _overwrite (overwrite) + , _metadata (m) +{ + +} + + +void +MonoPictureAssetWriter::start (uint8_t* data, int size) +{ + if (ASDCP_FAILURE (_state->j2k_parser.OpenReadFrame (data, size, _state->frame_buffer))) { + boost::throw_exception (MiscError ("could not parse J2K frame")); + } + + _state->j2k_parser.FillPictureDescriptor (_state->picture_descriptor); + _state->picture_descriptor.EditRate = ASDCP::Rational (_asset->edit_rate(), 1); + + _asset->fill_writer_info (&_state->writer_info, _asset->uuid(), _metadata); + + if (ASDCP_FAILURE (_state->mxf_writer.OpenWrite ( + _asset->path().string().c_str(), + _state->writer_info, + _state->picture_descriptor, + 16384, + _overwrite) + )) { + + boost::throw_exception (MXFFileError ("could not open MXF file for writing", _asset->path().string())); + } + + _started = true; +} + +FrameInfo +MonoPictureAssetWriter::write (uint8_t* data, int size) +{ + assert (!_finalized); + + if (!_started) { + start (data, size); + } + + if (ASDCP_FAILURE (_state->j2k_parser.OpenReadFrame (data, size, _state->frame_buffer))) { + boost::throw_exception (MiscError ("could not parse J2K frame")); + } + + uint64_t const before_offset = _state->mxf_writer.Tell (); + + string hash; + if (ASDCP_FAILURE (_state->mxf_writer.WriteFrame (_state->frame_buffer, 0, 0, &hash))) { + boost::throw_exception (MXFFileError ("error in writing video MXF", _asset->path().string())); + } + + ++_frames_written; + return FrameInfo (before_offset, _state->mxf_writer.Tell() - before_offset, hash); +} + +void +MonoPictureAssetWriter::fake_write (int size) +{ + assert (_started); + assert (!_finalized); + + if (ASDCP_FAILURE (_state->mxf_writer.FakeWriteFrame (size))) { + boost::throw_exception (MXFFileError ("error in writing video MXF", _asset->path().string())); + } + + ++_frames_written; +} + +void +MonoPictureAssetWriter::finalize () +{ + assert (!_finalized); + + if (ASDCP_FAILURE (_state->mxf_writer.Finalize())) { + boost::throw_exception (MXFFileError ("error in finalizing video MXF", _asset->path().string())); + } + + _finalized = true; + _asset->set_intrinsic_duration (_frames_written); + _asset->set_duration (_frames_written); } string diff --git a/src/picture_asset.h b/src/picture_asset.h index 96bf5659..08f892ed 100644 --- a/src/picture_asset.h +++ b/src/picture_asset.h @@ -17,12 +17,17 @@ */ +#ifndef LIBDCP_PICTURE_ASSET_H +#define LIBDCP_PICTURE_ASSET_H + /** @file src/picture_asset.h * @brief An asset made up of JPEG2000 files */ #include <openjpeg.h> #include "mxf_asset.h" +#include "util.h" +#include "metadata.h" namespace libdcp { @@ -34,55 +39,127 @@ class StereoPictureFrame; class PictureAsset : public MXFAsset { public: - PictureAsset ( - std::string directory, std::string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length, bool encrypted - ); + /** Construct a PictureAsset. + * This class will not write anything to disk in this constructor, but subclasses may. + * + * @param directory Directory where MXF file is. + * @param mxf_name Name of MXF file. + */ + PictureAsset (std::string directory, std::string mxf_name); + + /** Construct a PictureAsset. + * This class will not write anything to disk in this constructor, but subclasses may. + * + * @param directory Directory where MXF file is. + * @param mxf_name Name of MXF file. + * @param progress Signal to use to inform of progres, or 0. + * @param fps Video Frames per second. + * @param intrinsic_duration Duration of all the frames in the asset. + * @param size Size of video frame images in pixels. + */ + PictureAsset (std::string directory, std::string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, bool encrypted, Size); - /** Write details of the asset to a CPL AssetList node. - * @param p Parent node. + /** Write details of this asset to a CPL XML node. + * @param node Node. */ - void write_to_cpl (xmlpp::Element* p) const; + void write_to_cpl (xmlpp::Node* node) const; - bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, std::list<std::string>& notes) const; + bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, std::string)> note) const; - int width () const { - return _width; - } - - int height () const { - return _height; + Size size () const { + return _size; } protected: + std::string key_type () const; + bool frame_buffer_equals ( - int frame, EqualityOptions opt, std::list<std::string>& notes, + int frame, EqualityOptions opt, boost::function<void (NoteType, std::string)> note, uint8_t const * data_A, unsigned int size_A, uint8_t const * data_B, unsigned int size_B ) const; + + /** picture size in pixels */ + Size _size; +}; + +class MonoPictureAsset; + +struct FrameInfo +{ + FrameInfo (uint64_t o, uint64_t s, std::string h) + : offset (o) + , size (s) + , hash (h) + {} + + FrameInfo (std::istream& s); + + void write (std::ostream& s); - /** picture width in pixels */ - int _width; - /** picture height in pixels */ - int _height; + uint64_t offset; + uint64_t size; + std::string hash; +}; + +/** A helper class for writing to MonoPictureAssets progressively (i.e. writing frame-by-frame, + * rather than giving libdcp all the frames in one go). + * + * Objects of this class can only be created with MonoPictureAsset::start_write(). + * + * Frames can be written to the MonoPictureAsset by calling write() with a JPEG2000 image + * (a verbatim .j2 file). finalize() must be called after the last frame has been written. + * The action of finalize() can't be done in MonoPictureAssetWriter's destructor as it may + * throw an exception. + */ +class MonoPictureAssetWriter +{ +public: + FrameInfo write (uint8_t* data, int size); + void fake_write (int size); + void finalize (); private: - std::string key_type () const; + friend class MonoPictureAsset; + + MonoPictureAssetWriter (MonoPictureAsset *, bool, MXFMetadata const &); + void start (uint8_t *, int); + + /* no copy construction */ + MonoPictureAssetWriter (MonoPictureAssetWriter const &); + MonoPictureAssetWriter& operator= (MonoPictureAssetWriter const &); + + /* do this with an opaque pointer so we don't have to include + ASDCP headers + */ + + struct ASDCPState; + boost::shared_ptr<ASDCPState> _state; + + MonoPictureAsset* _asset; + /** Number of picture frames written to the asset so far */ + int _frames_written; + bool _started; + /** true if finalize() has been called */ + bool _finalized; + bool _overwrite; + MXFMetadata _metadata; }; /** A 2D (monoscopic) picture asset */ class MonoPictureAsset : public PictureAsset { public: - /** Construct a PictureAsset, generating the MXF from the JPEG2000 files. + /** Construct a MonoPictureAsset, generating the MXF from the JPEG2000 files. * This may take some time; progress is indicated by emission of the Progress signal. + * * @param files Pathnames of JPEG2000 files, in frame order. * @param directory Directory in which to create MXF file. * @param mxf_name Name of MXF file to create. * @param progress Signal to inform of progress. - * @param fps Frames per second. - * @param length Length in frames. - * @param width Width of images in pixels. - * @param height Height of images in pixels. + * @param fps Video frames per second. + * @param intrinsic_duration Length of the whole asset in frames. + * @param size Size of images in pixels. * @param encrypted true if asset should be encrypted. */ MonoPictureAsset ( @@ -91,22 +168,22 @@ public: std::string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, - int length, - int width, - int height, - bool encrypted + int intrinsic_duration, + bool encrypted, + Size size, + MXFMetadata const & metadata = MXFMetadata () ); - /** Construct a PictureAsset, generating the MXF from the JPEG2000 files. + /** Construct a MonoPictureAsset, generating the MXF from the JPEG2000 files. * This may take some time; progress is indicated by emission of the Progress signal. + * * @param get_path Functor which returns a JPEG2000 file path for a given frame (frames counted from 0). * @param directory Directory in which to create MXF file. * @param mxf_name Name of MXF file to create. * @param progress Signal to inform of progress. - * @param fps Frames per second. - * @param length Length in frames. - * @param width Width of images in pixels. - * @param height Height of images in pixels. + * @param fps Video frames per second. + * @param intrinsic_duration Length of the whole asset in frames. + * @param size Size of images in pixels. * @param encrypted true if asset should be encrypted. */ MonoPictureAsset ( @@ -115,31 +192,50 @@ public: std::string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, - int length, - int width, - int height, - bool encrypted + int intrinsic_duration, + bool encrypted, + Size size, + MXFMetadata const & metadata = MXFMetadata () ); - MonoPictureAsset (std::string directory, std::string mxf_name, int fps, int entry_point, int length); - + /** Construct a MonoPictureAsset, reading the MXF from disk. + * @param directory Directory that the MXF is in. + * @param mxf_name The filename of the MXF within `directory'. + */ + MonoPictureAsset (std::string directory, std::string mxf_name); + + /** Construct a MonoPictureAsset for progressive writing using + * start_write() and a MonoPictureAssetWriter. + * + * @param directory Directory to put the MXF in. + * @param mxf_name Filename of the MXF within this directory. + * @param fps Video frames per second. + * @param size Size in pixels that the picture frames will be. + */ + MonoPictureAsset (std::string directory, std::string mxf_name, int fps, Size size); + + /** Start a progressive write to a MonoPictureAsset */ + boost::shared_ptr<MonoPictureAssetWriter> start_write (bool, MXFMetadata const & metadata = MXFMetadata ()); + boost::shared_ptr<const MonoPictureFrame> get_frame (int n) const; - bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, std::list<std::string>& notes) const; + bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, std::string)> note) const; private: std::string path_from_list (int f, std::vector<std::string> const & files) const; - void construct (boost::function<std::string (int)>); + void construct (boost::function<std::string (int)>, MXFMetadata const &); }; /** A 3D (stereoscopic) picture asset */ class StereoPictureAsset : public PictureAsset { public: - StereoPictureAsset (std::string directory, std::string mxf_name, int fps, int entry_point, int length); + StereoPictureAsset (std::string directory, std::string mxf_name, int fps, int intrinsic_duration); boost::shared_ptr<const StereoPictureFrame> get_frame (int n) const; - bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, std::list<std::string>& notes) const; + bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, std::string)> note) const; }; } + +#endif diff --git a/src/picture_frame.cc b/src/picture_frame.cc index a2b90a0b..7e6bc1f8 100644 --- a/src/picture_frame.cc +++ b/src/picture_frame.cc @@ -25,6 +25,9 @@ #include "argb_frame.h" #include "lut.h" #include "util.h" +#include "gamma_lut.h" + +#define DCI_GAMMA 2.6 using std::string; using boost::shared_ptr; @@ -38,14 +41,14 @@ MonoPictureFrame::MonoPictureFrame (string mxf_path, int n) { ASDCP::JP2K::MXFReader reader; if (ASDCP_FAILURE (reader.OpenRead (mxf_path.c_str()))) { - throw FileError ("could not open MXF file for reading", mxf_path); + boost::throw_exception (FileError ("could not open MXF file for reading", mxf_path)); } /* XXX: unfortunate guesswork on this buffer size */ _buffer = new ASDCP::JP2K::FrameBuffer (4 * Kumu::Megabyte); if (ASDCP_FAILURE (reader.ReadFrame (n, *_buffer))) { - throw DCPReadError ("could not read video frame"); + boost::throw_exception (DCPReadError ("could not read video frame")); } } @@ -54,6 +57,18 @@ MonoPictureFrame::~MonoPictureFrame () delete _buffer; } +uint8_t const * +MonoPictureFrame::j2k_data () const +{ + return _buffer->RoData (); +} + +int +MonoPictureFrame::j2k_size () const +{ + return _buffer->Size (); +} + /** @param reduce a factor by which to reduce the resolution * of the image, expressed as a power of two (pass 0 for no * reduction). @@ -64,11 +79,11 @@ MonoPictureFrame::~MonoPictureFrame () * */ shared_ptr<ARGBFrame> -MonoPictureFrame::argb_frame (int reduce) const +MonoPictureFrame::argb_frame (int reduce, float srgb_gamma) const { opj_image_t* xyz_frame = decompress_j2k (const_cast<uint8_t*> (_buffer->RoData()), _buffer->Size(), reduce); assert (xyz_frame->numcomps == 3); - shared_ptr<ARGBFrame> f = xyz_to_rgb (xyz_frame); + shared_ptr<ARGBFrame> f = xyz_to_rgb (xyz_frame, GammaLUT::cache.get (12, DCI_GAMMA), GammaLUT::cache.get (12, 1 / srgb_gamma)); opj_image_destroy (xyz_frame); return f; } @@ -81,14 +96,14 @@ StereoPictureFrame::StereoPictureFrame (string mxf_path, int n) { ASDCP::JP2K::MXFSReader reader; if (ASDCP_FAILURE (reader.OpenRead (mxf_path.c_str()))) { - throw FileError ("could not open MXF file for reading", mxf_path); + boost::throw_exception (FileError ("could not open MXF file for reading", mxf_path)); } /* XXX: unfortunate guesswork on this buffer size */ _buffer = new ASDCP::JP2K::SFrameBuffer (4 * Kumu::Megabyte); if (ASDCP_FAILURE (reader.ReadFrame (n, *_buffer))) { - throw DCPReadError ("could not read video frame"); + boost::throw_exception (DCPReadError ("could not read video frame")); } } @@ -110,7 +125,7 @@ StereoPictureFrame::~StereoPictureFrame () * */ shared_ptr<ARGBFrame> -StereoPictureFrame::argb_frame (Eye eye, int reduce) const +StereoPictureFrame::argb_frame (Eye eye, int reduce, float srgb_gamma) const { opj_image_t* xyz_frame = 0; switch (eye) { @@ -123,7 +138,31 @@ StereoPictureFrame::argb_frame (Eye eye, int reduce) const } assert (xyz_frame->numcomps == 3); - shared_ptr<ARGBFrame> f = xyz_to_rgb (xyz_frame); + shared_ptr<ARGBFrame> f = xyz_to_rgb (xyz_frame, GammaLUT::cache.get (12, DCI_GAMMA), GammaLUT::cache.get (12, 1 / srgb_gamma)); opj_image_destroy (xyz_frame); return f; } + +uint8_t const * +StereoPictureFrame::left_j2k_data () const +{ + return _buffer->Left.RoData (); +} + +int +StereoPictureFrame::left_j2k_size () const +{ + return _buffer->Left.Size (); +} + +uint8_t const * +StereoPictureFrame::right_j2k_data () const +{ + return _buffer->Right.RoData (); +} + +int +StereoPictureFrame::right_j2k_size () const +{ + return _buffer->Right.Size (); +} diff --git a/src/picture_frame.h b/src/picture_frame.h index ad51abed..42c5d629 100644 --- a/src/picture_frame.h +++ b/src/picture_frame.h @@ -40,10 +40,9 @@ public: MonoPictureFrame (std::string mxf_path, int n); ~MonoPictureFrame (); - boost::shared_ptr<ARGBFrame> argb_frame (int reduce = 0) const; - ASDCP::JP2K::FrameBuffer* j2k_frame () const { - return _buffer; - } + boost::shared_ptr<ARGBFrame> argb_frame (int reduce = 0, float srgb_gamma = 2.4) const; + uint8_t const * j2k_data () const; + int j2k_size () const; private: ASDCP::JP2K::FrameBuffer* _buffer; @@ -56,10 +55,11 @@ public: StereoPictureFrame (std::string mxf_path, int n); ~StereoPictureFrame (); - boost::shared_ptr<ARGBFrame> argb_frame (Eye eye, int reduce = 0) const; - ASDCP::JP2K::SFrameBuffer* j2k_frame () const { - return _buffer; - } + boost::shared_ptr<ARGBFrame> argb_frame (Eye eye, int reduce = 0, float srgb_gamma = 2.4) const; + uint8_t const * left_j2k_data () const; + int left_j2k_size () const; + uint8_t const * right_j2k_data () const; + int right_j2k_size () const; private: ASDCP::JP2K::SFrameBuffer* _buffer; diff --git a/src/reel.cc b/src/reel.cc index d8703dd0..3f077269 100644 --- a/src/reel.cc +++ b/src/reel.cc @@ -28,12 +28,12 @@ using namespace std; using namespace libdcp; void -Reel::write_to_cpl (xmlpp::Node* parent) const +Reel::write_to_cpl (xmlpp::Node* node) const { - xmlpp::Element* reel = parent->add_child("Reel"); - reel->add_child("Id")->add_child_text("urn:uuid:" + make_uuid()); - xmlpp::Element* asset_list = reel->add_child("AssetList"); - + xmlpp::Node* reel = node->add_child ("Reel"); + reel->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid()); + xmlpp::Node* asset_list = reel->add_child ("AssetList"); + if (_main_picture) { _main_picture->write_to_cpl (asset_list); } @@ -48,32 +48,32 @@ Reel::write_to_cpl (xmlpp::Node* parent) const } bool -Reel::equals (boost::shared_ptr<const Reel> other, EqualityOptions opt, list<string>& notes) const +Reel::equals (boost::shared_ptr<const Reel> other, EqualityOptions opt, boost::function<void (NoteType, string)> note) const { if ((_main_picture && !other->_main_picture) || (!_main_picture && other->_main_picture)) { - notes.push_back ("reel has different assets"); + note (ERROR, "reel has different assets"); return false; } - if (_main_picture && !_main_picture->equals (other->_main_picture, opt, notes)) { + if (_main_picture && !_main_picture->equals (other->_main_picture, opt, note)) { return false; } if ((_main_sound && !other->_main_sound) || (!_main_sound && other->_main_sound)) { - notes.push_back ("reel has different assets"); + note (ERROR, "reel has different assets"); return false; } - if (_main_sound && !_main_sound->equals (other->_main_sound, opt, notes)) { + if (_main_sound && !_main_sound->equals (other->_main_sound, opt, note)) { return false; } if ((_main_subtitle && !other->_main_subtitle) || (!_main_subtitle && other->_main_subtitle)) { - notes.push_back ("reel has different assets"); + note (ERROR, "reel has different assets"); return false; } - if (_main_subtitle && !_main_subtitle->equals (other->_main_subtitle, opt, notes)) { + if (_main_subtitle && !_main_subtitle->equals (other->_main_subtitle, opt, note)) { return false; } @@ -19,6 +19,8 @@ #include <list> #include <boost/shared_ptr.hpp> +#include <boost/function.hpp> +#include <libxml++/libxml++.h> #include "types.h" namespace xmlpp { @@ -59,7 +61,7 @@ public: void write_to_cpl (xmlpp::Node *) const; - bool equals (boost::shared_ptr<const Reel> other, EqualityOptions opt, std::list<std::string>& notes) const; + bool equals (boost::shared_ptr<const Reel> other, EqualityOptions opt, boost::function<void (NoteType, std::string)> notes) const; private: boost::shared_ptr<const PictureAsset> _main_picture; diff --git a/src/sound_asset.cc b/src/sound_asset.cc index d964a46d..45a65646 100644 --- a/src/sound_asset.cc +++ b/src/sound_asset.cc @@ -48,18 +48,17 @@ SoundAsset::SoundAsset ( string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, - int length, - int start_frame, - bool encrypted + int intrinsic_duration, + bool encrypted, + MXFMetadata const & metadata ) - : MXFAsset (directory, mxf_name, progress, fps, 0, length, encrypted) + : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted) , _channels (files.size ()) , _sampling_rate (0) - , _start_frame (start_frame) { assert (_channels); - construct (boost::bind (&SoundAsset::path_from_channel, this, _1, files)); + construct (boost::bind (&SoundAsset::path_from_channel, this, _1, files), metadata); } SoundAsset::SoundAsset ( @@ -68,39 +67,47 @@ SoundAsset::SoundAsset ( string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, - int length, - int start_frame, + int intrinsic_duration, int channels, - bool encrypted + bool encrypted, + MXFMetadata const & metadata ) - : MXFAsset (directory, mxf_name, progress, fps, 0, length, encrypted) + : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted) , _channels (channels) , _sampling_rate (0) - , _start_frame (start_frame) { assert (_channels); - construct (get_path); + construct (get_path, metadata); } -SoundAsset::SoundAsset (string directory, string mxf_name, int fps, int entry_point, int length) - : MXFAsset (directory, mxf_name, 0, fps, entry_point, length, false) +SoundAsset::SoundAsset (string directory, string mxf_name) + : MXFAsset (directory, mxf_name) , _channels (0) - , _start_frame (0) { ASDCP::PCM::MXFReader reader; if (ASDCP_FAILURE (reader.OpenRead (path().string().c_str()))) { - throw MXFFileError ("could not open MXF file for reading", path().string()); + boost::throw_exception (MXFFileError ("could not open MXF file for reading", path().string())); } - ASDCP::PCM::AudioDescriptor desc; if (ASDCP_FAILURE (reader.FillAudioDescriptor (desc))) { - throw DCPReadError ("could not read audio MXF information"); + boost::throw_exception (DCPReadError ("could not read audio MXF information")); } _sampling_rate = desc.AudioSamplingRate.Numerator / desc.AudioSamplingRate.Denominator; _channels = desc.ChannelCount; + _edit_rate = desc.EditRate.Numerator; + assert (desc.EditRate.Denominator == 1); + _intrinsic_duration = desc.ContainerDuration; +} + +SoundAsset::SoundAsset (string directory, string mxf_name, int fps, int channels, int sampling_rate) + : MXFAsset (directory, mxf_name, 0, fps, 0, false) + , _channels (channels) + , _sampling_rate (sampling_rate) +{ + } string @@ -112,20 +119,20 @@ SoundAsset::path_from_channel (Channel channel, vector<string> const & files) } void -SoundAsset::construct (boost::function<string (Channel)> get_path) +SoundAsset::construct (boost::function<string (Channel)> get_path, MXFMetadata const & metadata) { - ASDCP::Rational asdcp_fps (_fps, 1); + ASDCP::Rational asdcp_edit_rate (_edit_rate, 1); ASDCP::PCM::WAVParser pcm_parser_channel[_channels]; - if (pcm_parser_channel[0].OpenRead (get_path(LEFT).c_str(), asdcp_fps)) { - throw FileError ("could not open WAV file for reading", get_path(LEFT)); + if (pcm_parser_channel[0].OpenRead (get_path(LEFT).c_str(), asdcp_edit_rate)) { + boost::throw_exception (FileError ("could not open WAV file for reading", get_path(LEFT))); } ASDCP::PCM::AudioDescriptor audio_desc; pcm_parser_channel[0].FillAudioDescriptor (audio_desc); audio_desc.ChannelCount = 0; audio_desc.BlockAlign = 0; - audio_desc.EditRate = asdcp_fps; + audio_desc.EditRate = asdcp_edit_rate; audio_desc.AvgBps = audio_desc.AvgBps * _channels; Channel channels[] = { @@ -149,8 +156,8 @@ SoundAsset::construct (boost::function<string (Channel)> get_path) string const path = get_path (channels[i]); - if (ASDCP_FAILURE (pcm_parser_channel[i].OpenRead (path.c_str(), asdcp_fps))) { - throw FileError ("could not open WAV file for reading", path); + if (ASDCP_FAILURE (pcm_parser_channel[i].OpenRead (path.c_str(), asdcp_edit_rate))) { + boost::throw_exception (FileError ("could not open WAV file for reading", path)); } pcm_parser_channel[i].FillAudioDescriptor (audio_desc_channel[i]); @@ -165,28 +172,19 @@ SoundAsset::construct (boost::function<string (Channel)> get_path) frame_buffer.Size (ASDCP::PCM::CalcFrameBufferSize (audio_desc)); ASDCP::WriterInfo writer_info; - fill_writer_info (&writer_info); + MXFAsset::fill_writer_info (&writer_info, _uuid, metadata); ASDCP::PCM::MXFWriter mxf_writer; if (ASDCP_FAILURE (mxf_writer.OpenWrite (path().string().c_str(), writer_info, audio_desc))) { - throw FileError ("could not open audio MXF for writing", path().string()); - } - - /* Skip through up to our _start_frame; this is pretty inefficient... */ - for (int i = 0; i < _start_frame; ++i) { - for (int j = 0; j < _channels; ++j) { - if (ASDCP_FAILURE (pcm_parser_channel[j].ReadFrame (frame_buffer_channel[j]))) { - throw MiscError ("could not read audio frame"); - } - } + boost::throw_exception (FileError ("could not open audio MXF for writing", path().string())); } - for (int i = 0; i < _length; ++i) { + for (int i = 0; i < _intrinsic_duration; ++i) { for (int j = 0; j < _channels; ++j) { memset (frame_buffer_channel[j].Data(), 0, frame_buffer_channel[j].Capacity()); if (ASDCP_FAILURE (pcm_parser_channel[j].ReadFrame (frame_buffer_channel[j]))) { - throw MiscError ("could not read audio frame"); + boost::throw_exception (MiscError ("could not read audio frame")); } } @@ -205,58 +203,58 @@ SoundAsset::construct (boost::function<string (Channel)> get_path) } if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, _encryption_context, 0))) { - throw MiscError ("could not write audio MXF frame"); + boost::throw_exception (MiscError ("could not write audio MXF frame")); } if (_progress) { - (*_progress) (0.5 * float (i) / _length); + (*_progress) (0.5 * float (i) / _intrinsic_duration); } } if (ASDCP_FAILURE (mxf_writer.Finalize())) { - throw MiscError ("could not finalise audio MXF"); + boost::throw_exception (MiscError ("could not finalise audio MXF")); } } void -SoundAsset::write_to_cpl (xmlpp::Element* parent) const +SoundAsset::write_to_cpl (xmlpp::Node* node) const { - xmlpp::Element* main_sound = parent->add_child("MainSound"); - main_sound->add_child("Id")->add_child_text("urn:uuid:" + _uuid); - main_sound->add_child("AnnotationText")->add_child_text(_file_name); - main_sound->add_child("EditRate")->add_child_text(boost::lexical_cast<string> (_fps) + " 1"); - main_sound->add_child("IntrinsicDuration")->add_child_text(boost::lexical_cast<string> (_length)); - main_sound->add_child("EntryPoint")->add_child_text("0"); - main_sound->add_child("Duration")->add_child_text(boost::lexical_cast<string> (_length)); + xmlpp::Node* ms = node->add_child ("MainSound"); + ms->add_child ("Id")->add_child_text ("urn:uuid:" + _uuid); + ms->add_child ("AnnotationText")->add_child_text (_file_name); + ms->add_child ("EditRate")->add_child_text (lexical_cast<string> (_edit_rate) + " 1"); + ms->add_child ("IntrinsicDuration")->add_child_text (lexical_cast<string> (_intrinsic_duration)); + ms->add_child ("EntryPoint")->add_child_text (lexical_cast<string> (_entry_point)); + ms->add_child ("Duration")->add_child_text (lexical_cast<string> (_duration)); if (_encrypted) { - main_sound->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id); + ms->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id); } } bool -SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, list<string>& notes) const +SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, string)> note) const { - if (!MXFAsset::equals (other, opt, notes)) { + if (!MXFAsset::equals (other, opt, note)) { return false; } ASDCP::PCM::MXFReader reader_A; if (ASDCP_FAILURE (reader_A.OpenRead (path().string().c_str()))) { - throw MXFFileError ("could not open MXF file for reading", path().string()); + boost::throw_exception (MXFFileError ("could not open MXF file for reading", path().string())); } ASDCP::PCM::MXFReader reader_B; if (ASDCP_FAILURE (reader_B.OpenRead (other->path().string().c_str()))) { - throw MXFFileError ("could not open MXF file for reading", path().string()); + boost::throw_exception (MXFFileError ("could not open MXF file for reading", path().string())); } ASDCP::PCM::AudioDescriptor desc_A; if (ASDCP_FAILURE (reader_A.FillAudioDescriptor (desc_A))) { - throw DCPReadError ("could not read audio MXF information"); + boost::throw_exception (DCPReadError ("could not read audio MXF information")); } ASDCP::PCM::AudioDescriptor desc_B; if (ASDCP_FAILURE (reader_B.FillAudioDescriptor (desc_B))) { - throw DCPReadError ("could not read audio MXF information"); + boost::throw_exception (DCPReadError ("could not read audio MXF information")); } if ( @@ -272,24 +270,24 @@ SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, list<str // desc_A.ChannelFormat != desc_B.ChannelFormat || ) { - notes.push_back ("audio MXF picture descriptors differ"); + note (ERROR, "audio MXF picture descriptors differ"); return false; } ASDCP::PCM::FrameBuffer buffer_A (1 * Kumu::Megabyte); ASDCP::PCM::FrameBuffer buffer_B (1 * Kumu::Megabyte); - for (int i = 0; i < _length; ++i) { + for (int i = 0; i < _intrinsic_duration; ++i) { if (ASDCP_FAILURE (reader_A.ReadFrame (i, buffer_A))) { - throw DCPReadError ("could not read audio frame"); + boost::throw_exception (DCPReadError ("could not read audio frame")); } if (ASDCP_FAILURE (reader_B.ReadFrame (i, buffer_B))) { - throw DCPReadError ("could not read audio frame"); + boost::throw_exception (DCPReadError ("could not read audio frame")); } if (buffer_A.Size() != buffer_B.Size()) { - notes.push_back ("sizes of audio data for frame " + lexical_cast<string>(i) + " differ"); + note (ERROR, "sizes of audio data for frame " + lexical_cast<string>(i) + " differ"); return false; } @@ -297,7 +295,7 @@ SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, list<str for (uint32_t i = 0; i < buffer_A.Size(); ++i) { int const d = abs (buffer_A.RoData()[i] - buffer_B.RoData()[i]); if (d > opt.max_audio_sample_error) { - notes.push_back ("PCM data difference of " + lexical_cast<string> (d)); + note (ERROR, "PCM data difference of " + lexical_cast<string> (d)); return false; } } @@ -310,7 +308,106 @@ SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, list<str shared_ptr<const SoundFrame> SoundAsset::get_frame (int n) const { - return shared_ptr<const SoundFrame> (new SoundFrame (path().string(), n + _entry_point)); + /* XXX: should add on entry point here? */ + return shared_ptr<const SoundFrame> (new SoundFrame (path().string(), n)); +} + +shared_ptr<SoundAssetWriter> +SoundAsset::start_write (MXFMetadata const & metadata) +{ + /* XXX: can't we use a shared_ptr here? */ + return shared_ptr<SoundAssetWriter> (new SoundAssetWriter (this, metadata)); +} + +struct SoundAssetWriter::ASDCPState +{ + ASDCP::PCM::MXFWriter mxf_writer; + ASDCP::PCM::FrameBuffer frame_buffer; + ASDCP::WriterInfo writer_info; + ASDCP::PCM::AudioDescriptor audio_desc; +}; + +SoundAssetWriter::SoundAssetWriter (SoundAsset* a, MXFMetadata const & m) + : _state (new SoundAssetWriter::ASDCPState) + , _asset (a) + , _finalized (false) + , _frames_written (0) + , _frame_buffer_offset (0) + , _metadata (m) +{ + /* Derived from ASDCP::Wav::SimpleWaveHeader::FillADesc */ + _state->audio_desc.EditRate = ASDCP::Rational (_asset->edit_rate(), 1); + _state->audio_desc.AudioSamplingRate = ASDCP::Rational (_asset->sampling_rate(), 1); + _state->audio_desc.Locked = 0; + _state->audio_desc.ChannelCount = _asset->channels (); + _state->audio_desc.QuantizationBits = 24; + _state->audio_desc.BlockAlign = 3 * _asset->channels(); + _state->audio_desc.AvgBps = _asset->sampling_rate() * _state->audio_desc.BlockAlign; + _state->audio_desc.LinkedTrackID = 0; + _state->audio_desc.ChannelFormat = ASDCP::PCM::CF_NONE; + + _state->frame_buffer.Capacity (ASDCP::PCM::CalcFrameBufferSize (_state->audio_desc)); + _state->frame_buffer.Size (ASDCP::PCM::CalcFrameBufferSize (_state->audio_desc)); + memset (_state->frame_buffer.Data(), 0, _state->frame_buffer.Capacity()); + + _asset->fill_writer_info (&_state->writer_info, _asset->uuid (), _metadata); + + if (ASDCP_FAILURE (_state->mxf_writer.OpenWrite (_asset->path().string().c_str(), _state->writer_info, _state->audio_desc))) { + boost::throw_exception (FileError ("could not open audio MXF for writing", _asset->path().string())); + } +} + +void +SoundAssetWriter::write (float const * const * data, int frames) +{ + for (int i = 0; i < frames; ++i) { + + byte_t* out = _state->frame_buffer.Data() + _frame_buffer_offset; + + /* Write one sample per channel */ + for (int j = 0; j < _asset->channels(); ++j) { + int32_t const s = data[j][i] * (1 << 23); + *out++ = (s & 0xff); + *out++ = (s & 0xff00) >> 8; + *out++ = (s & 0xff0000) >> 16; + } + _frame_buffer_offset += 3 * _asset->channels(); + + assert (_frame_buffer_offset <= int (_state->frame_buffer.Capacity())); + + /* Finish the MXF frame if required */ + if (_frame_buffer_offset == int (_state->frame_buffer.Capacity())) { + write_current_frame (); + _frame_buffer_offset = 0; + memset (_state->frame_buffer.Data(), 0, _state->frame_buffer.Capacity()); + } + } +} + +void +SoundAssetWriter::write_current_frame () +{ + if (ASDCP_FAILURE (_state->mxf_writer.WriteFrame (_state->frame_buffer, 0, 0))) { + boost::throw_exception (MiscError ("could not write audio MXF frame")); + } + + ++_frames_written; +} + +void +SoundAssetWriter::finalize () +{ + if (_frame_buffer_offset > 0) { + write_current_frame (); + } + + if (ASDCP_FAILURE (_state->mxf_writer.Finalize())) { + boost::throw_exception (MiscError ("could not finalise audio MXF")); + } + + _finalized = true; + _asset->set_intrinsic_duration (_frames_written); + _asset->set_duration (_frames_written); } string diff --git a/src/sound_asset.h b/src/sound_asset.h index 80122038..a587b551 100644 --- a/src/sound_asset.h +++ b/src/sound_asset.h @@ -26,11 +26,44 @@ #include "mxf_asset.h" #include "types.h" +#include "metadata.h" namespace libdcp { -class SoundFrame; +class SoundFrame; +class SoundAsset; + +class SoundAssetWriter +{ +public: + void write (float const * const *, int); + void finalize (); + +private: + friend class SoundAsset; + + SoundAssetWriter (SoundAsset *, MXFMetadata const &); + + /* no copy construction */ + SoundAssetWriter (SoundAssetWriter const &); + SoundAssetWriter& operator= (SoundAssetWriter const &); + + void write_current_frame (); + + /* do this with an opaque pointer so we don't have to include + ASDCP headers + */ + + struct ASDCPState; + boost::shared_ptr<ASDCPState> _state; + + SoundAsset* _asset; + bool _finalized; + int _frames_written; + int _frame_buffer_offset; + MXFMetadata _metadata; +}; /** @brief An asset made up of WAV files */ class SoundAsset : public MXFAsset @@ -45,7 +78,9 @@ public: * @param fps Frames per second. * @param length Length in frames. * @param start_frame Frame in the source to start writing from. + * @param intrinsic_duration Length of the whole asset in frames. * @param encrypted true if asset should be encrypted. + * Note that this is different to entry_point in that the asset will contain no data before start_frame. */ SoundAsset ( std::vector<std::string> const & files, @@ -53,9 +88,9 @@ public: std::string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, - int length, - int start_frame, - bool encrypted + int intrinsic_duration, + bool encrypted, + MXFMetadata const & metadata = MXFMetadata () ); /** Construct a SoundAsset, generating the MXF from some WAV files. @@ -65,8 +100,7 @@ public: * @param mxf_name Name of MXF file to create. * @param progress Signal to inform of progress. * @param fps Frames per second. - * @param length Length in frames. - * @param start_frame Frame in the source to start writing from. + * @param intrinsic_duration Length of the whole asset in frames. * @param channels Number of audio channels. * @param encrypted true if asset should be encrypted. */ @@ -76,26 +110,33 @@ public: std::string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, - int length, - int start_frame, + int intrinsic_duration, int channels, - bool encrypted + bool encrypted, + MXFMetadata const & metadata = MXFMetadata () + ); + + SoundAsset ( + std::string directory, + std::string mxf_name ); SoundAsset ( std::string directory, std::string mxf_name, int fps, - int entry_point, - int length + int channels, + int sampling_rate ); + + boost::shared_ptr<SoundAssetWriter> start_write (MXFMetadata const & metadata = MXFMetadata ()); - /** Write details of the asset to a CPL AssetList node. - * @param p Parent node. + /** Write details of this asset to a CPL XML node. + * @param node Node. */ - void write_to_cpl (xmlpp::Element* p) const; + void write_to_cpl (xmlpp::Node* node) const; - bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, std::list<std::string>& notes) const; + bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, std::string)> note) const; boost::shared_ptr<const SoundFrame> get_frame (int n) const; @@ -109,14 +150,12 @@ public: private: std::string key_type () const; - - void construct (boost::function<std::string (Channel)> get_path); + void construct (boost::function<std::string (Channel)> get_path, MXFMetadata const &); std::string path_from_channel (Channel channel, std::vector<std::string> const & files); /** Number of channels in the asset */ int _channels; int _sampling_rate; - int _start_frame; }; } diff --git a/src/sound_frame.cc b/src/sound_frame.cc index ed626f5e..c2a10564 100644 --- a/src/sound_frame.cc +++ b/src/sound_frame.cc @@ -29,14 +29,14 @@ SoundFrame::SoundFrame (string mxf_path, int n) { ASDCP::PCM::MXFReader reader; if (ASDCP_FAILURE (reader.OpenRead (mxf_path.c_str()))) { - throw FileError ("could not open MXF file for reading", mxf_path); + boost::throw_exception (FileError ("could not open MXF file for reading", mxf_path)); } /* XXX: unfortunate guesswork on this buffer size */ _buffer = new ASDCP::PCM::FrameBuffer (1 * Kumu::Megabyte); if (ASDCP_FAILURE (reader.ReadFrame (n, *_buffer))) { - throw DCPReadError ("could not read audio frame"); + boost::throw_exception (DCPReadError ("could not read audio frame")); } } diff --git a/src/subtitle_asset.cc b/src/subtitle_asset.cc index 998abc07..80cc1ea6 100644 --- a/src/subtitle_asset.cc +++ b/src/subtitle_asset.cc @@ -23,6 +23,7 @@ #include <libxml++/nodes/element.h> #include "subtitle_asset.h" #include "util.h" +#include "xml.h" using std::string; using std::list; @@ -31,6 +32,7 @@ using std::ofstream; using std::stringstream; using boost::shared_ptr; using boost::lexical_cast; +using boost::optional; using namespace libdcp; SubtitleAsset::SubtitleAsset (string directory, string xml_file) @@ -53,7 +55,7 @@ SubtitleAsset::SubtitleAsset (string directory, string movie_title, string langu void SubtitleAsset::read_xml (string xml_file) { - shared_ptr<XMLFile> xml (new XMLFile (xml_file, "DCSubtitle")); + shared_ptr<cxml::File> xml (new cxml::File (xml_file, "DCSubtitle")); _uuid = xml->string_child ("SubtitleID"); _movie_title = xml->string_child ("MovieTitle"); @@ -62,8 +64,8 @@ SubtitleAsset::read_xml (string xml_file) xml->ignore_child ("LoadFont"); - list<shared_ptr<FontNode> > font_nodes = xml->type_children<FontNode> ("Font"); - _load_font_nodes = xml->type_children<LoadFontNode> ("LoadFont"); + list<shared_ptr<libdcp::parse::Font> > font_nodes = type_children<libdcp::parse::Font> (xml, "Font"); + _load_font_nodes = type_children<libdcp::parse::LoadFont> (xml, "LoadFont"); /* Now make Subtitle objects to represent the raw XML nodes in a sane way. @@ -75,17 +77,17 @@ SubtitleAsset::read_xml (string xml_file) void SubtitleAsset::examine_font_nodes ( - shared_ptr<XMLFile> xml, - list<shared_ptr<FontNode> > const & font_nodes, + shared_ptr<const cxml::Node> xml, + list<shared_ptr<libdcp::parse::Font> > const & font_nodes, ParseState& parse_state ) { - for (list<shared_ptr<FontNode> >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) { + for (list<shared_ptr<libdcp::parse::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<SubtitleNode> >::iterator j = (*i)->subtitle_nodes.begin(); j != (*i)->subtitle_nodes.end(); ++j) { + for (list<shared_ptr<libdcp::parse::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); @@ -101,12 +103,12 @@ SubtitleAsset::examine_font_nodes ( void SubtitleAsset::examine_text_nodes ( - shared_ptr<XMLFile> xml, - list<shared_ptr<TextNode> > const & text_nodes, + shared_ptr<const cxml::Node> xml, + list<shared_ptr<libdcp::parse::Text> > const & text_nodes, ParseState& parse_state ) { - for (list<shared_ptr<TextNode> >::const_iterator i = text_nodes.begin(); i != text_nodes.end(); ++i) { + for (list<shared_ptr<libdcp::parse::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); @@ -128,9 +130,9 @@ SubtitleAsset::maybe_add_subtitle (string text, ParseState const & parse_state) assert (!parse_state.text_nodes.empty ()); assert (!parse_state.subtitle_nodes.empty ()); - FontNode effective_font (parse_state.font_nodes); - TextNode effective_text (*parse_state.text_nodes.back ()); - SubtitleNode effective_subtitle (*parse_state.subtitle_nodes.back ()); + libdcp::parse::Font effective_font (parse_state.font_nodes); + libdcp::parse::Text effective_text (*parse_state.text_nodes.back ()); + libdcp::parse::Subtitle effective_subtitle (*parse_state.subtitle_nodes.back ()); _subtitles.push_back ( shared_ptr<Subtitle> ( @@ -153,107 +155,6 @@ SubtitleAsset::maybe_add_subtitle (string text, ParseState const & parse_state) ); } -FontNode::FontNode (xmlpp::Node const * node) - : XMLNode (node) -{ - text = content (); - - id = optional_string_attribute ("Id"); - size = optional_int64_attribute ("Size"); - italic = optional_bool_attribute ("Italic"); - color = optional_color_attribute ("Color"); - string const e = optional_string_attribute ("Effect"); - if (!e.empty ()) { - effect = string_to_effect (e); - } - effect_color = optional_color_attribute ("EffectColor"); - subtitle_nodes = type_children<SubtitleNode> ("Subtitle"); - font_nodes = type_children<FontNode> ("Font"); - text_nodes = type_children<TextNode> ("Text"); -} - -FontNode::FontNode (list<shared_ptr<FontNode> > const & font_nodes) - : size (0) - , italic (false) - , color ("FFFFFFFF") - , effect_color ("FFFFFFFF") -{ - for (list<shared_ptr<FontNode> >::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 (); - } - } -} - -LoadFontNode::LoadFontNode (xmlpp::Node const * node) - : XMLNode (node) -{ - id = string_attribute ("Id"); - uri = string_attribute ("URI"); -} - - -SubtitleNode::SubtitleNode (xmlpp::Node const * node) - : XMLNode (node) -{ - in = time_attribute ("TimeIn"); - out = time_attribute ("TimeOut"); - font_nodes = type_children<FontNode> ("Font"); - text_nodes = type_children<TextNode> ("Text"); - fade_up_time = fade_time ("FadeUpTime"); - fade_down_time = fade_time ("FadeDownTime"); -} - -Time -SubtitleNode::fade_time (string name) -{ - string const u = optional_string_attribute (name); - 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; -} - -TextNode::TextNode (xmlpp::Node const * node) - : XMLNode (node) - , v_align (CENTER) -{ - text = content (); - v_position = float_attribute ("VPosition"); - string const v = optional_string_attribute ("VAlign"); - if (!v.empty ()) { - v_align = string_to_valign (v); - } - - font_nodes = type_children<FontNode> ("Font"); -} - list<shared_ptr<Subtitle> > SubtitleAsset::subtitles_at (Time t) const { @@ -270,7 +171,7 @@ SubtitleAsset::subtitles_at (Time t) const std::string SubtitleAsset::font_id_to_name (string id) const { - list<shared_ptr<LoadFontNode> >::const_iterator i = _load_font_nodes.begin(); + list<shared_ptr<libdcp::parse::LoadFont> >::const_iterator i = _load_font_nodes.begin(); while (i != _load_font_nodes.end() && (*i)->id != id) { ++i; } @@ -376,14 +277,15 @@ SubtitleAsset::add (shared_ptr<Subtitle> s) } void -SubtitleAsset::write_to_cpl (xmlpp::Element* parent) const +SubtitleAsset::write_to_cpl (xmlpp::Node* node) const { /* XXX: should EditRate, Duration and IntrinsicDuration be in here? */ - xmlpp::Element* main_subtitle = parent->add_child("MainSubtitle"); - main_subtitle->add_child("Id")->add_child_text("urn:uuid:" + _uuid); - main_subtitle->add_child("AnnotationText")->add_child_text(_file_name); - main_subtitle->add_child("EntryPoint")->add_child_text("0"); + xmlpp::Node* ms = node->add_child ("MainSubtitle"); + ms->add_child("Id")->add_child_text("urn:uuid:" + _uuid); + ms->add_child("AnnotationText")->add_child_text (_file_name); + /* XXX */ + ms->add_child("EntryPoint")->add_child_text ("0"); } struct SubtitleSorter { @@ -398,26 +300,30 @@ struct SubtitleSorter { void SubtitleAsset::write_xml () const { - ofstream f (path().string().c_str()); - write_xml (f); + ofstream s (path().string().c_str()); + write_xml (s); } void SubtitleAsset::write_xml (ostream& s) const { - s << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - << "<DCSubtitle Version=\"1.0\">\n" - << " <SubtitleID>" << _uuid << "</SubtitleID>\n" - << " <MovieTitle>" << _movie_title << "</MovieTitle>\n" - << " <ReelNumber>" << _reel_number << "</ReelNumber>\n" - << " <Language>" << _language << "</Language>\n"; + xmlpp::Document doc; + xmlpp::Element* root = doc.create_root_node ("DCSubtitle"); + root->set_attribute ("Version", "1.0"); + + root->add_child("SubtitleID")->add_child_text (_uuid); + root->add_child("MovieTitle")->add_child_text (_movie_title); + root->add_child("ReelNumber")->add_child_text (lexical_cast<string> (_reel_number)); + root->add_child("Language")->add_child_text (_language); if (_load_font_nodes.size() > 1) { - throw MiscError ("multiple LoadFont nodes not supported"); + boost::throw_exception (MiscError ("multiple LoadFont nodes not supported")); } if (!_load_font_nodes.empty ()) { - s << " <LoadFont Id=\"" << _load_font_nodes.front()->id << "\" URI=\"" << _load_font_nodes.front()->uri << "\"/>\n"; + xmlpp::Element* load_font = root->add_child("LoadFont"); + load_font->set_attribute("Id", _load_font_nodes.front()->id); + load_font->set_attribute("URI", _load_font_nodes.front()->uri); } list<shared_ptr<Subtitle> > sorted = _subtitles; @@ -428,7 +334,6 @@ SubtitleAsset::write_xml (ostream& s) const /* XXX: multiple fonts not supported */ /* XXX: script, underlined, weight not supported */ - bool first = true; bool italic = false; Color color; int size = 0; @@ -440,66 +345,61 @@ SubtitleAsset::write_xml (ostream& s) const Time last_fade_up_time; Time last_fade_down_time; + xmlpp::Element* font = 0; + xmlpp::Element* subtitle = 0; + for (list<shared_ptr<Subtitle> >::iterator i = sorted.begin(); i != sorted.end(); ++i) { /* We will start a new <Font>...</Font> whenever some font property changes. - I suppose should really make an optimal hierarchy of <Font> tags, but + I suppose we should really make an optimal hierarchy of <Font> tags, but that seems hard. */ - bool const font_changed = first || + bool const font_changed = italic != (*i)->italic() || color != (*i)->color() || size != (*i)->size() || effect != (*i)->effect() || effect_color != (*i)->effect_color(); - stringstream a; if (font_changed) { italic = (*i)->italic (); - a << "Italic=\"" << (italic ? "yes" : "no") << "\" "; color = (*i)->color (); - a << "Color=\"" << color.to_argb_string() << "\" "; size = (*i)->size (); - a << "Size=\"" << size << "\" "; effect = (*i)->effect (); - a << "Effect=\"" << effect_to_string(effect) << "\" "; effect_color = (*i)->effect_color (); - a << "EffectColor=\"" << effect_color.to_argb_string() << "\" "; - a << "Script=\"normal\" Underlined=\"no\" Weight=\"normal\""; } - if (first || font_changed || + 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()); + font->set_attribute ("Size", lexical_cast<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() )) { - if (!first) { - s << " </Subtitle>\n"; - } - - if (font_changed) { - if (!first) { - s << " </Font>\n"; - } - - string id = "theFontId"; - if (!_load_font_nodes.empty()) { - id = _load_font_nodes.front()->id; - } - - s << " <Font Id=\"" << id << "\" " << a.str() << ">\n"; - } - - s << " <Subtitle " - << "SpotNumber=\"" << spot_number++ << "\" " - << "TimeIn=\"" << (*i)->in().to_string() << "\" " - << "TimeOut=\"" << (*i)->out().to_string() << "\" " - << "FadeUpTime=\"" << (*i)->fade_up_time().to_ticks() << "\" " - << "FadeDownTime=\"" << (*i)->fade_down_time().to_ticks() << "\"" - << ">\n"; + subtitle = font->add_child ("Subtitle"); + subtitle->set_attribute ("SpotNumber", lexical_cast<string> (spot_number++)); + subtitle->set_attribute ("TimeIn", (*i)->in().to_string()); + subtitle->set_attribute ("TimeOut", (*i)->out().to_string()); + 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())); last_in = (*i)->in (); last_out = (*i)->out (); @@ -507,23 +407,12 @@ SubtitleAsset::write_xml (ostream& s) const last_fade_down_time = (*i)->fade_down_time (); } - s << " <Text " - << "VAlign=\"" << valign_to_string ((*i)->v_align()) << "\" " - << "VPosition=\"" << (*i)->v_position() << "\"" - << ">" << escape ((*i)->text()) << "</Text>\n"; - - first = false; + xmlpp::Element* text = subtitle->add_child ("Text"); + text->set_attribute ("VAlign", valign_to_string ((*i)->v_align())); + text->set_attribute ("VPosition", lexical_cast<string> ((*i)->v_position())); + text->add_child_text ((*i)->text()); } - s << " </Subtitle>\n"; - s << " </Font>\n"; - s << "</DCSubtitle>\n"; + doc.write_to_stream_formatted (s, "UTF-8"); } -/** XXX: Another reason why we should be writing with libxml++ */ -string -SubtitleAsset::escape (string s) const -{ - boost::replace_all (s, "&", "&"); - return s; -} diff --git a/src/subtitle_asset.h b/src/subtitle_asset.h index f4a57151..2da1ce7b 100644 --- a/src/subtitle_asset.h +++ b/src/subtitle_asset.h @@ -20,71 +20,11 @@ #include "asset.h" #include "xml.h" #include "dcp_time.h" +#include "parse/subtitle.h" namespace libdcp { -class FontNode; - -class TextNode : public XMLNode -{ -public: - TextNode () {} - TextNode (xmlpp::Node const * node); - - float v_position; - VAlign v_align; - std::string text; - std::list<boost::shared_ptr<FontNode> > font_nodes; -}; - -class SubtitleNode : public XMLNode -{ -public: - SubtitleNode () {} - SubtitleNode (xmlpp::Node const * node); - - Time in; - Time out; - Time fade_up_time; - Time fade_down_time; - std::list<boost::shared_ptr<FontNode> > font_nodes; - std::list<boost::shared_ptr<TextNode> > text_nodes; - -private: - Time fade_time (std::string name); -}; - -class FontNode : public XMLNode -{ -public: - FontNode () {} - FontNode (xmlpp::Node const * node); - FontNode (std::list<boost::shared_ptr<FontNode> > const & font_nodes); - - std::string text; - std::string id; - int size; - boost::optional<bool> italic; - boost::optional<Color> color; - boost::optional<Effect> effect; - boost::optional<Color> effect_color; - - std::list<boost::shared_ptr<SubtitleNode> > subtitle_nodes; - std::list<boost::shared_ptr<FontNode> > font_nodes; - std::list<boost::shared_ptr<TextNode> > text_nodes; -}; - -class LoadFontNode : public XMLNode -{ -public: - LoadFontNode () {} - LoadFontNode (xmlpp::Node const * node); - - std::string id; - std::string uri; -}; - class Subtitle { public: @@ -183,14 +123,10 @@ public: SubtitleAsset (std::string directory, std::string xml_file); SubtitleAsset (std::string directory, std::string movie_title, std::string language); - /** Write details of the asset to a CPL AssetList node. - * @param p Parent node. - */ - void write_to_cpl (xmlpp::Element* p) const; - - virtual bool equals (boost::shared_ptr<const Asset>, EqualityOptions, std::list<std::string>& notes) const { + void write_to_cpl (xmlpp::Node *) const; + virtual bool equals (boost::shared_ptr<const Asset>, EqualityOptions, boost::function<void (NoteType, std::string)> note) const { /* XXX */ - notes.push_back ("subtitle assets not compared yet"); + note (ERROR, "subtitle assets not compared yet"); return true; } @@ -207,29 +143,28 @@ public: void read_xml (std::string); void write_xml () const; - void write_xml (std::ostream& s) const; + void write_xml (std::ostream &) const; private: std::string font_id_to_name (std::string id) const; - std::string escape (std::string) const; struct ParseState { - std::list<boost::shared_ptr<FontNode> > font_nodes; - std::list<boost::shared_ptr<TextNode> > text_nodes; - std::list<boost::shared_ptr<SubtitleNode> > subtitle_nodes; + std::list<boost::shared_ptr<parse::Font> > font_nodes; + std::list<boost::shared_ptr<parse::Text> > text_nodes; + std::list<boost::shared_ptr<parse::Subtitle> > subtitle_nodes; }; void maybe_add_subtitle (std::string text, ParseState const & parse_state); void examine_font_nodes ( - boost::shared_ptr<XMLFile> xml, - std::list<boost::shared_ptr<FontNode> > const & font_nodes, + boost::shared_ptr<const cxml::Node> xml, + std::list<boost::shared_ptr<parse::Font> > const & font_nodes, ParseState& parse_state ); void examine_text_nodes ( - boost::shared_ptr<XMLFile> xml, - std::list<boost::shared_ptr<TextNode> > const & text_nodes, + boost::shared_ptr<const cxml::Node> xml, + std::list<boost::shared_ptr<parse::Text> > const & text_nodes, ParseState& parse_state ); @@ -237,7 +172,7 @@ private: /* strangely, this is sometimes a string */ std::string _reel_number; std::string _language; - std::list<boost::shared_ptr<LoadFontNode> > _load_font_nodes; + std::list<boost::shared_ptr<parse::LoadFont> > _load_font_nodes; std::list<boost::shared_ptr<Subtitle> > _subtitles; bool _need_sort; diff --git a/src/test_mode.cc b/src/test_mode.cc deleted file mode 100644 index bfe10fee..00000000 --- a/src/test_mode.cc +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright (C) 2012 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/test_mode.cc - * @brief A method to enable test mode for libdcp. - */ - -#include "KM_prng.h" -#include "test_mode.h" -#include "metadata.h" - -/** Calling this will seed the random number generator used to - * generate UUIDs with a known value, and set the DCP issue - * date to 1st January 2012 at midnight. This means that - * two runs of libdcp with the same inputs will produce - * the same output. - */ - -void -libdcp::enable_test_mode () -{ - Kumu::libdcp_test = true; - Metadata::instance()->issue_date = "2012-01-01T00:00:00+00:00"; - - /* Remove version strings */ - Metadata::instance()->issuer = "libdcp-test"; - Metadata::instance()->creator = "libdcp-test"; - Metadata::instance()->product_version = "test"; -} diff --git a/src/types.cc b/src/types.cc index ac01ae45..693b9ab2 100644 --- a/src/types.cc +++ b/src/types.cc @@ -15,7 +15,7 @@ Fraction::Fraction (string s) vector<string> b; split (b, s, is_any_of (" ")); if (b.size() != 2) { - throw XMLError ("malformed fraction " + s + " in XML node"); + boost::throw_exception (XMLError ("malformed fraction " + s + " in XML node")); } numerator = lexical_cast<int> (b[0]); denominator = lexical_cast<int> (b[1]); @@ -57,7 +57,7 @@ Color::Color (string argb_hex) { int alpha; if (sscanf (argb_hex.c_str(), "%2x%2x%2x%2x", &alpha, &r, &g, &b) < 4) { - throw XMLError ("could not parse colour string"); + boost::throw_exception (XMLError ("could not parse colour string")); } } @@ -118,7 +118,7 @@ libdcp::effect_to_string (Effect e) return "shadow"; } - throw MiscError ("unknown effect type"); + boost::throw_exception (MiscError ("unknown effect type")); } Effect @@ -132,7 +132,7 @@ libdcp::string_to_effect (string s) return SHADOW; } - throw DCPReadError ("unknown subtitle effect type"); + boost::throw_exception (DCPReadError ("unknown subtitle effect type")); } string @@ -147,7 +147,7 @@ libdcp::valign_to_string (VAlign v) return "bottom"; } - throw MiscError ("unknown valign type"); + boost::throw_exception (MiscError ("unknown valign type")); } VAlign @@ -161,7 +161,7 @@ libdcp::string_to_valign (string s) return BOTTOM; } - throw DCPReadError ("unknown subtitle valign type"); + boost::throw_exception (DCPReadError ("unknown subtitle valign type")); } diff --git a/src/types.h b/src/types.h index f1b5f640..edabb9e2 100644 --- a/src/types.h +++ b/src/types.h @@ -24,6 +24,8 @@ #ifndef LIBDCP_TYPES_H #define LIBDCP_TYPES_H +#include <string> + namespace libdcp { @@ -98,11 +100,24 @@ struct EqualityOptions { : max_mean_pixel_error (0) , max_std_dev_pixel_error (0) , max_audio_sample_error (0) + , cpl_names_can_differ (false) + , mxf_names_can_differ (false) {} double max_mean_pixel_error; double max_std_dev_pixel_error; int max_audio_sample_error; + bool cpl_names_can_differ; + bool mxf_names_can_differ; +}; + +/* Win32 defines this */ +#undef ERROR + +enum NoteType { + PROGRESS, + ERROR, + NOTE }; /** @class Color diff --git a/src/util.cc b/src/util.cc index 8277b2bf..0c63c305 100644 --- a/src/util.cc +++ b/src/util.cc @@ -26,6 +26,7 @@ #include <iostream> #include <iomanip> #include <boost/filesystem.hpp> +#include <boost/lexical_cast.hpp> #include <openssl/sha.h> #include <libxml++/nodes/element.h> #include <libxml++/document.h> @@ -39,8 +40,8 @@ #include "exceptions.h" #include "types.h" #include "argb_frame.h" -#include "lut.h" #include "certificates.h" +#include "gamma_lut.h" using std::string; using std::cout; @@ -49,6 +50,7 @@ using std::min; using std::max; using std::list; using boost::shared_ptr; +using boost::lexical_cast; using namespace libdcp; /** Create a UUID. @@ -74,7 +76,7 @@ libdcp::make_digest (string filename) { Kumu::FileReader reader; if (ASDCP_FAILURE (reader.OpenRead (filename.c_str ()))) { - throw FileError ("could not open file to compute digest", filename); + boost::throw_exception (FileError ("could not open file to compute digest", filename)); } SHA_CTX sha; @@ -89,7 +91,7 @@ libdcp::make_digest (string filename) if (r == Kumu::RESULT_ENDOFFILE) { break; } else if (ASDCP_FAILURE (r)) { - throw FileError ("could not read file to compute digest", filename); + boost::throw_exception (FileError ("could not read file to compute digest", filename)); } SHA1_Update (&sha, read_buffer.Data(), read); @@ -194,7 +196,7 @@ libdcp::decompress_j2k (uint8_t* data, int64_t size, int reduce) if (!image) { opj_destroy_decompress (decoder); opj_cio_close (cio); - throw DCPReadError ("could not decode JPEG2000 codestream"); + boost::throw_exception (DCPReadError ("could not decode JPEG2000 codestream of " + lexical_cast<string> (size) + " bytes.")); } opj_cio_close (cio); @@ -209,8 +211,22 @@ libdcp::decompress_j2k (uint8_t* data, int64_t size, int reduce) * @return RGB image. */ shared_ptr<ARGBFrame> -libdcp::xyz_to_rgb (opj_image_t* xyz_frame) +libdcp::xyz_to_rgb (opj_image_t* xyz_frame, shared_ptr<const GammaLUT> lut_in, shared_ptr<const GammaLUT> lut_out) { + float const dci_coefficient = 48.0 / 52.37; + + /* sRGB color matrix for XYZ -> RGB. This is the same as the one used by the Fraunhofer + EasyDCP player, I think. + */ + + float const colour_matrix[3][3] = { + { 3.24096989631653, -1.5373831987381, -0.498610764741898 }, + { -0.96924364566803, 1.87596750259399, 0.0415550582110882 }, + { 0.0556300804018974, -0.203976958990097, 1.05697154998779 } + }; + + int const max_colour = pow (2, lut_out->bit_depth()) - 1; + struct { double x, y, z; } s; @@ -223,7 +239,7 @@ libdcp::xyz_to_rgb (opj_image_t* xyz_frame) int* xyz_y = xyz_frame->comps[1].data; int* xyz_z = xyz_frame->comps[2].data; - shared_ptr<ARGBFrame> argb_frame (new ARGBFrame (xyz_frame->x1, xyz_frame->y1)); + shared_ptr<ARGBFrame> argb_frame (new ARGBFrame (Size (xyz_frame->x1, xyz_frame->y1))); uint8_t* argb = argb_frame->data (); @@ -234,19 +250,19 @@ libdcp::xyz_to_rgb (opj_image_t* xyz_frame) assert (*xyz_x >= 0 && *xyz_y >= 0 && *xyz_z >= 0 && *xyz_x < 4096 && *xyz_x < 4096 && *xyz_z < 4096); /* In gamma LUT */ - s.x = lut_in[*xyz_x++]; - s.y = lut_in[*xyz_y++]; - s.z = lut_in[*xyz_z++]; - + s.x = lut_in->lut()[*xyz_x++]; + s.y = lut_in->lut()[*xyz_y++]; + s.z = lut_in->lut()[*xyz_z++]; + /* DCI companding */ - s.x /= DCI_COEFFICIENT; - s.y /= DCI_COEFFICIENT; - s.z /= DCI_COEFFICIENT; + s.x /= dci_coefficient; + s.y /= dci_coefficient; + s.z /= dci_coefficient; /* XYZ to RGB */ - d.r = ((s.x * color_matrix[0][0]) + (s.y * color_matrix[0][1]) + (s.z * color_matrix[0][2])); - d.g = ((s.x * color_matrix[1][0]) + (s.y * color_matrix[1][1]) + (s.z * color_matrix[1][2])); - d.b = ((s.x * color_matrix[2][0]) + (s.y * color_matrix[2][1]) + (s.z * color_matrix[2][2])); + d.r = ((s.x * colour_matrix[0][0]) + (s.y * colour_matrix[0][1]) + (s.z * colour_matrix[0][2])); + d.g = ((s.x * colour_matrix[1][0]) + (s.y * colour_matrix[1][1]) + (s.z * colour_matrix[1][2])); + d.b = ((s.x * colour_matrix[2][0]) + (s.y * colour_matrix[2][1]) + (s.z * colour_matrix[2][2])); d.r = min (d.r, 1.0); d.r = max (d.r, 0.0); @@ -258,9 +274,9 @@ libdcp::xyz_to_rgb (opj_image_t* xyz_frame) d.b = max (d.b, 0.0); /* Out gamma LUT */ - *argb_line++ = lut_out[(int) (d.b * COLOR_DEPTH)]; - *argb_line++ = lut_out[(int) (d.g * COLOR_DEPTH)]; - *argb_line++ = lut_out[(int) (d.r * COLOR_DEPTH)]; + *argb_line++ = lut_out->lut()[(int) (d.b * max_colour)] * 0xff; + *argb_line++ = lut_out->lut()[(int) (d.g * max_colour)] * 0xff; + *argb_line++ = lut_out->lut()[(int) (d.r * max_colour)] * 0xff; *argb_line++ = 0xff; } @@ -412,3 +428,13 @@ libdcp::sign (xmlpp::Element* parent, CertificateChain const & certificates, str add_signature_value (signature, certificates, signer_key, "dsig"); } +bool libdcp::operator== (libdcp::Size const & a, libdcp::Size const & b) +{ + return (a.width == b.width && a.height == b.height); +} + +bool libdcp::operator!= (libdcp::Size const & a, libdcp::Size const & b) +{ + return !(a == b); +} + @@ -17,12 +17,16 @@ */ +#ifndef LIBDCP_UTIL_H +#define LIBDCP_UTIL_H + /** @file src/util.h * @brief Utility methods. */ #include <string> #include <stdint.h> +#include <boost/shared_ptr.hpp> #include <openjpeg.h> #include "types.h" @@ -34,14 +38,34 @@ namespace libdcp { class ARGBFrame; class CertificateChain; +class GammaLUT; +class XYZsRGBLUT; + +struct Size { + Size () + : width (0) + , height (0) + {} + + Size (int w, int h) + : width (w) + , height (h) + {} + int width; + int height; +}; + +extern bool operator== (Size const & a, Size const & b); +extern bool operator!= (Size const & a, Size const & b); + extern std::string make_uuid (); extern std::string make_digest (std::string filename); extern std::string content_kind_to_string (ContentKind kind); extern ContentKind content_kind_from_string (std::string kind); extern bool empty_or_white_space (std::string s); extern opj_image_t* decompress_j2k (uint8_t* data, int64_t size, int reduce); -extern boost::shared_ptr<ARGBFrame> xyz_to_rgb (opj_image_t* xyz_frame); +extern boost::shared_ptr<ARGBFrame> xyz_to_rgb (opj_image_t* xyz_frame, boost::shared_ptr<const GammaLUT>, boost::shared_ptr<const GammaLUT>); extern void init (); @@ -50,3 +74,5 @@ extern void add_signature_value (xmlpp::Element* parent, CertificateChain const extern void add_signer (xmlpp::Element* parent, CertificateChain const & certificates, std::string const & ns); } + +#endif diff --git a/src/wscript b/src/wscript index 0922cb56..93d6d5c1 100644 --- a/src/wscript +++ b/src/wscript @@ -1,5 +1,5 @@ def build(bld): - if bld.env.STATIC_LIBDCP: + if bld.env.STATIC: obj = bld(features = 'cxx cxxstlib') else: obj = bld(features = 'cxx cxxshlib') @@ -7,37 +7,38 @@ def build(bld): obj.name = 'libdcp' obj.target = 'dcp' obj.export_includes = ['.'] - obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 BOOST_DATETIME OPENSSL SIGC++ LIBXML++ OPENJPEG XMLSEC1' + obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 BOOST_DATETIME OPENSSL SIGC++ LIBXML++ OPENJPEG CXML XMLSEC1' obj.use = 'libkumu-libdcp libasdcp-libdcp' obj.source = """ asset.cc - asset_map.cc + dcp.cc certificates.cc - cpl_file.cc crypt_chain.cc - dcp.cc + cpl.cc dcp_time.cc - lut.cc + gamma_lut.cc metadata.cc mxf_asset.cc picture_asset.cc picture_frame.cc - pkl_file.cc reel.cc argb_frame.cc sound_asset.cc sound_frame.cc subtitle_asset.cc - test_mode.cc types.cc util.cc version.cc - xml.cc + parse/asset_map.cc + parse/cpl.cc + parse/pkl.cc + parse/subtitle.cc """ headers = """ asset.h certificates.h + cpl.h crypt_chain.h dcp.h dcp_time.h @@ -51,13 +52,12 @@ def build(bld): sound_asset.h sound_frame.h subtitle_asset.h - test_mode.h types.h util.h version.h - xml.h """ bld.install_files('${PREFIX}/include/libdcp', headers) - if bld.env.STATIC_LIBDCP: + if bld.env.STATIC: bld.install_files('${PREFIX}/lib', 'libdcp.a') + diff --git a/src/xml.cc b/src/xml.cc deleted file mode 100644 index 22e91ac0..00000000 --- a/src/xml.cc +++ /dev/null @@ -1,263 +0,0 @@ -#include <sstream> -#include <iostream> -#include <boost/lexical_cast.hpp> -#include <boost/filesystem.hpp> -#include <boost/algorithm/string.hpp> -#include <libxml++/libxml++.h> -#include "xml.h" -#include "exceptions.h" -#include "util.h" - -using namespace std; -using namespace boost; -using namespace libdcp; - -XMLNode::XMLNode () - : _node (0) -{ - -} - -XMLNode::XMLNode (xmlpp::Node const * node) - : _node (node) -{ - -} - -xmlpp::Node * -XMLNode::node_child (string name) -{ - list<xmlpp::Node*> n = node_children (name); - if (n.size() > 1) { - throw XMLError ("duplicate XML tag " + name); - } else if (n.empty ()) { - throw XMLError ("missing XML tag " + name + " in " + _node->get_name()); - } - - return n.front (); -} - -list<xmlpp::Node*> -XMLNode::node_children (string name) -{ - /* XXX: using find / get_path should work here, but I can't follow - how get_path works. - */ - - xmlpp::Node::NodeList c = _node->get_children (); - - list<xmlpp::Node*> n; - for (xmlpp::Node::NodeList::iterator i = c.begin (); i != c.end(); ++i) { - if ((*i)->get_name() == name) { - n.push_back (*i); - } - } - - _taken.push_back (name); - return n; -} - -string -XMLNode::string_child (string name) -{ - return XMLNode (node_child (name)).content (); -} - -string -XMLNode::optional_string_child (string name) -{ - list<xmlpp::Node*> nodes = node_children (name); - if (nodes.size() > 2) { - throw XMLError ("duplicate XML tag " + name); - } - - if (nodes.empty ()) { - return ""; - } - - return string_child (name); -} - -ContentKind -XMLNode::kind_child (string name) -{ - return content_kind_from_string (string_child (name)); -} - -Fraction -XMLNode::fraction_child (string name) -{ - return Fraction (string_child (name)); -} - -int64_t -XMLNode::int64_child (string name) -{ - string s = string_child (name); - erase_all (s, " "); - return lexical_cast<int64_t> (s); -} - -int64_t -XMLNode::optional_int64_child (string name) -{ - list<xmlpp::Node*> nodes = node_children (name); - if (nodes.size() > 2) { - throw XMLError ("duplicate XML tag " + name); - } - - if (nodes.empty ()) { - return 0; - } - - return int64_child (name); -} - -float -XMLNode::float_child (string name) -{ - return lexical_cast<float> (string_child (name)); -} - -void -XMLNode::ignore_child (string name) -{ - _taken.push_back (name); -} - -Time -XMLNode::time_attribute (string name) -{ - return Time (string_attribute (name)); -} - -string -XMLNode::string_attribute (string name) -{ - xmlpp::Element const * e = dynamic_cast<const xmlpp::Element *> (_node); - if (!e) { - throw XMLError ("missing attribute"); - } - - xmlpp::Attribute* a = e->get_attribute (name); - if (!a) { - throw XMLError ("missing attribute"); - } - - return a->get_value (); -} - -string -XMLNode::optional_string_attribute (string name) -{ - xmlpp::Element const * e = dynamic_cast<const xmlpp::Element *> (_node); - if (!e) { - return ""; - } - - xmlpp::Attribute* a = e->get_attribute (name); - if (!a) { - return ""; - } - - return a->get_value (); -} - -float -XMLNode::float_attribute (string name) -{ - return lexical_cast<float> (string_attribute (name)); -} - -int64_t -XMLNode::int64_attribute (string name) -{ - return lexical_cast<int64_t> (string_attribute (name)); -} - -int64_t -XMLNode::optional_int64_attribute (string name) -{ - string const s = optional_string_attribute (name); - if (s.empty ()) { - return 0; - } - - return lexical_cast<int64_t> (s); -} - -optional<bool> -XMLNode::optional_bool_attribute (string name) -{ - string const s = optional_string_attribute (name); - if (s.empty ()) { - return optional<bool> (); - } - - if (s == "1" || s == "yes") { - return optional<bool> (true); - } - - return optional<bool> (false); -} - -optional<Color> -XMLNode::optional_color_attribute (string name) -{ - string const s = optional_string_attribute (name); - if (s.empty ()) { - return optional<Color> (); - } - - return optional<Color> (Color (s)); -} - -void -XMLNode::done () -{ - xmlpp::Node::NodeList c = _node->get_children (); - for (xmlpp::Node::NodeList::iterator i = c.begin(); i != c.end(); ++i) { - if (dynamic_cast<xmlpp::Element *> (*i) && find (_taken.begin(), _taken.end(), (*i)->get_name()) == _taken.end ()) { - throw XMLError ("unexpected XML node " + (*i)->get_name()); - } - } -} - -string -XMLNode::content () -{ - string content; - - xmlpp::Node::NodeList c = _node->get_children (); - for (xmlpp::Node::NodeList::const_iterator i = c.begin(); i != c.end(); ++i) { - xmlpp::ContentNode const * v = dynamic_cast<xmlpp::ContentNode const *> (*i); - if (v) { - content += v->get_content (); - } - } - - return content; -} - -XMLFile::XMLFile (string file, string root_name) -{ - if (!filesystem::exists (file)) { - throw FileError ("XML file does not exist", file); - } - - _parser = new xmlpp::DomParser; - _parser->parse_file (file); - if (!_parser) { - throw XMLError ("could not parse XML"); - } - - _node = _parser->get_document()->get_root_node (); - if (_node->get_name() != root_name) { - throw XMLError ("unrecognised root node"); - } -} - -XMLFile::~XMLFile () -{ - delete _parser; -} @@ -1,103 +1,91 @@ -#ifndef LIBDCP_XML_H -#define LIBDCP_XML_H +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> -#include <string> -#include <list> -#include <stdint.h> -#include <glibmm.h> -#include <boost/shared_ptr.hpp> -#include <boost/optional.hpp> -#include "types.h" -#include "exceptions.h" -#include "dcp_time.h" + 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. -namespace xmlpp { - class Node; - class DomParser; -} + 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. -namespace libdcp { + 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. -/** @brief A helper class for XML nodes */ -class XMLNode -{ -public: - XMLNode (); - XMLNode (xmlpp::Node const * node); +*/ - std::string string_child (std::string); - std::string optional_string_child (std::string); - ContentKind kind_child (std::string); - Fraction fraction_child (std::string); - int64_t int64_child (std::string); - int64_t optional_int64_child (std::string); - float float_child (std::string); - void ignore_child (std::string); - void done (); +#ifndef LIBDCP_XML_H +#define LIBDCP_XML_H - Time time_attribute (std::string); - float float_attribute (std::string); - std::string string_attribute (std::string); - std::string optional_string_attribute (std::string); - int64_t int64_attribute (std::string); - int64_t optional_int64_attribute (std::string); - boost::optional<bool> optional_bool_attribute (std::string); - boost::optional<Color> optional_color_attribute (std::string); +#include <libcxml/cxml.h> +#include "exceptions.h" - std::string content (); +namespace libdcp +{ - template <class T> - boost::shared_ptr<T> type_child (std::string name) { - return boost::shared_ptr<T> (new T (node_child (name))); +template <class T> +boost::shared_ptr<T> +optional_type_child (cxml::Node const & node, std::string name) +{ + std::list<boost::shared_ptr<cxml::Node> > n = node.node_children (name); + if (n.size() > 1) { + throw XMLError ("duplicate XML tag"); + } else if (n.empty ()) { + return boost::shared_ptr<T> (); } - template <class T> - boost::shared_ptr<T> optional_type_child (std::string name) { - std::list<xmlpp::Node*> n = node_children (name); - if (n.size() > 1) { - throw XMLError ("duplicate XML tag"); - } else if (n.empty ()) { - return boost::shared_ptr<T> (); - } - - return boost::shared_ptr<T> (new T (n.front ())); - } + return boost::shared_ptr<T> (new T (n.front ())); +} + +template <class T> +boost::shared_ptr<T> type_child (boost::shared_ptr<const cxml::Node> node, std::string name) { + return boost::shared_ptr<T> (new T (node->node_child (name))); +} - template <class T> - std::list<boost::shared_ptr<T> > type_children (std::string name) { - std::list<xmlpp::Node*> n = node_children (name); - std::list<boost::shared_ptr<T> > r; - for (typename std::list<xmlpp::Node*>::iterator i = n.begin(); i != n.end(); ++i) { - r.push_back (boost::shared_ptr<T> (new T (*i))); - } - return r; - } +template <class T> +boost::shared_ptr<T> +optional_type_child (boost::shared_ptr<const cxml::Node> node, std::string name) +{ + return optional_type_child<T> (*node.get(), name); +} - template <class T> - std::list<boost::shared_ptr<T> > type_grand_children (std::string name, std::string sub) { - XMLNode p (node_child (name)); - return p.type_children<T> (sub); +template <class T> +std::list<boost::shared_ptr<T> > +type_children (cxml::Node const & node, std::string name) +{ + std::list<boost::shared_ptr<cxml::Node> > n = node.node_children (name); + std::list<boost::shared_ptr<T> > r; + for (typename std::list<boost::shared_ptr<cxml::Node> >::iterator i = n.begin(); i != n.end(); ++i) { + r.push_back (boost::shared_ptr<T> (new T (*i))); } + return r; +} - xmlpp::Node const * _node; - -private: - xmlpp::Node* node_child (std::string); - std::list<xmlpp::Node*> node_children (std::string); - std::list<Glib::ustring> _taken; -}; - -/** @brief A helper class for XML files */ -class XMLFile : public XMLNode +template <class T> +std::list<boost::shared_ptr<T> > +type_children (boost::shared_ptr<const cxml::Node> node, std::string name) { -public: - XMLFile (std::string file, std::string root_name); - virtual ~XMLFile (); - -private: - xmlpp::DomParser* _parser; -}; + return type_children<T> (*node.get(), name); +} + +template <class T> +std::list<boost::shared_ptr<T> > +type_grand_children (cxml::Node const & node, std::string name, std::string sub) +{ + boost::shared_ptr<const cxml::Node> p = node.node_child (name); + return type_children<T> (p, sub); +} +template <class T> +std::list<boost::shared_ptr<T> > +type_grand_children (boost::shared_ptr<const cxml::Node> node, std::string name, std::string sub) +{ + return type_grand_children<T> (*node.get(), name, sub); +} + } #endif |
