]> git.sesse.net Git - kdenlive/blob - src/utils/freesound.cpp
Online resources: display description and license
[kdenlive] / src / utils / freesound.cpp
1 /***************************************************************************
2  *   Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
3  *   Copyright (C) 2011 by Marco Gittler (marco@gitma.de)                  *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
19  ***************************************************************************/
20
21
22 #include "freesound.h"
23
24 #include <QPushButton>
25 #include <QSpinBox>
26 #include <QListWidget>
27 #include <QDomDocument>
28
29 #include <KDebug>
30 #include "kdenlivesettings.h"
31 #include <KGlobalSettings>
32 #include <KMessageBox>
33 #include <KFileDialog>
34 #include <kio/job.h>
35 #include <KIO/NetAccess>
36 #include <Solid/Networking>
37 #include <KRun>
38
39 #ifdef USE_QJSON
40 #include <qjson/parser.h>
41 #endif
42
43 const int imageRole = Qt::UserRole;
44 const int urlRole = Qt::UserRole + 1;
45 const int downloadRole = Qt::UserRole + 2;
46 const int durationRole = Qt::UserRole + 3;
47 const int previewRole = Qt::UserRole + 4;
48 const int authorRole = Qt::UserRole + 5;
49 const int authorUrl = Qt::UserRole + 6;
50 const int infoUrl = Qt::UserRole + 7;
51 const int infoData = Qt::UserRole + 8;
52 const int idRole = Qt::UserRole + 9;
53 const int licenseRole = Qt::UserRole + 10;
54 const int descriptionRole = Qt::UserRole + 11;
55
56 FreeSound::FreeSound(const QString & folder, QWidget * parent) :
57         QDialog(parent),
58         m_folder(folder),
59         m_service(FREESOUND)
60 {
61     setFont(KGlobalSettings::toolBarFont());
62     setupUi(this);
63     setAttribute(Qt::WA_DeleteOnClose);
64 #ifdef USE_QJSON
65     service_list->addItem(i18n("Freesound Audio Library"), FREESOUND);
66 #endif
67     service_list->addItem(i18n("Open Clip Art Graphic Library"), OPENCLIPART);
68     setWindowTitle(i18n("Search Online Resources"));
69     info_widget->setStyleSheet(QString("QTreeWidget { background-color: transparent;}"));
70     item_description->setStyleSheet(QString("KTextBrowser { background-color: transparent;}"));
71     connect(button_search, SIGNAL(clicked()), this, SLOT(slotStartSearch()));
72     connect(search_results, SIGNAL(currentRowChanged(int)), this, SLOT(slotUpdateCurrentSound()));
73     connect(button_preview, SIGNAL(clicked()), this, SLOT(slotPlaySound()));
74     connect(button_import, SIGNAL(clicked()), this, SLOT(slotSaveSound()));
75     connect(sound_author, SIGNAL(leftClickedUrl(const QString &)), this, SLOT(slotOpenUrl(const QString &)));
76     connect(item_license, SIGNAL(leftClickedUrl(const QString &)), this, SLOT(slotOpenUrl(const QString &)));
77     connect(sound_name, SIGNAL(leftClickedUrl(const QString &)), this, SLOT(slotOpenUrl(const QString &)));
78     m_previewProcess = new QProcess;
79     connect(m_previewProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(slotPreviewStatusChanged(QProcess::ProcessState)));
80     connect(service_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChangeService()));
81     sound_image->setFixedWidth(180);
82     if (Solid::Networking::status() == Solid::Networking::Unconnected) {
83         slotOffline();
84     }
85     connect(Solid::Networking::notifier(), SIGNAL(shouldConnect()), this, SLOT(slotOnline()));
86     connect(Solid::Networking::notifier(), SIGNAL(shouldDisconnect()), this, SLOT(slotOffline()));
87     connect(page_next, SIGNAL(clicked()), this, SLOT(slotNextPage()));
88     connect(page_prev, SIGNAL(clicked()), this, SLOT(slotPreviousPage()));
89     connect(page_number, SIGNAL(valueChanged(int)), this, SLOT(slotStartSearch(int)));
90     sound_box->setEnabled(false);
91     search_text->setFocus();
92 }
93
94 FreeSound::~FreeSound()
95 {
96     if (m_previewProcess) delete m_previewProcess;
97 }
98
99 void FreeSound::slotStartSearch(int page)
100 {
101     m_currentPreview.clear();
102     m_currentUrl.clear();
103     page_number->blockSignals(true);
104     page_number->setValue(page);
105     page_number->blockSignals(false);
106     QString uri;
107     if (m_service == FREESOUND) {
108         uri = "http://www.freesound.org/api/sounds/search/?q=";
109         uri.append(search_text->text());
110         if (page > 1) uri.append("&p=" + QString::number(page));
111         uri.append("&api_key=a1772c8236e945a4bee30a64058dabf8");
112     }
113     else if (m_service == OPENCLIPART) {
114         uri = "http://openclipart.org/api/search/?query=";
115         uri.append(search_text->text());
116         if (page > 1) uri.append("&page=" + QString::number(page));
117     }
118     
119     KJob* resolveJob = KIO::storedGet( KUrl(uri), KIO::NoReload, KIO::HideProgressInfo );
120     connect( resolveJob, SIGNAL( result( KJob* ) ), this, SLOT( slotShowResults( KJob* ) ) );
121 }
122
123
124 void FreeSound::slotShowResults(KJob* job)
125 {
126     if (job->error() != 0 ) return;
127     search_results->blockSignals(true);
128     search_results->clear();
129     KIO::StoredTransferJob* storedQueryJob = static_cast<KIO::StoredTransferJob*>( job );
130     if (m_service == FREESOUND) {
131 #ifdef USE_QJSON
132         QJson::Parser parser;
133         bool ok;
134         //kDebug()<<"// GOT RESULT: "<<m_result;
135         QVariant data = parser.parse(storedQueryJob->data(), &ok);
136         QVariant sounds;
137         if (data.canConvert(QVariant::Map)) {
138             QMap <QString, QVariant> map = data.toMap();
139             QMap<QString, QVariant>::const_iterator i = map.constBegin();
140             while (i != map.constEnd()) {
141                 if (i.key() == "num_results") search_info->setText(i18np("Found %1 result", "Found %1 results", i.value().toInt()));
142                 else if (i.key() == "num_pages") {
143                     page_number->setMaximum(i.value().toInt());
144                 }
145                 else if (i.key() == "sounds") {
146                     sounds = i.value();
147                     if (sounds.canConvert(QVariant::List)) {
148                         QList <QVariant> soundsList = sounds.toList();
149                         for (int j = 0; j < soundsList.count(); j++) {
150                             if (soundsList.at(j).canConvert(QVariant::Map)) {
151                                 QMap <QString, QVariant> soundmap = soundsList.at(j).toMap();
152                                 if (soundmap.contains("original_filename")) {
153                                     QListWidgetItem *item = new   QListWidgetItem(soundmap.value("original_filename").toString(), search_results);
154                                     item->setData(imageRole, soundmap.value("waveform_m").toString());
155                                     item->setData(infoUrl, soundmap.value("url").toString());
156                                     item->setData(infoData, soundmap.value("ref").toString() + "?api_key=a1772c8236e945a4bee30a64058dabf8");
157                                     item->setData(durationRole, soundmap.value("duration").toDouble());
158                                     item->setData(idRole, soundmap.value("id").toInt());
159                                     item->setData(previewRole, soundmap.value("preview-hq-mp3").toString());
160                                     item->setData(downloadRole, soundmap.value("serve").toString() + "?api_key=a1772c8236e945a4bee30a64058dabf8");
161                                     QVariant authorInfo = soundmap.value("user");
162                                     if (authorInfo.canConvert(QVariant::Map)) {
163                                         QMap <QString, QVariant> authorMap = authorInfo.toMap();
164                                         if (authorMap.contains("username")) {
165                                             item->setData(authorRole, authorMap.value("username").toString());
166                                             item->setData(authorUrl, authorMap.value("url").toString());
167                                         }
168                                     }
169                                 }
170                             }
171                         }
172                     }
173                 }
174                 ++i;
175             }
176         }
177 #endif  
178     }
179     else if (m_service == OPENCLIPART) {
180         QDomDocument doc;
181         doc.setContent(storedQueryJob->data());
182         QDomNodeList items = doc.documentElement().elementsByTagName("item");
183         for (int i = 0; i < items.count(); i++) {
184             QDomElement currentClip = items.at(i).toElement();
185             QDomElement title = currentClip.firstChildElement("title");
186             QListWidgetItem *item = new QListWidgetItem(title.firstChild().nodeValue(), search_results);
187             QDomElement thumb = currentClip.firstChildElement("media:thumbnail");
188             item->setData(imageRole, thumb.attribute("url"));
189             QDomElement enclosure = currentClip.firstChildElement("enclosure");
190             item->setData(downloadRole, enclosure.attribute("url"));
191             QDomElement link = currentClip.firstChildElement("link");
192             item->setData(infoUrl, link.firstChild().nodeValue());
193             QDomElement license = currentClip.firstChildElement("cc:license");
194             item->setData(licenseRole, license.firstChild().nodeValue());
195             QDomElement desc = currentClip.firstChildElement("description");
196             item->setData(descriptionRole, desc.firstChild().nodeValue());
197             QDomElement author = currentClip.firstChildElement("dc:creator");
198             item->setData(authorRole, author.firstChild().nodeValue());
199             item->setData(authorUrl, QString("http://openclipart.org/user-detail/") + author.firstChild().nodeValue());
200         }
201     }
202     search_results->blockSignals(false);
203     search_results->setCurrentRow(0);
204 }
205
206 void FreeSound::slotUpdateCurrentSound()
207 {
208     if (!sound_autoplay->isChecked()) slotForcePlaySound(false);
209     m_currentPreview.clear();
210     m_currentUrl.clear();
211     info_widget->clear();
212     item_description->clear();
213     item_license->clear();
214     QListWidgetItem *item = search_results->currentItem();
215     if (!item) {
216         sound_box->setEnabled(false);
217         return;
218     }
219     m_currentPreview = item->data(previewRole).toString();
220     m_currentUrl = item->data(downloadRole).toString();
221     m_currentId = item->data(idRole).toInt();
222     if (sound_autoplay->isChecked()) slotForcePlaySound(true);
223     button_preview->setEnabled(!m_currentPreview.isEmpty());
224     sound_box->setEnabled(true);
225     sound_name->setText(item->text());
226     sound_name->setUrl(item->data(infoUrl).toString());
227     sound_author->setText(item->data(authorRole).toString());
228     sound_author->setUrl(item->data(authorUrl).toString());
229     if (!item->data(durationRole).isNull()) {
230         new QTreeWidgetItem(info_widget, QStringList() << i18n("Duration") << QString::number(item->data(durationRole).toDouble()));
231     }
232     if (!item->data(licenseRole).isNull()) {
233         parseLicense(item->data(licenseRole).toString());
234     }
235     if (!item->data(descriptionRole).isNull()) {
236         item_description->setHtml(item->data(descriptionRole).toString());
237     }
238     QString extraInfo = item->data(infoData).toString();
239     if (!extraInfo.isEmpty()) {
240         KJob* resolveJob = KIO::storedGet( KUrl(extraInfo), KIO::NoReload, KIO::HideProgressInfo );
241         connect( resolveJob, SIGNAL( result( KJob* ) ), this, SLOT( slotParseResults( KJob* ) ) );
242     }
243     else info_widget->resizeColumnToContents(0);
244
245     KUrl img(item->data(imageRole).toString());
246     if (img.isEmpty()) return;
247     if (KIO::NetAccess::exists(img, KIO::NetAccess::SourceSide, this)) {
248         QString tmpFile;
249         if (KIO::NetAccess::download(img, tmpFile, this)) {
250             QPixmap pix(tmpFile);
251             int newHeight = pix.height() * sound_image->width() / pix.width();
252             if (newHeight > 200) {
253                 sound_image->setScaledContents(false);
254                 //sound_image->setFixedHeight(sound_image->width());
255             }
256             else {
257                 sound_image->setScaledContents(true);
258                 sound_image->setFixedHeight(newHeight);
259             }
260             sound_image->setPixmap(pix);
261             KIO::NetAccess::removeTempFile(tmpFile);
262         }
263     }
264 }
265
266
267 void FreeSound::slotParseResults(KJob* job)
268 {
269 #ifdef USE_QJSON
270     KIO::StoredTransferJob* storedQueryJob = static_cast<KIO::StoredTransferJob*>( job );
271     QJson::Parser parser;
272     bool ok;
273     QVariant data = parser.parse(storedQueryJob->data(), &ok);
274     if (data.canConvert(QVariant::Map)) {
275         QMap <QString, QVariant> infos = data.toMap();
276         if (m_currentId != infos.value("id").toInt()) return;
277         if (infos.contains("samplerate"))
278             new QTreeWidgetItem(info_widget, QStringList() << i18n("Samplerate") << QString::number(infos.value("samplerate").toDouble()));
279         if (infos.contains("channels"))
280             new QTreeWidgetItem(info_widget, QStringList() << i18n("Channels") << QString::number(infos.value("channels").toInt()));
281         if (infos.contains("filesize")) {
282             KIO::filesize_t fSize = infos.value("filesize").toDouble();
283             new QTreeWidgetItem(info_widget, QStringList() << i18n("File size") << KIO::convertSize(fSize));
284         }
285         if (infos.contains("description")) {
286             item_description->setHtml(infos.value("description").toString());
287         }
288         if (infos.contains("license")) {
289             parseLicense(infos.value("license").toString());
290         }
291     }
292     info_widget->resizeColumnToContents(0);
293     info_widget->resizeColumnToContents(1);
294 #endif    
295 }
296
297
298 void FreeSound::slotPlaySound()
299 {
300     if (m_currentPreview.isEmpty()) return;
301     if (m_previewProcess && m_previewProcess->state() != QProcess::NotRunning) {
302         m_previewProcess->close();
303         return;
304     }
305     m_previewProcess->start("ffplay", QStringList() << m_currentPreview << "-nodisp");
306 }
307
308
309 void FreeSound::slotForcePlaySound(bool play)
310 {
311     if (m_service != FREESOUND) return;
312     m_previewProcess->close();
313     if (m_currentPreview.isEmpty()) return;
314     if (play)
315         m_previewProcess->start("ffplay", QStringList() << m_currentPreview << "-nodisp");
316 }
317
318 void FreeSound::slotPreviewStatusChanged(QProcess::ProcessState state)
319 {
320     if (state == QProcess::NotRunning)
321         button_preview->setText(i18n("Preview"));
322     else 
323         button_preview->setText(i18n("Stop"));
324 }
325
326 void FreeSound::slotSaveSound()
327 {
328     if (m_currentUrl.isEmpty()) return;
329     QString path = m_folder;
330     if (!path.endsWith('/')) path.append('/');
331     path.append(sound_name->text());
332     QString ext;
333     if (m_service == FREESOUND) {
334         ext = "*." + sound_name->text().section('.', -1);
335     }
336     else if (m_service == OPENCLIPART) {
337         path.append("." + m_currentUrl.section('.', -1));
338         ext = "*." + m_currentUrl.section('.', -1);
339     }
340     QString saveUrl = KFileDialog::getSaveFileName(KUrl(path), ext);
341     if (saveUrl.isEmpty()) return;
342     if (KIO::NetAccess::download(KUrl(m_currentUrl), saveUrl, this)) {
343         emit addClip(KUrl(saveUrl), sound_name->url());
344     }
345 }
346
347 void FreeSound::slotOpenUrl(const QString &url)
348 {
349     new KRun(KUrl(url), this);
350 }
351
352 void FreeSound::slotChangeService()
353 {
354     m_service = (SERVICETYPE) service_list->itemData(service_list->currentIndex()).toInt();
355     if (m_service == FREESOUND) {
356         button_preview->setVisible(true);
357         search_info->setVisible(true);
358         sound_autoplay->setVisible(true);
359         info_widget->setVisible(true);
360     }
361     else if (m_service == OPENCLIPART) {
362         button_preview->setVisible(false);
363         search_info->setVisible(false);
364         sound_autoplay->setVisible(false);
365         info_widget->setVisible(false);
366     }
367     if (!search_text->text().isEmpty()) slotStartSearch();
368 }
369
370 void FreeSound::slotOnline()
371 {
372     button_search->setEnabled(true);
373     search_info->setText(QString());
374 }
375
376 void FreeSound::slotOffline()
377 {
378     button_search->setEnabled(false);
379     search_info->setText(i18n("You need to be online\n for searching"));
380 }
381
382 void FreeSound::slotNextPage()
383 {
384     int ix = page_number->value();
385     if (search_results->count() > 0) page_number->setValue(ix + 1);
386 }
387
388 void FreeSound::slotPreviousPage()
389 {
390     int ix = page_number->value();
391     if (ix > 1) page_number->setValue(ix - 1);
392 }
393
394 void FreeSound::parseLicense(const QString &licenseUrl)
395 {
396     QString licenseName;
397     if (licenseUrl.contains("/sampling+/"))
398         licenseName = "Sampling+";
399     else if (licenseUrl.contains("/by/"))
400         licenseName = "Attribution";
401     else if (licenseUrl.contains("/by-nd/"))
402         licenseName = "Attribution-NoDerivs";
403     else if (licenseUrl.contains("/by-nc-sa/"))
404         licenseName = "Attribution-NonCommercial-ShareAlike";
405     else if (licenseUrl.contains("/by-sa/"))
406         licenseName = "Attribution-ShareAlike";
407     else if (licenseUrl.contains("/by-nc/"))
408         licenseName = "Attribution-NonCommercial";
409     else if (licenseUrl.contains("/by-nc-nd/"))
410         licenseName = "Attribution-NonCommercial-NoDerivs";
411     else if (licenseUrl.contains("/publicdomain/zero/"))
412         licenseName = "Creative Commons 0";
413     else if (licenseUrl.endsWith("/publicdomain"))
414         licenseName = "Public Domain";
415     item_license->setText(i18n("License: %1", licenseName));
416     item_license->setUrl(licenseUrl);
417 }
418