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 "interop_subtitle_asset.h"
43 #include "mono_picture_asset.h"
44 #include "picture_asset.h"
46 #include "reel_picture_asset.h"
47 #include "reel_sound_asset.h"
48 #include "reel_subtitle_asset.h"
49 #include "smpte_subtitle_asset.h"
50 #include "sound_asset.h"
51 #include "subtitle_asset.h"
52 #include "subtitle_image.h"
53 #include "subtitle_string.h"
55 #include <boost/filesystem.hpp>
56 #include <boost/algorithm/string.hpp>
65 using std::dynamic_pointer_cast;
71 using std::shared_ptr;
73 using std::stringstream;
75 using boost::optional;
82 cerr << "Syntax: " << n << " [options] [<DCP>] [<CPL>]\n"
83 << " -s, --subtitles list all subtitles\n"
84 << " -p, --picture analyse picture\n"
85 << " -d, --decompress decompress picture when analysing (this is slow)\n"
86 << " -o, --only only output certain pieces of information; see below.\n"
87 << " --kdm KDM to decrypt DCP\n"
88 << " --private-key private key for the certificate that the KDM is targeted at\n"
89 << " --ignore-missing-assets ignore missing asset files\n";
91 cerr << "--only takes a comma-separated list of strings, one or more of:\n"
92 " dcp-path DCP path\n"
93 " cpl-name-id CPL name and ID\n"
94 " picture picture information\n"
95 " sound sound information\n"
96 " subtitle picture information\n"
97 " total-time total DCP time\n";
101 mbits_per_second (int size, Fraction frame_rate)
103 return size * 8 * frame_rate.as_float() / 1e6;
106 #define OUTPUT_DCP_PATH(...) maybe_output(only, "dcp-path", String::compose(__VA_ARGS__));
107 #define OUTPUT_CPL_NAME_ID(...) maybe_output(only, "cpl-name-id", String::compose(__VA_ARGS__));
108 #define OUTPUT_PICTURE(...) maybe_output(only, "picture", String::compose(__VA_ARGS__));
109 #define OUTPUT_PICTURE_NC(x) maybe_output(only, "picture", (x));
110 #define SHOULD_PICTURE should_output(only, "picture")
111 #define OUTPUT_SOUND(...) maybe_output(only, "sound", String::compose(__VA_ARGS__));
112 #define OUTPUT_SOUND_NC(x) maybe_output(only, "sound", (x));
113 #define OUTPUT_SUBTITLE(...) maybe_output(only, "subtitle", String::compose(__VA_ARGS__));
114 #define OUTPUT_SUBTITLE_NC(x) maybe_output(only, "subtitle", (x));
115 #define OUTPUT_TOTAL_TIME(...) maybe_output(only, "total-time", String::compose(__VA_ARGS__));
118 should_output(vector<string> const& only, string t)
120 return only.empty() || find(only.begin(), only.end(), t) != only.end();
124 maybe_output(vector<string> const& only, string t, string s)
126 if (should_output(only, t)) {
133 main_picture (vector<string> const& only, shared_ptr<Reel> reel, bool analyse, bool decompress)
135 shared_ptr<dcp::ReelPictureAsset> mp = reel->main_picture ();
140 OUTPUT_PICTURE(" Picture ID: %1", mp->id());
141 if (mp->entry_point()) {
142 OUTPUT_PICTURE(" entry %1", *mp->entry_point());
144 if (mp->duration()) {
146 " duration %1 (%2) intrinsic %3",
148 dcp::Time(*mp->duration(), mp->frame_rate().as_float(), mp->frame_rate().as_float()).as_string(dcp::Standard::SMPTE),
149 mp->intrinsic_duration()
152 OUTPUT_PICTURE(" intrinsic duration %1", mp->intrinsic_duration());
155 if (mp->asset_ref().resolved()) {
157 OUTPUT_PICTURE("\n Picture: %1x%2\n", mp->asset()->size().width, mp->asset()->size().height);
160 shared_ptr<MonoPictureAsset> ma = dynamic_pointer_cast<MonoPictureAsset>(mp->asset());
162 shared_ptr<MonoPictureAssetReader> reader = ma->start_read ();
163 pair<int, int> j2k_size_range (INT_MAX, 0);
164 for (int64_t i = 0; i < ma->intrinsic_duration(); ++i) {
165 shared_ptr<const MonoPictureFrame> frame = reader->get_frame (i);
166 if (SHOULD_PICTURE) {
167 printf("Frame %" PRId64 " J2K size %7d", i, frame->size());
169 j2k_size_range.first = min(j2k_size_range.first, frame->size());
170 j2k_size_range.second = max(j2k_size_range.second, frame->size());
175 if (SHOULD_PICTURE) {
176 printf(" decrypted OK");
178 } catch (exception& e) {
179 if (SHOULD_PICTURE) {
180 printf(" decryption FAILED");
185 if (SHOULD_PICTURE) {
190 if (SHOULD_PICTURE) {
192 "J2K size ranges from %d (%.1f Mbit/s) to %d (%.1f Mbit/s)\n",
193 j2k_size_range.first, mbits_per_second(j2k_size_range.first, ma->frame_rate()),
194 j2k_size_range.second, mbits_per_second(j2k_size_range.second, ma->frame_rate())
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 shared_ptr<dcp::ReelSoundAsset> 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 shared_ptr<dcp::ReelSubtitleAsset> 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 shared_ptr<InteropSubtitleAsset> iop = dynamic_pointer_cast<InteropSubtitleAsset> (ms->asset());
257 OUTPUT_SUBTITLE(" in %1\n", iop->language());
259 shared_ptr<SMPTESubtitleAsset> 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;
292 bool decompress = false;
293 bool ignore_missing_assets = false;
294 optional<boost::filesystem::path> kdm;
295 optional<boost::filesystem::path> private_key;
296 optional<string> only_string;
298 int option_index = 0;
300 static struct option long_options[] = {
301 { "version", no_argument, 0, 'v' },
302 { "help", no_argument, 0, 'h' },
303 { "subtitles", no_argument, 0, 's' },
304 { "picture", no_argument, 0, 'p' },
305 { "decompress", no_argument, 0, 'd' },
306 { "only", required_argument, 0, 'o' },
307 { "ignore-missing-assets", no_argument, 0, 'A' },
308 { "kdm", required_argument, 0, 'B' },
309 { "private-key", required_argument, 0, 'C' },
313 int c = getopt_long (argc, argv, "vhspdo:AB:C:", long_options, &option_index);
321 cout << "libdcp version " << dcp::version << "\n";
336 only_string = optarg;
339 ignore_missing_assets = true;
345 private_key = optarg;
350 if (argc <= optind || argc > (optind + 1)) {
355 if (!boost::filesystem::exists (argv[optind])) {
356 cerr << argv[0] << ": DCP or CPL " << argv[optind] << " not found.\n";
362 only = boost::split(only, *only_string, boost::is_any_of(","));
365 vector<shared_ptr<CPL>> cpls;
366 if (boost::filesystem::is_directory(argv[optind])) {
368 vector<dcp::VerificationNote> notes;
370 dcp = new DCP (argv[optind]);
372 if (kdm && private_key) {
373 dcp->add(DecryptedKDM(EncryptedKDM(file_to_string(*kdm)), file_to_string(*private_key)));
375 } catch (FileError& e) {
376 cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
378 } catch (ReadError& e) {
379 cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
381 } catch (KDMDecryptionError& e) {
382 cerr << e.what() << "\n";
386 OUTPUT_DCP_PATH("DCP: %1\n", boost::filesystem::path(argv[optind]).string());
388 dcp::filter_notes (notes, ignore_missing_assets);
389 for (auto i: notes) {
390 cerr << "Error: " << note_to_string(i) << "\n";
395 cpls.push_back (shared_ptr<CPL>(new CPL(boost::filesystem::path(argv[optind]))));
396 ignore_missing_assets = true;
399 dcp::Time total_time;
402 OUTPUT_CPL_NAME_ID(" CPL: %1 %2\n", i->annotation_text().get_value_or(""), i->id());
405 for (auto j: i->reels()) {
406 if (should_output(only, "picture") || should_output(only, "sound") || should_output(only, "subtitle")) {
407 cout << " Reel " << R << "\n";
411 total_time += main_picture(only, j, picture, decompress);
412 } catch (UnresolvedRefError& e) {
413 if (!ignore_missing_assets) {
414 cerr << e.what() << " (for main picture)\n";
420 } catch (UnresolvedRefError& e) {
421 if (!ignore_missing_assets) {
422 cerr << e.what() << " (for main sound)\n";
427 main_subtitle (only, j, subtitles);
428 } catch (UnresolvedRefError& e) {
429 if (!ignore_missing_assets) {
430 cerr << e.what() << " (for main subtitle)\n";
438 OUTPUT_TOTAL_TIME("Total: %1\n", total_time.as_string(dcp::Standard::SMPTE));