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[])
288 bool subtitles = false;
289 bool picture = false;
290 bool decompress = false;
291 bool ignore_missing_assets = false;
292 optional<boost::filesystem::path> kdm;
293 optional<boost::filesystem::path> private_key;
294 optional<string> only_string;
296 int option_index = 0;
298 static struct option long_options[] = {
299 { "version", no_argument, 0, 'v' },
300 { "help", no_argument, 0, 'h' },
301 { "subtitles", no_argument, 0, 's' },
302 { "picture", no_argument, 0, 'p' },
303 { "decompress", no_argument, 0, 'd' },
304 { "only", required_argument, 0, 'o' },
305 { "ignore-missing-assets", no_argument, 0, 'A' },
306 { "kdm", required_argument, 0, 'B' },
307 { "private-key", required_argument, 0, 'C' },
311 int c = getopt_long (argc, argv, "vhspdo:AB:C:", long_options, &option_index);
319 cout << "libdcp version " << LIBDCP_VERSION << "\n";
334 only_string = optarg;
337 ignore_missing_assets = true;
343 private_key = optarg;
348 if (argc <= optind || argc > (optind + 1)) {
353 if (!boost::filesystem::exists (argv[optind])) {
354 cerr << argv[0] << ": DCP or CPL " << argv[optind] << " not found.\n";
360 only = boost::split(only, *only_string, boost::is_any_of(","));
363 list<shared_ptr<CPL> > cpls;
364 if (boost::filesystem::is_directory(argv[optind])) {
366 list<dcp::VerificationNote> notes;
368 dcp = new DCP (argv[optind]);
370 if (kdm && private_key) {
371 dcp->add(DecryptedKDM(EncryptedKDM(file_to_string(*kdm)), file_to_string(*private_key)));
373 } catch (FileError& e) {
374 cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
376 } catch (ReadError& e) {
377 cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
379 } catch (KDMDecryptionError& e) {
380 cerr << e.what() << "\n";
384 OUTPUT_DCP_PATH("DCP: %1\n", boost::filesystem::path(argv[optind]).string());
386 dcp::filter_notes (notes, ignore_missing_assets);
387 BOOST_FOREACH (dcp::VerificationNote i, notes) {
388 cerr << "Error: " << note_to_string(i) << "\n";
393 cpls.push_back (shared_ptr<CPL>(new CPL(boost::filesystem::path(argv[optind]))));
394 ignore_missing_assets = true;
397 dcp::Time total_time;
399 BOOST_FOREACH (shared_ptr<CPL> i, cpls) {
400 OUTPUT_CPL_NAME_ID(" CPL: %1 %2\n", i->annotation_text(), i->id());
403 BOOST_FOREACH (shared_ptr<Reel> j, i->reels()) {
404 if (should_output(only, "picture") || should_output(only, "sound") || should_output(only, "subtitle")) {
405 cout << " Reel " << R << "\n";
409 total_time += main_picture(only, j, picture, decompress);
410 } catch (UnresolvedRefError& e) {
411 if (!ignore_missing_assets) {
412 cerr << e.what() << " (for main picture)\n";
418 } catch (UnresolvedRefError& e) {
419 if (!ignore_missing_assets) {
420 cerr << e.what() << " (for main sound)\n";
425 main_subtitle (only, j, subtitles);
426 } catch (UnresolvedRefError& e) {
427 if (!ignore_missing_assets) {
428 cerr << e.what() << " (for main subtitle)\n";
436 OUTPUT_TOTAL_TIME("Total: %1\n", total_time.as_string(dcp::SMPTE));