1 /* SoundcloudJsonParser.cpp */
2
3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
4 *
5 * This file is part of sayonara player
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "SoundcloudGlobal.h"
22 #include "SoundcloudJsonParser.h"
23
24 #include "Utils/Utils.h"
25 #include "Utils/FileUtils.h"
26 #include "Utils/MetaData/Artist.h"
27 #include "Utils/MetaData/Album.h"
28 #include "Utils/MetaData/MetaDataList.h"
29 #include "Utils/MetaData/Genre.h"
30 #include "Utils/Settings/Settings.h"
31 #include "Utils/Logger/Logger.h"
32 #include "Utils/Language/Language.h"
33 #include "Utils/StandardPaths.h"
34
35 #include <QJsonDocument>
36 #include <QJsonParseError>
37 #include <QDateTime>
38
39 struct SC::JsonParser::Private
40 {
41 QJsonDocument jsonDocument;
42 QByteArray content;
43 QJsonParseError error;
44
PrivateSC::JsonParser::Private45 Private(const QByteArray& content) :
46 content(content)
47 {
48 jsonDocument = QJsonDocument::fromJson(content, &error);
49 }
50 };
51
JsonParser(const QByteArray & content)52 SC::JsonParser::JsonParser(const QByteArray& content) :
53 QObject()
54 {
55 m = Pimpl::make<Private>(content);
56 QString targetFile = Util::tempPath("soundcloud.json");
57
58 Util::File::writeFile(
59 m->jsonDocument.toJson(QJsonDocument::Indented), targetFile
60 );
61
62 QJsonParseError::ParseError pe = m->error.error;
63 if(pe != QJsonParseError::NoError){
64 spLog(Log::Warning, this) << "Cannot parse json document: " << m->error.errorString();
65 }
66 }
67
68 SC::JsonParser::~JsonParser() = default;
69
parseArtists(ArtistList & artists)70 bool SC::JsonParser::parseArtists(ArtistList& artists)
71 {
72 if(m->jsonDocument.isArray()){
73 return parseArtistList(artists, m->jsonDocument.array());
74 }
75
76 else if(m->jsonDocument.isObject()){
77 Artist artist;
78 if(parseArtist(artist, m->jsonDocument.object())){
79 artists << artist;
80 return true;
81 }
82 }
83
84 return false;
85 }
86
87
parseArtistList(ArtistList & artists,QJsonArray arr)88 bool SC::JsonParser::parseArtistList(ArtistList& artists, QJsonArray arr)
89 {
90 artists.clear();
91
92 for(auto it = arr.begin(); it != arr.end(); it++){
93 QJsonValueRef ref = *it;
94 if(ref.isObject()){
95 Artist artist;
96 if(parseArtist(artist, ref.toObject())){
97 artists << artist;
98 }
99 }
100 }
101
102 return true;
103 }
104
105
parseArtist(Artist & artist,QJsonObject object)106 bool SC::JsonParser::parseArtist(Artist& artist, QJsonObject object)
107 {
108 QString cover_download_url;
109
110 ArtistId artistId;
111 if(getInt("id", object, artistId)) {
112 artist.setId(artistId);
113 }
114
115 QString artist_name;
116 getString("username", object, artist_name);
117 artist.setName(artist_name);
118
119 getString("avatar_url", object, cover_download_url);
120 artist.setCoverDownloadUrls({cover_download_url});
121
122 QString description, website, permalink;
123 if(getString("website", object, website)){
124 artist.addCustomField("website", tr("Website"), website);
125 }
126
127 if(getString("permalink", object, permalink)){
128 artist.addCustomField("permalink", tr("Permalink Url"), permalink);
129 }
130
131 if(getString("description", object, description)){
132 artist.addCustomField("description", Lang::get(Lang::About), description);
133 }
134
135 int followers=-1;
136 int following=-1;
137 getInt("followers_count", object, followers);
138 getInt("followings_count", object, following);
139
140 if(followers != -1 && following != -1){
141 artist.addCustomField("followers_following", tr("Followers/Following"), QString::number(followers) + "/" + QString::number(following));
142 }
143
144 return (artist.id() > 0);
145 }
146
147
parseTracks(ArtistList & artists,MetaDataList & v_md)148 bool SC::JsonParser::parseTracks(ArtistList& artists, MetaDataList &v_md)
149 {
150 if(!m->jsonDocument.isArray()){
151 return false;
152 }
153
154 return parseTrackList(artists, v_md, m->jsonDocument.array());
155 }
156
157
parseTrackList(ArtistList & artists,MetaDataList & tracks,QJsonArray arr)158 bool SC::JsonParser::parseTrackList(ArtistList& artists, MetaDataList& tracks, QJsonArray arr){
159 tracks.clear();
160
161 for(auto it = arr.begin(); it != arr.end(); it++)
162 {
163 QJsonValueRef ref = *it;
164 if(ref.isObject())
165 {
166 MetaData md;
167 Artist artist;
168 if(parseTrack(artist, md, ref.toObject()))
169 {
170 md.setTrackNumber(TrackNum(tracks.size() + 1));
171
172 tracks << md;
173
174 if(!artists.contains(artist.id()))
175 {
176 artists << artist;
177 }
178 }
179
180 else{
181 spLog(Log::Debug, this) << "Invalid md found";
182 }
183 }
184 }
185
186 return true;
187 }
188
parseTrack(Artist & artist,MetaData & md,QJsonObject object)189 bool SC::JsonParser::parseTrack(Artist& artist, MetaData& md, QJsonObject object)
190 {
191 QString coverDownloadUrl;
192
193 TrackID id;
194 if(getInt("id", object, id)){
195 md.setId(id);
196 }
197
198 getString("artwork_url", object, coverDownloadUrl);
199 md.setCoverDownloadUrls({coverDownloadUrl});
200
201 int length;
202 if(getInt("duration", object, length)){
203 md.setDurationMs(MilliSeconds(length));
204 }
205
206 int year;
207 if(getInt("release_year", object, year)){
208 md.setYear(Year(year));
209 }
210
211 int filesize;
212 if(getInt("original_content_size", object, filesize)){
213 md.setFilesize(Filesize(filesize));
214 }
215
216 QString title;
217 if(getString("title", object, title)){
218 md.setTitle(title);
219 }
220
221 QString streamUrl;
222 if(getString("stream_url", object, streamUrl)){
223 md.setFilepath(streamUrl + '?' + CLIENT_ID_STR);
224 }
225
226 QString genre;
227 if(getString("genre", object, genre)){
228 md.addGenre(Genre(genre));
229 }
230
231 QString purchaseUrl;
232 if(getString("purchase_url", object, purchaseUrl)){
233 md.addCustomField("purchase_url", tr("Purchase Url"), createLink(purchaseUrl, purchaseUrl));
234 }
235
236 QJsonObject artistObject;
237 if(getObject("user", object, artistObject))
238 {
239 if( parseArtist(artist, artistObject) )
240 {
241 md.setArtist(artist.name());
242 md.setArtistId(artist.id());
243
244 if(md.albumId() < 0)
245 {
246 md.setAlbumId(0);
247 md.setAlbum(Lang::get(Lang::UnknownAlbum));
248 }
249 }
250 }
251
252 QString lastModifiedString;
253 if(getString("last_modified", object, lastModifiedString))
254 {
255 QDateTime dt = QDateTime::fromString(lastModifiedString, Qt::DateFormat::ISODate);
256 md.setModifiedDate(Util::dateToInt(dt));
257 }
258
259 QString createdString;
260 if(getString("created_at", object, createdString))
261 {
262 QDateTime dt = QDateTime::fromString(createdString, Qt::DateFormat::ISODate);
263 md.setCreatedDate(Util::dateToInt(dt));
264 }
265
266 QString description;
267 if(getString("description", object, description))
268 {
269 md.setComment(description);
270 }
271
272 return (md.filepath().size() > 0 && md.id() > 0);
273 }
274
275
parsePlaylists(ArtistList & artists,AlbumList & albums,MetaDataList & v_md)276 bool SC::JsonParser::parsePlaylists(ArtistList& artists, AlbumList &albums, MetaDataList &v_md)
277 {
278 if(m->jsonDocument.isArray()){
279 return parsePlaylistList(artists, albums, v_md, m->jsonDocument.array());
280 }
281
282 else if(m->jsonDocument.isObject()){
283 Album album;
284 if(parsePlaylist(artists, album, v_md, m->jsonDocument.object())){
285 albums << album;
286 return true;
287 }
288 }
289
290 return false;
291 }
292
293
parsePlaylistList(ArtistList & artists,AlbumList & albums,MetaDataList & v_md,QJsonArray arr)294 bool SC::JsonParser::parsePlaylistList(ArtistList& artists, AlbumList& albums, MetaDataList& v_md, QJsonArray arr)
295 {
296 albums.clear();
297
298 for(auto it = arr.begin(); it != arr.end(); it++){
299 QJsonValueRef ref = *it;
300 if(ref.isObject()){
301 Album album;
302 MetaDataList v_md_tmp;
303 ArtistList artists_tmp;
304
305 if(parsePlaylist(artists_tmp, album, v_md_tmp, ref.toObject())){
306 v_md << v_md_tmp;
307
308 for(const Artist& artist_tmp : artists_tmp){
309 if(!artists.contains(artist_tmp.id()) && artist_tmp.id() > 0){
310 artists << artist_tmp;
311 }
312 }
313
314 if(!albums.contains(album.id())){
315 albums << album;
316 }
317 }
318 }
319 }
320
321 return true;
322 }
323
324
parsePlaylist(ArtistList & artists,Album & album,MetaDataList & v_md,QJsonObject object)325 bool SC::JsonParser::parsePlaylist(ArtistList& artists, Album& album, MetaDataList& v_md, QJsonObject object)
326 {
327 Artist pl_artist;
328 QString cover_download_url;
329
330 AlbumId albumId;
331 getInt("id", object, albumId);
332 album.setId(albumId);
333
334 QString album_name;
335 getString("title", object, album_name);
336 album.setName(album_name);
337
338 getString("artwork_url", object, cover_download_url);
339 album.setCoverDownloadUrls({cover_download_url});
340
341 int num_songs;
342 if(getInt("track_count", object, num_songs)){
343 album.setSongcount(TrackNum(num_songs));
344 }
345
346 int length;
347 if(getInt("duration", object, length)){
348 album.setDurationSec(length / 1000);
349 }
350
351 QJsonObject artist_object;
352 if(getObject("user", object, artist_object))
353 {
354 parseArtist(pl_artist, artist_object);
355 if(!artists.contains(pl_artist.id()) && pl_artist.id() > 0){
356 artists << pl_artist;
357 }
358 }
359
360 QJsonArray track_array;
361 if(getArray("tracks", object, track_array))
362 {
363 ArtistList tmp_artists;
364 MetaDataList v_md_tmp;
365 parseTrackList(tmp_artists, v_md_tmp, track_array);
366 for(const Artist& tmp_artist : tmp_artists)
367 {
368 if(!artists.contains(tmp_artist.id())){
369 artists << tmp_artist;
370 }
371 }
372
373 for(const MetaData& md : v_md_tmp)
374 {
375 if(!v_md.contains(md.id())){
376 v_md << md;
377 }
378 }
379 }
380
381 QString permalink, purchase_url;
382 if(getString("permalink", object, permalink)){
383 album.addCustomField(permalink, tr("Permalink Url"), createLink("Soundcloud", permalink));
384 }
385
386 if(getString("purchase_url", object, purchase_url)){
387 album.addCustomField(purchase_url, tr("Purchase Url"), createLink(purchase_url, purchase_url));
388 }
389
390 album_name = album.name();
391
392 for(int i=0; i<v_md.count(); i++)
393 {
394 MetaData& md = v_md[i];
395 md.setTrackNumber(TrackNum(i+1));
396 md.setAlbum(album.name());
397 md.setAlbumId(album.id());
398
399 if(md.artistId() != pl_artist.id() && pl_artist.id() > 0 && md.artistId() > 0)
400 {
401 md.setAlbum( md.album() + " (by " + pl_artist.name() + ")");
402 album_name = album.name() + " (by " + pl_artist.name() + ")";
403 }
404
405 if(!album.coverDownloadUrls().isEmpty()){
406 v_md[i].setCoverDownloadUrls(album.coverDownloadUrls());
407 }
408 }
409
410 album.setName(album_name);
411
412 QStringList lst;
413 for(const Artist& artist : artists){
414 lst << artist.name();
415 }
416
417 album.setArtists(lst);
418
419 return (album.id() > 0);
420 }
421
422
createLink(const QString & name,const QString & target)423 QString SC::JsonParser::createLink(const QString& name, const QString& target)
424 {
425 Settings* s = Settings::instance();
426 bool dark = (s->get<Set::Player_Style>() == 0);
427 return Util::createLink(name, dark, true, target);
428 }
429
430
getString(const QString & key,const QJsonObject & object,QString & str)431 bool SC::JsonParser::getString(const QString& key, const QJsonObject& object, QString& str)
432 {
433 auto it = object.find(key);
434 if(it != object.end()){
435 QJsonValue ref = *it;
436 if(ref.isString()){
437 str = ref.toString();
438 str.replace("\\n", "<br />");
439 str.replace("\\\"", "\"");
440 str = str.trimmed();
441 return true;
442 }
443 }
444
445 return false;
446 }
447
getInt(const QString & key,const QJsonObject & object,int & i)448 bool SC::JsonParser::getInt(const QString& key, const QJsonObject& object, int& i)
449 {
450 auto it = object.find(key);
451 if(it != object.end()){
452 QJsonValue ref = *it;
453 if(ref.isDouble()){
454 i = ref.toInt();
455 return true;
456 }
457 }
458
459 return false;
460 }
461
462
getArray(const QString & key,const QJsonObject & object,QJsonArray & arr)463 bool SC::JsonParser::getArray(const QString& key, const QJsonObject& object, QJsonArray& arr)
464 {
465 auto it = object.find(key);
466 if(it != object.end()){
467 QJsonValue ref = *it;
468 if(ref.isArray()){
469 arr = ref.toArray();
470 return true;
471 }
472 }
473
474 return false;
475 }
476
getObject(const QString & key,const QJsonObject & object,QJsonObject & o)477 bool SC::JsonParser::getObject(const QString& key, const QJsonObject& object, QJsonObject& o)
478 {
479 auto it = object.find(key);
480 if(it != object.end()){
481 QJsonValue ref = *it;
482 if(ref.isObject()){
483 o = ref.toObject();
484 return true;
485 }
486 }
487
488 return false;
489 }
490