Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit 9fc4ad61 authored by Camilo Higuita's avatar Camilo Higuita

initial work on youtube video integration

parent 4eb85928
......@@ -7,6 +7,7 @@ QT += network
QT += xml
QT += qml
QT += quickcontrols2
QT += webkit
TARGET = babe
TEMPLATE = app
......@@ -61,7 +62,8 @@ SOURCES += main.cpp \
settings/BabeSettings.cpp \
db/conthread.cpp \
services/web/babeit.cpp \
utils/babeconsole.cpp
utils/babeconsole.cpp \
services/local/youtubedl.cpp
RESOURCES += qml.qrc
......@@ -115,7 +117,8 @@ HEADERS += \
db/conthread.h \
services/web/babeit.h \
utils/babeconsole.h \
utils/singleton.h
utils/singleton.h \
services/local/youtubedl.h
#TAGLIB
......
......@@ -15,6 +15,7 @@
#include "utils/bae.h"
#include <QCommandLineParser>
#include "services/web/youtube.h"
int main(int argc, char *argv[])
{
......@@ -51,6 +52,9 @@ int main(int argc, char *argv[])
Player player;
Babe bae;
/* Services */
YouTube youtube;
QFontDatabase::addApplicationFont(":/utils/materialdesignicons-webfont.ttf");
qDebug()<<QQuickStyle::availableStyles();
......@@ -66,6 +70,7 @@ int main(int argc, char *argv[])
auto context = engine.rootContext();
context->setContextProperty("player", &player);
context->setContextProperty("bae", &bae);
context->setContextProperty("youtube", &youtube);
#ifdef Q_OS_ANDROID
KirigamiPlugin::getInstance().registerTypes();
......
......@@ -18,6 +18,7 @@ import "widgets/SearchView"
import "view_models"
import "view_models/BabeDialog"
import "services/web"
import "db/Queries.js" as Q
import "utils/Player.js" as Player
......@@ -106,6 +107,7 @@ Kirigami.ApplicationWindow
"playlists" : 3,
"babeit": 4,
"search" : 5,
"youtube" : 6
})
property bool mainlistEmpty : !mainPlaylist.table.count > 0
......@@ -756,6 +758,11 @@ Kirigami.ApplicationWindow
}
}
YouTube
{
id: youtubeView
}
}
}
}
......@@ -820,6 +827,17 @@ Kirigami.ApplicationWindow
}
/*CONNECTIONS*/
Connections
{
target: youtube
onQueryResultsReady:
{
if(res.length > 0)
youtubeView.web.url= res[0].url
}
}
Connections
{
target: player
......
......@@ -7,128 +7,128 @@
namespace PULPO
{
enum class SERVICES : uint8_t
{
LastFm,
Spotify,
iTunes,
MusicBrainz,
Genius,
LyricWikia,
Wikipedia,
WikiLyrics,
Deezer,
ALL,
NONE
};
enum class ONTOLOGY : uint8_t
{
ARTIST,
ALBUM,
TRACK,
NONE
};
enum class INFO : uint8_t
{
ARTWORK,
WIKI,
TAGS,
METADATA,
LYRICS,
ALL,
NONE
};
/*Generic context names. It's encouraged to use these instead of a unkown string*/
enum class CONTEXT : uint8_t
{
TRACK_STAT,
TRACK_NUMBER,
TRACK_TITLE,
TRACK_DATE,
TRACK_TEAM,
TRACK_AUTHOR,
TRACK_LANGUAGE,
TRACK_SIMILAR,
ALBUM_TEAM,
ALBUM_STAT,
ALBUM_TITLE,
ALBUM_DATE,
ALBUM_LANGUAGE,
ALBUM_SIMILAR,
ALBUM_LABEL,
ARTIST_STAT,
ARTIST_TITLE,
ARTIST_DATE,
ARTIST_LANGUAGE,
ARTIST_PLACE,
ARTIST_SIMILAR,
ARTIST_TEAM,
ARTIST_ALIAS,
ARTIST_GENDER,
GENRE,
TAG,
WIKI,
IMAGE,
LYRIC,
SOURCE
};
static const QMap<CONTEXT,QString> CONTEXT_MAP =
{
{CONTEXT::ALBUM_STAT, "album_stat"},
{CONTEXT::ALBUM_TITLE, "album_title"},
{CONTEXT::ALBUM_DATE, "album_date"},
{CONTEXT::ALBUM_LANGUAGE, "album_language"},
{CONTEXT::ALBUM_SIMILAR, "album_similar"},
{CONTEXT::ALBUM_LABEL, "album_label"},
{CONTEXT::ALBUM_TEAM, "album_team"},
{CONTEXT::ARTIST_STAT, "artist_stat"},
{CONTEXT::ARTIST_TITLE, "artist_title"},
{CONTEXT::ARTIST_DATE, "artist_date"},
{CONTEXT::ARTIST_LANGUAGE, "artist_language"},
{CONTEXT::ARTIST_PLACE, "artist_place"},
{CONTEXT::ARTIST_SIMILAR, "artist_similar"},
{CONTEXT::ARTIST_ALIAS, "artist_alias"},
{CONTEXT::ARTIST_GENDER, "artist_gender"},
{CONTEXT::ARTIST_TEAM, "artist_team"},
{CONTEXT::TRACK_STAT, "track_stat"},
{CONTEXT::TRACK_DATE, "track_date"},
{CONTEXT::TRACK_TITLE, "track_title"},
{CONTEXT::TRACK_NUMBER, "track_number"},
{CONTEXT::TRACK_TEAM, "track_team"},
{CONTEXT::TRACK_AUTHOR, "track_author"},
{CONTEXT::TRACK_LANGUAGE, "track_language"},
{CONTEXT::TRACK_SIMILAR, "track_similar"},
{CONTEXT::GENRE, "genre"},
{CONTEXT::TAG, "tag"},
{CONTEXT::WIKI, "wiki"},
{CONTEXT::IMAGE, "image"},
{CONTEXT::LYRIC, "lyric"},
{CONTEXT::SOURCE, "source"}
};
enum class RECURSIVE : bool
{
ON = true,
OFF = false
};
typedef QMap<CONTEXT, QVariant> VALUE;
typedef QMap<INFO, VALUE> INFO_K;
typedef QMap<ONTOLOGY, INFO_K> RESPONSE;
typedef QMap<ONTOLOGY, QList<INFO>> AVAILABLE;
enum class SERVICES : uint8_t
{
LastFm,
Spotify,
iTunes,
MusicBrainz,
Genius,
LyricWikia,
Wikipedia,
WikiLyrics,
Deezer,
ALL,
NONE
};
enum class ONTOLOGY : uint8_t
{
ARTIST,
ALBUM,
TRACK,
NONE
};
enum class INFO : uint8_t
{
ARTWORK,
WIKI,
TAGS,
METADATA,
LYRICS,
ALL,
NONE
};
/*Generic context names. It's encouraged to use these instead of a unkown string*/
enum class CONTEXT : uint8_t
{
TRACK_STAT,
TRACK_NUMBER,
TRACK_TITLE,
TRACK_DATE,
TRACK_TEAM,
TRACK_AUTHOR,
TRACK_LANGUAGE,
TRACK_SIMILAR,
ALBUM_TEAM,
ALBUM_STAT,
ALBUM_TITLE,
ALBUM_DATE,
ALBUM_LANGUAGE,
ALBUM_SIMILAR,
ALBUM_LABEL,
ARTIST_STAT,
ARTIST_TITLE,
ARTIST_DATE,
ARTIST_LANGUAGE,
ARTIST_PLACE,
ARTIST_SIMILAR,
ARTIST_TEAM,
ARTIST_ALIAS,
ARTIST_GENDER,
GENRE,
TAG,
WIKI,
IMAGE,
LYRIC,
SOURCE
};
static const QMap<CONTEXT,QString> CONTEXT_MAP =
{
{CONTEXT::ALBUM_STAT, "album_stat"},
{CONTEXT::ALBUM_TITLE, "album_title"},
{CONTEXT::ALBUM_DATE, "album_date"},
{CONTEXT::ALBUM_LANGUAGE, "album_language"},
{CONTEXT::ALBUM_SIMILAR, "album_similar"},
{CONTEXT::ALBUM_LABEL, "album_label"},
{CONTEXT::ALBUM_TEAM, "album_team"},
{CONTEXT::ARTIST_STAT, "artist_stat"},
{CONTEXT::ARTIST_TITLE, "artist_title"},
{CONTEXT::ARTIST_DATE, "artist_date"},
{CONTEXT::ARTIST_LANGUAGE, "artist_language"},
{CONTEXT::ARTIST_PLACE, "artist_place"},
{CONTEXT::ARTIST_SIMILAR, "artist_similar"},
{CONTEXT::ARTIST_ALIAS, "artist_alias"},
{CONTEXT::ARTIST_GENDER, "artist_gender"},
{CONTEXT::ARTIST_TEAM, "artist_team"},
{CONTEXT::TRACK_STAT, "track_stat"},
{CONTEXT::TRACK_DATE, "track_date"},
{CONTEXT::TRACK_TITLE, "track_title"},
{CONTEXT::TRACK_NUMBER, "track_number"},
{CONTEXT::TRACK_TEAM, "track_team"},
{CONTEXT::TRACK_AUTHOR, "track_author"},
{CONTEXT::TRACK_LANGUAGE, "track_language"},
{CONTEXT::TRACK_SIMILAR, "track_similar"},
{CONTEXT::GENRE, "genre"},
{CONTEXT::TAG, "tag"},
{CONTEXT::WIKI, "wiki"},
{CONTEXT::IMAGE, "image"},
{CONTEXT::LYRIC, "lyric"},
{CONTEXT::SOURCE, "source"}
};
enum class RECURSIVE : bool
{
ON = true,
OFF = false
};
typedef QMap<CONTEXT, QVariant> VALUE;
typedef QMap<INFO, VALUE> INFO_K;
typedef QMap<ONTOLOGY, INFO_K> RESPONSE;
typedef QMap<ONTOLOGY, QList<INFO>> AVAILABLE;
}
......
......@@ -234,8 +234,10 @@ QByteArray Pulpo::startConnection(const QString &url, const QMap<QString,QString
if(reply->bytesAvailable())
{
auto data = reply->readAll();
reply->deleteLater();
return reply->readAll();
return data;
}
}
......
......@@ -16,7 +16,6 @@
#include <QNetworkRequest>
#include <QJsonDocument>
#include <QVariantMap>
#include <QSqlQuery>
#include "../utils/bae.h"
#include "enums.h"
......@@ -25,49 +24,49 @@ using namespace PULPO;
class Pulpo : public QObject
{
Q_OBJECT
Q_OBJECT
public:
explicit Pulpo(const BAE::DB &song, QObject *parent = nullptr);
explicit Pulpo(QObject *parent = nullptr);
~Pulpo();
public:
explicit Pulpo(const BAE::DB &song, QObject *parent = nullptr);
explicit Pulpo(QObject *parent = nullptr);
~Pulpo();
bool feed(const BAE::DB &song, const PULPO::RECURSIVE &recursive = PULPO::RECURSIVE::ON );
void registerServices(const QList<PULPO::SERVICES> &services);
void setInfo(const PULPO::INFO &info);
void setOntology(const PULPO::ONTOLOGY &ontology);
PULPO::ONTOLOGY getOntology();
void setRecursive(const PULPO::RECURSIVE &state);
bool feed(const BAE::DB &song, const PULPO::RECURSIVE &recursive = PULPO::RECURSIVE::ON );
void registerServices(const QList<PULPO::SERVICES> &services);
void setInfo(const PULPO::INFO &info);
void setOntology(const PULPO::ONTOLOGY &ontology);
PULPO::ONTOLOGY getOntology();
void setRecursive(const PULPO::RECURSIVE &state);
private:
void initServices();
PULPO::RECURSIVE recursive = PULPO::RECURSIVE::ON;
QList<SERVICES> registeredServices = {};
private:
void initServices();
PULPO::RECURSIVE recursive = PULPO::RECURSIVE::ON;
QList<SERVICES> registeredServices = {};
void passSignal(const BAE::DB &track, const PULPO::RESPONSE &response);
void passSignal(const BAE::DB &track, const PULPO::RESPONSE &response);
protected:
QByteArray array;
BAE::DB track;
PULPO::INFO info = INFO::NONE;
PULPO::ONTOLOGY ontology = ONTOLOGY::NONE;
PULPO::AVAILABLE availableInfo;
protected:
QByteArray array;
BAE::DB track;
PULPO::INFO info = INFO::NONE;
PULPO::ONTOLOGY ontology = ONTOLOGY::NONE;
PULPO::AVAILABLE availableInfo;
PULPO::RESPONSE packResponse(const PULPO::ONTOLOGY ontology, const PULPO::INFO &infoKey, const PULPO::CONTEXT &contextName, const QVariant &value);
PULPO::RESPONSE packResponse(const PULPO::ONTOLOGY ontology, const PULPO::INFO &infoKey, const PULPO::VALUE &map);
PULPO::RESPONSE packResponse(const PULPO::ONTOLOGY ontology, const PULPO::INFO &infoKey, const PULPO::CONTEXT &contextName, const QVariant &value);
PULPO::RESPONSE packResponse(const PULPO::ONTOLOGY ontology, const PULPO::INFO &infoKey, const PULPO::VALUE &map);
QByteArray startConnection(const QString &url, const QMap<QString, QString> &headers = {});
bool parseArray();
QByteArray startConnection(const QString &url, const QMap<QString, QString> &headers = {});
bool parseArray();
/* expected methods to be overrided by services */
bool setUpService(const PULPO::ONTOLOGY &ontology, const PULPO::INFO &info);
virtual bool parseArtist() {return false;}
virtual bool parseAlbum() {return false;}
virtual bool parseTrack() {return false;}
/* expected methods to be overrided by services */
bool setUpService(const PULPO::ONTOLOGY &ontology, const PULPO::INFO &info);
virtual bool parseArtist() {return false;}
virtual bool parseAlbum() {return false;}
virtual bool parseTrack() {return false;}
signals:
void infoReady(BAE::DB track, PULPO::RESPONSE response);
void serviceFail(const QString &message);
signals:
void infoReady(BAE::DB track, PULPO::RESPONSE response);
void serviceFail(const QString &message);
};
#endif // ARTWORK_H
......@@ -52,7 +52,7 @@ bool genius::setUpService(const PULPO::ONTOLOGY &ontology, const PULPO::INFO &in
qDebug()<< "[genius service]: "<< newUrl;
this->array = this->startConnection( newUrl,{{"Authorization", this->KEY}} );
this->array = this->startConnection(newUrl,{{"Authorization", this->KEY}} );
if(this->array.isEmpty()) return false;
return this->parseArray();
......
......@@ -72,5 +72,6 @@
<file>widgets/SettingsView/BabeConsole.qml</file>
<file>widgets/SearchView/SearchTable.qml</file>
<file>widgets/SearchView/SearchSuggestions.qml</file>
<file>services/web/YouTube.qml</file>
</qresource>
</RCC>
/*
Babe - tiny music player
Copyright (C) 2017 Camilo Higuita
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 3 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; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "youtubedl.h"
#include "../../pulpo/pulpo.h"
#include "../../db/collectionDB.h"
#include "../../utils/babeconsole.h"
#if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID))
#include "kde/notify.h"
#endif
using namespace BAE;
youtubedl::youtubedl(QObject *parent) : QObject(parent)
{
#if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID))
this->nof = new Notify(this);
#endif
}
youtubedl::~youtubedl(){}
void youtubedl::fetch(const QString &json)
{
QJsonParseError jsonParseError;
auto jsonResponse = QJsonDocument::fromJson(json.toUtf8(), &jsonParseError);
if (jsonParseError.error != QJsonParseError::NoError) return;
if (!jsonResponse.isObject()) return;
QJsonObject mainJsonObject(jsonResponse.object());
auto data = mainJsonObject.toVariantMap();
auto id = data.value("id").toString().trimmed();
auto title = data.value("title").toString().trimmed();
auto artist = data.value("artist").toString().trimmed();
auto album = data.value("album").toString().trimmed();
auto playlist = data.value("playlist").toString().trimmed();
auto page = data.value("page").toString().replace('"',"").trimmed();
bDebug::Instance()->msg("Fetching from Youtube: "+id+" "+title+" "+artist);
DB infoMap;
infoMap.insert(KEY::TITLE, title);
infoMap.insert(KEY::ARTIST, artist);
infoMap.insert(KEY::ALBUM, album);
infoMap.insert(KEY::URL, page);
infoMap.insert(KEY::ID, id);
infoMap.insert(KEY::PLAYLIST, playlist);
if(!this->ids.contains(infoMap[KEY::ID]))
{
this->ids << infoMap[KEY::ID];
auto process = new QProcess(this);
process->setWorkingDirectory(YoutubeCachePath);
//connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(processFinished()));
//connect(process, SIGNAL(finished(int)), this, SLOT(processFinished_totally(int)));
connect(process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
[=](int exitCode, QProcess::ExitStatus exitStatus)
{
// qDebug()<<"processFinished_totally"<<exitCode<<exitStatus;
processFinished_totally(exitCode, infoMap, exitStatus);
process->deleteLater();
});
#if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID))
this->nof->notify("Song received!", infoMap[KEY::TITLE]+ " - "+ infoMap[KEY::ARTIST]+".\nWait a sec while the track is added to your collection :)");
#endif
auto command = ydl;
command = command.replace("$$$",infoMap[KEY::ID])+" "+infoMap[KEY::ID];
bDebug::Instance()->msg(command);
process->start(command);
}
}
void youtubedl::processFinished_totally(const int &state,const DB &info,const QProcess::ExitStatus &exitStatus)
{
auto track = info;
auto doneId = track[KEY::ID];
auto file = YoutubeCachePath+track[KEY::ID]+".m4a";
if(!BAE::fileExists(file)) return;
ids.removeAll(doneId);
track.insert(KEY::URL,file);
bDebug::Instance()->msg("Finished collection track with youtube-dl");
// qDebug()<<track[KEY::ID]<<track[KEY::TITLE]<<track[KEY::ARTIST]<<track[KEY::PLAYLIST]<<track[KEY::URL];
/*here get metadata*/
TagInfo tag;
if(exitStatus == QProcess::NormalExit)
{
if(BAE::fileExists(file))
{
tag.feed(file);
tag.setArtist(track[KEY::ARTIST]);
tag.setTitle(track[KEY::TITLE]);
tag.setAlbum(track[KEY::ALBUM]);
tag.setComment(track[KEY::URL]);
bDebug::Instance()->msg("Trying to collect metadata of downloaded track");
Pulpo pulpo;
pulpo.registerServices({PULPO::SERVICES::LastFm, PULPO::SERVICES::Spotify});
pulpo.setOntology(PULPO::ONTOLOGY::TRACK);
pulpo.setInfo(PULPO::INFO::METADATA);
QEventLoop loop;
QTimer timer;
connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
timer.setSingleShot(true);
timer.setInterval(1000);
connect(&pulpo, &Pulpo::infoReady, [&loop](const BAE::DB &track, const PULPO::RESPONSE &res)
{
bDebug::Instance()->msg("Setting collected track metadata");
if(!res[PULPO::ONTOLOGY::TRACK][PULPO::INFO::METADATA].isEmpty())
{
bDebug::Instance()->msg(res[PULPO::ONTOLOGY::TRACK][PULPO::INFO::METADATA][PULPO::CONTEXT::ALBUM_TITLE].toString());
bDebug::Instance()->msg(res[PULPO::ONTOLOGY::TRACK][PULPO::INFO::METADATA][PULPO::CONTEXT::TRACK_NUMBER].toString());
TagInfo tag;
tag.feed(track[KEY::URL]);
auto albumRes = res[PULPO::ONTOLOGY::TRACK][PULPO::INFO::METADATA][PULPO::CONTEXT::ALBUM_TITLE].toString();
if(!albumRes.isEmpty() && albumRes != BAE::SLANG[W::UNKNOWN])
tag.setAlbum(res[PULPO::ONTOLOGY::TRACK][PULPO::INFO::METADATA][PULPO::CONTEXT::ALBUM_TITLE].toString());
else tag.setAlbum(track[KEY::TITLE]);
if(!res[PULPO::ONTOLOGY::TRACK][PULPO::INFO::METADATA][PULPO::CONTEXT::TRACK_NUMBER].toString().isEmpty())
tag.setTrack(res[PULPO::ONTOLOGY::TRACK][PULPO::INFO::METADATA][PULPO::CONTEXT::TRACK_NUMBER].toInt());
}
loop.quit();
});
pulpo.feed(track, PULPO::RECURSIVE::OFF);
timer.start();
loop.exec();
timer.stop();
bDebug::Instance()->msg("Process finished totally for "+QString(state)+" "+doneId+" "+QString(exitStatus));
bDebug::Instance()->msg("Need to delete the id "+ doneId);
bDebug::Instance()->msg("Ids left to process: " + this->ids.join(","));
}
}
tag.feed(file);
auto album = BAE::fixString(tag.getAlbum());
auto trackNum = tag.getTrack();
auto title = BAE::fixString(tag.getTitle()); /* to fix*/
auto artist = BAE::fixString(tag.getArtist());
auto genre = tag.getGenre();
auto sourceUrl = QFileInfo(file).dir().path();
auto duration = tag.getDuration();
auto year = tag.getYear();
BAE::DB trackMap =
{
{BAE::KEY::URL,file},
{BAE::KEY::TRACK,QString::number(trackNum)},
{BAE::KEY::TITLE,title},
{BAE::KEY::ARTIST,artist},
{BAE::KEY::ALBUM,album},
{BAE::KEY::DURATION,QString::number(duration)},
{BAE::KEY::GENRE,genre},
{BAE::KEY::SOURCES_URL,sourceUrl},
{BAE::KEY::BABE, file.startsWith(BAE::YoutubeCachePath)?"1":"0"},
{BAE::KEY::RELEASE_DATE,QString::number(year)}
};
CollectionDB con(nullptr);
con.addTrack(trackMap);
con.trackPlaylist({file}, track[KEY::PLAYLIST]);
if(this->ids.isEmpty()) emit this->done();
}
void youtubedl::processFinished()
{
/* QByteArray processOutput;
processOutput = process->readAllStandardOutput();
if (!QString(proce