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