Cleanup: sorting.
[libdcp.git] / tools / dcpinfo.cc
index 19be6ecabaea189b5e27b88403e70e793a2d78e0..6a37be1c1f4941347e1d218a22f9774658e55007 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
 
     This file is part of libdcp.
 
     files in the program, then also delete it here.
 */
 
+
+#include "common.h"
+#include "compose.hpp"
+#include "cpl.h"
 #include "dcp.h"
+#include "decrypted_kdm.h"
+#include "encrypted_kdm.h"
 #include "exceptions.h"
-#include "reel.h"
-#include "sound_asset.h"
+#include "interop_subtitle_asset.h"
+#include "mono_picture_asset.h"
 #include "picture_asset.h"
-#include "subtitle_asset.h"
+#include "reel.h"
 #include "reel_picture_asset.h"
 #include "reel_sound_asset.h"
 #include "reel_subtitle_asset.h"
-#include "subtitle_string.h"
-#include "interop_subtitle_asset.h"
 #include "smpte_subtitle_asset.h"
-#include "mono_picture_asset.h"
-#include "cpl.h"
-#include "common.h"
+#include "sound_asset.h"
+#include "subtitle_asset.h"
+#include "subtitle_image.h"
+#include "subtitle_string.h"
 #include <getopt.h>
 #include <boost/filesystem.hpp>
-#include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
 #include <iostream>
 #include <cstdlib>
-#include <cinttypes>
+#include <sstream>
+#include <inttypes.h>
+
 
-using std::string;
 using std::cerr;
 using std::cout;
+using std::dynamic_pointer_cast;
+using std::exception;
 using std::list;
-using std::pair;
-using std::min;
 using std::max;
-using boost::shared_ptr;
-using boost::dynamic_pointer_cast;
+using std::min;
+using std::pair;
+using std::shared_ptr;
+using std::string;
+using std::stringstream;
+using std::vector;
+using boost::optional;
 using namespace dcp;
 
+
 static void
 help (string n)
 {
        cerr << "Syntax: " << n << " [options] [<DCP>] [<CPL>]\n"
             << "  -s, --subtitles              list all subtitles\n"
             << "  -p, --picture                analyse picture\n"
-            << "  -k, --keep-going             carry on in the event of errors, if possible\n"
+            << "  -d, --decompress             decompress picture when analysing (this is slow)\n"
+            << "  -o, --only                   only output certain pieces of information; see below.\n"
+            << "      --kdm                    KDM to decrypt DCP\n"
+            << "      --private-key            private key for the certificate that the KDM is targeted at\n"
             << "      --ignore-missing-assets  ignore missing asset files\n";
+
+       cerr << "--only takes a comma-separated list of strings, one or more of:\n"
+               "    dcp-path     DCP path\n"
+               "    cpl-name-id  CPL name and ID\n"
+               "    picture      picture information\n"
+               "    sound        sound information\n"
+               "    subtitle     picture information\n"
+               "    total-time   total DCP time\n";
 }
 
 static double
@@ -80,105 +103,197 @@ mbits_per_second (int size, Fraction frame_rate)
        return size * 8 * frame_rate.as_float() / 1e6;
 }
 
+#define OUTPUT_DCP_PATH(...)    maybe_output(only, "dcp-path", String::compose(__VA_ARGS__));
+#define OUTPUT_CPL_NAME_ID(...) maybe_output(only, "cpl-name-id", String::compose(__VA_ARGS__));
+#define OUTPUT_PICTURE(...)     maybe_output(only, "picture", String::compose(__VA_ARGS__));
+#define OUTPUT_PICTURE_NC(x)    maybe_output(only, "picture", (x));
+#define SHOULD_PICTURE          should_output(only, "picture")
+#define OUTPUT_SOUND(...)       maybe_output(only, "sound", String::compose(__VA_ARGS__));
+#define OUTPUT_SOUND_NC(x)      maybe_output(only, "sound", (x));
+#define OUTPUT_SUBTITLE(...)    maybe_output(only, "subtitle", String::compose(__VA_ARGS__));
+#define OUTPUT_SUBTITLE_NC(x)   maybe_output(only, "subtitle", (x));
+#define OUTPUT_TOTAL_TIME(...)  maybe_output(only, "total-time", String::compose(__VA_ARGS__));
+
+static bool
+should_output(vector<string> const& only, string t)
+{
+       return only.empty() || find(only.begin(), only.end(), t) != only.end();
+}
+
 static void
