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>
63 using std::dynamic_pointer_cast;
66 using std::make_shared;
70 using std::shared_ptr;
72 using std::stringstream;
74 using boost::optional;
80 cerr << "Syntax: " << n << " [options] [<DCP>] [<CPL>]\n"
81 << " -s, --subtitles list all subtitles\n"
82 << " -p, --picture analyse picture\n"
83 << " -f, --frame show details of individual picture frames\n"
84 << " -d, --decompress decompress picture when analysing (this is slow)\n"
85 << " -o, --only only output certain pieces of information; see below.\n"
86 << " --kdm KDM to decrypt DCP\n"
87 << " --private-key private key for the certificate that the KDM is targeted at\n"
88 << " --ignore-missing-assets ignore missing asset files\n";
90 cerr << "--only takes a comma-separated list of strings, one or more of:\n"
91 " dcp-path DCP path\n"
92 " cpl-name-id CPL name and ID\n"
93 " picture picture information\n"
94 " sound sound information\n"
95 " subtitle picture information\n"
96 " total-time total DCP time\n";
100 mbits_per_second (int size, Fraction frame_rate)
102 return size * 8 * frame_rate.as_float() / 1e6;
105 #define OUTPUT_DCP_PATH(...) maybe_output(only, "dcp-path", String::compose(__VA_ARGS__));
106 #define OUTPUT_CPL_NAME_ID(...) maybe_output(only, "cpl-name-id", String::compose(__VA_ARGS__));
107 #define OUTPUT_PICTURE(...) maybe_output(only, "picture", String::compose(__VA_ARGS__));
108 #define OUTPUT_PICTURE_NC(x) maybe_output(only, "picture", (x));
109 #define SHOULD_PICTURE should_output(only, "picture")
110 #define OUTPUT_SOUND(...) maybe_output(only, "sound", String::compose(__VA_ARGS__));
111 #define OUTPUT_SOUND_NC(x) maybe_output(only, "sound", (x));
112 #define OUTPUT_SUBTITLE(...) maybe_output(only, "subtitle", String::compose(__VA_ARGS__));
113 #define OUTPUT_SUBTITLE_NC(x) maybe_output(only, "subtitle", (x));
114 #define OUTPUT_TOTAL_TIME(...) maybe_output(only, "total-time", String::compose(__VA_ARGS__));
117 should_output(vector<string> const& only, string t)
119 return only.empty() || find(only.begin(), only.end(), t) != only.end();
123 maybe_output(vector<string> const& only, string t, string s)
125 if (should_output(only, t)) {
132 main_picture (vector<string> const& only, shared_ptr<Reel> reel, bool analyse, bool frame_detail, bool decompress)
134 auto mp = reel->main_picture ();
139 OUTPUT_PICTURE(" Picture ID: %1", mp->id());
140 if (mp->entry_point()) {
141 OUTPUT_PICTURE(" entry %1", *mp->entry_point());
143 if (mp->duration()) {
145 " duration %1 (%2) intrinsic %3",
147 dcp::Time(*mp->duration(), mp->frame_rate().as_float(), mp->frame_rate().as_float()).as_string(dcp::Standard::SMPTE),
148 mp->intrinsic_duration()
151 OUTPUT_PICTURE(" intrinsic duration %1", mp->intrinsic_duration());
154 if (mp->asset_ref().resolved()) {
156 OUTPUT_PICTURE("\n Picture: %1x%2\n", mp->asset()->size().width, mp->asset()->size().height);
159 auto ma = dynamic_pointer_cast<MonoPictureAsset>(mp->asset());
161 auto reader = ma->start_read ();
162 pair<int, int> j2k_size_range (INT_MAX, 0);
163 for (int64_t i = 0; i < ma->intrinsic_duration(); ++i) {
164 auto frame = reader->get_frame (i);
165 if (SHOULD_PICTURE && frame_detail) {
166 printf("Frame %" PRId64 " J2K size %7d", i, frame->size());
168 j2k_size_range.first = min(j2k_size_range.first, frame->size());
169 j2k_size_range.second = max(j2k_size_range.second, frame->size());
174 if (SHOULD_PICTURE && frame_detail) {
175 printf(" decrypted OK");
177 } catch (exception& e) {
178 if (SHOULD_PICTURE && frame_detail) {
179 printf(" decryption FAILED");
184 if (SHOULD_PICTURE && frame_detail) {
189 if (SHOULD_PICTURE) {
191 " Minimum frame size: %5.1f MBit/s (%6d)\n"
192 " Maximum frame size: %5.1f MBit/s (%6d)\n",
193 mbits_per_second(j2k_size_range.first, ma->frame_rate()), j2k_size_range.first,
194 mbits_per_second(j2k_size_range.second, ma->frame_rate()), j2k_size_range.second
199 OUTPUT_PICTURE_NC(" - not present in this DCP.\n");
203 mp->duration().get_value_or(mp->intrinsic_duration()),
204 mp->frame_rate().as_float(),
205 mp->frame_rate().as_float()
211 main_sound (vector<string> const& only, shared_ptr<Reel> reel)
213 auto ms = reel->main_sound ();
218 OUTPUT_SOUND(" Sound ID: %1", ms->id());
219 if (ms->entry_point()) {
220 OUTPUT_SOUND(" entry %1", *ms->entry_point());
222 if (ms->duration()) {
223 OUTPUT_SOUND(" duration %1 intrinsic %2", *ms->duration(), ms->intrinsic_duration());
225 OUTPUT_SOUND(" intrinsic duration %1", ms->intrinsic_duration());
228 if (ms->asset_ref().resolved()) {
231 "\n Sound: %1 channels at %2Hz\n",
232 ms->asset()->channels(),
233 ms->asset()->sampling_rate()
237 OUTPUT_SOUND_NC(" - not present in this DCP.\n");
243 main_subtitle (vector<string> const& only, shared_ptr<Reel> reel, bool list_subtitles)
245 auto ms = reel->main_subtitle ();
250 OUTPUT_SUBTITLE(" Subtitle ID: %1", ms->id());
252 if (ms->asset_ref().resolved()) {
253 auto subs = ms->asset()->subtitles ();
254 OUTPUT_SUBTITLE("\n Subtitle: %1 subtitles", subs.size());
255 auto iop = dynamic_pointer_cast<InteropSubtitleAsset> (ms->asset());
257 OUTPUT_SUBTITLE(" in %1\n", iop->language());
259 auto smpte = dynamic_pointer_cast<SMPTESubtitleAsset> (ms->asset());
260 if (smpte && smpte->language ()) {
261 OUTPUT_SUBTITLE(" in %1\n", smpte->language().get());
263 if (list_subtitles) {
265 auto ks = dynamic_pointer_cast<const SubtitleString>(k);
269 OUTPUT_SUBTITLE("%1\n", s.str());
271 auto is = dynamic_pointer_cast<const SubtitleImage>(k);
275 OUTPUT_SUBTITLE("%1\n", s.str());
280 OUTPUT_SUBTITLE_NC(" - not present in this DCP.\n");
286 main (int argc, char* argv[])
290 bool subtitles = false;
291 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 { "frame", no_argument, 0, 'f' },
307 { "decompress", no_argument, 0, 'd' },
308 { "only", required_argument, 0, 'o' },
309 { "ignore-missing-assets", no_argument, 0, 'A' },
310 { "kdm", required_argument, 0, 'B' },
311 { "private-key", required_argument, 0, 'C' },
315 int c = getopt_long (argc, argv, "vhspfdo:AB:C:", long_options, &option_index);
323 cout << "libdcp version " << LIBDCP_VERSION << "\n";
341 only_string = optarg;
344 ignore_missing_assets = true;
350 private_key = optarg;
355 if (argc <= optind || argc > (optind + 1)) {
360 if (!boost::filesystem::exists (argv[optind])) {
361 cerr << argv[0] << ": DCP or CPL " << argv[optind] << " not found.\n";
367 only = boost::split(only, *only_string, boost::is_any_of(","));
370 vector<shared_ptr<CPL>> cpls;
371 if (boost::filesystem::is_directory(argv[optind])) {
373 vector<dcp::VerificationNote> notes;
375 dcp = new DCP (argv[optind]);
377 if (kdm && private_key) {
378 dcp->add(DecryptedKDM(EncryptedKDM(file_to_string(*kdm)), file_to_string(*private_key)));
380 } catch (FileError& e) {
381 cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
383 } catch (ReadError& e) {
384 cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
386 } catch (KDMDecryptionError& e) {
387 cerr << e.what() << "\n";
391 OUTPUT_DCP_PATH("DCP: %1\n", boost::filesystem::path(argv[optind]).string());
393 dcp::filter_notes (notes, ignore_missing_assets);
394 for (auto i: notes) {
395 cerr << "Error: " << note_to_string(i) << "\n";
400 cpls.push_back (std::make_shared<CPL>(boost::filesystem::path(argv[optind])));
401 ignore_missing_assets = true;
404 dcp::Time total_time;
407 OUTPUT_CPL_NAME_ID(" CPL: %1 %2\n", i->annotation_text().get_value_or(""), i->id());
410 for (auto j: i->reels()) {
411 if (should_output(only, "picture") || should_output(only, "sound") || should_output(only, "subtitle")) {
412 cout << " Reel " << R << "\n";
416 total_time += main_picture(only, j, picture, frame, decompress);
417 } catch (UnresolvedRefError& e) {
418 if (!ignore_missing_assets) {
419 cerr << e.what() << " (for main picture)\n";
425 } catch (UnresolvedRefError& e) {
426 if (!ignore_missing_assets) {
427 cerr << e.what() << " (for main sound)\n";
432 main_subtitle (only, j, subtitles);
433 } catch (UnresolvedRefError& e) {
434 if (!ignore_missing_assets) {
435 cerr << e.what() << " (for main subtitle)\n";
443 OUTPUT_TOTAL_TIME("Total: %1\n", total_time.as_string(dcp::Standard::SMPTE));