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