/*
- Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2022 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
*/
+
+#include "lib/ansi.h"
+#include "lib/audio_content.h"
+#include "lib/config.h"
+#include "lib/cross.h"
+#include "lib/dcpomatic_log.h"
+#include "lib/encode_server_finder.h"
+#include "lib/ffmpeg_encoder.h"
#include "lib/film.h"
#include "lib/filter.h"
-#include "lib/transcode_job.h"
+#ifdef DCPOMATIC_GROK
+#include "lib/grok/context.h"
+#endif
+#include "lib/hints.h"
#include "lib/job_manager.h"
-#include "lib/util.h"
-#include "lib/version.h"
-#include "lib/cross.h"
-#include "lib/config.h"
-#include "lib/log.h"
-#include "lib/signal_manager.h"
-#include "lib/encode_server_finder.h"
#include "lib/json_server.h"
+#include "lib/log.h"
+#include "lib/make_dcp.h"
#include "lib/ratio.h"
+#include "lib/signal_manager.h"
+#include "lib/transcode_job.h"
+#include "lib/util.h"
+#include "lib/version.h"
#include "lib/video_content.h"
-#include "lib/audio_content.h"
-#include "lib/dcpomatic_log.h"
+#include <dcp/filesystem.h>
#include <dcp/version.h>
#include <getopt.h>
#include <iostream>
#include <iomanip>
-using std::string;
+
using std::cerr;
using std::cout;
-using std::vector;
+using std::dynamic_pointer_cast;
+using std::list;
using std::pair;
+using std::runtime_error;
using std::setw;
-using std::list;
using std::shared_ptr;
+using std::string;
+using std::vector;
using boost::optional;
-using std::dynamic_pointer_cast;
+
static void
help (string n)
{
cerr << "Syntax: " << n << " [OPTION] [<FILM>]\n"
- << " -v, --version show DCP-o-matic version\n"
- << " -h, --help show this help\n"
- << " -f, --flags show flags passed to C++ compiler on build\n"
- << " -n, --no-progress do not print progress to stdout\n"
- << " -r, --no-remote do not use any remote servers\n"
- << " -t, --threads specify number of local encoding threads (overriding configuration)\n"
- << " -j, --json <port> run a JSON server on the specified port\n"
- << " -k, --keep-going keep running even when the job is complete\n"
- << " -s, --servers <file> specify servers to use in a text file\n"
- << " -l, --list-servers just display a list of encoding servers that DCP-o-matic is configured to use; don't encode\n"
- << " -d, --dcp-path echo DCP's path to stdout on successful completion (implies -n)\n"
- << " -c, --config <dir> directory containing config.xml and cinemas.xml\n"
- << " --dump just dump a summary of the film's settings; don't encode\n"
- << " --no-check don't check project's content files for changes before making the DCP\n"
+ << " -v, --version show DCP-o-matic version\n"
+ << " -h, --help show this help\n"
+ << " -f, --flags show flags passed to C++ compiler on build\n"
+ << " -n, --no-progress do not print progress to stdout\n"
+ << " -r, --no-remote do not use any remote servers\n"
+ << " -t, --threads specify number of local encoding threads (overriding configuration)\n"
+ << " -j, --json <port> run a JSON server on the specified port\n"
+ << " -k, --keep-going keep running even when the job is complete\n"
+ << " -s, --servers <file> specify servers to use in a text file\n"
+ << " -l, --list-servers just display a list of encoding servers that DCP-o-matic is configured to use; don't encode\n"
+ << " -d, --dcp-path echo DCP's path to stdout on successful completion (implies -n)\n"
+ << " -c, --config <dir> directory containing config.xml and cinemas.xml\n"
+ << " --dump just dump a summary of the film's settings; don't encode\n"
+ << " --no-check don't check project's content files for changes before making the DCP\n"
+ << " --export-format <format> export project to a file, rather than making a DCP: specify mov or mp4\n"
+ << " --export-filename <filename> filename to export to with --export-format\n"
+ << " --hints analyze film for hints before encoding and abort if any are found\n"
<< "\n"
<< "<FILM> is the film directory.\n";
}
+
static void
print_dump (shared_ptr<Film> film)
{
cout << film->dcp_name (true) << "\n"
- << film->container()->container_nickname() << " at " << ((film->resolution() == RESOLUTION_2K) ? "2K" : "4K") << "\n"
+ << film->container()->container_nickname() << " at " << ((film->resolution() == Resolution::TWO_K) ? "2K" : "4K") << "\n"
<< (film->j2k_bandwidth() / 1000000) << "Mbit/s" << "\n"
<< "Output " << film->video_frame_rate() << "fps " << (film->three_d() ? "3D" : "2D") << " " << (film->audio_frame_rate() / 1000) << "kHz\n"
<< (film->interop() ? "Inter-Op" : "SMPTE") << " " << (film->encrypted() ? "encrypted" : "unencrypted") << "\n";
if (c->video) {
cout << "\t" << c->video->size().width << "x" << c->video->size().height << "\n"
<< "\t" << c->active_video_frame_rate(film) << "fps\n"
- << "\tcrop left " << c->video->left_crop()
- << " right " << c->video->right_crop()
- << " top " << c->video->top_crop()
- << " bottom " << c->video->bottom_crop() << "\n";
+ << "\tcrop left " << c->video->requested_left_crop()
+ << " right " << c->video->requested_right_crop()
+ << " top " << c->video->requested_top_crop()
+ << " bottom " << c->video->requested_bottom_crop() << "\n";
if (c->video->custom_ratio()) {
cout << "\tscale to custom ratio " << *c->video->custom_ratio() << ":1\n";
}
}
}
+
static void
list_servers ()
{
while (true) {
int N = 0;
- list<EncodeServerDescription> servers = EncodeServerFinder::instance()->servers();
+ auto servers = EncodeServerFinder::instance()->servers();
/* This is a bit fiddly because we want to list configured servers that are down as well
as all those (configured and found by broadcast) that are up.
the number of threads it is offering.
*/
optional<int> threads;
- list<EncodeServerDescription>::iterator j = servers.begin ();
+ auto j = servers.begin ();
while (j != servers.end ()) {
if (i == j->host_name() && j->current_link_version()) {
threads = j->threads();
- list<EncodeServerDescription>::iterator tmp = j;
+ auto tmp = j;
++tmp;
servers.erase (j);
j = tmp;
}
+bool
+show_jobs_on_console (bool progress)
+{
+ bool first = true;
+ bool error = false;
+ while (true) {
+
+ dcpomatic_sleep_seconds (5);
+
+ auto jobs = JobManager::instance()->get();
+
+ if (!first && progress) {
+ for (size_t i = 0; i < jobs.size(); ++i) {
+ cout << UP_ONE_LINE_AND_ERASE;
+ }
+ cout.flush ();
+ }
+
+ first = false;
+
+ for (auto i: jobs) {
+ if (progress) {
+ cout << i->name();
+ if (!i->sub_name().empty()) {
+ cout << "; " << i->sub_name();
+ }
+ cout << ": ";
+
+ if (i->progress ()) {
+ cout << i->status() << " \n";
+ } else {
+ cout << ": Running \n";
+ }
+ }
+
+ if (!progress && i->finished_in_error()) {
+ /* We won't see this error if we haven't been showing progress,
+ so show it now.
+ */
+ cout << i->status() << "\n";
+ }
+
+ if (i->finished_in_error()) {
+ error = true;
+ }
+ }
+
+ if (!JobManager::instance()->work_to_do()) {
+ break;
+ }
+ }
+
+ return error;
+}
+
+
int
main (int argc, char* argv[])
{
bool dcp_path = false;
optional<boost::filesystem::path> config;
bool check = true;
+ optional<string> export_format;
+ optional<boost::filesystem::path> export_filename;
+ bool hints = false;
int option_index = 0;
while (true) {
/* Just using A, B, C ... from here on */
{ "dump", no_argument, 0, 'A' },
{ "no-check", no_argument, 0, 'B' },
+ { "export-format", required_argument, 0, 'C' },
+ { "export-filename", required_argument, 0, 'D' },
+ { "hints", no_argument, 0, 'E' },
{ 0, 0, 0, 0 }
};
- int c = getopt_long (argc, argv, "vhfnrt:j:kAs:ldc:B", long_options, &option_index);
+ int c = getopt_long (argc, argv, "vhfnrt:j:kAs:ldc:BC:D:E", long_options, &option_index);
if (c == -1) {
break;
case 'B':
check = false;
break;
+ case 'C':
+ export_format = optarg;
+ break;
+ case 'D':
+ export_filename = optarg;
+ break;
+ case 'E':
+ hints = true;
+ break;
}
}
}
if (servers) {
- FILE* f = fopen_boost (*servers, "r");
+ dcp::File f(*servers, "r");
if (!f) {
cerr << "Could not open servers list file " << *servers << "\n";
exit (EXIT_FAILURE);
}
vector<string> servers;
- while (!feof (f)) {
+ while (!f.eof()) {
char buffer[128];
- if (fscanf (f, "%s.127", buffer) == 1) {
+ if (fscanf(f.get(), "%s.127", buffer) == 1) {
servers.push_back (buffer);
}
}
- fclose (f);
Config::instance()->set_servers (servers);
}
exit (EXIT_FAILURE);
}
+ if (export_format && !export_filename) {
+ cerr << "Argument --export-filename is required with --export-format\n";
+ exit (EXIT_FAILURE);
+ }
+
+ if (!export_format && export_filename) {
+ cerr << "Argument --export-format is required with --export-filename\n";
+ exit (EXIT_FAILURE);
+ }
+
+ if (export_format && *export_format != "mp4" && *export_format != "mov") {
+ cerr << "Unrecognised export format: must be mp4 or mov\n";
+ exit (EXIT_FAILURE);
+ }
+
film_dir = argv[optind];
dcpomatic_setup_path_encoding ();
dcpomatic_setup ();
signal_manager = new SignalManager ();
- if (no_remote) {
- EncodeServerFinder::instance()->stop ();
+ if (no_remote || export_format) {
+ EncodeServerFinder::drop();
}
if (json_port) {
dcpomatic_log = film->log ();
- ContentList content = film->content ();
- for (ContentList::const_iterator i = content.begin(); i != content.end(); ++i) {
- vector<boost::filesystem::path> paths = (*i)->paths ();
- for (vector<boost::filesystem::path>::const_iterator j = paths.begin(); j != paths.end(); ++j) {
- if (!boost::filesystem::exists (*j)) {
- cerr << argv[0] << ": content file " << *j << " not found.\n";
+ for (auto i: film->content()) {
+ auto paths = i->paths();
+ for (auto j: paths) {
+ if (!dcp::filesystem::exists(j)) {
+ cerr << argv[0] << ": content file " << j << " not found.\n";
exit (EXIT_FAILURE);
}
}
}
+ if (!export_format && hints) {
+ string const prefix = "Checking project for hints";
+ bool pulse_phase = false;
+ vector<string> hints;
+ bool finished = false;
+
+ Hints hint_finder(film);
+ hint_finder.Progress.connect([prefix](string progress) {
+ std::cout << UP_ONE_LINE_AND_ERASE << prefix << ": " << progress << "\n";
+ std::cout.flush();
+ });
+ hint_finder.Pulse.connect([prefix, &pulse_phase]() {
+ std::cout << UP_ONE_LINE_AND_ERASE << prefix << ": " << (pulse_phase ? "X" : "x") << "\n";
+ std::cout.flush();
+ pulse_phase = !pulse_phase;
+ });
+ hint_finder.Hint.connect([&hints](string hint) {
+ hints.push_back(hint);
+ });
+ hint_finder.Finished.connect([&finished]() {
+ finished = true;
+ });
+
+ std::cout << prefix << ":\n";
+ std::cout.flush();
+
+ hint_finder.start();
+ while (!finished) {
+ signal_manager->ui_idle();
+ dcpomatic_sleep_milliseconds(200);
+ }
+
+ std::cout << UP_ONE_LINE_AND_ERASE;
+
+ if (!hints.empty()) {
+ std::cerr << "Hints:\n\n";
+ for (auto hint: hints) {
+ std::cerr << word_wrap("* " + hint, 70) << "\n";
+ }
+ std::cerr << "*** Encoding aborted because hints were found ***\n\n";
+ std::cerr << "Modify your settings and run the command again, or run without\n";
+ std::cerr << "the `--hints' option to ignore these hints and encode anyway.\n";
+ exit(EXIT_FAILURE);
+ }
+ }
+
+#ifdef DCPOMATIC_GROK
+ grk_plugin::setMessengerLogger(new grk_plugin::GrokLogger("[GROK] "));
+#endif
+
if (progress) {
- cout << "\nMaking DCP for " << film->name() << "\n";
+ if (export_format) {
+ cout << "\nExporting " << film->name() << "\n";
+ } else {
+ cout << "\nMaking DCP for " << film->name() << "\n";
+ }
+ }
+
+ TranscodeJob::ChangedBehaviour const behaviour = check ? TranscodeJob::ChangedBehaviour::STOP : TranscodeJob::ChangedBehaviour::IGNORE;
+
+ if (export_format) {
+ auto job = std::make_shared<TranscodeJob>(film, behaviour);
+ job->set_encoder (
+ std::make_shared<FFmpegEncoder> (
+ film, job, *export_filename, *export_format == "mp4" ? ExportFormat::H264_AAC : ExportFormat::PRORES_HQ, false, false, false, 23
+ )
+ );
+ JobManager::instance()->add (job);
+ } else {
+ try {
+ make_dcp (film, behaviour);
+ } catch (runtime_error& e) {
+ std::cerr << "Could not make DCP: " << e.what() << "\n";
+ exit(EXIT_FAILURE);
+ }
}
- film->make_dcp (false, check);
bool const error = show_jobs_on_console (progress);
if (keep_going) {