13
0

freesound: update to API v2

Add a new client id & token for Ardour 7, and update to Freesound's API
v2.

Implement OAuth authentication for Freesound downloads, as described at:
https://freesound.org/docs/api/authentication.html#oauth-authentication

Open the Freesound login page in the default browser, so that the user
can log in and get an authorization code, to copy-&-paste from the
browser. Exchange this authorization code for an access token, and use
it in a custom 'Authorization: Bearer $TOKEN' http header.

If logging in to Freesound to download a file fails or is cancelled,
clear the 'downloading' flag for that file in the list so that a
subsequent click on it will try to log in again.

Show login progress in download progress bar, and disable preview if
file hasn't yet been downloaded.

If a download fails for any reason (except the user cancelling it),
report an error in the Log window.

Use curl_free() for pointers returned by curl_easy_escape(), as per the
curl documentation, rather than plain free().

Also, don't use the www. sub-domain of freesound.org: although it
appears to work for most things, it returns an empty document from
https://freesound.org/apiv2/oauth2/access_token/

Remove default empty token value from Mootcher constructor, to make it
explicit when we construct a Mootcher that doesn't require
authorisation, by requiring an empty token parameter in that case.
This commit is contained in:
Colin Fletcher 2022-05-16 18:10:34 +01:00
parent 84267cfa32
commit 9fe0a4f4dd
4 changed files with 314 additions and 125 deletions

View File

