Use VerificationNote for non-fatal errors in DCP::read.
[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              << "      --kdm                    KDM to decrypt DCP\n"
80              << "      --private-key            private key for the certificate that the KDM is targeted at\n"
81              << "      --ignore-missing-assets  ignore missing asset files\n";
82 }
83
84 static double
85 mbits_per_second (int size, Fraction frame_rate)
86 {
87         return size * 8 * frame_rate.as_float() / 1e6;
88 }
89
90 static void
91 main_picture (shared_ptr<Reel> reel, bool analyse, bool decompress)
92 {
93         if (!reel->main_picture()) {
94                 return;
95         }
96
97         cout << "      Picture ID:  " << reel->main_picture()->id();
98         if (reel->main_picture()->entry_point()) {
99                 cout << " entry " << *reel->main_picture()->entry_point();
100         }
101         if (reel->main_picture()->duration()) {
102                 cout << " duration " << *reel->main_picture()->duration()
103                      << " (" << dcp::Time(*reel->main_picture()->duration(), reel->main_picture()->frame_rate().as_float(), reel->main_picture()->frame_rate().as_float()).as_string(dcp::SMPTE) << ")"
104                      << " intrinsic " << reel->main_picture()->intrinsic_duration();
105         } else {
106                 cout << " intrinsic duration " << reel->main_picture()->intrinsic_duration();
107         }
108
109         if (reel->main_picture()->asset_ref().resolved()) {
110                 if (reel->main_picture()->asset()) {
111                         cout << "\n      Picture:     "
112                              << reel->main_picture()->asset()->size().width
113                              << "x"
114                              << reel->main_picture()->asset()->size().height << "\n";
115                 }
116
117                 shared_ptr<MonoPictureAsset> ma = dynamic_pointer_cast<MonoPictureAsset>(reel->main_picture()->asset());
118                 if (analyse && ma) {
119                         shared_ptr<MonoPictureAssetReader> reader = ma->start_read ();
120                         pair<int, int> j2k_size_range (INT_MAX, 0);
121                         for (int64_t i = 0; i < ma->intrinsic_duration(); ++i) {
122                                 shared_ptr<const MonoPictureFrame> frame = reader->get_frame (i);
123                                 printf("Frame %" PRId64 " J2K size %7d", i, frame->j2k_size());
124                                 j2k_size_range.first = min(j2k_size_range.first, frame->j2k_size());
125                                 j2k_size_range.second = max(j2k_size_range.second, frame->j2k_size());
126
127                                 if (decompress) {
128                                         try {
129                                                 frame->xyz_image();
130                                                 printf(" decrypted OK");
131                                         } catch (exception& e) {
132                                                 printf(" decryption FAILED");
133                                         }
134                                 }
135
136                                 printf("\n");
137
138                         }
139                         printf(
140                                 "J2K size ranges from %d (%.1f Mbit/s) to %d (%.1f Mbit/s)\n",
141                                 j2k_size_range.first, mbits_per_second(j2k_size_range.first, ma->frame_rate()),
142                                 j2k_size_range.second, mbits_per_second(j2k_size_range.second, ma->frame_rate())
143                                 );
144                 }
145         } else {
146                 cout << " - not present in this DCP.\n";
147         }
148 }
149
150 static void
151 main_sound (shared_ptr<Reel> reel)
152 {
153         if (reel->main_sound()) {
154                 cout << "      Sound ID:    " << reel->main_sound()->id();
155                 if (reel->main_sound()->entry_point()) {
156                      cout << " entry " << *reel->main_sound()->entry_point();
157                 }
158                 if (reel->main_sound()->duration()) {
159                         cout << " duration " << *reel->main_sound()->duration()
160                              << " intrinsic " << reel->main_sound()->intrinsic_duration();
161                 } else {
162                         cout << " intrinsic duration " << reel->main_sound()->intrinsic_duration();
163                 }
164
165                 if (reel->main_sound()->asset_ref().resolved()) {
166                         if (reel->main_sound()->asset()) {
167                                 cout << "\n      Sound:       "
168                                      << reel->main_sound()->asset()->channels()
169                                      << " channels at "
170                                      << reel->main_sound()->asset()->sampling_rate() << "Hz\n";
171                         }
172                 } else {
173                         cout << " - not present in this DCP.\n";
174                 }
175         }
176 }
177
178 static void
179 main_subtitle (shared_ptr<Reel> reel, bool list_subtitles)
180 {
181         if (!reel->main_subtitle()) {
182                 return;
183         }
184
185         cout << "      Subtitle ID: " << reel->main_subtitle()->id();
186
187         if (reel->main_subtitle()->asset_ref().resolved()) {
188                 list<shared_ptr<Subtitle> > subs = reel->main_subtitle()->asset()->subtitles ();
189                 cout << "\n      Subtitle:    " << subs.size() << " subtitles";
190                 shared_ptr<InteropSubtitleAsset> iop = dynamic_pointer_cast<InteropSubtitleAsset> (reel->main_subtitle()->asset());
191                 if (iop) {
192                         cout << " in " << iop->language() << "\n";
193                 }
194                 shared_ptr<SMPTESubtitleAsset> smpte = dynamic_pointer_cast<SMPTESubtitleAsset> (reel->main_subtitle()->asset());
195                 if (smpte && smpte->language ()) {
196                         cout << " in " << smpte->language().get() << "\n";
197                 }
198                 if (list_subtitles) {
199                         BOOST_FOREACH (shared_ptr<Subtitle> k, subs) {
200                                 shared_ptr<SubtitleString> ks = dynamic_pointer_cast<SubtitleString> (k);
201                                 if (ks) {
202                                         cout << *ks << "\n";
203                                 }
204                                 shared_ptr<SubtitleImage> is = dynamic_pointer_cast<SubtitleImage> (k);
205                                 if (is) {
206                                         cout << *is << "\n";
207                                 }
208                         }
209                 }
210         } else {
211                 cout << " - not present in this DCP.\n";
212         }
213 }
214
215 int
216 main (int argc, char* argv[])
217 {
218         bool subtitles = false;
219         bool picture = false;
220         bool decompress = false;
221         bool ignore_missing_assets = false;
222         optional<boost::filesystem::path> kdm;
223         optional<boost::filesystem::path> private_key;
224
225         int option_index = 0;
226         while (true) {
227                 static struct option long_options[] = {
228                         { "version", no_argument, 0, 'v' },
229                         { "help", no_argument, 0, 'h' },
230                         { "subtitles", no_argument, 0, 's' },
231                         { "picture", no_argument, 0, 'p' },
232                         { "decompress", no_argument, 0, 'd' },
233                         { "ignore-missing-assets", no_argument, 0, 'A' },
234                         { "kdm", required_argument, 0, 'B' },
235                         { "private-key", required_argument, 0, 'C' },
236                         { 0, 0, 0, 0 }
237                 };
238
239                 int c = getopt_long (argc, argv, "vhspdAB:C:", long_options, &option_index);
240
241                 if (c == -1) {
242                         break;
243                 }
244
245                 switch (c) {
246                 case 'v':
247                         cout << "libdcp version " << LIBDCP_VERSION << "\n";
248                         exit (EXIT_SUCCESS);
249                 case 'h':
250                         help (argv[0]);
251                         exit (EXIT_SUCCESS);
252                 case 's':
253                         subtitles = true;
254                         break;
255                 case 'p':
256                         picture = true;
257                         break;
258                 case 'd':
259                         decompress = true;
260                         break;
261                 case 'A':
262                         ignore_missing_assets = true;
263                         break;
264                 case 'B':
265                         kdm = optarg;
266                         break;
267                 case 'C':
268                         private_key = optarg;
269                         break;
270                 }
271         }
272
273         if (argc <= optind || argc > (optind + 1)) {
274                 help (argv[0]);
275                 exit (EXIT_FAILURE);
276         }
277
278         if (!boost::filesystem::exists (argv[optind])) {
279                 cerr << argv[0] << ": DCP or CPL " << argv[optind] << " not found.\n";
280                 exit (EXIT_FAILURE);
281         }
282
283         list<shared_ptr<CPL> > cpls;
284         if (boost::filesystem::is_directory(argv[optind])) {
285                 DCP* dcp = 0;
286                 list<dcp::VerificationNote> notes;
287                 try {
288                         dcp = new DCP (argv[optind]);
289                         dcp->read (&notes);
290                         if (kdm && private_key) {
291                                 dcp->add(DecryptedKDM(EncryptedKDM(file_to_string(*kdm)), file_to_string(*private_key)));
292                         }
293                 } catch (FileError& e) {
294                         cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
295                         exit (EXIT_FAILURE);
296                 } catch (DCPReadError& e) {
297                         cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
298                         exit (EXIT_FAILURE);
299                 } catch (KDMDecryptionError& e) {
300                         cerr << e.what() << "\n";
301                         exit (EXIT_FAILURE);
302                 }
303
304                 cout << "DCP: " << boost::filesystem::path(argv[optind]).string() << "\n";
305
306                 dcp::filter_notes (notes, ignore_missing_assets);
307                 BOOST_FOREACH (dcp::VerificationNote i, notes) {
308                         cerr << "Error: " << note_to_string(i) << "\n";
309                 }
310
311                 cpls = dcp->cpls ();
312         } else {
313                 cpls.push_back (shared_ptr<CPL>(new CPL(boost::filesystem::path(argv[optind]))));
314                 ignore_missing_assets = true;
315         }
316
317         BOOST_FOREACH (shared_ptr<CPL> i, cpls) {
318                 cout << "  CPL: " << i->annotation_text() << "\n";
319
320                 int R = 1;
321                 BOOST_FOREACH (shared_ptr<Reel> j, i->reels()) {
322                         cout << "    Reel " << R << "\n";
323
324                         try {
325                                 main_picture (j, picture, decompress);
326                         } catch (UnresolvedRefError& e) {
327                                 if (!ignore_missing_assets) {
328                                         cerr << e.what() << " (for main picture)\n";
329                                 }
330                         }
331
332                         try {
333                                 main_sound (j);
334                         } catch (UnresolvedRefError& e) {
335                                 if (!ignore_missing_assets) {
336                                         cerr << e.what() << " (for main sound)\n";
337                                 }
338                         }
339
340                         try {
341                                 main_subtitle (j, subtitles);
342                         } catch (UnresolvedRefError& e) {
343                                 if (!ignore_missing_assets) {
344                                         cerr << e.what() << " (for main subtitle)\n";
345                                 }
346                         }
347
348                         ++R;
349                 }
350         }
351
352         return 0;
353 }