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.
35 #include "exceptions.h"
37 #include "sound_asset.h"
38 #include "picture_asset.h"
39 #include "subtitle_asset.h"
40 #include "reel_picture_asset.h"
41 #include "reel_sound_asset.h"
42 #include "reel_subtitle_asset.h"
43 #include "subtitle_string.h"
44 #include "subtitle_image.h"
45 #include "interop_subtitle_asset.h"
46 #include "smpte_subtitle_asset.h"
47 #include "mono_picture_asset.h"
48 #include "encrypted_kdm.h"
49 #include "decrypted_kdm.h"
52 #include "compose.hpp"
54 #include <boost/filesystem.hpp>
55 #include <boost/algorithm/string.hpp>
70 using std::stringstream;
71 using std::shared_ptr;
72 using std::dynamic_pointer_cast;
73 using boost::optional;
79 cerr << "Syntax: " << n << " [options] [<DCP>] [<CPL>]\n"
80 << " -s, --subtitles list all subtitles\n"
81 << " -p, --picture analyse picture\n"
82 << " -d, --decompress decompress picture when analysing (this is slow)\n"
83 << " -o, --only only output certain pieces of information; see below.\n"
84 << " --kdm KDM to decrypt DCP\n"
85 << " --private-key private key for the certificate that the KDM is targeted at\n"
86 << " --ignore-missing-assets ignore missing asset files\n";
88 cerr << "--only takes a comma-separated list of strings, one or more of:\n"
89 " dcp-path DCP path\n"
90 " cpl-name-id CPL name and ID\n"
91 " picture picture information\n"
92 " sound sound information\n"
93 " subtitle picture information\n"
94 " total-time total DCP time\n";
98 mbits_per_second (int size, Fraction frame_rate)
100 return size * 8 * frame_rate.as_float() / 1e6;
103 #define OUTPUT_DCP_PATH(...) maybe_output(only, "dcp-path", String::compose(__VA_ARGS__));
104 #define OUTPUT_CPL_NAME_ID(...) maybe_output(only, "cpl-name-id", String::compose(__VA_ARGS__));
105 #define OUTPUT_PICTURE(...) maybe_output(only, "picture", String::compose(__VA_ARGS__));
106 #define OUTPUT_PICTURE_NC(x) maybe_output(only, "picture", (x));
107 #define SHOULD_PICTURE should_output(only, "picture")
108 #define OUTPUT_SOUND(...) maybe_output(only, "sound", String::compose(__VA_ARGS__));
109 #define OUTPUT_SOUND_NC(x) maybe_output(only, "sound", (x));
110 #define OUTPUT_SUBTITLE(...) maybe_output(only, "subtitle", String::compose(__VA_ARGS__));
111 #define OUTPUT_SUBTITLE_NC(x) maybe_output(only, "subtitle", (x));
112 #define OUTPUT_TOTAL_TIME(...) maybe_output(only, "total-time", String::compose(__VA_ARGS__));
115 should_output(vector<string> const& only, string t)
117 return only.empty() || find(only.begin(), only.end(), t) != only.end();
121 maybe_output(vector<string> const& only, string t, string s)
123 if (should_output(only, t)) {
130 main_picture (vector<string> const& only, shared_ptr<Reel> reel, bool analyse, bool decompress)
132 shared_ptr<dcp::ReelPictureAsset> mp = reel->main_picture ();
137 OUTPUT_PICTURE(" Picture ID: %1", mp->id());
138 if (mp->entry_point()) {
139 OUTPUT_PICTURE(" entry %1", *mp->entry_point());
141 if (mp->duration()) {
143 " duration %1 (%2) intrinsic %3",
145 dcp::Time(*mp->duration(), mp->frame_rate().as_float(), mp->frame_rate().as_float()).as_string(dcp::Standard::SMPTE),
146 mp->intrinsic_duration()
149 OUTPUT_PICTURE(" intrinsic duration %1", mp->intrinsic_duration());
152 if (mp->asset_ref().resolved()) {
154 OUTPUT_PICTURE("\n Picture: %1x%2\n", mp->asset()->size().width, mp->asset()->size().height);
157 shared_ptr<MonoPictureAsset> ma = dynamic_pointer_cast<MonoPictureAsset>(mp->asset());
159 shared_ptr<MonoPictureAssetReader> reader = ma->start_read ();
160 pair<int, int> j2k_size_range (INT_MAX, 0);
161 for (int64_t i = 0; i < ma->intrinsic_duration(); ++i) {
162 shared_ptr<const MonoPictureFrame> frame = reader->get_frame (i);
163 if (SHOULD_PICTURE) {
164 printf("Frame %" PRId64 " J2K size %7d", i, frame->size());
166 j2k_size_range.first = min(j2k_size_range.first, frame->size());
167 j2k_size_range.second = max(j2k_size_range.second, frame->size());
172 if (SHOULD_PICTURE) {
173 printf(" decrypted OK");
175 } catch (exception& e) {
176 if (SHOULD_PICTURE) {
177 printf(" decryption FAILED");
182 if (SHOULD_PICTURE) {
187 if (SHOULD_PICTURE) {
189 "J2K size ranges from %d (%.1f Mbit/s) to %d (%.1f Mbit/s)\n",
190 j2k_size_range.first, mbits_per_second(j2k_size_range.first, ma->frame_rate()),
191 j2k_size_range.second, mbits_per_second(j2k_size_range.second, ma->frame_rate())
196 OUTPUT_PICTURE_NC(" - not present in this DCP.\n");
200 mp->duration().get_value_or(mp->intrinsic_duration()),
201 mp->frame_rate().as_float(),
202 mp->frame_rate().as_float()
208 main_sound (vector<string> const& only, shared_ptr<Reel> reel)
210 shared_ptr<dcp::ReelSoundAsset> ms = reel->main_sound ();
215 OUTPUT_SOUND(" Sound ID: %1", ms->id());
216 if (ms->entry_point()) {
217 OUTPUT_SOUND(" entry %1", *ms->entry_point());
219 if (ms->duration()) {
220 OUTPUT_SOUND(" duration %1 intrinsic %2", *ms->duration(), ms->intrinsic_duration());
222 OUTPUT_SOUND(" intrinsic duration %1", ms->intrinsic_duration());
225 if (ms->asset_ref().resolved()) {
228 "\n Sound: %1 channels at %2Hz\n",
229 ms->asset()->channels(),
230 ms->asset()->sampling_rate()
234 OUTPUT_SOUND_NC(" - not present in this DCP.\n");
240 main_subtitle (vector<string> const& only, shared_ptr<Reel> reel, bool list_subtitles)
242 shared_ptr<dcp::ReelSubtitleAsset> ms = reel->main_subtitle ();
247 OUTPUT_SUBTITLE(" Subtitle ID: %1", ms->id());
249 if (ms->asset_ref().resolved()) {
250 auto subs = ms->asset()->subtitles ();
251 OUTPUT_SUBTITLE("\n Subtitle: %1 subtitles", subs.size());
252 shared_ptr<InteropSubtitleAsset> iop = dynamic_pointer_cast<InteropSubtitleAsset> (ms->asset());
254 OUTPUT_SUBTITLE(" in %1\n", iop->language());
256 shared_ptr<SMPTESubtitleAsset> smpte = dynamic_pointer_cast<SMPTESubtitleAsset> (ms->asset());
257 if (smpte && smpte->language ()) {
258 OUTPUT_SUBTITLE(" in %1\n", smpte->language().get());
260 if (list_subtitles) {
262 auto ks = dynamic_pointer_cast<const SubtitleString>(k);
266 OUTPUT_SUBTITLE("%1\n", s.str());
268 auto is = dynamic_pointer_cast<const SubtitleImage>(k);
272 OUTPUT_SUBTITLE("%1\n", s.str());
277 OUTPUT_SUBTITLE_NC(" - not present in this DCP.\n");
283 main (int argc, char* argv[])
287 bool subtitles = false;
288 bool picture = false;
289 bool decompress = false;
290 bool ignore_missing_assets = false;
291 optional<boost::filesystem::path> kdm;
292 optional<boost::filesystem::path> private_key;
293 optional<string> only_string;
295 int option_index = 0;
297 static struct option long_options[] = {
298 { "version", no_argument, 0, 'v' },
299 { "help", no_argument, 0, 'h' },
300 { "subtitles", no_argument, 0, 's' },
301 { "picture", no_argument, 0, 'p' },
302 { "decompress", no_argument, 0, 'd' },
303 { "only", required_argument, 0, 'o' },
304 { "ignore-missing-assets", no_argument, 0, 'A' },
305 { "kdm", required_argument, 0, 'B' },
306 { "private-key", required_argument, 0, 'C' },
310 int c = getopt_long (argc, argv, "vhspdo:AB:C:", long_options, &option_index);
318 cout << "libdcp version " << LIBDCP_VERSION << "\n";
333 only_string = optarg;
336 ignore_missing_assets = true;
342 private_key = optarg;
347 if (argc <= optind || argc > (optind + 1)) {
352 if (!boost::filesystem::exists (argv[optind])) {
353 cerr << argv[0] << ": DCP or CPL " << argv[optind] << " not found.\n";
359 only = boost::split(only, *only_string, boost::is_any_of(","));
362 vector<shared_ptr<CPL> > cpls;
363 if (boost::filesystem::is_directory(argv[optind])) {
365 vector<dcp::VerificationNote> notes;
367 dcp = new DCP (argv[optind]);
369 if (kdm && private_key) {
370 dcp->add(DecryptedKDM(EncryptedKDM(file_to_string(*kdm)), file_to_string(*private_key)));
372 } catch (FileError& e) {
373 cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
375 } catch (ReadError& e) {
376 cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
378 } catch (KDMDecryptionError& e) {
379 cerr << e.what() << "\n";
383 OUTPUT_DCP_PATH("DCP: %1\n", boost::filesystem::path(argv[optind]).string());
385 dcp::filter_notes (notes, ignore_missing_assets);
386 for (auto i: notes) {
387 cerr << "Error: " << note_to_string(i) << "\n";
392 cpls.push_back (shared_ptr<CPL>(new CPL(boost::filesystem::path(argv[optind]))));
393 ignore_missing_assets = true;
396 dcp::Time total_time;
399 OUTPUT_CPL_NAME_ID(" CPL: %1 %2\n", i->annotation_text().get_value_or(""), i->id());
402 for (auto j: i->reels()) {
403 if (should_output(only, "picture") || should_output(only, "sound") || should_output(only, "subtitle")) {
404 cout << " Reel " << R << "\n";
408 total_time += main_picture(only, j, picture, decompress);
409 } catch (UnresolvedRefError& e) {
410 if (!ignore_missing_assets) {
411 cerr << e.what() << " (for main picture)\n";
417 } catch (UnresolvedRefError& e) {
418 if (!ignore_missing_assets) {
419 cerr << e.what() << " (for main sound)\n";
424 main_subtitle (only, j, subtitles);
425 } catch (UnresolvedRefError& e) {
426 if (!ignore_missing_assets) {
427 cerr << e.what() << " (for main subtitle)\n";
435 OUTPUT_TOTAL_TIME("Total: %1\n", total_time.as_string(dcp::Standard::SMPTE));