2 Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
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.
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.
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/>.
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
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.
36 #include "compose.hpp"
39 #include "decrypted_kdm.h"
40 #include "encrypted_kdm.h"
41 #include "exceptions.h"
42 #include "filesystem.h"
43 #include "interop_subtitle_asset.h"
44 #include "mono_picture_asset.h"
45 #include "picture_asset.h"
47 #include "reel_picture_asset.h"
48 #include "reel_sound_asset.h"
49 #include "reel_subtitle_asset.h"
50 #include "smpte_subtitle_asset.h"
51 #include "sound_asset.h"
52 #include "subtitle_asset.h"
53 #include "subtitle_image.h"
54 #include "subtitle_string.h"
56 #include <boost/filesystem.hpp>
57 #include <boost/algorithm/string.hpp>
66 using std::dynamic_pointer_cast;
72 using std::shared_ptr;
74 using std::stringstream;
76 using boost::optional;
83 cerr << "Syntax: " << n << " [options] [<DCP>] [<CPL>]\n"
84 << " -s, --subtitles list all subtitles\n"
85 << " -p, --picture analyse picture\n"
86 << " -d, --decompress decompress picture when analysing (this is slow)\n"
87 << " -o, --only only output certain pieces of information; see below.\n"
88 << " --kdm KDM to decrypt DCP\n"
89 << " --private-key private key for the certificate that the KDM is targeted at\n"
90 << " --ignore-missing-assets ignore missing asset files\n";
92 cerr << "--only takes a comma-separated list of strings, one or more of:\n"
93 " dcp-path DCP path\n"
94 " cpl-name-id CPL name and ID\n"
95 " picture picture information\n"
96 " sound sound information\n"
97 " subtitle picture information\n"
98 " total-time total DCP time\n";
102 mbits_per_second (int size, Fraction frame_rate)
104 return size * 8 * frame_rate.as_float() / 1e6;
107 #define OUTPUT_DCP_PATH(...) maybe_output(only, "dcp-path", String::compose(__VA_ARGS__));
108 #define OUTPUT_CPL_NAME_ID(...) maybe_output(only, "cpl-name-id", String::compose(__VA_ARGS__));
109 #define OUTPUT_PICTURE(...) maybe_output(only, "picture", String::compose(__VA_ARGS__));
110 #define OUTPUT_PICTURE_NC(x) maybe_output(only, "picture", (x));
111 #define SHOULD_PICTURE should_output(only, "picture")
112 #define OUTPUT_SOUND(...) maybe_output(only, "sound", String::compose(__VA_ARGS__));
113 #define OUTPUT_SOUND_NC(x) maybe_output(only, "sound", (x));
114 #define OUTPUT_SUBTITLE(...) maybe_output(only, "subtitle", String::compose(__VA_ARGS__));
115 #define OUTPUT_SUBTITLE_NC(x) maybe_output(only, "subtitle", (x));
116 #define OUTPUT_TOTAL_TIME(...) maybe_output(only, "total-time", String::compose(__VA_ARGS__));
119 should_output(vector<string> const& only, string t)
121 return only.empty() || find(only.begin(), only.end(), t) != only.end();
125 maybe_output(vector<string> const& only, string t, string s)
127 if (should_output(only, t)) {
134 main_picture (vector<string> const& only, shared_ptr<Reel> reel, bool analyse, bool decompress)
136 shared_ptr<dcp::ReelPictureAsset> mp = reel->main_picture ();
141 OUTPUT_PICTURE(" Picture ID: %1", mp->id());
142 if (mp->entry_point()) {
143 OUTPUT_PICTURE(" entry %1", *mp->entry_point());
145 if (mp->duration()) {
147 " duration %1 (%2) intrinsic %3",
149 dcp::Time(*mp->duration(), mp->frame_rate().as_float(), mp->frame_rate().as_float()).as_string(dcp::Standard::SMPTE),
150 mp->intrinsic_duration()
153 OUTPUT_PICTURE(" intrinsic duration %1", mp->intrinsic_duration());
156 if (mp->asset_ref().resolved()) {
158 OUTPUT_PICTURE("\n Picture: %1x%2\n", mp->asset()->size().width, mp->asset()->size().height);
161 shared_ptr<MonoPictureAsset> ma = dynamic_pointer_cast<MonoPictureAsset>(mp->asset());
163 shared_ptr<MonoPictureAssetReader> reader = ma->start_read ();
164 pair<int, int> j2k_size_range (INT_MAX, 0);
165 for (int64_t i = 0; i < ma->intrinsic_duration(); ++i) {
166 shared_ptr<const MonoPictureFrame> frame = reader->get_frame (i);
167 if (SHOULD_PICTURE) {
168 printf("Frame %" PRId64 " J2K size %7d", i, frame->size());
170 j2k_size_range.first = min(j2k_size_range.first, frame->size());
171 j2k_size_range.second = max(j2k_size_range.second, frame->size());
176 if (SHOULD_PICTURE) {
177 printf(" decrypted OK");
179 } catch (exception& e) {
180 if (SHOULD_PICTURE) {
181 printf(" decryption FAILED");
186 if (SHOULD_PICTURE) {
191 if (SHOULD_PICTURE) {
193 "J2K size ranges from %d (%.1f Mbit/s) to %d (%.1f Mbit/s)\n",
194 j2k_size_range.first, mbits_per_second(j2k_size_range.first, ma->frame_rate()),
195 j2k_size_range.second, mbits_per_second(j2k_size_range.second, ma->frame_rate())
200 OUTPUT_PICTURE_NC(" - not present in this DCP.\n");
204 mp->duration().get_value_or(mp->intrinsic_duration()),
205 mp->frame_rate().as_float(),
206 mp->frame_rate().as_float()
212 main_sound (vector<string> const& only, shared_ptr<Reel> reel)
214 shared_ptr<dcp::ReelSoundAsset> ms = reel->main_sound ();
219 OUTPUT_SOUND(" Sound ID: %1", ms->id());
220 if (ms->entry_point()) {
221 OUTPUT_SOUND(" entry %1", *ms->entry_point());
223 if (ms->duration()) {
224 OUTPUT_SOUND(" duration %1 intrinsic %2", *ms->duration(), ms->intrinsic_duration());
226 OUTPUT_SOUND(" intrinsic duration %1", ms->intrinsic_duration());
229 if (ms->asset_ref().resolved()) {
232 "\n Sound: %1 channels at %2Hz\n",
233 ms->asset()->channels(),
234 ms->asset()->sampling_rate()
238 OUTPUT_SOUND_NC(" - not present in this DCP.\n");
244 main_subtitle (vector<string> const& only, shared_ptr<Reel> reel, bool list_subtitles)
246 shared_ptr<dcp::ReelSubtitleAsset> ms = reel->main_subtitle ();
251 OUTPUT_SUBTITLE(" Subtitle ID: %1", ms->id());
253 if (ms->asset_ref().resolved()) {
254 auto subs = ms->asset()->subtitles ();
255 OUTPUT_SUBTITLE("\n Subtitle: %1 subtitles", subs.size());
256 shared_ptr<InteropSubtitleAsset> iop = dynamic_pointer_cast<InteropSubtitleAsset> (ms->asset());
258 OUTPUT_SUBTITLE(" in %1\n", iop->language());
260 shared_ptr<SMPTESubtitleAsset> smpte = dynamic_pointer_cast<SMPTESubtitleAsset> (ms->asset());
261 if (smpte && smpte->language ()) {
262 OUTPUT_SUBTITLE(" in %1\n", smpte->language().get());
264 if (list_subtitles) {
266 auto ks = dynamic_pointer_cast<const SubtitleString>(k);
270 OUTPUT_SUBTITLE("%1\n", s.str());
272 auto is = dynamic_pointer_cast<const SubtitleImage>(k);
276 OUTPUT_SUBTITLE("%1\n", s.str());
281 OUTPUT_SUBTITLE_NC(" - not present in this DCP.\n");
287 main (int argc, char* argv[])
291 bool subtitles = false;
292 bool picture = false;
293 bool decompress = false;
294 bool ignore_missing_assets = false;
295 optional<boost::filesystem::path> kdm;
296 optional<boost::filesystem::path> private_key;
297 optional<string> only_string;
299 int option_index = 0;
301 static struct option long_options[] = {
302 { "version", no_argument, 0, 'v' },
303 { "help", no_argument, 0, 'h' },
304 { "subtitles", no_argument, 0, 's' },
305 { "picture", no_argument, 0, 'p' },
306 { "decompress", no_argument, 0, 'd' },
307 { "only", required_argument, 0, 'o' },
308 { "ignore-missing-assets", no_argument, 0, 'A' },
309 { "kdm", required_argument, 0, 'B' },
310 { "private-key", required_argument, 0, 'C' },
314 int c = getopt_long (argc, argv, "vhspdo:AB:C:", long_options, &option_index);
322 cout << "libdcp version " << dcp::version << "\n";
337 only_string = optarg;
340 ignore_missing_assets = true;
346 private_key = optarg;
351 if (argc <= optind || argc > (optind + 1)) {
356 if (!filesystem::exists(argv[optind])) {
357 cerr << argv[0] << ": DCP or CPL " << argv[optind] << " not found.\n";
363 only = boost::split(only, *only_string, boost::is_any_of(","));
366 vector<shared_ptr<CPL>> cpls;
367 if (boost::filesystem::is_directory(argv[optind])) {
369 vector<dcp::VerificationNote> notes;
371 dcp = new DCP (argv[optind]);
373 if (kdm && private_key) {
374 dcp->add(DecryptedKDM(EncryptedKDM(file_to_string(*kdm)), file_to_string(*private_key)));
376 } catch (FileError& e) {
377 cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
379 } catch (ReadError& e) {
380 cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
382 } catch (KDMDecryptionError& e) {
383 cerr << e.what() << "\n";
387 OUTPUT_DCP_PATH("DCP: %1\n", boost::filesystem::path(argv[optind]).string());
389 dcp::filter_notes (notes, ignore_missing_assets);
390 for (auto i: notes) {
391 cerr << "Error: " << note_to_string(i) << "\n";
396 cpls.push_back (shared_ptr<CPL>(new CPL(boost::filesystem::path(argv[optind]))));
397 ignore_missing_assets = true;
400 dcp::Time total_time;
403 OUTPUT_CPL_NAME_ID(" CPL: %1 %2\n", i->annotation_text().get_value_or(""), i->id());
406 for (auto j: i->reels()) {
407 if (should_output(only, "picture") || should_output(only, "sound") || should_output(only, "subtitle")) {
408 cout << " Reel " << R << "\n";
412 total_time += main_picture(only, j, picture, decompress);
413 } catch (UnresolvedRefError& e) {
414 if (!ignore_missing_assets) {
415 cerr << e.what() << " (for main picture)\n";
421 } catch (UnresolvedRefError& e) {
422 if (!ignore_missing_assets) {
423 cerr << e.what() << " (for main sound)\n";
428 main_subtitle (only, j, subtitles);
429 } catch (UnresolvedRefError& e) {
430 if (!ignore_missing_assets) {
431 cerr << e.what() << " (for main subtitle)\n";
439 OUTPUT_TOTAL_TIME("Total: %1\n", total_time.as_string(dcp::Standard::SMPTE));