summaryrefslogtreecommitdiff
path: root/src/tools
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/tools
parent66c9be6bdb1361e5681e094a0c8170d268aa9518 (diff)
Move things round a bit.
Diffstat (limited to 'src/tools')
-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
10 files changed, 1492 insertions, 0 deletions
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