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