@ -2,11 +2,12 @@
Adapted for Ardour by Ben Loftis, March 2008 Adapted for Ardour by Ben Loftis, March 2008
Updated to new Freesound API by Colin Fletcher, November 2011 Updated to new Freesound API by Colin Fletcher, November 2011
Updated to Freesound API v2 by Colin Fletcher, May 2022
Mootcher 23-8-2005 Mootcher 23-8-2005
Mootcher Online Access to thefreesoundproject website Mootcher Online Access to thefreesoundproject website
http://freesound.iua.upf.edu/ http://freesound.org/
GPL 2005 Jorn Lemon GPL 2005 Jorn Lemon
mail for questions/remarks: mootcher@twistedlemon.nl mail for questions/remarks: mootcher@twistedlemon.nl
@ -53,19 +54,35 @@
#include "pbd/i18n.h" #include "pbd/i18n.h"
#include "ardour/audio_library.h" #include "ardour/audio_library.h"
#include "ardour/debug.h"
#include "ardour/filesystem_paths.h"
#include "ardour/rc_configuration.h" #include "ardour/rc_configuration.h"
#include "pbd/pthread_utils.h" #include "pbd/pthread_utils.h"
#include "pbd/openuri.h"
#include "ardour_dialog.h"
#include "gui_thread.h" #include "gui_thread.h"
#include "widgets/prompter.h"
using namespace PBD; using namespace PBD;
static const std::string base_url = "http://www.freesound.org/api"; // freesound API URLs are always https://, and don't include the www. subdomain
static const std::string api_key = "9d77cb8d841b4bcfa960e1aae62224eb"; // ardour3 static const std::string base_url = "https://freesound.org/apiv2";
// Ardour 7
static const std::string default_token = "t3TjQ67WNh6zJLZRnWmArSiZ8bKlgTc2aEsV1cP7";
static const std::string client_id = "yesyr1g4StTtg2F50KT1";
static const std::string fields = "id,name,duration,filesize,samplerate,license,download,previews";
//------------------------------------------------------------------------ //------------------------------------------------------------------------
Mootcher::Mootcher() Mootcher::Mootcher(const std::string &the_token)
: curl(curl_easy_init()) : curl(curl_easy_init())
{ {
DEBUG_TRACE(PBD::DEBUG::Freesound, "Created new Mootcher, oauth_token =\"" + the_token + "\"\n");
custom_headers = NULL;
oauth_token = the_token;
cancel_download_btn.set_label (_("Cancel")); cancel_download_btn.set_label (_("Cancel"));
progress_hbox.pack_start (progress_bar, true, true); progress_hbox.pack_start (progress_bar, true, true);
progress_hbox.pack_end (cancel_download_btn, false, false); progress_hbox.pack_end (cancel_download_btn, false, false);
@ -77,6 +94,10 @@ Mootcher::Mootcher()
Mootcher:: ~Mootcher() Mootcher:: ~Mootcher()
{ {
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
if (custom_headers) {
curl_slist_free_all (custom_headers);
}
DEBUG_TRACE(PBD::DEBUG::Freesound, "Destroyed Mootcher\n");
} }
//------------------------------------------------------------------------ //------------------------------------------------------------------------
@ -85,6 +106,7 @@ void Mootcher::ensureWorkingDir ()
{ {
std::string p = ARDOUR::Config->get_freesound_download_dir(); std::string p = ARDOUR::Config->get_freesound_download_dir();
DEBUG_TRACE(PBD::DEBUG::Freesound, "ensureWorkingDir() - " + p + "\n");
if (!Glib::file_test (p, Glib::FILE_TEST_IS_DIR)) { if (!Glib::file_test (p, Glib::FILE_TEST_IS_DIR)) {
if (g_mkdir_with_parents (p.c_str(), 0775) != 0) { if (g_mkdir_with_parents (p.c_str(), 0775) != 0) {
PBD::error << "Unable to create Mootcher working dir" << endmsg; PBD::error << "Unable to create Mootcher working dir" << endmsg;
@ -127,14 +149,14 @@ std::string Mootcher::sortMethodString(enum sortMethod sort)
// sort the results in the requested way. // sort the results in the requested way.
switch (sort) { switch (sort) {
case sort_duration_desc: return "duration_desc"; case sort_duration_descending: return "duration_desc";
case sort_duration_asc: return "duration_asc"; case sort_duration_ascending: return "duration_asc";
case sort_created_desc: return "created_desc"; case sort_created_descending: return "created_desc";
case sort_created_asc: return "created_asc"; case sort_created_ascending: return "created_asc";
case sort_downloads_desc: return "downloads_desc"; case sort_downloads_descending: return "downloads_desc";
case sort_downloads_asc: return "downloads_asc"; case sort_downloads_ascending: return "downloads_asc";
case sort_rating_desc: return "rating_desc"; case sort_rating_descending: return "rating_desc";
case sort_rating_asc: return "rating_asc"; case sort_rating_ascending: return "rating_asc";
default: return ""; default: return "";
} }
} }
@ -152,8 +174,6 @@ void Mootcher::setcUrlOptions()
// Allow connections to time out (without using signals) // Allow connections to time out (without using signals)
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30);
} }
std::string Mootcher::doRequest(std::string uri, std::string params) std::string Mootcher::doRequest(std::string uri, std::string params)
@ -167,24 +187,24 @@ std::string Mootcher::doRequest(std::string uri, std::string params)
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &xml_page); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &xml_page);
// curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
// curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postMessage.c_str());
// curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, -1);
// the url to get // the url to get
std::string url = base_url + uri + "?"; std::string url = base_url + uri + "?";
if (params != "") { if (params != "") {
url += params + "&api_key=" + api_key + "&format=xml"; url += params + "&token=" + default_token + "&format=xml";
} else { } else {
url += "api_key=" + api_key + "&format=xml"; url += "token=" + default_token + "&format=xml";
} }
curl_easy_setopt(curl, CURLOPT_URL, url.c_str() ); curl_easy_setopt(curl, CURLOPT_URL, url.c_str() );
DEBUG_TRACE(PBD::DEBUG::Freesound, "doRequest() " + url + "\n");
// perform online request // perform online request
CURLcode res = curl_easy_perform(curl); CURLcode res = curl_easy_perform(curl);
if( res != 0 ) { if( res != 0 ) {
error << string_compose (_("curl error %1 (%2)"), res, curl_easy_strerror(res)) << endmsg; std::string errmsg = string_compose (_("curl error %1 (%2)"), res, curl_easy_strerror(res));
error << errmsg << endmsg;
DEBUG_TRACE(PBD::DEBUG::Freesound, errmsg + "\n");
return ""; return "";
} }
@ -197,6 +217,7 @@ std::string Mootcher::doRequest(std::string uri, std::string params)
xml_page.memory = NULL; xml_page.memory = NULL;
xml_page.size = 0; xml_page.size = 0;
DEBUG_TRACE(PBD::DEBUG::Freesound, result + "\n");
return result; return result;
} }
@ -205,55 +226,158 @@ std::string Mootcher::searchSimilar(std::string id)
{ {
std::string params = ""; std::string params = "";
params += "&fields=id,original_filename,duration,filesize,samplerate,license,serve"; params += "&fields=" + fields;
params += "&num_results=100"; params += "&num_results=100";
// XXX should we filter out MP3s here, too?
// XXX and what if there are more than 100 similar sounds?
return doRequest("/sounds/" + id + "/similar", params); return doRequest("/sounds/" + id + "/similar/", params);
} }
//------------------------------------------------------------------------ //------------------------------------------------------------------------
void
Mootcher::report_login_error(const std::string &msg)
{
DEBUG_TRACE(PBD::DEBUG::Freesound, "Login failed:" + msg + "\n");
error << "Freesound login failed: " << msg << endmsg;
}
bool
Mootcher::get_oauth_token()
{
std::string oauth_url = base_url + "/oauth2/authorize/?client_id="+client_id+"&response_type=code&state=hello";
std::string auth_code;
/* use the user's default browser to get an authorization token */
if (!PBD::open_uri (oauth_url)) {
report_login_error ("cannot open " + oauth_url);
return false;
}
ArdourWidgets::Prompter token_entry(true);
token_entry.set_prompt(_("Please log in to Freesound in the browser window that's just been opened, and paste the authorization code here"));
token_entry.set_title(_("Authorization Code"));
token_entry.set_name ("TokenEntryWindow");
// token_entry.set_size_request (250, -1);
token_entry.set_position (Gtk::WIN_POS_MOUSE);
token_entry.add_button (Gtk::Stock::OK, Gtk::RESPONSE_ACCEPT);
token_entry.show ();
if (token_entry.run () != Gtk::RESPONSE_ACCEPT)
return false;
token_entry.get_result(auth_code);
if (auth_code == "")
return false;
oauth_token = auth_code_to_oauth_token(auth_code);
setcUrlOptions();
return oauth_token != "";;
}
std::string Mootcher::auth_code_to_oauth_token(const std::string &auth_code)
{
struct SfdbMemoryStruct json_page;
json_page.memory = NULL;
json_page.size = 0;
CURLcode res;
setcUrlOptions();
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &json_page);
std::string oauth_url = base_url + "/oauth2/access_token/";
std::string new_oauth_token;
curl_easy_setopt(curl, CURLOPT_URL, oauth_url.c_str());
curl_easy_setopt(curl, CURLOPT_POST, 5);
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS,
("client_id=" + client_id +
"&client_secret=" + default_token +
"&grant_type=authorization_code" +
"&code=" + auth_code).c_str());
progress_bar.set_text(_("Fetching Access Token (auth_code=") + auth_code + "...");
while (gtk_events_pending()) gtk_main_iteration (); // allow the progress bar text to update
res = curl_easy_perform (curl);
if (res != CURLE_OK) {
if (res != CURLE_ABORTED_BY_CALLBACK) {
report_login_error (string_compose ("curl failed: %1, error=%2", oauth_url, res));
}
return "";
}
if (!json_page.memory) {
report_login_error (string_compose ("curl returned nothing, url=%1!", oauth_url));
return "";
}
std::string access_token_json_str = json_page.memory;
free (json_page.memory);
json_page.memory = NULL;
json_page.size = 0;
DEBUG_TRACE(PBD::DEBUG::Freesound, access_token_json_str);
// one of these days ardour's gonna need a proper JSON parser...
size_t token_pos = access_token_json_str.find ("access_token");
oauth_token = access_token_json_str.substr (token_pos + 16, 30);
// we've set a bunch of curl options - reset the important ones now
curl_easy_setopt(curl, CURLOPT_POST, 0);
DEBUG_TRACE(PBD::DEBUG::Freesound, "oauth_token is :" + oauth_token + "\n");
return oauth_token;
}
std::string Mootcher::searchText(std::string query, int page, std::string filter, enum sortMethod sort) std::string Mootcher::searchText(std::string query, int page, std::string filter, enum sortMethod sort)
{ {
std::string params = ""; std::string params = "";
char buf[24]; char buf[24];
if (page > 1) { if (page > 1) {
snprintf(buf, 23, "p=%d&", page); snprintf(buf, 23, "page=%d&", page);
params += buf; params += buf;
} }
char *eq = curl_easy_escape(curl, query.c_str(), query.length()); char *eq = curl_easy_escape(curl, query.c_str(), query.length());
params += "q=\"" + std::string(eq) + "\""; params += "query=\"" + std::string(eq) + "\"";
free(eq); curl_free(eq);
if (filter != "") { if (filter != "") {
char *ef = curl_easy_escape(curl, filter.c_str(), filter.length()); char *ef = curl_easy_escape(curl, filter.c_str(), filter.length());
params += "&f=" + std::string(ef); params += "&filter=" + std::string(ef);
free(ef); curl_free(ef);
} }
if (sort) if (sort)
params += "&s=" + sortMethodString(sort); params += "&sort=" + sortMethodString(sort);
params += "&fields=id,original_filename,duration,filesize,samplerate,license,serve"; params += "&fields=" + fields;
params += "&sounds_per_page=100"; params += "&page_size=100";
return doRequest("/sounds/search", params); return doRequest("/search/text/", params);
} }
//------------------------------------------------------------------------ //------------------------------------------------------------------------
std::string Mootcher::getSoundResourceFile(std::string ID) std::string Mootcher::getSoundResourceFile(std::string ID)
{ {
/* get the resource file for the sound with given ID.
* return the file name of the sound
*/
std::string originalSoundURI; std::string originalSoundURI;
std::string audioFileName; std::string audioFileName;
std::string xml; std::string xml;
DEBUG_TRACE(PBD::DEBUG::Freesound, "getSoundResourceFile(" + ID + ")\n");
// download the xmlfile into xml_page // download the xmlfile into xml_page
xml = doRequest("/sounds/" + ID, ""); xml = doRequest("/sounds/" + ID + "/", "");
XMLTree doc; XMLTree doc;
doc.read_buffer( xml.c_str() ); doc.read_buffer( xml.c_str() );
@ -265,14 +389,15 @@ std::string Mootcher::getSoundResourceFile(std::string ID)
return ""; return "";
} }
if (strcmp(doc.root()->name().c_str(), "response") != 0) { if (strcmp(doc.root()->name().c_str(), "root") != 0) {
error << string_compose (_("getSoundResourceFile: root = %1, != response"), doc.root()->name()) << endmsg; error << string_compose (_("getSoundResourceFile: root = %1, != \"root\""), doc.root()->name()) << endmsg;
return ""; return "";
} }
XMLNode *name = freesound->child("original_filename"); XMLNode *name = freesound->child("name");
// get the file name and size from xml file // get the file name and size from xml file
// assert (name);
if (name) { if (name) {
audioFileName = Glib::build_filename (basePath, ID + "-" + name->child("text")->content()); audioFileName = Glib::build_filename (basePath, ID + "-" + name->child("text")->content());
@ -285,7 +410,7 @@ std::string Mootcher::getSoundResourceFile(std::string ID)
std::vector<std::string> strings; std::vector<std::string> strings;
for (niter = children.begin(); niter != children.end(); ++niter) { for (niter = children.begin(); niter != children.end(); ++niter) {
XMLNode *node = *niter; XMLNode *node = *niter;
if( strcmp( node->name().c_str(), "resource") == 0 ) { if( strcmp( node->name().c_str(), "list-item") == 0 ) {
XMLNode *text = node->child("text"); XMLNode *text = node->child("text");
if (text) { if (text) {
// std::cerr << "tag: " << text->content() << std::endl; // std::cerr << "tag: " << text->content() << std::endl;
@ -312,6 +437,7 @@ void *
Mootcher::threadFunc() { Mootcher::threadFunc() {
CURLcode res; CURLcode res;
DEBUG_TRACE(PBD::DEBUG::Freesound, "threadFunc\n");
res = curl_easy_perform (curl); res = curl_easy_perform (curl);
fclose (theFile); fclose (theFile);
curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1); // turn off the progress bar curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1); // turn off the progress bar
@ -323,10 +449,17 @@ CURLcode res;
} }
remove ( (audioFileName+".part").c_str() ); remove ( (audioFileName+".part").c_str() );
} else { } else {
rename ( (audioFileName+".part").c_str(), audioFileName.c_str() ); DEBUG_TRACE(PBD::DEBUG::Freesound, string_compose("renaming %1.part to %1\n", audioFileName));
int r = rename ( (audioFileName+".part").c_str(), audioFileName.c_str() );
if (r != 0) {
const char *err = strerror(errno);
DEBUG_TRACE(PBD::DEBUG::Freesound, string_compose("rename() failed: %1\n", err));
assert(0);
} else {
// now download the tags &c. // now download the tags &c.
getSoundResourceFile(ID); getSoundResourceFile(ID);
} }
}
return (void *) res; return (void *) res;
} }
@ -334,7 +467,6 @@ CURLcode res;
void void
Mootcher::doneWithMootcher() Mootcher::doneWithMootcher()
{ {
// update the sound info pane if the selection in the list box is still us // update the sound info pane if the selection in the list box is still us
sfb->refresh_display(ID, audioFileName); sfb->refresh_display(ID, audioFileName);
@ -347,10 +479,11 @@ freesound_download_thread_func(void *arg)
Mootcher *thisMootcher = (Mootcher *) arg; Mootcher *thisMootcher = (Mootcher *) arg;
void *res; void *res;
// std::cerr << "freesound_download_thread_func(" << arg << ")" << std::endl; DEBUG_TRACE(PBD::DEBUG::Freesound, string_compose("freesound_download_thread_func(%1)\n", arg));
res = thisMootcher->threadFunc(); res = thisMootcher->threadFunc();
DEBUG_TRACE(PBD::DEBUG::Freesound, string_compose("threadFunc returns %1\n", res));
thisMootcher->Finished(); /* EMIT SIGNAL */ thisMootcher->Finished(); /* EMIT SIGNAL */
DEBUG_TRACE(PBD::DEBUG::Freesound, string_compose("returning from freesound_download_thread_func()\n", res));
return res; return res;
} }
@ -359,6 +492,9 @@ freesound_download_thread_func(void *arg)
bool Mootcher::checkAudioFile(std::string originalFileName, std::string theID) bool Mootcher::checkAudioFile(std::string originalFileName, std::string theID)
{ {
// return true if file already exists locally and is larger than 256
// bytes, false otherwise
DEBUG_TRACE(PBD::DEBUG::Freesound, string_compose("checkAudiofile(%1, %2)\n", originalFileName, theID));
ensureWorkingDir(); ensureWorkingDir();
ID = theID; ID = theID;
audioFileName = Glib::build_filename (basePath, ID + "-" + originalFileName); audioFileName = Glib::build_filename (basePath, ID + "-" + originalFileName);
@ -369,19 +505,27 @@ bool Mootcher::checkAudioFile(std::string originalFileName, std::string theID)
fseek (testFile , 0 , SEEK_END); fseek (testFile , 0 , SEEK_END);
if (ftell (testFile) > 256) { if (ftell (testFile) > 256) {
fclose (testFile); fclose (testFile);
DEBUG_TRACE(PBD::DEBUG::Freesound, "checkAudiofile() - found " + audioFileName + "\n");
return true; return true;
} }
// else file was small, probably an error, delete it // else file was small, probably an error, delete it
DEBUG_TRACE(PBD::DEBUG::Freesound, "checkAudiofile() - " + audioFileName + " <= 256 bytes, removing it\n");
fclose (testFile); fclose (testFile);
remove( audioFileName.c_str() ); // remove (audioFileName.c_str() );
rename (audioFileName.c_str(), (audioFileName + ".bad").c_str() );
} }
DEBUG_TRACE(PBD::DEBUG::Freesound, "checkAudiofile() - not found " + audioFileName + "\n");
return false; return false;
} }
bool Mootcher::fetchAudioFile(std::string originalFileName, std::string theID, std::string audioURL, SoundFileBrowser *caller) bool
Mootcher::fetchAudioFile(std::string originalFileName, std::string theID, std::string audioURL, SoundFileBrowser *caller, std::string &token)
{ {
DEBUG_TRACE(PBD::DEBUG::Freesound, string_compose("fetchAudiofile(%1, %2, %3, ...)\n", originalFileName, theID, audioURL));
ensureWorkingDir(); ensureWorkingDir();
ID = theID; ID = theID;
audioFileName = Glib::build_filename (basePath, ID + "-" + originalFileName); audioFileName = Glib::build_filename (basePath, ID + "-" + originalFileName);
@ -389,34 +533,51 @@ bool Mootcher::fetchAudioFile(std::string originalFileName, std::string theID, s
if (!curl) { if (!curl) {
return false; return false;
} }
Gtk::VBox *freesound_vbox = dynamic_cast<Gtk::VBox *> (caller->notebook.get_nth_page(2));
freesound_vbox->pack_start(progress_hbox, Gtk::PACK_SHRINK);
cancel_download = false;
curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0); // turn on the progress bar
curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, progress_callback);
curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, this);
if (oauth_token == "") {
if (!get_oauth_token()) {
DEBUG_TRACE(PBD::DEBUG::Freesound, "get_oauth_token() failed!\n");
return false;
}
}
token = oauth_token;
// now download the actual file // now download the actual file
theFile = g_fopen( (audioFileName + ".part").c_str(), "wb" ); theFile = g_fopen( (audioFileName + ".part").c_str(), "wb" );
if (!theFile) { if (!theFile) {
DEBUG_TRACE(PBD::DEBUG::Freesound, "Can't open file for writing:" + audioFileName + ".part\n");
return false; return false;
} }
// create the download url // create the download url
audioURL += "?api_key=" + api_key; audioURL += "?token=" + default_token;
setcUrlOptions(); setcUrlOptions();
std::string auth_header = "Authorization: Bearer " + oauth_token;
DEBUG_TRACE(PBD::DEBUG::Freesound, "auth_header = " + auth_header + "\n");
custom_headers = curl_slist_append (custom_headers, auth_header.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, custom_headers);
curl_easy_setopt(curl, CURLOPT_URL, audioURL.c_str() ); curl_easy_setopt(curl, CURLOPT_URL, audioURL.c_str() );
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, audioFileWrite); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, audioFileWrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, theFile); curl_easy_setopt(curl, CURLOPT_WRITEDATA, theFile);
DEBUG_TRACE(PBD::DEBUG::Freesound, "Downloading audio from " + audioURL + " into " + audioFileName + ".part\n");
std::string prog; std::string prog;
prog = string_compose (_("%1"), originalFileName); prog = string_compose (_("%1"), originalFileName);
progress_bar.set_text(prog); progress_bar.set_text(prog);
Gtk::VBox *freesound_vbox = dynamic_cast<Gtk::VBox *> (caller->notebook.get_nth_page(2));
freesound_vbox->pack_start(progress_hbox, Gtk::PACK_SHRINK);
progress_hbox.show(); progress_hbox.show();
cancel_download = false;
sfb = caller;
curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0); // turn on the progress bar sfb = caller;
curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, progress_callback);
curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, this);
Progress.connect(*this, invalidator (*this), boost::bind(&Mootcher::updateProgress, this, _1, _2), gui_context()); Progress.connect(*this, invalidator (*this), boost::bind(&Mootcher::updateProgress, this, _1, _2), gui_context());
Finished.connect(*this, invalidator (*this), boost::bind(&Mootcher::doneWithMootcher, this), gui_context()); Finished.connect(*this, invalidator (*this), boost::bind(&Mootcher::doneWithMootcher, this), gui_context());
@ -433,7 +594,7 @@ Mootcher::updateProgress(double dlnow, double dltotal)
{ {
if (dltotal > 0) { if (dltotal > 0) {
double fraction = dlnow / dltotal; double fraction = dlnow / dltotal;
// std::cerr << "progress idle: " << progress->bar->get_text() << ". " << progress->dlnow << " / " << progress->dltotal << " = " << fraction << std::endl; // std::cerr << "progress idle: " << progress_bar.get_text() << ". " << dlnow << " / " << dltotal << " = " << fraction << std::endl;
if (fraction > 1.0) { if (fraction > 1.0) {
fraction = 1.0; fraction = 1.0;
} else if (fraction < 0.0) { } else if (fraction < 0.0) {

View File

@ -58,25 +58,25 @@ struct SfdbMemoryStruct {
enum sortMethod { enum sortMethod {
sort_none, // no sort sort_none, // no sort
sort_duration_desc, // Sort by the duration of the sounds, longest sounds first. sort_duration_descending, // Sort by the duration of the sounds, longest sounds first.
sort_duration_asc, // Same as above, but shortest sounds first. sort_duration_ascending, // Same as above, but shortest sounds first.
sort_created_desc, // Sort by the date of when the sound was added. newest sounds first. sort_created_descending, // Sort by the date of when the sound was added. newest sounds first.
sort_created_asc, // Same as above, but oldest sounds first. sort_created_ascending, // Same as above, but oldest sounds first.
sort_downloads_desc, // Sort by the number of downloads, most downloaded sounds first. sort_downloads_descending, // Sort by the number of downloads, most downloaded sounds first.
sort_downloads_asc, // Same as above, but least downloaded sounds first. sort_downloads_ascending, // Same as above, but least downloaded sounds first.
sort_rating_desc, // Sort by the average rating given to the sounds, highest rated first. sort_rating_descending, // Sort by the average rating given to the sounds, highest rated first.
sort_rating_asc // Same as above, but lowest rated sounds first. sort_rating_ascending // Same as above, but lowest rated sounds first.
}; };
class Mootcher: public sigc::trackable, public PBD::ScopedConnectionList class Mootcher: public sigc::trackable, public PBD::ScopedConnectionList
{ {
public: public:
Mootcher(); Mootcher(const std::string &token);
~Mootcher(); ~Mootcher();
bool checkAudioFile(std::string originalFileName, std::string ID); bool checkAudioFile(std::string originalFileName, std::string ID);
bool fetchAudioFile(std::string originalFileName, std::string ID, std::string audioURL, SoundFileBrowser *caller); bool fetchAudioFile(std::string originalFileName, std::string ID, std::string audioURL, SoundFileBrowser *caller, std::string &token);
std::string searchText(std::string query, int page, std::string filter, enum sortMethod sort); std::string searchText(std::string query, int page, std::string filter, enum sortMethod sort);
std::string searchSimilar(std::string id); std::string searchSimilar(std::string id);
void* threadFunc(); void* threadFunc();
@ -95,6 +95,9 @@ public:
private: private:
void ensureWorkingDir (); void ensureWorkingDir ();
bool get_oauth_token();
std::string auth_code_to_oauth_token(const std::string &auth_code);
std::string doRequest(std::string uri, std::string params); std::string doRequest(std::string uri, std::string params);
void setcUrlOptions(); void setcUrlOptions();
@ -110,6 +113,7 @@ private:
void updateProgress(double dlnow, double dltotal); void updateProgress(double dlnow, double dltotal);
void doneWithMootcher(); void doneWithMootcher();
void report_login_error(const std::string &msg);
Gtk::HBox progress_hbox; Gtk::HBox progress_hbox;
Gtk::ProgressBar progress_bar; Gtk::ProgressBar progress_bar;
@ -123,6 +127,8 @@ private:
std::string basePath; std::string basePath;
std::string xmlLocation; std::string xmlLocation;
std::string oauth_token;
struct curl_slist *custom_headers;
}; };
#endif // __gtk_ardour_sfdb_freesound_mootcher_h__ #endif // __gtk_ardour_sfdb_freesound_mootcher_h__

View File

@ -49,6 +49,9 @@
#include <gtkmm/scrolledwindow.h> #include <gtkmm/scrolledwindow.h>
#include <gtkmm/stock.h> #include <gtkmm/stock.h>
#include "ardour/debug.h"
#include "pbd/convert.h"
#include "pbd/tokenizer.h" #include "pbd/tokenizer.h"
#include "pbd/enumwriter.h" #include "pbd/enumwriter.h"
#include "pbd/file_utils.h" #include "pbd/file_utils.h"
@ -712,7 +715,6 @@ SoundFileBrowser::SoundFileBrowser (string title, ARDOUR::Session* s, bool persi
VBox* vbox; VBox* vbox;
HBox* hbox; HBox* hbox;
hbox = manage(new HBox); hbox = manage(new HBox);
hbox->pack_start (found_entry); hbox->pack_start (found_entry);
hbox->pack_start (found_search_btn); hbox->pack_start (found_search_btn);
@ -1063,25 +1065,33 @@ std::string
SoundFileBrowser::freesound_get_audio_file(Gtk::TreeIter iter) SoundFileBrowser::freesound_get_audio_file(Gtk::TreeIter iter)
{ {
Mootcher *mootcher = new Mootcher; Mootcher *mootcher = new Mootcher(freesound_token);
std::string file; std::string file;
string id = (*iter)[freesound_list_columns.id]; string id = (*iter)[freesound_list_columns.id];
string uri = (*iter)[freesound_list_columns.uri]; string uri = (*iter)[freesound_list_columns.uri];
string ofn = (*iter)[freesound_list_columns.filename]; string filename = (*iter)[freesound_list_columns.filename];
if (mootcher->checkAudioFile(ofn, id)) { if (mootcher->checkAudioFile(filename, id)) {
// file already exists, no need to download it again // file already exists, no need to download it again
file = mootcher->audioFileName; file = mootcher->audioFileName;
delete mootcher; delete mootcher;
(*iter)[freesound_list_columns.started] = false; (*iter)[freesound_list_columns.downloading] = false;
return file; return file;
} }
if (!(*iter)[freesound_list_columns.started]) {
if (!(*iter)[freesound_list_columns.downloading]) {
// start downloading the sound file // start downloading the sound file
(*iter)[freesound_list_columns.started] = true; DEBUG_TRACE(PBD::DEBUG::Freesound, string_compose("downloading %1 (id %2) from %3...\n", filename, id, uri));
mootcher->fetchAudioFile(ofn, id, uri, this); (*iter)[freesound_list_columns.downloading] = true;
// if we don't already have a token, fetchAudioFile() will get
// one: otherwse it'll return the one we already gave it.
if (!mootcher->fetchAudioFile(filename, id, uri, this, freesound_token)) {
// download cancelled or failed
(*iter)[freesound_list_columns.downloading] = false;
} }
}
return ""; return "";
} }
@ -1111,6 +1121,10 @@ SoundFileBrowser::freesound_list_view_selected ()
chooser.set_filename (file); chooser.set_filename (file);
preview.setup_labels (file); preview.setup_labels (file);
set_action_sensitive (true); set_action_sensitive (true);
} else {
// file doesn't exist yet, maybe still downloading:
// disable preview
preview.setup_labels ("");
} }
freesound_similar_btn.set_sensitive (true); freesound_similar_btn.set_sensitive (true);
break; break;
@ -1167,7 +1181,7 @@ SoundFileBrowser::freesound_similar_clicked ()
{ {
ListPath rows = freesound_list_view.get_selection()->get_selected_rows (); ListPath rows = freesound_list_view.get_selection()->get_selected_rows ();
if (rows.size() == 1) { if (rows.size() == 1) {
Mootcher mootcher; Mootcher mootcher(""); // no need for oauth token when searching
string id; string id;
Gtk::TreeIter iter = freesound_list->get_iter(*rows.begin()); Gtk::TreeIter iter = freesound_list->get_iter(*rows.begin());
id = (*iter)[freesound_list_columns.id]; id = (*iter)[freesound_list_columns.id];
@ -1188,7 +1202,7 @@ SoundFileBrowser::freesound_similar_clicked ()
void void
SoundFileBrowser::freesound_search() SoundFileBrowser::freesound_search()
{ {
Mootcher mootcher; Mootcher mootcher(""); // no need for oauth token when searching
string search_string = freesound_entry.get_text (); string search_string = freesound_entry.get_text ();
enum sortMethod sort_method = (enum sortMethod) freesound_sort.get_active_row_number(); enum sortMethod sort_method = (enum sortMethod) freesound_sort.get_active_row_number();
@ -1204,7 +1218,7 @@ SoundFileBrowser::freesound_search()
#ifdef __APPLE__ #ifdef __APPLE__
"", // OSX eats anything incl mp3 "", // OSX eats anything incl mp3
#else #else
"type:wav OR type:aiff OR type:flac OR type:aif OR type:ogg OR type:oga", "type:(wav OR aiff OR flac OR aif OR ogg OR oga)",
#endif #endif
sort_method sort_method
); );
@ -1224,14 +1238,14 @@ SoundFileBrowser::handle_freesound_results(std::string theString) {
return; return;
} }
if ( strcmp(root->name().c_str(), "response") != 0) { if ( strcmp(root->name().c_str(), "root") != 0) {
error << string_compose ("root node name == %1 != \"response\"", root->name()) << endmsg; error << string_compose ("root node name == %1 != \"root\"", root->name()) << endmsg;
return; return;
} }
// find out how many pages are available to search // find out how many pages are available to search
int freesound_n_pages = 1; int freesound_n_pages = 1;
XMLNode *res = root->child("num_pages"); XMLNode *res = root->child("count");
if (res) { if (res) {
string result = res->child("text")->content(); string result = res->child("text")->content();
freesound_n_pages = atoi(result); freesound_n_pages = atoi(result);
@ -1250,9 +1264,9 @@ SoundFileBrowser::handle_freesound_results(std::string theString) {
freesound_more_btn.set_tooltip_text(_("No more results available")); freesound_more_btn.set_tooltip_text(_("No more results available"));
} }
XMLNode *sounds_root = root->child("sounds"); XMLNode *sounds_root = root->child("results");
if (!sounds_root) { if (!sounds_root) {
error << "no child node \"sounds\" found!" << endmsg; error << "no child node \"results\" found!" << endmsg;
return; return;
} }
@ -1266,36 +1280,43 @@ SoundFileBrowser::handle_freesound_results(std::string theString) {
XMLNode *node; XMLNode *node;
for (niter = sounds.begin(); niter != sounds.end(); ++niter) { for (niter = sounds.begin(); niter != sounds.end(); ++niter) {
node = *niter; node = *niter;
if( strcmp( node->name().c_str(), "resource") != 0 ) { if( strcmp( node->name().c_str(), "list-item") != 0 ) {
error << string_compose ("node->name()=%1 != \"resource\"", node->name()) << endmsg; error << string_compose ("node->name()=%1 != \"list-item\"", node->name()) << endmsg;
break; break;
} }
// node->dump(cerr, "node:"); // node->dump(cerr, "node:");
XMLNode *id_node = node->child ("id"); XMLNode *id_node = node->child ("id");
XMLNode *uri_node = node->child ("serve"); XMLNode *uri_node;
XMLNode *ofn_node = node->child ("original_filename"); XMLNode *pre_node = node->child ("previews");
XMLNode *dur_node = node->child ("duration"); if (false && pre_node) {
XMLNode *siz_node = node->child ("filesize"); uri_node = pre_node->child ("preview-hq-ogg");
XMLNode *srt_node = node->child ("samplerate"); } else {
XMLNode *lic_node = node->child ("license"); uri_node = node->child ("download");
}
XMLNode *filename_node = node->child ("name");
XMLNode *duration_node = node->child ("duration");
XMLNode *filesize_node = node->child ("filesize");
XMLNode *samplerate_node = node->child ("samplerate");
XMLNode *licence_node = node->child ("license");
if (id_node && uri_node && ofn_node && dur_node && siz_node && srt_node) { if (id_node && uri_node && filename_node && duration_node && filesize_node && samplerate_node) {
std::string id = id_node->child("text")->content(); std::string id = id_node->child("text")->content();
std::string uri = uri_node->child("text")->content(); std::string uri = uri_node->child("text")->content();
std::string ofn = ofn_node->child("text")->content(); std::string filename = filename_node->child("text")->content();
std::string dur = dur_node->child("text")->content(); std::string duration = duration_node->child("text")->content();
std::string siz = siz_node->child("text")->content(); std::string filesize = filesize_node->child("text")->content();
std::string srt = srt_node->child("text")->content(); std::string samplerate = samplerate_node->child("text")->content();
std::string lic = lic_node->child("text")->content(); std::string licence = licence_node->child("text")->content();
std::string r; DEBUG_TRACE(PBD::DEBUG::Freesound, string_compose(
// cerr << "id=" << id << ",uri=" << uri << ",ofn=" << ofn << ",dur=" << dur << endl; "id=%1 ,uri=%2 ,filename=%3 ,duration=%4\n",
id, uri, filename, duration
));
double duration_seconds = atof(dur); double duration_seconds = atof (duration);
double h, m, s; double h, m, s;
char duration_hhmmss[16]; char duration_hhmmss[16];
if (duration_seconds > 99 * 60 * 60) { if (duration_seconds > 99 * 60 * 60) {
@ -1308,7 +1329,7 @@ SoundFileBrowser::handle_freesound_results(std::string theString) {
); );
} }
double size_bytes = atof(siz); double size_bytes = atof(filesize);
char bsize[32]; char bsize[32];
if (size_bytes < 1000) { if (size_bytes < 1000) {
sprintf(bsize, "%.0f %s", size_bytes, _("B")); sprintf(bsize, "%.0f %s", size_bytes, _("B"));
@ -1324,16 +1345,16 @@ SoundFileBrowser::handle_freesound_results(std::string theString) {
/* see http://www.freesound.org/help/faq/#licenses */ /* see http://www.freesound.org/help/faq/#licenses */
char shortlicense[64]; char shortlicense[64];
if(!lic.compare(0, 42, "http://creativecommons.org/licenses/by-nc/")){ if(!licence.compare(0, 42, "http://creativecommons.org/licenses/by-nc/")){
sprintf(shortlicense, "CC-BY-NC"); sprintf(shortlicense, "CC-BY-NC");
} else if(!lic.compare(0, 39, "http://creativecommons.org/licenses/by/")) { } else if(!licence.compare(0, 39, "http://creativecommons.org/licenses/by/")) {
sprintf(shortlicense, "CC-BY"); sprintf(shortlicense, "CC-BY");
} else if(!lic.compare("http://creativecommons.org/licenses/sampling+/1.0/")) { } else if(!licence.compare("http://creativecommons.org/licenses/sampling+/1.0/")) {
sprintf(shortlicense, "sampling+"); sprintf(shortlicense, "sampling+");
} else if(!lic.compare(0, 40, "http://creativecommons.org/publicdomain/")) { } else if(!licence.compare(0, 40, "http://creativecommons.org/publicdomain/")) {
sprintf(shortlicense, "PD"); sprintf(shortlicense, "PD");
} else { } else {
snprintf(shortlicense, 64, "%s", lic.c_str()); snprintf(shortlicense, 64, "%s", licence.c_str());
shortlicense[63]= '\0'; shortlicense[63]= '\0';
} }
@ -1342,12 +1363,14 @@ SoundFileBrowser::handle_freesound_results(std::string theString) {
row[freesound_list_columns.id ] = id; row[freesound_list_columns.id ] = id;
row[freesound_list_columns.uri ] = uri; row[freesound_list_columns.uri ] = uri;
row[freesound_list_columns.filename] = ofn; row[freesound_list_columns.filename] = filename;
row[freesound_list_columns.duration] = duration_hhmmss; row[freesound_list_columns.duration] = duration_hhmmss;
row[freesound_list_columns.filesize] = bsize; row[freesound_list_columns.filesize] = bsize;
row[freesound_list_columns.smplrate] = srt; row[freesound_list_columns.smplrate] = samplerate;
row[freesound_list_columns.license ] = shortlicense; row[freesound_list_columns.license ] = shortlicense;
matches++; matches++;
} else {
error << _("Failed to retrieve XML for file") << std::endl;
} }
} }
} }
@ -1510,8 +1533,6 @@ SoundFileOmega::reset_options ()
set_popdown_strings (action_combo, action_strings); set_popdown_strings (action_combo, action_strings);
/* preserve any existing choice, if possible */ /* preserve any existing choice, if possible */
if (existing_choice.length()) { if (existing_choice.length()) {
vector<string>::iterator x; vector<string>::iterator x;
for (x = action_strings.begin(); x != action_strings.end(); ++x) { for (x = action_strings.begin(); x != action_strings.end(); ++x) {

View File

@ -157,7 +157,7 @@ private:
Gtk::TreeModelColumn<std::string> filesize; Gtk::TreeModelColumn<std::string> filesize;
Gtk::TreeModelColumn<std::string> smplrate; Gtk::TreeModelColumn<std::string> smplrate;
Gtk::TreeModelColumn<std::string> license; Gtk::TreeModelColumn<std::string> license;
Gtk::TreeModelColumn<bool> started; Gtk::TreeModelColumn<bool> downloading;
FreesoundColumns() { FreesoundColumns() {
add(id); add(id);
@ -167,7 +167,7 @@ private:
add(filesize); add(filesize);
add(smplrate); add(smplrate);
add(license); add(license);
add(started); add(downloading);
} }
}; };
@ -251,6 +251,7 @@ protected:
void freesound_more_clicked (); void freesound_more_clicked ();
void freesound_similar_clicked (); void freesound_similar_clicked ();
int freesound_page; int freesound_page;
std::string freesound_token; // keep oauth token while ardour is running
void chooser_file_activated (); void chooser_file_activated ();
std::string freesound_get_audio_file(Gtk::TreeIter iter); std::string freesound_get_audio_file(Gtk::TreeIter iter);