[videodb] remove unused seasons table from episode_view
[xbmc.git] / xbmc / playlists / SmartPlayList.cpp
blob4338bbec8cf1e98c923a48384baea486ca1b8112
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "SmartPlayList.h"
11 #include "ServiceBroker.h"
12 #include "Util.h"
13 #include "dbwrappers/Database.h"
14 #include "filesystem/File.h"
15 #include "filesystem/SmartPlaylistDirectory.h"
16 #include "guilib/LocalizeStrings.h"
17 #include "settings/Settings.h"
18 #include "settings/SettingsComponent.h"
19 #include "utils/DatabaseUtils.h"
20 #include "utils/JSONVariantParser.h"
21 #include "utils/JSONVariantWriter.h"
22 #include "utils/StreamDetails.h"
23 #include "utils/StringUtils.h"
24 #include "utils/StringValidation.h"
25 #include "utils/URIUtils.h"
26 #include "utils/Variant.h"
27 #include "utils/XMLUtils.h"
28 #include "utils/log.h"
30 #include <cstdlib>
31 #include <memory>
32 #include <set>
33 #include <string>
34 #include <vector>
36 using namespace XFILE;
38 namespace KODI::PLAYLIST
41 typedef struct
43 char string[17];
44 Field field;
45 CDatabaseQueryRule::FIELD_TYPE type;
46 StringValidation::Validator validator;
47 bool browseable;
48 int localizedString;
49 } translateField;
51 // clang-format off
52 static const translateField fields[] = {
53 { "none", FieldNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 231 },
54 { "filename", FieldFilename, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 561 },
55 { "path", FieldPath, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 573 },
56 { "album", FieldAlbum, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 558 },
57 { "albumartist", FieldAlbumArtist, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 566 },
58 { "artist", FieldArtist, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 557 },
59 { "tracknumber", FieldTrackNumber, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, false, 554 },
60 { "role", FieldRole, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 38033 },
61 { "comment", FieldComment, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 569 },
62 { "review", FieldReview, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 183 },
63 { "themes", FieldThemes, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21895 },
64 { "moods", FieldMoods, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 175 },
65 { "styles", FieldStyles, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 176 },
66 { "type", FieldAlbumType, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 564 },
67 { "compilation", FieldCompilation, CDatabaseQueryRule::BOOLEAN_FIELD, NULL, false, 204 },
68 { "label", FieldMusicLabel, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21899 },
69 { "title", FieldTitle, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 556 },
70 { "sorttitle", FieldSortTitle, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 171 },
71 { "originaltitle", FieldOriginalTitle, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 20376 },
72 { "year", FieldYear, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, true, 562 },
73 { "time", FieldTime, CDatabaseQueryRule::SECONDS_FIELD, StringValidation::IsTime, false, 180 },
74 { "playcount", FieldPlaycount, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, false, 567 },
75 { "lastplayed", FieldLastPlayed, CDatabaseQueryRule::DATE_FIELD, NULL, false, 568 },
76 { "inprogress", FieldInProgress, CDatabaseQueryRule::BOOLEAN_FIELD, NULL, false, 575 },
77 { "rating", FieldRating, CDatabaseQueryRule::REAL_FIELD, CSmartPlaylistRule::ValidateRating, false, 563 },
78 { "userrating", FieldUserRating, CDatabaseQueryRule::REAL_FIELD, CSmartPlaylistRule::ValidateMyRating, false, 38018 },
79 { "votes", FieldVotes, CDatabaseQueryRule::REAL_FIELD, StringValidation::IsPositiveInteger, false, 205 },
80 { "top250", FieldTop250, CDatabaseQueryRule::NUMERIC_FIELD, NULL, false, 13409 },
81 { "mpaarating", FieldMPAA, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 20074 },
82 { "dateadded", FieldDateAdded, CDatabaseQueryRule::DATE_FIELD, NULL, false, 570 },
83 { "datemodified", FieldDateModified, CDatabaseQueryRule::DATE_FIELD, NULL, false, 39119 },
84 { "datenew", FieldDateNew, CDatabaseQueryRule::DATE_FIELD, NULL, false, 21877 },
85 { "genre", FieldGenre, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 515 },
86 { "plot", FieldPlot, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 207 },
87 { "plotoutline", FieldPlotOutline, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 203 },
88 { "tagline", FieldTagline, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 202 },
89 { "set", FieldSet, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 20457 },
90 { "director", FieldDirector, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 20339 },
91 { "actor", FieldActor, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 20337 },
92 { "writers", FieldWriter, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 20417 },
93 { "airdate", FieldAirDate, CDatabaseQueryRule::DATE_FIELD, NULL, false, 20416 },
94 { "hastrailer", FieldTrailer, CDatabaseQueryRule::BOOLEAN_FIELD, NULL, false, 20423 },
95 { "studio", FieldStudio, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 572 },
96 { "country", FieldCountry, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 574 },
97 { "tvshow", FieldTvShowTitle, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 20364 },
98 { "status", FieldTvShowStatus, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 126 },
99 { "season", FieldSeason, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, false, 20373 },
100 { "episode", FieldEpisodeNumber, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, false, 20359 },
101 { "numepisodes", FieldNumberOfEpisodes, CDatabaseQueryRule::REAL_FIELD, StringValidation::IsPositiveInteger, false, 20360 },
102 { "numwatched", FieldNumberOfWatchedEpisodes, CDatabaseQueryRule::REAL_FIELD, StringValidation::IsPositiveInteger, false, 21457 },
103 { "videoresolution", FieldVideoResolution, CDatabaseQueryRule::REAL_FIELD, NULL, false, 21443 },
104 { "videocodec", FieldVideoCodec, CDatabaseQueryRule::TEXTIN_FIELD, NULL, false, 21445 },
105 { "videoaspect", FieldVideoAspectRatio, CDatabaseQueryRule::REAL_FIELD, NULL, false, 21374 },
106 { "audiochannels", FieldAudioChannels, CDatabaseQueryRule::REAL_FIELD, NULL, false, 21444 },
107 { "audiocodec", FieldAudioCodec, CDatabaseQueryRule::TEXTIN_FIELD, NULL, false, 21446 },
108 { "audiolanguage", FieldAudioLanguage, CDatabaseQueryRule::TEXTIN_FIELD, NULL, false, 21447 },
109 { "audiocount", FieldAudioCount, CDatabaseQueryRule::REAL_FIELD, StringValidation::IsPositiveInteger, false, 21481 },
110 { "subtitlecount", FieldSubtitleCount, CDatabaseQueryRule::REAL_FIELD, StringValidation::IsPositiveInteger, false, 21482 },
111 { "subtitlelanguage", FieldSubtitleLanguage, CDatabaseQueryRule::TEXTIN_FIELD, NULL, false, 21448 },
112 { "random", FieldRandom, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 590 },
113 { "playlist", FieldPlaylist, CDatabaseQueryRule::PLAYLIST_FIELD, NULL, true, 559 },
114 { "virtualfolder", FieldVirtualFolder, CDatabaseQueryRule::PLAYLIST_FIELD, NULL, true, 614 },
115 { "tag", FieldTag, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 20459 },
116 { "instruments", FieldInstruments, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21892 },
117 { "biography", FieldBiography, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21887 },
118 { "born", FieldBorn, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21893 },
119 { "bandformed", FieldBandFormed, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21894 },
120 { "disbanded", FieldDisbanded, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21896 },
121 { "died", FieldDied, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21897 },
122 { "artisttype", FieldArtistType, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 564 },
123 { "gender", FieldGender, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 39025 },
124 { "disambiguation", FieldDisambiguation, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 39026 },
125 { "source", FieldSource, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 39030 },
126 { "disctitle", FieldDiscTitle, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 38076 },
127 { "isboxset", FieldIsBoxset, CDatabaseQueryRule::BOOLEAN_FIELD, NULL, false, 38074 },
128 { "totaldiscs", FieldTotalDiscs, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, false, 38077 },
129 { "originalyear", FieldOrigYear, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, true, 38078 },
130 { "bpm", FieldBPM, CDatabaseQueryRule::NUMERIC_FIELD, NULL, false, 38080 },
131 { "samplerate", FieldSampleRate, CDatabaseQueryRule::NUMERIC_FIELD, NULL, false, 613 },
132 { "bitrate", FieldMusicBitRate, CDatabaseQueryRule::NUMERIC_FIELD, NULL, false, 623 },
133 { "channels", FieldNoOfChannels, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, false, 253 },
134 { "albumstatus", FieldAlbumStatus, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 38081 },
135 { "albumduration", FieldAlbumDuration, CDatabaseQueryRule::SECONDS_FIELD, StringValidation::IsTime, false, 180 },
136 { "hdrtype", FieldHdrType, CDatabaseQueryRule::TEXTIN_FIELD, NULL, false, 20474 },
138 // clang-format on
140 typedef struct
142 std::string name;
143 Field field;
144 bool canMix;
145 int localizedString;
146 } group;
148 // clang-format off
149 static const group groups[] = { { "", FieldUnknown, false, 571 },
150 { "none", FieldNone, false, 231 },
151 { "sets", FieldSet, true, 20434 },
152 { "genres", FieldGenre, false, 135 },
153 { "years", FieldYear, false, 652 },
154 { "actors", FieldActor, false, 344 },
155 { "directors", FieldDirector, false, 20348 },
156 { "writers", FieldWriter, false, 20418 },
157 { "studios", FieldStudio, false, 20388 },
158 { "countries", FieldCountry, false, 20451 },
159 { "artists", FieldArtist, false, 133 },
160 { "albums", FieldAlbum, false, 132 },
161 { "tags", FieldTag, false, 20459 },
162 { "originalyears", FieldOrigYear, false, 38078 },
164 // clang-format on
166 #define RULE_VALUE_SEPARATOR " / "
168 CSmartPlaylistRule::CSmartPlaylistRule() = default;
170 int CSmartPlaylistRule::TranslateField(const char *field) const
172 for (const translateField& f : fields)
173 if (StringUtils::EqualsNoCase(field, f.string)) return f.field;
174 return FieldNone;
177 std::string CSmartPlaylistRule::TranslateField(int field) const
179 for (const translateField& f : fields)
180 if (field == f.field) return f.string;
181 return "none";
184 SortBy CSmartPlaylistRule::TranslateOrder(const char *order)
186 return SortUtils::SortMethodFromString(order);
189 std::string CSmartPlaylistRule::TranslateOrder(SortBy order)
191 std::string sortOrder = SortUtils::SortMethodToString(order);
192 if (sortOrder.empty())
193 return "none";
195 return sortOrder;
198 Field CSmartPlaylistRule::TranslateGroup(const char *group)
200 for (const auto & i : groups)
202 if (StringUtils::EqualsNoCase(group, i.name))
203 return i.field;
206 return FieldUnknown;
209 std::string CSmartPlaylistRule::TranslateGroup(Field group)
211 for (const auto & i : groups)
213 if (group == i.field)
214 return i.name;
217 return "";
220 std::string CSmartPlaylistRule::GetLocalizedField(int field)
222 for (const translateField& f : fields)
223 if (field == f.field) return g_localizeStrings.Get(f.localizedString);
224 return g_localizeStrings.Get(16018);
227 CDatabaseQueryRule::FIELD_TYPE CSmartPlaylistRule::GetFieldType(int field) const
229 for (const translateField& f : fields)
230 if (field == f.field) return f.type;
231 return TEXT_FIELD;
234 bool CSmartPlaylistRule::IsFieldBrowseable(int field)
236 for (const translateField& f : fields)
237 if (field == f.field) return f.browseable;
239 return false;
242 bool CSmartPlaylistRule::Validate(const std::string &input, void *data)
244 if (data == NULL)
245 return true;
247 CSmartPlaylistRule *rule = static_cast<CSmartPlaylistRule*>(data);
249 // check if there's a validator for this rule
250 StringValidation::Validator validator = NULL;
251 for (const translateField& field : fields)
253 if (rule->m_field == field.field)
255 validator = field.validator;
256 break;
259 if (validator == NULL)
260 return true;
262 // split the input into multiple values and validate every value separately
263 std::vector<std::string> values = StringUtils::Split(input, RULE_VALUE_SEPARATOR);
264 for (std::vector<std::string>::const_iterator it = values.begin(); it != values.end(); ++it)
266 if (!validator(*it, data))
267 return false;
270 return true;
273 bool CSmartPlaylistRule::ValidateRating(const std::string &input, void *data)
275 char *end = NULL;
276 std::string strRating = input;
277 StringUtils::Trim(strRating);
279 double rating = std::strtod(strRating.c_str(), &end);
280 return (end == NULL || *end == '\0') &&
281 rating >= 0.0 && rating <= 10.0;
284 bool CSmartPlaylistRule::ValidateMyRating(const std::string &input, void *data)
286 std::string strRating = input;
287 StringUtils::Trim(strRating);
289 int rating = atoi(strRating.c_str());
290 return StringValidation::IsPositiveInteger(input, data) && rating <= 10;
293 std::vector<Field> CSmartPlaylistRule::GetFields(const std::string &type)
295 std::vector<Field> fields;
296 bool isVideo = false;
297 if (type == "mixed")
299 fields.push_back(FieldGenre);
300 fields.push_back(FieldAlbum);
301 fields.push_back(FieldArtist);
302 fields.push_back(FieldAlbumArtist);
303 fields.push_back(FieldTitle);
304 fields.push_back(FieldOriginalTitle);
305 fields.push_back(FieldYear);
306 fields.push_back(FieldTime);
307 fields.push_back(FieldTrackNumber);
308 fields.push_back(FieldFilename);
309 fields.push_back(FieldPath);
310 fields.push_back(FieldPlaycount);
311 fields.push_back(FieldLastPlayed);
313 else if (type == "songs")
315 fields.push_back(FieldGenre);
316 fields.push_back(FieldSource);
317 fields.push_back(FieldAlbum);
318 fields.push_back(FieldDiscTitle);
319 fields.push_back(FieldArtist);
320 fields.push_back(FieldAlbumArtist);
321 fields.push_back(FieldTitle);
322 fields.push_back(FieldYear);
323 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
324 CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
325 fields.push_back(FieldOrigYear);
326 fields.push_back(FieldTime);
327 fields.push_back(FieldTrackNumber);
328 fields.push_back(FieldFilename);
329 fields.push_back(FieldPath);
330 fields.push_back(FieldPlaycount);
331 fields.push_back(FieldLastPlayed);
332 fields.push_back(FieldRating);
333 fields.push_back(FieldUserRating);
334 fields.push_back(FieldComment);
335 fields.push_back(FieldMoods);
336 fields.push_back(FieldBPM);
337 fields.push_back(FieldSampleRate);
338 fields.push_back(FieldMusicBitRate);
339 fields.push_back(FieldNoOfChannels);
340 fields.push_back(FieldDateAdded);
341 fields.push_back(FieldDateModified);
342 fields.push_back(FieldDateNew);
344 else if (type == "albums")
346 fields.push_back(FieldGenre);
347 fields.push_back(FieldSource);
348 fields.push_back(FieldAlbum);
349 fields.push_back(FieldDiscTitle);
350 fields.push_back(FieldTotalDiscs);
351 fields.push_back(FieldIsBoxset);
352 fields.push_back(FieldArtist); // any artist
353 fields.push_back(FieldAlbumArtist); // album artist
354 fields.push_back(FieldYear);
355 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
356 CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
357 fields.push_back(FieldOrigYear);
358 fields.push_back(FieldAlbumDuration);
359 fields.push_back(FieldReview);
360 fields.push_back(FieldThemes);
361 fields.push_back(FieldMoods);
362 fields.push_back(FieldStyles);
363 fields.push_back(FieldCompilation);
364 fields.push_back(FieldAlbumType);
365 fields.push_back(FieldMusicLabel);
366 fields.push_back(FieldRating);
367 fields.push_back(FieldUserRating);
368 fields.push_back(FieldPlaycount);
369 fields.push_back(FieldLastPlayed);
370 fields.push_back(FieldPath);
371 fields.push_back(FieldAlbumStatus);
372 fields.push_back(FieldDateAdded);
373 fields.push_back(FieldDateModified);
374 fields.push_back(FieldDateNew);
376 else if (type == "artists")
378 fields.push_back(FieldArtist);
379 fields.push_back(FieldSource);
380 fields.push_back(FieldGenre);
381 fields.push_back(FieldMoods);
382 fields.push_back(FieldStyles);
383 fields.push_back(FieldInstruments);
384 fields.push_back(FieldBiography);
385 fields.push_back(FieldArtistType);
386 fields.push_back(FieldGender);
387 fields.push_back(FieldDisambiguation);
388 fields.push_back(FieldBorn);
389 fields.push_back(FieldBandFormed);
390 fields.push_back(FieldDisbanded);
391 fields.push_back(FieldDied);
392 fields.push_back(FieldRole);
393 fields.push_back(FieldPath);
394 fields.push_back(FieldDateAdded);
395 fields.push_back(FieldDateModified);
396 fields.push_back(FieldDateNew);
398 else if (type == "tvshows")
400 fields.push_back(FieldTitle);
401 fields.push_back(FieldOriginalTitle);
402 fields.push_back(FieldPlot);
403 fields.push_back(FieldTvShowStatus);
404 fields.push_back(FieldVotes);
405 fields.push_back(FieldRating);
406 fields.push_back(FieldUserRating);
407 fields.push_back(FieldYear);
408 fields.push_back(FieldGenre);
409 fields.push_back(FieldDirector);
410 fields.push_back(FieldActor);
411 fields.push_back(FieldNumberOfEpisodes);
412 fields.push_back(FieldNumberOfWatchedEpisodes);
413 fields.push_back(FieldPlaycount);
414 fields.push_back(FieldPath);
415 fields.push_back(FieldStudio);
416 fields.push_back(FieldMPAA);
417 fields.push_back(FieldDateAdded);
418 fields.push_back(FieldLastPlayed);
419 fields.push_back(FieldInProgress);
420 fields.push_back(FieldTag);
422 else if (type == "episodes")
424 fields.push_back(FieldTitle);
425 fields.push_back(FieldTvShowTitle);
426 fields.push_back(FieldOriginalTitle);
427 fields.push_back(FieldPlot);
428 fields.push_back(FieldVotes);
429 fields.push_back(FieldRating);
430 fields.push_back(FieldUserRating);
431 fields.push_back(FieldTime);
432 fields.push_back(FieldWriter);
433 fields.push_back(FieldAirDate);
434 fields.push_back(FieldPlaycount);
435 fields.push_back(FieldLastPlayed);
436 fields.push_back(FieldInProgress);
437 fields.push_back(FieldGenre);
438 fields.push_back(FieldYear); // premiered
439 fields.push_back(FieldDirector);
440 fields.push_back(FieldActor);
441 fields.push_back(FieldEpisodeNumber);
442 fields.push_back(FieldSeason);
443 fields.push_back(FieldFilename);
444 fields.push_back(FieldPath);
445 fields.push_back(FieldStudio);
446 fields.push_back(FieldMPAA);
447 fields.push_back(FieldDateAdded);
448 fields.push_back(FieldTag);
449 isVideo = true;
451 else if (type == "movies")
453 fields.push_back(FieldTitle);
454 fields.push_back(FieldOriginalTitle);
455 fields.push_back(FieldPlot);
456 fields.push_back(FieldPlotOutline);
457 fields.push_back(FieldTagline);
458 fields.push_back(FieldVotes);
459 fields.push_back(FieldRating);
460 fields.push_back(FieldUserRating);
461 fields.push_back(FieldTime);
462 fields.push_back(FieldWriter);
463 fields.push_back(FieldPlaycount);
464 fields.push_back(FieldLastPlayed);
465 fields.push_back(FieldInProgress);
466 fields.push_back(FieldGenre);
467 fields.push_back(FieldCountry);
468 fields.push_back(FieldYear); // premiered
469 fields.push_back(FieldDirector);
470 fields.push_back(FieldActor);
471 fields.push_back(FieldMPAA);
472 fields.push_back(FieldTop250);
473 fields.push_back(FieldStudio);
474 fields.push_back(FieldTrailer);
475 fields.push_back(FieldFilename);
476 fields.push_back(FieldPath);
477 fields.push_back(FieldSet);
478 fields.push_back(FieldTag);
479 fields.push_back(FieldDateAdded);
480 isVideo = true;
482 else if (type == "musicvideos")
484 fields.push_back(FieldTitle);
485 fields.push_back(FieldGenre);
486 fields.push_back(FieldAlbum);
487 fields.push_back(FieldYear);
488 fields.push_back(FieldArtist);
489 fields.push_back(FieldFilename);
490 fields.push_back(FieldPath);
491 fields.push_back(FieldPlaycount);
492 fields.push_back(FieldLastPlayed);
493 fields.push_back(FieldRating);
494 fields.push_back(FieldUserRating);
495 fields.push_back(FieldTime);
496 fields.push_back(FieldDirector);
497 fields.push_back(FieldStudio);
498 fields.push_back(FieldPlot);
499 fields.push_back(FieldTag);
500 fields.push_back(FieldDateAdded);
501 isVideo = true;
503 if (isVideo)
505 fields.push_back(FieldVideoResolution);
506 fields.push_back(FieldAudioChannels);
507 fields.push_back(FieldAudioCount);
508 fields.push_back(FieldSubtitleCount);
509 fields.push_back(FieldVideoCodec);
510 fields.push_back(FieldAudioCodec);
511 fields.push_back(FieldAudioLanguage);
512 fields.push_back(FieldSubtitleLanguage);
513 fields.push_back(FieldVideoAspectRatio);
514 fields.push_back(FieldHdrType);
516 fields.push_back(FieldPlaylist);
517 fields.push_back(FieldVirtualFolder);
519 return fields;
522 std::vector<SortBy> CSmartPlaylistRule::GetOrders(const std::string &type)
524 std::vector<SortBy> orders;
525 orders.push_back(SortByNone);
526 if (type == "mixed")
528 orders.push_back(SortByGenre);
529 orders.push_back(SortByAlbum);
530 orders.push_back(SortByArtist);
531 orders.push_back(SortByTitle);
532 orders.push_back(SortByYear);
533 orders.push_back(SortByTime);
534 orders.push_back(SortByTrackNumber);
535 orders.push_back(SortByFile);
536 orders.push_back(SortByPath);
537 orders.push_back(SortByPlaycount);
538 orders.push_back(SortByLastPlayed);
540 else if (type == "songs")
542 orders.push_back(SortByGenre);
543 orders.push_back(SortByAlbum);
544 orders.push_back(SortByArtist);
545 orders.push_back(SortByTitle);
546 orders.push_back(SortByYear);
547 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
548 CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
549 orders.push_back(SortByOrigDate);
550 orders.push_back(SortByTime);
551 orders.push_back(SortByTrackNumber);
552 orders.push_back(SortByFile);
553 orders.push_back(SortByPath);
554 orders.push_back(SortByPlaycount);
555 orders.push_back(SortByLastPlayed);
556 orders.push_back(SortByDateAdded);
557 orders.push_back(SortByRating);
558 orders.push_back(SortByUserRating);
559 orders.push_back(SortByBPM);
561 else if (type == "albums")
563 orders.push_back(SortByGenre);
564 orders.push_back(SortByAlbum);
565 orders.push_back(SortByTotalDiscs);
566 orders.push_back(SortByArtist); // any artist
567 orders.push_back(SortByYear);
568 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
569 CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
570 orders.push_back(SortByOrigDate);
571 //orders.push_back(SortByThemes);
572 //orders.push_back(SortByMoods);
573 //orders.push_back(SortByStyles);
574 orders.push_back(SortByAlbumType);
575 //orders.push_back(SortByMusicLabel);
576 orders.push_back(SortByRating);
577 orders.push_back(SortByUserRating);
578 orders.push_back(SortByPlaycount);
579 orders.push_back(SortByLastPlayed);
580 orders.push_back(SortByDateAdded);
582 else if (type == "artists")
584 orders.push_back(SortByArtist);
586 else if (type == "tvshows")
588 orders.push_back(SortBySortTitle);
589 orders.push_back(SortByOriginalTitle);
590 orders.push_back(SortByTvShowStatus);
591 orders.push_back(SortByVotes);
592 orders.push_back(SortByRating);
593 orders.push_back(SortByUserRating);
594 orders.push_back(SortByYear);
595 orders.push_back(SortByGenre);
596 orders.push_back(SortByNumberOfEpisodes);
597 orders.push_back(SortByNumberOfWatchedEpisodes);
598 //orders.push_back(SortByPlaycount);
599 orders.push_back(SortByPath);
600 orders.push_back(SortByStudio);
601 orders.push_back(SortByMPAA);
602 orders.push_back(SortByDateAdded);
603 orders.push_back(SortByLastPlayed);
605 else if (type == "episodes")
607 orders.push_back(SortByTitle);
608 orders.push_back(SortByOriginalTitle);
609 orders.push_back(SortByTvShowTitle);
610 orders.push_back(SortByVotes);
611 orders.push_back(SortByRating);
612 orders.push_back(SortByUserRating);
613 orders.push_back(SortByTime);
614 orders.push_back(SortByPlaycount);
615 orders.push_back(SortByLastPlayed);
616 orders.push_back(SortByYear); // premiered/dateaired
617 orders.push_back(SortByEpisodeNumber);
618 orders.push_back(SortBySeason);
619 orders.push_back(SortByFile);
620 orders.push_back(SortByPath);
621 orders.push_back(SortByStudio);
622 orders.push_back(SortByMPAA);
623 orders.push_back(SortByDateAdded);
625 else if (type == "movies")
627 orders.push_back(SortBySortTitle);
628 orders.push_back(SortByOriginalTitle);
629 orders.push_back(SortByVotes);
630 orders.push_back(SortByRating);
631 orders.push_back(SortByUserRating);
632 orders.push_back(SortByTime);
633 orders.push_back(SortByPlaycount);
634 orders.push_back(SortByLastPlayed);
635 orders.push_back(SortByGenre);
636 orders.push_back(SortByCountry);
637 orders.push_back(SortByYear); // premiered
638 orders.push_back(SortByMPAA);
639 orders.push_back(SortByTop250);
640 orders.push_back(SortByStudio);
641 orders.push_back(SortByFile);
642 orders.push_back(SortByPath);
643 orders.push_back(SortByDateAdded);
645 else if (type == "musicvideos")
647 orders.push_back(SortByTitle);
648 orders.push_back(SortByGenre);
649 orders.push_back(SortByAlbum);
650 orders.push_back(SortByYear);
651 orders.push_back(SortByArtist);
652 orders.push_back(SortByFile);
653 orders.push_back(SortByPath);
654 orders.push_back(SortByPlaycount);
655 orders.push_back(SortByLastPlayed);
656 orders.push_back(SortByTime);
657 orders.push_back(SortByRating);
658 orders.push_back(SortByUserRating);
659 orders.push_back(SortByStudio);
660 orders.push_back(SortByDateAdded);
662 orders.push_back(SortByRandom);
664 return orders;
667 std::vector<Field> CSmartPlaylistRule::GetGroups(const std::string &type)
669 std::vector<Field> groups;
670 groups.push_back(FieldUnknown);
672 if (type == "artists")
673 groups.push_back(FieldGenre);
674 else if (type == "albums")
676 groups.push_back(FieldYear);
677 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
678 CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
679 groups.push_back(FieldOrigYear);
681 if (type == "movies")
683 groups.push_back(FieldNone);
684 groups.push_back(FieldSet);
685 groups.push_back(FieldGenre);
686 groups.push_back(FieldYear);
687 groups.push_back(FieldActor);
688 groups.push_back(FieldDirector);
689 groups.push_back(FieldWriter);
690 groups.push_back(FieldStudio);
691 groups.push_back(FieldCountry);
692 groups.push_back(FieldTag);
694 else if (type == "tvshows")
696 groups.push_back(FieldGenre);
697 groups.push_back(FieldYear);
698 groups.push_back(FieldActor);
699 groups.push_back(FieldDirector);
700 groups.push_back(FieldStudio);
701 groups.push_back(FieldTag);
703 else if (type == "musicvideos")
705 groups.push_back(FieldArtist);
706 groups.push_back(FieldAlbum);
707 groups.push_back(FieldGenre);
708 groups.push_back(FieldYear);
709 groups.push_back(FieldDirector);
710 groups.push_back(FieldStudio);
711 groups.push_back(FieldTag);
714 return groups;
717 std::string CSmartPlaylistRule::GetLocalizedGroup(Field group)
719 for (const auto & i : groups)
721 if (group == i.field)
722 return g_localizeStrings.Get(i.localizedString);
725 return g_localizeStrings.Get(groups[0].localizedString);
728 bool CSmartPlaylistRule::CanGroupMix(Field group)
730 for (const auto & i : groups)
732 if (group == i.field)
733 return i.canMix;
736 return false;
739 std::string CSmartPlaylistRule::GetLocalizedRule() const
741 return StringUtils::Format("{} {} {}", GetLocalizedField(m_field),
742 GetLocalizedOperator(m_operator), GetParameter());
745 std::string CSmartPlaylistRule::GetVideoResolutionQuery(const std::string &parameter) const
747 std::string retVal(" IN (SELECT DISTINCT idFile FROM streamdetails WHERE iVideoWidth ");
748 int iRes = (int)std::strtol(parameter.c_str(), NULL, 10);
750 int min, max;
751 if (iRes >= 2160)
753 min = 1921;
754 max = INT_MAX;
756 else if (iRes >= 1080) { min = 1281; max = 1920; }
757 else if (iRes >= 720) { min = 961; max = 1280; }
758 else if (iRes >= 540) { min = 721; max = 960; }
759 else { min = 0; max = 720; }
761 switch (m_operator)
763 case OPERATOR_EQUALS:
764 retVal += StringUtils::Format(">= {} AND iVideoWidth <= {}", min, max);
765 break;
766 case OPERATOR_DOES_NOT_EQUAL:
767 retVal += StringUtils::Format("< {} OR iVideoWidth > {}", min, max);
768 break;
769 case OPERATOR_LESS_THAN:
770 retVal += StringUtils::Format("< {}", min);
771 break;
772 case OPERATOR_GREATER_THAN:
773 retVal += StringUtils::Format("> {}", max);
774 break;
775 default:
776 break;
779 retVal += ")";
780 return retVal;
783 std::string CSmartPlaylistRule::GetBooleanQuery(const std::string &negate, const std::string &strType) const
785 if (strType == "movies")
787 if (m_field == FieldInProgress)
788 return "movie_view.idFile " + negate + " IN (SELECT DISTINCT idFile FROM bookmark WHERE type = 1)";
789 else if (m_field == FieldTrailer)
790 return negate + GetField(m_field, strType) + "!= ''";
792 else if (strType == "episodes")
794 if (m_field == FieldInProgress)
795 return "episode_view.idFile " + negate + " IN (SELECT DISTINCT idFile FROM bookmark WHERE type = 1)";
797 else if (strType == "tvshows")
799 if (m_field == FieldInProgress)
800 return negate + " ("
801 "(tvshow_view.watchedcount > 0 AND tvshow_view.watchedcount < tvshow_view.totalCount) OR "
802 "(tvshow_view.watchedcount = 0 AND EXISTS "
803 "(SELECT 1 FROM episode_view WHERE episode_view.idShow = " + GetField(FieldId, strType) + " AND episode_view.resumeTimeInSeconds > 0)"
805 ")";
807 if (strType == "albums")
809 if (m_field == FieldCompilation)
810 return negate + GetField(m_field, strType);
811 if (m_field == FieldIsBoxset)
812 return negate + "albumview.bBoxedSet = 1";
814 return "";
817 CDatabaseQueryRule::SEARCH_OPERATOR CSmartPlaylistRule::GetOperator(const std::string &strType) const
819 SEARCH_OPERATOR op = CDatabaseQueryRule::GetOperator(strType);
820 if ((strType == "tvshows" || strType == "episodes") && m_field == FieldYear)
821 { // special case for premiered which is a date rather than a year
822 //! @todo SMARTPLAYLISTS do we really need this, or should we just make this field the premiered date and request a date?
823 if (op == OPERATOR_EQUALS)
824 op = OPERATOR_CONTAINS;
825 else if (op == OPERATOR_DOES_NOT_EQUAL)
826 op = OPERATOR_DOES_NOT_CONTAIN;
828 return op;
831 std::string CSmartPlaylistRule::FormatParameter(const std::string &operatorString, const std::string &param, const CDatabase &db, const std::string &strType) const
833 // special-casing
834 if (m_field == FieldTime || m_field == FieldAlbumDuration)
835 { // translate time to seconds
836 std::string seconds = std::to_string(StringUtils::TimeStringToSeconds(param));
837 return db.PrepareSQL(operatorString, seconds.c_str());
839 return CDatabaseQueryRule::FormatParameter(operatorString, param, db, strType);
842 std::string CSmartPlaylistRule::FormatLinkQuery(const char *field, const char *table, const MediaType& mediaType, const std::string& mediaField, const std::string& parameter)
844 // NOTE: no need for a PrepareSQL here, as the parameter has already been formatted
845 return StringUtils::Format(
846 " EXISTS (SELECT 1 FROM {}_link"
847 " JOIN {} ON {}.{}_id={}_link.{}_id"
848 " WHERE {}_link.media_id={} AND {}.name {} AND {}_link.media_type = '{}')",
849 field, table, table, table, field, table, field, mediaField, table, parameter, field,
850 mediaType);
853 std::string CSmartPlaylistRule::FormatYearQuery(const std::string& field,
854 const std::string& param,
855 const std::string& parameter) const
857 std::string query;
858 if (m_operator == OPERATOR_EQUALS && param == "0")
859 query = "(TRIM(" + field + ") = '' OR " + field + " IS NULL)";
860 else if (m_operator == OPERATOR_DOES_NOT_EQUAL && param == "0")
861 query = "(TRIM(" + field + ") <> '' AND " + field + " IS NOT NULL)";
862 else
863 { // Get year from ISO8601 date string, cast as INTEGER
864 query = "CAST(" + field + " as INTEGER)" + parameter;
865 if (m_operator == OPERATOR_LESS_THAN)
866 query = "(TRIM(" + field + ") = '' OR " + field + " IS NULL OR " + query + ")";
868 return query;
871 std::string CSmartPlaylistRule::FormatWhereClause(const std::string &negate, const std::string &oper, const std::string &param,
872 const CDatabase &db, const std::string &strType) const
874 std::string parameter = FormatParameter(oper, param, db, strType);
876 std::string query;
877 std::string table;
878 if (strType == "songs")
880 table = "songview";
882 if (m_field == FieldGenre)
883 query = negate + " EXISTS (SELECT 1 FROM song_genre, genre WHERE song_genre.idSong = " + GetField(FieldId, strType) + " AND song_genre.idGenre = genre.idGenre AND genre.strGenre" + parameter + ")";
884 else if (m_field == FieldArtist)
885 query = negate + " EXISTS (SELECT 1 FROM song_artist, artist WHERE song_artist.idSong = " + GetField(FieldId, strType) + " AND song_artist.idArtist = artist.idArtist AND artist.strArtist" + parameter + ")";
886 else if (m_field == FieldAlbumArtist)
887 query = negate + " EXISTS (SELECT 1 FROM album_artist, artist WHERE album_artist.idAlbum = " + table + ".idAlbum AND album_artist.idArtist = artist.idArtist AND artist.strArtist" + parameter + ")";
888 else if (m_field == FieldLastPlayed && (m_operator == OPERATOR_LESS_THAN || m_operator == OPERATOR_BEFORE || m_operator == OPERATOR_NOT_IN_THE_LAST))
889 query = GetField(m_field, strType) + " is NULL or " + GetField(m_field, strType) + parameter;
890 else if (m_field == FieldSource)
891 query = negate + " EXISTS (SELECT 1 FROM album_source, source WHERE album_source.idAlbum = " + table + ".idAlbum AND album_source.idSource = source.idSource AND source.strName" + parameter + ")";
892 else if (m_field == FieldYear || m_field == FieldOrigYear)
894 std::string field;
895 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
896 CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
897 field = GetField(FieldOrigYear, strType);
898 else
899 field = GetField(m_field, strType);
900 query = FormatYearQuery(field, param, parameter);
903 else if (strType == "albums")
905 table = "albumview";
907 if (m_field == FieldGenre)
908 query = negate + " EXISTS (SELECT 1 FROM song, song_genre, genre WHERE song.idAlbum = " + GetField(FieldId, strType) + " AND song.idSong = song_genre.idSong AND song_genre.idGenre = genre.idGenre AND genre.strGenre" + parameter + ")";
909 else if (m_field == FieldArtist)
910 query = negate + " EXISTS (SELECT 1 FROM song, song_artist, artist WHERE song.idAlbum = " + GetField(FieldId, strType) + " AND song.idSong = song_artist.idSong AND song_artist.idArtist = artist.idArtist AND artist.strArtist" + parameter + ")";
911 else if (m_field == FieldAlbumArtist)
912 query = negate + " EXISTS (SELECT 1 FROM album_artist, artist WHERE album_artist.idAlbum = " + GetField(FieldId, strType) + " AND album_artist.idArtist = artist.idArtist AND artist.strArtist" + parameter + ")";
913 else if (m_field == FieldPath)
914 query = negate + " EXISTS (SELECT 1 FROM song JOIN path on song.idpath = path.idpath WHERE song.idAlbum = " + GetField(FieldId, strType) + " AND path.strPath" + parameter + ")";
915 else if (m_field == FieldLastPlayed && (m_operator == OPERATOR_LESS_THAN || m_operator == OPERATOR_BEFORE || m_operator == OPERATOR_NOT_IN_THE_LAST))
916 query = GetField(m_field, strType) + " is NULL or " + GetField(m_field, strType) + parameter;
917 else if (m_field == FieldSource)
918 query = negate + " EXISTS (SELECT 1 FROM album_source, source WHERE album_source.idAlbum = " + GetField(FieldId, strType) + " AND album_source.idSource = source.idSource AND source.strName" + parameter + ")";
919 else if (m_field == FieldDiscTitle)
920 query = negate +
921 " EXISTS (SELECT 1 FROM song WHERE song.idAlbum = " + GetField(FieldId, strType) +
922 " AND song.strDiscSubtitle" + parameter + ")";
923 else if (m_field == FieldYear || m_field == FieldOrigYear)
925 std::string field;
926 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
927 CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
928 field = GetField(FieldOrigYear, strType);
929 else
930 field = GetField(m_field, strType);
931 query = FormatYearQuery(field, param, parameter);
934 else if (strType == "artists")
936 table = "artistview";
938 if (m_field == FieldGenre)
940 query = negate + " (EXISTS (SELECT DISTINCT song_artist.idArtist FROM song_artist, song_genre, genre WHERE song_artist.idArtist = " + GetField(FieldId, strType) + " AND song_artist.idSong = song_genre.idSong AND song_genre.idGenre = genre.idGenre AND genre.strGenre" + parameter + ")";
941 query += " OR ";
942 query += "EXISTS (SELECT DISTINCT album_artist.idArtist FROM album_artist, song, song_genre, genre WHERE album_artist.idArtist = " + GetField(FieldId, strType) + " AND song.idAlbum = album_artist.idAlbum AND song.idSong = song_genre.idSong AND song_genre.idGenre = genre.idGenre AND genre.strGenre" + parameter + "))";
944 else if (m_field == FieldRole)
946 query = negate + " (EXISTS (SELECT DISTINCT song_artist.idArtist FROM song_artist, role WHERE song_artist.idArtist = " + GetField(FieldId, strType) + " AND song_artist.idRole = role.idRole AND role.strRole" + parameter + "))";
948 else if (m_field == FieldPath)
950 query = negate + " (EXISTS (SELECT DISTINCT song_artist.idArtist FROM song_artist JOIN song ON song.idSong = song_artist.idSong JOIN path ON song.idpath = path.idpath ";
951 query += "WHERE song_artist.idArtist = " + GetField(FieldId, strType) + " AND path.strPath" + parameter + "))";
953 else if (m_field == FieldSource)
955 query = negate + " (EXISTS(SELECT 1 FROM song_artist, song, album_source, source WHERE song_artist.idArtist = " + GetField(FieldId, strType) + " AND song.idSong = song_artist.idSong AND song_artist.idRole = 1 AND album_source.idAlbum = song.idAlbum AND album_source.idSource = source.idSource AND source.strName" + parameter + ")";
956 query += " OR ";
957 query += " EXISTS (SELECT 1 FROM album_artist, album_source, source WHERE album_artist.idArtist = " + GetField(FieldId, strType) + " AND album_source.idAlbum = album_artist.idAlbum AND album_source.idSource = source.idSource AND source.strName" + parameter + "))";
960 else if (strType == "movies")
962 table = "movie_view";
964 if (m_field == FieldGenre)
965 query = negate + FormatLinkQuery("genre", "genre", MediaTypeMovie, GetField(FieldId, strType), parameter);
966 else if (m_field == FieldDirector)
967 query = negate + FormatLinkQuery("director", "actor", MediaTypeMovie, GetField(FieldId, strType), parameter);
968 else if (m_field == FieldActor)
969 query = negate + FormatLinkQuery("actor", "actor", MediaTypeMovie, GetField(FieldId, strType), parameter);
970 else if (m_field == FieldWriter)
971 query = negate + FormatLinkQuery("writer", "actor", MediaTypeMovie, GetField(FieldId, strType), parameter);
972 else if (m_field == FieldStudio)
973 query = negate + FormatLinkQuery("studio", "studio", MediaTypeMovie, GetField(FieldId, strType), parameter);
974 else if (m_field == FieldCountry)
975 query = negate + FormatLinkQuery("country", "country", MediaTypeMovie, GetField(FieldId, strType), parameter);
976 else if ((m_field == FieldLastPlayed || m_field == FieldDateAdded) && (m_operator == OPERATOR_LESS_THAN || m_operator == OPERATOR_BEFORE || m_operator == OPERATOR_NOT_IN_THE_LAST))
977 query = GetField(m_field, strType) + " IS NULL OR " + GetField(m_field, strType) + parameter;
978 else if (m_field == FieldTag)
979 query = negate + FormatLinkQuery("tag", "tag", MediaTypeMovie, GetField(FieldId, strType), parameter);
981 else if (strType == "musicvideos")
983 table = "musicvideo_view";
985 if (m_field == FieldGenre)
986 query = negate + FormatLinkQuery("genre", "genre", MediaTypeMusicVideo, GetField(FieldId, strType), parameter);
987 else if (m_field == FieldArtist || m_field == FieldAlbumArtist)
988 query = negate + FormatLinkQuery("actor", "actor", MediaTypeMusicVideo, GetField(FieldId, strType), parameter);
989 else if (m_field == FieldStudio)
990 query = negate + FormatLinkQuery("studio", "studio", MediaTypeMusicVideo, GetField(FieldId, strType), parameter);
991 else if (m_field == FieldDirector)
992 query = negate + FormatLinkQuery("director", "actor", MediaTypeMusicVideo, GetField(FieldId, strType), parameter);
993 else if ((m_field == FieldLastPlayed || m_field == FieldDateAdded) && (m_operator == OPERATOR_LESS_THAN || m_operator == OPERATOR_BEFORE || m_operator == OPERATOR_NOT_IN_THE_LAST))
994 query = GetField(m_field, strType) + " IS NULL OR " + GetField(m_field, strType) + parameter;
995 else if (m_field == FieldTag)
996 query = negate + FormatLinkQuery("tag", "tag", MediaTypeMusicVideo, GetField(FieldId, strType), parameter);
998 else if (strType == "tvshows")
1000 table = "tvshow_view";
1002 if (m_field == FieldGenre)
1003 query = negate + FormatLinkQuery("genre", "genre", MediaTypeTvShow, GetField(FieldId, strType), parameter);
1004 else if (m_field == FieldDirector)
1005 query = negate + FormatLinkQuery("director", "actor", MediaTypeTvShow, GetField(FieldId, strType), parameter);
1006 else if (m_field == FieldActor)
1007 query = negate + FormatLinkQuery("actor", "actor", MediaTypeTvShow, GetField(FieldId, strType), parameter);
1008 else if (m_field == FieldStudio)
1009 query = negate + FormatLinkQuery("studio", "studio", MediaTypeTvShow, GetField(FieldId, strType), parameter);
1010 else if (m_field == FieldMPAA)
1011 query = negate + " (" + GetField(m_field, strType) + parameter + ")";
1012 else if ((m_field == FieldLastPlayed || m_field == FieldDateAdded) && (m_operator == OPERATOR_LESS_THAN || m_operator == OPERATOR_BEFORE || m_operator == OPERATOR_NOT_IN_THE_LAST))
1013 query = GetField(m_field, strType) + " IS NULL OR " + GetField(m_field, strType) + parameter;
1014 else if (m_field == FieldPlaycount)
1015 query = "CASE WHEN COALESCE(" + GetField(FieldNumberOfEpisodes, strType) + " - " + GetField(FieldNumberOfWatchedEpisodes, strType) + ", 0) > 0 THEN 0 ELSE 1 END " + parameter;
1016 else if (m_field == FieldTag)
1017 query = negate + FormatLinkQuery("tag", "tag", MediaTypeTvShow, GetField(FieldId, strType), parameter);
1019 else if (strType == "episodes")
1021 table = "episode_view";
1023 if (m_field == FieldGenre)
1024 query = negate + FormatLinkQuery("genre", "genre", MediaTypeTvShow, (table + ".idShow").c_str(), parameter);
1025 else if (m_field == FieldTag)
1026 query = negate + FormatLinkQuery("tag", "tag", MediaTypeTvShow, (table + ".idShow").c_str(), parameter);
1027 else if (m_field == FieldDirector)
1028 query = negate + FormatLinkQuery("director", "actor", MediaTypeEpisode, GetField(FieldId, strType), parameter);
1029 else if (m_field == FieldActor)
1030 query = negate + FormatLinkQuery("actor", "actor", MediaTypeEpisode, GetField(FieldId, strType), parameter);
1031 else if (m_field == FieldWriter)
1032 query = negate + FormatLinkQuery("writer", "actor", MediaTypeEpisode, GetField(FieldId, strType), parameter);
1033 else if ((m_field == FieldLastPlayed || m_field == FieldDateAdded) && (m_operator == OPERATOR_LESS_THAN || m_operator == OPERATOR_BEFORE || m_operator == OPERATOR_NOT_IN_THE_LAST))
1034 query = GetField(m_field, strType) + " IS NULL OR " + GetField(m_field, strType) + parameter;
1035 else if (m_field == FieldStudio)
1036 query = negate + FormatLinkQuery("studio", "studio", MediaTypeTvShow, (table + ".idShow").c_str(), parameter);
1037 else if (m_field == FieldMPAA)
1038 query = negate + " (" + GetField(m_field, strType) + parameter + ")";
1040 if (m_field == FieldVideoResolution)
1041 query = table + ".idFile" + negate + GetVideoResolutionQuery(param);
1042 else if (m_field == FieldAudioChannels)
1043 query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND iAudioChannels " + parameter + ")";
1044 else if (m_field == FieldVideoCodec)
1045 query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strVideoCodec " + parameter + ")";
1046 else if (m_field == FieldAudioCodec)
1047 query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strAudioCodec " + parameter + ")";
1048 else if (m_field == FieldAudioLanguage)
1049 query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strAudioLanguage " + parameter + ")";
1050 else if (m_field == FieldSubtitleLanguage)
1051 query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strSubtitleLanguage " + parameter + ")";
1052 else if (m_field == FieldVideoAspectRatio)
1053 query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND fVideoAspect " + parameter + ")";
1054 else if (m_field == FieldAudioCount)
1055 query = db.PrepareSQL(negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND streamdetails.iStreamtype = %i GROUP BY streamdetails.idFile HAVING COUNT(streamdetails.iStreamType) " + parameter + ")",CStreamDetail::AUDIO);
1056 else if (m_field == FieldSubtitleCount)
1057 query = db.PrepareSQL(negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND streamdetails.iStreamType = %i GROUP BY streamdetails.idFile HAVING COUNT(streamdetails.iStreamType) " + parameter + ")",CStreamDetail::SUBTITLE);
1058 else if (m_field == FieldHdrType)
1059 query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strHdrType " + parameter + ")";
1060 if (m_field == FieldPlaycount && strType != "songs" && strType != "albums" && strType != "tvshows")
1061 { // playcount IS stored as NULL OR number IN video db
1062 if ((m_operator == OPERATOR_EQUALS && param == "0") ||
1063 (m_operator == OPERATOR_DOES_NOT_EQUAL && param != "0") ||
1064 (m_operator == OPERATOR_LESS_THAN))
1066 std::string field = GetField(FieldPlaycount, strType);
1067 query = field + " IS NULL OR " + field + parameter;
1070 if (query.empty())
1071 query = CDatabaseQueryRule::FormatWhereClause(negate, oper, param, db, strType);
1072 return query;
1075 std::string CSmartPlaylistRule::GetField(int field, const std::string &type) const
1077 if (field >= FieldUnknown && field < FieldMax)
1078 return DatabaseUtils::GetField((Field)field, CMediaTypes::FromString(type), DatabaseQueryPartWhere);
1079 return "";
1082 std::string CSmartPlaylistRuleCombination::GetWhereClause(const CDatabase &db, const std::string& strType, std::set<std::string> &referencedPlaylists) const
1084 std::string rule;
1086 // translate the combinations into SQL
1087 for (CDatabaseQueryRuleCombinations::const_iterator it = m_combinations.begin(); it != m_combinations.end(); ++it)
1089 if (it != m_combinations.begin())
1090 rule += m_type == CombinationAnd ? " AND " : " OR ";
1091 std::shared_ptr<CSmartPlaylistRuleCombination> combo = std::static_pointer_cast<CSmartPlaylistRuleCombination>(*it);
1092 if (combo)
1093 rule += "(" + combo->GetWhereClause(db, strType, referencedPlaylists) + ")";
1096 // translate the rules into SQL
1097 for (CDatabaseQueryRules::const_iterator it = m_rules.begin(); it != m_rules.end(); ++it)
1099 // don't include playlists that are meant to be displayed
1100 // as a virtual folders in the SQL WHERE clause
1101 if ((*it)->m_field == FieldVirtualFolder)
1102 continue;
1104 if (!rule.empty())
1105 rule += m_type == CombinationAnd ? " AND " : " OR ";
1106 rule += "(";
1107 std::string currentRule;
1108 if ((*it)->m_field == FieldPlaylist)
1110 std::string playlistFile = CSmartPlaylistDirectory::GetPlaylistByName((*it)->m_parameter.at(0), strType);
1111 if (!playlistFile.empty() && referencedPlaylists.find(playlistFile) == referencedPlaylists.end())
1113 referencedPlaylists.insert(playlistFile);
1114 CSmartPlaylist playlist;
1115 if (playlist.Load(playlistFile))
1117 std::string playlistQuery;
1118 // only playlists of same type will be part of the query
1119 if (playlist.GetType() == strType || (playlist.GetType() == "mixed" && (strType == "songs" || strType == "musicvideos")) || playlist.GetType().empty())
1121 playlist.SetType(strType);
1122 playlistQuery = playlist.GetWhereClause(db, referencedPlaylists);
1124 if (playlist.GetType() == strType)
1126 if ((*it)->m_operator == CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL)
1127 currentRule = StringUtils::Format("NOT ({})", playlistQuery);
1128 else
1129 currentRule = playlistQuery;
1134 else
1135 currentRule = (*it)->GetWhereClause(db, strType);
1136 // if we don't get a rule, we add '1' or '0' so the query is still valid and doesn't fail
1137 if (currentRule.empty())
1138 currentRule = m_type == CombinationAnd ? "'1'" : "'0'";
1139 rule += currentRule;
1140 rule += ")";
1143 return rule;
1146 void CSmartPlaylistRuleCombination::GetVirtualFolders(const std::string& strType, std::vector<std::string> &virtualFolders) const
1148 for (CDatabaseQueryRuleCombinations::const_iterator it = m_combinations.begin(); it != m_combinations.end(); ++it)
1150 std::shared_ptr<CSmartPlaylistRuleCombination> combo = std::static_pointer_cast<CSmartPlaylistRuleCombination>(*it);
1151 if (combo)
1152 combo->GetVirtualFolders(strType, virtualFolders);
1155 for (CDatabaseQueryRules::const_iterator it = m_rules.begin(); it != m_rules.end(); ++it)
1157 if (((*it)->m_field != FieldVirtualFolder && (*it)->m_field != FieldPlaylist) || (*it)->m_operator != CDatabaseQueryRule::OPERATOR_EQUALS)
1158 continue;
1160 std::string playlistFile = CSmartPlaylistDirectory::GetPlaylistByName((*it)->m_parameter.at(0), strType);
1161 if (playlistFile.empty())
1162 continue;
1164 if ((*it)->m_field == FieldVirtualFolder)
1165 virtualFolders.push_back(playlistFile);
1166 else
1168 // look for any virtual folders in the expanded playlists
1169 CSmartPlaylist playlist;
1170 if (!playlist.Load(playlistFile))
1171 continue;
1173 if (CSmartPlaylist::CheckTypeCompatibility(playlist.GetType(), strType))
1174 playlist.GetVirtualFolders(virtualFolders);
1179 void CSmartPlaylistRuleCombination::AddRule(const CSmartPlaylistRule &rule)
1181 std::shared_ptr<CSmartPlaylistRule> ptr(new CSmartPlaylistRule(rule));
1182 m_rules.push_back(ptr);
1185 CSmartPlaylist::CSmartPlaylist()
1187 Reset();
1190 bool CSmartPlaylist::OpenAndReadName(const CURL &url)
1192 if (readNameFromPath(url) == NULL)
1193 return false;
1195 return !m_playlistName.empty();
1198 const TiXmlNode* CSmartPlaylist::readName(const TiXmlNode *root)
1200 if (root == NULL)
1201 return NULL;
1203 const TiXmlElement *rootElem = root->ToElement();
1204 if (rootElem == NULL)
1205 return NULL;
1207 if (!StringUtils::EqualsNoCase(root->Value(), "smartplaylist"))
1209 CLog::Log(LOGERROR, "Error loading Smart playlist");
1210 return NULL;
1213 // load the playlist type
1214 const char* type = rootElem->Attribute("type");
1215 if (type)
1216 m_playlistType = type;
1217 // backward compatibility:
1218 if (m_playlistType == "music")
1219 m_playlistType = "songs";
1220 if (m_playlistType == "video")
1221 m_playlistType = "musicvideos";
1223 // load the playlist name
1224 XMLUtils::GetString(root, "name", m_playlistName);
1226 return root;
1229 const TiXmlNode* CSmartPlaylist::readNameFromPath(const CURL &url)
1231 CFileStream file;
1232 if (!file.Open(url))
1234 CLog::Log(LOGERROR, "Error loading Smart playlist {} (failed to read file)", url.GetRedacted());
1235 return NULL;
1238 m_xmlDoc.Clear();
1239 file >> m_xmlDoc;
1241 const TiXmlNode *root = readName(m_xmlDoc.RootElement());
1242 if (m_playlistName.empty())
1244 m_playlistName = CUtil::GetTitleFromPath(url.Get());
1245 if (URIUtils::HasExtension(m_playlistName, ".xsp"))
1246 URIUtils::RemoveExtension(m_playlistName);
1249 return root;
1252 const TiXmlNode* CSmartPlaylist::readNameFromXml(const std::string &xml)
1254 if (xml.empty())
1256 CLog::Log(LOGERROR, "Error loading empty Smart playlist");
1257 return NULL;
1260 m_xmlDoc.Clear();
1261 if (!m_xmlDoc.Parse(xml))
1263 CLog::Log(LOGERROR, "Error loading Smart playlist (failed to parse xml: {})",
1264 m_xmlDoc.ErrorDesc());
1265 return NULL;
1268 const TiXmlNode *root = readName(m_xmlDoc.RootElement());
1270 return root;
1273 bool CSmartPlaylist::load(const TiXmlNode *root)
1275 if (root == NULL)
1276 return false;
1278 return LoadFromXML(root);
1281 bool CSmartPlaylist::Load(const CURL &url)
1283 return load(readNameFromPath(url));
1286 bool CSmartPlaylist::Load(const std::string &path)
1288 const CURL pathToUrl(path);
1289 return load(readNameFromPath(pathToUrl));
1292 bool CSmartPlaylist::Load(const CVariant &obj)
1294 if (!obj.isObject())
1295 return false;
1297 // load the playlist type
1298 if (obj.isMember("type") && obj["type"].isString())
1299 m_playlistType = obj["type"].asString();
1301 // backward compatibility
1302 if (m_playlistType == "music")
1303 m_playlistType = "songs";
1304 if (m_playlistType == "video")
1305 m_playlistType = "musicvideos";
1307 // load the playlist name
1308 if (obj.isMember("name") && obj["name"].isString())
1309 m_playlistName = obj["name"].asString();
1311 if (obj.isMember("rules"))
1312 m_ruleCombination.Load(obj["rules"], this);
1314 if (obj.isMember("group") && obj["group"].isMember("type") && obj["group"]["type"].isString())
1316 m_group = obj["group"]["type"].asString();
1317 if (obj["group"].isMember("mixed") && obj["group"]["mixed"].isBoolean())
1318 m_groupMixed = obj["group"]["mixed"].asBoolean();
1321 // now any limits
1322 if (obj.isMember("limit") && (obj["limit"].isInteger() || obj["limit"].isUnsignedInteger()) && obj["limit"].asUnsignedInteger() > 0)
1323 m_limit = (unsigned int)obj["limit"].asUnsignedInteger();
1325 // and order
1326 if (obj.isMember("order") && obj["order"].isMember("method") && obj["order"]["method"].isString())
1328 const CVariant &order = obj["order"];
1329 if (order.isMember("direction") && order["direction"].isString())
1330 m_orderDirection = StringUtils::EqualsNoCase(order["direction"].asString(), "ascending") ? SortOrderAscending : SortOrderDescending;
1332 if (order.isMember("ignorefolders") && obj["ignorefolders"].isBoolean())
1333 m_orderAttributes = obj["ignorefolders"].asBoolean() ? SortAttributeIgnoreFolders : SortAttributeNone;
1335 m_orderField = CSmartPlaylistRule::TranslateOrder(obj["order"]["method"].asString().c_str());
1338 return true;
1341 bool CSmartPlaylist::LoadFromXml(const std::string &xml)
1343 return load(readNameFromXml(xml));
1346 bool CSmartPlaylist::LoadFromXML(const TiXmlNode *root, const std::string &encoding)
1348 if (!root)
1349 return false;
1351 std::string tmp;
1352 if (XMLUtils::GetString(root, "match", tmp))
1353 m_ruleCombination.SetType(StringUtils::EqualsNoCase(tmp, "all") ? CSmartPlaylistRuleCombination::CombinationAnd : CSmartPlaylistRuleCombination::CombinationOr);
1355 // now the rules
1356 const TiXmlNode *ruleNode = root->FirstChild("rule");
1357 while (ruleNode)
1359 CSmartPlaylistRule rule;
1360 if (rule.Load(ruleNode, encoding))
1361 m_ruleCombination.AddRule(rule);
1363 ruleNode = ruleNode->NextSibling("rule");
1366 const TiXmlElement *groupElement = root->FirstChildElement("group");
1367 if (groupElement != NULL && groupElement->FirstChild() != NULL)
1369 m_group = groupElement->FirstChild()->ValueStr();
1370 const char* mixed = groupElement->Attribute("mixed");
1371 m_groupMixed = mixed != NULL && StringUtils::EqualsNoCase(mixed, "true");
1374 // now any limits
1375 // format is <limit>25</limit>
1376 XMLUtils::GetUInt(root, "limit", m_limit);
1378 // and order
1379 // format is <order direction="ascending">field</order>
1380 const TiXmlElement *order = root->FirstChildElement("order");
1381 if (order && order->FirstChild())
1383 const char *direction = order->Attribute("direction");
1384 if (direction)
1385 m_orderDirection = StringUtils::EqualsNoCase(direction, "ascending") ? SortOrderAscending : SortOrderDescending;
1387 const char *ignorefolders = order->Attribute("ignorefolders");
1388 if (ignorefolders != NULL)
1389 m_orderAttributes = StringUtils::EqualsNoCase(ignorefolders, "true") ? SortAttributeIgnoreFolders : SortAttributeNone;
1391 m_orderField = CSmartPlaylistRule::TranslateOrder(order->FirstChild()->Value());
1393 return true;
1396 bool CSmartPlaylist::LoadFromJson(const std::string &json)
1398 if (json.empty())
1399 return false;
1401 CVariant obj;
1402 if (!CJSONVariantParser::Parse(json, obj))
1403 return false;
1405 return Load(obj);
1408 bool CSmartPlaylist::Save(const std::string &path) const
1410 CXBMCTinyXML doc;
1411 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
1412 doc.InsertEndChild(decl);
1414 TiXmlElement xmlRootElement("smartplaylist");
1415 xmlRootElement.SetAttribute("type",m_playlistType.c_str());
1416 TiXmlNode *pRoot = doc.InsertEndChild(xmlRootElement);
1417 if (!pRoot)
1418 return false;
1420 // add the <name> tag
1421 XMLUtils::SetString(pRoot, "name", m_playlistName);
1423 // add the <match> tag
1424 XMLUtils::SetString(pRoot, "match", m_ruleCombination.GetType() == CSmartPlaylistRuleCombination::CombinationAnd ? "all" : "one");
1426 // add <rule> tags
1427 m_ruleCombination.Save(pRoot);
1429 // add <group> tag if necessary
1430 if (!m_group.empty())
1432 TiXmlElement nodeGroup("group");
1433 if (m_groupMixed)
1434 nodeGroup.SetAttribute("mixed", "true");
1435 TiXmlText group(m_group.c_str());
1436 nodeGroup.InsertEndChild(group);
1437 pRoot->InsertEndChild(nodeGroup);
1440 // add <limit> tag
1441 if (m_limit)
1442 XMLUtils::SetInt(pRoot, "limit", m_limit);
1444 // add <order> tag
1445 if (m_orderField != SortByNone)
1447 TiXmlText order(CSmartPlaylistRule::TranslateOrder(m_orderField).c_str());
1448 TiXmlElement nodeOrder("order");
1449 nodeOrder.SetAttribute("direction", m_orderDirection == SortOrderDescending ? "descending" : "ascending");
1450 if (m_orderAttributes & SortAttributeIgnoreFolders)
1451 nodeOrder.SetAttribute("ignorefolders", "true");
1452 nodeOrder.InsertEndChild(order);
1453 pRoot->InsertEndChild(nodeOrder);
1455 return doc.SaveFile(path);
1458 bool CSmartPlaylist::Save(CVariant &obj, bool full /* = true */) const
1460 if (obj.type() == CVariant::VariantTypeConstNull)
1461 return false;
1463 obj.clear();
1464 // add "type"
1465 obj["type"] = m_playlistType;
1467 // add "rules"
1468 CVariant rulesObj = CVariant(CVariant::VariantTypeObject);
1469 if (m_ruleCombination.Save(rulesObj))
1470 obj["rules"] = rulesObj;
1472 // add "group"
1473 if (!m_group.empty())
1475 obj["group"]["type"] = m_group;
1476 obj["group"]["mixed"] = m_groupMixed;
1479 // add "limit"
1480 if (full && m_limit)
1481 obj["limit"] = m_limit;
1483 // add "order"
1484 if (full && m_orderField != SortByNone)
1486 obj["order"] = CVariant(CVariant::VariantTypeObject);
1487 obj["order"]["method"] = CSmartPlaylistRule::TranslateOrder(m_orderField);
1488 obj["order"]["direction"] = m_orderDirection == SortOrderDescending ? "descending" : "ascending";
1489 obj["order"]["ignorefolders"] = (m_orderAttributes & SortAttributeIgnoreFolders);
1492 return true;
1495 bool CSmartPlaylist::SaveAsJson(std::string &json, bool full /* = true */) const
1497 CVariant xsp(CVariant::VariantTypeObject);
1498 if (!Save(xsp, full))
1499 return false;
1501 return CJSONVariantWriter::Write(xsp, json, true) && !json.empty();
1504 void CSmartPlaylist::Reset()
1506 m_ruleCombination.clear();
1507 m_limit = 0;
1508 m_orderField = SortByNone;
1509 m_orderDirection = SortOrderNone;
1510 m_orderAttributes = SortAttributeNone;
1511 m_playlistType = "songs"; // sane default
1512 m_group.clear();
1513 m_groupMixed = false;
1516 void CSmartPlaylist::SetName(const std::string &name)
1518 m_playlistName = name;
1521 void CSmartPlaylist::SetType(const std::string &type)
1523 m_playlistType = type;
1526 bool CSmartPlaylist::IsVideoType() const
1528 return IsVideoType(m_playlistType);
1531 bool CSmartPlaylist::IsMusicType() const
1533 return IsMusicType(m_playlistType);
1536 bool CSmartPlaylist::IsVideoType(const std::string &type)
1538 return type == "movies" || type == "tvshows" || type == "episodes" ||
1539 type == "musicvideos" || type == "mixed";
1542 bool CSmartPlaylist::IsMusicType(const std::string &type)
1544 return type == "artists" || type == "albums" ||
1545 type == "songs" || type == "mixed";
1548 std::string CSmartPlaylist::GetWhereClause(const CDatabase &db, std::set<std::string> &referencedPlaylists) const
1550 return m_ruleCombination.GetWhereClause(db, GetType(), referencedPlaylists);
1553 void CSmartPlaylist::GetVirtualFolders(std::vector<std::string> &virtualFolders) const
1555 m_ruleCombination.GetVirtualFolders(GetType(), virtualFolders);
1558 std::string CSmartPlaylist::GetSaveLocation() const
1560 if (m_playlistType == "mixed")
1561 return "mixed";
1562 if (IsMusicType())
1563 return "music";
1564 // all others are video
1565 return "video";
1568 void CSmartPlaylist::GetAvailableFields(const std::string &type, std::vector<std::string> &fieldList)
1570 std::vector<Field> typeFields = CSmartPlaylistRule::GetFields(type);
1571 for (std::vector<Field>::const_iterator field = typeFields.begin(); field != typeFields.end(); ++field)
1573 for (const translateField& i : fields)
1575 if (*field == i.field)
1576 fieldList.emplace_back(i.string);
1581 bool CSmartPlaylist::IsEmpty(bool ignoreSortAndLimit /* = true */) const
1583 bool empty = m_ruleCombination.empty();
1584 if (empty && !ignoreSortAndLimit)
1585 empty = m_limit <= 0 && m_orderField == SortByNone && m_orderDirection == SortOrderNone;
1587 return empty;
1590 bool CSmartPlaylist::CheckTypeCompatibility(const std::string &typeLeft, const std::string &typeRight)
1592 if (typeLeft == typeRight)
1593 return true;
1595 if (typeLeft == "mixed" &&
1596 (typeRight == "songs" || typeRight == "musicvideos"))
1597 return true;
1599 if (typeRight == "mixed" &&
1600 (typeLeft == "songs" || typeLeft == "musicvideos"))
1601 return true;
1603 return false;
1606 CDatabaseQueryRule *CSmartPlaylist::CreateRule() const
1608 return new CSmartPlaylistRule();
1610 CDatabaseQueryRuleCombination *CSmartPlaylist::CreateCombination() const
1612 return new CSmartPlaylistRuleCombination();
1615 } // namespace KODI::PLAYLIST