2 Copyright (C) 2012-2020 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/foreach.hpp>
56 #include <boost/algorithm/string.hpp>
71 using std::stringstream;
72 using boost::shared_ptr;
73 using boost::dynamic_pointer_cast;
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 << " -d, --decompress decompress picture when analysing (this is slow)\n"
84 << " -o, --only only output certain pieces of information; see below.\n"
85 << " --kdm KDM to decrypt DCP\n"
86 << " --private-key private key for the certificate that the KDM is targeted at\n"
87 << " --ignore-missing-assets ignore missing asset files\n";
89 cerr << "--only takes a comma-separated list of strings, one or more of:\n"
90 " dcp-path DCP path\n"
91 " cpl-name-id CPL name and ID\n"
92 " picture picture information\n"
93 " sound sound information\n"
94 " subtitle picture information\n"
95 " total-time total DCP time\n";
99 mbits_per_second (int size, Fraction frame_rate)
101 return size * 8 * frame_rate.as_float() / 1e6;
104 #define OUTPUT_DCP_PATH(...) maybe_output(only, "dcp-path", String::compose(__VA_ARGS__));
105 #define OUTPUT_CPL_NAME_ID(...) maybe_output(only, "cpl-name-id", String::compose(__VA_ARGS__));
106 #define OUTPUT_PICTURE(...) maybe_output(only, "picture", String::compose(__VA_ARGS__));
107 #define OUTPUT_PICTURE_NC(x) maybe_output(only, "picture", (x));
108 #define SHOULD_PICTURE should_output(only, "picture")
109 #define OUTPUT_SOUND(...) maybe_output(only, "sound", String::compose(__VA_ARGS__));
110 #define OUTPUT_SOUND_NC(x) maybe_output(only, "sound", (x));
111 #define OUTPUT_SUBTITLE(...) maybe_output(only, "subtitle", String::compose(__VA_ARGS__));
112 #define OUTPUT_SUBTITLE_NC(x) maybe_output(only, "subtitle", (x));
113 #define OUTPUT_TOTAL_TIME(...) maybe_output(only, "total-time", String::compose(__VA_ARGS__));
116 should_output(vector<string> const& only, string t)
118 return only.empty() || find(only.begin(), only.end(), t) != only.end();
122 maybe_output(vector<string> const& only, string t, string s)
124 if (should_output(only, t)) {
131 main_picture (vector<string> const& only, shared_ptr<Reel> reel, bool analyse, bool decompress)
133 shared_ptr<dcp::ReelPictureAsset> mp = reel->main_picture ();
138 OUTPUT_PICTURE(" Picture ID: %1", mp->id());
139 if (mp->entry_point()) {
140 OUTPUT_PICTURE(" entry %1", *mp->entry_point());
142 if (mp->duration()) {
144 " duration %1 (%2) intrinsic %3",
146 dcp::Time(*mp->duration(), mp->frame_rate().as_float(), mp->frame_rate().as_float()).as_string(dcp::SMPTE),
147 mp->intrinsic_duration()
150 OUTPUT_PICTURE(" intrinsic duration %1", mp->intrinsic_duration());
153 if (mp->asset_ref().resolved()) {
155 OUTPUT_PICTURE("\n Picture: %1x%2\n", mp->asset()->size().width, mp->asset()->size().height);
158 shared_ptr<MonoPictureAsset> ma = dynamic_pointer_cast<MonoPictureAsset>(mp->asset());
160 shared_ptr<MonoPictureAssetReader> reader = ma->start_read ();
161 pair<int, int> j2k_size_range (INT_MAX, 0);
162 for (int64_t i = 0; i < ma->intrinsic_duration(); ++i) {
163 shared_ptr<const MonoPictureFrame> frame = reader->get_frame (i);
164 if (SHOULD_PICTURE) {
165 printf("Frame %" PRId64 " J2K size %7d", i, frame->j2k_size());
167 j2k_size_range.first = min(j2k_size_range.first, frame->j2k_size());
168 j2k_size_range.second = max(j2k_size_range.second, frame->j2k_size());
173 if (SHOULD_PICTURE) {
174 printf(" decrypted OK");
176 } catch (exception& e) {
177 if (SHOULD_PICTURE) {
178 printf(" decryption FAILED");
183 if (SHOULD_PICTURE) {
188 if (SHOULD_PICTURE) {
190 "J2K size ranges from %d (%.1f Mbit/s) to %d (%.1f Mbit/s)\n",
191 j2k_size_range.first, mbits_per_second(j2k_size_range.first, ma->frame_rate()),
192 j2k_size_range.second, mbits_per_second(j2k_size_range.second, ma->frame_rate())
197 OUTPUT_PICTURE_NC(" - not present in this DCP.\n");
201 mp->duration().get_value_or(mp->intrinsic_duration()),
202 mp->frame_rate().as_float(),
203 mp->frame_rate().as_float()
209 main_sound (vector<string> const& only, shared_ptr<Reel> reel)
211 shared_ptr<dcp::ReelSoundAsset> ms = reel->main_sound ();
216 OUTPUT_SOUND(" Sound ID: %1", ms->id());
217 if (ms->entry_point()) {
218 OUTPUT_SOUND(" entry %1", *ms->entry_point());
220 if (ms->duration()) {
221 OUTPUT_SOUND(" duration %1 intrinsic %2", *ms->duration(), ms->intrinsic_duration());
223 OUTPUT_SOUND(" intrinsic duration %1", ms->intrinsic_duration());
226 if (ms->asset_ref().resolved()) {
229 "\n Sound: %1 channels at %2Hz\n",
230 ms->asset()->channels(),
231 ms->asset()->sampling_rate()
235 OUTPUT_SOUND_NC(" - not present in this DCP.\n");
241 main_subtitle (vector<string> const& only, shared_ptr<Reel> reel, bool list_subtitles)
243 shared_ptr<dcp::ReelSubtitleAsset> ms = reel->main_subtitle ();
248 OUTPUT_SUBTITLE(" Subtitle ID: %1", ms->id());
250 if (ms->asset_ref().resolved()) {
251 list<shared_ptr<Subtitle> > subs = ms->asset()->subtitles ();
252 OUTPUT_SUBTITLE("\n Subtitle: %1 subtitles", subs.size());
253 shared_ptr<InteropSubtitleAsset> iop = dynamic_pointer_cast<InteropSubtitleAsset> (ms->asset());
255 OUTPUT_SUBTITLE(" in %1\n", iop->language());
257 shared_ptr<SMPTESubtitleAsset> smpte = dynamic_pointer_cast<SMPTESubtitleAsset> (ms->asset());
258 if (smpte && smpte->language ()) {
259 OUTPUT_SUBTITLE(" in %1\n", smpte->language().get());
261 if (list_subtitles) {
262 BOOST_FOREACH (shared_ptr<Subtitle> k, subs) {
263 shared_ptr<SubtitleString> ks = dynamic_pointer_cast<SubtitleString> (k);
267 OUTPUT_SUBTITLE("%1\n", s.str());
269 shared_ptr<SubtitleImage> is = dynamic_pointer_cast<SubtitleImage> (k);
273 OUTPUT_SUBTITLE("%1\n", s.str());
278 OUTPUT_SUBTITLE_NC(" - not present in this DCP.\n");
284 main (int argc, char* argv[])
286 bool subtitles = false;
287 bool picture = false;
288 bool decompress = false;
289 bool ignore_missing_assets = false;
290 optional<boost::filesystem::path> kdm;
291 optional<boost::filesystem::path> private_key;
292 optional<string> only_string;
294 int option_index = 0;
296 static struct option long_options[] = {
297 { "version", no_argument, 0, 'v' },
298 { "help", no_argument, 0, 'h' },
299 { "subtitles", no_argument, 0, 's' },
300 { "picture", no_argument, 0, 'p' },
301 { "decompress", no_argument, 0, 'd' },
302 { "only", required_argument, 0, 'o' },
303 { "ignore-missing-assets", no_argument, 0, 'A' },
304 { "kdm", required_argument, 0, 'B' },
305 { "private-key", required_argument, 0, 'C' },
309 int c = getopt_long (argc, argv, "vhspdo:AB:C:", long_options, &option_index);
317 cout << "libdcp version " << LIBDCP_VERSION << "\n";
332 only_string = optarg;
335 ignore_missing_assets = true;
341 private_key = optarg;
346 if (argc <= optind || argc > (optind + 1)) {
351 if (!boost::filesystem::exists (argv[optind])) {
352 cerr << argv[0] << ": DCP or CPL " << argv[optind] << " not found.\n";
358 only = boost::split(only, *only_string, boost::is_any_of(","));
361 list<shared_ptr<CPL> > cpls;
362 if (boost::filesystem::is_directory(argv[optind])) {
364 list<dcp::VerificationNote> notes;
366 dcp = new DCP (argv[optind]);
368 if (kdm && private_key) {
369 dcp->add(DecryptedKDM(EncryptedKDM(file_to_string(*kdm)), file_to_string(*private_key)));
371 } catch (FileError& e) {
372 cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
374 } catch (ReadError& e) {
375 cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
377 } catch (KDMDecryptionError& e) {
378 cerr << e.what() << "\n";
382 OUTPUT_DCP_PATH("DCP: %1\n", boost::filesystem::path(argv[optind]).string());
384 dcp::filter_notes (notes, ignore_missing_assets);
385 BOOST_FOREACH (dcp::VerificationNote i, notes) {
386 cerr << "Error: " << note_to_string(i) << "\n";
391 cpls.push_back (shared_ptr<CPL>(new CPL(boost::filesystem::path(argv[optind]))));
392 ignore_missing_assets = true;
395 dcp::Time total_time;
397 BOOST_FOREACH (shared_ptr<CPL> i, cpls) {
398 OUTPUT_CPL_NAME_ID(" CPL: %1 %2\n", i->annotation_text(), i->id());
401 BOOST_FOREACH (shared_ptr<Reel> j, i->reels()) {
402 if (should_output(only, "picture") || should_output(only, "sound") || should_output(only, "subtitle")) {
403 cout << " Reel " << R << "\n";
407 total_time += main_picture(only, j, picture, decompress);
408 } catch (UnresolvedRefError& e) {
409 if (!ignore_missing_assets) {
410 cerr << e.what() << " (for main picture)\n";
416 } catch (UnresolvedRefError& e) {
417 if (!ignore_missing_assets) {
418 cerr << e.what() << " (for main sound)\n";
423 main_subtitle (only, j, subtitles);
424 } catch (UnresolvedRefError& e) {
425 if (!ignore_missing_assets) {
426 cerr << e.what() << " (for main subtitle)\n";
434 OUTPUT_TOTAL_TIME("Total: %1\n", total_time.as_string(dcp::SMPTE));