summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Gareus <robin@gareus.org>2013-03-12 22:00:09 +0100
committerRobin Gareus <robin@gareus.org>2013-03-13 20:28:15 +0100
commit0c3e840700a915fc1476cef73c591048f688f81e (patch)
tree3241845f899cfed86217db2f6f589740b2b9c04a
parentd91565093965b3405774acd878b3baf38839f4e4 (diff)
videotimline
squashed 694 commits from http://gareus.org/gitweb/?p=ardour3.git
-rw-r--r--gtk2_ardour/add_video_dialog.cc584
-rw-r--r--gtk2_ardour/add_video_dialog.h113
-rw-r--r--gtk2_ardour/ardour.menus.in28
-rw-r--r--gtk2_ardour/ardour3_ui_default.conf1
-rw-r--r--gtk2_ardour/ardour_ui.cc261
-rw-r--r--gtk2_ardour/ardour_ui.h26
-rw-r--r--gtk2_ardour/ardour_ui_dialogs.cc8
-rw-r--r--gtk2_ardour/ardour_ui_ed.cc20
-rw-r--r--gtk2_ardour/ardour_ui_options.cc3
-rw-r--r--gtk2_ardour/canvas_vars.h3
-rw-r--r--gtk2_ardour/editor.cc41
-rw-r--r--gtk2_ardour/editor.h33
-rw-r--r--gtk2_ardour/editor_actions.cc52
-rw-r--r--gtk2_ardour/editor_canvas.cc31
-rw-r--r--gtk2_ardour/editor_canvas_events.cc8
-rw-r--r--gtk2_ardour/editor_drag.cc180
-rw-r--r--gtk2_ardour/editor_drag.h41
-rw-r--r--gtk2_ardour/editor_items.h3
-rw-r--r--gtk2_ardour/editor_mouse.cc10
-rw-r--r--gtk2_ardour/editor_ops.cc27
-rw-r--r--gtk2_ardour/editor_rulers.cc77
-rw-r--r--gtk2_ardour/editor_selection.cc19
-rw-r--r--gtk2_ardour/editor_videotimeline.cc126
-rw-r--r--gtk2_ardour/export_video_dialog.cc917
-rw-r--r--gtk2_ardour/export_video_dialog.h122
-rw-r--r--gtk2_ardour/export_video_infobox.cc76
-rw-r--r--gtk2_ardour/export_video_infobox.h49
-rw-r--r--gtk2_ardour/open_video_monitor_dialog.cc196
-rw-r--r--gtk2_ardour/open_video_monitor_dialog.h80
-rw-r--r--gtk2_ardour/public_editor.h14
-rw-r--r--gtk2_ardour/rc_option_editor.cc118
-rw-r--r--gtk2_ardour/region_view.cc12
-rw-r--r--gtk2_ardour/session_option_editor.cc15
-rw-r--r--gtk2_ardour/system_exec.cc664
-rw-r--r--gtk2_ardour/system_exec.h204
-rw-r--r--gtk2_ardour/transcode_ffmpeg.cc512
-rw-r--r--gtk2_ardour/transcode_ffmpeg.h162
-rw-r--r--gtk2_ardour/transcode_video_dialog.cc524
-rw-r--r--gtk2_ardour/transcode_video_dialog.h106
-rw-r--r--gtk2_ardour/utils_videotl.cc301
-rw-r--r--gtk2_ardour/utils_videotl.h56
-rw-r--r--gtk2_ardour/video_copy_dialog.cc270
-rw-r--r--gtk2_ardour/video_copy_dialog.h90
-rw-r--r--gtk2_ardour/video_image_frame.cc364
-rw-r--r--gtk2_ardour/video_image_frame.h116
-rw-r--r--gtk2_ardour/video_monitor.cc406
-rw-r--r--gtk2_ardour/video_monitor.h109
-rw-r--r--gtk2_ardour/video_server_dialog.cc205
-rw-r--r--gtk2_ardour/video_server_dialog.h72
-rw-r--r--gtk2_ardour/video_timeline.cc769
-rw-r--r--gtk2_ardour/video_timeline.h145
-rw-r--r--gtk2_ardour/wscript21
-rw-r--r--libs/ardour/ardour/directory_names.h3
-rw-r--r--libs/ardour/ardour/rc_configuration_vars.h8
-rw-r--r--libs/ardour/ardour/region.h12
-rw-r--r--libs/ardour/ardour/session_configuration_vars.h4
-rw-r--r--libs/ardour/ardour/session_directory.h8
-rw-r--r--libs/ardour/directory_names.cc3
-rw-r--r--libs/ardour/region.cc48
-rw-r--r--libs/ardour/session_directory.cc8
-rw-r--r--tools/videotimeline/vsrv.php113
-rw-r--r--wscript6
62 files changed, 8595 insertions, 8 deletions
diff --git a/gtk2_ardour/add_video_dialog.cc b/gtk2_ardour/add_video_dialog.cc
new file mode 100644
index 0000000000..3e9b3d3b08
--- /dev/null
+++ b/gtk2_ardour/add_video_dialog.cc
@@ -0,0 +1,584 @@
+/*
+ Copyright (C) 2010-2013 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include <cstdio>
+#include <cmath>
+
+#include <sigc++/bind.h>
+#include <curl/curl.h>
+
+#include "pbd/error.h"
+#include "pbd/convert.h"
+#include "gtkmm2ext/utils.h"
+#include "gtkmm2ext/rgb_macros.h"
+#include "ardour/session_directory.h"
+#include "ardour/profile.h"
+#include "ardour/template_utils.h"
+#include "ardour/session.h"
+#include "ardour_ui.h"
+
+#include "utils.h"
+#include "add_video_dialog.h"
+#include "utils_videotl.h"
+#include "i18n.h"
+
+using namespace Gtk;
+using namespace std;
+using namespace PBD;
+using namespace ARDOUR;
+
+#define PREVIEW_WIDTH (240)
+#define PREVIEW_HEIGHT (180)
+
+#ifndef MIN
+#define MIN(a,b) ( (a) < (b) ? (a) : (b) )
+#endif
+
+AddVideoDialog::AddVideoDialog (Session* s)
+ : ArdourDialog (_("Set Video Track"))
+ , seek_slider (0,1000,1)
+ , preview_path ("")
+ , pi_duration ("-", Gtk::ALIGN_RIGHT, Gtk::ALIGN_CENTER, false)
+ , pi_aspect ("-", Gtk::ALIGN_RIGHT, Gtk::ALIGN_CENTER, false)
+ , pi_fps ("-", Gtk::ALIGN_RIGHT, Gtk::ALIGN_CENTER, false)
+ , chooser (FILE_CHOOSER_ACTION_OPEN)
+ , xjadeo_checkbox (_("Launch External Video Monitor"))
+ , set_session_fps_checkbox (_("Adjust Session Framerate to Match Video Framerate"))
+ , harvid_path ("")
+ , harvid_reset (_("Reload docroot"))
+ , harvid_list (ListStore::create(harvid_list_columns))
+ , harvid_list_view (harvid_list)
+{
+ set_session (s);
+ set_name ("AddVideoDialog");
+ set_position (Gtk::WIN_POS_MOUSE);
+ set_modal (true);
+ set_skip_taskbar_hint (true);
+ set_resizable (true);
+ set_size_request (800, -1);
+
+ harvid_initialized = false;
+ std::string dstdir = video_dest_dir(_session->session_directory().video_path(), Config->get_video_server_docroot());
+
+
+ /* Harvid Browser */
+ harvid_list_view.append_column("", pixBufRenderer);
+ harvid_list_view.append_column(_("Filename"), harvid_list_columns.filename);
+
+ harvid_list_view.get_column(0)->set_alignment(0.5);
+ harvid_list_view.get_column(0)->add_attribute(pixBufRenderer, "stock-id", harvid_list_columns.id);
+ harvid_list_view.get_column(1)->set_expand(true);
+ harvid_list_view.get_column(1)->set_sort_column(harvid_list_columns.filename);
+ harvid_list_view.set_enable_search(true);
+ harvid_list_view.set_search_column(1);
+
+
+ //Glib::RefPtr<Gtk::TreeModelSort> refTreeModelSort = Gtk::TreeModelSort::create(harvid_list_view.get_model());
+ //refTreeModelSort->set_sort_column(harvid_list_columns.filename, Gtk::SORT_ASCENDING);
+ //harvid_list_view.set_model(refTreeModelSort);
+
+ harvid_list_view.get_selection()->set_mode (SELECTION_SINGLE);
+
+ harvid_list_view.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &AddVideoDialog::harvid_list_view_selected));
+ harvid_list_view.signal_row_activated().connect (sigc::mem_fun (*this, &AddVideoDialog::harvid_list_view_activated));
+
+ VBox* vbox = manage (new VBox);
+ Gtk::ScrolledWindow *scroll = manage(new ScrolledWindow);
+ scroll->add(harvid_list_view);
+ scroll->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+
+ HBox* hbox = manage (new HBox);
+ harvid_path.set_alignment (0, 0.5);
+ hbox->pack_start (harvid_path, true, true);
+ hbox->pack_start (harvid_reset, false, false);
+
+ vbox->pack_start (*hbox, false, false);
+ vbox->pack_start (*scroll, true, true);
+
+ notebook.append_page (*vbox, _("VideoServerIndex"));
+
+
+
+ /* file chooser */
+ chooser.set_border_width (4);
+#ifdef GTKOSX
+ /* some broken redraw behaviour - this is a bandaid */
+ chooser.signal_selection_changed().connect (mem_fun (chooser, &Widget::queue_draw));
+#endif
+ chooser.set_current_folder (dstdir);
+
+ Gtk::FileFilter video_filter;
+ Gtk::FileFilter matchall_filter;
+ video_filter.add_custom (FILE_FILTER_FILENAME, mem_fun(*this, &AddVideoDialog::on_video_filter));
+ video_filter.set_name (_("Video files"));
+
+ matchall_filter.add_pattern ("*.*");
+ matchall_filter.set_name (_("All files"));
+
+ chooser.add_filter (video_filter);
+ chooser.add_filter (matchall_filter);
+ chooser.set_select_multiple (false);
+
+ /* file import options */
+ import_combo.set_name ("PaddedButton");
+ import_combo.append_text(_("Reference From Current Location"));
+ import_combo.append_text(_("Hardlink or Copy to Session"));
+ import_combo.append_text(_("Transcode to Session"));
+ import_combo.set_active(2);
+
+ vbox = manage (new VBox);
+ vbox->pack_start (chooser, true, true, 0);
+ vbox->pack_start (import_combo, false, true, 4);
+
+ if (Config->get_video_server_docroot().size() > 0) {
+ notebook.append_page (*vbox, _("Browse Files"));
+ }
+
+ /* Global Options*/
+ Gtk::Label* l;
+ VBox* options_box = manage (new VBox);
+
+ l = manage (new Label (_("<b>Options</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_use_markup ();
+
+ options_box->pack_start (*l, false, true, 4);
+ options_box->pack_start (xjadeo_checkbox, false, true, 2);
+ options_box->pack_start (set_session_fps_checkbox, false, true, 2);
+
+ /* preview pane */
+ VBox* previewpane = manage (new VBox);
+ Gtk::Table *table = manage(new Table(4,2));
+
+ table->set_row_spacings(2);
+ table->set_col_spacings(4);
+
+ l = manage (new Label (_("<b>Video Information</b>"), Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER, false));
+ l->set_use_markup ();
+ table->attach (*l, 0, 2, 0, 1, FILL, FILL);
+ l = manage (new Label (_("Duration:"), Gtk::ALIGN_RIGHT, Gtk::ALIGN_CENTER, false));
+ table->attach (*l, 0, 1, 1, 2, FILL, FILL);
+ table->attach (pi_duration, 1, 2, 1, 2, FILL, FILL);
+ l = manage (new Label (_("Frame rate:"), Gtk::ALIGN_RIGHT, Gtk::ALIGN_CENTER, false));
+ table->attach (*l, 0, 1, 2, 3, FILL, FILL);
+ table->attach (pi_fps, 1, 2, 2, 3, FILL, FILL);
+ l = manage (new Label (_("Aspect Ratio:"), Gtk::ALIGN_RIGHT, Gtk::ALIGN_CENTER, false));
+ table->attach (*l, 0, 1, 3, 4, FILL, FILL);
+ table->attach (pi_aspect, 1, 2, 3, 4, FILL, FILL);
+
+ preview_image = manage(new Gtk::Image);
+
+ imgbuf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, PREVIEW_WIDTH, PREVIEW_HEIGHT);
+ imgbuf->fill(RGBA_TO_UINT(127,0,0,255));
+ preview_image->set(imgbuf);
+ seek_slider.set_draw_value(false);
+
+ hbox = manage (new HBox);
+ hbox->pack_start (*table, true, false);
+
+ Gtk::Alignment *al = manage(new Gtk::Alignment());
+ al->set_size_request(-1, 20);
+
+ previewpane->pack_start (*al, false, false);
+ previewpane->pack_start (*hbox, true, true, 6);
+ previewpane->pack_start (*preview_image, false, false);
+ previewpane->pack_start (seek_slider, false, false);
+
+ /* Overall layout */
+ hbox = manage (new HBox);
+ hbox->pack_start (notebook, true, true);
+ hbox->pack_start (*previewpane, false, false);
+
+ get_vbox()->set_spacing (4);
+ get_vbox()->pack_start (*hbox, true, true);
+ get_vbox()->pack_start (*options_box, false, false);
+
+
+ /* xjadeo checkbox */
+ if (ARDOUR_UI::instance()->video_timeline->found_xjadeo()
+ /* TODO xjadeo setup w/ xjremote */
+ && Config->get_video_server_docroot().size() > 0) {
+ xjadeo_checkbox.set_active(true); /* set in ardour_ui.cpp ?! */
+ } else {
+ xjadeo_checkbox.set_active(false);
+ xjadeo_checkbox.set_sensitive(false);
+ }
+
+ /* FPS checkbox */
+ set_session_fps_checkbox.set_active(true);
+
+ /* Buttons */
+ add_button (Stock::CANCEL, RESPONSE_CANCEL);
+ ok_button = add_button (Stock::OK, RESPONSE_ACCEPT);
+ //ok_button->set_sensitive(false);
+ set_action_ok(false);
+
+ /* connect signals after eveything has been initialized */
+ chooser.signal_selection_changed().connect (mem_fun (*this, &AddVideoDialog::file_selection_changed));
+ chooser.signal_file_activated().connect (mem_fun (*this, &AddVideoDialog::file_activated));
+ //chooser.signal_update_preview().connect(sigc::mem_fun(*this, &AddVideoDialog::update_preview));
+ notebook.signal_switch_page().connect (sigc::hide_return (sigc::hide (sigc::hide (sigc::mem_fun (*this, &AddVideoDialog::page_switch)))));
+ seek_slider.signal_value_changed().connect(sigc::mem_fun(*this, &AddVideoDialog::seek_preview));
+ harvid_reset.signal_clicked().connect (sigc::mem_fun (*this, &AddVideoDialog::harvid_load_docroot));
+
+ show_all_children ();
+}
+
+AddVideoDialog::~AddVideoDialog ()
+{
+}
+
+void
+AddVideoDialog::on_show ()
+{
+ Dialog::on_show ();
+}
+
+static bool check_video_file_extension(std::string file)
+{
+ const char* suffixes[] = {
+ ".avi" , ".AVI" ,
+ ".mov" , ".MOV" ,
+ ".ogg" , ".OGG" ,
+ ".ogv" , ".OGV" ,
+ ".mpg" , ".MPG" ,
+ ".mov" , ".MOV" ,
+ ".mp4" , ".MP4" ,
+ ".mkv" , ".MKV" ,
+ ".vob" , ".VOB" ,
+ ".asf" , ".ASF" ,
+ ".avs" , ".AVS" ,
+ ".dts" , ".DTS" ,
+ ".flv" , ".FLV" ,
+ ".m4v" , ".M4V" ,
+ ".matroska", ".MATROSKA",
+ ".h264" , ".H264" ,
+ ".dv" , ".DV" ,
+ ".dirac" , ".DIRAC" ,
+ ".webm" , ".WEBM" ,
+ };
+
+ for (size_t n = 0; n < sizeof(suffixes)/sizeof(suffixes[0]); ++n) {
+ if (file.rfind (suffixes[n]) == file.length() - strlen (suffixes[n])) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+AddVideoDialog::on_video_filter (const FileFilter::Info& filter_info)
+{
+ return check_video_file_extension(filter_info.filename);
+}
+
+std::string
+AddVideoDialog::file_name (bool &local_file)
+{
+ int n = notebook.get_current_page ();
+ if (n == 1) {
+ local_file = true;
+ return chooser.get_filename();
+ } else {
+ local_file = false;
+ Gtk::TreeModel::iterator iter = harvid_list_view.get_selection()->get_selected();
+ if(!iter) return "";
+ return (*iter)[harvid_list_columns.uri];
+ }
+}
+
+enum VtlImportOption
+AddVideoDialog::import_option ()
+{
+ int n = notebook.get_current_page ();
+ if (n == 0) { return VTL_IMPORT_NONE; }
+ int i = import_combo.get_active_row_number();
+ return static_cast<VtlImportOption>(i);
+}
+
+bool
+AddVideoDialog::launch_xjadeo ()
+{
+ return xjadeo_checkbox.get_active();
+}
+
+bool
+AddVideoDialog::auto_set_session_fps ()
+{
+ return set_session_fps_checkbox.get_active();
+}
+
+void
+AddVideoDialog::set_action_ok (bool yn)
+{
+ if (yn) {
+ ok_button->set_sensitive(true);
+ } else {
+ preview_path = "";
+ pi_duration.set_text("-");
+ pi_aspect.set_text("-");
+ pi_fps.set_text("-");
+ ok_button->set_sensitive(false);
+ imgbuf->fill(RGBA_TO_UINT(0,0,0,255));
+ video_draw_cross(imgbuf);
+ preview_image->set(imgbuf);
+ preview_image->show();
+ }
+}
+
+void
+AddVideoDialog::file_selection_changed ()
+{
+ if (chooser.get_filename().size() > 0) {
+ std::string path = chooser.get_filename();
+ bool ok =
+ check_video_file_extension(path)
+ && Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_REGULAR | Glib::FILE_TEST_IS_SYMLINK)
+ && !Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_DIR);
+ set_action_ok(ok);
+ if (ok) {
+ request_preview(video_map_path(Config->get_video_server_docroot(), path));
+ }
+ } else {
+ set_action_ok(false);
+ }
+}
+
+void
+AddVideoDialog::file_activated ()
+{
+ if (chooser.get_filename().size() > 0) {
+ std::string path = chooser.get_filename();
+ // TODO check docroot -> set import options
+ bool ok =
+ check_video_file_extension(path)
+ && Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_REGULAR | Glib::FILE_TEST_IS_SYMLINK)
+ && !Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_DIR);
+ if (ok) {
+ Gtk::Dialog::response(RESPONSE_ACCEPT);
+ }
+ }
+}
+
+/**** Tree List Interaction ***/
+
+void
+AddVideoDialog::harvid_list_view_selected () {
+ Gtk::TreeModel::iterator iter = harvid_list_view.get_selection()->get_selected();
+ // TODO check docroot -> set import options, xjadeo
+ if(!iter) {
+ set_action_ok(false);
+ return;
+ }
+ if ((std::string)((*iter)[harvid_list_columns.id]) == Stock::DIRECTORY.id) {
+ set_action_ok(false);
+ } else {
+ set_action_ok(true);
+ request_preview((*iter)[harvid_list_columns.uri]);
+ }
+}
+
+void
+AddVideoDialog::harvid_list_view_activated (const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn*) {
+ Gtk::TreeModel::iterator iter = harvid_list->get_iter(path);
+ if (!iter) return;
+ std::string type = (*iter)[harvid_list_columns.id];
+ std::string url = (*iter)[harvid_list_columns.uri];
+
+#if 0
+ printf ("A: %s %s %s\n",
+ ((std::string)((*iter)[harvid_list_columns.id])).c_str(),
+ ((std::string)((*iter)[harvid_list_columns.uri])).c_str(),
+ ((std::string)((*iter)[harvid_list_columns.filename])).c_str());
+#endif
+
+ if (type == Gtk::Stock::DIRECTORY.id) {
+ harvid_request(url.c_str());
+ } else {
+ Gtk::Dialog::response(RESPONSE_ACCEPT);
+ }
+}
+
+void
+AddVideoDialog::harvid_load_docroot() {
+ set_action_ok(false);
+
+ std::string video_server_url = Config->get_video_server_url();
+ char url[2048];
+ snprintf(url, sizeof(url), "%s%sindex/"
+ , video_server_url.c_str()
+ , (video_server_url.length()>0 && video_server_url.at(video_server_url.length()-1) == '/')?"":"/");
+ harvid_request(url);
+ harvid_initialized = true;
+}
+
+bool
+AddVideoDialog::page_switch() {
+ if (notebook.get_current_page () == 1) {
+ file_selection_changed();
+ return true;
+ }
+
+ if (harvid_initialized) {
+ harvid_list_view_selected();
+ } else {
+ harvid_load_docroot();
+ }
+ return true;
+}
+
+/**** Harvid HTTP interface ***/
+void
+AddVideoDialog::harvid_request(std::string u)
+{
+ char url[2048];
+ int status;
+ snprintf(url, sizeof(url), "%s?format=csv", u.c_str());
+
+ harvid_list->clear();
+
+ char *res = curl_http_get(url, &status);
+ if (status != 200) {
+ printf("request failed\n"); // XXX
+ harvid_path.set_text(" - request failed -");
+ free(res);
+ return;
+ }
+
+ /* add up-to-parent */
+ size_t se = u.find_last_of("/", u.size()-2);
+ size_t ss = u.find("/index/");
+ if (se != string::npos && ss != string::npos && se > ss) {
+ TreeModel::iterator new_row = harvid_list->append();
+ TreeModel::Row row = *new_row;
+ row[harvid_list_columns.id ] = Gtk::Stock::DIRECTORY.id;
+ row[harvid_list_columns.uri ] = u.substr(0, se + 1);
+ row[harvid_list_columns.filename] = X_("..");
+ }
+ if (se != string::npos) {
+ int plen;
+ std::string path = u.substr(ss + 6);
+ CURL *curl;
+ curl = curl_easy_init();
+ char *ue = curl_easy_unescape(curl, path.c_str(), path.length(), &plen);
+ harvid_path.set_text(std::string(ue));
+ curl_easy_cleanup(curl);
+ curl_free(ue);
+ } else {
+ harvid_path.set_text(" ??? ");
+ }
+
+ if (!res) return;
+
+ std::vector<std::vector<std::string> > lines;
+ ParseCSV(std::string(res), lines);
+ for (std::vector<std::vector<std::string> >::iterator i = lines.begin(); i != lines.end(); ++i) {
+ TreeModel::iterator new_row = harvid_list->append();
+ TreeModel::Row row = *new_row;
+
+ if (i->at(0) == X_("D")) {
+ row[harvid_list_columns.id ] = Gtk::Stock::DIRECTORY.id;
+ row[harvid_list_columns.uri ] = i->at(1).c_str();
+ row[harvid_list_columns.filename] = i->at(2).c_str();
+ } else {
+ row[harvid_list_columns.id ] = Gtk::Stock::MEDIA_PLAY.id;
+ row[harvid_list_columns.uri ] = i->at(2).c_str();
+ row[harvid_list_columns.filename] = i->at(3).c_str();
+ }
+ }
+
+ free(res);
+}
+
+void
+AddVideoDialog::seek_preview()
+{
+ if (preview_path.size() > 0)
+ request_preview(preview_path);
+}
+
+void
+AddVideoDialog::request_preview(std::string u)
+{
+ std::string video_server_url = Config->get_video_server_url();
+
+ double video_file_fps;
+ long long int video_duration;
+ double video_start_offset;
+ double video_aspect_ratio;
+
+ int clip_width = PREVIEW_WIDTH;
+ int clip_height = PREVIEW_HEIGHT;
+ int clip_xoff, clip_yoff;
+
+ if (!video_query_info(video_server_url, u,
+ video_file_fps, video_duration, video_start_offset, video_aspect_ratio))
+ {
+ printf("image preview info request failed\n");
+ // set_action_ok(false); // XXX only if docroot mismatch
+ preview_path = "";
+ pi_duration.set_text("-");
+ pi_aspect.set_text("-");
+ pi_fps.set_text("-");
+ return;
+ }
+
+ if ((PREVIEW_WIDTH / (double)PREVIEW_HEIGHT) > video_aspect_ratio ) {
+ clip_width = MIN(PREVIEW_WIDTH, rint(clip_height * video_aspect_ratio));
+ } else {
+ clip_height = MIN(PREVIEW_HEIGHT, rint(clip_width / video_aspect_ratio));
+ }
+
+ pi_duration.set_text(string_compose("%1 sec", video_duration / video_file_fps));
+ pi_aspect.set_text(string_compose("%1", video_aspect_ratio));
+ pi_fps.set_text(string_compose("%1 fps", video_file_fps));
+
+ clip_xoff = (PREVIEW_WIDTH - clip_width)/2;
+ clip_yoff = (PREVIEW_HEIGHT - clip_height)/2;
+
+ char url[2048];
+ snprintf(url, sizeof(url), "%s%s?frame=%lli&w=%d&h=%di&file=%s&format=rgb"
+ , video_server_url.c_str()
+ , (video_server_url.length()>0 && video_server_url.at(video_server_url.length()-1) == '/')?"":"/"
+ , (long long) (video_duration * seek_slider.get_value() / 1000.0)
+ , clip_width, clip_height, u.c_str());
+
+ char *data = curl_http_get(url, NULL);
+ if (!data) {
+ printf("image preview request failed %s\n", url);
+ imgbuf->fill(RGBA_TO_UINT(0,0,0,255));
+ video_draw_cross(imgbuf);
+ preview_path = "";
+ } else {
+ Glib::RefPtr<Gdk::Pixbuf> tmp;
+ tmp = Gdk::Pixbuf::create_from_data ((guint8*) data, Gdk::COLORSPACE_RGB, false, 8, clip_width, clip_height, clip_width*3);
+ if (clip_width != PREVIEW_WIDTH || clip_height != PREVIEW_HEIGHT) {
+ imgbuf->fill(RGBA_TO_UINT(0,0,0,255));
+ }
+ tmp->copy_area (0, 0, clip_width, clip_height, imgbuf, clip_xoff, clip_yoff);
+ preview_path = u;
+ free(data);
+ }
+ preview_image->set(imgbuf);
+ preview_image->show();
+}
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/add_video_dialog.h b/gtk2_ardour/add_video_dialog.h
new file mode 100644
index 0000000000..12409895ee
--- /dev/null
+++ b/gtk2_ardour/add_video_dialog.h
@@ -0,0 +1,113 @@
+/*
+ Copyright (C) 2010-2013 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#ifndef __gtk_ardour_add_video_dialog_h__
+#define __gtk_ardour_add_video_dialog_h__
+
+#include <string>
+
+#include <gtkmm.h>
+
+#include "ardour/types.h"
+#include "ardour/template_utils.h"
+#include <gtkmm/filechooserwidget.h>
+#include "ardour_dialog.h"
+
+enum VtlImportOption {
+ VTL_IMPORT_NONE = 0,
+ VTL_IMPORT_COPY = 1,
+ VTL_IMPORT_TRANSCODE = 2,
+};
+
+class AddVideoDialog : public ArdourDialog
+{
+ public:
+ AddVideoDialog (ARDOUR::Session*);
+ ~AddVideoDialog ();
+
+ std::string file_name (bool &local_file);
+ VtlImportOption import_option ();
+ bool launch_xjadeo ();
+ bool auto_set_session_fps ();
+
+ private:
+ void on_show ();
+ bool page_switch();
+ void set_action_ok(bool yn);
+
+ /* preview pane related */
+ void request_preview(std::string vpath);
+ void seek_preview();
+
+ Gtk::Image *preview_image;
+ Gtk::HScale seek_slider;
+ Glib::RefPtr<Gdk::Pixbuf> imgbuf;
+ std::string preview_path;
+ Gtk::Label pi_duration;
+ Gtk::Label pi_aspect;
+ Gtk::Label pi_fps;
+
+ /* file chooser related */
+ void file_selection_changed ();
+ void file_activated ();
+ bool on_video_filter (const Gtk::FileFilter::Info& filter_info);
+
+ Gtk::FileChooserWidget chooser;
+
+ Gtk::CheckButton xjadeo_checkbox;
+ Gtk::CheckButton set_session_fps_checkbox;
+ Gtk::ComboBoxText import_combo;
+ Gtk::Notebook notebook;
+ Gtk::Button *ok_button;
+
+ /* Harvid Browser related */
+ class HarvidColumns : public Gtk::TreeModel::ColumnRecord
+ {
+ public:
+ Gtk::TreeModelColumn<std::string> id;
+ Gtk::TreeModelColumn<std::string> uri;
+ Gtk::TreeModelColumn<std::string> filename;
+
+ HarvidColumns() {
+ add(id);
+ add(filename);
+ add(uri);
+ }
+ };
+
+ Gtk::CellRendererPixbuf pixBufRenderer;
+
+ void harvid_list_view_selected ();
+ void harvid_list_view_activated (const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn*);
+ void harvid_request(std::string u);
+ void harvid_load_docroot();
+
+ bool harvid_initialized;
+ Gtk::Label harvid_path;
+ Gtk::Button harvid_reset;
+ HarvidColumns harvid_list_columns;
+ Glib::RefPtr<Gtk::ListStore> harvid_list;
+ Gtk::TreeView harvid_list_view;
+};
+
+#endif /* __gtk_ardour_add_video_dialog_h__ */
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/ardour.menus.in b/gtk2_ardour/ardour.menus.in
index 4d018ae815..1e47016280 100644
--- a/gtk2_ardour/ardour.menus.in
+++ b/gtk2_ardour/ardour.menus.in
@@ -34,9 +34,20 @@
<separator/>
<menuitem action='addExistingAudioFiles'/>
<!--menuitem action='importFromSession'/-->
+#ifdef WITH_VIDEOTIMELINE
+ <menuitem action='OpenVideo'/>
+ <menu name='Video' action='Video'>
+ <menuitem action='StartVideoServer'/>
+ <menuitem action='StopVideoServer'/>
+ <menuitem action='VideoTimelineFlushCache'/>
+ </menu>
+#endif
<menu name='Export' action='Export'>
<menuitem action='ExportAudio'/>
<menuitem action='StemExport'/>
+#ifdef WITH_VIDEOTIMELINE
+ <menuitem action='ExportVideo'/>
+#endif
</menu>
<menu name='Cleanup' action='Cleanup'>
<menuitem action='CleanupUnused'/>
@@ -276,6 +287,9 @@
<menuitem action='snap-regions-to-grid'/>
<menuitem action='toggle-region-lock'/>
<menuitem action='toggle-region-lock-style'/>
+#ifdef WITH_VIDEOTIMELINE
+ <menuitem action='toggle-region-video-lock'/>
+#endif
<menuitem action='set-region-sync-position'/>
<menuitem action='remove-region-sync'/>
<menuitem action='nudge-forward'/>
@@ -427,6 +441,10 @@
<menuitem action="toggle-loop-punch-ruler"/>
<menuitem action="toggle-cd-marker-ruler"/>
<menuitem action="toggle-marker-ruler"/>
+#ifdef WITH_VIDEOTIMELINE
+ <separator/>
+ <menuitem action="toggle-video-ruler"/>
+#endif
</menu>
<menu action="ScrollMenu">
<menuitem action='scroll-tracks-down'/>
@@ -469,6 +487,9 @@
<menuitem action='ToggleSummary'/>
<menuitem action='ToggleGroupTabs'/>
<menuitem action='show-marker-lines'/>
+#ifdef WITH_VIDEOTIMELINE
+ <menuitem action='ToggleJadeo'/>
+#endif
</menu>
<menu name='JACK' action='JACK'>
<menuitem action='JACKDisconnect'/>
@@ -530,6 +551,10 @@
<menuitem action="toggle-marker-ruler"/>
<menuitem action="toggle-cd-marker-ruler"/>
<menuitem action="toggle-loop-punch-ruler"/>
+#ifdef WITH_VIDEOTIMELINE
+ <separator/>
+ <menuitem action="toggle-video-ruler"/>
+#endif
</popup>
<popup name='ProcessorMenu'>
@@ -625,6 +650,9 @@
<menuitem action='naturalize-region'/>
<menuitem action='toggle-region-lock'/>
<menuitem action='toggle-region-lock-style'/>
+#ifdef WITH_VIDEOTIMELINE
+ <menuitem action='toggle-region-video-lock'/>
+#endif
<menuitem action='snap-regions-to-grid'/>
<menuitem action='set-region-sync-position'/>
<menuitem action='remove-region-sync'/>
diff --git a/gtk2_ardour/ardour3_ui_default.conf b/gtk2_ardour/ardour3_ui_default.conf
index ce81062135..7a663cef57 100644
--- a/gtk2_ardour/ardour3_ui_default.conf
+++ b/gtk2_ardour/ardour3_ui_default.conf
@@ -138,6 +138,7 @@
<Option name="trim handle" value="1900ff44"/>
<Option name="verbose canvas cursor" value="fffd2ebc"/>
<Option name="vestigial frame" value="0000000f"/>
+ <Option name="video timeline bar" value="303030ff"/>
<Option name="region base" value="99a7b5a0"/>
<Option name="region area covered by another region" value="505050b0"/>
<Option name="waveform outline" value="0f0f0fc8"/>
diff --git a/gtk2_ardour/ardour_ui.cc b/gtk2_ardour/ardour_ui.cc
index 7b4b9663f0..238c530627 100644
--- a/gtk2_ardour/ardour_ui.cc
+++ b/gtk2_ardour/ardour_ui.cc
@@ -116,6 +116,13 @@ typedef uint64_t microseconds_t;
#include "time_axis_view_item.h"
#include "utils.h"
#include "window_proxy.h"
+#ifdef WITH_VIDEOTIMELINE
+#include "video_server_dialog.h"
+#include "add_video_dialog.h"
+#include "transcode_video_dialog.h"
+#include "video_copy_dialog.h"
+#include "system_exec.h" /* to launch video-server */
+#endif
#include "i18n.h"
@@ -195,6 +202,10 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir)
session_selector_window = 0;
last_key_press_time = 0;
add_route_dialog = 0;
+#ifdef WITH_VIDEOTIMELINE
+ add_video_dialog = 0;
+ video_server_process = 0;
+#endif
route_params = 0;
bundle_manager = 0;
rc_option_editor = 0;
@@ -461,6 +472,10 @@ ARDOUR_UI::~ARDOUR_UI ()
delete editor;
delete mixer;
delete add_route_dialog;
+#ifdef WITH_VIDEOTIMELINE
+ if (add_video_dialog) delete add_video_dialog;
+ stop_video_server();
+#endif
}
void
@@ -769,6 +784,12 @@ ARDOUR_UI::idle_finish ()
void
ARDOUR_UI::finish()
{
+#ifdef WITH_VIDEOTIMELINE
+ /* close video-monitor & pending requests
+ * would better be done in ~Editor() but that is not called..
+ */
+ ARDOUR_UI::instance()->video_timeline->close_session();
+#endif
if (_session) {
if (_session->dirty()) {
@@ -805,6 +826,11 @@ If you still wish to quit, please use the\n\n\
point_zero_one_second_connection.disconnect();
}
+#ifdef WITH_VIDEOTIMELINE
+ delete ARDOUR_UI::instance()->video_timeline;
+ stop_video_server();
+#endif
+
/* Save state before deleting the session, as that causes some
windows to be destroyed before their visible state can be
saved.
@@ -2466,7 +2492,11 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri
* treat a non-dirty session this way, so that it stays visible
* as we bring up the new session dialog.
*/
-
+#ifdef WITH_VIDEOTIMELINE
+ if (_session && ARDOUR_UI::instance()->video_timeline) {
+ ARDOUR_UI::instance()->video_timeline->close_session();
+ }
+#endif
if (_session && _session->dirty()) {
if (unload_session (false)) {
/* unload cancelled by user */
@@ -2647,6 +2677,9 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri
void
ARDOUR_UI::close_session()
{
+#ifdef WITH_VIDEOTIMELINE
+ ARDOUR_UI::instance()->video_timeline->close_session();
+#endif
if (!check_audioengine()) {
return;
}
@@ -3234,6 +3267,229 @@ ARDOUR_UI::add_route (Gtk::Window* float_window)
/* idle connection will end at scope end */
}
+#ifdef WITH_VIDEOTIMELINE
+void
+ARDOUR_UI::stop_video_server (bool ask_confirm)
+{
+ if (!video_server_process && ask_confirm) {
+ warning << _("Video-Server was not launched by Ardour. The request to stop it is ignored.") << endmsg;
+ }
+ if (video_server_process) {
+ if(ask_confirm) {
+ ArdourDialog confirm (_("Stop Video-Server"), true);
+ Label m (_("Do you really want to stop the Video Server?"));
+ confirm.get_vbox()->pack_start (m, true, true);
+ confirm.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ confirm.add_button (_("Yes, Stop It"), Gtk::RESPONSE_ACCEPT);
+ confirm.show_all ();
+ if (confirm.run() == RESPONSE_CANCEL) {
+ return;
+ }
+ }
+ delete video_server_process;
+ video_server_process =0;
+ }
+}
+
+void
+ARDOUR_UI::start_video_server_menu (Gtk::Window* float_window)
+{
+ ARDOUR_UI::start_video_server( float_window, true);
+}
+
+bool
+ARDOUR_UI::start_video_server (Gtk::Window* float_window, bool popup_msg)
+{
+ if (!_session) {
+ return false;
+ }
+ if (popup_msg) {
+ if (ARDOUR_UI::instance()->video_timeline->check_server()) {
+ if (video_server_process) {
+ popup_error(_("The Video Server is already started."));
+ } else {
+ popup_error(_("An external Video Server is configured and can be reached. Not starting a new instance."));
+ }
+ }
+ }
+
+ int firsttime = 0;
+ while (!ARDOUR_UI::instance()->video_timeline->check_server()) {
+ if (firsttime++) {
+ warning << _("Could not connect to the Video Server. Start it or configure its access URL in Edit -> Preferences.") << endmsg;
+ }
+ VideoServerDialog *video_server_dialog = new VideoServerDialog (_session);
+ if (float_window) {
+ video_server_dialog->set_transient_for (*float_window);
+ }
+
+ if (!Config->get_show_video_server_dialog() && firsttime < 2) {
+ video_server_dialog->hide();
+ } else {
+ ResponseType r = (ResponseType) video_server_dialog->run ();
+ video_server_dialog->hide();
+ if (r != RESPONSE_ACCEPT) { return false; }
+ if (video_server_dialog->show_again()) {
+ Config->set_show_video_server_dialog(false);
+ }
+ }
+
+ std::string icsd_exec = video_server_dialog->get_exec_path();
+ std::string icsd_docroot = video_server_dialog->get_docroot();
+ if (icsd_docroot.empty()) {icsd_docroot = "/";}
+
+ struct stat sb;
+ if (!lstat (icsd_docroot.c_str(), &sb) == 0 || !S_ISDIR(sb.st_mode)) {
+ warning << _("Specified docroot is not an existing directory.") << endmsg;
+ continue;
+ }
+ if ( (!lstat (icsd_exec.c_str(), &sb) == 0)
+ || (sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0 ) {
+ warning << _("Given Video Server is not an executable file.") << endmsg;
+ continue;
+ }
+
+ char **argp;
+ argp=(char**) calloc(9,sizeof(char*));
+ argp[0] = strdup(icsd_exec.c_str());
+ argp[1] = strdup("-P");
+ argp[2] = (char*) calloc(16,sizeof(char)); snprintf(argp[2], 16, "%s", video_server_dialog->get_listenaddr().c_str());
+ argp[3] = strdup("-p");
+ argp[4] = (char*) calloc(6,sizeof(char)); snprintf(argp[4], 6, "%i", video_server_dialog->get_listenport());
+ argp[5] = strdup("-C");
+ argp[6] = (char*) calloc(6,sizeof(char)); snprintf(argp[6], 6, "%i", video_server_dialog->get_cachesize());
+ argp[7] = strdup(icsd_docroot.c_str());
+ argp[8] = 0;
+ stop_video_server();
+
+ std::ostringstream osstream;
+ osstream << "http://localhost:" << video_server_dialog->get_listenport() << "/";
+ Config->set_video_server_url(osstream.str());
+ Config->set_video_server_docroot(icsd_docroot);
+ video_server_process = new SystemExec(icsd_exec, argp);
+ video_server_process->start();
+ sleep(1);
+ }
+ return true;
+}
+
+void
+ARDOUR_UI::add_video (Gtk::Window* float_window)
+{
+ if (!_session) {
+ return;
+ }
+
+ if (!start_video_server(float_window, false)) {
+ warning << _("Could not connect to the Video Server. Start it or configure its access URL in Edit -> Preferences.") << endmsg;
+ return;
+ }
+
+ if (add_video_dialog == 0) {
+ add_video_dialog = new AddVideoDialog (_session);
+ if (float_window) {
+ add_video_dialog->set_transient_for (*float_window);
+ }
+ }
+
+ if (add_video_dialog->is_visible()) {
+ /* we're already doing this */
+ return;
+ }
+ ResponseType r = (ResponseType) add_video_dialog->run ();
+ add_video_dialog->hide();
+ if (r != RESPONSE_ACCEPT) { return; }
+
+ bool local_file;
+ std::string path = add_video_dialog->file_name(local_file);
+ bool auto_set_session_fps = add_video_dialog->auto_set_session_fps();
+
+ if (local_file && !Glib::file_test(path, Glib::FILE_TEST_EXISTS)) {
+ warning << string_compose(_("could not open %1"), path) << endmsg;
+ return;
+ }
+ if (!local_file && path.length() == 0) {
+ warning << _("no video-file selected") << endmsg;
+ return;
+ }
+
+ switch (add_video_dialog->import_option()) {
+ case VTL_IMPORT_COPY:
+ {
+ VideoCopyDialog *video_copy_dialog;
+ video_copy_dialog = new VideoCopyDialog(_session, path);
+ //video_copy_dialog->setup_non_interactive_copy();
+ ResponseType r = (ResponseType) video_copy_dialog->run ();
+ video_copy_dialog->hide();
+ if (r != RESPONSE_ACCEPT) { return; }
+ path = video_copy_dialog->get_filename();
+ delete video_copy_dialog;
+ }
+ break;
+ case VTL_IMPORT_TRANSCODE:
+ {
+ TranscodeVideoDialog *transcode_video_dialog;
+ transcode_video_dialog = new TranscodeVideoDialog (_session, path);
+ ResponseType r = (ResponseType) transcode_video_dialog->run ();
+ transcode_video_dialog->hide();
+ if (r != RESPONSE_ACCEPT) { return; }
+ path = transcode_video_dialog->get_filename();
+ if (!transcode_video_dialog->get_audiofile().empty()) {
+ editor->embed_audio_from_video(transcode_video_dialog->get_audiofile());
+ }
+ delete transcode_video_dialog;
+ }
+ break;
+ default:
+ case VTL_IMPORT_NONE:
+ break;
+ }
+
+ if (path.empty()) {
+ /* may have been overriden by 'audio only import'
+ * in transcode_video_dialog */
+ path = add_video_dialog->file_name(local_file);;
+ }
+
+ /* strip _session->session_directory().video_path() from video file if possible */
+ if (local_file && !path.compare(0, _session->session_directory().video_path().size(), _session->session_directory().video_path())) {
+ path=path.substr(_session->session_directory().video_path().size());
+ if (path.at(0) == G_DIR_SEPARATOR) {
+ path=path.substr(1);
+ }
+ }
+
+ video_timeline->set_update_session_fps(auto_set_session_fps);
+ if (video_timeline->video_file_info(path, local_file)) {
+ XMLNode* node = new XMLNode(X_("Videotimeline"));
+ node->add_property (X_("Filename"), path);
+ node->add_property (X_("AutoFPS"), auto_set_session_fps?X_("1"):X_("0"));
+ node->add_property (X_("LocalFile"), local_file?X_("1"):X_("0"));
+ _session->add_extra_xml (*node);
+ _session->set_dirty ();
+
+ if (add_video_dialog->launch_xjadeo() && local_file) {
+ editor->set_xjadeo_sensitive(true);
+ editor->toggle_xjadeo_proc(1);
+ } else {
+ editor->toggle_xjadeo_proc(0);
+ }
+ editor->toggle_ruler_video(true);
+ }
+}
+
+void
+ARDOUR_UI::flush_videotimeline_cache (bool localcacheonly)
+{
+ if (localcacheonly) {
+ video_timeline->vmon_update();
+ } else {
+ video_timeline->flush_cache();
+ }
+ editor->queue_visual_videotimeline_update();
+}
+#endif
+
XMLNode*
ARDOUR_UI::mixer_settings () const
{
@@ -3502,6 +3758,9 @@ ARDOUR_UI::update_transport_clocks (framepos_t pos)
if (big_clock_window->get()) {
big_clock->set (pos);
}
+#ifdef WITH_VIDEOTIMELINE
+ ARDOUR_UI::instance()->video_timeline->manual_seek_video_monitor(pos);
+#endif
}
diff --git a/gtk2_ardour/ardour_ui.h b/gtk2_ardour/ardour_ui.h
index bb5e9940bc..0716813436 100644
--- a/gtk2_ardour/ardour_ui.h
+++ b/gtk2_ardour/ardour_ui.h
@@ -65,6 +65,10 @@
#include "ardour/plugin.h"
#include "ardour/session_handle.h"
+#ifdef WITH_VIDEOTIMELINE
+#include "video_timeline.h"
+#endif
+
#include "ardour_dialog.h"
#include "ardour_button.h"
#include "editing.h"
@@ -75,6 +79,11 @@
class About;
class AddRouteDialog;
+#ifdef WITH_VIDEOTIMELINE
+class AddVideoDialog;
+class VideoTimeLine;
+class SystemExec;
+#endif
class ArdourStartup;
class ArdourKeyboard;
class AudioClock;
@@ -203,6 +212,10 @@ class ARDOUR_UI : public Gtkmm2ext::UI, public ARDOUR::SessionHandlePtr
TimeInfoBox* time_info_box;
+#ifdef WITH_VIDEOTIMELINE
+ VideoTimeLine *video_timeline;
+#endif
+
void store_clock_modes ();
void restore_clock_modes ();
void reset_main_clocks ();
@@ -212,6 +225,13 @@ class ARDOUR_UI : public Gtkmm2ext::UI, public ARDOUR::SessionHandlePtr
void add_route (Gtk::Window* float_window);
void add_routes_part_two ();
void add_routes_thread ();
+#ifdef WITH_VIDEOTIMELINE
+ void add_video (Gtk::Window* float_window);
+ void start_video_server_menu (Gtk::Window* float_window);
+ bool start_video_server (Gtk::Window* float_window, bool popup_msg);
+ void stop_video_server (bool ask_confirm=false);
+ void flush_videotimeline_cache (bool localcacheonly=false);
+#endif
void session_add_audio_track (
int input_channels,
@@ -606,6 +626,12 @@ class ARDOUR_UI : public Gtkmm2ext::UI, public ARDOUR::SessionHandlePtr
AddRouteDialog *add_route_dialog;
+#ifdef WITH_VIDEOTIMELINE
+ /* video dialog */
+ AddVideoDialog *add_video_dialog;
+ SystemExec *video_server_process;
+#endif
+
/* Keyboard Handling */
ArdourKeyboard* keyboard;
diff --git a/gtk2_ardour/ardour_ui_dialogs.cc b/gtk2_ardour/ardour_ui_dialogs.cc
index 02c72d7230..2789c37464 100644
--- a/gtk2_ardour/ardour_ui_dialogs.cc
+++ b/gtk2_ardour/ardour_ui_dialogs.cc
@@ -121,6 +121,9 @@ ARDOUR_UI::set_session (Session *s)
secondary_clock->set_session (s);
big_clock->set_session (s);
time_info_box->set_session (s);
+#ifdef WITH_VIDEOTIMELINE
+ video_timeline->set_session (s);
+#endif
/* sensitize menu bar options that are now valid */
@@ -212,6 +215,11 @@ ARDOUR_UI::set_session (Session *s)
int
ARDOUR_UI::unload_session (bool hide_stuff)
{
+#ifdef WITH_VIDEOTIMELINE
+ if (_session) {
+ ARDOUR_UI::instance()->video_timeline->close_session();
+ }
+#endif
if (_session && _session->dirty()) {
std::vector<std::string> actions;
actions.push_back (_("Don't close"));
diff --git a/gtk2_ardour/ardour_ui_ed.cc b/gtk2_ardour/ardour_ui_ed.cc
index c9ede29f30..6228fcb4ba 100644
--- a/gtk2_ardour/ardour_ui_ed.cc
+++ b/gtk2_ardour/ardour_ui_ed.cc
@@ -132,6 +132,26 @@ ARDOUR_UI::install_actions ()
ActionManager::session_sensitive_actions.push_back (act);
ActionManager::write_sensitive_actions.push_back (act);
+#ifdef WITH_VIDEOTIMELINE
+ act = ActionManager::register_action (main_actions, X_("OpenVideo"), _("Open Video"),
+ sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::add_video), (Gtk::Window*) 0));
+ ActionManager::session_sensitive_actions.push_back (act);
+ act = ActionManager::register_action (main_actions, X_("ExportVideo"), _("Export To Video File"),
+ sigc::mem_fun (*editor, &PublicEditor::export_video));
+ ActionManager::session_sensitive_actions.push_back (act);
+ act = ActionManager::register_action (main_actions, X_("Video"), _("Video maintenance"));
+ ActionManager::session_sensitive_actions.push_back (act);
+ act = ActionManager::register_action (main_actions, X_("StartVideoServer"), _("Start Video Server"),
+ sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::start_video_server_menu), (Gtk::Window*) 0));
+ ActionManager::session_sensitive_actions.push_back (act);
+ act = ActionManager::register_action (main_actions, X_("StopVideoServer"), _("Stop Video Server"),
+ sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::stop_video_server), true ));
+ ActionManager::session_sensitive_actions.push_back (act);
+ act = ActionManager::register_action (main_actions, X_("VideoTimelineFlushCache"), _("Reload video-files"),
+ sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::flush_videotimeline_cache), false));
+ ActionManager::session_sensitive_actions.push_back (act);
+#endif
+
#ifdef WITH_CMT
std::string anicomp_file_path;
diff --git a/gtk2_ardour/ardour_ui_options.cc b/gtk2_ardour/ardour_ui_options.cc
index 0af27c6bf3..9b7f8fb178 100644
--- a/gtk2_ardour/ardour_ui_options.cc
+++ b/gtk2_ardour/ardour_ui_options.cc
@@ -382,6 +382,9 @@ ARDOUR_UI::parameter_changed (std::string p)
synchronize_sync_source_and_video_pullup ();
reset_main_clocks ();
+#ifdef WITH_VIDEOTIMELINE
+ editor->queue_visual_videotimeline_update();
+#endif
} else if (p == "sync-source") {
diff --git a/gtk2_ardour/canvas_vars.h b/gtk2_ardour/canvas_vars.h
index 53fe2a5440..33a48f3ec6 100644
--- a/gtk2_ardour/canvas_vars.h
+++ b/gtk2_ardour/canvas_vars.h
@@ -151,6 +151,9 @@ CANVAS_VARIABLE(canvasvar_TrimHandleLocked, "trim handle locked")
CANVAS_VARIABLE(canvasvar_TrimHandle, "trim handle")
CANVAS_VARIABLE(canvasvar_VerboseCanvasCursor, "verbose canvas cursor")
CANVAS_VARIABLE(canvasvar_VestigialFrame, "vestigial frame")
+#ifdef WITH_VIDEOTIMELINE
+CANVAS_VARIABLE(canvasvar_VideoBar, "video timeline bar")
+#endif
CANVAS_VARIABLE(canvasvar_FrameBase, "region base")
CANVAS_VARIABLE(canvasvar_CoveredRegion, "region area covered by another region")
CANVAS_VARIABLE(canvasvar_WaveForm, "waveform outline")
diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc
index 88d60a15cf..4f3390e752 100644
--- a/gtk2_ardour/editor.cc
+++ b/gtk2_ardour/editor.cc
@@ -246,6 +246,9 @@ Editor::Editor ()
, range_mark_label (_("Range Markers"))
, transport_mark_label (_("Loop/Punch Ranges"))
, cd_mark_label (_("CD Markers"))
+#ifdef WITH_VIDEOTIMELINE
+ , videotl_label (_("Video Timeline"))
+#endif
, edit_packer (4, 4, true)
/* the values here don't matter: layout widgets
@@ -437,6 +440,16 @@ Editor::Editor ()
cd_mark_label.hide();
cd_mark_label.set_no_show_all();
+#ifdef WITH_VIDEOTIMELINE
+ videotl_bar_height = 4;
+ videotl_label.set_name ("EditorRulerLabel");
+ videotl_label.set_size_request (-1, (int)timebar_height * videotl_bar_height);
+ videotl_label.set_alignment (1.0, 0.5);
+ videotl_label.set_padding (5,0);
+ videotl_label.hide();
+ videotl_label.set_no_show_all();
+#endif
+
range_mark_label.set_name ("EditorRulerLabel");
range_mark_label.set_size_request (-1, (int)timebar_height);
range_mark_label.set_alignment (1.0, 0.5);
@@ -4280,6 +4293,21 @@ Editor::set_frames_per_unit (double fpu)
instant_save ();
}
+#ifdef WITH_VIDEOTIMELINE
+void
+Editor::queue_visual_videotimeline_update ()
+{
+ /* TODO:
+ * pending_visual_change.add (VisualChange::VideoTimeline);
+ * or maybe even more specific: which videotimeline-image
+ * currently it calls update_video_timeline() to update
+ * _all outdated_ images on the video-timeline.
+ * see 'exposeimg()' in video_image_frame.cc
+ */
+ ensure_visual_change_idle_handler ();
+}
+#endif
+
void
Editor::ensure_visual_change_idle_handler ()
{
@@ -4329,9 +4357,17 @@ Editor::idle_visual_changer ()
current_bbt_points_begin, current_bbt_points_end);
update_tempo_based_rulers (current_bbt_points_begin, current_bbt_points_end);
}
+
+#ifdef WITH_VIDEOTIMELINE
+ if (p & VisualChange::ZoomLevel) {
+ update_video_timeline();
+ }
+#endif
+
if (p & VisualChange::TimeOrigin) {
set_horizontal_position (pending_visual_change.time_origin / frames_per_unit);
}
+
if (p & VisualChange::YOrigin) {
vertical_adjustment.set_value (pending_visual_change.y_origin);
}
@@ -4341,6 +4377,11 @@ Editor::idle_visual_changer ()
update_fixed_rulers ();
redisplay_tempo (true);
}
+#ifdef WITH_VIDEOTIMELINE
+ if (!(p & VisualChange::ZoomLevel)) {
+ update_video_timeline();
+ }
+#endif
_summary->set_overlays_dirty ();
diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h
index e95be847cd..081d6d5c3c 100644
--- a/gtk2_ardour/editor.h
+++ b/gtk2_ardour/editor.h
@@ -783,6 +783,9 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
ruler_time_range_marker = 7,
ruler_time_transport_marker = 8,
ruler_time_cd_marker = 9,
+#ifdef WITH_VIDEOTIMELINE
+ ruler_video_timeline = 10,
+#endif
};
static GtkCustomMetric ruler_metrics[4];
@@ -918,6 +921,23 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
Gtk::Label transport_mark_label;
Gtk::Label cd_mark_label;
+#ifdef WITH_VIDEOTIMELINE
+ ArdourCanvas::SimpleRect* videotl_bar;
+ Gtk::Label videotl_label;
+ ArdourCanvas::Group* videotl_bar_group;
+ ArdourCanvas::Group* videotl_group;
+ Glib::RefPtr<Gtk::ToggleAction> ruler_video_action;
+ Glib::RefPtr<Gtk::ToggleAction> xjadeo_proc_action;
+ void set_xjadeo_proc ();
+ void toggle_xjadeo_proc (int state=-1);
+ void set_xjadeo_sensitive (bool onoff);
+ void toggle_ruler_video (bool onoff) {ruler_video_action->set_active(onoff);}
+ int videotl_bar_height; /* in units of timebar_height; default: 4 */
+ int get_videotl_bar_height () const { return videotl_bar_height; }
+ void export_video ();
+ void toggle_region_video_lock ();
+#endif
+
Gtk::VBox time_button_vbox;
Gtk::HBox time_button_hbox;
@@ -1427,6 +1447,16 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
bool canvas_range_marker_bar_event (GdkEvent* event, ArdourCanvas::Item*);
bool canvas_transport_marker_bar_event (GdkEvent* event, ArdourCanvas::Item*);
bool canvas_cd_marker_bar_event (GdkEvent* event, ArdourCanvas::Item*);
+#ifdef WITH_VIDEOTIMELINE
+ bool canvas_videotl_bar_event (GdkEvent* event, ArdourCanvas::Item*);
+ void update_video_timeline (bool flush = false);
+ void set_video_timeline_height (const int);
+ bool is_video_timeline_locked ();
+ void toggle_video_timeline_locked ();
+ void set_video_timeline_locked (const bool);
+ void queue_visual_videotimeline_update ();
+ void embed_audio_from_video (std::string);
+#endif
bool canvas_imageframe_item_view_event(GdkEvent* event, ArdourCanvas::Item*,ImageFrameView*);
bool canvas_imageframe_view_event(GdkEvent* event, ArdourCanvas::Item*,ImageFrameTimeAxis*);
@@ -2145,6 +2175,9 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
friend class RegionCreateDrag;
friend class RegionMotionDrag;
friend class RegionInsertDrag;
+#ifdef WITH_VIDEOTIMELINE
+ friend class VideoTimeLineDrag;
+#endif
friend class EditorSummary;
friend class EditorGroupTabs;
diff --git a/gtk2_ardour/editor_actions.cc b/gtk2_ardour/editor_actions.cc
index 54c36218f0..a8c381cd5f 100644
--- a/gtk2_ardour/editor_actions.cc
+++ b/gtk2_ardour/editor_actions.cc
@@ -543,6 +543,11 @@ Editor::register_actions ()
ruler_samples_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (ruler_actions, X_("toggle-samples-ruler"), _("Samples"), sigc::bind (sigc::mem_fun(*this, &Editor::toggle_ruler_visibility), ruler_metric_samples)));
ruler_timecode_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (ruler_actions, X_("toggle-timecode-ruler"), _("Timecode"), sigc::bind (sigc::mem_fun(*this, &Editor::toggle_ruler_visibility), ruler_metric_timecode)));
ruler_minsec_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (ruler_actions, X_("toggle-minsec-ruler"), _("Min:Sec"), sigc::bind (sigc::mem_fun(*this, &Editor::toggle_ruler_visibility), ruler_metric_minsec)));
+#ifdef WITH_VIDEOTIMELINE
+ ruler_video_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (ruler_actions, X_("toggle-video-ruler"), _("Video"), sigc::bind (sigc::mem_fun(*this, &Editor::toggle_ruler_visibility), ruler_video_timeline)));
+ xjadeo_proc_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (editor_actions, X_("ToggleJadeo"), _("Show Video Monitor"), sigc::mem_fun (*this, &Editor::set_xjadeo_proc)));
+
+#endif
/* set defaults here */
@@ -553,6 +558,11 @@ Editor::register_actions ()
ruler_range_action->set_active (true);
ruler_loop_punch_action->set_active (true);
ruler_loop_punch_action->set_active (true);
+#ifdef WITH_VIDEOTIMELINE
+ ruler_video_action->set_active (false);
+ xjadeo_proc_action->set_active (false);
+ xjadeo_proc_action->set_sensitive (false);
+#endif
if (Profile->get_sae()) {
ruler_bbt_action->set_active (true);
ruler_cd_marker_action->set_active (false);
@@ -708,6 +718,11 @@ Editor::toggle_ruler_visibility (RulerType rt)
case ruler_time_cd_marker:
action = "toggle-cd-marker-ruler";
break;
+#ifdef WITH_VIDEOTIMELINE
+ case ruler_video_timeline:
+ action = "toggle-video-ruler";
+ break;
+#endif
}
Glib::RefPtr<Action> act = ActionManager::get_action (X_("Rulers"), action);
@@ -739,6 +754,39 @@ Editor::set_group_tabs ()
}
}
+#ifdef WITH_VIDEOTIMELINE
+void
+Editor::set_xjadeo_sensitive (bool onoff)
+{
+ xjadeo_proc_action->set_sensitive(onoff);
+}
+void
+Editor::toggle_xjadeo_proc (int state)
+{
+ switch(state) {
+ case 1:
+ xjadeo_proc_action->set_active(true);
+ break;
+ case 0:
+ xjadeo_proc_action->set_active(false);
+ break;
+ default:
+ xjadeo_proc_action->set_active(!xjadeo_proc_action->get_active());
+ break;
+ }
+}
+
+void
+Editor::set_xjadeo_proc ()
+{
+ if (xjadeo_proc_action->get_active()) {
+ ARDOUR_UI::instance()->video_timeline->open_video_monitor();
+ } else {
+ ARDOUR_UI::instance()->video_timeline->close_video_monitor();
+ }
+}
+#endif
+
void
Editor::toggle_measure_visibility ()
{
@@ -1575,6 +1623,10 @@ Editor::register_region_actions ()
/* Toggle `locked' status of selected regions */
toggle_reg_sens (_region_actions, "toggle-region-lock", _("Lock"), sigc::mem_fun(*this, &Editor::toggle_region_lock));
+#ifdef WITH_VIDEOTIMELINE
+ toggle_reg_sens (_region_actions, "toggle-region-video-lock", _("Lock to Video"), sigc::mem_fun(*this, &Editor::toggle_region_video_lock));
+#endif
+
toggle_reg_sens (
_region_actions,
"toggle-region-lock-style",
diff --git a/gtk2_ardour/editor_canvas.cc b/gtk2_ardour/editor_canvas.cc
index c0656ba90a..ec5f4c50e5 100644
--- a/gtk2_ardour/editor_canvas.cc
+++ b/gtk2_ardour/editor_canvas.cc
@@ -51,6 +51,7 @@
#include "region_view.h"
#include "editor_group_tabs.h"
#include "editor_summary.h"
+#include "video_timeline.h"
#include "keyboard.h"
#include "editor_cursors.h"
#include "mouse_cursors.h"
@@ -186,6 +187,19 @@ Editor::initialize_canvas ()
cd_marker_bar->property_outline_pixels() = 1;
cd_marker_bar->property_outline_what() = 0x8;
+#ifdef WITH_VIDEOTIMELINE
+ videotl_bar_group = new ArdourCanvas::Group (*track_canvas->root ());
+ if (Profile->get_sae()) {
+ videotl_bar = new ArdourCanvas::SimpleRect (*videotl_bar_group, 0.0, 0.0, phys_width, (timebar_height * videotl_bar_height - 1));
+ videotl_bar->property_outline_pixels() = 1;
+ } else {
+ videotl_bar = new ArdourCanvas::SimpleRect (*videotl_bar_group, 0.0, 0.0, phys_width, (timebar_height * videotl_bar_height));
+ videotl_bar->property_outline_pixels() = 0;
+ }
+ videotl_bar->property_outline_what() = (0x1 | 0x8);
+ ARDOUR_UI::instance()->video_timeline = new VideoTimeLine(this, videotl_bar_group, (timebar_height * videotl_bar_height));
+#endif
+
timebar_group = new ArdourCanvas::Group (*track_canvas->root(), 0.0, 0.0);
cursor_group = new ArdourCanvas::Group (*track_canvas->root(), 0.0, 0.0);
@@ -195,6 +209,9 @@ Editor::initialize_canvas ()
transport_marker_group = new ArdourCanvas::Group (*timebar_group, 0.0, timebar_height * 2.0);
marker_group = new ArdourCanvas::Group (*timebar_group, 0.0, timebar_height);
cd_marker_group = new ArdourCanvas::Group (*timebar_group, 0.0, 0.0);
+#ifdef WITH_VIDEOTIMELINE
+ videotl_group = new ArdourCanvas::Group (*timebar_group, 0.0, 0.0);
+#endif
cd_marker_bar_drag_rect = new ArdourCanvas::SimpleRect (*cd_marker_group, 0.0, 0.0, 100, timebar_height);
cd_marker_bar_drag_rect->property_outline_pixels() = 0;
@@ -239,6 +256,10 @@ Editor::initialize_canvas ()
meter_bar->signal_event().connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_meter_bar_event), meter_bar));
marker_bar->signal_event().connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_marker_bar_event), marker_bar));
cd_marker_bar->signal_event().connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_cd_marker_bar_event), cd_marker_bar));
+#ifdef WITH_VIDEOTIMELINE
+ videotl_bar_group->signal_event().connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_videotl_bar_event), videotl_bar));
+ //videotl_bar->signal_event().connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_videotl_bar_event), videotl_bar));
+#endif
range_marker_bar->signal_event().connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_range_marker_bar_event), range_marker_bar));
transport_marker_bar->signal_event().connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_transport_marker_bar_event), transport_marker_bar));
@@ -808,6 +829,10 @@ Editor::set_horizontal_position (double p)
_summary->set_overlays_dirty ();
}
+#ifdef WITH_VIDEOTIMELINE
+ update_video_timeline();
+#endif
+
HorizontalPositionChanged (); /* EMIT SIGNAL */
#ifndef GTKOSX
@@ -818,7 +843,6 @@ Editor::set_horizontal_position (double p)
}
}
#endif
-
}
void
@@ -858,6 +882,11 @@ Editor::color_handler()
cd_marker_bar->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_CDMarkerBar.get();
cd_marker_bar->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_MarkerBarSeparator.get();
+#ifdef WITH_VIDEOTIMELINE
+ videotl_bar->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_VideoBar.get();
+ videotl_bar->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_MarkerBarSeparator.get();
+#endif
+
range_marker_bar->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeMarkerBar.get();
range_marker_bar->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_MarkerBarSeparator.get();
diff --git a/gtk2_ardour/editor_canvas_events.cc b/gtk2_ardour/editor_canvas_events.cc
index 778d116797..9d092ebc8f 100644
--- a/gtk2_ardour/editor_canvas_events.cc
+++ b/gtk2_ardour/editor_canvas_events.cc
@@ -947,6 +947,14 @@ Editor::canvas_cd_marker_bar_event (GdkEvent *event, ArdourCanvas::Item* item)
return typed_event (item, event, CdMarkerBarItem);
}
+#ifdef WITH_VIDEOTIMELINE
+bool
+Editor::canvas_videotl_bar_event (GdkEvent *event, ArdourCanvas::Item* item)
+{
+ return typed_event (item, event, VideoBarItem);
+}
+#endif
+
bool
Editor::canvas_tempo_marker_event (GdkEvent *event, ArdourCanvas::Item* item, TempoMarker* /*marker*/)
{
diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc
index 5d8a4cc672..9591de95de 100644
--- a/gtk2_ardour/editor_drag.cc
+++ b/gtk2_ardour/editor_drag.cc
@@ -658,7 +658,11 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
RegionView* rv = i->view;
- if (rv->region()->locked()) {
+ if (rv->region()->locked()
+#ifdef WITH_VIDEOTIMELINE
+ || rv->region()->video_locked()
+#endif
+ ) {
continue;
}
@@ -929,7 +933,11 @@ RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed
/* insert the regions into their new playlists */
for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
- if (i->view->region()->locked()) {
+ if (i->view->region()->locked()
+#ifdef WITH_VIDEOTIMELINE
+ || i->view->region()->video_locked()
+#endif
+ ) {
continue;
}
@@ -1008,7 +1016,11 @@ RegionMoveDrag::finished_no_copy (
RouteTimeAxisView* const dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
double const dest_layer = i->layer;
- if (rv->region()->locked()) {
+ if (rv->region()->locked()
+#ifdef WITH_VIDEOTIMELINE
+ || rv->region()->video_locked()
+#endif
+ ) {
++i;
continue;
}
@@ -1597,6 +1609,168 @@ NoteResizeDrag::aborted (bool)
}
}
+#ifdef WITH_VIDEOTIMELINE
+
+AVDraggingView::AVDraggingView (RegionView* v)
+ : view (v)
+{
+ initial_position = v->region()->position ();
+}
+
+VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
+ : Drag (e, i)
+{
+ DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
+
+ /* create a list of regions to move along */
+#if 1 /* all reagions -- with video_locked() */
+ RegionSelection rs;
+ TrackViewList empty;
+ empty.clear();
+ _editor->get_regions_after(rs, (framepos_t) 0, empty);
+ std::list<RegionView*> views = rs.by_layer();
+#else /* selected regions -- with video_locked() */
+ std::list<RegionView*> views = _editor->selection->regions.by_layer();
+#endif
+ for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
+ RegionView* rv = (*i);
+ if (!rv->region()->video_locked()) {
+ continue;
+ }
+ _views.push_back (AVDraggingView (rv));
+ }
+}
+
+void
+VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
+{
+ Drag::start_grab (event);
+ if (_editor->session() == 0) {
+ return;
+ }
+
+ _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
+ _max_backwards_drag = (
+ ARDOUR_UI::instance()->video_timeline->get_duration()
+ + ARDOUR_UI::instance()->video_timeline->get_offset()
+ - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
+ );
+
+ for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+ if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
+ _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
+ }
+ }
+ DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
+
+ char buf[128];
+ Timecode::Time timecode;
+ _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
+ snprintf (buf, sizeof (buf), "Video Start:\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, (_startdrag_video_offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
+ _editor->verbose_cursor()->set(buf, event->button.x + 10, event->button.y + 10);
+ _editor->verbose_cursor()->show ();
+}
+
+void
+VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
+{
+ if (_editor->session() == 0) {
+ return;
+ }
+ if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
+ return;
+ }
+
+ framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
+ dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(dt);
+
+ if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
+ dt = - _max_backwards_drag;
+ }
+
+ ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
+ ARDOUR_UI::instance()->flush_videotimeline_cache(true);
+
+ for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+ RegionView* rv = i->view;
+ DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
+ if (first_move) {
+ rv->drag_start ();
+ _editor->update_canvas_now ();
+ rv->fake_set_opaque (true);
+ rv->region()->clear_changes ();
+ rv->region()->suspend_property_changes();
+ }
+ rv->region()->set_position(i->initial_position + dt);
+ rv->region_changed(ARDOUR::Properties::position);
+ }
+
+ const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
+ Timecode::Time timecode;
+ Timecode::Time timediff;
+ char buf[128];
+ _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
+ _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
+ snprintf (buf, sizeof (buf),
+ "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
+ "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
+ , _("Video Start:"),
+ (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
+ , _("Diff:"),
+ (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
+ );
+ _editor->verbose_cursor()->set(buf, event->button.x + 10, event->button.y + 10);
+ _editor->verbose_cursor()->show ();
+}
+
+void
+VideoTimeLineDrag::finished (GdkEvent *event, bool movement_occurred)
+{
+ if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
+ return;
+ }
+
+ if (!movement_occurred || ! _editor->session()) {
+ return;
+ }
+
+ ARDOUR_UI::instance()->flush_videotimeline_cache(true);
+
+ _editor->begin_reversible_command (_("Move Video"));
+
+ XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
+ ARDOUR_UI::instance()->video_timeline->save_undo();
+ XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
+ _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
+
+ for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+ i->view->drag_end();
+ i->view->fake_set_opaque (false);
+ i->view->region()->resume_property_changes ();
+
+ _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
+ }
+
+ _editor->commit_reversible_command ();
+ _editor->update_canvas_now ();
+}
+
+void
+VideoTimeLineDrag::aborted (bool)
+{
+ if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
+ return;
+ }
+ ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
+ ARDOUR_UI::instance()->flush_videotimeline_cache(true);
+
+ for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+ i->view->region()->resume_property_changes ();
+ i->view->region()->set_position(i->initial_position);
+ }
+}
+#endif
+
TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
: RegionDrag (e, i, p, v)
{
diff --git a/gtk2_ardour/editor_drag.h b/gtk2_ardour/editor_drag.h
index 196552f96a..9e9f263d76 100644
--- a/gtk2_ardour/editor_drag.h
+++ b/gtk2_ardour/editor_drag.h
@@ -505,6 +505,47 @@ private:
double _cumulative_dx;
};
+#ifdef WITH_VIDEOTIMELINE
+/** Container for details about audio regions being dragged along with video */
+class AVDraggingView
+{
+public:
+ AVDraggingView (RegionView *);
+
+ RegionView* view; ///< the view
+ framepos_t initial_position; ///< initial position of the region
+};
+
+/** Drag of video offset */
+class VideoTimeLineDrag : public Drag
+ //TODO , public sigc::trackable
+{
+public:
+ VideoTimeLineDrag (Editor *e, ArdourCanvas::Item *i);
+
+ void motion (GdkEvent *, bool);
+ void finished (GdkEvent *, bool);
+ void start_grab (GdkEvent *, Gdk::Cursor* c = 0);
+
+ bool y_movement_matters () const {
+ return false;
+ }
+
+ bool allow_vertical_autoscroll () const {
+ return false;
+ }
+
+ void aborted (bool);
+
+protected:
+ std::list<AVDraggingView> _views; ///< information about all audio that are being dragged along
+
+private:
+ ARDOUR::frameoffset_t _startdrag_video_offset;
+ ARDOUR::frameoffset_t _max_backwards_drag;
+};
+#endif
+
/** Drag to trim region(s) */
class TrimDrag : public RegionDrag
{
diff --git a/gtk2_ardour/editor_items.h b/gtk2_ardour/editor_items.h
index 51db6dbc87..39b6310a67 100644
--- a/gtk2_ardour/editor_items.h
+++ b/gtk2_ardour/editor_items.h
@@ -28,6 +28,9 @@ enum ItemType {
MarkerBarItem,
RangeMarkerBarItem,
CdMarkerBarItem,
+#ifdef WITH_VIDEOTIMELINE
+ VideoBarItem,
+#endif
TransportMarkerBarItem,
SelectionItem,
ControlPointItem,
diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc
index 4fce8f368f..8709a0fcfe 100644
--- a/gtk2_ardour/editor_mouse.cc
+++ b/gtk2_ardour/editor_mouse.cc
@@ -737,6 +737,13 @@ Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemT
}
}
+#ifdef WITH_VIDEOTIMELINE
+ case VideoBarItem:
+ _drags->set (new VideoTimeLineDrag (this, item), event);
+ return true;
+ break;
+#endif
+
case MarkerBarItem:
case TempoBarItem:
case MeterBarItem:
@@ -1547,6 +1554,9 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
case CdMarkerBarItem:
case TempoBarItem:
case MeterBarItem:
+#ifdef WITH_VIDEOTIMELINE
+ case VideoBarItem:
+#endif
popup_ruler_menu (where, item_type);
break;
diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc
index 479866bf9d..415614b811 100644
--- a/gtk2_ardour/editor_ops.cc
+++ b/gtk2_ardour/editor_ops.cc
@@ -5052,6 +5052,33 @@ Editor::toggle_region_lock ()
_session->commit_reversible_command ();
}
+#ifdef WITH_VIDEOTIMELINE
+void
+Editor::toggle_region_video_lock ()
+{
+ if (_ignore_region_action) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ _session->begin_reversible_command (_("Toggle Video Lock"));
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ (*i)->region()->clear_changes ();
+ (*i)->region()->set_video_locked (!(*i)->region()->video_locked());
+ _session->add_command (new StatefulDiffCommand ((*i)->region()));
+ }
+
+ _session->commit_reversible_command ();
+}
+#endif
+
+
void
Editor::toggle_region_lock_style ()
{
diff --git a/gtk2_ardour/editor_rulers.cc b/gtk2_ardour/editor_rulers.cc
index 3c73f0ce23..76152208c3 100644
--- a/gtk2_ardour/editor_rulers.cc
+++ b/gtk2_ardour/editor_rulers.cc
@@ -131,6 +131,9 @@ Editor::initialize_rulers ()
lab_children.push_back (Element(transport_mark_label, PACK_SHRINK, PACK_START));
lab_children.push_back (Element(cd_mark_label, PACK_SHRINK, PACK_START));
lab_children.push_back (Element(mark_label, PACK_SHRINK, PACK_START));
+#ifdef WITH_VIDEOTIMELINE
+ lab_children.push_back (Element(videotl_label, PACK_SHRINK, PACK_START));
+#endif
ruler_lab_children.push_back (Element(minsec_label, PACK_SHRINK, PACK_START));
ruler_children.insert (canvaspos, Element(*minsec_ruler, PACK_SHRINK, PACK_START));
@@ -368,6 +371,32 @@ Editor::popup_ruler_menu (framepos_t where, ItemType t)
ruler_items.push_back (SeparatorElem ());
break;
+#ifdef WITH_VIDEOTIMELINE
+ case VideoBarItem:
+ ruler_items.push_back (MenuElem (_("Timeline height")));
+ static_cast<MenuItem*>(&ruler_items.back())->set_sensitive(false);
+ ruler_items.push_back (CheckMenuElem (_("Large"), sigc::bind ( sigc::mem_fun(*this, &Editor::set_video_timeline_height), 6)));
+ if (videotl_bar_height == 6) { static_cast<CheckMenuItem*>(&ruler_items.back())->set_active(true);}
+ ruler_items.push_back (CheckMenuElem (_("Normal"), sigc::bind ( sigc::mem_fun(*this, &Editor::set_video_timeline_height), 4)));
+ if (videotl_bar_height == 4) { static_cast<CheckMenuItem*>(&ruler_items.back())->set_active(true);}
+ ruler_items.push_back (CheckMenuElem (_("Small"), sigc::bind ( sigc::mem_fun(*this, &Editor::set_video_timeline_height), 3)));
+ if (videotl_bar_height == 3) { static_cast<CheckMenuItem*>(&ruler_items.back())->set_active(true);}
+ ruler_items.push_back (SeparatorElem ());
+
+ ruler_items.push_back (MenuElem (_("Align Video Track")));
+ static_cast<MenuItem*>(&ruler_items.back())->set_sensitive(false);
+
+ ruler_items.push_back (CheckMenuElem (_("Lock")));
+ {
+ CheckMenuItem* vtl_lock = static_cast<CheckMenuItem*>(&ruler_items.back());
+ vtl_lock->set_active(is_video_timeline_locked());
+ vtl_lock->signal_activate().connect (sigc::mem_fun(*this, &Editor::toggle_video_timeline_locked));
+ }
+
+ ruler_items.push_back (SeparatorElem ());
+ break;
+#endif
+
default:
break;
}
@@ -418,6 +447,12 @@ Editor::popup_ruler_menu (framepos_t where, ItemType t)
if (action) {
ruler_items.push_back (MenuElem (*action->create_menu_item()));
}
+#ifdef WITH_VIDEOTIMELINE
+ action = ActionManager::get_action ("Rulers", "toggle-video-ruler");
+ if (action) {
+ ruler_items.push_back (MenuElem (*action->create_menu_item()));
+ }
+#endif
editor_ruler_menu->popup (1, gtk_get_current_event_time());
@@ -439,6 +474,9 @@ Editor::store_ruler_visibility ()
node->add_property (X_("rangemarker"), ruler_range_action->get_active() ? "yes": "no");
node->add_property (X_("transportmarker"), ruler_loop_punch_action->get_active() ? "yes": "no");
node->add_property (X_("cdmarker"), ruler_cd_marker_action->get_active() ? "yes": "no");
+#ifdef WITH_VIDEOTIMELINE
+ node->add_property (X_("videotl"), ruler_video_action->get_active() ? "yes": "no");
+#endif
_session->add_extra_xml (*node);
_session->set_dirty ();
@@ -539,6 +577,16 @@ Editor::restore_ruler_visibility ()
}
}
+#ifdef WITH_VIDEOTIMELINE
+ if ((prop = node->property ("videotl")) != 0) {
+ if (string_is_affirmative (prop->value())) {
+ ruler_video_action->set_active (true);
+ } else {
+ ruler_video_action->set_active (false);
+ }
+ }
+#endif
+
}
no_ruler_shown_update = false;
@@ -604,6 +652,9 @@ Editor::update_ruler_visibility ()
transport_mark_label.hide();
cd_mark_label.hide();
mark_label.hide();
+ #ifdef WITH_VIDEOTIMELINE
+ videotl_label.hide();
+ #endif
#endif
if (ruler_meter_action->get_active()) {
old_unit_pos = meter_group->property_y();
@@ -736,6 +787,32 @@ Editor::update_ruler_visibility ()
mark_label.hide();
}
+#ifdef WITH_VIDEOTIMELINE
+
+ if (ruler_video_action->get_active()) {
+ old_unit_pos = videotl_group->property_y();
+ if (tbpos != old_unit_pos) {
+ videotl_group->move ( 0.0, tbpos - old_unit_pos);
+ }
+ old_unit_pos = videotl_bar_group->property_y();
+ if (tbgpos != old_unit_pos) {
+ videotl_bar_group->move ( 0.0, tbgpos - old_unit_pos);
+ }
+ videotl_bar_group->show();
+ videotl_group->show();
+ videotl_label.show();
+ tbpos += timebar_height * videotl_bar_height;
+ tbgpos += timebar_height * videotl_bar_height;
+ visible_timebars+=videotl_bar_height;
+ queue_visual_videotimeline_update();
+ } else {
+ videotl_bar_group->hide();
+ videotl_group->hide();
+ videotl_label.hide();
+ update_video_timeline(true);
+ }
+#endif
+
gdouble old_canvas_timebars_vsize = canvas_timebars_vsize;
canvas_timebars_vsize = (timebar_height * visible_timebars) - 1;
gdouble vertical_pos_delta = canvas_timebars_vsize - old_canvas_timebars_vsize;
diff --git a/gtk2_ardour/editor_selection.cc b/gtk2_ardour/editor_selection.cc
index 1089381a15..fbe46a1ae9 100644
--- a/gtk2_ardour/editor_selection.cc
+++ b/gtk2_ardour/editor_selection.cc
@@ -1047,6 +1047,10 @@ Editor::sensitize_the_right_region_actions ()
bool have_midi = false;
bool have_locked = false;
bool have_unlocked = false;
+#ifdef WITH_VIDEOTIMELINE
+ bool have_video_locked = false;
+ bool have_video_unlocked = false;
+#endif
bool have_position_lock_style_audio = false;
bool have_position_lock_style_music = false;
bool have_muted = false;
@@ -1088,6 +1092,13 @@ Editor::sensitize_the_right_region_actions ()
} else {
have_unlocked = true;
}
+#ifdef WITH_VIDEOTIMELINE
+ if (r->video_locked()) {
+ have_video_locked = true;
+ } else {
+ have_video_unlocked = true;
+ }
+#endif
if (r->position_lock_style() == MusicTime) {
have_position_lock_style_music = true;
@@ -1212,6 +1223,14 @@ Editor::sensitize_the_right_region_actions ()
// a->set_inconsistent ();
}
+#ifdef WITH_VIDEOTIMELINE
+ a = Glib::RefPtr<ToggleAction>::cast_dynamic (_region_actions->get_action("toggle-region-video-lock"));
+ a->set_active (have_video_locked && !have_video_unlocked);
+ if (have_video_locked && have_video_unlocked) {
+ // a->set_inconsistent ();
+ }
+#endif
+
a = Glib::RefPtr<ToggleAction>::cast_dynamic (_region_actions->get_action("toggle-region-lock-style"));
a->set_active (have_position_lock_style_music && !have_position_lock_style_audio);
diff --git a/gtk2_ardour/editor_videotimeline.cc b/gtk2_ardour/editor_videotimeline.cc
new file mode 100644
index 0000000000..18a49fc9b6
--- /dev/null
+++ b/gtk2_ardour/editor_videotimeline.cc
@@ -0,0 +1,126 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include <jack/types.h>
+
+#include "ardour/profile.h"
+#include "ardour/rc_configuration.h"
+
+#include "ardour_ui.h"
+#include "editor.h"
+#include "simplerect.h"
+#include "canvas_impl.h"
+#include "editing.h"
+#include "audio_time_axis.h"
+#include "video_image_frame.h"
+#include "export_video_dialog.h"
+#include "export_video_infobox.h"
+
+#include "i18n.h"
+
+using namespace std;
+
+void
+Editor::set_video_timeline_height (const int h)
+{
+ if (videotl_bar_height == h) { return; }
+ if (h < 2 || h > 8) { return; }
+ videotl_bar_height = h;
+ const double nh = (videotl_bar_height * timebar_height - ((ARDOUR::Profile->get_sae())?1.0:0.0));
+ videotl_label.set_size_request (-1, (int)timebar_height * videotl_bar_height);
+ videotl_bar->property_y2().set_value(nh);
+ ARDOUR_UI::instance()->video_timeline->set_height(videotl_bar_height * timebar_height);
+ update_ruler_visibility();
+}
+
+void
+Editor::update_video_timeline (bool flush)
+{
+#if DEBUG
+ framepos_t rightmost_frame = leftmost_frame + current_page_frames();
+ std::cout << "VIDEO SCROLL: " << leftmost_frame << " -- " << rightmost_frame << std::endl;
+ std::cout << "SCROLL UNITS: " << frame_to_unit(leftmost_frame) << " -- " << frame_to_unit(rightmost_frame)
+ << " = " << frame_to_unit(rightmost_frame) - frame_to_unit(leftmost_frame)
+ << std::endl;
+#endif
+
+ // TODO later: make this a list for mult. video tracks
+ // also modify ardour_ui_dialogs.cc : set_session()
+ if (flush) {
+ ARDOUR_UI::instance()->video_timeline->flush_local_cache();
+ }
+ if (!ruler_video_action->get_active()) return;
+ ARDOUR_UI::instance()->video_timeline->update_video_timeline();
+}
+
+bool
+Editor::is_video_timeline_locked ()
+{
+ return ARDOUR_UI::instance()->video_timeline->is_offset_locked();
+}
+
+void
+Editor::set_video_timeline_locked (const bool l)
+{
+ ARDOUR_UI::instance()->video_timeline->set_offset_locked(l);
+}
+
+void
+Editor::toggle_video_timeline_locked ()
+{
+ ARDOUR_UI::instance()->video_timeline->toggle_offset_locked();
+}
+
+void
+Editor::embed_audio_from_video (std::string path)
+{
+ vector<std::string> paths;
+ framepos_t n = 0; /* -1: use file's timestamp - but br0ken with ffmpeg wav extract */
+ paths.push_back(path);
+#if 0
+ do_embed (paths, Editing::ImportDistinctFiles, Editing::ImportAsTrack, n);
+#else
+ do_import (paths, Editing::ImportDistinctFiles, Editing::ImportAsTrack, ARDOUR::SrcBest, n);
+ unlink(path.c_str());
+#endif
+}
+
+void
+Editor::export_video ()
+{
+ if (ARDOUR::Config->get_show_video_export_info()) {
+ ExportVideoInfobox infobox (_session);
+ infobox.run();
+ if (infobox.show_again()) {
+ ARDOUR::Config->set_show_video_export_info(false);
+ }
+ }
+ ExportVideoDialog dialog (*this, _session);
+ Gtk::ResponseType r = (Gtk::ResponseType) dialog.run();
+ dialog.hide();
+#if 0
+ if (r == Gtk::RESPONSE_ACCEPT) {
+ ARDOUR_UI::instance()->popup_error(string_compose(_("Export Successful: %1"),dialog.get_exported_filename()));
+ }
+#endif
+}
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/export_video_dialog.cc b/gtk2_ardour/export_video_dialog.cc
new file mode 100644
index 0000000000..89693aee8e
--- /dev/null
+++ b/gtk2_ardour/export_video_dialog.cc
@@ -0,0 +1,917 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include <cstdio>
+#include <string>
+#include <sstream>
+#include <iomanip>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sigc++/bind.h>
+#include <libgen.h>
+
+#include "pbd/error.h"
+#include "pbd/convert.h"
+#include "gtkmm2ext/utils.h"
+#include "ardour/session_directory.h"
+#include "ardour/profile.h"
+#include "ardour/template_utils.h"
+#include "ardour/session.h"
+#include "ardour_ui.h"
+#include "gui_thread.h"
+
+#include "ardour/export_handler.h"
+#include "ardour/export_status.h"
+#include "ardour/export_timespan.h"
+#include "ardour/export_channel_configuration.h"
+#include "ardour/export_format_specification.h"
+#include "ardour/export_filename.h"
+#include "ardour/route.h"
+#include "ardour/session_metadata.h"
+#include "ardour/broadcast_info.h"
+
+#include "utils.h"
+#include "opts.h"
+#include "export_video_dialog.h"
+#include "utils_videotl.h"
+#include "i18n.h"
+
+using namespace Gtk;
+using namespace std;
+using namespace PBD;
+using namespace ARDOUR;
+
+ExportVideoDialog::ExportVideoDialog (PublicEditor& ed, Session* s)
+ : ArdourDialog (_("Export Video File "))
+ , editor (ed)
+ , outfn_path_label (_("Output File:"), Gtk::ALIGN_LEFT)
+ , outfn_browse_button (_("Browse"))
+ , invid_path_label (_("Input Video File:"), Gtk::ALIGN_LEFT)
+ , invid_browse_button (_("Browse"))
+ , transcode_button (_("Export"))
+ , abort_button (_("Abort"))
+ , scale_checkbox (_("Scale Video (W x H):"))
+ , width_adjustment (128, 768, 1920, 1, 16, 0)
+ , width_spinner (width_adjustment)
+ , height_adjustment (128, 576, 1920, 1, 16, 0)
+ , height_spinner (height_adjustment)
+ , aspect_checkbox (_("Set Aspect Ratio:"))
+ , normalize_checkbox (_("Normalize Audio"))
+ , twopass_checkbox (_("2 Pass Encoding"))
+ , optimizations_checkbox (_("Optimizations:"))
+ , optimizations_label ("-")
+ , deinterlace_checkbox (_("Deinterlace"))
+ , bframes_checkbox (_("Use [2] B-frames (MPEG 2 or 4 only)"))
+ , fps_checkbox (_("Override FPS (Default is to retain FPS from the input video file):"))
+ , meta_checkbox (_("Include Session Metadata"))
+#if 1 /* tentative debug mode */
+ , debug_checkbox (_("Enable Debug Mode: Print ffmpeg Command & Output to stdout."))
+#endif
+{
+ set_session (s);
+
+ transcoder = 0;
+
+ set_name ("ExportVideoDialog");
+ set_position (Gtk::WIN_POS_MOUSE);
+ set_modal (true);
+ set_skip_taskbar_hint (true);
+ set_resizable (false);
+
+ Gtk::Label* l;
+ vbox = manage (new VBox);
+ VBox* options_box = manage (new VBox);
+ HBox* path_hbox;
+
+#if 0
+ l = manage (new Label (_("<b>Export Video File</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_use_markup ();
+ vbox->pack_start (*l, false, false);
+ l = manage (new Label (_("The file-format is determined by the extension you choose for the output file."), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_size_request(700,-1);
+ l->set_line_wrap();
+ vbox->pack_start (*l, false, false, 8);
+#endif
+
+ l = manage (new Label (_("<b>Files:</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_use_markup ();
+ vbox->pack_start (*l, false, false);
+
+ path_hbox = manage (new HBox);
+ path_hbox->pack_start (outfn_path_label, false, false, 3);
+ path_hbox->pack_start (outfn_path_entry, true, true, 3);
+ path_hbox->pack_start (outfn_browse_button, false, false, 3);
+ outfn_browse_button.set_name ("PaddedButton");
+ vbox->pack_start (*path_hbox, false, false);
+
+ path_hbox = manage (new HBox);
+ path_hbox->pack_start (invid_path_label, false, false, 3);
+ path_hbox->pack_start (invid_path_entry, true, true, 3);
+ path_hbox->pack_start (invid_browse_button, false, false, 3);
+ invid_browse_button.set_name ("PaddedButton");
+ vbox->pack_start (*path_hbox, false, false);
+
+ path_hbox = manage (new HBox);
+ l = manage (new Label (_("Input Audio (Ardour Session):"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ path_hbox->pack_start (*l, true, true, 3);
+ path_hbox->pack_start (insnd_combo, true, true, 3);
+ vbox->pack_start (*path_hbox, false, false);
+
+ insnd_combo.set_name ("PaddedButton");
+ insnd_combo.append_text("from ardour session-start to session-end");
+
+ frameoffset_t av_offset = ARDOUR_UI::instance()->video_timeline->get_offset();
+ if (av_offset < 0 ) {
+ insnd_combo.append_text("from 00:00:00:00 to video-end");
+ } else {
+ insnd_combo.append_text("from video-start to video-end");
+ }
+ insnd_combo.set_active(0);
+
+#if 1
+ // TODO probe input file - don't use VTL duration.
+ framecnt_t duration_v = ARDOUR_UI::instance()->video_timeline->get_duration();
+ framecnt_t duration_a = _session->current_end_frame() - _session->current_start_frame();
+ if ( duration_a > duration_v ) {
+ warning << _("Audio is longer than video. This file may not play correctly.") << endmsg;
+ }
+#endif
+
+ outfn_path_entry.set_width_chars(38);
+ outfn_path_entry.set_text (_session->session_directory().export_path() + G_DIR_SEPARATOR +"export.avi");
+
+ XMLNode* node = _session->extra_xml (X_("Videotimeline"));
+ if (node && node->property(X_("Filename"))) {
+ std::string filename = node->property(X_("Filename"))->value();
+ if (filename.at(0) != G_DIR_SEPARATOR) {
+ filename = Glib::build_filename (_session->session_directory().video_path(), filename);
+ }
+ invid_path_entry.set_text (filename);
+ }
+
+ l = manage (new Label (_("<b>Settings:</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_use_markup ();
+ options_box->pack_start (*l, false, true, 4);
+
+ Table* t = manage (new Table (4, 11));
+ t->set_spacings (4);
+ options_box->pack_start (*t, true, true, 4);
+ l = manage (new Label (_("Preset:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 0, 1, 0, 1);
+ t->attach (preset_combo, 1, 4, 0, 1);
+ l = manage (new Label (_("Video Codec:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 0, 1, 1, 2);
+ t->attach (video_codec_combo, 1, 2, 1, 2);
+ l = manage (new Label (_("Video KBit/s:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 2, 3, 1, 2);
+ t->attach (video_bitrate_combo, 3, 4, 1, 2);
+ l = manage (new Label (_("Audio Codec:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 0, 1, 2, 3);
+ t->attach (audio_codec_combo, 1, 2, 2, 3);
+ l = manage (new Label (_("Audio KBit/s:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 2, 3, 2, 3);
+ t->attach (audio_bitrate_combo, 3, 4, 2, 3);
+ l = manage (new Label (_("Audio Samplerate:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 0, 1, 3, 4);
+ t->attach (audio_samplerate_combo, 1, 2, 3, 4);
+ t->attach (normalize_checkbox, 2, 4, 3, 4);
+ t->attach (scale_checkbox, 0, 2, 4, 5);
+ t->attach (width_spinner, 2, 3, 4, 5);
+ t->attach (height_spinner, 3, 4, 4, 5);
+ t->attach (fps_checkbox, 0, 3, 5, 6);
+ t->attach (fps_combo, 3, 4, 5, 6);
+ t->attach (aspect_checkbox, 2, 3, 6, 7);
+ t->attach (aspect_combo, 3, 4, 6, 7);
+ t->attach (twopass_checkbox, 0, 2, 6, 7);
+ t->attach (bframes_checkbox, 0, 2, 7, 8);
+ t->attach (deinterlace_checkbox, 2, 4, 7, 8);
+ t->attach (meta_checkbox, 2, 4, 8, 9);
+ t->attach (optimizations_checkbox, 0, 1, 9, 10);
+ t->attach (optimizations_label, 1, 4, 9, 10);
+#if 1 /* tentative debug mode */
+ t->attach (debug_checkbox, 0, 4, 10, 11);
+#endif
+
+ preset_combo.set_name ("PaddedButton");
+ preset_combo.append_text("none");
+ preset_combo.append_text("dvd-mp2");
+ preset_combo.append_text("dvd-NTSC");
+ preset_combo.append_text("dvd-PAL");
+ preset_combo.append_text("flv");
+ preset_combo.append_text("mpeg4");
+ preset_combo.append_text("ogg");
+ preset_combo.append_text("you-tube");
+ preset_combo.set_active(0);
+
+ audio_codec_combo.set_name ("PaddedButton");
+ audio_codec_combo.append_text("ac3");
+ audio_codec_combo.append_text("libfaac");
+ audio_codec_combo.append_text("libmp3lame");
+ audio_codec_combo.append_text("libvorbis");
+ audio_codec_combo.append_text("mp2");
+ audio_codec_combo.append_text("pcm_s16le");
+ audio_codec_combo.set_active(2);
+
+ video_codec_combo.set_name ("PaddedButton");
+ video_codec_combo.append_text("flv");
+ video_codec_combo.append_text("libtheora");
+ video_codec_combo.append_text("mjpeg");
+ video_codec_combo.append_text("mpeg2video");
+ video_codec_combo.append_text("mpeg4");
+ video_codec_combo.append_text("x264 (default)");
+ video_codec_combo.append_text("x264 (hq)");
+ video_codec_combo.append_text("copy");
+ video_codec_combo.set_active(4);
+
+ audio_bitrate_combo.set_name ("PaddedButton");
+ audio_bitrate_combo.append_text("64k");
+ audio_bitrate_combo.append_text("128k");
+ audio_bitrate_combo.append_text("192k");
+ audio_bitrate_combo.append_text("256k");
+ audio_bitrate_combo.append_text("320k");
+ audio_bitrate_combo.set_active(2);
+
+ audio_samplerate_combo.set_name ("PaddedButton");
+ audio_samplerate_combo.append_text("22050");
+ audio_samplerate_combo.append_text("44100");
+ audio_samplerate_combo.append_text("48000");
+ audio_samplerate_combo.set_active(2);
+
+ video_bitrate_combo.set_name ("PaddedButton");
+ video_bitrate_combo.append_text("200k");
+ video_bitrate_combo.append_text("800k");
+ video_bitrate_combo.append_text("2000k");
+ video_bitrate_combo.append_text("5000k");
+ video_bitrate_combo.append_text("8000k");
+ video_bitrate_combo.append_text("retain");
+ video_bitrate_combo.set_active(3);
+
+ fps_combo.set_name ("PaddedButton");
+ fps_combo.append_text("23.976");
+ fps_combo.append_text("24");
+ fps_combo.append_text("24.976");
+ fps_combo.append_text("25");
+ fps_combo.append_text("29.97");
+ fps_combo.append_text("30");
+ fps_combo.append_text("59.94");
+ fps_combo.append_text("60");
+ float tcfps = _session->timecode_frames_per_second();
+ if (fabs(tcfps - 23.976) < 0.01) { fps_combo.set_active(0); }
+ else if (fabs(tcfps - 24.0 ) < 0.01) { fps_combo.set_active(1); }
+ else if (fabs(tcfps - 24.976) < 0.01) { fps_combo.set_active(2); }
+ else if (fabs(tcfps - 25.0 ) < 0.01) { fps_combo.set_active(3); }
+ else if (fabs(tcfps - 29.97 ) < 0.01) { fps_combo.set_active(4); }
+ else if (fabs(tcfps - 30.0 ) < 0.01) { fps_combo.set_active(5); }
+ else if (fabs(tcfps - 59.94 ) < 0.01) { fps_combo.set_active(6); }
+ else if (fabs(tcfps - 60.0 ) < 0.01) { fps_combo.set_active(7); }
+ else { fps_combo.set_active(5); }
+
+ aspect_combo.set_name ("PaddedButton");
+ aspect_combo.append_text("4:3");
+ aspect_combo.append_text("16:9");
+ aspect_combo.set_active(1);
+
+ optimizations_checkbox.set_sensitive(false);
+ scale_checkbox_toggled();
+ aspect_checkbox_toggled();
+ fps_checkbox_toggled();
+ video_codec_combo_changed();
+
+ vbox->pack_start (*options_box, false, true);
+ get_vbox()->set_spacing (4);
+ get_vbox()->pack_start (*vbox, false, false);
+
+ progress_box = manage (new VBox);
+ progress_box->pack_start (pbar, false, false);
+ progress_box->pack_start (abort_button, false, false);
+ get_vbox()->pack_start (*progress_box, false, false);
+
+ scale_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &ExportVideoDialog::scale_checkbox_toggled));
+ aspect_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &ExportVideoDialog::aspect_checkbox_toggled));
+ fps_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &ExportVideoDialog::fps_checkbox_toggled));
+ preset_combo.signal_changed().connect (sigc::mem_fun (*this, &ExportVideoDialog::preset_combo_changed));
+ video_codec_combo.signal_changed().connect (sigc::mem_fun (*this, &ExportVideoDialog::video_codec_combo_changed));
+ outfn_browse_button.signal_clicked().connect (sigc::mem_fun (*this, &ExportVideoDialog::open_outfn_dialog));
+ invid_browse_button.signal_clicked().connect (sigc::mem_fun (*this, &ExportVideoDialog::open_invid_dialog));
+ transcode_button.signal_clicked().connect (sigc::mem_fun (*this, &ExportVideoDialog::launch_export));
+ abort_button.signal_clicked().connect (sigc::mem_fun (*this, &ExportVideoDialog::abort_clicked));
+
+ cancel_button = add_button (Stock::CANCEL, RESPONSE_CANCEL);
+ get_action_area()->pack_start (transcode_button, false, false);
+ show_all_children ();
+ progress_box->hide();
+}
+
+ExportVideoDialog::~ExportVideoDialog ()
+{
+ if (transcoder) { delete transcoder; transcoder = 0;}
+}
+
+void
+ExportVideoDialog::on_show ()
+{
+ Dialog::on_show ();
+}
+
+void
+ExportVideoDialog::abort_clicked ()
+{
+ aborted = true;
+ if (transcoder) {
+ transcoder->cancel();
+ }
+}
+
+void
+ExportVideoDialog::update_progress (framecnt_t c, framecnt_t a)
+{
+ if (a == 0 || c > a) {
+ pbar.set_pulse_step(.1);
+ pbar.pulse();
+ return;
+ }
+ pbar.set_fraction ((double)c / (double) a);
+}
+
+void
+ExportVideoDialog::finished ()
+{
+ if (aborted) {
+ unlink(outfn_path_entry.get_text().c_str());
+ unlink (insnd.c_str());
+ warning << _("Video Export Failed or Was Aborted") << endmsg;
+ Gtk::Dialog::response(RESPONSE_CANCEL);
+ } else if (twopass) {
+ twopass = false;
+ if (transcoder) { delete transcoder; transcoder = 0;}
+ encode_pass(2);
+ } else {
+ if (twopass_checkbox.get_active()) {
+ std::string outfn = outfn_path_entry.get_text();
+ std::string p2log = Glib::path_get_dirname (outfn) + G_DIR_SEPARATOR + "ffmpeg2pass";
+ unlink (p2log.c_str());
+ }
+ unlink (insnd.c_str());
+ Gtk::Dialog::response(RESPONSE_ACCEPT);
+ }
+}
+
+void
+ExportVideoDialog::launch_export ()
+{
+ std::string outfn = outfn_path_entry.get_text();
+ if (!confirm_video_outfn(outfn)) { return; }
+
+ vbox->hide();
+ cancel_button->hide();
+ transcode_button.hide();
+ pbar.set_size_request(300,-1);
+ pbar.set_text(_("Exporting Audio.."));
+ progress_box->show();
+ aborted = false;
+ twopass = twopass_checkbox.get_active();
+
+ /* export audio track */
+ ExportTimespanPtr tsp = _session->get_export_handler()->add_timespan();
+ boost::shared_ptr<ExportChannelConfiguration> ccp = _session->get_export_handler()->add_channel_config();
+ boost::shared_ptr<ARDOUR::ExportFilename> fnp = _session->get_export_handler()->add_filename();
+ boost::shared_ptr<AudioGrapher::BroadcastInfo> b;
+ XMLTree tree;
+ std::string vtl_samplerate = audio_samplerate_combo.get_active_text();
+ std::string vtl_normalize = normalize_checkbox.get_active()?"true":"false";
+ tree.read_buffer(std::string(
+ /* TODO: move this into a header file */
+"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+"<ExportFormatSpecification name=\"VTL-WAV-16\" id=\"3094591e-ccb9-4385-a93f-c9955ffeb1f0\">"
+" <Encoding id=\"F_WAV\" type=\"T_Sndfile\" extension=\"wav\" name=\"WAV\" has-sample-format=\"true\" channel-limit=\"256\"/>"
+" <SampleRate rate=\""+ vtl_samplerate +"\"/>"
+" <SRCQuality quality=\"SRC_SincBest\"/>"
+" <EncodingOptions>"
+" <Option name=\"sample-format\" value=\"SF_16\"/>"
+" <Option name=\"dithering\" value=\"D_None\"/>"
+" <Option name=\"tag-metadata\" value=\"true\"/>"
+" <Option name=\"tag-support\" value=\"false\"/>"
+" <Option name=\"broadcast-info\" value=\"false\"/>"
+" </EncodingOptions>"
+" <Processing>"
+" <Normalize enabled=\""+ vtl_normalize +"\" target=\"0\"/>"
+" <Silence>"
+" <Start>"
+" <Trim enabled=\"false\"/>"
+" <Add enabled=\"false\">"
+" <Duration format=\"Timecode\" hours=\"0\" minutes=\"0\" seconds=\"0\" frames=\"0\"/>"
+" </Add>"
+" </Start>"
+" <End>"
+" <Trim enabled=\"false\"/>"
+" <Add enabled=\"false\">"
+" <Duration format=\"Timecode\" hours=\"0\" minutes=\"0\" seconds=\"0\" frames=\"0\"/>"
+" </Add>"
+" </End>"
+" </Silence>"
+" </Processing>"
+"</ExportFormatSpecification>"
+));
+ boost::shared_ptr<ExportFormatSpecification> fmp = _session->get_export_handler()->add_format(*tree.root());
+
+ /* set up range */
+ framepos_t start, end;
+ start = end = 0;
+ if (insnd_combo.get_active_row_number() == 1) {
+ transcoder = new TranscodeFfmpeg(invid_path_entry.get_text());
+ if (transcoder->probe_ok()) {
+ end = transcoder->get_duration() * _session->nominal_frame_rate() / transcoder->get_fps();
+ }
+ if (transcoder) { delete transcoder; transcoder = 0;}
+
+ frameoffset_t av_offset = ARDOUR_UI::instance()->video_timeline->get_offset();
+ if (av_offset > 0) {
+ start += (av_offset / (double)_session->nominal_frame_rate());
+ }
+ end += (av_offset / (double)_session->nominal_frame_rate());
+ }
+ if (end <= 0) {
+ start = _session->current_start_frame();
+ end = _session->current_end_frame();
+ }
+#if 0 /* DEBUG */
+ printf("export-range %ld -> %ld\n", start, end);
+#endif
+
+ tsp->set_range (start, end);
+ tsp->set_name ("mysession");
+ tsp->set_range_id ("session");
+
+ /* add master outs as default */
+ IO* master_out = _session->master_out()->output().get();
+ if (!master_out) {
+ warning << _("Export Video: No Master Out Ports to Connect for Audio Export") << endmsg;
+ Gtk::Dialog::response(RESPONSE_CANCEL);
+ return;
+ }
+ for (uint32_t n = 0; n < master_out->n_ports().n_audio(); ++n) {
+ PortExportChannel * channel = new PortExportChannel ();
+ channel->add_port (master_out->audio (n));
+ ExportChannelPtr chan_ptr (channel);
+ ccp->register_channel (chan_ptr);
+ }
+
+ /* outfile */
+ fnp->set_timespan(tsp);
+ fnp->set_label("vtl");
+ fnp->include_label = true;
+ insnd = fnp->get_path(fmp);
+
+ /* do sound export */
+ _session->get_export_handler()->add_export_config (tsp, ccp, fmp, fnp, b);
+ _session->get_export_handler()->do_export();
+ boost::shared_ptr<ARDOUR::ExportStatus> status = _session->get_export_status ();
+ //status->running = true;
+
+ float previous_progress = 0.0;
+ while (status->running) {
+ if (aborted) { status->abort(); }
+
+ float progress = 0.0;
+ if (status->normalizing) {
+ pbar.set_text (_("Normalizing audio"));
+ progress = ((float) status->current_normalize_cycle) / status->total_normalize_cycles;
+ } else {
+ pbar.set_text (_("Exporting audio"));
+ progress = ((float) status->processed_frames_current_timespan) / status->total_frames_current_timespan;
+ }
+ if (progress < previous_progress) {
+ // Work around gtk bug
+ pbar.hide();
+ pbar.show();
+ }
+ previous_progress = progress;
+ pbar.set_fraction (progress);
+ if (gtk_events_pending()) {
+ gtk_main_iteration ();
+ } else {
+ usleep (10000);
+ }
+ }
+ if (status->aborted()) {
+ unlink (insnd.c_str());
+ Gtk::Dialog::response(RESPONSE_CANCEL);
+ return;
+ }
+ status->finish ();
+ pbar.set_text (_("Encoding Video.."));
+ encode_pass(1);
+}
+
+void
+ExportVideoDialog::encode_pass (int pass)
+{
+ std::string outfn = outfn_path_entry.get_text();
+ std::string invid = invid_path_entry.get_text();
+
+ /* TODO: optimize
+ * if (insnd_combo.get_active_row_number() == 0)
+ * we can skip parsing the input video-file and use
+ * new TranscodeFfmpeg("");
+ */
+ transcoder = new TranscodeFfmpeg(invid);
+ if (!transcoder->ffexec_ok()) {
+ warning << _("No ffprobe or ffmpeg executables could be found on this system. Transcoding is not possible until you install those tools.") << endmsg;
+ unlink (insnd.c_str());
+ Gtk::Dialog::response(RESPONSE_CANCEL);
+ return;
+ }
+
+ std::string preset = preset_combo.get_active_text();
+ FFSettings ffs ; /* = transcoder->default_encoder_settings(); */
+ ffs.clear();
+
+ if (fps_checkbox.get_active()) {
+ ffs["-r"] = fps_combo.get_active_text();
+ }
+
+ if (scale_checkbox.get_active()) {
+ ffs["-s"] = string_compose("%1x%2", width_spinner.get_value(), height_spinner.get_value());
+ }
+ ffs["-vcodec"] = video_codec_combo.get_active_text();
+ ffs["-acodec"] = audio_codec_combo.get_active_text();
+
+ if (video_bitrate_combo.get_active_text() == "retain" ) {
+ ffs["-sameq"] = "-y"; // we use '-y' as dummy parameter for non key/value options
+ } else {
+ ffs["-b:v"] = video_bitrate_combo.get_active_text();
+ }
+ ffs["-b:a"] = audio_bitrate_combo.get_active_text();
+ if (video_codec_combo.get_active_text() == "x264 (hq)" ) {
+ ffs["-vcodec"] = "libx264";
+ ffs["-vpre"] = "slow";
+ }
+ else if (video_codec_combo.get_active_text() == "x264 (default)" ) {
+ ffs["-vcodec"] = "libx264";
+ ffs["-vpre"] = "medium";
+ }
+
+ if (optimizations_checkbox.get_active()) {
+ /* TODO: define these smartly in a header-file so that they can be
+ * merged for printing as label and used here easyly
+ */
+ if (video_codec_combo.get_active_text() == "mpeg2video") {
+ ffs["-mbd"] = "rd";
+ ffs["-trellis"] = "2";
+ ffs["-cmp"] = "2";
+ ffs["-subcmp"] = "2";
+ }
+ else if (video_codec_combo.get_active_text() == "mpeg4") {
+ ffs["-mbd"] = "rd";
+ ffs["-flags"] = "+4mv+aic";
+ ffs["-trellis"] = "2";
+ ffs["-cmp"] = "2";
+ ffs["-subcmp"] = "2";
+ ffs["-g"] = "300";
+ }
+ else if (video_codec_combo.get_active_text() == "flv") {
+ ffs["-mbd"] = "2";
+ ffs["-cmp"] = "2";
+ ffs["-subcmp"] = "2";
+ ffs["-trellis"] = "2";
+ ffs["-flags"] = "+aic+mv0+mv4";
+ ffs["-g"] = "160";
+ }
+ }
+
+ if (bframes_checkbox.get_active()) {
+ ffs["-bf"] = "2";
+ }
+
+ if (preset == "dvd-PAL") {
+ ffs.clear(); /* ignore all prev settings */
+ ffs["-target"] = "pal-dvd";
+ ffs["-aspect"] = "4:3"; /* required for DVD - may be overridden below */
+ }
+ else if (preset == "dvd-NTSC") {
+ ffs.clear(); /* ignore all prev settings */
+ ffs["-target"] = "ntsc-dvd";
+ ffs["-aspect"] = "4:3"; /* required for DVD - may be overridden below */
+ }
+
+ if (aspect_checkbox.get_active()) {
+ ffs["-aspect"] = aspect_combo.get_active_text();
+ }
+ if (deinterlace_checkbox.get_active()) {
+ ffs["-deinterlace"] = "-y"; // we use '-y' as dummy parameter for non key/value options
+ }
+
+ bool map = true;
+ if (pass == 1 && twopass) {
+ pbar.set_text (_("Encoding Video.. Pass 1/2"));
+ map = false;
+ ffs["-pass"] = "1";
+ ffs["-an"] = "-y";
+ ffs["-passlogfile"] = Glib::path_get_dirname (outfn) + G_DIR_SEPARATOR + "ffmpeg2pass";
+ ffs["-f"] = get_file_extension(invid).empty()?"mov":get_file_extension(invid);
+#ifdef _OS_WIN32
+ outfn = "NUL";
+#else
+ outfn = "/dev/null";
+#endif
+ } else if (pass == 2) {
+ pbar.set_text (_("Encoding Video.. Pass 2/2"));
+ ffs["-pass"] = "2";
+ ffs["-passlogfile"] = Glib::path_get_dirname (outfn) + G_DIR_SEPARATOR + "ffmpeg2pass";
+ }
+
+ frameoffset_t av_offset = ARDOUR_UI::instance()->video_timeline->get_offset();
+
+ if (insnd_combo.get_active_row_number() == 0) {
+ framecnt_t duration_f = _session->current_end_frame() - _session->current_start_frame();
+ double duration_s = (double)duration_f / (double)_session->nominal_frame_rate();
+ std::ostringstream osstream; osstream << duration_s;
+ ffs["-t"] = osstream.str();
+ if (fps_checkbox.get_active()) {
+ transcoder->set_duration(duration_s * atof(fps_combo.get_active_text()));
+ } else {
+ transcoder->set_duration(duration_s * transcoder->get_fps());
+ }
+ } else {
+ ;
+ // TODO use (end-start) from above
+ // in case offset is negative, video will be shorter..
+ // CHECK; maybe this is not needed -itsoffset may take care of it
+ }
+
+ if (insnd_combo.get_active_row_number() == 0) {
+ framepos_t start = _session->current_start_frame();
+#if 0
+ double start_s = ((double)start / (double)_session->nominal_frame_rate());
+ std::ostringstream osstream; osstream << start_s;
+ ffs["-ss"] = osstream.str();
+#endif
+ av_offset -= start;
+ }
+ transcoder->set_avoffset(av_offset / (double)_session->nominal_frame_rate());
+
+ FFSettings meta = transcoder->default_meta_data();
+ if (meta_checkbox.get_active()) {
+ ARDOUR::SessionMetadata * session_data = ARDOUR::SessionMetadata::Metadata();
+ if (session_data->year() > 0 ) {
+ std::ostringstream osstream; osstream << session_data->year();
+ meta["year"] = osstream.str();
+ }
+ if (session_data->track_number() > 0 ) {
+ std::ostringstream osstream; osstream << session_data->track_number();
+ meta["track"] = osstream.str();
+ }
+ if (session_data->disc_number() > 0 ) {
+ std::ostringstream osstream; osstream << session_data->disc_number();
+ meta["disc"] = osstream.str();
+ }
+ if (!session_data->title().empty()) {meta["title"] = session_data->title();}
+ if (!session_data->artist().empty()) {meta["author"] = session_data->artist();}
+ if (!session_data->album_artist().empty()) {meta["album_artist"] = session_data->album_artist();}
+ if (!session_data->album().empty()) {meta["album"] = session_data->album();}
+ if (!session_data->genre().empty()) {meta["genre"] = session_data->genre();}
+ if (!session_data->composer().empty()) {meta["composer"] = session_data->composer();}
+ if (!session_data->comment().empty()) {meta["comment"] = session_data->comment();}
+ if (!session_data->copyright().empty()) {meta["copyright"] = session_data->copyright();}
+ if (!session_data->subtitle().empty()) {meta["description"] = session_data->subtitle();}
+ }
+
+#if 1 /* tentative debug mode */
+ if (debug_checkbox.get_active()) {
+ transcoder->set_debug(true);
+ }
+#endif
+
+ transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&ExportVideoDialog::update_progress , this, _1, _2), gui_context());
+ transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&ExportVideoDialog::finished, this), gui_context());
+ if (!transcoder->encode(outfn, insnd, invid, ffs, meta, map)) {
+ ARDOUR_UI::instance()->popup_error(_("Transcoding failed."));
+ Gtk::Dialog::response(RESPONSE_CANCEL);
+ return;
+ }
+}
+
+void
+ExportVideoDialog::change_file_extension (std::string ext)
+{
+ outfn_path_entry.set_text (
+ strip_file_extension(outfn_path_entry.get_text()) + ext
+ );
+}
+
+void
+ExportVideoDialog::scale_checkbox_toggled ()
+{
+ width_spinner.set_sensitive(scale_checkbox.get_active());
+ height_spinner.set_sensitive(scale_checkbox.get_active());
+}
+
+void
+ExportVideoDialog::fps_checkbox_toggled ()
+{
+ fps_combo.set_sensitive(fps_checkbox.get_active());
+}
+
+void
+ExportVideoDialog::aspect_checkbox_toggled ()
+{
+ aspect_combo.set_sensitive(aspect_checkbox.get_active());
+}
+
+void
+ExportVideoDialog::video_codec_combo_changed ()
+{
+ if (( video_codec_combo.get_active_text() == "mpeg4"
+ ||video_codec_combo.get_active_text() == "mpeg2video"
+ ||video_codec_combo.get_active_text() == "flv"
+ ) && !(
+ preset_combo.get_active_text() == "dvd-PAL"
+ ||preset_combo.get_active_text() == "dvd-NTSC"
+ )) {
+ bframes_checkbox.set_sensitive(true);
+ optimizations_checkbox.set_sensitive(true);
+ if (video_codec_combo.get_active_text() == "mpeg2video") {
+ optimizations_label.set_text("-mbd rd -trellis 2 -cmp 2 -subcmp 2"); // mpeg2
+ } else if (video_codec_combo.get_active_text() == "mpeg4") {
+ optimizations_label.set_text("-mbd rd -flags +4mv+aic -trellis 2 -cmp 2 -subcmp 2 -g 300"); // mpeg4
+ } else {
+ optimizations_label.set_text("-mbd 2 -cmp 2 -subcmp 2 -trellis 2 -flags +aic+mv0+mv4 -g 160"); // flv
+ }
+ } else {
+ bframes_checkbox.set_sensitive(false);
+ bframes_checkbox.set_active(false);
+ optimizations_checkbox.set_sensitive(false);
+ optimizations_checkbox.set_active(false);
+ optimizations_label.set_text("-");
+ }
+}
+
+void
+ExportVideoDialog::preset_combo_changed ()
+{
+ std::string p = preset_combo.get_active_text();
+ scale_checkbox.set_sensitive(true);
+
+ if (p == "flv") {
+ change_file_extension(".flv");
+ audio_codec_combo.set_active(1);
+ video_codec_combo.set_active(0);
+ audio_bitrate_combo.set_active(1);
+ video_bitrate_combo.set_active(1);
+ audio_samplerate_combo.set_active(1);
+ }
+ else if (p == "you-tube") {
+ change_file_extension(".avi");
+ audio_codec_combo.set_active(2);
+ video_codec_combo.set_active(5);
+ audio_bitrate_combo.set_active(1);
+ video_bitrate_combo.set_active(2);
+ if (_session->nominal_frame_rate() == 48000 || _session->nominal_frame_rate() == 96000) {
+ audio_samplerate_combo.set_active(2);
+ } else {
+ audio_samplerate_combo.set_active(1);
+ }
+ }
+ else if (p == "ogg") {
+ change_file_extension(".ogv");
+ audio_codec_combo.set_active(3);
+ video_codec_combo.set_active(1);
+ audio_bitrate_combo.set_active(2);
+ video_bitrate_combo.set_active(2);
+ if (_session->nominal_frame_rate() == 48000 || _session->nominal_frame_rate() == 96000) {
+ audio_samplerate_combo.set_active(2);
+ } else {
+ audio_samplerate_combo.set_active(1);
+ }
+ }
+ else if (p == "dvd-mp2") {
+ change_file_extension(".mpg");
+ audio_codec_combo.set_active(4);
+ video_codec_combo.set_active(3);
+ audio_bitrate_combo.set_active(3);
+ video_bitrate_combo.set_active(3);
+ audio_samplerate_combo.set_active(2);
+ }
+ else if (p == "dvd-NTSC" || p == "dvd-PAL") {
+ change_file_extension(".mpg");
+ audio_codec_combo.set_active(5);
+ video_codec_combo.set_active(3);
+ audio_bitrate_combo.set_active(3);
+ video_bitrate_combo.set_active(3);
+ audio_samplerate_combo.set_active(2);
+
+ scale_checkbox.set_active(false);
+ scale_checkbox.set_sensitive(false);
+ }
+ else if (p == "mpeg4") {
+ change_file_extension(".mp4");
+ audio_codec_combo.set_active(0);
+ video_codec_combo.set_active(4);
+ audio_bitrate_combo.set_active(3);
+ video_bitrate_combo.set_active(3);
+ if (_session->nominal_frame_rate() == 48000 || _session->nominal_frame_rate() == 96000) {
+ audio_samplerate_combo.set_active(2);
+ } else {
+ audio_samplerate_combo.set_active(1);
+ }
+ }
+
+ if (p == "none") {
+ audio_codec_combo.set_sensitive(true);
+ video_codec_combo.set_sensitive(true);
+ audio_bitrate_combo.set_sensitive(true);
+ video_bitrate_combo.set_sensitive(true);
+ audio_samplerate_combo.set_sensitive(true);
+ } else {
+ audio_codec_combo.set_sensitive(false);
+ video_codec_combo.set_sensitive(false);
+ audio_bitrate_combo.set_sensitive(false);
+ video_bitrate_combo.set_sensitive(false);
+ audio_samplerate_combo.set_sensitive(false);
+ }
+
+ Gtk::Table *t = (Gtk::Table*) preset_combo.get_parent();
+ Gtk::Table_Helpers::TableList c = t->children();
+ Gtk::Table_Helpers::TableList::iterator it;
+ if (p == "dvd-PAL" || p == "dvd-NTSC") {
+ for (it = c.begin(); it != c.end(); ++it) {
+ int row = it->get_top_attach();
+ if (row ==1 || row ==2 || row==4 || row==5 || row == 8) {
+ it->get_widget()->hide();
+ }
+ }
+ } else {
+ for (it = c.begin(); it != c.end(); ++it) {
+ int row = it->get_top_attach();
+ if (row ==1 || row ==2 || row==4 || row==5 || row == 8) {
+ it->get_widget()->show();
+ }
+ }
+ }
+
+ video_codec_combo_changed();
+}
+
+void
+ExportVideoDialog::open_outfn_dialog ()
+{
+ Gtk::FileChooserDialog dialog(_("Save Exported Video File"), Gtk::FILE_CHOOSER_ACTION_SAVE);
+ dialog.set_filename (outfn_path_entry.get_text());
+
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
+
+ int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK) {
+ std::string filename = dialog.get_filename();
+
+ if (filename.length()) {
+ outfn_path_entry.set_text (filename);
+ }
+ }
+}
+
+void
+ExportVideoDialog::open_invid_dialog ()
+{
+ Gtk::FileChooserDialog dialog(_("Save Exported Video File"), Gtk::FILE_CHOOSER_ACTION_SAVE);
+ dialog.set_filename (invid_path_entry.get_text());
+
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
+
+ int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK) {
+ std::string filename = dialog.get_filename();
+
+ if (filename.length()) {
+ invid_path_entry.set_text (filename);
+ }
+ }
+}
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/export_video_dialog.h b/gtk2_ardour/export_video_dialog.h
new file mode 100644
index 0000000000..ae0beafd37
--- /dev/null
+++ b/gtk2_ardour/export_video_dialog.h
@@ -0,0 +1,122 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#ifndef __gtk_ardour_export_video_dialog_h__
+#define __gtk_ardour_export_video_dialog_h__
+
+#include <string>
+
+#include <gtkmm.h>
+
+#include "ardour/types.h"
+#include "ardour/template_utils.h"
+#include "ardour_dialog.h"
+
+#include "transcode_ffmpeg.h"
+
+/** @class ExportVideoDialog
+ * @brief dialog box and controller for video-file export
+ *
+ * The ExportVideoDialog includes audio export functions,
+ * video-encoder presets, progress dialogs and uses
+ * \ref TranscodeFfmpeg to communicate with ffmpeg.
+ */
+class ExportVideoDialog : public ArdourDialog , public PBD::ScopedConnectionList
+{
+ public:
+ ExportVideoDialog (PublicEditor&, ARDOUR::Session*);
+ ~ExportVideoDialog ();
+
+ std::string get_exported_filename () { return outfn_path_entry.get_text(); }
+
+ private:
+ PublicEditor& editor;
+
+ void on_show ();
+ void abort_clicked ();
+ void launch_export ();
+ void encode_pass (int);
+ void change_file_extension (std::string);
+
+ void open_outfn_dialog ();
+ void open_invid_dialog ();
+ void scale_checkbox_toggled ();
+ void preset_combo_changed ();
+ void video_codec_combo_changed ();
+ void aspect_checkbox_toggled ();
+ void fps_checkbox_toggled ();
+
+ bool aborted;
+ bool twopass;
+
+ void finished ();
+ void update_progress (ARDOUR::framecnt_t, ARDOUR::framecnt_t);
+
+ TranscodeFfmpeg *transcoder;
+ std::string insnd;
+
+ Gtk::Label outfn_path_label;
+ Gtk::Entry outfn_path_entry;
+ Gtk::Button outfn_browse_button;
+ Gtk::Label invid_path_label;
+ Gtk::Entry invid_path_entry;
+ Gtk::Button invid_browse_button;
+ Gtk::ComboBoxText insnd_combo;
+ Gtk::Button transcode_button;
+
+ Gtk::VBox* vbox;
+ Gtk::Button *cancel_button;
+ Gtk::Button abort_button;
+
+ Gtk::VBox* progress_box;
+ Gtk::ProgressBar pbar;
+
+ Gtk::ComboBoxText audio_codec_combo;
+ Gtk::ComboBoxText video_codec_combo;
+ Gtk::ComboBoxText video_bitrate_combo;
+ Gtk::ComboBoxText audio_bitrate_combo;
+ Gtk::ComboBoxText audio_samplerate_combo;
+ Gtk::ComboBoxText preset_combo;
+
+ Gtk::CheckButton scale_checkbox;
+ Gtk::Adjustment width_adjustment;
+ Gtk::SpinButton width_spinner;
+ Gtk::Adjustment height_adjustment;
+ Gtk::SpinButton height_spinner;
+ Gtk::CheckButton aspect_checkbox;
+ Gtk::ComboBoxText aspect_combo;
+ Gtk::CheckButton normalize_checkbox;
+ Gtk::CheckButton twopass_checkbox;
+ Gtk::CheckButton optimizations_checkbox;
+ Gtk::Label optimizations_label;
+ Gtk::CheckButton deinterlace_checkbox;
+ Gtk::CheckButton bframes_checkbox;
+ Gtk::CheckButton fps_checkbox;
+ Gtk::ComboBoxText fps_combo;
+ Gtk::CheckButton meta_checkbox;
+#if 1 /* tentative debug mode */
+ Gtk::CheckButton debug_checkbox;
+#endif
+};
+
+#endif /* __gtk_ardour_export_video_dialog_h__ */
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/export_video_infobox.cc b/gtk2_ardour/export_video_infobox.cc
new file mode 100644
index 0000000000..d12ceb7ed8
--- /dev/null
+++ b/gtk2_ardour/export_video_infobox.cc
@@ -0,0 +1,76 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include "ardour/session.h"
+#include "export_video_infobox.h"
+#include "i18n.h"
+
+using namespace Gtk;
+using namespace std;
+using namespace PBD;
+using namespace ARDOUR;
+
+ExportVideoInfobox::ExportVideoInfobox (Session* s)
+ : ArdourDialog (_("Video Export Info"))
+ , showagain_checkbox (_("Don't show this dialog again. (Reset in Edit->Preferences)."))
+{
+ set_session (s);
+
+ set_name ("ExportVideoInfobox");
+ set_position (Gtk::WIN_POS_MOUSE);
+ set_modal (true);
+ set_skip_taskbar_hint (true);
+ set_resizable (false);
+
+ Gtk::Label* l;
+ VBox* vbox = manage (new VBox);
+
+ l = manage (new Label (_("<b>Video Export Info</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_use_markup ();
+ vbox->pack_start (*l, false, true);
+ l = manage (new Label (_("The video export is not recommended for mastering!\nThe video input file defaults to that listed from the video timeline, which may not the the best quality to start with. While 'ffmpeg' can produce high-quality files, this export lacks the possibility to tweak many settings. We recommend to use 'winff', 'devede' or 'dvdauthor' to mux & master. Nevertheless this video-export may come in handy to do quick snapshots, intermediates or online videos.\n\nThe file-format is determined by the extension you choose for the output file (.avi, .mov, .flv, .ogv; run 'ffmpeg -formats' for a full list of supported formats)\nNote: not all combinations do work, i.e., flv files require samplerates of 22.1kHz or 44.1kHz, mpeg containers can not be used with ac3 audio-codec, etc. If in doubt, use one of the presets."), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_size_request(700,-1);
+ l->set_line_wrap();
+ vbox->pack_start (*l, false, true,4);
+
+ vbox->pack_start (*(manage (new HSeparator())), true, true, 2);
+ vbox->pack_start (showagain_checkbox, false, true, 2);
+
+ get_vbox()->set_spacing (4);
+ get_vbox()->pack_start (*vbox, false, false);
+
+ showagain_checkbox.set_active(false);
+ show_all_children ();
+ add_button (Stock::OK, RESPONSE_ACCEPT);
+}
+
+ExportVideoInfobox::~ExportVideoInfobox ()
+{
+}
+/*
+void
+ExportVideoInfobox::on_show ()
+{
+ Dialog::on_show ();
+}
+*/
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/export_video_infobox.h b/gtk2_ardour/export_video_infobox.h
new file mode 100644
index 0000000000..20944b934c
--- /dev/null
+++ b/gtk2_ardour/export_video_infobox.h
@@ -0,0 +1,49 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#ifndef __gtk_ardour_export_video_infobox_h__
+#define __gtk_ardour_export_video_infobox_h__
+
+#include <gtkmm.h>
+#include "ardour_dialog.h"
+
+/** @class ExportVideoInfobox
+ * @brief optional info box regarding video-export
+ *
+ * This dialog is optional and can be en/disabled in the
+ * Preferences.
+ */
+class ExportVideoInfobox : public ArdourDialog
+{
+ public:
+ ExportVideoInfobox (ARDOUR::Session*);
+ ~ExportVideoInfobox ();
+
+ bool show_again () { return showagain_checkbox.get_active(); }
+
+ private:
+ //void on_show ();
+ Gtk::CheckButton showagain_checkbox;
+};
+
+#endif /* __gtk_ardour_export_video_infobox_h__ */
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/open_video_monitor_dialog.cc b/gtk2_ardour/open_video_monitor_dialog.cc
new file mode 100644
index 0000000000..55762cc014
--- /dev/null
+++ b/gtk2_ardour/open_video_monitor_dialog.cc
@@ -0,0 +1,196 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include <cstdio>
+#include <cmath>
+
+#include <sigc++/bind.h>
+
+#include "pbd/file_utils.h"
+#include "pbd/error.h"
+#include "pbd/convert.h"
+#include "gtkmm2ext/utils.h"
+#include "ardour/profile.h"
+#include "ardour/template_utils.h"
+#include "ardour/session.h"
+#include "ardour_ui.h"
+
+#include "utils.h"
+#include "add_video_dialog.h"
+#include "video_monitor.h"
+#include "i18n.h"
+
+using namespace Gtk;
+using namespace std;
+using namespace PBD;
+using namespace ARDOUR;
+
+OpenVideoMonitorDialog::OpenVideoMonitorDialog (Session* s)
+ : ArdourDialog (_("Open Video Monitor"))
+ , filename_label ()
+ , showagain_checkbox (_("Don't show this dialog again. (Reset in Edit->Preferences)."))
+ , win_checkbox (_("Restore last window size and position."))
+ , att_checkbox (_("Restore Window Attributes (fullscreen, on-top)."))
+ , osd_checkbox (_("Restore On-Screen-Display settings."))
+ , off_checkbox (_("Restore Time Offset."))
+ , label_winsize ()
+ , label_winpos ()
+ , label_letterbox ()
+ , label_ontop ()
+ , label_fullscreen ()
+ , label_osd ()
+ , label_offset ()
+#if 1
+ , debug_checkbox (_("Enable Debug Mode: Dump Communication to stdout."))
+#endif
+{
+ set_session (s);
+
+ set_name ("OpenVideoMonitorDialog");
+ set_position (Gtk::WIN_POS_MOUSE);
+ set_modal (true);
+ set_skip_taskbar_hint (true);
+ set_resizable (false);
+
+ Gtk::Label* l;
+ VBox* vbox = manage (new VBox);
+ VBox* options_box = manage (new VBox);
+
+ l = manage (new Label (_("<b>Video Monitor Window</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_use_markup ();
+ vbox->pack_start (*l, false, true);
+ l = manage (new Label (_("The video monitor state can restored to the last known settings for this session. To modify the settings, interact with the monitor itself: Move its window or focus it and use keyboard shortcuts (or the OSX menu bar). Consult the xjadeo documentation for available keyboard shortcuts."), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_size_request(550,-1);
+ l->set_line_wrap();
+ vbox->pack_start (*l, false, true,4);
+
+ l = manage (new Label (_("<b>Open Video file:</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_use_markup ();
+ vbox->pack_start (*l, false, true, 4);
+ vbox->pack_start (filename_label, false, false);
+
+ l = manage (new Label (_("<b>Session Options</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_use_markup ();
+ options_box->pack_start (*l, false, true, 4);
+
+ options_box->pack_start (win_checkbox, false, true, 2);
+ options_box->pack_start (label_winpos, false, false, 2);
+ options_box->pack_start (label_winsize, false, false, 2);
+ options_box->pack_start (label_letterbox, false, false, 2);
+
+ options_box->pack_start (att_checkbox, false, true, 2);
+ options_box->pack_start (label_fullscreen, false, false, 2);
+ options_box->pack_start (label_ontop, false, false, 2);
+
+ options_box->pack_start (osd_checkbox, false, true, 2);
+ options_box->pack_start (label_osd, false, false, 2);
+
+ options_box->pack_start (off_checkbox, false, true, 2);
+ options_box->pack_start (label_offset, false, false, 2);
+
+ options_box->pack_start (*(manage (new HSeparator())), true, true, 2);
+ options_box->pack_start (showagain_checkbox, false, true, 2);
+
+#if 1
+ options_box->pack_start (debug_checkbox, false, true, 2);
+ debug_checkbox.set_active(false);
+#endif
+
+ vbox->pack_start (*options_box, false, true);
+
+ get_vbox()->set_spacing (4);
+ get_vbox()->pack_start (*vbox, false, false);
+
+ showagain_checkbox.set_active(false);
+ show_all_children ();
+ add_button (Stock::CANCEL, RESPONSE_CANCEL);
+ add_button (Stock::OPEN, RESPONSE_ACCEPT);
+}
+
+OpenVideoMonitorDialog::~OpenVideoMonitorDialog ()
+{
+}
+
+void
+OpenVideoMonitorDialog::on_show ()
+{
+ label_offset.set_text(string_compose(_("Offset: %1 Video frame(s)"), "-"));
+ label_osd.set_text(string_compose(_("On-Screen-Display: %1"), "-"));
+ label_letterbox.set_text(string_compose(_("Letterbox: %1"), "-"));
+ label_winsize.set_text(string_compose(_("Size: %1"), "-"));
+ label_winpos.set_text(string_compose(_("Position: %1"), "-"));
+ label_fullscreen.set_text(string_compose(_("Fullscreen: %1"), "-"));
+ label_ontop.set_text(string_compose(_("Window on Top: %1"), "-"));
+
+#define L_YESNO(v) (atoi(v)?_("Yes"):_("No"))
+#define L_OSDMODE(i) ( std::string((i)? "":_("(Off)")) \
+ +std::string((i&1)?_("Frame Number "):"") \
+ +std::string((i&2)?_("SMPTE "):"") \
+ +std::string((i&4)?_("Text "):"") \
+ +std::string((i&8)?_("Box "):"") )
+
+ XMLNode* node = _session->extra_xml (X_("XJSettings"));
+ if (node) {
+ XMLNodeList nlist = node->children();
+ XMLNodeConstIterator niter;
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ std::string k = (*niter)->property(X_("k"))->value();
+ std::string v = (*niter)->property(X_("v"))->value();
+ if (k == "osd mode") { label_osd.set_text(string_compose(_("On-Screen-Display: %1"), L_OSDMODE(atoi(v)))); }
+ if (k == "window letterbox") { label_letterbox.set_text(string_compose(_("Letterbox: %1"), L_YESNO(v))); }
+ if (k == "window xy") { label_winpos.set_text(string_compose(_("Position: %1"), v)); }
+ if (k == "window ontop") { label_ontop.set_text(string_compose(_("Window On Top: %1"), L_YESNO(v))); }
+ if (k == "window zoom") { label_fullscreen.set_text(string_compose(_("Fullscreen: %1"), L_YESNO(v))); }
+ if (k == "window size") { label_winsize.set_text(string_compose(_("Size: %1"), v)); }
+ if (k == "set offset") { label_offset.set_text(string_compose(_("Offset: %1 video-frame(s)"), v)); }
+ }
+ }
+
+ Dialog::on_show ();
+}
+
+const int
+OpenVideoMonitorDialog::xj_settings_mask ()
+{
+ int rv =0;
+ if (!win_checkbox.get_active()) { rv |= XJ_WINDOW_SIZE | XJ_WINDOW_POS | XJ_LETTERBOX; }
+ if (!att_checkbox.get_active()) { rv |= XJ_WINDOW_ONTOP | XJ_FULLSCREEN; }
+ if (!osd_checkbox.get_active()) { rv |= XJ_OSD; }
+ if (!off_checkbox.get_active()) { rv |= XJ_OFFSET; }
+ return rv;
+}
+
+void
+OpenVideoMonitorDialog::set_filename (const std::string fn)
+{
+ filename_label.set_text(fn);
+}
+
+void
+OpenVideoMonitorDialog::setup_settings_mask (const int f)
+{
+ win_checkbox.set_active((f&XJ_WINDOW_SIZE) == 0);
+ att_checkbox.set_active((f&XJ_WINDOW_ONTOP) == 0);
+ osd_checkbox.set_active((f&XJ_OSD) == 0);
+ off_checkbox.set_active((f&XJ_OFFSET) == 0);
+}
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/open_video_monitor_dialog.h b/gtk2_ardour/open_video_monitor_dialog.h
new file mode 100644
index 0000000000..f0324a7caf
--- /dev/null
+++ b/gtk2_ardour/open_video_monitor_dialog.h
@@ -0,0 +1,80 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#ifndef __gtk_ardour_open_video_monitor_dialog_h__
+#define __gtk_ardour_open_video_monitor_dialog_h__
+
+#include <string>
+
+#include <gtkmm.h>
+
+#include "ardour/types.h"
+#include "ardour/template_utils.h"
+#include "ardour_dialog.h"
+
+/** @class OpenVideoMonitorDialog
+ * @brief video-monitor start-option dialog
+ *
+ * This dialog allows to override xjadeo startup-options
+ * eg. restore previous size&position, offset or letterbox
+ * settings.
+ *
+ * This dialog is optional and can be en/disabled in the
+ * Preferences.
+ */
+class OpenVideoMonitorDialog : public ArdourDialog
+{
+ public:
+ OpenVideoMonitorDialog (ARDOUR::Session*);
+ ~OpenVideoMonitorDialog ();
+
+ bool show_again () { return showagain_checkbox.get_active(); }
+ const int xj_settings_mask ();
+ void setup_settings_mask (const int);
+ void set_filename (const std::string);
+#if 1
+ bool enable_debug () { return debug_checkbox.get_active(); }
+#endif
+
+ private:
+ void on_show ();
+ Gtk::Label filename_label;
+ Gtk::CheckButton showagain_checkbox;
+ Gtk::CheckButton win_checkbox;
+ Gtk::CheckButton att_checkbox;
+ Gtk::CheckButton osd_checkbox;
+ Gtk::CheckButton off_checkbox;
+
+ Gtk::Label label_winsize;
+ Gtk::Label label_winpos;
+ Gtk::Label label_letterbox;
+ Gtk::Label label_ontop;
+ Gtk::Label label_fullscreen;
+ Gtk::Label label_osd;
+ Gtk::Label label_offset;
+#if 1
+ Gtk::CheckButton debug_checkbox;
+#endif
+};
+
+#endif /* __gtk_ardour_open_video_monitor_dialog_h__ */
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h
index aebb818d9b..788951f143 100644
--- a/gtk2_ardour/public_editor.h
+++ b/gtk2_ardour/public_editor.h
@@ -292,6 +292,17 @@ class PublicEditor : public Gtk::Window, public PBD::StatefulDestructible {
virtual framecnt_t get_nudge_distance (framepos_t pos, framecnt_t& next) = 0;
virtual Evoral::MusicalTime get_grid_type_as_beats (bool& success, framepos_t position) = 0;
+#ifdef WITH_VIDEOTIMELINE
+ virtual void queue_visual_videotimeline_update () = 0;
+ virtual void toggle_ruler_video (bool) = 0;
+ virtual void toggle_xjadeo_proc (int) = 0;
+ virtual void set_xjadeo_sensitive (bool onoff) = 0;
+ virtual int get_videotl_bar_height () const = 0;
+ virtual void set_video_timeline_height (const int h) = 0;
+ virtual void embed_audio_from_video (std::string) = 0;
+ virtual void export_video () = 0;
+#endif
+
#ifdef WITH_CMT
virtual void connect_to_image_compositor() = 0;
virtual void add_imageframe_time_axis(const std::string & track_name, void*) = 0;
@@ -336,6 +347,9 @@ class PublicEditor : public Gtk::Window, public PBD::StatefulDestructible {
virtual bool canvas_feature_line_event (GdkEvent* event, ArdourCanvas::Item*, RegionView*) = 0;
virtual bool canvas_stream_view_event (GdkEvent* event, ArdourCanvas::Item*, RouteTimeAxisView*) = 0;
virtual bool canvas_marker_event (GdkEvent* event, ArdourCanvas::Item*, Marker*) = 0;
+#ifdef WITH_VIDEOTIMELINE
+ virtual bool canvas_videotl_bar_event (GdkEvent* event, ArdourCanvas::Item*) = 0;
+#endif
virtual bool canvas_zoom_rect_event (GdkEvent* event, ArdourCanvas::Item*) = 0;
virtual bool canvas_tempo_marker_event (GdkEvent* event, ArdourCanvas::Item*, TempoMarker*) = 0;
virtual bool canvas_meter_marker_event (GdkEvent* event, ArdourCanvas::Item*, MeterMarker*) = 0;
diff --git a/gtk2_ardour/rc_option_editor.cc b/gtk2_ardour/rc_option_editor.cc
index 7d290ede2f..d58ad2994c 100644
--- a/gtk2_ardour/rc_option_editor.cc
+++ b/gtk2_ardour/rc_option_editor.cc
@@ -809,6 +809,118 @@ private:
PBD::ScopedConnection protocol_status_connection;
};
+#ifdef WITH_VIDEOTIMELINE
+class VideoTimelineOptions : public OptionEditorBox
+{
+public:
+ VideoTimelineOptions (RCConfiguration* c)
+ : _rc_config (c)
+ , _show_xjadeo_setup_button (_("Show Video Monitor Option Dialog"))
+ , _show_video_export_info_button (_("Show Video Export Info before export"))
+ , _show_video_server_dialog_button (_("Show Video Server Startup Dialog"))
+ {
+ Table* t = manage (new Table (2, 6));
+ t->set_spacings (4);
+
+ Label* l = manage (new Label (_("Video Server URL:")));
+ l->set_alignment (0, 0.5);
+ t->attach (*l, 0, 1, 0, 1, FILL);
+ t->attach (_video_server_url_entry, 1, 2, 0, 1, FILL);
+ Gtkmm2ext::UI::instance()->set_tip (_video_server_url_entry,
+ _("Base URL of the video-server including http prefix. This is usually 'http://hostname.example.org:1554/' and defaults to 'http://localhost:1554/' when the video-server is runing locally"));
+
+ l = manage (new Label (_("Video Server Docroot:")));
+ l->set_alignment (0, 0.5);
+ t->attach (*l, 0, 1, 1, 2, FILL);
+ t->attach (_video_server_docroot_entry, 1, 2, 1, 2);
+ Gtkmm2ext::UI::instance()->set_tip (_video_server_docroot_entry,
+ _("Local path to the video-server docroot. If the server runs remotely, it should point to a network mounted folder of the server's docroot or be left empty if it is unvailable. It is used for the local video-monitor and file-browsing when opening/adding a video file."));
+
+ t->attach (_show_xjadeo_setup_button, 0, 2, 3, 4);
+ _show_xjadeo_setup_button.signal_toggled().connect (sigc::mem_fun (*this, &VideoTimelineOptions::show_xjadeo_setup_toggled));
+ Gtkmm2ext::UI::instance()->set_tip (_show_xjadeo_setup_button,
+ _("<b>When enabled</b> an option dialog is presented before opening the video monitor"));
+
+ t->attach (_show_video_export_info_button, 0, 2, 4, 5);
+ _show_video_export_info_button.signal_toggled().connect (sigc::mem_fun (*this, &VideoTimelineOptions::show_video_export_info_toggled));
+ Gtkmm2ext::UI::instance()->set_tip (_show_video_export_info_button,
+ _("<b>When enabled</b> an information window with details is displayed before the video-export dialog."));
+
+ t->attach (_show_video_server_dialog_button, 0, 2, 5, 6);
+ _show_video_server_dialog_button.signal_toggled().connect (sigc::mem_fun (*this, &VideoTimelineOptions::show_video_server_dialog_toggled));
+ Gtkmm2ext::UI::instance()->set_tip (_show_video_server_dialog_button,
+ _("<b>When enabled</b> the video server is never launched automatically without confirmation"));
+
+ _video_server_url_entry.signal_changed().connect (sigc::mem_fun(*this, &VideoTimelineOptions::server_url_changed));
+ _video_server_url_entry.signal_activate().connect (sigc::mem_fun(*this, &VideoTimelineOptions::server_url_changed));
+ _video_server_docroot_entry.signal_changed().connect (sigc::mem_fun(*this, &VideoTimelineOptions::server_docroot_changed));
+ _video_server_docroot_entry.signal_activate().connect (sigc::mem_fun(*this, &VideoTimelineOptions::server_docroot_changed));
+
+ _box->pack_start (*t,true,true);
+ }
+
+ void server_url_changed ()
+ {
+ _rc_config->set_video_server_url (_video_server_url_entry.get_text());
+ }
+
+ void server_docroot_changed ()
+ {
+ _rc_config->set_video_server_docroot (_video_server_docroot_entry.get_text());
+ }
+
+ void show_xjadeo_setup_toggled ()
+ {
+ bool const x = _show_xjadeo_setup_button.get_active ();
+ _rc_config->set_video_monitor_setup_dialog (x);
+ }
+
+ void show_video_export_info_toggled ()
+ {
+ bool const x = _show_video_export_info_button.get_active ();
+ _rc_config->set_show_video_export_info (x);
+ }
+
+ void show_video_server_dialog_toggled ()
+ {
+ bool const x = _show_video_server_dialog_button.get_active ();
+ _rc_config->set_show_video_server_dialog (x);
+ }
+
+ void parameter_changed (string const & p)
+ {
+ if (p == "video-server-url") {
+ _video_server_url_entry.set_text (_rc_config->get_video_server_url());
+ } else if (p == "video-server-docroot") {
+ _video_server_docroot_entry.set_text (_rc_config->get_video_server_docroot());
+ } else if (p == "video-monitor-setup-dialog") {
+ bool const x = _rc_config->get_video_monitor_setup_dialog();
+ _show_xjadeo_setup_button.set_active (x);
+ } else if (p == "show-video-export-info") {
+ bool const x = _rc_config->get_show_video_export_info();
+ _show_video_export_info_button.set_active (x);
+ }
+ }
+
+ void set_state_from_config ()
+ {
+ parameter_changed ("video-server-url");
+ parameter_changed ("video-server-docroot");
+ parameter_changed ("video-monitor-setup-dialog");
+ parameter_changed ("show-video-export-info");
+ parameter_changed ("how-video-server-dialog");
+ }
+
+private:
+ RCConfiguration* _rc_config;
+ Entry _video_server_url_entry;
+ Entry _video_server_docroot_entry;
+ CheckButton _show_xjadeo_setup_button;
+ CheckButton _show_video_export_info_button;
+ CheckButton _show_video_server_dialog_button;
+};
+#endif
+
/** A class which allows control of visibility of some editor components usign
* a VisibilityGroup. The caller should pass in a `dummy' VisibilityGroup
* which has the correct members, but with null widget pointers. This
@@ -870,6 +982,7 @@ private:
};
+
RCOptionEditor::RCOptionEditor ()
: OptionEditor (Config, string_compose (_("%1 Preferences"), PROGRAM_NAME))
, _rc_config (Config)
@@ -1711,6 +1824,11 @@ RCOptionEditor::RCOptionEditor ()
add_option (_("User interaction"), rm);
+#ifdef WITH_VIDEOTIMELINE
+ /* VIDEO Timeline */
+ add_option (_("Video"), new VideoTimelineOptions (_rc_config));
+#endif
+
/* INTERFACE */
add_option (S_("GUI"),
diff --git a/gtk2_ardour/region_view.cc b/gtk2_ardour/region_view.cc
index 8ea84409cc..dd99a5237b 100644
--- a/gtk2_ardour/region_view.cc
+++ b/gtk2_ardour/region_view.cc
@@ -386,6 +386,12 @@ RegionView::region_changed (const PropertyChange& what_changed)
if (what_changed.contains (ARDOUR::Properties::locked)) {
region_locked ();
}
+#ifdef WITH_VIDEOTIMELINE
+ if (what_changed.contains (ARDOUR::Properties::locked)) {
+ /* name will show locked status */
+ region_renamed ();
+ }
+#endif
}
void
@@ -578,6 +584,12 @@ RegionView::make_name () const
str += '{';
str += _region->name();
str += '}';
+#ifdef WITH_VIDEOTIMELINE
+ } else if (_region->video_locked()) {
+ str += '[';
+ str += _region->name();
+ str += ']';
+#endif
} else {
str = _region->name();
}
diff --git a/gtk2_ardour/session_option_editor.cc b/gtk2_ardour/session_option_editor.cc
index e49e9e33f4..229d376255 100644
--- a/gtk2_ardour/session_option_editor.cc
+++ b/gtk2_ardour/session_option_editor.cc
@@ -79,6 +79,21 @@ SessionOptionEditor::SessionOptionEditor (Session* s)
add_option (_("Timecode"), _vpu);
+#ifdef WITH_VIDEOTIMELINE
+ add_option (_("Sync"), new BoolOption (
+ "use-video-file-fps",
+ _("Use Video File's FPS Instead of Timecode Value for Timeline and Video Monitor."),
+ sigc::mem_fun (*_session_config, &SessionConfiguration::get_use_video_file_fps),
+ sigc::mem_fun (*_session_config, &SessionConfiguration::set_use_video_file_fps)
+ ));
+
+ add_option (_("Sync"), new BoolOption (
+ "videotimeline-pullup",
+ _("Apply Pull-Up/Down to Video Timeline and Video Monitor (Unless in JACK-sync)."),
+ sigc::mem_fun (*_session_config, &SessionConfiguration::get_videotimeline_pullup),
+ sigc::mem_fun (*_session_config, &SessionConfiguration::set_videotimeline_pullup)
+ ));
+#endif
add_option (_("Timecode"), new OptionEditorHeading (_("Ext Timecode Offsets")));
diff --git a/gtk2_ardour/system_exec.cc b/gtk2_ardour/system_exec.cc
new file mode 100644
index 0000000000..22e71d7534
--- /dev/null
+++ b/gtk2_ardour/system_exec.cc
@@ -0,0 +1,664 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#ifdef __WIN32__
+#include <windows.h>
+#else
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#endif
+
+#include "system_exec.h"
+
+using namespace std;
+void * interposer_thread (void *arg);
+
+SystemExec::SystemExec (std::string c, std::string a)
+ : cmd(c)
+{
+ pthread_mutex_init(&write_lock, NULL);
+ thread_active=false;
+ pid = 0;
+ pin[1] = -1;
+ nicelevel = 0;
+ envp = NULL;
+ argp = NULL;
+#ifdef __WIN32__
+ stdinP[0] = stdinP[1] = INVALID_HANDLE_VALUE;
+ stdoutP[0] = stdoutP[1] = INVALID_HANDLE_VALUE;
+ stderrP[0] = stderrP[1] = INVALID_HANDLE_VALUE;
+#endif
+ make_envp();
+ make_argp(a);
+}
+
+SystemExec::SystemExec (std::string c, char **a)
+ : cmd(c) , argp(a)
+{
+ pthread_mutex_init(&write_lock, NULL);
+ thread_active=false;
+ pid = 0;
+ pin[1] = -1;
+ nicelevel = 0;
+ envp = NULL;
+#ifdef __WIN32__
+ stdinP[0] = stdinP[1] = INVALID_HANDLE_VALUE;
+ stdoutP[0] = stdoutP[1] = INVALID_HANDLE_VALUE;
+ stderrP[0] = stderrP[1] = INVALID_HANDLE_VALUE;
+ make_wargs(a);
+#endif
+ make_envp();
+}
+
+SystemExec::~SystemExec ()
+{
+ terminate ();
+ if (envp) {
+ for (int i=0;envp[i];++i) {
+ free(envp[i]);
+ }
+ free (envp);
+ }
+ if (argp) {
+ for (int i=0;argp[i];++i) {
+ free(argp[i]);
+ }
+ free (argp);
+ }
+#ifdef __WIN32__
+ if (w_args) free(w_args);
+#endif
+ pthread_mutex_destroy(&write_lock);
+}
+
+void *
+interposer_thread (void *arg) {
+ SystemExec *sex = static_cast<SystemExec *>(arg);
+ sex->output_interposer();
+ pthread_exit(0);
+ return 0;
+}
+
+#ifdef __WIN32__ /* Windows Process */
+
+/* HELPER FUNCTIONS */
+
+static void create_pipe (HANDLE *pipe, bool in) {
+ SECURITY_ATTRIBUTES secAtt = { sizeof( SECURITY_ATTRIBUTES ), NULL, TRUE };
+ HANDLE tmpHandle;
+ if (in) {
+ if (!CreatePipe(&pipe[0], &tmpHandle, &secAtt, 1024 * 1024)) return;
+ if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, GetCurrentProcess(), &pipe[1], 0, FALSE, DUPLICATE_SAME_ACCESS)) return;
+ } else {
+ if (!CreatePipe(&tmpHandle, &pipe[1], &secAtt, 1024 * 1024)) return;
+ if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, GetCurrentProcess(), &pipe[0], 0, FALSE, DUPLICATE_SAME_ACCESS)) return;
+ }
+ CloseHandle(tmpHandle);
+}
+
+static void destroy_pipe (HANDLE pipe[2]) {
+ if (pipe[0] != INVALID_HANDLE_VALUE) {
+ CloseHandle(pipe[0]);
+ pipe[0] = INVALID_HANDLE_VALUE;
+ }
+ if (pipe[1] != INVALID_HANDLE_VALUE) {
+ CloseHandle(pipe[1]);
+ pipe[1] = INVALID_HANDLE_VALUE;
+ }
+}
+
+static BOOL CALLBACK my_terminateApp(HWND hwnd, LPARAM procId)
+{
+ DWORD currentProcId = 0;
+ GetWindowThreadProcessId(hwnd, &currentProcId);
+ if (currentProcId == (DWORD)procId)
+ PostMessage(hwnd, WM_CLOSE, 0, 0);
+ return TRUE;
+}
+
+/* PROCESS API */
+
+void
+SystemExec::make_envp() {
+ ;/* environemt is copied over with CreateProcess(...,env=0 ,..) */
+}
+
+void
+SystemExec::make_wargs(char **a) {
+ std::string wa = cmd;
+ if (cmd[0] != '"' && cmd[cmd.size()] != '"' && strchr(cmd.c_str(), ' ')) { wa = "\"" + cmd + "\""; }
+ std::replace(cmd.begin(), cmd.end(), '/', '\\' );
+ char **tmp = a;
+ while (tmp && *tmp) {
+ wa.append(" \"");
+ wa.append(*tmp);
+ wa.append("\"");
+ tmp++;
+ }
+ w_args = strdup(wa.c_str());
+}
+
+void
+SystemExec::make_argp(std::string args) {
+ std::string wa = cmd;
+ if (cmd[0] != '"' && cmd[cmd.size()] != '"' && strchr(cmd.c_str(), ' ')) { wa = "\"" + cmd + "\""; }
+ std::replace(cmd.begin(), cmd.end(), '/', '\\' );
+ wa.append(" ");
+ wa.append(args);
+ w_args = strdup(wa.c_str());
+}
+
+void
+SystemExec::terminate ()
+{
+ ::pthread_mutex_lock(&write_lock);
+ if (pid) {
+ /* terminate */
+ EnumWindows(my_terminateApp, (LPARAM)pid->dwProcessId);
+ PostThreadMessage(pid->dwThreadId, WM_CLOSE, 0, 0);
+
+ /* kill ! */
+ TerminateProcess(pid->hProcess, 0xf291);
+
+ CloseHandle(pid->hThread);
+ CloseHandle(pid->hProcess);
+ destroy_pipe(stdinP);
+ destroy_pipe(stdoutP);
+ destroy_pipe(stderrP);
+ delete pid;
+ pid=0;
+ }
+ ::pthread_mutex_unlock(&write_lock);
+}
+
+int
+SystemExec::wait (int options)
+{
+ while (is_running()) {
+ WaitForSingleObject(pid->hProcess, INFINITE);
+ Sleep(20);
+ }
+ return 0;
+}
+
+bool
+SystemExec::is_running ()
+{
+ return pid?true:false;
+}
+
+int
+SystemExec::start (int stderr_mode)
+{
+ char* working_dir = 0;
+
+ if (pid) { return 0; }
+
+ pid = new PROCESS_INFORMATION;
+ memset(pid, 0, sizeof(PROCESS_INFORMATION));
+
+ create_pipe(stdinP, true);
+ create_pipe(stdoutP, false);
+
+ if (stderr_mode == 2) {
+ /* merge stout & stderr */
+ DuplicateHandle(GetCurrentProcess(), stdoutP[1], GetCurrentProcess(), &stderrP[1], 0, TRUE, DUPLICATE_SAME_ACCESS);
+ } else if (stderr_mode == 1) {
+ //TODO read/flush this pipe or close it...
+ create_pipe(stderrP, false);
+ } else {
+ //TODO: keep stderr of this process mode.
+ }
+
+ bool success = false;
+ STARTUPINFOA startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0,
+ (unsigned long)CW_USEDEFAULT, (unsigned long)CW_USEDEFAULT,
+ (unsigned long)CW_USEDEFAULT, (unsigned long)CW_USEDEFAULT,
+ 0, 0, 0,
+ STARTF_USESTDHANDLES,
+ 0, 0, 0,
+ stdinP[0], stdoutP[1], stderrP[1]
+ };
+
+ success = CreateProcess(0, w_args,
+ 0, 0, /* bInheritHandles = */ TRUE,
+ (CREATE_NO_WINDOW&0) | CREATE_UNICODE_ENVIRONMENT | (0&CREATE_NEW_CONSOLE),
+ /*env = */ 0,
+ working_dir,
+ &startupInfo, pid);
+
+ if (stdinP[0] != INVALID_HANDLE_VALUE) {
+ CloseHandle(stdinP[0]);
+ stdinP[0] = INVALID_HANDLE_VALUE;
+ }
+ if (stdoutP[1] != INVALID_HANDLE_VALUE) {
+ CloseHandle(stdoutP[1]);
+ stdoutP[1] = INVALID_HANDLE_VALUE;
+ }
+ if (stderrP[1] != INVALID_HANDLE_VALUE) {
+ CloseHandle(stderrP[1]);
+ stderrP[1] = INVALID_HANDLE_VALUE;
+ }
+
+ if (!success) {
+ CloseHandle(pid->hThread);
+ CloseHandle(pid->hProcess);
+ destroy_pipe(stdinP);
+ destroy_pipe(stdoutP);
+ destroy_pipe(stderrP);
+ delete pid;
+ pid=0;
+ return -1;
+ }
+
+ int rv = pthread_create(&thread_id_tt, NULL, interposer_thread, this);
+ thread_active=true;
+ if (rv) {
+ thread_active=false;
+ terminate();
+ return -2;
+ }
+ Sleep(20);
+ return 0;
+}
+
+void
+SystemExec::output_interposer()
+{
+ DWORD bytesRead = 0;
+ char data[BUFSIZ];
+ while(1) {
+#if 0 // for non-blocking pipes..
+ DWORD bytesAvail = 0;
+ PeekNamedPipe(stdoutP[0], 0, 0, 0, &bytesAvail, 0);
+ if (bytesAvail < 1) {Sleep(500); printf("N/A\n"); continue;}
+#endif
+ if (stdoutP[0] == INVALID_HANDLE_VALUE) break;
+ if (!ReadFile(stdoutP[0], data, BUFSIZ, &bytesRead, 0)) break;
+ if (bytesRead < 1) continue; /* actually not needed; but this is safe. */
+ data[bytesRead] = 0;
+ ReadStdout(data, bytesRead);/* EMIT SIGNAL */
+ }
+ Terminated();/* EMIT SIGNAL */
+ terminate();
+}
+
+void
+SystemExec::close_stdin()
+{
+ if (stdinP[0]!= INVALID_HANDLE_VALUE) FlushFileBuffers(stdinP[0]);
+ if (stdinP[1]!= INVALID_HANDLE_VALUE) FlushFileBuffers(stdinP[1]);
+ Sleep(200);
+ destroy_pipe(stdinP);
+}
+
+int
+SystemExec::write_to_stdin(std::string d, size_t len)
+{
+ const char *data;
+ DWORD r,c;
+
+ ::pthread_mutex_lock(&write_lock);
+
+ data=d.c_str();
+ if (len == 0) {
+ len=(d.length());
+ }
+ c=0;
+ while (c < len) {
+ if (!WriteFile(stdinP[1], data+c, len-c, &r, NULL)) {
+ if (GetLastError() == 0xE8 /*NT_STATUS_INVALID_USER_BUFFER*/) {
+ Sleep(100);
+ continue;
+ } else {
+ fprintf(stderr, "SYSTEM-EXEC: stdin write error.\n");
+ break;
+ }
+ }
+ c += r;
+ }
+ ::pthread_mutex_unlock(&write_lock);
+ return c;
+}
+
+
+/* end windows process */
+#else
+/* UNIX/POSIX process */
+
+extern char **environ;
+void
+SystemExec::make_envp() {
+ int i=0;
+ envp = (char **) calloc(1, sizeof(char*));
+ /* copy current environment */
+ for (i=0;environ[i];++i) {
+ envp[i] = strdup(environ[i]);
+ envp = (char **) realloc(envp, (i+2) * sizeof(char*));
+ }
+ envp[i] = 0;
+}
+
+void
+SystemExec::make_argp(std::string args) {
+ int argn = 1;
+ char *cp1;
+ char *cp2;
+
+ char *carg = strdup(args.c_str());
+
+ argp = (char **) malloc((argn + 1) * sizeof(char *));
+ if (argp == (char **) 0) {
+ free(carg);
+ return; // FATAL
+ }
+
+ argp[0] = strdup(cmd.c_str());
+
+ /* TODO: quotations and escapes
+ * http://stackoverflow.com/questions/1511797/convert-string-to-argv-in-c
+ *
+ * It's actually not needed. All relevant invocations specify 'argp' directly.
+ * Only 'xjadeo -L -R' uses this function and that uses neither quotations
+ * nor arguments with white-space.
+ */
+ for (cp1 = cp2 = carg; *cp2 != '\0'; ++cp2) {
+ if (*cp2 == ' ') {
+ *cp2 = '\0';
+ argp[argn++] = strdup(cp1);
+ cp1 = cp2 + 1;
+ argp = (char **) realloc(argp, (argn + 1) * sizeof(char *));
+ }
+ }
+ if (cp2 != cp1) {
+ argp[argn++] = strdup(cp1);
+ argp = (char **) realloc(argp, (argn + 1) * sizeof(char *));
+ }
+ argp[argn] = (char *) 0;
+ free(carg);
+}
+
+
+
+void
+SystemExec::terminate ()
+{
+ close_stdin();
+ if (pid) {
+ ::usleep(100000);
+ wait(WNOHANG);
+ }
+
+ if (pid) {
+ ::fprintf(stderr, "Child process is running. trying SIGTERM\n");
+ ::kill(pid, SIGTERM);
+ ::usleep(10000);
+ wait(WNOHANG);
+ }
+ if (pid) {
+ ::fprintf(stderr, "Process is still running! trying SIGKILL\n");
+ ::kill(pid, SIGKILL);
+ }
+
+ wait();
+ if (thread_active) pthread_join(thread_id_tt, NULL);
+}
+
+int
+SystemExec::wait (int options)
+{
+ int status=0;
+ if (pid==0) return -1;
+ if (pid==::waitpid(pid, &status, options)) {
+ pid=0;
+ }
+ if (errno == ECHILD) {
+ pid=0;
+ }
+ return status;
+}
+
+bool
+SystemExec::is_running ()
+{
+ int status=0;
+ if (pid==0) return false;
+ if (::waitpid(pid, &status, WNOHANG)==0) return true;
+ return false;
+}
+
+int
+SystemExec::start (int stderr_mode)
+{
+ if (is_running()) {
+ return 0; // mmh what to return here?
+ }
+ int r;
+
+ if (::pipe(pin) < 0 || ::pipe(pout) < 0 || ::pipe(pok) < 0) {
+ /* Something unexpected went wrong creating a pipe. */
+ return -1;
+ }
+
+ r = ::fork();
+ if (r < 0) {
+ /* failed to fork */
+ return -2;
+ }
+
+ if (r > 0) {
+ /* main */
+ pid=r;
+
+ /* check if execve was successful. */
+ ::close(pok[1]);
+ char buf;
+ for ( ;; ) {
+ int n = ::read(pok[0], &buf, 1 );
+ if ( n==1 ) {
+ /* child process returned from execve */
+ pid=0;
+ ::close(pok[0]);
+ ::close(pin[1]);
+ ::close(pin[0]);
+ ::close(pout[1]);
+ ::close(pout[0]);
+ pin[1] = -1;
+ return -3;
+ } else if ( n==-1 ) {
+ if ( errno==EAGAIN || errno==EINTR )
+ continue;
+ }
+ break;
+ }
+ ::close(pok[0]);
+ /* child started successfully */
+
+#if 0
+/* use fork for output-interposer
+ * it will run in a separated process
+ */
+ /* catch stdout thread */
+ r = ::fork();
+ if (r < 0) {
+ // failed to fork
+ terminate();
+ return -2;
+ }
+ if (r == 0) {
+ /* 2nd child process - catch stdout */
+ ::close(pin[1]);
+ ::close(pout[1]);
+ output_interposer();
+ exit(0);
+ }
+ ::close(pout[1]);
+ ::close(pin[0]);
+ ::close(pout[0]);
+#else /* use pthread */
+ ::close(pout[1]);
+ ::close(pin[0]);
+ int rv = pthread_create(&thread_id_tt, NULL, interposer_thread, this);
+
+ thread_active=true;
+ if (rv) {
+ thread_active=false;
+ terminate();
+ return -2;
+ }
+#endif
+ return 0; /* all systems go - return to main */
+ }
+
+ /* child process - exec external process */
+ ::close(pok[0]);
+ ::fcntl(pok[1], F_SETFD, FD_CLOEXEC);
+
+ ::close(pin[1]);
+ if (pin[0] != STDIN_FILENO) {
+ ::dup2(pin[0], STDIN_FILENO);
+ }
+ ::close(pin[0]);
+ ::close(pout[0]);
+ if (pout[1] != STDOUT_FILENO) {
+ ::dup2(pout[1], STDOUT_FILENO);
+ }
+
+ if (stderr_mode == 2) {
+ /* merge STDERR into output */
+ if (pout[1] != STDERR_FILENO) {
+ ::dup2(pout[1], STDERR_FILENO);
+ }
+ } else if (stderr_mode == 1) {
+ /* ignore STDERR */
+ ::close(STDERR_FILENO);
+ } else {
+ /* keep STDERR */
+ }
+
+ if (pout[1] != STDOUT_FILENO && pout[1] != STDERR_FILENO) {
+ ::close(pout[1]);
+ }
+
+ if (nicelevel !=0) {
+ ::nice(nicelevel);
+ }
+
+#if 0
+ /* chdir to executable dir */
+ char *directory;
+ directory = strdup(cmd.c_str());
+ if (strrchr(directory, '/') != (char *) 0) {
+ ::chdir(directory);
+ }
+ free(directory);
+#endif
+
+#ifdef HAVE_SIGSET
+ sigset(SIGPIPE, SIG_DFL);
+#else
+ signal(SIGPIPE, SIG_DFL);
+#endif
+
+ ::execve(argp[0], argp, envp);
+ /* if we reach here something went wrong.. */
+ char buf = 0;
+ (void) ::write(pok[1], &buf, 1 );
+ (void) ::close(pok[1]);
+ exit(-1);
+ return -1;
+}
+
+void
+SystemExec::output_interposer()
+{
+ int rfd=pout[0];
+ char buf[BUFSIZ];
+ size_t r;
+ for (;fcntl(rfd, F_GETFL)!=-1;) {
+ r = read(rfd, buf, sizeof(buf));
+ if (r < 0 && (errno == EINTR || errno == EAGAIN)) {
+ ::usleep(1000);
+ continue;
+ }
+ if (r <= 0) {
+ break;
+ }
+ buf[r]=0;
+ std::string rv = std::string(buf,r); // TODO: check allocation strategy
+ ReadStdout(rv, r);/* EMIT SIGNAL */
+ }
+ Terminated();/* EMIT SIGNAL */
+ terminate();
+}
+
+void
+SystemExec::close_stdin()
+{
+ if (pin[1]<0) return;
+ ::close(pin[0]);
+ ::close(pin[1]);
+ ::close(pout[0]);
+ ::close(pout[1]);
+}
+
+int
+SystemExec::write_to_stdin(std::string d, size_t len)
+{
+ const char *data;
+ size_t r,c;
+ ::pthread_mutex_lock(&write_lock);
+
+ data=d.c_str();
+ if (len == 0) {
+ len=(d.length());
+ }
+ c=0;
+ while (c < len) {
+ for (;;) {
+ r=::write(pin[1], data+c, len-c);
+ if (r < 0 && (errno == EINTR || errno == EAGAIN)) {
+ sleep(1);
+ continue;
+ }
+ if (r != (len-c)) {
+ ::pthread_mutex_unlock(&write_lock);
+ return c;
+ }
+ break;
+ }
+ c += r;
+ }
+ ::pthread_mutex_unlock(&write_lock);
+ return c;
+}
+
+#endif // end UNIX process
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/system_exec.h b/gtk2_ardour/system_exec.h
new file mode 100644
index 0000000000..35123d4ccf
--- /dev/null
+++ b/gtk2_ardour/system_exec.h
@@ -0,0 +1,204 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#ifndef __ardour_system_exec_h__
+#define __ardour_system_exec_h__
+
+#ifndef STDIN_FILENO
+#define STDIN_FILENO 0
+#endif
+#ifndef STDOUT_FILENO
+#define STDOUT_FILENO 1
+#endif
+#ifndef STDERR_FILENO
+#define STDERR_FILENO 2
+#endif
+
+#include <string>
+#include <pthread.h>
+#include <signal.h>
+#ifdef NOPBD /* outside ardour */
+#include <sigc++/bind.h>
+#include <sigc++/signal.h>
+#else
+#include <pbd/signals.h>
+#endif
+
+/** @class: SystemExec
+ * @brief execute an external command
+ *
+ * This class allows launche an external command-line application
+ * opening a full-duplex connection to its standard I/O.
+ *
+ * In Ardour context it is used to launch xjadeo and ffmpeg.
+ *
+ * The \ref write_to_stdin function provides for injecting data into STDIN
+ * of the child-application while output of the program to STDOUT/STDERR is
+ * forwarded using the \ref ReadStdout signal.
+ * \ref Terminated is sent if the child application exits.
+ *
+ */
+class SystemExec
+{
+ public:
+ /** prepare execution of a program with 'execve'
+ *
+ * This function takes over the existing environment variable and provides
+ * an easy way to speciy command-line arguments for the new process.
+ *
+ * Note: The argument parser does not interpret quotation-marks and splits
+ * arugments on whitespace. The argument string can be empty.
+ * The alternative constructor below allows to specify quoted parameters
+ * incl. whitespace.
+ *
+ * @param c program pathname that identifies the new process image file.
+ * @param a string of commandline-arguments to be passed to the new program.
+ */
+ SystemExec (std::string c, std::string a = "");
+ /** similar to \ref SystemExec but allows to specify custom arguments
+ *
+ * @param c program pathname that identifies the new process image file.
+ * @param a array of argument strings passed to the new program as 'argv'.
+ * it must be terminated by a null pointer (see the 'evecve'
+ * POSIX-C documentation for more information)
+ * The array must be dynamically allocated using malloc or strdup.
+ * Unless they're NULL, the array itself and each of its content
+ * memory is freed() in the destructor.
+ *
+ */
+ SystemExec (std::string c, char ** a);
+ virtual ~SystemExec ();
+
+ /** fork and execute the given program
+ *
+ * @param stderr_mode select what to do with program's standard error
+ * output:
+ * '0': keep STDERR; mix it with parent-process' STDERR
+ * '1': ignore STDERR of child-program
+ * '2': merge STDERR into STDOUT and send it with the
+ * ReadStdout signal.
+ * @return If the process is already running or was launched successfully
+ * the function returns zero (0). A negative number indicates an error.
+ */
+ int start (int stderr_mode = 1);
+ /** kill running child-process
+ *
+ * if a child process exists trt to shut it down by closing its STDIN.
+ * if the program dies not react try SIGTERM and eventually SIGKILL
+ */
+ void terminate ();
+ /** check if the child programm is (still) running.
+ *
+ * This function calls waitpid(WNOHANG) to check the state of the
+ * child-process.
+ * @return true if the program is (still) running.
+ */
+ bool is_running ();
+ /** call the waitpid system-call with the pid of the child-program
+ *
+ * Basically what \ref terminate uses internally.
+ *
+ * This function is only useful if you want to control application
+ * termination yourself (eg timeouts or progress-dialog).
+ * @param option flags - see waitpid manual
+ * @return status info from waitpid call (not waitpid's return value)
+ * or -1 if the child-program is not running.
+ */
+ int wait (int options=0);
+ /** closes both STDIN and STDOUT connections to/from
+ * the child-program.
+ * With the output-interposer thread gone, the program
+ * should terminate.
+ * used by \ref terminate()
+ */
+ void close_stdin ();
+ /** write into child-program's STDIN
+ * @param d data to write
+ * @param len length of data to write, if it is 0 (zero), d.length() is
+ * used to determine the number of bytes to transmit.
+ * @return number of bytes written.
+ */
+ int write_to_stdin (std::string d, size_t len=0);
+
+ /** The ReadStdout signal is emitted when the application writes to STDOUT.
+ * it passes the written data and its length in bytes as arguments to the bound
+ * slot(s).
+ */
+#ifdef NOPBD /* outside ardour */
+ sigc::signal<void, std::string,size_t> ReadStdout;
+#else
+ PBD::Signal2<void, std::string,size_t> ReadStdout;
+#endif
+
+ /** The Terminated signal is emitted when application terminates. */
+#ifdef NOPBD /* outside ardour */
+ sigc::signal<void> Terminated;
+#else
+ PBD::Signal0<void> Terminated;
+#endif
+
+ /** interposer to emit signal for writes to STDOUT/ERR.
+ *
+ * Thread that reads the stdout of the forked
+ * process and signal-sends it to the main thread.
+ * It also emits the Terminated() signal once
+ * the the forked process closes it's stdout.
+ *
+ * Note: it's actually 'private' function but used
+ * by the internal pthread, which only has a pointer
+ * to this instance and thus can only access public fn.
+ */
+ void output_interposer ();
+
+ protected:
+ std::string cmd; ///< path to command - set when creating the class
+ int nicelevel; ///< process nice level - defaults to 0
+
+ void make_argp(std::string);
+ void make_envp();
+
+ char **argp;
+ char **envp;
+
+ private:
+#ifdef __WIN32__
+ PROCESS_INFORMATION *pid;
+ HANDLE stdinP[2];
+ HANDLE stdoutP[2];
+ HANDLE stderrP[2];
+ char *w_args;
+ void make_wargs(char **);
+#else
+ pid_t pid;
+#endif
+ pthread_mutex_t write_lock;
+
+ int fdin; ///< file-descriptor for writing to child's STDIN. This variable is identical to pin[1] but also used as status check if the stdin pipe is open: <0 means closed.
+ int pok[2];
+ int pin[2];
+ int pout[2];
+
+ pthread_t thread_id_tt;
+ bool thread_active;
+};
+
+#endif /* __ardour_system_exec_h__ */
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/transcode_ffmpeg.cc b/gtk2_ardour/transcode_ffmpeg.cc
new file mode 100644
index 0000000000..7696ecd61f
--- /dev/null
+++ b/gtk2_ardour/transcode_ffmpeg.cc
@@ -0,0 +1,512 @@
+/*
+ Copyright (C) 2010-2013 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include <stdio.h>
+#include <string.h>
+#include <sstream>
+#include <sys/types.h>
+
+#include "pbd/error.h"
+#include "pbd/file_utils.h"
+#include "pbd/file_utils.h"
+#include "gui_thread.h"
+
+#include "transcode_ffmpeg.h"
+#include "utils_videotl.h"
+
+#include "i18n.h"
+
+TranscodeFfmpeg::TranscodeFfmpeg (std::string f)
+ : infile(f)
+{
+ probeok = false;
+ ffexecok = false;
+ ffmpeg_exe = "";
+ ffprobe_exe = "";
+ m_duration = 0;
+#if 1 /* tentative debug mode */
+ debug_enable = false;
+#endif
+
+ std::string ff_file_path;
+ if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("ffmpeg"), ff_file_path)) { ffmpeg_exe = ff_file_path; }
+ else if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("avconv"), ff_file_path)) { ffmpeg_exe = ff_file_path; }
+ else if (find_file_in_search_path (PBD::SearchPath(std::string("/usr/local/bin/")), X_("ffmpeg_harvid"), ff_file_path)) { ffmpeg_exe = ff_file_path; }
+ else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe"), Glib::FILE_TEST_EXISTS)) {
+ ffmpeg_exe = X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe");
+ }
+
+ if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("ffprobe"), ff_file_path)) { ffprobe_exe = ff_file_path; }
+//else if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("avprobe"), ff_file_path)) { ffprobe_exe = ff_file_path; }
+ else if (find_file_in_search_path (PBD::SearchPath(std::string("/usr/local/bin/")), X_("ffprobe_harvid"), ff_file_path)) { ffprobe_exe = ff_file_path; }
+ else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffprobe.exe"), Glib::FILE_TEST_EXISTS)) {
+ ffprobe_exe = X_("C:\\Program Files\\ffmpeg\\ffprobe.exe");
+ }
+
+ if (ffmpeg_exe.empty() || ffprobe_exe.empty()) {
+ PBD::warning << _("No ffprobe or ffmpeg executables could be found on this system. Transcoding is not possible until you install those tools.") << endmsg;
+ return;
+ }
+ ffexecok = true;
+
+ if (infile.empty() || !probe()) {
+ return;
+ }
+ probeok = true;
+}
+
+TranscodeFfmpeg::~TranscodeFfmpeg ()
+{
+ ;
+}
+
+bool
+TranscodeFfmpeg::probe ()
+{
+ ffoutput = "";
+ char **argp;
+ argp=(char**) calloc(7,sizeof(char*));
+ argp[0] = strdup(ffprobe_exe.c_str());
+ argp[1] = strdup("-print_format"); // "-of" ; new version and avprobe compat but avprobe does not yet support csv
+ argp[2] = strdup("csv"); // TODO use "csv=nk=0" and parse key/value pairs -> ffprobe version agnostic or parse XML or JSON key/value
+ argp[3] = strdup("-show_format");
+ argp[4] = strdup("-show_streams");
+ argp[5] = strdup(infile.c_str());
+ argp[6] = 0;
+ ffcmd = new SystemExec(ffprobe_exe, argp);
+ ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffprobeparse, this, _1 ,_2));
+ ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
+ if (ffcmd->start(1)) {
+ ffexit();
+ return false;
+ }
+ ffcmd->wait();
+
+ /* parse */
+
+ std::vector<std::vector<std::string> > lines;
+ ParseCSV(ffoutput, lines);
+ m_width = m_height = 0;
+ m_fps = m_aspect = 0;
+ m_duration = 0;
+ m_codec.clear();
+ m_audio.clear();
+
+ for (std::vector<std::vector<std::string> >::iterator i = lines.begin(); i != lines.end(); ++i) {
+ if (i->at(0) == X_("format")) {
+ /* format,filename,#streams,format-name,format-long-name,start-time,duration,size,bitrate */
+ } else
+ if (i->at(0) == X_("stream")) {
+ /*--------- Stream format
+ * stream,index,codec-name,codec-name-long,PROFILE,
+ * codec_time_base,codec_tag_string,codec_tag[hex],
+ * VIDEO:
+ * width,height,has_b_frames,sample_aspect_ratio,display_aspect_ratio
+ * pix_fmt,level,
+ * timecode
+ * AUDIO:
+ * sample_fmt,sample_rate,channels,bits_per_sample
+ *
+ * all cont'd;
+ * r_frame_rate,avg_frame_rate,time_base,start_time,duration,
+ * bit_rate,nb_frames,nb_read_frames,nb_read_packets
+ *
+ *---------- Example
+ * stream,0,mpeg2video,MPEG-2 video,video,1/50,[0][0][0][0],0x0000,720,576,1,16:15,4:3,yuv420p,8,00:02:30:00,0x1e0,25/1,25/1,1/90000,0.360000,N/A,7000000,N/A,N/A,N/A
+ * stream,1,ac3,ATSC A/52A (AC-3),audio,1/48000,[0][0][0][0],0x0000,s16,48000,6,0,-1,-1.000000,-1.000000,-1.000000,-1.000000,0x80,0/0,0/0,1/90000,0.280000,312.992000,448000,N/A,N/A,N/A
+ * stream,2,ac3,ATSC A/52A (AC-3),audio,1/48000,[0][0][0][0],0x0000,s16,48000,2,0,-1,-1.000000,-1.000000,-1.000000,-1.000000,0x82,0/0,0/0,1/90000,0.280000,312.992000,384000,N/A,N/A,N/A
+ * stream,3,ac3,ATSC A/52A (AC-3),audio,1/48000,[0][0][0][0],0x0000,s16,48000,2,0,-1,-1.000000,-1.000000,-1.000000,-1.000000,0x81,0/0,0/0,1/90000,0.280000,312.992000,192000,N/A,N/A,N/A
+ */
+ if (i->at(4) == X_("video") && m_width == 0) {
+ std::string::size_type pos;
+
+ m_width = atoi(i->at(8).c_str());
+ m_height = atoi(i->at(9).c_str());
+ m_codec = i->at(3) + " -- " + i->at(2);
+ m_fps = atof(i->at(17).c_str());
+
+ pos = i->at(17).find_first_of('/');
+ if (pos != std::string::npos) {
+ m_fps = atof(i->at(17).substr(0, pos).c_str()) / atof(i->at(17).substr(pos+1).c_str());
+ }
+
+ pos = i->at(12).find_first_of(':');
+ m_aspect = 0;
+ if (pos != std::string::npos && atof(i->at(12).substr(pos+1).c_str()) != 0) {
+ m_aspect = atof(i->at(12).substr(0, pos).c_str()) / atof(i->at(12).substr(pos+1).c_str());
+ }
+ if (m_aspect == 0) {
+ m_aspect = (double)m_width / (double)m_height;
+ }
+
+ int h,m,s; char f[7];
+ if (sscanf(i->at(15).c_str(), "%d:%d:%d:%s",&h,&m,&s,f) == 4) {
+ m_duration = (ARDOUR::framecnt_t) floor(m_fps * (
+ h * 3600.0
+ + m * 60.0
+ + s * 1.0
+ + atoi(f) / pow(10, strlen(f))
+ ));
+ } else {
+ m_duration = atof(i->at(21).c_str()) * m_fps;
+ }
+
+ } else if (i->at(4) == X_("audio")) {
+ AudioStream as;
+ as.name = i->at(3) + " " + i->at(2) + " " + i->at(8) + " " + i->at(9);
+ as.stream_id = i->at(1);
+ as.channels = atoi(i->at(10).c_str());
+ m_audio.push_back(as);
+
+ } else if (i->at(5) == X_("video") && m_width == 0) { /* new ffprobe */
+ std::string::size_type pos;
+
+ m_width = atoi(i->at(9).c_str());
+ m_height = atoi(i->at(10).c_str());
+ m_codec = i->at(3) + " -- " + i->at(2);
+ m_fps = atof(i->at(18).c_str());
+
+ pos = i->at(18).find_first_of('/');
+ if (pos != std::string::npos) {
+ m_fps = atof(i->at(18).substr(0, pos).c_str()) / atof(i->at(18).substr(pos+1).c_str());
+ }
+
+ pos = i->at(13).find_first_of(':');
+ m_aspect = 0;
+ if (pos != std::string::npos && atof(i->at(13).substr(pos+1).c_str()) != 0) {
+ m_aspect = atof(i->at(13).substr(0, pos).c_str()) / atof(i->at(13).substr(pos+1).c_str());
+ }
+ if (m_aspect == 0) {
+ m_aspect = (double)m_width / (double)m_height;
+ }
+
+ int h,m,s; char f[7];
+ if (sscanf(i->at(17).c_str(), "%d:%d:%d:%s",&h,&m,&s,f) == 4) {
+ m_duration = (ARDOUR::framecnt_t) floor(m_fps * (
+ h * 3600.0
+ + m * 60.0
+ + s * 1.0
+ + atoi(f) / pow(10, strlen(f))
+ ));
+ } else if (atof(i->at(23).c_str()) != 0) {
+ m_duration = atof(i->at(23).c_str());
+ } else {
+ m_duration = atof(i->at(24).c_str()) * m_fps;
+ }
+
+ } else if (i->at(5) == X_("audio")) { /* new ffprobe */
+ AudioStream as;
+ as.name = i->at(3) + " " + i->at(2) + " " + i->at(9) + " " + i->at(10);
+ as.stream_id = i->at(1);
+ as.channels = atoi(i->at(11).c_str());
+ m_audio.push_back(as);
+ }
+ }
+ }
+ /* end parse */
+
+
+ int timeout = 500;
+ while (ffcmd && --timeout) usleep (1000); // wait until 'ffprobe' terminated.
+ if (timeout == 0) return false;
+
+#if 0 /* DEBUG */
+ printf("FPS: %f\n", m_fps);
+ printf("Duration: %lu frames\n",(unsigned long)m_duration);
+ printf("W/H: %ix%i\n",m_width, m_height);
+ printf("aspect: %f\n",m_aspect);
+ printf("codec: %s\n",m_codec.c_str());
+ if (m_audio.size() > 0) {
+ for (AudioStreams::iterator it = m_audio.begin(); it < m_audio.end(); ++it) {
+ printf("audio: %s - %i channels\n",(*it).stream_id.c_str(), (*it).channels);
+ }
+ } else {
+ printf("audio: no audio streams in file.\n");
+ }
+#endif
+
+ return true;
+}
+
+FFSettings
+TranscodeFfmpeg::default_encoder_settings ()
+{
+ FFSettings ffs;
+ ffs.clear();
+ ffs["-vcodec"] = "mpeg4";
+ ffs["-acodec"] = "ac3";
+ ffs["-b"] = "5000k";
+ ffs["-ab"] = "160k";
+ return ffs;
+}
+
+FFSettings
+TranscodeFfmpeg::default_meta_data ()
+{
+ FFSettings ffm;
+ ffm.clear();
+ ffm["comment"] = "Created with ardour";
+ return ffm;
+}
+
+char *
+TranscodeFfmpeg::format_metadata (std::string key, std::string value)
+{
+ size_t start_pos = 0;
+ std::string v1 = value;
+ while((start_pos = v1.find_first_not_of(
+ "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(),.\"'",
+ start_pos)) != std::string::npos)
+ {
+ v1.replace(start_pos, 1, "_");
+ start_pos += 1;
+ }
+
+ start_pos = 0;
+ while((start_pos = v1.find("\"", start_pos)) != std::string::npos) {
+ v1.replace(start_pos, 1, "\\\"");
+ start_pos += 2;
+ }
+
+ size_t len = key.length() + v1.length() + 4;
+ char *mds = (char*) calloc(len, sizeof(char));
+ snprintf(mds, len, "%s=\"%s\"", key.c_str(), v1.c_str());
+ return mds;
+}
+
+bool
+TranscodeFfmpeg::encode (std::string outfile, std::string inf_a, std::string inf_v, FFSettings ffs, FFSettings meta, bool map)
+{
+#define MAX_FFMPEG_ENCODER_ARGS (100)
+ char **argp;
+ int a=0;
+
+ argp=(char**) calloc(MAX_FFMPEG_ENCODER_ARGS,sizeof(char*));
+ argp[a++] = strdup(ffmpeg_exe.c_str());
+ if (m_avoffset < 0 || m_avoffset > 0) {
+ std::ostringstream osstream; osstream << m_avoffset;
+ argp[a++] = strdup("-itsoffset");
+ argp[a++] = strdup(osstream.str().c_str());
+ }
+ argp[a++] = strdup("-i");
+ argp[a++] = strdup(inf_v.c_str());
+
+ argp[a++] = strdup("-i");
+ argp[a++] = strdup(inf_a.c_str());
+ for(FFSettings::const_iterator it = ffs.begin(); it != ffs.end(); ++it) {
+ argp[a++] = strdup(it->first.c_str());
+ argp[a++] = strdup(it->second.c_str());
+ }
+ for(FFSettings::const_iterator it = meta.begin(); it != meta.end(); ++it) {
+ argp[a++] = strdup("-metadata");
+ argp[a++] = format_metadata(it->first.c_str(), it->second.c_str());
+ }
+ if (map) {
+ argp[a++] = strdup("-map");
+ argp[a++] = strdup("0:0");
+ argp[a++] = strdup("-map");
+ argp[a++] = strdup("1:0");
+ }
+ argp[a++] = strdup("-y");
+ argp[a++] = strdup(outfile.c_str());
+ argp[a] = (char *)0;
+ assert(a<MAX_FFMPEG_ENCODER_ARGS);
+ /* Note: these are free()d in ~SystemExec */
+#if 1 /* DEBUG */
+ if (debug_enable) { /* tentative debug mode */
+ printf("EXPORT ENCODE:\n");
+ for (int i=0; i< a; ++i) {
+ printf("%s ", argp[i]);
+ }
+ printf("\n");
+ }
+#endif
+
+ ffcmd = new SystemExec(ffmpeg_exe, argp);
+ ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
+ ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
+ if (ffcmd->start(2)) {
+ ffexit();
+ return false;
+ }
+ return true;
+}
+
+bool
+TranscodeFfmpeg::extract_audio (std::string outfile, ARDOUR::framecnt_t samplerate, unsigned int stream)
+{
+ if (!probeok) return false;
+ if (stream >= m_audio.size()) return false;
+
+ char **argp;
+
+ argp=(char**) calloc(15,sizeof(char*));
+ argp[0] = strdup(ffmpeg_exe.c_str());
+ argp[1] = strdup("-i");
+ argp[2] = strdup(infile.c_str());
+ argp[3] = strdup("-ar");
+ argp[4] = (char*) calloc(7,sizeof(char)); snprintf(argp[4], 7, "%"PRId64, samplerate);
+ argp[5] = strdup("-ac");
+ argp[6] = (char*) calloc(3,sizeof(char)); snprintf(argp[6], 3, "%i", m_audio.at(stream).channels);
+ argp[7] = strdup("-map");
+ argp[8] = (char*) calloc(8,sizeof(char)); snprintf(argp[8], 8, "0:%s", m_audio.at(stream).stream_id.c_str());
+ argp[9] = strdup("-vn");
+ argp[10] = strdup("-acodec");
+ argp[11] = strdup("pcm_f32le");
+ argp[12] = strdup("-y");
+ argp[13] = strdup(outfile.c_str());
+ argp[14] = (char *)0;
+ /* Note: argp is free()d in ~SystemExec */
+#if 1 /* DEBUG */
+ if (debug_enable) { /* tentative debug mode */
+ printf("EXTRACT AUDIO:\n");
+ for (int i=0; i< 14; ++i) {
+ printf("%s ", argp[i]);
+ }
+ printf("\n");
+ }
+#endif
+
+ ffcmd = new SystemExec(ffmpeg_exe, argp);
+ ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_a, this, _1 ,_2));
+ ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
+ if (ffcmd->start(2)) {
+ ffexit();
+ return false;
+ }
+ return true;
+}
+
+
+bool
+TranscodeFfmpeg::transcode (std::string outfile, const int outw, const int outh, const int kbitps)
+{
+ if (!probeok) return false;
+
+ char **argp;
+ int bitrate = kbitps;
+ int width = outw;
+ int height = outh;
+
+ if (width < 1 || width > m_width) { width = m_width; } /* don't allow upscaling */
+ if (height < 1 || height > m_height) { height = floor(width / m_aspect); }
+
+ if (bitrate == 0) {
+ const double bitperpixel = .7; /* avg quality */
+ bitrate = floor(m_fps * width * height * bitperpixel / 10000.0);
+ } else {
+ bitrate = bitrate / 10;
+ }
+ if (bitrate < 10) bitrate = 10;
+ if (bitrate > 1000) bitrate = 1000;
+
+ argp=(char**) calloc(16,sizeof(char*));
+ argp[0] = strdup(ffmpeg_exe.c_str());
+ argp[1] = strdup("-i");
+ argp[2] = strdup(infile.c_str());
+ argp[3] = strdup("-b");
+ argp[4] = (char*) calloc(7,sizeof(char)); snprintf(argp[4], 7, "%i0k", bitrate);
+ argp[5] = strdup("-s");
+ argp[6] = (char*) calloc(10,sizeof(char)); snprintf(argp[6], 10, "%ix%i", width, height);
+ argp[7] = strdup("-y");
+ argp[8] = strdup("-vcodec");
+ argp[9] = strdup("mjpeg");
+ argp[10] = strdup("-an");
+ argp[11] = strdup("-intra");
+ argp[12] = strdup("-g");
+ argp[13] = strdup("1");
+ argp[14] = strdup(outfile.c_str());
+ argp[15] = (char *)0;
+ /* Note: these are free()d in ~SystemExec */
+#if 1 /* DEBUG */
+ if (debug_enable) { /* tentative debug mode */
+ printf("TRANSCODE VIDEO:\n");
+ for (int i=0; i< 15; ++i) {
+ printf("%s ", argp[i]);
+ }
+ printf("\n");
+ }
+#endif
+ ffcmd = new SystemExec(ffmpeg_exe, argp);
+ ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
+ ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
+ if (ffcmd->start(2)) {
+ ffexit();
+ return false;
+ }
+ return true;
+}
+
+void
+TranscodeFfmpeg::cancel ()
+{
+ if (!ffcmd || !ffcmd->is_running()) { return;}
+ ffcmd->write_to_stdin("q");
+ sleep (1);
+ if (ffcmd) {
+ ffcmd->terminate();
+ }
+}
+
+void
+TranscodeFfmpeg::ffexit ()
+{
+ delete ffcmd;
+ ffcmd=0;
+ Finished(); /* EMIT SIGNAL */
+}
+
+void
+TranscodeFfmpeg::ffprobeparse (std::string d, size_t s)
+{
+ ffoutput+=d;
+}
+
+void
+TranscodeFfmpeg::ffmpegparse_a (std::string d, size_t s)
+{
+ const char *t;
+ if (!(t=strstr(d.c_str(), "time="))) { return; }
+ ARDOUR::framecnt_t f = (ARDOUR::framecnt_t) floorf (atof(t+5) * m_fps);
+ if (f > m_duration ) { f = m_duration; }
+ Progress(f, m_duration); /* EMIT SIGNAL */
+}
+
+void
+TranscodeFfmpeg::ffmpegparse_v (std::string d, size_t s)
+{
+ if (strstr(d.c_str(), "ERROR") || strstr(d.c_str(), "Error") || strstr(d.c_str(), "error")) {
+ PBD::warning << "ffmpeg-error: " << d << endmsg;
+ }
+ if (strncmp(d.c_str(), "frame=",6)) {
+#if 1 /* DEBUG */
+ if (debug_enable) {
+ d.erase(d.find_last_not_of(" \t\r\n") + 1);
+ printf("ffmpeg: '%s'\n", d.c_str());
+ }
+#endif
+ return;
+ }
+ ARDOUR::framecnt_t f = atol(d.substr(6).c_str());
+ Progress(f, m_duration); /* EMIT SIGNAL */
+}
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/transcode_ffmpeg.h b/gtk2_ardour/transcode_ffmpeg.h
new file mode 100644
index 0000000000..2fd9f2f3f2
--- /dev/null
+++ b/gtk2_ardour/transcode_ffmpeg.h
@@ -0,0 +1,162 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#ifndef __ardour_transcode_ffmpeg_h__
+#define __ardour_transcode_ffmpeg_h__
+
+#include <string>
+#include "ardour/types.h"
+#include "system_exec.h"
+
+/* TODO: use a namespace here ? */
+struct AudioStream {
+ std::string name;
+ std::string stream_id;
+ uint32_t channels;
+};
+typedef std::vector<AudioStream> AudioStreams;
+typedef std::map<std::string,std::string> FFSettings;
+
+/** @class TranscodeFfmpeg
+ * @brief wrapper around ffmpeg and ffprobe command-line utils
+ *
+ * This class includes parsers for stdi/o communication with
+ * 'ffmpeg' and 'ffprobe' and provide an abstraction to
+ * transcode video-files and extract aufio tracks and query
+ * file information.
+ */
+class TranscodeFfmpeg : public sigc::trackable
+ , public PBD::ScopedConnectionList
+{
+ public:
+
+ /** instantiate a new transcoder. If a file-name is given, the file's
+ * attributes (fps, duration, geometry etc) are read.
+ *
+ * @param f path to the video-file to probe or use as input for
+ * \ref extract_audio and \ref transcode.
+ */
+ TranscodeFfmpeg (std::string f);
+ virtual ~TranscodeFfmpeg ();
+ /** transcode/import a video-file
+ * @param outfile full-path (incl. file-extension)
+ * @param outwidth video-width, \c <0 no scaling)
+ * @param outheight video-height \c <0 use aspect \c \ref outwidth /c / \c aspect-ratio
+ * @param kbitps video bitrate \c 0 calculate to use 0.7 bits per pixel on average
+ * @return \c true if the transcoder process was successfully started.
+ */
+ bool transcode (std::string, const int outwidth=0, const int outheight=0, const int kbitps =0);
+ /** Extract an audio track from the given input file to a new 32bit float little-endian PCM WAV file.
+ * @param outfile full-path (incl. file-extension) to .wav file to write
+ * @param samplerate target samplerate
+ * @param stream Stream-ID of the audio-track to extract
+ * specified as element-number in \ref get_audio().
+ * @return \c true if the transcoder process was successfully started.
+ */
+ bool extract_audio (std::string outfile, ARDOUR::framecnt_t samplerate, unsigned int stream=0);
+ /** transcode video and mux audio files into a new video-file.
+ * @param outfile full-path of output file to create (existing files are overwritten)
+ * @param inf_a filename of input audio-file
+ * @param inf_v filename of input video-file
+ * @param ffs additional command-line parameters for 'ffmpeg'. key/value pairs
+ * eg ffs["-vcodec"] = "mpeg4"
+ * @param meta additional meta-data results in -metadata "<key>"="<value>" command-line
+ * arguments
+ * @param map if set to \c true stream mapping from input streams to output streams is set to use
+ * only the first available stream from the audio & video file (-map 0.0 -map 1.0).
+ * @return \c true if the encoder process was successfully started.
+ */
+ bool encode (std::string outfile, std::string inf_a, std::string inf_v, FFSettings ffs, FFSettings meta, bool map = true);
+ /** @return array with default encoder settings */
+ FFSettings default_encoder_settings ();
+ /** @return array with default meta data */
+ FFSettings default_meta_data ();
+ /** abort any running transcoding process */
+ void cancel();
+ /**
+ * @return \c true if the input file was parsed correctly on class creation. */
+ bool probe_ok () { return probeok; }
+ /** @return \c true if the ffmpeg/ffparse executables are avail on this system */
+ bool ffexec_ok () { return ffexecok; }
+
+ /** signal emitted when ffmpeg reports progress updates
+ * during \ref encode \ref transcode and \ref extract_audio
+ * The parameters are current and last video-frame.
+ */
+ PBD::Signal2<void, ARDOUR::framecnt_t, ARDOUR::framecnt_t> Progress;
+ /** signal emitted when the transcoder process terminates. */
+ PBD::Signal0<void> Finished;
+
+ double get_fps () { return m_fps; }
+ double get_aspect () { return m_aspect; }
+ int get_width() { return m_width; }
+ int get_height() { return m_height; }
+ ARDOUR::framecnt_t get_duration() { return m_duration; }
+ std::string get_codec() { return m_codec; }
+
+ AudioStreams get_audio() { return m_audio; }
+
+ /** override file duration used with the \ref Progress signal.
+ * @param d duration in video-frames = length_in_seconds * get_fps()
+ */
+ void set_duration(ARDOUR::framecnt_t d) { m_duration = d; }
+
+ void set_avoffset(double av_offset) { m_avoffset = av_offset; }
+
+
+#if 1 /* tentative debug mode */
+ void set_debug (bool onoff) { debug_enable = onoff; }
+#endif
+ protected:
+ std::string infile;
+ SystemExec *ffcmd;
+
+ bool probe ();
+
+ double m_fps;
+ double m_aspect;
+ ARDOUR::framecnt_t m_duration;
+ int m_width;
+ int m_height;
+ std::string m_codec;
+
+ double m_avoffset;
+ bool ffexecok;
+ bool probeok;
+
+ AudioStreams m_audio;
+
+ char *format_metadata (std::string, std::string);
+ void ffmpegparse_v (std::string d, size_t s);
+ void ffmpegparse_a (std::string d, size_t s);
+ void ffprobeparse (std::string d, size_t s);
+ void ffexit ();
+ std::string ffoutput;
+
+ std::string ffmpeg_exe;
+ std::string ffprobe_exe;
+#if 1 /* tentative debug mode */
+ bool debug_enable;
+#endif
+};
+
+#endif /* __ardour_transcode_ffmpeg_h__ */
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/transcode_video_dialog.cc b/gtk2_ardour/transcode_video_dialog.cc
new file mode 100644
index 0000000000..8d927b915e
--- /dev/null
+++ b/gtk2_ardour/transcode_video_dialog.cc
@@ -0,0 +1,524 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include <cstdio>
+#include <string>
+#include <sstream>
+#include <iomanip>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sigc++/bind.h>
+#include <libgen.h>
+
+#include "pbd/error.h"
+#include "pbd/convert.h"
+#include "gtkmm2ext/utils.h"
+#include "ardour/session_directory.h"
+#include "ardour/profile.h"
+#include "ardour/template_utils.h"
+#include "ardour/session.h"
+#include "ardour_ui.h"
+#include "gui_thread.h"
+
+#include "utils.h"
+#include "opts.h"
+#include "transcode_video_dialog.h"
+#include "video_copy_dialog.h"
+#include "utils_videotl.h"
+#include "i18n.h"
+
+using namespace Gtk;
+using namespace std;
+using namespace PBD;
+using namespace ARDOUR;
+
+TranscodeVideoDialog::TranscodeVideoDialog (Session* s, std::string infile)
+ : ArdourDialog (_("Transcode/Import Video File "))
+ , infn (infile)
+ , path_label (_("Output File:"), Gtk::ALIGN_LEFT)
+ , browse_button (_("Browse"))
+ , transcode_button (_("Transcode Video\n And Import"))
+ , copy_button (_("Copy Video\nFile Only"))
+ , audio_button (_("Extract and\nImport Audio Only"))
+ , abort_button (_("Abort"))
+ , progress_label ()
+ , aspect_checkbox (_("Height = "))
+ , height_adjustment (128, 0, 1920, 1, 16, 0)
+ , height_spinner (height_adjustment)
+ , bitrate_checkbox (_("Manual Override"))
+ , bitrate_adjustment (2000, 100, 10000, 10, 100, 0)
+ , bitrate_spinner (bitrate_adjustment)
+#if 1 /* tentative debug mode */
+ , debug_checkbox (_("Debug Mode: Print ffmpeg Command and Output to stdout."))
+#endif
+{
+ set_session (s);
+
+ transcoder = new TranscodeFfmpeg(infile);
+ audiofile = "";
+ pending_audio_extract = false;
+ pending_copy_file = false;
+
+ set_name ("TranscodeVideoDialog");
+ set_position (Gtk::WIN_POS_MOUSE);
+ set_modal (true);
+ set_skip_taskbar_hint (true);
+ set_resizable (false);
+
+ Gtk::Label* l;
+ vbox = manage (new VBox);
+ VBox* options_box = manage (new VBox);
+ HBox* path_hbox = manage (new HBox);
+
+ int w = 0, h = 0;
+ m_aspect = 4.0/3.0;
+ AudioStreams as; as.clear();
+
+ path_hbox->pack_start (path_label, false, false, 3);
+ path_hbox->pack_start (path_entry, true, true, 3);
+ path_hbox->pack_start (browse_button, false, false, 3);
+ browse_button.set_name ("PaddedButton");
+
+ path_entry.set_width_chars(38);
+ height_spinner.set_sensitive(false);
+ bitrate_spinner.set_sensitive(false);
+
+ std::string dstdir = video_dest_dir(_session->session_directory().video_path(), Config->get_video_server_docroot());
+ std::string dstfn = video_dest_file(dstdir, infile);
+ path_entry.set_text (dstfn);
+
+ l = manage (new Label (_("<b>Info</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_use_markup ();
+ options_box->pack_start (*l, false, true, 4);
+
+
+ if (!transcoder->ffexec_ok()) {
+ l = manage (new Label (_("No ffprobe or ffmpeg executables could be found on this system. Transcoding is not possible until you install those tools."), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_line_wrap();
+ options_box->pack_start (*l, false, true, 4);
+ transcode_button.set_sensitive(false);
+ aspect_checkbox.set_sensitive(false);
+ bitrate_checkbox.set_sensitive(false);
+ }
+ else if (!transcoder->probe_ok()) {
+ l = manage (new Label (string_compose(_("Video file-info could not be read. Most likely '%1' is not a valid video-file. It could also be a rare unsupported video codec or format."), infn), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ options_box->pack_start (*l, false, true, 4);
+ transcode_button.set_sensitive(false);
+ aspect_checkbox.set_sensitive(false);
+ bitrate_checkbox.set_sensitive(false);
+ } else {
+ w = transcoder->get_width();
+ h = transcoder->get_height();
+ as = transcoder->get_audio();
+ m_aspect = transcoder->get_aspect();
+
+ Table* t = manage (new Table (4, 2));
+ t->set_spacings (4);
+ options_box->pack_start (*t, true, true, 4);
+ l = manage (new Label (_("FPS:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 0, 1, 0, 1);
+ l = manage (new Label (_("Duration:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 2, 3, 0, 1);
+ l = manage (new Label (_("Codec:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 0, 1, 1, 2);
+ l = manage (new Label (_("Geometry:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 2, 3, 1, 2);
+
+ std::ostringstream osstream;
+ osstream << transcoder->get_fps();
+ l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 1, 2, 0, 1);
+
+ osstream.str("");
+ osstream << w << "x" << h;
+ l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 3, 4, 1, 2);
+
+ osstream.str("");
+ unsigned long sec = transcoder->get_duration() / transcoder->get_fps();
+ if (sec == 0) {
+ osstream << _("??");
+ } else {
+ osstream << setfill('0') << setw(2);
+ osstream << (sec / 3600) << ":";
+ osstream << setfill('0') << setw(2);
+ osstream << ((sec /60 )%60) << ":";
+ osstream << setfill('0') << setw(2);
+ osstream << (sec%60) << ":";
+ osstream << setfill('0') << setw(2);
+ osstream << (transcoder->get_duration() % (int) floor(transcoder->get_fps()));
+ }
+ l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 3, 4, 0, 1);
+
+ osstream.str("");
+ osstream << transcoder->get_codec();
+ l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 1, 2, 1, 2);
+ }
+
+ l = manage (new Label (_("<b>Options</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_use_markup ();
+ options_box->pack_start (*l, false, true, 4);
+
+ Table* t = manage (new Table (4, 3));
+ t->set_spacings (4);
+ options_box->pack_start (*t, true, true, 4);
+
+ l = manage (new Label (_("Scale Video: Width = "), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 0, 1, 0, 1);
+ scale_combo.set_name ("PaddedButton");
+ t->attach (scale_combo, 1, 2, 0, 1);
+ t->attach (aspect_checkbox, 2, 3, 0, 1);
+ t->attach (height_spinner, 3, 4, 0, 1);
+
+ scale_combo.append_text(_("Original Width"));
+ if (w > 1920) { scale_combo.append_text("1920 (hd1080)"); }
+ if (w > 1408) { scale_combo.append_text("1408 (16cif)"); }
+ if (w > 1280) { scale_combo.append_text("1280 (sxga, hd720)"); }
+ if (w > 1024) { scale_combo.append_text("1024 (xga)"); }
+ if (w > 852) { scale_combo.append_text(" 852 (hd480)"); }
+ if (w > 768) { scale_combo.append_text(" 768 (PAL)"); }
+ if (w > 720) { scale_combo.append_text(" 720 (PAL)"); }
+ if (w > 640) { scale_combo.append_text(" 640 (vga, ega)"); }
+ if (w > 352) { scale_combo.append_text(" 352 (cif)"); }
+ if (w > 320) { scale_combo.append_text(" 320 (cga, qvga)"); }
+ if (w > 176) { scale_combo.append_text(" 176 (qcif)"); }
+ scale_combo.set_active(0);
+ height_spinner.set_value(h);
+
+ l = manage (new Label (_("Bitrate (KBit/s):"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 0, 1, 1, 2);
+ t->attach (bitrate_checkbox, 2, 3, 1, 2);
+ t->attach (bitrate_spinner, 3, 4, 1, 2);
+
+ l = manage (new Label (_("Extract Audio:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ t->attach (*l, 0, 1, 2, 3);
+ audio_combo.set_name ("PaddedButton");
+ t->attach (audio_combo, 1, 4, 2, 3);
+ audio_combo.append_text("No audio");
+ if (as.size() > 0) {
+ for (AudioStreams::iterator it = as.begin(); it < as.end(); ++it) {
+ audio_combo.append_text((*it).name);
+ }
+ }
+ audio_combo.set_active(0);
+
+#if 1 /* tentative debug mode */
+ options_box->pack_start (debug_checkbox, false, true, 4);
+#endif
+
+ vbox->pack_start (*path_hbox, false, false);
+ vbox->pack_start (*options_box, false, true);
+
+ get_vbox()->set_spacing (4);
+ get_vbox()->pack_start (*vbox, false, false);
+
+ progress_box = manage (new VBox);
+ progress_box->pack_start (progress_label, false, false);
+ progress_box->pack_start (pbar, false, false);
+ progress_box->pack_start (abort_button, false, false);
+ get_vbox()->pack_start (*progress_box, false, false);
+
+ browse_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::open_browse_dialog));
+ copy_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::prepare_copy));
+ audio_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::launch_audioonly));
+ transcode_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::launch_transcode));
+ abort_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::abort_clicked));
+
+ audio_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::audio_combo_changed));
+ scale_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::scale_combo_changed));
+ aspect_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::aspect_checkbox_toggled));
+ height_spinner.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::update_bitrate));
+ bitrate_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::bitrate_checkbox_toggled));
+
+ update_bitrate();
+
+ audio_button.set_sensitive(false);
+
+ cancel_button = add_button (Stock::CANCEL, RESPONSE_CANCEL);
+ get_action_area()->pack_start (audio_button, false, false);
+ get_action_area()->pack_start (copy_button, false, false);
+ get_action_area()->pack_start (transcode_button, false, false);
+ show_all_children ();
+ progress_box->hide();
+}
+
+TranscodeVideoDialog::~TranscodeVideoDialog ()
+{
+ delete transcoder;
+}
+
+void
+TranscodeVideoDialog::on_show ()
+{
+ Dialog::on_show ();
+}
+
+void
+TranscodeVideoDialog::abort_clicked ()
+{
+ aborted = true;
+ transcoder->cancel();
+}
+
+void
+TranscodeVideoDialog::update_progress (framecnt_t c, framecnt_t a)
+{
+ if (a == 0 || c > a) {
+ pbar.set_pulse_step(.5);
+ pbar.pulse();
+ return;
+ }
+ pbar.set_fraction ((double)c / (double) a);
+}
+
+void
+TranscodeVideoDialog::finished ()
+{
+ if (aborted) {
+ unlink(path_entry.get_text().c_str());
+ if (!audiofile.empty()) {
+ unlink(audiofile.c_str());
+ }
+ Gtk::Dialog::response(RESPONSE_CANCEL);
+ } else {
+ if (pending_audio_extract || pending_copy_file) {
+ StartNextStage();
+ } else {
+ Gtk::Dialog::response(RESPONSE_ACCEPT);
+ }
+ }
+}
+
+void
+TranscodeVideoDialog::prepare_copy ()
+{
+ dialog_progress_mode();
+#if 1 /* tentative debug mode */
+ if (debug_checkbox.get_active()) {
+ transcoder->set_debug(true);
+ }
+#endif
+
+ if (audio_combo.get_active_row_number() == 0) {
+ launch_copy();
+ } else {
+ aborted = false;
+ pending_copy_file = true;
+ StartNextStage.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::launch_copy , this), gui_context());
+ transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
+ transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
+ launch_extract();
+ }
+}
+
+void
+TranscodeVideoDialog::launch_audioonly ()
+{
+ dialog_progress_mode();
+ path_entry.set_text("");
+#if 1 /* tentative debug mode */
+ if (debug_checkbox.get_active()) {
+ transcoder->set_debug(true);
+ }
+#endif
+ if (audio_combo.get_active_row_number() == 0) {
+ return;
+ }
+ transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
+ transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
+ launch_extract();
+}
+
+void
+TranscodeVideoDialog::launch_copy ()
+{
+ hide();
+ VideoCopyDialog *video_copy_dialog;
+ video_copy_dialog = new VideoCopyDialog(_session, infn);
+ video_copy_dialog->setup_non_interactive_copy(path_entry.get_text());
+ ResponseType r = (ResponseType) video_copy_dialog->run ();
+ video_copy_dialog->hide();
+ delete video_copy_dialog;
+ Gtk::Dialog::response(r);
+}
+
+void
+TranscodeVideoDialog::launch_extract ()
+{
+ audiofile= path_entry.get_text() + ".wav"; /* TODO: mktemp & check if file exists in audiofiles */
+ /* think: do_embed() vs do_import() - editor_videotimeline.cc
+ * directly use _session->session_directory().sound_path() ?!
+ */
+ int audio_stream;
+ pending_audio_extract = false;
+ aborted = false;
+ audio_stream = audio_combo.get_active_row_number() -1;
+ progress_label.set_text (_("Extracting Audio.."));
+
+ if (!transcoder->extract_audio(audiofile, _session->nominal_frame_rate(), audio_stream)) {
+ ARDOUR_UI::instance()->popup_error(_("Audio Extraction Failed."));
+ audiofile="";
+ Gtk::Dialog::response(RESPONSE_CANCEL);
+ return;
+ }
+}
+
+void
+TranscodeVideoDialog::dialog_progress_mode ()
+{
+ vbox->hide();
+ cancel_button->hide();
+ copy_button.hide();
+ transcode_button.hide();
+ pbar.set_size_request(300,-1);
+ progress_box->show();
+}
+
+void
+TranscodeVideoDialog::launch_transcode ()
+{
+ std::string outfn = path_entry.get_text();
+ if (!confirm_video_outfn(outfn, Config->get_video_server_docroot())) return;
+ progress_label.set_text (_("Transcoding Video.."));
+ dialog_progress_mode();
+#if 1 /* tentative debug mode */
+ if (debug_checkbox.get_active()) {
+ transcoder->set_debug(true);
+ }
+#endif
+
+ aborted = false;
+ if (audio_combo.get_active_row_number() != 0) {
+ pending_audio_extract = true;
+ StartNextStage.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::launch_extract , this), gui_context());
+ }
+
+ int scale_width, scale_height, bitrate;
+ if (scale_combo.get_active_row_number() == 0 ) {
+ scale_width =0;
+ } else {
+ scale_width = atoi(scale_combo.get_active_text().c_str());
+ }
+ if (!aspect_checkbox.get_active()) {
+ scale_height = 0;
+ } else {
+ scale_height = (int) floor(height_spinner.get_value());
+ }
+ if (bitrate_checkbox.get_active() ){
+ bitrate = (int) floor(bitrate_spinner.get_value());
+ } else {
+ bitrate = 0;
+ }
+
+ transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
+ transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
+ if (!transcoder->transcode(outfn, scale_width, scale_height, bitrate)) {
+ ARDOUR_UI::instance()->popup_error(_("Transcoding Failed."));
+ Gtk::Dialog::response(RESPONSE_CANCEL);
+ return;
+ }
+}
+
+void
+TranscodeVideoDialog::audio_combo_changed ()
+{
+ bool use_audio = audio_combo.get_active_row_number() != 0;
+ audio_button.set_sensitive(use_audio);
+ if (use_audio) {
+ copy_button.set_label(_("Copy File And\nExtract Audio"));
+ } else {
+ copy_button.set_label(_("Copy Video\nFile Only"));
+ }
+}
+
+void
+TranscodeVideoDialog::scale_combo_changed ()
+{
+ update_bitrate();
+ if (!aspect_checkbox.get_active()) {
+ int h;
+ if (scale_combo.get_active_row_number() == 0 ) {
+ h = transcoder->get_height();
+ } else {
+ h = floor(atof(scale_combo.get_active_text().c_str()) / m_aspect);
+ }
+ height_spinner.set_value(h);
+ }
+}
+
+void
+TranscodeVideoDialog::aspect_checkbox_toggled ()
+{
+ height_spinner.set_sensitive(aspect_checkbox.get_active());
+ scale_combo_changed();
+}
+
+void
+TranscodeVideoDialog::bitrate_checkbox_toggled ()
+{
+ bitrate_spinner.set_sensitive(bitrate_checkbox.get_active());
+ if (!bitrate_checkbox.get_active()) {
+ update_bitrate();
+ }
+}
+
+void
+TranscodeVideoDialog::update_bitrate ()
+{
+ double br = .7; /* avg quality - bits per pixel */
+ if (bitrate_checkbox.get_active() || !transcoder->probe_ok()) { return; }
+ br *= transcoder->get_fps();
+ br *= height_spinner.get_value();
+ if (scale_combo.get_active_row_number() == 0 ) {
+ br *= transcoder->get_height();
+ } else {
+ br *= atof(scale_combo.get_active_text().c_str());
+ }
+ bitrate_spinner.set_value(floor(br/10000.0)*10);
+}
+
+void
+TranscodeVideoDialog::open_browse_dialog ()
+{
+ Gtk::FileChooserDialog dialog(_("Save Transcoded Video File"), Gtk::FILE_CHOOSER_ACTION_SAVE);
+ dialog.set_filename (path_entry.get_text());
+
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
+
+ int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK) {
+ std::string filename = dialog.get_filename();
+
+ if (filename.length()) {
+ path_entry.set_text (filename);
+ }
+ }
+}
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/transcode_video_dialog.h b/gtk2_ardour/transcode_video_dialog.h
new file mode 100644
index 0000000000..9d334cf0d2
--- /dev/null
+++ b/gtk2_ardour/transcode_video_dialog.h
@@ -0,0 +1,106 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#ifndef __gtk_ardour_transcode_video_dialog_h__
+#define __gtk_ardour_transcode_video_dialog_h__
+
+#include <string>
+
+#include <gtkmm.h>
+
+#include "ardour/types.h"
+#include "ardour/template_utils.h"
+#include "ardour_dialog.h"
+
+#include "transcode_ffmpeg.h"
+
+/** @class TranscodeVideoDialog
+ * @brief dialog-box and controller for importing video-files
+ */
+class TranscodeVideoDialog : public ArdourDialog , public PBD::ScopedConnectionList
+{
+ public:
+ TranscodeVideoDialog (ARDOUR::Session*, std::string);
+ ~TranscodeVideoDialog ();
+
+ std::string get_filename () { return path_entry.get_text(); }
+ std::string get_audiofile () { return audiofile; }
+
+ private:
+ void on_show ();
+ void open_browse_dialog ();
+ void abort_clicked ();
+ void scale_combo_changed ();
+ void audio_combo_changed ();
+ void aspect_checkbox_toggled ();
+ void bitrate_checkbox_toggled ();
+ void update_bitrate ();
+ void launch_audioonly ();
+ void launch_transcode ();
+ void launch_extract ();
+ void prepare_copy ();
+ void launch_copy ();
+ void dialog_progress_mode ();
+ bool aborted;
+ bool pending_audio_extract;
+ bool pending_copy_file;
+ std::string audiofile;
+ std::string infn;
+ double m_aspect;
+
+ PBD::Signal0<void> StartNextStage;
+ void finished ();
+ void update_progress (ARDOUR::framecnt_t, ARDOUR::framecnt_t);
+
+ TranscodeFfmpeg *transcoder;
+
+ Gtk::Label path_label;
+ Gtk::Entry path_entry;
+ Gtk::Button browse_button;
+ Gtk::Button transcode_button;
+ Gtk::Button copy_button;
+ Gtk::Button audio_button;
+
+ Gtk::VBox* vbox;
+ Gtk::Button *cancel_button;
+ Gtk::Button abort_button;
+
+ Gtk::VBox* progress_box;
+ Gtk::Label progress_label;
+ Gtk::ProgressBar pbar;
+
+ Gtk::ComboBoxText scale_combo;
+ Gtk::CheckButton aspect_checkbox;
+ Gtk::Adjustment height_adjustment;
+ Gtk::SpinButton height_spinner;
+ Gtk::ComboBoxText audio_combo;
+ Gtk::CheckButton bitrate_checkbox;
+ Gtk::Adjustment bitrate_adjustment;
+ Gtk::SpinButton bitrate_spinner;
+#if 1 /* tentative debug mode */
+ Gtk::CheckButton debug_checkbox;
+#endif
+
+};
+
+#endif /* __gtk_ardour_transcode_video_dialog_h__ */
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/utils_videotl.cc b/gtk2_ardour/utils_videotl.cc
new file mode 100644
index 0000000000..adf7d25640
--- /dev/null
+++ b/gtk2_ardour/utils_videotl.cc
@@ -0,0 +1,301 @@
+/*
+ Copyright (C) 2010-2013 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include <cstdio>
+#include <string>
+#include <cerrno>
+#include <gtkmm.h>
+#include <curl/curl.h>
+
+#include "pbd/error.h"
+#include "ardour/ardour.h"
+#include "ardour/session_directory.h"
+#include "ardour_dialog.h"
+#include "video_image_frame.h"
+
+#include "i18n.h"
+
+using namespace Gtk;
+using namespace std;
+using namespace PBD;
+using namespace ARDOUR;
+
+bool
+confirm_video_outfn (std::string outfn, std::string docroot)
+{
+ /* replace docroot's '/' to G_DIR_SEPARATOR for the comparison */
+ size_t look_here = 0;
+ size_t found_here;
+ const char ds = G_DIR_SEPARATOR;
+ while((found_here = docroot.find('/', look_here)) != string::npos) {
+ docroot.replace(found_here, 1, std::string(&ds, 1));
+ look_here = found_here + 1;
+ }
+
+ if (!docroot.empty() && docroot.compare(0, docroot.length(), outfn, 0, docroot.length())) {
+ ArdourDialog confirm (_("Destination is outside Video Server's docroot. "), true);
+ Label m (_("The destination file path is outside of the Video Server's docroot. The file will not be readable by the Video Server. Do you still want to continue?"));
+ confirm.get_vbox()->pack_start (m, true, true);
+ confirm.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ confirm.add_button (_("Continue"), Gtk::RESPONSE_ACCEPT);
+ confirm.show_all ();
+ if (confirm.run() == RESPONSE_CANCEL) { return false; }
+ }
+
+ if (Glib::file_test(outfn, Glib::FILE_TEST_EXISTS)) {
+ ArdourDialog confirm (_("Confirm Overwrite"), true);
+ Label m (_("A file with the same name already exists. Do you want to overwrite it?"));
+ confirm.get_vbox()->pack_start (m, true, true);
+ confirm.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ confirm.add_button (_("Overwrite"), Gtk::RESPONSE_ACCEPT);
+ confirm.show_all ();
+ if (confirm.run() == RESPONSE_CANCEL) { return false; }
+ }
+
+ std::string dir = Glib::path_get_dirname (outfn);
+ if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
+ error << string_compose(_("Cannot create video folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
+ return false;
+ }
+ return true;
+}
+
+std::string
+video_dest_dir (const std::string sessiondir, const std::string docroot)
+{
+ std::string dir = docroot;
+ if (dir.empty() || !dir.compare(0, dir.length(), sessiondir, 0, dir.length())) {
+ dir=sessiondir;
+ }
+ if ((dir.empty() || dir.at(dir.length()-1) != G_DIR_SEPARATOR)) { dir += G_DIR_SEPARATOR; }
+
+ if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
+ error << string_compose(_("Cannot create video folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
+ }
+ return dir;
+}
+
+std::string
+strip_file_extension (const std::string infile)
+{
+ std::string rv;
+ char *ext, *bn = strdup(infile.c_str());
+ if ((ext=strrchr(bn, '.'))) {
+ if (!strchr(ext, G_DIR_SEPARATOR)) {
+ *ext = 0;
+ }
+ }
+ rv = std::string(bn);
+ free(bn);
+ return rv;
+}
+
+std::string
+get_file_extension (const std::string infile)
+{
+ std::string rv = "";
+ char *ext, *bn = strdup(infile.c_str());
+ if ((ext=strrchr(bn, '.'))) {
+ if (!strchr(ext, G_DIR_SEPARATOR)) {
+ rv=std::string(ext+1);
+ }
+ }
+ free(bn);
+ return rv;
+}
+
+std::string
+video_dest_file (const std::string dir, const std::string infile)
+{
+ return dir + "a3_" + strip_file_extension(Glib::path_get_basename(infile)) + ".avi";
+}
+
+std::string
+video_map_path (std::string server_docroot, std::string filepath)
+{
+ std::string rv = filepath;
+
+ /* replace all G_DIR_SEPARATOR with '/' */
+ size_t look_here = 0;
+ size_t found_here;
+ while((found_here = rv.find(G_DIR_SEPARATOR, look_here)) != string::npos) {
+ rv.replace(found_here, 1, "/");
+ look_here = found_here + 1;
+ }
+
+ /* strip docroot */
+ if (server_docroot.length() > 0) {
+ if (rv.compare(0, server_docroot.length(), server_docroot) == 0 ) {
+ rv = rv.substr(server_docroot.length());
+ }
+ }
+
+ CURL *curl;
+ char *ue;
+ curl = curl_easy_init();
+ ue = curl_easy_escape(curl, rv.c_str(),rv.length());
+ if (ue) {
+ rv = std::string(ue);
+ curl_free(ue);
+ }
+ curl_easy_cleanup(curl);
+
+ return rv;
+}
+
+void
+ParseCSV (const std::string &csv, std::vector<std::vector<std::string> > &lines)
+{
+ bool inQuote(false);
+ bool newLine(false);
+ std::string field;
+ lines.clear();
+ std::vector<std::string> line;
+
+ std::string::const_iterator aChar = csv.begin();
+ while (aChar != csv.end()) {
+ switch (*aChar) {
+ case '"':
+ newLine = false;
+ inQuote = !inQuote;
+ break;
+
+ case ',':
+ newLine = false;
+ if (inQuote == true) {
+ field += *aChar;
+ } else {
+ line.push_back(field);
+ field.clear();
+ }
+ break;
+
+ case '\n':
+ case '\r':
+ if (inQuote == true) {
+ field += *aChar;
+ } else {
+ if (newLine == false) {
+ line.push_back(field);
+ lines.push_back(line);
+ field.clear();
+ line.clear();
+ newLine = true;
+ }
+ }
+ break;
+
+ default:
+ newLine = false;
+ field.push_back(*aChar);
+ break;
+ }
+ aChar++;
+ }
+
+ if (field.size())
+ line.push_back(field);
+
+ if (line.size())
+ lines.push_back(line);
+}
+
+bool
+video_query_info (
+ std::string video_server_url,
+ std::string filepath,
+ double &video_file_fps,
+ long long int &video_duration,
+ double &video_start_offset,
+ double &video_aspect_ratio
+ )
+{
+ char url[2048];
+
+ snprintf(url, sizeof(url), "%s%sinfo/?file=%s&format=plain"
+ , video_server_url.c_str()
+ , (video_server_url.length()>0 && video_server_url.at(video_server_url.length()-1) == '/')?"":"/"
+ , filepath.c_str());
+ char *res = curl_http_get(url, NULL);
+ int pid=0;
+ if (res) {
+ char *pch, *pst;
+ int version;
+ pch = strtok_r(res, "\n", &pst);
+ while (pch) {
+#if 0 /* DEBUG */
+ printf("VideoFileInfo [%i] -> '%s'\n", pid, pch);
+#endif
+ switch (pid) {
+ case 0:
+ version = atoi(pch);
+ if (version != 1) break;
+ case 1:
+ video_file_fps = atof(pch);
+ break;
+ case 2:
+ video_duration = atoll(pch);
+ break;
+ case 3:
+ video_start_offset = atof(pch);
+ break;
+ case 4:
+ video_aspect_ratio = atof(pch);
+ break;
+ default:
+ break;
+ }
+ pch = strtok_r(NULL,"\n", &pst);
+ ++pid;
+ }
+ free(res);
+ }
+ if (pid!=5) {
+ return false;
+ }
+ return true;
+}
+
+void
+video_draw_cross (Glib::RefPtr<Gdk::Pixbuf> img)
+{
+
+ int rowstride = img->get_rowstride();
+ int n_channels = img->get_n_channels();
+ guchar *pixels, *p;
+ pixels = img->get_pixels();
+
+ int x,y;
+ int clip_width = img->get_width();
+ int clip_height = img->get_height();
+
+ for (x=0;x<clip_width;x++) {
+ y = clip_height * x / clip_width;
+ p = pixels + y * rowstride + x * n_channels;
+ p[0] = 192; p[1] = 192; p[2] = 192;
+ if (n_channels>3) p[3] = 255;
+ p = pixels + y * rowstride + (clip_width-x-1) * n_channels;
+ p[0] = 192; p[1] = 192; p[2] = 192;
+ if (n_channels>3) p[3] = 255;
+ }
+}
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/utils_videotl.h b/gtk2_ardour/utils_videotl.h
new file mode 100644
index 0000000000..d4633be91a
--- /dev/null
+++ b/gtk2_ardour/utils_videotl.h
@@ -0,0 +1,56 @@
+/*
+ Copyright (C) 2010-2013 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+/** @file utils_videotl.h
+ * @brief common functions used for video-file im/export
+ */
+
+#ifndef __gtk_ardour_video_utils_h__
+#define __gtk_ardour_video_utils_h__
+
+#include <string>
+#include <gtkmm.h>
+
+#include "ardour/types.h"
+#include "ardour/template_utils.h"
+#include "ardour_dialog.h"
+
+bool confirm_video_outfn (std::string, std::string docroot="");
+std::string video_dest_dir (const std::string, const std::string);
+std::string video_dest_file (const std::string, const std::string);
+std::string strip_file_extension (const std::string infile);
+std::string get_file_extension (const std::string infile);
+
+void ParseCSV(const std::string &csv, std::vector<std::vector<std::string> > &lines);
+std::string video_map_path (std::string server_docroot, std::string filepath);
+void video_draw_cross (Glib::RefPtr<Gdk::Pixbuf> img);
+
+bool video_query_info (
+ std::string video_server_url,
+ std::string filepath,
+ double &video_file_fps,
+ long long int &video_duration,
+ double &video_start_offset,
+ double &video_aspect_ratio
+ );
+
+#endif /* __gtk_ardour_video_utils_h__ */
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/video_copy_dialog.cc b/gtk2_ardour/video_copy_dialog.cc
new file mode 100644
index 0000000000..30d93837a7
--- /dev/null
+++ b/gtk2_ardour/video_copy_dialog.cc
@@ -0,0 +1,270 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include <cstdio>
+#include <string>
+#include <sstream>
+#include <iomanip>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sigc++/bind.h>
+#include <libgen.h>
+
+#include "pbd/error.h"
+#include "pbd/convert.h"
+#include "gtkmm2ext/utils.h"
+#include "ardour/session_directory.h"
+#include "ardour/profile.h"
+#include "ardour/template_utils.h"
+#include "ardour/session.h"
+#include "ardour_ui.h"
+#include "gui_thread.h"
+
+#include "utils_videotl.h"
+#include "utils.h"
+#include "opts.h"
+#include "video_copy_dialog.h"
+#include "i18n.h"
+
+using namespace Gtk;
+using namespace std;
+using namespace PBD;
+using namespace ARDOUR;
+
+VideoCopyDialog::VideoCopyDialog (Session* s, std::string infile)
+ : ArdourDialog (_("Import Video File "))
+ , infn (infile)
+ , path_label (_("Output File:"), Gtk::ALIGN_LEFT)
+ , browse_button (_("Browse"))
+ , copy_button (_("Copy/Embed"))
+ , abort_button (_("Abort"))
+ , progress_label ()
+{
+ set_session (s);
+ autostart = false;
+
+ set_name ("VideoCopyDialog");
+ set_position (Gtk::WIN_POS_MOUSE);
+ set_modal (true);
+ set_skip_taskbar_hint (true);
+ set_resizable (false);
+ p_connection = sigc::connection();
+
+ std::string dstdir = video_dest_dir(_session->session_directory().video_path(), Config->get_video_server_docroot());
+ std::string dstfn = dstdir + G_DIR_SEPARATOR + Glib::path_get_basename(infile);
+ path_entry.set_text (dstfn);
+
+ path_hbox = manage (new HBox);
+ path_hbox->pack_start (path_label, false, false, 3);
+ path_hbox->pack_start (path_entry, true, true, 3);
+ path_hbox->pack_start (browse_button, false, false, 3);
+ browse_button.set_name ("PaddedButton");
+ path_entry.set_width_chars(38);
+
+ browse_button.signal_clicked().connect (sigc::mem_fun (*this, &VideoCopyDialog::open_browse_dialog));
+ copy_button.signal_clicked().connect (sigc::mem_fun (*this, &VideoCopyDialog::launch_copy));
+ abort_button.signal_clicked().connect (sigc::mem_fun (*this, &VideoCopyDialog::abort_clicked));
+
+ progress_box = manage (new VBox);
+ progress_box->pack_start (progress_label, false, false);
+ progress_box->pack_start (pbar, false, false);
+ progress_box->pack_start (abort_button, false, false);
+
+ get_vbox()->pack_start (*path_hbox, false, false);
+ get_vbox()->pack_start (*progress_box, false, false);
+
+
+ cancel_button = add_button (Stock::CANCEL, RESPONSE_CANCEL);
+ get_action_area()->pack_start (copy_button, false, false);
+ show_all_children ();
+ progress_box->hide();
+}
+
+VideoCopyDialog::~VideoCopyDialog ()
+{
+}
+
+void
+VideoCopyDialog::setup_non_interactive_copy (std::string destfn)
+{
+ if (destfn.empty()) {
+ std::string dstdir = video_dest_dir(_session->session_directory().video_path(), Config->get_video_server_docroot());
+ outfn= dstdir + G_DIR_SEPARATOR + Glib::path_get_basename(infn);
+ } else {
+ outfn=destfn;
+ }
+ autostart=true;
+}
+
+void
+VideoCopyDialog::on_show ()
+{
+ if (autostart) {
+ Glib::signal_timeout().connect_once (sigc::mem_fun(*this, &VideoCopyDialog::launch_copy), 200);
+ }
+ Dialog::on_show ();
+}
+
+void
+VideoCopyDialog::abort_clicked ()
+{
+ aborted = true;
+}
+
+gint
+VideoCopyDialog::progress_timeout ()
+{
+ if (p_tot == 0) {
+ pbar.set_pulse_step(.5);
+ pbar.pulse();
+ return 1;
+ }
+ pbar.set_fraction ((double)p_cur / (double) p_tot);
+ return 1;
+}
+
+void*
+video_copy_thread (void *arg)
+{
+ VideoCopyDialog *cvd = static_cast<VideoCopyDialog*>(arg);
+ cvd->do_copy();
+ return 0;
+}
+
+
+void
+VideoCopyDialog::launch_copy ()
+{
+ if (!autostart) {
+ outfn = path_entry.get_text();
+ }
+ if (!confirm_video_outfn(outfn)) { return; }
+ p_cur = 0; p_tot = 0;
+
+ p_connection = Glib::signal_timeout().connect (sigc::mem_fun(*this, &VideoCopyDialog::progress_timeout), 80);
+
+ pbar.set_size_request(300,-1);
+ progress_box->show();
+ path_hbox->hide();
+ cancel_button->hide();
+ copy_button.hide();
+ aborted = false;
+ finished = false;
+
+ pthread_create(&thread, NULL, video_copy_thread ,this);
+ while (!finished) {
+ if (gtk_events_pending()) {
+ gtk_main_iteration ();
+ } else {
+ usleep (10000);
+ }
+ }
+ pthread_join(thread, NULL);
+
+ p_connection.disconnect();
+
+ if (aborted) {
+ Gtk::Dialog::response(RESPONSE_CANCEL);
+ } else {
+ Gtk::Dialog::response(RESPONSE_ACCEPT);
+ }
+}
+
+void
+VideoCopyDialog::do_copy ()
+{
+ progress_label.set_text (_("Linking File."));
+
+ unlink (outfn.c_str());
+
+ bool try_hardlink = false; // Config->get_try_link_for_embed(); /* XXX */
+ struct stat sb;
+ if (lstat (infn.c_str(), &sb) == 0) {
+ p_tot = sb.st_size;
+ /* don't hardlink a symlink */
+ if ((sb.st_mode&S_IFMT) == S_IFLNK) {
+ try_hardlink = false;
+ if (stat (infn.c_str(), &sb) == 0) {
+ p_tot = sb.st_size;
+ }
+ }
+ } else {
+ /* Can not stat() input file */
+ warning << _("Can not read input file.") << endmsg;
+ aborted=true;
+ finished=true;
+ return;
+ }
+
+ if ( !try_hardlink || link(infn.c_str(), outfn.c_str()) ) {
+ /* hard-link failed , try copy */
+ progress_label.set_text (_("Copying File."));
+ int infd = open (infn.c_str(), O_RDONLY);
+ int outfd = open (outfn.c_str(), O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (infd <0 || outfd <0) {
+ if (infd != -1) close(infd);
+ warning << _("Can not open files for copy.") << endmsg;
+ aborted=true;
+ finished=true;
+ return;
+ }
+ char buffer[BUFSIZ];
+ ssize_t nrb, ret;
+ while ((nrb = read(infd, buffer, BUFSIZ)) > 0 && nrb != -1 ) {
+ ret = write(outfd, buffer, nrb);
+ if(ret != nrb || aborted) {
+ warning << _("File copy failed.") << endmsg;
+ unlink(outfn.c_str());
+ aborted=true;
+ finished=true;
+ return;
+ }
+ p_cur+=ret;
+ }
+ }
+ finished=true;
+ return;
+}
+
+void
+VideoCopyDialog::open_browse_dialog ()
+{
+ Gtk::FileChooserDialog dialog(_("Video File Copy Destination"), Gtk::FILE_CHOOSER_ACTION_SAVE);
+ dialog.set_filename (path_entry.get_text());
+
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
+
+ int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK) {
+ std::string filename = dialog.get_filename();
+
+ if (filename.length()) {
+ path_entry.set_text (filename);
+ }
+ }
+}
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/video_copy_dialog.h b/gtk2_ardour/video_copy_dialog.h
new file mode 100644
index 0000000000..400cb3e83c
--- /dev/null
+++ b/gtk2_ardour/video_copy_dialog.h
@@ -0,0 +1,90 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#ifndef __gtk_ardour_video_copy_dialog_h__
+#define __gtk_ardour_video_copy_dialog_h__
+
+#include <string>
+
+#include <gtkmm.h>
+
+#include "ardour/types.h"
+#include "ardour/template_utils.h"
+#include "ardour_dialog.h"
+
+/** @class ExportVideoDialog
+ * @brief dialog box and progress report for linking and copying video-files to the session.
+ */
+class VideoCopyDialog : public ArdourDialog , public PBD::ScopedConnectionList
+{
+ public:
+ /** @param infile absolute-path to the file to copy or link */
+ VideoCopyDialog (ARDOUR::Session*, std::string infile);
+ ~VideoCopyDialog ();
+ /** if set to true before calling dialog->show()
+ * the dialog will only show the progres report and
+ * start copying or linking immediatly
+ * @param destfn destination path to copy or link the infile to.
+ */
+ void setup_non_interactive_copy(std::string destfn ="");
+ std::string get_filename () { return outfn; }
+
+ /*
+ * Note: it's actually 'private' function but used
+ * by the internal pthread, which only has a pointer
+ * to this instance and thus can only access public fn.
+ */
+ void do_copy ();
+
+ private:
+ void on_show ();
+ void abort_clicked ();
+ bool aborted;
+ bool autostart;
+ bool finished;
+ pthread_t thread;
+
+ void launch_copy ();
+ std::string infn;
+ std::string outfn;
+
+ gint progress_timeout ();
+ sigc::connection p_connection;
+ ssize_t p_cur;
+ off_t p_tot;
+
+ void open_browse_dialog ();
+ Gtk::Label path_label;
+ Gtk::Entry path_entry;
+ Gtk::Button browse_button;
+ Gtk::Button *cancel_button;
+ Gtk::Button copy_button;
+
+ Gtk::HBox* path_hbox;
+ Gtk::VBox* progress_box;
+ Gtk::Button abort_button;
+ Gtk::Label progress_label;
+ Gtk::ProgressBar pbar;
+};
+
+#endif /* __gtk_ardour_video_copy_dialog_h__ */
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/video_image_frame.cc b/gtk2_ardour/video_image_frame.cc
new file mode 100644
index 0000000000..420f2a2e12
--- /dev/null
+++ b/gtk2_ardour/video_image_frame.cc
@@ -0,0 +1,364 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include <sigc++/bind.h>
+#include "ardour/tempo.h"
+
+#include "ardour_ui.h"
+#include "video_image_frame.h"
+#include "public_editor.h"
+#include "utils.h"
+#include "canvas_impl.h"
+#include "simpleline.h"
+#include "rgb_macros.h"
+#include "utils_videotl.h"
+
+#include <gtkmm2ext/utils.h>
+#include <pthread.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+
+VideoImageFrame::VideoImageFrame (PublicEditor& ed, ArdourCanvas::Group& parent, int w, int h, std::string vsurl, std::string vfn)
+ : editor (ed)
+ , _parent(&parent)
+ , clip_width(w)
+ , clip_height(h)
+ , video_server_url(vsurl)
+ , video_filename(vfn)
+{
+ pthread_mutex_init(&request_lock, NULL);
+ pthread_mutex_init(&queue_lock, NULL);
+ queued_request=false;
+ video_frame_number = -1;
+ rightend = -1;
+ frame_position = 0;
+ thread_active=false;
+
+#if 0 /* DEBUG */
+ printf("New VideoImageFrame (%ix%i) %s - %s\n", w, h, vsurl.c_str(), vfn.c_str());
+#endif
+
+ unit_position = editor.frame_to_unit (frame_position);
+ group = new Group (parent, unit_position, 1.0);
+ img_pixbuf = new ArdourCanvas::Pixbuf(*group);
+
+ Glib::RefPtr<Gdk::Pixbuf> img;
+
+ img = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height);
+ img->fill(RGBA_TO_UINT(0,0,0,255));
+ img_pixbuf->property_pixbuf() = img;
+
+ draw_line();
+ video_draw_cross(img_pixbuf->property_pixbuf());
+
+ group->signal_event().connect (sigc::bind (sigc::mem_fun (editor, &PublicEditor::canvas_videotl_bar_event), _parent));
+ //img_pixbuf->signal_event().connect (sigc::bind (sigc::mem_fun (editor, &PublicEditor::canvas_videotl_bar_event), _parent));
+}
+
+VideoImageFrame::~VideoImageFrame ()
+{
+ if (thread_active) pthread_join(thread_id_tt, NULL);
+ delete img_pixbuf;
+ delete group;
+ pthread_mutex_destroy(&request_lock);
+ pthread_mutex_destroy(&queue_lock);
+}
+
+void
+VideoImageFrame::set_position (framepos_t frame)
+{
+ double new_unit_position = editor.frame_to_unit (frame);
+ group->move (new_unit_position - unit_position, 0.0);
+ frame_position = frame;
+ unit_position = new_unit_position;
+}
+
+void
+VideoImageFrame::reposition ()
+{
+ set_position (frame_position);
+}
+
+void
+VideoImageFrame::exposeimg ()
+{
+ img_pixbuf->show();
+ /* Note: we can not use this thread to update the window
+ * it needs to be done from the Editor's thread idle_update */
+ ImgChanged(); /* EMIT SIGNAL */
+}
+
+void
+VideoImageFrame::set_videoframe (framepos_t videoframenumber, int re)
+{
+ if (video_frame_number == videoframenumber && rightend == re) return;
+
+ video_frame_number = videoframenumber;
+ rightend = re;
+#if 0 /* dummy mode: print framenumber */
+ gchar buf[16];
+ snprintf (buf, sizeof(buf), "%li", (long int) videoframenumber);
+ img_pixbuf->property_pixbuf() = pixbuf_from_ustring(g_strdup (buf), get_font_for_style (N_("MarkerText")), 80, 60, Gdk::Color ("#C0C0C0"));
+ return;
+#endif
+#if 1 /* draw "empty frame" while we request the data */
+ Glib::RefPtr<Gdk::Pixbuf> img;
+ img = img_pixbuf->property_pixbuf();
+ img->fill(RGBA_TO_UINT(0,0,0,255));
+ video_draw_cross(img_pixbuf->property_pixbuf());
+ draw_line();
+ cut_rightend();
+ exposeimg();
+#endif
+ /* request video-frame from decoder in background thread */
+ http_get(video_frame_number);
+}
+
+void
+VideoImageFrame::draw_line ()
+{
+ Glib::RefPtr<Gdk::Pixbuf> img;
+ img = img_pixbuf->property_pixbuf();
+
+ int rowstride = img->get_rowstride();
+ int clip_height = img->get_height();
+ int n_channels = img->get_n_channels();
+ guchar *pixels, *p;
+ pixels = img->get_pixels();
+
+ int y;
+ for (y=0;y<clip_height;y++) {
+ p = pixels + y * rowstride;
+ p[0] = 255; p[1] = 255; p[2] = 255;
+ if (n_channels>3) p[3] = 255;
+ }
+}
+
+void
+VideoImageFrame::cut_rightend ()
+{
+ if (rightend < 0 ) { return; }
+ Glib::RefPtr<Gdk::Pixbuf> img;
+ img = img_pixbuf->property_pixbuf();
+
+ int rowstride = img->get_rowstride();
+ int clip_height = img->get_height();
+ int clip_width = img->get_width();
+ int n_channels = img->get_n_channels();
+ guchar *pixels, *p;
+ pixels = img->get_pixels();
+ if (rightend > clip_width) { return; }
+
+ int x,y;
+ for (y=0;y<clip_height;++y) {
+ p = pixels + y * rowstride + rightend * n_channels;
+ p[0] = 192; p[1] = 127; p[2] = 127;
+ if (n_channels>3) p[3] = 255;
+ for (x=rightend+1; x<clip_width; ++x) {
+ p = pixels + y * rowstride + x * n_channels;
+ p[0] = 0; p[1] = 0; p[2] = 0;
+ if (n_channels>3) p[3] = 0;
+ }
+ }
+}
+
+void *
+http_get_thread (void *arg) {
+ VideoImageFrame *vif = static_cast<VideoImageFrame *>(arg);
+ char url[2048];
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+ snprintf(url, sizeof(url), "%s?frame=%li&w=%d&h=%di&file=%s&format=rgb",
+ vif->get_video_server_url().c_str(),
+ (long int) vif->get_req_frame(), vif->get_width(), vif->get_height(),
+ vif->get_video_filename().c_str()
+ );
+ char *res=curl_http_get(url, NULL);
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+ vif->http_download_done(res);
+ pthread_exit(0);
+ return 0;
+}
+
+void
+VideoImageFrame::http_download_done (char *data){
+ if (queued_request) {
+ http_get_again(want_video_frame_number);
+ return;
+ }
+
+ if (!data) {
+ /* Image request failed (HTTP error or timeout) */
+ Glib::RefPtr<Gdk::Pixbuf> img;
+ img = img_pixbuf->property_pixbuf();
+ img->fill(RGBA_TO_UINT(128,0,0,255));
+ video_draw_cross(img_pixbuf->property_pixbuf());
+ cut_rightend();
+ draw_line();
+ cut_rightend();
+ /* TODO: mark as invalid:
+ * video_frame_number = -1;
+ * TODO: but prevent live-loops when calling update again
+ */
+ } else {
+ Glib::RefPtr<Gdk::Pixbuf> tmp, img;
+#if 0 // RGBA
+ tmp = Gdk::Pixbuf::create_from_data ((guint8*) data, Gdk::COLORSPACE_RGB, true, 8, clip_width, clip_height, clip_width*4);
+#else // RGB
+ tmp = Gdk::Pixbuf::create_from_data ((guint8*) data, Gdk::COLORSPACE_RGB, false, 8, clip_width, clip_height, clip_width*3);
+#endif
+ img = img_pixbuf->property_pixbuf();
+ tmp->copy_area (0, 0, clip_width, clip_height, img, 0, 0);
+ free(data);
+ draw_line();
+ cut_rightend();
+ }
+
+ exposeimg();
+ /* don't request frames too quickly, wait after user has zoomed */
+ usleep(40000);
+
+ if (queued_request) {
+ http_get_again(want_video_frame_number);
+ }
+ pthread_mutex_unlock(&request_lock);
+}
+
+
+void
+VideoImageFrame::http_get(framepos_t fn) {
+ if (pthread_mutex_trylock(&request_lock)) {
+ /* remember last request and schedule after the lock has been released. */
+ pthread_mutex_lock(&queue_lock);
+ queued_request=true;
+ want_video_frame_number=fn;
+ pthread_mutex_unlock(&queue_lock);
+#if 0
+ /* TODO: cancel request and start a new one
+ * but only if we're waiting for curl request.
+ * don't interrupt http_download_done()
+ *
+ * This should work, but requires testing:
+ */
+ if (!pthread_cancel(thread_id_tt)) {
+ pthread_mutex_unlock(&request_lock);
+ } else return;
+#else
+ return;
+#endif
+ }
+ if (thread_active) pthread_join(thread_id_tt, NULL);
+ pthread_mutex_lock(&queue_lock);
+ queued_request=false;
+ req_video_frame_number=fn;
+ pthread_mutex_unlock(&queue_lock);
+ int rv = pthread_create(&thread_id_tt, NULL, http_get_thread, this);
+ thread_active=true;
+ if (rv) {
+ thread_active=false;
+ printf("thread creation failed. %i\n",rv);
+ http_download_done(NULL);
+ }
+}
+
+void
+VideoImageFrame::http_get_again(framepos_t fn) {
+ pthread_mutex_lock(&queue_lock);
+ queued_request=false;
+ req_video_frame_number=want_video_frame_number;
+ pthread_mutex_unlock(&queue_lock);
+
+ http_get_thread(this);
+}
+
+
+extern "C" {
+#include <curl/curl.h>
+
+ struct MemoryStruct {
+ char *data;
+ size_t size;
+ };
+
+ static size_t
+ WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data) {
+ size_t realsize = size * nmemb;
+ struct MemoryStruct *mem = (struct MemoryStruct *)data;
+
+ mem->data = (char *)realloc(mem->data, mem->size + realsize + 1);
+ if (mem->data) {
+ memcpy(&(mem->data[mem->size]), ptr, realsize);
+ mem->size += realsize;
+ mem->data[mem->size] = 0;
+ }
+ return realsize;
+ }
+
+ char *curl_http_get (const char *u, int *status) {
+ CURL *curl;
+ CURLcode res;
+ struct MemoryStruct chunk;
+ long int httpstatus;
+ if (status) *status = 0;
+ //usleep(500000); return NULL; // TEST & DEBUG
+ if (strncmp("http://", u, 7)) return NULL;
+
+ chunk.data=NULL;
+ chunk.size=0;
+
+ curl = curl_easy_init();
+ if(!curl) return NULL;
+ curl_easy_setopt(curl, CURLOPT_URL, u);
+
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, ARDOUR_USER_AGENT);
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, ARDOUR_CURL_TIMEOUT);
+ curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
+#define CURLERRORDEBUG /* XXX */
+#ifdef CURLERRORDEBUG
+ char curlerror[CURL_ERROR_SIZE] = "";
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlerror);
+#endif
+
+ res = curl_easy_perform(curl);
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpstatus);
+ curl_easy_cleanup(curl);
+ if (status) *status = httpstatus;
+ if (res) {
+#ifdef CURLERRORDEBUG
+ printf("curl_http_get() failed: %s\n", curlerror);
+#endif
+ return NULL;
+ }
+ if (httpstatus != 200) {
+ free (chunk.data);
+ chunk.data = NULL;
+ }
+ return (chunk.data);
+ }
+
+} /* end extern "C" */
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/video_image_frame.h b/gtk2_ardour/video_image_frame.h
new file mode 100644
index 0000000000..2f384c3d8e
--- /dev/null
+++ b/gtk2_ardour/video_image_frame.h
@@ -0,0 +1,116 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#ifndef __ardour_video_image_frame_h__
+#define __ardour_video_image_frame_h__
+
+#define ARDOUR_USER_AGENT (PROGRAM_NAME VERSIONSTRING)
+#define ARDOUR_CURL_TIMEOUT (60)
+
+#include <string>
+#include <glib.h>
+
+#include <libgnomecanvasmm/pixbuf.h>
+#include <sigc++/signal.h>
+#include <pthread.h>
+
+#include "ardour/ardour.h"
+#include "pbd/signals.h"
+
+#include "canvas.h"
+
+namespace ARDOUR {
+ class TempoSection;
+ class MeterSection;
+}
+
+class PublicEditor;
+
+/** @class VideoImageFrame
+ * @brief a single video-frame to be displayed in the video timeline
+ */
+class VideoImageFrame : public sigc::trackable
+{
+ public:
+ VideoImageFrame (PublicEditor&, ArdourCanvas::Group&, int, int, std::string, std::string);
+ virtual ~VideoImageFrame ();
+
+ void set_position (framepos_t);
+ void set_videoframe (framepos_t, int rightend = -1);
+ framepos_t get_video_frame_number() {return video_frame_number;}
+
+ int get_height () {return clip_height;}
+ int get_width () {return clip_width;}
+ int get_rightend() { return rightend;}
+ framepos_t get_req_frame () {return req_video_frame_number;}
+ std::string get_video_server_url () {return video_server_url;}
+ std::string get_video_filename () {return video_filename;}
+
+ void http_download_done (char *);
+ PBD::Signal0<void> ImgChanged;
+
+ protected:
+
+ PublicEditor& editor;
+ ArdourCanvas::Group *_parent;
+ ArdourCanvas::Group *group;
+ ArdourCanvas::Pixbuf *img_pixbuf;
+
+ int clip_width;
+ int clip_height;
+ int rightend;
+
+ std::string video_server_url;
+ std::string video_filename;
+
+ double unit_position;
+ framepos_t frame_position;
+ framepos_t video_frame_number;
+
+ void reposition ();
+ void exposeimg ();
+
+ void draw_line ();
+ void cut_rightend ();
+
+
+ void http_get(framepos_t fn);
+ void http_get_again(framepos_t fn);
+
+ framepos_t req_video_frame_number;
+ framepos_t want_video_frame_number;
+ bool queued_request;
+
+ pthread_mutex_t request_lock;
+ pthread_mutex_t queue_lock;
+
+ pthread_t thread_id_tt;
+ bool thread_active;
+
+};
+
+extern "C" {
+ char *curl_http_get (const char *u, int *status);
+}
+
+#endif /* __ardour_video_image_frame_h__ */
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/video_monitor.cc b/gtk2_ardour/video_monitor.cc
new file mode 100644
index 0000000000..3abc11be28
--- /dev/null
+++ b/gtk2_ardour/video_monitor.cc
@@ -0,0 +1,406 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include "pbd/file_utils.h"
+#include "gui_thread.h"
+#include "ardour_ui.h"
+
+#include <stdio.h>
+#include "public_editor.h"
+#include "video_monitor.h"
+
+#include "i18n.h"
+
+using namespace std;
+
+VideoMonitor::VideoMonitor (PublicEditor *ed, std::string xjadeo_bin_path)
+ : editor (ed)
+{
+ manually_seeked_frame = 0;
+ fps =0.0; // = _session->timecode_frames_per_second();
+ sync_by_manual_seek = false;
+ _restore_settings_mask = 0;
+ clock_connection = sigc::connection();
+ debug_enable = false;
+
+ process = new SystemExec(xjadeo_bin_path);
+ process->ReadStdout.connect (*this, invalidator (*this), boost::bind (&VideoMonitor::parse_output, this, _1 ,_2), gui_context());
+ process->Terminated.connect (*this, invalidator (*this), boost::bind (&VideoMonitor::terminated, this), gui_context());
+}
+
+VideoMonitor::~VideoMonitor ()
+{
+ if (clock_connection.connected()) {
+ clock_connection.disconnect();
+ }
+ delete process;
+}
+
+bool
+VideoMonitor::start ()
+{
+ if (is_started()) {
+ return true;
+ }
+
+ manually_seeked_frame = 0;
+ sync_by_manual_seek = false;
+ if (clock_connection.connected()) { clock_connection.disconnect(); }
+
+ if (process->start(debug_enable?2:1)) {
+ return false;
+ }
+ return true;
+}
+
+void
+VideoMonitor::quit ()
+{
+ if (!is_started()) return;
+ process->write_to_stdin("get windowsize\n");
+ process->write_to_stdin("get windowpos\n");
+ process->write_to_stdin("get letterbox\n");
+ process->write_to_stdin("get fullscreen\n");
+ process->write_to_stdin("get ontop\n");
+ process->write_to_stdin("get offset\n");
+ process->write_to_stdin("get osdcfg\n");
+ process->write_to_stdin("quit\n");
+#if 1
+ /* wait for replies to the config requests above.
+ * the 'quit' command should result in process termination
+ * but in case it fails (communication failure, SIGSTOP, ??)
+ * here's a timeout..
+ */
+ int timeout = 40;
+ while (is_started() && --timeout) {
+ usleep(50000);
+ }
+ if (timeout == 0) {
+ printf("xjadeo connection: time-out. session may not be saved.\n");
+ }
+#endif
+ process->terminate();
+ if (clock_connection.connected()) { clock_connection.disconnect(); }
+}
+
+void
+VideoMonitor::open (std::string filename)
+{
+ if (!is_started()) return;
+ manually_seeked_frame = 0;
+ sync_by_manual_seek = false;
+ process->write_to_stdin("load " + filename + "\n");
+ process->write_to_stdin("set fps -1\n");
+ process->write_to_stdin("window resize 100%\n");
+ process->write_to_stdin("window ontop on\n");
+ process->write_to_stdin("set seekmode 1\n");
+ process->write_to_stdin("set override 47\n");
+ process->write_to_stdin("window letterbox on\n");
+ process->write_to_stdin("osd mode 10\n");
+ for(XJSettings::const_iterator it = xjadeo_settings.begin(); it != xjadeo_settings.end(); ++it) {
+ if (skip_setting(it->first)) { continue; }
+ process->write_to_stdin(it->first + " " + it->second + "\n");
+ }
+ xjadeo_sync_setup();
+}
+
+bool
+VideoMonitor::skip_setting (std::string which)
+{
+ if (_restore_settings_mask & XJ_OSD && which == "osd mode") { return true; }
+ if (_restore_settings_mask & XJ_LETTERBOX && which == "window letterbox") { return true; }
+ if (_restore_settings_mask & XJ_WINDOW_SIZE && which == "window size") { return true; }
+ if (_restore_settings_mask & XJ_WINDOW_POS && which == "window xy") { return true; }
+ if (_restore_settings_mask & XJ_WINDOW_ONTOP && which == "window ontop") { return true; }
+ if (_restore_settings_mask & XJ_LETTERBOX && which == "window letterbox") { return true; }
+ if (_restore_settings_mask & XJ_OFFSET && which == "set offset") { return true; }
+ if (_restore_settings_mask & XJ_FULLSCREEN && which == "window zoom") { return true; }
+ return false;
+}
+
+bool
+VideoMonitor::is_started ()
+{
+ return process->is_running();
+}
+
+void
+VideoMonitor::parse_output (std::string d, size_t s)
+{
+ std::string line = d;
+ std::string::size_type start = 0;
+ std::string::size_type end = 0;
+
+ while (1) {
+ end = d.find('\n', start);
+ if (end == std::string::npos) break;
+ line = d.substr(start,end-start);
+ start=end+1;
+ if (line.length() <4 || line.at(0)!='@') continue;
+#if 1 /* DEBUG */
+ if (debug_enable) {
+ printf("xjadeo: '%s'\n", line.c_str());
+ }
+#endif
+ int status = atoi(line.substr(1,3).c_str());
+ switch(status / 100) {
+ case 4: /* errors */
+ if (status == 403) {
+ PBD::warning << _("Video Monitor: File Not Found.") << endmsg;
+ /* check: we should only write from the main thread.
+ * However it should not matter for 'quit'.
+ */
+ process->write_to_stdin("quit\n");
+ }
+ case 1: /* requested async notifications */
+ case 3: /* warnings ; command succeeded, but status is negative. */
+ break;
+ case 2:
+/* replies:
+ * 201: var=<int>
+ * 202: var=<double>
+ * 210: var=<int>x<int>
+ * 220: var=<string>
+ * 228: var=<smpte-string>
+ */
+ {
+ std::string::size_type equalsign = line.find('=');
+ std::string::size_type comment = line.find('#');
+ if (comment != std::string::npos) { line = line.substr(0,comment); }
+ if (equalsign != std::string::npos) {
+ std::string key = line.substr(5, equalsign - 5);
+ std::string value = line.substr(equalsign + 1);
+#if 0 /* DEBUG */
+ std::cout << "parsed: " << key << " => " << value << std::endl;
+#endif
+ if(key == "windowpos") {
+ xjadeo_settings["window xy"] = value;
+ } else if(key == "windowsize") {
+ xjadeo_settings["window size"] = value;
+ } else if(key == "windowontop") {
+ xjadeo_settings["window ontop"] = value;
+ } else if(key == "fullscreen") {
+ xjadeo_settings["window zoom"] = value;
+ } else if(key == "letterbox") {
+ xjadeo_settings["window letterbox"] = value;
+ } else if(key == "osdmode") {
+ xjadeo_settings["osd mode"] = value;
+ } else if(key == "offset") {
+ xjadeo_settings["set offset"] = value;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void
+VideoMonitor::terminated ()
+{
+ save_session();
+ Terminated();
+}
+
+void
+VideoMonitor::save_session ()
+{
+ if (!_session) { return; }
+ bool is_dirty = false;
+
+ XMLNode* prev = _session->extra_xml (X_("XJSettings"));
+ XMLNode* node = new XMLNode(X_("XJSettings"));
+ XMLNodeList nlist;
+ if (!prev) { is_dirty = true; }
+ else { nlist = prev->children(); }
+
+ for(XJSettings::const_iterator it = xjadeo_settings.begin(); it != xjadeo_settings.end(); ++it) {
+ XMLNode* child = node->add_child (X_("XJSetting"));
+ child->add_property (X_("k"), it->first);
+ child->add_property (X_("v"), it->second);
+ if (!is_dirty) {
+ bool found = false;
+ XMLNodeConstIterator i;
+ for (i = nlist.begin(); i != nlist.end(); ++i) {
+ if ((*i)->property(X_("k"))->value() == it->first &&
+ (*i)->property(X_("v"))->value() == it->second ) {
+ found=true;
+ break;
+ }
+ }
+ if (!found) {is_dirty = true;}
+ }
+ }
+
+ if (is_dirty) {
+ _session->add_extra_xml (*node);
+ _session->set_dirty ();
+ }
+}
+
+
+void
+VideoMonitor::set_session (ARDOUR::Session *s)
+{
+ SessionHandlePtr::set_session (s);
+ if (!_session) { return; }
+ _session->config.ParameterChanged.connect (*this, invalidator (*this), ui_bind (&VideoMonitor::parameter_changed, this, _1), gui_context());
+ XMLNode* node = _session->extra_xml (X_("XJSettings"));
+ if (!node) { return;}
+ xjadeo_settings.clear();
+
+ XMLNodeList nlist = node->children();
+ XMLNodeConstIterator niter;
+ for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+ xjadeo_settings[(*niter)->property(X_("k"))->value()] = (*niter)->property(X_("v"))->value();
+ }
+}
+
+void
+VideoMonitor::clear_session_state ()
+{
+ xjadeo_settings.clear();
+ if (!_session) { return; }
+ XMLNode* node = new XMLNode(X_("XJSettings"));
+ _session->add_extra_xml (*node);
+ _session->set_dirty ();
+}
+
+bool
+VideoMonitor::set_custom_setting (const std::string k, const std::string v)
+{
+ xjadeo_settings[k] = v;
+ return true; /* TODO: check if key is valid */
+}
+
+const std::string
+VideoMonitor::get_custom_setting (const std::string k)
+{
+ return (xjadeo_settings[k]);
+}
+#define NO_OFFSET (1<<31) //< skip setting or modifying offset -- TODO check ARDOUR::frameoffset_t max value.
+void
+VideoMonitor::srsupdate ()
+{
+ if (!_session) { return; }
+ if (editor->dragging_playhead()) { return ;}
+ manual_seek(_session->audible_frame(), false, NO_OFFSET);
+}
+
+void
+VideoMonitor::set_offset (ARDOUR::frameoffset_t offset)
+{
+ if (!is_started()) { return; }
+ if (!_session) { return; }
+ if (offset == NO_OFFSET ) { return; }
+
+ framecnt_t video_frame_offset;
+ framecnt_t audio_frame_rate;
+ if (_session->config.get_videotimeline_pullup()) {
+ audio_frame_rate = _session->frame_rate();
+ } else {
+ audio_frame_rate = _session->nominal_frame_rate();
+ }
+
+ /* Note: pull-up/down are applied here: frame_rate() vs. nominal_frame_rate() */
+ if (_session->config.get_use_video_file_fps()) {
+ video_frame_offset = floor(offset * fps / audio_frame_rate);
+ } else {
+ video_frame_offset = floor(offset * _session->timecode_frames_per_second() / audio_frame_rate);
+ }
+
+ // TODO remember if changed..
+ std::ostringstream osstream1; osstream1 << -1 * video_frame_offset;
+ process->write_to_stdin("set offset " + osstream1.str() + "\n");
+}
+
+void
+VideoMonitor::manual_seek (framepos_t when, bool force, ARDOUR::frameoffset_t offset)
+{
+ if (!is_started()) { return; }
+ if (!_session) { return; }
+ framecnt_t video_frame;
+ framecnt_t audio_frame_rate;
+ if (_session->config.get_videotimeline_pullup()) {
+ audio_frame_rate = _session->frame_rate();
+ } else {
+ audio_frame_rate = _session->nominal_frame_rate();
+ }
+
+ /* Note: pull-up/down are applied here: frame_rate() vs. nominal_frame_rate() */
+ if (_session->config.get_use_video_file_fps()) {
+ video_frame = floor(when * fps / audio_frame_rate);
+ } else {
+ video_frame = floor(when * _session->timecode_frames_per_second() / audio_frame_rate);
+ }
+ if (video_frame < 0 ) video_frame = 0;
+
+ if (video_frame == manually_seeked_frame) { return; }
+ manually_seeked_frame = video_frame;
+
+#if 0 /* DEBUG */
+ std::cout <<"seek: " << video_frame << std::endl;
+#endif
+ std::ostringstream osstream; osstream << video_frame;
+ process->write_to_stdin("seek " + osstream.str() + "\n");
+
+ set_offset(offset);
+}
+
+void
+VideoMonitor::parameter_changed (std::string const & p)
+{
+ if (!is_started()) { return; }
+ if (!_session) { return; }
+ if (p != "external-sync" && p != "sync-source") {
+ return;
+ }
+ xjadeo_sync_setup();
+}
+
+void
+VideoMonitor::xjadeo_sync_setup ()
+{
+ if (!is_started()) { return; }
+ if (!_session) { return; }
+
+ bool my_manual_seek = true;
+ if (_session->config.get_external_sync()) {
+ if (ARDOUR::Config->get_sync_source() == ARDOUR::JACK)
+ my_manual_seek = false;
+ }
+
+ if (my_manual_seek != sync_by_manual_seek) {
+ if (sync_by_manual_seek) {
+ if (clock_connection.connected()) {
+ clock_connection.disconnect();
+ }
+ process->write_to_stdin("jack connect\n");
+ } else {
+ process->write_to_stdin("jack disconnect\n");
+ clock_connection = ARDOUR_UI::SuperRapidScreenUpdate.connect (sigc::mem_fun (*this, &VideoMonitor::srsupdate));
+ }
+ sync_by_manual_seek = my_manual_seek;
+ }
+}
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/video_monitor.h b/gtk2_ardour/video_monitor.h
new file mode 100644
index 0000000000..3f391d01f6
--- /dev/null
+++ b/gtk2_ardour/video_monitor.h
@@ -0,0 +1,109 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#ifndef __ardour_video_monitor_h__
+#define __ardour_video_monitor_h__
+
+#include <string>
+
+#include "ardour/ardour.h"
+#include "ardour/types.h"
+#include "ardour/session.h"
+#include "ardour/session_handle.h"
+#include "system_exec.h"
+
+namespace ARDOUR {
+ class Session;
+}
+class PublicEditor;
+
+enum XJSettingOptions {
+ XJ_WINDOW_SIZE = 1,
+ XJ_WINDOW_POS = 2,
+ XJ_WINDOW_ONTOP = 4,
+ XJ_LETTERBOX = 8,
+ XJ_OSD = 16,
+ XJ_OFFSET = 32,
+ XJ_FULLSCREEN = 64,
+};
+
+/** @class VideoMonitor
+ * @brief communication with xjadeo's remote-control interface
+ */
+class VideoMonitor : public sigc::trackable , public ARDOUR::SessionHandlePtr, public PBD::ScopedConnectionList
+{
+ public:
+ VideoMonitor (PublicEditor*, std::string);
+ virtual ~VideoMonitor ();
+
+ void set_filename (std::string filename);
+ void set_fps (float f) {fps = f;}
+ bool is_started ();
+ bool start ();
+ void quit ();
+ void open (std::string);
+
+ void set_session (ARDOUR::Session *s);
+ void clear_session_state ();
+ bool set_custom_setting (const std::string, const std::string);
+ const std::string get_custom_setting (const std::string);
+ void restore_settings_mask (int i) { _restore_settings_mask = i;}
+ const int restore_settings_mask () { return _restore_settings_mask;}
+
+ void set_offset (ARDOUR::frameoffset_t);
+ void manual_seek (ARDOUR::framepos_t, bool, ARDOUR::frameoffset_t);
+ void srsupdate ();
+ bool synced_by_manual_seeks() { return sync_by_manual_seek; }
+
+ sigc::signal<void> Terminated;
+
+#if 1
+ void set_debug (bool onoff) { debug_enable = onoff; }
+#endif
+
+ protected:
+ PublicEditor *editor;
+ SystemExec *process;
+ float fps;
+ void parse_output (std::string d, size_t s);
+ void terminated ();
+
+ void save_session ();
+ void parameter_changed (std::string const & p);
+
+ typedef std::map<std::string,std::string> XJSettings;
+
+ int _restore_settings_mask;
+ bool skip_setting(std::string);
+ XJSettings xjadeo_settings;
+
+ void xjadeo_sync_setup ();
+ ARDOUR::framepos_t manually_seeked_frame;
+ bool sync_by_manual_seek;
+ sigc::connection clock_connection;
+#if 1
+ bool debug_enable;
+#endif
+};
+
+#endif /* __ardour_video_monitor_h__ */
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/video_server_dialog.cc b/gtk2_ardour/video_server_dialog.cc
new file mode 100644
index 0000000000..f6236a2aee
--- /dev/null
+++ b/gtk2_ardour/video_server_dialog.cc
@@ -0,0 +1,205 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include <cstdio>
+#include <cmath>
+
+#include <sigc++/bind.h>
+
+#include "pbd/error.h"
+#include "pbd/file_utils.h"
+#include "ardour/session_directory.h"
+#include "gtkmm2ext/utils.h"
+#include "ardour/template_utils.h"
+#include "ardour/session.h"
+
+#include "video_server_dialog.h"
+#include "i18n.h"
+
+using namespace Gtk;
+using namespace std;
+using namespace PBD;
+using namespace ARDOUR;
+
+VideoServerDialog::VideoServerDialog (Session* s)
+ : ArdourDialog (_("Launch Video Server"))
+ , path_label (_("Server Executable:"), Gtk::ALIGN_LEFT)
+ , path_browse_button (_("Browse"))
+ , docroot_label (_("Server Docroot:"), Gtk::ALIGN_LEFT)
+ , docroot_browse_button (_("Browse"))
+ , listenport_adjustment (1554, 1025, 65536, 1, 10, 0)
+ , listenport_spinner (listenport_adjustment)
+ , cachesize_adjustment (256, 32, 32768, 1, 32, 0)
+ , cachesize_spinner (cachesize_adjustment)
+ , showagain_checkbox (_("Don't show this dialog again. (Reset in Edit->Preferences)."))
+{
+ set_session (s);
+
+ set_name ("VideoServerDialog");
+ set_position (Gtk::WIN_POS_MOUSE);
+ set_modal (true);
+ set_skip_taskbar_hint (true);
+ set_resizable (false);
+
+ Gtk::Label* l;
+ VBox* vbox = manage (new VBox);
+ VBox* options_box = manage (new VBox);
+ HBox* path_hbox = manage (new HBox);
+ HBox* docroot_hbox = manage (new HBox);
+
+ path_entry.set_width_chars(38);
+ path_browse_button.set_name ("PaddedButton");
+ path_entry.set_text("/usr/bin/harvid");
+ docroot_entry.set_width_chars(38);
+ docroot_entry.set_text(Config->get_video_server_docroot());
+ docroot_browse_button.set_name ("PaddedButton");
+
+ listenaddr_combo.set_name ("PaddedButton");
+#ifndef __APPLE__
+ /* Note: on OSX icsd is not able to bind to IPv4 localhost */
+ listenaddr_combo.append_text("127.0.0.1");
+#endif
+ listenaddr_combo.append_text("0.0.0.0");
+ listenaddr_combo.set_active(0);
+
+ std::string icsd_file_path;
+ if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("harvid"), icsd_file_path)) {
+ path_entry.set_text(icsd_file_path);
+ }
+ else if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("icsd"), icsd_file_path)) {
+ path_entry.set_text(icsd_file_path);
+ }
+ else if (Glib::file_test(X_("C:\\Program Files\\harvid\\harvid.exe"), Glib::FILE_TEST_EXISTS)) {
+ path_entry.set_text(X_("C:\\Program Files\\harvid\\harvid.exe"));
+ }
+ else {
+ PBD::warning << _("The external video server 'harvid' can not be found, see https://github.com/x42/harvid") << endmsg;
+ }
+
+
+ if (docroot_entry.get_text().empty()) {
+ std::string docroot = Glib::path_get_dirname(_session->session_directory().root_path());
+ if ((docroot.empty() || docroot.at(docroot.length()-1) != '/')) { docroot += "/"; }
+ docroot_entry.set_text(docroot);
+ }
+
+ path_hbox->pack_start (path_label, false, false, 3);
+ path_hbox->pack_start (path_entry, true, true, 3);
+ path_hbox->pack_start (path_browse_button, false, false, 3);
+
+ docroot_hbox->pack_start (docroot_label, false, false, 3);
+ docroot_hbox->pack_start (docroot_entry, true, true, 3);
+ docroot_hbox->pack_start (docroot_browse_button, false, false, 3);
+
+ l = manage (new Label (_("<b>Options</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_use_markup ();
+ options_box->pack_start (*l, false, true, 4);
+
+ Table* t = manage (new Table (2, 3));
+ t->set_spacings (4);
+ options_box->pack_start (*t, true, true, 4);
+
+ l = manage (new Label (_("Listen Address:")));
+ l->set_alignment (0, 0.5);
+ t->attach (*l, 0, 1, 0, 1, FILL);
+ t->attach (listenaddr_combo, 1, 2, 0, 1);
+
+ l = manage (new Label (_("Listen Port:")));
+ l->set_alignment (0, 0.5);
+ t->attach (*l, 0, 1, 1, 2, FILL);
+ t->attach (listenport_spinner, 1, 2, 1, 2);
+
+ l = manage (new Label (_("Cache Size:")));
+ l->set_alignment (0, 0.5);
+ t->attach (*l, 0, 1, 2, 3, FILL);
+ t->attach (cachesize_spinner, 1, 2, 2, 3);
+
+ l = manage (new Label (_("Ardour relies on an external Video Server for the videotimeline. The server configured in Edit -> Prefereces -> Video is not reachable. Do you want ardour to launch 'harvid' on this machine?"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+ l->set_line_wrap();
+ vbox->pack_start (*l, false, true, 4);
+ vbox->pack_start (*path_hbox, false, false);
+ vbox->pack_start (*docroot_hbox, false, false);
+ vbox->pack_start (*options_box, false, true);
+
+ get_vbox()->set_spacing (4);
+ get_vbox()->pack_start (*vbox, false, false);
+ get_vbox()->pack_start (showagain_checkbox, false, false);
+ showagain_checkbox.set_active(false);
+
+ path_browse_button.signal_clicked().connect (sigc::mem_fun (*this, &VideoServerDialog::open_path_dialog));
+ docroot_browse_button.signal_clicked().connect (sigc::mem_fun (*this, &VideoServerDialog::open_docroot_dialog));
+
+ show_all_children ();
+ add_button (Stock::CANCEL, RESPONSE_CANCEL);
+ add_button (Stock::EXECUTE, RESPONSE_ACCEPT);
+}
+
+VideoServerDialog::~VideoServerDialog ()
+{
+}
+
+void
+VideoServerDialog::on_show ()
+{
+ Dialog::on_show ();
+}
+
+void
+VideoServerDialog::open_path_dialog ()
+{
+ Gtk::FileChooserDialog dialog(_("Set Video Server Executable"), Gtk::FILE_CHOOSER_ACTION_OPEN);
+ dialog.set_filename (path_entry.get_text());
+
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
+
+ int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK) {
+ std::string filename = dialog.get_filename();
+
+ if (filename.length()) {
+ path_entry.set_text (filename);
+ }
+ }
+}
+
+void
+VideoServerDialog::open_docroot_dialog ()
+{
+ Gtk::FileChooserDialog dialog(_("Server docroot"), Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
+ dialog.set_filename (docroot_entry.get_text());
+
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
+
+ int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK) {
+ std::string dirname = dialog.get_filename();
+
+ if (dirname.length()) {
+ docroot_entry.set_text (dirname);
+ }
+ }
+}
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/video_server_dialog.h b/gtk2_ardour/video_server_dialog.h
new file mode 100644
index 0000000000..e6216cfed7
--- /dev/null
+++ b/gtk2_ardour/video_server_dialog.h
@@ -0,0 +1,72 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#ifndef __gtk_ardour_video_server_dialog_h__
+#define __gtk_ardour_video_server_dialog_h__
+
+#include <string>
+
+#include <gtkmm.h>
+
+#include "ardour/types.h"
+#include "ardour/template_utils.h"
+#include "ardour_dialog.h"
+
+/** @class VideoServerDialog
+ * @brief dialog box to collect video-server settings
+ */
+class VideoServerDialog : public ArdourDialog
+{
+ public:
+ VideoServerDialog (ARDOUR::Session*);
+ ~VideoServerDialog ();
+
+ std::string get_exec_path () { return path_entry.get_text(); }
+ std::string get_docroot () { return docroot_entry.get_text(); }
+ std::string get_listenaddr () { return listenaddr_combo.get_active_text();}
+ int get_listenport () { return listenport_spinner.get_value_as_int();}
+ int get_cachesize () { return cachesize_spinner.get_value_as_int();}
+ bool show_again () { return showagain_checkbox.get_active();}
+
+ private:
+ void on_show ();
+ void open_path_dialog ();
+ void open_docroot_dialog ();
+
+ Gtk::Label path_label;
+ Gtk::Entry path_entry;
+ Gtk::Button path_browse_button;
+
+ Gtk::Label docroot_label;
+ Gtk::Entry docroot_entry;
+ Gtk::Button docroot_browse_button;
+
+ Gtk::ComboBoxText listenaddr_combo;
+ Gtk::Adjustment listenport_adjustment;
+ Gtk::SpinButton listenport_spinner;
+ Gtk::Adjustment cachesize_adjustment;
+ Gtk::SpinButton cachesize_spinner;
+ Gtk::CheckButton showagain_checkbox;
+};
+
+#endif /* __gtk_ardour_video_server_dialog_h__ */
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/video_timeline.cc b/gtk2_ardour/video_timeline.cc
new file mode 100644
index 0000000000..09301bdcec
--- /dev/null
+++ b/gtk2_ardour/video_timeline.cc
@@ -0,0 +1,769 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#include <sigc++/bind.h>
+#include "ardour/tempo.h"
+
+#include "pbd/file_utils.h"
+#include "ardour/session_directory.h"
+
+#include "ardour_ui.h"
+#include "public_editor.h"
+#include "gui_thread.h"
+#include "utils.h"
+#include "canvas_impl.h"
+#include "simpleline.h"
+#include "utils_videotl.h"
+#include "rgb_macros.h"
+#include "video_timeline.h"
+
+#include <gtkmm2ext/utils.h>
+#include <pthread.h>
+#include <curl/curl.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace PBD;
+using namespace Timecode;
+
+
+VideoTimeLine::VideoTimeLine (PublicEditor *ed, ArdourCanvas::Group *vbg, int initial_height)
+ : editor (ed)
+ , videotl_bar_group(vbg)
+ , bar_height(initial_height)
+{
+ video_start_offset = 0L;
+ video_offset = 0L;
+ video_offset_p = 0L;
+ video_duration = 0L;
+ auto_set_session_fps = false;
+ video_offset_lock = false;
+ video_aspect_ratio = 4.0/3.0;
+ open_video_monitor_dialog = 0;
+ Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&VideoTimeLine::parameter_changed, this, _1), gui_context());
+ video_server_url = Config->get_video_server_url();
+ server_docroot = Config->get_video_server_docroot();
+ video_filename = "";
+ local_file = true;
+ video_file_fps = 25.0;
+ flush_frames = false;
+ vmonitor=0;
+ reopen_vmonitor=false;
+ find_xjadeo();
+
+ VtlUpdate.connect (*this, invalidator (*this), boost::bind (&PublicEditor::queue_visual_videotimeline_update, editor), gui_context());
+ GuiUpdate.connect (*this, invalidator (*this), boost::bind (&VideoTimeLine::gui_update, this, _1), gui_context());
+}
+
+VideoTimeLine::~VideoTimeLine ()
+{
+ close_session();
+}
+
+/* close and save settings */
+void
+VideoTimeLine::save_session ()
+{
+ if (!_session) {
+ return;
+ }
+
+ LocaleGuard lg (X_("POSIX"));
+
+ bool is_dirty = false;
+
+ XMLNode* prev = _session->extra_xml (X_("Videomonitor"));
+
+ /* remember if vmonitor was open.. */
+ XMLNode* node = new XMLNode(X_("Videomonitor"));
+
+ node->add_property (X_("active"), (vmonitor && vmonitor->is_started())?"yes":"no");
+ if (!prev || !(prev->property (X_("active")) && prev->property (X_("active"))->value() == node->property(X_("active"))->value()) ){
+ _session->add_extra_xml (*node);
+ is_dirty=true; // TODO not if !prev && value==default
+ }
+
+ /* VTL settings */
+ node = _session->extra_xml (X_("Videotimeline"));
+
+ if (node) {
+ if (!(node->property(X_("id")) && node->property(X_("id"))->value() == id().to_s())) {
+ node->add_property (X_("id"), id().to_s());
+ is_dirty=true;
+ }
+ }
+
+ /* remember timeline height.. */
+ if (node) {
+ int h = editor->get_videotl_bar_height();
+ if (!(node->property(X_("Height")) && atoi(node->property(X_("Height"))->value().c_str())==h)) {
+ node->add_property (X_("Height"), h);
+ is_dirty=true;
+ }
+ }
+
+ /* save video-offset-lock */
+ if (node) {
+ if (!(node->property(X_("VideoOffsetLock")) && atoi(node->property(X_("VideoOffsetLock"))->value().c_str())==video_offset_lock)) {
+ node->add_property (X_("VideoOffsetLock"), video_offset_lock?X_("1"):X_("0"));
+ is_dirty=true;
+ }
+ }
+ /* save video-offset */
+ if (node) {
+ if (!(node->property(X_("VideoOffset")) && atoll(node->property(X_("VideoOffset"))->value().c_str())==video_offset)) {
+ node->add_property (X_("VideoOffset"), video_offset);
+ is_dirty=true;
+ }
+ }
+
+ /* save 'auto_set_session_fps' */
+ if (node) {
+ if (!(node->property(X_("AutoFPS")) && atoi(node->property(X_("AutoFPS"))->value().c_str())==auto_set_session_fps)) {
+ node->add_property (X_("AutoFPS"), auto_set_session_fps?X_("1"):X_("0"));
+ is_dirty=true;
+ }
+ }
+ if (is_dirty) {
+ _session->set_dirty ();
+ }
+}
+
+/* close and save settings */
+void
+VideoTimeLine::close_session ()
+{
+ close_video_monitor();
+ save_session();
+
+ remove_frames();
+ video_filename = "";
+ video_duration = 0L;
+}
+
+/** load settings from session */
+void
+VideoTimeLine::set_session (ARDOUR::Session *s)
+{
+ SessionHandlePtr::set_session (s);
+ if (!_session) { return ; }
+
+ LocaleGuard lg (X_("POSIX"));
+
+ XMLNode* node = _session->extra_xml (X_("Videotimeline"));
+ if (node) {
+ ARDOUR_UI::instance()->start_video_server((Gtk::Window*)0, false);
+
+ set_id(*node);
+
+ const XMLProperty* proph = node->property (X_("Height"));
+ if (proph) {
+ editor->set_video_timeline_height(atoi(proph->value().c_str()));
+ }
+#if 0 /* TODO THINK: set FPS first time only ?! */
+ const XMLProperty* propasfps = node->property (X_("AutoFPS"));
+ if (propasfps) {
+ auto_set_session_fps = atoi(propasfps->value().c_str())?true:false;
+ }
+#endif
+
+ const XMLProperty* propoffset = node->property (X_("VideoOffset"));
+ if (propoffset) {
+ video_offset = atoll(propoffset->value().c_str());
+ video_offset_p = video_offset;
+ }
+
+ const XMLProperty* proplock = node->property (X_("VideoOffsetLock"));
+ if (proplock) {
+ video_offset_lock = atoi(proplock->value().c_str())?true:false;
+ }
+
+ const XMLProperty* localfile = node->property (X_("LocalFile"));
+ if (localfile) {
+ local_file = atoi(localfile->value().c_str())?true:false;
+ }
+
+ const XMLProperty* propf = node->property (X_("Filename"));
+ video_file_info(propf->value(), local_file);
+ }
+
+ node = _session->extra_xml (X_("Videomonitor"));
+ if (node) {
+ const XMLProperty* prop = node->property (X_("active"));
+ if (prop->value() == "yes" && found_xjadeo() && !video_filename.empty() && local_file) {
+ open_video_monitor(false);
+ }
+ }
+
+ _session->register_with_memento_command_factory(id(), this);
+ _session->config.ParameterChanged.connect (*this, invalidator (*this), ui_bind (&VideoTimeLine::parameter_changed, this, _1), gui_context());
+}
+
+void
+VideoTimeLine::save_undo ()
+{
+ video_offset_p = video_offset;
+}
+
+int
+VideoTimeLine::set_state (const XMLNode& node, int /*version*/)
+{
+ LocaleGuard lg (X_("POSIX"));
+ const XMLProperty* propoffset = node.property (X_("VideoOffset"));
+ if (propoffset) {
+ video_offset = atoll(propoffset->value().c_str());
+ }
+ ARDOUR_UI::instance()->flush_videotimeline_cache(true);
+ return 0;
+}
+
+XMLNode&
+VideoTimeLine::get_state ()
+{
+ XMLNode* node = new XMLNode (X_("Videotimeline"));
+ LocaleGuard lg (X_("POSIX"));
+ node->add_property (X_("VideoOffset"), video_offset_p);
+ return *node;
+}
+
+void
+VideoTimeLine::remove_frames ()
+{
+ for (VideoFrames::iterator i = video_frames.begin(); i != video_frames.end(); ++i ) {
+ VideoImageFrame *frame = (*i);
+ delete frame;
+ (*i) = 0;
+ }
+ video_frames.clear();
+}
+
+VideoImageFrame *
+VideoTimeLine::get_video_frame (framepos_t vfn, int cut, int rightend)
+{
+ if (vfn==0) cut=0;
+ for (VideoFrames::iterator i = video_frames.begin(); i != video_frames.end(); ++i) {
+ VideoImageFrame *frame = (*i);
+ if (abs(frame->get_video_frame_number()-vfn)<=cut
+ && frame->get_rightend() == rightend) { return frame; }
+ }
+ return 0;
+}
+
+float
+VideoTimeLine::get_apv()
+{
+ // XXX: dup code - TODO use this fn in update_video_timeline()
+ float apv = -1; /* audio frames per video frame; */
+ if (!_session) return apv;
+
+ if (_session->config.get_use_video_file_fps()) {
+ if (video_file_fps == 0 ) return apv;
+ } else {
+ if (_session->timecode_frames_per_second() == 0 ) return apv;
+ }
+
+ if (_session->config.get_videotimeline_pullup()) {
+ apv = _session->frame_rate();
+ } else {
+ apv = _session->nominal_frame_rate();
+ }
+ if (_session->config.get_use_video_file_fps()) {
+ apv /= video_file_fps;
+ } else {
+ apv /= _session->timecode_frames_per_second();
+ }
+ return apv;
+}
+
+void
+VideoTimeLine::update_video_timeline()
+{
+ if (!_session) return;
+
+ if (_session->config.get_use_video_file_fps()) {
+ if (video_file_fps == 0 ) return;
+ } else {
+ if (_session->timecode_frames_per_second() == 0 ) return;
+ }
+
+ double frames_per_unit = editor->unit_to_frame(1.0);
+ framepos_t leftmost_frame = editor->leftmost_position();
+
+ /* Outline:
+ * 1) calculate how many frames there should be in current zoom (plus 1 page on each side)
+ * 2) calculate first frame and distance between video-frames (according to zoom)
+ * 3) destroy/add frames
+ * 4) reposition existing frames
+ * 5) assign framenumber to frames -> request/decode video.
+ */
+
+ /* video-file and session properties */
+ double display_vframe_width; /* unit: pixels ; width of one thumbnail in the timeline */
+ float apv; /* audio frames per video frame; */
+ framepos_t leftmost_video_frame; /* unit: video-frame number ; temporary var -> vtl_start */
+
+ /* variables needed to render videotimeline -- what needs to computed first */
+ framepos_t vtl_start; /* unit: audio-frames ; first displayed video-frame */
+ framepos_t vtl_dist; /* unit: audio-frames ; distance between displayed video-frames */
+ unsigned int visible_video_frames; /* number of frames that fit on current canvas */
+
+ if (_session->config.get_videotimeline_pullup()) {
+ apv = _session->frame_rate();
+ } else {
+ apv = _session->nominal_frame_rate();
+ }
+ if (_session->config.get_use_video_file_fps()) {
+ apv /= video_file_fps;
+ } else {
+ apv /= _session->timecode_frames_per_second();
+ }
+
+ display_vframe_width = bar_height * video_aspect_ratio;
+
+ if (apv > frames_per_unit * display_vframe_width) {
+ /* high-zoom: need space between successive video-frames */
+ vtl_dist = rint(apv);
+ } else {
+ /* continous timeline: skip video-frames */
+ vtl_dist = ceil(display_vframe_width * frames_per_unit / apv) * apv;
+ }
+
+ assert (vtl_dist > 0);
+ assert (apv > 0);
+
+#define GOFFSET (video_offset)
+
+ leftmost_video_frame = floor (floor((leftmost_frame - video_start_offset - GOFFSET ) / vtl_dist) * vtl_dist / apv);
+
+ vtl_start = rint (GOFFSET + video_start_offset + leftmost_video_frame * apv);
+ visible_video_frames = 2 + ceil(editor->current_page_frames() / vtl_dist); /* +2 left+right partial frames */
+
+ /* expand timeline (cache next/prev page images) */
+ vtl_start -= visible_video_frames * vtl_dist;
+ visible_video_frames *=3;
+
+ if (vtl_start < GOFFSET ) {
+ visible_video_frames += ceil(vtl_start/vtl_dist);
+ vtl_start = GOFFSET;
+ }
+
+ /* apply video-file constraints */
+ if (vtl_start > video_start_offset + video_duration + GOFFSET ) {
+ visible_video_frames = 0;
+ }
+ /* TODO optimize: compute rather than iterate */
+ while (visible_video_frames > 0 && vtl_start + (visible_video_frames-1) * vtl_dist >= video_start_offset + video_duration + GOFFSET) {
+ --visible_video_frames;
+ }
+
+ if (flush_frames) {
+ remove_frames();
+ flush_frames=false;
+ }
+
+ while (video_frames.size() < visible_video_frames) {
+ VideoImageFrame *frame;
+ frame = new VideoImageFrame(*editor, *videotl_bar_group, display_vframe_width, bar_height, video_server_url, translated_filename());
+ frame->ImgChanged.connect (*this, invalidator (*this), boost::bind (&PublicEditor::queue_visual_videotimeline_update, editor), gui_context());
+ video_frames.push_back(frame);
+ }
+
+ VideoFrames outdated_video_frames;
+ std::list<int> remaining;
+
+ outdated_video_frames = video_frames;
+
+#if 1
+ /* when zoomed out, ignore shifts by +-1 frame
+ * which can occur due to rounding errors when
+ * scrolling to a new leftmost-audio frame.
+ */
+ int cut =1;
+ if (vtl_dist/apv < 3.0) cut =0;
+#else
+ int cut =0;
+#endif
+
+ for (unsigned int vfcount=0; vfcount < visible_video_frames; ++vfcount){
+ framepos_t vfpos = vtl_start + vfcount * vtl_dist; /* unit: audio-frames */
+ framepos_t vframeno = rint ( (vfpos - GOFFSET) / apv); /* unit: video-frames */
+ vfpos = (vframeno * apv ) + GOFFSET; /* audio-frame corresponding to /rounded/ video-frame */
+
+ int rightend = -1; /* unit: pixels */
+ if (vfpos + vtl_dist > video_start_offset + video_duration + GOFFSET) {
+ rightend = display_vframe_width * (video_start_offset + video_duration + GOFFSET - vfpos) / vtl_dist;
+ //printf("lf(e): %lu\n", vframeno); // XXX
+ }
+ VideoImageFrame * frame = get_video_frame(vframeno, cut, rightend);
+ if (frame) {
+ frame->set_position(vfpos-leftmost_frame);
+ outdated_video_frames.remove(frame);
+ } else {
+ remaining.push_back(vfcount);
+ }
+ }
+
+ for (VideoFrames::iterator i = outdated_video_frames.begin(); i != outdated_video_frames.end(); ++i ) {
+ VideoImageFrame *frame = (*i);
+ if (remaining.empty()) {
+ frame->set_position(-2 * vtl_dist); /* move off screen */
+ } else {
+ int vfcount=remaining.front();
+ remaining.pop_front();
+ framepos_t vfpos = vtl_start + vfcount * vtl_dist; /* unit: audio-frames */
+ framepos_t vframeno = rint ((vfpos - GOFFSET) / apv); /* unit: video-frames */
+ int rightend = -1; /* unit: pixels */
+ if (vfpos + vtl_dist > video_start_offset + video_duration + GOFFSET) {
+ rightend = display_vframe_width * (video_start_offset + video_duration + GOFFSET - vfpos) / vtl_dist;
+ //printf("lf(n): %lu\n", vframeno); // XXX
+ }
+ frame->set_position(vfpos-leftmost_frame);
+ frame->set_videoframe(vframeno, rightend);
+ }
+ }
+}
+
+std::string
+VideoTimeLine::translated_filename ()
+{
+ if (!local_file){
+ return video_filename;
+ } else {
+ return video_map_path(server_docroot, video_filename);
+ }
+}
+
+bool
+VideoTimeLine::video_file_info (std::string filename, bool local)
+{
+ local_file = local;
+ if (filename.at(0) == G_DIR_SEPARATOR || !local_file) {
+ video_filename = filename;
+ } else {
+ video_filename = Glib::build_filename (_session->session_directory().video_path(), filename);
+ }
+
+ long long int _duration;
+ double _start_offset;
+
+ if (!video_query_info(
+ video_server_url, translated_filename(),
+ video_file_fps, _duration, _start_offset, video_aspect_ratio)) {
+ warning << _("Parsing video file info failed. Is the Video Server running? Is the file readable by the Video Server? Does the docroot match? Is it a video file?") << endmsg;
+ return false;
+ }
+ video_duration = _duration * _session->nominal_frame_rate() / video_file_fps;
+ video_start_offset = _start_offset * _session->nominal_frame_rate();
+
+ if (auto_set_session_fps && video_file_fps != _session->timecode_frames_per_second()) {
+ switch ((int)floorf(video_file_fps*1000.0)) {
+ case 23976:
+ _session->config.set_timecode_format(timecode_23976);
+ break;
+ case 24000:
+ _session->config.set_timecode_format(timecode_24);
+ break;
+ case 24975:
+ case 24976:
+ _session->config.set_timecode_format(timecode_24976);
+ break;
+ case 25000:
+ _session->config.set_timecode_format(timecode_25);
+ break;
+ case 29970:
+ _session->config.set_timecode_format(timecode_2997drop);
+ break;
+ case 30000:
+ _session->config.set_timecode_format(timecode_30);
+ break;
+ case 59940:
+ _session->config.set_timecode_format(timecode_5994);
+ break;
+ case 60000:
+ _session->config.set_timecode_format(timecode_60);
+ break;
+ default:
+ warning << _("Failed to set session-framerate: ") << video_file_fps << _(" does not have a corresponding option setting in Ardour.") << endmsg; /* TODO: gettext arg */
+ break;
+ }
+ _session->config.set_video_pullup(0); /* TODO only set if set_timecode_format() was successful ?!*/
+ }
+ if (video_file_fps != _session->timecode_frames_per_second()) {
+ warning << _("Video file's framerate is not equal to Ardour session timecode's framerate: ")
+ << video_file_fps << _(" vs ") << _session->timecode_frames_per_second() << endmsg;
+ }
+ flush_local_cache ();
+
+ if (found_xjadeo() && local_file) {
+ GuiUpdate("set-xjadeo-sensitive-on");
+ if (vmonitor && vmonitor->is_started()) {
+ vmonitor->set_fps(video_file_fps);
+ vmonitor->open(video_filename);
+ }
+ } else if (!local_file) {
+ GuiUpdate("set-xjadeo-sensitive-off");
+ }
+ VtlUpdate();
+ return true;
+}
+
+bool
+VideoTimeLine::check_server ()
+{
+ bool ok = false;
+ char url[1024];
+ snprintf(url, sizeof(url), "%s%sstatus"
+ , video_server_url.c_str()
+ , (video_server_url.length()>0 && video_server_url.at(video_server_url.length()-1) == '/')?"":"/"
+ );
+ char *res=curl_http_get(url, NULL);
+ if (res) {
+ if (strstr(res, "status: ok, online.")) { ok = true; }
+ free(res);
+ }
+ return ok;
+}
+
+void
+VideoTimeLine::gui_update(std::string const & t) {
+ /* this is to be called via GuiUpdate() only. */
+ ENSURE_GUI_THREAD (*this, &VideoTimeLine::queue_visual_videotimeline_update)
+ if (t == "videotimeline-update") {
+ editor->queue_visual_videotimeline_update();
+ } else if (t == "set-xjadeo-active-off") {
+ editor->toggle_xjadeo_proc(0);
+ } else if (t == "set-xjadeo-active-on") {
+ editor->toggle_xjadeo_proc(1);
+ } else if (t == "set-xjadeo-sensitive-on") {
+ editor->set_xjadeo_sensitive(true);
+ } else if (t == "set-xjadeo-sensitive-off") {
+ editor->toggle_xjadeo_proc(0);
+ //close_video_monitor();
+ editor->set_xjadeo_sensitive(false);
+ }
+}
+
+void
+VideoTimeLine::set_height (int height) {
+ bar_height = height;
+ flush_local_cache();
+}
+
+void
+VideoTimeLine::vmon_update () {
+ if (vmonitor && vmonitor->is_started()) {
+ vmonitor->set_offset( GOFFSET); // TODO proper re-init xjadeo w/o restart not just offset.
+ }
+}
+
+void
+VideoTimeLine::flush_local_cache () {
+ flush_frames = true;
+ vmon_update();
+}
+
+void
+VideoTimeLine::flush_cache () {
+ flush_local_cache();
+ char url[1024];
+ snprintf(url, sizeof(url), "%s%sadmin/flush_cache"
+ , video_server_url.c_str()
+ , (video_server_url.length()>0 && video_server_url.at(video_server_url.length()-1) == '/')?"":"/"
+ );
+ char *res=curl_http_get(url, NULL);
+ if (res) {
+ free (res);
+ }
+ if (vmonitor && vmonitor->is_started()) {
+ reopen_vmonitor=true;
+ vmonitor->quit();
+ }
+ video_file_info(video_filename, local_file);
+}
+
+/* config */
+void
+VideoTimeLine::parameter_changed (std::string const & p)
+{
+ if (p == "video-server-url") {
+ set_video_server_url (Config->get_video_server_url ());
+ } else if (p == "video-server-docroot") {
+ set_video_server_docroot (Config->get_video_server_docroot ());
+ }
+ if (p == "use-video-file-fps" || p == "videotimeline-pullup" ) { /* session->config parameter */
+ VtlUpdate();
+ }
+}
+
+void
+VideoTimeLine::set_video_server_url(std::string vsu) {
+ flush_local_cache ();
+ video_server_url = vsu;
+ VtlUpdate();
+}
+
+void
+VideoTimeLine::set_video_server_docroot(std::string vsr) {
+ flush_local_cache ();
+ server_docroot = vsr;
+ VtlUpdate();
+}
+
+/* video-monitor for this timeline */
+void
+VideoTimeLine::find_xjadeo () {
+ std::string xjadeo_file_path;
+ if (getenv("XJREMOTE")) {
+ _xjadeo_bin = strdup(getenv("XJREMOTE")); // XXX TODO: free it?!
+ } else if (find_file_in_search_path (SearchPath(Glib::getenv("PATH")), X_("xjremote"), xjadeo_file_path)) {
+ _xjadeo_bin = xjadeo_file_path;
+ }
+ else if (Glib::file_test(X_("/Applications/Jadeo.app/Contents/MacOS/xjremote"), Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_EXECUTABLE)) {
+ _xjadeo_bin = X_("/Applications/Jadeo.app/Contents/MacOS/xjremote");
+ }
+ /* TODO: win32: allow to configure PATH to xjremote */
+ else if (Glib::file_test(X_("C:\\Program Files\\xjadeo\\xjremote.exe"), Glib::FILE_TEST_EXISTS)) {
+ _xjadeo_bin = X_("C:\\Program Files\\xjadeo\\xjremote.exe");
+ }
+ else if (Glib::file_test(X_("C:\\Program Files\\xjadeo\\xjremote.bat"), Glib::FILE_TEST_EXISTS)) {
+ _xjadeo_bin = X_("C:\\Program Files\\xjadeo\\xjremote.bat");
+ }
+ else {
+ _xjadeo_bin = X_("");
+ }
+}
+
+void
+VideoTimeLine::open_video_monitor(bool interactive) {
+ if (!found_xjadeo()) return;
+ if (!vmonitor) {
+ vmonitor = new VideoMonitor(editor, _xjadeo_bin);
+ vmonitor->set_session(_session);
+ vmonitor->Terminated.connect (sigc::mem_fun (*this, &VideoTimeLine::terminated_video_monitor));
+ } else if (vmonitor->is_started()) {
+ return;
+ }
+
+ int xj_settings_mask = vmonitor->restore_settings_mask();
+ if (_session) {
+ /* load mask from Session */
+ XMLNode* node = _session->extra_xml (X_("XJRestoreSettings"));
+ if (node) {
+ const XMLProperty* prop = node->property (X_("mask"));
+ if (prop) {
+ xj_settings_mask = atoi(prop->value().c_str());
+ }
+ }
+ }
+
+ if (interactive && Config->get_video_monitor_setup_dialog()) {
+ if (open_video_monitor_dialog == 0) {
+ open_video_monitor_dialog = new OpenVideoMonitorDialog(_session);
+ }
+ if (open_video_monitor_dialog->is_visible()) {
+ return;
+ }
+ open_video_monitor_dialog->setup_settings_mask(xj_settings_mask);
+ open_video_monitor_dialog->set_filename(video_filename);
+ Gtk::ResponseType r = (Gtk::ResponseType) open_video_monitor_dialog->run ();
+ open_video_monitor_dialog->hide();
+ if (r != Gtk::RESPONSE_ACCEPT) {
+ GuiUpdate("set-xjadeo-active-off");
+ return;
+ }
+
+ if (_session && (xj_settings_mask != open_video_monitor_dialog->xj_settings_mask()) ) {
+ /* save mask to Session */
+ XMLNode* node = new XMLNode(X_("XJRestoreSettings"));
+ node->add_property (X_("mask"), (const long) open_video_monitor_dialog->xj_settings_mask() );
+ _session->add_extra_xml (*node);
+ _session->set_dirty ();
+ }
+
+ if (open_video_monitor_dialog->show_again()) {
+ Config->set_video_monitor_setup_dialog(false);
+ }
+#if 1
+ vmonitor->set_debug(open_video_monitor_dialog->enable_debug());
+#endif
+ vmonitor->restore_settings_mask(open_video_monitor_dialog->xj_settings_mask());
+ } else {
+ vmonitor->restore_settings_mask(xj_settings_mask);
+ }
+
+
+ if (!vmonitor->start()) {
+ warning << "launching xjadeo failed.." << endmsg;
+ close_video_monitor();
+ } else {
+ GuiUpdate("set-xjadeo-active-on");
+ vmonitor->set_fps(video_file_fps);
+ vmonitor->open(video_filename);
+ }
+}
+
+void
+VideoTimeLine::close_video_monitor() {
+ if (vmonitor && vmonitor->is_started()) {
+ vmonitor->quit();
+ }
+}
+
+void
+VideoTimeLine::terminated_video_monitor () {
+ if (vmonitor) {
+ delete vmonitor;
+ }
+ GuiUpdate("set-xjadeo-active-off");
+ vmonitor=0;
+ if (reopen_vmonitor) {
+ reopen_vmonitor=false;
+ open_video_monitor(false);
+ }
+}
+
+/*
+void
+VideoTimeLine::clear_video_monitor_session_state ()
+{
+ if (vmonitor) {
+ vmonitor->clear_session_state();
+ } else {
+ if (!_session) { return; }
+ XMLNode* node = new XMLNode(X_("XJSettings"));
+ _session->add_extra_xml (*node);
+ _session->set_dirty ();
+ }
+}
+*/
+
+void
+VideoTimeLine::manual_seek_video_monitor (framepos_t pos)
+{
+ if (!vmonitor) { return; }
+ if (!vmonitor->is_started()) { return; }
+ if (!vmonitor->synced_by_manual_seeks()) { return; }
+ vmonitor->manual_seek(pos, false, GOFFSET); // XXX -> set offset in xjadeo
+}
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/video_timeline.h b/gtk2_ardour/video_timeline.h
new file mode 100644
index 0000000000..d352af9faf
--- /dev/null
+++ b/gtk2_ardour/video_timeline.h
@@ -0,0 +1,145 @@
+/*
+ Copyright (C) 2010 Paul Davis
+ Author: Robin Gareus <robin@gareus.org>
+
+ 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.
+
+*/
+#ifdef WITH_VIDEOTIMELINE
+
+#ifndef __ardour_video_timeline_h__
+#define __ardour_video_timeline_h__
+
+#include <string>
+
+#include <sigc++/signal.h>
+#include "ardour/ardour.h"
+#include "ardour/session.h"
+#include "ardour/session_handle.h"
+#include "video_image_frame.h"
+#include "open_video_monitor_dialog.h"
+#include "video_monitor.h"
+#include "pbd/signals.h"
+#include "canvas.h"
+
+namespace ARDOUR {
+ class Session;
+}
+
+class PublicEditor;
+
+/** @class VideoTimeLine
+ * @brief video-timline controller and display
+ *
+ * The video-timeline can be displayed in a canvas-group. Given a filename
+ * it queries the video-server about file-information and
+ * creates \ref VideoImageFrame as neccesary (which
+ * query the server for image-data).
+ *
+ * This class contains the algorithm to position the single frames
+ * on the timeline according to current-zoom level and video-file
+ * attributes. see \ref update_video_timeline()
+ *
+ * The VideoTimeLine class includes functionality to launch a video-monitor
+ * corresponding to its currently diplayed file.
+ */
+class VideoTimeLine : public sigc::trackable, public ARDOUR::SessionHandlePtr, public PBD::ScopedConnectionList, public PBD::StatefulDestructible
+{
+ public:
+ VideoTimeLine (PublicEditor*, ArdourCanvas::Group*, int);
+ virtual ~VideoTimeLine ();
+
+ void set_session (ARDOUR::Session *s);
+ void update_video_timeline ();
+ void set_height (int);
+
+ void save_undo (void);
+ XMLNode& get_state ();
+ int set_state (const XMLNode&, int version);
+
+ bool video_file_info (std::string, bool);
+ double get_video_file_fps () { return video_file_fps; }
+ void set_update_session_fps (bool v=true) { auto_set_session_fps = v; }
+
+ void set_offset_locked (bool v) { video_offset_lock = v; }
+ void toggle_offset_locked () { video_offset_lock = !video_offset_lock; }
+ bool is_offset_locked () { return video_offset_lock; }
+
+ void open_video_monitor (bool interactive=true);
+ void close_video_monitor ();
+ void terminated_video_monitor ();
+ void manual_seek_video_monitor (framepos_t pos);
+
+ void parameter_changed (std::string const & p);
+ void set_video_server_url (std::string);
+ void set_video_server_docroot (std::string);
+
+ bool found_xjadeo () { return ((_xjadeo_bin.empty())?false:true); }
+ bool check_server ();
+ void flush_local_cache ();
+ void vmon_update ();
+ void flush_cache ();
+ void save_session ();
+ void close_session ();
+ float get_apv(); /* audio frames per video frame; */
+ ARDOUR::framecnt_t get_duration () { return video_duration;}
+ ARDOUR::frameoffset_t get_offset () { return video_offset;}
+ ARDOUR::frameoffset_t quantify_frames_to_apv (ARDOUR::frameoffset_t offset) { return floor(offset/get_apv())*get_apv(); }
+ void set_offset (ARDOUR::frameoffset_t offset) { video_offset = quantify_frames_to_apv(offset); } // this function does not update video_offset_p, call save_undo() to finalize changes to this! - this fn is currently only used from editor_drag.cc
+
+ protected:
+
+ PublicEditor *editor;
+ ArdourCanvas::Group *videotl_bar_group;
+ int bar_height;
+
+ std::string _xjadeo_bin;
+ void find_xjadeo ();
+
+
+ ARDOUR::frameoffset_t video_start_offset; /**< unit: audio-frames - video-file */
+ ARDOUR::frameoffset_t video_offset; /**< unit: audio-frames - session */
+ ARDOUR::frameoffset_t video_offset_p; /**< used for undo from editor_drag.cc */
+ framepos_t video_duration; /**< unit: audio-frames */
+ std::string video_filename;
+ bool local_file;
+ double video_aspect_ratio;
+ double video_file_fps;
+ bool auto_set_session_fps;
+ bool video_offset_lock;
+
+ std::string video_server_url;
+ std::string server_docroot;
+
+ typedef std::list<VideoImageFrame*> VideoFrames;
+ VideoFrames video_frames;
+ VideoImageFrame *get_video_frame (framepos_t vfn, int cut=0, int rightend = -1);
+ bool flush_frames;
+ void remove_frames ();
+
+ std::string translated_filename ();
+
+ VideoMonitor *vmonitor;
+ OpenVideoMonitorDialog *open_video_monitor_dialog;
+ bool reopen_vmonitor;
+
+ PBD::Signal0<void> VtlUpdate;
+ PBD::Signal1<void,std::string> GuiUpdate;
+ void gui_update (const std::string &);
+};
+
+#endif /* __ardour_video_timeline_h__ */
+
+#endif /* WITH_VIDEOTIMELINE */
diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript
index 8b6b49f8b0..3705fb3ac7 100644
--- a/gtk2_ardour/wscript
+++ b/gtk2_ardour/wscript
@@ -417,6 +417,24 @@ def build(bld):
]
obj.includes += ['../libs']
+ if bld.env['VIDEOTIMELINE']:
+ obj.source += [
+ 'video_image_frame.cc',
+ 'add_video_dialog.cc',
+ 'editor_videotimeline.cc',
+ 'video_timeline.cc',
+ 'system_exec.cc',
+ 'video_monitor.cc',
+ 'open_video_monitor_dialog.cc',
+ 'transcode_ffmpeg.cc',
+ 'transcode_video_dialog.cc',
+ 'video_server_dialog.cc',
+ 'video_copy_dialog.cc',
+ 'utils_videotl.cc',
+ 'export_video_dialog.cc',
+ 'export_video_infobox.cc'
+ ]
+
if bld.is_defined('HAVE_SUIL'):
obj.source += [ 'lv2_plugin_ui.cc' ]
obj.use += [ 'SUIL' ]
@@ -636,6 +654,9 @@ def build(bld):
menus_argv = [ '-E', '-P', '-DGTKOSX' ]
else:
menus_argv = [ '-E', '-P' ]
+
+ if bld.env['VIDEOTIMELINE']:
+ menus_argv.append ('-DWITH_VIDEOTIMELINE')
obj = bld(features = 'command-output')
obj.command = 'cpp'
obj.command_is_external = True
diff --git a/libs/ardour/ardour/directory_names.h b/libs/ardour/ardour/directory_names.h
index e91ece266a..1f9c48eba3 100644
--- a/libs/ardour/ardour/directory_names.h
+++ b/libs/ardour/ardour/directory_names.h
@@ -27,6 +27,9 @@ extern const char* const old_sound_dir_name;
extern const char* const sound_dir_name;
extern const char* const midi_dir_name;
extern const char* const midi_patch_dir_name;
+#ifdef WITH_VIDEOTIMELINE
+extern const char* const video_dir_name;
+#endif
extern const char* const dead_dir_name;
extern const char* const interchange_dir_name;
extern const char* const peak_dir_name;
diff --git a/libs/ardour/ardour/rc_configuration_vars.h b/libs/ardour/ardour/rc_configuration_vars.h
index 711e6fb1af..2dc13a3660 100644
--- a/libs/ardour/ardour/rc_configuration_vars.h
+++ b/libs/ardour/ardour/rc_configuration_vars.h
@@ -214,3 +214,11 @@ CONFIG_VARIABLE (std::string, linux_pingback_url, "linux-pingback-url", "http://
CONFIG_VARIABLE (std::string, tutorial_manual_url, "tutorial-manual-url", "http://ardour.org/flossmanual")
CONFIG_VARIABLE (std::string, reference_manual_url, "reference-manual-url", "http://manual.ardour.org/")
CONFIG_VARIABLE (std::string, updates_url, "updates-url", "http://ardour.org/whatsnew.html")
+
+#ifdef WITH_VIDEOTIMELINE
+CONFIG_VARIABLE (std::string, video_server_url, "video-server-url", "http://localhost:1554")
+CONFIG_VARIABLE (std::string, video_server_docroot, "video-server-docroot", "/")
+CONFIG_VARIABLE (bool, video_monitor_setup_dialog, "video-monitor-setup-dialog", false)
+CONFIG_VARIABLE (bool, show_video_export_info, "show-video-export-info", true)
+CONFIG_VARIABLE (bool, show_video_server_dialog, "show-video-server-dialog", true)
+#endif
diff --git a/libs/ardour/ardour/region.h b/libs/ardour/ardour/region.h
index bc8e7383f2..eaa23cc8f6 100644
--- a/libs/ardour/ardour/region.h
+++ b/libs/ardour/ardour/region.h
@@ -45,6 +45,9 @@ namespace Properties {
extern PBD::PropertyDescriptor<bool> muted;
extern PBD::PropertyDescriptor<bool> opaque;
extern PBD::PropertyDescriptor<bool> locked;
+#ifdef WITH_VIDEOTIMELINE
+ extern PBD::PropertyDescriptor<bool> video_locked;
+#endif
extern PBD::PropertyDescriptor<bool> automatic;
extern PBD::PropertyDescriptor<bool> whole_file;
extern PBD::PropertyDescriptor<bool> import;
@@ -163,6 +166,9 @@ class Region
bool opaque () const { return _opaque; }
bool locked () const { return _locked; }
bool position_locked () const { return _position_locked; }
+#ifdef WITH_VIDEOTIMELINE
+ bool video_locked () const { return _video_locked; }
+#endif
bool valid_transients () const { return _valid_transients; }
bool automatic () const { return _automatic; }
bool whole_file () const { return _whole_file; }
@@ -240,6 +246,9 @@ class Region
void set_automatic (bool yn);
void set_opaque (bool yn);
void set_locked (bool yn);
+#ifdef WITH_VIDEOTIMELINE
+ void set_video_locked (bool yn);
+#endif
void set_position_locked (bool yn);
int apply (Filter &, Progress* progress = 0);
@@ -392,6 +401,9 @@ class Region
PBD::Property<bool> _muted;
PBD::Property<bool> _opaque;
PBD::Property<bool> _locked;
+#ifdef WITH_VIDEOTIMELINE
+ PBD::Property<bool> _video_locked;
+#endif
PBD::Property<bool> _automatic;
PBD::Property<bool> _whole_file;
PBD::Property<bool> _import;
diff --git a/libs/ardour/ardour/session_configuration_vars.h b/libs/ardour/ardour/session_configuration_vars.h
index 3e2d21d107..1f8b2356d1 100644
--- a/libs/ardour/ardour/session_configuration_vars.h
+++ b/libs/ardour/ardour/session_configuration_vars.h
@@ -55,3 +55,7 @@ CONFIG_VARIABLE (std::string, timecode_generator_offset, "timecode-generator-off
CONFIG_VARIABLE (bool, glue_new_markers_to_bars_and_beats, "glue-new-markers-to-bars-and-beats", false)
CONFIG_VARIABLE (bool, midi_copy_is_fork, "midi-copy-is-fork", false)
CONFIG_VARIABLE (bool, glue_new_regions_to_bars_and_beats, "glue-new-regions-to-bars-and-beats", false)
+#ifdef WITH_VIDEOTIMELINE
+CONFIG_VARIABLE (bool, use_video_file_fps, "use-video-file-fps", false)
+CONFIG_VARIABLE (bool, videotimeline_pullup, "videotimeline-pullup", true)
+#endif
diff --git a/libs/ardour/ardour/session_directory.h b/libs/ardour/ardour/session_directory.h
index 9f4410ce69..72bc731a01 100644
--- a/libs/ardour/ardour/session_directory.h
+++ b/libs/ardour/ardour/session_directory.h
@@ -85,6 +85,14 @@ public:
*/
const std::string peak_path () const;
+#ifdef WITH_VIDEOTIMELINE
+ /**
+ * @return The absolute path to the directory in which all
+ * video files are stored for a session.
+ */
+ const std::string video_path () const;
+#endif
+
/**
* @return The absolute path to the directory that source
* files are moved to when they are no longer part of the
diff --git a/libs/ardour/directory_names.cc b/libs/ardour/directory_names.cc
index 5ba01d83a4..b500fdc193 100644
--- a/libs/ardour/directory_names.cc
+++ b/libs/ardour/directory_names.cc
@@ -27,6 +27,9 @@ const char* const old_sound_dir_name = X_("sounds");
const char* const sound_dir_name = X_("audiofiles");
const char* const midi_dir_name = X_("midifiles");
const char* const midi_patch_dir_name = X_("patchfiles");
+#ifdef WITH_VIDEOTIMELINE
+const char* const video_dir_name = X_("videofiles");
+#endif
const char* const peak_dir_name = X_("peaks");
const char* const dead_dir_name = X_("dead");
const char* const interchange_dir_name = X_("interchange");
diff --git a/libs/ardour/region.cc b/libs/ardour/region.cc
index 4ccd3c77b0..cc57e774c8 100644
--- a/libs/ardour/region.cc
+++ b/libs/ardour/region.cc
@@ -49,6 +49,9 @@ namespace ARDOUR {
PBD::PropertyDescriptor<bool> muted;
PBD::PropertyDescriptor<bool> opaque;
PBD::PropertyDescriptor<bool> locked;
+#ifdef WITH_VIDEOTIMELINE
+ PBD::PropertyDescriptor<bool> video_locked;
+#endif
PBD::PropertyDescriptor<bool> automatic;
PBD::PropertyDescriptor<bool> whole_file;
PBD::PropertyDescriptor<bool> import;
@@ -84,6 +87,10 @@ Region::make_property_quarks ()
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for opaque = %1\n", Properties::opaque.property_id));
Properties::locked.property_id = g_quark_from_static_string (X_("locked"));
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for locked = %1\n", Properties::locked.property_id));
+#ifdef WITH_VIDEOTIMELINE
+ Properties::video_locked.property_id = g_quark_from_static_string (X_("video-locked"));
+ DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for video-locked = %1\n", Properties::video_locked.property_id));
+#endif
Properties::automatic.property_id = g_quark_from_static_string (X_("automatic"));
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for automatic = %1\n", Properties::automatic.property_id));
Properties::whole_file.property_id = g_quark_from_static_string (X_("whole-file"));
@@ -136,6 +143,9 @@ Region::register_properties ()
add_property (_muted);
add_property (_opaque);
add_property (_locked);
+#ifdef WITH_VIDEOTIMELINE
+ add_property (_video_locked);
+#endif
add_property (_automatic);
add_property (_whole_file);
add_property (_import);
@@ -157,6 +167,13 @@ Region::register_properties ()
add_property (_position_lock_style);
add_property (_layering_index);
}
+#ifdef WITH_VIDEOTIMELINE
+#define VTLSTATE , _video_locked (Properties::video_locked, false)
+#define VTLCSTATE , _video_locked (Properties::video_locked, other->_video_locked)
+#else
+#define VTLSTATE
+#define VTLCSTATE
+#endif
#define REGION_DEFAULT_STATE(s,l) \
_sync_marked (Properties::sync_marked, false) \
@@ -170,6 +187,7 @@ Region::register_properties ()
, _muted (Properties::muted, false) \
, _opaque (Properties::opaque, true) \
, _locked (Properties::locked, false) \
+ VTLSTATE \
, _automatic (Properties::automatic, false) \
, _whole_file (Properties::whole_file, false) \
, _import (Properties::import, false) \
@@ -195,6 +213,7 @@ Region::register_properties ()
, _muted (Properties::muted, other->_muted) \
, _opaque (Properties::opaque, other->_opaque) \
, _locked (Properties::locked, other->_locked) \
+ VTLCSTATE \
, _automatic (Properties::automatic, other->_automatic) \
, _whole_file (Properties::whole_file, other->_whole_file) \
, _import (Properties::import, other->_import) \
@@ -627,7 +646,11 @@ Region::recompute_position_from_lock_style ()
void
Region::nudge_position (frameoffset_t n)
{
- if (locked()) {
+ if (locked()
+#ifdef WITH_VIDEOTIMELINE
+ || video_locked()
+#endif
+ ) {
return;
}
@@ -668,7 +691,11 @@ Region::set_ancestral_data (framepos_t s, framecnt_t l, float st, float sh)
void
Region::set_start (framepos_t pos)
{
- if (locked() || position_locked()) {
+ if (locked() || position_locked()
+#ifdef WITH_VIDEOTIMELINE
+ || video_locked()
+#endif
+ ) {
return;
}
/* This just sets the start, nothing else. It effectively shifts
@@ -694,7 +721,11 @@ Region::set_start (framepos_t pos)
void
Region::trim_start (framepos_t new_position)
{
- if (locked() || position_locked()) {
+ if (locked() || position_locked()
+#ifdef WITH_VIDEOTIMELINE
+ || video_locked()
+#endif
+ ) {
return;
}
@@ -979,6 +1010,17 @@ Region::set_locked (bool yn)
}
}
+#ifdef WITH_VIDEOTIMELINE
+void
+Region::set_video_locked (bool yn)
+{
+ if (video_locked() != yn) {
+ _video_locked = yn;
+ send_change (Properties::video_locked);
+ }
+}
+#endif
+
void
Region::set_position_locked (bool yn)
{
diff --git a/libs/ardour/session_directory.cc b/libs/ardour/session_directory.cc
index 7ff9c6f5a7..61b313f6b3 100644
--- a/libs/ardour/session_directory.cc
+++ b/libs/ardour/session_directory.cc
@@ -145,6 +145,14 @@ SessionDirectory::midi_patch_path () const
return Glib::build_filename (sources_root(), midi_patch_dir_name);
}
+#ifdef WITH_VIDEOTIMELINE
+const std::string
+SessionDirectory::video_path () const
+{
+ return Glib::build_filename (sources_root(), video_dir_name);
+}
+#endif
+
const std::string
SessionDirectory::peak_path () const
{
diff --git a/tools/videotimeline/vsrv.php b/tools/videotimeline/vsrv.php
new file mode 100644
index 0000000000..2a6830c1b0
--- /dev/null
+++ b/tools/videotimeline/vsrv.php
@@ -0,0 +1,113 @@
+<?php
+$exe_ffmpeg='ffmpeg';
+$exe_ffprobe='ffprobe';
+$exe_imagemagick='convert';
+$docroot='/'; # must be identical to ardour3->Edit->Preferences->Video->Docroot
+
+$mode = '';
+if (isset($_SERVER['PATH_INFO'])) {
+ switch($_SERVER['PATH_INFO']) {
+ case '/status/':
+ case '/status':
+ echo 'status: ok, online.';
+ exit;
+ break;
+ case '/info/':
+ case '/info':
+ $mode='info';
+ break;
+ default:
+ break;
+ }
+}
+
+$infile='/tmp/test.avi';
+$w=80; $h=60; $f=0;
+$fr=0; $df=0;
+$so=0; $ar=4.0/3.0;
+$fmt='rgb';
+
+if (isset($_REQUEST['format'])) {
+ switch ($_REQUEST['format']) {
+ case 'jpeg':
+ case 'jpg':
+ $fmt='jpeg';
+ break;
+ case 'png':
+ $fmt='png';
+ break;
+ case 'rgba':
+ $fmt='rgba';
+ break;
+ case 'rgb':
+ $fmt='rgb';
+ break;
+ default:
+ break;
+ }
+}
+
+if (isset($_REQUEST['w']))
+ $w=intval(rawurldecode($_REQUEST['w']));
+if (isset($_REQUEST['h']))
+ $h=intval(rawurldecode($_REQUEST['h']));
+if (isset($_REQUEST['frame']))
+ $f=intval(rawurldecode($_REQUEST['frame']));
+if (isset($_REQUEST['file']))
+ $infile=rawurldecode($_REQUEST['file']);
+
+if (!is_readable($docroot.$infile)) {
+ header('HTTP/1.0 404 Not Found', true, 404);
+ exit;
+}
+
+$fn=escapeshellarg($docroot.$infile);
+
+#$fr=`$exe_ffprobe $fn 2>&1 | awk '/Video:/{printf "%f\\n", $11}'`;
+$nfo=shell_exec("$exe_ffprobe $fn 2>&1");
+if (preg_match('@Video:.* ([0-9.]+) tbr,@m',$nfo, $m))
+ $fr=floatval($m[1]);
+if ($fr<1) exit;
+
+if (preg_match('@Duration: ([0-9:.]+),@m',$nfo, $m)) {
+ $d=preg_split('@[\.:]@',$m[1]);
+ $dr=0;
+ $dr+=intval($d[0])*3600;
+ $dr+=intval($d[1])*60;
+ $dr+=intval($d[2]);
+ $dr+=floatval($d[3]) / pow(10,strlen($d[3]));
+ $df=$fr*$dr;
+}
+if (preg_match('@start: ([0-9:.]+),@m',$nfo, $m)) {
+ $so=floatval($m[1]);
+}
+if (preg_match('@DAR ([0-9:]+)\]@m',$nfo, $m)) {
+ $d=explode(':',$m[1]);
+ $ar=floatval($d[0]/$d[1]);
+}
+else if (preg_match('@Video:.* ([0-9]+x[0-9]+),@m',$nfo, $m)) {
+ $d=explode('x',$m[1]);
+ $ar=floatval($d[0]/$d[1]);
+}
+
+if ($mode=='info') {
+ # Protocol Version number
+ # FPS
+ # duration (in frames)
+ # start-offset (in seconds)
+ # aspect-ratio
+ echo "1\n$fr\n$df\n$so\n$ar\n";
+ exit;
+}
+
+if ($df<1 || $f>$df ) exit;
+$st=floor(1000.0*$f/$fr)/1000.0;
+
+$wh=escapeshellarg($w.'x'.$h);
+$ss=escapeshellarg($st);
+
+header('Content-Type: image/'.$fmt);
+passthru($exe_ffmpeg.' -loglevel quiet'
+ .' -i '.$fn.' -s '.$wh.' -ss '.$ss.' -vframes 1 '
+ .'-f image2 -vcodec png - 2>/dev/null'
+ .'| '.$exe_imagemagick.' - '.$fmt.':-');
diff --git a/wscript b/wscript
index 9cb627d483..794c2f1124 100644
--- a/wscript
+++ b/wscript
@@ -406,6 +406,8 @@ def options(opt):
help='Do not build with Freesound database support')
opt.add_option('--gprofile', action='store_true', default=False, dest='gprofile',
help='Compile for use with gprofile')
+ opt.add_option('--videotimeline', action='store_true', default=False, dest='videotimeline',
+ help='Compile with support for video-timeline')
opt.add_option('--lv2', action='store_true', default=True, dest='lv2',
help='Compile with support for LV2 (if Lilv+Suil is available)')
opt.add_option('--no-lv2', action='store_false', dest='lv2',
@@ -622,6 +624,9 @@ def configure(conf):
conf.env['BUILD_TESTS'] = opts.build_tests
#if opts.tranzport:
# conf.env['TRANZPORT'] = 1
+ if opts.videotimeline:
+ conf.define('WITH_VIDEOTIMELINE',1)
+ conf.env['VIDEOTIMELINE'] = 1
if opts.windows_vst:
conf.define('WINDOWS_VST_SUPPORT', 1)
conf.env['WINDOWS_VST_SUPPORT'] = True
@@ -699,6 +704,7 @@ const char* const ardour_config_info = "\\n\\
# write_config_text('Tranzport', opts.tranzport)
write_config_text('Unit tests', conf.env['BUILD_TESTS'])
write_config_text('Universal binary', opts.universal)
+ write_config_text('Videotimeline', opts.videotimeline)
write_config_text('Generic x86 CPU', opts.generic)
write_config_text('Windows VST support', opts.windows_vst)
write_config_text('Wiimote support', conf.is_defined('BUILD_WIIMOTE'))