1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "AudioLibrary.h"
10
11 #include "FileItem.h"
12 #include "ServiceBroker.h"
13 #include "TextureDatabase.h"
14 #include "Util.h"
15 #include "filesystem/Directory.h"
16 #include "messaging/ApplicationMessenger.h"
17 #include "music/Album.h"
18 #include "music/Artist.h"
19 #include "music/MusicDatabase.h"
20 #include "music/MusicThumbLoader.h"
21 #include "music/Song.h"
22 #include "music/tags/MusicInfoTag.h"
23 #include "settings/AdvancedSettings.h"
24 #include "settings/Settings.h"
25 #include "settings/SettingsComponent.h"
26 #include "utils/SortUtils.h"
27 #include "utils/StringUtils.h"
28 #include "utils/URIUtils.h"
29 #include "utils/Variant.h"
30
31 using namespace MUSIC_INFO;
32 using namespace JSONRPC;
33 using namespace XFILE;
34 using namespace KODI::MESSAGING;
35
GetProperties(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)36 JSONRPC_STATUS CAudioLibrary::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
37 {
38 CVariant properties = CVariant(CVariant::VariantTypeObject);
39 CMusicDatabase musicdatabase;
40 // Make db connection once if one or more properties needs db access
41 for (CVariant::const_iterator_array it = parameterObject["properties"].begin_array();
42 it != parameterObject["properties"].end_array(); it++)
43 {
44 std::string propertyName = it->asString();
45 if (propertyName == "librarylastupdated" || propertyName == "librarylastcleaned" ||
46 propertyName == "artistlinksupdated" || propertyName == "songslastadded" ||
47 propertyName == "albumslastadded" || propertyName == "artistslastadded" ||
48 propertyName == "songsmodified" || propertyName == "albumsmodified" ||
49 propertyName == "artistsmodified")
50 {
51 if (!musicdatabase.Open())
52 return InternalError;
53 else
54 break;
55 }
56 }
57
58 for (CVariant::const_iterator_array it = parameterObject["properties"].begin_array(); it != parameterObject["properties"].end_array(); it++)
59 {
60 std::string propertyName = it->asString();
61 CVariant property;
62 if (propertyName == "missingartistid")
63 property = (int)BLANKARTIST_ID;
64 else if (propertyName == "librarylastupdated")
65 property = musicdatabase.GetLibraryLastUpdated();
66 else if (propertyName == "librarylastcleaned")
67 property = musicdatabase.GetLibraryLastCleaned();
68 else if (propertyName == "artistlinksupdated")
69 property = musicdatabase.GetArtistLinksUpdated();
70 else if (propertyName == "songslastadded")
71 property = musicdatabase.GetSongsLastAdded();
72 else if (propertyName == "albumslastadded")
73 property = musicdatabase.GetAlbumsLastAdded();
74 else if (propertyName == "artistslastadded")
75 property = musicdatabase.GetArtistsLastAdded();
76 else if (propertyName == "genreslastadded")
77 property = musicdatabase.GetGenresLastAdded();
78 else if (propertyName == "songsmodified")
79 property = musicdatabase.GetSongsLastModified();
80 else if (propertyName == "albumsmodified")
81 property = musicdatabase.GetAlbumsLastModified();
82 else if (propertyName == "artistsmodified")
83 property = musicdatabase.GetArtistsLastModified();
84
85 properties[propertyName] = property;
86 }
87
88 result = properties;
89 return OK;
90 }
91
92
GetArtists(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)93 JSONRPC_STATUS CAudioLibrary::GetArtists(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
94 {
95 CMusicDatabase musicdatabase;
96 if (!musicdatabase.Open())
97 return InternalError;
98
99 CMusicDbUrl musicUrl;
100 if (!musicUrl.FromString("musicdb://artists/"))
101 return InternalError;
102
103 bool allroles = false;
104 if (parameterObject["allroles"].isBoolean())
105 allroles = parameterObject["allroles"].asBoolean();
106
107 const CVariant &filter = parameterObject["filter"];
108
109 if (allroles)
110 musicUrl.AddOption("roleid", -1000); //All roles, any negative parameter overrides implicit roleid=1 filter required for backward compatibility
111 else if (filter.isMember("roleid"))
112 musicUrl.AddOption("roleid", static_cast<int>(filter["roleid"].asInteger()));
113 else if (filter.isMember("role"))
114 musicUrl.AddOption("role", filter["role"].asString());
115 // Only one of (song) genreid/genre, albumid/album or songid/song or rules type filter is allowed by filter syntax
116 if (filter.isMember("genreid")) //Deprecated. Use "songgenre" or "artistgenre"
117 musicUrl.AddOption("genreid", static_cast<int>(filter["genreid"].asInteger()));
118 else if (filter.isMember("genre"))
119 musicUrl.AddOption("genre", filter["genre"].asString());
120 if (filter.isMember("songgenreid"))
121 musicUrl.AddOption("genreid", static_cast<int>(filter["songgenreid"].asInteger()));
122 else if (filter.isMember("songgenre"))
123 musicUrl.AddOption("genre", filter["songgenre"].asString());
124 else if (filter.isMember("albumid"))
125 musicUrl.AddOption("albumid", static_cast<int>(filter["albumid"].asInteger()));
126 else if (filter.isMember("album"))
127 musicUrl.AddOption("album", filter["album"].asString());
128 else if (filter.isMember("songid"))
129 musicUrl.AddOption("songid", static_cast<int>(filter["songid"].asInteger()));
130 else if (filter.isObject())
131 {
132 std::string xsp;
133 if (!GetXspFiltering("artists", filter, xsp))
134 return InvalidParams;
135
136 musicUrl.AddOption("xsp", xsp);
137 }
138
139 bool albumArtistsOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS);
140 if (parameterObject["albumartistsonly"].isBoolean())
141 albumArtistsOnly = parameterObject["albumartistsonly"].asBoolean();
142 musicUrl.AddOption("albumartistsonly", albumArtistsOnly);
143
144 SortDescription sorting;
145 ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd);
146 if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes))
147 return InvalidParams;
148
149 int total;
150 std::set<std::string> fields;
151 if (parameterObject.isMember("properties") && parameterObject["properties"].isArray())
152 {
153 for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array();
154 field != parameterObject["properties"].end_array(); field++)
155 fields.insert(field->asString());
156 }
157
158 musicdatabase.SetTranslateBlankArtist(false);
159 if (!musicdatabase.GetArtistsByWhereJSON(fields, musicUrl.ToString(), result, total, sorting))
160 return InternalError;
161
162 int start, end;
163 HandleLimits(parameterObject, result, total, start, end);
164
165 return OK;
166 }
167
GetArtistDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)168 JSONRPC_STATUS CAudioLibrary::GetArtistDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
169 {
170 int artistID = (int)parameterObject["artistid"].asInteger();
171
172 CMusicDbUrl musicUrl;
173 if (!musicUrl.FromString("musicdb://artists/"))
174 return InternalError;
175
176 CMusicDatabase musicdatabase;
177 if (!musicdatabase.Open())
178 return InternalError;
179
180 musicUrl.AddOption("artistid", artistID);
181
182 CFileItemList items;
183 CDatabase::Filter filter;
184 if (!musicdatabase.GetArtistsByWhere(musicUrl.ToString(), filter, items) || items.Size() != 1)
185 return InvalidParams;
186
187 // Add "artist" to "properties" array by default
188 CVariant param = parameterObject;
189 if (!param.isMember("properties"))
190 param["properties"] = CVariant(CVariant::VariantTypeArray);
191 param["properties"].append("artist");
192
193 //Get roleids, roles etc. if needed
194 JSONRPC_STATUS ret = GetAdditionalArtistDetails(parameterObject, items, musicdatabase);
195 if (ret != OK)
196 return ret;
197
198 HandleFileItem("artistid", false, "artistdetails", items[0], param, param["properties"], result, false);
199 return OK;
200 }
201
GetAlbums(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)202 JSONRPC_STATUS CAudioLibrary::GetAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
203 {
204 CMusicDatabase musicdatabase;
205 if (!musicdatabase.Open())
206 return InternalError;
207
208 CMusicDbUrl musicUrl;
209 if (!musicUrl.FromString("musicdb://albums/"))
210 return InternalError;
211
212 if (parameterObject["includesingles"].asBoolean())
213 musicUrl.AddOption("show_singles", true);
214
215 bool allroles = false;
216 if (parameterObject["allroles"].isBoolean())
217 allroles = parameterObject["allroles"].asBoolean();
218
219 const CVariant &filter = parameterObject["filter"];
220
221 if (allroles)
222 musicUrl.AddOption("roleid", -1000); //All roles, override implicit roleid=1 filter required for backward compatibility
223 else if (filter.isMember("roleid"))
224 musicUrl.AddOption("roleid", static_cast<int>(filter["roleid"].asInteger()));
225 else if (filter.isMember("role"))
226 musicUrl.AddOption("role", filter["role"].asString());
227 // Only one of genreid/genre, artistid/artist or rules type filter is allowed by filter syntax
228 if (filter.isMember("artistid"))
229 musicUrl.AddOption("artistid", static_cast<int>(filter["artistid"].asInteger()));
230 else if (filter.isMember("artist"))
231 musicUrl.AddOption("artist", filter["artist"].asString());
232 else if (filter.isMember("genreid"))
233 musicUrl.AddOption("genreid", static_cast<int>(filter["genreid"].asInteger()));
234 else if (filter.isMember("genre"))
235 musicUrl.AddOption("genre", filter["genre"].asString());
236 else if (filter.isObject())
237 {
238 std::string xsp;
239 if (!GetXspFiltering("albums", filter, xsp))
240 return InvalidParams;
241
242 musicUrl.AddOption("xsp", xsp);
243 }
244
245 SortDescription sorting;
246 ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd);
247 if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes))
248 return InvalidParams;
249
250 int total;
251 std::set<std::string> fields;
252 if (parameterObject.isMember("properties") && parameterObject["properties"].isArray())
253 {
254 for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array();
255 field != parameterObject["properties"].end_array(); field++)
256 fields.insert(field->asString());
257 }
258
259 if (!musicdatabase.GetAlbumsByWhereJSON(fields, musicUrl.ToString(), result, total, sorting))
260 return InternalError;
261
262 if (!result.isNull())
263 {
264 bool bFetchArt = fields.find("art") != fields.end();
265 bool bFetchFanart = fields.find("fanart") != fields.end();
266 if (bFetchArt || bFetchFanart)
267 {
268 CThumbLoader* thumbLoader = new CMusicThumbLoader();
269 thumbLoader->OnLoaderStart();
270
271 std::set<std::string> artfields;
272 if (bFetchArt)
273 artfields.insert("art");
274 if (bFetchFanart)
275 artfields.insert("fanart");
276
277 for (unsigned int index = 0; index < result["albums"].size(); index++)
278 {
279 CFileItem item;
280 item.GetMusicInfoTag()->SetDatabaseId(result["albums"][index]["albumid"].asInteger32(), MediaTypeAlbum);
281
282 // Could use FillDetails, but it does unnecessary serialization of empty MusiInfoTag
283 // CFileItemPtr itemptr(new CFileItem(item));
284 // FillDetails(item.GetMusicInfoTag(), itemptr, artfields, result["albums"][index], thumbLoader);
285
286 thumbLoader->FillLibraryArt(item);
287
288 if (bFetchFanart)
289 {
290 if (item.HasArt("fanart"))
291 result["albums"][index]["fanart"] = CTextureUtils::GetWrappedImageURL(item.GetArt("fanart"));
292 else
293 result["albums"][index]["fanart"] = "";
294 }
295 if (bFetchArt)
296 {
297 CGUIListItem::ArtMap artMap = item.GetArt();
298 CVariant artObj(CVariant::VariantTypeObject);
299 for (const auto& artIt : artMap)
300 {
301 if (!artIt.second.empty())
302 artObj[artIt.first] = CTextureUtils::GetWrappedImageURL(artIt.second);
303 }
304 result["albums"][index]["art"] = artObj;
305 }
306 }
307
308 delete thumbLoader;
309 }
310 }
311
312 int start, end;
313 HandleLimits(parameterObject, result, total, start, end);
314
315 return OK;
316 }
317
GetAlbumDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)318 JSONRPC_STATUS CAudioLibrary::GetAlbumDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
319 {
320 int albumID = (int)parameterObject["albumid"].asInteger();
321
322 CMusicDatabase musicdatabase;
323 if (!musicdatabase.Open())
324 return InternalError;
325
326 CAlbum album;
327 if (!musicdatabase.GetAlbum(albumID, album, false))
328 return InvalidParams;
329
330 std::string path = StringUtils::Format("musicdb://albums/%li/", albumID);
331
332 CFileItemPtr albumItem;
333 FillAlbumItem(album, path, albumItem);
334
335 CFileItemList items;
336 items.Add(albumItem);
337 JSONRPC_STATUS ret = GetAdditionalAlbumDetails(parameterObject, items, musicdatabase);
338 if (ret != OK)
339 return ret;
340
341 HandleFileItem("albumid", false, "albumdetails", items[0], parameterObject, parameterObject["properties"], result, false);
342
343 return OK;
344 }
345
GetSongs(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)346 JSONRPC_STATUS CAudioLibrary::GetSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
347 {
348 CMusicDatabase musicdatabase;
349 if (!musicdatabase.Open())
350 return InternalError;
351
352 CMusicDbUrl musicUrl;
353 if (!musicUrl.FromString("musicdb://songs/"))
354 return InternalError;
355
356 if (parameterObject["singlesonly"].asBoolean())
357 musicUrl.AddOption("singles", true);
358 else if (!parameterObject["includesingles"].asBoolean())
359 musicUrl.AddOption("singles", false);
360
361 bool allroles = false;
362 if (parameterObject["allroles"].isBoolean())
363 allroles = parameterObject["allroles"].asBoolean();
364
365 const CVariant &filter = parameterObject["filter"];
366
367 if (allroles)
368 musicUrl.AddOption("roleid", -1000); //All roles, override implicit roleid=1 filter required for backward compatibility
369 else if (filter.isMember("roleid"))
370 musicUrl.AddOption("roleid", static_cast<int>(filter["roleid"].asInteger()));
371 else if (filter.isMember("role"))
372 musicUrl.AddOption("role", filter["role"].asString());
373 // Only one of genreid/genre, artistid/artist, albumid/album or rules type filter is allowed by filter syntax
374 if (filter.isMember("artistid"))
375 musicUrl.AddOption("artistid", static_cast<int>(filter["artistid"].asInteger()));
376 else if (filter.isMember("artist"))
377 musicUrl.AddOption("artist", filter["artist"].asString());
378 else if (filter.isMember("genreid"))
379 musicUrl.AddOption("genreid", static_cast<int>(filter["genreid"].asInteger()));
380 else if (filter.isMember("genre"))
381 musicUrl.AddOption("genre", filter["genre"].asString());
382 else if (filter.isMember("albumid"))
383 musicUrl.AddOption("albumid", static_cast<int>(filter["albumid"].asInteger()));
384 else if (filter.isMember("album"))
385 musicUrl.AddOption("album", filter["album"].asString());
386 else if (filter.isObject())
387 {
388 std::string xsp;
389 if (!GetXspFiltering("songs", filter, xsp))
390 return InvalidParams;
391
392 musicUrl.AddOption("xsp", xsp);
393 }
394
395 SortDescription sorting;
396 ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd);
397 if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes))
398 return InvalidParams;
399
400 int total;
401 std::set<std::string> fields;
402 if (parameterObject.isMember("properties") && parameterObject["properties"].isArray())
403 {
404 for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array();
405 field != parameterObject["properties"].end_array(); field++)
406 fields.insert(field->asString());
407 }
408
409 if (!musicdatabase.GetSongsByWhereJSON(fields, musicUrl.ToString(), result, total, sorting))
410 return InternalError;
411
412 if (!result.isNull())
413 {
414 bool bFetchArt = fields.find("art") != fields.end();
415 bool bFetchFanart = fields.find("fanart") != fields.end();
416 bool bFetchThumb = fields.find("thumbnail") != fields.end();
417 if (bFetchArt || bFetchFanart || bFetchThumb)
418 {
419 CThumbLoader* thumbLoader = new CMusicThumbLoader();
420 thumbLoader->OnLoaderStart();
421
422 std::set<std::string> artfields;
423 if (bFetchArt)
424 artfields.insert("art");
425 if (bFetchFanart)
426 artfields.insert("fanart");
427 if (bFetchThumb)
428 artfields.insert("thumbnail");
429
430 for (unsigned int index = 0; index < result["songs"].size(); index++)
431 {
432 CFileItem item;
433 // Only needs song and album id (if we have it) set to get art
434 // Getting art is quicker if "albumid" has been fetched
435 item.GetMusicInfoTag()->SetDatabaseId(result["songs"][index]["songid"].asInteger32(), MediaTypeSong);
436 if (result["songs"][index].isMember("albumid"))
437 item.GetMusicInfoTag()->SetAlbumId(result["songs"][index]["albumid"].asInteger32());
438 else
439 item.GetMusicInfoTag()->SetAlbumId(-1);
440
441 // Could use FillDetails, but it does unnecessary serialization of empty MusiInfoTag
442 // CFileItemPtr itemptr(new CFileItem(item));
443 // FillDetails(item.GetMusicInfoTag(), itemptr, artfields, result["songs"][index], thumbLoader);
444
445 thumbLoader->FillLibraryArt(item);
446
447 if (bFetchThumb)
448 {
449 if (item.HasArt("thumb"))
450 result["songs"][index]["thumbnail"] = CTextureUtils::GetWrappedImageURL(item.GetArt("thumb"));
451 else
452 result["songs"][index]["thumbnail"] = "";
453 }
454 if (bFetchFanart)
455 {
456 if (item.HasArt("fanart"))
457 result["songs"][index]["fanart"] = CTextureUtils::GetWrappedImageURL(item.GetArt("fanart"));
458 else
459 result["songs"][index]["fanart"] = "";
460 }
461 if (bFetchArt)
462 {
463 CGUIListItem::ArtMap artMap = item.GetArt();
464 CVariant artObj(CVariant::VariantTypeObject);
465 for (const auto& artIt : artMap)
466 {
467 if (!artIt.second.empty())
468 artObj[artIt.first] = CTextureUtils::GetWrappedImageURL(artIt.second);
469 }
470 result["songs"][index]["art"] = artObj;
471 }
472 }
473
474 delete thumbLoader;
475 }
476 }
477
478 int start, end;
479 HandleLimits(parameterObject, result, total, start, end);
480
481 return OK;
482 }
483
GetSongDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)484 JSONRPC_STATUS CAudioLibrary::GetSongDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
485 {
486 int idSong = (int)parameterObject["songid"].asInteger();
487
488 CMusicDatabase musicdatabase;
489 if (!musicdatabase.Open())
490 return InternalError;
491
492 CSong song;
493 if (!musicdatabase.GetSong(idSong, song))
494 return InvalidParams;
495
496 CFileItemList items;
497 CFileItemPtr item = CFileItemPtr(new CFileItem(song));
498 FillItemArtistIDs(song.GetArtistIDArray(), item);
499 items.Add(item);
500
501 JSONRPC_STATUS ret = GetAdditionalSongDetails(parameterObject, items, musicdatabase);
502 if (ret != OK)
503 return ret;
504
505 HandleFileItem("songid", true, "songdetails", items[0], parameterObject, parameterObject["properties"], result, false);
506 return OK;
507 }
508
GetRecentlyAddedAlbums(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)509 JSONRPC_STATUS CAudioLibrary::GetRecentlyAddedAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
510 {
511 CMusicDatabase musicdatabase;
512 if (!musicdatabase.Open())
513 return InternalError;
514
515 VECALBUMS albums;
516 if (!musicdatabase.GetRecentlyAddedAlbums(albums))
517 return InternalError;
518
519 CFileItemList items;
520 for (unsigned int index = 0; index < albums.size(); index++)
521 {
522 std::string path = StringUtils::Format("musicdb://recentlyaddedalbums/%li/", albums[index].idAlbum);
523
524 CFileItemPtr item;
525 FillAlbumItem(albums[index], path, item);
526 items.Add(item);
527 }
528
529 JSONRPC_STATUS ret = GetAdditionalAlbumDetails(parameterObject, items, musicdatabase);
530 if (ret != OK)
531 return ret;
532
533 HandleFileItemList("albumid", false, "albums", items, parameterObject, result);
534 return OK;
535 }
536
GetRecentlyAddedSongs(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)537 JSONRPC_STATUS CAudioLibrary::GetRecentlyAddedSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
538 {
539 CMusicDatabase musicdatabase;
540 if (!musicdatabase.Open())
541 return InternalError;
542
543 int amount = (int)parameterObject["albumlimit"].asInteger();
544 if (amount < 0)
545 amount = 0;
546
547 CFileItemList items;
548 if (!musicdatabase.GetRecentlyAddedAlbumSongs("musicdb://songs/", items, (unsigned int)amount))
549 return InternalError;
550
551 JSONRPC_STATUS ret = GetAdditionalSongDetails(parameterObject, items, musicdatabase);
552 if (ret != OK)
553 return ret;
554
555 HandleFileItemList("songid", true, "songs", items, parameterObject, result);
556 return OK;
557 }
558
GetRecentlyPlayedAlbums(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)559 JSONRPC_STATUS CAudioLibrary::GetRecentlyPlayedAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
560 {
561 CMusicDatabase musicdatabase;
562 if (!musicdatabase.Open())
563 return InternalError;
564
565 VECALBUMS albums;
566 if (!musicdatabase.GetRecentlyPlayedAlbums(albums))
567 return InternalError;
568
569 CFileItemList items;
570 for (unsigned int index = 0; index < albums.size(); index++)
571 {
572 std::string path = StringUtils::Format("musicdb://recentlyplayedalbums/%li/", albums[index].idAlbum);
573
574 CFileItemPtr item;
575 FillAlbumItem(albums[index], path, item);
576 items.Add(item);
577 }
578
579 JSONRPC_STATUS ret = GetAdditionalAlbumDetails(parameterObject, items, musicdatabase);
580 if (ret != OK)
581 return ret;
582
583 HandleFileItemList("albumid", false, "albums", items, parameterObject, result);
584 return OK;
585 }
586
GetRecentlyPlayedSongs(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)587 JSONRPC_STATUS CAudioLibrary::GetRecentlyPlayedSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
588 {
589 CMusicDatabase musicdatabase;
590 if (!musicdatabase.Open())
591 return InternalError;
592
593 CFileItemList items;
594 if (!musicdatabase.GetRecentlyPlayedAlbumSongs("musicdb://songs/", items))
595 return InternalError;
596
597 JSONRPC_STATUS ret = GetAdditionalSongDetails(parameterObject, items, musicdatabase);
598 if (ret != OK)
599 return ret;
600
601 HandleFileItemList("songid", true, "songs", items, parameterObject, result);
602 return OK;
603 }
604
GetGenres(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)605 JSONRPC_STATUS CAudioLibrary::GetGenres(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
606 {
607 CMusicDatabase musicdatabase;
608 if (!musicdatabase.Open())
609 return InternalError;
610
611 // Check if sources for genre wanted
612 bool sourcesneeded(false);
613 std::set<std::string> checkProperties;
614 checkProperties.insert("sourceid");
615 std::set<std::string> additionalProperties;
616 if (CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties))
617 sourcesneeded = (additionalProperties.find("sourceid") != additionalProperties.end());
618
619 CFileItemList items;
620 if (!musicdatabase.GetGenresJSON(items, sourcesneeded))
621 return InternalError;
622
623 HandleFileItemList("genreid", false, "genres", items, parameterObject, result);
624 return OK;
625 }
626
GetRoles(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)627 JSONRPC_STATUS CAudioLibrary::GetRoles(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
628 {
629 CMusicDatabase musicdatabase;
630 if (!musicdatabase.Open())
631 return InternalError;
632
633 CFileItemList items;
634 if (!musicdatabase.GetRolesNav("musicdb://songs/", items))
635 return InternalError;
636
637 /* need to set strTitle in each item*/
638 for (unsigned int i = 0; i < (unsigned int)items.Size(); i++)
639 items[i]->GetMusicInfoTag()->SetTitle(items[i]->GetLabel());
640
641 HandleFileItemList("roleid", false, "roles", items, parameterObject, result);
642 return OK;
643 }
644
GetSources(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)645 JSONRPC_STATUS JSONRPC::CAudioLibrary::GetSources(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result)
646 {
647 CMusicDatabase musicdatabase;
648 if (!musicdatabase.Open())
649 return InternalError;
650
651 // Add "file" to "properties" array by default
652 CVariant param = parameterObject;
653 if (!param.isMember("properties"))
654 param["properties"] = CVariant(CVariant::VariantTypeArray);
655 if (!param["properties"].isMember("file"))
656 param["properties"].append("file");
657
658 CFileItemList items;
659 if (!musicdatabase.GetSources(items))
660 return InternalError;
661
662 HandleFileItemList("sourceid", true, "sources", items, param, result);
663 return OK;
664 }
665
GetAvailableArtTypes(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)666 JSONRPC_STATUS CAudioLibrary::GetAvailableArtTypes(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result)
667 {
668 std::string mediaType;
669 int mediaID = -1;
670 if (parameterObject["item"].isMember("albumid"))
671 {
672 mediaType = MediaTypeAlbum;
673 mediaID = parameterObject["item"]["albumid"].asInteger32();
674 }
675 if (parameterObject["item"].isMember("artistid"))
676 {
677 mediaType = MediaTypeArtist;
678 mediaID = parameterObject["item"]["artistid"].asInteger32();
679 }
680 if (mediaID == -1)
681 return InternalError;
682
683 CMusicDatabase musicdatabase;
684 if (!musicdatabase.Open())
685 return InternalError;
686
687 CVariant availablearttypes = CVariant(CVariant::VariantTypeArray);
688 for (const auto& artType : musicdatabase.GetAvailableArtTypesForItem(mediaID, mediaType))
689 {
690 availablearttypes.append(artType);
691 }
692 result = CVariant(CVariant::VariantTypeObject);
693 result["availablearttypes"] = availablearttypes;
694
695 return OK;
696 }
697
GetAvailableArt(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)698 JSONRPC_STATUS CAudioLibrary::GetAvailableArt(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result)
699 {
700 std::string mediaType;
701 int mediaID = -1;
702 if (parameterObject["item"].isMember("albumid"))
703 {
704 mediaType = MediaTypeAlbum;
705 mediaID = parameterObject["item"]["albumid"].asInteger32();
706 }
707 if (parameterObject["item"].isMember("artistid"))
708 {
709 mediaType = MediaTypeArtist;
710 mediaID = parameterObject["item"]["artistid"].asInteger32();
711 }
712 if (mediaID == -1)
713 return InternalError;
714
715 std::string artType = parameterObject["arttype"].asString();
716 StringUtils::ToLower(artType);
717
718 CMusicDatabase musicdatabase;
719 if (!musicdatabase.Open())
720 return InternalError;
721
722 CVariant availableart = CVariant(CVariant::VariantTypeArray);
723 for (const auto& artentry : musicdatabase.GetAvailableArtForItem(mediaID, mediaType, artType))
724 {
725 CVariant item = CVariant(CVariant::VariantTypeObject);
726 item["url"] = CTextureUtils::GetWrappedImageURL(artentry.m_url);
727 item["arttype"] = artentry.m_aspect;
728 if (!artentry.m_preview.empty())
729 item["previewurl"] = CTextureUtils::GetWrappedImageURL(artentry.m_preview);
730 availableart.append(item);
731 }
732 result = CVariant(CVariant::VariantTypeObject);
733 result["availableart"] = availableart;
734
735 return OK;
736 }
737
SetArtistDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)738 JSONRPC_STATUS CAudioLibrary::SetArtistDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
739 {
740 int id = (int)parameterObject["artistid"].asInteger();
741
742 CMusicDatabase musicdatabase;
743 if (!musicdatabase.Open())
744 return InternalError;
745
746 CArtist artist;
747 if (!musicdatabase.GetArtist(id, artist) || artist.idArtist <= 0)
748 return InvalidParams;
749
750 if (ParameterNotNull(parameterObject, "artist"))
751 artist.strArtist = parameterObject["artist"].asString();
752 if (ParameterNotNull(parameterObject, "instrument"))
753 CopyStringArray(parameterObject["instrument"], artist.instruments);
754 if (ParameterNotNull(parameterObject, "style"))
755 CopyStringArray(parameterObject["style"], artist.styles);
756 if (ParameterNotNull(parameterObject, "mood"))
757 CopyStringArray(parameterObject["mood"], artist.moods);
758 if (ParameterNotNull(parameterObject, "born"))
759 artist.strBorn = parameterObject["born"].asString();
760 if (ParameterNotNull(parameterObject, "formed"))
761 artist.strFormed = parameterObject["formed"].asString();
762 if (ParameterNotNull(parameterObject, "description"))
763 artist.strBiography = parameterObject["description"].asString();
764 if (ParameterNotNull(parameterObject, "genre"))
765 CopyStringArray(parameterObject["genre"], artist.genre);
766 if (ParameterNotNull(parameterObject, "died"))
767 artist.strDied = parameterObject["died"].asString();
768 if (ParameterNotNull(parameterObject, "disbanded"))
769 artist.strDisbanded = parameterObject["disbanded"].asString();
770 if (ParameterNotNull(parameterObject, "yearsactive"))
771 CopyStringArray(parameterObject["yearsactive"], artist.yearsActive);
772 if (ParameterNotNull(parameterObject, "musicbrainzartistid"))
773 artist.strMusicBrainzArtistID = parameterObject["musicbrainzartistid"].asString();
774 if (ParameterNotNull(parameterObject, "sortname"))
775 artist.strSortName = parameterObject["sortname"].asString();
776 if (ParameterNotNull(parameterObject, "type"))
777 artist.strType = parameterObject["type"].asString();
778 if (ParameterNotNull(parameterObject, "gender"))
779 artist.strGender = parameterObject["gender"].asString();
780 if (ParameterNotNull(parameterObject, "disambiguation"))
781 artist.strDisambiguation = parameterObject["disambiguation"].asString();
782
783 // Update existing art. Any existing artwork that isn't specified in this request stays as is.
784 // If the value is null then the existing art with that type is removed.
785 if (ParameterNotNull(parameterObject, "art"))
786 {
787 // Get current artwork
788 musicdatabase.GetArtForItem(artist.idArtist, MediaTypeArtist, artist.art);
789
790 std::set<std::string> removedArtwork;
791 CVariant art = parameterObject["art"];
792 for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); artIt++)
793 {
794 if (artIt->second.isString() && !artIt->second.asString().empty())
795 artist.art[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString());
796 else if (artIt->second.isNull())
797 {
798 artist.art.erase(artIt->first);
799 removedArtwork.insert(artIt->first);
800 }
801 }
802 // Remove null art now, as not done by update
803 if (!musicdatabase.RemoveArtForItem(artist.idArtist, MediaTypeArtist, removedArtwork))
804 return InternalError;
805 }
806
807 // Update artist including adding or replacing (but not removing) art
808 if (!musicdatabase.UpdateArtist(artist))
809 return InternalError;
810
811 CJSONRPCUtils::NotifyItemUpdated();
812 return ACK;
813 }
814
SetAlbumDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)815 JSONRPC_STATUS CAudioLibrary::SetAlbumDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
816 {
817 int id = (int)parameterObject["albumid"].asInteger();
818
819 CMusicDatabase musicdatabase;
820 if (!musicdatabase.Open())
821 return InternalError;
822
823 CAlbum album;
824 // Get current album details, but not songs as we do not want to update them here
825 if (!musicdatabase.GetAlbum(id, album, false) || album.idAlbum <= 0)
826 return InvalidParams;
827
828 if (ParameterNotNull(parameterObject, "title"))
829 album.strAlbum = parameterObject["title"].asString();
830 if (ParameterNotNull(parameterObject, "displayartist"))
831 album.strArtistDesc = parameterObject["displayartist"].asString();
832 // Set album sort string before processing artist credits
833 if (ParameterNotNull(parameterObject, "sortartist"))
834 album.strArtistSort = parameterObject["sortartist"].asString();
835
836 // Match up artist names and mbids to make new artist credits
837 // Mbid values only apply if there are names
838 if (ParameterNotNull(parameterObject, "artist"))
839 {
840 std::vector<std::string> artists;
841 std::vector<std::string> mbids;
842 CopyStringArray(parameterObject["artist"], artists);
843 // Check for Musicbrainz ids
844 if (ParameterNotNull(parameterObject, "musicbrainzalbumartistid"))
845 CopyStringArray(parameterObject["musicbrainzalbumartistid"], mbids);
846 // When display artist is not provided and yet artists is changing make by concatenation
847 if (!ParameterNotNull(parameterObject, "displayartist"))
848 album.strArtistDesc = StringUtils::Join(artists, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
849 album.SetArtistCredits(artists, std::vector<std::string>(), mbids);
850 // On updatealbum artists will be changed
851 album.bArtistSongMerge = true;
852 }
853
854 if (ParameterNotNull(parameterObject, "description"))
855 album.strReview = parameterObject["description"].asString();
856 if (ParameterNotNull(parameterObject, "genre"))
857 CopyStringArray(parameterObject["genre"], album.genre);
858 if (ParameterNotNull(parameterObject, "theme"))
859 CopyStringArray(parameterObject["theme"], album.themes);
860 if (ParameterNotNull(parameterObject, "mood"))
861 CopyStringArray(parameterObject["mood"], album.moods);
862 if (ParameterNotNull(parameterObject, "style"))
863 CopyStringArray(parameterObject["style"], album.styles);
864 if (ParameterNotNull(parameterObject, "type"))
865 album.strType = parameterObject["type"].asString();
866 if (ParameterNotNull(parameterObject, "albumlabel"))
867 album.strLabel = parameterObject["albumlabel"].asString();
868 if (ParameterNotNull(parameterObject, "rating"))
869 album.fRating = parameterObject["rating"].asFloat();
870 if (ParameterNotNull(parameterObject, "userrating"))
871 album.iUserrating = static_cast<int>(parameterObject["userrating"].asInteger());
872 if (ParameterNotNull(parameterObject, "votes"))
873 album.iVotes = static_cast<int>(parameterObject["votes"].asInteger());
874 if (ParameterNotNull(parameterObject, "year"))
875 album.strReleaseDate = parameterObject["year"].asString();
876 if (ParameterNotNull(parameterObject, "musicbrainzalbumid"))
877 album.strMusicBrainzAlbumID = parameterObject["musicbrainzalbumid"].asString();
878 if (ParameterNotNull(parameterObject, "musicbrainzreleasegroupid"))
879 album.strReleaseGroupMBID = parameterObject["musicbrainzreleasegroupid"].asString();
880 if (ParameterNotNull(parameterObject, "isboxset"))
881 album.bBoxedSet = parameterObject["isboxset"].asBoolean();
882 if (ParameterNotNull(parameterObject, "originaldate"))
883 album.strOrigReleaseDate = parameterObject["originaldate"].asString();
884 if (ParameterNotNull(parameterObject, "releasedate"))
885 album.strReleaseDate = parameterObject["releasedate"].asString();
886 if (ParameterNotNull(parameterObject, "albumstatus"))
887 album.strReleaseStatus = parameterObject["albumstatus"].asString();
888
889 // Update existing art. Any existing artwork that isn't specified in this request stays as is.
890 // If the value is null then the existing art with that type is removed.
891 if (ParameterNotNull(parameterObject, "art"))
892 {
893 // Get current artwork
894 musicdatabase.GetArtForItem(album.idAlbum, MediaTypeAlbum, album.art);
895
896 std::set<std::string> removedArtwork;
897 CVariant art = parameterObject["art"];
898 for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); artIt++)
899 {
900 if (artIt->second.isString() && !artIt->second.asString().empty())
901 album.art[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString());
902 else if (artIt->second.isNull())
903 {
904 album.art.erase(artIt->first);
905 removedArtwork.insert(artIt->first);
906 }
907 }
908 // Remove null art now, as not done by update
909 if (!musicdatabase.RemoveArtForItem(album.idAlbum, MediaTypeAlbum, removedArtwork))
910 return InternalError;
911 }
912
913 // Update artist including adding or replacing (but not removing) art
914 if (!musicdatabase.UpdateAlbum(album))
915 return InternalError;
916
917 CJSONRPCUtils::NotifyItemUpdated();
918 return ACK;
919 }
920
SetSongDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)921 JSONRPC_STATUS CAudioLibrary::SetSongDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
922 {
923 int id = (int)parameterObject["songid"].asInteger();
924
925 CMusicDatabase musicdatabase;
926 if (!musicdatabase.Open())
927 return InternalError;
928
929 CSong song;
930 if (!musicdatabase.GetSong(id, song) || song.idSong != id)
931 return InvalidParams;
932
933 if (ParameterNotNull(parameterObject, "title"))
934 song.strTitle = parameterObject["title"].asString();
935
936 if (ParameterNotNull(parameterObject, "displayartist"))
937 song.strArtistDesc = parameterObject["displayartist"].asString();
938 // Set album sort string before processing artist credits
939 if (ParameterNotNull(parameterObject, "sortartist"))
940 song.strArtistSort = parameterObject["sortartist"].asString();
941
942 // Match up artist names and mbids to make new artist credits
943 // Mbid values only apply if there are names
944 bool updateartists = false;
945 if (ParameterNotNull(parameterObject, "artist"))
946 {
947 std::vector<std::string> artists, mbids;
948 updateartists = true;
949 CopyStringArray(parameterObject["artist"], artists);
950 // Check for Musicbrainz ids
951 if (ParameterNotNull(parameterObject, "musicbrainzartistid"))
952 CopyStringArray(parameterObject["musicbrainzartistid"], mbids);
953 // When display artist is not provided and yet artists is changing make by concatenation
954 if (!ParameterNotNull(parameterObject, "displayartist"))
955 song.strArtistDesc = StringUtils::Join(artists, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
956 song.SetArtistCredits(artists, std::vector<std::string>(), mbids);
957 }
958
959 if (ParameterNotNull(parameterObject, "genre"))
960 CopyStringArray(parameterObject["genre"], song.genre);
961 if (ParameterNotNull(parameterObject, "year"))
962 song.strReleaseDate = parameterObject["year"].asString();
963 if (ParameterNotNull(parameterObject, "rating"))
964 song.rating = parameterObject["rating"].asFloat();
965 if (ParameterNotNull(parameterObject, "userrating"))
966 song.userrating = static_cast<int>(parameterObject["userrating"].asInteger());
967 if (ParameterNotNull(parameterObject, "track"))
968 song.iTrack = (song.iTrack & 0xffff0000) | ((int)parameterObject["track"].asInteger() & 0xffff);
969 if (ParameterNotNull(parameterObject, "disc"))
970 song.iTrack = (song.iTrack & 0xffff) | ((int)parameterObject["disc"].asInteger() << 16);
971 if (ParameterNotNull(parameterObject, "duration"))
972 song.iDuration = (int)parameterObject["duration"].asInteger();
973 if (ParameterNotNull(parameterObject, "comment"))
974 song.strComment = parameterObject["comment"].asString();
975 if (ParameterNotNull(parameterObject, "musicbrainztrackid"))
976 song.strMusicBrainzTrackID = parameterObject["musicbrainztrackid"].asString();
977 if (ParameterNotNull(parameterObject, "playcount"))
978 song.iTimesPlayed = static_cast<int>(parameterObject["playcount"].asInteger());
979 if (ParameterNotNull(parameterObject, "lastplayed"))
980 song.lastPlayed.SetFromDBDateTime(parameterObject["lastplayed"].asString());
981 if (ParameterNotNull(parameterObject, "mood"))
982 song.strMood = parameterObject["mood"].asString();
983 if (ParameterNotNull(parameterObject, "disctitle"))
984 song.strDiscSubtitle = parameterObject["disctitle"].asString();
985 if (ParameterNotNull(parameterObject, "bpm"))
986 song.iBPM = static_cast<int>(parameterObject["bpm"].asInteger());
987 if (ParameterNotNull(parameterObject, "originaldate"))
988 song.strOrigReleaseDate = parameterObject["originaldate"].asString();
989 if (ParameterNotNull(parameterObject, "albumreleasedate"))
990 song.strReleaseDate = parameterObject["albumreleasedate"].asString();
991
992 // Update existing art. Any existing artwork that isn't specified in this request stays as is.
993 // If the value is null then the existing art with that type is removed.
994 if (ParameterNotNull(parameterObject, "art"))
995 {
996 // Get current artwork
997 std::map<std::string, std::string> artwork;
998 musicdatabase.GetArtForItem(song.idSong, MediaTypeSong, artwork);
999
1000 std::set<std::string> removedArtwork;
1001 CVariant art = parameterObject["art"];
1002 for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); artIt++)
1003 {
1004 if (artIt->second.isString() && !artIt->second.asString().empty())
1005 artwork[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString());
1006 else if (artIt->second.isNull())
1007 {
1008 artwork.erase(artIt->first);
1009 removedArtwork.insert(artIt->first);
1010 }
1011 }
1012 //Update artwork, not done in update song
1013 musicdatabase.SetArtForItem(song.idSong, MediaTypeSong, artwork);
1014 if (!musicdatabase.RemoveArtForItem(song.idSong, MediaTypeSong, removedArtwork))
1015 return InternalError;
1016 }
1017
1018 // Update song (not including artwork)
1019 if (!musicdatabase.UpdateSong(song, updateartists))
1020 return InternalError;
1021
1022 CJSONRPCUtils::NotifyItemUpdated();
1023 return ACK;
1024 }
1025
Scan(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)1026 JSONRPC_STATUS CAudioLibrary::Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
1027 {
1028 std::string directory = parameterObject["directory"].asString();
1029 std::string cmd = StringUtils::Format("updatelibrary(music, %s, %s)", StringUtils::Paramify(directory).c_str(), parameterObject["showdialogs"].asBoolean() ? "true" : "false");
1030
1031 CApplicationMessenger::GetInstance().SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd);
1032 return ACK;
1033 }
1034
Export(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)1035 JSONRPC_STATUS CAudioLibrary::Export(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
1036 {
1037 std::string cmd;
1038 if (parameterObject["options"].isMember("path"))
1039 cmd = StringUtils::Format("exportlibrary2(music, singlefile, %s, albums, albumartists)", StringUtils::Paramify(parameterObject["options"]["path"].asString()).c_str());
1040 else
1041 {
1042 cmd = "exportlibrary2(music, library, dummy, albums, albumartists";
1043 if (parameterObject["options"]["images"].isBoolean() &&
1044 parameterObject["options"]["images"].asBoolean() == true)
1045 cmd += ", artwork";
1046 if (parameterObject["options"]["overwrite"].isBoolean() &&
1047 parameterObject["options"]["overwrite"].asBoolean() == true)
1048 cmd += ", overwrite";
1049 cmd += ")";
1050 }
1051 CApplicationMessenger::GetInstance().SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd);
1052 return ACK;
1053 }
1054
Clean(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)1055 JSONRPC_STATUS CAudioLibrary::Clean(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
1056 {
1057 std::string cmd = StringUtils::Format("cleanlibrary(music, %s)", parameterObject["showdialogs"].asBoolean() ? "true" : "false");
1058 CApplicationMessenger::GetInstance().SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd);
1059 return ACK;
1060 }
1061
FillFileItem(const std::string & strFilename,CFileItemPtr & item,const CVariant & parameterObject)1062 bool CAudioLibrary::FillFileItem(const std::string &strFilename, CFileItemPtr &item, const CVariant ¶meterObject /* = CVariant(CVariant::VariantTypeArray) */)
1063 {
1064 CMusicDatabase musicdatabase;
1065 if (strFilename.empty())
1066 return false;
1067
1068 bool filled = false;
1069 if (musicdatabase.Open())
1070 {
1071 if (CDirectory::Exists(strFilename))
1072 {
1073 CAlbum album;
1074 int albumid = musicdatabase.GetAlbumIdByPath(strFilename);
1075 if (musicdatabase.GetAlbum(albumid, album, false))
1076 {
1077 item->SetFromAlbum(album);
1078 FillItemArtistIDs(album.GetArtistIDArray(), item);
1079
1080 CFileItemList items;
1081 items.Add(item);
1082
1083 if (GetAdditionalAlbumDetails(parameterObject, items, musicdatabase) == OK)
1084 filled = true;
1085 }
1086 }
1087 else
1088 {
1089 CSong song;
1090 if (musicdatabase.GetSongByFileName(strFilename, song))
1091 {
1092 item->SetFromSong(song);
1093 FillItemArtistIDs(song.GetArtistIDArray(), item);
1094
1095 CFileItemList items;
1096 items.Add(item);
1097 if (GetAdditionalSongDetails(parameterObject, items, musicdatabase) == OK)
1098 filled = true;
1099 }
1100 }
1101 }
1102
1103 if (item->GetLabel().empty())
1104 {
1105 item->SetLabel(CUtil::GetTitleFromPath(strFilename, false));
1106 if (item->GetLabel().empty())
1107 item->SetLabel(URIUtils::GetFileName(strFilename));
1108 }
1109
1110 return filled;
1111 }
1112
FillFileItemList(const CVariant & parameterObject,CFileItemList & list)1113 bool CAudioLibrary::FillFileItemList(const CVariant ¶meterObject, CFileItemList &list)
1114 {
1115 CMusicDatabase musicdatabase;
1116 if (!musicdatabase.Open())
1117 return false;
1118
1119 std::string file = parameterObject["file"].asString();
1120 int artistID = (int)parameterObject["artistid"].asInteger(-1);
1121 int albumID = (int)parameterObject["albumid"].asInteger(-1);
1122 int genreID = (int)parameterObject["genreid"].asInteger(-1);
1123
1124 bool success = false;
1125 CFileItemPtr fileItem(new CFileItem());
1126 if (FillFileItem(file, fileItem, parameterObject))
1127 {
1128 success = true;
1129 list.Add(fileItem);
1130 }
1131
1132 if (artistID != -1 || albumID != -1 || genreID != -1)
1133 success |= musicdatabase.GetSongsNav("musicdb://songs/", list, genreID, artistID, albumID);
1134
1135 int songID = (int)parameterObject["songid"].asInteger(-1);
1136 if (songID != -1)
1137 {
1138 CSong song;
1139 if (musicdatabase.GetSong(songID, song))
1140 {
1141 list.Add(CFileItemPtr(new CFileItem(song)));
1142 success = true;
1143 }
1144 }
1145
1146 if (success)
1147 {
1148 // If we retrieved the list of songs by "artistid"
1149 // we sort by album (and implicitly by track number)
1150 if (artistID != -1)
1151 list.Sort(SortByAlbum, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
1152 // If we retrieve the list of songs by "genreid"
1153 // we sort by artist (and implicitly by album and track number)
1154 else if (genreID != -1)
1155 list.Sort(SortByArtist, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
1156 // otherwise we sort by track number
1157 else
1158 list.Sort(SortByTrackNumber, SortOrderAscending);
1159
1160 }
1161
1162 return success;
1163 }
1164
FillItemArtistIDs(const std::vector<int> & artistids,CFileItemPtr & item)1165 void CAudioLibrary::FillItemArtistIDs(const std::vector<int>& artistids, CFileItemPtr& item)
1166 {
1167 // Add artistIds as separate property as not part of CMusicInfoTag
1168 CVariant artistidObj(CVariant::VariantTypeArray);
1169 for (const auto& artistid : artistids)
1170 artistidObj.push_back(artistid);
1171
1172 item->SetProperty("artistid", artistidObj);
1173 }
1174
FillAlbumItem(const CAlbum & album,const std::string & path,CFileItemPtr & item)1175 void CAudioLibrary::FillAlbumItem(const CAlbum &album, const std::string &path, CFileItemPtr &item)
1176 {
1177 item = CFileItemPtr(new CFileItem(path, album));
1178 // Add album artistIds as separate property as not part of CMusicInfoTag
1179 std::vector<int> artistids = album.GetArtistIDArray();
1180 FillItemArtistIDs(artistids, item);
1181 }
1182
GetAdditionalDetails(const CVariant & parameterObject,CFileItemList & items)1183 JSONRPC_STATUS CAudioLibrary::GetAdditionalDetails(const CVariant ¶meterObject, CFileItemList &items)
1184 {
1185 if (items.IsEmpty())
1186 return OK;
1187
1188 CMusicDatabase musicdb;
1189 if (CMediaTypes::IsMediaType(items.GetContent(), MediaTypeArtist))
1190 return GetAdditionalArtistDetails(parameterObject, items, musicdb);
1191 else if (CMediaTypes::IsMediaType(items.GetContent(), MediaTypeAlbum))
1192 return GetAdditionalAlbumDetails(parameterObject, items, musicdb);
1193 else if (CMediaTypes::IsMediaType(items.GetContent(), MediaTypeSong))
1194 return GetAdditionalSongDetails(parameterObject, items, musicdb);
1195
1196 return OK;
1197 }
1198
GetAdditionalArtistDetails(const CVariant & parameterObject,CFileItemList & items,CMusicDatabase & musicdatabase)1199 JSONRPC_STATUS CAudioLibrary::GetAdditionalArtistDetails(const CVariant ¶meterObject, CFileItemList &items, CMusicDatabase &musicdatabase)
1200 {
1201 if (!musicdatabase.Open())
1202 return InternalError;
1203
1204 std::set<std::string> checkProperties;
1205 checkProperties.insert("roles");
1206 checkProperties.insert("songgenres");
1207 checkProperties.insert("isalbumartist");
1208 checkProperties.insert("sourceid");
1209 std::set<std::string> additionalProperties;
1210 if (!CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties))
1211 return OK;
1212
1213 if (additionalProperties.find("roles") != additionalProperties.end())
1214 {
1215 for (int i = 0; i < items.Size(); i++)
1216 {
1217 CFileItemPtr item = items[i];
1218 musicdatabase.GetRolesByArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get());
1219 }
1220 }
1221 if (additionalProperties.find("songgenres") != additionalProperties.end())
1222 {
1223 for (int i = 0; i < items.Size(); i++)
1224 {
1225 CFileItemPtr item = items[i];
1226 musicdatabase.GetGenresByArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get());
1227 }
1228 }
1229 if (additionalProperties.find("isalbumartist") != additionalProperties.end())
1230 {
1231 for (int i = 0; i < items.Size(); i++)
1232 {
1233 CFileItemPtr item = items[i];
1234 musicdatabase.GetIsAlbumArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get());
1235 }
1236 }
1237 if (additionalProperties.find("sourceid") != additionalProperties.end())
1238 {
1239 for (int i = 0; i < items.Size(); i++)
1240 {
1241 CFileItemPtr item = items[i];
1242 musicdatabase.GetSourcesByArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get());
1243 }
1244 }
1245
1246 return OK;
1247 }
1248
GetAdditionalAlbumDetails(const CVariant & parameterObject,CFileItemList & items,CMusicDatabase & musicdatabase)1249 JSONRPC_STATUS CAudioLibrary::GetAdditionalAlbumDetails(const CVariant ¶meterObject, CFileItemList &items, CMusicDatabase &musicdatabase)
1250 {
1251 if (!musicdatabase.Open())
1252 return InternalError;
1253
1254 std::set<std::string> checkProperties;
1255 checkProperties.insert("songgenres");
1256 checkProperties.insert("sourceid");
1257 std::set<std::string> additionalProperties;
1258 if (!CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties))
1259 return OK;
1260
1261 if (additionalProperties.find("songgenres") != additionalProperties.end())
1262 {
1263 for (int i = 0; i < items.Size(); i++)
1264 {
1265 CFileItemPtr item = items[i];
1266 musicdatabase.GetGenresByAlbum(item->GetMusicInfoTag()->GetDatabaseId(), item.get());
1267 }
1268 }
1269 if (additionalProperties.find("sourceid") != additionalProperties.end())
1270 {
1271 for (int i = 0; i < items.Size(); i++)
1272 {
1273 CFileItemPtr item = items[i];
1274 musicdatabase.GetSourcesByAlbum(item->GetMusicInfoTag()->GetDatabaseId(), item.get());
1275 }
1276 }
1277
1278 return OK;
1279 }
1280
GetAdditionalSongDetails(const CVariant & parameterObject,CFileItemList & items,CMusicDatabase & musicdatabase)1281 JSONRPC_STATUS CAudioLibrary::GetAdditionalSongDetails(const CVariant ¶meterObject, CFileItemList &items, CMusicDatabase &musicdatabase)
1282 {
1283 if (!musicdatabase.Open())
1284 return InternalError;
1285
1286 std::set<std::string> checkProperties;
1287 checkProperties.insert("genreid");
1288 checkProperties.insert("sourceid");
1289 // Query (songview join songartistview) returns song.strAlbumArtists = CMusicInfoTag.m_strAlbumArtistDesc only
1290 // Actual album artist data, if required, comes from album_artist and artist tables.
1291 // It may differ from just splitting album artist description string
1292 checkProperties.insert("albumartist");
1293 checkProperties.insert("albumartistid");
1294 checkProperties.insert("musicbrainzalbumartistid");
1295 std::set<std::string> additionalProperties;
1296 if (!CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties))
1297 return OK;
1298
1299 for (int i = 0; i < items.Size(); i++)
1300 {
1301 CFileItemPtr item = items[i];
1302 if (additionalProperties.find("genreid") != additionalProperties.end())
1303 {
1304 std::vector<int> genreids;
1305 if (musicdatabase.GetGenresBySong(item->GetMusicInfoTag()->GetDatabaseId(), genreids))
1306 {
1307 CVariant genreidObj(CVariant::VariantTypeArray);
1308 for (const auto& genreid : genreids)
1309 genreidObj.push_back(genreid);
1310
1311 item->SetProperty("genreid", genreidObj);
1312 }
1313 }
1314 if (additionalProperties.find("sourceid") != additionalProperties.end())
1315 {
1316 musicdatabase.GetSourcesBySong(item->GetMusicInfoTag()->GetDatabaseId(), item->GetPath(), item.get());
1317 }
1318 if (item->GetMusicInfoTag()->GetAlbumId() > 0)
1319 {
1320 if (additionalProperties.find("albumartist") != additionalProperties.end() ||
1321 additionalProperties.find("albumartistid") != additionalProperties.end() ||
1322 additionalProperties.find("musicbrainzalbumartistid") != additionalProperties.end())
1323 {
1324 musicdatabase.GetArtistsByAlbum(item->GetMusicInfoTag()->GetAlbumId(), item.get());
1325 }
1326 }
1327 }
1328
1329 return OK;
1330 }
1331
CheckForAdditionalProperties(const CVariant & properties,const std::set<std::string> & checkProperties,std::set<std::string> & foundProperties)1332 bool CAudioLibrary::CheckForAdditionalProperties(const CVariant &properties, const std::set<std::string> &checkProperties, std::set<std::string> &foundProperties)
1333 {
1334 if (!properties.isArray() || properties.empty())
1335 return false;
1336
1337 std::set<std::string> checkingProperties = checkProperties;
1338 for (CVariant::const_iterator_array itr = properties.begin_array(); itr != properties.end_array() && !checkingProperties.empty(); itr++)
1339 {
1340 if (!itr->isString())
1341 continue;
1342
1343 std::string property = itr->asString();
1344 if (checkingProperties.find(property) != checkingProperties.end())
1345 {
1346 checkingProperties.erase(property);
1347 foundProperties.insert(property);
1348 }
1349 }
1350
1351 return !foundProperties.empty();
1352 }
1353