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.
9 #include "SmartPlayList.h"
11 #include "ServiceBroker.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"
36 using namespace XFILE
;
38 namespace KODI::PLAYLIST
45 CDatabaseQueryRule::FIELD_TYPE type
;
46 StringValidation::Validator validator
;
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 },
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 },
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
;
177 std::string
CSmartPlaylistRule::TranslateField(int field
) const
179 for (const translateField
& f
: fields
)
180 if (field
== f
.field
) return f
.string
;
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())
198 Field
CSmartPlaylistRule::TranslateGroup(const char *group
)
200 for (const auto & i
: groups
)
202 if (StringUtils::EqualsNoCase(group
, i
.name
))
209 std::string
CSmartPlaylistRule::TranslateGroup(Field group
)
211 for (const auto & i
: groups
)
213 if (group
== i
.field
)
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
;
234 bool CSmartPlaylistRule::IsFieldBrowseable(int field
)
236 for (const translateField
& f
: fields
)
237 if (field
== f
.field
) return f
.browseable
;
242 bool CSmartPlaylistRule::Validate(const std::string
&input
, void *data
)
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
;
259 if (validator
== NULL
)
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
))
273 bool CSmartPlaylistRule::ValidateRating(const std::string
&input
, void *data
)
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;
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
);
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
);
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
);
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
);
522 std::vector
<SortBy
> CSmartPlaylistRule::GetOrders(const std::string
&type
)
524 std::vector
<SortBy
> orders
;
525 orders
.push_back(SortByNone
);
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
);
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
);
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
)
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
¶meter
) const
747 std::string
retVal(" IN (SELECT DISTINCT idFile FROM streamdetails WHERE iVideoWidth ");
748 int iRes
= (int)std::strtol(parameter
.c_str(), NULL
, 10);
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; }
763 case OPERATOR_EQUALS
:
764 retVal
+= StringUtils::Format(">= {} AND iVideoWidth <= {}", min
, max
);
766 case OPERATOR_DOES_NOT_EQUAL
:
767 retVal
+= StringUtils::Format("< {} OR iVideoWidth > {}", min
, max
);
769 case OPERATOR_LESS_THAN
:
770 retVal
+= StringUtils::Format("< {}", min
);
772 case OPERATOR_GREATER_THAN
:
773 retVal
+= StringUtils::Format("> {}", max
);
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
)
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)"
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";
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
;
831 std::string
CSmartPlaylistRule::FormatParameter(const std::string
&operatorString
, const std::string
¶m
, const CDatabase
&db
, const std::string
&strType
) const
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
,
853 std::string
CSmartPlaylistRule::FormatYearQuery(const std::string
& field
,
854 const std::string
& param
,
855 const std::string
& parameter
) const
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)";
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
+ ")";
871 std::string
CSmartPlaylistRule::FormatWhereClause(const std::string
&negate
, const std::string
&oper
, const std::string
¶m
,
872 const CDatabase
&db
, const std::string
&strType
) const
874 std::string parameter
= FormatParameter(oper
, param
, db
, strType
);
878 if (strType
== "songs")
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
)
895 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
896 CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE
))
897 field
= GetField(FieldOrigYear
, strType
);
899 field
= GetField(m_field
, strType
);
900 query
= FormatYearQuery(field
, param
, parameter
);
903 else if (strType
== "albums")
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
)
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
)
926 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
927 CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE
))
928 field
= GetField(FieldOrigYear
, strType
);
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
+ ")";
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
+ ")";
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
;
1071 query
= CDatabaseQueryRule::FormatWhereClause(negate
, oper
, param
, db
, strType
);
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
);
1082 std::string
CSmartPlaylistRuleCombination::GetWhereClause(const CDatabase
&db
, const std::string
& strType
, std::set
<std::string
> &referencedPlaylists
) const
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
);
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
)
1105 rule
+= m_type
== CombinationAnd
? " AND " : " OR ";
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
);
1129 currentRule
= playlistQuery
;
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
;
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
);
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
)
1160 std::string playlistFile
= CSmartPlaylistDirectory::GetPlaylistByName((*it
)->m_parameter
.at(0), strType
);
1161 if (playlistFile
.empty())
1164 if ((*it
)->m_field
== FieldVirtualFolder
)
1165 virtualFolders
.push_back(playlistFile
);
1168 // look for any virtual folders in the expanded playlists
1169 CSmartPlaylist playlist
;
1170 if (!playlist
.Load(playlistFile
))
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()
1190 bool CSmartPlaylist::OpenAndReadName(const CURL
&url
)
1192 if (readNameFromPath(url
) == NULL
)
1195 return !m_playlistName
.empty();
1198 const TiXmlNode
* CSmartPlaylist::readName(const TiXmlNode
*root
)
1203 const TiXmlElement
*rootElem
= root
->ToElement();
1204 if (rootElem
== NULL
)
1207 if (!StringUtils::EqualsNoCase(root
->Value(), "smartplaylist"))
1209 CLog::Log(LOGERROR
, "Error loading Smart playlist");
1213 // load the playlist type
1214 const char* type
= rootElem
->Attribute("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
);
1229 const TiXmlNode
* CSmartPlaylist::readNameFromPath(const CURL
&url
)
1232 if (!file
.Open(url
))
1234 CLog::Log(LOGERROR
, "Error loading Smart playlist {} (failed to read file)", url
.GetRedacted());
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
);
1252 const TiXmlNode
* CSmartPlaylist::readNameFromXml(const std::string
&xml
)
1256 CLog::Log(LOGERROR
, "Error loading empty Smart playlist");
1261 if (!m_xmlDoc
.Parse(xml
))
1263 CLog::Log(LOGERROR
, "Error loading Smart playlist (failed to parse xml: {})",
1264 m_xmlDoc
.ErrorDesc());
1268 const TiXmlNode
*root
= readName(m_xmlDoc
.RootElement());
1273 bool CSmartPlaylist::load(const TiXmlNode
*root
)
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())
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();
1322 if (obj
.isMember("limit") && (obj
["limit"].isInteger() || obj
["limit"].isUnsignedInteger()) && obj
["limit"].asUnsignedInteger() > 0)
1323 m_limit
= (unsigned int)obj
["limit"].asUnsignedInteger();
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());
1341 bool CSmartPlaylist::LoadFromXml(const std::string
&xml
)
1343 return load(readNameFromXml(xml
));
1346 bool CSmartPlaylist::LoadFromXML(const TiXmlNode
*root
, const std::string
&encoding
)
1352 if (XMLUtils::GetString(root
, "match", tmp
))
1353 m_ruleCombination
.SetType(StringUtils::EqualsNoCase(tmp
, "all") ? CSmartPlaylistRuleCombination::CombinationAnd
: CSmartPlaylistRuleCombination::CombinationOr
);
1356 const TiXmlNode
*ruleNode
= root
->FirstChild("rule");
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");
1375 // format is <limit>25</limit>
1376 XMLUtils::GetUInt(root
, "limit", m_limit
);
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");
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());
1396 bool CSmartPlaylist::LoadFromJson(const std::string
&json
)
1402 if (!CJSONVariantParser::Parse(json
, obj
))
1408 bool CSmartPlaylist::Save(const std::string
&path
) const
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
);
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");
1427 m_ruleCombination
.Save(pRoot
);
1429 // add <group> tag if necessary
1430 if (!m_group
.empty())
1432 TiXmlElement
nodeGroup("group");
1434 nodeGroup
.SetAttribute("mixed", "true");
1435 TiXmlText
group(m_group
.c_str());
1436 nodeGroup
.InsertEndChild(group
);
1437 pRoot
->InsertEndChild(nodeGroup
);
1442 XMLUtils::SetInt(pRoot
, "limit", m_limit
);
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
)
1465 obj
["type"] = m_playlistType
;
1468 CVariant rulesObj
= CVariant(CVariant::VariantTypeObject
);
1469 if (m_ruleCombination
.Save(rulesObj
))
1470 obj
["rules"] = rulesObj
;
1473 if (!m_group
.empty())
1475 obj
["group"]["type"] = m_group
;
1476 obj
["group"]["mixed"] = m_groupMixed
;
1480 if (full
&& m_limit
)
1481 obj
["limit"] = m_limit
;
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
);
1495 bool CSmartPlaylist::SaveAsJson(std::string
&json
, bool full
/* = true */) const
1497 CVariant
xsp(CVariant::VariantTypeObject
);
1498 if (!Save(xsp
, full
))
1501 return CJSONVariantWriter::Write(xsp
, json
, true) && !json
.empty();
1504 void CSmartPlaylist::Reset()
1506 m_ruleCombination
.clear();
1508 m_orderField
= SortByNone
;
1509 m_orderDirection
= SortOrderNone
;
1510 m_orderAttributes
= SortAttributeNone
;
1511 m_playlistType
= "songs"; // sane default
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")
1564 // all others are 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
;
1590 bool CSmartPlaylist::CheckTypeCompatibility(const std::string
&typeLeft
, const std::string
&typeRight
)
1592 if (typeLeft
== typeRight
)
1595 if (typeLeft
== "mixed" &&
1596 (typeRight
== "songs" || typeRight
== "musicvideos"))
1599 if (typeRight
== "mixed" &&
1600 (typeLeft
== "songs" || typeLeft
== "musicvideos"))
1606 CDatabaseQueryRule
*CSmartPlaylist::CreateRule() const
1608 return new CSmartPlaylistRule();
1610 CDatabaseQueryRuleCombination
*CSmartPlaylist::CreateCombination() const
1612 return new CSmartPlaylistRuleCombination();
1615 } // namespace KODI::PLAYLIST