1 /***************************************************************************
2 * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
3 * Copyright (C) 2011 by Marco Gittler (marco@gitma.de) *
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. *
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. *
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 ***************************************************************************/
22 #include "freesound.h"
24 #include <QPushButton>
26 #include <QListWidget>
27 #include <QDomDocument>
30 #include "kdenlivesettings.h"
31 #include <KGlobalSettings>
32 #include <KMessageBox>
33 #include <KFileDialog>
35 #include <KIO/NetAccess>
36 #include <Solid/Networking>
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>
49 #include <qjson/parser.h>
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;
65 FreeSound::FreeSound(const QString & folder, QWidget * parent) :
70 setFont(KGlobalSettings::toolBarFont());
72 setAttribute(Qt::WA_DeleteOnClose);
74 service_list->addItem(i18n("Freesound Audio Library"), FREESOUND);
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) {
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();
104 FreeSound::~FreeSound()
106 if (m_previewProcess) delete m_previewProcess;
109 void FreeSound::slotStartSearch(int page)
111 m_currentPreview.clear();
112 m_currentUrl.clear();
113 page_number->blockSignals(true);
114 page_number->setValue(page);
115 page_number->blockSignals(false);
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");
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));
129 KJob* resolveJob = KIO::storedGet( KUrl(uri), KIO::NoReload, KIO::HideProgressInfo );
130 connect( resolveJob, SIGNAL( result( KJob* ) ), this, SLOT( slotShowResults( KJob* ) ) );
134 void FreeSound::slotShowResults(KJob* job)
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) {
142 QJson::Parser parser;
144 //kDebug()<<"// GOT RESULT: "<<m_result;
145 QVariant data = parser.parse(storedQueryJob->data(), &ok);
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());
155 else if (i.key() == "sounds") {
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());
189 else if (m_service == OPENCLIPART) {
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());
212 search_results->blockSignals(false);
213 search_results->setCurrentRow(0);
216 void FreeSound::slotUpdateCurrentSound()
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();
226 sound_box->setEnabled(false);
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()));
242 if (!item->data(licenseRole).isNull()) {
243 parseLicense(item->data(licenseRole).toString());
245 if (!item->data(descriptionRole).isNull()) {
246 item_description->setHtml(item->data(descriptionRole).toString());
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* ) ) );
253 else info_widget->resizeColumnToContents(0);
255 KUrl img(item->data(imageRole).toString());
256 if (img.isEmpty()) return;
257 if (KIO::NetAccess::exists(img, KIO::NetAccess::SourceSide, this)) {
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());
267 sound_image->setScaledContents(true);
268 sound_image->setFixedHeight(newHeight);
270 sound_image->setPixmap(pix);
271 KIO::NetAccess::removeTempFile(tmpFile);
277 void FreeSound::slotParseResults(KJob* job)
280 KIO::StoredTransferJob* storedQueryJob = static_cast<KIO::StoredTransferJob*>( job );
281 QJson::Parser parser;
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));
295 if (infos.contains("description")) {
296 item_description->setHtml(infos.value("description").toString());
298 if (infos.contains("license")) {
299 parseLicense(infos.value("license").toString());
302 info_widget->resizeColumnToContents(0);
303 info_widget->resizeColumnToContents(1);
308 void FreeSound::slotPlaySound()
310 if (m_currentPreview.isEmpty()) return;
311 if (m_previewProcess && m_previewProcess->state() != QProcess::NotRunning) {
312 m_previewProcess->close();
315 m_previewProcess->start("ffplay", QStringList() << m_currentPreview << "-nodisp");
319 void FreeSound::slotForcePlaySound(bool play)
321 if (m_service != FREESOUND) return;
322 m_previewProcess->close();
323 if (m_currentPreview.isEmpty()) return;
325 m_previewProcess->start("ffplay", QStringList() << m_currentPreview << "-nodisp");
328 void FreeSound::slotPreviewStatusChanged(QProcess::ProcessState state)
330 if (state == QProcess::NotRunning)
331 button_preview->setText(i18n("Preview"));
333 button_preview->setText(i18n("Stop"));
336 void FreeSound::slotSaveSound()
338 if (m_currentUrl.isEmpty()) return;
339 QString path = m_folder;
340 if (!path.endsWith('/')) path.append('/');
341 path.append(sound_name->text());
343 if (m_service == FREESOUND) {
344 ext = "*." + sound_name->text().section('.', -1);
346 else if (m_service == OPENCLIPART) {
347 path.append("." + m_currentUrl.section('.', -1));
348 ext = "*." + m_currentUrl.section('.', -1);
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);
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(),
362 emit addClip(KUrl(saveUrl), QString());//, sound_name->url());
366 void FreeSound::slotOpenUrl(const QString &url)
368 new KRun(KUrl(url), this);
371 void FreeSound::slotChangeService()
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);
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);
386 if (!search_text->text().isEmpty()) slotStartSearch();
389 void FreeSound::slotOnline()
391 button_search->setEnabled(true);
392 search_info->setText(QString());
395 void FreeSound::slotOffline()
397 button_search->setEnabled(false);
398 search_info->setText(i18n("You need to be online\n for searching"));
401 void FreeSound::slotNextPage()
403 int ix = page_number->value();
404 if (search_results->count() > 0) page_number->setValue(ix + 1);
407 void FreeSound::slotPreviousPage()
409 int ix = page_number->value();
410 if (ix > 1) page_number->setValue(ix - 1);
413 void FreeSound::parseLicense(const QString &licenseUrl)
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);