Accept that <Duration> and <EntryPoint> are optional, and account for this in tests.
[libdcp.git] / tools / dcpinfo.cc
1 /*
2     Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
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.
10
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.
15
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/>.
18
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
23     including the two.
24
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.
32 */
33
34 #include "dcp.h"
35 #include "exceptions.h"
36 #include "reel.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"
50 #include "cpl.h"
51 #include "common.h"
52 #include <getopt.h>
53 #include <boost/filesystem.hpp>
54 #include <boost/foreach.hpp>
55 #include <iostream>
56 #include <cstdlib>
57 #include <inttypes.h>
58
59 using std::string;
60 using std::cerr;
61 using std::cout;
62 using std::list;
63 using std::pair;
64 using std::min;
65 using std::max;
66 using std::exception;
67 using boost::shared_ptr;
68 using boost::dynamic_pointer_cast;
69 using boost::optional;
70 using namespace dcp;
71
72 static void
73 help (string n)
74 {
75         cerr << "Syntax: " << n << " [options] [<DCP>] [<CPL>]\n"
76              << "  -s, --subtitles              list all subtitles\n"
77              << "  -p, --picture                analyse picture\n"
78              << "  -d, --decompress             decompress picture when analysing (this is slow)\n"
79              << "  -k, --keep-going             carry on in the event of errors, if possible\n"
80              << "      --kdm                    KDM to decrypt DCP\n"
81              << "      --private-key            private key for the certificate that the KDM is targeted at\n"
82              << "      --ignore-missing-assets  ignore missing asset files\n";
83 }
84
85 static double
86 mbits_per_second (int size, Fraction frame_rate)
87 {
88         return size * 8 * frame_rate.as_float() / 1e6;
89 }
90
91 static void
92 main_picture (shared_ptr<Reel> reel, bool analyse, bool decompress)
93 {
94         if (!reel->main_picture()) {
95                 return;
96         }
97
98         cout << "      Picture ID:  " << reel->main_picture()->id();
99         if (reel->main_picture()->entry_point()) {
100                 cout << " entry " << *reel->main_picture()->entry_point();
101         }
102         if (reel->main_picture()->duration()) {
103                 cout << " duration " << *reel->main_picture()->duration()
104                      << " (" << dcp::Time(*reel->main_picture()->duration(), reel->main_picture()->frame_rate().as_float(), reel->main_picture()->frame_rate().as_float()).as_string(dcp::SMPTE) << ")"
105                      << " intrinsic " << reel->main_picture()->intrinsic_duration();
106         } else {
107                 cout << " intrinsic duration " << reel->main_picture()->intrinsic_duration();
108         }
109
110         if (reel->main_picture()->asset_ref().resolved()) {
111                 if (reel->main_picture()->asset()) {
112                         cout << "\n      Picture:     "
113                              << reel->main_picture()->asset()->size().width
114                              << "x"
115                              << reel->main_picture()->asset()->size().height << "\n";
116                 }
117
118                 shared_ptr<MonoPictureAsset> ma = dynamic_pointer_cast<MonoPictureAsset>(reel->main_picture()->asset());
119                 if (analyse && ma) {
120                         shared_ptr<MonoPictureAssetReader> reader = ma->start_read ();
121                         pair<int, int> j2k_size_range (INT_MAX, 0);
122                         for (int64_t i = 0; i < ma->intrinsic_duration(); ++i) {
123                                 shared_ptr<const MonoPictureFrame> frame = reader->get_frame (i);
124                                 printf("Frame %" PRId64 " J2K size %7d", i, frame->j2k_size());
125                                 j2k_size_range.first = min(j2k_size_range.first, frame->j2k_size());
126                                 j2k_size_range.second = max(j2k_size_range.second, frame->j2k_size());
127
128                                 if (decompress) {
129                                         try {
130                                                 frame->xyz_image();
131                                                 printf(" decrypted OK");
132                                         } catch (exception& e) {
133                                                 printf(" decryption FAILED");
134                                         }
135                                 }
136
137                                 printf("\n");
138
139                         }
140                         printf(
141                                 "J2K size ranges from %d (%.1f Mbit/s) to %d (%.1f Mbit/s)\n",
142                                 j2k_size_range.first, mbits_per_second(j2k_size_range.first, ma->frame_rate()),
143                                 j2k_size_range.second, mbits_per_second(j2k_size_range.second, ma->frame_rate())
144                                 );
145                 }
146         } else {
147                 cout << " - not present in this DCP.\n";
148         }
149 }
150
151 static void
152 main_sound (shared_ptr<Reel> reel)
153 {
154         if (reel->main_sound()) {
155                 cout << "      Sound ID:    " << reel->main_sound()->id();
156                 if (reel->main_sound()->entry_point()) {
157                      cout << " entry " << *reel->main_sound()->entry_point();
158                 }
159                 if (reel->main_sound()->duration()) {
160                         cout << " duration " << *reel->main_sound()->duration()
161                              << " intrinsic " << reel->main_sound()->intrinsic_duration();
162                 } else {
163                         cout << " intrinsic duration " << reel->main_sound()->intrinsic_duration();
164                 }
165
166                 if (reel->main_sound()->asset_ref().resolved()) {
167                         if (reel->main_sound()->asset()) {
168                                 cout << "\n      Sound:       "
169                                      << reel->main_sound()->asset()->channels()
170                                      << " channels at "
171                                      << reel->main_sound()->asset()->sampling_rate() << "Hz\n";
172                         }
173                 } else {
174                         cout << " - not present in this DCP.\n";
175                 }
176         }
177 }
178
179 static void
180 main_subtitle (shared_ptr<Reel> reel, bool list_subtitles)
181 {
182         if (!reel->main_subtitle()) {
183                 return;
184         }
185
186         cout << "      Subtitle ID: " << reel->main_subtitle()->id();
187
188         if (reel->main_subtitle()->asset_ref().resolved()) {
189                 list<shared_ptr<Subtitle> > subs = reel->main_subtitle()->asset()->subtitles ();
190                 cout << "\n      Subtitle:    " << subs.size() << " subtitles";
191                 shared_ptr<InteropSubtitleAsset> iop = dynamic_pointer_cast<InteropSubtitleAsset> (reel->main_subtitle()->asset());
192                 if (iop) {
193                         cout << " in " << iop->language() << "\n";
194                 }
195                 shared_ptr<SMPTESubtitleAsset> smpte = dynamic_pointer_cast<SMPTESubtitleAsset> (reel->main_subtitle()->asset());
196                 if (smpte && smpte->language ()) {
197                         cout << " in " << smpte->language().get() << "\n";
198                 }
199                 if (list_subtitles) {
200                         BOOST_FOREACH (shared_ptr<Subtitle> k, subs) {
201                                 shared_ptr<SubtitleString> ks = dynamic_pointer_cast<SubtitleString> (k);
202                                 if (ks) {
203                                         cout << *ks << "\n";
204                                 }
205                                 shared_ptr<SubtitleImage> is = dynamic_pointer_cast<SubtitleImage> (k);
206                                 if (is) {
207                                         cout << *is << "\n";
208                                 }
209                         }
210                 }
211         } else {
212                 cout << " - not present in this DCP.\n";
213         }
214 }
215
216 int
217 main (int argc, char* argv[])
218 {
219         bool subtitles = false;
220         bool keep_going = false;
221         bool picture = false;
222         bool decompress = false;
223         bool ignore_missing_assets = false;
224         optional<boost::filesystem::path> kdm;
225         optional<boost::filesystem::path> private_key;
226
227         int option_index = 0;
228         while (true) {
229                 static struct option long_options[] = {
230                         { "version", no_argument, 0, 'v' },
231                         { "help", no_argument, 0, 'h' },
232                         { "subtitles", no_argument, 0, 's' },
233                         { "keep-going", no_argument, 0, 'k' },
234                         { "picture", no_argument, 0, 'p' },
235                         { "decompress", no_argument, 0, 'd' },
236                         { "ignore-missing-assets", no_argument, 0, 'A' },
237                         { "kdm", required_argument, 0, 'B' },
238                         { "private-key", required_argument, 0, 'C' },
239                         { 0, 0, 0, 0 }
240                 };
241
242                 int c = getopt_long (argc, argv, "vhskpdAB:C:", long_options, &option_index);
243
244                 if (c == -1) {
245                         break;
246                 }
247
248                 switch (c) {
249                 case 'v':
250                         cout << "libdcp version " << LIBDCP_VERSION << "\n";
251                         exit (EXIT_SUCCESS);
252                 case 'h':
253                         help (argv[0]);
254                         exit (EXIT_SUCCESS);
255                 case 's':
256                         subtitles = true;
257                         break;
258                 case 'k':
259                         keep_going = true;
260                         break;
261                 case 'p':
262                         picture = true;
263                         break;
264                 case 'd':
265                         decompress = true;
266                         break;
267                 case 'A':
268                         ignore_missing_assets = true;
269                         break;
270                 case 'B':
271                         kdm = optarg;
272                         break;
273                 case 'C':
274                         private_key = optarg;
275                         break;
276                 }
277         }
278
279         if (argc <= optind || argc > (optind + 1)) {
280                 help (argv[0]);
281                 exit (EXIT_FAILURE);
282         }
283
284         if (!boost::filesystem::exists (argv[optind])) {
285                 cerr << argv[0] << ": DCP or CPL " << argv[optind] << " not found.\n";
286                 exit (EXIT_FAILURE);
287         }
288
289         list<shared_ptr<CPL> > cpls;
290         if (boost::filesystem::is_directory(argv[optind])) {
291                 DCP* dcp = 0;
292                 DCP::ReadErrors errors;
293                 try {
294                         dcp = new DCP (argv[optind]);
295                         dcp->read (keep_going, &errors);
296                         if (kdm && private_key) {
297                                 dcp->add(DecryptedKDM(EncryptedKDM(file_to_string(*kdm)), file_to_string(*private_key)));
298                         }
299                 } catch (FileError& e) {
300                         cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
301                         exit (EXIT_FAILURE);
302                 } catch (DCPReadError& e) {
303                         cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
304                         exit (EXIT_FAILURE);
305                 } catch (KDMDecryptionError& e) {
306                         cerr << e.what() << "\n";
307                         exit (EXIT_FAILURE);
308                 }
309
310                 cout << "DCP: " << boost::filesystem::path(argv[optind]).string() << "\n";
311
312                 dcp::filter_errors (errors, ignore_missing_assets);
313                 for (DCP::ReadErrors::const_iterator i = errors.begin(); i != errors.end(); ++i) {
314                         cerr << "Error: " << (*i)->what() << "\n";
315                 }
316
317                 cpls = dcp->cpls ();
318         } else {
319                 cpls.push_back (shared_ptr<CPL>(new CPL(boost::filesystem::path(argv[optind]))));
320                 keep_going = true;
321                 ignore_missing_assets = true;
322         }
323
324         BOOST_FOREACH (shared_ptr<CPL> i, cpls) {
325                 cout << "  CPL: " << i->annotation_text() << "\n";
326
327                 int R = 1;
328                 BOOST_FOREACH (shared_ptr<Reel> j, i->reels()) {
329                         cout << "    Reel " << R << "\n";
330
331                         try {
332                                 main_picture (j, picture, decompress);
333                         } catch (UnresolvedRefError& e) {
334                                 if (keep_going) {
335                                         if (!ignore_missing_assets) {
336                                                 cerr << e.what() << " (for main picture)\n";
337                                         }
338                                 } else {
339                                         throw;
340                                 }
341                         }
342
343                         try {
344                                 main_sound (j);
345                         } catch (UnresolvedRefError& e) {
346                                 if (keep_going) {
347                                         if (!ignore_missing_assets) {
348                                                 cerr << e.what() << " (for main sound)\n";
349                                         }
350                                 } else {
351                                         throw;
352                                 }
353                         }
354
355                         try {
356                                 main_subtitle (j, subtitles);
357                         } catch (UnresolvedRefError& e) {
358                                 if (keep_going) {
359                                         if (!ignore_missing_assets) {
360                                                 cerr << e.what() << " (for main subtitle)\n";
361                                         }
362                                 } else {
363                                         throw;
364                                 }
365                         }
366
367                         ++R;
368                 }
369         }
370
371         return 0;
372 }