summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2012-07-15 00:14:28 +0100
committerCarl Hetherington <cth@carlh.net>2012-07-15 00:14:28 +0100
commitbb767c7e338414beee132af3e96829c1448e214b (patch)
treebec2858dcc7225a9bcc2acd8170c25508f6df6cb /src
parent66c9be6bdb1361e5681e094a0c8170d268aa9518 (diff)
Move things round a bit.
Diffstat (limited to 'src')
-rw-r--r--src/gtk/alignment.cc167
-rw-r--r--src/gtk/alignment.h35
-rw-r--r--src/gtk/config_dialog.cc369
-rw-r--r--src/gtk/config_dialog.h113
-rw-r--r--src/gtk/dcp_range_dialog.cc117
-rw-r--r--src/gtk/dcp_range_dialog.h46
-rw-r--r--src/gtk/film_editor.cc572
-rw-r--r--src/gtk/film_editor.h125
-rw-r--r--src/gtk/film_list.cc65
-rw-r--r--src/gtk/film_list.h41
-rw-r--r--src/gtk/film_player.cc310
-rw-r--r--src/gtk/film_player.h63
-rw-r--r--src/gtk/film_viewer.cc224
-rw-r--r--src/gtk/film_viewer.h63
-rw-r--r--src/gtk/filter_dialog.cc47
-rw-r--r--src/gtk/filter_dialog.h43
-rw-r--r--src/gtk/filter_view.cc72
-rw-r--r--src/gtk/filter_view.h47
-rw-r--r--src/gtk/gpl.cc370
-rw-r--r--src/gtk/gpl.h24
-rw-r--r--src/gtk/gtk_util.cc45
-rw-r--r--src/gtk/gtk_util.h27
-rw-r--r--src/gtk/job_manager_view.cc134
-rw-r--r--src/gtk/job_manager_view.h83
-rw-r--r--src/gtk/job_wrapper.cc50
-rw-r--r--src/gtk/job_wrapper.h27
-rw-r--r--src/gtk/wscript22
-rw-r--r--src/lib/ab_transcode_job.cc69
-rw-r--r--src/lib/ab_transcode_job.h47
-rw-r--r--src/lib/ab_transcoder.cc127
-rw-r--r--src/lib/ab_transcoder.h69
-rw-r--r--src/lib/config.cc139
-rw-r--r--src/lib/config.h207
-rw-r--r--src/lib/copy_from_dvd_job.cc103
-rw-r--r--src/lib/copy_from_dvd_job.h36
-rw-r--r--src/lib/dcp_content_type.cc91
-rw-r--r--src/lib/dcp_content_type.h58
-rw-r--r--src/lib/dcp_video_frame.cc433
-rw-r--r--src/lib/dcp_video_frame.h143
-rw-r--r--src/lib/decoder.cc300
-rw-r--r--src/lib/decoder.h136
-rw-r--r--src/lib/decoder_factory.cc48
-rw-r--r--src/lib/decoder_factory.h32
-rw-r--r--src/lib/delay_line.cc110
-rw-r--r--src/lib/delay_line.h36
-rw-r--r--src/lib/dvd.cc78
-rw-r--r--src/lib/dvd.h21
-rw-r--r--src/lib/encoder.cc71
-rw-r--r--src/lib/encoder.h87
-rw-r--r--src/lib/encoder_factory.cc40
-rw-r--r--src/lib/encoder_factory.h30
-rw-r--r--src/lib/examine_content_job.cc69
-rw-r--r--src/lib/examine_content_job.h45
-rw-r--r--src/lib/exceptions.h215
-rw-r--r--src/lib/ffmpeg_decoder.cc256
-rw-r--r--src/lib/ffmpeg_decoder.h90
-rw-r--r--src/lib/film.cc631
-rw-r--r--src/lib/film.h275
-rw-r--r--src/lib/film_state.cc254
-rw-r--r--src/lib/film_state.h155
-rw-r--r--src/lib/filter.cc131
-rw-r--r--src/lib/filter.h78
-rw-r--r--src/lib/format.cc189
-rw-r--r--src/lib/format.h101
-rw-r--r--src/lib/image.cc392
-rw-r--r--src/lib/image.h164
-rw-r--r--src/lib/imagemagick_decoder.cc55
-rw-r--r--src/lib/imagemagick_decoder.h63
-rw-r--r--src/lib/j2k_still_encoder.cc73
-rw-r--r--src/lib/j2k_still_encoder.h43
-rw-r--r--src/lib/j2k_wav_encoder.cc289
-rw-r--r--src/lib/j2k_wav_encoder.h65
-rw-r--r--src/lib/job.cc242
-rw-r--r--src/lib/job.h115
-rw-r--r--src/lib/job_manager.cc101
-rw-r--r--src/lib/job_manager.h54
-rw-r--r--src/lib/log.cc63
-rw-r--r--src/lib/log.h55
-rw-r--r--src/lib/lut.h51
-rw-r--r--src/lib/make_dcp_job.cc94
-rw-r--r--src/lib/make_dcp_job.h37
-rw-r--r--src/lib/make_mxf_job.cc81
-rw-r--r--src/lib/make_mxf_job.h48
-rw-r--r--src/lib/options.h109
-rw-r--r--src/lib/player.cc227
-rw-r--r--src/lib/player.h70
-rw-r--r--src/lib/player_manager.cc137
-rw-r--r--src/lib/player_manager.h59
-rw-r--r--src/lib/scaler.cc117
-rw-r--r--src/lib/scaler.h78
-rw-r--r--src/lib/scp_dcp_job.cc242
-rw-r--r--src/lib/scp_dcp_job.h40
-rw-r--r--src/lib/screen.cc104
-rw-r--r--src/lib/screen.h68
-rw-r--r--src/lib/server.cc58
-rw-r--r--src/lib/server.h60
-rw-r--r--src/lib/shell_command_job.cc71
-rw-r--r--src/lib/shell_command_job.h46
-rw-r--r--src/lib/thumbs_job.cc68
-rw-r--r--src/lib/thumbs_job.h37
-rw-r--r--src/lib/tiff_decoder.cc224
-rw-r--r--src/lib/tiff_decoder.h69
-rw-r--r--src/lib/tiff_encoder.cc77
-rw-r--r--src/lib/tiff_encoder.h43
-rw-r--r--src/lib/timer.cc89
-rw-r--r--src/lib/timer.h78
-rw-r--r--src/lib/transcode_job.cc100
-rw-r--r--src/lib/transcode_job.h43
-rw-r--r--src/lib/transcoder.cc72
-rw-r--r--src/lib/transcoder.h59
-rw-r--r--src/lib/trim_action.h28
-rw-r--r--src/lib/util.cc496
-rw-r--r--src/lib/util.h121
-rw-r--r--src/lib/wscript52
-rw-r--r--src/tools/alignomatic.cc317
-rw-r--r--src/tools/dvdomatic.cc328
-rw-r--r--src/tools/fixlengths.cc209
-rw-r--r--src/tools/makedcp.cc138
-rw-r--r--src/tools/playomatic.cc67
-rwxr-xr-xsrc/tools/run_film_editor4
-rw-r--r--src/tools/servomatic.cc238
-rw-r--r--src/tools/servomatictest.cc159
-rw-r--r--src/tools/test.cc15
-rw-r--r--src/tools/wscript17
-rw-r--r--src/wscript5
125 files changed, 15025 insertions, 0 deletions
diff --git a/src/gtk/alignment.cc b/src/gtk/alignment.cc
new file mode 100644
index 000000000..ee4ca51c1
--- /dev/null
+++ b/src/gtk/alignment.cc
@@ -0,0 +1,167 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <gtkmm.h>
+#include <cairomm/cairomm.h>
+#include "alignment.h"
+
+using namespace std;
+
+class AlignmentWidget : public Gtk::DrawingArea
+{
+public:
+ void set_text_line (int n, string t)
+ {
+ if (int(_text.size()) < (n + 1)) {
+ _text.resize (n + 1);
+ }
+
+ _text[n] = t;
+ queue_draw ();
+ }
+
+private:
+ bool on_expose_event (GdkEventExpose* ev)
+ {
+ if (!get_window ()) {
+ return false;
+ }
+
+ Cairo::RefPtr<Cairo::Context> c = get_window()->create_cairo_context ();
+
+ Gtk::Allocation a = get_allocation ();
+ int const w = a.get_width ();
+ int const h = a.get_height ();
+
+ c->rectangle (0, 0, w, h);
+ c->set_source_rgb (0, 0, 0);
+ c->fill ();
+
+ c->set_source_rgb (1, 1, 1);
+ c->set_line_width (1);
+
+ int const arrow_size = h / 8;
+ int const head_size = h / 32;
+
+ /* arrow to left edge */
+ c->move_to (arrow_size, h / 2);
+ c->line_to (0, h / 2);
+ c->rel_line_to (head_size, head_size);
+ c->move_to (0, h / 2);
+ c->rel_line_to (head_size, -head_size);
+ c->stroke ();
+
+ /* arrow to right edge */
+ c->move_to (w - arrow_size, h / 2);
+ c->line_to (w, h / 2);
+ c->rel_line_to (-head_size, head_size);
+ c->move_to (w, h / 2);
+ c->rel_line_to (-head_size, -head_size);
+ c->stroke ();
+
+ /* arrow to top edge */
+ c->move_to (w / 2, arrow_size);
+ c->line_to (w / 2, 0);
+ c->rel_line_to (head_size, head_size);
+ c->move_to (w / 2, 0);
+ c->rel_line_to (-head_size, head_size);
+ c->stroke ();
+
+ /* arrow to bottom edge */
+ c->move_to (w / 2, h - h / 8);
+ c->line_to (w / 2, h);
+ c->rel_line_to (head_size, -head_size);
+ c->move_to (w / 2, h);
+ c->rel_line_to (-head_size, -head_size);
+ c->stroke ();
+
+ /* arrow to top-left corner */
+ c->move_to (arrow_size, arrow_size);
+ c->line_to (0, 0);
+ c->rel_line_to (head_size, 0);
+ c->move_to (0, 0);
+ c->rel_line_to (0, head_size);
+ c->stroke ();
+
+ /* arrow to top-right corner */
+ c->move_to (w - arrow_size, arrow_size);
+ c->line_to (w, 0);
+ c->rel_line_to (0, head_size);
+ c->move_to (w, 0);
+ c->rel_line_to (-head_size, 0);
+ c->stroke ();
+
+ /* arrow to bottom-left corner */
+ c->move_to (arrow_size, h - arrow_size);
+ c->line_to (0, h);
+ c->rel_line_to (head_size, 0);
+ c->move_to (0, h);
+ c->rel_line_to (0, -head_size);
+ c->stroke ();
+
+ /* arrow to bottom-right corner */
+ c->move_to (w - arrow_size, h - arrow_size);
+ c->line_to (w, h);
+ c->rel_line_to (-head_size, 0);
+ c->line_to (w, h);
+ c->rel_line_to (0, -head_size);
+ c->stroke ();
+
+ /* text */
+ int max_height = 0;
+ for (vector<string>::iterator i = _text.begin(); i != _text.end(); ++i) {
+ Cairo::TextExtents e;
+ c->get_text_extents (*i, e);
+ max_height = max (max_height, int (e.height));
+ }
+
+ int total_height = max_height * _text.size() * 2;
+
+ for (vector<string>::size_type i = 0; i < _text.size(); ++i) {
+ Cairo::TextExtents e;
+ c->get_text_extents (_text[i], e);
+ c->move_to ((w - e.width) / 2, ((h - total_height) / 2) + ((i * 2) + 1) * max_height);
+ c->text_path (_text[i]);
+ c->stroke ();
+ }
+
+ return true;
+ }
+
+ std::vector<std::string> _text;
+};
+
+Alignment::Alignment (Position p, Size s)
+{
+ _widget = Gtk::manage (new AlignmentWidget);
+ add (*_widget);
+ show_all ();
+
+ set_decorated (false);
+ set_resizable (false);
+ set_size_request (s.width, s.height);
+ move (p.x, p.y);
+}
+
+void
+Alignment::set_text_line (int n, string t)
+{
+ _widget->set_text_line (n, t);
+}
diff --git a/src/gtk/alignment.h b/src/gtk/alignment.h
new file mode 100644
index 000000000..fb740b7c0
--- /dev/null
+++ b/src/gtk/alignment.h
@@ -0,0 +1,35 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <gtkmm.h>
+#include <string>
+#include "lib/util.h"
+
+class AlignmentWidget;
+
+class Alignment : public Gtk::Window
+{
+public:
+ Alignment (Position, Size);
+
+ void set_text_line (int, std::string);
+
+private:
+ AlignmentWidget* _widget;
+};
diff --git a/src/gtk/config_dialog.cc b/src/gtk/config_dialog.cc
new file mode 100644
index 000000000..03f5b99a0
--- /dev/null
+++ b/src/gtk/config_dialog.cc
@@ -0,0 +1,369 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/config_dialog.cc
+ * @brief A dialogue to edit DVD-o-matic configuration.
+ */
+
+#include <iostream>
+#include <boost/lexical_cast.hpp>
+#include "lib/config.h"
+#include "lib/server.h"
+#include "lib/screen.h"
+#include "lib/format.h"
+#include "lib/scaler.h"
+#include "lib/filter.h"
+#include "config_dialog.h"
+#include "gtk_util.h"
+#include "filter_dialog.h"
+
+using namespace std;
+using namespace boost;
+
+ConfigDialog::ConfigDialog ()
+ : Gtk::Dialog ("DVD-o-matic Configuration")
+ , _reference_filters_button ("Edit...")
+ , _add_server ("Add Server")
+ , _remove_server ("Remove Server")
+ , _add_screen ("Add Screen")
+ , _remove_screen ("Remove Screen")
+{
+ Gtk::Table* t = manage (new Gtk::Table);
+ t->set_row_spacings (6);
+ t->set_col_spacings (6);
+ t->set_border_width (6);
+
+ Config* config = Config::instance ();
+
+ _tms_ip.set_text (config->tms_ip ());
+ _tms_ip.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::tms_ip_changed));
+ _tms_path.set_text (config->tms_path ());
+ _tms_path.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::tms_path_changed));
+ _tms_user.set_text (config->tms_user ());
+ _tms_user.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::tms_user_changed));
+ _tms_password.set_text (config->tms_password ());
+ _tms_password.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::tms_password_changed));
+
+ _num_local_encoding_threads.set_range (1, 128);
+ _num_local_encoding_threads.set_increments (1, 4);
+ _num_local_encoding_threads.set_value (config->num_local_encoding_threads ());
+ _num_local_encoding_threads.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::num_local_encoding_threads_changed));
+
+ _colour_lut.append_text ("sRGB");
+ _colour_lut.append_text ("Rec 709");
+ _colour_lut.set_active (config->colour_lut_index ());
+ _colour_lut.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::colour_lut_changed));
+
+ _j2k_bandwidth.set_range (50, 250);
+ _j2k_bandwidth.set_increments (10, 50);
+ _j2k_bandwidth.set_value (config->j2k_bandwidth() / 1e6);
+ _j2k_bandwidth.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::j2k_bandwidth_changed));
+
+ vector<Scaler const *> const sc = Scaler::all ();
+ for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
+ _reference_scaler.append_text ((*i)->name ());
+ }
+ _reference_scaler.set_active (Scaler::as_index (config->reference_scaler ()));
+ _reference_scaler.signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::reference_scaler_changed));
+
+ _reference_filters.set_alignment (0, 0.5);
+ pair<string, string> p = Filter::ffmpeg_strings (config->reference_filters ());
+ _reference_filters.set_text (p.first + " " + p.second);
+ _reference_filters_button.signal_clicked().connect (sigc::mem_fun (*this, &ConfigDialog::edit_reference_filters_clicked));
+
+ _servers_store = Gtk::ListStore::create (_servers_columns);
+ vector<Server*> servers = config->servers ();
+ for (vector<Server*>::iterator i = servers.begin(); i != servers.end(); ++i) {
+ add_server_to_store (*i);
+ }
+
+ _servers_view.set_model (_servers_store);
+ _servers_view.append_column_editable ("Host Name", _servers_columns._host_name);
+ _servers_view.append_column_editable ("Threads", _servers_columns._threads);
+
+ _add_server.signal_clicked().connect (sigc::mem_fun (*this, &ConfigDialog::add_server_clicked));
+ _remove_server.signal_clicked().connect (sigc::mem_fun (*this, &ConfigDialog::remove_server_clicked));
+
+ _servers_view.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::server_selection_changed));
+ server_selection_changed ();
+
+ _screens_store = Gtk::TreeStore::create (_screens_columns);
+ vector<shared_ptr<Screen> > screens = config->screens ();
+ for (vector<shared_ptr<Screen> >::iterator i = screens.begin(); i != screens.end(); ++i) {
+ add_screen_to_store (*i);
+ }
+
+ _screens_view.set_model (_screens_store);
+ _screens_view.append_column_editable ("Screen", _screens_columns._name);
+ _screens_view.append_column ("Format", _screens_columns._format_name);
+ _screens_view.append_column_editable ("x", _screens_columns._x);
+ _screens_view.append_column_editable ("y", _screens_columns._y);
+ _screens_view.append_column_editable ("Width", _screens_columns._width);
+ _screens_view.append_column_editable ("Height", _screens_columns._height);
+
+ _add_screen.signal_clicked().connect (sigc::mem_fun (*this, &ConfigDialog::add_screen_clicked));
+ _remove_screen.signal_clicked().connect (sigc::mem_fun (*this, &ConfigDialog::remove_screen_clicked));
+
+ _screens_view.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &ConfigDialog::screen_selection_changed));
+ screen_selection_changed ();
+
+ int n = 0;
+ t->attach (left_aligned_label ("TMS IP address"), 0, 1, n, n + 1);
+ t->attach (_tms_ip, 1, 2, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("TMS target path"), 0, 1, n, n + 1);
+ t->attach (_tms_path, 1, 2, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("TMS user name"), 0, 1, n, n + 1);
+ t->attach (_tms_user, 1, 2, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("TMS password"), 0, 1, n, n + 1);
+ t->attach (_tms_password, 1, 2, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("Threads to use for encoding on this host"), 0, 1, n, n + 1);
+ t->attach (_num_local_encoding_threads, 1, 2, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("Colour look-up table"), 0, 1, n, n + 1);
+ t->attach (_colour_lut, 1, 2, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("JPEG2000 bandwidth"), 0, 1, n, n + 1);
+ t->attach (_j2k_bandwidth, 1, 2, n, n + 1);
+ t->attach (left_aligned_label ("MBps"), 2, 3, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("Reference scaler for A/B"), 0, 1, n, n + 1);
+ t->attach (_reference_scaler, 1, 2, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("Reference filters for A/B"), 0, 1, n, n + 1);
+ Gtk::HBox* fb = Gtk::manage (new Gtk::HBox);
+ fb->set_spacing (4);
+ fb->pack_start (_reference_filters, true, true);
+ fb->pack_start (_reference_filters_button, false, false);
+ t->attach (*fb, 1, 2, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("Encoding Servers"), 0, 1, n, n + 1);
+ t->attach (_servers_view, 1, 2, n, n + 1);
+ Gtk::VBox* b = manage (new Gtk::VBox);
+ b->pack_start (_add_server, false, false);
+ b->pack_start (_remove_server, false, false);
+ t->attach (*b, 2, 3, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("Screens"), 0, 1, n, n + 1);
+ t->attach (_screens_view, 1, 2, n, n + 1);
+ b = manage (new Gtk::VBox);
+ b->pack_start (_add_screen, false, false);
+ b->pack_start (_remove_screen, false, false);
+ t->attach (*b, 2, 3, n, n + 1);
+ ++n;
+
+ t->show_all ();
+ get_vbox()->pack_start (*t);
+
+ get_vbox()->set_border_width (24);
+
+ add_button ("Close", Gtk::RESPONSE_CLOSE);
+}
+
+void
+ConfigDialog::tms_ip_changed ()
+{
+ Config::instance()->set_tms_ip (_tms_ip.get_text ());
+}
+
+void
+ConfigDialog::tms_path_changed ()
+{
+ Config::instance()->set_tms_path (_tms_path.get_text ());
+}
+
+void
+ConfigDialog::tms_user_changed ()
+{
+ Config::instance()->set_tms_user (_tms_user.get_text ());
+}
+
+void
+ConfigDialog::tms_password_changed ()
+{
+ Config::instance()->set_tms_password (_tms_password.get_text ());
+}
+
+
+void
+ConfigDialog::num_local_encoding_threads_changed ()
+{
+ Config::instance()->set_num_local_encoding_threads (_num_local_encoding_threads.get_value ());
+}
+
+void
+ConfigDialog::colour_lut_changed ()
+{
+ Config::instance()->set_colour_lut_index (_colour_lut.get_active_row_number ());
+}
+
+void
+ConfigDialog::j2k_bandwidth_changed ()
+{
+ Config::instance()->set_j2k_bandwidth (_j2k_bandwidth.get_value() * 1e6);
+}
+
+void
+ConfigDialog::on_response (int r)
+{
+ vector<Server*> servers;
+
+ Gtk::TreeModel::Children c = _servers_store->children ();
+ for (Gtk::TreeModel::Children::iterator i = c.begin(); i != c.end(); ++i) {
+ Gtk::TreeModel::Row r = *i;
+ Server* s = new Server (r[_servers_columns._host_name], r[_servers_columns._threads]);
+ servers.push_back (s);
+ }
+
+ Config::instance()->set_servers (servers);
+
+ vector<shared_ptr<Screen> > screens;
+
+ c = _screens_store->children ();
+ for (Gtk::TreeModel::Children::iterator i = c.begin(); i != c.end(); ++i) {
+
+ Gtk::TreeModel::Row r = *i;
+ shared_ptr<Screen> s (new Screen (r[_screens_columns._name]));
+
+ Gtk::TreeModel::Children cc = r.children ();
+ for (Gtk::TreeModel::Children::iterator j = cc.begin(); j != cc.end(); ++j) {
+ Gtk::TreeModel::Row r = *j;
+ string const x_ = r[_screens_columns._x];
+ string const y_ = r[_screens_columns._y];
+ string const width_ = r[_screens_columns._width];
+ string const height_ = r[_screens_columns._height];
+ s->set_geometry (
+ Format::from_nickname (r[_screens_columns._format_nickname]),
+ Position (lexical_cast<int> (x_), lexical_cast<int> (y_)),
+ Size (lexical_cast<int> (width_), lexical_cast<int> (height_))
+ );
+ }
+
+ screens.push_back (s);
+ }
+
+ Config::instance()->set_screens (screens);
+
+ Gtk::Dialog::on_response (r);
+}
+
+void
+ConfigDialog::add_server_to_store (Server* s)
+{
+ Gtk::TreeModel::iterator i = _servers_store->append ();
+ Gtk::TreeModel::Row r = *i;
+ r[_servers_columns._host_name] = s->host_name ();
+ r[_servers_columns._threads] = s->threads ();
+}
+
+void
+ConfigDialog::add_server_clicked ()
+{
+ Server s ("localhost", 1);
+ add_server_to_store (&s);
+}
+
+void
+ConfigDialog::remove_server_clicked ()
+{
+ Gtk::TreeModel::iterator i = _servers_view.get_selection()->get_selected ();
+ if (i) {
+ _servers_store->erase (i);
+ }
+}
+
+void
+ConfigDialog::server_selection_changed ()
+{
+ Gtk::TreeModel::iterator i = _servers_view.get_selection()->get_selected ();
+ _remove_server.set_sensitive (i);
+}
+
+
+void
+ConfigDialog::add_screen_to_store (shared_ptr<Screen> s)
+{
+ Gtk::TreeModel::iterator i = _screens_store->append ();
+ Gtk::TreeModel::Row r = *i;
+ r[_screens_columns._name] = s->name ();
+
+ Screen::GeometryMap geoms = s->geometries ();
+ for (Screen::GeometryMap::const_iterator j = geoms.begin(); j != geoms.end(); ++j) {
+ i = _screens_store->append (r.children ());
+ Gtk::TreeModel::Row c = *i;
+ c[_screens_columns._format_name] = j->first->name ();
+ c[_screens_columns._format_nickname] = j->first->nickname ();
+ c[_screens_columns._x] = lexical_cast<string> (j->second.position.x);
+ c[_screens_columns._y] = lexical_cast<string> (j->second.position.y);
+ c[_screens_columns._width] = lexical_cast<string> (j->second.size.width);
+ c[_screens_columns._height] = lexical_cast<string> (j->second.size.height);
+ }
+}
+
+void
+ConfigDialog::add_screen_clicked ()
+{
+ shared_ptr<Screen> s (new Screen ("New Screen"));
+ add_screen_to_store (s);
+}
+
+void
+ConfigDialog::remove_screen_clicked ()
+{
+ Gtk::TreeModel::iterator i = _screens_view.get_selection()->get_selected ();
+ if (i) {
+ _screens_store->erase (i);
+ }
+}
+
+void
+ConfigDialog::screen_selection_changed ()
+{
+ Gtk::TreeModel::iterator i = _screens_view.get_selection()->get_selected ();
+ _remove_screen.set_sensitive (i);
+}
+
+
+void
+ConfigDialog::reference_scaler_changed ()
+{
+ int const n = _reference_scaler.get_active_row_number ();
+ if (n >= 0) {
+ Config::instance()->set_reference_scaler (Scaler::from_index (n));
+ }
+}
+
+void
+ConfigDialog::edit_reference_filters_clicked ()
+{
+ FilterDialog d (Config::instance()->reference_filters ());
+ d.ActiveChanged.connect (sigc::mem_fun (*this, &ConfigDialog::reference_filters_changed));
+ d.run ();
+}
+
+void
+ConfigDialog::reference_filters_changed (vector<Filter const *> f)
+{
+ Config::instance()->set_reference_filters (f);
+ pair<string, string> p = Filter::ffmpeg_strings (Config::instance()->reference_filters ());
+ _reference_filters.set_text (p.first + " " + p.second);
+}
diff --git a/src/gtk/config_dialog.h b/src/gtk/config_dialog.h
new file mode 100644
index 000000000..ec345750a
--- /dev/null
+++ b/src/gtk/config_dialog.h
@@ -0,0 +1,113 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/config_dialog.h
+ * @brief A dialogue to edit DVD-o-matic configuration.
+ */
+
+#include <gtkmm.h>
+
+class Screen;
+class Server;
+
+/** @class ConfigDialog
+ * @brief A dialogue to edit DVD-o-matic configuration.
+ */
+class ConfigDialog : public Gtk::Dialog
+{
+public:
+ ConfigDialog ();
+
+private:
+ void on_response (int);
+
+ void tms_ip_changed ();
+ void tms_path_changed ();
+ void tms_user_changed ();
+ void tms_password_changed ();
+ void num_local_encoding_threads_changed ();
+ void colour_lut_changed ();
+ void j2k_bandwidth_changed ();
+ void add_server_clicked ();
+ void remove_server_clicked ();
+ void server_selection_changed ();
+ void add_screen_clicked ();
+ void remove_screen_clicked ();
+ void screen_selection_changed ();
+ void reference_scaler_changed ();
+ void edit_reference_filters_clicked ();
+ void reference_filters_changed (std::vector<Filter const *>);
+
+ void add_screen_to_store (boost::shared_ptr<Screen>);
+ void add_server_to_store (Server *);
+
+ struct ServersModelColumns : public Gtk::TreeModelColumnRecord
+ {
+ ServersModelColumns () {
+ add (_host_name);
+ add (_threads);
+ }
+
+ Gtk::TreeModelColumn<std::string> _host_name;
+ Gtk::TreeModelColumn<int> _threads;
+ };
+
+ struct ScreensModelColumns : public Gtk::TreeModelColumnRecord
+ {
+ ScreensModelColumns () {
+ add (_name);
+ add (_format_name);
+ add (_format_nickname);
+ add (_x);
+ add (_y);
+ add (_width);
+ add (_height);
+ }
+
+ Gtk::TreeModelColumn<std::string> _name;
+ Gtk::TreeModelColumn<std::string> _format_name;
+ Gtk::TreeModelColumn<std::string> _format_nickname;
+ Gtk::TreeModelColumn<std::string> _x;
+ Gtk::TreeModelColumn<std::string> _y;
+ Gtk::TreeModelColumn<std::string> _width;
+ Gtk::TreeModelColumn<std::string> _height;
+ };
+
+ Gtk::Entry _tms_ip;
+ Gtk::Entry _tms_path;
+ Gtk::Entry _tms_user;
+ Gtk::Entry _tms_password;
+ Gtk::SpinButton _num_local_encoding_threads;
+ Gtk::ComboBoxText _colour_lut;
+ Gtk::SpinButton _j2k_bandwidth;
+ Gtk::ComboBoxText _reference_scaler;
+ Gtk::Label _reference_filters;
+ Gtk::Button _reference_filters_button;
+ ServersModelColumns _servers_columns;
+ Glib::RefPtr<Gtk::ListStore> _servers_store;
+ Gtk::TreeView _servers_view;
+ Gtk::Button _add_server;
+ Gtk::Button _remove_server;
+ ScreensModelColumns _screens_columns;
+ Glib::RefPtr<Gtk::TreeStore> _screens_store;
+ Gtk::TreeView _screens_view;
+ Gtk::Button _add_screen;
+ Gtk::Button _remove_screen;
+};
+
diff --git a/src/gtk/dcp_range_dialog.cc b/src/gtk/dcp_range_dialog.cc
new file mode 100644
index 000000000..d1fef0e8b
--- /dev/null
+++ b/src/gtk/dcp_range_dialog.cc
@@ -0,0 +1,117 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "dcp_range_dialog.h"
+#include "lib/film.h"
+
+DCPRangeDialog::DCPRangeDialog (Film* f)
+ : _film (f)
+ , _whole ("Whole film")
+ , _first ("First")
+ , _cut ("Cut remainder")
+ , _black_out ("Black-out remainder")
+{
+ set_title ("DCP range");
+
+ Gtk::Table* table = Gtk::manage (new Gtk::Table ());
+ table->set_border_width (6);
+ table->set_row_spacings (6);
+ table->set_col_spacings (6);
+ table->attach (_whole, 0, 4, 0, 1);
+ table->attach (_first, 0, 1, 1, 2);
+ table->attach (_n_frames, 1, 2, 1, 2);
+ table->attach (*manage (new Gtk::Label ("frames")), 2, 3, 1, 2);
+ table->attach (_cut, 1, 2, 2, 3);
+ table->attach (_black_out, 1, 2, 3, 4);
+
+ Gtk::RadioButtonGroup g = _whole.get_group ();
+ _first.set_group (g);
+
+ g = _black_out.get_group ();
+ _cut.set_group (g);
+
+ _n_frames.set_range (1, INT_MAX - 1);
+ _n_frames.set_increments (24, 24 * 60);
+ if (_film->dcp_frames() > 0) {
+ _whole.set_active (false);
+ _first.set_active (true);
+ _n_frames.set_value (_film->dcp_frames ());
+ } else {
+ _whole.set_active (true);
+ _first.set_active (false);
+ _n_frames.set_value (24);
+ }
+
+ _black_out.set_active (_film->dcp_trim_action() == BLACK_OUT);
+ _cut.set_active (_film->dcp_trim_action() == CUT);
+
+ _whole.signal_toggled().connect (sigc::mem_fun (*this, &DCPRangeDialog::whole_toggled));
+ _cut.signal_toggled().connect (sigc::mem_fun (*this, &DCPRangeDialog::cut_toggled));
+ _n_frames.signal_value_changed().connect (sigc::mem_fun (*this, &DCPRangeDialog::n_frames_changed));
+
+ get_vbox()->pack_start (*table);
+
+ add_button ("Close", Gtk::RESPONSE_CLOSE);
+ show_all_children ();
+
+ set_sensitivity ();
+}
+
+void
+DCPRangeDialog::whole_toggled ()
+{
+ set_sensitivity ();
+ emit_changed ();
+}
+
+void
+DCPRangeDialog::set_sensitivity ()
+{
+ _n_frames.set_sensitive (_first.get_active ());
+ _black_out.set_sensitive (_first.get_active ());
+ _cut.set_sensitive (_first.get_active ());
+}
+
+void
+DCPRangeDialog::cut_toggled ()
+{
+ emit_changed ();
+}
+
+void
+DCPRangeDialog::n_frames_changed ()
+{
+ emit_changed ();
+}
+
+void
+DCPRangeDialog::emit_changed ()
+{
+ int frames = 0;
+ if (!_whole.get_active ()) {
+ frames = _n_frames.get_value_as_int ();
+ }
+
+ TrimAction action = CUT;
+ if (_black_out.get_active ()) {
+ action = BLACK_OUT;
+ }
+
+ Changed (frames, action);
+}
diff --git a/src/gtk/dcp_range_dialog.h b/src/gtk/dcp_range_dialog.h
new file mode 100644
index 000000000..7469a2576
--- /dev/null
+++ b/src/gtk/dcp_range_dialog.h
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <gtkmm.h>
+#include "lib/trim_action.h"
+
+class Film;
+
+class DCPRangeDialog : public Gtk::Dialog
+{
+public:
+ DCPRangeDialog (Film *);
+
+ sigc::signal2<void, int, TrimAction> Changed;
+
+private:
+ void whole_toggled ();
+ void cut_toggled ();
+ void n_frames_changed ();
+
+ void set_sensitivity ();
+ void emit_changed ();
+
+ Film* _film;
+ Gtk::RadioButton _whole;
+ Gtk::RadioButton _first;
+ Gtk::SpinButton _n_frames;
+ Gtk::RadioButton _cut;
+ Gtk::RadioButton _black_out;
+};
diff --git a/src/gtk/film_editor.cc b/src/gtk/film_editor.cc
new file mode 100644
index 000000000..0e410aa6b
--- /dev/null
+++ b/src/gtk/film_editor.cc
@@ -0,0 +1,572 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/film_editor.cc
+ * @brief A GTK widget to edit a film's metadata, and perform various functions.
+ */
+
+#include <iostream>
+#include <gtkmm.h>
+#include <boost/thread.hpp>
+#include <boost/filesystem.hpp>
+#include "lib/format.h"
+#include "lib/film.h"
+#include "lib/transcode_job.h"
+#include "lib/exceptions.h"
+#include "lib/ab_transcode_job.h"
+#include "lib/thumbs_job.h"
+#include "lib/make_mxf_job.h"
+#include "lib/job_manager.h"
+#include "lib/filter.h"
+#include "lib/screen.h"
+#include "lib/config.h"
+#include "lib/scp_dcp_job.h"
+#include "filter_dialog.h"
+#include "gtk_util.h"
+#include "film_editor.h"
+#include "dcp_range_dialog.h"
+
+using namespace std;
+using namespace boost;
+using namespace Gtk;
+
+/** @param f Film to edit */
+FilmEditor::FilmEditor (Film* f)
+ : _film (f)
+ , _filters_button ("Edit...")
+ , _change_dcp_range_button ("Edit...")
+ , _dcp_ab ("A/B")
+{
+ _vbox.set_border_width (12);
+ _vbox.set_spacing (12);
+
+ /* Set up our editing widgets */
+ _left_crop.set_range (0, 1024);
+ _left_crop.set_increments (1, 16);
+ _top_crop.set_range (0, 1024);
+ _top_crop.set_increments (1, 16);
+ _right_crop.set_range (0, 1024);
+ _right_crop.set_increments (1, 16);
+ _bottom_crop.set_range (0, 1024);
+ _bottom_crop.set_increments (1, 16);
+ _filters.set_alignment (0, 0.5);
+ _audio_gain.set_range (-60, 60);
+ _audio_gain.set_increments (1, 3);
+ _audio_delay.set_range (-1000, 1000);
+ _audio_delay.set_increments (1, 20);
+ _still_duration.set_range (0, 60 * 60);
+ _still_duration.set_increments (1, 5);
+ _dcp_range.set_alignment (0, 0.5);
+
+ vector<Format const *> fmt = Format::all ();
+ for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) {
+ _format.append_text ((*i)->name ());
+ }
+
+ _frames_per_second.set_increments (1, 5);
+ _frames_per_second.set_digits (2);
+ _frames_per_second.set_range (0, 60);
+
+ vector<DCPContentType const *> const ct = DCPContentType::all ();
+ for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
+ _dcp_content_type.append_text ((*i)->pretty_name ());
+ }
+
+ vector<Scaler const *> const sc = Scaler::all ();
+ for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
+ _scaler.append_text ((*i)->name ());
+ }
+
+ _original_size.set_alignment (0, 0.5);
+ _length.set_alignment (0, 0.5);
+ _audio.set_alignment (0, 0.5);
+
+ /* And set their values from the Film */
+ set_film (f);
+
+ /* Now connect to them, since initial values are safely set */
+ _name.signal_changed().connect (sigc::mem_fun (*this, &FilmEditor::name_changed));
+ _frames_per_second.signal_changed().connect (sigc::mem_fun (*this, &FilmEditor::frames_per_second_changed));
+ _format.signal_changed().connect (sigc::mem_fun (*this, &FilmEditor::format_changed));
+ _content.signal_file_set().connect (sigc::mem_fun (*this, &FilmEditor::content_changed));
+ _left_crop.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::left_crop_changed));
+ _right_crop.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::right_crop_changed));
+ _top_crop.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::top_crop_changed));
+ _bottom_crop.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::bottom_crop_changed));
+ _filters_button.signal_clicked().connect (sigc::mem_fun (*this, &FilmEditor::edit_filters_clicked));
+ _scaler.signal_changed().connect (sigc::mem_fun (*this, &FilmEditor::scaler_changed));
+ _dcp_content_type.signal_changed().connect (sigc::mem_fun (*this, &FilmEditor::dcp_content_type_changed));
+ _dcp_ab.signal_toggled().connect (sigc::mem_fun (*this, &FilmEditor::dcp_ab_toggled));
+ _audio_gain.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::audio_gain_changed));
+ _audio_delay.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::audio_delay_changed));
+ _still_duration.signal_value_changed().connect (sigc::mem_fun (*this, &FilmEditor::still_duration_changed));
+ _change_dcp_range_button.signal_clicked().connect (sigc::mem_fun (*this, &FilmEditor::change_dcp_range_clicked));
+
+ /* Set up the table */
+
+ Table* t = manage (new Table);
+
+ t->set_row_spacings (4);
+ t->set_col_spacings (12);
+
+ int n = 0;
+ t->attach (left_aligned_label ("Name"), 0, 1, n, n + 1);
+ t->attach (_name, 1, 2, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("Content"), 0, 1, n, n + 1);
+ t->attach (_content, 1, 2, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("Content Type"), 0, 1, n, n + 1);
+ t->attach (_dcp_content_type, 1, 2, n, n + 1);
+ ++n;
+ t->attach (video_widget (left_aligned_label ("Frames Per Second")), 0, 1, n, n + 1);
+ t->attach (video_widget (_frames_per_second), 1, 2, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("Format"), 0, 1, n, n + 1);
+ t->attach (_format, 1, 2, n, n + 1);
+ ++n;
+ t->attach (left_aligned_label ("Crop"), 0, 1, n, n + 1);
+ HBox* c = manage (new HBox);
+ c->set_spacing (4);
+ c->pack_start (left_aligned_label ("L"), false, false);
+ c->pack_start (_left_crop, true, true);
+ c->pack_start (left_aligned_label ("R"), false, false);
+ c->pack_start (_right_crop, true, true);
+ c->pack_start (left_aligned_label ("T"), false, false);
+ c->pack_start (_top_crop, true, true);
+ c->pack_start (left_aligned_label ("B"), false, false);
+ c->pack_start (_bottom_crop, true, true);
+ t->attach (*c, 1, 2, n, n + 1);
+ ++n;
+
+ /* VIDEO-only stuff */
+ int const special = n;
+ t->attach (video_widget (left_aligned_label ("Filters")), 0, 1, n, n + 1);
+ HBox* fb = manage (new HBox);
+ fb->set_spacing (4);
+ fb->pack_start (video_widget (_filters), true, true);
+ fb->pack_start (video_widget (_filters_button), false, false);
+ t->attach (*fb, 1, 2, n, n + 1);
+ ++n;
+ t->attach (video_widget (left_aligned_label ("Scaler")), 0, 1, n, n + 1);
+ t->attach (video_widget (_scaler), 1, 2, n, n + 1);
+ ++n;
+ t->attach (video_widget (left_aligned_label ("Audio Gain")), 0, 1, n, n + 1);
+ t->attach (video_widget (_audio_gain), 1, 2, n, n + 1);
+ t->attach (video_widget (left_aligned_label ("dB")), 2, 3, n, n + 1);
+ ++n;
+ t->attach (video_widget (left_aligned_label ("Audio Delay")), 0, 1, n, n + 1);
+ t->attach (video_widget (_audio_delay), 1, 2, n, n + 1);
+ t->attach (video_widget (left_aligned_label ("ms")), 2, 3, n, n + 1);
+ ++n;
+ t->attach (video_widget (left_aligned_label ("Original Size")), 0, 1, n, n + 1);
+ t->attach (video_widget (_original_size), 1, 2, n, n + 1);
+ ++n;
+ t->attach (video_widget (left_aligned_label ("Length")), 0, 1, n, n + 1);
+ t->attach (video_widget (_length), 1, 2, n, n + 1);
+ ++n;
+ t->attach (video_widget (left_aligned_label ("Audio")), 0, 1, n, n + 1);
+ t->attach (video_widget (_audio), 1, 2, n, n + 1);
+ ++n;
+ t->attach (video_widget (left_aligned_label ("Range")), 0, 1, n, n + 1);
+ Gtk::HBox* db = manage (new Gtk::HBox);
+ db->pack_start (_dcp_range, true, true);
+ db->pack_start (_change_dcp_range_button, false, false);
+ t->attach (*db, 1, 2, n, n + 1);
+ ++n;
+ t->attach (_dcp_ab, 0, 3, n, n + 1);
+
+ /* STILL-only stuff */
+ n = special;
+ t->attach (still_widget (left_aligned_label ("Duration")), 0, 1, n, n + 1);
+ t->attach (still_widget (_still_duration), 1, 2, n, n + 1);
+ t->attach (still_widget (left_aligned_label ("s")), 2, 3, n, n + 1);
+ ++n;
+
+ t->show_all ();
+ _vbox.pack_start (*t, false, false);
+}
+
+/** @return Our main widget, which contains everything else */
+Widget&
+FilmEditor::widget ()
+{
+ return _vbox;
+}
+
+/** Called when the left crop widget has been changed */
+void
+FilmEditor::left_crop_changed ()
+{
+ if (_film) {
+ _film->set_left_crop (_left_crop.get_value ());
+ }
+}
+
+/** Called when the right crop widget has been changed */
+void
+FilmEditor::right_crop_changed ()
+{
+ if (_film) {
+ _film->set_right_crop (_right_crop.get_value ());
+ }
+}
+
+/** Called when the top crop widget has been changed */
+void
+FilmEditor::top_crop_changed ()
+{
+ if (_film) {
+ _film->set_top_crop (_top_crop.get_value ());
+ }
+}
+
+/** Called when the bottom crop value has been changed */
+void
+FilmEditor::bottom_crop_changed ()
+{
+ if (_film) {
+ _film->set_bottom_crop (_bottom_crop.get_value ());
+ }
+}
+
+/** Called when the content filename has been changed */
+void
+FilmEditor::content_changed ()
+{
+ if (!_film) {
+ return;
+ }
+
+ try {
+ _film->set_content (_content.get_filename ());
+ } catch (std::exception& e) {
+ _content.set_filename (_film->directory ());
+ stringstream m;
+ m << "Could not set content: " << e.what() << ".";
+ Gtk::MessageDialog d (m.str(), false, MESSAGE_ERROR);
+ d.set_title ("DVD-o-matic");
+ d.run ();
+ }
+}
+
+/** Called when the DCP A/B switch has been toggled */
+void
+FilmEditor::dcp_ab_toggled ()
+{
+ if (_film) {
+ _film->set_dcp_ab (_dcp_ab.get_active ());
+ }
+}
+
+/** Called when the name widget has been changed */
+void
+FilmEditor::name_changed ()
+{
+ if (_film) {
+ _film->set_name (_name.get_text ());
+ }
+}
+
+/** Called when the metadata stored in the Film object has changed;
+ * so that we can update the GUI.
+ * @param p Property of the Film that has changed.
+ */
+void
+FilmEditor::film_changed (Film::Property p)
+{
+ if (!_film) {
+ return;
+ }
+
+ stringstream s;
+
+ switch (p) {
+ case Film::CONTENT:
+ _content.set_filename (_film->content ());
+ setup_visibility ();
+ break;
+ case Film::FORMAT:
+ _format.set_active (Format::as_index (_film->format ()));
+ break;
+ case Film::LEFT_CROP:
+ _left_crop.set_value (_film->left_crop ());
+ break;
+ case Film::RIGHT_CROP:
+ _right_crop.set_value (_film->right_crop ());
+ break;
+ case Film::TOP_CROP:
+ _top_crop.set_value (_film->top_crop ());
+ break;
+ case Film::BOTTOM_CROP:
+ _bottom_crop.set_value (_film->bottom_crop ());
+ break;
+ case Film::FILTERS:
+ {
+ pair<string, string> p = Filter::ffmpeg_strings (_film->filters ());
+ _filters.set_text (p.first + " " + p.second);
+ break;
+ }
+ case Film::NAME:
+ _name.set_text (_film->name ());
+ break;
+ case Film::FRAMES_PER_SECOND:
+ _frames_per_second.set_value (_film->frames_per_second ());
+ break;
+ case Film::AUDIO_CHANNELS:
+ case Film::AUDIO_SAMPLE_RATE:
+ s << _film->audio_channels () << " channels, " << _film->audio_sample_rate() << "Hz";
+ _audio.set_text (s.str ());
+ break;
+ case Film::SIZE:
+ s << _film->size().width << " x " << _film->size().height;
+ _original_size.set_text (s.str ());
+ break;
+ case Film::LENGTH:
+ if (_film->frames_per_second() > 0) {
+ s << _film->length() << " frames; " << seconds_to_hms (_film->length() / _film->frames_per_second());
+ } else {
+ s << _film->length() << " frames";
+ }
+ _length.set_text (s.str ());
+ break;
+ case Film::DCP_CONTENT_TYPE:
+ _dcp_content_type.set_active (DCPContentType::as_index (_film->dcp_content_type ()));
+ break;
+ case Film::THUMBS:
+ break;
+ case Film::DCP_FRAMES:
+ if (_film->dcp_frames() == 0) {
+ _dcp_range.set_text ("Whole film");
+ } else {
+ stringstream s;
+ s << "First " << _film->dcp_frames() << " frames";
+ _dcp_range.set_text (s.str ());
+ }
+ break;
+ case Film::DCP_TRIM_ACTION:
+ break;
+ case Film::DCP_AB:
+ _dcp_ab.set_active (_film->dcp_ab ());
+ break;
+ case Film::SCALER:
+ _scaler.set_active (Scaler::as_index (_film->scaler ()));
+ break;
+ case Film::AUDIO_GAIN:
+ _audio_gain.set_value (_film->audio_gain ());
+ break;
+ case Film::AUDIO_DELAY:
+ _audio_delay.set_value (_film->audio_delay ());
+ break;
+ case Film::STILL_DURATION:
+ _still_duration.set_value (_film->still_duration ());
+ break;
+ }
+}
+
+/** Called when the format widget has been changed */
+void
+FilmEditor::format_changed ()
+{
+ if (_film) {
+ int const n = _format.get_active_row_number ();
+ if (n >= 0) {
+ _film->set_format (Format::from_index (n));
+ }
+ }
+}
+
+/** Called when the DCP content type widget has been changed */
+void
+FilmEditor::dcp_content_type_changed ()
+{
+ if (_film) {
+ int const n = _dcp_content_type.get_active_row_number ();
+ if (n >= 0) {
+ _film->set_dcp_content_type (DCPContentType::from_index (n));
+ }
+ }
+}
+
+/** Sets the Film that we are editing */
+void
+FilmEditor::set_film (Film* f)
+{
+ _film = f;
+
+ set_things_sensitive (_film != 0);
+
+ if (_film) {
+ _film->Changed.connect (sigc::mem_fun (*this, &FilmEditor::film_changed));
+ }
+
+ if (_film) {
+ FileChanged (_film->directory ());
+ } else {
+ FileChanged ("");
+ }
+
+ film_changed (Film::NAME);
+ film_changed (Film::CONTENT);
+ film_changed (Film::DCP_CONTENT_TYPE);
+ film_changed (Film::FORMAT);
+ film_changed (Film::LEFT_CROP);
+ film_changed (Film::RIGHT_CROP);
+ film_changed (Film::TOP_CROP);
+ film_changed (Film::BOTTOM_CROP);
+ film_changed (Film::FILTERS);
+ film_changed (Film::DCP_FRAMES);
+ film_changed (Film::DCP_TRIM_ACTION);
+ film_changed (Film::DCP_AB);
+ film_changed (Film::SIZE);
+ film_changed (Film::LENGTH);
+ film_changed (Film::FRAMES_PER_SECOND);
+ film_changed (Film::AUDIO_CHANNELS);
+ film_changed (Film::AUDIO_SAMPLE_RATE);
+ film_changed (Film::SCALER);
+ film_changed (Film::AUDIO_GAIN);
+ film_changed (Film::AUDIO_DELAY);
+ film_changed (Film::STILL_DURATION);
+}
+
+/** Updates the sensitivity of lots of widgets to a given value.
+ * @param s true to make sensitive, false to make insensitive.
+ */
+void
+FilmEditor::set_things_sensitive (bool s)
+{
+ _name.set_sensitive (s);
+ _frames_per_second.set_sensitive (s);
+ _format.set_sensitive (s);
+ _content.set_sensitive (s);
+ _left_crop.set_sensitive (s);
+ _right_crop.set_sensitive (s);
+ _top_crop.set_sensitive (s);
+ _bottom_crop.set_sensitive (s);
+ _filters_button.set_sensitive (s);
+ _scaler.set_sensitive (s);
+ _dcp_content_type.set_sensitive (s);
+ _dcp_range.set_sensitive (s);
+ _change_dcp_range_button.set_sensitive (s);
+ _dcp_ab.set_sensitive (s);
+ _audio_gain.set_sensitive (s);
+ _audio_delay.set_sensitive (s);
+ _still_duration.set_sensitive (s);
+}
+
+/** Called when the `Edit filters' button has been clicked */
+void
+FilmEditor::edit_filters_clicked ()
+{
+ FilterDialog d (_film->filters ());
+ d.ActiveChanged.connect (sigc::mem_fun (*_film, &Film::set_filters));
+ d.run ();
+}
+
+/** Called when the scaler widget has been changed */
+void
+FilmEditor::scaler_changed ()
+{
+ if (_film) {
+ int const n = _scaler.get_active_row_number ();
+ if (n >= 0) {
+ _film->set_scaler (Scaler::from_index (n));
+ }
+ }
+}
+
+/** Called when the frames per second widget has been changed */
+void
+FilmEditor::frames_per_second_changed ()
+{
+ if (_film) {
+ _film->set_frames_per_second (_frames_per_second.get_value ());
+ }
+}
+
+void
+FilmEditor::audio_gain_changed ()
+{
+ if (_film) {
+ _film->set_audio_gain (_audio_gain.get_value ());
+ }
+}
+
+void
+FilmEditor::audio_delay_changed ()
+{
+ if (_film) {
+ _film->set_audio_delay (_audio_delay.get_value ());
+ }
+}
+
+Widget&
+FilmEditor::video_widget (Widget& w)
+{
+ _video_widgets.push_back (&w);
+ return w;
+}
+
+Widget&
+FilmEditor::still_widget (Widget& w)
+{
+ _still_widgets.push_back (&w);
+ return w;
+}
+
+void
+FilmEditor::setup_visibility ()
+{
+ if (!_film) {
+ return;
+ }
+
+ ContentType const c = _film->content_type ();
+
+ for (list<Widget *>::iterator i = _video_widgets.begin(); i != _video_widgets.end(); ++i) {
+ (*i)->property_visible() = (c == VIDEO);
+ }
+
+ for (list<Widget *>::iterator i = _still_widgets.begin(); i != _still_widgets.end(); ++i) {
+ (*i)->property_visible() = (c == STILL);
+ }
+}
+
+void
+FilmEditor::still_duration_changed ()
+{
+ if (_film) {
+ _film->set_still_duration (_still_duration.get_value ());
+ }
+}
+
+void
+FilmEditor::change_dcp_range_clicked ()
+{
+ DCPRangeDialog d (_film);
+ d.Changed.connect (sigc::mem_fun (*this, &FilmEditor::dcp_range_changed));
+ d.run ();
+}
+
+void
+FilmEditor::dcp_range_changed (int frames, TrimAction action)
+{
+ _film->set_dcp_frames (frames);
+ _film->set_dcp_trim_action (action);
+}
diff --git a/src/gtk/film_editor.h b/src/gtk/film_editor.h
new file mode 100644
index 000000000..9d15b436d
--- /dev/null
+++ b/src/gtk/film_editor.h
@@ -0,0 +1,125 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/film_editor.h
+ * @brief A GTK widget to edit a film's metadata, and perform various functions.
+ */
+
+#include <gtkmm.h>
+
+class Film;
+
+/** @class FilmEditor
+ * @brief A GTK widget to edit a film's metadata, and perform various functions.
+ */
+class FilmEditor
+{
+public:
+ FilmEditor (Film *);
+
+ Gtk::Widget& widget ();
+
+ void set_film (Film *);
+ void setup_visibility ();
+
+ sigc::signal1<void, std::string> FileChanged;
+
+private:
+ /* Handle changes to the view */
+ void name_changed ();
+ void left_crop_changed ();
+ void right_crop_changed ();
+ void top_crop_changed ();
+ void bottom_crop_changed ();
+ void content_changed ();
+ void frames_per_second_changed ();
+ void format_changed ();
+ void dcp_range_changed (int, TrimAction);
+ void dcp_content_type_changed ();
+ void dcp_ab_toggled ();
+ void scaler_changed ();
+ void audio_gain_changed ();
+ void audio_delay_changed ();
+ void still_duration_changed ();
+
+ /* Handle changes to the model */
+ void film_changed (Film::Property);
+
+ /* Button clicks */
+ void edit_filters_clicked ();
+ void change_dcp_range_clicked ();
+
+ void set_things_sensitive (bool);
+
+ Gtk::Widget & video_widget (Gtk::Widget &);
+ Gtk::Widget & still_widget (Gtk::Widget &);
+
+ /** The film we are editing */
+ Film* _film;
+ /** The overall VBox containing our widget */
+ Gtk::VBox _vbox;
+ /** The Film's name */
+ Gtk::Entry _name;
+ /** The Film's frames per second */
+ Gtk::SpinButton _frames_per_second;
+ /** The Film's format */
+ Gtk::ComboBoxText _format;
+ /** The Film's content file */
+ Gtk::FileChooserButton _content;
+ /** The Film's left crop */
+ Gtk::SpinButton _left_crop;
+ /** The Film's right crop */
+ Gtk::SpinButton _right_crop;
+ /** The Film's top crop */
+ Gtk::SpinButton _top_crop;
+ /** The Film's bottom crop */
+ Gtk::SpinButton _bottom_crop;
+ /** Currently-applied filters */
+ Gtk::Label _filters;
+ /** Button to open the filters dialogue */
+ Gtk::Button _filters_button;
+ /** The Film's scaler */
+ Gtk::ComboBoxText _scaler;
+ /** The Film's audio gain */
+ Gtk::SpinButton _audio_gain;
+ /** The Film's audio delay */
+ Gtk::SpinButton _audio_delay;
+ /** The Film's DCP content type */
+ Gtk::ComboBoxText _dcp_content_type;
+ /** The Film's original size */
+ Gtk::Label _original_size;
+ /** The Film's length */
+ Gtk::Label _length;
+ /** The Film's audio details */
+ Gtk::Label _audio;
+ /** The Film's duration for still sources */
+ Gtk::SpinButton _still_duration;
+
+ /** Button to start making a DCP from existing J2K and WAV files */
+ Gtk::Button _make_dcp_from_existing_button;
+ /** Display of the range of frames that will be used */
+ Gtk::Label _dcp_range;
+ /** Button to change the range */
+ Gtk::Button _change_dcp_range_button;
+ /** Selector to generate an A/B comparison DCP */
+ Gtk::CheckButton _dcp_ab;
+
+ std::list<Gtk::Widget*> _video_widgets;
+ std::list<Gtk::Widget*> _still_widgets;
+};
diff --git a/src/gtk/film_list.cc b/src/gtk/film_list.cc
new file mode 100644
index 000000000..1a9854450
--- /dev/null
+++ b/src/gtk/film_list.cc
@@ -0,0 +1,65 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/filesystem.hpp>
+#include "lib/film.h"
+#include "film_list.h"
+
+using namespace std;
+using namespace boost;
+
+FilmList::FilmList (string d)
+ : _directory (d)
+ , _list (1)
+{
+ for (filesystem::directory_iterator i = filesystem::directory_iterator (_directory); i != filesystem::directory_iterator(); ++i) {
+ if (is_directory (*i)) {
+ filesystem::path m = filesystem::path (*i) / filesystem::path ("metadata");
+ if (is_regular_file (m)) {
+ Film* f = new Film (i->path().string());
+ _films.push_back (f);
+ }
+ }
+ }
+
+ for (vector<Film const *>::iterator i = _films.begin(); i != _films.end(); ++i) {
+ _list.append_text ((*i)->name ());
+ }
+
+ _list.set_headers_visible (false);
+ _list.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &FilmList::selection_changed));
+}
+
+Gtk::Widget&
+FilmList::widget ()
+{
+ return _list;
+}
+
+void
+FilmList::selection_changed ()
+{
+ Gtk::ListViewText::SelectionList s = _list.get_selected ();
+ if (s.empty ()) {
+ return;
+ }
+
+ assert (s[0] < int (_films.size ()));
+ SelectionChanged (_films[s[0]]);
+}
diff --git a/src/gtk/film_list.h b/src/gtk/film_list.h
new file mode 100644
index 000000000..5a4ac3cc1
--- /dev/null
+++ b/src/gtk/film_list.h
@@ -0,0 +1,41 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <string>
+#include <vector>
+#include <gtkmm.h>
+
+class Film;
+
+class FilmList
+{
+public:
+ FilmList (std::string);
+
+ Gtk::Widget& widget ();
+
+ sigc::signal<void, Film const *> SelectionChanged;
+
+private:
+ void selection_changed ();
+
+ std::string _directory;
+ std::vector<Film const *> _films;
+ Gtk::ListViewText _list;
+};
diff --git a/src/gtk/film_player.cc b/src/gtk/film_player.cc
new file mode 100644
index 000000000..63e6e49ee
--- /dev/null
+++ b/src/gtk/film_player.cc
@@ -0,0 +1,310 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "lib/screen.h"
+#include "lib/config.h"
+#include "lib/player_manager.h"
+#include "lib/film.h"
+#include "film_player.h"
+#include "gtk_util.h"
+
+using namespace std;
+using namespace boost;
+
+FilmPlayer::FilmPlayer (Film const * f)
+ : _play ("Play")
+ , _pause ("Pause")
+ , _stop ("Stop")
+ , _ab ("A/B")
+ , _ignore_position_changed (false)
+{
+ set_film (f);
+
+ vector<shared_ptr<Screen> > const scr = Config::instance()->screens ();
+ for (vector<shared_ptr<Screen> >::const_iterator i = scr.begin(); i != scr.end(); ++i) {
+ _screen.append_text ((*i)->name ());
+ }
+
+ if (!scr.empty ()) {
+ _screen.set_active (0);
+ }
+
+ _status.set_use_markup (true);
+
+ _position.set_digits (0);
+
+ _main_vbox.set_spacing (12);
+
+ Gtk::HBox* l = manage (new Gtk::HBox);
+ l->pack_start (_play);
+ l->pack_start (_pause);
+ l->pack_start (_stop);
+
+ Gtk::VBox* r = manage (new Gtk::VBox);
+ r->pack_start (_screen, false, false);
+ r->pack_start (_ab, false, false);
+ r->pack_start (*manage (new Gtk::Label ("")), true, true);
+
+ Gtk::HBox* t = manage (new Gtk::HBox);
+ t->pack_start (*l, true, true);
+ t->pack_start (*r, false, false);
+
+ _main_vbox.pack_start (*t, true, true);
+ _main_vbox.pack_start (_position, false, false);
+ _main_vbox.pack_start (_status, false, false);
+
+ _play.signal_clicked().connect (sigc::mem_fun (*this, &FilmPlayer::play_clicked));
+ _pause.signal_clicked().connect (sigc::mem_fun (*this, &FilmPlayer::pause_clicked));
+ _stop.signal_clicked().connect (sigc::mem_fun (*this, &FilmPlayer::stop_clicked));
+ _position.signal_value_changed().connect (sigc::mem_fun (*this, &FilmPlayer::position_changed));
+ _position.signal_format_value().connect (sigc::mem_fun (*this, &FilmPlayer::format_position));
+
+ set_button_states ();
+ Glib::signal_timeout().connect (sigc::bind_return (sigc::mem_fun (*this, &FilmPlayer::update), true), 1000);
+
+ Config::instance()->Changed.connect (sigc::mem_fun (*this, &FilmPlayer::update_screens));
+}
+
+void
+FilmPlayer::set_film (Film const * f)
+{
+ _film = f;
+
+ if (_film && _film->length() != 0 && _film->frames_per_second() != 0) {
+ _position.set_range (0, _film->length() / _film->frames_per_second());
+ }
+
+ if (_film) {
+ _film->Changed.connect (sigc::mem_fun (*this, &FilmPlayer::film_changed));
+ }
+}
+
+Gtk::Widget &
+FilmPlayer::widget ()
+{
+ return _main_vbox;
+}
+
+void
+FilmPlayer::set_button_states ()
+{
+ if (_film == 0) {
+ _play.set_sensitive (false);
+ _pause.set_sensitive (false);
+ _stop.set_sensitive (false);
+ _screen.set_sensitive (false);
+ _position.set_sensitive (false);
+ _ab.set_sensitive (false);
+ return;
+ }
+
+ PlayerManager::State s = PlayerManager::instance()->state ();
+
+ switch (s) {
+ case PlayerManager::QUIESCENT:
+ _play.set_sensitive (true);
+ _pause.set_sensitive (false);
+ _stop.set_sensitive (false);
+ _screen.set_sensitive (true);
+ _position.set_sensitive (false);
+ _ab.set_sensitive (true);
+ break;
+ case PlayerManager::PLAYING:
+ _play.set_sensitive (false);
+ _pause.set_sensitive (true);
+ _stop.set_sensitive (true);
+ _screen.set_sensitive (false);
+ _position.set_sensitive (true);
+ _ab.set_sensitive (false);
+ break;
+ case PlayerManager::PAUSED:
+ _play.set_sensitive (true);
+ _pause.set_sensitive (false);
+ _stop.set_sensitive (true);
+ _screen.set_sensitive (false);
+ _position.set_sensitive (false);
+ _ab.set_sensitive (false);
+ break;
+ }
+}
+
+void
+FilmPlayer::play_clicked ()
+{
+ PlayerManager* p = PlayerManager::instance ();
+
+ switch (p->state ()) {
+ case PlayerManager::QUIESCENT:
+ _last_play_fs = _film->state_copy ();
+ if (_ab.get_active ()) {
+ shared_ptr<FilmState> fs_a = _film->state_copy ();
+ fs_a->filters.clear ();
+ /* This is somewhat arbitrary, but hey ho */
+ fs_a->scaler = Scaler::from_id ("bicubic");
+ p->setup (fs_a, _last_play_fs, screen ());
+ } else {
+ p->setup (_last_play_fs, screen ());
+ }
+ p->pause_or_unpause ();
+ break;
+ case PlayerManager::PLAYING:
+ break;
+ case PlayerManager::PAUSED:
+ p->pause_or_unpause ();
+ break;
+ }
+}
+
+void
+FilmPlayer::pause_clicked ()
+{
+ PlayerManager* p = PlayerManager::instance ();
+
+ switch (p->state ()) {
+ case PlayerManager::QUIESCENT:
+ break;
+ case PlayerManager::PLAYING:
+ p->pause_or_unpause ();
+ break;
+ case PlayerManager::PAUSED:
+ break;
+ }
+}
+
+void
+FilmPlayer::stop_clicked ()
+{
+ PlayerManager::instance()->stop ();
+}
+
+shared_ptr<Screen>
+FilmPlayer::screen () const
+{
+ vector<shared_ptr<Screen> > const s = Config::instance()->screens ();
+ if (s.empty ()) {
+ return shared_ptr<Screen> ();
+ }
+
+ int const r = _screen.get_active_row_number ();
+ if (r >= int (s.size ())) {
+ return s[0];
+ }
+
+ return s[r];
+}
+
+void
+FilmPlayer::update ()
+{
+ set_button_states ();
+ set_status ();
+}
+
+void
+FilmPlayer::set_status ()
+{
+ PlayerManager::State s = PlayerManager::instance()->state ();
+
+ stringstream m;
+ switch (s) {
+ case PlayerManager::QUIESCENT:
+ m << "Idle";
+ break;
+ case PlayerManager::PLAYING:
+ m << "<span foreground=\"red\" weight=\"bold\">PLAYING</span>";
+ break;
+ case PlayerManager::PAUSED:
+ m << "<b>Paused</b>";
+ break;
+ }
+
+ _ignore_position_changed = true;
+
+ if (s != PlayerManager::QUIESCENT) {
+ float const p = PlayerManager::instance()->position ();
+ if (_last_play_fs->frames_per_second != 0 && _last_play_fs->length != 0) {
+ m << " <i>(" << seconds_to_hms (_last_play_fs->length / _last_play_fs->frames_per_second - p) << " remaining)</i>";
+ }
+
+ _position.set_value (p);
+ } else {
+ _position.set_value (0);
+ }
+
+ _ignore_position_changed = false;
+
+ _status.set_markup (m.str ());
+}
+
+void
+FilmPlayer::position_changed ()
+{
+ if (_ignore_position_changed) {
+ return;
+ }
+
+ PlayerManager::instance()->set_position (_position.get_value ());
+}
+
+string
+FilmPlayer::format_position (double v)
+{
+ return seconds_to_hms (v);
+}
+
+void
+FilmPlayer::update_screens ()
+{
+ string const c = _screen.get_active_text ();
+
+ _screen.clear ();
+
+ vector<shared_ptr<Screen> > const scr = Config::instance()->screens ();
+ bool have_last_active_text = false;
+ for (vector<shared_ptr<Screen> >::const_iterator i = scr.begin(); i != scr.end(); ++i) {
+ _screen.append_text ((*i)->name ());
+ if ((*i)->name() == c) {
+ have_last_active_text = true;
+ }
+ }
+
+ if (have_last_active_text) {
+ _screen.set_active_text (c);
+ } else if (!scr.empty ()) {
+ _screen.set_active (0);
+ }
+}
+
+void
+FilmPlayer::film_changed (Film::Property p)
+{
+ if (p == Film::CONTENT) {
+ setup_visibility ();
+ }
+}
+
+void
+FilmPlayer::setup_visibility ()
+{
+ if (!_film) {
+ return;
+ }
+
+ widget().property_visible() = (_film->content_type() == VIDEO);
+}
diff --git a/src/gtk/film_player.h b/src/gtk/film_player.h
new file mode 100644
index 000000000..bb60eeb3b
--- /dev/null
+++ b/src/gtk/film_player.h
@@ -0,0 +1,63 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <gtkmm.h>
+#include "lib/film.h"
+
+class Film;
+class Screen;
+class FilmState;
+
+class FilmPlayer
+{
+public:
+ FilmPlayer (Film const * f = 0);
+
+ Gtk::Widget& widget ();
+
+ void set_film (Film const *);
+ void setup_visibility ();
+
+private:
+ void play_clicked ();
+ void pause_clicked ();
+ void stop_clicked ();
+ void position_changed ();
+ std::string format_position (double);
+ void film_changed (Film::Property);
+
+ void set_button_states ();
+ boost::shared_ptr<Screen> screen () const;
+ void set_status ();
+ void update ();
+ void update_screens ();
+
+ Film const * _film;
+ boost::shared_ptr<const FilmState> _last_play_fs;
+
+ Gtk::VBox _main_vbox;
+ Gtk::Button _play;
+ Gtk::Button _pause;
+ Gtk::Button _stop;
+ Gtk::Label _status;
+ Gtk::CheckButton _ab;
+ Gtk::ComboBoxText _screen;
+ Gtk::HScale _position;
+ bool _ignore_position_changed;
+};
diff --git a/src/gtk/film_viewer.cc b/src/gtk/film_viewer.cc
new file mode 100644
index 000000000..0408d50b8
--- /dev/null
+++ b/src/gtk/film_viewer.cc
@@ -0,0 +1,224 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/film_viewer.cc
+ * @brief A GTK widget to view `thumbnails' of a Film.
+ */
+
+#include <iostream>
+#include <iomanip>
+#include "lib/film.h"
+#include "lib/format.h"
+#include "lib/util.h"
+#include "lib/thumbs_job.h"
+#include "lib/job_manager.h"
+#include "lib/film_state.h"
+#include "lib/options.h"
+#include "film_viewer.h"
+
+using namespace std;
+using namespace boost;
+
+FilmViewer::FilmViewer (Film* f)
+ : _film (f)
+ , _update_button ("Update")
+{
+ _scroller.add (_image);
+
+ Gtk::HBox* controls = manage (new Gtk::HBox);
+ controls->set_spacing (6);
+ controls->pack_start (_update_button, false, false);
+ controls->pack_start (_position_slider);
+
+ _vbox.pack_start (_scroller, true, true);
+ _vbox.pack_start (*controls, false, false);
+ _vbox.set_border_width (12);
+
+ _update_button.signal_clicked().connect (sigc::mem_fun (*this, &FilmViewer::update_thumbs));
+
+ _position_slider.set_digits (0);
+ _position_slider.signal_format_value().connect (sigc::mem_fun (*this, &FilmViewer::format_position_slider_value));
+ _position_slider.signal_value_changed().connect (sigc::mem_fun (*this, &FilmViewer::position_slider_changed));
+
+ _scroller.signal_size_allocate().connect (sigc::mem_fun (*this, &FilmViewer::scroller_size_allocate));
+
+ set_film (_film);
+}
+
+void
+FilmViewer::load_thumbnail (int n)
+{
+ if (_film == 0 || _film->num_thumbs() <= n) {
+ return;
+ }
+
+ int const left = _film->left_crop ();
+ int const right = _film->right_crop ();
+ int const top = _film->top_crop ();
+ int const bottom = _film->bottom_crop ();
+
+ _pixbuf = Gdk::Pixbuf::create_from_file (_film->thumb_file (n));
+
+ int const cw = _film->size().width - left - right;
+ int const ch = _film->size().height - top - bottom;
+ _cropped_pixbuf = Gdk::Pixbuf::create_subpixbuf (_pixbuf, left, top, cw, ch);
+ update_scaled_pixbuf ();
+ _image.set (_scaled_pixbuf);
+}
+
+void
+FilmViewer::reload_current_thumbnail ()
+{
+ load_thumbnail (_position_slider.get_value ());
+}
+
+void
+FilmViewer::position_slider_changed ()
+{
+ reload_current_thumbnail ();
+}
+
+string
+FilmViewer::format_position_slider_value (double v) const
+{
+ stringstream s;
+
+ if (_film && int (v) < _film->num_thumbs ()) {
+ int const f = _film->thumb_frame (int (v));
+ s << f << " " << seconds_to_hms (f / _film->frames_per_second ());
+ } else {
+ s << "-";
+ }
+
+ return s.str ();
+}
+
+void
+FilmViewer::film_changed (Film::Property p)
+{
+ if (p == Film::LEFT_CROP || p == Film::RIGHT_CROP || p == Film::TOP_CROP || p == Film::BOTTOM_CROP) {
+ reload_current_thumbnail ();
+ } else if (p == Film::THUMBS) {
+ if (_film && _film->num_thumbs() > 1) {
+ _position_slider.set_range (0, _film->num_thumbs () - 1);
+ } else {
+ _image.clear ();
+ _position_slider.set_range (0, 1);
+ }
+
+ _position_slider.set_value (0);
+ reload_current_thumbnail ();
+ } else if (p == Film::FORMAT) {
+ reload_current_thumbnail ();
+ } else if (p == Film::CONTENT) {
+ setup_visibility ();
+ update_thumbs ();
+ }
+}
+
+void
+FilmViewer::set_film (Film* f)
+{
+ _film = f;
+
+ _update_button.set_sensitive (_film != 0);
+
+ if (!_film) {
+ _image.clear ();
+ return;
+ }
+
+ _film->Changed.connect (sigc::mem_fun (*this, &FilmViewer::film_changed));
+
+ film_changed (Film::THUMBS);
+}
+
+pair<int, int>
+FilmViewer::scaled_pixbuf_size () const
+{
+ if (_film == 0) {
+ return make_pair (0, 0);
+ }
+
+ int const cw = _film->size().width - _film->left_crop() - _film->right_crop();
+ int const ch = _film->size().height - _film->top_crop() - _film->bottom_crop();
+
+ float ratio = 1;
+ if (_film->format()) {
+ ratio = _film->format()->ratio_as_float() * ch / cw;
+ }
+
+ Gtk::Allocation const a = _scroller.get_allocation ();
+ float const zoom = min (float (a.get_width()) / (cw * ratio), float (a.get_height()) / cw);
+ return make_pair (cw * zoom * ratio, ch * zoom);
+}
+
+void
+FilmViewer::update_scaled_pixbuf ()
+{
+ pair<int, int> const s = scaled_pixbuf_size ();
+
+ if (s.first > 0 && s.second > 0 && _cropped_pixbuf) {
+ _scaled_pixbuf = _cropped_pixbuf->scale_simple (s.first, s.second, Gdk::INTERP_HYPER);
+ _image.set (_scaled_pixbuf);
+ }
+}
+
+void
+FilmViewer::update_thumbs ()
+{
+ if (!_film) {
+ return;
+ }
+
+ _film->update_thumbs_pre_gui ();
+
+ shared_ptr<const FilmState> s = _film->state_copy ();
+ shared_ptr<Options> o (new Options (s->dir ("thumbs"), ".tiff", ""));
+ o->out_size = _film->size ();
+ o->apply_crop = false;
+ o->decode_audio = false;
+ o->decode_video_frequency = 128;
+
+ shared_ptr<Job> j (new ThumbsJob (s, o, _film->log ()));
+ j->Finished.connect (sigc::mem_fun (_film, &Film::update_thumbs_post_gui));
+ JobManager::instance()->add (j);
+}
+
+void
+FilmViewer::scroller_size_allocate (Gtk::Allocation a)
+{
+ if (a.get_width() != _last_scroller_allocation.get_width() || a.get_height() != _last_scroller_allocation.get_height()) {
+ update_scaled_pixbuf ();
+ }
+
+ _last_scroller_allocation = a;
+}
+
+void
+FilmViewer::setup_visibility ()
+{
+ if (!_film) {
+ return;
+ }
+
+ ContentType const c = _film->content_type ();
+ _update_button.property_visible() = (c == VIDEO);
+ _position_slider.property_visible() = (c == VIDEO);
+}
diff --git a/src/gtk/film_viewer.h b/src/gtk/film_viewer.h
new file mode 100644
index 000000000..e01d6c096
--- /dev/null
+++ b/src/gtk/film_viewer.h
@@ -0,0 +1,63 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/film_viewer.h
+ * @brief A GTK widget to view `thumbnails' of a Film.
+ */
+
+#include <gtkmm.h>
+#include "lib/film.h"
+
+/** @class FilmViewer
+ * @brief A GTK widget to view `thumbnails' of a Film.
+ */
+class FilmViewer
+{
+public:
+ FilmViewer (Film *);
+
+ Gtk::Widget& widget () {
+ return _vbox;
+ }
+
+ void set_film (Film *);
+ void setup_visibility ();
+
+private:
+ void position_slider_changed ();
+ void update_thumbs ();
+ std::string format_position_slider_value (double) const;
+ void load_thumbnail (int);
+ void film_changed (Film::Property);
+ void reload_current_thumbnail ();
+ void update_scaled_pixbuf ();
+ std::pair<int, int> scaled_pixbuf_size () const;
+ void scroller_size_allocate (Gtk::Allocation);
+
+ Film* _film;
+ Gtk::VBox _vbox;
+ Gtk::ScrolledWindow _scroller;
+ Gtk::Image _image;
+ Glib::RefPtr<Gdk::Pixbuf> _pixbuf;
+ Glib::RefPtr<Gdk::Pixbuf> _cropped_pixbuf;
+ Glib::RefPtr<Gdk::Pixbuf> _scaled_pixbuf;
+ Gtk::HScale _position_slider;
+ Gtk::Button _update_button;
+ Gtk::Allocation _last_scroller_allocation;
+};
diff --git a/src/gtk/filter_dialog.cc b/src/gtk/filter_dialog.cc
new file mode 100644
index 000000000..e52efb68b
--- /dev/null
+++ b/src/gtk/filter_dialog.cc
@@ -0,0 +1,47 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/filter_dialog.cc
+ * @brief A dialog to select FFmpeg filters.
+ */
+
+#include "lib/film.h"
+#include "filter_dialog.h"
+
+using namespace std;
+
+FilterDialog::FilterDialog (vector<Filter const *> const & f)
+ : Gtk::Dialog ("Filters")
+ , _filters (f)
+{
+ get_vbox()->pack_start (_filters.widget ());
+
+ _filters.ActiveChanged.connect (sigc::mem_fun (*this, &FilterDialog::active_changed));
+
+ add_button ("Close", Gtk::RESPONSE_CLOSE);
+
+ show_all ();
+}
+
+
+void
+FilterDialog::active_changed ()
+{
+ ActiveChanged (_filters.active ());
+}
diff --git a/src/gtk/filter_dialog.h b/src/gtk/filter_dialog.h
new file mode 100644
index 000000000..84c6e2966
--- /dev/null
+++ b/src/gtk/filter_dialog.h
@@ -0,0 +1,43 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/filter_dialog.h
+ * @brief A dialog to select FFmpeg filters.
+ */
+
+#include <gtkmm.h>
+#include "filter_view.h"
+
+class Film;
+
+/** @class FilterDialog
+ * @brief A dialog to select FFmpeg filters.
+ */
+class FilterDialog : public Gtk::Dialog
+{
+public:
+ FilterDialog (std::vector<Filter const *> const &);
+
+ sigc::signal1<void, std::vector<Filter const *> > ActiveChanged;
+
+private:
+ void active_changed ();
+
+ FilterView _filters;
+};
diff --git a/src/gtk/filter_view.cc b/src/gtk/filter_view.cc
new file mode 100644
index 000000000..f686c204d
--- /dev/null
+++ b/src/gtk/filter_view.cc
@@ -0,0 +1,72 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/filter_view.cc
+ * @brief A widget to select FFmpeg filters.
+ */
+
+#include <iostream>
+#include "lib/filter.h"
+#include "filter_view.h"
+
+using namespace std;
+
+FilterView::FilterView (vector<Filter const *> const & active)
+{
+ _box.set_spacing (4);
+
+ vector<Filter const *> filters = Filter::all ();
+
+ for (vector<Filter const *>::iterator i = filters.begin(); i != filters.end(); ++i) {
+ Gtk::CheckButton* b = Gtk::manage (new Gtk::CheckButton ((*i)->name()));
+ bool const a = find (active.begin(), active.end(), *i) != active.end ();
+ b->set_active (a);
+ _filters[*i] = a;
+ b->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &FilterView::filter_toggled), *i));
+ _box.pack_start (*b, false, false);
+ }
+
+ _box.show_all ();
+}
+
+Gtk::Widget &
+FilterView::widget ()
+{
+ return _box;
+}
+
+void
+FilterView::filter_toggled (Filter const * f)
+{
+ _filters[f] = !_filters[f];
+ ActiveChanged ();
+}
+
+vector<Filter const*>
+FilterView::active () const
+{
+ vector<Filter const *> active;
+ for (map<Filter const *, bool>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
+ if (i->second) {
+ active.push_back (i->first);
+ }
+ }
+
+ return active;
+}
diff --git a/src/gtk/filter_view.h b/src/gtk/filter_view.h
new file mode 100644
index 000000000..0c96b0e14
--- /dev/null
+++ b/src/gtk/filter_view.h
@@ -0,0 +1,47 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/filter_view.h
+ * @brief A widget to select FFmpeg filters.
+ */
+
+#include <gtkmm.h>
+#include <vector>
+
+class Filter;
+
+/** @class FilterView
+ * @brief A widget to select FFmpeg filters.
+ */
+class FilterView
+{
+public:
+ FilterView (std::vector<Filter const *> const &);
+
+ Gtk::Widget & widget ();
+ std::vector<Filter const *> active () const;
+
+ sigc::signal0<void> ActiveChanged;
+
+private:
+ void filter_toggled (Filter const *);
+
+ Gtk::VBox _box;
+ std::map<Filter const *, bool> _filters;
+};
diff --git a/src/gtk/gpl.cc b/src/gtk/gpl.cc
new file mode 100644
index 000000000..b7bcae9e0
--- /dev/null
+++ b/src/gtk/gpl.cc
@@ -0,0 +1,370 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/gpl.cc
+ * @brief The GPL.
+ */
+
+const char* gpl = "\n\
+DVD-o-matic comes with NO WARRANTY. It is free software, and you are \n\
+welcome to redistribute it under the terms of the GNU Public License,\n\
+shown below.\n \
+\n\
+ GNU GENERAL PUBLIC LICENSE\n\
+ Version 2, June 1991\n\
+\n\
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.\n\
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n\
+ Everyone is permitted to copy and distribute verbatim copies\n\
+ of this license document, but changing it is not allowed.\n\
+\n\
+ Preamble\n\
+\n\
+ The licenses for most software are designed to take away your\n\
+freedom to share and change it. By contrast, the GNU General Public\n\
+License is intended to guarantee your freedom to share and change free\n\
+software--to make sure the software is free for all its users. This\n\
+General Public License applies to most of the Free Software\n\
+Foundation's software and to any other program whose authors commit to\n\
+using it. (Some other Free Software Foundation software is covered by\n\
+the GNU Library General Public License instead.) You can apply it to\n\
+your programs, too.\n\
+\n\
+ When we speak of free software, we are referring to freedom, not\n\
+price. Our General Public Licenses are designed to make sure that you\n\
+have the freedom to distribute copies of free software (and charge for\n\
+this service if you wish), that you receive source code or can get it\n\
+if you want it, that you can change the software or use pieces of it\n\
+in new free programs; and that you know you can do these things.\n\
+\n\
+ To protect your rights, we need to make restrictions that forbid\n\
+anyone to deny you these rights or to ask you to surrender the rights.\n\
+These restrictions translate to certain responsibilities for you if you\n\
+distribute copies of the software, or if you modify it.\n\
+\n\
+ For example, if you distribute copies of such a program, whether\n\
+gratis or for a fee, you must give the recipients all the rights that\n\
+you have. You must make sure that they, too, receive or can get the\n\
+source code. And you must show them these terms so they know their\n\
+rights.\n\
+\n\
+ We protect your rights with two steps: (1) copyright the software, and\n\
+(2) offer you this license which gives you legal permission to copy,\n\
+distribute and/or modify the software.\n\
+\n\
+ Also, for each author's protection and ours, we want to make certain\n\
+that everyone understands that there is no warranty for this free\n\
+software. If the software is modified by someone else and passed on, we\n\
+want its recipients to know that what they have is not the original, so\n\
+that any problems introduced by others will not reflect on the original\n\
+authors' reputations.\n\
+\n\
+ Finally, any free program is threatened constantly by software\n\
+patents. We wish to avoid the danger that redistributors of a free\n\
+program will individually obtain patent licenses, in effect making the\n\
+program proprietary. To prevent this, we have made it clear that any\n\
+patent must be licensed for everyone's free use or not licensed at all.\n\
+\n\
+ The precise terms and conditions for copying, distribution and\n\
+modification follow.\n\
+\n\
+ GNU GENERAL PUBLIC LICENSE\n\
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\
+\n\
+ 0. This License applies to any program or other work which contains\n\
+a notice placed by the copyright holder saying it may be distributed\n\
+under the terms of this General Public License. The \"Program\", below,\n\
+refers to any such program or work, and a \"work based on the Program\"\n\
+means either the Program or any derivative work under copyright law:\n\
+that is to say, a work containing the Program or a portion of it,\n\
+either verbatim or with modifications and/or translated into another\n\
+language. (Hereinafter, translation is included without limitation in\n\
+the term \"modification\".) Each licensee is addressed as \"you\".\n\
+\n\
+Activities other than copying, distribution and modification are not\n\
+covered by this License; they are outside its scope. The act of\n\
+running the Program is not restricted, and the output from the Program\n\
+is covered only if its contents constitute a work based on the\n\
+Program (independent of having been made by running the Program).\n\
+Whether that is true depends on what the Program does.\n\
+\n\
+ 1. You may copy and distribute verbatim copies of the Program's\n\
+source code as you receive it, in any medium, provided that you\n\
+conspicuously and appropriately publish on each copy an appropriate\n\
+copyright notice and disclaimer of warranty; keep intact all the\n\
+notices that refer to this License and to the absence of any warranty;\n\
+and give any other recipients of the Program a copy of this License\n\
+along with the Program.\n\
+\n\
+You may charge a fee for the physical act of transferring a copy, and\n\
+you may at your option offer warranty protection in exchange for a fee.\n\
+\n\
+ 2. You may modify your copy or copies of the Program or any portion\n\
+of it, thus forming a work based on the Program, and copy and\n\
+distribute such modifications or work under the terms of Section 1\n\
+above, provided that you also meet all of these conditions:\n\
+\n\
+ a) You must cause the modified files to carry prominent notices\n\
+ stating that you changed the files and the date of any change.\n\
+\n\
+ b) You must cause any work that you distribute or publish, that in\n\
+ whole or in part contains or is derived from the Program or any\n\
+ part thereof, to be licensed as a whole at no charge to all third\n\
+ parties under the terms of this License.\n\
+\n\
+ c) If the modified program normally reads commands interactively\n\
+ when run, you must cause it, when started running for such\n\
+ interactive use in the most ordinary way, to print or display an\n\
+ announcement including an appropriate copyright notice and a\n\
+ notice that there is no warranty (or else, saying that you provide\n\
+ a warranty) and that users may redistribute the program under\n\
+ these conditions, and telling the user how to view a copy of this\n\
+ License. (Exception: if the Program itself is interactive but\n\
+ does not normally print such an announcement, your work based on\n\
+ the Program is not required to print an announcement.)\n\
+\n\
+These requirements apply to the modified work as a whole. If\n\
+identifiable sections of that work are not derived from the Program,\n\
+and can be reasonably considered independent and separate works in\n\
+themselves, then this License, and its terms, do not apply to those\n\
+sections when you distribute them as separate works. But when you\n\
+distribute the same sections as part of a whole which is a work based\n\
+on the Program, the distribution of the whole must be on the terms of\n\
+this License, whose permissions for other licensees extend to the\n\
+entire whole, and thus to each and every part regardless of who wrote it.\n\
+\n\
+Thus, it is not the intent of this section to claim rights or contest\n\
+your rights to work written entirely by you; rather, the intent is to\n\
+exercise the right to control the distribution of derivative or\n\
+collective works based on the Program.\n\
+\n\
+In addition, mere aggregation of another work not based on the Program\n\
+with the Program (or with a work based on the Program) on a volume of\n\
+a storage or distribution medium does not bring the other work under\n\
+the scope of this License.\n\
+\n\
+ 3. You may copy and distribute the Program (or a work based on it,\n\
+under Section 2) in object code or executable form under the terms of\n\
+Sections 1 and 2 above provided that you also do one of the following:\n\
+\n\
+ a) Accompany it with the complete corresponding machine-readable\n\
+ source code, which must be distributed under the terms of Sections\n\
+ 1 and 2 above on a medium customarily used for software interchange; or,\n\
+\n\
+ b) Accompany it with a written offer, valid for at least three\n\
+ years, to give any third party, for a charge no more than your\n\
+ cost of physically performing source distribution, a complete\n\
+ machine-readable copy of the corresponding source code, to be\n\
+ distributed under the terms of Sections 1 and 2 above on a medium\n\
+ customarily used for software interchange; or,\n\
+\n\
+ c) Accompany it with the information you received as to the offer\n\
+ to distribute corresponding source code. (This alternative is\n\
+ allowed only for noncommercial distribution and only if you\n\
+ received the program in object code or executable form with such\n\
+ an offer, in accord with Subsection b above.)\n\
+\n\
+The source code for a work means the preferred form of the work for\n\
+making modifications to it. For an executable work, complete source\n\
+code means all the source code for all modules it contains, plus any\n\
+associated interface definition files, plus the scripts used to\n\
+control compilation and installation of the executable. However, as a\n\
+special exception, the source code distributed need not include\n\
+anything that is normally distributed (in either source or binary\n\
+form) with the major components (compiler, kernel, and so on) of the\n\
+operating system on which the executable runs, unless that component\n\
+itself accompanies the executable.\n\
+\n\
+If distribution of executable or object code is made by offering\n\
+access to copy from a designated place, then offering equivalent\n\
+access to copy the source code from the same place counts as\n\
+distribution of the source code, even though third parties are not\n\
+compelled to copy the source along with the object code.\n\
+ \n\
+ 4. You may not copy, modify, sublicense, or distribute the Program\n\
+except as expressly provided under this License. Any attempt\n\
+otherwise to copy, modify, sublicense or distribute the Program is\n\
+void, and will automatically terminate your rights under this License.\n\
+However, parties who have received copies, or rights, from you under\n\
+this License will not have their licenses terminated so long as such\n\
+parties remain in full compliance.\n\
+\n\
+ 5. You are not required to accept this License, since you have not\n\
+signed it. However, nothing else grants you permission to modify or\n\
+distribute the Program or its derivative works. These actions are\n\
+prohibited by law if you do not accept this License. Therefore, by\n\
+modifying or distributing the Program (or any work based on the\n\
+Program), you indicate your acceptance of this License to do so, and\n\
+all its terms and conditions for copying, distributing or modifying\n\
+the Program or works based on it.\n\
+\n\
+ 6. Each time you redistribute the Program (or any work based on the\n\
+Program), the recipient automatically receives a license from the\n\
+original licensor to copy, distribute or modify the Program subject to\n\
+these terms and conditions. You may not impose any further\n\
+restrictions on the recipients' exercise of the rights granted herein.\n\
+You are not responsible for enforcing compliance by third parties to\n\
+this License.\n\
+\n\
+ 7. If, as a consequence of a court judgment or allegation of patent\n\
+infringement or for any other reason (not limited to patent issues),\n\
+conditions are imposed on you (whether by court order, agreement or\n\
+otherwise) that contradict the conditions of this License, they do not\n\
+excuse you from the conditions of this License. If you cannot\n\
+distribute so as to satisfy simultaneously your obligations under this\n\
+License and any other pertinent obligations, then as a consequence you\n\
+may not distribute the Program at all. For example, if a patent\n\
+license would not permit royalty-free redistribution of the Program by\n\
+all those who receive copies directly or indirectly through you, then\n\
+the only way you could satisfy both it and this License would be to\n\
+refrain entirely from distribution of the Program.\n\
+\n\
+If any portion of this section is held invalid or unenforceable under\n\
+any particular circumstance, the balance of the section is intended to\n\
+apply and the section as a whole is intended to apply in other\n\
+circumstances.\n\
+\n\
+It is not the purpose of this section to induce you to infringe any\n\
+patents or other property right claims or to contest validity of any\n\
+such claims; this section has the sole purpose of protecting the\n\
+integrity of the free software distribution system, which is\n\
+implemented by public license practices. Many people have made\n\
+generous contributions to the wide range of software distributed\n\
+through that system in reliance on consistent application of that\n\
+system; it is up to the author/donor to decide if he or she is willing\n\
+to distribute software through any other system and a licensee cannot\n\
+impose that choice.\n\
+\n\
+This section is intended to make thoroughly clear what is believed to\n\
+be a consequence of the rest of this License.\n\
+\n\
+ 8. If the distribution and/or use of the Program is restricted in\n\
+certain countries either by patents or by copyrighted interfaces, the\n\
+original copyright holder who places the Program under this License\n\
+may add an explicit geographical distribution limitation excluding\n\
+those countries, so that distribution is permitted only in or among\n\
+countries not thus excluded. In such case, this License incorporates\n\
+the limitation as if written in the body of this License.\n\
+\n\
+ 9. The Free Software Foundation may publish revised and/or new versions\n\
+of the General Public License from time to time. Such new versions will\n\
+be similar in spirit to the present version, but may differ in detail to\n\
+address new problems or concerns.\n\
+\n\
+Each version is given a distinguishing version number. If the Program\n\
+specifies a version number of this License which applies to it and \"any\n\
+later version\", you have the option of following the terms and conditions\n\
+either of that version or of any later version published by the Free\n\
+Software Foundation. If the Program does not specify a version number of\n\
+this License, you may choose any version ever published by the Free Software\n\
+Foundation.\n\
+\n\
+ 10. If you wish to incorporate parts of the Program into other free\n\
+programs whose distribution conditions are different, write to the author\n\
+to ask for permission. For software which is copyrighted by the Free\n\
+Software Foundation, write to the Free Software Foundation; we sometimes\n\
+make exceptions for this. Our decision will be guided by the two goals\n\
+of preserving the free status of all derivatives of our free software and\n\
+of promoting the sharing and reuse of software generally.\n\
+\n\
+ NO WARRANTY\n\
+\n\
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\n\
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN\n\
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\n\
+PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\n\
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\n\
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS\n\
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE\n\
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\n\
+REPAIR OR CORRECTION.\n\
+\n\
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\n\
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\n\
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\n\
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\n\
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\n\
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\n\
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\n\
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\n\
+POSSIBILITY OF SUCH DAMAGES.\n\
+\n\
+ END OF TERMS AND CONDITIONS\n\
+\n\
+ How to Apply These Terms to Your New Programs\n\
+\n\
+ If you develop a new program, and you want it to be of the greatest\n\
+possible use to the public, the best way to achieve this is to make it\n\
+free software which everyone can redistribute and change under these terms.\n\
+\n\
+ To do so, attach the following notices to the program. It is safest\n\
+to attach them to the start of each source file to most effectively\n\
+convey the exclusion of warranty; and each file should have at least\n\
+the \"copyright\" line and a pointer to where the full notice is found.\n\
+\n\
+ <one line to give the program's name and a brief idea of what it does.>\n\
+ Copyright (C) <year> <name of author>\n\
+\n\
+ This program is free software; you can redistribute it and/or modify\n\
+ it under the terms of the GNU General Public License as published by\n\
+ the Free Software Foundation; either version 2 of the License, or\n\
+ (at your option) any later version.\n\
+\n\
+ This program is distributed in the hope that it will be useful,\n\
+ but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\
+ GNU General Public License for more details.\n\
+\n\
+ You should have received a copy of the GNU General Public License\n\
+ along with this program; if not, write to the Free Software\n\
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n\
+\n\
+\n\
+Also add information on how to contact you by electronic and paper mail.\n\
+\n\
+If the program is interactive, make it output a short notice like this\n\
+when it starts in an interactive mode:\n\
+\n\
+ Gnomovision version 69, Copyright (C) year name of author\n\
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n\
+ This is free software, and you are welcome to redistribute it\n\
+ under certain conditions; type `show c' for details.\n\
+\n\
+The hypothetical commands `show w' and `show c' should show the appropriate\n\
+parts of the General Public License. Of course, the commands you use may\n\
+be called something other than `show w' and `show c'; they could even be\n\
+mouse-clicks or menu items--whatever suits your program.\n\
+\n\
+You should also get your employer (if you work as a programmer) or your\n\
+school, if any, to sign a \"copyright disclaimer\" for the program, if\n\
+necessary. Here is a sample; alter the names:\n\
+\n\
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n\
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\
+\n\
+ <signature of Ty Coon>, 1 April 1989\n\
+ Ty Coon, President of Vice\n\
+\n\
+This General Public License does not permit incorporating your program into\n\
+proprietary programs. If your program is a subroutine library, you may\n\
+consider it more useful to permit linking proprietary applications with the\n\
+library. If this is what you want to do, use the GNU Library General\n\
+Public License instead of this License.\n\
+";
+
diff --git a/src/gtk/gpl.h b/src/gtk/gpl.h
new file mode 100644
index 000000000..c9c4ffe46
--- /dev/null
+++ b/src/gtk/gpl.h
@@ -0,0 +1,24 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/gpl.h
+ * @brief The GPL.
+ */
+
+extern const char * gpl;
diff --git a/src/gtk/gtk_util.cc b/src/gtk/gtk_util.cc
new file mode 100644
index 000000000..41f8cb5b5
--- /dev/null
+++ b/src/gtk/gtk_util.cc
@@ -0,0 +1,45 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/gtk/util.cc
+ * @brief Some utility functions.
+ */
+
+#include <gtkmm.h>
+
+using namespace std;
+
+/** @param t Label text.
+ * @return GTK label containing t, left-aligned (passed through Gtk::manage)
+ */
+Gtk::Label &
+left_aligned_label (string t)
+{
+ Gtk::Label* l = Gtk::manage (new Gtk::Label (t));
+ l->set_alignment (0, 0.5);
+ return *l;
+}
+
+void
+error_dialog (string m)
+{
+ Gtk::MessageDialog d (m, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
+ d.set_title ("DVD-o-matic");
+ d.run ();
+}
diff --git a/src/gtk/gtk_util.h b/src/gtk/gtk_util.h
new file mode 100644
index 000000000..518842872
--- /dev/null
+++ b/src/gtk/gtk_util.h
@@ -0,0 +1,27 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <gtkmm.h>
+
+/** @file src/gtk/util.h
+ * @brief Some utility functions.
+ */
+
+extern void error_dialog (std::string);
+extern Gtk::Label & left_aligned_label (std::string);
diff --git a/src/gtk/job_manager_view.cc b/src/gtk/job_manager_view.cc
new file mode 100644
index 000000000..60c13990d
--- /dev/null
+++ b/src/gtk/job_manager_view.cc
@@ -0,0 +1,134 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/job_manager_view.cc
+ * @brief Class generating a GTK widget to show the progress of jobs.
+ */
+
+#include "lib/job_manager.h"
+#include "lib/job.h"
+#include "lib/util.h"
+#include "lib/exceptions.h"
+#include "job_manager_view.h"
+#include "gtk_util.h"
+
+using namespace std;
+using namespace boost;
+
+/** Must be called in the GUI thread */
+JobManagerView::JobManagerView ()
+{
+ _scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
+
+ _store = Gtk::TreeStore::create (_columns);
+ _view.set_model (_store);
+ _view.append_column ("Name", _columns.name);
+
+ Gtk::CellRendererProgress* r = Gtk::manage (new Gtk::CellRendererProgress ());
+ int const n = _view.append_column ("Progress", *r);
+ Gtk::TreeViewColumn* c = _view.get_column (n - 1);
+ c->add_attribute (r->property_value(), _columns.progress);
+ c->add_attribute (r->property_pulse(), _columns.progress_unknown);
+ c->add_attribute (r->property_text(), _columns.text);
+
+ _scroller.add (_view);
+ _scroller.set_size_request (-1, 150);
+
+ update ();
+}
+
+/** Update the view by examining the state of each jobs.
+ * Must be called in the GUI thread.
+ */
+void
+JobManagerView::update ()
+{
+ list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
+
+ for (list<shared_ptr<Job> >::iterator i = jobs.begin(); i != jobs.end(); ++i) {
+ Gtk::ListStore::iterator j = _store->children().begin();
+ while (j != _store->children().end()) {
+ Gtk::TreeRow r = *j;
+ shared_ptr<Job> job = r[_columns.job];
+ if (job == *i) {
+ break;
+ }
+ ++j;
+ }
+
+ Gtk::TreeRow r;
+ if (j == _store->children().end ()) {
+ j = _store->append ();
+ r = *j;
+ r[_columns.name] = (*i)->name ();
+ r[_columns.job] = *i;
+ r[_columns.progress_unknown] = -1;
+ r[_columns.informed_of_finish] = false;
+ } else {
+ r = *j;
+ }
+
+ bool inform_of_finish = false;
+ string const st = (*i)->status ();
+
+ if (!(*i)->finished ()) {
+ float const p = (*i)->overall_progress ();
+ if (p >= 0) {
+ r[_columns.text] = st;
+ r[_columns.progress] = p * 100;
+ } else {
+ r[_columns.progress_unknown] = r[_columns.progress_unknown] + 1;
+ }
+ }
+
+ /* Hack to work around our lack of cross-thread
+ signalling; we tell the job to emit_finished()
+ from here (the GUI thread).
+ */
+
+ if ((*i)->finished_ok ()) {
+ bool i = r[_columns.informed_of_finish];
+ if (!i) {
+ r[_columns.progress_unknown] = -1;
+ r[_columns.progress] = 100;
+ r[_columns.text] = st;
+ inform_of_finish = true;
+ }
+ } else if ((*i)->finished_in_error ()) {
+ bool i = r[_columns.informed_of_finish];
+ if (!i) {
+ r[_columns.progress_unknown] = -1;
+ r[_columns.progress] = 100;
+ r[_columns.text] = st;
+ inform_of_finish = true;
+ }
+ }
+
+ if (inform_of_finish) {
+ try {
+ (*i)->emit_finished ();
+ } catch (OpenFileError& e) {
+ stringstream s;
+ s << "Error: " << e.what();
+ error_dialog (s.str ());
+ }
+ r[_columns.informed_of_finish] = true;
+ }
+ }
+}
diff --git a/src/gtk/job_manager_view.h b/src/gtk/job_manager_view.h
new file mode 100644
index 000000000..c88a1ce9a
--- /dev/null
+++ b/src/gtk/job_manager_view.h
@@ -0,0 +1,83 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/job_manager_view.h
+ * @brief Class generating a GTK widget to show the progress of jobs.
+ */
+
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include <gtkmm.h>
+
+class Job;
+
+/** @class JobManagerView
+ * @brief Class generating a GTK widget to show the progress of jobs.
+ */
+class JobManagerView
+{
+public:
+ JobManagerView ();
+
+ /** @return Our main widget, which contains everything else */
+ Gtk::Widget& widget () {
+ return _scroller;
+ }
+
+ void update ();
+
+private:
+ /** Scroller for all our contents */
+ Gtk::ScrolledWindow _scroller;
+ /** View for the jobs */
+ Gtk::TreeView _view;
+ /** Store for the jobs */
+ Glib::RefPtr<Gtk::TreeStore> _store;
+
+ /** The TreeModelColumnRecord for the store */
+ class StoreColumns : public Gtk::TreeModelColumnRecord
+ {
+ public:
+ StoreColumns ()
+ {
+ add (name);
+ add (job);
+ add (progress);
+ add (progress_unknown);
+ add (text);
+ add (informed_of_finish);
+ }
+
+ /** Job name */
+ Gtk::TreeModelColumn<std::string> name;
+ /** Job */
+ Gtk::TreeModelColumn<boost::shared_ptr<Job> > job;
+ /** Progress */
+ Gtk::TreeModelColumn<float> progress;
+ /** An increasing integer number if the progress is unknown */
+ Gtk::TreeModelColumn<int> progress_unknown;
+ /** Text to write into the progress bar */
+ Gtk::TreeModelColumn<std::string> text;
+ /** true if the job has been informed of its finish */
+ Gtk::TreeModelColumn<bool> informed_of_finish;
+ };
+
+ /** The columns for the store */
+ StoreColumns _columns;
+};
diff --git a/src/gtk/job_wrapper.cc b/src/gtk/job_wrapper.cc
new file mode 100644
index 000000000..be214b0ac
--- /dev/null
+++ b/src/gtk/job_wrapper.cc
@@ -0,0 +1,50 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <sstream>
+#include "lib/film.h"
+#include "lib/exceptions.h"
+#include "job_wrapper.h"
+#include "gtk_util.h"
+
+using namespace std;
+
+void
+JobWrapper::make_dcp (Film* film, bool transcode)
+{
+ if (!film) {
+ return;
+ }
+
+ try {
+ film->make_dcp (transcode);
+ } catch (BadSettingError& e) {
+ stringstream s;
+ if (e.setting() == "dcp_long_name") {
+ s << "Could not make DCP: long name is invalid (" << e.what() << ")";
+ } else {
+ s << "Bad setting for " << e.setting() << "(" << e.what() << ")";
+ }
+ error_dialog (s.str ());
+ } catch (std::exception& e) {
+ stringstream s;
+ s << "Could not make DCP: " << e.what () << ".";
+ error_dialog (s.str ());
+ }
+}
diff --git a/src/gtk/job_wrapper.h b/src/gtk/job_wrapper.h
new file mode 100644
index 000000000..b8760f6e5
--- /dev/null
+++ b/src/gtk/job_wrapper.h
@@ -0,0 +1,27 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+class Film;
+
+namespace JobWrapper
+{
+
+void make_dcp (Film *, bool);
+
+}
diff --git a/src/gtk/wscript b/src/gtk/wscript
new file mode 100644
index 000000000..c68efea90
--- /dev/null
+++ b/src/gtk/wscript
@@ -0,0 +1,22 @@
+def build(bld):
+ obj = bld(features = 'cxx cxxshlib')
+ obj.name = 'libdvdomatic-gtk'
+ obj.includes = [ '..' ]
+ obj.export_includes = ['.']
+ obj.uselib = 'GLIB GTKMM CAIROMM'
+ obj.source = """
+ alignment.cc
+ config_dialog.cc
+ dcp_range_dialog.cc
+ film_editor.cc
+ film_list.cc
+ film_player.cc
+ film_viewer.cc
+ filter_dialog.cc
+ filter_view.cc
+ gpl.cc
+ job_manager_view.cc
+ gtk_util.cc
+ job_wrapper.cc
+ """
+ obj.target = 'dvdomatic-gtk'
diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc
new file mode 100644
index 000000000..1a6104251
--- /dev/null
+++ b/src/lib/ab_transcode_job.cc
@@ -0,0 +1,69 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <stdexcept>
+#include "ab_transcode_job.h"
+#include "j2k_wav_encoder.h"
+#include "film.h"
+#include "format.h"
+#include "filter.h"
+#include "ab_transcoder.h"
+#include "film_state.h"
+#include "encoder_factory.h"
+#include "config.h"
+
+using namespace std;
+using namespace boost;
+
+/** @param s FilmState to compare (with filters and/or a non-bicubic scaler).
+ * @param o Options.
+ * @Param l A log that we can write to.
+ */
+ABTranscodeJob::ABTranscodeJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l)
+ : Job (s, o, l)
+{
+ _fs_b.reset (new FilmState (*_fs));
+ _fs_b->scaler = Config::instance()->reference_scaler ();
+ _fs_b->filters = Config::instance()->reference_filters ();
+}
+
+string
+ABTranscodeJob::name () const
+{
+ stringstream s;
+ s << "A/B transcode " << _fs->name;
+ return s.str ();
+}
+
+void
+ABTranscodeJob::run ()
+{
+ try {
+ /* _fs_b is the one with no filters */
+ ABTranscoder w (_fs_b, _fs, _opt, this, _log, encoder_factory (_fs, _opt, _log));
+ w.go ();
+ set_progress (1);
+ set_state (FINISHED_OK);
+
+ } catch (std::exception& e) {
+
+ set_state (FINISHED_ERROR);
+
+ }
+}
diff --git a/src/lib/ab_transcode_job.h b/src/lib/ab_transcode_job.h
new file mode 100644
index 000000000..478049068
--- /dev/null
+++ b/src/lib/ab_transcode_job.h
@@ -0,0 +1,47 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/ab_transcode_job.h
+ * @brief Job to run a transcoder which produces output for A/B comparison of various settings.
+ */
+
+#include <boost/shared_ptr.hpp>
+#include "job.h"
+
+/** @class ABTranscodeJob
+ * @brief Job to run a transcoder which produces output for A/B comparison of various settings.
+ *
+ * The right half of the frame will be processed using the FilmState supplied;
+ * the left half will be processed using the same state but *without* filters
+ * and with the scaler set to SWS_BICUBIC.
+ */
+class ABTranscodeJob : public Job
+{
+public:
+ ABTranscodeJob (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l);
+
+ std::string name () const;
+ void run ();
+
+private:
+ /** Copy of our FilmState with filters removed and scaler set back to bicubic;
+ * this is the `reference' (left-half-frame) state.
+ */
+ boost::shared_ptr<FilmState> _fs_b;
+};
diff --git a/src/lib/ab_transcoder.cc b/src/lib/ab_transcoder.cc
new file mode 100644
index 000000000..aabaf2d03
--- /dev/null
+++ b/src/lib/ab_transcoder.cc
@@ -0,0 +1,127 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <boost/shared_ptr.hpp>
+#include <sigc++/bind.h>
+#include "ab_transcoder.h"
+#include "film.h"
+#include "decoder.h"
+#include "encoder.h"
+#include "job.h"
+#include "film_state.h"
+#include "options.h"
+#include "image.h"
+#include "decoder_factory.h"
+
+/** @file src/ab_transcoder.cc
+ * @brief A transcoder which uses one FilmState for the left half of the screen, and a different one
+ * for the right half (to facilitate A/B comparisons of settings)
+ */
+
+using namespace std;
+using namespace boost;
+
+/** @param a FilmState to use for the left half of the screen.
+ * @param b FilmState to use for the right half of the screen.
+ * @param o Options.
+ * @param j Job that we are associated with.
+ * @param l Log.
+ * @param e Encoder to use.
+ */
+
+ABTranscoder::ABTranscoder (
+ shared_ptr<const FilmState> a, shared_ptr<const FilmState> b, shared_ptr<const Options> o, Job* j, Log* l, shared_ptr<Encoder> e)
+ : _fs_a (a)
+ , _fs_b (b)
+ , _opt (o)
+ , _job (j)
+ , _log (l)
+ , _encoder (e)
+ , _last_frame (0)
+{
+ _da = decoder_factory (_fs_a, o, j, _log);
+ _db = decoder_factory (_fs_b, o, j, _log);
+
+ _da->Video.connect (sigc::bind (sigc::mem_fun (*this, &ABTranscoder::process_video), 0));
+ _db->Video.connect (sigc::bind (sigc::mem_fun (*this, &ABTranscoder::process_video), 1));
+ _da->Audio.connect (sigc::mem_fun (*e, &Encoder::process_audio));
+}
+
+ABTranscoder::~ABTranscoder ()
+{
+
+}
+
+void
+ABTranscoder::process_video (shared_ptr<Image> yuv, int frame, int index)
+{
+ if (index == 0) {
+ /* Keep this image around until we get the other half */
+ _image = yuv;
+ } else {
+ /* Copy the right half of yuv into _image */
+ for (int i = 0; i < yuv->components(); ++i) {
+ int const line_size = yuv->line_size()[i];
+ int const half_line_size = line_size / 2;
+
+ uint8_t* p = _image->data()[i];
+ uint8_t* q = yuv->data()[i];
+
+ for (int j = 0; j < yuv->lines (i); ++j) {
+ memcpy (p + half_line_size, q + half_line_size, half_line_size);
+ p += line_size;
+ q += line_size;
+ }
+ }
+
+ /* And pass it to the encoder */
+ _encoder->process_video (_image, frame);
+ _image.reset ();
+ }
+
+ _last_frame = frame;
+}
+
+
+void
+ABTranscoder::go ()
+{
+ _encoder->process_begin ();
+ _da->process_begin ();
+ _db->process_begin ();
+
+ while (1) {
+ bool const a = _da->pass ();
+ bool const b = _db->pass ();
+
+ if (_job) {
+ _job->set_progress (float (_last_frame) / _da->decoding_frames ());
+ }
+
+ if (a && b) {
+ break;
+ }
+ }
+
+ _encoder->process_end ();
+ _da->process_end ();
+ _db->process_end ();
+}
+
diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h
new file mode 100644
index 000000000..0310bb923
--- /dev/null
+++ b/src/lib/ab_transcoder.h
@@ -0,0 +1,69 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/ab_transcoder.h
+ * @brief A transcoder which uses one FilmState for the left half of the screen, and a different one
+ * for the right half (to facilitate A/B comparisons of settings)
+ */
+
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+
+class Job;
+class Encoder;
+class Decoder;
+class FilmState;
+class Options;
+class Image;
+class Log;
+
+/** @class ABTranscoder
+ * @brief A transcoder which uses one FilmState for the left half of the screen, and a different one
+ * for the right half (to facilitate A/B comparisons of settings)
+ */
+class ABTranscoder
+{
+public:
+ ABTranscoder (
+ boost::shared_ptr<const FilmState> a,
+ boost::shared_ptr<const FilmState> b,
+ boost::shared_ptr<const Options> o,
+ Job* j,
+ Log* l,
+ boost::shared_ptr<Encoder> e
+ );
+
+ ~ABTranscoder ();
+
+ void go ();
+
+private:
+ void process_video (boost::shared_ptr<Image>, int, int);
+
+ boost::shared_ptr<const FilmState> _fs_a;
+ boost::shared_ptr<const FilmState> _fs_b;
+ boost::shared_ptr<const Options> _opt;
+ Job* _job;
+ Log* _log;
+ boost::shared_ptr<Encoder> _encoder;
+ boost::shared_ptr<Decoder> _da;
+ boost::shared_ptr<Decoder> _db;
+ int _last_frame;
+ boost::shared_ptr<Image> _image;
+};
diff --git a/src/lib/config.cc b/src/lib/config.cc
new file mode 100644
index 000000000..6d31ccd9e
--- /dev/null
+++ b/src/lib/config.cc
@@ -0,0 +1,139 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <sstream>
+#include <cstdlib>
+#include <fstream>
+#include "config.h"
+#include "server.h"
+#include "scaler.h"
+#include "screen.h"
+#include "filter.h"
+
+using namespace std;
+using namespace boost;
+
+Config* Config::_instance = 0;
+
+/** Construct default configuration */
+Config::Config ()
+ : _num_local_encoding_threads (2)
+ , _server_port (6192)
+ , _colour_lut_index (0)
+ , _j2k_bandwidth (250000000)
+ , _reference_scaler (Scaler::from_id ("bicubic"))
+ , _tms_path (".")
+{
+ ifstream f (file().c_str ());
+ string line;
+ while (getline (f, line)) {
+ if (line.empty ()) {
+ continue;
+ }
+
+ if (line[0] == '#') {
+ continue;
+ }
+
+ size_t const s = line.find (' ');
+ if (s == string::npos) {
+ continue;
+ }
+
+ string const k = line.substr (0, s);
+ string const v = line.substr (s + 1);
+
+ if (k == "num_local_encoding_threads") {
+ _num_local_encoding_threads = atoi (v.c_str ());
+ } else if (k == "server_port") {
+ _server_port = atoi (v.c_str ());
+ } else if (k == "colour_lut_index") {
+ _colour_lut_index = atoi (v.c_str ());
+ } else if (k == "j2k_bandwidth") {
+ _j2k_bandwidth = atoi (v.c_str ());
+ } else if (k == "reference_scaler") {
+ _reference_scaler = Scaler::from_id (v);
+ } else if (k == "reference_filter") {
+ _reference_filters.push_back (Filter::from_id (v));
+ } else if (k == "server") {
+ _servers.push_back (Server::create_from_metadata (v));
+ } else if (k == "screen") {
+ _screens.push_back (Screen::create_from_metadata (v));
+ } else if (k == "tms_ip") {
+ _tms_ip = v;
+ } else if (k == "tms_path") {
+ _tms_path = v;
+ } else if (k == "tms_user") {
+ _tms_user = v;
+ } else if (k == "tms_password") {
+ _tms_password = v;
+ }
+ }
+
+ Changed ();
+}
+
+/** @return Filename to write configuration to */
+string
+Config::file () const
+{
+ stringstream s;
+ s << getenv ("HOME") << "/.dvdomatic";
+ return s.str ();
+}
+
+/** @return Singleton instance */
+Config *
+Config::instance ()
+{
+ if (_instance == 0) {
+ _instance = new Config;
+ }
+
+ return _instance;
+}
+
+/** Write our configuration to disk */
+void
+Config::write () const
+{
+ ofstream f (file().c_str ());
+ f << "num_local_encoding_threads " << _num_local_encoding_threads << "\n"
+ << "server_port " << _server_port << "\n"
+ << "colour_lut_index " << _colour_lut_index << "\n"
+ << "j2k_bandwidth " << _j2k_bandwidth << "\n"
+ << "reference_scaler " << _reference_scaler->id () << "\n";
+
+ for (vector<Filter const *>::const_iterator i = _reference_filters.begin(); i != _reference_filters.end(); ++i) {
+ f << "reference_filter " << (*i)->id () << "\n";
+ }
+
+ for (vector<Server*>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) {
+ f << "server " << (*i)->as_metadata () << "\n";
+ }
+
+ for (vector<shared_ptr<Screen> >::const_iterator i = _screens.begin(); i != _screens.end(); ++i) {
+ f << "screen " << (*i)->as_metadata () << "\n";
+ }
+
+ f << "tms_ip " << _tms_ip << "\n";
+ f << "tms_path " << _tms_path << "\n";
+ f << "tms_user " << _tms_user << "\n";
+ f << "tms_password " << _tms_password << "\n";
+}
diff --git a/src/lib/config.h b/src/lib/config.h
new file mode 100644
index 000000000..62fcebbc3
--- /dev/null
+++ b/src/lib/config.h
@@ -0,0 +1,207 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/config.h
+ * @brief Class holding configuration.
+ */
+
+#ifndef DVDOMATIC_CONFIG_H
+#define DVDOMATIC_CONFIG_H
+
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include <sigc++/signal.h>
+
+class Server;
+class Screen;
+class Scaler;
+class Filter;
+
+/** @class Config
+ * @brief A singleton class holding configuration.
+ */
+class Config
+{
+public:
+
+ /** @return number of threads to use for J2K encoding on the local machine */
+ int num_local_encoding_threads () const {
+ return _num_local_encoding_threads;
+ }
+
+ /** @return port to use for J2K encoding servers */
+ int server_port () const {
+ return _server_port;
+ }
+
+ /** @return index of colour LUT to use when converting RGB to XYZ.
+ * 0: sRGB
+ * 1: Rec 709
+ * 2: DC28
+ */
+ int colour_lut_index () const {
+ return _colour_lut_index;
+ }
+
+ /** @return bandwidth for J2K files in bits per second */
+ int j2k_bandwidth () const {
+ return _j2k_bandwidth;
+ }
+
+ /** @return J2K encoding servers to use */
+ std::vector<Server*> servers () const {
+ return _servers;
+ }
+
+ std::vector<boost::shared_ptr<Screen> > screens () const {
+ return _screens;
+ }
+
+ Scaler const * reference_scaler () const {
+ return _reference_scaler;
+ }
+
+ std::vector<Filter const *> reference_filters () const {
+ return _reference_filters;
+ }
+
+ std::string tms_ip () const {
+ return _tms_ip;
+ }
+
+ std::string tms_path () const {
+ return _tms_path;
+ }
+
+ std::string tms_user () const {
+ return _tms_user;
+ }
+
+ std::string tms_password () const {
+ return _tms_password;
+ }
+
+ /** @param n New number of local encoding threads */
+ void set_num_local_encoding_threads (int n) {
+ _num_local_encoding_threads = n;
+ Changed ();
+ }
+
+ /** @param p New server port */
+ void set_sever_port (int p) {
+ _server_port = p;
+ Changed ();
+ }
+
+ /** @param i New colour LUT index */
+ void set_colour_lut_index (int i) {
+ _colour_lut_index = i;
+ Changed ();
+ }
+
+ /** @param b New J2K bandwidth */
+ void set_j2k_bandwidth (int b) {
+ _j2k_bandwidth = b;
+ Changed ();
+ }
+
+ /** @param s New list of servers */
+ void set_servers (std::vector<Server*> s) {
+ _servers = s;
+ Changed ();
+ }
+
+ void set_screens (std::vector<boost::shared_ptr<Screen> > s) {
+ _screens = s;
+ Changed ();
+ }
+
+ void set_reference_scaler (Scaler const * s) {
+ _reference_scaler = s;
+ Changed ();
+ }
+
+ void set_reference_filters (std::vector<Filter const *> const & f) {
+ _reference_filters = f;
+ Changed ();
+ }
+
+ void set_tms_ip (std::string i) {
+ _tms_ip = i;
+ Changed ();
+ }
+
+ void set_tms_path (std::string p) {
+ _tms_path = p;
+ Changed ();
+ }
+
+ void set_tms_user (std::string u) {
+ _tms_user = u;
+ Changed ();
+ }
+
+ void set_tms_password (std::string p) {
+ _tms_password = p;
+ Changed ();
+ }
+
+ void write () const;
+
+ sigc::signal0<void> Changed;
+
+ static Config* instance ();
+
+private:
+ Config ();
+ std::string file () const;
+
+ /** number of threads to use for J2K encoding on the local machine */
+ int _num_local_encoding_threads;
+ /** port to use for J2K encoding servers */
+ int _server_port;
+ /** index of colour LUT to use when converting RGB to XYZ
+ * (see colour_lut_index ())
+ */
+ int _colour_lut_index;
+ /** bandwidth for J2K files in Mb/s */
+ int _j2k_bandwidth;
+
+ /** J2K encoding servers to use */
+ std::vector<Server *> _servers;
+
+ /** Screen definitions */
+ std::vector<boost::shared_ptr<Screen> > _screens;
+
+ /** Scaler to use for the "A" part of A/B comparisons */
+ Scaler const * _reference_scaler;
+
+ /** Filters to use for the "A" part of A/B comparisons */
+ std::vector<Filter const *> _reference_filters;
+
+ std::string _tms_ip;
+ std::string _tms_path;
+ std::string _tms_user;
+ std::string _tms_password;
+
+ /** Singleton instance, or 0 */
+ static Config* _instance;
+};
+
+#endif
diff --git a/src/lib/copy_from_dvd_job.cc b/src/lib/copy_from_dvd_job.cc
new file mode 100644
index 000000000..b82169ad1
--- /dev/null
+++ b/src/lib/copy_from_dvd_job.cc
@@ -0,0 +1,103 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/copy_from_dvd_job.cc
+ * @brief A job to copy a film from a DVD.
+ */
+
+#include <stdio.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/filesystem.hpp>
+#include "copy_from_dvd_job.h"
+#include "film_state.h"
+#include "dvd.h"
+
+using namespace std;
+using namespace boost;
+
+/** @param fs FilmState for the film to write DVD data into.
+ * @param l Log that we can write to.
+ */
+CopyFromDVDJob::CopyFromDVDJob (shared_ptr<const FilmState> fs, Log* l)
+ : Job (fs, shared_ptr<Options> (), l)
+{
+
+}
+
+string
+CopyFromDVDJob::name () const
+{
+ return "Copy film from DVD";
+}
+
+void
+CopyFromDVDJob::run ()
+{
+ /* Remove any old DVD rips */
+ filesystem::remove_all (_fs->dir ("dvd"));
+
+ string const dvd = find_dvd ();
+ if (dvd.empty ()) {
+ set_error ("could not find DVD");
+ set_state (FINISHED_ERROR);
+ }
+
+ vector<uint64_t> const t = dvd_titles (dvd);
+ if (t.empty ()) {
+ set_error ("no titles found on DVD");
+ set_state (FINISHED_ERROR);
+ }
+
+ int longest_title = 0;
+ uint64_t longest_size = 0;
+ for (vector<int>::size_type i = 0; i < t.size(); ++i) {
+ if (longest_size < t[i]) {
+ longest_size = t[i];
+ longest_title = i;
+ }
+ }
+
+ stringstream c;
+ c << "vobcopy -n " << longest_title << " -l -o \"" << _fs->dir ("dvd") << "\" 2>&1";
+
+ FILE* f = popen (c.str().c_str(), "r");
+ if (f == 0) {
+ set_error ("could not run vobcopy command");
+ set_state (FINISHED_ERROR);
+ return;
+ }
+
+ while (!feof (f)) {
+ char buf[256];
+ if (fscanf (f, "%s", buf)) {
+ string s (buf);
+ if (!s.empty () && s[s.length() - 1] == '%') {
+ set_progress (atof (s.substr(0, s.length() - 1).c_str()) / 100.0);
+ }
+ }
+ }
+
+ int const r = pclose (f);
+ if (WEXITSTATUS (r) != 0) {
+ set_error ("call to vobcopy failed");
+ set_state (FINISHED_ERROR);
+ } else {
+ set_state (FINISHED_OK);
+ }
+}
diff --git a/src/lib/copy_from_dvd_job.h b/src/lib/copy_from_dvd_job.h
new file mode 100644
index 000000000..6b56f6f0a
--- /dev/null
+++ b/src/lib/copy_from_dvd_job.h
@@ -0,0 +1,36 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/copy_from_dvd_job.h
+ * @brief A job to copy a film from a DVD.
+ */
+
+#include "job.h"
+
+/** @class CopyFromDVDJob
+ * @brief A job to copy a film from a DVD using `vobcopy'.
+ */
+class CopyFromDVDJob : public Job
+{
+public:
+ CopyFromDVDJob (boost::shared_ptr<const FilmState>, Log *);
+
+ std::string name () const;
+ void run ();
+};
diff --git a/src/lib/dcp_content_type.cc b/src/lib/dcp_content_type.cc
new file mode 100644
index 000000000..72a26e407
--- /dev/null
+++ b/src/lib/dcp_content_type.cc
@@ -0,0 +1,91 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/content_type.cc
+ * @brief A description of the type of content for a DCP (e.g. feature, trailer etc.)
+ */
+
+#include <cassert>
+#include "dcp_content_type.h"
+
+using namespace std;
+
+vector<DCPContentType const *> DCPContentType::_dcp_content_types;
+
+DCPContentType::DCPContentType (string p, string o)
+ : _pretty_name (p)
+ , _opendcp_name (o)
+{
+
+}
+
+void
+DCPContentType::setup_dcp_content_types ()
+{
+ _dcp_content_types.push_back (new DCPContentType ("Feature", "feature"));
+ _dcp_content_types.push_back (new DCPContentType ("Short", "short"));
+ _dcp_content_types.push_back (new DCPContentType ("Trailer", "trailer"));
+ _dcp_content_types.push_back (new DCPContentType ("Test", "test"));
+ _dcp_content_types.push_back (new DCPContentType ("Transitional", "transitional"));
+ _dcp_content_types.push_back (new DCPContentType ("Rating", "rating"));
+ _dcp_content_types.push_back (new DCPContentType ("Teaser", "teaster"));
+ _dcp_content_types.push_back (new DCPContentType ("Policy", "policy"));
+ _dcp_content_types.push_back (new DCPContentType ("Public Service Announcement", "psa"));
+ _dcp_content_types.push_back (new DCPContentType ("Advertisement", "advertisement"));
+}
+
+DCPContentType const *
+DCPContentType::from_pretty_name (string n)
+{
+ for (vector<DCPContentType const *>::const_iterator i = _dcp_content_types.begin(); i != _dcp_content_types.end(); ++i) {
+ if ((*i)->pretty_name() == n) {
+ return *i;
+ }
+ }
+
+ return 0;
+}
+
+DCPContentType const *
+DCPContentType::from_index (int n)
+{
+ assert (n >= 0 && n < int (_dcp_content_types.size ()));
+ return _dcp_content_types[n];
+}
+
+int
+DCPContentType::as_index (DCPContentType const * c)
+{
+ vector<DCPContentType*>::size_type i = 0;
+ while (i < _dcp_content_types.size() && _dcp_content_types[i] != c) {
+ ++i;
+ }
+
+ if (i == _dcp_content_types.size ()) {
+ return -1;
+ }
+
+ return i;
+}
+
+vector<DCPContentType const *>
+DCPContentType::all ()
+{
+ return _dcp_content_types;
+}
diff --git a/src/lib/dcp_content_type.h b/src/lib/dcp_content_type.h
new file mode 100644
index 000000000..cade673bc
--- /dev/null
+++ b/src/lib/dcp_content_type.h
@@ -0,0 +1,58 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/content_type.h
+ * @brief A description of the type of content for a DCP (e.g. feature, trailer etc.)
+ */
+
+#include <string>
+#include <vector>
+
+/** @class DCPContentType
+ * @brief A description of the type of content for a DCP (e.g. feature, trailer etc.)
+ */
+class DCPContentType
+{
+public:
+ DCPContentType (std::string, std::string);
+
+ /** @return user-visible `pretty' name */
+ std::string pretty_name () const {
+ return _pretty_name;
+ }
+
+ /** @return name as understood by OpenDCP */
+ std::string opendcp_name () const {
+ return _opendcp_name;
+ }
+
+ static DCPContentType const * from_pretty_name (std::string);
+ static DCPContentType const * from_index (int);
+ static int as_index (DCPContentType const *);
+ static std::vector<DCPContentType const *> all ();
+ static void setup_dcp_content_types ();
+
+private:
+ std::string _pretty_name;
+ std::string _opendcp_name;
+
+ /** All available DCP content types */
+ static std::vector<DCPContentType const *> _dcp_content_types;
+};
+
diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc
new file mode 100644
index 000000000..cbd70898e
--- /dev/null
+++ b/src/lib/dcp_video_frame.cc
@@ -0,0 +1,433 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Taken from code Copyright (C) 2010-2011 Terrence Meiczinger
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/dcp_video_frame.cc
+ * @brief A single frame of video destined for a DCP.
+ *
+ * Given an Image and some settings, this class knows how to encode
+ * the image to J2K either on the local host or on a remote server.
+ *
+ * Objects of this class are used for the queue that we keep
+ * of images that require encoding.
+ */
+
+#include <stdint.h>
+#include <cstring>
+#include <cstdlib>
+#include <stdexcept>
+#include <cstdio>
+#include <iomanip>
+#include <sstream>
+#include <iostream>
+#include <unistd.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <boost/filesystem.hpp>
+#include "film.h"
+#include "dcp_video_frame.h"
+#include "lut.h"
+#include "config.h"
+#include "film_state.h"
+#include "options.h"
+#include "exceptions.h"
+#include "server.h"
+#include "util.h"
+#include "scaler.h"
+#include "image.h"
+#include "log.h"
+
+#ifdef DEBUG_HASH
+#include <mhash.h>
+#endif
+
+using namespace std;
+using namespace boost;
+
+/** Construct a DCP video frame.
+ * @param input Input image.
+ * @param out Required size of output, in pixels (including any padding).
+ * @param s Scaler to use.
+ * @param p Number of pixels of padding either side of the image.
+ * @param f Index of the frame within the Film.
+ * @param fps Frames per second of the Film.
+ * @param pp FFmpeg post-processing string to use.
+ * @param clut Colour look-up table to use (see Config::colour_lut_index ())
+ * @param bw J2K bandwidth to use (see Config::j2k_bandwidth ())
+ * @param l Log to write to.
+ */
+DCPVideoFrame::DCPVideoFrame (
+ shared_ptr<Image> yuv, Size out, int p, Scaler const * s, int f, float fps, string pp, int clut, int bw, Log* l)
+ : _input (yuv)
+ , _out_size (out)
+ , _padding (p)
+ , _scaler (s)
+ , _frame (f)
+ /* we round here; not sure if this is right */
+ , _frames_per_second (rint (fps))
+ , _post_process (pp)
+ , _colour_lut_index (clut)
+ , _j2k_bandwidth (bw)
+ , _log (l)
+ , _image (0)
+ , _parameters (0)
+ , _cinfo (0)
+ , _cio (0)
+{
+
+}
+
+/** Create a libopenjpeg container suitable for our output image */
+void
+DCPVideoFrame::create_openjpeg_container ()
+{
+ for (int i = 0; i < 3; ++i) {
+ _cmptparm[i].dx = 1;
+ _cmptparm[i].dy = 1;
+ _cmptparm[i].w = _out_size.width;
+ _cmptparm[i].h = _out_size.height;
+ _cmptparm[i].x0 = 0;
+ _cmptparm[i].y0 = 0;
+ _cmptparm[i].prec = 12;
+ _cmptparm[i].bpp = 12;
+ _cmptparm[i].sgnd = 0;
+ }
+
+ _image = opj_image_create (3, &_cmptparm[0], CLRSPC_SRGB);
+ if (_image == 0) {
+ throw EncodeError ("could not create libopenjpeg image");
+ }
+
+ _image->x0 = 0;
+ _image->y0 = 0;
+ _image->x1 = _out_size.width;
+ _image->y1 = _out_size.height;
+}
+
+DCPVideoFrame::~DCPVideoFrame ()
+{
+ if (_image) {
+ opj_image_destroy (_image);
+ }
+
+ if (_cio) {
+ opj_cio_close (_cio);
+ }
+
+ if (_cinfo) {
+ opj_destroy_compress (_cinfo);
+ }
+
+ if (_parameters) {
+ free (_parameters->cp_comment);
+ free (_parameters->cp_matrice);
+ }
+
+ delete _parameters;
+}
+
+/** J2K-encode this frame on the local host.
+ * @return Encoded data.
+ */
+shared_ptr<EncodedData>
+DCPVideoFrame::encode_locally ()
+{
+ shared_ptr<Image> prepared = _input;
+
+ if (!_post_process.empty ()) {
+ prepared = prepared->post_process (_post_process);
+ }
+
+ prepared = prepared->scale_and_convert_to_rgb (_out_size, _padding, _scaler);
+
+ create_openjpeg_container ();
+
+ int const size = _out_size.width * _out_size.height;
+
+ struct {
+ double r, g, b;
+ } s;
+
+ struct {
+ double x, y, z;
+ } d;
+
+ /* Copy our RGB into the openjpeg container, converting to XYZ in the process */
+
+ uint8_t* p = prepared->data()[0];
+ for (int i = 0; i < size; ++i) {
+ /* In gamma LUT (converting 8-bit input to 12-bit) */
+ s.r = lut_in[_colour_lut_index][*p++ << 4];
+ s.g = lut_in[_colour_lut_index][*p++ << 4];
+ s.b = lut_in[_colour_lut_index][*p++ << 4];
+
+ /* RGB to XYZ Matrix */
+ d.x = ((s.r * color_matrix[_colour_lut_index][0][0]) + (s.g * color_matrix[_colour_lut_index][0][1]) + (s.b * color_matrix[_colour_lut_index][0][2]));
+ d.y = ((s.r * color_matrix[_colour_lut_index][1][0]) + (s.g * color_matrix[_colour_lut_index][1][1]) + (s.b * color_matrix[_colour_lut_index][1][2]));
+ d.z = ((s.r * color_matrix[_colour_lut_index][2][0]) + (s.g * color_matrix[_colour_lut_index][2][1]) + (s.b * color_matrix[_colour_lut_index][2][2]));
+
+ /* DCI companding */
+ d.x = d.x * DCI_COEFFICENT * (DCI_LUT_SIZE - 1);
+ d.y = d.y * DCI_COEFFICENT * (DCI_LUT_SIZE - 1);
+ d.z = d.z * DCI_COEFFICENT * (DCI_LUT_SIZE - 1);
+
+ /* Out gamma LUT */
+ _image->comps[0].data[i] = lut_out[LO_DCI][(int) d.x];
+ _image->comps[1].data[i] = lut_out[LO_DCI][(int) d.y];
+ _image->comps[2].data[i] = lut_out[LO_DCI][(int) d.z];
+ }
+
+ /* Set the max image and component sizes based on frame_rate */
+ int const max_cs_len = ((float) _j2k_bandwidth) / 8 / _frames_per_second;
+ int const max_comp_size = max_cs_len / 1.25;
+
+ /* Set encoding parameters to default values */
+ _parameters = new opj_cparameters_t;
+ opj_set_default_encoder_parameters (_parameters);
+
+ /* Set default cinema parameters */
+ _parameters->tile_size_on = false;
+ _parameters->cp_tdx = 1;
+ _parameters->cp_tdy = 1;
+
+ /* Tile part */
+ _parameters->tp_flag = 'C';
+ _parameters->tp_on = 1;
+
+ /* Tile and Image shall be at (0,0) */
+ _parameters->cp_tx0 = 0;
+ _parameters->cp_ty0 = 0;
+ _parameters->image_offset_x0 = 0;
+ _parameters->image_offset_y0 = 0;
+
+ /* Codeblock size = 32x32 */
+ _parameters->cblockw_init = 32;
+ _parameters->cblockh_init = 32;
+ _parameters->csty |= 0x01;
+
+ /* The progression order shall be CPRL */
+ _parameters->prog_order = CPRL;
+
+ /* No ROI */
+ _parameters->roi_compno = -1;
+
+ _parameters->subsampling_dx = 1;
+ _parameters->subsampling_dy = 1;
+
+ /* 9-7 transform */
+ _parameters->irreversible = 1;
+
+ _parameters->tcp_rates[0] = 0;
+ _parameters->tcp_numlayers++;
+ _parameters->cp_disto_alloc = 1;
+ _parameters->cp_rsiz = CINEMA2K;
+ _parameters->cp_comment = strdup ("OpenDCP");
+ _parameters->cp_cinema = CINEMA2K_24;
+
+ /* 3 components, so use MCT */
+ _parameters->tcp_mct = 1;
+
+ /* set max image */
+ _parameters->max_comp_size = max_comp_size;
+ _parameters->tcp_rates[0] = ((float) (3 * _image->comps[0].w * _image->comps[0].h * _image->comps[0].prec)) / (max_cs_len * 8);
+
+ /* get a J2K compressor handle */
+ _cinfo = opj_create_compress (CODEC_J2K);
+
+ /* Set event manager to null (openjpeg 1.3 bug) */
+ _cinfo->event_mgr = 0;
+
+#ifdef DEBUG_HASH
+ md5_data ("J2K in X", _image->comps[0].data, size * sizeof (int));
+ md5_data ("J2K in Y", _image->comps[1].data, size * sizeof (int));
+ md5_data ("J2K in Z", _image->comps[2].data, size * sizeof (int));
+#endif
+
+ /* Setup the encoder parameters using the current image and user parameters */
+ opj_setup_encoder (_cinfo, _parameters, _image);
+
+ _cio = opj_cio_open ((opj_common_ptr) _cinfo, 0, 0);
+
+ int const r = opj_encode (_cinfo, _cio, _image, 0);
+ if (r == 0) {
+ throw EncodeError ("jpeg2000 encoding failed");
+ }
+
+#ifdef DEBUG_HASH
+ md5_data ("J2K out", _cio->buffer, cio_tell (_cio));
+#endif
+
+ {
+ stringstream s;
+ s << "Finished locally-encoded frame " << _frame << " length " << cio_tell (_cio);
+ _log->log (s.str ());
+ }
+
+ return shared_ptr<EncodedData> (new LocallyEncodedData (_cio->buffer, cio_tell (_cio)));
+}
+
+/** Send this frame to a remote server for J2K encoding, then read the result.
+ * @param serv Server to send to.
+ * @return Encoded data.
+ */
+shared_ptr<EncodedData>
+DCPVideoFrame::encode_remotely (Server const * serv)
+{
+ int const fd = socket (AF_INET, SOCK_STREAM, 0);
+ if (fd < 0) {
+ throw NetworkError ("could not create socket");
+ }
+
+ struct timeval tv;
+ tv.tv_sec = 20;
+ tv.tv_usec = 0;
+
+ if (setsockopt (fd, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof (tv)) < 0) {
+ close (fd);
+ throw NetworkError ("setsockopt failed");
+ }
+
+ if (setsockopt (fd, SOL_SOCKET, SO_SNDTIMEO, (void *) &tv, sizeof (tv)) < 0) {
+ close (fd);
+ throw NetworkError ("setsockopt failed");
+ }
+
+ struct hostent* server = gethostbyname (serv->host_name().c_str ());
+ if (server == 0) {
+ close (fd);
+ throw NetworkError ("gethostbyname failed");
+ }
+
+ struct sockaddr_in server_address;
+ memset (&server_address, 0, sizeof (server_address));
+ server_address.sin_family = AF_INET;
+ memcpy (&server_address.sin_addr.s_addr, server->h_addr, server->h_length);
+ server_address.sin_port = htons (Config::instance()->server_port ());
+ if (connect (fd, (struct sockaddr *) &server_address, sizeof (server_address)) < 0) {
+ close (fd);
+ stringstream s;
+ s << "could not connect (" << strerror (errno) << ")";
+ throw NetworkError (s.str());
+ }
+
+#ifdef DEBUG_HASH
+ _input->hash ("Input for remote encoding (before sending)");
+#endif
+
+ stringstream s;
+ s << "encode "
+ << _input->size().width << " " << _input->size().height << " "
+ << _input->pixel_format() << " "
+ << _out_size.width << " " << _out_size.height << " "
+ << _padding << " "
+ << _scaler->id () << " "
+ << _frame << " "
+ << _frames_per_second << " "
+ << (_post_process.empty() ? "none" : _post_process) << " "
+ << Config::instance()->colour_lut_index () << " "
+ << Config::instance()->j2k_bandwidth () << " ";
+
+ for (int i = 0; i < _input->components(); ++i) {
+ s << _input->line_size()[i] << " ";
+ }
+
+ socket_write (fd, (uint8_t *) s.str().c_str(), s.str().length() + 1);
+
+ for (int i = 0; i < _input->components(); ++i) {
+ socket_write (fd, _input->data()[i], _input->line_size()[i] * _input->lines(i));
+ }
+
+ SocketReader reader (fd);
+
+ char buffer[32];
+ reader.read_indefinite ((uint8_t *) buffer, sizeof (buffer));
+ reader.consume (strlen (buffer) + 1);
+ shared_ptr<EncodedData> e (new RemotelyEncodedData (atoi (buffer)));
+
+ /* now read the rest */
+ reader.read_definite_and_consume (e->data(), e->size());
+
+#ifdef DEBUG_HASH
+ e->hash ("Encoded image (after receiving)");
+#endif
+
+ {
+ stringstream s;
+ s << "Finished remotely-encoded frame " << _frame << " length " << e->size();
+ _log->log (s.str ());
+ }
+
+ close (fd);
+ return e;
+}
+
+/** Write this data to a J2K file.
+ * @param opt Options.
+ * @param frame Frame index.
+ */
+void
+EncodedData::write (shared_ptr<const Options> opt, int frame)
+{
+ string const tmp_j2k = opt->frame_out_path (frame, true);
+
+ FILE* f = fopen (tmp_j2k.c_str (), "wb");
+
+ if (!f) {
+ throw WriteFileError (tmp_j2k, errno);
+ }
+
+ fwrite (_data, 1, _size, f);
+ fclose (f);
+
+ /* Rename the file from foo.j2c.tmp to foo.j2c now that it is complete */
+ filesystem::rename (tmp_j2k, opt->frame_out_path (frame, false));
+}
+
+/** Send this data to a file descriptor.
+ * @param fd File descriptor.
+ */
+void
+EncodedData::send (int fd)
+{
+ stringstream s;
+ s << _size;
+ socket_write (fd, (uint8_t *) s.str().c_str(), s.str().length() + 1);
+ socket_write (fd, _data, _size);
+}
+
+#ifdef DEBUG_HASH
+void
+EncodedData::hash (string n) const
+{
+ md5_data (n, _data, _size);
+}
+#endif
+
+/** @param s Size of data in bytes */
+RemotelyEncodedData::RemotelyEncodedData (int s)
+ : EncodedData (new uint8_t[s], s)
+{
+
+}
+
+RemotelyEncodedData::~RemotelyEncodedData ()
+{
+ delete[] _data;
+}
diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h
new file mode 100644
index 000000000..26b44ad43
--- /dev/null
+++ b/src/lib/dcp_video_frame.h
@@ -0,0 +1,143 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Taken from code Copyright (C) 2010-2011 Terrence Meiczinger
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <openjpeg.h>
+#include "util.h"
+
+/** @file src/dcp_video_frame.h
+ * @brief A single frame of video destined for a DCP.
+ */
+
+class FilmState;
+class Options;
+class Server;
+class Scaler;
+class Image;
+class Log;
+
+/** @class EncodedData
+ * @brief Container for J2K-encoded data.
+ */
+class EncodedData
+{
+public:
+ /** @param d Data (will not be freed by this class, but may be by subclasses)
+ * @param s Size of data, in bytes.
+ */
+ EncodedData (uint8_t* d, int s)
+ : _data (d)
+ , _size (s)
+ {}
+
+ virtual ~EncodedData () {}
+
+ void send (int);
+ void write (boost::shared_ptr<const Options>, int);
+
+#ifdef DEBUG_HASH
+ void hash (std::string) const;
+#endif
+
+ /** @return data */
+ uint8_t* data () const {
+ return _data;
+ }
+
+ /** @return data size, in bytes */
+ int size () const {
+ return _size;
+ }
+
+protected:
+ uint8_t* _data; ///< data
+ int _size; ///< data size in bytes
+};
+
+/** @class LocallyEncodedData
+ * @brief EncodedData that was encoded locally; this class
+ * just keeps a pointer to the data, but does no memory
+ * management.
+ */
+class LocallyEncodedData : public EncodedData
+{
+public:
+ /** @param d Data (which will not be freed by this class)
+ * @param s Size of data, in bytes.
+ */
+ LocallyEncodedData (uint8_t* d, int s)
+ : EncodedData (d, s)
+ {}
+};
+
+/** @class RemotelyEncodedData
+ * @brief EncodedData that is being read from a remote server;
+ * this class allocates and manages memory for the data.
+ */
+class RemotelyEncodedData : public EncodedData
+{
+public:
+ RemotelyEncodedData (int s);
+ ~RemotelyEncodedData ();
+};
+
+/** @class DCPVideoFrame
+ * @brief A single frame of video destined for a DCP.
+ *
+ * Given an Image and some settings, this class knows how to encode
+ * the image to J2K either on the local host or on a remote server.
+ *
+ * Objects of this class are used for the queue that we keep
+ * of images that require encoding.
+ */
+class DCPVideoFrame
+{
+public:
+ DCPVideoFrame (boost::shared_ptr<Image>, Size, int, Scaler const *, int, float, std::string, int, int, Log *);
+ virtual ~DCPVideoFrame ();
+
+ boost::shared_ptr<EncodedData> encode_locally ();
+ boost::shared_ptr<EncodedData> encode_remotely (Server const *);
+
+ int frame () const {
+ return _frame;
+ }
+
+private:
+ void create_openjpeg_container ();
+ void write_encoded (boost::shared_ptr<const Options>, uint8_t *, int);
+
+ boost::shared_ptr<Image> _input; ///< the input image
+ Size _out_size; ///< the required size of the output, in pixels
+ int _padding;
+ Scaler const * _scaler; ///< scaler to use
+ int _frame; ///< frame index within the Film
+ int _frames_per_second; ///< Frames per second that we will use for the DCP (rounded)
+ std::string _post_process; ///< FFmpeg post-processing string to use
+ int _colour_lut_index; ///< Colour look-up table to use (see Config::colour_lut_index ())
+ int _j2k_bandwidth; ///< J2K bandwidth to use (see Config::j2k_bandwidth ())
+
+ Log* _log; ///< log
+
+ opj_image_cmptparm_t _cmptparm[3]; ///< libopenjpeg's opj_image_cmptparm_t
+ opj_image* _image; ///< libopenjpeg's image container
+ opj_cparameters_t* _parameters; ///< libopenjpeg's parameters
+ opj_cinfo_t* _cinfo; ///< libopenjpeg's opj_cinfo_t
+ opj_cio_t* _cio; ///< libopenjpeg's opj_cio_t
+};
diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc
new file mode 100644
index 000000000..a904e085b
--- /dev/null
+++ b/src/lib/decoder.cc
@@ -0,0 +1,300 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/decoder.cc
+ * @brief Parent class for decoders of content.
+ */
+
+#include <iostream>
+#include <stdint.h>
+extern "C" {
+#include <libavfilter/avfiltergraph.h>
+#include <libavfilter/buffersink.h>
+#include <libavfilter/avcodec.h>
+}
+#include "film.h"
+#include "format.h"
+#include "job.h"
+#include "film_state.h"
+#include "options.h"
+#include "exceptions.h"
+#include "image.h"
+#include "util.h"
+#include "log.h"
+#include "decoder.h"
+#include "filter.h"
+#include "delay_line.h"
+
+using namespace std;
+using namespace boost;
+
+/** @param s FilmState of the Film.
+ * @param o Options.
+ * @param j Job that we are running within, or 0
+ * @param l Log to use.
+ * @param minimal true to do the bare minimum of work; just run through the content. Useful for acquiring
+ * accurate frame counts as quickly as possible. This generates no video or audio output.
+ * @param ignore_length Ignore the content's claimed length when computing progress.
+ */
+Decoder::Decoder (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Job* j, Log* l, bool minimal, bool ignore_length)
+ : _fs (s)
+ , _opt (o)
+ , _job (j)
+ , _log (l)
+ , _minimal (minimal)
+ , _ignore_length (ignore_length)
+ , _video_frame (0)
+ , _buffer_src_context (0)
+ , _buffer_sink_context (0)
+ , _have_setup_video_filters (false)
+ , _delay_line (0)
+ , _delay_in_bytes (0)
+{
+ if (_opt->decode_video_frequency != 0 && _fs->length == 0) {
+ throw DecodeError ("cannot do a partial decode if length == 0");
+ }
+}
+
+Decoder::~Decoder ()
+{
+ delete _delay_line;
+}
+
+void
+Decoder::process_begin ()
+{
+ /* This assumes 2 bytes per sample */
+ _delay_in_bytes = _fs->audio_delay * _fs->audio_sample_rate * _fs->audio_channels * 2 / 1000;
+ delete _delay_line;
+ _delay_line = new DelayLine (_delay_in_bytes);
+}
+
+void
+Decoder::process_end ()
+{
+ if (_delay_in_bytes < 0) {
+ uint8_t remainder[-_delay_in_bytes];
+ _delay_line->get_remaining (remainder);
+ Audio (remainder, _delay_in_bytes);
+ }
+}
+
+/** Start decoding */
+void
+Decoder::go ()
+{
+ process_begin ();
+
+ if (_job && _ignore_length) {
+ _job->set_progress_unknown ();
+ }
+
+ while (pass () == false) {
+ if (_job && !_ignore_length) {
+ _job->set_progress (float (_video_frame) / decoding_frames ());
+ }
+ }
+
+ process_end ();
+}
+
+/** @return Number of frames that we will be decoding */
+int
+Decoder::decoding_frames () const
+{
+ if (_opt->num_frames > 0) {
+ return _opt->num_frames;
+ }
+
+ return _fs->length;
+}
+
+/** Run one pass. This may or may not generate any actual video / audio data;
+ * some decoders may require several passes to generate a single frame.
+ * @return true if we have finished processing all data; otherwise false.
+ */
+bool
+Decoder::pass ()
+{
+ if (!_have_setup_video_filters) {
+ setup_video_filters ();
+ _have_setup_video_filters = true;
+ }
+
+ if (_opt->num_frames != 0 && _video_frame >= _opt->num_frames) {
+ return true;
+ }
+
+ return do_pass ();
+}
+
+/** Called by subclasses to tell the world that some audio data is ready */
+void
+Decoder::process_audio (uint8_t* data, int channels, int size)
+{
+ if (_fs->audio_gain != 0) {
+ float const linear_gain = pow (10, _fs->audio_gain / 20);
+ uint8_t* p = data;
+ int const samples = size / 2;
+ switch (_fs->audio_sample_format) {
+ case AV_SAMPLE_FMT_S16:
+ for (int i = 0; i < samples; ++i) {
+ /* XXX: assumes little-endian; also we should probably be dithering here */
+ int const ou = p[0] | (p[1] << 8);
+ int const os = ou >= 0x8000 ? (- 0x10000 + ou) : ou;
+ int const gs = int (os * linear_gain);
+ int const gu = gs > 0 ? gs : (0x10000 + gs);
+ p[0] = gu & 0xff;
+ p[1] = (gu & 0xff00) >> 8;
+ p += 2;
+ }
+ break;
+ default:
+ assert (false);
+ }
+ }
+
+ int available = _delay_line->feed (data, size);
+ Audio (data, available);
+}
+
+/** Called by subclasses to tell the world that some video data is ready.
+ * We do some post-processing / filtering then emit it for listeners.
+ * @param frame to decode; caller manages memory.
+ */
+void
+Decoder::process_video (AVFrame* frame)
+{
+ if (_minimal) {
+ ++_video_frame;
+ return;
+ }
+
+ /* Use FilmState::length here as our one may be wrong */
+
+ int gap = 0;
+ if (_opt->decode_video_frequency != 0) {
+ gap = _fs->length / _opt->decode_video_frequency;
+ }
+
+ if (_opt->decode_video_frequency != 0 && gap != 0 && (_video_frame % gap) != 0) {
+ ++_video_frame;
+ return;
+ }
+
+ if (av_vsrc_buffer_add_frame (_buffer_src_context, frame, 0) < 0) {
+ throw DecodeError ("could not push buffer into filter chain.");
+ }
+
+ while (avfilter_poll_frame (_buffer_sink_context->inputs[0])) {
+ AVFilterBufferRef* filter_buffer;
+ if (av_buffersink_get_buffer_ref (_buffer_sink_context, &filter_buffer, 0) >= 0) {
+
+ /* This takes ownership of filter_buffer */
+ shared_ptr<Image> image (new FilterBufferImage ((PixelFormat) frame->format, filter_buffer));
+
+ if (_opt->black_after > 0 && _video_frame > _opt->black_after) {
+ image->make_black ();
+ }
+
+ Video (image, _video_frame);
+ ++_video_frame;
+ }
+ }
+}
+
+void
+Decoder::setup_video_filters ()
+{
+ stringstream fs;
+ Size size_after_crop;
+
+ if (_opt->apply_crop) {
+ size_after_crop = _fs->cropped_size (native_size ());
+ fs << crop_string (Position (_fs->left_crop, _fs->top_crop), size_after_crop);
+ } else {
+ size_after_crop = native_size ();
+ fs << crop_string (Position (0, 0), size_after_crop);
+ }
+
+ string filters = Filter::ffmpeg_strings (_fs->filters).first;
+ if (!filters.empty ()) {
+ filters += ",";
+ }
+
+ filters += fs.str ();
+
+ avfilter_register_all ();
+
+ AVFilterGraph* graph = avfilter_graph_alloc();
+ if (graph == 0) {
+ throw DecodeError ("Could not create filter graph.");
+ }
+
+ AVFilter* buffer_src = avfilter_get_by_name("buffer");
+ if (buffer_src == 0) {
+ throw DecodeError ("Could not create buffer src filter");
+ }
+
+ AVFilter* buffer_sink = avfilter_get_by_name("buffersink");
+ if (buffer_sink == 0) {
+ throw DecodeError ("Could not create buffer sink filter");
+ }
+
+ stringstream a;
+ a << native_size().width << ":"
+ << native_size().height << ":"
+ << pixel_format() << ":"
+ << time_base_numerator() << ":"
+ << time_base_denominator() << ":"
+ << sample_aspect_ratio_numerator() << ":"
+ << sample_aspect_ratio_denominator();
+
+ int r;
+ if ((r = avfilter_graph_create_filter (&_buffer_src_context, buffer_src, "in", a.str().c_str(), 0, graph)) < 0) {
+ throw DecodeError ("could not create buffer source");
+ }
+
+ enum PixelFormat pixel_formats[] = { pixel_format(), PIX_FMT_NONE };
+ if (avfilter_graph_create_filter (&_buffer_sink_context, buffer_sink, "out", 0, pixel_formats, graph) < 0) {
+ throw DecodeError ("could not create buffer sink.");
+ }
+
+ AVFilterInOut* outputs = avfilter_inout_alloc ();
+ outputs->name = av_strdup("in");
+ outputs->filter_ctx = _buffer_src_context;
+ outputs->pad_idx = 0;
+ outputs->next = 0;
+
+ AVFilterInOut* inputs = avfilter_inout_alloc ();
+ inputs->name = av_strdup("out");
+ inputs->filter_ctx = _buffer_sink_context;
+ inputs->pad_idx = 0;
+ inputs->next = 0;
+
+ _log->log ("Using filter chain `" + filters + "'");
+ if (avfilter_graph_parse (graph, filters.c_str(), &inputs, &outputs, 0) < 0) {
+ throw DecodeError ("could not set up filter graph.");
+ }
+
+ if (avfilter_graph_config (graph, 0) < 0) {
+ throw DecodeError ("could not configure filter graph.");
+ }
+}
+
diff --git a/src/lib/decoder.h b/src/lib/decoder.h
new file mode 100644
index 000000000..db51879a1
--- /dev/null
+++ b/src/lib/decoder.h
@@ -0,0 +1,136 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/decoder.h
+ * @brief Parent class for decoders of content.
+ */
+
+#ifndef DVDOMATIC_DECODER_H
+#define DVDOMATIC_DECODER_H
+
+#include <vector>
+#include <string>
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+#include <sigc++/sigc++.h>
+#include "util.h"
+
+class Job;
+class FilmState;
+class Options;
+class Image;
+class Log;
+class DelayLine;
+
+/** @class Decoder.
+ * @brief Parent class for decoders of content.
+ *
+ * These classes can be instructed run through their content
+ * (by calling ::go), and they emit signals when video or audio data is ready for something else
+ * to process.
+ */
+class Decoder
+{
+public:
+ Decoder (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Options>, Job *, Log *, bool, bool);
+ virtual ~Decoder ();
+
+ /* Methods to query our input video */
+
+ /** @return length in video frames */
+ virtual int length_in_frames () const = 0;
+ /** @return video frames per second, or 0 if unknown */
+ virtual float frames_per_second () const = 0;
+ /** @return native size in pixels */
+ virtual Size native_size () const = 0;
+ /** @return number of audio channels */
+ virtual int audio_channels () const = 0;
+ /** @return audio sampling rate in Hz */
+ virtual int audio_sample_rate () const = 0;
+ /** @return format of audio samples */
+ virtual AVSampleFormat audio_sample_format () const = 0;
+
+ void process_begin ();
+ bool pass ();
+ void process_end ();
+ void go ();
+
+ /** @return the index of the last video frame to be processed */
+ int last_video_frame () const {
+ return _video_frame;
+ }
+
+ int decoding_frames () const;
+
+ /** Emitted when a video frame is ready.
+ * First parameter is the frame.
+ * Second parameter is its index within the content.
+ */
+ sigc::signal<void, boost::shared_ptr<Image>, int> Video;
+
+ /** Emitted when some audio data is ready.
+ * First parameter is the interleaved sample data, format is given in the FilmState.
+ * Second parameter is the size of the data.
+ */
+ sigc::signal<void, uint8_t *, int> Audio;
+
+protected:
+ /** perform a single pass at our content */
+ virtual bool do_pass () = 0;
+ virtual PixelFormat pixel_format () const = 0;
+ virtual int time_base_numerator () const = 0;
+ virtual int time_base_denominator () const = 0;
+ virtual int sample_aspect_ratio_numerator () const = 0;
+ virtual int sample_aspect_ratio_denominator () const = 0;
+
+ void process_video (AVFrame *);
+ void process_audio (uint8_t *, int, int);
+
+ /** our FilmState */
+ boost::shared_ptr<const FilmState> _fs;
+ /** our options */
+ boost::shared_ptr<const Options> _opt;
+ /** associated Job, or 0 */
+ Job* _job;
+ /** log that we can write to */
+ Log* _log;
+
+ /** true to do the bare minimum of work; just run through the content. Useful for acquiring
+ * accurate frame counts as quickly as possible. This generates no video or audio output.
+ */
+ bool _minimal;
+
+ /** ignore_length Ignore the content's claimed length when computing progress */
+ bool _ignore_length;
+
+private:
+ void setup_video_filters ();
+
+ /** last video frame to be processed */
+ int _video_frame;
+
+ AVFilterContext* _buffer_src_context;
+ AVFilterContext* _buffer_sink_context;
+
+ bool _have_setup_video_filters;
+ DelayLine* _delay_line;
+ int _delay_in_bytes;
+};
+
+#endif
diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc
new file mode 100644
index 000000000..5f8fc55b3
--- /dev/null
+++ b/src/lib/decoder_factory.cc
@@ -0,0 +1,48 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/decoder_factory.cc
+ * @brief A method to create an appropriate decoder for some content.
+ */
+
+#include <boost/filesystem.hpp>
+#include "ffmpeg_decoder.h"
+#include "tiff_decoder.h"
+#include "imagemagick_decoder.h"
+#include "film_state.h"
+
+using namespace std;
+using namespace boost;
+
+shared_ptr<Decoder>
+decoder_factory (
+ shared_ptr<const FilmState> fs, shared_ptr<const Options> o, Job* j, Log* l, bool minimal = false, bool ignore_length = false
+ )
+{
+ if (filesystem::is_directory (fs->content_path ())) {
+ /* Assume a directory contains TIFFs */
+ return shared_ptr<Decoder> (new TIFFDecoder (fs, o, j, l, minimal, ignore_length));
+ }
+
+ if (fs->content_type() == STILL) {
+ return shared_ptr<Decoder> (new ImageMagickDecoder (fs, o, j, l, minimal, ignore_length));
+ }
+
+ return shared_ptr<Decoder> (new FFmpegDecoder (fs, o, j, l, minimal, ignore_length));
+}
diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h
new file mode 100644
index 000000000..36c14951f
--- /dev/null
+++ b/src/lib/decoder_factory.h
@@ -0,0 +1,32 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/decoder_factory.h
+ * @brief A method to create an appropriate decoder for some content.
+ */
+
+class Decoder;
+class FilmState;
+class Options;
+class Job;
+class Log;
+
+extern boost::shared_ptr<Decoder> decoder_factory (
+ boost::shared_ptr<const FilmState>, boost::shared_ptr<const Options>, Job *, Log *, bool minimal = false, bool ignore_length = false
+ );
diff --git a/src/lib/delay_line.cc b/src/lib/delay_line.cc
new file mode 100644
index 000000000..c510fb4e3
--- /dev/null
+++ b/src/lib/delay_line.cc
@@ -0,0 +1,110 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <stdint.h>
+#include <cstring>
+#include <algorithm>
+#include <iostream>
+#include "delay_line.h"
+
+using namespace std;
+
+/** Construct a DelayLine delaying by some number of bytes.
+ * @param d Number of bytes to delay by; +ve moves data later.
+ */
+DelayLine::DelayLine (int d)
+ : _delay (d)
+ , _buffer (0)
+ , _negative_delay_remaining (0)
+{
+ if (d > 0) {
+ /* We need a buffer to keep some data in */
+ _buffer = new uint8_t[d];
+ memset (_buffer, 0, d);
+ } else if (d < 0) {
+ /* We can do -ve delays just by chopping off
+ the start, so no buffer needed.
+ */
+ _negative_delay_remaining = -d;
+ }
+}
+
+DelayLine::~DelayLine ()
+{
+ delete[] _buffer;
+}
+
+int
+DelayLine::feed (uint8_t* data, int size)
+{
+ int available = size;
+
+ if (_delay > 0) {
+
+ /* Copy the input data */
+ uint8_t input[size];
+ memcpy (input, data, size);
+
+ int to_do = size;
+
+ /* Write some of our buffer to the output */
+ int const from_buffer = min (to_do, _delay);
+ memcpy (data, _buffer, from_buffer);
+ to_do -= from_buffer;
+
+ /* Write some of the input to the output */
+ int const from_input = min (to_do, size);
+ memcpy (data + from_buffer, input, from_input);
+
+ int const left_in_buffer = _delay - from_buffer;
+
+ /* Shuffle our buffer down */
+ memmove (_buffer, _buffer + from_buffer, left_in_buffer);
+
+ /* Copy remaining input data to our buffer */
+ memcpy (_buffer + left_in_buffer, input + from_input, size - from_input);
+
+ } else if (_delay < 0) {
+
+ /* Chop the initial data off until _negative_delay_remaining
+ is zero, then just pass data.
+ */
+
+ int const to_do = min (size, _negative_delay_remaining);
+ available = size - to_do;
+ memmove (data, data + to_do, available);
+ _negative_delay_remaining -= to_do;
+
+ }
+
+ return available;
+}
+
+/** With -ve delays, the DelayLine will have data to give after
+ * all input data has been passed to ::feed().
+ * Call this method after passing all input data.
+ *
+ * @param buffer Pointer to buffer of _delay bytes in length,
+ * which will be filled with remaining data.
+ */
+void
+DelayLine::get_remaining (uint8_t* buffer)
+{
+ memset (buffer, 0, -_delay);
+}
diff --git a/src/lib/delay_line.h b/src/lib/delay_line.h
new file mode 100644
index 000000000..377553de4
--- /dev/null
+++ b/src/lib/delay_line.h
@@ -0,0 +1,36 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** A class which can be fed a stream of bytes and which can
+ * delay them by a positive or negative amount.
+ */
+class DelayLine
+{
+public:
+ DelayLine (int);
+ ~DelayLine ();
+
+ int feed (uint8_t *, int);
+ void get_remaining (uint8_t *);
+
+private:
+ int _delay; ///< delay in bytes, +ve to move data later
+ uint8_t* _buffer; ///< buffer for +ve delays, or 0
+ int _negative_delay_remaining; ///< number of bytes of negative delay that remain to emit
+};
diff --git a/src/lib/dvd.cc b/src/lib/dvd.cc
new file mode 100644
index 000000000..629ba1ac8
--- /dev/null
+++ b/src/lib/dvd.cc
@@ -0,0 +1,78 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <fstream>
+#include <boost/filesystem.hpp>
+#include <boost/algorithm/string.hpp>
+
+using namespace std;
+using namespace boost;
+
+string
+find_dvd ()
+{
+ ifstream f ("/etc/mtab");
+ while (f.good ()) {
+ string s;
+ getline (f, s);
+ vector<string> b;
+ split (b, s, is_any_of (" "));
+ if (b.size() >= 3 && b[2] == "udf") {
+ replace_all (b[1], "\\040", " ");
+ return b[1];
+ }
+ }
+
+ return "";
+}
+
+vector<uint64_t>
+dvd_titles (string dvd)
+{
+ filesystem::path video (dvd);
+ video /= "VIDEO_TS";
+
+ vector<uint64_t> sizes;
+
+ for (filesystem::directory_iterator i = filesystem::directory_iterator (video); i != filesystem::directory_iterator(); ++i) {
+#if BOOST_FILESYSTEM_VERSION == 3
+ string const n = filesystem::path(*i).filename().generic_string();
+#else
+ string const n = filesystem::path(*i).filename();
+#endif
+ if (starts_with (n, "VTS_") && ends_with (n, ".VOB")) {
+ uint64_t const size = filesystem::file_size (filesystem::path (*i));
+ vector<string> p;
+ split (p, n, is_any_of ("_."));
+ if (p.size() == 4) {
+ int const a = atoi (p[1].c_str ());
+ int const b = atoi (p[2].c_str ());
+ while (a >= int (sizes.size())) {
+ sizes.push_back (0);
+ }
+
+ if (b > 0) {
+ sizes[a] += size;
+ }
+ }
+ }
+ }
+
+ return sizes;
+}
diff --git a/src/lib/dvd.h b/src/lib/dvd.h
new file mode 100644
index 000000000..170472121
--- /dev/null
+++ b/src/lib/dvd.h
@@ -0,0 +1,21 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+extern std::vector<uint64_t> dvd_titles (std::string);
+extern std::string find_dvd ();
diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc
new file mode 100644
index 000000000..c8eb24c80
--- /dev/null
+++ b/src/lib/encoder.cc
@@ -0,0 +1,71 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/encoder.h
+ * @brief Parent class for classes which can encode video and audio frames.
+ */
+
+#include "encoder.h"
+#include "util.h"
+
+using namespace boost;
+
+int const Encoder::_history_size = 25;
+
+/** @param s FilmState of the film that we are encoding.
+ * @param o Options.
+ * @param l Log.
+ */
+Encoder::Encoder (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l)
+ : _fs (s)
+ , _opt (o)
+ , _log (l)
+{
+
+}
+
+
+/** @return an estimate of the current number of frames we are encoding per second,
+ * or 0 if not known.
+ */
+float
+Encoder::current_frames_per_second () const
+{
+ boost::mutex::scoped_lock lock (_history_mutex);
+ if (int (_time_history.size()) < _history_size) {
+ return 0;
+ }
+
+ struct timeval now;
+ gettimeofday (&now, 0);
+
+ return _history_size / (seconds (now) - seconds (_time_history.back ()));
+}
+
+void
+Encoder::frame_done ()
+{
+ boost::mutex::scoped_lock lock (_history_mutex);
+ struct timeval tv;
+ gettimeofday (&tv, 0);
+ _time_history.push_front (tv);
+ if (int (_time_history.size()) > _history_size) {
+ _time_history.pop_back ();
+ }
+}
diff --git a/src/lib/encoder.h b/src/lib/encoder.h
new file mode 100644
index 000000000..bed2c0988
--- /dev/null
+++ b/src/lib/encoder.h
@@ -0,0 +1,87 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_ENCODER_H
+#define DVDOMATIC_ENCODER_H
+
+/** @file src/encoder.h
+ * @brief Parent class for classes which can encode video and audio frames.
+ */
+
+#include <boost/shared_ptr.hpp>
+#include <boost/thread/mutex.hpp>
+#include <list>
+#include <stdint.h>
+
+class FilmState;
+class Options;
+class Image;
+class Log;
+
+/** @class Encoder
+ * @brief Parent class for classes which can encode video and audio frames.
+ *
+ * Video is supplied to process_video as YUV frames, and audio
+ * is supplied as uncompressed PCM in blocks of various sizes.
+ *
+ * The subclass is expected to encode the video and/or audio in
+ * some way and write it to disk.
+ */
+
+class Encoder
+{
+public:
+ Encoder (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l);
+
+ /** Called to indicate that a processing run is about to begin */
+ virtual void process_begin () = 0;
+
+ /** Called with a frame of video.
+ * @param i Video frame image.
+ * @param f Frame number within the film.
+ */
+ virtual void process_video (boost::shared_ptr<Image> i, int f) = 0;
+
+ /** Called with some audio data.
+ * @param d Data.
+ * @param s Size of data (in bytes)
+ */
+ virtual void process_audio (uint8_t* d, int s) = 0;
+
+ /** Called when a processing run has finished */
+ virtual void process_end () = 0;
+
+ float current_frames_per_second () const;
+
+protected:
+ void frame_done ();
+
+ /** FilmState of the film that we are encoding */
+ boost::shared_ptr<const FilmState> _fs;
+ /** Options */
+ boost::shared_ptr<const Options> _opt;
+ /** Log */
+ Log* _log;
+
+ mutable boost::mutex _history_mutex;
+ std::list<struct timeval> _time_history;
+ static int const _history_size;
+};
+
+#endif
diff --git a/src/lib/encoder_factory.cc b/src/lib/encoder_factory.cc
new file mode 100644
index 000000000..d16150fa6
--- /dev/null
+++ b/src/lib/encoder_factory.cc
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/encoder_factory.cc
+ * @brief A method to create an appropriate encoder for some content.
+ */
+
+#include <boost/filesystem.hpp>
+#include "j2k_wav_encoder.h"
+#include "j2k_still_encoder.h"
+#include "film_state.h"
+
+using namespace std;
+using namespace boost;
+
+shared_ptr<Encoder>
+encoder_factory (shared_ptr<const FilmState> fs, shared_ptr<const Options> o, Log* l)
+{
+ if (!filesystem::is_directory (fs->content_path()) && fs->content_type() == STILL) {
+ return shared_ptr<Encoder> (new J2KStillEncoder (fs, o, l));
+ }
+
+ return shared_ptr<Encoder> (new J2KWAVEncoder (fs, o, l));
+}
diff --git a/src/lib/encoder_factory.h b/src/lib/encoder_factory.h
new file mode 100644
index 000000000..2803de6f0
--- /dev/null
+++ b/src/lib/encoder_factory.h
@@ -0,0 +1,30 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/encoder_factory.h
+ * @brief A method to create an appropriate encoder for some content.
+ */
+
+class Encoder;
+class FilmState;
+class Options;
+class Job;
+class Log;
+
+extern boost::shared_ptr<Encoder> encoder_factory (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Options>, Log *);
diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc
new file mode 100644
index 000000000..6927715bd
--- /dev/null
+++ b/src/lib/examine_content_job.cc
@@ -0,0 +1,69 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/examine_content_job.cc
+ * @brief A class to run through content at high speed to find its length.
+ */
+
+#include "examine_content_job.h"
+#include "options.h"
+#include "film_state.h"
+#include "decoder_factory.h"
+#include "decoder.h"
+
+using namespace std;
+using namespace boost;
+
+ExamineContentJob::ExamineContentJob (shared_ptr<const FilmState> fs, Log* l)
+ : Job (fs, shared_ptr<Options> (), l)
+{
+
+}
+
+ExamineContentJob::~ExamineContentJob ()
+{
+}
+
+string
+ExamineContentJob::name () const
+{
+ stringstream s;
+ s << "Examine content of " << _fs->name;
+ return s.str ();
+}
+
+void
+ExamineContentJob::run ()
+{
+ shared_ptr<Options> o (new Options ("", "", ""));
+ o->out_size = Size (512, 512);
+ o->apply_crop = false;
+
+ _decoder = decoder_factory (_fs, o, this, _log, true, true);
+ _decoder->go ();
+
+ set_progress (1);
+ set_state (FINISHED_OK);
+}
+
+int
+ExamineContentJob::last_video_frame () const
+{
+ return _decoder->last_video_frame ();
+}
diff --git a/src/lib/examine_content_job.h b/src/lib/examine_content_job.h
new file mode 100644
index 000000000..d149341b4
--- /dev/null
+++ b/src/lib/examine_content_job.h
@@ -0,0 +1,45 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/examine_content_job.h
+ * @brief A class to run through content at high speed to find its length.
+ */
+
+#include "job.h"
+
+class Decoder;
+
+/** @class ExamineContentJob
+ * @brief A class to run through content at high speed to find its length.
+ */
+class ExamineContentJob : public Job
+{
+public:
+ ExamineContentJob (boost::shared_ptr<const FilmState>, Log *);
+ ~ExamineContentJob ();
+
+ std::string name () const;
+ void run ();
+
+ int last_video_frame () const;
+
+private:
+ boost::shared_ptr<Decoder> _decoder;
+};
+
diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h
new file mode 100644
index 000000000..b16275c20
--- /dev/null
+++ b/src/lib/exceptions.h
@@ -0,0 +1,215 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/exceptions.h
+ * @brief Our exceptions.
+ */
+
+#include <stdexcept>
+#include <sstream>
+#include <cstring>
+
+/** @class StringError
+ * @brief A parent class for exceptions using messages held in a std::string
+ */
+class StringError : public std::exception
+{
+public:
+ /** @param w Error message */
+ StringError (std::string w) {
+ _what = w;
+ }
+
+ virtual ~StringError () throw () {}
+
+ /** @return error message */
+ char const * what () const throw () {
+ return _what.c_str ();
+ }
+
+protected:
+ /** error message */
+ std::string _what;
+};
+
+/** @class DecodeError
+ * @brief A low-level problem with the decoder (possibly due to the nature
+ * of a source file).
+ */
+class DecodeError : public StringError
+{
+public:
+ DecodeError (std::string s)
+ : StringError (s)
+ {}
+};
+
+/** @class EncodeError
+ * @brief A low-level problem with an encoder.
+ */
+class EncodeError : public StringError
+{
+public:
+ EncodeError (std::string s)
+ : StringError (s)
+ {}
+};
+
+/** @class FileError.
+ * @brief Parent class for file-related errors.
+ */
+class FileError : public StringError
+{
+public:
+ FileError (std::string m, std::string f)
+ : StringError (m)
+ , _file (f)
+ {}
+
+ virtual ~FileError () throw () {}
+
+ std::string file () const {
+ return _file;
+ }
+
+private:
+ std::string _file;
+};
+
+
+/** @class OpenFileError.
+ * @brief Indicates that some error occurred when trying to open a file.
+ */
+class OpenFileError : public FileError
+{
+public:
+ /** @param f File that we were trying to open */
+ OpenFileError (std::string f)
+ : FileError ("could not open file " + f, f)
+ {}
+};
+
+/** @class CreateFileError.
+ * @brief Indicates that some error occurred when trying to create a file.
+ */
+class CreateFileError : public FileError
+{
+public:
+ /** @param f File that we were trying to create */
+ CreateFileError (std::string f)
+ : FileError ("could not create file " + f, f)
+ {}
+};
+
+/** @class WriteFileError.
+ * @brief Indicates that some error occurred when trying to write to a file
+ */
+class WriteFileError : public FileError
+{
+public:
+ /** @param f File that we were trying to write to.
+ * @param e errno value, or 0.
+ */
+ WriteFileError (std::string f, int e)
+ : FileError ("", f)
+ {
+ std::stringstream s;
+ s << "could not write to file " << f;
+ if (e) {
+ s << " (" << strerror (e) << ")";
+ }
+ _what = s.str ();
+ }
+};
+
+/** @class SettingError.
+ * @brief Indicates that something is wrong with a setting.
+ */
+class SettingError : public StringError
+{
+public:
+ /** @param s Name of setting that was required.
+ * @param m Message.
+ */
+ SettingError (std::string s, std::string m)
+ : StringError (m)
+ , _setting (s)
+ {}
+
+ virtual ~SettingError () throw () {}
+
+ /** @return name of setting in question */
+ std::string setting () const {
+ return _setting;
+ }
+
+private:
+ std::string _setting;
+};
+
+/** @class MissingSettingError.
+ * @brief Indicates that a Film is missing a setting that is required for some operation.
+ */
+class MissingSettingError : public SettingError
+{
+public:
+ /** @param s Name of setting that was required */
+ MissingSettingError (std::string s)
+ : SettingError (s, "missing required setting " + s)
+ {}
+};
+
+/** @class BadSettingError
+ * @brief Indicates that a setting is bad in some way.
+ */
+class BadSettingError : public SettingError
+{
+public:
+ /** @param s Name of setting that is bad */
+ BadSettingError (std::string s, std::string m)
+ : SettingError (s, m)
+ {}
+};
+
+/** @class NetworkError.
+ * @brief Indicates some problem with communication on the network.
+ */
+class NetworkError : public StringError
+{
+public:
+ NetworkError (std::string s)
+ : StringError (s)
+ {}
+};
+
+class PlayError : public StringError
+{
+public:
+ PlayError (std::string s)
+ : StringError (s)
+ {}
+};
+
+class DVDError : public StringError
+{
+public:
+ DVDError (std::string s)
+ : StringError (s)
+ {}
+};
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
new file mode 100644
index 000000000..af258f381
--- /dev/null
+++ b/src/lib/ffmpeg_decoder.cc
@@ -0,0 +1,256 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/ffmpeg_decoder.cc
+ * @brief A decoder using FFmpeg to decode content.
+ */
+
+#include <stdexcept>
+#include <vector>
+#include <sstream>
+#include <iomanip>
+#include <iostream>
+#include <stdint.h>
+extern "C" {
+#include <tiffio.h>
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+#include <libpostproc/postprocess.h>
+}
+#include <sndfile.h>
+#include "film.h"
+#include "format.h"
+#include "transcoder.h"
+#include "job.h"
+#include "filter.h"
+#include "film_state.h"
+#include "options.h"
+#include "exceptions.h"
+#include "image.h"
+#include "util.h"
+#include "log.h"
+#include "ffmpeg_decoder.h"
+
+using namespace std;
+using namespace boost;
+
+FFmpegDecoder::FFmpegDecoder (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Job* j, Log* l, bool minimal, bool ignore_length)
+ : Decoder (s, o, j, l, minimal, ignore_length)
+ , _format_context (0)
+ , _video_stream (-1)
+ , _audio_stream (-1)
+ , _frame (0)
+ , _video_codec_context (0)
+ , _video_codec (0)
+ , _audio_codec_context (0)
+ , _audio_codec (0)
+{
+ setup_general ();
+ setup_video ();
+ setup_audio ();
+}
+
+FFmpegDecoder::~FFmpegDecoder ()
+{
+ if (_audio_codec_context) {
+ avcodec_close (_audio_codec_context);
+ }
+
+ if (_video_codec_context) {
+ avcodec_close (_video_codec_context);
+ }
+
+ av_free (_frame);
+ avformat_close_input (&_format_context);
+}
+
+void
+FFmpegDecoder::setup_general ()
+{
+ int r;
+
+ av_register_all ();
+
+ if ((r = avformat_open_input (&_format_context, _fs->content_path().c_str(), 0, 0)) != 0) {
+ throw OpenFileError (_fs->content_path ());
+ }
+
+ if (avformat_find_stream_info (_format_context, 0) < 0) {
+ throw DecodeError ("could not find stream information");
+ }
+
+ for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
+ if (_format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
+ _video_stream = i;
+ } else if (_format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
+ _audio_stream = i;
+ }
+ }
+
+ if (_video_stream < 0) {
+ throw DecodeError ("could not find video stream");
+ }
+ if (_audio_stream < 0) {
+ throw DecodeError ("could not find audio stream");
+ }
+
+ _frame = avcodec_alloc_frame ();
+ if (_frame == 0) {
+ throw DecodeError ("could not allocate frame");
+ }
+}
+
+void
+FFmpegDecoder::setup_video ()
+{
+ _video_codec_context = _format_context->streams[_video_stream]->codec;
+ _video_codec = avcodec_find_decoder (_video_codec_context->codec_id);
+
+ if (_video_codec == 0) {
+ throw DecodeError ("could not find video decoder");
+ }
+
+ if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) {
+ throw DecodeError ("could not open video decoder");
+ }
+}
+
+void
+FFmpegDecoder::setup_audio ()
+{
+ _audio_codec_context = _format_context->streams[_audio_stream]->codec;
+ _audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id);
+
+ if (_audio_codec == 0) {
+ throw DecodeError ("could not find audio decoder");
+ }
+
+ if (avcodec_open2 (_audio_codec_context, _audio_codec, 0) < 0) {
+ throw DecodeError ("could not open audio decoder");
+ }
+}
+
+bool
+FFmpegDecoder::do_pass ()
+{
+ int r = av_read_frame (_format_context, &_packet);
+ if (r < 0) {
+ return true;
+ }
+
+ if (_packet.stream_index == _video_stream && _opt->decode_video) {
+
+ int frame_finished;
+ if (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+ process_video (_frame);
+ }
+
+ } else if (_packet.stream_index == _audio_stream && _opt->decode_audio) {
+
+ avcodec_get_frame_defaults (_frame);
+
+ int frame_finished;
+ if (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+ int const data_size = av_samples_get_buffer_size (
+ 0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
+ );
+
+ process_audio (_frame->data[0], _audio_codec_context->channels, data_size);
+ }
+ }
+
+ av_free_packet (&_packet);
+ return false;
+}
+
+int
+FFmpegDecoder::length_in_frames () const
+{
+ return (_format_context->duration / AV_TIME_BASE) * frames_per_second ();
+}
+
+float
+FFmpegDecoder::frames_per_second () const
+{
+ return av_q2d (_format_context->streams[_video_stream]->avg_frame_rate);
+}
+
+int
+FFmpegDecoder::audio_channels () const
+{
+ if (_audio_codec_context == 0) {
+ return 0;
+ }
+
+ return _audio_codec_context->channels;
+}
+
+int
+FFmpegDecoder::audio_sample_rate () const
+{
+ if (_audio_codec_context == 0) {
+ return 0;
+ }
+
+ return _audio_codec_context->sample_rate;
+}
+
+AVSampleFormat
+FFmpegDecoder::audio_sample_format () const
+{
+ return _audio_codec_context->sample_fmt;
+}
+
+Size
+FFmpegDecoder::native_size () const
+{
+ return Size (_video_codec_context->width, _video_codec_context->height);
+}
+
+PixelFormat
+FFmpegDecoder::pixel_format () const
+{
+ return _video_codec_context->pix_fmt;
+}
+
+int
+FFmpegDecoder::time_base_numerator () const
+{
+ return _video_codec_context->time_base.num;
+}
+
+int
+FFmpegDecoder::time_base_denominator () const
+{
+ return _video_codec_context->time_base.den;
+}
+
+int
+FFmpegDecoder::sample_aspect_ratio_numerator () const
+{
+ return _video_codec_context->sample_aspect_ratio.num;
+}
+
+int
+FFmpegDecoder::sample_aspect_ratio_denominator () const
+{
+ return _video_codec_context->sample_aspect_ratio.den;
+}
+
diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h
new file mode 100644
index 000000000..d66acad48
--- /dev/null
+++ b/src/lib/ffmpeg_decoder.h
@@ -0,0 +1,90 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/ffmpeg_decoder.h
+ * @brief A decoder using FFmpeg to decode content.
+ */
+
+#include <vector>
+#include <string>
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libpostproc/postprocess.h>
+}
+#include "util.h"
+#include "decoder.h"
+
+struct AVFilterGraph;
+struct AVCodecContext;
+struct AVFilterContext;
+struct AVFormatContext;
+struct AVFrame;
+struct AVBufferContext;
+struct AVCodec;
+class Job;
+class FilmState;
+class Options;
+class Image;
+class Log;
+
+/** @class FFmpegDecoder
+ * @brief A decoder using FFmpeg to decode content.
+ */
+class FFmpegDecoder : public Decoder
+{
+public:
+ FFmpegDecoder (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Options>, Job *, Log *, bool, bool);
+ ~FFmpegDecoder ();
+
+ /* Methods to query our input video */
+ int length_in_frames () const;
+ int decoding_frames () const;
+ float frames_per_second () const;
+ Size native_size () const;
+ int audio_channels () const;
+ int audio_sample_rate () const;
+ AVSampleFormat audio_sample_format () const;
+
+private:
+
+ bool do_pass ();
+ PixelFormat pixel_format () const;
+ int time_base_numerator () const;
+ int time_base_denominator () const;
+ int sample_aspect_ratio_numerator () const;
+ int sample_aspect_ratio_denominator () const;
+
+ void setup_general ();
+ void setup_video ();
+ void setup_audio ();
+
+ AVFormatContext* _format_context;
+ int _video_stream;
+ int _audio_stream;
+ AVFrame* _frame;
+
+ AVCodecContext* _video_codec_context;
+ AVCodec* _video_codec;
+ AVCodecContext* _audio_codec_context;
+ AVCodec* _audio_codec;
+
+ AVPacket _packet;
+};
diff --git a/src/lib/film.cc b/src/lib/film.cc
new file mode 100644
index 000000000..3eea41c25
--- /dev/null
+++ b/src/lib/film.cc
@@ -0,0 +1,631 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <stdexcept>
+#include <iostream>
+#include <fstream>
+#include <cstdlib>
+#include <sstream>
+#include <iomanip>
+#include <unistd.h>
+#include <boost/filesystem.hpp>
+#include <boost/algorithm/string.hpp>
+#include "film.h"
+#include "format.h"
+#include "tiff_encoder.h"
+#include "job.h"
+#include "filter.h"
+#include "transcoder.h"
+#include "util.h"
+#include "job_manager.h"
+#include "ab_transcode_job.h"
+#include "transcode_job.h"
+#include "make_mxf_job.h"
+#include "scp_dcp_job.h"
+#include "copy_from_dvd_job.h"
+#include "make_dcp_job.h"
+#include "film_state.h"
+#include "log.h"
+#include "options.h"
+#include "exceptions.h"
+#include "examine_content_job.h"
+#include "scaler.h"
+#include "decoder_factory.h"
+#include "config.h"
+
+using namespace std;
+using namespace boost;
+
+/** Construct a Film object in a given directory, reading any metadata
+ * file that exists in that directory. An exception will be thrown if
+ * must_exist is true, and the specified directory does not exist.
+ *
+ * @param d Film directory.
+ * @param must_exist true to throw an exception if does not exist.
+ */
+
+Film::Film (string d, bool must_exist)
+ : _dirty (false)
+{
+ /* Make _state.directory a complete path without ..s (where possible)
+ (Code swiped from Adam Bowen on stackoverflow)
+ */
+
+ filesystem::path p (filesystem::system_complete (d));
+ filesystem::path result;
+ for(filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
+ if (*i == "..") {
+ if (filesystem::is_symlink (result) || result.filename() == "..") {
+ result /= *i;
+ } else {
+ result = result.parent_path ();
+ }
+ } else if (*i != ".") {
+ result /= *i;
+ }
+ }
+
+ _state.directory = result.string ();
+
+ if (must_exist && !filesystem::exists (_state.directory)) {
+ throw OpenFileError (_state.directory);
+ }
+
+ read_metadata ();
+
+ _log = new Log (_state.file ("log"));
+}
+
+/** Copy constructor */
+Film::Film (Film const & other)
+ : _state (other._state)
+ , _dirty (other._dirty)
+{
+
+}
+
+Film::~Film ()
+{
+ delete _log;
+}
+
+/** Read the `metadata' file inside this Film's directory, and fill the
+ * object's data with its content.
+ */
+
+void
+Film::read_metadata ()
+{
+ ifstream f (metadata_file().c_str ());
+ string line;
+ while (getline (f, line)) {
+ if (line.empty ()) {
+ continue;
+ }
+
+ if (line[0] == '#') {
+ continue;
+ }
+
+ size_t const s = line.find (' ');
+ if (s == string::npos) {
+ continue;
+ }
+
+ _state.read_metadata (line.substr (0, s), line.substr (s + 1));
+ }
+
+ _dirty = false;
+}
+
+/** Write our state to a file `metadata' inside the Film's directory */
+void
+Film::write_metadata () const
+{
+ filesystem::create_directories (_state.directory);
+
+ ofstream f (metadata_file().c_str ());
+ if (!f.good ()) {
+ throw CreateFileError (metadata_file ());
+ }
+
+ _state.write_metadata (f);
+
+ _dirty = false;
+}
+
+/** Set the name by which DVD-o-matic refers to this Film */
+void
+Film::set_name (string n)
+{
+ _state.name = n;
+ signal_changed (NAME);
+}
+
+/** Set the content file for this film.
+ * @param c New content file; if specified as an absolute path, the content should
+ * be within the film's _state.directory; if specified as a relative path, the content
+ * will be assumed to be within the film's _state.directory.
+ */
+void
+Film::set_content (string c)
+{
+ if (filesystem::path(c).has_root_directory () && starts_with (c, _state.directory)) {
+ c = c.substr (_state.directory.length() + 1);
+ }
+
+ if (c == _state.content) {
+ return;
+ }
+
+ /* Create a temporary decoder so that we can get information
+ about the content.
+ */
+ shared_ptr<FilmState> s = state_copy ();
+ s->content = c;
+ shared_ptr<Options> o (new Options ("", "", ""));
+ o->out_size = Size (1024, 1024);
+
+ shared_ptr<Decoder> d = decoder_factory (s, o, 0, _log);
+
+ _state.size = d->native_size ();
+ _state.length = d->length_in_frames ();
+ _state.frames_per_second = d->frames_per_second ();
+ _state.audio_channels = d->audio_channels ();
+ _state.audio_sample_rate = d->audio_sample_rate ();
+ _state.audio_sample_format = d->audio_sample_format ();
+
+ _state.content = c;
+
+ signal_changed (SIZE);
+ signal_changed (LENGTH);
+ signal_changed (FRAMES_PER_SECOND);
+ signal_changed (AUDIO_CHANNELS);
+ signal_changed (AUDIO_SAMPLE_RATE);
+ signal_changed (CONTENT);
+}
+
+/** Set the format that this Film should be shown in */
+void
+Film::set_format (Format const * f)
+{
+ _state.format = f;
+ signal_changed (FORMAT);
+}
+
+/** Set the type to specify the DCP as having
+ * (feature, trailer etc.)
+ */
+void
+Film::set_dcp_content_type (DCPContentType const * t)
+{
+ _state.dcp_content_type = t;
+ signal_changed (DCP_CONTENT_TYPE);
+}
+
+/** Set the number of pixels by which to crop the left of the source video */
+void
+Film::set_left_crop (int c)
+{
+ if (c == _state.left_crop) {
+ return;
+ }
+
+ _state.left_crop = c;
+ signal_changed (LEFT_CROP);
+}
+
+/** Set the number of pixels by which to crop the right of the source video */
+void
+Film::set_right_crop (int c)
+{
+ if (c == _state.right_crop) {
+ return;
+ }
+
+ _state.right_crop = c;
+ signal_changed (RIGHT_CROP);
+}
+
+/** Set the number of pixels by which to crop the top of the source video */
+void
+Film::set_top_crop (int c)
+{
+ if (c == _state.top_crop) {
+ return;
+ }
+
+ _state.top_crop = c;
+ signal_changed (TOP_CROP);
+}
+
+/** Set the number of pixels by which to crop the bottom of the source video */
+void
+Film::set_bottom_crop (int c)
+{
+ if (c == _state.bottom_crop) {
+ return;
+ }
+
+ _state.bottom_crop = c;
+ signal_changed (BOTTOM_CROP);
+}
+
+/** Set the filters to apply to the image when generating thumbnails
+ * or a DCP.
+ */
+void
+Film::set_filters (vector<Filter const *> const & f)
+{
+ _state.filters = f;
+ signal_changed (FILTERS);
+}
+
+/** Set the number of frames to put in any generated DCP (from
+ * the start of the film). 0 indicates that all frames should
+ * be used.
+ */
+void
+Film::set_dcp_frames (int n)
+{
+ _state.dcp_frames = n;
+ signal_changed (DCP_FRAMES);
+}
+
+void
+Film::set_dcp_trim_action (TrimAction a)
+{
+ _state.dcp_trim_action = a;
+ signal_changed (DCP_TRIM_ACTION);
+}
+
+/** Set whether or not to generate a A/B comparison DCP.
+ * Such a DCP has the left half of its frame as the Film
+ * content without any filtering or post-processing; the
+ * right half is rendered with filters and post-processing.
+ */
+void
+Film::set_dcp_ab (bool a)
+{
+ _state.dcp_ab = a;
+ signal_changed (DCP_AB);
+}
+
+void
+Film::set_audio_gain (float g)
+{
+ _state.audio_gain = g;
+ signal_changed (AUDIO_GAIN);
+}
+
+void
+Film::set_audio_delay (int d)
+{
+ _state.audio_delay = d;
+ signal_changed (AUDIO_DELAY);
+}
+
+/** @return path of metadata file */
+string
+Film::metadata_file () const
+{
+ return _state.file ("metadata");
+}
+
+/** @return full path of the content (actual video) file
+ * of this Film.
+ */
+string
+Film::content () const
+{
+ return _state.content_path ();
+}
+
+/** The pre-processing GUI part of a thumbs update.
+ * Must be called from the GUI thread.
+ */
+void
+Film::update_thumbs_pre_gui ()
+{
+ _state.thumbs.clear ();
+ filesystem::remove_all (_state.dir ("thumbs"));
+
+ /* This call will recreate the directory */
+ _state.dir ("thumbs");
+}
+
+/** The post-processing GUI part of a thumbs update.
+ * Must be called from the GUI thread.
+ */
+void
+Film::update_thumbs_post_gui ()
+{
+ string const tdir = _state.dir ("thumbs");
+
+ for (filesystem::directory_iterator i = filesystem::directory_iterator (tdir); i != filesystem::directory_iterator(); ++i) {
+
+ /* Aah, the sweet smell of progress */
+#if BOOST_FILESYSTEM_VERSION == 3
+ string const l = filesystem::path(*i).leaf().generic_string();
+#else
+ string const l = i->leaf ();
+#endif
+
+ size_t const d = l.find (".tiff");
+ if (d != string::npos) {
+ _state.thumbs.push_back (atoi (l.substr (0, d).c_str()));
+ }
+ }
+
+ sort (_state.thumbs.begin(), _state.thumbs.end());
+
+ write_metadata ();
+ signal_changed (THUMBS);
+}
+
+/** @return the number of thumbnail images that we have */
+int
+Film::num_thumbs () const
+{
+ return _state.thumbs.size ();
+}
+
+/** @param n A thumb index.
+ * @return The frame within the Film that it is for.
+ */
+int
+Film::thumb_frame (int n) const
+{
+ return _state.thumb_frame (n);
+}
+
+/** @param n A thumb index.
+ * @return The path to the thumb's image file.
+ */
+string
+Film::thumb_file (int n) const
+{
+ return _state.thumb_file (n);
+}
+
+/** @return The path to the directory to write JPEG2000 files to */
+string
+Film::j2k_dir () const
+{
+ assert (format());
+
+ stringstream s;
+
+ /* Start with j2c */
+ s << "j2c/";
+
+ pair<string, string> f = Filter::ffmpeg_strings (filters ());
+
+ /* Write stuff to specify the filter / post-processing settings that are in use,
+ so that we don't get confused about J2K files generated using different
+ settings.
+ */
+ s << _state.format->nickname()
+ << "_" << _state.content
+ << "_" << left_crop() << "_" << right_crop() << "_" << top_crop() << "_" << bottom_crop()
+ << "_" << f.first << "_" << f.second
+ << "_" << _state.scaler->id();
+
+ /* Similarly for the A/B case */
+ if (dcp_ab()) {
+ pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
+ s << "/ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
+ }
+
+ return _state.dir (s.str ());
+}
+
+/** Handle a change to the Film's metadata */
+void
+Film::signal_changed (Property p)
+{
+ _dirty = true;
+ Changed (p);
+}
+
+/** Add suitable Jobs to the JobManager to create a DCP for this Film.
+ * @param true to transcode, false to use the WAV and J2K files that are already there.
+ */
+void
+Film::make_dcp (bool transcode, int freq)
+{
+ string const t = name ();
+ if (t.find ("/") != string::npos) {
+ throw BadSettingError ("name", "cannot contain slashes");
+ }
+
+ {
+ stringstream s;
+ s << "DVD-o-matic " << DVDOMATIC_VERSION << " using " << dependency_version_summary ();
+ log()->log (s.str ());
+ }
+
+ {
+ char buffer[128];
+ gethostname (buffer, sizeof (buffer));
+ stringstream s;
+ s << "Starting to make a DCP on " << buffer;
+ log()->log (s.str ());
+ }
+
+ if (format() == 0) {
+ throw MissingSettingError ("format");
+ }
+
+ if (content().empty ()) {
+ throw MissingSettingError ("content");
+ }
+
+ if (dcp_content_type() == 0) {
+ throw MissingSettingError ("content type");
+ }
+
+ if (name().empty()) {
+ throw MissingSettingError ("name");
+ }
+
+ shared_ptr<const FilmState> fs = state_copy ();
+ shared_ptr<Options> o (new Options (j2k_dir(), ".j2c", _state.dir ("wavs")));
+ o->out_size = format()->dcp_size ();
+ if (dcp_frames() == 0) {
+ /* Decode the whole film, no blacking */
+ o->num_frames = 0;
+ o->black_after = 0;
+ } else {
+ switch (dcp_trim_action()) {
+ case CUT:
+ /* Decode only part of the film, no blacking */
+ o->num_frames = dcp_frames ();
+ o->black_after = 0;
+ break;
+ case BLACK_OUT:
+ /* Decode the whole film, but black some frames out */
+ o->num_frames = 0;
+ o->black_after = dcp_frames ();
+ }
+ }
+
+ o->decode_video_frequency = freq;
+ o->padding = format()->dcp_padding ();
+ o->ratio = format()->ratio_as_float ();
+
+ if (transcode) {
+ if (_state.dcp_ab) {
+ JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (fs, o, log ())));
+ } else {
+ JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (fs, o, log ())));
+ }
+ }
+
+ JobManager::instance()->add (shared_ptr<Job> (new MakeMXFJob (fs, o, log (), MakeMXFJob::VIDEO)));
+ if (audio_channels() > 0) {
+ JobManager::instance()->add (shared_ptr<Job> (new MakeMXFJob (fs, o, log (), MakeMXFJob::AUDIO)));
+ }
+ JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (fs, o, log ())));
+}
+
+shared_ptr<FilmState>
+Film::state_copy () const
+{
+ return shared_ptr<FilmState> (new FilmState (_state));
+}
+
+void
+Film::copy_from_dvd_post_gui ()
+{
+ const string dvd_dir = _state.dir ("dvd");
+
+ string largest_file;
+ uintmax_t largest_size = 0;
+ for (filesystem::directory_iterator i = filesystem::directory_iterator (dvd_dir); i != filesystem::directory_iterator(); ++i) {
+ uintmax_t const s = filesystem::file_size (*i);
+ if (s > largest_size) {
+
+#if BOOST_FILESYSTEM_VERSION == 3
+ largest_file = filesystem::path(*i).generic_string();
+#else
+ largest_file = i->string ();
+#endif
+ largest_size = s;
+ }
+ }
+
+ set_content (largest_file);
+}
+
+void
+Film::examine_content ()
+{
+ if (_examine_content_job) {
+ return;
+ }
+
+ _examine_content_job.reset (new ExamineContentJob (state_copy (), log ()));
+ _examine_content_job->Finished.connect (sigc::mem_fun (*this, &Film::examine_content_post_gui));
+ JobManager::instance()->add (_examine_content_job);
+}
+
+void
+Film::examine_content_post_gui ()
+{
+ _state.length = _examine_content_job->last_video_frame ();
+ signal_changed (LENGTH);
+
+ _examine_content_job.reset ();
+}
+
+void
+Film::set_scaler (Scaler const * s)
+{
+ _state.scaler = s;
+ signal_changed (SCALER);
+}
+
+void
+Film::set_frames_per_second (float f)
+{
+ _state.frames_per_second = f;
+ signal_changed (FRAMES_PER_SECOND);
+}
+
+/** @return full paths to any audio files that this Film has */
+vector<string>
+Film::audio_files () const
+{
+ vector<string> f;
+ for (filesystem::directory_iterator i = filesystem::directory_iterator (_state.dir("wavs")); i != filesystem::directory_iterator(); ++i) {
+ f.push_back (i->path().string ());
+ }
+
+ return f;
+}
+
+ContentType
+Film::content_type () const
+{
+ return _state.content_type ();
+}
+
+void
+Film::set_still_duration (int d)
+{
+ _state.still_duration = d;
+ signal_changed (STILL_DURATION);
+}
+
+void
+Film::send_dcp_to_tms ()
+{
+ shared_ptr<Job> j (new SCPDCPJob (state_copy (), log ()));
+ JobManager::instance()->add (j);
+}
+
+void
+Film::copy_from_dvd ()
+{
+ shared_ptr<Job> j (new CopyFromDVDJob (state_copy (), log ()));
+ j->Finished.connect (sigc::mem_fun (*this, &Film::copy_from_dvd_post_gui));
+ JobManager::instance()->add (j);
+}
+
diff --git a/src/lib/film.h b/src/lib/film.h
new file mode 100644
index 000000000..f746da480
--- /dev/null
+++ b/src/lib/film.h
@@ -0,0 +1,275 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/film.h
+ * @brief A representation of a piece of video (with sound), including naming,
+ * the source content file, and how it should be presented in a DCP.
+ */
+
+#ifndef DVDOMATIC_FILM_H
+#define DVDOMATIC_FILM_H
+
+#include <string>
+#include <vector>
+#include <inttypes.h>
+#include <boost/thread/mutex.hpp>
+#include <sigc++/signal.h>
+extern "C" {
+#include <libavcodec/avcodec.h>
+}
+#include "dcp_content_type.h"
+#include "film_state.h"
+
+class Format;
+class Job;
+class Filter;
+class Log;
+class ExamineContentJob;
+
+/** @class Film
+ * @brief A representation of a video with sound.
+ *
+ * A representation of a piece of video (with sound), including naming,
+ * the source content file, and how it should be presented in a DCP.
+ */
+class Film
+{
+public:
+ Film (std::string d, bool must_exist = true);
+ Film (Film const &);
+ ~Film ();
+
+ void write_metadata () const;
+
+ /** @return complete path to directory containing the film metadata */
+ std::string directory () const {
+ return _state.directory;
+ }
+
+ std::string content () const;
+ ContentType content_type () const;
+
+ /** @return name for DVD-o-matic */
+ std::string name () const {
+ return _state.name;
+ }
+
+ /** @return number of pixels to crop from the top of the original picture */
+ int top_crop () const {
+ return _state.top_crop;
+ }
+
+ /** @return number of pixels to crop from the bottom of the original picture */
+ int bottom_crop () const {
+ return _state.bottom_crop;
+ }
+
+ /** @return number of pixels to crop from the left-hand side of the original picture */
+ int left_crop () const {
+ return _state.left_crop;
+ }
+
+ /** @return number of pixels to crop from the right-hand side of the original picture */
+ int right_crop () const {
+ return _state.right_crop;
+ }
+
+ /** @return the format to present this film in (flat, scope, etc.) */
+ Format const * format () const {
+ return _state.format;
+ }
+
+ /** @return video filters that should be used when generating DCPs */
+ std::vector<Filter const *> filters () const {
+ return _state.filters;
+ }
+
+ /** @return scaler algorithm to use */
+ Scaler const * scaler () const {
+ return _state.scaler;
+ }
+
+ /** @return number of frames to put in the DCP, or 0 for all */
+ int dcp_frames () const {
+ return _state.dcp_frames;
+ }
+
+ TrimAction dcp_trim_action () const {
+ return _state.dcp_trim_action;
+ }
+
+ /** @return true to create an A/B comparison DCP, where the left half of the image
+ * is the video without any filters or post-processing, and the right half
+ * has the specified filters and post-processing.
+ */
+ bool dcp_ab () const {
+ return _state.dcp_ab;
+ }
+
+ float audio_gain () const {
+ return _state.audio_gain;
+ }
+
+ int audio_delay () const {
+ return _state.audio_delay;
+ }
+
+ int still_duration () const {
+ return _state.still_duration;
+ }
+
+ void set_filters (std::vector<Filter const *> const &);
+
+ void set_scaler (Scaler const *);
+
+ /** @return the type of content that this Film represents (feature, trailer etc.) */
+ DCPContentType const * dcp_content_type () {
+ return _state.dcp_content_type;
+ }
+
+ void set_dcp_frames (int);
+ void set_dcp_trim_action (TrimAction);
+ void set_dcp_ab (bool);
+
+ void set_name (std::string);
+ void set_content (std::string);
+ void set_top_crop (int);
+ void set_bottom_crop (int);
+ void set_left_crop (int);
+ void set_right_crop (int);
+ void set_frames_per_second (float);
+ void set_format (Format const *);
+ void set_dcp_content_type (DCPContentType const *);
+ void set_audio_gain (float);
+ void set_audio_delay (int);
+ void set_still_duration (int);
+
+ /** @return size, in pixels, of the source (ignoring cropping) */
+ Size size () const {
+ return _state.size;
+ }
+
+ /** @return length, in video frames */
+ int length () const {
+ return _state.length;
+ }
+
+ /** @return nnumber of video frames per second */
+ float frames_per_second () const {
+ return _state.frames_per_second;
+ }
+
+ /** @return number of audio channels */
+ int audio_channels () const {
+ return _state.audio_channels;
+ }
+
+ /** @return audio sample rate, in Hz */
+ int audio_sample_rate () const {
+ return _state.audio_sample_rate;
+ }
+
+ /** @return format of the audio samples */
+ AVSampleFormat audio_sample_format () const {
+ return _state.audio_sample_format;
+ }
+
+ std::string j2k_dir () const;
+
+ std::vector<std::string> audio_files () const;
+
+ void update_thumbs_pre_gui ();
+ void update_thumbs_post_gui ();
+ int num_thumbs () const;
+ int thumb_frame (int) const;
+ std::string thumb_file (int) const;
+
+ void copy_from_dvd_post_gui ();
+ void examine_content ();
+ void examine_content_post_gui ();
+ void send_dcp_to_tms ();
+ void copy_from_dvd ();
+
+ /** @return true if our metadata has been modified since it was last saved */
+ bool dirty () const {
+ return _dirty;
+ }
+
+ void make_dcp (bool, int freq = 0);
+
+ enum Property {
+ NAME,
+ CONTENT,
+ DCP_CONTENT_TYPE,
+ FORMAT,
+ LEFT_CROP,
+ RIGHT_CROP,
+ TOP_CROP,
+ BOTTOM_CROP,
+ FILTERS,
+ SCALER,
+ DCP_FRAMES,
+ DCP_TRIM_ACTION,
+ DCP_AB,
+ AUDIO_GAIN,
+ AUDIO_DELAY,
+ THUMBS,
+ SIZE,
+ LENGTH,
+ FRAMES_PER_SECOND,
+ AUDIO_CHANNELS,
+ AUDIO_SAMPLE_RATE,
+ STILL_DURATION
+ };
+
+ boost::shared_ptr<FilmState> state_copy () const;
+
+ /** @return Logger.
+ * It is safe to call this from any thread.
+ */
+ Log* log () const {
+ return _log;
+ }
+
+ /** Emitted when some metadata property has changed */
+ mutable sigc::signal1<void, Property> Changed;
+
+private:
+ void read_metadata ();
+ std::string metadata_file () const;
+ void update_dimensions ();
+ void signal_changed (Property);
+
+ /** The majority of our state. Kept in a separate object
+ * so that it can easily be copied for passing onto long-running
+ * jobs (which then have an unchanging set of parameters).
+ */
+ FilmState _state;
+
+ /** true if our metadata has changed since it was last written to disk */
+ mutable bool _dirty;
+
+ /** Log to write to */
+ Log* _log;
+
+ /** Any running ExamineContentJob, or 0 */
+ boost::shared_ptr<ExamineContentJob> _examine_content_job;
+};
+
+#endif
diff --git a/src/lib/film_state.cc b/src/lib/film_state.cc
new file mode 100644
index 000000000..16378086c
--- /dev/null
+++ b/src/lib/film_state.cc
@@ -0,0 +1,254 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/film_state.cc
+ * @brief The state of a Film. This is separate from Film so that
+ * state can easily be copied and kept around for reference
+ * by long-running jobs. This avoids the jobs getting confused
+ * by the user changing Film settings during their run.
+ */
+
+#include <fstream>
+#include <string>
+#include <iomanip>
+#include <sstream>
+#include <boost/filesystem.hpp>
+#include "film_state.h"
+#include "scaler.h"
+#include "filter.h"
+#include "format.h"
+#include "dcp_content_type.h"
+#include "util.h"
+
+using namespace std;
+using namespace boost;
+
+/** Write state to a stream.
+ * @param f Stream to write to.
+ */
+void
+FilmState::write_metadata (ofstream& f) const
+{
+ /* User stuff */
+ f << "name " << name << "\n";
+ f << "content " << content << "\n";
+ if (dcp_content_type) {
+ f << "dcp_content_type " << dcp_content_type->pretty_name () << "\n";
+ }
+ f << "frames_per_second " << frames_per_second << "\n";
+ if (format) {
+ f << "format " << format->as_metadata () << "\n";
+ }
+ f << "left_crop " << left_crop << "\n";
+ f << "right_crop " << right_crop << "\n";
+ f << "top_crop " << top_crop << "\n";
+ f << "bottom_crop " << bottom_crop << "\n";
+ for (vector<Filter const *>::const_iterator i = filters.begin(); i != filters.end(); ++i) {
+ f << "filter " << (*i)->id () << "\n";
+ }
+ f << "scaler " << scaler->id () << "\n";
+ f << "dcp_frames " << dcp_frames << "\n";
+
+ f << "dcp_trim_action ";
+ switch (dcp_trim_action) {
+ case CUT:
+ f << "cut\n";
+ break;
+ case BLACK_OUT:
+ f << "black_out\n";
+ break;
+ }
+
+ f << "dcp_ab " << (dcp_ab ? "1" : "0") << "\n";
+ f << "audio_gain " << audio_gain << "\n";
+ f << "audio_delay " << audio_delay << "\n";
+ f << "still_duration " << still_duration << "\n";
+
+ /* Cached stuff; this is information about our content; we could
+ look it up each time, but that's slow.
+ */
+ for (vector<int>::const_iterator i = thumbs.begin(); i != thumbs.end(); ++i) {
+ f << "thumb " << *i << "\n";
+ }
+ f << "width " << size.width << "\n";
+ f << "height " << size.height << "\n";
+ f << "length " << length << "\n";
+ f << "audio_channels " << audio_channels << "\n";
+ f << "audio_sample_rate " << audio_sample_rate << "\n";
+ f << "audio_sample_format " << audio_sample_format_to_string (audio_sample_format) << "\n";
+}
+
+/** Read state from a key / value pair.
+ * @param k Key.
+ * @param v Value.
+ */
+void
+FilmState::read_metadata (string k, string v)
+{
+ /* User-specified stuff */
+ if (k == "name") {
+ name = v;
+ } else if (k == "content") {
+ content = v;
+ } else if (k == "dcp_content_type") {
+ dcp_content_type = DCPContentType::from_pretty_name (v);
+ } else if (k == "frames_per_second") {
+ frames_per_second = atof (v.c_str ());
+ } else if (k == "format") {
+ format = Format::from_metadata (v);
+ } else if (k == "left_crop") {
+ left_crop = atoi (v.c_str ());
+ } else if (k == "right_crop") {
+ right_crop = atoi (v.c_str ());
+ } else if (k == "top_crop") {
+ top_crop = atoi (v.c_str ());
+ } else if (k == "bottom_crop") {
+ bottom_crop = atoi (v.c_str ());
+ } else if (k == "filter") {
+ filters.push_back (Filter::from_id (v));
+ } else if (k == "scaler") {
+ scaler = Scaler::from_id (v);
+ } else if (k == "dcp_frames") {
+ dcp_frames = atoi (v.c_str ());
+ } else if (k == "dcp_trim_action") {
+ if (v == "cut") {
+ dcp_trim_action = CUT;
+ } else if (v == "black_out") {
+ dcp_trim_action = BLACK_OUT;
+ }
+ } else if (k == "dcp_ab") {
+ dcp_ab = (v == "1");
+ } else if (k == "audio_gain") {
+ audio_gain = atof (v.c_str ());
+ } else if (k == "audio_delay") {
+ audio_delay = atoi (v.c_str ());
+ } else if (k == "still_duration") {
+ still_duration = atoi (v.c_str ());
+ }
+
+ /* Cached stuff */
+ if (k == "thumb") {
+ int const n = atoi (v.c_str ());
+ /* Only add it to the list if it still exists */
+ if (filesystem::exists (thumb_file_for_frame (n))) {
+ thumbs.push_back (n);
+ }
+ } else if (k == "width") {
+ size.width = atoi (v.c_str ());
+ } else if (k == "height") {
+ size.height = atoi (v.c_str ());
+ } else if (k == "length") {
+ length = atof (v.c_str ());
+ } else if (k == "audio_channels") {
+ audio_channels = atoi (v.c_str ());
+ } else if (k == "audio_sample_rate") {
+ audio_sample_rate = atoi (v.c_str ());
+ } else if (k == "audio_sample_format") {
+ audio_sample_format = audio_sample_format_from_string (v);
+ }
+}
+
+
+/** @param n A thumb index.
+ * @return The path to the thumb's image file.
+ */
+string
+FilmState::thumb_file (int n) const
+{
+ return thumb_file_for_frame (thumb_frame (n));
+}
+
+/** @param n A frame index within the Film.
+ * @return The path to the thumb's image file for this frame;
+ * we assume that it exists.
+ */
+string
+FilmState::thumb_file_for_frame (int n) const
+{
+ stringstream s;
+ s << dir ("thumbs") << "/";
+ s.width (8);
+ s << setfill('0') << n << ".tiff";
+ return s.str ();
+}
+
+
+/** @param n A thumb index.
+ * @return The frame within the Film that it is for.
+ */
+int
+FilmState::thumb_frame (int n) const
+{
+ assert (n < int (thumbs.size ()));
+ return thumbs[n];
+}
+
+Size
+FilmState::cropped_size (Size s) const
+{
+ s.width -= left_crop + right_crop;
+ s.height -= top_crop + bottom_crop;
+ return s;
+}
+
+/** Given a directory name, return its full path within the Film's directory.
+ * The directory (and its parents) will be created if they do not exist.
+ */
+string
+FilmState::dir (string d) const
+{
+ stringstream s;
+ s << directory << "/" << d;
+ filesystem::create_directories (s.str ());
+ return s.str ();
+}
+
+/** Given a file or directory name, return its full path within the Film's directory */
+string
+FilmState::file (string f) const
+{
+ stringstream s;
+ s << directory << "/" << f;
+ return s.str ();
+}
+
+string
+FilmState::content_path () const
+{
+ if (filesystem::path(content).has_root_directory ()) {
+ return content;
+ }
+
+ return file (content);
+}
+
+ContentType
+FilmState::content_type () const
+{
+#if BOOST_FILESYSTEM_VERSION == 3
+ string const ext = filesystem::path(content).extension().string();
+#else
+ string const ext = filesystem::path(content).extension();
+#endif
+ if (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png") {
+ return STILL;
+ }
+
+ return VIDEO;
+}
diff --git a/src/lib/film_state.h b/src/lib/film_state.h
new file mode 100644
index 000000000..52525ecd4
--- /dev/null
+++ b/src/lib/film_state.h
@@ -0,0 +1,155 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/film_state.h
+ * @brief The state of a Film. This is separate from Film so that
+ * state can easily be copied and kept around for reference
+ * by long-running jobs. This avoids the jobs getting confused
+ * by the user changing Film settings during their run.
+ */
+
+#ifndef DVDOMATIC_FILM_STATE_H
+#define DVDOMATIC_FILM_STATE_H
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libswscale/swscale.h>
+}
+#include "scaler.h"
+#include "util.h"
+#include "trim_action.h"
+
+class Format;
+class DCPContentType;
+class Filter;
+
+/** @class FilmState
+ * @brief The state of a Film.
+ *
+ * This is separate from Film so that state can easily be copied and
+ * kept around for reference by long-running jobs. This avoids the
+ * jobs getting confused by the user changing Film settings during
+ * their run.
+ */
+
+class FilmState
+{
+public:
+ FilmState ()
+ : dcp_content_type (0)
+ , frames_per_second (0)
+ , format (0)
+ , left_crop (0)
+ , right_crop (0)
+ , top_crop (0)
+ , bottom_crop (0)
+ , scaler (Scaler::from_id ("bicubic"))
+ , dcp_frames (0)
+ , dcp_trim_action (CUT)
+ , dcp_ab (false)
+ , audio_gain (0)
+ , audio_delay (0)
+ , still_duration (10)
+ , length (0)
+ , audio_channels (0)
+ , audio_sample_rate (0)
+ , audio_sample_format (AV_SAMPLE_FMT_NONE)
+ {}
+
+ std::string file (std::string f) const;
+ std::string dir (std::string d) const;
+
+ std::string content_path () const;
+ ContentType content_type () const;
+
+ bool content_is_dvd () const;
+
+ std::string thumb_file (int) const;
+ int thumb_frame (int) const;
+
+ void write_metadata (std::ofstream &) const;
+ void read_metadata (std::string, std::string);
+
+ Size cropped_size (Size) const;
+
+ /** Complete path to directory containing the film metadata;
+ must not be relative.
+ */
+ std::string directory;
+ /** Name for DVD-o-matic */
+ std::string name;
+ /** File or directory containing content; may be relative to our directory
+ * or an absolute path.
+ */
+ std::string content;
+ /** The type of content that this Film represents (feature, trailer etc.) */
+ DCPContentType const * dcp_content_type;
+ /** Frames per second of the source */
+ float frames_per_second;
+ /** The format to present this Film in (flat, scope, etc.) */
+ Format const * format;
+ /** Number of pixels to crop from the left-hand side of the original picture */
+ int left_crop;
+ /** Number of pixels to crop from the right-hand side of the original picture */
+ int right_crop;
+ /** Number of pixels to crop from the top of the original picture */
+ int top_crop;
+ /** Number of pixels to crop from the bottom of the original picture */
+ int bottom_crop;
+ /** Video filters that should be used when generating DCPs */
+ std::vector<Filter const *> filters;
+ /** Scaler algorithm to use */
+ Scaler const * scaler;
+ /** Number of frames to put in the DCP, or 0 for all */
+ int dcp_frames;
+
+ TrimAction dcp_trim_action;
+
+ /** true to create an A/B comparison DCP, where the left half of the image
+ is the video without any filters or post-processing, and the right half
+ has the specified filters and post-processing.
+ */
+ bool dcp_ab;
+ /** Gain to apply to audio in dB */
+ float audio_gain;
+ /** Delay to apply to audio (positive moves audio later) in milliseconds */
+ int audio_delay;
+ /** Duration to make still-sourced films (in seconds) */
+ int still_duration;
+
+ /* Data which is cached to speed things up */
+
+ /** Vector of frame indices for each of our `thumbnails */
+ std::vector<int> thumbs;
+ /** Size, in pixels, of the source (ignoring cropping) */
+ Size size;
+ /** Length in frames */
+ int length;
+ /** Number of audio channels */
+ int audio_channels;
+ /** Sample rate of the audio, in Hz */
+ int audio_sample_rate;
+ /** Format of the audio samples */
+ AVSampleFormat audio_sample_format;
+
+private:
+ std::string thumb_file_for_frame (int) const;
+};
+
+#endif
diff --git a/src/lib/filter.cc b/src/lib/filter.cc
new file mode 100644
index 000000000..ab5a6316f
--- /dev/null
+++ b/src/lib/filter.cc
@@ -0,0 +1,131 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/filter.cc
+ * @brief A class to describe one of FFmpeg's video or post-processing filters.
+ */
+
+#include "filter.h"
+
+using namespace std;
+
+vector<Filter const *> Filter::_filters;
+
+/** @param i Our id.
+ * @param n User-visible name.
+ * @param v String for a FFmpeg video filter descriptor, or "".
+ * @param p String for a FFmpeg post-processing descriptor, or "".
+ */
+Filter::Filter (string i, string n, string v, string p)
+ : _id (i)
+ , _name (n)
+ , _vf (v)
+ , _pp (p)
+{
+
+}
+
+/** @return All available filters */
+vector<Filter const *>
+Filter::all ()
+{
+ return _filters;
+}
+
+
+/** Set up the static _filters vector; must be called before from_*
+ * methods are used.
+ */
+void
+Filter::setup_filters ()
+{
+ /* Note: "none" is a magic id name, so don't use it here */
+
+ _filters.push_back (new Filter ("pphb", "Horizontal deblocking filter", "", "hb"));
+ _filters.push_back (new Filter ("ppvb", "Vertical deblocking filter", "", "vb"));
+ _filters.push_back (new Filter ("ppha", "Horizontal deblocking filter A", "", "ha"));
+ _filters.push_back (new Filter ("ppva", "Vertical deblocking filter A", "", "va"));
+ _filters.push_back (new Filter ("pph1", "Experimental horizontal deblocking filter 1", "", "h1"));
+ _filters.push_back (new Filter ("pphv", "Experimental vertical deblocking filter 1", "", "v1"));
+ _filters.push_back (new Filter ("ppdr", "Deringing filter", "", "dr"));
+ _filters.push_back (new Filter ("pplb", "Linear blend deinterlacer", "", "lb"));
+ _filters.push_back (new Filter ("ppli", "Linear interpolating deinterlacer", "", "li"));
+ _filters.push_back (new Filter ("ppci", "Cubic interpolating deinterlacer", "", "ci"));
+ _filters.push_back (new Filter ("ppmd", "Median deinterlacer", "", "md"));
+ _filters.push_back (new Filter ("ppfd", "FFMPEG deinterlacer", "", "fd"));
+ _filters.push_back (new Filter ("ppl5", "FIR low-pass deinterlacer", "", "l5"));
+ _filters.push_back (new Filter ("mcdeint", "Motion compensating deinterlacer", "mcdeint", ""));
+ _filters.push_back (new Filter ("kerndeint", "Kernel deinterlacer", "kerndeint", ""));
+ _filters.push_back (new Filter ("pptn", "Temporal noise reducer", "", "tn"));
+ _filters.push_back (new Filter ("ppfq", "Force quantizer", "", "fq"));
+ _filters.push_back (new Filter ("gradfun", "Gradient debander", "gradfun", ""));
+ _filters.push_back (new Filter ("unsharp", "Unsharp mask and Gaussian blur", "unsharp", ""));
+ _filters.push_back (new Filter ("denoise3d", "3D denoiser", "denoise3d", ""));
+ _filters.push_back (new Filter ("hqdn3d", "High quality 3D denoiser", "hqdn3d", ""));
+ _filters.push_back (new Filter ("telecine", "Telecine filter", "telecine", ""));
+ _filters.push_back (new Filter ("ow", "Overcomplete wavelet denoiser", "mp=ow", ""));
+}
+
+/** @param filters Set of filters.
+ * @return A pair; .first is a string to pass to FFmpeg for the video filters,
+ * .second is a string to pass for the post-processors.
+ */
+pair<string, string>
+Filter::ffmpeg_strings (vector<Filter const *> const & filters)
+{
+ string vf;
+ string pp;
+
+ for (vector<Filter const *>::const_iterator i = filters.begin(); i != filters.end(); ++i) {
+ if (!(*i)->vf().empty ()) {
+ if (!vf.empty ()) {
+ vf += ",";
+ }
+ vf += (*i)->vf ();
+ }
+
+ if (!(*i)->pp().empty ()) {
+ if (!pp.empty()) {
+ pp += ",";
+ }
+ pp += (*i)->pp ();
+ }
+ }
+
+ return make_pair (vf, pp);
+}
+
+/** @param d Our id.
+ * @return Corresponding Filter, or 0.
+ */
+Filter const *
+Filter::from_id (string d)
+{
+ vector<Filter const *>::iterator i = _filters.begin ();
+ while (i != _filters.end() && (*i)->id() != d) {
+ ++i;
+ }
+
+ if (i == _filters.end ()) {
+ return 0;
+ }
+
+ return *i;
+}
+
diff --git a/src/lib/filter.h b/src/lib/filter.h
new file mode 100644
index 000000000..20c55049c
--- /dev/null
+++ b/src/lib/filter.h
@@ -0,0 +1,78 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/filter.h
+ * @brief A class to describe one of FFmpeg's video or post-processing filters.
+ */
+
+#ifndef DVDOMATIC_FILTER_H
+#define DVDOMATIC_FILTER_H
+
+#include <string>
+#include <vector>
+
+/** @class Filter
+ * @brief A class to describe one of FFmpeg's video or post-processing filters.
+ */
+class Filter
+{
+public:
+ Filter (std::string, std::string, std::string, std::string);
+
+ /** @return our id */
+ std::string id () const {
+ return _id;
+ }
+
+ /** @return user-visible name */
+ std::string name () const {
+ return _name;
+ }
+
+ /** @return string for a FFmpeg video filter descriptor */
+ std::string vf () const {
+ return _vf;
+ }
+
+ /** @return string for a FFmpeg post-processing descriptor */
+ std::string pp () const {
+ return _pp;
+ }
+
+ static std::vector<Filter const *> all ();
+ static Filter const * from_id (std::string);
+ static void setup_filters ();
+ static std::pair<std::string, std::string> ffmpeg_strings (std::vector<Filter const *> const &);
+
+private:
+
+ /** our id */
+ std::string _id;
+ /** user-visible name */
+ std::string _name;
+ /** string for a FFmpeg video filter descriptor */
+ std::string _vf;
+ /** string for a FFmpeg post-processing descriptor */
+ std::string _pp;
+
+ /** all available filters */
+ static std::vector<Filter const *> _filters;
+};
+
+#endif
diff --git a/src/lib/format.cc b/src/lib/format.cc
new file mode 100644
index 000000000..dcc884412
--- /dev/null
+++ b/src/lib/format.cc
@@ -0,0 +1,189 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/format.cc
+ * @brief Class to describe a format (aspect ratio) that a Film should
+ * be shown in.
+ */
+
+#include <sstream>
+#include <cstdlib>
+#include <cassert>
+#include <iomanip>
+#include <iostream>
+#include "format.h"
+
+using namespace std;
+
+vector<Format const *> Format::_formats;
+
+/** @param r Ratio multiplied by 100 (e.g. 185)
+ * @param dcp Size (in pixels) of the images that we should put in a DCP.
+ * @param id ID (e.g. 185)
+ * @param n Nick name (e.g. Flat)
+ */
+Format::Format (int r, Size dcp, string id, string n)
+ : _ratio (r)
+ , _dcp_size (dcp)
+ , _id (id)
+ , _nickname (n)
+{
+
+}
+
+/** @return A name to be presented to the user */
+string
+Format::name () const
+{
+ stringstream s;
+ if (!_nickname.empty ()) {
+ s << _nickname << " (";
+ }
+
+ s << setprecision(3) << (_ratio / 100.0) << ":1";
+
+ if (!_nickname.empty ()) {
+ s << ")";
+ }
+
+ return s.str ();
+}
+
+/** @return Identifier for this format as metadata for a Film's metadata file */
+string
+Format::as_metadata () const
+{
+ return _id;
+}
+
+/** Fill our _formats vector with all available formats */
+void
+Format::setup_formats ()
+{
+ _formats.push_back (new Format (133, Size (1998, 1080), "133-in-flat", "4:3 within Flat"));
+ _formats.push_back (new Format (137, Size (1480, 1080), "137", "Academy"));
+ _formats.push_back (new Format (178, Size (1998, 1080), "178-in-flat", "16:9 within Flat"));
+ _formats.push_back (new Format (185, Size (1998, 1080), "185", "Flat"));
+ _formats.push_back (new Format (239, Size (2048, 858), "239", "Scope"));
+}
+
+/** @param r Ratio multiplied by 100.
+ * @return Matching Format, or 0.
+ */
+Format const *
+Format::from_ratio (int r)
+{
+ vector<Format const *>::iterator i = _formats.begin ();
+ while (i != _formats.end() && (*i)->ratio_as_integer() != r) {
+ ++i;
+ }
+
+ if (i == _formats.end ()) {
+ return 0;
+ }
+
+ return *i;
+}
+
+/** @param n Nickname.
+ * @return Matching Format, or 0.
+ */
+Format const *
+Format::from_nickname (string n)
+{
+ vector<Format const *>::iterator i = _formats.begin ();
+ while (i != _formats.end() && (*i)->nickname() != n) {
+ ++i;
+ }
+
+ if (i == _formats.end ()) {
+ return 0;
+ }
+
+ return *i;
+}
+
+/** @param i Id.
+ * @return Matching Format, or 0.
+ */
+Format const *
+Format::from_id (string i)
+{
+ vector<Format const *>::iterator j = _formats.begin ();
+ while (j != _formats.end() && (*j)->id() != i) {
+ ++j;
+ }
+
+ if (j == _formats.end ()) {
+ return 0;
+ }
+
+ return *j;
+}
+
+
+/** @param m Metadata, as returned from as_metadata().
+ * @return Matching Format, or 0.
+ */
+Format const *
+Format::from_metadata (string m)
+{
+ return from_id (m);
+}
+
+/** @param f A Format.
+ * @return Index of f within our static list, or -1.
+ */
+int
+Format::as_index (Format const * f)
+{
+ vector<Format*>::size_type i = 0;
+ while (i < _formats.size() && _formats[i] != f) {
+ ++i;
+ }
+
+ if (i == _formats.size ()) {
+ return -1;
+ }
+
+ return i;
+}
+
+/** @param i An index returned from as_index().
+ * @return Corresponding Format.
+ */
+Format const *
+Format::from_index (int i)
+{
+ assert (i >= 0 && i < int(_formats.size ()));
+ return _formats[i];
+}
+
+/** @return All available formats */
+vector<Format const *>
+Format::all ()
+{
+ return _formats;
+}
+
+int
+Format::dcp_padding () const
+{
+ return rint ((_dcp_size.width - (_dcp_size.height * _ratio / 100.0)) / 2.0);
+}
diff --git a/src/lib/format.h b/src/lib/format.h
new file mode 100644
index 000000000..4b727b2e3
--- /dev/null
+++ b/src/lib/format.h
@@ -0,0 +1,101 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/format.h
+ * @brief Class to describe a format (aspect ratio) that a Film should
+ * be shown in.
+ */
+
+#include <string>
+#include <vector>
+#include "util.h"
+
+/** @class Format
+ * @brief Class to describe a format (aspect ratio) that a Film should
+ * be shown in.
+ */
+class Format
+{
+public:
+ Format (int, Size, std::string, std::string);
+
+ /** @return the aspect ratio multiplied by 100
+ * (e.g. 239 for Cinemascope 2.39:1)
+ */
+ int ratio_as_integer () const {
+ return _ratio;
+ }
+
+ /** @return the ratio as a floating point number */
+ float ratio_as_float () const {
+ return _ratio / 100.0;
+ }
+
+ /** @return size in pixels of the images that we should
+ * put in a DCP for this ratio. This size will not correspond
+ * to the ratio when we are doing things like 16:9 in a Flat frame.
+ */
+ Size dcp_size () const {
+ return _dcp_size;
+ }
+
+ std::string id () const {
+ return _id;
+ }
+
+ /** @return Full name to present to the user */
+ std::string name () const;
+
+ /** @return Nickname (e.g. Flat, Scope) */
+ std::string nickname () const {
+ return _nickname;
+ }
+
+ std::string as_metadata () const;
+
+ int dcp_padding () const;
+
+ static Format const * from_ratio (int);
+ static Format const * from_nickname (std::string n);
+ static Format const * from_metadata (std::string m);
+ static Format const * from_index (int i);
+ static Format const * from_id (std::string i);
+ static int as_index (Format const * f);
+ static std::vector<Format const *> all ();
+ static void setup_formats ();
+
+private:
+
+ /** Ratio expressed as the actual ratio multiplied by 100 */
+ int _ratio;
+ /** Size in pixels of the images that we should
+ * put in a DCP for this ratio. This size will not correspond
+ * to the ratio when we are doing things like 16:9 in a Flat frame.
+ */
+ Size _dcp_size;
+ /** id for use in metadata */
+ std::string _id;
+ /** nickname (e.g. Flat, Scope) */
+ std::string _nickname;
+
+ /** all available formats */
+ static std::vector<Format const *> _formats;
+};
+
+
diff --git a/src/lib/image.cc b/src/lib/image.cc
new file mode 100644
index 000000000..7a9ac7dd5
--- /dev/null
+++ b/src/lib/image.cc
@@ -0,0 +1,392 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/image.cc
+ * @brief A set of classes to describe video images.
+ */
+
+#include <sstream>
+#include <iomanip>
+#include <iostream>
+#include <execinfo.h>
+#include <cxxabi.h>
+#include <sys/time.h>
+#include <boost/algorithm/string.hpp>
+#include <openjpeg.h>
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+#include <libswresample/swresample.h>
+#include <libavfilter/avfiltergraph.h>
+#include <libavfilter/avcodec.h>
+#include <libavfilter/buffersink.h>
+#include <libpostproc/postprocess.h>
+#include <libavutil/pixfmt.h>
+}
+#include "image.h"
+#include "exceptions.h"
+#include "scaler.h"
+
+#ifdef DEBUG_HASH
+#include <mhash.h>
+#endif
+
+using namespace std;
+using namespace boost;
+
+/** @param n Component index.
+ * @return Number of lines in the image for the given component.
+ */
+int
+Image::lines (int n) const
+{
+ switch (_pixel_format) {
+ case PIX_FMT_YUV420P:
+ if (n == 0) {
+ return size().height;
+ } else {
+ return size().height / 2;
+ }
+ break;
+ case PIX_FMT_RGB24:
+ return size().height;
+ default:
+ assert (false);
+ }
+
+ return 0;
+}
+
+/** @return Number of components */
+int
+Image::components () const
+{
+ switch (_pixel_format) {
+ case PIX_FMT_YUV420P:
+ return 3;
+ case PIX_FMT_RGB24:
+ return 1;
+ default:
+ assert (false);
+ }
+
+ return 0;
+}
+
+#ifdef DEBUG_HASH
+/** Write a MD5 hash of the image's data to stdout.
+ * @param n Title to give the output.
+ */
+void
+Image::hash (string n) const
+{
+ MHASH ht = mhash_init (MHASH_MD5);
+ if (ht == MHASH_FAILED) {
+ throw EncodeError ("could not create hash thread");
+ }
+
+ for (int i = 0; i < components(); ++i) {
+ mhash (ht, data()[i], line_size()[i] * lines(i));
+ }
+
+ uint8_t hash[16];
+ mhash_deinit (ht, hash);
+
+ printf ("%s: ", n.c_str ());
+ for (int i = 0; i < int (mhash_get_block_size (MHASH_MD5)); ++i) {
+ printf ("%.2x", hash[i]);
+ }
+ printf ("\n");
+}
+#endif
+
+/** Scale this image to a given size and convert it to RGB.
+ * @param out_size Output image size in pixels.
+ * @param scaler Scaler to use.
+ */
+shared_ptr<RGBFrameImage>
+Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scaler) const
+{
+ assert (scaler);
+
+ Size content_size = out_size;
+ content_size.width -= (padding * 2);
+
+ shared_ptr<RGBFrameImage> rgb (new RGBFrameImage (content_size));
+
+ struct SwsContext* scale_context = sws_getContext (
+ size().width, size().height, pixel_format(),
+ content_size.width, content_size.height, PIX_FMT_RGB24,
+ scaler->ffmpeg_id (), 0, 0, 0
+ );
+
+ /* Scale and convert to RGB from whatever its currently in (which may be RGB) */
+ sws_scale (
+ scale_context,
+ data(), line_size(),
+ 0, size().height,
+ rgb->data (), rgb->line_size ()
+ );
+
+ /* Put the image in the right place in a black frame if are padding; this is
+ a bit grubby and expensive, but probably inconsequential in the great
+ scheme of things.
+ */
+ if (padding > 0) {
+ shared_ptr<RGBFrameImage> padded_rgb (new RGBFrameImage (out_size));
+ padded_rgb->make_black ();
+
+ /* XXX: we are cheating a bit here; we know the frame is RGB so we can
+ make assumptions about its composition.
+ */
+ uint8_t* p = padded_rgb->data()[0] + padding * 3;
+ uint8_t* q = rgb->data()[0];
+ for (int j = 0; j < rgb->lines(0); ++j) {
+ memcpy (p, q, rgb->line_size()[0]);
+ p += padded_rgb->line_size()[0];
+ q += rgb->line_size()[0];
+ }
+
+ rgb = padded_rgb;
+ }
+
+ sws_freeContext (scale_context);
+
+ return rgb;
+}
+
+/** Run a FFmpeg post-process on this image and return the processed version.
+ * @param pp Flags for the required set of post processes.
+ * @return Post-processed image.
+ */
+shared_ptr<PostProcessImage>
+Image::post_process (string pp) const
+{
+ shared_ptr<PostProcessImage> out (new PostProcessImage (PIX_FMT_YUV420P, size ()));
+
+ pp_mode* mode = pp_get_mode_by_name_and_quality (pp.c_str (), PP_QUALITY_MAX);
+ pp_context* context = pp_get_context (size().width, size().height, PP_FORMAT_420 | PP_CPU_CAPS_MMX2);
+
+ pp_postprocess (
+ (const uint8_t **) data(), line_size(),
+ out->data(), out->line_size(),
+ size().width, size().height,
+ 0, 0, mode, context, 0
+ );
+
+ pp_free_mode (mode);
+ pp_free_context (context);
+
+ return out;
+}
+
+void
+Image::make_black ()
+{
+ switch (_pixel_format) {
+ case PIX_FMT_YUV420P:
+ memset (data()[0], 0, lines(0) * line_size()[0]);
+ memset (data()[1], 0x80, lines(1) * line_size()[1]);
+ memset (data()[2], 0x80, lines(2) * line_size()[2]);
+ break;
+
+ case PIX_FMT_RGB24:
+ memset (data()[0], 0, lines(0) * line_size()[0]);
+ break;
+
+ default:
+ assert (false);
+ }
+}
+
+/** Construct a SimpleImage of a given size and format, allocating memory
+ * as required.
+ *
+ * @param p Pixel format.
+ * @param s Size in pixels.
+ */
+SimpleImage::SimpleImage (PixelFormat p, Size s)
+ : Image (p)
+ , _size (s)
+{
+ _data = (uint8_t **) av_malloc (components() * sizeof (uint8_t *));
+ _line_size = (int *) av_malloc (components() * sizeof (int));
+
+ for (int i = 0; i < components(); ++i) {
+ _data[i] = 0;
+ _line_size[i] = 0;
+ }
+}
+
+/** Destroy a SimpleImage */
+SimpleImage::~SimpleImage ()
+{
+ for (int i = 0; i < components(); ++i) {
+ av_free (_data[i]);
+ }
+
+ av_free (_data);
+ av_free (_line_size);
+}
+
+/** Set the size in bytes of each horizontal line of a given component.
+ * @param i Component index.
+ * @param s Size of line in bytes.
+ */
+void
+SimpleImage::set_line_size (int i, int s)
+{
+ _line_size[i] = s;
+ _data[i] = (uint8_t *) av_malloc (s * lines (i));
+}
+
+uint8_t **
+SimpleImage::data () const
+{
+ return _data;
+}
+
+int *
+SimpleImage::line_size () const
+{
+ return _line_size;
+}
+
+Size
+SimpleImage::size () const
+{
+ return _size;
+}
+
+
+FilterBufferImage::FilterBufferImage (PixelFormat p, AVFilterBufferRef* b)
+ : Image (p)
+ , _buffer (b)
+{
+
+}
+
+FilterBufferImage::~FilterBufferImage ()
+{
+ avfilter_unref_buffer (_buffer);
+}
+
+uint8_t **
+FilterBufferImage::data () const
+{
+ return _buffer->data;
+}
+
+int *
+FilterBufferImage::line_size () const
+{
+ return _buffer->linesize;
+}
+
+Size
+FilterBufferImage::size () const
+{
+ return Size (_buffer->video->w, _buffer->video->h);
+}
+
+/** XXX: this could be generalised to use any format, but I don't
+ * understand how avpicture_fill is supposed to be called with
+ * multi-planar images.
+ */
+RGBFrameImage::RGBFrameImage (Size s)
+ : Image (PIX_FMT_RGB24)
+ , _size (s)
+{
+ _frame = avcodec_alloc_frame ();
+ if (_frame == 0) {
+ throw EncodeError ("could not allocate frame");
+ }
+
+ _data = (uint8_t *) av_malloc (size().width * size().height * 3);
+ avpicture_fill ((AVPicture *) _frame, _data, PIX_FMT_RGB24, size().width, size().height);
+ _frame->width = size().width;
+ _frame->height = size().height;
+ _frame->format = PIX_FMT_RGB24;
+}
+
+RGBFrameImage::~RGBFrameImage ()
+{
+ av_free (_data);
+ av_free (_frame);
+}
+
+uint8_t **
+RGBFrameImage::data () const
+{
+ return _frame->data;
+}
+
+int *
+RGBFrameImage::line_size () const
+{
+ return _frame->linesize;
+}
+
+Size
+RGBFrameImage::size () const
+{
+ return _size;
+}
+
+PostProcessImage::PostProcessImage (PixelFormat p, Size s)
+ : Image (p)
+ , _size (s)
+{
+ _data = new uint8_t*[4];
+ _line_size = new int[4];
+
+ for (int i = 0; i < 4; ++i) {
+ _data[i] = (uint8_t *) av_malloc (s.width * s.height);
+ _line_size[i] = s.width;
+ }
+}
+
+PostProcessImage::~PostProcessImage ()
+{
+ for (int i = 0; i < 4; ++i) {
+ av_free (_data[i]);
+ }
+
+ delete[] _data;
+ delete[] _line_size;
+}
+
+uint8_t **
+PostProcessImage::data () const
+{
+ return _data;
+}
+
+int *
+PostProcessImage::line_size () const
+{
+ return _line_size;
+}
+
+Size
+PostProcessImage::size () const
+{
+ return _size;
+}
diff --git a/src/lib/image.h b/src/lib/image.h
new file mode 100644
index 000000000..97ab1d5ff
--- /dev/null
+++ b/src/lib/image.h
@@ -0,0 +1,164 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/image.h
+ * @brief A set of classes to describe video images.
+ */
+
+#ifndef DVDOMATIC_IMAGE_H
+#define DVDOMATIC_IMAGE_H
+
+#include <string>
+#include <boost/shared_ptr.hpp>
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavfilter/avfilter.h>
+}
+#include "util.h"
+
+class Scaler;
+class RGBFrameImage;
+class PostProcessImage;
+
+/** @class Image
+ * @brief Parent class for wrappers of some image, in some format, that
+ * can present a set of components and a size in pixels.
+ *
+ * This class also has some conversion / processing methods.
+ *
+ * The main point of this class (and its subclasses) is to abstract
+ * details of FFmpeg's memory management and varying data formats.
+ */
+class Image
+{
+public:
+ Image (PixelFormat p)
+ : _pixel_format (p)
+ {}
+
+ virtual ~Image () {}
+
+ /** @return Array of pointers to arrays of the component data */
+ virtual uint8_t ** data () const = 0;
+
+ /** @return Array of sizes of each line, in pixels */
+ virtual int * line_size () const = 0;
+
+ /** @return Size of the image, in pixels */
+ virtual Size size () const = 0;
+
+ int components () const;
+ int lines (int) const;
+ boost::shared_ptr<RGBFrameImage> scale_and_convert_to_rgb (Size, int, Scaler const *) const;
+ boost::shared_ptr<PostProcessImage> post_process (std::string) const;
+
+#ifdef DEBUG_HASH
+ void hash (std::string) const;
+#endif
+
+ void make_black ();
+
+ PixelFormat pixel_format () const {
+ return _pixel_format;
+ }
+
+private:
+ PixelFormat _pixel_format; ///< FFmpeg's way of describing the pixel format of this Image
+};
+
+/** @class FilterBufferImage
+ * @brief An Image that is held in an AVFilterBufferRef.
+ */
+class FilterBufferImage : public Image
+{
+public:
+ FilterBufferImage (PixelFormat, AVFilterBufferRef *);
+ ~FilterBufferImage ();
+
+ uint8_t ** data () const;
+ int * line_size () const;
+ Size size () const;
+
+private:
+ AVFilterBufferRef* _buffer;
+};
+
+/** @class SimpleImage
+ * @brief An Image for which memory is allocated using a `simple' av_malloc().
+ */
+class SimpleImage : public Image
+{
+public:
+ SimpleImage (PixelFormat, Size);
+ ~SimpleImage ();
+
+ uint8_t ** data () const;
+ int * line_size () const;
+ Size size () const;
+
+ void set_line_size (int, int);
+
+private:
+ Size _size; ///< size in pixels
+ uint8_t** _data; ///< array of pointers to components
+ int* _line_size; ///< array of widths of each line, in bytes
+};
+
+/** @class RGBFrameImage
+ * @brief An RGB image that is held within an AVFrame.
+ */
+class RGBFrameImage : public Image
+{
+public:
+ RGBFrameImage (Size);
+ ~RGBFrameImage ();
+
+ uint8_t ** data () const;
+ int * line_size () const;
+ Size size () const;
+ AVFrame * frame () const {
+ return _frame;
+ }
+
+private:
+ Size _size;
+ AVFrame* _frame;
+ uint8_t* _data;
+};
+
+/** @class PostProcessImage
+ * @brief An image that is the result of an FFmpeg post-processing run.
+ */
+class PostProcessImage : public Image
+{
+public:
+ PostProcessImage (PixelFormat, Size);
+ ~PostProcessImage ();
+
+ uint8_t ** data () const;
+ int * line_size () const;
+ Size size () const;
+
+private:
+ Size _size;
+ uint8_t** _data;
+ int* _line_size;
+};
+
+#endif
diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc
new file mode 100644
index 000000000..7cee01ec5
--- /dev/null
+++ b/src/lib/imagemagick_decoder.cc
@@ -0,0 +1,55 @@
+#include <iostream>
+#include <Magick++/Image.h>
+#include "imagemagick_decoder.h"
+#include "film_state.h"
+#include "image.h"
+
+using namespace std;
+
+ImageMagickDecoder::ImageMagickDecoder (
+ boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Job* j, Log* l, bool minimal, bool ignore_length)
+ : Decoder (s, o, j, l, minimal, ignore_length)
+ , _done (false)
+{
+ _magick_image = new Magick::Image (_fs->content_path ());
+}
+
+Size
+ImageMagickDecoder::native_size () const
+{
+ return Size (_magick_image->columns(), _magick_image->rows());
+}
+
+bool
+ImageMagickDecoder::do_pass ()
+{
+ if (_done) {
+ return true;
+ }
+
+ Size size = native_size ();
+ RGBFrameImage image (size);
+
+ uint8_t* p = image.data()[0];
+ for (int y = 0; y < size.height; ++y) {
+ for (int x = 0; x < size.width; ++x) {
+ Magick::Color c = _magick_image->pixelColor (x, y);
+ *p++ = c.redQuantum() * 255 / MaxRGB;
+ *p++ = c.greenQuantum() * 255 / MaxRGB;
+ *p++ = c.blueQuantum() * 255 / MaxRGB;
+ }
+
+ }
+
+ process_video (image.frame ());
+
+ _done = true;
+ return false;
+}
+
+PixelFormat
+ImageMagickDecoder::pixel_format () const
+{
+ return PIX_FMT_RGB24;
+}
+
diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h
new file mode 100644
index 000000000..707e6cacd
--- /dev/null
+++ b/src/lib/imagemagick_decoder.h
@@ -0,0 +1,63 @@
+#include "decoder.h"
+
+namespace Magick {
+ class Image;
+}
+
+class ImageMagickDecoder : public Decoder
+{
+public:
+ ImageMagickDecoder (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Options>, Job *, Log *, bool, bool);
+
+ int length_in_frames () const {
+ return 1;
+ }
+
+ float frames_per_second () const {
+ return static_frames_per_second ();
+ }
+
+ Size native_size () const;
+
+ int audio_channels () const {
+ return 0;
+ }
+
+ int audio_sample_rate () const {
+ return 0;
+ }
+
+ AVSampleFormat audio_sample_format () const {
+ return AV_SAMPLE_FMT_NONE;
+ }
+
+ static float static_frames_per_second () {
+ return 24;
+ }
+
+protected:
+ bool do_pass ();
+ PixelFormat pixel_format () const;
+
+ int time_base_numerator () const {
+ return 0;
+ }
+
+ int time_base_denominator () const {
+ return 0;
+ }
+
+ int sample_aspect_ratio_numerator () const {
+ /* XXX */
+ return 1;
+ }
+
+ int sample_aspect_ratio_denominator () const {
+ /* XXX */
+ return 1;
+ }
+
+private:
+ Magick::Image* _magick_image;
+ bool _done;
+};
diff --git a/src/lib/j2k_still_encoder.cc b/src/lib/j2k_still_encoder.cc
new file mode 100644
index 000000000..f68bc5890
--- /dev/null
+++ b/src/lib/j2k_still_encoder.cc
@@ -0,0 +1,73 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/j2k_wav_encoder.cc
+ * @brief An encoder which writes JPEG2000 and WAV files.
+ */
+
+#include <sstream>
+#include <stdexcept>
+#include <iomanip>
+#include <iostream>
+#include <boost/filesystem.hpp>
+#include <sndfile.h>
+#include <openjpeg.h>
+#include "j2k_still_encoder.h"
+#include "config.h"
+#include "film_state.h"
+#include "options.h"
+#include "exceptions.h"
+#include "dcp_video_frame.h"
+#include "filter.h"
+#include "log.h"
+#include "imagemagick_decoder.h"
+
+using namespace std;
+using namespace boost;
+
+J2KStillEncoder::J2KStillEncoder (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l)
+ : Encoder (s, o, l)
+{
+
+}
+
+void
+J2KStillEncoder::process_video (shared_ptr<Image> yuv, int frame)
+{
+ pair<string, string> const s = Filter::ffmpeg_strings (_fs->filters);
+ DCPVideoFrame* f = new DCPVideoFrame (
+ yuv, _opt->out_size, _opt->padding, _fs->scaler, 0, _fs->frames_per_second, s.second,
+ Config::instance()->colour_lut_index(), Config::instance()->j2k_bandwidth(),
+ _log
+ );
+
+ if (!boost::filesystem::exists (_opt->frame_out_path (1, false))) {
+ boost::shared_ptr<EncodedData> e = f->encode_locally ();
+ e->write (_opt, 1);
+ }
+
+ string const real = _opt->frame_out_path (1, false);
+ for (int i = 2; i <= (_fs->still_duration * ImageMagickDecoder::static_frames_per_second()); ++i) {
+ if (!boost::filesystem::exists (_opt->frame_out_path (i, false))) {
+ string const link = _opt->frame_out_path (i, false);
+ symlink (real.c_str(), link.c_str());
+ }
+ frame_done ();
+ }
+}
diff --git a/src/lib/j2k_still_encoder.h b/src/lib/j2k_still_encoder.h
new file mode 100644
index 000000000..d4d68724e
--- /dev/null
+++ b/src/lib/j2k_still_encoder.h
@@ -0,0 +1,43 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/j2k_wav_encoder.h
+ * @brief An encoder which writes JPEG2000 and WAV files.
+ */
+
+#include <list>
+#include <vector>
+#include "encoder.h"
+
+class Image;
+class Log;
+
+/** @class J2KStillEncoder
+ * @brief An encoder which writes repeated JPEG2000 files from a single decoded input.
+ */
+class J2KStillEncoder : public Encoder
+{
+public:
+ J2KStillEncoder (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Options>, Log *);
+
+ void process_begin () {}
+ void process_video (boost::shared_ptr<Image>, int);
+ void process_audio (uint8_t *, int) {}
+ void process_end () {}
+};
diff --git a/src/lib/j2k_wav_encoder.cc b/src/lib/j2k_wav_encoder.cc
new file mode 100644
index 000000000..e6a1b285e
--- /dev/null
+++ b/src/lib/j2k_wav_encoder.cc
@@ -0,0 +1,289 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/j2k_wav_encoder.cc
+ * @brief An encoder which writes JPEG2000 and WAV files.
+ */
+
+#include <sstream>
+#include <stdexcept>
+#include <iomanip>
+#include <iostream>
+#include <boost/thread.hpp>
+#include <boost/filesystem.hpp>
+#include <sndfile.h>
+#include <openjpeg.h>
+#include "j2k_wav_encoder.h"
+#include "config.h"
+#include "film_state.h"
+#include "options.h"
+#include "exceptions.h"
+#include "dcp_video_frame.h"
+#include "server.h"
+#include "filter.h"
+#include "log.h"
+
+using namespace std;
+using namespace boost;
+
+J2KWAVEncoder::J2KWAVEncoder (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l)
+ : Encoder (s, o, l)
+ , _deinterleave_buffer_size (8192)
+ , _deinterleave_buffer (0)
+ , _process_end (false)
+{
+ /* Create sound output files with .tmp suffixes; we will rename
+ them if and when we complete.
+ */
+ for (int i = 0; i < _fs->audio_channels; ++i) {
+ SF_INFO sf_info;
+ sf_info.samplerate = _fs->audio_sample_rate;
+ /* We write mono files */
+ sf_info.channels = 1;
+ sf_info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24;
+ SNDFILE* f = sf_open (_opt->multichannel_audio_out_path (i, true).c_str (), SFM_WRITE, &sf_info);
+ if (f == 0) {
+ throw CreateFileError (_opt->multichannel_audio_out_path (i, true));
+ }
+ _sound_files.push_back (f);
+ }
+
+ /* Create buffer for deinterleaving audio */
+ _deinterleave_buffer = new uint8_t[_deinterleave_buffer_size];
+}
+
+J2KWAVEncoder::~J2KWAVEncoder ()
+{
+ terminate_worker_threads ();
+ delete[] _deinterleave_buffer;
+ close_sound_files ();
+}
+
+void
+J2KWAVEncoder::terminate_worker_threads ()
+{
+ boost::mutex::scoped_lock lock (_worker_mutex);
+ _process_end = true;
+ _worker_condition.notify_all ();
+ lock.unlock ();
+
+ for (list<boost::thread *>::iterator i = _worker_threads.begin(); i != _worker_threads.end(); ++i) {
+ (*i)->join ();
+ delete *i;
+ }
+}
+
+void
+J2KWAVEncoder::close_sound_files ()
+{
+ for (vector<SNDFILE*>::iterator i = _sound_files.begin(); i != _sound_files.end(); ++i) {
+ sf_close (*i);
+ }
+
+ _sound_files.clear ();
+}
+
+void
+J2KWAVEncoder::process_video (shared_ptr<Image> yuv, int frame)
+{
+ boost::mutex::scoped_lock lock (_worker_mutex);
+
+ /* Wait until the queue has gone down a bit */
+ while (_queue.size() >= _worker_threads.size() * 2 && !_process_end) {
+ _worker_condition.wait (lock);
+ }
+
+ if (_process_end) {
+ return;
+ }
+
+ /* Only do the processing if we don't already have a file for this frame */
+ if (!boost::filesystem::exists (_opt->frame_out_path (frame, false))) {
+ pair<string, string> const s = Filter::ffmpeg_strings (_fs->filters);
+ _queue.push_back (boost::shared_ptr<DCPVideoFrame> (
+ new DCPVideoFrame (
+ yuv, _opt->out_size, _opt->padding, _fs->scaler, frame, _fs->frames_per_second, s.second,
+ Config::instance()->colour_lut_index (), Config::instance()->j2k_bandwidth (),
+ _log
+ )
+ ));
+
+ _worker_condition.notify_all ();
+ }
+}
+
+void
+J2KWAVEncoder::encoder_thread (Server* server)
+{
+ /* Number of seconds that we currently wait between attempts
+ to connect to the server; not relevant for localhost
+ encodings.
+ */
+ int remote_backoff = 0;
+
+ while (1) {
+ boost::mutex::scoped_lock lock (_worker_mutex);
+ while (_queue.empty () && !_process_end) {
+ _worker_condition.wait (lock);
+ }
+
+ if (_process_end) {
+ return;
+ }
+
+ boost::shared_ptr<DCPVideoFrame> vf = _queue.front ();
+ _queue.pop_front ();
+
+ lock.unlock ();
+
+ shared_ptr<EncodedData> encoded;
+
+ if (server) {
+ try {
+ encoded = vf->encode_remotely (server);
+
+ if (remote_backoff > 0) {
+ stringstream s;
+ s << server->host_name() << " was lost, but now she is found; removing backoff";
+ _log->log (s.str ());
+ }
+
+ /* This job succeeded, so remove any backoff */
+ remote_backoff = 0;
+
+ } catch (std::exception& e) {
+ if (remote_backoff < 60) {
+ /* back off more */
+ remote_backoff += 10;
+ }
+ stringstream s;
+ s << "Remote encode on " << server->host_name() << " failed (" << e.what() << "); thread sleeping for " << remote_backoff << "s.";
+ _log->log (s.str ());
+ }
+
+ } else {
+ try {
+ encoded = vf->encode_locally ();
+ } catch (std::exception& e) {
+ stringstream s;
+ s << "Local encode failed " << e.what() << ".";
+ _log->log (s.str ());
+ }
+ }
+
+ if (encoded) {
+ encoded->write (_opt, vf->frame ());
+ frame_done ();
+ } else {
+ lock.lock ();
+ _queue.push_front (vf);
+ lock.unlock ();
+ }
+
+ if (remote_backoff > 0) {
+ sleep (remote_backoff);
+ }
+
+ lock.lock ();
+ _worker_condition.notify_all ();
+ }
+}
+
+void
+J2KWAVEncoder::process_begin ()
+{
+ for (int i = 0; i < Config::instance()->num_local_encoding_threads (); ++i) {
+ _worker_threads.push_back (new boost::thread (boost::bind (&J2KWAVEncoder::encoder_thread, this, (Server *) 0)));
+ }
+
+ vector<Server*> servers = Config::instance()->servers ();
+
+ for (vector<Server*>::iterator i = servers.begin(); i != servers.end(); ++i) {
+ for (int j = 0; j < (*i)->threads (); ++j) {
+ _worker_threads.push_back (new boost::thread (boost::bind (&J2KWAVEncoder::encoder_thread, this, *i)));
+ }
+ }
+}
+
+void
+J2KWAVEncoder::process_end ()
+{
+ boost::mutex::scoped_lock lock (_worker_mutex);
+
+ /* Keep waking workers until the queue is empty */
+ while (!_queue.empty ()) {
+ _worker_condition.notify_all ();
+ _worker_condition.wait (lock);
+ }
+
+ lock.unlock ();
+
+ terminate_worker_threads ();
+ close_sound_files ();
+
+ /* Rename .wav.tmp files to .wav */
+ for (int i = 0; i < _fs->audio_channels; ++i) {
+ if (boost::filesystem::exists (_opt->multichannel_audio_out_path (i, false))) {
+ boost::filesystem::remove (_opt->multichannel_audio_out_path (i, false));
+ }
+ boost::filesystem::rename (_opt->multichannel_audio_out_path (i, true), _opt->multichannel_audio_out_path (i, false));
+ }
+}
+
+void
+J2KWAVEncoder::process_audio (uint8_t* data, int data_size)
+{
+ /* Size of a sample in bytes */
+ int const sample_size = 2;
+
+ /* XXX: we are assuming that sample_size is right, the _deinterleave_buffer_size is a multiple
+ of the sample size and that data_size is a multiple of _fs->audio_channels * sample_size.
+ */
+
+ /* XXX: this code is very tricksy and it must be possible to make it simpler ... */
+
+ /* Number of bytes left to read this time */
+ int remaining = data_size;
+ /* Our position in the output buffers, in bytes */
+ int position = 0;
+ while (remaining > 0) {
+ /* How many bytes of the deinterleaved data to do this time */
+ int this_time = min (remaining / _fs->audio_channels, _deinterleave_buffer_size);
+ for (int i = 0; i < _fs->audio_channels; ++i) {
+ for (int j = 0; j < this_time; j += sample_size) {
+ for (int k = 0; k < sample_size; ++k) {
+ int const to = j + k;
+ int const from = position + (i * sample_size) + (j * _fs->audio_channels) + k;
+ _deinterleave_buffer[to] = data[from];
+ }
+ }
+
+ switch (_fs->audio_sample_format) {
+ case AV_SAMPLE_FMT_S16:
+ sf_write_short (_sound_files[i], (const short *) _deinterleave_buffer, this_time / sample_size);
+ break;
+ default:
+ throw DecodeError ("unknown audio sample format");
+ }
+ }
+
+ position += this_time;
+ remaining -= this_time * _fs->audio_channels;
+ }
+}
diff --git a/src/lib/j2k_wav_encoder.h b/src/lib/j2k_wav_encoder.h
new file mode 100644
index 000000000..c8485b0bc
--- /dev/null
+++ b/src/lib/j2k_wav_encoder.h
@@ -0,0 +1,65 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/j2k_wav_encoder.h
+ * @brief An encoder which writes JPEG2000 and WAV files.
+ */
+
+#include <list>
+#include <vector>
+#include <boost/thread/condition.hpp>
+#include <boost/thread/mutex.hpp>
+#include <sndfile.h>
+#include "encoder.h"
+
+class Server;
+class DCPVideoFrame;
+class Image;
+class Log;
+
+/** @class J2KWAVEncoder
+ * @brief An encoder which writes JPEG2000 and WAV files.
+ */
+class J2KWAVEncoder : public Encoder
+{
+public:
+ J2KWAVEncoder (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Options>, Log *);
+ ~J2KWAVEncoder ();
+
+ void process_begin ();
+ void process_video (boost::shared_ptr<Image>, int);
+ void process_audio (uint8_t *, int);
+ void process_end ();
+
+private:
+
+ void encoder_thread (Server *);
+ void close_sound_files ();
+ void terminate_worker_threads ();
+
+ std::vector<SNDFILE*> _sound_files;
+ int _deinterleave_buffer_size;
+ uint8_t* _deinterleave_buffer;
+
+ bool _process_end;
+ std::list<boost::shared_ptr<DCPVideoFrame> > _queue;
+ std::list<boost::thread *> _worker_threads;
+ mutable boost::mutex _worker_mutex;
+ boost::condition _worker_condition;
+};
diff --git a/src/lib/job.cc b/src/lib/job.cc
new file mode 100644
index 000000000..399b235d9
--- /dev/null
+++ b/src/lib/job.cc
@@ -0,0 +1,242 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/job.cc
+ * @brief A parent class to represent long-running tasks which are run in their own thread.
+ */
+
+#include <boost/thread.hpp>
+#include "job.h"
+#include "util.h"
+
+using namespace std;
+using namespace boost;
+
+/** @param s FilmState for the film that we are operating on.
+ * @param o Options.
+ * @param l A log that we can write to.
+ */
+Job::Job (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l)
+ : _fs (s)
+ , _opt (o)
+ , _log (l)
+ , _state (NEW)
+ , _start_time (0)
+ , _progress_unknown (false)
+{
+ assert (_log);
+
+ descend (1);
+}
+
+/** Start the job in a separate thread, returning immediately */
+void
+Job::start ()
+{
+ set_state (RUNNING);
+ _start_time = time (0);
+ boost::thread (boost::bind (&Job::run_wrapper, this));
+}
+
+/** A wrapper for the ::run() method to catch exceptions */
+void
+Job::run_wrapper ()
+{
+ try {
+
+ run ();
+
+ } catch (std::exception& e) {
+
+ set_progress (1);
+ set_state (FINISHED_ERROR);
+ set_error (e.what ());
+
+ }
+}
+
+/** @return true if the job is running */
+bool
+Job::running () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _state == RUNNING;
+}
+
+/** @return true if the job has finished (either successfully or unsuccessfully) */
+bool
+Job::finished () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _state == FINISHED_OK || _state == FINISHED_ERROR;
+}
+
+/** @return true if the job has finished successfully */
+bool
+Job::finished_ok () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _state == FINISHED_OK;
+}
+
+/** @return true if the job has finished unsuccessfully */
+bool
+Job::finished_in_error () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _state == FINISHED_ERROR;
+}
+
+/** Set the state of this job.
+ * @param s New state.
+ */
+void
+Job::set_state (State s)
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ _state = s;
+}
+
+/** A hack to work around our lack of cross-thread
+ * signalling; this emits Finished, and listeners
+ * assume that it will be emitted in the GUI thread,
+ * so this method must be called from the GUI thread.
+ */
+void
+Job::emit_finished ()
+{
+ Finished ();
+}
+
+/** @return Time (in seconds) that this job has been running */
+int
+Job::elapsed_time () const
+{
+ if (_start_time == 0) {
+ return 0;
+ }
+
+ return time (0) - _start_time;
+}
+
+/** Set the progress of the current part of the job.
+ * @param p Progress (from 0 to 1)
+ */
+void
+Job::set_progress (float p)
+{
+ boost::mutex::scoped_lock lm (_progress_mutex);
+ _stack.back().normalised = p;
+}
+
+/** @return fractional overall progress, or -1 if not known */
+float
+Job::overall_progress () const
+{
+ boost::mutex::scoped_lock lm (_progress_mutex);
+ if (_progress_unknown) {
+ return -1;
+ }
+
+ float overall = 0;
+ float factor = 1;
+ for (list<Level>::const_iterator i = _stack.begin(); i != _stack.end(); ++i) {
+ factor *= i->allocation;
+ overall += i->normalised * factor;
+ }
+
+ if (overall > 1) {
+ overall = 1;
+ }
+
+ return overall;
+}
+
+/** Ascend up one level in terms of progress reporting; see descend() */
+void
+Job::ascend ()
+{
+ boost::mutex::scoped_lock lm (_progress_mutex);
+
+ assert (!_stack.empty ());
+ float const a = _stack.back().allocation;
+ _stack.pop_back ();
+ _stack.back().normalised += a;
+}
+
+/** Descend down one level in terms of progress reporting; e.g. if
+ * there is a task which is split up into N subtasks, each of which
+ * report their progress from 0 to 100%, call descend() before executing
+ * each subtask, and ascend() afterwards to ensure that overall progress
+ * is reported correctly.
+ *
+ * @param a Fraction (from 0 to 1) of the current task to allocate to the subtask.
+ */
+void
+Job::descend (float a)
+{
+ boost::mutex::scoped_lock lm (_progress_mutex);
+ _stack.push_back (Level (a));
+}
+
+/** @return Any error string that the job has generated */
+string
+Job::error () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _error;
+}
+
+/** Set the current error string.
+ * @param e New error string.
+ */
+void
+Job::set_error (string e)
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ _error = e;
+}
+
+/** Set that this job's progress will always be unknown */
+void
+Job::set_progress_unknown ()
+{
+ boost::mutex::scoped_lock lm (_progress_mutex);
+ _progress_unknown = true;
+}
+
+string
+Job::status () const
+{
+ float const p = overall_progress ();
+ int const t = elapsed_time ();
+
+ stringstream s;
+ if (!finished () && p >= 0 && t > 10) {
+ s << rint (p * 100) << "%; about " << seconds_to_approximate_hms (t / p - t) << " remaining";
+ } else if (!finished () && t <= 10) {
+ s << rint (p * 100) << "%";
+ } else if (finished_ok ()) {
+ s << "OK (ran for " << seconds_to_hms (t) << ")";
+ } else if (finished_in_error ()) {
+ s << "Error (" << error() << ")";
+ }
+
+ return s.str ();
+}
diff --git a/src/lib/job.h b/src/lib/job.h
new file mode 100644
index 000000000..2a77f78f7
--- /dev/null
+++ b/src/lib/job.h
@@ -0,0 +1,115 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/job.h
+ * @brief A parent class to represent long-running tasks which are run in their own thread.
+ */
+
+#ifndef DVDOMATIC_JOB_H
+#define DVDOMATIC_JOB_H
+
+#include <string>
+#include <boost/thread/mutex.hpp>
+#include <sigc++/sigc++.h>
+
+class Log;
+class FilmState;
+class Options;
+
+/** @class Job
+ * @brief A parent class to represent long-running tasks which are run in their own thread.
+ */
+class Job
+{
+public:
+ Job (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l);
+
+ /** @return user-readable name of this job */
+ virtual std::string name () const = 0;
+ /** Run this job in the current thread. */
+ virtual void run () = 0;
+
+ void start ();
+
+ bool running () const;
+ bool finished () const;
+ bool finished_ok () const;
+ bool finished_in_error () const;
+
+ std::string error () const;
+
+ int elapsed_time () const;
+ virtual std::string status () const;
+
+ void set_progress_unknown ();
+ void set_progress (float);
+ void ascend ();
+ void descend (float);
+ float overall_progress () const;
+
+ void emit_finished ();
+
+ /** Emitted from the GUI thread */
+ sigc::signal0<void> Finished;
+
+protected:
+
+ enum State {
+ NEW, ///< the job hasn't been started yet
+ RUNNING, ///< the job is running
+ FINISHED_OK, ///< the job has finished successfully
+ FINISHED_ERROR ///< the job has finished in error
+ };
+
+ void set_state (State);
+ void set_error (std::string e);
+
+ boost::shared_ptr<const FilmState> _fs;
+ boost::shared_ptr<const Options> _opt;
+
+ /** A log that this job can write to */
+ Log* _log;
+
+private:
+
+ void run_wrapper ();
+
+ /** mutex for _state and _error */
+ mutable boost::mutex _state_mutex;
+ State _state;
+ std::string _error;
+
+ time_t _start_time;
+
+ mutable boost::mutex _progress_mutex;
+
+ struct Level {
+ Level (float a) : allocation (a), normalised (0) {}
+
+ float allocation;
+ float normalised;
+ };
+
+ std::list<Level> _stack;
+
+ /** true if this job's progress will always be unknown */
+ bool _progress_unknown;
+};
+
+#endif
diff --git a/src/lib/job_manager.cc b/src/lib/job_manager.cc
new file mode 100644
index 000000000..dd7c62c31
--- /dev/null
+++ b/src/lib/job_manager.cc
@@ -0,0 +1,101 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/job_manager.cc
+ * @brief A simple scheduler for jobs.
+ */
+
+#include <iostream>
+#include <boost/thread.hpp>
+#include "job_manager.h"
+#include "job.h"
+
+using namespace std;
+using namespace boost;
+
+JobManager* JobManager::_instance = 0;
+
+JobManager::JobManager ()
+{
+ boost::thread (boost::bind (&JobManager::scheduler, this));
+}
+
+void
+JobManager::add (shared_ptr<Job> j)
+{
+ boost::mutex::scoped_lock lm (_mutex);
+
+ _jobs.push_back (j);
+}
+
+list<shared_ptr<Job> >
+JobManager::get () const
+{
+ boost::mutex::scoped_lock lm (_mutex);
+
+ return _jobs;
+}
+
+bool
+JobManager::work_to_do () const
+{
+ boost::mutex::scoped_lock lm (_mutex);
+ list<shared_ptr<Job> >::const_iterator i = _jobs.begin();
+ while (i != _jobs.end() && (*i)->finished()) {
+ ++i;
+ }
+
+ return i != _jobs.end ();
+}
+
+void
+JobManager::scheduler ()
+{
+ while (1) {
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ int running = 0;
+ shared_ptr<Job> first_new;
+ for (list<shared_ptr<Job> >::iterator i = _jobs.begin(); i != _jobs.end(); ++i) {
+ if ((*i)->running ()) {
+ ++running;
+ } else if (!(*i)->finished () && first_new == 0) {
+ first_new = *i;
+ }
+
+ if (running == 0 && first_new) {
+ first_new->start ();
+ break;
+ }
+ }
+ }
+
+ sleep (1);
+ }
+}
+
+JobManager *
+JobManager::instance ()
+{
+ if (_instance == 0) {
+ _instance = new JobManager ();
+ }
+
+ return _instance;
+}
diff --git a/src/lib/job_manager.h b/src/lib/job_manager.h
new file mode 100644
index 000000000..f2f5e0057
--- /dev/null
+++ b/src/lib/job_manager.h
@@ -0,0 +1,54 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/job_manager.h
+ * @brief A simple scheduler for jobs.
+ */
+
+#include <list>
+#include <boost/thread/mutex.hpp>
+
+class Job;
+
+/** @class JobManager
+ * @brief A simple scheduler for jobs.
+ *
+ * JobManager simply keeps a list of pending jobs, and assumes that all the jobs
+ * are sufficiently CPU intensive that there is no point running them in parallel;
+ * so jobs are just run one after the other.
+ */
+class JobManager
+{
+public:
+
+ void add (boost::shared_ptr<Job>);
+ std::list<boost::shared_ptr<Job> > get () const;
+ bool work_to_do () const;
+
+ static JobManager* instance ();
+
+private:
+ JobManager ();
+ void scheduler ();
+
+ mutable boost::mutex _mutex;
+ std::list<boost::shared_ptr<Job> > _jobs;
+
+ static JobManager* _instance;
+};
diff --git a/src/lib/log.cc b/src/lib/log.cc
new file mode 100644
index 000000000..accf3694d
--- /dev/null
+++ b/src/lib/log.cc
@@ -0,0 +1,63 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/log.cc
+ * @brief A very simple logging class.
+ */
+
+#include <fstream>
+#include <time.h>
+#include "log.h"
+
+using namespace std;
+
+/** @param f Filename to write log to */
+Log::Log (string f)
+ : _file (f)
+ , _level (VERBOSE)
+{
+
+}
+
+/** @param n String to log */
+void
+Log::log (string m, Level l)
+{
+ boost::mutex::scoped_lock lm (_mutex);
+
+ if (l > _level) {
+ return;
+ }
+
+ ofstream f (_file.c_str(), fstream::app);
+
+ time_t t;
+ time (&t);
+ string a = ctime (&t);
+
+ f << a.substr (0, a.length() - 1) << ": " << m << "\n";
+}
+
+void
+Log::set_level (Level l)
+{
+ boost::mutex::scoped_lock lm (_mutex);
+ _level = l;
+}
+
diff --git a/src/lib/log.h b/src/lib/log.h
new file mode 100644
index 000000000..d4de8ebde
--- /dev/null
+++ b/src/lib/log.h
@@ -0,0 +1,55 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/log.h
+ * @brief A very simple logging class.
+ */
+
+#include <string>
+#include <boost/thread/mutex.hpp>
+
+/** @class Log
+ * @brief A very simple logging class.
+ *
+ * This class simply accepts log messages and writes them to a file.
+ * Its single nod to complexity is that it has a mutex to prevent
+ * multi-thread logging from clashing.
+ */
+class Log
+{
+public:
+ Log (std::string f);
+
+ enum Level {
+ STANDARD = 0,
+ VERBOSE = 1
+ };
+
+ void log (std::string m, Level l = STANDARD);
+
+ void set_level (Level l);
+
+private:
+ /** mutex to prevent simultaneous writes to the file */
+ boost::mutex _mutex;
+ /** filename to write to */
+ std::string _file;
+ /** level above which to ignore log messages */
+ Level _level;
+};
diff --git a/src/lib/lut.h b/src/lib/lut.h
new file mode 100644
index 000000000..26888a24a
--- /dev/null
+++ b/src/lib/lut.h
@@ -0,0 +1,51 @@
+/*
+ Taken from OpenDCP: Builds Digital Cinema Packages
+ Copyright (c) 2010-2011 Terrence Meiczinger, All Rights Reserved
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/** @file src/lut.h
+ * @brief Look-up tables for colour conversions (from OpenDCP)
+ */
+
+#define BIT_DEPTH 12
+#define BIT_PRECISION 16
+#define COLOR_DEPTH (4095)
+#define DCI_LUT_SIZE ((COLOR_DEPTH + 1) * BIT_PRECISION)
+#define DCI_GAMMA (2.6)
+#define DCI_DEGAMMA (1/DCI_GAMMA)
+#define DCI_COEFFICENT (48.0/52.37)
+
+enum COLOR_PROFILE_ENUM {
+ CP_SRGB = 0,
+ CP_REC709,
+ CP_DC28,
+ CP_MAX
+};
+
+enum LUT_IN_ENUM {
+ LI_SRGB = 0,
+ LI_REC709,
+ LI_MAX
+};
+
+enum LUT_OUT_ENUM {
+ LO_DCI = 0,
+ LO_MAX
+};
+
+extern float color_matrix[3][3][3];
+extern float lut_in[LI_MAX][4095+1];
+extern int lut_out[1][DCI_LUT_SIZE];
diff --git a/src/lib/make_dcp_job.cc b/src/lib/make_dcp_job.cc
new file mode 100644
index 000000000..81deb835d
--- /dev/null
+++ b/src/lib/make_dcp_job.cc
@@ -0,0 +1,94 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/make_dcp_job.cc
+ * @brief A job to create DCPs.
+ */
+
+#include <boost/filesystem.hpp>
+extern "C" {
+#include <libavutil/pixdesc.h>
+}
+#include "make_dcp_job.h"
+#include "film_state.h"
+#include "dcp_content_type.h"
+#include "exceptions.h"
+
+using namespace std;
+using namespace boost;
+
+/** @param s FilmState of the Film we are making the DCP for.
+ * @param o Options.
+ * @param l Log.
+ */
+MakeDCPJob::MakeDCPJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l)
+ : ShellCommandJob (s, o, l)
+{
+
+}
+
+string
+MakeDCPJob::name () const
+{
+ stringstream s;
+ s << "Make DCP for " << _fs->name;
+ return s.str ();
+}
+
+void
+MakeDCPJob::run ()
+{
+ set_progress_unknown ();
+
+ string const dcp_path = _fs->dir (_fs->name);
+
+ /* Check that we have our prerequisites */
+
+ if (!filesystem::exists (filesystem::path (_fs->file ("video.mxf")))) {
+ throw EncodeError ("missing video.mxf");
+ }
+
+ bool const have_audio = filesystem::exists (filesystem::path (_fs->file ("audio.mxf")));
+
+ /* Remove any old DCP */
+ filesystem::remove_all (dcp_path);
+
+ /* Re-make the DCP directory */
+ _fs->dir (_fs->name);
+
+ stringstream c;
+ c << "cd \"" << dcp_path << "\" && "
+ << " opendcp_xml -d -a " << _fs->name
+ << " -t \"" << _fs->name << "\""
+ << " -k " << _fs->dcp_content_type->opendcp_name()
+ << " --reel \"" << _fs->file ("video.mxf") << "\"";
+
+ if (have_audio) {
+ c << " \"" << _fs->file ("audio.mxf") << "\"";
+ }
+
+ command (c.str ());
+
+ filesystem::rename (filesystem::path (_fs->file ("video.mxf")), filesystem::path (dcp_path + "/video.mxf"));
+ if (have_audio) {
+ filesystem::rename (filesystem::path (_fs->file ("audio.mxf")), filesystem::path (dcp_path + "/audio.mxf"));
+ }
+
+ set_progress (1);
+}
diff --git a/src/lib/make_dcp_job.h b/src/lib/make_dcp_job.h
new file mode 100644
index 000000000..4e3193572
--- /dev/null
+++ b/src/lib/make_dcp_job.h
@@ -0,0 +1,37 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/make_dcp_job.h
+ * @brief A job to create DCPs.
+ */
+
+#include "shell_command_job.h"
+
+/** @class MakeDCPJob
+ * @brief A job to create DCPs
+ */
+class MakeDCPJob : public ShellCommandJob
+{
+public:
+ MakeDCPJob (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Options>, Log *);
+
+ std::string name () const;
+ void run ();
+};
+
diff --git a/src/lib/make_mxf_job.cc b/src/lib/make_mxf_job.cc
new file mode 100644
index 000000000..6b0c1a406
--- /dev/null
+++ b/src/lib/make_mxf_job.cc
@@ -0,0 +1,81 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/make_mxf_job.cc
+ * @brief A job that creates a MXF file from some data.
+ */
+
+#include <iostream>
+#include "make_mxf_job.h"
+#include "film.h"
+#include "film_state.h"
+#include "options.h"
+
+using namespace std;
+using namespace boost;
+
+/** @class MakeMXFJob
+ * @brief A job that creates a MXF file from some data.
+ */
+
+MakeMXFJob::MakeMXFJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l, Type t)
+ : ShellCommandJob (s, o, l)
+ , _type (t)
+{
+
+}
+
+string
+MakeMXFJob::name () const
+{
+ stringstream s;
+ switch (_type) {
+ case VIDEO:
+ s << "Make video MXF for " << _fs->name;
+ break;
+ case AUDIO:
+ s << "Make audio MXF for " << _fs->name;
+ break;
+ }
+
+ return s.str ();
+}
+
+void
+MakeMXFJob::run ()
+{
+ set_progress_unknown ();
+
+ /* We round for DCP: not sure if this is right */
+ float fps = rintf (_fs->frames_per_second);
+
+ stringstream c;
+ c << "opendcp_mxf -r " << fps << " -i ";
+ switch (_type) {
+ case VIDEO:
+ c << "\"" << _opt->frame_out_path () << "\" -o \"" << _fs->file ("video.mxf") << "\"";
+ break;
+ case AUDIO:
+ c << "\"" << _opt->multichannel_audio_out_path () << "\" -o \"" << _fs->file ("audio.mxf") << "\"";
+ break;
+ }
+
+ command (c.str ());
+ set_progress (1);
+}
diff --git a/src/lib/make_mxf_job.h b/src/lib/make_mxf_job.h
new file mode 100644
index 000000000..462381d23
--- /dev/null
+++ b/src/lib/make_mxf_job.h
@@ -0,0 +1,48 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/make_mxf_job.h
+ * @brief A job that creates a MXF file from some data.
+ */
+
+#include "shell_command_job.h"
+
+class FilmState;
+class Options;
+
+/** @class MakeMXFJob
+ * @brief A job that creates a MXF file from some data.
+ */
+class MakeMXFJob : public ShellCommandJob
+{
+public:
+ enum Type {
+ AUDIO,
+ VIDEO
+ };
+
+ MakeMXFJob (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Options>, Log *, Type);
+
+ std::string name () const;
+ void run ();
+
+private:
+ Type _type;
+};
+
diff --git a/src/lib/options.h b/src/lib/options.h
new file mode 100644
index 000000000..b283e330d
--- /dev/null
+++ b/src/lib/options.h
@@ -0,0 +1,109 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/options.h
+ * @brief Options for a transcoding operation.
+ */
+
+#include <string>
+#include <iomanip>
+#include <sstream>
+#include "util.h"
+
+/** @class Options
+ * @brief Options for a transcoding operation.
+ *
+ * These are settings which may be different, in different circumstances, for
+ * the same film; ie they are options for a particular transcode operation.
+ */
+class Options
+{
+public:
+
+ Options (std::string f, std::string e, std::string m)
+ : padding (0)
+ , apply_crop (true)
+ , num_frames (0)
+ , decode_video (true)
+ , decode_video_frequency (0)
+ , decode_audio (true)
+ , _frame_out_path (f)
+ , _frame_out_extension (e)
+ , _multichannel_audio_out_path (m)
+ {}
+
+ /** @return The path to write video frames to */
+ std::string frame_out_path () const {
+ return _frame_out_path;
+ }
+
+ /** @param f Frame index.
+ * @param t true to return a temporary file path, otherwise a permanent one.
+ * @return The path to write this video frame to.
+ */
+ std::string frame_out_path (int f, bool t) const {
+ std::stringstream s;
+ s << _frame_out_path << "/";
+ s.width (8);
+ s << std::setfill('0') << f << _frame_out_extension;
+
+ if (t) {
+ s << ".tmp";
+ }
+
+ return s.str ();
+ }
+
+ /** @return Path to write multichannel audio data to */
+ std::string multichannel_audio_out_path () const {
+ return _multichannel_audio_out_path;
+ }
+
+ /** @param c Audio channel index.
+ * @param t true to return a temporary file path, otherwise a permanent one.
+ * @return The path to write this audio file to.
+ */
+ std::string multichannel_audio_out_path (int c, bool t) const {
+ std::stringstream s;
+ s << _multichannel_audio_out_path << "/" << (c + 1) << ".wav";
+ if (t) {
+ s << ".tmp";
+ }
+
+ return s.str ();
+ }
+
+ Size out_size; ///< size of output images
+ float ratio; ///< ratio of the wanted output image (not considering padding)
+ int padding; ///< number of pixels of padding (in terms of the output size) each side of the image
+ bool apply_crop; ///< true to apply cropping
+ int num_frames; ///< number of video frames to run for, or 0 for all
+ int black_after; ///< first frame for which to output a black frame, rather than the actual video content, or 0 for none
+ bool decode_video; ///< true to decode video, otherwise false
+ int decode_video_frequency; ///< skip frames so that this many are decoded in all (or 0) (for generating thumbnails)
+ bool decode_audio; ///< true to decode audio, otherwise false
+
+private:
+ /** Path of the directory to write video frames to */
+ std::string _frame_out_path;
+ /** Extension to use for video frame files (including the leading .) */
+ std::string _frame_out_extension;
+ /** Path of the directory to write audio files to */
+ std::string _multichannel_audio_out_path;
+};
diff --git a/src/lib/player.cc b/src/lib/player.cc
new file mode 100644
index 000000000..db69c66d1
--- /dev/null
+++ b/src/lib/player.cc
@@ -0,0 +1,227 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <sstream>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <boost/thread.hpp>
+#include <boost/algorithm/string.hpp>
+#include "player.h"
+#include "film_state.h"
+#include "filter.h"
+#include "screen.h"
+#include "exceptions.h"
+
+using namespace std;
+using namespace boost;
+
+Player::Player (shared_ptr<const FilmState> fs, shared_ptr<const Screen> screen, Split split)
+ : _stdout_reader_should_run (true)
+ , _position (0)
+ , _paused (false)
+{
+ assert (fs->format);
+
+ if (pipe (_mplayer_stdin) < 0) {
+ throw PlayError ("could not create pipe");
+ }
+
+ if (pipe (_mplayer_stdout) < 0) {
+ throw PlayError ("could not create pipe");
+ }
+
+ if (pipe (_mplayer_stderr) < 0) {
+ throw PlayError ("could not create pipe");
+ }
+
+ int const p = fork ();
+ if (p < 0) {
+ throw PlayError ("could not fork for mplayer");
+ } else if (p == 0) {
+ close (_mplayer_stdin[1]);
+ dup2 (_mplayer_stdin[0], STDIN_FILENO);
+
+ close (_mplayer_stdout[0]);
+ dup2 (_mplayer_stdout[1], STDOUT_FILENO);
+
+ close (_mplayer_stderr[0]);
+ dup2 (_mplayer_stderr[1], STDERR_FILENO);
+
+ char* p[] = { strdup ("TERM=xterm"), strdup ("DISPLAY=:0"), 0 };
+ environ = p;
+
+ stringstream s;
+ s << "/usr/local/bin/mplayer";
+
+ s << " -vo x11 -noaspect -noautosub -nosub -vo x11 -noborder -slave -quiet -input nodefault-bindings:conf=/dev/null";
+ s << " -sws " << fs->scaler->mplayer_id ();
+
+ stringstream vf;
+
+ Position position = screen->position (fs->format);
+ Size screen_size = screen->size (fs->format);
+ Size const cropped_size = fs->cropped_size (fs->size);
+ switch (split) {
+ case SPLIT_NONE:
+ vf << crop_string (Position (fs->left_crop, fs->top_crop), cropped_size);
+ s << " -geometry " << position.x << ":" << position.y;
+ break;
+ case SPLIT_LEFT:
+ {
+ Size split_size = cropped_size;
+ split_size.width /= 2;
+ vf << crop_string (Position (fs->left_crop, fs->top_crop), split_size);
+ screen_size.width /= 2;
+ s << " -geometry " << position.x << ":" << position.y;
+ break;
+ }
+ case SPLIT_RIGHT:
+ {
+ Size split_size = cropped_size;
+ split_size.width /= 2;
+ vf << crop_string (Position (fs->left_crop + split_size.width, fs->top_crop), split_size);
+ screen_size.width /= 2;
+ s << " -geometry " << (position.x + screen_size.width) << ":" << position.y;
+ break;
+ }
+ }
+
+ vf << ",scale=" << screen_size.width << ":" << screen_size.height;
+
+ pair<string, string> filters = Filter::ffmpeg_strings (fs->filters);
+
+ if (!filters.first.empty()) {
+ vf << "," << filters.first;
+ }
+
+ if (!filters.second.empty ()) {
+ vf << ",pp=" << filters.second;
+ }
+
+ s << " -vf " << vf.str();
+ s << " \"" << fs->content_path() << "\" ";
+
+ string cmd (s.str ());
+
+ vector<string> b = split_at_spaces_considering_quotes (cmd);
+
+ char** cl = new char*[b.size() + 1];
+ for (vector<string>::size_type i = 0; i < b.size(); ++i) {
+ cl[i] = strdup (b[i].c_str ());
+ }
+ cl[b.size()] = 0;
+
+ execv (cl[0], cl);
+
+ stringstream e;
+ e << "exec of mplayer failed " << strerror (errno);
+ throw PlayError (e.str ());
+
+ } else {
+ _mplayer_pid = p;
+ command ("pause");
+
+ _stdout_reader = new boost::thread (boost::bind (&Player::stdout_reader, this));
+ }
+}
+
+Player::~Player ()
+{
+ _stdout_reader_should_run = false;
+ _stdout_reader->join ();
+ delete _stdout_reader;
+
+ close (_mplayer_stdin[0]);
+ close (_mplayer_stdout[1]);
+ kill (_mplayer_pid, SIGTERM);
+}
+
+void
+Player::command (string c)
+{
+ char buf[64];
+ snprintf (buf, sizeof (buf), "%s\n", c.c_str ());
+ write (_mplayer_stdin[1], buf, strlen (buf));
+}
+
+void
+Player::stdout_reader ()
+{
+ while (_stdout_reader_should_run) {
+ char buf[1024];
+ int r = read (_mplayer_stdout[0], buf, sizeof (buf));
+ if (r > 0) {
+ stringstream s (buf);
+ while (s.good ()) {
+ string line;
+ getline (s, line);
+
+ vector<string> b;
+ split (b, line, is_any_of ("="));
+ if (b.size() < 2) {
+ continue;
+ }
+
+ if (b[0] == "ANS_time_pos") {
+ set_position (atof (b[1].c_str ()));
+ } else if (b[0] == "ANS_pause") {
+ set_paused (b[1] == "yes");
+ }
+ }
+ }
+
+ usleep (5e5);
+
+ snprintf (buf, sizeof (buf), "pausing_keep_force get_property time_pos\npausing_keep_force get_property pause\n");
+ write (_mplayer_stdin[1], buf, strlen (buf));
+ }
+}
+
+void
+Player::set_position (float p)
+{
+ /* XXX: could be an atomic */
+ boost::mutex::scoped_lock lm (_state_mutex);
+ _position = p;
+}
+
+void
+Player::set_paused (bool p)
+{
+ /* XXX: could be an atomic */
+ boost::mutex::scoped_lock lm (_state_mutex);
+ _paused = p;
+}
+
+float
+Player::position () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _position;
+}
+
+bool
+Player::paused () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _paused;
+}
diff --git a/src/lib/player.h b/src/lib/player.h
new file mode 100644
index 000000000..fc08deb9f
--- /dev/null
+++ b/src/lib/player.h
@@ -0,0 +1,70 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_PLAYER_H
+#define DVDOMATIC_PLAYER_H
+
+#include <boost/shared_ptr.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/mutex.hpp>
+
+class FilmState;
+class Screen;
+
+class Player
+{
+public:
+ enum Split {
+ SPLIT_NONE,
+ SPLIT_LEFT,
+ SPLIT_RIGHT
+ };
+
+ Player (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Screen>, Split);
+ ~Player ();
+
+ void command (std::string);
+
+ float position () const;
+ bool paused () const;
+
+ pid_t mplayer_pid () const {
+ return _mplayer_pid;
+ }
+
+private:
+ void stdout_reader ();
+ void set_position (float);
+ void set_paused (bool);
+
+ int _mplayer_stdin[2];
+ int _mplayer_stdout[2];
+ int _mplayer_stderr[2];
+ pid_t _mplayer_pid;
+
+ boost::thread* _stdout_reader;
+ /* XXX: should probably be atomically accessed */
+ bool _stdout_reader_should_run;
+
+ mutable boost::mutex _state_mutex;
+ float _position;
+ bool _paused;
+};
+
+#endif
diff --git a/src/lib/player_manager.cc b/src/lib/player_manager.cc
new file mode 100644
index 000000000..74fd2383b
--- /dev/null
+++ b/src/lib/player_manager.cc
@@ -0,0 +1,137 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "player_manager.h"
+#include "player.h"
+#include "film_state.h"
+#include "screen.h"
+
+using namespace std;
+using namespace boost;
+
+PlayerManager* PlayerManager::_instance = 0;
+
+PlayerManager::PlayerManager ()
+{
+
+}
+
+PlayerManager *
+PlayerManager::instance ()
+{
+ if (_instance == 0) {
+ _instance = new PlayerManager ();
+ }
+
+ return _instance;
+}
+
+void
+PlayerManager::setup (shared_ptr<const FilmState> fs, shared_ptr<const Screen> sc)
+{
+ boost::mutex::scoped_lock lm (_players_mutex);
+
+ _players.clear ();
+ _players.push_back (shared_ptr<Player> (new Player (fs, sc, Player::SPLIT_NONE)));
+}
+
+void
+PlayerManager::setup (shared_ptr<const FilmState> fs_a, shared_ptr<const FilmState> fs_b, shared_ptr<const Screen> sc)
+{
+ boost::mutex::scoped_lock lm (_players_mutex);
+
+ _players.clear ();
+
+ _players.push_back (shared_ptr<Player> (new Player (fs_a, sc, Player::SPLIT_LEFT)));
+ _players.push_back (shared_ptr<Player> (new Player (fs_b, sc, Player::SPLIT_RIGHT)));
+}
+
+void
+PlayerManager::pause_or_unpause ()
+{
+ boost::mutex::scoped_lock lm (_players_mutex);
+
+ for (list<shared_ptr<Player> >::iterator i = _players.begin(); i != _players.end(); ++i) {
+ (*i)->command ("pause");
+ }
+}
+
+void
+PlayerManager::set_position (float p)
+{
+ boost::mutex::scoped_lock lm (_players_mutex);
+
+ stringstream s;
+ s << "pausing_keep_force seek " << p << " 2";
+ for (list<shared_ptr<Player> >::iterator i = _players.begin(); i != _players.end(); ++i) {
+ (*i)->command (s.str ());
+ }
+}
+
+float
+PlayerManager::position () const
+{
+ boost::mutex::scoped_lock lm (_players_mutex);
+
+ if (_players.empty ()) {
+ return 0;
+ }
+
+ return _players.front()->position ();
+}
+
+void
+PlayerManager::child_exited (pid_t pid)
+{
+ boost::mutex::scoped_lock lm (_players_mutex);
+
+ list<shared_ptr<Player> >::iterator i = _players.begin();
+ while (i != _players.end() && (*i)->mplayer_pid() != pid) {
+ ++i;
+ }
+
+ if (i == _players.end()) {
+ return;
+ }
+
+ _players.erase (i);
+}
+
+PlayerManager::State
+PlayerManager::state () const
+{
+ boost::mutex::scoped_lock lm (_players_mutex);
+
+ if (_players.empty ()) {
+ return QUIESCENT;
+ }
+
+ if (_players.front()->paused ()) {
+ return PAUSED;
+ }
+
+ return PLAYING;
+}
+
+void
+PlayerManager::stop ()
+{
+ boost::mutex::scoped_lock lm (_players_mutex);
+ _players.clear ();
+}
diff --git a/src/lib/player_manager.h b/src/lib/player_manager.h
new file mode 100644
index 000000000..70a31b229
--- /dev/null
+++ b/src/lib/player_manager.h
@@ -0,0 +1,59 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <list>
+#include <boost/shared_ptr.hpp>
+#include "player.h"
+
+class Player;
+class FilmState;
+class Screen;
+
+class PlayerManager
+{
+public:
+
+ void setup (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Screen>);
+ void setup (boost::shared_ptr<const FilmState>, boost::shared_ptr<const FilmState>, boost::shared_ptr<const Screen>);
+ void pause_or_unpause ();
+ void stop ();
+
+ float position () const;
+ void set_position (float);
+
+ enum State {
+ QUIESCENT,
+ PLAYING,
+ PAUSED
+ };
+
+ State state () const;
+
+ void child_exited (pid_t);
+
+ static PlayerManager* instance ();
+
+private:
+ PlayerManager ();
+
+ mutable boost::mutex _players_mutex;
+ std::list<boost::shared_ptr<Player> > _players;
+
+ static PlayerManager* _instance;
+};
diff --git a/src/lib/scaler.cc b/src/lib/scaler.cc
new file mode 100644
index 000000000..1e63d66b3
--- /dev/null
+++ b/src/lib/scaler.cc
@@ -0,0 +1,117 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/scaler.cc
+ * @brief A class to describe one of FFmpeg's software scalers.
+ */
+
+#include <iostream>
+#include <cassert>
+extern "C" {
+#include <libswscale/swscale.h>
+}
+#include "scaler.h"
+
+using namespace std;
+
+vector<Scaler const *> Scaler::_scalers;
+
+/** @param f FFmpeg id.
+ * @param m mplayer command line id.
+ * @param i Our id.
+ * @param n User-visible name.
+ */
+Scaler::Scaler (int f, int m, string i, string n)
+ : _ffmpeg_id (f)
+ , _mplayer_id (m)
+ , _id (i)
+ , _name (n)
+{
+
+}
+
+/** @return All available scalers */
+vector<Scaler const *>
+Scaler::all ()
+{
+ return _scalers;
+}
+
+/** Set up the static _scalers vector; must be called before from_*
+ * methods are used.
+ */
+void
+Scaler::setup_scalers ()
+{
+ _scalers.push_back (new Scaler (SWS_BICUBIC, 2, "bicubic", "Bicubic"));
+ _scalers.push_back (new Scaler (SWS_X, 3, "x", "X"));
+ _scalers.push_back (new Scaler (SWS_AREA, 5, "area", "Area"));
+ _scalers.push_back (new Scaler (SWS_GAUSS, 7, "gauss", "Gaussian"));
+ _scalers.push_back (new Scaler (SWS_LANCZOS, 9, "lanczos", "Lanczos"));
+ _scalers.push_back (new Scaler (SWS_SINC, 8, "sinc", "Sinc"));
+ _scalers.push_back (new Scaler (SWS_SPLINE, 10, "spline", "Spline"));
+ _scalers.push_back (new Scaler (SWS_BILINEAR, 1, "bilinear", "Bilinear"));
+ _scalers.push_back (new Scaler (SWS_FAST_BILINEAR, 0, "fastbilinear", "Fast Bilinear"));
+}
+
+/** @param id One of our ids.
+ * @return Corresponding scaler, or 0.
+ */
+Scaler const *
+Scaler::from_id (string id)
+{
+ vector<Scaler const *>::iterator i = _scalers.begin ();
+ while (i != _scalers.end() && (*i)->id() != id) {
+ ++i;
+ }
+
+ if (i == _scalers.end ()) {
+ return 0;
+ }
+
+ return *i;
+}
+
+/** @param s A scaler from our static list.
+ * @return Index of the scaler with the list, or -1.
+ */
+int
+Scaler::as_index (Scaler const * s)
+{
+ vector<Scaler*>::size_type i = 0;
+ while (i < _scalers.size() && _scalers[i] != s) {
+ ++i;
+ }
+
+ if (i == _scalers.size ()) {
+ return -1;
+ }
+
+ return i;
+}
+
+/** @param i An index returned from as_index().
+ * @return Corresponding scaler.
+ */
+Scaler const *
+Scaler::from_index (int i)
+{
+ assert (i <= int(_scalers.size ()));
+ return _scalers[i];
+}
diff --git a/src/lib/scaler.h b/src/lib/scaler.h
new file mode 100644
index 000000000..8ededfe2a
--- /dev/null
+++ b/src/lib/scaler.h
@@ -0,0 +1,78 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/scaler.h
+ * @brief A class to describe one of FFmpeg's software scalers.
+ */
+
+#ifndef DVDOMATIC_SCALER_H
+#define DVDOMATIC_SCALER_H
+
+#include <string>
+#include <vector>
+
+/** @class Scaler
+ * @brief Class to describe one of FFmpeg's software scalers
+ */
+class Scaler
+{
+public:
+ Scaler (int f, int m, std::string i, std::string n);
+
+ /** @return id used for calls to FFmpeg's pp_postprocess */
+ int ffmpeg_id () const {
+ return _ffmpeg_id;
+ }
+
+ /** @return number to use on an mplayer command line */
+ int mplayer_id () const {
+ return _mplayer_id;
+ }
+
+ /** @return id for our use */
+ std::string id () const {
+ return _id;
+ }
+
+ /** @return user-visible name for this scaler */
+ std::string name () const {
+ return _name;
+ }
+
+ static std::vector<Scaler const *> all ();
+ static void setup_scalers ();
+ static Scaler const * from_id (std::string id);
+ static Scaler const * from_index (int);
+ static int as_index (Scaler const *);
+
+private:
+
+ /** id used for calls to FFmpeg's pp_postprocess */
+ int _ffmpeg_id;
+ int _mplayer_id;
+ /** id for our use */
+ std::string _id;
+ /** user-visible name for this scaler */
+ std::string _name;
+
+ /** sll available scalers */
+ static std::vector<Scaler const *> _scalers;
+};
+
+#endif
diff --git a/src/lib/scp_dcp_job.cc b/src/lib/scp_dcp_job.cc
new file mode 100644
index 000000000..94e403816
--- /dev/null
+++ b/src/lib/scp_dcp_job.cc
@@ -0,0 +1,242 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/scp_dcp_job.cc
+ * @brief A job to copy DCPs to a SCP-enabled server.
+ */
+
+#include <iostream>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <boost/filesystem.hpp>
+#include <libssh/libssh.h>
+#include "scp_dcp_job.h"
+#include "exceptions.h"
+#include "config.h"
+#include "log.h"
+#include "film_state.h"
+
+using namespace std;
+using namespace boost;
+
+class SSHSession
+{
+public:
+ SSHSession ()
+ : _connected (false)
+ {
+ session = ssh_new ();
+ if (session == 0) {
+ throw NetworkError ("Could not start SSH session");
+ }
+ }
+
+ int connect ()
+ {
+ int r = ssh_connect (session);
+ if (r == 0) {
+ _connected = true;
+ }
+ return r;
+ }
+
+ ~SSHSession ()
+ {
+ if (_connected) {
+ ssh_disconnect (session);
+ }
+ ssh_free (session);
+ }
+
+ ssh_session session;
+
+private:
+ bool _connected;
+};
+
+class SSHSCP
+{
+public:
+ SSHSCP (ssh_session s)
+ {
+ scp = ssh_scp_new (s, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, Config::instance()->tms_path().c_str ());
+ if (!scp) {
+ stringstream s;
+ s << "Could not start SCP session (" << ssh_get_error (s) << ")";
+ throw NetworkError (s.str ());
+ }
+ }
+
+ ~SSHSCP ()
+ {
+ ssh_scp_free (scp);
+ }
+
+ ssh_scp scp;
+};
+
+
+SCPDCPJob::SCPDCPJob (shared_ptr<const FilmState> s, Log* l)
+ : Job (s, shared_ptr<const Options> (), l)
+ , _status ("Waiting")
+{
+
+}
+
+string
+SCPDCPJob::name () const
+{
+ stringstream s;
+ s << "Copy DCP to TMS";
+ return s.str ();
+}
+
+void
+SCPDCPJob::run ()
+{
+ try {
+ _log->log ("SCP DCP job starting");
+
+ SSHSession ss;
+
+ set_status ("Connecting");
+
+ ssh_options_set (ss.session, SSH_OPTIONS_HOST, Config::instance()->tms_ip().c_str ());
+ ssh_options_set (ss.session, SSH_OPTIONS_USER, Config::instance()->tms_user().c_str ());
+ int const port = 22;
+ ssh_options_set (ss.session, SSH_OPTIONS_PORT, &port);
+
+ int r = ss.connect ();
+ if (r != SSH_OK) {
+ stringstream s;
+ s << "Could not connect to server " << Config::instance()->tms_ip() << " (" << ssh_get_error (ss.session) << ")";
+ throw NetworkError (s.str ());
+ }
+
+ int const state = ssh_is_server_known (ss.session);
+ if (state == SSH_SERVER_ERROR) {
+ stringstream s;
+ s << "SSH error (" << ssh_get_error (ss.session) << ")";
+ throw NetworkError (s.str ());
+ }
+
+ r = ssh_userauth_password (ss.session, 0, Config::instance()->tms_password().c_str ());
+ if (r != SSH_AUTH_SUCCESS) {
+ stringstream s;
+ s << "Failed to authenticate with server (" << ssh_get_error (ss.session) << ")";
+ throw NetworkError (s.str ());
+ }
+
+ SSHSCP sc (ss.session);
+
+ r = ssh_scp_init (sc.scp);
+ if (r != SSH_OK) {
+ stringstream s;
+ s << "Could not start SCP session (" << ssh_get_error (ss.session) << ")";
+ throw NetworkError (s.str ());
+ }
+
+ r = ssh_scp_push_directory (sc.scp, _fs->name.c_str(), S_IRWXU);
+ if (r != SSH_OK) {
+ stringstream s;
+ s << "Could not create remote directory " << _fs->name << "(" << ssh_get_error (ss.session) << ")";
+ throw NetworkError (s.str ());
+ }
+
+ string const dcp_dir = _fs->dir (_fs->name);
+
+ int bytes_to_transfer = 0;
+ for (filesystem::directory_iterator i = filesystem::directory_iterator (dcp_dir); i != filesystem::directory_iterator(); ++i) {
+ bytes_to_transfer += filesystem::file_size (*i);
+ }
+
+ int buffer_size = 64 * 1024;
+ char buffer[buffer_size];
+ int bytes_transferred = 0;
+
+ for (filesystem::directory_iterator i = filesystem::directory_iterator (dcp_dir); i != filesystem::directory_iterator(); ++i) {
+
+ /* Aah, the sweet smell of progress */
+#if BOOST_FILESYSTEM_VERSION == 3
+ string const leaf = filesystem::path(*i).leaf().generic_string ();
+#else
+ string const leaf = i->leaf ();
+#endif
+
+ set_status ("Copying " + leaf);
+
+ int to_do = filesystem::file_size (*i);
+ ssh_scp_push_file (sc.scp, leaf.c_str(), to_do, S_IRUSR | S_IWUSR);
+
+ int fd = open (filesystem::path (*i).string().c_str(), O_RDONLY);
+ if (fd == 0) {
+ stringstream s;
+ s << "Could not open " << *i << " to send";
+ throw NetworkError (s.str ());
+ }
+
+ while (to_do > 0) {
+ int const t = min (to_do, buffer_size);
+ read (fd, buffer, t);
+ r = ssh_scp_write (sc.scp, buffer, t);
+ if (r != SSH_OK) {
+ stringstream s;
+ s << "Could not write to remote file (" << ssh_get_error (ss.session) << ")";
+ throw NetworkError (s.str ());
+ }
+ to_do -= t;
+ bytes_transferred += t;
+
+ set_progress ((double) bytes_transferred / bytes_to_transfer);
+ }
+ }
+
+ set_progress (1);
+ set_status ("OK");
+ set_state (FINISHED_OK);
+
+ } catch (std::exception& e) {
+
+ stringstream s;
+ set_progress (1);
+ set_state (FINISHED_ERROR);
+ set_status (e.what ());
+
+ s << "SCP DCP job failed (" << e.what() << ")";
+ _log->log (s.str ());
+
+ throw;
+ }
+}
+
+string
+SCPDCPJob::status () const
+{
+ boost::mutex::scoped_lock lm (_status_mutex);
+ return _status;
+}
+
+void
+SCPDCPJob::set_status (string s)
+{
+ boost::mutex::scoped_lock lm (_status_mutex);
+ _status = s;
+}
+
diff --git a/src/lib/scp_dcp_job.h b/src/lib/scp_dcp_job.h
new file mode 100644
index 000000000..1c795be47
--- /dev/null
+++ b/src/lib/scp_dcp_job.h
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/scp_dcp_job.h
+ * @brief A job to copy DCPs to a SCP-enabled server.
+ */
+
+#include "job.h"
+
+class SCPDCPJob : public Job
+{
+public:
+ SCPDCPJob (boost::shared_ptr<const FilmState>, Log *);
+
+ std::string name () const;
+ void run ();
+ std::string status () const;
+
+private:
+ void set_status (std::string);
+
+ mutable boost::mutex _status_mutex;
+ std::string _status;
+};
diff --git a/src/lib/screen.cc b/src/lib/screen.cc
new file mode 100644
index 000000000..25e44f77d
--- /dev/null
+++ b/src/lib/screen.cc
@@ -0,0 +1,104 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/algorithm/string.hpp>
+#include "screen.h"
+#include "format.h"
+#include "exceptions.h"
+
+using namespace std;
+using namespace boost;
+
+Screen::Screen (string n)
+ : _name (n)
+{
+ vector<Format const *> f = Format::all ();
+ for (vector<Format const *>::iterator i = f.begin(); i != f.end(); ++i) {
+ set_geometry (*i, Position (0, 0), Size (2048, 1080));
+ }
+}
+
+void
+Screen::set_geometry (Format const * f, Position p, Size s)
+{
+ _geometries[f] = Geometry (p, s);
+}
+
+Position
+Screen::position (Format const * f) const
+{
+ GeometryMap::const_iterator i = _geometries.find (f);
+ if (i == _geometries.end ()) {
+ throw PlayError ("format not found for screen");
+ }
+
+ return i->second.position;
+}
+
+Size
+Screen::size (Format const * f) const
+{
+ GeometryMap::const_iterator i = _geometries.find (f);
+ if (i == _geometries.end ()) {
+ throw PlayError ("format not found for screen");
+ }
+
+ return i->second.size;
+}
+
+string
+Screen::as_metadata () const
+{
+ stringstream s;
+ s << "\"" << _name << "\"";
+
+ for (GeometryMap::const_iterator i = _geometries.begin(); i != _geometries.end(); ++i) {
+ s << " " << i->first->as_metadata()
+ << " " << i->second.position.x << " " << i->second.position.y
+ << " " << i->second.size.width << " " << i->second.size.height;
+ }
+
+ return s.str ();
+}
+
+shared_ptr<Screen>
+Screen::create_from_metadata (string v)
+{
+ vector<string> b = split_at_spaces_considering_quotes (v);
+
+ if (b.size() < 1) {
+ return shared_ptr<Screen> ();
+ }
+
+ shared_ptr<Screen> s (new Screen (b[0]));
+
+ vector<string>::size_type i = 1;
+ while (b.size() > i) {
+ if (b.size() >= (i + 5)) {
+ s->set_geometry (
+ Format::from_metadata (b[i].c_str()),
+ Position (atoi (b[i+1].c_str()), atoi (b[i+2].c_str())),
+ Size (atoi (b[i+3].c_str()), atoi (b[i+4].c_str()))
+ );
+ }
+ i += 5;
+ }
+
+ return s;
+}
diff --git a/src/lib/screen.h b/src/lib/screen.h
new file mode 100644
index 000000000..663b3c3c4
--- /dev/null
+++ b/src/lib/screen.h
@@ -0,0 +1,68 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <string>
+#include <vector>
+#include <map>
+#include "util.h"
+
+class Format;
+
+class Screen
+{
+public:
+ Screen (std::string);
+
+ void set_geometry (Format const *, Position, Size);
+
+ std::string name () const {
+ return _name;
+ }
+
+ void set_name (std::string n) {
+ _name = n;
+ }
+
+ struct Geometry {
+ Geometry () {}
+
+ Geometry (Position p, Size s)
+ : position (p)
+ , size (s)
+ {}
+
+ Position position;
+ Size size;
+ };
+
+ typedef std::map<Format const *, Geometry> GeometryMap;
+ GeometryMap geometries () const {
+ return _geometries;
+ }
+
+ Position position (Format const *) const;
+ Size size (Format const *) const;
+
+ std::string as_metadata () const;
+ static boost::shared_ptr<Screen> create_from_metadata (std::string);
+
+private:
+ std::string _name;
+ GeometryMap _geometries;
+};
diff --git a/src/lib/server.cc b/src/lib/server.cc
new file mode 100644
index 000000000..8a5b5cfca
--- /dev/null
+++ b/src/lib/server.cc
@@ -0,0 +1,58 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/server.cc
+ * @brief Class to describe a server to which we can send
+ * encoding work.
+ */
+
+#include <string>
+#include <vector>
+#include <sstream>
+#include <boost/algorithm/string.hpp>
+#include "server.h"
+
+using namespace std;
+using namespace boost;
+
+/** Create a server from a string of metadata returned from as_metadata().
+ * @param v Metadata.
+ * @return Server, or 0.
+ */
+Server *
+Server::create_from_metadata (string v)
+{
+ vector<string> b;
+ split (b, v, is_any_of (" "));
+
+ if (b.size() != 2) {
+ return 0;
+ }
+
+ return new Server (b[0], atoi (b[1].c_str ()));
+}
+
+/** @return Description of this server as text */
+string
+Server::as_metadata () const
+{
+ stringstream s;
+ s << _host_name << " " << _threads;
+ return s.str ();
+}
diff --git a/src/lib/server.h b/src/lib/server.h
new file mode 100644
index 000000000..f7a0abb80
--- /dev/null
+++ b/src/lib/server.h
@@ -0,0 +1,60 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/server.h
+ * @brief Class to describe a server to which we can send
+ * encoding work.
+ */
+
+#include <string>
+
+/** @class Server
+ * @brief Class to describe a server to which we can send encoding work.
+ */
+class Server
+{
+public:
+ /** @param h Server host name or IP address in string form.
+ * @param t Number of threads to use on the server.
+ */
+ Server (std::string h, int t)
+ : _host_name (h)
+ , _threads (t)
+ {}
+
+ /** @return server's host name or IP address in string form */
+ std::string host_name () const {
+ return _host_name;
+ }
+
+ /** @return number of threads to use on the server */
+ int threads () const {
+ return _threads;
+ }
+
+ std::string as_metadata () const;
+
+ static Server * create_from_metadata (std::string v);
+
+private:
+ /** server's host name */
+ std::string _host_name;
+ /** number of threads to use on the server */
+ int _threads;
+};
diff --git a/src/lib/shell_command_job.cc b/src/lib/shell_command_job.cc
new file mode 100644
index 000000000..11805bdfe
--- /dev/null
+++ b/src/lib/shell_command_job.cc
@@ -0,0 +1,71 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/shell_command_job.cc
+ * @brief A job which calls a command via a shell.
+ */
+
+#include <stdio.h>
+#include "shell_command_job.h"
+#include "log.h"
+
+using namespace std;
+using namespace boost;
+
+/** @param s Our FilmState.
+ * @param o Options.
+ * @param l Log.
+ */
+ShellCommandJob::ShellCommandJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l)
+ : Job (s, o, l)
+{
+
+}
+
+/** Run a command via a shell.
+ * @param c Command to run.
+ */
+void
+ShellCommandJob::command (string c)
+{
+ _log->log ("Command: " + c, Log::VERBOSE);
+
+ int const r = system (c.c_str());
+ if (r < 0) {
+ set_state (FINISHED_ERROR);
+ return;
+ } else if (WEXITSTATUS (r) != 0) {
+ set_error ("command failed");
+ set_state (FINISHED_ERROR);
+ } else {
+ set_state (FINISHED_OK);
+ }
+}
+
+string
+ShellCommandJob::status () const
+{
+ if (!running () && !finished()) {
+ return "Waiting";
+ } else if (running ()) {
+ return "";
+ }
+
+ return Job::status ();
+}
diff --git a/src/lib/shell_command_job.h b/src/lib/shell_command_job.h
new file mode 100644
index 000000000..e5dd58867
--- /dev/null
+++ b/src/lib/shell_command_job.h
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/shell_command_job.h
+ * @brief A job which calls a command via a shell.
+ */
+
+#ifndef DVDOMATIC_SHELL_COMMAND_JOB_H
+#define DVDOMATIC_SHELL_COMMAND_JOB_H
+
+#include "job.h"
+
+class FilmState;
+class Log;
+
+/** @class ShellCommandJob
+ * @brief A job which calls a command via a shell.
+ */
+class ShellCommandJob : public Job
+{
+public:
+ ShellCommandJob (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l);
+
+ std::string status () const;
+
+protected:
+ void command (std::string c);
+};
+
+#endif
diff --git a/src/lib/thumbs_job.cc b/src/lib/thumbs_job.cc
new file mode 100644
index 000000000..0eb116fd1
--- /dev/null
+++ b/src/lib/thumbs_job.cc
@@ -0,0 +1,68 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/thumbs_job.cc
+ * @brief A job to create thumbnails.
+ */
+
+#include <exception>
+#include "thumbs_job.h"
+#include "film_state.h"
+#include "tiff_encoder.h"
+#include "transcoder.h"
+#include "options.h"
+
+using namespace std;
+using namespace boost;
+
+/** @param s FilmState to use.
+ * @param o Options.
+ * @param l A log that we can write to.
+ */
+ThumbsJob::ThumbsJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l)
+ : Job (s, o, l)
+{
+
+}
+
+string
+ThumbsJob::name () const
+{
+ stringstream s;
+ s << "Update thumbs for " << _fs->name;
+ return s.str ();
+}
+
+void
+ThumbsJob::run ()
+{
+ try {
+ shared_ptr<TIFFEncoder> e (new TIFFEncoder (_fs, _opt, _log));
+ Transcoder w (_fs, _opt, this, _log, e);
+ w.go ();
+ set_progress (1);
+ set_state (FINISHED_OK);
+
+ } catch (std::exception& e) {
+
+ set_progress (1);
+ set_error (e.what ());
+ set_state (FINISHED_ERROR);
+ }
+}
diff --git a/src/lib/thumbs_job.h b/src/lib/thumbs_job.h
new file mode 100644
index 000000000..1dd69a0f9
--- /dev/null
+++ b/src/lib/thumbs_job.h
@@ -0,0 +1,37 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/thumbs_job.h
+ * @brief A job to create thumbnails.
+ */
+
+#include "job.h"
+
+class FilmState;
+
+/** @class ThumbsJob
+ * @brief A job to create thumbnails (single frames of the film spaced out throughout the film).
+ */
+class ThumbsJob : public Job
+{
+public:
+ ThumbsJob (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l);
+ std::string name () const;
+ void run ();
+};
diff --git a/src/lib/tiff_decoder.cc b/src/lib/tiff_decoder.cc
new file mode 100644
index 000000000..6738de49b
--- /dev/null
+++ b/src/lib/tiff_decoder.cc
@@ -0,0 +1,224 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/tiff_decoder.cc
+ * @brief A decoder which reads a numbered set of TIFF files, one per frame.
+ */
+
+#include <vector>
+#include <string>
+#include <iostream>
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/filesystem.hpp>
+#include <tiffio.h>
+extern "C" {
+#include <libavformat/avformat.h>
+}
+#include "util.h"
+#include "tiff_decoder.h"
+#include "film_state.h"
+#include "exceptions.h"
+#include "image.h"
+#include "options.h"
+
+using namespace std;
+using namespace boost;
+
+/** @param fs FilmState of our Film.
+ * @param o Options.
+ * @param j Job that we are associated with, or 0.
+ * @param l Log that we can write to.
+ * @param minimal true to do the bare minimum of work; just run through the content. Useful for acquiring
+ * accurate frame counts as quickly as possible. This generates no video or audio output.
+ * @param ignore_length Ignore the content's claimed length when computing progress.
+ */
+TIFFDecoder::TIFFDecoder (boost::shared_ptr<const FilmState> fs, boost::shared_ptr<const Options> o, Job* j, Log* l, bool minimal, bool ignore_length)
+ : Decoder (fs, o, j, l, minimal, ignore_length)
+{
+ string const dir = _fs->content_path ();
+
+ if (!filesystem::is_directory (dir)) {
+ throw DecodeError ("TIFF content must be in a directory");
+ }
+
+ for (filesystem::directory_iterator i = filesystem::directory_iterator (dir); i != filesystem::directory_iterator(); ++i) {
+ /* Aah, the sweet smell of progress */
+#if BOOST_FILESYSTEM_VERSION == 3
+ string const ext = filesystem::path(*i).extension().string();
+ string const l = filesystem::path(*i).leaf().generic_string();
+#else
+ string const ext = filesystem::path(*i).extension();
+ string const l = i->leaf ();
+#endif
+ if (ext == ".tif" || ext == ".tiff") {
+ _files.push_back (l);
+ }
+ }
+
+ _files.sort ();
+
+ _iter = _files.begin ();
+
+}
+
+int
+TIFFDecoder::length_in_frames () const
+{
+ return _files.size ();
+}
+
+float
+TIFFDecoder::frames_per_second () const
+{
+ /* We don't know */
+ return 0;
+}
+
+Size
+TIFFDecoder::native_size () const
+{
+ if (_files.empty ()) {
+ throw DecodeError ("no TIFF files found");
+ }
+
+ TIFF* t = TIFFOpen (file_path (_files.front()).c_str (), "r");
+ if (t == 0) {
+ throw DecodeError ("could not open TIFF file");
+ }
+
+ uint32_t width;
+ TIFFGetField (t, TIFFTAG_IMAGEWIDTH, &width);
+ uint32_t height;
+ TIFFGetField (t, TIFFTAG_IMAGELENGTH, &height);
+
+ return Size (width, height);
+}
+
+int
+TIFFDecoder::audio_channels () const
+{
+ /* No audio */
+ return 0;
+}
+
+int
+TIFFDecoder::audio_sample_rate () const
+{
+ return 0;
+}
+
+AVSampleFormat
+TIFFDecoder::audio_sample_format () const
+{
+ return AV_SAMPLE_FMT_NONE;
+}
+
+bool
+TIFFDecoder::do_pass ()
+{
+ if (_iter == _files.end ()) {
+ return true;
+ }
+
+ TIFF* t = TIFFOpen (file_path (*_iter).c_str (), "r");
+ if (t == 0) {
+ throw DecodeError ("could not open TIFF file");
+ }
+
+ uint32_t width;
+ TIFFGetField (t, TIFFTAG_IMAGEWIDTH, &width);
+ uint32_t height;
+ TIFFGetField (t, TIFFTAG_IMAGELENGTH, &height);
+
+ int const num_pixels = width * height;
+ uint32_t * raster = (uint32_t *) _TIFFmalloc (num_pixels * sizeof (uint32_t));
+ if (raster == 0) {
+ throw DecodeError ("could not allocate memory to decode TIFF file");
+ }
+
+ if (TIFFReadRGBAImage (t, width, height, raster, 0) < 0) {
+ throw DecodeError ("could not read TIFF data");
+ }
+
+ RGBFrameImage image (Size (width, height));
+
+ uint8_t* p = image.data()[0];
+ for (uint32_t y = 0; y < height; ++y) {
+ for (uint32_t x = 0; x < width; ++x) {
+ uint32_t const i = (height - y - 1) * width + x;
+ *p++ = raster[i] & 0xff;
+ *p++ = (raster[i] & 0xff00) >> 8;
+ *p++ = (raster[i] & 0xff0000) >> 16;
+ }
+ }
+
+ _TIFFfree (raster);
+ TIFFClose (t);
+
+ process_video (image.frame ());
+
+ ++_iter;
+ return false;
+}
+
+/** @param file name within our content directory
+ * @return full path to the file
+ */
+string
+TIFFDecoder::file_path (string f) const
+{
+ stringstream s;
+ s << _fs->content_path() << "/" << f;
+ return _fs->file (s.str ());
+}
+
+PixelFormat
+TIFFDecoder::pixel_format () const
+{
+ return PIX_FMT_RGB24;
+}
+
+int
+TIFFDecoder::time_base_numerator () const
+{
+ return rint (_fs->frames_per_second);;
+}
+
+
+int
+TIFFDecoder::time_base_denominator () const
+{
+ return 1;
+}
+
+int
+TIFFDecoder::sample_aspect_ratio_numerator () const
+{
+ /* XXX */
+ return 1;
+}
+
+int
+TIFFDecoder::sample_aspect_ratio_denominator () const
+{
+ /* XXX */
+ return 1;
+}
+
diff --git a/src/lib/tiff_decoder.h b/src/lib/tiff_decoder.h
new file mode 100644
index 000000000..dbd76f36d
--- /dev/null
+++ b/src/lib/tiff_decoder.h
@@ -0,0 +1,69 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/tiff_decoder.h
+ * @brief A decoder which reads a numbered set of TIFF files, one per frame.
+ */
+
+#ifndef DVDOMATIC_TIFF_DECODER_H
+#define DVDOMATIC_TIFF_DECODER_H
+
+#include <vector>
+#include <string>
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+#include "util.h"
+#include "decoder.h"
+
+class Job;
+class FilmState;
+class Options;
+class Image;
+class Log;
+
+/** @class TIFFDecoder.
+ * @brief A decoder which reads a numbered set of TIFF files, one per frame.
+ */
+class TIFFDecoder : public Decoder
+{
+public:
+ TIFFDecoder (boost::shared_ptr<const FilmState>, boost::shared_ptr<const Options>, Job *, Log *, bool, bool);
+
+ /* Methods to query our input video */
+ int length_in_frames () const;
+ float frames_per_second () const;
+ Size native_size () const;
+ int audio_channels () const;
+ int audio_sample_rate () const;
+ AVSampleFormat audio_sample_format () const;
+
+private:
+ bool do_pass ();
+ PixelFormat pixel_format () const;
+ int time_base_numerator () const;
+ int time_base_denominator () const;
+ int sample_aspect_ratio_numerator () const;
+ int sample_aspect_ratio_denominator () const;
+
+ std::string file_path (std::string) const;
+ std::list<std::string> _files;
+ std::list<std::string>::iterator _iter;
+};
+
+#endif
diff --git a/src/lib/tiff_encoder.cc b/src/lib/tiff_encoder.cc
new file mode 100644
index 000000000..2cf238006
--- /dev/null
+++ b/src/lib/tiff_encoder.cc
@@ -0,0 +1,77 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/tiff_encoder.h
+ * @brief An encoder that writes TIFF files (and does nothing with audio).
+ */
+
+#include <stdexcept>
+#include <vector>
+#include <sstream>
+#include <iomanip>
+#include <iostream>
+#include <boost/filesystem.hpp>
+#include <tiffio.h>
+#include "tiff_encoder.h"
+#include "film.h"
+#include "film_state.h"
+#include "options.h"
+#include "exceptions.h"
+#include "image.h"
+
+using namespace std;
+using namespace boost;
+
+/** @param s FilmState of the film that we are encoding.
+ * @param o Options.
+ * @param l Log.
+ */
+TIFFEncoder::TIFFEncoder (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l)
+ : Encoder (s, o, l)
+{
+
+}
+
+void
+TIFFEncoder::process_video (shared_ptr<Image> image, int frame)
+{
+ shared_ptr<Image> scaled = image->scale_and_convert_to_rgb (_opt->out_size, _opt->padding, _fs->scaler);
+ string tmp_file = _opt->frame_out_path (frame, true);
+ TIFF* output = TIFFOpen (tmp_file.c_str (), "w");
+ if (output == 0) {
+ throw CreateFileError (tmp_file);
+ }
+
+ TIFFSetField (output, TIFFTAG_IMAGEWIDTH, _opt->out_size.width);
+ TIFFSetField (output, TIFFTAG_IMAGELENGTH, _opt->out_size.height);
+ TIFFSetField (output, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
+ TIFFSetField (output, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
+ TIFFSetField (output, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
+ TIFFSetField (output, TIFFTAG_BITSPERSAMPLE, 8);
+ TIFFSetField (output, TIFFTAG_SAMPLESPERPIXEL, 3);
+
+ if (TIFFWriteEncodedStrip (output, 0, scaled->data()[0], _opt->out_size.width * _opt->out_size.height * 3) == 0) {
+ throw WriteFileError (tmp_file, 0);
+ }
+
+ TIFFClose (output);
+
+ boost::filesystem::rename (tmp_file, _opt->frame_out_path (frame, false));
+ frame_done ();
+}
diff --git a/src/lib/tiff_encoder.h b/src/lib/tiff_encoder.h
new file mode 100644
index 000000000..ec8e38011
--- /dev/null
+++ b/src/lib/tiff_encoder.h
@@ -0,0 +1,43 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/tiff_encoder.h
+ * @brief An encoder that writes TIFF files (and does nothing with audio).
+ */
+
+#include <string>
+#include <sndfile.h>
+#include "encoder.h"
+
+class FilmState;
+class Log;
+
+/** @class TIFFEncoder
+ * @brief An encoder that writes TIFF files (and does nothing with audio).
+ */
+class TIFFEncoder : public Encoder
+{
+public:
+ TIFFEncoder (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l);
+
+ void process_begin () {}
+ void process_video (boost::shared_ptr<Image>, int);
+ void process_audio (uint8_t *, int) {}
+ void process_end () {}
+};
diff --git a/src/lib/timer.cc b/src/lib/timer.cc
new file mode 100644
index 000000000..a45e80dcb
--- /dev/null
+++ b/src/lib/timer.cc
@@ -0,0 +1,89 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/timer.cc
+ * @brief Some timing classes for debugging and profiling.
+ */
+
+#include <iostream>
+#include <sys/time.h>
+#include "timer.h"
+#include "util.h"
+
+using namespace std;
+
+/** @param n Name to use when giving output */
+PeriodTimer::PeriodTimer (string n)
+ : _name (n)
+{
+ gettimeofday (&_start, 0);
+}
+
+/** Destroy PeriodTimer and output the time elapsed since its construction */
+PeriodTimer::~PeriodTimer ()
+{
+ struct timeval stop;
+ gettimeofday (&stop, 0);
+ cout << "T: " << _name << ": " << (seconds (stop) - seconds (_start)) << "\n";
+}
+
+/** @param n Name to use when giving output.
+ * @param s Initial state.
+ */
+StateTimer::StateTimer (string n, string s)
+ : _name (n)
+{
+ struct timeval t;
+ gettimeofday (&t, 0);
+ _time = seconds (t);
+ _state = s;
+}
+
+/** @param s New state that the caller is in */
+void
+StateTimer::set_state (string s)
+{
+ double const last = _time;
+ struct timeval t;
+ gettimeofday (&t, 0);
+ _time = seconds (t);
+
+ if (_totals.find (s) == _totals.end ()) {
+ _totals[s] = 0;
+ }
+
+ _totals[_state] += _time - last;
+ _state = s;
+}
+
+/** Destroy StateTimer and generate a summary of the state timings on cout */
+StateTimer::~StateTimer ()
+{
+ if (_state.empty ()) {
+ return;
+ }
+
+
+ set_state ("");
+
+ cout << _name << ":\n";
+ for (map<string, double>::iterator i = _totals.begin(); i != _totals.end(); ++i) {
+ cout << "\t" << i->first << " " << i->second << "\n";
+ }
+}
diff --git a/src/lib/timer.h b/src/lib/timer.h
new file mode 100644
index 000000000..f509a7492
--- /dev/null
+++ b/src/lib/timer.h
@@ -0,0 +1,78 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2000-2007 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/timer.h
+ * @brief Some timing classes for debugging and profiling.
+ */
+
+#ifndef DVDOMATIC_TIMER_H
+#define DVDOMATIC_TIMER_H
+
+#include <string>
+#include <map>
+#include <sys/time.h>
+
+/** @class PeriodTimer
+ * @brief A class to allow timing of a period within the caller.
+ *
+ * On destruction, it will output the time since its construction.
+ */
+class PeriodTimer
+{
+public:
+ PeriodTimer (std::string n);
+ ~PeriodTimer ();
+
+private:
+
+ /** name to use when giving output */
+ std::string _name;
+ /** time that this class was constructed */
+ struct timeval _start;
+};
+
+/** @class StateTimer
+ * @brief A class to allow measurement of the amount of time a program
+ * spends in one of a set of states.
+ *
+ * Once constructed, the caller can call set_state() whenever
+ * its state changes. When StateTimer is destroyed, it will
+ * output (to cout) a summary of the time spent in each state.
+ */
+class StateTimer
+{
+public:
+ StateTimer (std::string n, std::string s);
+ ~StateTimer ();
+
+ void set_state (std::string s);
+
+private:
+ /** name to add to the output */
+ std::string _name;
+ /** current state */
+ std::string _state;
+ /** time that _state was entered */
+ double _time;
+ /** time that has been spent in each state so far */
+ std::map<std::string, double> _totals;
+};
+
+#endif
diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc
new file mode 100644
index 000000000..652a18441
--- /dev/null
+++ b/src/lib/transcode_job.cc
@@ -0,0 +1,100 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/transcode_job.cc
+ * @brief A job which transcodes from one format to another.
+ */
+
+#include <iostream>
+#include <iomanip>
+#include "transcode_job.h"
+#include "j2k_wav_encoder.h"
+#include "film.h"
+#include "format.h"
+#include "transcoder.h"
+#include "film_state.h"
+#include "log.h"
+#include "encoder_factory.h"
+
+using namespace std;
+using namespace boost;
+
+/** @param s FilmState to use.
+ * @param o Options.
+ * @param l A log that we can write to.
+ */
+TranscodeJob::TranscodeJob (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l)
+ : Job (s, o, l)
+{
+
+}
+
+string
+TranscodeJob::name () const
+{
+ stringstream s;
+ s << "Transcode " << _fs->name;
+ return s.str ();
+}
+
+void
+TranscodeJob::run ()
+{
+ try {
+
+ _log->log ("Transcode job starting");
+
+ _encoder = encoder_factory (_fs, _opt, _log);
+ Transcoder w (_fs, _opt, this, _log, _encoder);
+ w.go ();
+ set_progress (1);
+ set_state (FINISHED_OK);
+
+ _log->log ("Transcode job completed successfully");
+
+ } catch (std::exception& e) {
+
+ stringstream s;
+ set_progress (1);
+ set_state (FINISHED_ERROR);
+
+ s << "Transcode job failed (" << e.what() << ")";
+ _log->log (s.str ());
+
+ throw;
+
+ }
+}
+
+string
+TranscodeJob::status () const
+{
+ if (!_encoder) {
+ return "0%";
+ }
+
+ float const fps = _encoder->current_frames_per_second ();
+ if (fps == 0) {
+ return Job::status ();
+ }
+
+ stringstream s;
+ s << Job::status () << "; about " << fixed << setprecision (1) << fps << " frames per second.";
+ return s.str ();
+}
diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h
new file mode 100644
index 000000000..aa640f697
--- /dev/null
+++ b/src/lib/transcode_job.h
@@ -0,0 +1,43 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/transcode_job.h
+ * @brief A job which transcodes from one format to another.
+ */
+
+#include <boost/shared_ptr.hpp>
+#include "job.h"
+
+class Encoder;
+
+/** @class TranscodeJob
+ * @brief A job which transcodes from one format to another.
+ */
+class TranscodeJob : public Job
+{
+public:
+ TranscodeJob (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Log* l);
+
+ std::string name () const;
+ void run ();
+ std::string status () const;
+
+private:
+ boost::shared_ptr<Encoder> _encoder;
+};
diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc
new file mode 100644
index 000000000..3d71b68f5
--- /dev/null
+++ b/src/lib/transcoder.cc
@@ -0,0 +1,72 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/transcoder.cc
+ * @brief A class which takes a FilmState and some Options, then uses those to transcode a Film.
+ *
+ * A decoder is selected according to the content type, and the encoder can be specified
+ * as a parameter to the constructor.
+ */
+
+#include <iostream>
+#include <sigc++/signal.h>
+#include "transcoder.h"
+#include "encoder.h"
+#include "decoder_factory.h"
+
+using namespace std;
+using namespace boost;
+
+/** Construct a transcoder using a Decoder that we create and a supplied Encoder.
+ * @param s FilmState of Film that we are transcoding.
+ * @param o Options.
+ * @param j Job that we are running under, or 0.
+ * @param l Log that we can write to.
+ * @param e Encoder to use.
+ */
+Transcoder::Transcoder (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Job* j, Log* l, shared_ptr<Encoder> e)
+ : _job (j)
+ , _encoder (e)
+ , _decoder (decoder_factory (s, o, j, l))
+{
+ assert (_encoder);
+
+ _decoder->Video.connect (sigc::mem_fun (*e, &Encoder::process_video));
+ _decoder->Audio.connect (sigc::mem_fun (*e, &Encoder::process_audio));
+}
+
+/** Run the decoder, passing its output to the encoder, until the decoder
+ * has no more data to present.
+ */
+void
+Transcoder::go ()
+{
+ _encoder->process_begin ();
+ try {
+ _decoder->go ();
+ } catch (...) {
+ /* process_end() is important as the decoder may have worker
+ threads that need to be cleaned up.
+ */
+ _encoder->process_end ();
+ throw;
+ }
+
+ _encoder->process_end ();
+}
diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h
new file mode 100644
index 000000000..ad6530914
--- /dev/null
+++ b/src/lib/transcoder.h
@@ -0,0 +1,59 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "decoder.h"
+
+/** @file src/transcoder.h
+ * @brief A class which takes a FilmState and some Options, then uses those to transcode a Film.
+ *
+ * A decoder is selected according to the content type, and the encoder can be specified
+ * as a parameter to the constructor.
+ */
+
+class Film;
+class Job;
+class Encoder;
+class FilmState;
+
+/** @class Transcoder
+ * @brief A class which takes a FilmState and some Options, then uses those to transcode a Film.
+ *
+ * A decoder is selected according to the content type, and the encoder can be specified
+ * as a parameter to the constructor.
+ */
+class Transcoder
+{
+public:
+ Transcoder (boost::shared_ptr<const FilmState> s, boost::shared_ptr<const Options> o, Job* j, Log* l, boost::shared_ptr<Encoder> e);
+
+ void go ();
+
+ /** @return Our decoder */
+ boost::shared_ptr<Decoder> decoder () {
+ return _decoder;
+ }
+
+protected:
+ /** A Job that is running this Transcoder, or 0 */
+ Job* _job;
+ /** The encoder that we will use */
+ boost::shared_ptr<Encoder> _encoder;
+ /** The decoder that we will use */
+ boost::shared_ptr<Decoder> _decoder;
+};
diff --git a/src/lib/trim_action.h b/src/lib/trim_action.h
new file mode 100644
index 000000000..405d31bbc
--- /dev/null
+++ b/src/lib/trim_action.h
@@ -0,0 +1,28 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_TRIM_ACTION_H
+#define DVDOMATIC_TRIM_ACTION_H
+
+enum TrimAction {
+ CUT, ///< cut everything out after dcp_frames
+ BLACK_OUT ///< black out after dcp_frames so that the film stays the same length (and audio continues)
+};
+
+#endif
diff --git a/src/lib/util.cc b/src/lib/util.cc
new file mode 100644
index 000000000..b8531e26b
--- /dev/null
+++ b/src/lib/util.cc
@@ -0,0 +1,496 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2000-2007 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/lib/util.cc
+ * @brief Some utility functions and classes.
+ */
+
+#include <sstream>
+#include <iomanip>
+#include <iostream>
+#include <execinfo.h>
+#include <cxxabi.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <boost/algorithm/string.hpp>
+#include <openjpeg.h>
+#include <magick/MagickCore.h>
+#include <magick/version.h>
+#include <libssh/libssh.h>
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+#include <libswresample/swresample.h>
+#include <libavfilter/avfiltergraph.h>
+#include <libavfilter/avcodec.h>
+#include <libavfilter/buffersink.h>
+#include <libpostproc/postprocess.h>
+#include <libavutil/pixfmt.h>
+}
+#include "util.h"
+#include "exceptions.h"
+#include "scaler.h"
+#include "format.h"
+#include "dcp_content_type.h"
+#include "filter.h"
+#include "screen.h"
+#include "film_state.h"
+#include "player_manager.h"
+
+#ifdef DEBUG_HASH
+#include <mhash.h>
+#endif
+
+using namespace std;
+using namespace boost;
+
+/** Convert some number of seconds to a string representation
+ * in hours, minutes and seconds.
+ *
+ * @param s Seconds.
+ * @return String of the form H:M:S (where H is hours, M
+ * is minutes and S is seconds).
+ */
+string
+seconds_to_hms (int s)
+{
+ int m = s / 60;
+ s -= (m * 60);
+ int h = m / 60;
+ m -= (h * 60);
+
+ stringstream hms;
+ hms << h << ":";
+ hms.width (2);
+ hms << setfill ('0') << m << ":";
+ hms.width (2);
+ hms << setfill ('0') << s;
+
+ return hms.str ();
+}
+
+/** @param s Number of seconds.
+ * @return String containing an approximate description of s (e.g. "about 2 hours")
+ */
+string
+seconds_to_approximate_hms (int s)
+{
+ int m = s / 60;
+ s -= (m * 60);
+ int h = m / 60;
+ m -= (h * 60);
+
+ stringstream ap;
+
+ if (h > 0) {
+ if (m > 30) {
+ ap << (h + 1) << " hours";
+ } else {
+ if (h == 1) {
+ ap << "1 hour";
+ } else {
+ ap << h << " hours";
+ }
+ }
+ } else if (m > 0) {
+ if (m == 1) {
+ ap << "1 minute";
+ } else {
+ ap << m << " minutes";
+ }
+ } else {
+ ap << s << " seconds";
+ }
+
+ return ap.str ();
+}
+
+/** @param l Mangled C++ identifier.
+ * @return Demangled version.
+ */
+static string
+demangle (string l)
+{
+ string::size_type const b = l.find_first_of ("(");
+ if (b == string::npos) {
+ return l;
+ }
+
+ string::size_type const p = l.find_last_of ("+");
+ if (p == string::npos) {
+ return l;
+ }
+
+ if ((p - b) <= 1) {
+ return l;
+ }
+
+ string const fn = l.substr (b + 1, p - b - 1);
+
+ int status;
+ try {
+
+ char* realname = abi::__cxa_demangle (fn.c_str(), 0, 0, &status);
+ string d (realname);
+ free (realname);
+ return d;
+
+ } catch (std::exception) {
+
+ }
+
+ return l;
+}
+
+/** Write a stacktrace to an ostream.
+ * @param out Stream to write to.
+ * @param levels Number of levels to go up the call stack.
+ */
+void
+stacktrace (ostream& out, int levels)
+{
+ void *array[200];
+ size_t size;
+ char **strings;
+ size_t i;
+
+ size = backtrace (array, 200);
+ strings = backtrace_symbols (array, size);
+
+ if (strings) {
+ for (i = 0; i < size && (levels == 0 || i < size_t(levels)); i++) {
+ out << " " << demangle (strings[i]) << endl;
+ }
+
+ free (strings);
+ }
+}
+
+/** @param s Sample format.
+ * @return String representation.
+ */
+string
+audio_sample_format_to_string (AVSampleFormat s)
+{
+ /* Our sample format handling is not exactly complete */
+
+ switch (s) {
+ case AV_SAMPLE_FMT_S16:
+ return "S16";
+ default:
+ break;
+ }
+
+ return "Unknown";
+}
+
+/** @param s String representation of a sample format, as returned from audio_sample_format_to_string().
+ * @return Sample format.
+ */
+AVSampleFormat
+audio_sample_format_from_string (string s)
+{
+ if (s == "S16") {
+ return AV_SAMPLE_FMT_S16;
+ }
+
+ return AV_SAMPLE_FMT_NONE;
+}
+
+/** @return Version of OpenDCP that is on the path (and hence that we will use) */
+static string
+opendcp_version ()
+{
+ FILE* f = popen ("opendcp_xml", "r");
+ if (f == 0) {
+ throw EncodeError ("could not run opendcp_xml to check version");
+ }
+
+ string version = "unknown";
+
+ while (!feof (f)) {
+ char* buf = 0;
+ size_t n = 0;
+ ssize_t const r = getline (&buf, &n, f);
+ if (r > 0) {
+ string s (buf);
+ vector<string> b;
+ split (b, s, is_any_of (" "));
+ if (b.size() >= 3 && b[0] == "OpenDCP" && b[1] == "version") {
+ version = b[2];
+ }
+ free (buf);
+ }
+ }
+
+ pclose (f);
+
+ return version;
+}
+
+/** @return Version of vobcopy that is on the path (and hence that we will use) */
+static string
+vobcopy_version ()
+{
+ FILE* f = popen ("vobcopy -V 2>&1", "r");
+ if (f == 0) {
+ throw EncodeError ("could not run vobcopy to check version");
+ }
+
+ string version = "unknown";
+
+ while (!feof (f)) {
+ char* buf = 0;
+ size_t n = 0;
+ ssize_t const r = getline (&buf, &n, f);
+ if (r > 0) {
+ string s (buf);
+ vector<string> b;
+ split (b, s, is_any_of (" "));
+ if (b.size() >= 2 && b[0] == "Vobcopy") {
+ version = b[1];
+ }
+ free (buf);
+ }
+ }
+
+ pclose (f);
+
+ return version;
+}
+
+/** @param v Version as used by FFmpeg.
+ * @return A string representation of v.
+ */
+static string
+ffmpeg_version_to_string (int v)
+{
+ stringstream s;
+ s << ((v & 0xff0000) >> 16) << "." << ((v & 0xff00) >> 8) << "." << (v & 0xff);
+ return s.str ();
+}
+
+/** Return a user-readable string summarising the versions of our dependencies */
+string
+dependency_version_summary ()
+{
+ stringstream s;
+ s << "libopenjpeg " << opj_version () << ", "
+ << "opendcp " << opendcp_version () << ", "
+ << "vobcopy " << vobcopy_version() << ", "
+ << "libswresample " << ffmpeg_version_to_string (swresample_version()) << ", "
+ << "libavcodec " << ffmpeg_version_to_string (avcodec_version()) << ", "
+ << "libavfilter " << ffmpeg_version_to_string (avfilter_version()) << ", "
+ << "libavformat " << ffmpeg_version_to_string (avformat_version()) << ", "
+ << "libavutil " << ffmpeg_version_to_string (avutil_version()) << ", "
+ << "libpostproc " << ffmpeg_version_to_string (postproc_version()) << ", "
+ << "libswscale " << ffmpeg_version_to_string (swscale_version()) << ", "
+ << MagickVersion << ", "
+ << "libssh " << ssh_version (0);
+
+ return s.str ();
+}
+
+/** Write some data to a socket.
+ * @param fd Socket file descriptor.
+ * @param data Data.
+ * @param size Amount to write, in bytes.
+ */
+void
+socket_write (int fd, uint8_t const * data, int size)
+{
+ uint8_t const * p = data;
+ while (size) {
+ int const n = send (fd, p, size, MSG_NOSIGNAL);
+ if (n < 0) {
+ stringstream s;
+ s << "could not write (" << strerror (errno) << ")";
+ throw NetworkError (s.str ());
+ }
+
+ size -= n;
+ p += n;
+ }
+}
+
+double
+seconds (struct timeval t)
+{
+ return t.tv_sec + (double (t.tv_usec) / 1e6);
+}
+
+/** @param fd File descriptor to read from */
+SocketReader::SocketReader (int fd)
+ : _fd (fd)
+ , _buffer_data (0)
+{
+
+}
+
+/** Mark some data as being `consumed', so that it will not be returned
+ * as data again.
+ * @param size Amount of data to consume, in bytes.
+ */
+void
+SocketReader::consume (int size)
+{
+ assert (_buffer_data >= size);
+
+ _buffer_data -= size;
+ if (_buffer_data > 0) {
+ /* Shift still-valid data to the start of the buffer */
+ memmove (_buffer, _buffer + size, _buffer_data);
+ }
+}
+
+/** Read a definite amount of data from our socket, and mark
+ * it as consumed.
+ * @param data Where to put the data.
+ * @param size Number of bytes to read.
+ */
+void
+SocketReader::read_definite_and_consume (uint8_t* data, int size)
+{
+ int const from_buffer = min (_buffer_data, size);
+ if (from_buffer > 0) {
+ /* Get data from our buffer */
+ memcpy (data, _buffer, from_buffer);
+ consume (from_buffer);
+ /* Update our output state */
+ data += from_buffer;
+ size -= from_buffer;
+ }
+
+ /* read() the rest */
+ while (size > 0) {
+ int const n = ::read (_fd, data, size);
+ if (n <= 0) {
+ throw NetworkError ("could not read");
+ }
+
+ data += n;
+ size -= n;
+ }
+}
+
+/** Read as much data as is available, up to some limit.
+ * @param data Where to put the data.
+ * @param size Maximum amount of data to read.
+ */
+void
+SocketReader::read_indefinite (uint8_t* data, int size)
+{
+ assert (size < int (sizeof (_buffer)));
+
+ /* Amount of extra data we need to read () */
+ int to_read = size - _buffer_data;
+ while (to_read > 0) {
+ /* read as much of it as we can (into our buffer) */
+ int const n = ::read (_fd, _buffer + _buffer_data, to_read);
+ if (n <= 0) {
+ throw NetworkError ("could not read");
+ }
+
+ to_read -= n;
+ _buffer_data += n;
+ }
+
+ assert (_buffer_data >= size);
+
+ /* copy data into the output buffer */
+ assert (size >= _buffer_data);
+ memcpy (data, _buffer, size);
+}
+
+void
+sigchld_handler (int, siginfo_t* info, void *)
+{
+ PlayerManager::instance()->child_exited (info->si_pid);
+}
+
+/** Call the required functions to set up DVD-o-matic's static arrays, etc. */
+void
+dvdomatic_setup ()
+{
+ Format::setup_formats ();
+ DCPContentType::setup_dcp_content_types ();
+ Scaler::setup_scalers ();
+ Filter::setup_filters ();
+
+ struct sigaction sa;
+ sa.sa_flags = SA_SIGINFO;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_sigaction = sigchld_handler;
+ sigaction (SIGCHLD, &sa, 0);
+}
+
+string
+crop_string (Position start, Size size)
+{
+ stringstream s;
+ s << "crop=" << size.width << ":" << size.height << ":" << start.x << ":" << start.y;
+ return s.str ();
+}
+
+vector<string>
+split_at_spaces_considering_quotes (string s)
+{
+ vector<string> out;
+ bool in_quotes = false;
+ string c;
+ for (string::size_type i = 0; i < s.length(); ++i) {
+ if (s[i] == ' ' && !in_quotes) {
+ out.push_back (c);
+ c = "";
+ } else if (s[i] == '"') {
+ in_quotes = !in_quotes;
+ } else {
+ c += s[i];
+ }
+ }
+
+ out.push_back (c);
+ return out;
+}
+
+#ifdef DEBUG_HASH
+void
+md5_data (string title, void const * data, int size)
+{
+ MHASH ht = mhash_init (MHASH_MD5);
+ if (ht == MHASH_FAILED) {
+ throw EncodeError ("could not create hash thread");
+ }
+
+ mhash (ht, data, size);
+
+ uint8_t hash[16];
+ mhash_deinit (ht, hash);
+
+ printf ("%s [%d]: ", title.c_str (), size);
+ for (int i = 0; i < int (mhash_get_block_size (MHASH_MD5)); ++i) {
+ printf ("%.2x", hash[i]);
+ }
+ printf ("\n");
+}
+#endif
+
diff --git a/src/lib/util.h b/src/lib/util.h
new file mode 100644
index 000000000..3901e7ec6
--- /dev/null
+++ b/src/lib/util.h
@@ -0,0 +1,121 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2000-2007 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/util.h
+ * @brief Some utility functions and classes.
+ */
+
+#ifndef DVDOMATIC_UTIL_H
+#define DVDOMATIC_UTIL_H
+
+#include <string>
+#include <vector>
+#include <boost/shared_ptr.hpp>
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavfilter/avfilter.h>
+}
+
+class Scaler;
+
+extern std::string seconds_to_hms (int);
+extern std::string seconds_to_approximate_hms (int);
+extern void stacktrace (std::ostream &, int);
+extern std::string audio_sample_format_to_string (AVSampleFormat);
+extern AVSampleFormat audio_sample_format_from_string (std::string);
+extern std::string dependency_version_summary ();
+extern void socket_write (int, uint8_t const *, int);
+extern double seconds (struct timeval);
+extern void dvdomatic_setup ();
+extern std::vector<std::string> split_at_spaces_considering_quotes (std::string);
+
+enum ContentType {
+ STILL,
+ VIDEO
+};
+
+#ifdef DEBUG_HASH
+extern void md5_data (std::string, void const *, int);
+#endif
+
+/** @class SocketReader
+ * @brief A helper class from reading from sockets.
+ */
+class SocketReader
+{
+public:
+ SocketReader (int);
+
+ void read_definite_and_consume (uint8_t *, int);
+ void read_indefinite (uint8_t *, int);
+ void consume (int);
+
+private:
+ /** file descriptor we are reading from */
+ int _fd;
+ /** a buffer for small reads */
+ uint8_t _buffer[256];
+ /** amount of valid data in the buffer */
+ int _buffer_data;
+};
+
+/** @class Size
+ * @brief Representation of the size of something */
+struct Size
+{
+ /** Construct a zero Size */
+ Size ()
+ : width (0)
+ , height (0)
+ {}
+
+ /** @param w Width.
+ * @param h Height.
+ */
+ Size (int w, int h)
+ : width (w)
+ , height (h)
+ {}
+
+ /** width */
+ int width;
+ /** height */
+ int height;
+};
+
+struct Position
+{
+ Position ()
+ : x (0)
+ , y (0)
+ {}
+
+ Position (int x_, int y_)
+ : x (x_)
+ , y (y_)
+ {}
+
+ int x;
+ int y;
+};
+
+extern std::string crop_string (Position, Size);
+
+#endif
diff --git a/src/lib/wscript b/src/lib/wscript
new file mode 100644
index 000000000..ec5a723e4
--- /dev/null
+++ b/src/lib/wscript
@@ -0,0 +1,52 @@
+def build(bld):
+ obj = bld(features = 'cxx cxxshlib')
+ obj.name = 'libdvdomatic'
+ obj.export_includes = ['.']
+ obj.uselib = 'AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE SNDFILE BOOST_FILESYSTEM BOOST_THREAD OPENJPEG POSTPROC TIFF SIGC++ MAGICK SSH'
+ if bld.env.DEBUG_HASH:
+ obj.uselib += ' MHASH'
+ obj.source = """
+ ab_transcode_job.cc
+ ab_transcoder.cc
+ config.cc
+ copy_from_dvd_job.cc
+ dcp_content_type.cc
+ dcp_video_frame.cc
+ decoder.cc
+ decoder_factory.cc
+ delay_line.cc
+ dvd.cc
+ encoder.cc
+ encoder_factory.cc
+ examine_content_job.cc
+ ffmpeg_decoder.cc
+ film.cc
+ film_state.cc
+ filter.cc
+ format.cc
+ image.cc
+ imagemagick_decoder.cc
+ j2k_still_encoder.cc
+ j2k_wav_encoder.cc
+ job.cc
+ job_manager.cc
+ log.cc
+ lut.cc
+ make_dcp_job.cc
+ make_mxf_job.cc
+ player.cc
+ player_manager.cc
+ scaler.cc
+ screen.cc
+ server.cc
+ scp_dcp_job.cc
+ shell_command_job.cc
+ thumbs_job.cc
+ tiff_decoder.cc
+ tiff_encoder.cc
+ timer.cc
+ transcode_job.cc
+ transcoder.cc
+ util.cc
+ """
+ obj.target = 'dvdomatic'
diff --git a/src/tools/alignomatic.cc b/src/tools/alignomatic.cc
new file mode 100644
index 000000000..9cab6c430
--- /dev/null
+++ b/src/tools/alignomatic.cc
@@ -0,0 +1,317 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <gtkmm.h>
+#include "lib/util.h"
+#include "lib/config.h"
+#include "lib/screen.h"
+#include "lib/format.h"
+#include "gtk/gtk_util.h"
+#include "gtk/alignment.h"
+
+using namespace std;
+using namespace boost;
+
+static Alignment* alignment = 0;
+static Gtk::ComboBoxText* format_combo = 0;
+static Format const * format = 0;
+static Gtk::ComboBoxText* screen_combo = 0;
+static shared_ptr<Screen> screen;
+static Gtk::Button* add_screen = 0;
+static Gtk::Entry* screen_name = 0;
+static Gtk::SpinButton* x_position = 0;
+static Gtk::SpinButton* y_position = 0;
+static Gtk::SpinButton* width = 0;
+static Gtk::Button* calculate_width = 0;
+static Gtk::SpinButton* height = 0;
+static Gtk::Button* calculate_height = 0;
+static Gtk::Button* save = 0;
+static bool screen_dirty = false;
+
+enum GeometryPart {
+ GEOMETRY_PART_X,
+ GEOMETRY_PART_Y,
+ GEOMETRY_PART_WIDTH,
+ GEOMETRY_PART_HEIGHT
+};
+
+void
+update_sensitivity ()
+{
+ bool const dims = format && screen;
+
+ x_position->set_sensitive (dims);
+ y_position->set_sensitive (dims);
+ width->set_sensitive (dims);
+ calculate_width->set_sensitive (dims);
+ height->set_sensitive (dims);
+ calculate_height->set_sensitive (dims);
+
+ screen_name->set_sensitive (screen);
+ save->set_sensitive (screen_dirty);
+}
+
+void
+update_alignment ()
+{
+ if (!screen || !format) {
+ return;
+ }
+
+ delete alignment;
+ alignment = new Alignment (screen->position (format), screen->size (format));
+ alignment->set_text_line (0, screen->name ());
+ alignment->set_text_line (1, format->name ());
+}
+
+void
+update_entries ()
+{
+ if (!screen || !format) {
+ return;
+ }
+
+ Position p = screen->position (format);
+ x_position->set_value (p.x);
+ y_position->set_value (p.y);
+ Size s = screen->size (format);
+ width->set_value (s.width);
+ height->set_value (s.height);
+
+ update_sensitivity ();
+}
+
+void
+screen_changed ()
+{
+ if (screen_combo->get_active_row_number() < 0) {
+ return;
+ }
+
+ vector<shared_ptr<Screen> > screens = Config::instance()->screens ();
+
+ if (screens[screen_combo->get_active_row_number()] == screen) {
+ return;
+ }
+
+ screen = screens[screen_combo->get_active_row_number()];
+
+ update_entries ();
+ update_alignment ();
+
+ screen_name->set_text (screen->name ());
+
+ screen_dirty = false;
+ update_sensitivity ();
+}
+
+void
+format_changed ()
+{
+ vector<Format const *> formats = Format::all ();
+
+ if (formats[format_combo->get_active_row_number()] == format) {
+ return;
+ }
+
+ format = formats[format_combo->get_active_row_number()];
+
+ update_entries ();
+ update_alignment ();
+ update_sensitivity ();
+}
+
+void
+geometry_changed (GeometryPart p)
+{
+ if (p == GEOMETRY_PART_X && screen->position(format).x == x_position->get_value_as_int()) {
+ return;
+ }
+
+ if (p == GEOMETRY_PART_Y && screen->position(format).y == y_position->get_value_as_int()) {
+ return;
+ }
+
+ if (p == GEOMETRY_PART_WIDTH && screen->size(format).width == width->get_value_as_int()) {
+ return;
+ }
+
+ if (p == GEOMETRY_PART_HEIGHT && screen->size(format).height == height->get_value_as_int()) {
+ return;
+ }
+
+ screen->set_geometry (
+ format,
+ Position (x_position->get_value_as_int(), y_position->get_value_as_int()),
+ Size (width->get_value_as_int(), height->get_value_as_int())
+ );
+
+ update_alignment ();
+
+ screen_dirty = true;
+ update_sensitivity ();
+}
+
+void
+save_clicked ()
+{
+ Config::instance()->write ();
+ screen_dirty = false;
+ update_sensitivity ();
+}
+
+void
+calculate_width_clicked ()
+{
+ width->set_value (height->get_value_as_int() * format->ratio_as_float ());
+}
+
+void
+calculate_height_clicked ()
+{
+ height->set_value (width->get_value_as_int() / format->ratio_as_float ());
+}
+
+void
+update_screen_combo ()
+{
+ screen_combo->clear ();
+
+ vector<shared_ptr<Screen> > screens = Config::instance()->screens ();
+ for (vector<shared_ptr<Screen> >::iterator i = screens.begin(); i != screens.end(); ++i) {
+ screen_combo->append_text ((*i)->name ());
+ }
+}
+
+void
+screen_name_changed ()
+{
+ screen->set_name (screen_name->get_text ());
+
+ int const r = screen_combo->get_active_row_number ();
+ update_screen_combo ();
+ screen_combo->set_active (r);
+
+ screen_dirty = true;
+ update_sensitivity ();
+}
+
+void
+add_screen_clicked ()
+{
+ shared_ptr<Screen> s (new Screen ("New Screen"));
+ vector<shared_ptr<Screen> > screens = Config::instance()->screens ();
+ screens.push_back (s);
+ Config::instance()->set_screens (screens);
+ update_screen_combo ();
+ screen_combo->set_active (screens.size() - 1);
+}
+
+int
+main (int argc, char* argv[])
+{
+ dvdomatic_setup ();
+
+ Gtk::Main kit (argc, argv);
+
+ Gtk::Dialog dialog ("Align-o-matic");
+
+ screen_combo = Gtk::manage (new Gtk::ComboBoxText);
+ update_screen_combo ();
+ screen_combo->signal_changed().connect (sigc::ptr_fun (&screen_changed));
+
+ add_screen = Gtk::manage (new Gtk::Button ("Add"));
+ add_screen->signal_clicked().connect (sigc::ptr_fun (&add_screen_clicked));
+
+ screen_name = Gtk::manage (new Gtk::Entry ());
+ screen_name->signal_changed().connect (sigc::ptr_fun (&screen_name_changed));
+
+ format_combo = Gtk::manage (new Gtk::ComboBoxText);
+ vector<Format const *> formats = Format::all ();
+ for (vector<Format const *>::iterator i = formats.begin(); i != formats.end(); ++i) {
+ format_combo->append_text ((*i)->name ());
+ }
+
+ format_combo->signal_changed().connect (sigc::ptr_fun (&format_changed));
+
+ save = Gtk::manage (new Gtk::Button ("Save"));
+ save->signal_clicked().connect (sigc::ptr_fun (&save_clicked));
+
+ x_position = Gtk::manage (new Gtk::SpinButton ());
+ x_position->signal_value_changed().connect (sigc::bind (ptr_fun (&geometry_changed), GEOMETRY_PART_X));
+ x_position->set_range (0, 2048);
+ x_position->set_increments (1, 16);
+ y_position = Gtk::manage (new Gtk::SpinButton ());
+ y_position->signal_value_changed().connect (sigc::bind (sigc::ptr_fun (&geometry_changed), GEOMETRY_PART_Y));
+ y_position->set_range (0, 1080);
+ y_position->set_increments (1, 16);
+ width = Gtk::manage (new Gtk::SpinButton ());
+ width->signal_value_changed().connect (sigc::bind (sigc::ptr_fun (&geometry_changed), GEOMETRY_PART_WIDTH));
+ width->set_range (0, 2048);
+ width->set_increments (1, 16);
+ height = Gtk::manage (new Gtk::SpinButton ());
+ height->signal_value_changed().connect (sigc::bind (sigc::ptr_fun (&geometry_changed), GEOMETRY_PART_HEIGHT));
+ height->set_range (0, 1080);
+ height->set_increments (1, 16);
+
+ calculate_width = Gtk::manage (new Gtk::Button ("Calculate"));
+ calculate_width->signal_clicked().connect (sigc::ptr_fun (&calculate_width_clicked));
+ calculate_height = Gtk::manage (new Gtk::Button ("Calculate"));
+ calculate_height->signal_clicked().connect (sigc::ptr_fun (&calculate_height_clicked));
+
+ Gtk::Table table;
+ table.set_row_spacings (12);
+ table.set_col_spacings (12);
+ table.set_border_width (12);
+
+ int n = 0;
+ table.attach (left_aligned_label ("Screen"), 0, 1, n, n + 1);
+ table.attach (*screen_combo, 1, 2, n, n + 1);
+ table.attach (*add_screen, 2, 3, n, n + 1);
+ ++n;
+ table.attach (left_aligned_label ("Screen Name"), 0, 1, n, n + 1);
+ table.attach (*screen_name, 1, 2, n, n + 1);
+ ++n;
+ table.attach (left_aligned_label ("Format"), 0, 1, n, n + 1);
+ table.attach (*format_combo, 1, 2, n, n + 1);
+ ++n;
+ table.attach (left_aligned_label ("x"), 0, 1, n, n + 1);
+ table.attach (*x_position, 1, 2, n, n + 1);
+ ++n;
+ table.attach (left_aligned_label ("y"), 0, 1, n, n + 1);
+ table.attach (*y_position, 1, 2, n, n + 1);
+ ++n;
+ table.attach (left_aligned_label ("Width"), 0, 1, n, n + 1);
+ table.attach (*width, 1, 2, n, n + 1);
+ table.attach (*calculate_width, 2, 3, n, n + 1);
+ ++n;
+ table.attach (left_aligned_label ("Height"), 0, 1, n, n + 1);
+ table.attach (*height, 1, 2, n, n + 1);
+ table.attach (*calculate_height, 2, 3, n, n + 1);
+ ++n;
+
+ dialog.get_vbox()->pack_start (table, false, false);
+ dialog.add_action_widget (*save, 0);
+ update_sensitivity ();
+ dialog.show_all ();
+
+ Gtk::Main::run (dialog);
+
+ return 0;
+}
diff --git a/src/tools/dvdomatic.cc b/src/tools/dvdomatic.cc
new file mode 100644
index 000000000..803eec3c4
--- /dev/null
+++ b/src/tools/dvdomatic.cc
@@ -0,0 +1,328 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <boost/filesystem.hpp>
+#include "gtk/film_viewer.h"
+#include "gtk/film_editor.h"
+#include "gtk/film_player.h"
+#include "gtk/job_manager_view.h"
+#include "gtk/config_dialog.h"
+#include "gtk/gpl.h"
+#include "gtk/job_wrapper.h"
+#include "lib/film.h"
+#include "lib/format.h"
+#include "lib/config.h"
+#include "lib/filter.h"
+#include "lib/util.h"
+#include "lib/scaler.h"
+
+using namespace std;
+using namespace boost;
+
+static Gtk::Window* window = 0;
+static FilmViewer* film_viewer = 0;
+static FilmEditor* film_editor = 0;
+static FilmPlayer* film_player = 0;
+static Film* film = 0;
+
+class FilmChangedDialog : public Gtk::MessageDialog
+{
+public:
+ FilmChangedDialog ()
+ : Gtk::MessageDialog ("", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE)
+ {
+ stringstream s;
+ s << "Save changes to film \"" << film->name() << "\" before closing?";
+ set_message (s.str ());
+ add_button ("Close _without saving", Gtk::RESPONSE_NO);
+ add_button ("_Cancel", Gtk::RESPONSE_CANCEL);
+ add_button ("_Save", Gtk::RESPONSE_YES);
+ }
+};
+
+bool
+maybe_save_then_delete_film ()
+{
+ if (!film) {
+ return false;
+ }
+
+ if (film->dirty ()) {
+ FilmChangedDialog d;
+ switch (d.run ()) {
+ case Gtk::RESPONSE_CANCEL:
+ return true;
+ case Gtk::RESPONSE_YES:
+ film->write_metadata ();
+ break;
+ case Gtk::RESPONSE_NO:
+ return false;
+ }
+ }
+
+ delete film;
+ film = 0;
+ return false;
+}
+
+void
+file_new ()
+{
+ Gtk::FileChooserDialog c (*window, "New Film", Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER);
+ c.add_button ("_Cancel", Gtk::RESPONSE_CANCEL);
+ c.add_button ("C_reate", Gtk::RESPONSE_ACCEPT);
+
+ int const r = c.run ();
+ if (r == Gtk::RESPONSE_ACCEPT) {
+ if (maybe_save_then_delete_film ()) {
+ return;
+ }
+ film = new Film (c.get_filename ());
+#if BOOST_FILESYSTEM_VERSION == 3
+ film->set_name (filesystem::path (c.get_filename().c_str()).filename().generic_string());
+#else
+ film->set_name (filesystem::path (c.get_filename().c_str()).filename());
+#endif
+ film_viewer->set_film (film);
+ film_editor->set_film (film);
+ }
+}
+
+void
+file_open ()
+{
+ Gtk::FileChooserDialog c (*window, "Open Film", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
+ c.add_button ("_Cancel", Gtk::RESPONSE_CANCEL);
+ c.add_button ("_Open", Gtk::RESPONSE_ACCEPT);
+
+ int const r = c.run ();
+ if (r == Gtk::RESPONSE_ACCEPT) {
+ if (maybe_save_then_delete_film ()) {
+ return;
+ }
+ film = new Film (c.get_filename ());
+ film_viewer->set_film (film);
+ film_editor->set_film (film);
+ }
+}
+
+void
+file_save ()
+{
+ film->write_metadata ();
+}
+
+void
+file_quit ()
+{
+ if (maybe_save_then_delete_film ()) {
+ return;
+ }
+
+ Gtk::Main::quit ();
+}
+
+void
+edit_preferences ()
+{
+ ConfigDialog d;
+ d.run ();
+ Config::instance()->write ();
+}
+
+void
+jobs_make_dcp ()
+{
+ JobWrapper::make_dcp (film, true);
+}
+
+void
+jobs_make_dcp_from_existing_transcode ()
+{
+ JobWrapper::make_dcp (film, false);
+}
+
+void
+jobs_copy_from_dvd ()
+{
+ film->copy_from_dvd ();
+}
+
+void
+jobs_send_dcp_to_tms ()
+{
+ film->send_dcp_to_tms ();
+}
+
+void
+jobs_examine_content ()
+{
+ film->examine_content ();
+}
+
+void
+help_about ()
+{
+ Gtk::AboutDialog d;
+ d.set_name ("DVD-o-matic");
+ d.set_version (DVDOMATIC_VERSION);
+
+ stringstream s;
+ s << "DCP generation from arbitrary formats\n\n"
+ << "Using " << dependency_version_summary() << "\n";
+ d.set_comments (s.str ());
+
+ vector<string> authors;
+ authors.push_back ("Carl Hetherington");
+ authors.push_back ("Terrence Meiczinger");
+ authors.push_back ("Paul Davis");
+ d.set_authors (authors);
+
+ d.set_website ("http://carlh.net/software/dvdomatic");
+ d.set_license (gpl);
+
+ d.run ();
+}
+
+void
+setup_menu (Gtk::MenuBar& m)
+{
+ using namespace Gtk::Menu_Helpers;
+
+ Gtk::Menu* file = manage (new Gtk::Menu);
+ MenuList& file_items (file->items ());
+ file_items.push_back (MenuElem ("_New...", sigc::ptr_fun (file_new)));
+ file_items.push_back (MenuElem ("_Open...", sigc::ptr_fun (file_open)));
+ file_items.push_back (SeparatorElem ());
+ file_items.push_back (MenuElem ("_Save", sigc::ptr_fun (file_save)));
+ file_items.push_back (SeparatorElem ());
+ file_items.push_back (MenuElem ("_Quit", sigc::ptr_fun (file_quit)));
+
+ Gtk::Menu* edit = manage (new Gtk::Menu);
+ MenuList& edit_items (edit->items ());
+ edit_items.push_back (MenuElem ("_Preferences...", sigc::ptr_fun (edit_preferences)));
+
+ Gtk::Menu* jobs = manage (new Gtk::Menu);
+ MenuList& jobs_items (jobs->items ());
+ jobs_items.push_back (MenuElem ("_Make DCP", sigc::ptr_fun (jobs_make_dcp)));
+ jobs_items.push_back (MenuElem ("_Send DCP to TMS", sigc::ptr_fun (jobs_send_dcp_to_tms)));
+ jobs_items.push_back (MenuElem ("Copy from _DVD", sigc::ptr_fun (jobs_copy_from_dvd)));
+ jobs_items.push_back (MenuElem ("_Examine content", sigc::ptr_fun (jobs_examine_content)));
+ jobs_items.push_back (MenuElem ("Make DCP from _existing transcode", sigc::ptr_fun (jobs_make_dcp_from_existing_transcode)));
+
+ Gtk::Menu* help = manage (new Gtk::Menu);
+ MenuList& help_items (help->items ());
+ help_items.push_back (MenuElem ("_About", sigc::ptr_fun (help_about)));
+
+ MenuList& items (m.items ());
+ items.push_back (MenuElem ("_File", *file));
+ items.push_back (MenuElem ("_Edit", *edit));
+ items.push_back (MenuElem ("_Jobs", *jobs));
+ items.push_back (MenuElem ("_Help", *help));
+}
+
+bool
+window_closed (GdkEventAny *)
+{
+ if (maybe_save_then_delete_film ()) {
+ return true;
+ }
+
+ return false;
+}
+
+void
+file_changed (string f)
+{
+ stringstream s;
+ s << "DVD-o-matic";
+ if (!f.empty ()) {
+ s << " — " << f;
+ }
+
+ window->set_title (s.str ());
+}
+
+int
+main (int argc, char* argv[])
+{
+ dvdomatic_setup ();
+
+ Gtk::Main kit (argc, argv);
+
+ if (argc == 2 && boost::filesystem::is_directory (argv[1])) {
+ film = new Film (argv[1]);
+ }
+
+ window = new Gtk::Window ();
+ window->signal_delete_event().connect (sigc::ptr_fun (window_closed));
+
+ film_viewer = new FilmViewer (film);
+ film_editor = new FilmEditor (film);
+ film_player = new FilmPlayer (film);
+ JobManagerView jobs_view;
+
+ window->set_title ("DVD-o-matic");
+
+ Gtk::VBox vbox;
+
+ Gtk::MenuBar menu_bar;
+ vbox.pack_start (menu_bar, false, false);
+ setup_menu (menu_bar);
+
+ Gtk::HBox hbox;
+ hbox.set_spacing (12);
+
+ Gtk::VBox left_vbox;
+ left_vbox.set_spacing (12);
+ left_vbox.pack_start (film_editor->widget (), false, false);
+// left_vbox.pack_start (film_player->widget (), false, false);
+ hbox.pack_start (left_vbox, false, false);
+
+ Gtk::VBox right_vbox;
+ right_vbox.pack_start (film_viewer->widget (), true, true);
+ right_vbox.pack_start (jobs_view.widget(), false, false);
+ hbox.pack_start (right_vbox, true, true);
+
+ vbox.pack_start (hbox, true, true);
+
+ window->add (vbox);
+ window->show_all ();
+
+ /* XXX: calling these here is a bit of a hack */
+ film_editor->setup_visibility ();
+ film_player->setup_visibility ();
+ film_viewer->setup_visibility ();
+
+ film_editor->FileChanged.connect (ptr_fun (file_changed));
+ if (film) {
+ file_changed (film->directory ());
+ } else {
+ file_changed ("");
+ }
+
+ /* XXX this should be in JobManagerView, shouldn't it? */
+ Glib::signal_timeout().connect (sigc::bind_return (sigc::mem_fun (jobs_view, &JobManagerView::update), true), 1000);
+
+ window->maximize ();
+ Gtk::Main::run (*window);
+
+ return 0;
+}
diff --git a/src/tools/fixlengths.cc b/src/tools/fixlengths.cc
new file mode 100644
index 000000000..52696cd8b
--- /dev/null
+++ b/src/tools/fixlengths.cc
@@ -0,0 +1,209 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <stdexcept>
+#include <iostream>
+#include <iomanip>
+#include <getopt.h>
+#include <sndfile.h>
+#include <boost/filesystem.hpp>
+#include "lib/film.h"
+
+using namespace std;
+using namespace boost;
+
+void
+help (string n)
+{
+ cerr << "Syntax: " << n << " [--help] [--chop-audio-start] [--chop-audio-end] --film <film>\n";
+}
+
+void
+sox (vector<string> const & audio_files, string const & process)
+{
+ for (vector<string>::const_iterator i = audio_files.begin(); i != audio_files.end(); ++i) {
+ stringstream cmd;
+ cmd << "sox \"" << *i << "\" -t wav \"" << *i << ".tmp\" " << process;
+ cout << "> " << cmd.str() << "\n";
+ int r = ::system (cmd.str().c_str());
+ if (r == -1 || WEXITSTATUS (r) != 0) {
+ cerr << "fixlengths: call to sox failed.\n";
+ exit (EXIT_FAILURE);
+ }
+ filesystem::rename (*i + ".tmp", *i);
+ }
+}
+
+int main (int argc, char* argv[])
+{
+ string film_dir;
+ bool chop_audio_start = false;
+ bool chop_audio_end = false;
+ bool pad_audio_end = false;
+
+ while (1) {
+ static struct option long_options[] = {
+ { "help", no_argument, 0, 'h' },
+ { "chop-audio-start", no_argument, 0, 'c' },
+ { "chop-audio-end", no_argument, 0, 'd' },
+ { "pad-audio-end", no_argument, 0, 'p' },
+ { "film", required_argument, 0, 'f' },
+ { 0, 0, 0, 0 }
+ };
+
+ int option_index = 0;
+ int c = getopt_long (argc, argv, "hcf:", long_options, &option_index);
+
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'h':
+ help (argv[0]);
+ exit (EXIT_SUCCESS);
+ case 'c':
+ chop_audio_start = true;
+ break;
+ case 'd':
+ chop_audio_end = true;
+ break;
+ case 'p':
+ pad_audio_end = true;
+ break;
+ case 'f':
+ film_dir = optarg;
+ break;
+ }
+ }
+
+ if (film_dir.empty ()) {
+ help (argv[0]);
+ exit (EXIT_FAILURE);
+ }
+
+ dvdomatic_setup ();
+
+ Film* film = 0;
+ try {
+ film = new Film (film_dir, true);
+ } catch (std::exception& e) {
+ cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n";
+ exit (EXIT_FAILURE);
+ }
+
+ /* XXX: hack */
+ int video_frames = 0;
+ for (filesystem::directory_iterator i = filesystem::directory_iterator (film->j2k_dir()); i != filesystem::directory_iterator(); ++i) {
+ ++video_frames;
+ }
+
+ float const video_length = video_frames / film->frames_per_second();
+ cout << "Video length: " << video_length << " (" << video_frames << " frames at " << film->frames_per_second() << " frames per second).\n";
+
+ vector<string> audio_files = film->audio_files ();
+ if (audio_files.empty ()) {
+ cerr << argv[0] << ": film has no audio files.\n";
+ exit (EXIT_FAILURE);
+ }
+
+ sf_count_t audio_frames = 0;
+ int audio_sample_rate = 0;
+
+ for (vector<string>::iterator i = audio_files.begin(); i != audio_files.end(); ++i) {
+ SF_INFO info;
+ info.format = 0;
+ SNDFILE* sf = sf_open (i->c_str(), SFM_READ, &info);
+ if (sf == 0) {
+ cerr << argv[0] << ": could not open WAV file for reading.\n";
+ exit (EXIT_FAILURE);
+ }
+
+ if (audio_frames == 0) {
+ audio_frames = info.frames;
+ }
+
+ if (audio_sample_rate == 0) {
+ audio_sample_rate = info.samplerate;
+ }
+
+ if (audio_frames != info.frames) {
+ cerr << argv[0] << ": audio files have differing lengths.\n";
+ exit (EXIT_FAILURE);
+ }
+
+ if (audio_sample_rate != info.samplerate) {
+ cerr << argv[0] << ": audio files have differing sample rates.\n";
+ exit (EXIT_FAILURE);
+ }
+
+ sf_close (sf);
+ }
+
+ float const audio_length = audio_frames / float (audio_sample_rate);
+
+ cout << "Audio length: " << audio_length << " (" << audio_frames << " frames at " << audio_sample_rate << " frames per second).\n";
+
+ cout << "\n";
+
+ if (audio_length > video_length) {
+ cout << setprecision (3);
+ cout << "Audio " << (audio_length - video_length) << "s longer than video.\n";
+
+ float const delta = audio_length - video_length;
+ int const delta_samples = delta * audio_sample_rate;
+
+ if (chop_audio_start) {
+ cout << "Chopping difference off the start of the audio.\n";
+
+ stringstream s;
+ s << "trim " << delta_samples << "s";
+ sox (audio_files, s.str ());
+
+ } else if (chop_audio_end) {
+ cout << "Chopping difference off the end of the audio.\n";
+
+ stringstream s;
+ s << "reverse trim " << delta_samples << "s reverse";
+ sox (audio_files, s.str ());
+
+ } else {
+ cout << "Re-run with --chop-audio-start or --chop-audio-end, perhaps.\n";
+ }
+
+ } else if (audio_length < video_length) {
+ cout << setprecision (3);
+ cout << "Audio " << (video_length - audio_length) << "s shorter than video.\n";
+
+ if (pad_audio_end) {
+
+ float const delta = video_length - audio_length;
+ int const delta_samples = delta * audio_sample_rate;
+ stringstream s;
+ s << "pad 0 " << delta_samples << "s";
+ sox (audio_files, s.str ());
+
+ } else {
+ cout << "Re-run with --pad-audio-end, perhaps.\n";
+ }
+ }
+
+
+ return 0;
+}
diff --git a/src/tools/makedcp.cc b/src/tools/makedcp.cc
new file mode 100644
index 000000000..76cda8202
--- /dev/null
+++ b/src/tools/makedcp.cc
@@ -0,0 +1,138 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <iomanip>
+#include <getopt.h>
+#include "format.h"
+#include "film.h"
+#include "filter.h"
+#include "transcode_job.h"
+#include "make_mxf_job.h"
+#include "make_dcp_job.h"
+#include "job_manager.h"
+#include "ab_transcode_job.h"
+#include "util.h"
+#include "scaler.h"
+
+using namespace std;
+using namespace boost;
+
+static void
+help (string n)
+{
+ cerr << "Syntax: " << n << " [--help] [--deps] [--film <film>]\n";
+}
+
+int
+main (int argc, char* argv[])
+{
+ string film_dir;
+
+ while (1) {
+ static struct option long_options[] = {
+ { "help", no_argument, 0, 'h'},
+ { "deps", no_argument, 0, 'd'},
+ { "film", required_argument, 0, 'f'},
+ { 0, 0, 0, 0 }
+ };
+
+ int option_index = 0;
+ int c = getopt_long (argc, argv, "hdf:", long_options, &option_index);
+
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'h':
+ help (argv[0]);
+ exit (EXIT_SUCCESS);
+ case 'd':
+ cout << dependency_version_summary () << "\n";
+ exit (EXIT_SUCCESS);
+ case 'f':
+ film_dir = optarg;
+ break;
+ }
+ }
+
+ if (film_dir.empty ()) {
+ help (argv[0]);
+ exit (EXIT_FAILURE);
+ }
+
+ dvdomatic_setup ();
+
+ Film* film = 0;
+ try {
+ film = new Film (film_dir, true);
+ } catch (std::exception& e) {
+ cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n";
+ exit (EXIT_FAILURE);
+ }
+
+ cout << "\nMaking ";
+ if (film->dcp_ab ()) {
+ cout << "A/B ";
+ }
+ cout << "DCP for " << film->name() << "\n";
+ cout << "Content: " << film->content() << "\n";
+ pair<string, string> const f = Filter::ffmpeg_strings (film->filters ());
+ cout << "Filters: " << f.first << " " << f.second << "\n";
+
+ film->make_dcp (true);
+
+ list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
+
+ bool all_done = false;
+ bool first = true;
+ while (!all_done) {
+
+ sleep (5);
+
+ if (!first) {
+ cout << "\033[" << jobs.size() << "A";
+ cout.flush ();
+ }
+
+ first = false;
+
+ all_done = true;
+ for (list<shared_ptr<Job> >::iterator i = jobs.begin(); i != jobs.end(); ++i) {
+ cout << (*i)->name() << ": ";
+
+ float const p = (*i)->overall_progress ();
+
+ if (p >= 0) {
+ cout << (*i)->status() << " \n";
+ } else {
+ cout << ": Running \n";
+ }
+
+ if (!(*i)->finished ()) {
+ all_done = false;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
diff --git a/src/tools/playomatic.cc b/src/tools/playomatic.cc
new file mode 100644
index 000000000..b6fcd43cd
--- /dev/null
+++ b/src/tools/playomatic.cc
@@ -0,0 +1,67 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include "lib/util.h"
+#include "gtk/film_player.h"
+#include "gtk/film_list.h"
+
+using namespace std;
+
+static FilmPlayer* film_player = 0;
+
+void
+film_changed (Film const * f)
+{
+ film_player->set_film (f);
+}
+
+int
+main (int argc, char* argv[])
+{
+ dvdomatic_setup ();
+
+ Gtk::Main kit (argc, argv);
+
+ if (argc != 2) {
+ cerr << "Syntax: " << argv[0] << " <directory>\n";
+ exit (EXIT_FAILURE);
+ }
+
+ Gtk::Window* window = new Gtk::Window ();
+
+ FilmList* film_list = new FilmList (argv[1]);
+ film_player = new FilmPlayer ();
+
+ Gtk::HBox hbox;
+ hbox.pack_start (film_list->widget(), true, true);
+ hbox.pack_start (film_player->widget(), true, true);
+
+ film_list->SelectionChanged.connect (sigc::ptr_fun (&film_changed));
+
+ window->set_title ("Play-o-matic");
+ window->add (hbox);
+ window->show_all ();
+
+ window->maximize ();
+ Gtk::Main::run (*window);
+
+ return 0;
+}
+
diff --git a/src/tools/run_film_editor b/src/tools/run_film_editor
new file mode 100755
index 000000000..3a3079e92
--- /dev/null
+++ b/src/tools/run_film_editor
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:build/src
+build/tools/film_editor $*
diff --git a/src/tools/servomatic.cc b/src/tools/servomatic.cc
new file mode 100644
index 000000000..b312af352
--- /dev/null
+++ b/src/tools/servomatic.cc
@@ -0,0 +1,238 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <stdexcept>
+#include <sstream>
+#include <cstring>
+#include <vector>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/thread/condition.hpp>
+#include "config.h"
+#include "dcp_video_frame.h"
+#include "exceptions.h"
+#include "util.h"
+#include "config.h"
+#include "scaler.h"
+#include "image.h"
+#include "log.h"
+
+#define BACKLOG 8
+
+using namespace std;
+using namespace boost;
+
+static vector<thread *> worker_threads;
+
+static std::list<int> queue;
+static mutex worker_mutex;
+static condition worker_condition;
+static Log log_ ("servomatic.log");
+
+int
+process (int fd)
+{
+ SocketReader reader (fd);
+
+ char buffer[128];
+ reader.read_indefinite ((uint8_t *) buffer, sizeof (buffer));
+ reader.consume (strlen (buffer) + 1);
+
+ stringstream s (buffer);
+
+ string command;
+ s >> command;
+ if (command != "encode") {
+ close (fd);
+ return -1;
+ }
+
+ Size in_size;
+ int pixel_format_int;
+ Size out_size;
+ int padding;
+ string scaler_id;
+ int frame;
+ float frames_per_second;
+ string post_process;
+ int colour_lut_index;
+ int j2k_bandwidth;
+
+ s >> in_size.width >> in_size.height
+ >> pixel_format_int
+ >> out_size.width >> out_size.height
+ >> padding
+ >> scaler_id
+ >> frame
+ >> frames_per_second
+ >> post_process
+ >> colour_lut_index
+ >> j2k_bandwidth;
+
+ PixelFormat pixel_format = (PixelFormat) pixel_format_int;
+ Scaler const * scaler = Scaler::from_id (scaler_id);
+ if (post_process == "none") {
+ post_process = "";
+ }
+
+ shared_ptr<SimpleImage> image (new SimpleImage (pixel_format, in_size));
+
+ for (int i = 0; i < image->components(); ++i) {
+ int line_size;
+ s >> line_size;
+ image->set_line_size (i, line_size);
+ }
+
+ for (int i = 0; i < image->components(); ++i) {
+ reader.read_definite_and_consume (image->data()[i], image->line_size()[i] * image->lines(i));
+ }
+
+#ifdef DEBUG_HASH
+ image->hash ("Image for encoding (as received by server)");
+#endif
+
+ DCPVideoFrame dcp_video_frame (image, out_size, padding, scaler, frame, frames_per_second, post_process, colour_lut_index, j2k_bandwidth, &log_);
+ shared_ptr<EncodedData> encoded = dcp_video_frame.encode_locally ();
+ encoded->send (fd);
+
+#ifdef DEBUG_HASH
+ encoded->hash ("Encoded image (as made by server and as sent back)");
+#endif
+
+
+ return frame;
+}
+
+void
+worker_thread ()
+{
+ while (1) {
+ mutex::scoped_lock lock (worker_mutex);
+ while (queue.empty ()) {
+ worker_condition.wait (lock);
+ }
+
+ int fd = queue.front ();
+ queue.pop_front ();
+
+ lock.unlock ();
+
+ int frame = -1;
+
+ struct timeval start;
+ gettimeofday (&start, 0);
+
+ try {
+ frame = process (fd);
+ } catch (std::exception& e) {
+ cerr << "Error: " << e.what() << "\n";
+ }
+
+ close (fd);
+
+ lock.lock ();
+
+ if (frame >= 0) {
+ struct timeval end;
+ gettimeofday (&end, 0);
+ cout << "Encoded frame " << frame << " in " << (seconds (end) - seconds (start)) << "\n";
+ }
+
+ worker_condition.notify_all ();
+ }
+}
+
+int
+main ()
+{
+ Scaler::setup_scalers ();
+
+ int const num_threads = Config::instance()->num_local_encoding_threads ();
+
+ for (int i = 0; i < num_threads; ++i) {
+ worker_threads.push_back (new thread (worker_thread));
+ }
+
+ int fd = socket (AF_INET, SOCK_STREAM, 0);
+ if (fd < 0) {
+ throw NetworkError ("could not open socket");
+ }
+
+ int const o = 1;
+ setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &o, sizeof (o));
+
+ struct timeval tv;
+ tv.tv_sec = 20;
+ tv.tv_usec = 0;
+ setsockopt (fd, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof (tv));
+ setsockopt (fd, SOL_SOCKET, SO_SNDTIMEO, (void *) &tv, sizeof (tv));
+
+ struct sockaddr_in server_address;
+ memset (&server_address, 0, sizeof (server_address));
+ server_address.sin_family = AF_INET;
+ server_address.sin_addr.s_addr = INADDR_ANY;
+ server_address.sin_port = htons (Config::instance()->server_port ());
+ if (::bind (fd, (struct sockaddr *) &server_address, sizeof (server_address)) < 0) {
+ stringstream s;
+ s << "could not bind to port " << Config::instance()->server_port() << " (" << strerror (errno) << ")";
+ throw NetworkError (s.str());
+ }
+
+ listen (fd, BACKLOG);
+
+ while (1) {
+ struct sockaddr_in client_address;
+ socklen_t client_length = sizeof (client_address);
+ int new_fd = accept (fd, (struct sockaddr *) &client_address, &client_length);
+ if (new_fd < 0) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ throw NetworkError ("accept failed");
+ }
+
+ continue;
+ }
+
+ mutex::scoped_lock lock (worker_mutex);
+
+ /* Wait until the queue has gone down a bit */
+ while (int (queue.size()) >= num_threads * 2) {
+ worker_condition.wait (lock);
+ }
+
+ struct timeval tv;
+ tv.tv_sec = 20;
+ tv.tv_usec = 0;
+ setsockopt (new_fd, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof (tv));
+ setsockopt (new_fd, SOL_SOCKET, SO_SNDTIMEO, (void *) &tv, sizeof (tv));
+
+ queue.push_back (new_fd);
+ worker_condition.notify_all ();
+ }
+
+ close (fd);
+
+ return 0;
+}
diff --git a/src/tools/servomatictest.cc b/src/tools/servomatictest.cc
new file mode 100644
index 000000000..0f37e73a5
--- /dev/null
+++ b/src/tools/servomatictest.cc
@@ -0,0 +1,159 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <iomanip>
+#include <exception>
+#include <getopt.h>
+#include "format.h"
+#include "film.h"
+#include "filter.h"
+#include "util.h"
+#include "scaler.h"
+#include "server.h"
+#include "dcp_video_frame.h"
+#include "options.h"
+#include "decoder.h"
+#include "exceptions.h"
+#include "scaler.h"
+#include "log.h"
+#include "decoder_factory.h"
+
+using namespace std;
+using namespace boost;
+
+static Server* server;
+static Log log_ ("servomatictest.log");
+
+void
+process_video (shared_ptr<Image> image, int frame)
+{
+ shared_ptr<DCPVideoFrame> local (new DCPVideoFrame (image, Size (1024, 1024), 0, Scaler::from_id ("bicubic"), frame, 24, "", 0, 250000000, &log_));
+ shared_ptr<DCPVideoFrame> remote (new DCPVideoFrame (image, Size (1024, 1024), 0, Scaler::from_id ("bicubic"), frame, 24, "", 0, 250000000, &log_));
+
+#if defined(DEBUG_HASH)
+ cout << "Frame " << frame << ":\n";
+#else
+ cout << "Frame " << frame << ": ";
+ cout.flush ();
+#endif
+
+ shared_ptr<EncodedData> local_encoded = local->encode_locally ();
+ shared_ptr<EncodedData> remote_encoded;
+
+ string remote_error;
+ try {
+ remote_encoded = remote->encode_remotely (server);
+ } catch (NetworkError& e) {
+ remote_error = e.what ();
+ }
+
+#if defined(DEBUG_HASH)
+ cout << "Frame " << frame << ": ";
+ cout.flush ();
+#endif
+
+ if (!remote_error.empty ()) {
+ cout << "\033[0;31mnetwork problem: " << remote_error << "\033[0m\n";
+ return;
+ }
+
+ if (local_encoded->size() != remote_encoded->size()) {
+ cout << "\033[0;31msizes differ\033[0m\n";
+ return;
+ }
+
+ uint8_t* p = local_encoded->data();
+ uint8_t* q = remote_encoded->data();
+ for (int i = 0; i < local_encoded->size(); ++i) {
+ if (*p++ != *q++) {
+ cout << "\033[0;31mdata differ\033[0m at byte " << i << "\n";
+ return;
+ }
+ }
+
+ cout << "\033[0;32mgood\033[0m\n";
+}
+
+static void
+help (string n)
+{
+ cerr << "Syntax: " << n << " [--help] --film <film> --server <host>\n";
+ exit (EXIT_FAILURE);
+}
+
+int
+main (int argc, char* argv[])
+{
+ string film_dir;
+ string server_host;
+
+ while (1) {
+ static struct option long_options[] = {
+ { "help", no_argument, 0, 'h'},
+ { "server", required_argument, 0, 's'},
+ { "film", required_argument, 0, 'f'},
+ { 0, 0, 0, 0 }
+ };
+
+ int option_index = 0;
+ int c = getopt_long (argc, argv, "hs:f:", long_options, &option_index);
+
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'h':
+ help (argv[0]);
+ exit (EXIT_SUCCESS);
+ case 's':
+ server_host = optarg;
+ break;
+ case 'f':
+ film_dir = optarg;
+ break;
+ }
+ }
+
+ if (server_host.empty() || film_dir.empty()) {
+ help (argv[0]);
+ exit (EXIT_FAILURE);
+ }
+
+ dvdomatic_setup ();
+
+ server = new Server (server_host, 1);
+ Film film (film_dir, true);
+
+ shared_ptr<Options> opt (new Options ("fred", "jim", "sheila"));
+ opt->out_size = Size (1024, 1024);
+ opt->apply_crop = false;
+ opt->decode_audio = false;
+
+ shared_ptr<Decoder> decoder = decoder_factory (film.state_copy(), opt, 0, &log_);
+ try {
+ decoder->Video.connect (sigc::ptr_fun (process_video));
+ decoder->go ();
+ } catch (std::exception& e) {
+ cerr << "Error: " << e.what() << "\n";
+ }
+
+ return 0;
+}
diff --git a/src/tools/test.cc b/src/tools/test.cc
new file mode 100644
index 000000000..f81814160
--- /dev/null
+++ b/src/tools/test.cc
@@ -0,0 +1,15 @@
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+#include "image.h"
+#include "server.h"
+
+using namespace boost;
+
+int main ()
+{
+ uint8_t* rgb = new uint8_t[256];
+ shared_ptr<Image> image (new Image (rgb, 0, 32, 32, 24));
+ Server* s = new Server ("localhost", 2);
+ image->encode_remotely (s);
+ return 0;
+}
diff --git a/src/tools/wscript b/src/tools/wscript
new file mode 100644
index 000000000..919c98e3f
--- /dev/null
+++ b/src/tools/wscript
@@ -0,0 +1,17 @@
+def build(bld):
+ for t in ['makedcp', 'servomatic', 'servomatictest', 'fixlengths']:
+ obj = bld(features = 'cxx cxxprogram')
+ obj.uselib = 'BOOST_THREAD'
+ obj.includes = ['..']
+ obj.use = ['libdvdomatic']
+ obj.source = '%s.cc' % t
+ obj.target = t
+
+ if not bld.env.DISABLE_GUI:
+ for t in ['dvdomatic', 'playomatic', 'alignomatic']:
+ obj = bld(features = 'cxx cxxprogram')
+ obj.uselib = 'BOOST_THREAD GTKMM'
+ obj.includes = ['..']
+ obj.use = ['libdvdomatic', 'libdvdomatic-gtk']
+ obj.source = '%s.cc' % t
+ obj.target = t
diff --git a/src/wscript b/src/wscript
new file mode 100644
index 000000000..9ae35d507
--- /dev/null
+++ b/src/wscript
@@ -0,0 +1,5 @@
+def build(bld):
+ bld.recurse('lib')
+ bld.recurse('tools')
+ if not bld.env.DISABLE_GUI:
+ bld.recurse('gtk')