/* Copyright (C) 2012 Carl Hetherington 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/dcp.cc * @brief A class to create a DCP. */ #include #include #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 "asset_map.h" #include "reel.h" using namespace std; using namespace boost; using namespace libdcp; DCP::DCP (string directory, string name, ContentKind content_kind, int fps, int length) : _directory (directory) , _name (name) , _content_kind (content_kind) , _fps (fps) , _length (length) { filesystem::create_directories (directory); } void DCP::add_reel (shared_ptr reel) { _reels.push_back (reel); } 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); string pkl_uuid = make_uuid (); string pkl_path = write_pkl (pkl_uuid, cpl_uuid, cpl_digest, cpl_length); 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"; for (list >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) { (*i)->write_to_cpl (cpl); } cpl << " \n" << " \n" << " \n" << "\n"; return p.string (); } std::string DCP::write_pkl (string pkl_uuid, string cpl_uuid, string cpl_digest, int cpl_length) const { filesystem::path p; p /= _directory; stringstream s; s << pkl_uuid << "_pkl.xml"; p /= s.str(); ofstream pkl (p.string().c_str()); pkl << "\n" << "\n" << " urn:uuid:" << pkl_uuid << "\n" << " " << _name << "\n" << " " << Metadata::instance()->issue_date << "\n" << " " << Metadata::instance()->issuer << "\n" << " " << Metadata::instance()->creator << "\n" << " \n"; for (list >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) { (*i)->write_to_pkl (pkl); } pkl << " \n" << " urn:uuid:" << cpl_uuid << "\n" << " " << cpl_digest << "\n" << " " << cpl_length << "\n" << " text/xml\n" << " \n"; pkl << " \n" << "\n"; return p.string (); } void DCP::write_volindex () const { filesystem::path p; p /= _directory; p /= "VOLINDEX.xml"; ofstream vi (p.string().c_str()); vi << "\n" << "\n" << " 1\n" << "\n"; } void DCP::write_assetmap (string cpl_uuid, int cpl_length, string pkl_uuid, int pkl_length) const { filesystem::path p; p /= _directory; p /= "ASSETMAP.xml"; ofstream am (p.string().c_str()); am << "\n" << "\n" << " urn:uuid:" << make_uuid() << "\n" << " " << Metadata::instance()->creator << "\n" << " 1\n" << " " << Metadata::instance()->issue_date << "\n" << " " << Metadata::instance()->issuer << "\n" << " \n"; am << " \n" << " urn:uuid:" << pkl_uuid << "\n" << " true\n" << " \n" << " \n" << " " << pkl_uuid << "_pkl.xml\n" << " 1\n" << " 0\n" << " " << pkl_length << "\n" << " \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 = _reels.begin(); i != _reels.end(); ++i) { (*i)->write_to_assetmap (am); } am << " \n" << "\n"; } DCP::DCP (string directory) : _directory (directory) { Files files; scan (files, directory); if (files.cpl.empty ()) { throw FileError ("no CPL file found", ""); } if (files.pkl.empty ()) { throw FileError ("no PKL file found", ""); } if (files.asset_map.empty ()) { throw FileError ("no AssetMap file found", ""); } /* 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); } shared_ptr pkl; try { pkl.reset (new PKL (files.pkl)); } catch (FileError& e) { throw FileError ("could not load PKL file", files.pkl); } shared_ptr asset_map; try { asset_map.reset (new AssetMap (files.asset_map)); } catch (FileError& e) { throw FileError ("could not load AssetMap file", files.asset_map); } /* Cross-check */ /* XXX */ /* Now cherry-pick the required bits into our own data structure */ _name = cpl->annotation_text; _content_kind = cpl->content_kind; _length = 0; _fps = 0; 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; } assert (_fps == 0 || _fps == p->edit_rate.numerator); _fps = p->edit_rate.numerator; _length += p->duration; shared_ptr picture; shared_ptr sound; shared_ptr subtitle; if ((*i)->asset_list->main_picture) { string n = pkl->asset_from_id (p->id)->original_file_name; if (n.empty ()) { n = p->annotation_text; } picture.reset (new MonoPictureAsset ( _directory, n, _fps, (*i)->asset_list->main_picture->entry_point, (*i)->asset_list->main_picture->duration ) ); } else if ((*i)->asset_list->main_stereoscopic_picture) { string n = pkl->asset_from_id (p->id)->original_file_name; if (n.empty ()) { n = p->annotation_text; } picture.reset (new StereoPictureAsset ( _directory, n, _fps, (*i)->asset_list->main_stereoscopic_picture->entry_point, (*i)->asset_list->main_stereoscopic_picture->duration ) ); } if ((*i)->asset_list->main_sound) { string n = pkl->asset_from_id ((*i)->asset_list->main_sound->id)->original_file_name; if (n.empty ()) { n = (*i)->asset_list->main_sound->annotation_text; } sound.reset (new SoundAsset ( _directory, n, _fps, (*i)->asset_list->main_sound->entry_point, (*i)->asset_list->main_sound->duration ) ); } if ((*i)->asset_list->main_subtitle) { string n = pkl->asset_from_id ((*i)->asset_list->main_subtitle->id)->original_file_name; if (n.empty ()) { n = (*i)->asset_list->main_subtitle->annotation_text; } subtitle.reset (new SubtitleAsset ( _directory, n ) ); } _reels.push_back (shared_ptr (new Reel (picture, sound, subtitle))); } } void DCP::scan (Files& files, string directory) const { for (filesystem::directory_iterator i = filesystem::directory_iterator(directory); i != filesystem::directory_iterator(); ++i) { string const t = i->path().string (); if (filesystem::is_directory (*i)) { scan (files, t); continue; } if (ends_with (t, ".mxf") || ends_with (t, ".ttf")) { continue; } xmlpp::DomParser* p = new xmlpp::DomParser; try { p->parse_file (t); } catch (std::exception& e) { delete p; continue; } if (!p) { delete p; continue; } string const root = p->get_document()->get_root_node()->get_name (); delete p; if (root == "CompositionPlaylist") { if (files.cpl.empty ()) { files.cpl = t; } else { throw DCPReadError ("duplicate CPLs found"); } } else if (root == "PackingList") { if (files.pkl.empty ()) { files.pkl = t; } else { throw DCPReadError ("duplicate PKLs found"); } } else if (root == "AssetMap") { if (files.asset_map.empty ()) { files.asset_map = t; } else { throw DCPReadError ("duplicate AssetMaps found"); } files.asset_map = t; } else if (root == "DCSubtitle") { files.subtitles.push_back (t); } } } list DCP::equals (DCP const & other, EqualityOptions opt) const { list notes; 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"); } if (_fps != other._fps) { notes.push_back ("frames per second differ"); } if (_length != other._length) { notes.push_back ("lengths differ"); } } if (_reels.size() != other._reels.size()) { notes.push_back ("reel counts differ"); } list >::const_iterator a = _reels.begin (); list >::const_iterator b = other._reels.begin (); while (a != _reels.end ()) { list n = (*a)->equals (*b, opt); notes.merge (n); ++a; ++b; } return notes; }