diff options
| author | Carl Hetherington <cth@carlh.net> | 2012-07-15 00:14:28 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2012-07-15 00:14:28 +0100 |
| commit | bb767c7e338414beee132af3e96829c1448e214b (patch) | |
| tree | bec2858dcc7225a9bcc2acd8170c25508f6df6cb /src | |
| parent | 66c9be6bdb1361e5681e094a0c8170d268aa9518 (diff) | |
Move things round a bit.
Diffstat (limited to 'src')
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') |
