401 lines
13 KiB
C++
401 lines
13 KiB
C++
/*
|
|
* Cantata
|
|
*
|
|
* Copyright (c) 2015 Craig Drummond <craig.p.drummond@gmail.com>
|
|
*
|
|
* ----
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "jamendoservice.h"
|
|
#include "jamendosettingsdialog.h"
|
|
#include "db/onlinedb.h"
|
|
#include "models/roles.h"
|
|
#include "support/localize.h"
|
|
#include "support/icon.h"
|
|
#include "support/configuration.h"
|
|
#include <QXmlStreamReader>
|
|
#include <QUrl>
|
|
|
|
#ifdef TAGLIB_FOUND
|
|
|
|
#ifdef ENABLE_EXTERNAL_TAGS
|
|
#include <taglib/tstring.h>
|
|
#include <taglib/id3v1genres.h>
|
|
#include <QTextCodec>
|
|
static QString id3Genre(int id)
|
|
{
|
|
static QTextCodec *codec = QTextCodec::codecForName("UTF-8");
|
|
// Clementine: In theory, genre 0 is "blues"; in practice it's invalid.
|
|
return 0==id ? QString() : codec->toUnicode(TagLib::ID3v1::genre(id).toCString(true)).trimmed();
|
|
}
|
|
#else
|
|
#include "tags/tags.h"
|
|
using namespace Tags;
|
|
#endif
|
|
|
|
#else // TAGLIB_FOUND
|
|
static QString id3Genre(int id)
|
|
{
|
|
// Clementine: In theory, genre 0 is "blues"; in practice it's invalid.
|
|
if (0==id) {
|
|
return QString();
|
|
}
|
|
|
|
static QMap<int, QString> idMap;
|
|
|
|
if (idMap.isEmpty()) {
|
|
idMap.insert(0, "Blues");
|
|
idMap.insert(1, "Classic Rock");
|
|
idMap.insert(2, "Country");
|
|
idMap.insert(3, "Dance");
|
|
idMap.insert(4, "Disco");
|
|
idMap.insert(5, "Funk");
|
|
idMap.insert(6, "Grunge");
|
|
idMap.insert(7, "Hip-Hop");
|
|
idMap.insert(8, "Jazz");
|
|
idMap.insert(9, "Metal");
|
|
idMap.insert(10, "New Age");
|
|
idMap.insert(11, "Oldies");
|
|
idMap.insert(12, "Other");
|
|
idMap.insert(13, "Pop");
|
|
idMap.insert(14, "R&B");
|
|
idMap.insert(15, "Rap");
|
|
idMap.insert(16, "Reggae");
|
|
idMap.insert(17, "Rock");
|
|
idMap.insert(18, "Techno");
|
|
idMap.insert(19, "Industrial");
|
|
idMap.insert(20, "Alternative");
|
|
idMap.insert(21, "Ska");
|
|
idMap.insert(22, "Death Metal");
|
|
idMap.insert(23, "Pranks");
|
|
idMap.insert(24, "Soundtrack");
|
|
idMap.insert(25, "Euro-Techno");
|
|
idMap.insert(26, "Ambient");
|
|
idMap.insert(27, "Trip-Hop");
|
|
idMap.insert(28, "Vocal");
|
|
idMap.insert(29, "Jazz+Funk");
|
|
idMap.insert(30, "Fusion");
|
|
idMap.insert(31, "Trance");
|
|
idMap.insert(32, "Classical");
|
|
idMap.insert(33, "Instrumental");
|
|
idMap.insert(34, "Acid");
|
|
idMap.insert(35, "House");
|
|
idMap.insert(36, "Game");
|
|
idMap.insert(37, "Sound Clip");
|
|
idMap.insert(38, "Gospel");
|
|
idMap.insert(39, "Noise");
|
|
idMap.insert(40, "AlternRock");
|
|
idMap.insert(41, "Bass");
|
|
idMap.insert(42, "Soul");
|
|
idMap.insert(43, "Punk");
|
|
idMap.insert(44, "Space");
|
|
idMap.insert(45, "Meditative");
|
|
idMap.insert(46, "Instrumental Pop");
|
|
idMap.insert(47, "Instrumental Rock");
|
|
idMap.insert(48, "Ethnic");
|
|
idMap.insert(49, "Gothic");
|
|
idMap.insert(50, "Darkwave");
|
|
idMap.insert(51, "Techno-Industrial");
|
|
idMap.insert(52, "Electronic");
|
|
idMap.insert(53, "Pop-Folk");
|
|
idMap.insert(54, "Eurodance");
|
|
idMap.insert(55, "Dream");
|
|
idMap.insert(56, "Southern Rock");
|
|
idMap.insert(57, "Comedy");
|
|
idMap.insert(58, "Cult");
|
|
idMap.insert(59, "Gangsta");
|
|
idMap.insert(60, "Top 40");
|
|
idMap.insert(61, "Christian Rap");
|
|
idMap.insert(62, "Pop/Funk");
|
|
idMap.insert(63, "Jungle");
|
|
idMap.insert(64, "Native American");
|
|
idMap.insert(65, "Cabaret");
|
|
idMap.insert(66, "New Wave");
|
|
idMap.insert(67, "Psychadelic");
|
|
idMap.insert(68, "Rave");
|
|
idMap.insert(69, "Showtunes");
|
|
idMap.insert(70, "Trailer");
|
|
idMap.insert(71, "Lo-Fi");
|
|
idMap.insert(72, "Tribal");
|
|
idMap.insert(73, "Acid Punk");
|
|
idMap.insert(74, "Acid Jazz");
|
|
idMap.insert(75, "Polka");
|
|
idMap.insert(76, "Retro");
|
|
idMap.insert(77, "Musical");
|
|
idMap.insert(78, "Rock & Roll");
|
|
idMap.insert(79, "Hard Rock");
|
|
idMap.insert(80, "Folk");
|
|
idMap.insert(81, "Folk-Rock");
|
|
idMap.insert(82, "National Folk");
|
|
idMap.insert(83, "Swing");
|
|
idMap.insert(84, "Fast Fusion");
|
|
idMap.insert(85, "Bebob");
|
|
idMap.insert(86, "Latin");
|
|
idMap.insert(87, "Revival");
|
|
idMap.insert(88, "Celtic");
|
|
idMap.insert(89, "Bluegrass");
|
|
idMap.insert(90, "Avantgarde");
|
|
idMap.insert(91, "Gothic Rock");
|
|
idMap.insert(92, "Progressive Rock");
|
|
idMap.insert(93, "Psychedelic Rock");
|
|
idMap.insert(94, "Symphonic Rock");
|
|
idMap.insert(95, "Slow Rock");
|
|
idMap.insert(96, "Big Band");
|
|
idMap.insert(97, "Chorus");
|
|
idMap.insert(98, "Easy Listening");
|
|
idMap.insert(99, "Acoustic");
|
|
idMap.insert(100, "Humour");
|
|
idMap.insert(101, "Speech");
|
|
idMap.insert(102, "Chanson");
|
|
idMap.insert(103, "Opera");
|
|
idMap.insert(104, "Chamber Music");
|
|
idMap.insert(105, "Sonata");
|
|
idMap.insert(106, "Symphony");
|
|
idMap.insert(107, "Booty Bass");
|
|
idMap.insert(108, "Primus");
|
|
idMap.insert(109, "Porn Groove");
|
|
idMap.insert(110, "Satire");
|
|
idMap.insert(111, "Slow Jam");
|
|
idMap.insert(112, "Club");
|
|
idMap.insert(113, "Tango");
|
|
idMap.insert(114, "Samba");
|
|
idMap.insert(115, "Folklore");
|
|
idMap.insert(116, "Ballad");
|
|
idMap.insert(117, "Power Ballad");
|
|
idMap.insert(118, "Rhythmic Soul");
|
|
idMap.insert(119, "Freestyle");
|
|
idMap.insert(120, "Duet");
|
|
idMap.insert(121, "Punk Rock");
|
|
idMap.insert(122, "Drum Solo");
|
|
idMap.insert(123, "A capella");
|
|
idMap.insert(124, "Euro-House");
|
|
idMap.insert(125, "Dance Hall");
|
|
}
|
|
|
|
return idMap[id];
|
|
}
|
|
#endif
|
|
|
|
static const QLatin1String constStreamUrl("http://api.jamendo.com/get2/stream/track/redirect/?id=%1&streamencoding=");
|
|
static const QLatin1String constListingUrl("http://imgjam.com/data/dbdump_artistalbumtrack.xml.gz");
|
|
static const QLatin1String constName("jamendo");
|
|
|
|
int JamendoXmlParser::parse(QXmlStreamReader &xml)
|
|
{
|
|
int artistCount=0;
|
|
while (!xml.atEnd()) {
|
|
xml.readNext();
|
|
if (QXmlStreamReader::StartElement==xml.tokenType() && QLatin1String("artist")==xml.name()) {
|
|
parseArtist(xml);
|
|
artistCount++;
|
|
}
|
|
}
|
|
return artistCount;
|
|
}
|
|
|
|
void JamendoXmlParser::parseArtist(QXmlStreamReader &xml)
|
|
{
|
|
Song song;
|
|
|
|
while (!xml.atEnd()) {
|
|
xml.readNext();
|
|
|
|
if (QXmlStreamReader::StartElement==xml.tokenType()) {
|
|
QStringRef name = xml.name();
|
|
|
|
if (QLatin1String("name")==name) {
|
|
song.artist=xml.readElementText().trimmed();
|
|
} else if (QLatin1String("album")==name) {
|
|
parseAlbum(song, xml);
|
|
} /*else if (artist && QLatin1String("image")==name) {
|
|
artist->setImageUrl(xml.readElementText().trimmed());
|
|
}*/
|
|
} else if (xml.isEndElement() && QLatin1String("artist")==xml.name()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void JamendoXmlParser::parseAlbum(Song &song, QXmlStreamReader &xml)
|
|
{
|
|
QString id;
|
|
song.track=0;
|
|
song.album=QString();
|
|
QList<Song> *songList=new QList<Song>();
|
|
|
|
while (!xml.atEnd()) {
|
|
xml.readNext();
|
|
|
|
if (QXmlStreamReader::StartElement==xml.tokenType()) {
|
|
QStringRef name = xml.name();
|
|
|
|
if (QLatin1String("name")==name) {
|
|
song.album=xml.readElementText().trimmed();
|
|
} else if (QLatin1String("track")==name) {
|
|
song.track++;
|
|
parseSong(song, xml);
|
|
songList->append(song);
|
|
} else if (QLatin1String("id")==name) {
|
|
id=xml.readElementText().trimmed();
|
|
}
|
|
} else if (xml.isEndElement() && QLatin1String("album")==xml.name()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!id.isEmpty()) {
|
|
emit coverUrl(song.artistOrComposer(), song.album, id);
|
|
}
|
|
emit songs(songList);
|
|
}
|
|
|
|
void JamendoXmlParser::parseSong(Song &song, QXmlStreamReader &xml)
|
|
{
|
|
song.time=0;
|
|
song.title=QString();
|
|
song.genre=QString();
|
|
|
|
while (!xml.atEnd()) {
|
|
xml.readNext();
|
|
|
|
if (QXmlStreamReader::StartElement==xml.tokenType()) {
|
|
QStringRef name = xml.name();
|
|
|
|
if (QLatin1String("name")==name) {
|
|
song.title=xml.readElementText().trimmed();
|
|
} else if (QLatin1String("duration")==name) {
|
|
song.time=xml.readElementText().toFloat();
|
|
} else if (QLatin1String("id3genre")==name) {
|
|
int g=xml.readElementText().toInt();
|
|
if (0!=g) {
|
|
song.genre=id3Genre(g);
|
|
}
|
|
} else if (QLatin1String("id")==name) {
|
|
song.file=xml.readElementText().trimmed();
|
|
}
|
|
} else if (xml.isEndElement() && QLatin1String("track")==xml.name()) {
|
|
break;
|
|
}
|
|
}
|
|
song.fillEmptyFields();
|
|
}
|
|
|
|
static const QLatin1String constMp3Format("mp3");
|
|
static const QLatin1String constOggFormat("ogg");
|
|
|
|
static QString formatStr(JamendoService::Format f)
|
|
{
|
|
return JamendoService::FMT_MP3==f ? "mp3" : "ogg";
|
|
}
|
|
|
|
static JamendoService::Format toFormat(const QString &f)
|
|
{
|
|
return f=="ogg" ? JamendoService::FMT_Ogg : JamendoService::FMT_MP3;
|
|
}
|
|
|
|
JamendoService::JamendoService(QObject *p)
|
|
: OnlineDbService(new OnlineDb(constName, p), p)
|
|
{
|
|
tl=T_Genre;
|
|
icn.addFile(":"+constName);
|
|
useCovers(name());
|
|
Configuration cfg(constName);
|
|
format=toFormat(cfg.get("format", formatStr(format)));
|
|
}
|
|
|
|
QVariant JamendoService::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (index.isValid()) {
|
|
switch (role) {
|
|
case Cantata::Role_CoverSong: {
|
|
QVariant v;
|
|
Item *item = static_cast<Item *>(index.internalPointer());
|
|
switch (item->getType()) {
|
|
case T_Album:
|
|
if (item->getSong().isEmpty()) {
|
|
Song song;
|
|
song.artist=item->getParent()->getId();
|
|
song.album=item->getId();
|
|
song.setIsFromOnlineService(constName);
|
|
song.file=constName; // Just so that isEmpty() is false!
|
|
QString id=static_cast<OnlineDb *>(db)->getCoverUrl(/*T_Album==topLevel() ? static_cast<AlbumItem *>(item)->getArtistId() : */item->getParent()->getId(), item->getId());
|
|
song.setExtraField(Song::OnlineImageUrl, QString("http://api.jamendo.com/get2/image/album/redirect/?id=%1&imagesize=300").arg(id));
|
|
item->setSong(song);
|
|
}
|
|
v.setValue<Song>(item->getSong());
|
|
break;
|
|
case T_Artist:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return v;
|
|
}
|
|
}
|
|
}
|
|
return OnlineDbService::data(index, role);
|
|
}
|
|
|
|
QString JamendoService::name() const
|
|
{
|
|
return constName;
|
|
}
|
|
|
|
QString JamendoService::title() const
|
|
{
|
|
return QLatin1String("Jamendo");
|
|
}
|
|
|
|
QString JamendoService::descr() const
|
|
{
|
|
return i18n("The world's largest digital service for free music");
|
|
}
|
|
|
|
OnlineXmlParser * JamendoService::createParser()
|
|
{
|
|
return new JamendoXmlParser();
|
|
}
|
|
|
|
QUrl JamendoService::listingUrl() const
|
|
{
|
|
return QUrl(constListingUrl);
|
|
}
|
|
|
|
Song & JamendoService::fixPath(Song &s) const
|
|
{
|
|
s.file=QString(constStreamUrl).replace("id=%1", "id="+s.file);
|
|
s.file+=FMT_MP3==format ? QLatin1String("mp31") : QLatin1String("ogg2");
|
|
s.genre=FMT_MP3==format ? QLatin1String("mp3") : QLatin1String("ogg");
|
|
s.type=Song::OnlineSvrTrack;
|
|
s.setIsFromOnlineService(name());
|
|
return encode(s);
|
|
}
|
|
|
|
void JamendoService::configure(QWidget *p)
|
|
{
|
|
JamendoSettingsDialog dlg(p);
|
|
if (dlg.run(FMT_MP3==format)) {
|
|
Format f=0==dlg.format() ? FMT_MP3 : FMT_Ogg;
|
|
if (f!=format) {
|
|
format=f;
|
|
Configuration cfg(constName);
|
|
cfg.set("format", formatStr(format));
|
|
}
|
|
}
|
|
}
|