libdcp
combine.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2020-2021 Carl Hetherington <cth@carlh.net>
3 
4  This file is part of libdcp.
5 
6  libdcp is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10 
11  libdcp is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with libdcp. If not, see <http://www.gnu.org/licenses/>.
18 
19  In addition, as a special exception, the copyright holders give
20  permission to link the code of portions of this program with the
21  OpenSSL library under certain conditions as described in each
22  individual source file, and distribute linked combinations
23  including the two.
24 
25  You must obey the GNU General Public License in all respects
26  for all of the code used other than OpenSSL. If you modify
27  file(s) with this exception, you may extend this exception to your
28  version of the file(s), but you are not obligated to do so. If you
29  do not wish to do so, delete this exception statement from your
30  version. If you delete this exception statement from all source
31  files in the program, then also delete it here.
32 */
33 
34 
40 #include "asset.h"
41 #include "combine.h"
42 #include "cpl.h"
43 #include "dcp.h"
44 #include "dcp_assert.h"
45 #include "exceptions.h"
46 #include "filesystem.h"
47 #include "font_asset.h"
48 #include "interop_text_asset.h"
49 #include "raw_convert.h"
50 #include <boost/filesystem.hpp>
51 #include <set>
52 #include <string>
53 #include <vector>
54 
55 
56 using std::dynamic_pointer_cast;
57 using std::map;
58 using std::set;
59 using std::shared_ptr;
60 using std::string;
61 using std::vector;
62 using boost::optional;
63 
64 
65 boost::filesystem::path
66 make_unique (boost::filesystem::path path)
67 {
68  if (!dcp::filesystem::exists(path)) {
69  return path;
70  }
71 
72  for (int i = 0; i < 10000; ++i) {
73  boost::filesystem::path p = path.parent_path() / (path.stem().string() + dcp::raw_convert<string>(i) + path.extension().string());
74  if (!dcp::filesystem::exists(p)) {
75  return p;
76  }
77  }
78 
79  DCP_ASSERT (false);
80  return path;
81 }
82 
83 
84 static
85 void
86 create_hard_link_or_copy (boost::filesystem::path from, boost::filesystem::path to)
87 {
88  try {
89  dcp::filesystem::create_hard_link(from, to);
90  } catch (boost::filesystem::filesystem_error& e) {
91  if (e.code() == boost::system::errc::cross_device_link) {
92  dcp::filesystem::copy_file(from, to);
93  } else {
94  throw;
95  }
96  }
97 }
98 
99 
100 void
101 dcp::combine (
102  vector<boost::filesystem::path> inputs,
103  boost::filesystem::path output,
104  string issuer,
105  string creator,
106  string issue_date,
107  string annotation_text,
108  shared_ptr<const CertificateChain> signer
109  )
110 {
111  DCP_ASSERT (!inputs.empty());
112 
113  DCP output_dcp (output);
114  optional<dcp::Standard> standard;
115 
116  for (auto i: inputs) {
117  DCP dcp (i);
118  dcp.read ();
119  if (!standard) {
120  standard = *dcp.standard();
121  } else if (standard != dcp.standard()) {
122  throw CombineError ("Cannot combine Interop and SMPTE DCPs.");
123  }
124  }
125 
126  vector<boost::filesystem::path> paths;
127  vector<shared_ptr<dcp::Asset>> assets;
128 
129  for (auto i: inputs) {
130  DCP dcp (i);
131  dcp.read ();
132 
133  for (auto j: dcp.cpls()) {
134  output_dcp.add (j);
135  }
136 
137  for (auto j: dcp.assets(true)) {
138  if (dynamic_pointer_cast<dcp::CPL>(j)) {
139  continue;
140  }
141 
142  auto sub = dynamic_pointer_cast<dcp::InteropTextAsset>(j);
143  if (sub) {
144  /* Interop fonts are really fiddly. The font files are assets (in the ASSETMAP)
145  * and also linked from the font XML by filename. We have to fix both these things,
146  * and re-write the font XML file since the font URI might have changed if it's a duplicate
147  * with another DCP.
148  */
149  auto fonts = sub->font_filenames ();
150  for (auto const& k: fonts) {
151  sub->set_font_file (k.first, make_unique(output / k.second.filename()));
152  }
153  auto file = sub->file();
154  DCP_ASSERT (file);
155  auto new_path = make_unique(output / file->filename());
156  sub->write (new_path);
157  add_to_container(assets, sub->font_assets());
158  }
159 
160  assets.push_back (j);
161  }
162  }
163 
164  output_dcp.resolve_refs (assets);
165 
166  for (auto i: output_dcp.assets()) {
167  if (!dynamic_pointer_cast<dcp::FontAsset>(i) && !dynamic_pointer_cast<dcp::CPL>(i)) {
168  auto file = i->file();
169  DCP_ASSERT (file);
170  auto new_path = make_unique(output / file->filename());
171  create_hard_link_or_copy (*file, new_path);
172  i->set_file (new_path);
173  }
174  }
175 
176  output_dcp.set_issuer(issuer);
177  output_dcp.set_creator(creator);
178  output_dcp.set_issue_date(issue_date);
179  output_dcp.set_annotation_text(annotation_text);
180 
181  output_dcp.write_xml(signer);
182 }
Asset class.
Method to combine DCPs.
CPL class.
DCP class.
DCP_ASSERT macro.
Exceptions thrown by libdcp.
FontAsset class.
InteropTextAsset class.
Namespace for everything in libdcp.
Definition: array_data.h:50
Methods for conversion to/from string.