Template Package and bring some stuff up to there and PackageBase.
[libdcp.git] / src / package_base.cc
1 /*
2     Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include "package_base.h"
21 #include "compose.hpp"
22 #include "exceptions.h"
23 #include "util.h"
24 #include "dcp_assert.h"
25 #include "font_asset.h"
26 #include "raw_convert.h"
27 #include <libcxml/cxml.h>
28 #include <libxml++/libxml++.h>
29 #include <boost/foreach.hpp>
30 #include <boost/algorithm/string.hpp>
31
32 using std::list;
33 using std::map;
34 using std::string;
35 using boost::shared_ptr;
36 using boost::algorithm::starts_with;
37 using namespace dcp;
38
39 PackageBase::PackageBase (boost::filesystem::path directory)
40 {
41         if (!boost::filesystem::exists (directory)) {
42                 boost::filesystem::create_directories (directory);
43         }
44
45         _directory = boost::filesystem::canonical (directory);
46 }
47
48 list<shared_ptr<Asset> >
49 PackageBase::read_assetmap (bool keep_going, ReadErrors* errors) const
50 {
51         boost::filesystem::path asset_map_file;
52         if (boost::filesystem::exists (_directory / "ASSETMAP")) {
53                 asset_map_file = _directory / "ASSETMAP";
54         } else if (boost::filesystem::exists (_directory / "ASSETMAP.xml")) {
55                 asset_map_file = _directory / "ASSETMAP.xml";
56         } else {
57                 boost::throw_exception (PackageReadError (String::compose ("could not find AssetMap file in `%1'", _directory.string())));
58         }
59
60         cxml::Document asset_map ("AssetMap");
61         asset_map.read_file (asset_map_file);
62         list<shared_ptr<cxml::Node> > asset_nodes = asset_map.node_child("AssetList")->node_children ("Asset");
63         map<string, boost::filesystem::path> paths;
64         BOOST_FOREACH (shared_ptr<cxml::Node> i, asset_nodes) {
65                 if (i->node_child("ChunkList")->node_children("Chunk").size() != 1) {
66                         boost::throw_exception (XMLError ("unsupported asset chunk count"));
67                 }
68                 string p = i->node_child("ChunkList")->node_child("Chunk")->string_child ("Path");
69                 if (starts_with (p, "file://")) {
70                         p = p.substr (7);
71                 }
72                 paths.insert (make_pair (remove_urn_uuid (i->string_child ("Id")), p));
73         }
74
75         /* Read all the assets from the asset map */
76         /* XXX: I think we should be looking at the PKL here to decide type, not
77            the extension of the file.
78         */
79
80         list<shared_ptr<Asset> > assets;
81
82         for (map<string, boost::filesystem::path>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
83                 boost::filesystem::path path = _directory / i->second;
84
85                 if (!boost::filesystem::exists (path)) {
86                         if (keep_going) {
87                                 if (errors) {
88                                         errors->push_back (shared_ptr<MissingAssetError> (new MissingAssetError (path)));
89                                 }
90                         } else {
91                                 throw MissingAssetError (path);
92                         }
93                         continue;
94                 }
95
96                 if (boost::filesystem::extension(path) == ".xml") {
97                         xmlpp::DomParser* p = new xmlpp::DomParser;
98                         try {
99                                 p->parse_file (path.string());
100                         } catch (std::exception& e) {
101                                 delete p;
102                                 continue;
103                         }
104
105                         string const root = p->get_document()->get_root_node()->get_name ();
106                         delete p;
107
108                         shared_ptr<Asset> a = xml_asset_factory (path, root);
109                         if (a) {
110                                 assets.push_back (a);
111                         }
112                 } else if (boost::filesystem::extension (path) == ".mxf") {
113                         assets.push_back (mxf_asset_factory (path));
114                 } else if (boost::filesystem::extension (path) == ".ttf") {
115                         assets.push_back (shared_ptr<FontAsset> (new FontAsset (i->first, path)));
116                 }
117         }
118
119         return assets;
120 }
121
122
123 /** Write the VOLINDEX file.
124  *  @param standard DCP standard to use (INTEROP or SMPTE)
125  */
126 void
127 PackageBase::write_volindex (Standard standard) const
128 {
129         boost::filesystem::path p = _directory;
130         switch (standard) {
131         case DCP_INTEROP:
132                 p /= "VOLINDEX";
133                 break;
134         case DCP_SMPTE:
135                 p /= "VOLINDEX.xml";
136                 break;
137         case IMP:
138                 /* XXX */
139                 DCP_ASSERT (false);
140                 break;
141         }
142
143         xmlpp::Document doc;
144         xmlpp::Element* root = 0;
145
146         switch (standard) {
147         case DCP_INTEROP:
148                 root = doc.create_root_node ("VolumeIndex", "http://www.digicine.com/PROTO-ASDCP-AM-20040311#");
149                 break;
150         case DCP_SMPTE:
151         case IMP:
152                 root = doc.create_root_node ("VolumeIndex", "http://www.smpte-ra.org/schemas/429-9/2007/AM");
153                 break;
154         }
155
156         root->add_child("Index")->add_child_text ("1");
157         doc.write_to_file (p.string (), "UTF-8");
158 }
159
160 void
161 PackageBase::write_assetmap (Standard standard, string pkl_uuid, int pkl_length, dcp::XMLMetadata metadata) const
162 {
163         boost::filesystem::path p = _directory;
164
165         switch (standard) {
166         case DCP_INTEROP:
167                 p /= "ASSETMAP";
168                 break;
169         case DCP_SMPTE:
170         case IMP:
171                 p /= "ASSETMAP.xml";
172                 break;
173         }
174
175         xmlpp::Document doc;
176         xmlpp::Element* root = 0;
177
178         switch (standard) {
179         case DCP_INTEROP:
180                 root = doc.create_root_node ("AssetMap", "http://www.digicine.com/PROTO-ASDCP-AM-20040311#");
181                 break;
182         case DCP_SMPTE:
183         case IMP:
184                 root = doc.create_root_node ("AssetMap", "http://www.smpte-ra.org/schemas/429-9/2007/AM");
185                 break;
186         }
187
188         root->add_child("Id")->add_child_text ("urn:uuid:" + make_uuid());
189         root->add_child("AnnotationText")->add_child_text ("Created by " + metadata.creator);
190
191         switch (standard) {
192         case DCP_INTEROP:
193                 root->add_child("VolumeCount")->add_child_text ("1");
194                 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
195                 root->add_child("Issuer")->add_child_text (metadata.issuer);
196                 root->add_child("Creator")->add_child_text (metadata.creator);
197                 break;
198         case DCP_SMPTE:
199         case IMP:
200                 root->add_child("Creator")->add_child_text (metadata.creator);
201                 root->add_child("VolumeCount")->add_child_text ("1");
202                 root->add_child("IssueDate")->add_child_text (metadata.issue_date);
203                 root->add_child("Issuer")->add_child_text (metadata.issuer);
204                 break;
205         }
206
207         xmlpp::Node* asset_list = root->add_child ("AssetList");
208
209         xmlpp::Node* asset = asset_list->add_child ("Asset");
210         asset->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid);
211         asset->add_child("PackingList")->add_child_text ("true");
212         xmlpp::Node* chunk_list = asset->add_child ("ChunkList");
213         xmlpp::Node* chunk = chunk_list->add_child ("Chunk");
214         chunk->add_child("Path")->add_child_text ("pkl_" + pkl_uuid + ".xml");
215         chunk->add_child("VolumeIndex")->add_child_text ("1");
216         chunk->add_child("Offset")->add_child_text ("0");
217         chunk->add_child("Length")->add_child_text (raw_convert<string> (pkl_length));
218
219         BOOST_FOREACH (shared_ptr<dcp::Asset> i, assets ()) {
220                 i->write_to_assetmap (asset_list, _directory);
221         }
222
223         /* This must not be the _formatted version otherwise signature digests will be wrong */
224         doc.write_to_file (p.string (), "UTF-8");
225 }