-main_picture (shared_ptr<Reel> reel, bool analyse)
+maybe_output(vector<string> const& only, string t, string s)
 {
-       if (!reel->main_picture()) {
-               return;
+       if (should_output(only, t)) {
+               cout << s;
        }
+}
 
-       cout << "      Picture ID:  " << reel->main_picture()->id()
-            << " entry " << reel->main_picture()->entry_point()
-            << " duration " << reel->main_picture()->duration()
-            << " intrinsic " << reel->main_picture()->intrinsic_duration();
-
-       if (reel->main_picture()->asset_ref().resolved()) {
-               if (reel->main_picture()->asset()) {
-                       cout << "\n      Picture:     "
-                            << reel->main_picture()->asset()->size().width
-                            << "x"
-                            << reel->main_picture()->asset()->size().height << "\n";
+static
+dcp::Time
+main_picture (vector<string> const& only, shared_ptr<Reel> reel, bool analyse, bool decompress)
+{
+       shared_ptr<dcp::ReelPictureAsset> mp = reel->main_picture ();
+       if (!mp) {
+               return dcp::Time();
+       }
+
+       OUTPUT_PICTURE("      Picture ID:  %1", mp->id());
+       if (mp->entry_point()) {
+               OUTPUT_PICTURE(" entry %1", *mp->entry_point());
+       }
+       if (mp->duration()) {
+               OUTPUT_PICTURE(
+                       " duration %1 (%2) intrinsic %3",
+                       *mp->duration(),
+                       dcp::Time(*mp->duration(), mp->frame_rate().as_float(), mp->frame_rate().as_float()).as_string(dcp::Standard::SMPTE),
+                       mp->intrinsic_duration()
+                       );
+       } else {
+               OUTPUT_PICTURE(" intrinsic duration %1", mp->intrinsic_duration());
+       }
+
+       if (mp->asset_ref().resolved()) {
+               if (mp->asset()) {
+                       OUTPUT_PICTURE("\n      Picture:     %1x%2\n", mp->asset()->size().width, mp->asset()->size().height);
                }
 
-               shared_ptr<MonoPictureAsset> ma = dynamic_pointer_cast<MonoPictureAsset>(reel->main_picture()->asset());
+               shared_ptr<MonoPictureAsset> ma = dynamic_pointer_cast<MonoPictureAsset>(mp->asset());
                if (analyse && ma) {
                        shared_ptr<MonoPictureAssetReader> reader = ma->start_read ();
                        pair<int, int> j2k_size_range (INT_MAX, 0);
                        for (int64_t i = 0; i < ma->intrinsic_duration(); ++i) {
                                shared_ptr<const MonoPictureFrame> frame = reader->get_frame (i);
-                               printf("Frame %" PRId64 " J2K size %7d\n", i, frame->j2k_size());
-                               j2k_size_range.first = min(j2k_size_range.first, frame->j2k_size());
-                               j2k_size_range.second = max(j2k_size_range.second, frame->j2k_size());
+                               if (SHOULD_PICTURE) {
+                                       printf("Frame %" PRId64 " J2K size %7d", i, frame->size());
+                               }
+                               j2k_size_range.first = min(j2k_size_range.first, frame->size());
+                               j2k_size_range.second = max(j2k_size_range.second, frame->size());
+
+                               if (decompress) {
+                                       try {
+                                               frame->xyz_image();
+                                               if (SHOULD_PICTURE) {
+                                                       printf(" decrypted OK");
+                                               }
+                                       } catch (exception& e) {
+                                               if (SHOULD_PICTURE) {
+                                                       printf(" decryption FAILED");
+                                               }
+                                       }
+                               }
+
+                               if (SHOULD_PICTURE) {
+                                       printf("\n");
+                               }
+
+                       }
+                       if (SHOULD_PICTURE) {
+                               printf(
+                                               "J2K size ranges from %d (%.1f Mbit/s) to %d (%.1f Mbit/s)\n",
+                                               j2k_size_range.first, mbits_per_second(j2k_size_range.first, ma->frame_rate()),
+                                               j2k_size_range.second, mbits_per_second(j2k_size_range.second, ma->frame_rate())
+                                     );
                        }
-                       printf(
-                               "J2K size ranges from %d (%.1f Mbit/s) to %d (%.1f Mbit/s)\n",
-                               j2k_size_range.first, mbits_per_second(j2k_size_range.first, ma->frame_rate()),
-                               j2k_size_range.second, mbits_per_second(j2k_size_range.second, ma->frame_rate())
-                               );
                }
        } else {
-               cout << " - not present in this DCP.\n";
+               OUTPUT_PICTURE_NC(" - not present in this DCP.\n");
        }
+
+       return dcp::Time (
+                       mp->duration().get_value_or(mp->intrinsic_duration()),
+                       mp->frame_rate().as_float(),
+                       mp->frame_rate().as_float()
+                       );
 }
 
-static void
-main_sound (shared_ptr<Reel> reel)
+static
+void
+main_sound (vector<string> const& only, shared_ptr<Reel> reel)
 {
-       if (reel->main_sound()) {
-               cout << "      Sound ID:    " << reel->main_sound()->id()
-                    << " entry " << reel->main_picture()->entry_point()
-                    << " duration " << reel->main_picture()->duration()
-                    << " intrinsic " << reel->main_picture()->intrinsic_duration();
-               if (reel->main_sound()->asset_ref().resolved()) {
-                       if (reel->main_sound()->asset()) {
-                               cout << "\n      Sound:       "
-                                    << reel->main_sound()->asset()->channels()
-                                    << " channels at "
-                                    << reel->main_sound()->asset()->sampling_rate() << "Hz\n";
-                       }
-               } else {
-                       cout << " - not present in this DCP.\n";
+       shared_ptr<dcp::ReelSoundAsset> ms = reel->main_sound ();
+       if (!ms) {
+               return;
+       }
+
+       OUTPUT_SOUND("      Sound ID:    %1", ms->id());
+       if (ms->entry_point()) {
+               OUTPUT_SOUND(" entry %1", *ms->entry_point());
+       }
+       if (ms->duration()) {
+               OUTPUT_SOUND(" duration %1 intrinsic %2", *ms->duration(), ms->intrinsic_duration());
+       } else {
+               OUTPUT_SOUND(" intrinsic duration %1", ms->intrinsic_duration());
+       }
+
+       if (ms->asset_ref().resolved()) {
+               if (ms->asset()) {
+                       OUTPUT_SOUND(
+                               "\n      Sound:       %1 channels at %2Hz\n",
+                               ms->asset()->channels(),
+                               ms->asset()->sampling_rate()
+                               );
                }
+       } else {
+               OUTPUT_SOUND_NC(" - not present in this DCP.\n");
        }
 }
 
-static void
-main_subtitle (shared_ptr<Reel> reel, bool list_subtitles)
+static
+void
+main_subtitle (vector<string> const& only, shared_ptr<Reel> reel, bool list_subtitles)
 {
-       if (!reel->main_subtitle()) {
+       shared_ptr<dcp::ReelSubtitleAsset> ms = reel->main_subtitle ();
+       if (!ms) {
                return;
        }
 
-       cout << "      Subtitle ID: " << reel->main_subtitle()->id();
+       OUTPUT_SUBTITLE("      Subtitle ID: %1", ms->id());
 
-       if (reel->main_subtitle()->asset_ref().resolved()) {
-               list<SubtitleString> subs = reel->main_subtitle()->asset()->subtitles ();
-               cout << "\n      Subtitle:    " << subs.size() << " subtitles";
-               shared_ptr<InteropSubtitleAsset> iop = dynamic_pointer_cast<InteropSubtitleAsset> (reel->main_subtitle()->asset());
+       if (ms->asset_ref().resolved()) {
+               auto subs = ms->asset()->subtitles ();
+               OUTPUT_SUBTITLE("\n      Subtitle:    %1 subtitles", subs.size());
+               shared_ptr<InteropSubtitleAsset> iop = dynamic_pointer_cast<InteropSubtitleAsset> (ms->asset());
                if (iop) {
-                       cout << " in " << iop->language() << "\n";
+                       OUTPUT_SUBTITLE(" in %1\n", iop->language());
                }
-               shared_ptr<SMPTESubtitleAsset> smpte = dynamic_pointer_cast<SMPTESubtitleAsset> (reel->main_subtitle()->asset());
+               shared_ptr<SMPTESubtitleAsset> smpte = dynamic_pointer_cast<SMPTESubtitleAsset> (ms->asset());
                if (smpte && smpte->language ()) {
-                       cout << " in " << smpte->language().get() << "\n";
+                       OUTPUT_SUBTITLE(" in %1\n", smpte->language().get());
                }
                if (list_subtitles) {
-                       BOOST_FOREACH (SubtitleString const& k, subs) {
-                               cout << k << "\n";
+                       for (auto k: subs) {
+                               auto ks = dynamic_pointer_cast<const SubtitleString>(k);
+                               if (ks) {
+                                       stringstream s;
+                                       s << *ks;
+                                       OUTPUT_SUBTITLE("%1\n", s.str());
+                               }
+                               auto is = dynamic_pointer_cast<const SubtitleImage>(k);
+                               if (is) {
+                                       stringstream s;
+                                       s << *is;
+                                       OUTPUT_SUBTITLE("%1\n", s.str());
+                               }
                        }
                }
        } else {
-               cout << " - not present in this DCP.\n";
+               OUTPUT_SUBTITLE_NC(" - not present in this DCP.\n");
        }
 }
 
+
 int
 main (int argc, char* argv[])
 {
+       dcp::init ();
+
        bool subtitles = false;
-       bool keep_going = false;
        bool picture = false;
+       bool decompress = false;
        bool ignore_missing_assets = false;
+       optional<boost::filesystem::path> kdm;
+       optional<boost::filesystem::path> private_key;
+       optional<string> only_string;
 
        int option_index = 0;
        while (true) {
@@ -186,13 +301,16 @@ main (int argc, char* argv[])
                        { "version", no_argument, 0, 'v' },
                        { "help", no_argument, 0, 'h' },
                        { "subtitles", no_argument, 0, 's' },
-                       { "keep-going", no_argument, 0, 'k' },
                        { "picture", no_argument, 0, 'p' },
+                       { "decompress", no_argument, 0, 'd' },
+                       { "only", required_argument, 0, 'o' },
                        { "ignore-missing-assets", no_argument, 0, 'A' },
+                       { "kdm", required_argument, 0, 'B' },
+                       { "private-key", required_argument, 0, 'C' },
                        { 0, 0, 0, 0 }
                };
 
-               int c = getopt_long (argc, argv, "vhskpA", long_options, &option_index);
+               int c = getopt_long (argc, argv, "vhspdo:AB:C:", long_options, &option_index);
 
                if (c == -1) {
                        break;
@@ -200,7 +318,7 @@ main (int argc, char* argv[])
 
                switch (c) {
                case 'v':
-                       cout << "libdcp version " << LIBDCP_VERSION << "\n";
+                       cout << "libdcp version " << dcp::version << "\n";
                        exit (EXIT_SUCCESS);
                case 'h':
                        help (argv[0]);
@@ -208,15 +326,24 @@ main (int argc, char* argv[])
                case 's':
                        subtitles = true;
                        break;
-               case 'k':
-                       keep_going = true;
-                       break;
                case 'p':
                        picture = true;
                        break;
+               case 'd':
+                       decompress = true;
+                       break;
+               case 'o':
+                       only_string = optarg;
+                       break;
                case 'A':
                        ignore_missing_assets = true;
                        break;
+               case 'B':
+                       kdm = optarg;
+                       break;
+               case 'C':
+                       private_key = optarg;
+                       break;
                }
        }
 
@@ -230,75 +357,77 @@ main (int argc, char* argv[])
                exit (EXIT_FAILURE);
        }
 
-       list<shared_ptr<CPL> > cpls;
+       vector<string> only;
+       if (only_string) {
+               only = boost::split(only, *only_string, boost::is_any_of(","));
+       }
+
+       vector<shared_ptr<CPL>> cpls;
        if (boost::filesystem::is_directory(argv[optind])) {
                DCP* dcp = 0;
-               DCP::ReadErrors errors;
+               vector<dcp::VerificationNote> notes;
                try {
                        dcp = new DCP (argv[optind]);
-                       dcp->read (keep_going, &errors);
+                       dcp->read (&notes);
+                       if (kdm && private_key) {
+                               dcp->add(DecryptedKDM(EncryptedKDM(file_to_string(*kdm)), file_to_string(*private_key)));
+                       }
                } catch (FileError& e) {
                        cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
                        exit (EXIT_FAILURE);
-               } catch (DCPReadError& e) {
+               } catch (ReadError& e) {
                        cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n";
                        exit (EXIT_FAILURE);
+               } catch (KDMDecryptionError& e) {
+                       cerr << e.what() << "\n";
+                       exit (EXIT_FAILURE);
                }
 
-               cout << "DCP: " << boost::filesystem::path(argv[optind]).string() << "\n";
+               OUTPUT_DCP_PATH("DCP: %1\n", boost::filesystem::path(argv[optind]).string());
 
-               dcp::filter_errors (errors, ignore_missing_assets);
-               for (DCP::ReadErrors::const_iterator i = errors.begin(); i != errors.end(); ++i) {
-                       cerr << "Error: " << (*i)->what() << "\n";
+               dcp::filter_notes (notes, ignore_missing_assets);
+               for (auto i: notes) {
+                       cerr << "Error: " << note_to_string(i) << "\n";
                }
 
                cpls = dcp->cpls ();
        } else {
                cpls.push_back (shared_ptr<CPL>(new CPL(boost::filesystem::path(argv[optind]))));
-               keep_going = true;
                ignore_missing_assets = true;
        }
 
-       BOOST_FOREACH (shared_ptr<CPL> i, cpls) {
-               cout << "  CPL: " << i->annotation_text() << "\n";
+       dcp::Time total_time;
+
+       for (auto i: cpls) {
+               OUTPUT_CPL_NAME_ID("  CPL: %1 %2\n", i->annotation_text().get_value_or(""), i->id());
 
                int R = 1;
-               BOOST_FOREACH (shared_ptr<Reel> j, i->reels()) {
-                       cout << "    Reel " << R << "\n";
+               for (auto j: i->reels()) {
+                       if (should_output(only, "picture") || should_output(only, "sound") || should_output(only, "subtitle")) {
+                               cout << "    Reel " << R << "\n";
+                       }
 
                        try {
-                               main_picture (j, picture);
+                               total_time += main_picture(only, j, picture, decompress);
                        } catch (UnresolvedRefError& e) {
-                               if (keep_going) {
-                                       if (!ignore_missing_assets) {
-                                               cerr << e.what() << " (for main picture)\n";
-                                       }
-                               } else {
-                                       throw;
+                               if (!ignore_missing_assets) {
+                                       cerr << e.what() << " (for main picture)\n";
                                }
                        }
 
                        try {
-                               main_sound (j);
+                               main_sound(only, j);
                        } catch (UnresolvedRefError& e) {
-                               if (keep_going) {
-                                       if (!ignore_missing_assets) {
-                                               cerr << e.what() << " (for main sound)\n";
-                                       }
-                               } else {
-                                       throw;
+                               if (!ignore_missing_assets) {
+                                       cerr << e.what() << " (for main sound)\n";
                                }
                        }
 
                        try {
-                               main_subtitle (j, subtitles);
+                               main_subtitle (only, j, subtitles);
                        } catch (UnresolvedRefError& e) {
-                               if (keep_going) {
-                                       if (!ignore_missing_assets) {
-                                               cerr << e.what() << " (for main subtitle)\n";
-                                       }
-                               } else {
-                                       throw;
+                               if (!ignore_missing_assets) {
+                                       cerr << e.what() << " (for main subtitle)\n";
                                }
                        }
 
@@ -306,5 +435,7 @@ main (int argc, char* argv[])
                }
        }
 
+       OUTPUT_TOTAL_TIME("Total: %1\n", total_time.as_string(dcp::Standard::SMPTE));
+
        return 0;
 }