X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fdcp.cc;h=4f1732da3ca2149dec2e5fe2bb637ba9c1e5d7ae;hb=6a5bb039b3bfd508cc87b6b15102b9eb60c62f8d;hp=5eadbca6dbdcf45e68ab7049800f8fe07cc29a82;hpb=a1a33941351365cc371f468c6c9c8f0cf8ca32d2;p=libdcp.git diff --git a/src/dcp.cc b/src/dcp.cc index 5eadbca6..4f1732da 100644 --- a/src/dcp.cc +++ b/src/dcp.cc @@ -27,116 +27,55 @@ #include #include #include +#include #include #include "dcp.h" #include "asset.h" #include "sound_asset.h" #include "picture_asset.h" +#include "subtitle_asset.h" #include "util.h" #include "metadata.h" #include "exceptions.h" -#include "cpl.h" -#include "pkl.h" +#include "cpl_file.h" +#include "pkl_file.h" #include "asset_map.h" - -using namespace std; -using namespace boost; +#include "reel.h" + +using std::string; +using std::list; +using std::stringstream; +using std::ofstream; +using std::ostream; +using boost::shared_ptr; using namespace libdcp; -DCP::DCP (string directory, string name, ContentKind content_kind, int fps, int length) +DCP::DCP (string directory) : _directory (directory) - , _name (name) - , _content_kind (content_kind) - , _fps (fps) - , _length (length) -{ - -} - -void -DCP::add_sound_asset (vector const & files) -{ - _assets.push_back (shared_ptr (new SoundAsset (files, _directory, "audio.mxf", &Progress, _fps, _length))); -} - -void -DCP::add_sound_asset (sigc::slot get_path, int channels) { - _assets.push_back (shared_ptr (new SoundAsset (get_path, _directory, "audio.mxf", &Progress, _fps, _length, channels))); -} - -void -DCP::add_picture_asset (vector const & files, int width, int height) -{ - _assets.push_back (shared_ptr (new PictureAsset (files, _directory, "video.mxf", &Progress, _fps, _length, width, height))); -} - -void -DCP::add_picture_asset (sigc::slot get_path, int width, int height) -{ - _assets.push_back (shared_ptr (new PictureAsset (get_path, _directory, "video.mxf", &Progress, _fps, _length, width, height))); + boost::filesystem::create_directories (directory); } void DCP::write_xml () const { - string cpl_uuid = make_uuid (); - string cpl_path = write_cpl (cpl_uuid); - int cpl_length = filesystem::file_size (cpl_path); - string cpl_digest = make_digest (cpl_path, 0); + for (list >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) { + (*i)->write_xml (); + } string pkl_uuid = make_uuid (); - string pkl_path = write_pkl (pkl_uuid, cpl_uuid, cpl_digest, cpl_length); + string pkl_path = write_pkl (pkl_uuid); write_volindex (); - write_assetmap (cpl_uuid, cpl_length, pkl_uuid, filesystem::file_size (pkl_path)); -} - -string -DCP::write_cpl (string cpl_uuid) const -{ - filesystem::path p; - p /= _directory; - stringstream s; - s << cpl_uuid << "_cpl.xml"; - p /= s.str(); - ofstream cpl (p.string().c_str()); - - cpl << "\n" - << "\n" - << " urn:uuid:" << cpl_uuid << "\n" - << " " << _name << "\n" - << " " << Metadata::instance()->issue_date << "\n" - << " " << Metadata::instance()->creator << "\n" - << " " << _name << "\n" - << " " << content_kind_to_string (_content_kind) << "\n" - << " \n" - << " urn:uri:" << cpl_uuid << "_" << Metadata::instance()->issue_date << "\n" - << " " << cpl_uuid << "_" << Metadata::instance()->issue_date << "\n" - << " \n" - << " \n" - << " \n"; - - cpl << " \n" - << " urn:uuid:" << make_uuid() << "\n" - << " \n"; - - for (list >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) { - (*i)->write_to_cpl (cpl); - } - - cpl << " \n" - << " \n" - << " \n" - << "\n"; - - return p.string (); + write_assetmap (pkl_uuid, boost::filesystem::file_size (pkl_path)); } std::string -DCP::write_pkl (string pkl_uuid, string cpl_uuid, string cpl_digest, int cpl_length) const +DCP::write_pkl (string pkl_uuid) const { - filesystem::path p; + assert (!_cpls.empty ()); + + boost::filesystem::path p; p /= _directory; stringstream s; s << pkl_uuid << "_pkl.xml"; @@ -146,22 +85,21 @@ DCP::write_pkl (string pkl_uuid, string cpl_uuid, string cpl_digest, int cpl_len pkl << "\n" << "\n" << " urn:uuid:" << pkl_uuid << "\n" - << " " << _name << "\n" + /* XXX: this is a bit of a hack */ + << " " << _cpls.front()->name() << "\n" << " " << Metadata::instance()->issue_date << "\n" << " " << Metadata::instance()->issuer << "\n" << " " << Metadata::instance()->creator << "\n" << " \n"; - for (list >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) { + list > a = assets (); + for (list >::const_iterator i = a.begin(); i != a.end(); ++i) { (*i)->write_to_pkl (pkl); } - pkl << " \n" - << " urn:uuid:" << cpl_uuid << "\n" - << " " << cpl_digest << "\n" - << " " << cpl_length << "\n" - << " text/xml\n" - << " \n"; + for (list >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) { + (*i)->write_to_pkl (pkl); + } pkl << " \n" << "\n"; @@ -172,7 +110,7 @@ DCP::write_pkl (string pkl_uuid, string cpl_uuid, string cpl_digest, int cpl_len void DCP::write_volindex () const { - filesystem::path p; + boost::filesystem::path p; p /= _directory; p /= "VOLINDEX.xml"; ofstream vi (p.string().c_str()); @@ -184,9 +122,9 @@ DCP::write_volindex () const } void -DCP::write_assetmap (string cpl_uuid, int cpl_length, string pkl_uuid, int pkl_length) const +DCP::write_assetmap (string pkl_uuid, int pkl_length) const { - filesystem::path p; + boost::filesystem::path p; p /= _directory; p /= "ASSETMAP.xml"; ofstream am (p.string().c_str()); @@ -212,20 +150,13 @@ DCP::write_assetmap (string cpl_uuid, int cpl_length, string pkl_uuid, int pkl_l << " \n" << " \n" << " \n"; - - am << " \n" - << " urn:uuid:" << cpl_uuid << "\n" - << " \n" - << " \n" - << " " << cpl_uuid << "_cpl.xml\n" - << " 1\n" - << " 0\n" - << " " << cpl_length << "\n" - << " \n" - << " \n" - << " \n"; - for (list >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) { + for (list >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) { + (*i)->write_to_assetmap (am); + } + + list > a = assets (); + for (list >::const_iterator i = a.begin(); i != a.end(); ++i) { (*i)->write_to_assetmap (am); } @@ -234,209 +165,394 @@ DCP::write_assetmap (string cpl_uuid, int cpl_length, string pkl_uuid, int pkl_l } -DCP::DCP (string directory) - : _directory (directory) +void +DCP::read (bool require_mxfs) { Files files; - scan (files, directory); - if (files.cpl.empty ()) { - throw FileError ("no CPL file found", ""); + shared_ptr asset_map; + try { + boost::filesystem::path p = _directory; + p /= "ASSETMAP"; + if (boost::filesystem::exists (p)) { + asset_map.reset (new AssetMap (p.string ())); + } else { + p = _directory; + p /= "ASSETMAP.xml"; + if (boost::filesystem::exists (p)) { + asset_map.reset (new AssetMap (p.string ())); + } else { + throw DCPReadError ("could not find AssetMap file"); + } + } + + } catch (FileError& e) { + throw FileError ("could not load AssetMap file", files.asset_map); } - if (files.pkl.empty ()) { - throw FileError ("no PKL file found", ""); - } + for (list >::const_iterator i = asset_map->assets.begin(); i != asset_map->assets.end(); ++i) { + if ((*i)->chunks.size() != 1) { + throw XMLError ("unsupported asset chunk count"); + } - if (files.asset_map.empty ()) { - throw FileError ("no AssetMap file found", ""); - } + boost::filesystem::path t = _directory; + t /= (*i)->chunks.front()->path; + + if (boost::algorithm::ends_with (t.string(), ".mxf") || boost::algorithm::ends_with (t.string(), ".ttf")) { + continue; + } - /* Read the XML */ - shared_ptr cpl; - try { - cpl.reset (new CPL (files.cpl)); - } catch (FileError& e) { - throw FileError ("could not load CPL file", files.cpl); + xmlpp::DomParser* p = new xmlpp::DomParser; + try { + p->parse_file (t.string()); + } catch (std::exception& e) { + delete p; + continue; + } + + string const root = p->get_document()->get_root_node()->get_name (); + delete p; + + if (root == "CompositionPlaylist") { + files.cpls.push_back (t.string()); + } else if (root == "PackingList") { + if (files.pkl.empty ()) { + files.pkl = t.string(); + } else { + throw DCPReadError ("duplicate PKLs found"); + } + } + } + + if (files.cpls.empty ()) { + throw FileError ("no CPL files found", ""); } - shared_ptr pkl; - try { - pkl.reset (new PKL (files.pkl)); - } catch (FileError& e) { - throw FileError ("could not load PKL file", files.pkl); + if (files.pkl.empty ()) { + throw FileError ("no PKL file found", ""); } - shared_ptr asset_map; + shared_ptr pkl; try { - asset_map.reset (new AssetMap (files.asset_map)); + pkl.reset (new PKLFile (files.pkl)); } catch (FileError& e) { - throw FileError ("could not load AssetMap file", files.asset_map); + throw FileError ("could not load PKL file", files.pkl); } /* Cross-check */ /* XXX */ - /* Now cherry-pick the required bits into our own data structure */ - - _name = cpl->annotation_text; - _content_kind = cpl->content_kind; - - shared_ptr cpl_assets = cpl->reels.front()->asset_list; - - /* XXX */ - _fps = cpl_assets->main_picture->frame_rate.numerator; - _length = cpl_assets->main_picture->duration; - - string n = cpl_assets->main_picture->annotation_text; - if (n.empty ()) { - n = pkl->asset_from_id(cpl_assets->main_picture->id)->original_file_name; + for (list::iterator i = files.cpls.begin(); i != files.cpls.end(); ++i) { + _cpls.push_back (shared_ptr (new CPL (_directory, *i, asset_map, require_mxfs))); } +} - _assets.push_back (shared_ptr ( - new PictureAsset ( - _directory, - n, - _fps, - _length - ) - )); +bool +DCP::equals (DCP const & other, EqualityOptions opt, list& notes) const +{ + if (_cpls.size() != other._cpls.size()) { + notes.push_back ("CPL counts differ"); + return false; + } - if (cpl_assets->main_sound) { + list >::const_iterator a = _cpls.begin (); + list >::const_iterator b = other._cpls.begin (); - n = cpl_assets->main_sound->annotation_text; - if (n.empty ()) { - n = pkl->asset_from_id(cpl_assets->main_sound->id)->original_file_name; + while (a != _cpls.end ()) { + if (!(*a)->equals (*b->get(), opt, notes)) { + return false; } - - _assets.push_back (shared_ptr ( - new SoundAsset ( - _directory, - n, - _fps, - _length - ) - )); + ++a; + ++b; } + + return true; } void -DCP::scan (Files& files, string directory) const +DCP::add_cpl (shared_ptr cpl) { - for (filesystem::directory_iterator i = filesystem::directory_iterator(directory); i != filesystem::directory_iterator(); ++i) { - - string const t = i->path().string (); + _cpls.push_back (cpl); +} - if (filesystem::is_directory (*i)) { - scan (files, t); - continue; - } +class AssetComparator +{ +public: + bool operator() (shared_ptr a, shared_ptr b) { + return a->uuid() < b->uuid(); + } +}; - if (ends_with (t, ".mxf")) { - continue; - } +list > +DCP::assets () const +{ + list > a; + for (list >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) { + list > t = (*i)->assets (); + a.merge (t); + } - xmlpp::DomParser* p = new xmlpp::DomParser; + a.sort (); + a.unique (); + return a; +} - try { - p->parse_file (t); - } catch (std::exception& e) { - delete p; - continue; - } - - if (!p) { - delete p; - continue; - } +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 (); +} - string const root = p->get_document()->get_root_node()->get_name (); - delete p; +/** 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 asset_map, bool require_mxfs) + : _directory (directory) + , _content_kind (FEATURE) + , _length (0) + , _fps (0) +{ + /* Read the XML */ + shared_ptr 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 >::iterator i = cpl->reels.begin(); i != cpl->reels.end(); ++i) { + + shared_ptr p; + + if ((*i)->asset_list->main_picture) { + p = (*i)->asset_list->main_picture; + } else { + p = (*i)->asset_list->main_stereoscopic_picture; + } - if (root == "CompositionPlaylist") { - if (files.cpl.empty ()) { - files.cpl = t; - } else { - throw DCPReadError ("duplicate CPLs found"); + _fps = p->edit_rate.numerator; + _length += p->duration; + + shared_ptr picture; + shared_ptr sound; + shared_ptr 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 ((*i)->asset_list->main_picture->entry_point); + } catch (MXFFileError) { + if (require_mxfs) { + throw; + } } - } else if (root == "PackingList") { - if (files.pkl.empty ()) { - files.pkl = t; - } else { - throw DCPReadError ("duplicate PKLs found"); + + } 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); + + } catch (MXFFileError) { + if (require_mxfs) { + throw; + } } - } else if (root == "AssetMap") { - if (files.asset_map.empty ()) { - files.asset_map = t; - } else { - throw DCPReadError ("duplicate AssetMaps found"); + + } + + 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); + } catch (MXFFileError) { + if (require_mxfs) { + throw; + } } - files.asset_map = t; } + + 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 (new Reel (picture, sound, subtitle))); } } +void +CPL::add_reel (shared_ptr reel) +{ + _reels.push_back (reel); +} -list -DCP::equals (DCP const & other, EqualityOptions opt) const +void +CPL::write_xml () const { - list notes; + boost::filesystem::path p; + p /= _directory; + stringstream s; + s << _uuid << "_cpl.xml"; + p /= s.str(); + ofstream os (p.string().c_str()); - if (opt.flags & LIBDCP_METADATA) { - if (_name != other._name) { - notes.push_back ("names differ"); - } - if (_content_kind != other._content_kind) { - notes.push_back ("content kinds differ"); + os << "\n" + << "\n" + << " urn:uuid:" << _uuid << "\n" + << " " << _name << "\n" + << " " << Metadata::instance()->issue_date << "\n" + << " " << Metadata::instance()->creator << "\n" + << " " << _name << "\n" + << " " << content_kind_to_string (_content_kind) << "\n" + << " \n" + << " urn:uri:" << _uuid << "_" << Metadata::instance()->issue_date << "\n" + << " " << _uuid << "_" << Metadata::instance()->issue_date << "\n" + << " \n" + << " \n" + << " \n"; + + for (list >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) { + (*i)->write_to_cpl (os); + } + + os << " \n" + << "\n"; + + os.close (); + + _digest = make_digest (p.string ()); + _length = boost::filesystem::file_size (p.string ()); +} + +void +CPL::write_to_pkl (ostream& s) const +{ + s << " \n" + << " urn:uuid:" << _uuid << "\n" + << " " << _digest << "\n" + << " " << _length << "\n" + << " text/xml\n" + << " \n"; +} + +list > +CPL::assets () const +{ + list > a; + for (list >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) { + if ((*i)->main_picture ()) { + a.push_back ((*i)->main_picture ()); } - if (_fps != other._fps) { - notes.push_back ("frames per second differ"); + if ((*i)->main_sound ()) { + a.push_back ((*i)->main_sound ()); } - if (_length != other._length) { - notes.push_back ("lengths differ"); + if ((*i)->main_subtitle ()) { + a.push_back ((*i)->main_subtitle ()); } } - if (_assets.size() != other._assets.size()) { - notes.push_back ("asset counts differ"); - } + return a; +} + +void +CPL::write_to_assetmap (ostream& s) const +{ + s << " \n" + << " urn:uuid:" << _uuid << "\n" + << " \n" + << " \n" + << " " << _uuid << "_cpl.xml\n" + << " 1\n" + << " 0\n" + << " " << _length << "\n" + << " \n" + << " \n" + << " \n"; +} - list >::const_iterator a = _assets.begin (); - list >::const_iterator b = other._assets.begin (); - while (a != _assets.end ()) { - list n = (*a)->equals (*b, opt); - notes.merge (n); - ++a; - ++b; + +bool +CPL::equals (CPL const & other, EqualityOptions opt, list& notes) const +{ + if (_name != other._name) { + notes.push_back ("names differ"); + return false; } - return notes; -} + if (_content_kind != other._content_kind) { + notes.push_back ("content kinds differ"); + return false; + } -shared_ptr -DCP::picture_asset () const -{ - for (list >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) { - shared_ptr p = dynamic_pointer_cast (*i); - if (p) { - return p; - } + if (_fps != other._fps) { + notes.push_back ("frames per second differ"); + return false; } - return shared_ptr (); -} + if (_length != other._length) { + notes.push_back ("lengths differ"); + return false; + } -shared_ptr -DCP::sound_asset () const -{ - for (list >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) { - shared_ptr s = dynamic_pointer_cast (*i); - if (s) { - return s; + if (_reels.size() != other._reels.size()) { + notes.push_back ("reel counts differ"); + return false; + } + + list >::const_iterator a = _reels.begin (); + list >::const_iterator b = other._reels.begin (); + + while (a != _reels.end ()) { + if (!(*a)->equals (*b, opt, notes)) { + return false; } + ++a; + ++b; } - return shared_ptr (); + return true; }