X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=tools%2Fdcpinfo.cc;h=c85198587947ce876f73bbace97ccafae09a7af4;hb=1deb04653db2074891ca7be9e2165e7ffc776bf6;hp=2779b76a5e24a090c3e27207e7e1568d55f01c47;hpb=618962440a3474ab304772cda7b6aed08bb6a00a;p=libdcp.git diff --git a/tools/dcpinfo.cc b/tools/dcpinfo.cc index 2779b76a..c8519858 100644 --- a/tools/dcpinfo.cc +++ b/tools/dcpinfo.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2014 Carl Hetherington + Copyright (C) 2012-2021 Carl Hetherington This file is part of libdcp. @@ -41,85 +41,256 @@ #include "reel_sound_asset.h" #include "reel_subtitle_asset.h" #include "subtitle_string.h" +#include "subtitle_image.h" #include "interop_subtitle_asset.h" #include "smpte_subtitle_asset.h" +#include "mono_picture_asset.h" +#include "encrypted_kdm.h" +#include "decrypted_kdm.h" #include "cpl.h" #include "common.h" +#include "compose.hpp" #include #include -#include +#include #include #include +#include +#include using std::string; using std::cerr; using std::cout; using std::list; -using boost::shared_ptr; -using boost::dynamic_pointer_cast; +using std::pair; +using std::min; +using std::max; +using std::exception; +using std::vector; +using std::stringstream; +using std::shared_ptr; +using std::dynamic_pointer_cast; +using boost::optional; using namespace dcp; static void help (string n) { - cerr << "Syntax: " << n << " [options] \n" + cerr << "Syntax: " << n << " [options] [] []\n" << " -s, --subtitles list all subtitles\n" - << " -k, --keep-going carry on in the event of errors, if possible\n" + << " -p, --picture analyse picture\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 +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 const& only, string t) +{ + return only.empty() || find(only.begin(), only.end(), t) != only.end(); } static void -main_picture (shared_ptr reel) +maybe_output(vector const& only, string t, string s) { - if (reel->main_picture() && reel->main_picture()->asset()) { - cout << " Picture: " - << reel->main_picture()->asset()->size().width - << "x" - << reel->main_picture()->asset()->size().height << "\n"; + if (should_output(only, t)) { + cout << s; } } -static void -main_sound (shared_ptr reel) +static +dcp::Time +main_picture (vector const& only, shared_ptr reel, bool analyse, bool decompress) { - if (reel->main_sound() && reel->main_sound()->asset()) { - cout << " Sound: " - << reel->main_sound()->asset()->channels() - << " channels at " - << reel->main_sound()->asset()->sampling_rate() << "Hz\n"; + shared_ptr 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 ma = dynamic_pointer_cast(mp->asset()); + if (analyse && ma) { + shared_ptr reader = ma->start_read (); + pair j2k_size_range (INT_MAX, 0); + for (int64_t i = 0; i < ma->intrinsic_duration(); ++i) { + shared_ptr frame = reader->get_frame (i); + 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()) + ); + } + } + } else { + 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_subtitle (shared_ptr reel, bool list_subtitles) +static +void +main_sound (vector const& only, shared_ptr reel) { - if (!reel->main_subtitle()) { + shared_ptr ms = reel->main_sound (); + if (!ms) { return; } - list subs = reel->main_subtitle()->asset()->subtitles (); - cout << " Subtitle: " << subs.size() << " subtitles"; - shared_ptr iop = dynamic_pointer_cast (reel->main_subtitle()->asset()); - if (iop) { - cout << " in " << iop->language() << "\n"; + 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"); } - shared_ptr smpte = dynamic_pointer_cast (reel->main_subtitle()->asset()); - if (smpte && smpte->language ()) { - cout << " in " << smpte->language().get() << "\n"; +} + +static +void +main_subtitle (vector const& only, shared_ptr reel, bool list_subtitles) +{ + shared_ptr ms = reel->main_subtitle (); + if (!ms) { + return; } - if (list_subtitles) { - BOOST_FOREACH (SubtitleString const& k, subs) { - cout << k << "\n"; + + OUTPUT_SUBTITLE(" Subtitle ID: %1", ms->id()); + + if (ms->asset_ref().resolved()) { + auto subs = ms->asset()->subtitles (); + OUTPUT_SUBTITLE("\n Subtitle: %1 subtitles", subs.size()); + shared_ptr iop = dynamic_pointer_cast (ms->asset()); + if (iop) { + OUTPUT_SUBTITLE(" in %1\n", iop->language()); } + shared_ptr smpte = dynamic_pointer_cast (ms->asset()); + if (smpte && smpte->language ()) { + OUTPUT_SUBTITLE(" in %1\n", smpte->language().get()); + } + if (list_subtitles) { + for (auto k: subs) { + auto ks = dynamic_pointer_cast(k); + if (ks) { + stringstream s; + s << *ks; + OUTPUT_SUBTITLE("%1\n", s.str()); + } + auto is = dynamic_pointer_cast(k); + if (is) { + stringstream s; + s << *is; + OUTPUT_SUBTITLE("%1\n", s.str()); + } + } + } + } else { + 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 kdm; + optional private_key; + optional only_string; int option_index = 0; while (true) { @@ -127,12 +298,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, "vhskA", long_options, &option_index); + int c = getopt_long (argc, argv, "vhspdo:AB:C:", long_options, &option_index); if (c == -1) { break; @@ -148,12 +323,24 @@ main (int argc, char* argv[]) case 's': subtitles = true; break; - case 'k': - keep_going = true; + 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; } } @@ -163,74 +350,81 @@ main (int argc, char* argv[]) } if (!boost::filesystem::exists (argv[optind])) { - cerr << argv[0] << ": DCP " << argv[optind] << " not found.\n"; + cerr << argv[0] << ": DCP or CPL " << argv[optind] << " not found.\n"; exit (EXIT_FAILURE); } - DCP* dcp = 0; - DCP::ReadErrors errors; - try { - dcp = new DCP (argv[optind]); - dcp->read (keep_going, &errors); - } catch (FileError& e) { - cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n"; - exit (EXIT_FAILURE); - } catch (DCPReadError& e) { - cerr << "Could not read DCP " << argv[optind] << "; " << e.what() << "\n"; - exit (EXIT_FAILURE); + vector only; + if (only_string) { + only = boost::split(only, *only_string, boost::is_any_of(",")); } - cout << "DCP: " << boost::filesystem::path(argv[optind]).filename().string() << "\n"; + vector > cpls; + if (boost::filesystem::is_directory(argv[optind])) { + DCP* dcp = 0; + vector notes; + try { + dcp = new DCP (argv[optind]); + dcp->read (¬es); + 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 (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); + } - dcp::filter_errors (errors, ignore_missing_assets); - for (DCP::ReadErrors::const_iterator i = errors.begin(); i != errors.end(); ++i) { - cerr << "Error: " << (*i)->what() << "\n"; - } + OUTPUT_DCP_PATH("DCP: %1\n", boost::filesystem::path(argv[optind]).string()); - list > cpls = dcp->cpls (); + dcp::filter_notes (notes, ignore_missing_assets); + for (auto i: notes) { + cerr << "Error: " << note_to_string(i) << "\n"; + } - for (list >::iterator i = cpls.begin(); i != cpls.end(); ++i) { - cout << " CPL: " << (*i)->annotation_text() << "\n"; + cpls = dcp->cpls (); + } else { + cpls.push_back (shared_ptr(new CPL(boost::filesystem::path(argv[optind])))); + ignore_missing_assets = true; + } + + dcp::Time total_time; - list > reels = (*i)->reels (); + for (auto i: cpls) { + OUTPUT_CPL_NAME_ID(" CPL: %1 %2\n", i->annotation_text().get_value_or(""), i->id()); int R = 1; - for (list >::const_iterator j = reels.begin(); j != reels.end(); ++j) { - 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); + 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"; } } @@ -238,5 +432,7 @@ main (int argc, char* argv[]) } } + OUTPUT_TOTAL_TIME("Total: %1\n", total_time.as_string(dcp::Standard::SMPTE)); + return 0; }