From: Colin Fletcher Date: Mon, 19 May 2014 19:54:36 +0000 (+0100) Subject: Merge branch 'export-dialog' into cairocanvas X-Git-Tag: 4.0-rc1~1601^2~1224 X-Git-Url: https://main.carlh.net/gitweb/?a=commitdiff_plain;h=5399425f534e2d96d07cf29f427bfa0f39d904b7;hp=529a31bde7d17cade6c942e11e098f2c37d984a3;p=ardour.git Merge branch 'export-dialog' into cairocanvas Fix merge conflicts in: gtk2_ardour/export_range_markers_dialog.cc gtk2_ardour/wscript libs/ardour/ardour/export_handler.h libs/ardour/system_exec.cc libs/pbd/pbd/system_exec.h libs/pbd/system_exec.cc --- diff --git a/export/Soundcloud upload (lossless).format b/export/Soundcloud upload (lossless).format new file mode 100644 index 0000000000..eb966af86b --- /dev/null +++ b/export/Soundcloud upload (lossless).format @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/export/Soundcloud upload (lossy).format b/export/Soundcloud upload (lossy).format new file mode 100644 index 0000000000..d948b1d97d --- /dev/null +++ b/export/Soundcloud upload (lossy).format @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gtk2_ardour/export_channel_selector.cc b/gtk2_ardour/export_channel_selector.cc index 10e3135b53..20155471af 100644 --- a/gtk2_ardour/export_channel_selector.cc +++ b/gtk2_ardour/export_channel_selector.cc @@ -458,15 +458,15 @@ RegionExportChannelSelector::RegionExportChannelSelector (ARDOUR::Session * _ses raw_button.set_label (string_compose (_("Region contents without fades nor region gain (channels: %1)"), region_chans)); raw_button.signal_toggled ().connect (sigc::mem_fun (*this, &RegionExportChannelSelector::handle_selection)); - vbox.pack_start (raw_button); + vbox.pack_start (raw_button, false, false); fades_button.set_label (string_compose (_("Region contents with fades and region gain (channels: %1)"), region_chans)); fades_button.signal_toggled ().connect (sigc::mem_fun (*this, &RegionExportChannelSelector::handle_selection)); - vbox.pack_start (fades_button); + vbox.pack_start (fades_button, false, false); processed_button.set_label (string_compose (_("Track output (channels: %1)"), track_chans)); processed_button.signal_toggled ().connect (sigc::mem_fun (*this, &RegionExportChannelSelector::handle_selection)); - vbox.pack_start (processed_button); + vbox.pack_start (processed_button, false, false); sync_with_manager(); vbox.show_all_children (); @@ -541,7 +541,7 @@ TrackExportChannelSelector::TrackExportChannelSelector (ARDOUR::Session * sessio // Options options_box.pack_start(region_contents_button); options_box.pack_start(track_output_button); - main_layout.pack_start(options_box); + main_layout.pack_start(options_box, false, false); // Track scroller track_scroller.add (track_view); diff --git a/gtk2_ardour/export_channel_selector.h b/gtk2_ardour/export_channel_selector.h index bc165273c7..3dbb9b8265 100644 --- a/gtk2_ardour/export_channel_selector.h +++ b/gtk2_ardour/export_channel_selector.h @@ -126,7 +126,7 @@ class PortExportChannelSelector : public ExportChannelSelector typedef Gtk::TreeModelColumn > ComboCol; ComboCol port_list_col; - /* Channel struct, that represents the selected port and it's name */ + /* Channel struct, that represents the selected port and its name */ struct Channel { public: diff --git a/gtk2_ardour/export_dialog.cc b/gtk2_ardour/export_dialog.cc index 6351c512c1..76b62cde57 100644 --- a/gtk2_ardour/export_dialog.cc +++ b/gtk2_ardour/export_dialog.cc @@ -140,69 +140,27 @@ ExportDialog::init () progress_widget.hide_all(); } -void -ExportDialog::expanded_changed () -{ - set_resizable(advanced->get_expanded()); -} - void ExportDialog::init_gui () { Gtk::Alignment * preset_align = Gtk::manage (new Gtk::Alignment()); preset_align->add (*preset_selector); preset_align->set_padding (0, 12, 0, 0); - get_vbox()->pack_start (*preset_align, false, false, 0); - - Gtk::VPaned * advanced_paned = Gtk::manage (new Gtk::VPaned()); - - Gtk::VBox* timespan_vbox = Gtk::manage (new Gtk::VBox()); - timespan_vbox->set_spacing (12); - timespan_vbox->set_border_width (12); - - Gtk::Alignment * timespan_align = Gtk::manage (new Gtk::Alignment()); - timespan_label = Gtk::manage (new Gtk::Label (_("Time Span"), Gtk::ALIGN_LEFT)); - timespan_align->add (*timespan_selector); - timespan_align->set_padding (0, 0, 18, 0); - timespan_vbox->pack_start (*timespan_label, false, false, 0); - timespan_vbox->pack_start (*timespan_align, true, true, 0); - advanced_paned->pack1(*timespan_vbox, true, false); - - Gtk::VBox* channels_vbox = Gtk::manage (new Gtk::VBox()); - channels_vbox->set_spacing (12); - channels_vbox->set_border_width (12); - - Gtk::Alignment * channels_align = Gtk::manage (new Gtk::Alignment()); - channels_label = Gtk::manage (new Gtk::Label (_("Channels"), Gtk::ALIGN_LEFT)); - channels_align->add (*channel_selector); - channels_align->set_padding (0, 12, 18, 0); - channels_vbox->pack_start (*channels_label, false, false, 0); - channels_vbox->pack_start (*channels_align, true, true, 0); - advanced_paned->pack2(*channels_vbox, channel_selector_is_expandable(), false); - - get_vbox()->pack_start (*file_notebook, false, false, 0); - get_vbox()->pack_start (warning_widget, false, false, 0); - get_vbox()->pack_start (progress_widget, false, false, 0); - - advanced = Gtk::manage (new Gtk::Expander (_("Time span and channel options"))); - advanced->property_expanded().signal_changed().connect( - sigc::mem_fun(*this, &ExportDialog::expanded_changed)); - advanced->add (*advanced_paned); - - if (channel_selector_is_expandable()) { - advanced_sizegroup = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_VERTICAL); - advanced_sizegroup->add_widget(*timespan_selector); - advanced_sizegroup->add_widget(*channel_selector); - } - get_vbox()->pack_start (*advanced, true, true); + Gtk::VBox * file_format_selector = Gtk::manage (new Gtk::VBox()); + file_format_selector->set_homogeneous (false); + file_format_selector->pack_start (*preset_align, false, false, 0); + file_format_selector->pack_start (*file_notebook, false, false, 0); + file_format_selector->pack_start (*soundcloud_selector, false, false, 0); - Pango::AttrList bold; - Pango::Attribute b = Pango::Attribute::create_attr_weight (Pango::WEIGHT_BOLD); - bold.insert (b); + export_notebook.append_page (*file_format_selector, _("File format")); + export_notebook.append_page (*timespan_selector, _("Time Span")); + export_notebook.append_page (*channel_selector, _("Channels")); + + get_vbox()->pack_start (export_notebook, true, true, 0); + get_vbox()->pack_end (warning_widget, false, false, 0); + get_vbox()->pack_end (progress_widget, false, false, 0); - timespan_label->set_attributes (bold); - channels_label->set_attributes (bold); } void @@ -211,6 +169,7 @@ ExportDialog::init_components () preset_selector.reset (new ExportPresetSelector ()); timespan_selector.reset (new ExportTimespanSelectorMultiple (_session, profile_manager)); channel_selector.reset (new PortExportChannelSelector (_session, profile_manager)); + soundcloud_selector.reset (new SoundcloudExportSelector ()); file_notebook.reset (new ExportFileNotebook ()); } @@ -300,11 +259,34 @@ ExportDialog::show_conflicting_files () dialog.run(); } +void +ExportDialog::soundcloud_upload_progress(double total, double now, std::string title) +{ + soundcloud_selector->do_progress_callback(total, now, title); + +} + void ExportDialog::do_export () { try { profile_manager->prepare_for_export (); + handler->upload_username = soundcloud_selector->username(); + handler->upload_password = soundcloud_selector->password(); + handler->upload_public = soundcloud_selector->upload_public(); + handler->upload_open = soundcloud_selector->upload_open(); + + handler->SoundcloudProgress.connect_same_thread( + *this, + boost::bind(&ExportDialog::soundcloud_upload_progress, this, _1, _2, _3) + ); +#if 0 + handler->SoundcloudProgress.connect( + *this, invalidator (*this), + boost::bind(&ExportDialog::soundcloud_upload_progress, this, _1, _2, _3), + gui_context() + ); +#endif handler->do_export (); show_progress (); } catch(std::exception & e) { @@ -418,6 +400,7 @@ ExportRangeDialog::init_components () preset_selector.reset (new ExportPresetSelector ()); timespan_selector.reset (new ExportTimespanSelectorSingle (_session, profile_manager, range_id)); channel_selector.reset (new PortExportChannelSelector (_session, profile_manager)); + soundcloud_selector.reset (new SoundcloudExportSelector ()); file_notebook.reset (new ExportFileNotebook ()); } @@ -431,6 +414,7 @@ ExportSelectionDialog::init_components () preset_selector.reset (new ExportPresetSelector ()); timespan_selector.reset (new ExportTimespanSelectorSingle (_session, profile_manager, X_("selection"))); channel_selector.reset (new PortExportChannelSelector (_session, profile_manager)); + soundcloud_selector.reset (new SoundcloudExportSelector ()); file_notebook.reset (new ExportFileNotebook ()); } @@ -444,8 +428,7 @@ void ExportRegionDialog::init_gui () { ExportDialog::init_gui (); - - channels_label->set_text (_("Source")); + export_notebook.set_tab_label_text(*export_notebook.get_nth_page(2), _("Source")); } void @@ -456,6 +439,7 @@ ExportRegionDialog::init_components () preset_selector.reset (new ExportPresetSelector ()); timespan_selector.reset (new ExportTimespanSelectorSingle (_session, profile_manager, loc_id)); channel_selector.reset (new RegionExportChannelSelector (_session, profile_manager, region, track)); + soundcloud_selector.reset (new SoundcloudExportSelector ()); file_notebook.reset (new ExportFileNotebook ()); } @@ -471,5 +455,6 @@ StemExportDialog::init_components () preset_selector.reset (new ExportPresetSelector ()); timespan_selector.reset (new ExportTimespanSelectorMultiple (_session, profile_manager)); channel_selector.reset (new TrackExportChannelSelector (_session, profile_manager)); + soundcloud_selector.reset (new SoundcloudExportSelector ()); file_notebook.reset (new ExportFileNotebook ()); } diff --git a/gtk2_ardour/export_dialog.h b/gtk2_ardour/export_dialog.h index 756a3e7b53..315780750e 100644 --- a/gtk2_ardour/export_dialog.h +++ b/gtk2_ardour/export_dialog.h @@ -32,6 +32,7 @@ #include "export_file_notebook.h" #include "export_preset_selector.h" #include "ardour_dialog.h" +#include "soundcloud_export_selector.h" #include @@ -43,7 +44,8 @@ namespace ARDOUR { class ExportTimespanSelector; class ExportChannelSelector; -class ExportDialog : public ArdourDialog { +class ExportDialog : public ArdourDialog, public PBD::ScopedConnectionList +{ public: @@ -75,26 +77,22 @@ class ExportDialog : public ArdourDialog { // Must initialize all the shared_ptrs below virtual void init_components (); - // Override if the channel selector should not be grown - virtual bool channel_selector_is_expandable() { return true; } - boost::scoped_ptr preset_selector; boost::scoped_ptr timespan_selector; boost::scoped_ptr channel_selector; boost::scoped_ptr file_notebook; + boost::scoped_ptr soundcloud_selector; Gtk::VBox warning_widget; Gtk::VBox progress_widget; - Gtk::Label * timespan_label; - Gtk::Label * channels_label; + /*** GUI components ***/ + Gtk::Notebook export_notebook; private: void init (); - void expanded_changed(); - void notify_errors (bool force = false); void close_dialog (); @@ -112,10 +110,7 @@ class ExportDialog : public ArdourDialog { PublicEditor & editor; StatusPtr status; - /*** GUI components ***/ - Glib::RefPtr advanced_sizegroup; - Gtk::Expander * advanced; /* Warning area */ @@ -138,6 +133,8 @@ class ExportDialog : public ArdourDialog { float previous_progress; // Needed for gtk bug workaround + void soundcloud_upload_progress(double total, double now, std::string title); + /* Buttons */ Gtk::Button * cancel_button; @@ -170,9 +167,6 @@ class ExportRegionDialog : public ExportDialog public: ExportRegionDialog (PublicEditor & editor, ARDOUR::AudioRegion const & region, ARDOUR::AudioTrack & track); - protected: - virtual bool channel_selector_is_expandable() { return false; } - private: void init_gui (); void init_components (); diff --git a/gtk2_ardour/export_format_dialog.cc b/gtk2_ardour/export_format_dialog.cc index c5d1573d54..69c494e090 100644 --- a/gtk2_ardour/export_format_dialog.cc +++ b/gtk2_ardour/export_format_dialog.cc @@ -51,6 +51,9 @@ ExportFormatDialog::ExportFormatDialog (FormatPtr format, bool new_dialog) : silence_end_checkbox (_("Add silence at end:")), silence_end_clock ("silence_end", true, "", true, false, true), + upload_checkbox(_("Upload to Soundcloud")), + command_label(_("Command to run post-export\n(%f=full path & filename, %d=directory, %b=basename, %u=username, %p=password):")), + format_table (3, 4), compatibility_label (_("Compatibility"), Gtk::ALIGN_LEFT), quality_label (_("Quality"), Gtk::ALIGN_LEFT), @@ -113,6 +116,10 @@ ExportFormatDialog::ExportFormatDialog (FormatPtr format, bool new_dialog) : silence_table.attach (silence_end_checkbox, 1, 2, 2, 3); silence_table.attach (silence_end_clock, 2, 3, 2, 3); + get_vbox()->pack_start (upload_checkbox, false, false); + get_vbox()->pack_start (command_label, false, false); + get_vbox()->pack_start (command_entry, false, false); + /* Format table */ init_format_table(); @@ -142,6 +149,8 @@ ExportFormatDialog::ExportFormatDialog (FormatPtr format, bool new_dialog) : with_cue.signal_toggled().connect (sigc::mem_fun (*this, &ExportFormatDialog::update_with_cue)); with_toc.signal_toggled().connect (sigc::mem_fun (*this, &ExportFormatDialog::update_with_toc)); + upload_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &ExportFormatDialog::update_upload)); + command_entry.signal_changed().connect (sigc::mem_fun (*this, &ExportFormatDialog::update_command)); cue_toc_vbox.pack_start (with_cue, false, false); cue_toc_vbox.pack_start (with_toc, false, false); @@ -296,6 +305,8 @@ ExportFormatDialog::load_state (FormatPtr spec) } tag_checkbox.set_active (spec->tag()); + upload_checkbox.set_active (spec->upload()); + command_entry.set_text (spec->command()); } void @@ -717,6 +728,18 @@ ExportFormatDialog::update_with_toc () manager.select_with_toc (with_toc.get_active()); } +void +ExportFormatDialog::update_upload () +{ + manager.select_upload (upload_checkbox.get_active()); +} + +void +ExportFormatDialog::update_command () +{ + manager.set_command (command_entry.get_text()); +} + void ExportFormatDialog::update_description() { diff --git a/gtk2_ardour/export_format_dialog.h b/gtk2_ardour/export_format_dialog.h index 3e38cf09d6..6b92625e7a 100644 --- a/gtk2_ardour/export_format_dialog.h +++ b/gtk2_ardour/export_format_dialog.h @@ -179,6 +179,12 @@ class ExportFormatDialog : public ArdourDialog, public PBD::ScopedConnectionList Gtk::CheckButton silence_end_checkbox; AudioClock silence_end_clock; + /* Upload */ + + Gtk::CheckButton upload_checkbox; + Gtk::Label command_label; + Gtk::Entry command_entry; + /* Format table */ struct CompatibilityCols : public Gtk::TreeModelColumnRecord @@ -311,6 +317,8 @@ class ExportFormatDialog : public ArdourDialog, public PBD::ScopedConnectionList void update_with_toc (); void update_with_cue (); + void update_upload (); + void update_command (); Gtk::TreeView sample_format_view; Gtk::TreeView dither_type_view; diff --git a/gtk2_ardour/export_range_markers_dialog.cc b/gtk2_ardour/export_range_markers_dialog.cc index 97a8dba25f..e69de29bb2 100644 --- a/gtk2_ardour/export_range_markers_dialog.cc +++ b/gtk2_ardour/export_range_markers_dialog.cc @@ -1,209 +0,0 @@ -/* - Copyright (C) 2006 Paul Davis - Author: Andre Raue - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include - -#include - -#include "ardour/audioengine.h" -#include "ardour/sndfile_helpers.h" - -#include "ardour_ui.h" -#include "export_range_markers_dialog.h" - -#include "i18n.h" - -using namespace Gtk; -using namespace ARDOUR; -using namespace PBD; -using namespace std; - -ExportRangeMarkersDialog::ExportRangeMarkersDialog (PublicEditor& editor) - : ExportDialog(editor) -{ - set_title (_("Export Ranges")); - file_frame.set_label (_("Export to Directory")); - - do_not_allow_export_cd_markers(); - - total_duration = 0; - current_range_marker_index = 0; -} - -Gtk::FileChooserAction -ExportRangeMarkersDialog::browse_action () const -{ - return Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER; -} - -void -ExportRangeMarkersDialog::export_data () -{ - getSession().locations()->apply(*this, &ExportRangeMarkersDialog::process_range_markers_export); -} - -void -ExportRangeMarkersDialog::process_range_markers_export(Locations::LocationList& locations) -{ - Locations::LocationList::iterator locationIter; - current_range_marker_index = 0; - init_progress_computing(locations); - - for (locationIter = locations.begin(); locationIter != locations.end(); ++locationIter) { - Location *currentLocation = (*locationIter); - - if(currentLocation->is_range_marker()){ - // init filename - string filepath = get_target_filepath( - get_selected_file_name(), - currentLocation->name(), - get_selected_header_format()); - - initSpec(filepath); - - spec.start_frame = currentLocation->start(); - spec.end_frame = currentLocation->end(); - - if (getSession().start_export(spec)){ - // if export fails - return; - } - - // wait until export of this range finished - gtk_main_iteration(); - - while (spec.running){ - if(gtk_events_pending()){ - gtk_main_iteration(); - }else { - Glib::usleep(10000); - } - } - - current_range_marker_index++; - - getSession().stop_export (spec); - } - } - - spec.running = false; -} - - -string -ExportRangeMarkersDialog::get_target_filepath(string path, string filename, string postfix) -{ - string target_path = path; - if ((target_path.find_last_of ('/')) != string::npos) { - target_path += '/'; - } - - string target_filepath = target_path + filename + postfix; - struct stat statbuf; - - for(int counter=1; (stat (target_filepath.c_str(), &statbuf) == 0); counter++){ - // while file exists - ostringstream scounter; - scounter.flush(); - scounter << counter; - - target_filepath = - target_path + filename + "_" + scounter.str() + postfix; - } - - return target_filepath; -} - -bool -ExportRangeMarkersDialog::is_filepath_valid(string &filepath) -{ - // sanity check file name first - struct stat statbuf; - - if (filepath.empty()) { - // warning dialog - string txt = _("Please enter a valid target directory."); - MessageDialog msg (*this, txt, false, MESSAGE_ERROR, BUTTONS_OK, true); - msg.run(); - return false; - } - - if ( (stat (filepath.c_str(), &statbuf) != 0) || - (!S_ISDIR (statbuf.st_mode)) ) { - string txt = _("Please select an existing target directory. Files are not allowed!"); - MessageDialog msg (*this, txt, false, MESSAGE_ERROR, BUTTONS_OK, true); - msg.run(); - return false; - } - - // directory needs to exist and be writable - string dirpath = Glib::path_get_dirname (filepath); - if (!exists_and_writable (dirpath)) { - string txt = _("Cannot write file in: ") + dirpath; - MessageDialog msg (*this, txt, false, MESSAGE_ERROR, BUTTONS_OK, true); - msg.run(); - return false; - } - - return true; -} - -void -ExportRangeMarkersDialog::init_progress_computing(Locations::LocationList& locations) -{ - // flush vector - range_markers_durations_aggregated.resize(0); - - framecnt_t duration_before_current_location = 0; - Locations::LocationList::iterator locationIter; - - for (locationIter = locations.begin(); locationIter != locations.end(); ++locationIter) { - Location *currentLocation = (*locationIter); - - if(currentLocation->is_range_marker()){ - range_markers_durations_aggregated.push_back (duration_before_current_location); - - framecnt_t duration = currentLocation->end() - currentLocation->start(); - - range_markers_durations.push_back (duration); - duration_before_current_location += duration; - } - } - - total_duration = duration_before_current_location; -} - - -gint -ExportRangeMarkersDialog::progress_timeout () -{ - double progress = 0.0; - - if (current_range_marker_index >= range_markers_durations.size()){ - progress = 1.0; - } else{ - progress = ((double) range_markers_durations_aggregated[current_range_marker_index] + - (spec.progress * (double) range_markers_durations[current_range_marker_index])) / - (double) total_duration; - } - - set_progress_fraction( progress ); - return TRUE; -} diff --git a/gtk2_ardour/export_range_markers_dialog.h b/gtk2_ardour/export_range_markers_dialog.h index b0a29b5dc2..e69de29bb2 100644 --- a/gtk2_ardour/export_range_markers_dialog.h +++ b/gtk2_ardour/export_range_markers_dialog.h @@ -1,66 +0,0 @@ -/* - Copyright (C) 2006 Andre Raue - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#ifndef __export_range_markers_dialog_h__ -#define __export_range_markers_dialog_h__ - -#include "ardour/location.h" - -#include "export_dialog.h" - - -class ExportRangeMarkersDialog : public ExportDialog -{ - public: - ExportRangeMarkersDialog (PublicEditor&); - - Gtk::FileChooserAction browse_action() const; - - protected: - virtual bool is_filepath_valid(string &filepath); - - void export_data(); - - bool wants_dir() { return true; } - - private: - // keeps the duration of all range_markers before the current - vector range_markers_durations_aggregated; - vector range_markers_durations; - // duration of all range markers - nframes_t total_duration; - // index of range marker, that get's exported right now - unsigned int current_range_marker_index; - - // sets value of progress bar - virtual gint progress_timeout (); - - // initializes range_markers_durations_aggregated, range_markers_durations - // and total_duration - void init_progress_computing(ARDOUR::Locations::LocationList& locations); - - // searches for a filename like "." in path, that - // does not exist - string get_target_filepath(string path, string filename, string postfix); - - void process_range_markers_export(ARDOUR::Locations::LocationList&); -}; - - -#endif // __export_range_markers_dialog_h__ diff --git a/gtk2_ardour/export_timespan_selector.cc b/gtk2_ardour/export_timespan_selector.cc index d6ca02fe03..61d813d222 100644 --- a/gtk2_ardour/export_timespan_selector.cc +++ b/gtk2_ardour/export_timespan_selector.cc @@ -105,6 +105,9 @@ ExportTimespanSelector::ExportTimespanSelector (ARDOUR::Session * session, Profi /* Range view */ range_list = Gtk::ListStore::create (range_cols); + // order by location start times + range_list->set_sort_column(range_cols.location, Gtk::SORT_ASCENDING); + range_list->set_sort_func(range_cols.location, sigc::mem_fun(*this, &ExportTimespanSelector::location_sorter)); range_view.set_model (range_list); range_view.set_headers_visible (true); } @@ -114,6 +117,22 @@ ExportTimespanSelector::~ExportTimespanSelector () } +int +ExportTimespanSelector::location_sorter(Gtk::TreeModel::iterator a, Gtk::TreeModel::iterator b) +{ + Location *l1 = (*a)[range_cols.location]; + Location *l2 = (*b)[range_cols.location]; + const Location *ls = _session->locations()->session_range_location(); + + // always sort session range first + if (l1 == ls) + return -1; + if (l2 == ls) + return +1; + + return l1->start() - l2->start(); +} + void ExportTimespanSelector::add_range_to_selection (ARDOUR::Location const * loc) { diff --git a/gtk2_ardour/export_timespan_selector.h b/gtk2_ardour/export_timespan_selector.h index 5556f5f676..1216670991 100644 --- a/gtk2_ardour/export_timespan_selector.h +++ b/gtk2_ardour/export_timespan_selector.h @@ -89,6 +89,7 @@ class ExportTimespanSelector : public Gtk::VBox, public ARDOUR::SessionHandlePtr void update_range_name (std::string const & path, std::string const & new_text); void set_selection_state_of_all_timespans (bool); + int location_sorter(Gtk::TreeModel::iterator a, Gtk::TreeModel::iterator b); /*** GUI components ***/ @@ -132,7 +133,7 @@ class ExportTimespanSelector : public Gtk::VBox, public ARDOUR::SessionHandlePtr Gtk::ScrolledWindow range_scroller; }; -/// Allows seleting multiple timespans +/// Allows selecting multiple timespans class ExportTimespanSelectorMultiple : public ExportTimespanSelector { public: diff --git a/gtk2_ardour/icons/soundcloud.png b/gtk2_ardour/icons/soundcloud.png new file mode 100644 index 0000000000..39c50fe7b3 Binary files /dev/null and b/gtk2_ardour/icons/soundcloud.png differ diff --git a/gtk2_ardour/soundcloud_export_selector.cc b/gtk2_ardour/soundcloud_export_selector.cc new file mode 100644 index 0000000000..1ecab514ab --- /dev/null +++ b/gtk2_ardour/soundcloud_export_selector.cc @@ -0,0 +1,110 @@ +/* soundcloud_export_selector.cpp *************************************************** + + Adapted for Ardour by Ben Loftis, March 2012 + + Licence GPL: + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +*************************************************************************************/ +#include "ardour/soundcloud_upload.h" +#include "soundcloud_export_selector.h" + +#include +#include "pbd/openuri.h" + +#include +#include +#include +#include + +#include "i18n.h" + +using namespace PBD; + +#include "ardour/session_metadata.h" +#include "utils.h" + +SoundcloudExportSelector::SoundcloudExportSelector() : + sc_table (4, 3), + soundcloud_public_checkbox (_("Make file(s) public")), + soundcloud_username_label (_("User Email"), 1.0, 0.5), + soundcloud_password_label (_("Password"), 1.0, 0.5), + soundcloud_open_checkbox (_("Open uploaded files in browser")), + progress_bar() +{ + + + soundcloud_public_checkbox.set_name ("ExportCheckbox"); + soundcloud_username_label.set_name ("ExportFormatLabel"); + soundcloud_username_entry.set_name ("ExportFormatDisplay"); + soundcloud_password_label.set_name ("ExportFormatLabel"); + soundcloud_password_entry.set_name ("ExportFormatDisplay"); + + soundcloud_username_entry.set_text (ARDOUR::SessionMetadata::Metadata()->user_email()); + soundcloud_password_entry.set_visibility(false); + + Gtk::Frame *sc_frame = manage(new Gtk::Frame); + sc_frame->set_border_width(4); + sc_frame->set_shadow_type(Gtk::SHADOW_ETCHED_OUT); + sc_frame->set_name("soundcloud_export_box"); + pack_start(*sc_frame, false, false); + + sc_table.set_border_width(4); + sc_table.set_col_spacings (5); + sc_table.set_row_spacings (5); + sc_frame->add (sc_table); + + // sc_table.attach ( *( manage (new EventBox (::get_icon (X_("soundcloud"))))) , 0, 1, 0, 1); + sc_table.attach ( *(Gtk::manage (new Gtk::Image (get_icon (X_("soundcloud"))))) , 0, 1, 0, 2); + + sc_table.attach (soundcloud_public_checkbox, 2, 3, 1, 2); + sc_table.attach (soundcloud_username_label, 0, 1, 3, 4); + sc_table.attach (soundcloud_username_entry, 1, 3, 3, 4); + sc_table.attach (soundcloud_password_label, 0, 1, 5, 6); + sc_table.attach (soundcloud_password_entry, 1, 3, 5, 6); + sc_table.attach (soundcloud_open_checkbox, 2, 3, 7, 8); + + pack_end(progress_bar, false, false); + sc_frame->show_all(); +} + + +int +SoundcloudExportSelector::do_progress_callback(double ultotal, double ulnow, const std::string &filename) +{ + std::cerr << "SoundcloudExportSelector::do_progress_callback(" << ultotal << ", " << ulnow << ", " << filename << ")..." << std::endl; + if (soundcloud_cancel) { + progress_bar.set_fraction (0); + // cancel_button.set_label (""); + return -1; + } + + double fraction = 0.0; + if (ultotal != 0) { + fraction = ulnow / ultotal; + } + + progress_bar.set_fraction ( fraction ); + + std::string prog; + prog = string_compose (_("%1: %2 of %3 bytes uploaded"), filename, ulnow, ultotal); + progress_bar.set_text( prog ); + + + return 0; +} + diff --git a/gtk2_ardour/soundcloud_export_selector.h b/gtk2_ardour/soundcloud_export_selector.h new file mode 100644 index 0000000000..7962ba8b06 --- /dev/null +++ b/gtk2_ardour/soundcloud_export_selector.h @@ -0,0 +1,41 @@ +/*soundcloud_export_selector.h*********************************************** + + Adapted for Ardour by Ben Loftis, March 2012 + +*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class SoundcloudExportSelector : public Gtk::VBox, public ARDOUR::SessionHandlePtr +{ + public: + SoundcloudExportSelector (); + int do_progress_callback (double ultotal, double ulnow, const std::string &filename); + std::string username () { return soundcloud_username_entry.get_text (); } + std::string password () { return soundcloud_password_entry.get_text (); } + bool upload_public () { return soundcloud_public_checkbox.get_active (); } + bool upload_open () { return soundcloud_open_checkbox.get_active (); } + void cancel () { soundcloud_cancel = true; } + + private: + Gtk::Table sc_table; + Gtk::CheckButton soundcloud_public_checkbox; + Gtk::Label soundcloud_username_label; + Gtk::Entry soundcloud_username_entry; + Gtk::Label soundcloud_password_label; + Gtk::Entry soundcloud_password_entry; + Gtk::CheckButton soundcloud_open_checkbox; + bool soundcloud_cancel; + Gtk::ProgressBar progress_bar; + +}; + diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index 920f71bd7d..2cfd2ea497 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -204,6 +204,7 @@ gtk2_ardour_sources = [ 'session_option_editor.cc', 'sfdb_ui.cc', 'shuttle_control.cc', + 'soundcloud_export_selector.cc', 'splash.cc', 'speaker_dialog.cc', 'startup.cc', diff --git a/libs/ardour/ardour/export_format_manager.h b/libs/ardour/ardour/export_format_manager.h index 9a95111509..dad7d84b72 100644 --- a/libs/ardour/ardour/export_format_manager.h +++ b/libs/ardour/ardour/export_format_manager.h @@ -100,6 +100,8 @@ class LIBARDOUR_API ExportFormatManager : public PBD::ScopedConnectionList void select_with_cue (bool); void select_with_toc (bool); + void select_upload (bool); + void set_command (std::string); void select_src_quality (ExportFormatBase::SRCQuality value); void select_trim_beginning (bool value); void select_silence_beginning (AnyTime const & time); diff --git a/libs/ardour/ardour/export_format_specification.h b/libs/ardour/ardour/export_format_specification.h index 1593990d35..d41fe3e97a 100644 --- a/libs/ardour/ardour/export_format_specification.h +++ b/libs/ardour/ardour/export_format_specification.h @@ -96,6 +96,8 @@ class LIBARDOUR_API ExportFormatSpecification : public ExportFormatBase { void set_tag (bool tag_it) { _tag = tag_it; } void set_with_cue (bool yn) { _with_cue = yn; } void set_with_toc (bool yn) { _with_toc = yn; } + void set_upload (bool yn) { _upload = yn; } + void set_command (std::string command) { _command = command; } void set_silence_beginning (AnyTime const & value) { _silence_beginning = value; } void set_silence_end (AnyTime const & value) { _silence_end = value; } @@ -125,6 +127,8 @@ class LIBARDOUR_API ExportFormatSpecification : public ExportFormatBase { float normalize_target () const { return _normalize_target; } bool with_toc() const { return _with_toc; } bool with_cue() const { return _with_cue; } + bool upload() const { return _upload; } + std::string command() const { return _command; } bool tag () const { return _tag && supports_tagging; } @@ -174,6 +178,8 @@ class LIBARDOUR_API ExportFormatSpecification : public ExportFormatBase { float _normalize_target; bool _with_toc; bool _with_cue; + bool _upload; + std::string _command; /* serialization helpers */ diff --git a/libs/ardour/ardour/export_handler.h b/libs/ardour/ardour/export_handler.h index 1bc80a80e9..25a87045a8 100644 --- a/libs/ardour/ardour/export_handler.h +++ b/libs/ardour/ardour/export_handler.h @@ -31,6 +31,7 @@ #include "ardour/session.h" #include "ardour/libardour_visibility.h" #include "ardour/types.h" +#include "pbd/signals.h" namespace AudioGrapher { class BroadcastInfo; @@ -68,7 +69,7 @@ class LIBARDOUR_API ExportElementFactory Session & session; }; -class LIBARDOUR_API ExportHandler : public ExportElementFactory +class LIBARDOUR_API ExportHandler : public ExportElementFactory, public sigc::trackable { public: struct FileSpec { @@ -95,6 +96,8 @@ class LIBARDOUR_API ExportHandler : public ExportElementFactory friend boost::shared_ptr Session::get_export_handler(); ExportHandler (Session & session); + void command_output(std::string output, size_t size); + public: ~ExportHandler (); @@ -105,6 +108,17 @@ class LIBARDOUR_API ExportHandler : public ExportElementFactory std::string get_cd_marker_filename(std::string filename, CDMarkerFormat format); + /** signal emitted when soundcloud export reports progress updates during upload. + * The parameters are total and current bytes downloaded, and the current filename + */ + PBD::Signal3 SoundcloudProgress; + + /* upload credentials & preferences */ + std::string upload_username; + std::string upload_password; + bool upload_public; + bool upload_open; + private: void handle_duplicate_format_extensions(); diff --git a/libs/ardour/ardour/soundcloud_upload.h b/libs/ardour/ardour/soundcloud_upload.h new file mode 100644 index 0000000000..6b8700e784 --- /dev/null +++ b/libs/ardour/ardour/soundcloud_upload.h @@ -0,0 +1,55 @@ +/* soundcloud_upload.h ****************************************************** + + Adapted for Ardour by Ben Loftis, March 2012 + +*****************************************************************************/ + +#ifndef __ardour_soundcloud_upload_h__ +#define __ardour_soundcloud_upload_h__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "curl/curl.h" +#include "ardour/session_handle.h" +#include "ardour/export_handler.h" +#include "pbd/signals.h" + +//--- struct to store XML file +struct MemoryStruct { + char *memory; + size_t size; +}; + + +class SoundcloudUploader +{ +public: + SoundcloudUploader(); + ~SoundcloudUploader(); + + std::string Get_Auth_Token(std::string username, std::string password); + std::string Upload (std::string file_path, std::string title, std::string token, bool ispublic, ARDOUR::ExportHandler *caller); + static int progress_callback(void *caller, double dltotal, double dlnow, double ultotal, double ulnow); + + +private: + + void setcUrlOptions(); + + CURL *curl_handle; + CURLM *multi_handle; + char errorBuffer[CURL_ERROR_SIZE]; // storage for cUrl error message + + std::string title; + ARDOUR::ExportHandler *caller; + +}; + +#endif /* __ardour_soundcloud_upload_h__ */ diff --git a/libs/ardour/ardour/system_exec.h b/libs/ardour/ardour/system_exec.h index 40e429720e..ae865c7bff 100644 --- a/libs/ardour/ardour/system_exec.h +++ b/libs/ardour/ardour/system_exec.h @@ -32,6 +32,7 @@ class LIBARDOUR_API SystemExec public: SystemExec (std::string c, std::string a = ""); SystemExec (std::string c, char ** a); + SystemExec (std::string c, const std::map subs); ~SystemExec (); int start (int stderr_mode = 1) { diff --git a/libs/ardour/export_format_manager.cc b/libs/ardour/export_format_manager.cc index 890623c114..3ee940ffb6 100644 --- a/libs/ardour/export_format_manager.cc +++ b/libs/ardour/export_format_manager.cc @@ -293,6 +293,20 @@ ExportFormatManager::select_with_toc (bool value) check_for_description_change (); } +void +ExportFormatManager::select_upload (bool value) +{ + current_selection->set_upload (value); + check_for_description_change (); +} + +void +ExportFormatManager::set_command (std::string command) +{ + current_selection->set_command (command); + check_for_description_change (); +} + void ExportFormatManager::select_trim_beginning (bool value) { diff --git a/libs/ardour/export_format_specification.cc b/libs/ardour/export_format_specification.cc index b139faeee2..8b921519f7 100644 --- a/libs/ardour/export_format_specification.cc +++ b/libs/ardour/export_format_specification.cc @@ -170,6 +170,8 @@ ExportFormatSpecification::ExportFormatSpecification (Session & s) , _normalize_target (1.0) , _with_toc (false) , _with_cue (false) + , _upload (false) + , _command ("") { format_ids.insert (F_None); endiannesses.insert (E_FileDefault); @@ -244,6 +246,8 @@ ExportFormatSpecification::get_state () root->add_property ("id", _id.to_s()); root->add_property ("with-cue", _with_cue ? "true" : "false"); root->add_property ("with-toc", _with_toc ? "true" : "false"); + root->add_property ("upload", _upload ? "true" : "false"); + root->add_property ("command", _command); node = root->add_child ("Encoding"); node->add_property ("id", enum_2_string (format_id())); @@ -321,6 +325,18 @@ ExportFormatSpecification::set_state (const XMLNode & root) _with_toc = false; } + if ((prop = root.property ("upload"))) { + _upload = string_is_affirmative (prop->value()); + } else { + _upload = false; + } + + if ((prop = root.property ("command"))) { + _command = prop->value(); + } else { + _command = ""; + } + /* Encoding and SRC */ if ((child = root.child ("Encoding"))) { @@ -590,6 +606,14 @@ ExportFormatSpecification::description (bool include_name) components.push_back ("CUE"); } + if (_upload) { + components.push_back ("Upload"); + } + + if (!_command.empty()) { + components.push_back ("+"); + } + string desc; if (include_name) { desc = _name + ": "; diff --git a/libs/ardour/export_handler.cc b/libs/ardour/export_handler.cc index 20abc80de1..c9f20d182e 100644 --- a/libs/ardour/export_handler.cc +++ b/libs/ardour/export_handler.cc @@ -33,6 +33,10 @@ #include "ardour/export_status.h" #include "ardour/export_format_specification.h" #include "ardour/export_filename.h" +#include "ardour/soundcloud_upload.h" +#include "ardour/system_exec.h" +#include "pbd/openuri.h" +#include "pbd/basename.h" #include "ardour/session_metadata.h" #include "i18n.h" @@ -277,6 +281,13 @@ ExportHandler::process_normalize () return 0; } +void +ExportHandler::command_output(std::string output, size_t size) +{ + std::cerr << "command: " << size << ", " << output << std::endl; + info << output << endmsg; +} + void ExportHandler::finish_timespan () { @@ -297,13 +308,77 @@ ExportHandler::finish_timespan () AudiofileTagger::tag_file(filename, *SessionMetadata::Metadata()); } + if (!fmt->command().empty()) { + +#if 0 // would be nicer with C++11 initialiser... + std::map subs { + { 'f', filename }, + { 'd', Glib::path_get_dirname(filename) }, + { 'b', PBD::basename_nosuffix(filename) }, + { 'u', upload_username }, + { 'p', upload_password} + }; +#endif + + PBD::ScopedConnection command_connection; + std::map subs; + subs.insert (std::pair ('f', filename)); + subs.insert (std::pair ('d', Glib::path_get_dirname(filename))); + subs.insert (std::pair ('b', PBD::basename_nosuffix(filename))); + subs.insert (std::pair ('u', upload_username)); + subs.insert (std::pair ('p', upload_password)); + + + std::cerr << "running command: " << fmt->command() << "..." << std::endl; + ARDOUR::SystemExec *se = new ARDOUR::SystemExec(fmt->command(), subs); + se->ReadStdout.connect_same_thread(command_connection, boost::bind(&ExportHandler::command_output, this, _1, _2)); + if (se->start (2) == 0) { + // successfully started + std::cerr << "started!" << std::endl; + while (se->is_running ()) { + // wait for system exec to terminate + // std::cerr << "waiting..." << std::endl; + usleep (1000); + } + } + std::cerr << "done! deleting..." << std::endl; + delete (se); + } + + if (fmt->upload()) { + SoundcloudUploader *soundcloud_uploader = new SoundcloudUploader; + std::string token = soundcloud_uploader->Get_Auth_Token(upload_username, upload_password); + std::cerr + << "uploading " + << filename << std::endl + << "username = " << upload_username + << ", password = " << upload_password + << " - token = " << token << " ..." + << std::endl; + std::string path = soundcloud_uploader->Upload ( + filename, + PBD::basename_nosuffix(filename), // title + token, + upload_public, + this); + + if (path.length() != 0) { + if (upload_open) { + std::cerr << "opening " << path << " ..." << std::endl; + open_uri(path.c_str()); // open the soundcloud website to the new file + } + } else { + error << _("upload to Soundcloud failed. Perhaps your email or password are incorrect?\n") << endmsg; + } + delete soundcloud_uploader; + } config_map.erase (config_map.begin()); } start_timespan (); } -/*** CD Marker sutff ***/ +/*** CD Marker stuff ***/ struct LocationSortByStart { bool operator() (Location *a, Location *b) { diff --git a/libs/ardour/soundcloud_upload.cc b/libs/ardour/soundcloud_upload.cc new file mode 100644 index 0000000000..f003d5ab65 --- /dev/null +++ b/libs/ardour/soundcloud_upload.cc @@ -0,0 +1,349 @@ +/* soundcloud_export.cpp ********************************************************************** + + Adapted for Ardour by Ben Loftis, March 2012 + + Licence GPL: + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +*************************************************************************************/ +#include "ardour/soundcloud_upload.h" + +#include "pbd/xml++.h" +#include +//#include "pbd/filesystem.h" + +#include +#include +#include +#include + +#include "i18n.h" + +using namespace PBD; + +// static const std::string base_url = "http://api.soundcloud.com/tracks/13158665?client_id="; + +size_t +WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data) +{ + register int realsize = (int)(size * nmemb); + struct MemoryStruct *mem = (struct MemoryStruct *)data; + + mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1); + + if (mem->memory) { + memcpy(&(mem->memory[mem->size]), ptr, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + } + return realsize; +} + +SoundcloudUploader::SoundcloudUploader() +{ + curl_handle = curl_easy_init(); + multi_handle = curl_multi_init(); +} + +std::string +SoundcloudUploader::Get_Auth_Token( std::string username, std::string password ) +{ + struct MemoryStruct xml_page; + xml_page.memory = NULL; + xml_page.size = 0; + + setcUrlOptions(); + + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &xml_page); + + struct curl_httppost *formpost=NULL; + struct curl_httppost *lastptr=NULL; + + /* Fill in the filename field */ + curl_formadd(&formpost, + &lastptr, + CURLFORM_COPYNAME, "client_id", + CURLFORM_COPYCONTENTS, "e7ac891eef866f139773cf8102b7a719", + CURLFORM_END); + + curl_formadd(&formpost, + &lastptr, + CURLFORM_COPYNAME, "client_secret", + CURLFORM_COPYCONTENTS, "d78f34d19f09d26731801a0cb0f382c4", + CURLFORM_END); + + curl_formadd(&formpost, + &lastptr, + CURLFORM_COPYNAME, "grant_type", + CURLFORM_COPYCONTENTS, "password", + CURLFORM_END); + + curl_formadd(&formpost, + &lastptr, + CURLFORM_COPYNAME, "username", + CURLFORM_COPYCONTENTS, username.c_str(), + CURLFORM_END); + + curl_formadd(&formpost, + &lastptr, + CURLFORM_COPYNAME, "password", + CURLFORM_COPYCONTENTS, password.c_str(), + CURLFORM_END); + + struct curl_slist *headerlist=NULL; + headerlist = curl_slist_append(headerlist, "Expect:"); + headerlist = curl_slist_append(headerlist, "Accept: application/xml"); + curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headerlist); + + /* what URL that receives this POST */ + std::string url = "https://api.soundcloud.com/oauth2/token"; + curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl_handle, CURLOPT_HTTPPOST, formpost); + + // curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L); + + // perform online request + CURLcode res = curl_easy_perform(curl_handle); + if( res != 0 ) { + std::cerr << "curl error " << res << " (" << curl_easy_strerror(res) << ")" << std::endl; + return ""; + } + + if(xml_page.memory){ + //cheesy way to parse the json return value. find access_token, then advance 3 quotes + + if ( strstr ( xml_page.memory , "access_token" ) == NULL) { + error << _("Upload to Soundcloud failed. Perhaps your email or password are incorrect?\n") << endmsg; + return ""; + } + + std::string token = strtok( xml_page.memory, "access_token" ); + token = strtok( NULL, "\"" ); + token = strtok( NULL, "\"" ); + token = strtok( NULL, "\"" ); + + free( xml_page.memory ); + return token; + } + + return ""; +} + +int +SoundcloudUploader::progress_callback(void *caller, double dltotal, double dlnow, double ultotal, double ulnow) +{ + SoundcloudUploader *scu = (SoundcloudUploader *) caller; + std::cerr << scu->title << ": uploaded " << ulnow << " of " << ultotal << std::endl; + scu->caller->SoundcloudProgress(ultotal, ulnow, scu->title); /* EMIT SIGNAL */ + return 0; +} + + +std::string +SoundcloudUploader::Upload(std::string file_path, std::string title, std::string token, bool ispublic, ARDOUR::ExportHandler *caller) +{ + int still_running; + + struct MemoryStruct xml_page; + xml_page.memory = NULL; + xml_page.size = 0; + + setcUrlOptions(); + + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &xml_page); + + struct curl_httppost *formpost=NULL; + struct curl_httppost *lastptr=NULL; + + /* Fill in the file upload field. This makes libcurl load data from + the given file name when curl_easy_perform() is called. */ + curl_formadd(&formpost, + &lastptr, + CURLFORM_COPYNAME, "track[asset_data]", + CURLFORM_FILE, file_path.c_str(), + CURLFORM_END); + + /* Fill in the filename field */ + curl_formadd(&formpost, + &lastptr, + CURLFORM_COPYNAME, "oauth_token", + CURLFORM_COPYCONTENTS, token.c_str(), + CURLFORM_END); + + curl_formadd(&formpost, + &lastptr, + CURLFORM_COPYNAME, "track[title]", + CURLFORM_COPYCONTENTS, title.c_str(), + CURLFORM_END); + + curl_formadd(&formpost, + &lastptr, + CURLFORM_COPYNAME, "track[sharing]", + CURLFORM_COPYCONTENTS, ispublic ? "public" : "private", + CURLFORM_END); + + /* initalize custom header list (stating that Expect: 100-continue is not + wanted */ + struct curl_slist *headerlist=NULL; + static const char buf[] = "Expect:"; + headerlist = curl_slist_append(headerlist, buf); + + + if (curl_handle && multi_handle) { + + /* what URL that receives this POST */ + std::string url = "https://api.soundcloud.com/tracks"; + curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str()); + // curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L); + + curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headerlist); + curl_easy_setopt(curl_handle, CURLOPT_HTTPPOST, formpost); + + this->title = title; // save title to show in progress bar + this->caller = caller; + + curl_easy_setopt (curl_handle, CURLOPT_NOPROGRESS, 0); // turn on the progress bar + curl_easy_setopt (curl_handle, CURLOPT_PROGRESSFUNCTION, &SoundcloudUploader::progress_callback); + curl_easy_setopt (curl_handle, CURLOPT_PROGRESSDATA, this); + + curl_multi_add_handle(multi_handle, curl_handle); + + curl_multi_perform(multi_handle, &still_running); + + + while(still_running) { + struct timeval timeout; + int rc; /* select() return code */ + + fd_set fdread; + fd_set fdwrite; + fd_set fdexcep; + int maxfd = -1; + + long curl_timeo = -1; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + FD_ZERO(&fdexcep); + + /* set a suitable timeout to play around with */ + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + curl_multi_timeout(multi_handle, &curl_timeo); + if(curl_timeo >= 0) { + timeout.tv_sec = curl_timeo / 1000; + if(timeout.tv_sec > 1) + timeout.tv_sec = 1; + else + timeout.tv_usec = (curl_timeo % 1000) * 1000; + } + + /* get file descriptors from the transfers */ + curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd); + + /* In a real-world program you OF COURSE check the return code of the + function calls. On success, the value of maxfd is guaranteed to be + greater or equal than -1. We call select(maxfd + 1, ...), specially in + case of (maxfd == -1), we call select(0, ...), which is basically equal + to sleep. */ + + rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout); + + switch(rc) { + case -1: + /* select error */ + break; + case 0: + default: + /* timeout or readable/writable sockets */ + curl_multi_perform(multi_handle, &still_running); + break; + } + } + + /* then cleanup the formpost chain */ + curl_formfree(formpost); + + /* free slist */ + curl_slist_free_all (headerlist); + } + + curl_easy_setopt (curl_handle, CURLOPT_NOPROGRESS, 1); // turn off the progress bar + + if(xml_page.memory){ + + std::cout << xml_page.memory << std::endl; + + XMLTree doc; + doc.read_buffer( xml_page.memory ); + XMLNode *root = doc.root(); + + if (!root) { + std::cout << "no root XML node!" << std::endl; + return ""; + } + + XMLNode *url_node = root->child("permalink-url"); + if (!url_node) { + std::cout << "no child node \"permalink-url\" found!" << std::endl; + return ""; + } + + XMLNode *text_node = url_node->child("text"); + if (!text_node) { + std::cout << "no text node found!" << std::endl; + return ""; + } + + free( xml_page.memory ); + return text_node->content(); + } + + return ""; +}; + + +SoundcloudUploader:: ~SoundcloudUploader() +{ + curl_easy_cleanup(curl_handle); + curl_multi_cleanup(multi_handle); +} + + +void +SoundcloudUploader::setcUrlOptions() +{ + // basic init for curl + curl_global_init(CURL_GLOBAL_ALL); + // some servers don't like requests that are made without a user-agent field, so we provide one + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); + // setup curl error buffer + curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, errorBuffer); + // Allow redirection + curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); + + // Allow connections to time out (without using signals) + curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 30); + + curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0); +} + diff --git a/libs/ardour/system_exec.cc b/libs/ardour/system_exec.cc index 90e729a7f6..760a9b7878 100644 --- a/libs/ardour/system_exec.cc +++ b/libs/ardour/system_exec.cc @@ -65,4 +65,14 @@ SystemExec::SystemExec (std::string c, std::string a) #endif } +SystemExec::SystemExec (std::string c, const std::map subs) + : PBD::SystemExec(c, subs) +{ +#ifndef PLATFORM_WINDOWS + if (!_vfork_exec_wrapper) { + _vfork_exec_wrapper = vfork_exec_wrapper_path(); + } +#endif +} + SystemExec::~SystemExec() { } diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 4a50064545..85326e98f8 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -194,6 +194,7 @@ libardour_sources = [ 'sndfile_helpers.cc', 'sndfileimportable.cc', 'sndfilesource.cc', + 'soundcloud_upload.cc', 'source.cc', 'source_factory.cc', 'speakers.cc', diff --git a/libs/pbd/pbd/system_exec.h b/libs/pbd/pbd/system_exec.h index 1232175fec..ce6e5a9c4f 100644 --- a/libs/pbd/pbd/system_exec.h +++ b/libs/pbd/pbd/system_exec.h @@ -42,6 +42,8 @@ #include #include #include +#include + #ifdef NOPBD /* unit-test outside ardour */ #include #include @@ -94,6 +96,23 @@ class LIBPBD_API SystemExec * */ SystemExec (std::string c, char ** a); + + /** similar to \ref SystemExec but expects a whole command line, and + * handles some simple escape sequences. + * + * @param command complete command-line to be executed + * @param subs a map of listing the % substitutions to + * be made. + * + * creates an argv array from the given command string, splitting into + * parameters at spaces. + * "\ " is non-splitting space, "\\" (and "\" at end of command) as "\", + * for "%", is looked up in subs and the corresponding string + * substituted. "%%" (and "%" at end of command) + * returns an argv array suitable for creating a new SystemExec with + */ + SystemExec (std::string command, const std::map subs); + virtual ~SystemExec (); /** fork and execute the given program @@ -182,6 +201,7 @@ class LIBPBD_API SystemExec int nicelevel; ///< process nice level - defaults to 0 void make_argp(std::string); + void make_argp_escaped(std::string command, const std::map subs); void make_envp(); char **argp; @@ -198,6 +218,7 @@ class LIBPBD_API SystemExec #else pid_t pid; #endif + void init (); 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. diff --git a/libs/pbd/system_exec.cc b/libs/pbd/system_exec.cc index 0102323505..82398af0c8 100644 --- a/libs/pbd/system_exec.cc +++ b/libs/pbd/system_exec.cc @@ -151,9 +151,8 @@ static int close_allv(const int except_fds[]) { } #endif /* not on windows, nor vfork */ - -SystemExec::SystemExec (std::string c, std::string a) - : cmd(c) +void +SystemExec::init () { pthread_mutex_init(&write_lock, NULL); thread_active=false; @@ -161,12 +160,19 @@ SystemExec::SystemExec (std::string c, std::string a) pin[1] = -1; nicelevel = 0; envp = NULL; - argp = NULL; #ifdef PLATFORM_WINDOWS stdinP[0] = stdinP[1] = INVALID_HANDLE_VALUE; stdoutP[0] = stdoutP[1] = INVALID_HANDLE_VALUE; stderrP[0] = stderrP[1] = INVALID_HANDLE_VALUE; #endif +} + +SystemExec::SystemExec (std::string c, std::string a) + : cmd(c) +{ + init (); + + argp = NULL; make_envp(); make_argp(a); } @@ -174,21 +180,101 @@ SystemExec::SystemExec (std::string c, std::string 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; + init (); + #ifdef PLATFORM_WINDOWS - 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 (std::string command, const std::map subs) +{ + init (); + make_argp_escaped(command, subs); + cmd = argp[0]; + // cmd = strdup(argp[0]); + make_envp(); +} + +void +SystemExec::make_argp_escaped(std::string command, const std::map subs) +{ + + int inquotes = 0; + int n = 0; + size_t i = 0; + std::string arg = ""; + + argp = (char **) malloc(sizeof(char *)); + + for (i = 0; i <= command.length(); i++) { // include terminating '\0' + char c = command.c_str()[i]; + if (inquotes) { + if (c == '"') { + inquotes = 0; + } else { + // still in quotes - just copy + arg += c; + } + } else switch (c) { + case '%' : + c = command.c_str()[++i]; + if (c == '%' || c == '\0') { + // "%%", "%" at end-of-string => "%" + arg += '%'; + } else { + // search subs for string to substitute for char + std::map::const_iterator s = subs.find(c); + if (s != subs.end()) { + // found substitution + arg += s->second; + } else { + // not a valid substitution, just copy + arg += '%'; + arg += c; + } + } + break; + case '\\': + c = command.c_str()[++i]; + switch (c) { + case ' ' : + case '"' : arg += c; break; // "\\", "\" at end-of-string => "\" + case '\0': + case '\\': arg += '\\'; break; + default : arg += '\\'; arg += c; break; + } + break; + case '"' : + inquotes = 1; + break; + case ' ' : + case '\t': + case '\0': + if (arg.length() > 0) { + // if there wasn't already a space or tab, start a new parameter + argp = (char **) realloc(argp, (n + 2) * sizeof(char *)); + argp[n++] = strdup (arg.c_str()); + arg = ""; + } + break; + default : + arg += c; + break; + } + } + argp[n] = NULL; + + char *p = argp[0]; + n = 0; + do { + std::cerr << "argv[" << n << "] == \"" << p << "\"" << std::endl; + p = argp[n++]; + } while (p); + +} + SystemExec::~SystemExec () { terminate (); @@ -522,7 +608,7 @@ SystemExec::make_argp(std::string args) { *cp2 = '\0'; argp[argn++] = strdup(cp1); cp1 = cp2 + 1; - argp = (char **) realloc(argp, (argn + 1) * sizeof(char *)); + argp = (char **) realloc(argp, (argn + 1) * sizeof(char *)); } } if (cp2 != cp1) {