integer_bools = {{
///
/// }
// clang-format off
-constexpr std::array player_labels = {{
+
+constexpr std::array player_labels = {{
{"hasmedia", PLAYER_HAS_MEDIA},
{"hasaudio", PLAYER_HAS_AUDIO},
{"hasvideo", PLAYER_HAS_VIDEO},
@@ -903,9 +904,10 @@ constexpr std::array player_labels = {{
{"scenemarkers", PLAYER_SCENE_MARKERS},
{"hasscenemarkers", PLAYER_HAS_SCENE_MARKERS},
{"chapters", PLAYER_CHAPTERS},
+ {"chapterlength", PLAYER_CHAPTERLENGTH},
+ {"chapterelapsed", PLAYER_CHAPTER_ELAPSED},
}};
// clang-format on
-
/// \page modules__infolabels_boolean_conditions
/// \table_row3{ `Player.Art(type)`,
/// \anchor Player_Art_type
@@ -4171,7 +4173,8 @@ constexpr std::array musicplayer = {{
///
/// -----------------------------------------------------------------------------
// clang-format off
-constexpr std::array videoplayer = {{
+
+constexpr std::array videoplayer = {{
{"title", VIDEOPLAYER_TITLE},
{"genre", VIDEOPLAYER_GENRE},
{"country", VIDEOPLAYER_COUNTRY},
@@ -4255,7 +4258,11 @@ constexpr std::array videoplayer = {{
{"mediaproviders", VIDEOPLAYER_MEDIAPROVIDERS},
{"titleextrainfo", VIDEOPLAYER_TITLE_EXTRAINFO},
{"hdrdetail", VIDEOPLAYER_HDR_DETAIL},
+ { "chapterlength", VIDEOPLAYER_CHAPTERLENGTH},
+ { "chapterelapsed", PLAYER_CHAPTER_ELAPSED},
+ { "ismusicvideo", VIDEOPLAYER_IS_MUSIC_VIDEO},
}};
+
// clang-format on
/// \page modules__infolabels_boolean_conditions
@@ -7361,6 +7368,29 @@ constexpr std::array container_str = {{
///
/// @skinning_v21 **[New Infolabel]** \link ListItem_HasVideoExtras `ListItem.HasVideoExtras`\endlink
/// }
+/// \table_row3{ `ListItem.HdrType`,
+/// \anchor ListItem_HdrType
+/// _string_,
+/// @return String containing the name of the detected HDR type or empty if not HDR. See \ref StreamHdrType for the list of possible values.
+///
+/// @skinning_v20 **[New Infolabel]** \link ListItem_HdrType `ListItem.HdrType`\endlink
+/// }
+/// \table_row3{ `ListItem.MusicBitsPerSample`,
+/// \anchor ListItem_MusicBitsPerSample
+/// _string_,
+/// @return The bits per sample of a song or empty string if not valid.
+///
+/// @skinning_v21 **[New Infolabel]** \link ListItem_MusicBitsPerSample `ListItem.MusicBitsPerSample`\endlink
+///
+/// }
+/// \table_row3{ `ListItem.MusicCodec`,
+/// \anchor ListItem_MusicCodec
+/// _string_,
+/// @return The codec of a song or empty string if unknown.
+///
+/// @skinning_v21 **[New Infolabel]** \link ListItem_MusicCodec `ListItem.MusicCodec`\endlink
+///
+/// }
/// \table_row3{ `ListItem.PVRClientName`,
/// \anchor ListItem_PVRClientName
/// _string_,
@@ -7417,235 +7447,236 @@ constexpr std::array container_str = {{
///
/// -----------------------------------------------------------------------------
// clang-format off
-constexpr std::array listitem_labels = {{
- {"thumb", LISTITEM_THUMB},
- {"icon", LISTITEM_ICON},
- {"actualicon", LISTITEM_ACTUAL_ICON},
- {"overlay", LISTITEM_OVERLAY},
- {"label", LISTITEM_LABEL},
- {"label2", LISTITEM_LABEL2},
- {"title", LISTITEM_TITLE},
- {"tracknumber", LISTITEM_TRACKNUMBER},
- {"artist", LISTITEM_ARTIST},
- {"album", LISTITEM_ALBUM},
- {"albumartist", LISTITEM_ALBUM_ARTIST},
- {"year", LISTITEM_YEAR},
- {"genre", LISTITEM_GENRE},
- {"contributors", LISTITEM_CONTRIBUTORS},
- {"contributorandrole", LISTITEM_CONTRIBUTOR_AND_ROLE},
- {"director", LISTITEM_DIRECTOR},
- {"disctitle", LISTITEM_DISC_TITLE},
- {"filename", LISTITEM_FILENAME},
- {"filenameandpath", LISTITEM_FILENAME_AND_PATH},
- {"decodedfilenameandpath", LISTITEM_DECODED_FILENAME_AND_PATH},
- {"fileextension", LISTITEM_FILE_EXTENSION},
- {"filenamenoextension", LISTITEM_FILENAME_NO_EXTENSION},
- {"date", LISTITEM_DATE},
- {"datetime", LISTITEM_DATETIME},
- {"size", LISTITEM_SIZE},
- {"rating", LISTITEM_RATING},
- {"ratingandvotes", LISTITEM_RATING_AND_VOTES},
- {"userrating", LISTITEM_USER_RATING},
- {"votes", LISTITEM_VOTES},
- {"mood", LISTITEM_MOOD},
- {"programcount", LISTITEM_PROGRAM_COUNT},
- {"duration", LISTITEM_DURATION},
- {"isselected", LISTITEM_ISSELECTED},
- {"isplaying", LISTITEM_ISPLAYING},
- {"plot", LISTITEM_PLOT},
- {"plotoutline", LISTITEM_PLOT_OUTLINE},
- {"episode", LISTITEM_EPISODE},
- {"season", LISTITEM_SEASON},
- {"tvshowtitle", LISTITEM_TVSHOW},
- {"premiered", LISTITEM_PREMIERED},
- {"comment", LISTITEM_COMMENT},
- {"path", LISTITEM_PATH},
- {"foldername", LISTITEM_FOLDERNAME},
- {"folderpath", LISTITEM_FOLDERPATH},
- {"picturepath", LISTITEM_PICTURE_PATH},
- {"pictureresolution", LISTITEM_PICTURE_RESOLUTION},
- {"picturedatetime", LISTITEM_PICTURE_DATETIME},
- {"picturedate", LISTITEM_PICTURE_DATE},
- {"picturelongdatetime", LISTITEM_PICTURE_LONGDATETIME},
- {"picturelongdate", LISTITEM_PICTURE_LONGDATE},
- {"picturecomment", LISTITEM_PICTURE_COMMENT},
- {"picturecaption", LISTITEM_PICTURE_CAPTION},
- {"picturedesc", LISTITEM_PICTURE_DESC},
- {"picturekeywords", LISTITEM_PICTURE_KEYWORDS},
- {"picturecammake", LISTITEM_PICTURE_CAM_MAKE},
- {"picturecammodel", LISTITEM_PICTURE_CAM_MODEL},
- {"pictureaperture", LISTITEM_PICTURE_APERTURE},
- {"picturefocallen", LISTITEM_PICTURE_FOCAL_LEN},
- {"picturefocusdist", LISTITEM_PICTURE_FOCUS_DIST},
- {"pictureexpmode", LISTITEM_PICTURE_EXP_MODE},
- {"pictureexptime", LISTITEM_PICTURE_EXP_TIME},
- {"pictureiso", LISTITEM_PICTURE_ISO},
- {"pictureauthor", LISTITEM_PICTURE_AUTHOR},
- {"picturebyline", LISTITEM_PICTURE_BYLINE},
- {"picturebylinetitle", LISTITEM_PICTURE_BYLINE_TITLE},
- {"picturecategory", LISTITEM_PICTURE_CATEGORY},
- {"pictureccdwidth", LISTITEM_PICTURE_CCD_WIDTH},
- {"picturecity", LISTITEM_PICTURE_CITY},
- {"pictureurgency", LISTITEM_PICTURE_URGENCY},
- {"picturecopyrightnotice", LISTITEM_PICTURE_COPYRIGHT_NOTICE},
- {"picturecountry", LISTITEM_PICTURE_COUNTRY},
- {"picturecountrycode", LISTITEM_PICTURE_COUNTRY_CODE},
- {"picturecredit", LISTITEM_PICTURE_CREDIT},
- {"pictureiptcdate", LISTITEM_PICTURE_IPTCDATE},
- {"picturedigitalzoom", LISTITEM_PICTURE_DIGITAL_ZOOM},
- {"pictureexposure", LISTITEM_PICTURE_EXPOSURE},
- {"pictureexposurebias", LISTITEM_PICTURE_EXPOSURE_BIAS},
- {"pictureflashused", LISTITEM_PICTURE_FLASH_USED},
- {"pictureheadline", LISTITEM_PICTURE_HEADLINE},
- {"picturecolour", LISTITEM_PICTURE_COLOUR},
- {"picturelightsource", LISTITEM_PICTURE_LIGHT_SOURCE},
- {"picturemeteringmode", LISTITEM_PICTURE_METERING_MODE},
- {"pictureobjectname", LISTITEM_PICTURE_OBJECT_NAME},
- {"pictureorientation", LISTITEM_PICTURE_ORIENTATION},
- {"pictureprocess", LISTITEM_PICTURE_PROCESS},
- {"picturereferenceservice", LISTITEM_PICTURE_REF_SERVICE},
- {"picturesource", LISTITEM_PICTURE_SOURCE},
- {"picturespecialinstructions", LISTITEM_PICTURE_SPEC_INSTR},
- {"picturestate", LISTITEM_PICTURE_STATE},
- {"picturesupplementalcategories", LISTITEM_PICTURE_SUP_CATEGORIES},
- {"picturetransmissionreference", LISTITEM_PICTURE_TX_REFERENCE},
- {"picturewhitebalance", LISTITEM_PICTURE_WHITE_BALANCE},
- {"pictureimagetype", LISTITEM_PICTURE_IMAGETYPE},
- {"picturesublocation", LISTITEM_PICTURE_SUBLOCATION},
- {"pictureiptctime", LISTITEM_PICTURE_TIMECREATED},
- {"picturegpslat", LISTITEM_PICTURE_GPS_LAT},
- {"picturegpslon", LISTITEM_PICTURE_GPS_LON},
- {"picturegpsalt", LISTITEM_PICTURE_GPS_ALT},
- {"studio", LISTITEM_STUDIO},
- {"country", LISTITEM_COUNTRY},
- {"mpaa", LISTITEM_MPAA},
- {"cast", LISTITEM_CAST},
- {"castandrole", LISTITEM_CAST_AND_ROLE},
- {"writer", LISTITEM_WRITER},
- {"tagline", LISTITEM_TAGLINE},
- {"status", LISTITEM_STATUS},
- {"top250", LISTITEM_TOP250},
- {"trailer", LISTITEM_TRAILER},
- {"sortletter", LISTITEM_SORT_LETTER},
- {"tag", LISTITEM_TAG},
- {"set", LISTITEM_SET},
- {"setid", LISTITEM_SETID},
- {"videocodec", LISTITEM_VIDEO_CODEC},
- {"videoresolution", LISTITEM_VIDEO_RESOLUTION},
- {"videowidth", LISTITEM_VIDEO_WIDTH},
- {"videoheight", LISTITEM_VIDEO_HEIGHT},
- {"videoaspect", LISTITEM_VIDEO_ASPECT},
- {"audiocodec", LISTITEM_AUDIO_CODEC},
- {"audiochannels", LISTITEM_AUDIO_CHANNELS},
- {"audiolanguage", LISTITEM_AUDIO_LANGUAGE},
- {"subtitlelanguage", LISTITEM_SUBTITLE_LANGUAGE},
- {"isresumable", LISTITEM_IS_RESUMABLE},
- {"percentplayed", LISTITEM_PERCENT_PLAYED},
- {"isfolder", LISTITEM_IS_FOLDER},
- {"isparentfolder", LISTITEM_IS_PARENTFOLDER},
- {"iscollection", LISTITEM_IS_COLLECTION},
- {"originaltitle", LISTITEM_ORIGINALTITLE},
- {"lastplayed", LISTITEM_LASTPLAYED},
- {"playcount", LISTITEM_PLAYCOUNT},
- {"discnumber", LISTITEM_DISC_NUMBER},
- {"starttime", LISTITEM_STARTTIME},
- {"endtime", LISTITEM_ENDTIME},
- {"endtimeresume", LISTITEM_ENDTIME_RESUME},
- {"startdate", LISTITEM_STARTDATE},
- {"enddate", LISTITEM_ENDDATE},
- {"nexttitle", LISTITEM_NEXT_TITLE},
- {"nextgenre", LISTITEM_NEXT_GENRE},
- {"nextplot", LISTITEM_NEXT_PLOT},
- {"nextplotoutline", LISTITEM_NEXT_PLOT_OUTLINE},
- {"nextstarttime", LISTITEM_NEXT_STARTTIME},
- {"nextendtime", LISTITEM_NEXT_ENDTIME},
- {"nextstartdate", LISTITEM_NEXT_STARTDATE},
- {"nextenddate", LISTITEM_NEXT_ENDDATE},
- {"nextduration", LISTITEM_NEXT_DURATION},
- {"channelname", LISTITEM_CHANNEL_NAME},
- {"channellogo", LISTITEM_CHANNEL_LOGO},
- {"channelnumberlabel", LISTITEM_CHANNEL_NUMBER},
- {"channelgroup", LISTITEM_CHANNEL_GROUP},
- {"hasepg", LISTITEM_HAS_EPG},
- {"hastimer", LISTITEM_HASTIMER},
- {"hastimerschedule", LISTITEM_HASTIMERSCHEDULE},
- {"hasreminder", LISTITEM_HASREMINDER},
- {"hasreminderrule", LISTITEM_HASREMINDERRULE},
- {"hasrecording", LISTITEM_HASRECORDING},
- {"isrecording", LISTITEM_ISRECORDING},
- {"isplayable", LISTITEM_ISPLAYABLE},
- {"hasarchive", LISTITEM_HASARCHIVE},
- {"inprogress", LISTITEM_INPROGRESS},
- {"isencrypted", LISTITEM_ISENCRYPTED},
- {"progress", LISTITEM_PROGRESS},
- {"dateadded", LISTITEM_DATE_ADDED},
- {"dbtype", LISTITEM_DBTYPE},
- {"dbid", LISTITEM_DBID},
- {"appearances", LISTITEM_APPEARANCES},
- {"stereoscopicmode", LISTITEM_STEREOSCOPIC_MODE},
- {"isstereoscopic", LISTITEM_IS_STEREOSCOPIC},
- {"imdbnumber", LISTITEM_IMDBNUMBER},
- {"episodename", LISTITEM_EPISODENAME},
- {"timertype", LISTITEM_TIMERTYPE},
- {"epgeventtitle", LISTITEM_EPG_EVENT_TITLE},
- {"epgeventicon", LISTITEM_EPG_EVENT_ICON},
- {"timerisactive", LISTITEM_TIMERISACTIVE},
- {"timerhaserror", LISTITEM_TIMERHASERROR},
- {"timerhasconflict", LISTITEM_TIMERHASCONFLICT},
- {"addonname", LISTITEM_ADDON_NAME},
- {"addonversion", LISTITEM_ADDON_VERSION},
- {"addoncreator", LISTITEM_ADDON_CREATOR},
- {"addonsummary", LISTITEM_ADDON_SUMMARY},
- {"addondescription", LISTITEM_ADDON_DESCRIPTION},
- {"addondisclaimer", LISTITEM_ADDON_DISCLAIMER},
- {"addonnews", LISTITEM_ADDON_NEWS},
- {"addonbroken", LISTITEM_ADDON_BROKEN},
- {"addonlifecycletype", LISTITEM_ADDON_LIFECYCLE_TYPE},
- {"addonlifecycledesc", LISTITEM_ADDON_LIFECYCLE_DESC},
- {"addontype", LISTITEM_ADDON_TYPE},
- {"addoninstalldate", LISTITEM_ADDON_INSTALL_DATE},
- {"addonlastupdated", LISTITEM_ADDON_LAST_UPDATED},
- {"addonlastused", LISTITEM_ADDON_LAST_USED},
- {"addonorigin", LISTITEM_ADDON_ORIGIN},
- {"addonsize", LISTITEM_ADDON_SIZE},
- {"expirationdate", LISTITEM_EXPIRATION_DATE},
- {"expirationtime", LISTITEM_EXPIRATION_TIME},
- {"art", LISTITEM_ART},
- {"property", LISTITEM_PROPERTY},
- {"parentalrating", LISTITEM_PARENTAL_RATING},
- {"parentalratingcode", LISTITEM_PARENTAL_RATING_CODE},
- {"parentalratingicon", LISTITEM_PARENTAL_RATING_ICON},
- {"parentalratingsource", LISTITEM_PARENTAL_RATING_SOURCE},
- {"currentitem", LISTITEM_CURRENTITEM},
- {"isnew", LISTITEM_IS_NEW},
- {"isboxset", LISTITEM_IS_BOXSET},
- {"totaldiscs", LISTITEM_TOTALDISCS},
- {"releasedate", LISTITEM_RELEASEDATE},
- {"originaldate", LISTITEM_ORIGINALDATE},
- {"bpm", LISTITEM_BPM},
- {"uniqueid", LISTITEM_UNIQUEID},
- {"bitrate", LISTITEM_BITRATE},
- {"samplerate", LISTITEM_SAMPLERATE},
- {"musicchannels", LISTITEM_MUSICCHANNELS},
- {"ispremiere", LISTITEM_IS_PREMIERE},
- {"isfinale", LISTITEM_IS_FINALE},
- {"islive", LISTITEM_IS_LIVE},
- {"tvshowdbid", LISTITEM_TVSHOWDBID},
- {"albumstatus", LISTITEM_ALBUMSTATUS},
- {"isautoupdateable", LISTITEM_ISAUTOUPDATEABLE},
- {"hdrtype", LISTITEM_VIDEO_HDR_TYPE},
- {"songvideourl", LISTITEM_SONG_VIDEO_URL},
- {"hasvideoversions", LISTITEM_HASVIDEOVERSIONS},
- {"isvideoextra", LISTITEM_ISVIDEOEXTRA},
- {"videoversionname", LISTITEM_VIDEOVERSION_NAME},
- {"hasvideoextras", LISTITEM_HASVIDEOEXTRAS},
- {"pvrclientname", LISTITEM_PVR_CLIENT_NAME},
- {"pvrinstancename", LISTITEM_PVR_INSTANCE_NAME},
- {"pvrgrouporigin", LISTITEM_PVR_GROUP_ORIGIN},
- {"episodepart", LISTITEM_EPISODEPART},
- {"mediaproviders", LISTITEM_MEDIAPROVIDERS},
- {"titleextrainfo", LISTITEM_TITLE_EXTRAINFO},
- {"hdrdetail", LISTITEM_VIDEO_HDR_DETAIL},
+
+constexpr std::array listitem_labels = {{
+ {"thumb", LISTITEM_THUMB },
+ { "icon", LISTITEM_ICON },
+ { "actualicon", LISTITEM_ACTUAL_ICON },
+ { "overlay", LISTITEM_OVERLAY },
+ { "label", LISTITEM_LABEL },
+ { "label2", LISTITEM_LABEL2 },
+ { "title", LISTITEM_TITLE },
+ { "tracknumber", LISTITEM_TRACKNUMBER },
+ { "artist", LISTITEM_ARTIST },
+ { "album", LISTITEM_ALBUM },
+ { "albumartist", LISTITEM_ALBUM_ARTIST },
+ { "year", LISTITEM_YEAR },
+ { "genre", LISTITEM_GENRE },
+ { "contributors", LISTITEM_CONTRIBUTORS },
+ { "contributorandrole", LISTITEM_CONTRIBUTOR_AND_ROLE },
+ { "director", LISTITEM_DIRECTOR },
+ { "disctitle", LISTITEM_DISC_TITLE },
+ { "filename", LISTITEM_FILENAME },
+ { "filenameandpath", LISTITEM_FILENAME_AND_PATH },
+ { "fileextension", LISTITEM_FILE_EXTENSION },
+ { "filenamenoextension", LISTITEM_FILENAME_NO_EXTENSION },
+ { "date", LISTITEM_DATE },
+ { "datetime", LISTITEM_DATETIME },
+ { "size", LISTITEM_SIZE },
+ { "rating", LISTITEM_RATING },
+ { "ratingandvotes", LISTITEM_RATING_AND_VOTES },
+ { "userrating", LISTITEM_USER_RATING },
+ { "votes", LISTITEM_VOTES },
+ { "mood", LISTITEM_MOOD },
+ { "programcount", LISTITEM_PROGRAM_COUNT },
+ { "duration", LISTITEM_DURATION },
+ { "isselected", LISTITEM_ISSELECTED },
+ { "isplaying", LISTITEM_ISPLAYING },
+ { "plot", LISTITEM_PLOT },
+ { "plotoutline", LISTITEM_PLOT_OUTLINE },
+ { "episode", LISTITEM_EPISODE },
+ { "season", LISTITEM_SEASON },
+ { "tvshowtitle", LISTITEM_TVSHOW },
+ { "premiered", LISTITEM_PREMIERED },
+ { "comment", LISTITEM_COMMENT },
+ { "path", LISTITEM_PATH },
+ { "foldername", LISTITEM_FOLDERNAME },
+ { "folderpath", LISTITEM_FOLDERPATH },
+ { "picturepath", LISTITEM_PICTURE_PATH },
+ { "pictureresolution",LISTITEM_PICTURE_RESOLUTION },
+ { "picturedatetime", LISTITEM_PICTURE_DATETIME },
+ { "picturedate", LISTITEM_PICTURE_DATE },
+ { "picturelongdatetime",LISTITEM_PICTURE_LONGDATETIME },
+ { "picturelongdate", LISTITEM_PICTURE_LONGDATE },
+ { "picturecomment", LISTITEM_PICTURE_COMMENT },
+ { "picturecaption", LISTITEM_PICTURE_CAPTION },
+ { "picturedesc", LISTITEM_PICTURE_DESC },
+ { "picturekeywords", LISTITEM_PICTURE_KEYWORDS },
+ { "picturecammake", LISTITEM_PICTURE_CAM_MAKE },
+ { "picturecammodel", LISTITEM_PICTURE_CAM_MODEL },
+ { "pictureaperture", LISTITEM_PICTURE_APERTURE },
+ { "picturefocallen", LISTITEM_PICTURE_FOCAL_LEN },
+ { "picturefocusdist", LISTITEM_PICTURE_FOCUS_DIST },
+ { "pictureexpmode", LISTITEM_PICTURE_EXP_MODE },
+ { "pictureexptime", LISTITEM_PICTURE_EXP_TIME },
+ { "pictureiso", LISTITEM_PICTURE_ISO },
+ { "pictureauthor", LISTITEM_PICTURE_AUTHOR },
+ { "picturebyline", LISTITEM_PICTURE_BYLINE },
+ { "picturebylinetitle", LISTITEM_PICTURE_BYLINE_TITLE },
+ { "picturecategory", LISTITEM_PICTURE_CATEGORY },
+ { "pictureccdwidth", LISTITEM_PICTURE_CCD_WIDTH },
+ { "picturecity", LISTITEM_PICTURE_CITY },
+ { "pictureurgency", LISTITEM_PICTURE_URGENCY },
+ { "picturecopyrightnotice", LISTITEM_PICTURE_COPYRIGHT_NOTICE },
+ { "picturecountry", LISTITEM_PICTURE_COUNTRY },
+ { "picturecountrycode", LISTITEM_PICTURE_COUNTRY_CODE },
+ { "picturecredit", LISTITEM_PICTURE_CREDIT },
+ { "pictureiptcdate", LISTITEM_PICTURE_IPTCDATE },
+ { "picturedigitalzoom", LISTITEM_PICTURE_DIGITAL_ZOOM },
+ { "pictureexposure", LISTITEM_PICTURE_EXPOSURE },
+ { "pictureexposurebias", LISTITEM_PICTURE_EXPOSURE_BIAS },
+ { "pictureflashused", LISTITEM_PICTURE_FLASH_USED },
+ { "pictureheadline", LISTITEM_PICTURE_HEADLINE },
+ { "picturecolour", LISTITEM_PICTURE_COLOUR },
+ { "picturelightsource", LISTITEM_PICTURE_LIGHT_SOURCE },
+ { "picturemeteringmode", LISTITEM_PICTURE_METERING_MODE },
+ { "pictureobjectname", LISTITEM_PICTURE_OBJECT_NAME },
+ { "pictureorientation", LISTITEM_PICTURE_ORIENTATION },
+ { "pictureprocess", LISTITEM_PICTURE_PROCESS },
+ { "picturereferenceservice", LISTITEM_PICTURE_REF_SERVICE },
+ { "picturesource", LISTITEM_PICTURE_SOURCE },
+ { "picturespecialinstructions", LISTITEM_PICTURE_SPEC_INSTR },
+ { "picturestate", LISTITEM_PICTURE_STATE },
+ { "picturesupplementalcategories", LISTITEM_PICTURE_SUP_CATEGORIES },
+ { "picturetransmissionreference", LISTITEM_PICTURE_TX_REFERENCE },
+ { "picturewhitebalance", LISTITEM_PICTURE_WHITE_BALANCE },
+ { "pictureimagetype", LISTITEM_PICTURE_IMAGETYPE },
+ { "picturesublocation", LISTITEM_PICTURE_SUBLOCATION },
+ { "pictureiptctime", LISTITEM_PICTURE_TIMECREATED },
+ { "picturegpslat", LISTITEM_PICTURE_GPS_LAT },
+ { "picturegpslon", LISTITEM_PICTURE_GPS_LON },
+ { "picturegpsalt", LISTITEM_PICTURE_GPS_ALT },
+ { "studio", LISTITEM_STUDIO },
+ { "country", LISTITEM_COUNTRY },
+ { "mpaa", LISTITEM_MPAA },
+ { "cast", LISTITEM_CAST },
+ { "castandrole", LISTITEM_CAST_AND_ROLE },
+ { "writer", LISTITEM_WRITER },
+ { "tagline", LISTITEM_TAGLINE },
+ { "status", LISTITEM_STATUS },
+ { "top250", LISTITEM_TOP250 },
+ { "trailer", LISTITEM_TRAILER },
+ { "sortletter", LISTITEM_SORT_LETTER },
+ { "tag", LISTITEM_TAG },
+ { "set", LISTITEM_SET },
+ { "setid", LISTITEM_SETID },
+ { "videocodec", LISTITEM_VIDEO_CODEC },
+ { "videoresolution", LISTITEM_VIDEO_RESOLUTION },
+ { "videowidth", LISTITEM_VIDEO_WIDTH},
+ { "videoheight", LISTITEM_VIDEO_HEIGHT},
+ { "videoaspect", LISTITEM_VIDEO_ASPECT },
+ { "audiocodec", LISTITEM_AUDIO_CODEC },
+ { "audiochannels", LISTITEM_AUDIO_CHANNELS },
+ { "audiolanguage", LISTITEM_AUDIO_LANGUAGE },
+ { "subtitlelanguage", LISTITEM_SUBTITLE_LANGUAGE },
+ { "isresumable", LISTITEM_IS_RESUMABLE},
+ { "percentplayed", LISTITEM_PERCENT_PLAYED},
+ { "isfolder", LISTITEM_IS_FOLDER },
+ { "isparentfolder", LISTITEM_IS_PARENTFOLDER },
+ { "iscollection", LISTITEM_IS_COLLECTION },
+ { "originaltitle", LISTITEM_ORIGINALTITLE },
+ { "lastplayed", LISTITEM_LASTPLAYED },
+ { "playcount", LISTITEM_PLAYCOUNT },
+ { "discnumber", LISTITEM_DISC_NUMBER },
+ { "starttime", LISTITEM_STARTTIME },
+ { "endtime", LISTITEM_ENDTIME },
+ { "endtimeresume", LISTITEM_ENDTIME_RESUME },
+ { "startdate", LISTITEM_STARTDATE },
+ { "enddate", LISTITEM_ENDDATE },
+ { "nexttitle", LISTITEM_NEXT_TITLE },
+ { "nextgenre", LISTITEM_NEXT_GENRE },
+ { "nextplot", LISTITEM_NEXT_PLOT },
+ { "nextplotoutline", LISTITEM_NEXT_PLOT_OUTLINE },
+ { "nextstarttime", LISTITEM_NEXT_STARTTIME },
+ { "nextendtime", LISTITEM_NEXT_ENDTIME },
+ { "nextstartdate", LISTITEM_NEXT_STARTDATE },
+ { "nextenddate", LISTITEM_NEXT_ENDDATE },
+ { "nextduration", LISTITEM_NEXT_DURATION },
+ { "channelname", LISTITEM_CHANNEL_NAME },
+ { "channellogo", LISTITEM_CHANNEL_LOGO },
+ { "channelnumberlabel", LISTITEM_CHANNEL_NUMBER },
+ { "channelgroup", LISTITEM_CHANNEL_GROUP },
+ { "hasepg", LISTITEM_HAS_EPG },
+ { "hastimer", LISTITEM_HASTIMER },
+ { "hastimerschedule", LISTITEM_HASTIMERSCHEDULE },
+ { "hasreminder", LISTITEM_HASREMINDER },
+ { "hasreminderrule", LISTITEM_HASREMINDERRULE },
+ { "hasrecording", LISTITEM_HASRECORDING },
+ { "isrecording", LISTITEM_ISRECORDING },
+ { "isplayable", LISTITEM_ISPLAYABLE },
+ { "hasarchive", LISTITEM_HASARCHIVE },
+ { "inprogress", LISTITEM_INPROGRESS },
+ { "isencrypted", LISTITEM_ISENCRYPTED },
+ { "progress", LISTITEM_PROGRESS },
+ { "dateadded", LISTITEM_DATE_ADDED },
+ { "dbtype", LISTITEM_DBTYPE },
+ { "dbid", LISTITEM_DBID },
+ { "appearances", LISTITEM_APPEARANCES },
+ { "stereoscopicmode", LISTITEM_STEREOSCOPIC_MODE },
+ { "isstereoscopic", LISTITEM_IS_STEREOSCOPIC },
+ { "imdbnumber", LISTITEM_IMDBNUMBER },
+ { "episodename", LISTITEM_EPISODENAME },
+ { "timertype", LISTITEM_TIMERTYPE },
+ { "epgeventtitle", LISTITEM_EPG_EVENT_TITLE },
+ { "epgeventicon", LISTITEM_EPG_EVENT_ICON },
+ { "timerisactive", LISTITEM_TIMERISACTIVE },
+ { "timerhaserror", LISTITEM_TIMERHASERROR },
+ { "timerhasconflict", LISTITEM_TIMERHASCONFLICT },
+ { "addonname", LISTITEM_ADDON_NAME },
+ { "addonversion", LISTITEM_ADDON_VERSION },
+ { "addoncreator", LISTITEM_ADDON_CREATOR },
+ { "addonsummary", LISTITEM_ADDON_SUMMARY },
+ { "addondescription", LISTITEM_ADDON_DESCRIPTION },
+ { "addondisclaimer", LISTITEM_ADDON_DISCLAIMER },
+ { "addonnews", LISTITEM_ADDON_NEWS },
+ { "addonbroken", LISTITEM_ADDON_BROKEN },
+ { "addonlifecycletype", LISTITEM_ADDON_LIFECYCLE_TYPE },
+ { "addonlifecycledesc", LISTITEM_ADDON_LIFECYCLE_DESC },
+ { "addontype", LISTITEM_ADDON_TYPE },
+ { "addoninstalldate", LISTITEM_ADDON_INSTALL_DATE },
+ { "addonlastupdated", LISTITEM_ADDON_LAST_UPDATED },
+ { "addonlastused", LISTITEM_ADDON_LAST_USED },
+ { "addonorigin", LISTITEM_ADDON_ORIGIN },
+ { "addonsize", LISTITEM_ADDON_SIZE },
+ { "expirationdate", LISTITEM_EXPIRATION_DATE },
+ { "expirationtime", LISTITEM_EXPIRATION_TIME },
+ { "art", LISTITEM_ART },
+ { "property", LISTITEM_PROPERTY },
+ { "parentalrating", LISTITEM_PARENTAL_RATING },
+ { "parentalratingcode", LISTITEM_PARENTAL_RATING_CODE },
+ { "parentalratingicon", LISTITEM_PARENTAL_RATING_ICON },
+ { "parentalratingsource", LISTITEM_PARENTAL_RATING_SOURCE },
+ { "currentitem", LISTITEM_CURRENTITEM },
+ { "isnew", LISTITEM_IS_NEW },
+ { "isboxset", LISTITEM_IS_BOXSET },
+ { "totaldiscs", LISTITEM_TOTALDISCS },
+ { "releasedate", LISTITEM_RELEASEDATE },
+ { "originaldate", LISTITEM_ORIGINALDATE },
+ { "bpm", LISTITEM_BPM },
+ { "uniqueid", LISTITEM_UNIQUEID },
+ { "bitrate", LISTITEM_BITRATE },
+ { "samplerate", LISTITEM_SAMPLERATE },
+ { "musicchannels", LISTITEM_MUSICCHANNELS },
+ { "ispremiere", LISTITEM_IS_PREMIERE },
+ { "isfinale", LISTITEM_IS_FINALE },
+ { "islive", LISTITEM_IS_LIVE },
+ { "tvshowdbid", LISTITEM_TVSHOWDBID },
+ { "albumstatus", LISTITEM_ALBUMSTATUS },
+ { "isautoupdateable", LISTITEM_ISAUTOUPDATEABLE },
+ { "hdrtype", LISTITEM_VIDEO_HDR_TYPE },
+ { "songvideourl", LISTITEM_SONG_VIDEO_URL },
+ { "hasvideoversions", LISTITEM_HASVIDEOVERSIONS },
+ { "isvideoextra", LISTITEM_ISVIDEOEXTRA },
+ { "videoversionname", LISTITEM_VIDEOVERSION_NAME },
+ { "hasvideoextras", LISTITEM_HASVIDEOEXTRAS },
+ { "pvrclientname", LISTITEM_PVR_CLIENT_NAME },
+ { "pvrinstancename", LISTITEM_PVR_INSTANCE_NAME },
+ { "pvrgrouporigin", LISTITEM_PVR_GROUP_ORIGIN },
+ { "episodepart", LISTITEM_EPISODEPART },
+ { "mediaproviders", LISTITEM_MEDIAPROVIDERS },
+ { "titleextrainfo", LISTITEM_TITLE_EXTRAINFO },
+ { "musicbitspersample", LISTITEM_MUSIC_BITSPERSAMPLE },
+ { "musiccodec", LISTITEM_MUSIC_CODEC },
}};
// clang-format on
@@ -10885,6 +10916,10 @@ int CGUIInfoManager::TranslateSingleString(const std::string &strCondition, bool
return LIBRARY_HAS_COMPILATIONS;
else if (content == "boxsets")
return LIBRARY_HAS_BOXSETS;
+ else if (content == "musicconcerts")
+ return LIBRARY_HAS_MUSIC_CONCERTS;
+ else if (content == "audiobooks")
+ return LIBRARY_HAS_AUDIOBOOKS;
else if (content == "role" && prop.num_params() > 1)
return AddMultiInfo(CGUIInfo(LIBRARY_HAS_ROLE, prop.param(1), 0));
}
diff --git a/xbmc/dialogs/GUIDialogMediaFilter.cpp b/xbmc/dialogs/GUIDialogMediaFilter.cpp
index 21cbc7dea4e5f..16b97b8626e7a 100644
--- a/xbmc/dialogs/GUIDialogMediaFilter.cpp
+++ b/xbmc/dialogs/GUIDialogMediaFilter.cpp
@@ -107,19 +107,21 @@ static const CGUIDialogMediaFilter::Filter filterList[] = {
{ "artists", Field::DISBANDED, 21896, SettingType::String, "edit", "string", OPERATOR_CONTAINS },
{ "artists", Field::DIED, 21897, SettingType::String, "edit", "string", OPERATOR_CONTAINS },
- { "albums", Field::ALBUM, 556, SettingType::String, "edit", "string", OPERATOR_CONTAINS },
- { "albums", Field::DISC_TITLE, 38076, SettingType::String, "edit", "string", OPERATOR_CONTAINS },
- { "albums", Field::ALBUM_ARTIST, 566, SettingType::List, "list", "string", OPERATOR_EQUALS },
- { "albums", Field::SOURCE, 39030, SettingType::List, "list", "string", OPERATOR_EQUALS },
- { "albums", Field::RATING, 563, SettingType::Number, "range", "number", OPERATOR_BETWEEN },
- { "albums", Field::USER_RATING, 38018, SettingType::Integer, "range", "integer", OPERATOR_BETWEEN },
- { "albums", Field::ALBUM_TYPE, 564, SettingType::List, "list", "string", OPERATOR_EQUALS },
- { "albums", Field::YEAR, 562, SettingType::Integer, "range", "integer", OPERATOR_BETWEEN },
- { "albums", Field::GENRE, 515, SettingType::List, "list", "string", OPERATOR_EQUALS },
- { "albums", Field::MUSIC_LABEL, 21899, SettingType::List, "list", "string", OPERATOR_EQUALS },
- { "albums", Field::COMPILATION, 204, SettingType::Boolean, "toggle", "", OPERATOR_FALSE },
- { "albums", Field::IS_BOXSET, 38074, SettingType::Boolean, "toggle", "", OPERATOR_FALSE },
- { "albums", Field::ORIG_YEAR, 38078, SettingType::String, "edit", "string", OPERATOR_CONTAINS },
+ { "albums", Field::ALBUM, 556, SettingType::String, "edit", "string", OPERATOR_CONTAINS },
+// { "albums", Field::ARTIST, 557, SettingType::List, "list", "string", OPERATOR_EQUALS },
+ { "albums", Field::DISC_TITLE, 38076, SettingType::String, "edit", "string", OPERATOR_CONTAINS },
+ { "albums", Field::ALBUM_ARTIST, 566, SettingType::List, "list", "string", OPERATOR_EQUALS },
+ { "albums", Field::SOURCE, 39030, SettingType::List, "list", "string", OPERATOR_EQUALS },
+ { "albums", Field::RATING, 563, SettingType::Number, "range", "number", OPERATOR_BETWEEN },
+ { "albums", Field::USER_RATING, 38018, SettingType::Integer, "range", "integer", OPERATOR_BETWEEN },
+ { "albums", Field::ALBUM_TYPE, 564, SettingType::List, "list", "string", OPERATOR_EQUALS },
+ { "albums", Field::YEAR, 562, SettingType::Integer, "range", "integer", OPERATOR_BETWEEN },
+ { "albums", Field::GENRE, 515, SettingType::List, "list", "string", OPERATOR_EQUALS },
+ { "albums", Field::MUSIC_LABEL, 21899, SettingType::List, "list", "string", OPERATOR_EQUALS },
+ { "albums", Field::COMPILATION, 204, SettingType::Boolean, "toggle", "", OPERATOR_FALSE },
+ { "albums", Field::IS_BOXSET, 38074, SettingType::Boolean, "toggle", "", OPERATOR_FALSE },
+ { "albums", Field::ORIG_YEAR, 38078, SettingType::String, "edit", "string", OPERATOR_CONTAINS },
+ { "albums", Field::IS_MUSIC_CONCERT,21486, SettingType::Boolean, "toggle", "", OPERATOR_FALSE},
{ "songs", Field::TITLE, 556, SettingType::String, "edit", "string", OPERATOR_CONTAINS },
{ "songs", Field::ALBUM, 558, SettingType::List, "list", "string", OPERATOR_EQUALS },
diff --git a/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp b/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp
index 66fb0b033597f..5f6f6dc5126a5 100644
--- a/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp
+++ b/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp
@@ -6,12 +6,11 @@
* See LICENSES/README.md for more information.
*/
-#include "GUIDialogSmartPlaylistRule.h"
-
#include "FileItem.h"
#include "FileItemList.h"
#include "GUIDialogFileBrowser.h"
#include "GUIDialogSelect.h"
+#include "GUIDialogSmartPlaylistRule.h"
#include "ServiceBroker.h"
#include "filesystem/Directory.h"
#include "guilib/GUIComponent.h"
@@ -34,15 +33,15 @@
using enum CDatabaseQueryRule::FieldType;
using namespace KODI;
-#define CONTROL_FIELD 15
-#define CONTROL_OPERATOR 16
-#define CONTROL_VALUE 17
-#define CONTROL_OK 18
-#define CONTROL_CANCEL 19
-#define CONTROL_BROWSE 20
+#define CONTROL_FIELD 15
+#define CONTROL_OPERATOR 16
+#define CONTROL_VALUE 17
+#define CONTROL_OK 18
+#define CONTROL_CANCEL 19
+#define CONTROL_BROWSE 20
CGUIDialogSmartPlaylistRule::CGUIDialogSmartPlaylistRule(void)
- : CGUIDialog(WINDOW_DIALOG_SMART_PLAYLIST_RULE, "SmartPlaylistRule.xml")
+ : CGUIDialog(WINDOW_DIALOG_SMART_PLAYLIST_RULE, "SmartPlaylistRule.xml")
{
m_cancelled = false;
m_loadType = KEEP_IN_MEMORY;
@@ -58,9 +57,9 @@ bool CGUIDialogSmartPlaylistRule::OnBack(int actionID)
bool CGUIDialogSmartPlaylistRule::OnMessage(CGUIMessage& message)
{
- switch ( message.GetMessage() )
+ switch (message.GetMessage())
{
- case GUI_MSG_CLICKED:
+ case GUI_MSG_CLICKED:
{
int iControl = message.GetSenderId();
if (iControl == CONTROL_OK)
@@ -83,9 +82,9 @@ bool CGUIDialogSmartPlaylistRule::OnMessage(CGUIMessage& message)
}
break;
- case GUI_MSG_VALIDITY_CHANGED:
- CONTROL_ENABLE_ON_CONDITION(CONTROL_OK, message.GetParam1());
- break;
+ case GUI_MSG_VALIDITY_CHANGED:
+ CONTROL_ENABLE_ON_CONDITION(CONTROL_OK, message.GetParam1());
+ break;
}
return CGUIDialog::OnMessage(message);
}
@@ -137,17 +136,11 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
int iLabel = 0;
if (m_rule.m_field == static_cast(Field::GENRE))
{
- if (m_type == "tvshows" ||
- m_type == "episodes" ||
- m_type == "movies")
+ if (m_type == "tvshows" || m_type == "episodes" || m_type == "movies")
videodatabase.GetGenresNav(basePath + "genres/", items, type);
- else if (m_type == "songs" ||
- m_type == "albums" ||
- m_type == "artists" ||
- m_type == "mixed")
- database.GetGenresNav("musicdb://genres/",items);
- if (m_type == "musicvideos" ||
- m_type == "mixed")
+ else if (m_type == "songs" || m_type == "albums" || m_type == "artists" || m_type == "mixed")
+ database.GetGenresNav("musicdb://genres/", items);
+ if (m_type == "musicvideos" || m_type == "mixed")
{
CFileItemList items2;
videodatabase.GetGenresNav("videodb://musicvideos/genres/", items2,
@@ -158,10 +151,7 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
}
else if (m_rule.m_field == static_cast(Field::SOURCE))
{
- if (m_type == "songs" ||
- m_type == "albums" ||
- m_type == "artists" ||
- m_type == "mixed")
+ if (m_type == "songs" || m_type == "albums" || m_type == "artists" || m_type == "mixed")
{
database.GetSourcesNav("musicdb://sources/", items);
iLabel = 39030;
@@ -186,8 +176,7 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
if (PLAYLIST::CSmartPlaylist::IsMusicType(m_type))
database.GetArtistsNav("musicdb://artists/", items, SortDescription(),
m_rule.m_field == static_cast(Field::ALBUM_ARTIST), -1);
- if (m_type == "musicvideos" ||
- m_type == "mixed")
+ if (m_type == "musicvideos" || m_type == "mixed")
{
CFileItemList items2;
videodatabase.GetMusicVideoArtistsByName("", items2);
@@ -199,8 +188,7 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
{
if (PLAYLIST::CSmartPlaylist::IsMusicType(m_type))
database.GetAlbumsNav("musicdb://albums/", items, SortDescription());
- if (m_type == "musicvideos" ||
- m_type == "mixed")
+ if (m_type == "musicvideos" || m_type == "mixed")
{
CFileItemList items2;
videodatabase.GetMusicVideoAlbumsByName("", items2);
@@ -210,7 +198,7 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
}
else if (m_rule.m_field == static_cast(Field::ACTOR))
{
- videodatabase.GetActorsNav(basePath + "actors/",items,type);
+ videodatabase.GetActorsNav(basePath + "actors/", items, type);
iLabel = 20337;
}
else if (m_rule.m_field == static_cast(Field::YEAR))
@@ -287,11 +275,13 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
// think there's any decent way to deal with this, as the infinite loop may be an arbitrary
// number of playlists deep, eg playlist1 -> playlist2 -> playlist3 ... -> playlistn -> playlist1
if (PLAYLIST::CSmartPlaylist::IsVideoType(m_type))
- XFILE::CDirectory::GetDirectory("special://videoplaylists/", items, ".xsp", XFILE::DIR_FLAG_NO_FILE_DIRS);
+ XFILE::CDirectory::GetDirectory("special://videoplaylists/", items, ".xsp",
+ XFILE::DIR_FLAG_NO_FILE_DIRS);
if (PLAYLIST::CSmartPlaylist::IsMusicType(m_type))
{
CFileItemList items2;
- XFILE::CDirectory::GetDirectory("special://musicplaylists/", items2, ".xsp", XFILE::DIR_FLAG_NO_FILE_DIRS);
+ XFILE::CDirectory::GetDirectory("special://musicplaylists/", items2, ".xsp",
+ XFILE::DIR_FLAG_NO_FILE_DIRS);
items.Append(items2);
}
@@ -324,7 +314,7 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
if (PLAYLIST::CSmartPlaylist::IsVideoType(m_type))
{
std::vector sources2 = *CMediaSourceSettings::GetInstance().GetSources("video");
- sources.insert(sources.end(),sources2.begin(),sources2.end());
+ sources.insert(sources.end(), sources2.begin(), sources2.end());
}
CServiceBroker::GetMediaManager().GetLocalDrives(sources);
@@ -348,8 +338,7 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
else if (m_rule.m_field == static_cast(Field::TAG))
{
VideoDbContentType type = VideoDbContentType::MOVIES;
- if (m_type == "tvshows" ||
- m_type == "episodes")
+ if (m_type == "tvshows" || m_type == "episodes")
type = VideoDbContentType::TVSHOWS;
else if (m_type == "musicvideos")
type = VideoDbContentType::MUSICVIDEOS;
@@ -359,6 +348,22 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
videodatabase.GetTagsNav(basePath + "tags/", items, type);
iLabel = 20459;
}
+ else if ((m_rule.m_field == static_cast(Field::ALBUM_CODEC)) ||
+ (m_rule.m_field == static_cast(Field::BITS_PER_SAMPLE)))
+ {
+ std::string reqField;
+ if (m_rule.m_field == static_cast(Field::ALBUM_CODEC))
+ {
+ iLabel = 21446;
+ reqField = "strCodec";
+ }
+ else if (m_rule.m_field == static_cast(Field::BITS_PER_SAMPLE))
+ {
+ iLabel = 612;
+ reqField = "iBitsPerSample";
+ }
+ database.GetMusicDetails(items, reqField);
+ }
else
{ //! @todo Add browseability in here.
assert(false);
@@ -371,7 +376,9 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
? SortAttributeIgnoreArticle
: SortAttributeNone);
- CGUIDialogSelect* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT);
+ CGUIDialogSelect* pDialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow(
+ WINDOW_DIALOG_SELECT);
pDialog->Reset();
pDialog->SetItems(items);
std::string strHeading =
@@ -463,7 +470,9 @@ void CGUIDialogSmartPlaylistRule::OnCancel()
void CGUIDialogSmartPlaylistRule::OnField()
{
const auto fields = PLAYLIST::CSmartPlaylistRule::GetFields(m_type);
- CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT);
+ CGUIDialogSelect* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow(
+ WINDOW_DIALOG_SELECT);
dialog->Reset();
dialog->SetHeading(CVariant{20427});
int selected = -1;
@@ -499,9 +508,11 @@ void CGUIDialogSmartPlaylistRule::OnField()
void CGUIDialogSmartPlaylistRule::OnOperator()
{
const auto labels = GetValidOperators(m_rule);
- CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT);
+ CGUIDialogSelect* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow(
+ WINDOW_DIALOG_SELECT);
dialog->Reset();
- dialog->SetHeading(CVariant{ 16023 });
+ dialog->SetHeading(CVariant{16023});
for (auto label : labels)
dialog->Add(std::get<0>(label));
dialog->SetSelected(PLAYLIST::CSmartPlaylistRule::GetLocalizedOperator(m_rule.m_operator));
@@ -565,7 +576,7 @@ void CGUIDialogSmartPlaylistRule::OnInitWindow()
UpdateButtons();
- CGUIEditControl *editControl = dynamic_cast(GetControl(CONTROL_VALUE));
+ CGUIEditControl* editControl = dynamic_cast(GetControl(CONTROL_VALUE));
if (editControl != NULL)
editControl->SetInputValidation(PLAYLIST::CSmartPlaylistRule::Validate, &m_rule);
}
@@ -583,8 +594,11 @@ void CGUIDialogSmartPlaylistRule::OnDeinitWindow(int nextWindowID)
bool CGUIDialogSmartPlaylistRule::EditRule(PLAYLIST::CSmartPlaylistRule& rule,
const std::string& type)
{
- CGUIDialogSmartPlaylistRule *editor = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SMART_PLAYLIST_RULE);
- if (!editor) return false;
+ CGUIDialogSmartPlaylistRule* editor =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow(
+ WINDOW_DIALOG_SMART_PLAYLIST_RULE);
+ if (!editor)
+ return false;
editor->m_rule = rule;
editor->m_type = type;
@@ -592,4 +606,3 @@ bool CGUIDialogSmartPlaylistRule::EditRule(PLAYLIST::CSmartPlaylistRule& rule,
rule = editor->m_rule;
return !editor->m_cancelled;
}
-
diff --git a/xbmc/filesystem/AudioBookFileDirectory.cpp b/xbmc/filesystem/AudioBookFileDirectory.cpp
index 8eb8b19c42430..fa88a9be1034c 100644
--- a/xbmc/filesystem/AudioBookFileDirectory.cpp
+++ b/xbmc/filesystem/AudioBookFileDirectory.cpp
@@ -6,7 +6,6 @@
*/
#include "AudioBookFileDirectory.h"
-
#include "FileItem.h"
#include "FileItemList.h"
#include "ServiceBroker.h"
@@ -15,6 +14,8 @@
#include "cores/FFmpeg.h"
#include "filesystem/File.h"
#include "imagefiles/ImageFileURL.h"
+#include "music/MusicEmbeddedCoverLoaderFFmpeg.h"
+#include "music/tags/MusicInfoTag.h"
#include "resources/LocalizeStrings.h"
#include "resources/ResourcesComponent.h"
#include "settings/AdvancedSettings.h"
@@ -95,6 +96,15 @@ bool CAudioBookFileDirectory::GetDirectory(const CURL& url,
else
{
std::string key = StringUtils::ToUpper(tag->key);
+
+ /* The matroska Tag and chapter editor from https://www.videohelp.com/software/chapterEditor
+ prefaces level 50 tags with the target type (album/concert/episde/movie/etc). That needs
+ removing for the tag processing to work correctly. MKVToolnix & mp3tag correctly target
+ level 50 and do not preface the tag with the target type. We're only interested in albums
+ at this level (50) so strip off the preface if it exists.
+ */
+ if (StringUtils::StartsWith(key, "ALBUM/"))
+ key.erase(0, 6);
// track is matroska's discnumber when at level 50 (these tags) as set by mp3tag
// part_number is the matroska spec key
if (key == "TRACK" || key == "PART_NUMBER")
@@ -110,38 +120,54 @@ bool CAudioBookFileDirectory::GetDirectory(const CURL& url,
else if (key == "ARTISTSORT" || key == "ARTIST SORT")
albumtag.SetArtistSort(
StringUtils::Join(StringUtils::Split(tag->value, separators), musicsep));
- else if (key == "ALBUMARTIST" || key == "ALBUM ARTIST")
+ else if (key == "ALBUMARTIST" || key == "ALBUM ARTIST" || key == "ALBUM_ARTIST")
albumtag.SetAlbumArtist(
StringUtils::Join(StringUtils::Split(tag->value, separators), musicsep));
else if (key == "ALBUMARTSTS" || key == "ALBUM ARTISTS")
albumtag.SetAlbumArtist(StringUtils::Split(tag->value, separators));
- else if (key == "ALBUMARTISTSORT" || key == "ALBUM ARTIST SORT")
+ else if (key == "ALBUMARTISTSORT" || key == "ALBUM ARTIST SORT" || key == "SORT_ALBUM_ARTIST")
albumtag.SetAlbumArtistSort(
StringUtils::Join(StringUtils::Split(tag->value, separators), musicsep));
else if (key == "MUSICBRAINZ_ARTISTID")
albumtag.SetMusicBrainzArtistID(StringUtils::Split(tag->value, separators));
- else if (key == "MUSICBRAINZ_ALBUMARTISTID")
+ else if (key == "MUSICBRAINZ_ALBUMARTISTID" || key == "MUSICBRAINZ ALBUM ARTIST ID")
albumtag.SetMusicBrainzAlbumArtistID(StringUtils::Split(tag->value, separators));
else if (key == "MUSICBRAINZ_ALBUMARTIST")
albumtag.SetAlbumArtist(tag->value);
- else if (key == "MUSICBRAINZ_ALBUMID")
+ else if (key == "MUSICBRAINZ_ALBUMID" || key == "MUSICBRAINZ ALBUM ID")
albumtag.SetMusicBrainzAlbumID(tag->value);
- else if (key == "MUSICBRAINZ_RELEASEGROUPID")
+ else if (key == "MUSICBRAINZ_RELEASEGROUPID" || key == "MUSICBRAINZ RELEASE GROUP ID")
+ albumtag.SetMusicBrainzReleaseGroupID(tag->value);
+ else if (key == "COMPOSERSORT" || key == "COMPOSER SORT")
+ albumtag.SetComposerSort(
+ StringUtils::Join(StringUtils::Split(tag->value, separators), musicsep));
+ else if (key == "MUSICBRAINZ_ARTISTID")
+ albumtag.SetMusicBrainzArtistID(StringUtils::Split(tag->value, separators));
+ else if (key == "MUSICBRAINZ_ALBUMARTISTID" || key == "MUSICBRAINZ ALBUM ARTIST ID")
+ albumtag.SetMusicBrainzAlbumArtistID(StringUtils::Split(tag->value, separators));
+ else if (key == "MUSICBRAINZ_ALBUMARTIST")
+ albumtag.SetAlbumArtist(tag->value);
+ else if (key == "MUSICBRAINZ_ALBUMID" || key == "MUSICBRAINZ ALBUM ID")
+ albumtag.SetMusicBrainzAlbumID(tag->value);
+ else if (key == "MUSICBRAINZ_RELEASEGROUPID" || key == "MUSICBRAINZ RELEASE GROUP ID")
albumtag.SetMusicBrainzReleaseGroupID(tag->value);
else if (key == "MUSICBRAINZ_ALBUMSTATUS")
albumtag.SetAlbumReleaseStatus(tag->value);
else if (key == "MUSICBRAINZ_ALBUMTYPE")
albumtag.SetMusicBrainzReleaseType(tag->value);
+ else if (key == "COMPILATION")
+ albumtag.SetCompilation(true);
else if (key == "PUBLISHER")
albumtag.SetRecordLabel(tag->value);
// mp3tag info shows year but the value is stored in date_recorded
// equates to TDRC in id3v2.4 ISO 8601 yyyy-mm-dd or part thereof
- else if (key == "YEAR" || key == "DATE_RECORDED")
+ else if (key == "YEAR" || key == "DATE_RELEASED") // proper matroska tag is date_released
albumtag.SetReleaseDate(tag->value);
- else if (key == "ORIGYEAR") // ISO 8601 as above. Equates to TDOR in id3v2.4 (set by mp3tag)
+ // ISO 8601 as above. Equates to TDOR in id3v2.4 (set by mp3tag)
+ else if (key == "ORIGYEAR" || key == "DATE_RECORDED")
albumtag.SetOriginalDate(tag->value);
else if (key == "MOOD")
- albumtag.SetMood(tag->value);
+ albumtag.SetMood( StringUtils::Join(StringUtils::Split(tag->value, separators), musicsep));
// genre could be comma delimited or not. Temporarily add the comma just in case. true trims
// any whitespace around the genre(s)
else if (key == "GENRE")
@@ -151,7 +177,7 @@ bool CAudioBookFileDirectory::GetDirectory(const CURL& url,
separators.pop_back();
}
// comma separated list of role, person
- else if (key == "INVOLVEDPEOPLE")
+ else if (key == "INVOLVEDPEOPLE" || key == "ACTOR")
{
tagdata = StringUtils::Split(tag->value, ",");
AddCommaDelimitedString(tagdata, separators, albumtag);
@@ -160,172 +186,325 @@ bool CAudioBookFileDirectory::GetDirectory(const CURL& url,
albumtag.SetDiscSubtitle(tag->value);
else if (key == "REMIXED_BY")
albumtag.AddArtistRole("Remixer", tag->value);
+ else if (key == "MIXED_BY" || key == "MIXER")
+ albumtag.AddArtistRole("Mixer", tag->value);
else if (key == "COMMENT")
albumtag.SetComment(tag->value);
}
}
- std::string thumb;
- if (m_fctx->nb_chapters > 1)
- thumb = IMAGE_FILES::URLFromFile(url.Get(), "music");
-
- ChplChapterResult neroChapterResult{chplNone};
- std::vector nero;
+ AVStream* st = nullptr;
+ std::string codec_name = "unknown";
+ int streamIndex = -1;
+ // Look for the default audio stream first
+ for (unsigned int i = 0; i < m_fctx->nb_streams; ++i)
+ {
+ if (m_fctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
+ {
+ if (m_fctx->streams[i]->disposition & AV_DISPOSITION_DEFAULT)
+ {
+ streamIndex = i;
+ break; // Found a default audio stream, however, more than 1 stream can be set as default !!
+ }
+ }
+ }
- if (isAudioBook)
+ // If no default stream was found, look for the first audio stream as usually highest quality 1st
+ if (streamIndex == -1)
{
- neroChapterResult = CChplChapterReader::ScanNeroChapters(url, nero);
- if (neroChapterResult.IsError())
+ for (unsigned int i = 0; i < m_fctx->nb_streams; ++i)
{
- CLog::Log(LOGERROR,
- "AudioBookFileDirectory: Error scanning for Nero style chapters in file {}. The "
- "error returned was {}",
- url.GetRedacted(), *neroChapterResult.errorMessage);
+ if (m_fctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
+ {
+ streamIndex = i;
+ break; // Found the first audio stream
+ }
}
- else if (neroChapterResult.IsNone())
- { // can't get here without some form of chapter so must be QT style chapters (chap atom)
- CLog::Log(
- LOGDEBUG,
- "AudioBookFileDirectory: Scanned for nero style chapters but didn't find any in {}, "
- "using QT chapters",
- url.GetRedacted());
+ }
+ if (streamIndex > -1)
+ {
+ st = m_fctx->streams[streamIndex];
+
+ albumtag.SetBitsPerSample(st->codecpar->bits_per_coded_sample);
+ albumtag.SetSampleRate(st->codecpar->sample_rate);
+ albumtag.SetBitRate(st->codecpar->bit_rate);
+ albumtag.SetNoOfChannels(st->codecpar->ch_layout.nb_channels);
+ codec_name = avcodec_get_name(st->codecpar->codec_id);
+ int par_profile = st->codecpar->profile;
+ if (st->codecpar->codec_id == AV_CODEC_ID_DTS)
+ {
+ switch (par_profile)
+ {
+ case AV_PROFILE_DTS_HD_MA_X:
+ codec_name = "dtshd_ma_x";
+ break;
+ case AV_PROFILE_DTS_HD_MA_X_IMAX:
+ codec_name = "dtshd_ma_x_imax";
+ break;
+ case AV_PROFILE_DTS_ES:
+ codec_name = "dts_es";
+ break;
+ case AV_PROFILE_DTS_96_24:
+ codec_name = "dts_96_24";
+ break;
+ case AV_PROFILE_DTS_HD_HRA:
+ codec_name = "dtshd_hra";
+ break;
+ case AV_PROFILE_DTS_EXPRESS:
+ codec_name = "dts_express";
+ break;
+ case AV_PROFILE_DTS_HD_MA:
+ codec_name = "dtshd_ma";
+ break;
+ default:
+ codec_name = "dca";
+ break;
+ }
}
+ if (st->codecpar->codec_id == AV_CODEC_ID_EAC3 && par_profile == AV_PROFILE_EAC3_DDP_ATMOS)
+ codec_name = "eac3_ddp_atmos";
+
+ if (st->codecpar->codec_id == AV_CODEC_ID_TRUEHD && par_profile == AV_PROFILE_TRUEHD_ATMOS)
+ codec_name = "truehd_atmos";
}
- const size_t ns = nero.size();
+ albumtag.SetCodec(codec_name);
- for (size_t i=0;inb_chapters;++i)
- {
- tag=nullptr;
- std::string chaptitle = StringUtils::Format(
- CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(25010), i + 1);
- std::string chapauthor;
- std::string chapalbum;
+ std::string thumb;
+
+ ChplChapterResult neroChapterResult{chplNone};
+ std::vector nero;
- std::shared_ptr item(new CFileItem(url.Get(), false));
- *item->GetMusicInfoTag() = albumtag;
+ if (m_fctx->nb_chapters > 1)
+ {
- auto addRole = [&](const std::string& role, const std::string& value)
+ if (isAudioBook)
{
- if (!value.empty())
- item->GetMusicInfoTag()->AddArtistRole(role, StringUtils::Split(value, separators));
- };
+ neroChapterResult = CChplChapterReader::ScanNeroChapters(url, nero);
+ if (neroChapterResult.IsError())
+ {
+ CLog::Log(LOGERROR,
+ "AudioBookFileDirectory: Error scanning for Nero style chapters in file {}. The "
+ "error returned was {}",
+ url.GetRedacted(), *neroChapterResult.errorMessage);
+ }
+ else if (neroChapterResult.IsNone())
+ { // can't get here without some form of chapter so must be QT style chapters (chap atom)
+ CLog::Log(
+ LOGDEBUG,
+ "AudioBookFileDirectory: Scanned for nero style chapters but didn't find any in {}, "
+ "using QT chapters",
+ url.GetRedacted());
+ }
+ }
+ const size_t ns = nero.size();
- while ((tag=av_dict_get(m_fctx->chapters[i]->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
+ float chapter_size = 0;
+
+ bool chapter_error = false;
+
+ thumb = IMAGE_FILES::URLFromFile(url.Get(), "music");
+ // Look for any embedded cover art
+ CMusicEmbeddedCoverLoaderFFmpeg::GetEmbeddedCover(m_fctx, albumtag);
+
+ for (size_t i = 0; i < m_fctx->nb_chapters; ++i)
{
- if (isAudioBook)
+ if (m_fctx->chapters[i]->start < 0) // negative start time, ignore it
+ continue;
+ chapter_size = m_fctx->chapters[i]->end * av_q2d(m_fctx->chapters[i]->time_base);
+ if (chapter_size < 1 &&
+ chapter_size > 0) // Chapter must have positive time of more than 1 sec
{
- if (StringUtils::CompareNoCase(tag->key, "title") == 0)
- chaptitle = tag->value;
- else if (StringUtils::CompareNoCase(tag->key, "artist") == 0)
- chapauthor = tag->value;
- else if (StringUtils::CompareNoCase(tag->key, "album") == 0)
- chapalbum = tag->value;
- // Prefer nero titles if we have them over QT titles and they are different
- if (neroChapterResult.IsFound() && (i < ns) && (nero[i].title != chaptitle))
- chaptitle = nero[i].title;
+ CLog::Log(
+ LOGWARNING,
+ "CAudioBookFileDirectory: Tiny chapter of size {}s detected when scanning {} Most "
+ "likely this file needs the chapters correcting",
+ chapter_size, url.GetRedacted());
+ chapter_error = true;
+ continue;
}
- else
+ tag = nullptr;
+ std::string chaptitle = StringUtils::Format(
+ CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(25010), i + 1);
+ std::string chapauthor;
+ std::string chapalbum;
+
+ std::shared_ptr item(new CFileItem(url.Get(), false));
+ *item->GetMusicInfoTag() = albumtag;
+
+ auto addRole = [&](const std::string& role, const std::string& value)
{
- std::string key = StringUtils::ToUpper(tag->key);
- if (key == "TITLE")
- item->GetMusicInfoTag()->SetTitle(tag->value);
- else if (key == "ARTIST")
- item->GetMusicInfoTag()->SetArtist(tag->value);
- else if (key == "MUSICBRAINZ_TRACKID")
- item->GetMusicInfoTag()->SetMusicBrainzTrackID(tag->value);
- else if (key == "COMPOSER")
- addRole("Composer", tag->value);
- else if (key == "LYRICIST")
- addRole("Lyricist", tag->value);
- else if (key == "CONDUCTOR")
- addRole("Conductor", tag->value);
- else if (key == "WRITER")
- addRole("Writer", tag->value);
- else if (key == "ARRANGER")
- addRole("Arranger", tag->value);
- else if (key == "BAND")
- addRole("Band", tag->value);
- else if (key == "ENGINEER")
- addRole("Engineer", tag->value);
- else if (key == "PRODUCER")
- addRole("Producer", tag->value);
- else if (key == "REMIXED_BY")
- addRole("Remixer", tag->value);
- else if (key == "YEAR" || key == "DATE_RECORDED")
- item->GetMusicInfoTag()->SetReleaseDate(tag->value);
- else if (key == "ORIGYEAR")
- item->GetMusicInfoTag()->SetOriginalDate(tag->value);
- else if (key == "SUBTITLE" || key == "SETSUBTITLE")
- item->GetMusicInfoTag()->SetDiscSubtitle(tag->value);
- else if (key == "COMMENT")
- item->GetMusicInfoTag()->SetComment(tag->value);
- else if (key == "MOOD")
- item->GetMusicInfoTag()->SetMood(tag->value);
- else if (key == "GENRE")
- {
- separators.emplace_back(",");
- item->GetMusicInfoTag()->SetGenre(StringUtils::Split(tag->value, separators), true);
- separators.pop_back();
- }
- // comma separated list of instrument, person
- else if (key == "INSTRUMENTS")
+ if (!value.empty())
+ item->GetMusicInfoTag()->AddArtistRole(role, StringUtils::Split(value, separators));
+ };
+
+ while ((tag = av_dict_get(m_fctx->chapters[i]->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
+ {
+ if (isAudioBook)
{
- tagdata = StringUtils::Split(tag->value, ",");
- AddCommaDelimitedString(tagdata, separators, *item->GetMusicInfoTag());
+ if (StringUtils::CompareNoCase(tag->key, "title") == 0)
+ chaptitle = tag->value;
+ else if (StringUtils::CompareNoCase(tag->key, "artist") == 0)
+ chapauthor = tag->value;
+ else if (StringUtils::CompareNoCase(tag->key, "album") == 0)
+ chapalbum = tag->value;
+ // Prefer nero titles if we have them over QT titles and they are different
+ if (neroChapterResult.IsFound() && (i < ns) && (nero[i].title != chaptitle))
+ chaptitle = nero[i].title;
}
- // comma separated list of role, person
- else if (key == "INVOLVEDPEOPLE")
+ else
{
- tagdata = StringUtils::Split(tag->value, ",");
- AddCommaDelimitedString(tagdata, separators, *item->GetMusicInfoTag());
+ std::string key = StringUtils::ToUpper(tag->key);
+ if (key == "TITLE")
+ item->GetMusicInfoTag()->SetTitle(tag->value);
+ else if (key == "ARTIST")
+ item->GetMusicInfoTag()->SetArtist(tag->value);
+ else if (key == "MUSICBRAINZ_TRACKID")
+ item->GetMusicInfoTag()->SetMusicBrainzTrackID(tag->value);
+ else if (key == "ARTISTSORT" || key == "ARTIST SORT")
+ item->GetMusicInfoTag()->SetArtistSort(
+ StringUtils::Join(StringUtils::Split(tag->value, separators), musicsep));
+ else if (key == "ALBUMARTIST" || key == "ALBUM ARTIST")
+ item->GetMusicInfoTag()->SetAlbumArtist(
+ StringUtils::Join(StringUtils::Split(tag->value, separators), musicsep));
+ else if (key == "ALBUMARTSTS" || key == "ALBUM ARTISTS")
+ item->GetMusicInfoTag()->SetAlbumArtist(StringUtils::Split(tag->value, separators));
+ else if (key == "ALBUMARTISTSORT" || key == "ALBUM ARTIST SORT")
+ item->GetMusicInfoTag()->SetAlbumArtistSort(
+ StringUtils::Join(StringUtils::Split(tag->value, separators), musicsep));
+ else if (key == "COMPOSERSORT" || key == "COMPOSER SORT")
+ item->GetMusicInfoTag()->SetComposerSort(
+ StringUtils::Join(StringUtils::Split(tag->value, separators), musicsep));
+ else if (key == "MUSICBRAINZ_ARTISTID")
+ item->GetMusicInfoTag()->SetMusicBrainzArtistID(
+ StringUtils::Split(tag->value, separators));
+ else if (key == "MUSICBRAINZ_ALBUMARTISTID")
+ item->GetMusicInfoTag()->SetMusicBrainzAlbumArtistID(
+ StringUtils::Split(tag->value, separators));
+ else if (key == "MUSICBRAINZ_ALBUMARTIST")
+ item->GetMusicInfoTag()->SetAlbumArtist(tag->value);
+ else if (key == "MUSICBRAINZ_ALBUMID")
+ item->GetMusicInfoTag()->SetMusicBrainzAlbumID(tag->value);
+ else if (key == "MUSICBRAINZ_RELEASEGROUPID")
+ item->GetMusicInfoTag()->SetMusicBrainzReleaseGroupID(tag->value);
+ else if (key == "MUSICBRAINZ_ALBUMSTATUS")
+ item->GetMusicInfoTag()->SetAlbumReleaseStatus(tag->value);
+ else if (key == "MUSICBRAINZ_ALBUMTYPE")
+ item->GetMusicInfoTag()->SetMusicBrainzReleaseType(tag->value);
+ else if (key == "PUBLISHER")
+ item->GetMusicInfoTag()->SetRecordLabel(tag->value);
+ // mp3tag info shows year but the value is stored in date_recorded
+ // equates to TDRC in id3v2.4 ISO 8601 yyyy-mm-dd or part thereof
+ else if (key == "YEAR" || key == "DATE_RELEASED") // proper matroska tag is date_released
+ item->GetMusicInfoTag()->SetReleaseDate(tag->value);
+ // ISO 8601 as above. Equates to TDOR in id3v2.4 (set by mp3tag)
+ else if (key == "ORIGYEAR" || key == "DATE_RECORDED")
+ item->GetMusicInfoTag()->SetOriginalDate(tag->value);
+ else if (key == "COMPOSER")
+ addRole("Composer", tag->value);
+ else if (key == "LYRICIST")
+ addRole("Lyricist", tag->value);
+ else if (key == "CONDUCTOR")
+ addRole("Conductor", tag->value);
+ else if (key == "WRITER")
+ addRole("Writer", tag->value);
+ else if (key == "ARRANGER")
+ addRole("Arranger", tag->value);
+ else if (key == "BAND")
+ addRole("Band", tag->value);
+ else if (key == "ENGINEER")
+ addRole("Engineer", tag->value);
+ else if (key == "PRODUCER")
+ addRole("Producer", tag->value);
+ else if (key == "REMIXED_BY")
+ addRole("Remixer", tag->value);
+ else if (key == "YEAR" || key == "DATE_RECORDED")
+ item->GetMusicInfoTag()->SetReleaseDate(tag->value);
+ else if (key == "ORIGYEAR")
+ item->GetMusicInfoTag()->SetOriginalDate(tag->value);
+ else if (key == "MIXED_BY" || key == "MIXER")
+ addRole("Mixer", tag->value);
+ else if (key == "SUBTITLE" || key == "SETSUBTITLE")
+ item->GetMusicInfoTag()->SetDiscSubtitle(tag->value);
+ else if (key == "COMMENT")
+ item->GetMusicInfoTag()->SetComment(tag->value);
+ else if (key == "MOOD")
+ item->GetMusicInfoTag()->SetMood(tag->value);
+ else if (key == "COMPILATION")
+ item->GetMusicInfoTag()->SetCompilation(true);
+ else if (key == "GENRE")
+ {
+ separators.emplace_back(",");
+ item->GetMusicInfoTag()->SetGenre(StringUtils::Split(tag->value, separators), true);
+ separators.pop_back();
+ }
+ // comma separated list of instrument, person
+ else if (key == "INSTRUMENTS")
+ {
+ tagdata = StringUtils::Split(tag->value, ",");
+ AddCommaDelimitedString(tagdata, separators, *item->GetMusicInfoTag());
+ }
+ /* comma separated list of role, person
+ The key value depends on tagging software but between 'INSTRUMENTS', 'INVOLVEDPEOPLE' and
+ 'ACTOR', everything should be covered. For instance https://github.com/Martchus/tageditor
+ (window & linux) shows 'performers' in the gui but names the key 'ACTOR' in the file.
+ mp3tag uses both 'instruments' & 'involvedpeople'
+ https://www.poikosoft.com/metadata-editor (windows only) can create freeform tags as can
+ https://www.videohelp.com/software/chapterEditor (Win & Linux) although it also shows the
+ correct matroska spec tags
+ */
+ else if (key == "INVOLVEDPEOPLE" || key == "ACTOR")
+ {
+ tagdata = StringUtils::Split(tag->value, ",");
+ AddCommaDelimitedString(tagdata, separators, *item->GetMusicInfoTag());
+ }
}
- }
- /* The comma separated lists are outside the Matroska spec
+ /* The comma separated lists are outside the Matroska spec
(see https://www.matroska.org/technical/tagging.html) as it states to use multiple simple
tags for eg 2 or more composers. However, ffmpeg returns just the last tag and drops the
rest (https://trac.ffmpeg.org/ticket/9641). Therefore until (if) it gets fixed, this is
the best solution.
*/
- }
- if (isAudioBook)
- {
- item->GetMusicInfoTag()->SetTitle(chaptitle);
- item->GetMusicInfoTag()->SetAlbum(chapalbum.empty() ? album.empty() ? title : album
- : chapalbum);
- item->GetMusicInfoTag()->SetArtist(chapauthor.empty() ? author : chapauthor);
- if (!desc.empty())
- item->GetMusicInfoTag()->SetComment(desc);
- }
- item->GetMusicInfoTag()->SetTrackNumber(i+1);
- item->GetMusicInfoTag()->SetLoaded(true);
+ }
+ if (isAudioBook)
+ {
+ item->GetMusicInfoTag()->SetTitle(chaptitle);
+ item->GetMusicInfoTag()->SetAlbum(chapalbum.empty() ? album.empty() ? title : album
+ : chapalbum);
+ item->GetMusicInfoTag()->SetArtist(chapauthor.empty() ? author : chapauthor);
+ if (!desc.empty())
+ item->GetMusicInfoTag()->SetComment(desc);
+ }
+ item->GetMusicInfoTag()->SetTrackNumber(i + 1);
+ item->GetMusicInfoTag()->SetLoaded(true);
- item->SetLabel(StringUtils::Format("{0:02}. {1} - {2}", i + 1,
- item->GetMusicInfoTag()->GetAlbum(),
- item->GetMusicInfoTag()->GetTitle()));
- item->SetStartOffset(CUtil::ConvertSecsToMilliSecs(m_fctx->chapters[i]->start *
+ item->SetLabel(StringUtils::Format("{0:02}. {1} - {2}", i + 1,
+ item->GetMusicInfoTag()->GetAlbum(),
+ item->GetMusicInfoTag()->GetTitle()));
+ item->SetStartOffset(CUtil::ConvertSecsToMilliSecs(m_fctx->chapters[i]->start *
+ av_q2d(m_fctx->chapters[i]->time_base)));
+ item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(m_fctx->chapters[i]->end *
av_q2d(m_fctx->chapters[i]->time_base)));
- item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(m_fctx->chapters[i]->end *
- av_q2d(m_fctx->chapters[i]->time_base)));
- if (item->GetEndOffset() < 0 ||
- item->GetEndOffset() > CUtil::ConvertMilliSecsToSecs(m_fctx->duration))
- {
- if (i < m_fctx->nb_chapters - 1)
- item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(
- m_fctx->chapters[i + 1]->start * av_q2d(m_fctx->chapters[i + 1]->time_base)));
- else
+ if (item->GetEndOffset() < 0 ||
+ item->GetEndOffset() > CUtil::ConvertMilliSecsToSecs(m_fctx->duration))
{
- item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(end_time_mka_file)); // mka file
- if (item->GetEndOffset() < 0)
- item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(end_time_m4b_file)); // m4b file
+ if (i < m_fctx->nb_chapters - 1)
+ item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(
+ m_fctx->chapters[i + 1]->start * av_q2d(m_fctx->chapters[i + 1]->time_base)));
+ else
+ {
+ item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(end_time_mka_file)); // mka file
+ if (item->GetEndOffset() < 0)
+ item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(end_time_m4b_file)); // m4b file
+ }
}
+ item->GetMusicInfoTag()->SetDuration(
+ CUtil::ConvertMilliSecsToSecsInt(item->GetEndOffset() - item->GetStartOffset()));
+ item->SetProperty("item_start", item->GetStartOffset());
+ item->SetProperty("audio_bookmark", item->GetStartOffset());
+ if (!thumb.empty() && !chapter_error)
+ item->SetArt("thumb", thumb);
+ items.Add(item);
}
- item->GetMusicInfoTag()->SetDuration(
- CUtil::ConvertMilliSecsToSecsInt(item->GetEndOffset() - item->GetStartOffset()));
- item->SetProperty("item_start", item->GetStartOffset());
- item->SetProperty("audio_bookmark", item->GetStartOffset());
- if (!thumb.empty())
- item->SetArt("thumb", thumb);
- items.Add(item);
}
return true;
@@ -367,6 +546,7 @@ bool CAudioBookFileDirectory::ContainsFiles(const CURL& url)
m_fctx = avformat_alloc_context();
m_fctx->pb = m_ioctx;
+ m_fctx->flags |= AVFMT_FLAG_CUSTOM_IO;
if (file.IoControl(IOControl::SEEK_POSSIBLE, nullptr) == 0)
m_ioctx->seekable = 0;
@@ -377,6 +557,7 @@ bool CAudioBookFileDirectory::ContainsFiles(const CURL& url)
av_probe_input_buffer(m_ioctx, &iformat, url.Get().c_str(), nullptr, 0, 0);
bool contains = false;
+
if (avformat_open_input(&m_fctx, url.Get().c_str(), iformat, nullptr) < 0)
{
if (m_fctx)
@@ -385,6 +566,10 @@ bool CAudioBookFileDirectory::ContainsFiles(const CURL& url)
av_free(m_ioctx);
return false;
}
+ m_fctx->flags |= AVFMT_FLAG_NOPARSE;
+ int err = avformat_find_stream_info(m_fctx, NULL);
+ if (err < 0)
+ CLog::Log(LOGERROR, "Can't detect codec info in file {}", url.GetRedacted());
contains = m_fctx->nb_chapters > 1;
diff --git a/xbmc/filesystem/FileDirectoryFactory.cpp b/xbmc/filesystem/FileDirectoryFactory.cpp
index 5b2101ca25721..67e2799b86c5c 100644
--- a/xbmc/filesystem/FileDirectoryFactory.cpp
+++ b/xbmc/filesystem/FileDirectoryFactory.cpp
@@ -29,6 +29,7 @@
#include "ServiceBroker.h"
#include "SmartPlaylistDirectory.h"
#include "URL.h"
+#include "Util.h"
#include "XbtDirectory.h"
#include "ZipDirectory.h"
#include "addons/AudioDecoder.h"
@@ -37,6 +38,7 @@
#include "addons/addoninfo/AddonInfo.h"
#include "playlists/PlayListFactory.h"
#include "playlists/SmartPlayList.h"
+#include "settings/MediaSourceSettings.h"
#include "utils/StringUtils.h"
#include "utils/log.h"
@@ -248,13 +250,29 @@ IFileDirectory* CFileDirectoryFactory::Create(const CURL& url, CFileItem* pItem,
return NULL;
}
- if (MUSIC::IsAudioBook(*pItem))
+ if (pItem->IsAudioBook() || pItem->IsMatroskaAudio())
{
if (!pItem->HasMusicInfoTag() || pItem->GetEndOffset() <= 0)
{
- auto pDir = std::make_unique();
- if (pDir->ContainsFiles(url))
- return pDir.release();
+ std::unique_ptr pDir(new CAudioBookFileDirectory);
+ if (pDir->ContainsFiles(url))
+ return pDir.release();
+ }
+ return NULL;
+ }
+ else if (pItem->IsMatroskaVideo() || url.IsFileType("mp4"))
+ {
+ std::vector* musicSources = CMediaSourceSettings::GetInstance().GetSources("music");
+ bool isSource;
+ int sourceIndex = CUtil::GetMatchingSource(pItem->GetPath(), *musicSources, isSource);
+ if (sourceIndex >= 0 && sourceIndex < static_cast(musicSources->size()))
+ {
+ if (!pItem->HasMusicInfoTag() || pItem->GetEndOffset() <= 0)
+ {
+ std::unique_ptr pDir(new CAudioBookFileDirectory);
+ if (pDir->ContainsFiles(url))
+ return pDir.release();
+ }
}
return NULL;
}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.cpp
index 647ceb89d55f6..7fcb07e5d43ab 100644
--- a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.cpp
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.cpp
@@ -7,7 +7,6 @@
*/
#include "DirectoryNodeAlbumRecentlyAdded.h"
-
#include "FileItem.h"
#include "FileItemList.h"
#include "ServiceBroker.h"
@@ -15,9 +14,11 @@
#include "music/MusicDatabase.h"
#include "resources/LocalizeStrings.h"
#include "resources/ResourcesComponent.h"
+#include "music/tags/MusicInfoTag.h"
#include "utils/StringUtils.h"
using namespace XFILE::MUSICDATABASEDIRECTORY;
+using namespace MUSIC_INFO;
CDirectoryNodeAlbumRecentlyAdded::CDirectoryNodeAlbumRecentlyAdded(const std::string& strName,
CDirectoryNode* pParent)
@@ -60,7 +61,9 @@ bool CDirectoryNodeAlbumRecentlyAdded::GetContent(CFileItemList& items) const
for (const CAlbum& album : albums)
{
std::string strDir = StringUtils::Format("{}{}/", BuildPath(), album.idAlbum);
+ std::string albumPath = musicdatabase.GetPathForAlbum(album.idAlbum);
CFileItemPtr pItem(new CFileItem(strDir, album));
+ pItem->GetMusicInfoTag()->SetURL(albumPath);
items.Add(pItem);
}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.h
index 8448a261f075a..d432453a154c8 100644
--- a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.h
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.h
@@ -9,7 +9,10 @@
#pragma once
#include "DirectoryNode.h"
-
+namespace MUSIC_INFO
+{
+class CMusicInfoTag;
+}
namespace XFILE
{
namespace MUSICDATABASEDIRECTORY
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayed.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayed.cpp
index f85a7ae7adb24..f78b5c6ec3151 100644
--- a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayed.cpp
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayed.cpp
@@ -15,6 +15,7 @@
#include "music/MusicDatabase.h"
#include "resources/LocalizeStrings.h"
#include "resources/ResourcesComponent.h"
+#include "music/tags/MusicInfoTag.h"
#include "utils/StringUtils.h"
using namespace XFILE::MUSICDATABASEDIRECTORY;
@@ -60,7 +61,9 @@ bool CDirectoryNodeAlbumRecentlyPlayed::GetContent(CFileItemList& items) const
for (const CAlbum& album : albums)
{
std::string strDir = StringUtils::Format("{}{}/", BuildPath(), album.idAlbum);
+ std::string albumPath = musicdatabase.GetPathForAlbum(album.idAlbum);
CFileItemPtr pItem(new CFileItem(strDir, album));
+ pItem->GetMusicInfoTag()->SetURL(albumPath);
items.Add(pItem);
}
diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h
index 385cf694c2331..e1cc339857f71 100644
--- a/xbmc/guilib/guiinfo/GUIInfoLabels.h
+++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h
@@ -81,7 +81,9 @@ constexpr uint32_t PLAYER_EDITLIST = 69;
constexpr uint32_t PLAYER_CUTS = 70;
constexpr uint32_t PLAYER_SCENE_MARKERS = 71;
constexpr uint32_t PLAYER_HAS_SCENE_MARKERS = 72;
-// unused id 73 to 80
+constexpr uint32_t PLAYER_CHAPTER_ELAPSED = 73;
+constexpr uint32_t PLAYER_CHAPTERLENGTH = 74;
+// unused id 75 to 80
// Keep player infolabels that work with offset and position together
constexpr uint32_t PLAYER_PATH = 81;
@@ -300,6 +302,8 @@ constexpr uint32_t VIDEOPLAYER_AUDIOSTREAMCOUNT = 295;
constexpr uint32_t VIDEOPLAYER_VIDEOVERSION_NAME = 296;
constexpr uint32_t VIDEOPLAYER_VIDEOSTREAMCOUNT = 297;
constexpr uint32_t VIDEOPLAYER_HDR_DETAIL = 298;
+constexpr uint32_t VIDEOPLAYER_CHAPTERLENGTH = 299;
+
// Videoplayer infobools
constexpr uint32_t VIDEOPLAYER_HASSUBTITLES = 300;
@@ -314,6 +318,7 @@ constexpr uint32_t VIDEOPLAYER_HAS_INFO = 308;
constexpr uint32_t VIDEOPLAYER_HASTELETEXT = 309;
constexpr uint32_t VIDEOPLAYER_IS_STEREOSCOPIC = 310;
constexpr uint32_t VIDEOPLAYER_HAS_VIDEOVERSIONS = 311;
+constexpr uint32_t VIDEOPLAYER_IS_MUSIC_VIDEO = 312;
// PVR infolabels
constexpr uint32_t VIDEOPLAYER_TITLE_EXTRAINFO = 312;
@@ -504,7 +509,9 @@ constexpr uint32_t LIBRARY_HAS_COMPILATIONS = 727;
constexpr uint32_t LIBRARY_IS_SCANNING = 728;
constexpr uint32_t LIBRARY_IS_SCANNING_VIDEO = 729;
constexpr uint32_t LIBRARY_IS_SCANNING_MUSIC = 730;
-// unused id 731 to 734
+constexpr uint32_t LIBRARY_HAS_MUSIC_CONCERTS = 731;
+constexpr uint32_t LIBRARY_HAS_AUDIOBOOKS = 732;
+// unused id 733 to 734
constexpr uint32_t LIBRARY_HAS_ROLE = 735;
constexpr uint32_t LIBRARY_HAS_BOXSETS = 736;
constexpr uint32_t LIBRARY_HAS_NODE = 737;
@@ -1035,6 +1042,8 @@ constexpr uint32_t LISTITEM_MEDIAPROVIDERS = LISTITEM_START + 224;
constexpr uint32_t LISTITEM_TITLE_EXTRAINFO = LISTITEM_START + 225;
constexpr uint32_t LISTITEM_DECODED_FILENAME_AND_PATH = LISTITEM_START + 226;
constexpr uint32_t LISTITEM_VIDEO_HDR_DETAIL = LISTITEM_START + 227;
+constexpr uint32_t LISTITEM_MUSIC_BITSPERSAMPLE = LISTITEM_START + 228;
+constexpr uint32_t LISTITEM_MUSIC_CODEC = LISTITEM_START + 229;
constexpr int LISTITEM_END = LISTITEM_START + 2500;
diff --git a/xbmc/guilib/guiinfo/LibraryGUIInfo.cpp b/xbmc/guilib/guiinfo/LibraryGUIInfo.cpp
index 9b105d7231dce..8aa3069ba5b86 100644
--- a/xbmc/guilib/guiinfo/LibraryGUIInfo.cpp
+++ b/xbmc/guilib/guiinfo/LibraryGUIInfo.cpp
@@ -67,6 +67,12 @@ void CLibraryGUIInfo::SetLibraryBool(int condition, bool value)
case LIBRARY_HAS_BOXSETS:
m_libraryHasBoxsets = value ? 1 : 0;
break;
+ case LIBRARY_HAS_MUSIC_CONCERTS:
+ m_libraryHasMusicConcerts = value ? 1 : 0;
+ break;
+ case LIBRARY_HAS_AUDIOBOOKS:
+ m_libraryHasAudiobooks = value ? 1 : 0;
+ break;
default:
break;
}
@@ -82,6 +88,8 @@ void CLibraryGUIInfo::ResetLibraryBools()
m_libraryHasSingles = -1;
m_libraryHasCompilations = -1;
m_libraryHasBoxsets = -1;
+ m_libraryHasMusicConcerts = -1;
+ m_libraryHasAudiobooks = -1;
m_libraryRoleCounts.clear();
}
@@ -229,6 +237,34 @@ bool CLibraryGUIInfo::GetBool(bool& value,
value = m_libraryHasBoxsets > 0;
return true;
}
+ case LIBRARY_HAS_MUSIC_CONCERTS:
+ {
+ if (m_libraryHasMusicConcerts < 0)
+ {
+ CMusicDatabase db;
+ if (db.Open())
+ {
+ m_libraryHasMusicConcerts = (db.GetConcertsCount() > 0) ? 1 : 0;
+ db.Close();
+ }
+ }
+ value = m_libraryHasMusicConcerts > 0;
+ return true;
+ }
+ case LIBRARY_HAS_AUDIOBOOKS:
+ {
+ if (m_libraryHasAudiobooks < 0)
+ {
+ CMusicDatabase db;
+ if (db.Open())
+ {
+ m_libraryHasAudiobooks = (db.GetAudioBookCount() > 0) ? 1 : 0;
+ db.Close();
+ }
+ }
+ value = m_libraryHasAudiobooks > 0;
+ return true;
+ }
case LIBRARY_HAS_VIDEO:
{
return (GetBool(value, gitem, contextWindow, CGUIInfo(LIBRARY_HAS_MOVIES)) ||
diff --git a/xbmc/guilib/guiinfo/LibraryGUIInfo.h b/xbmc/guilib/guiinfo/LibraryGUIInfo.h
index 41a99e6917420..286836346ba90 100644
--- a/xbmc/guilib/guiinfo/LibraryGUIInfo.h
+++ b/xbmc/guilib/guiinfo/LibraryGUIInfo.h
@@ -54,6 +54,8 @@ class CLibraryGUIInfo : public CGUIInfoProvider
mutable int m_libraryHasSingles;
mutable int m_libraryHasCompilations;
mutable int m_libraryHasBoxsets;
+ mutable int m_libraryHasMusicConcerts;
+ mutable int m_libraryHasAudiobooks;
//Count of artists in music library contributing to song by role e.g. composers, conductors etc.
//For checking visibility of custom nodes for a role.
diff --git a/xbmc/guilib/guiinfo/MusicGUIInfo.cpp b/xbmc/guilib/guiinfo/MusicGUIInfo.cpp
index f36dd0630f956..c913c4afad2cf 100644
--- a/xbmc/guilib/guiinfo/MusicGUIInfo.cpp
+++ b/xbmc/guilib/guiinfo/MusicGUIInfo.cpp
@@ -342,7 +342,10 @@ bool CMusicGUIInfo::GetLabel(std::string& value,
int BitRate = tag->GetBitRate();
if (BitRate > 0)
{
- value = std::to_string(BitRate);
+ if (BitRate > 100000)
+ value = StringUtils::Format("{:.0f}", static_cast(BitRate) / 1000.0);
+ else
+ value = std::to_string(BitRate);
return true;
}
break;
@@ -369,6 +372,21 @@ bool CMusicGUIInfo::GetLabel(std::string& value,
}
break;
}
+ case LISTITEM_MUSIC_BITSPERSAMPLE:
+ {
+ int bitsPerSample = tag->GetBitsPerSample();
+ if (bitsPerSample > 0)
+ {
+ value = std::to_string(bitsPerSample);
+ return true;
+ }
+ break;
+ }
+ case LISTITEM_MUSIC_CODEC:
+ case MUSICPLAYER_CODEC:
+ value = tag->GetCodec();
+ return true;
+
case LISTITEM_ALBUMSTATUS:
value = tag->GetAlbumReleaseStatus();
return true;
@@ -522,9 +540,6 @@ bool CMusicGUIInfo::GetLabel(std::string& value,
}
break;
}
- case MUSICPLAYER_CODEC:
- value = m_audioInfo.codecName;
- return true;
default:
break;
}
diff --git a/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp b/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp
index 10542ee0f0593..06116b10f18d9 100644
--- a/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp
+++ b/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp
@@ -159,11 +159,28 @@ bool CPlayerGUIInfo::InitCurrentItem(CFileItem* item)
return false;
}
-bool CPlayerGUIInfo::GetLabel(std::string& value,
- const CFileItem* item,
- int contextWindow,
- const CGUIInfo& info,
- std::string* fallback) const
+int CPlayerGUIInfo::GetChapterLength(int chapterIdx) const
+{
+ int chapStart = m_appPlayer->GetChapterPos(chapterIdx);
+ int chapEnd;
+ if (chapterIdx >= m_appPlayer->GetChapterCount())
+ chapEnd = GetTotalPlayTime();
+ else
+ chapEnd = m_appPlayer->GetChapterPos(chapterIdx + 1);
+ return chapEnd - chapStart;
+}
+
+int CPlayerGUIInfo::GetChapterElapsedTime(int chapterIdx) const
+{
+ int now = GetPlayTime();
+ int chapStart = m_appPlayer->GetChapterPos(chapterIdx);
+ int elapsed = now -chapStart;
+ if (elapsed < 0)
+ elapsed = 0;
+ return elapsed;
+}
+
+bool CPlayerGUIInfo::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
{
switch (info.GetInfo())
{
@@ -206,6 +223,12 @@ bool CPlayerGUIInfo::GetLabel(std::string& value,
case PLAYER_CHAPTERNAME:
m_appPlayer->GetChapterName(value);
return true;
+ case PLAYER_CHAPTERLENGTH:
+ value = StringUtils::SecondsToTimeString(GetChapterLength(m_appPlayer->GetChapter()));
+ return true;
+ case PLAYER_CHAPTER_ELAPSED:
+ value = StringUtils::SecondsToTimeString(GetChapterElapsedTime(m_appPlayer->GetChapter()));
+ return true;
case PLAYER_PATH:
case PLAYER_FILENAME:
case PLAYER_FILEPATH:
diff --git a/xbmc/guilib/guiinfo/PlayerGUIInfo.h b/xbmc/guilib/guiinfo/PlayerGUIInfo.h
index f843f03a9fd85..58c4c028e5d25 100644
--- a/xbmc/guilib/guiinfo/PlayerGUIInfo.h
+++ b/xbmc/guilib/guiinfo/PlayerGUIInfo.h
@@ -75,6 +75,8 @@ class CPlayerGUIInfo : public CGUIInfoProvider
std::string GetDuration(TIME_FORMAT format) const;
std::string GetCurrentSeekTime(TIME_FORMAT format) const;
std::string GetSeekTime(TIME_FORMAT format) const;
+ int GetChapterLength(int chap) const;
+ int GetChapterElapsedTime(int chap) const;
std::string GetContentRanges(int iInfo) const;
std::vector> GetEditList(const CDataCacheCore& data,
diff --git a/xbmc/guilib/guiinfo/VideoGUIInfo.cpp b/xbmc/guilib/guiinfo/VideoGUIInfo.cpp
index ab4583ff99e8e..543cb7fde2768 100644
--- a/xbmc/guilib/guiinfo/VideoGUIInfo.cpp
+++ b/xbmc/guilib/guiinfo/VideoGUIInfo.cpp
@@ -697,6 +697,19 @@ bool CVideoGUIInfo::GetLabel(std::string& value,
}
break;
}
+ case VIDEOPLAYER_CHAPTERLENGTH:
+ {
+ int chap = m_appPlayer->GetChapter();
+ int chapStart = m_appPlayer->GetChapterPos(chap);
+ int chapEnd;
+ if (chap >= m_appPlayer->GetChapterCount())
+ chapEnd = std::lrint(g_application.GetTotalTime());
+ else
+ chapEnd = m_appPlayer->GetChapterPos(chap + 1);
+ value = StringUtils::Format("{:02}", chapEnd - chapStart);
+ return true;
+ break;
+ }
case VIDEOPLAYER_VIDEO_BITRATE:
{
int iBitrate = m_videoInfo.bitrate;
@@ -925,6 +938,9 @@ bool CVideoGUIInfo::GetBool(bool& value,
case VIDEOPLAYER_IS_STEREOSCOPIC:
value = !CServiceBroker::GetDataCacheCore().GetVideoStereoMode().empty();
return true;
+ case VIDEOPLAYER_IS_MUSIC_VIDEO:
+ value = item->HasMusicInfoTag();
+ return true;
///////////////////////////////////////////////////////////////////////////////////////////////
// LISTITEM_*
diff --git a/xbmc/music/Album.cpp b/xbmc/music/Album.cpp
index 67e0ff6a28e99..8b15da03a1f2a 100644
--- a/xbmc/music/Album.cpp
+++ b/xbmc/music/Album.cpp
@@ -23,14 +23,6 @@
using namespace MUSIC_INFO;
-struct ReleaseTypeInfo
-{
- ReleaseType type;
- std::string name;
-};
-
-ReleaseTypeInfo releaseTypes[] = {{ReleaseType::Album, "album"}, {ReleaseType::Single, "single"}};
-
CAlbum::CAlbum(const CFileItem& item)
{
Reset();
@@ -407,12 +399,14 @@ std::vector CAlbum::GetArtistIDArray() const
std::string CAlbum::GetReleaseType() const
{
- return ReleaseTypeToString(releaseType);
+ return releaseType.ToString();
+ //return ReleaseTypeToString(releaseType);
}
void CAlbum::SetReleaseType(const std::string& strReleaseType)
{
- releaseType = ReleaseTypeFromString(strReleaseType);
+ releaseType.FromString(strReleaseType);
+ //releaseType = ReleaseTypeFromString(strReleaseType);
}
void CAlbum::SetDateAdded(const std::string& strDateAdded)
@@ -435,26 +429,14 @@ void CAlbum::SetLastPlayed(const std::string& strLastPlayed)
lastPlayed.SetFromDBDateTime(strLastPlayed);
}
-std::string CAlbum::ReleaseTypeToString(ReleaseType releaseType)
+std::string CAlbum::ReleaseTypeToString(AudioType releaseType)
{
- for (const ReleaseTypeInfo& releaseTypeInfo : releaseTypes)
- {
- if (releaseTypeInfo.type == releaseType)
- return releaseTypeInfo.name;
- }
-
- return "album";
+ return releaseType.ToString();
}
-ReleaseType CAlbum::ReleaseTypeFromString(const std::string& strReleaseType)
+AudioType CAlbum::ReleaseTypeFromString(const std::string& strReleaseType)
{
- for (const ReleaseTypeInfo& releaseTypeInfo : releaseTypes)
- {
- if (releaseTypeInfo.name == strReleaseType)
- return releaseTypeInfo.type;
- }
-
- return ReleaseType::Album;
+ return AudioType::FromString(strReleaseType).value_or(AudioType::Content::Album);
}
bool CAlbum::operator<(const CAlbum &a) const
@@ -597,7 +579,7 @@ bool CAlbum::Load(const TiXmlElement *album, bool append, bool prioritise)
if (XMLUtils::GetString(album, "releasetype", strReleaseType))
SetReleaseType(strReleaseType);
else
- releaseType = ReleaseType::Album;
+ releaseType = AudioType::Content::Album;
return true;
}
diff --git a/xbmc/music/Album.h b/xbmc/music/Album.h
index 6c9288c815bac..57e6f6df4e05e 100644
--- a/xbmc/music/Album.h
+++ b/xbmc/music/Album.h
@@ -14,6 +14,7 @@
*/
#include "Artist.h"
+#include "MusicType.h"
#include "Song.h"
#include "XBDateTime.h"
#include "utils/Artwork.h"
@@ -25,12 +26,6 @@ class TiXmlElement;
class TiXmlNode;
class CFileItem;
-enum class ReleaseType
-{
- Album = 0,
- Single
-};
-
class CAlbum
{
public:
@@ -73,7 +68,8 @@ class CAlbum
lastPlayed.Reset();
iTotalDiscs = -1;
songs.clear();
- releaseType = ReleaseType::Album;
+ releaseType = AudioType::Content::Album;
+ //releaseType = ReleaseType::Album;
strLastScraped.clear();
bScrapedMBID = false;
bArtistSongMerge = false;
@@ -115,8 +111,8 @@ class CAlbum
void SetDateNew(const std::string& strDateNew);
void SetLastPlayed(const std::string& strLastPlayed);
- static std::string ReleaseTypeToString(ReleaseType releaseType);
- static ReleaseType ReleaseTypeFromString(const std::string& strReleaseType);
+ static std::string ReleaseTypeToString(AudioType releaseType);
+ static AudioType ReleaseTypeFromString(const std::string& strReleaseType);
/*! \brief Set album artist credits using the arrays of tag values.
If strArtistSort (as from ALBUMARTISTSORT tag) is already set then individual
@@ -176,9 +172,14 @@ class CAlbum
CDateTime lastPlayed;
int iTotalDiscs = -1;
std::vector songs; ///< Local songs
- ReleaseType releaseType = ReleaseType::Album;
+ AudioType releaseType = AudioType::Content::Album;
std::string strLastScraped;
bool bScrapedMBID = false;
bool bArtistSongMerge = false;
+ std::string strCodec;
+ int iChannels = 0;
+ int iBitrate = 0;
+ int iSampleRate = 0;
+ int iBitsPerSample = 0;
int iAlbumDuration = 0;
};
diff --git a/xbmc/music/CMakeLists.txt b/xbmc/music/CMakeLists.txt
index f0575d2e8eb9c..a5ae24b711266 100644
--- a/xbmc/music/CMakeLists.txt
+++ b/xbmc/music/CMakeLists.txt
@@ -4,6 +4,7 @@ set(SOURCES Album.cpp
GUIViewStateMusic.cpp
MusicDatabase.cpp
MusicDbUrl.cpp
+ MusicEmbeddedCoverLoaderFFmpeg.cpp
MusicEmbeddedImageFileLoader.cpp
MusicFileItemClassify.cpp
MusicInfoLoader.cpp
@@ -18,11 +19,13 @@ set(HEADERS Album.h
GUIViewStateMusic.h
MusicDatabase.h
MusicDbUrl.h
+ MusicEmbeddedCoverLoaderFFmpeg.h
MusicEmbeddedImageFileLoader.h
MusicFileItemClassify.h
MusicInfoLoader.h
MusicLibraryQueue.h
MusicThumbLoader.h
+ MusicType.h
MusicUtils.h
Song.h)
diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp
index 0525a57174079..85a1573248ad4 100644
--- a/xbmc/music/MusicDatabase.cpp
+++ b/xbmc/music/MusicDatabase.cpp
@@ -214,7 +214,8 @@ void CMusicDatabase::CreateTables()
" userrating INTEGER NOT NULL DEFAULT 0, "
" comment text, mood text, iBPM INTEGER NOT NULL DEFAULT 0, "
" iBitRate INTEGER NOT NULL DEFAULT 0, "
- " iSampleRate INTEGER NOT NULL DEFAULT 0, iChannels INTEGER NOT NULL DEFAULT 0, "
+ " iSampleRate INTEGER NOT NULL DEFAULT 0, iBitsPerSample INTEGER NOT NULL DEFAULT 0, "
+ " strCodec TEXT, iChannels INTEGER NOT NULL DEFAULT 0, "
" strVideoURL TEXT, "
" strReplayGain text, "
" dateAdded TEXT, dateNew TEXT, dateModified TEXT)");
@@ -464,6 +465,8 @@ void CMusicDatabase::CreateViews()
" iBPM, "
" iBitRate, "
" iSampleRate, "
+ " iBitsPerSample, "
+ " strCodec, "
" iChannels, "
" song.strVideoURL as strVideoURL, "
" album.iAlbumDuration AS iAlbumDuration, "
@@ -510,6 +513,16 @@ void CMusicDatabase::CreateViews()
"iDiscTotal, "
"(SELECT MAX(song.lastplayed) FROM song "
"WHERE song.idAlbum = album.idAlbum) AS lastplayed, "
+ "(SELECT song.strCodec FROM song WHERE song.idAlbum = album.idAlbum LIMIT 1)"
+ " AS strCodec, "
+ "(SELECT song.iChannels FROM song WHERE song.idAlbum = album.idAlbum LIMIT 1)"
+ " as iChannels, "
+ "(SELECT ROUND(AVG(song.iBitrate)) FROM song WHERE song.idAlbum = album.idAlbum"
+ " LIMIT 1) AS iBitrate , "
+ "(SELECT song.iSampleRate FROM song WHERE song.idAlbum = album.idAlbum LIMIT 1)"
+ " AS iSampleRate, "
+ "(SELECT song.iBitsPerSample FROM song WHERE song.idAlbum = album.idAlbum LIMIT 1)"
+ " AS iBitsPerSample, "
"iAlbumDuration "
"FROM album");
@@ -810,6 +823,8 @@ bool CMusicDatabase::AddAlbum(CAlbum& album, int idSource)
song->userrating, //
song->votes, //
song->iBPM, song->iBitRate, song->iSampleRate, song->iChannels, //
+ song->iBitsPerSample,
+ song->strCodec,
song->songVideoURL, //
song->replayGain);
@@ -954,6 +969,12 @@ bool CMusicDatabase::UpdateAlbum(CAlbum& album)
}
}
}
+ // Keep releasetype unless it's missing
+ std::string strSQL =
+ PrepareSQL("SELECT strReleaseType FROM album WHERE idAlbum = '%i'", album.idAlbum);
+ std::string currentReleaseType = GetSingleValue(strSQL);
+ album.releaseType = AudioType::FromString(currentReleaseType).value_or(AudioType::Content::Album);
+
UpdateAlbum(album.idAlbum, album.strAlbum, album.strMusicBrainzAlbumID, //
album.strReleaseGroupMBID, //
album.GetAlbumArtistString(), album.GetAlbumArtistSort(), //
@@ -1066,6 +1087,8 @@ int CMusicDatabase::AddSong(const int idSong,
int iBitRate,
int iSampleRate,
int iChannels,
+ int iBitsPerSample,
+ const std::string& strCodec,
const std::string& songVideoURL,
const ReplayGain& replayGain)
{
@@ -1127,7 +1150,7 @@ int CMusicDatabase::AddSong(const int idSong,
"idSong, dateNew, idAlbum, idPath, strArtistDisp, "
"strTitle, iTrack, iDuration, "
"strReleaseDate, strOrigReleaseDate, iBPM, "
- "iBitrate, iSampleRate, iChannels, "
+ "iBitrate, iSampleRate, iChannels, iBitsPerSample, strCodec, "
"strDiscSubtitle, strFileName, dateAdded, "
"strMusicBrainzTrackID, strArtistSort, "
"iTimesPlayed, iStartOffset, iEndOffset, "
@@ -1140,11 +1163,12 @@ int CMusicDatabase::AddSong(const int idSong,
//Reuse song Id and original date when the Id added
strSQL += PrepareSQL("VALUES (%i, '%s', ", idSong, dtDateNew.GetAsDBDateTime().c_str());
- strSQL +=
- PrepareSQL("%i, %i, '%s', '%s', %i, %i, '%s', '%s', %i, %i, %i, %i,'%s', '%s', '%s' ",
- idAlbum, idPath, artistDisp.c_str(), strTitle.c_str(), iTrack, iDuration,
- strRelease.c_str(), strOriginal.c_str(), iBPM, iBitRate, iSampleRate,
- iChannels, strDiscSubtitle.c_str(), strFileName.c_str(), strDateMedia.c_str());
+ strSQL += PrepareSQL(
+ "%i, %i, '%s', '%s', %i, %i, '%s', '%s', %i, %i, %i, %i, %i, '%s', '%s', '%s', '%s' ",
+ idAlbum, idPath, artistDisp.c_str(), strTitle.c_str(), iTrack, iDuration,
+ strRelease.c_str(), strOriginal.c_str(), iBPM, iBitRate, iSampleRate, iChannels,
+ iBitsPerSample, strCodec.c_str(), strDiscSubtitle.c_str(), strFileName.c_str(),
+ strDateMedia.c_str());
if (strMusicBrainzTrackID.empty())
strSQL += PrepareSQL(",NULL");
@@ -1196,7 +1220,7 @@ int CMusicDatabase::AddSong(const int idSong,
dtLastPlayed, //
rating, userrating, votes, //
replayGain, //
- iBPM, iBitRate, iSampleRate, iChannels, songVideoURL);
+ iBPM, iBitRate, iSampleRate, iChannels, iBitsPerSample, strCodec, songVideoURL);
}
if (!strThumb.empty())
SetArtForItem(idNew, MediaTypeSong, "thumb", strThumb);
@@ -1289,7 +1313,7 @@ bool CMusicDatabase::UpdateSong(CSong& song, bool bArtists /*= true*/, bool bArt
song.rating, song.userrating, song.votes, //
song.replayGain, //
song.iBPM, song.iBitRate, song.iSampleRate, song.iChannels, //
- song.songVideoURL);
+ song.iBitsPerSample, song.strCodec, song.songVideoURL);
if (result < 0)
return false;
@@ -1349,6 +1373,8 @@ int CMusicDatabase::UpdateSong(int idSong,
int iBitRate,
int iSampleRate,
int iChannels,
+ int iBitsPerSample,
+ const std::string& strCodec,
const std::string& songVideoURL)
{
if (idSong < 0)
@@ -1372,7 +1398,8 @@ int CMusicDatabase::UpdateSong(int idSong,
" strTitle = '%s', iTrack = %i, iDuration = %i, "
"strReleaseDate = '%s', strOrigReleaseDate = '%s', strDiscSubtitle = '%s', "
"strFileName = '%s', iBPM = %i, iBitrate = %i, iSampleRate = %i, iChannels = %i, "
- "dateAdded = '%s', strVideoURL = '%s'",
+
+ "iBitsPerSample = %i, strCodec = '%s', strVideoURL = '%s', dateAdded = '%s'",
idPath, artistDisp.c_str(),
StringUtils::Join(
genres,
@@ -1380,7 +1407,8 @@ int CMusicDatabase::UpdateSong(int idSong,
.c_str(),
strTitle.c_str(), iTrack, iDuration, strRelease.c_str(), strOriginal.c_str(),
strDiscSubtitle.c_str(), strFileName.c_str(), iBPM, iBitRate, iSampleRate, iChannels,
- strDateMedia.c_str(), songVideoURL.c_str());
+ iBitsPerSample, strCodec.c_str(), songVideoURL.c_str(),
+ strDateMedia.c_str());
if (strMusicBrainzTrackID.empty())
strSQL += PrepareSQL(", strMusicBrainzTrackID = NULL");
else
@@ -1425,7 +1453,7 @@ int CMusicDatabase::AddAlbum(const std::string& strAlbum,
const std::string& strType,
const std::string& strReleaseStatus,
bool bCompilation,
- ReleaseType releaseType)
+ AudioType releaseType)
{
std::string strSQL;
try
@@ -1448,6 +1476,8 @@ int CMusicDatabase::AddAlbum(const std::string& strAlbum,
StringUtils::ToLower(strCheckFlag);
if (strCheckFlag.find("boxset") != std::string::npos) //boxset flagged in album type
bBoxedSet = true;
+ if (strCheckFlag.find("audiobook") != std::string::npos) // audiobook flagged in album type
+ releaseType = AudioType::Content::AudioBook;
if (m_pDS->num_rows() == 0)
{
m_pDS->close();
@@ -1462,7 +1492,7 @@ int CMusicDatabase::AddAlbum(const std::string& strAlbum,
strAlbum.c_str(), strArtist.c_str(), strGenre.c_str(), //
strReleaseDate.c_str(), strOrigReleaseDate.c_str(), bBoxedSet, //
strRecordLabel.c_str(), strType.c_str(), strReleaseStatus.c_str(), //
- bCompilation, CAlbum::ReleaseTypeToString(releaseType).c_str());
+ bCompilation, releaseType.ToString().c_str());
if (strMusicBrainzAlbumID.empty())
strSQL += PrepareSQL(", NULL");
@@ -1516,7 +1546,7 @@ int CMusicDatabase::AddAlbum(const std::string& strAlbum,
"WHERE idAlbum=%i",
strGenre.c_str(), strReleaseDate.c_str(), strOrigReleaseDate.c_str(), //
bBoxedSet, strRecordLabel.c_str(), strType.c_str(), strReleaseStatus.c_str(),
- bCompilation, CAlbum::ReleaseTypeToString(releaseType).c_str(), //
+ bCompilation, releaseType.ToString().c_str(), //
idAlbum);
m_pDS->exec(strSQL);
DeleteAlbumArtistsByAlbum(idAlbum);
@@ -1554,7 +1584,7 @@ int CMusicDatabase::UpdateAlbum(int idAlbum,
const std::string& strOrigReleaseDate,
bool bBoxedSet,
bool bCompilation,
- ReleaseType releaseType,
+ AudioType releaseType,
bool bScrapedMBID)
{
if (idAlbum < 0)
@@ -1584,7 +1614,7 @@ int CMusicDatabase::UpdateAlbum(int idAlbum,
strType.c_str(), static_cast(fRating), iUserrating, iVotes, //
strReleaseDate.c_str(), strOrigReleaseDate.c_str(), //
bBoxedSet, bCompilation, //
- CAlbum::ReleaseTypeToString(releaseType).c_str(), strReleaseStatus.c_str(), //
+ releaseType.ToString().c_str(), strReleaseStatus.c_str(), //
CDateTime::GetUTCDateTime().GetAsDBDateTime().c_str(), bScrapedMBID);
if (strMusicBrainzAlbumID.empty())
strSQL += PrepareSQL(", strMusicBrainzAlbumID = NULL");
@@ -3159,6 +3189,8 @@ CSong CMusicDatabase::GetSongFromDataset(const dbiplus::sql_record* const record
song.iBitRate = record->at(offset + song_iBitRate).get_asInt();
song.iSampleRate = record->at(offset + song_iSampleRate).get_asInt();
song.iChannels = record->at(offset + song_iChannels).get_asInt();
+ song.iBitsPerSample = record->at(offset + song_iBitsPerSample).get_asInt();
+ song.strCodec = record->at(offset + song_strCodec).get_asString();
song.songVideoURL = record->at(offset + song_songVideoURL).get_asString();
return song;
}
@@ -3212,11 +3244,14 @@ void CMusicDatabase::GetFileItemFromDataset(const dbiplus::sql_record* const rec
// get the album artist string from songview (not the album_artist and artist tables)
item->GetMusicInfoTag()->SetAlbumArtist(record->at(song_strAlbumArtists).get_asString());
item->GetMusicInfoTag()->SetAlbumReleaseType(
- CAlbum::ReleaseTypeFromString(record->at(song_strAlbumReleaseType).get_asString()));
+ AudioType::FromString(record->at(song_strAlbumReleaseType).get_asString())
+ .value_or(AudioType::Content::Album));
item->GetMusicInfoTag()->SetBPM(record->at(song_iBPM).get_asInt());
item->GetMusicInfoTag()->SetBitRate(record->at(song_iBitRate).get_asInt());
item->GetMusicInfoTag()->SetSampleRate(record->at(song_iSampleRate).get_asInt());
item->GetMusicInfoTag()->SetNoOfChannels(record->at(song_iChannels).get_asInt());
+ item->GetMusicInfoTag()->SetBitsPerSample(record->at(song_iBitsPerSample).get_asInt());
+ item->GetMusicInfoTag()->SetCodec(record->at(song_strCodec).get_asString());
// Replay gain data (needed for songs from cuesheets, both separate .cue files and embedded metadata)
ReplayGain replaygain;
replaygain.Set(record->at(song_strReplayGain).get_asString());
@@ -3324,6 +3359,11 @@ CAlbum CMusicDatabase::GetAlbumFromDataset(const dbiplus::sql_record* const reco
album.SetDateNew(record->at(offset + album_dateNew).get_asString());
album.SetDateUpdated(record->at(offset + album_dateModified).get_asString());
album.SetLastPlayed(record->at(offset + album_dtLastPlayed).get_asString());
+ album.strCodec = record->at(offset + album_strCodec).get_asString();
+ album.iChannels = record->at(offset + album_iChannels).get_asInt();
+ album.iBitrate = record->at(offset + album_iBitrate).get_asInt();
+ album.iSampleRate = record->at(offset + album_iSampleRate).get_asInt();
+ album.iBitsPerSample = record->at(offset + album_iBitsPerSample).get_asInt();
album.iAlbumDuration = record->at(offset + album_iAlbumDuration).get_asInt();
return album;
}
@@ -3747,7 +3787,7 @@ bool CMusicDatabase::GetRecentlyPlayedAlbums(std::vector& albums)
"JOIN albumview ON albumview.idAlbum = playedalbums.idAlbum "
"JOIN albumartistview ON albumview.idAlbum = albumartistview.idAlbum "
"ORDER BY albumview.lastplayed DESC, albumartistview.iorder ",
- CAlbum::ReleaseTypeToString(ReleaseType::Album).c_str(), RECENTLY_PLAYED_LIMIT);
+ AudioType::ToString(AudioType::Content::Album).c_str(), RECENTLY_PLAYED_LIMIT);
auto queryStart = std::chrono::steady_clock::now();
CLog::LogF(LOGDEBUG, "query: {}", strSQL);
@@ -3902,11 +3942,12 @@ bool CMusicDatabase::GetRecentlyAddedAlbums(std::vector& albums, unsigne
// timestamps, nothing to do with when albums added to library)
std::string strSQL =
PrepareSQL("SELECT albumview.*, albumartistview.* "
- "FROM (SELECT idAlbum FROM album WHERE strAlbum != '' "
+ "FROM (SELECT idAlbum FROM album WHERE strAlbum != '' AND strReleaseType = '%s' "
"ORDER BY dateAdded DESC LIMIT %u) AS recentalbums "
"JOIN albumview ON albumview.idAlbum = recentalbums.idAlbum "
"JOIN albumartistview ON albumview.idAlbum = albumartistview.idAlbum "
"ORDER BY dateAdded DESC, albumview.idAlbum desc, albumartistview.iOrder ",
+ AudioType::ToString(AudioType::Content::Album).c_str(),
limit ? limit
: CServiceBroker::GetSettingsComponent()
->GetAdvancedSettings()
@@ -5936,6 +5977,8 @@ bool CMusicDatabase::GetAlbumsByWhere(const std::string& baseDir,
// Set icon now to avoid slow per item processing in FillInDefaultIcon later
pItem->SetProperty("icon_never_overlay", true);
pItem->SetArt("icon", "DefaultAlbumCover.png");
+ pItem->GetMusicInfoTag()->SetURL(GetPathForAlbum(record->at(album_idAlbum).get_asInt()));
+
items.Add(std::move(pItem));
}
catch (...)
@@ -8557,10 +8600,10 @@ void CMusicDatabase::UpdateTables(int version)
// set strReleaseType based on album name
m_pDS->exec(PrepareSQL(
"UPDATE album SET strReleaseType = '%s' WHERE strAlbum IS NOT NULL AND strAlbum <> ''",
- CAlbum::ReleaseTypeToString(ReleaseType::Album).c_str()));
+ AudioType::ToString(AudioType::Content::Album).c_str()));
m_pDS->exec(
PrepareSQL("UPDATE album SET strReleaseType = '%s' WHERE strAlbum IS NULL OR strAlbum = ''",
- CAlbum::ReleaseTypeToString(ReleaseType::Single).c_str()));
+ AudioType::ToString(AudioType::Content::Single).c_str()));
}
if (version < 51)
{
@@ -9361,6 +9404,12 @@ void CMusicDatabase::UpdateTables(int version)
if (version < 83)
m_pDS->exec("ALTER TABLE song ADD strVideoURL TEXT");
+ if (version < 84) // add iBitsPerSample and strCodec to song table
+ {
+ m_pDS->exec("ALTER TABLE song ADD iBitsPerSample INTEGER NOT NULL DEFAULT 0");
+ m_pDS->exec("ALTER TABLE song ADD strCodec TEXT");
+ }
+
// Set the version of tag scanning required.
// Not every schema change requires the tags to be rescanned, set to the highest schema version
// that needs this. Forced rescanning (of music files that have not changed since they were
@@ -9369,10 +9418,10 @@ void CMusicDatabase::UpdateTables(int version)
// The original db version when the tags were scanned, and the minimal db version needed are
// later used to determine if a forced rescan should be prompted
- // The last schema change needing forced rescanning was 73.
- // This is because Kodi can now read and process extra tags involved in the creation of box sets
+ // The last schema change needing forced rescanning was 84.
+ // This is because Kodi can now read and process extra info for codec information etc
- SetMusicNeedsTagScan(73);
+ SetMusicNeedsTagScan(84);
// After all updates, store the original db version.
// This indicates the version of tag processing that was used to populate db
@@ -10699,6 +10748,14 @@ bool CMusicDatabase::IsAlbumBoxset(int idAlbum) const
return (isBoxSet == 1 ? true : false);
}
+
+bool CMusicDatabase::IsItemConcert(const CFileItem& item) const
+{
+ if (item.HasMusicInfoTag())
+ return item.GetMusicInfoTag()->GetAlbumReleaseType() == AudioType(AudioType::Content::Concert);
+ return false;
+}
+
int CMusicDatabase::GetAlbumByName(const std::string& strAlbum, const std::string& strArtist)
{
try
@@ -11134,7 +11191,7 @@ int CMusicDatabase::GetSinglesCount()
{
CDatabase::Filter filter(
PrepareSQL("songview.idAlbum IN (SELECT idAlbum FROM album WHERE strReleaseType = '%s')",
- CAlbum::ReleaseTypeToString(ReleaseType::Single).c_str()));
+ AudioType::ToString(AudioType::Content::Single).c_str()));
return GetSongsCount(filter);
}
@@ -11154,6 +11211,21 @@ int CMusicDatabase::GetArtistCountForRole(const std::string& strRole) const
return GetSingleValueInt(strSQL);
}
+int CMusicDatabase::GetConcertsCount()
+{
+ std::string strSQL =
+ PrepareSQL("SELECT COUNT (DISTINCT idAlbum) FROM album WHERE strReleaseType like '%s'",
+ AudioType::ToString(AudioType::Content::Concert).c_str());
+ return GetSingleValueInt(strSQL);
+}
+
+int CMusicDatabase::GetAudioBookCount()
+{
+ std::string strSQL =
+ "SELECT COUNT (DISTINCT idAlbum) FROM album WHERE album.strType LIKE '%audiobook%'";
+ return GetSingleValueInt(strSQL);
+}
+
bool CMusicDatabase::SetPathHash(const std::string& path, const std::string& hash)
{
try
@@ -11874,7 +11946,7 @@ void CMusicDatabase::ExportToXML(const CLibExportSettings& settings,
// Find albums to export
std::vector albumIds;
std::string strSQL = PrepareSQL("SELECT idAlbum FROM album WHERE strReleaseType = '%s' ",
- CAlbum::ReleaseTypeToString(ReleaseType::Album).c_str());
+ AudioType::ToString(AudioType::Content::Album).c_str());
if (!settings.IsUnscraped())
strSQL += "AND lastScraped IS NOT NULL";
CLog::LogF(LOGDEBUG, "{}", strSQL);
@@ -12764,9 +12836,17 @@ void CMusicDatabase::SetPropertiesFromAlbum(CFileItem& item, const CAlbum& album
item.SetProperty("album_isboxset", album.bBoxedSet);
item.SetProperty("album_totaldiscs", album.iTotalDiscs);
- item.SetProperty("album_releasetype", CAlbum::ReleaseTypeToString(album.releaseType));
+ item.SetProperty("album_releasetype", AudioType::ToString(album.releaseType));
item.SetProperty("album_duration",
StringUtils::SecondsToTimeString(album.iAlbumDuration, TIME_FORMAT_GUESS));
+
+ if (album.songs.size() > 0)
+ {
+ item.SetProperty("album_codec", album.songs[0].strCodec);
+ item.SetProperty("album_bitspersample", album.songs[0].iBitsPerSample);
+ item.SetProperty("album_channels", album.songs[0].iChannels);
+ item.SetProperty("album_samplerate", album.songs[0].iSampleRate);
+ }
}
void CMusicDatabase::SetPropertiesForFileItem(CFileItem& item)
@@ -12797,7 +12877,7 @@ void CMusicDatabase::SetPropertiesForFileItem(CFileItem& item)
if (idAlbum > -1)
{
CAlbum album;
- if (GetAlbum(idAlbum, album, false))
+ if (GetAlbum(idAlbum, album, true))
SetPropertiesFromAlbum(item, album);
}
}
@@ -13197,8 +13277,14 @@ int CMusicDatabase::GetOrderFilter(const std::string& type,
if (StringUtils::EndsWith(name, "strArtists") || StringUtils::EndsWith(name, "strArtist"))
{
if (StringUtils::EndsWith(name, "strArtists"))
- sortSQL = SortnameBuildSQL("artistsortname", sorting.sortAttributes, name,
- table + "strArtistSort");
+ {
+ if (StringUtils::StartsWith(name, "albumview"))
+ sortSQL = SortnameBuildSQL("artistsortname", sorting.sortAttributes, name,
+ "albumview.strArtistSort");
+ else
+ sortSQL =
+ SortnameBuildSQL("artistsortname", sorting.sortAttributes, name, "strArtistSort");
+ }
else
sortSQL = SortnameBuildSQL("artistsortname", sorting.sortAttributes, name, "strSortName");
if (!sortSQL.empty())
@@ -13617,12 +13703,19 @@ bool CMusicDatabase::GetFilter(CDbUrl& musicUrl, Filter& filter, SortDescription
genreSub.BuildSQL(genreSQL);
filter.AppendWhere(genreSQL);
}
+ // can't exclude singles from concerts, audiobooks or podcasts because there aren't any !
+ if (filter.where.find("concert") == std::string::npos &&
+ filter.where.find("audiobook") == std::string::npos &&
+ filter.where.find("podcast") == std::string::npos)
+ {
// Exclude any single albums (aka empty tagged albums)
// This causes "albums" media filter artist selection to only offer album artists
- option = options.find("show_singles");
- if (option == options.end() || !option->second.asBoolean())
- filter.AppendWhere(PrepareSQL("albumview.strReleaseType = '%s'",
- CAlbum::ReleaseTypeToString(ReleaseType::Album).c_str()));
+ option = options.find("show_singles");
+ if (option == options.end() || !option->second.asBoolean())
+ filter.AppendWhere(PrepareSQL("albumview.strReleaseType = '%s'",
+ AudioType::ToString(AudioType::Content::Album).c_str()));
+ }
+
}
}
else if (type == "discs")
@@ -13725,7 +13818,7 @@ bool CMusicDatabase::GetFilter(CDbUrl& musicUrl, Filter& filter, SortDescription
filter.AppendWhere(PrepareSQL(
"songview.idAlbum %sIN (SELECT idAlbum FROM album WHERE strReleaseType = '%s')",
option->second.asBoolean() ? "" : "NOT ",
- CAlbum::ReleaseTypeToString(ReleaseType::Single).c_str()));
+ AudioType::ToString(AudioType::Content::Single).c_str()));
// When have idAlbum skip year, compilation, boxset criteria as already applied via album
if (idAlbum < 0)
@@ -13967,3 +14060,62 @@ std::vector CMusicDatabase::GetUsedImages(
}
return {};
}
+void CMusicDatabase::GetMusicDetails(CFileItemList& items, std::string& reqField)
+{
+ try
+ {
+ if (nullptr == m_pDB)
+ return;
+ if (nullptr == m_pDS)
+ return;
+ std::string strSQL = StringUtils::Format(
+ "SELECT DISTINCT {} FROM song WHERE {} IS NOT NULL AND {} <> ''", reqField,
+ reqField, reqField);
+ if (reqField == "iBitsPerSample")
+ {
+ std::string strSQLExtra = StringUtils::Format(" AND {} <> 0", reqField);
+ strSQL += strSQLExtra;
+ }
+ CLog::Log(LOGDEBUG, LOGDATABASE, "CMusicDatabase::GetMusicDetails: Query {}", strSQL);
+ if (!m_pDS->query(strSQL.c_str()))
+ return;
+ while (!m_pDS->eof())
+ {
+ std::shared_ptr pItem = std::make_shared(m_pDS->fv(0).get_asString());
+ items.Add(pItem);
+ m_pDS->next();
+ }
+ m_pDS->close();
+ return;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
+ }
+
+ return;
+}
+
+std::string CMusicDatabase::GetPathForAlbum(int idAlbum)
+{
+ std::string albumPath;
+ try
+ { // use 2nd data set as we are likely called in a loop using 1st set
+ m_pDS2->open();
+ std::string strSQL = PrepareSQL(
+ "select album.strAlbum, path.strPath from album join song on album.idAlbum = "
+ "song.idAlbum join path on song.idPath = path.idPath where album.idAlbum = %i limit 1",
+ idAlbum);
+ if (!m_pDS2->query(strSQL) || m_pDS2->num_rows() == 0)
+ return albumPath;
+ albumPath = m_pDS2->fv(1).get_asString();
+ m_pDS2->close();
+ return albumPath;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
+ }
+ return "";
+}
+
diff --git a/xbmc/music/MusicDatabase.h b/xbmc/music/MusicDatabase.h
index 393021e3420f1..096b8b7755e9d 100644
--- a/xbmc/music/MusicDatabase.h
+++ b/xbmc/music/MusicDatabase.h
@@ -13,6 +13,7 @@
\brief
*/
+#include "MusicType.h"
#include "addons/Scraper.h"
#include "dbwrappers/Database.h"
#include "settings/LibExportSettings.h"
@@ -166,6 +167,8 @@ class CMusicDatabase : public CDatabase
int iBitRate,
int iSampleRate,
int iChannels,
+ int iBitsPerSample,
+ const std::string& strCodec,
const std::string& songVideoURL,
const ReplayGain& replayGain);
bool GetSong(int idSong, CSong& song);
@@ -205,6 +208,8 @@ class CMusicDatabase : public CDatabase
\param iBPM [in] the beats per minute of a song
\param iBitRate [in] the bitrate of the song file
\param iSampleRate [in] the sample rate of the song file
+ \param iBitsPerSample [in] the bitspersample of the song file or zero if not supported
+ \param strCodec [in] name of the codec used or empty string if unknown
\param iChannels [in] the number of audio channels in the song file
\param songVideoURL [in] url link to a video of the song
\return the id of the song
@@ -236,6 +241,8 @@ class CMusicDatabase : public CDatabase
int iBitRate,
int iSampleRate,
int iChannels,
+ int iBitsPerSample,
+ const std::string& strCodec,
const std::string& songVideoURL);
//// Misc Song
@@ -300,7 +307,7 @@ class CMusicDatabase : public CDatabase
const std::string& strType,
const std::string& strReleaseStatus,
bool bCompilation,
- ReleaseType releaseType);
+ AudioType releaseType);
/*! \brief retrieve an album, optionally with all songs.
\param idAlbum the database id of the album.
@@ -331,7 +338,7 @@ class CMusicDatabase : public CDatabase
const std::string& strOrigReleaseDate,
bool bBoxedSet,
bool bCompilation,
- ReleaseType releaseType,
+ AudioType releaseType,
bool bScrapedMBID);
bool ClearAlbumLastScrapedTime(int idAlbum);
bool HasAlbumBeenScraped(int idAlbum) const;
@@ -363,6 +370,7 @@ class CMusicDatabase : public CDatabase
std::string GetAlbumDiscTitle(int idAlbum, int idDisc) const;
bool SetAlbumUserrating(const int idAlbum, int userrating);
int GetAlbumDiscsCount(int idAlbum) const;
+ std::string GetPathForAlbum(int idAlbum);
/////////////////////////////////////////////////
// Artist CRUD
@@ -541,6 +549,9 @@ class CMusicDatabase : public CDatabase
int GetArtistCountForRole(int role) const;
int GetArtistCountForRole(const std::string& strRole) const;
+ int GetConcertsCount();
+ int GetAudioBookCount();
+ bool IsItemConcert(const CFileItem& item) const;
/*! \brief Increment the playcount of an item
Increments the playcount and updates the last played date
@@ -639,6 +650,7 @@ class CMusicDatabase : public CDatabase
int GetSongsCount(const Filter& filter = Filter());
bool GetFilter(CDbUrl& musicUrl, Filter& filter, SortDescription& sorting) override;
int GetOrderFilter(const std::string& type, const SortDescription& sorting, Filter& filter) const;
+ void GetMusicDetails(CFileItemList& items, std::string& reqField);
/////////////////////////////////////////////////
// Party Mode
@@ -1016,6 +1028,8 @@ class CMusicDatabase : public CDatabase
song_iBPM,
song_iBitRate,
song_iSampleRate,
+ song_iBitsPerSample,
+ song_strCodec,
song_iChannels,
song_songVideoURL,
song_iAlbumDuration,
@@ -1061,6 +1075,11 @@ class CMusicDatabase : public CDatabase
album_strReleaseType,
album_iTotalDiscs,
album_dtLastPlayed,
+ album_strCodec,
+ album_iChannels,
+ album_iBitrate,
+ album_iSampleRate,
+ album_iBitsPerSample,
album_iAlbumDuration,
album_enumCount // end of the enum, do not add past here
};
diff --git a/xbmc/music/MusicEmbeddedCoverLoaderFFmpeg.cpp b/xbmc/music/MusicEmbeddedCoverLoaderFFmpeg.cpp
new file mode 100644
index 0000000000000..8d5919949ad3a
--- /dev/null
+++ b/xbmc/music/MusicEmbeddedCoverLoaderFFmpeg.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2005-2025 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "MusicEmbeddedCoverLoaderFFmpeg.h"
+
+#include "cores/FFmpeg.h"
+#include "tags/MusicInfoTag.h"
+#include "utils/EmbeddedArt.h"
+
+using namespace MUSIC_INFO;
+
+void CMusicEmbeddedCoverLoaderFFmpeg::GetEmbeddedCover(AVFormatContext* fctx,
+ CMusicInfoTag& tag,
+ EmbeddedArt* art /* nullptr */)
+{
+ for (size_t i = 0; i < fctx->nb_streams; ++i)
+ {
+ const AVStream* fctx_pic = fctx->streams[i];
+ if ((fctx_pic->disposition & AV_DISPOSITION_ATTACHED_PIC) == 0)
+ continue;
+
+ AVCodecID pic_id = fctx_pic->codecpar->codec_id;
+ const std::map mime_map = {{AV_CODEC_ID_MJPEG, "image/jpeg"},
+ {AV_CODEC_ID_PNG, "image/png"},
+ {AV_CODEC_ID_BMP, "image/bmp"}};
+
+ auto it = mime_map.find(pic_id);
+ if (it != mime_map.end())
+ {
+ const auto& mimetype = it->second;
+ size_t pic_size = fctx_pic->attached_pic.size;
+ uint8_t* pic = fctx_pic->attached_pic.data;
+
+ tag.SetCoverArtInfo(pic_size, mimetype);
+ if (art)
+ art->Set(pic, pic_size, mimetype, "thumb");
+ break; // just need one cover
+ }
+ }
+}
diff --git a/xbmc/music/MusicEmbeddedCoverLoaderFFmpeg.h b/xbmc/music/MusicEmbeddedCoverLoaderFFmpeg.h
new file mode 100644
index 0000000000000..b51a6b9b02d1f
--- /dev/null
+++ b/xbmc/music/MusicEmbeddedCoverLoaderFFmpeg.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2005-2025 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+struct AVFormatContext;
+class EmbeddedArt;
+
+namespace MUSIC_INFO
+{
+class CMusicInfoTag;
+
+class CMusicEmbeddedCoverLoaderFFmpeg
+{
+public:
+ /*!
+ * \brief Searches the supplied AVFormatContext for the first stream containing a supported embedded
+ * picture.
+ * This function is called by CMusicInfoTagLoaderFFmpeg when browsing mka music in files view, and
+ * by CAudioBookFileDirectory when scanning mka files into the music library.
+ *
+ * If art is found, it is added to CMusicInfoTag and EmbeddedArt (if available).
+ *
+ * If there is no embedded picture or it is not of type jpg/png/bmp then no art is set.
+ *
+ * \param fctx An AVFormatContext containing one or more streams, one of which may contain art
+ * \param tag MusicInfoTag holding information about the current music track to which found art is
+ * added
+ * \param art Class containing embedded art details (if available)
+ */
+ static void GetEmbeddedCover(AVFormatContext* fctx,
+ CMusicInfoTag& tag,
+ EmbeddedArt* art = nullptr);
+};
+} // namespace MUSIC_INFO
diff --git a/xbmc/music/MusicType.h b/xbmc/music/MusicType.h
new file mode 100644
index 0000000000000..82ec53c0dc50b
--- /dev/null
+++ b/xbmc/music/MusicType.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2026 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#pragma once
+#include
+#include
+#include
+#include
+#include
+
+class AudioType
+{
+public:
+ enum class Content : uint8_t
+ {
+ Album = 0,
+ Single,
+ AudioBook,
+ Podcast,
+ Concert
+ };
+
+ // Implicit construction from enum - allows: AudioType x = AudioType::Content::Album;
+ constexpr AudioType(Content type = Content::Album) noexcept : m_type(type) {}
+
+ // Implicit conversion to enum - allows switch/case and comparisons
+ constexpr operator Content() const noexcept { return m_type; }
+
+ // Explicit getter if preferred over implicit conversion
+ constexpr Content GetContent() const noexcept { return m_type; }
+
+ // Instance method for converting to string
+ constexpr std::string ToString() const noexcept
+ {
+ auto it = std::ranges::find(releaseTypes, m_type, &ReleaseTypeInfo::type);
+ return it != releaseTypes.end() ? it->name : "album";
+ }
+
+ // Static factory from string
+ static constexpr std::optional FromString(std::string str) noexcept
+ {
+ auto it = std::ranges::find(releaseTypes, str, &ReleaseTypeInfo::name);
+ return it != releaseTypes.end() ? std::optional{AudioType{it->type}} : std::nullopt;
+ }
+
+ static std::string ToString(Content type)
+{
+ return AudioType(type).ToString();
+}
+
+ // Comparisons (defaulted generates all 6 operators: ==, !=, <, <=, >, >=)
+ constexpr auto operator<=>(const AudioType&) const noexcept = default;
+
+private:
+ Content m_type;
+
+ struct ReleaseTypeInfo
+ {
+ Content type;
+ std::string name;
+ };
+
+ static constexpr std::array releaseTypes{
+ ReleaseTypeInfo{Content::Album, "album"},
+ ReleaseTypeInfo{Content::Single, "single"},
+ ReleaseTypeInfo{Content::AudioBook, "audiobook"},
+ ReleaseTypeInfo{Content::Podcast, "podcast"},
+ ReleaseTypeInfo{Content::Concert, "concert"}
+ };
+};
\ No newline at end of file
diff --git a/xbmc/music/Song.cpp b/xbmc/music/Song.cpp
index e9478244c2dee..b42ba6860375d 100644
--- a/xbmc/music/Song.cpp
+++ b/xbmc/music/Song.cpp
@@ -74,7 +74,9 @@ CSong::CSong(CFileItem& item)
iBPM = tag.GetBPM();
iSampleRate = tag.GetSampleRate();
iBitRate = tag.GetBitRate();
+ iBitsPerSample = tag.GetBitsPerSample();
iChannels = tag.GetNoOfChannels();
+ strCodec = tag.GetCodec();
songVideoURL = tag.GetSongVideoURL();
m_chapters = tag.GetChapterMarks();
}
@@ -242,6 +244,8 @@ void CSong::Serialize(CVariant& value) const
value["bpm"] = iBPM;
value["bitrate"] = iBitRate;
value["samplerate"] = iSampleRate;
+ value["bitspersample"] = iBitsPerSample;
+ value["codec"] = strCodec;
value["channels"] = iChannels;
value["songvideourl"] = songVideoURL;
}
@@ -283,6 +287,8 @@ void CSong::Clear()
iBPM = 0;
iBitRate = 0;
iSampleRate = 0;
+ iBitsPerSample = 0;
+ strCodec.clear();
iChannels = 0;
songVideoURL.clear();
m_chapters.clear();
diff --git a/xbmc/music/Song.h b/xbmc/music/Song.h
index aa96c6908d94c..c79db4a32505c 100644
--- a/xbmc/music/Song.h
+++ b/xbmc/music/Song.h
@@ -200,6 +200,8 @@ class CSong final : public ISerializable
int iSampleRate;
int iBitRate;
int iChannels;
+ int iBitsPerSample;
+ std::string strCodec;
std::string strRecordLabel; // Record label from tag for album processing by CMusicInfoScanner::FileItemsToAlbums
std::string strAlbumType; // (Musicbrainz release type) album type from tag for album processing by CMusicInfoScanner::FileItemsToAlbums
std::string songVideoURL; // url to song video
diff --git a/xbmc/music/infoscanner/MusicInfoScanner.cpp b/xbmc/music/infoscanner/MusicInfoScanner.cpp
index 3143016887333..9a08d79e2a123 100644
--- a/xbmc/music/infoscanner/MusicInfoScanner.cpp
+++ b/xbmc/music/infoscanner/MusicInfoScanner.cpp
@@ -41,6 +41,7 @@
#include "music/MusicFileItemClassify.h"
#include "music/MusicLibraryQueue.h"
#include "music/MusicThumbLoader.h"
+#include "music/MusicType.h"
#include "music/MusicUtils.h"
#include "music/tags/MusicInfoTag.h"
#include "music/tags/MusicInfoTagLoaderFactory.h"
@@ -692,6 +693,9 @@ void CMusicInfoScanner::FileItemsToAlbums(
if (it == albums.end())
{
CAlbum album(*items[i]);
+ if ((StringUtils::EndsWithNoCase(song.strFileName, "mkv")) or
+ (StringUtils::EndsWithNoCase(song.strFileName, "mp4")))
+ album.releaseType = AudioType::Content::Concert;
album.songs.push_back(song);
albums.push_back(album);
}
@@ -897,6 +901,8 @@ void CMusicInfoScanner::FileItemsToAlbums(
album.strReleaseDate = k->strReleaseDate,
album.strLabel = k->strRecordLabel;
album.strType = k->strAlbumType;
+ if ((StringUtils::EndsWithNoCase(k->strFileName, "mkv")) or (StringUtils::EndsWithNoCase(k->strFileName, "mp4")))
+ album.releaseType = AudioType::Content::Concert;
album.songs.push_back(*k);
}
albums.push_back(album);
@@ -982,7 +988,7 @@ int CMusicInfoScanner::RetrieveMusicInfo(const std::string& strDirectory, CFileI
// mark albums without a title as singles
if (album.strAlbum.empty())
- album.releaseType = ReleaseType::Single;
+ album.releaseType = AudioType::Content::Single;
album.strPath = strDirectory;
m_musicDatabase.AddAlbum(album, m_idSourcePath);
diff --git a/xbmc/music/tags/CMakeLists.txt b/xbmc/music/tags/CMakeLists.txt
index 63f352dfc7cc6..a0a4f411e3c90 100644
--- a/xbmc/music/tags/CMakeLists.txt
+++ b/xbmc/music/tags/CMakeLists.txt
@@ -1,4 +1,6 @@
-set(SOURCES MusicInfoTag.cpp
+set(SOURCES MusicCodecInfoFFmpeg.cpp
+ MusicInfoTag.cpp
+ MusicInfoTagLoaderCDDA.cpp
MusicInfoTagLoaderDatabase.cpp
MusicInfoTagLoaderFactory.cpp
MusicInfoTagLoaderFFmpeg.cpp
@@ -8,6 +10,7 @@ set(SOURCES MusicInfoTag.cpp
TagLoaderTagLib.cpp)
set(HEADERS ImusicInfoTagLoader.h
+ MusicCodecInfoFFmpeg.h
MusicInfoTag.h
MusicInfoTagLoaderDatabase.h
MusicInfoTagLoaderFactory.h
diff --git a/xbmc/music/tags/MusicCodecInfoFFmpeg.cpp b/xbmc/music/tags/MusicCodecInfoFFmpeg.cpp
new file mode 100644
index 0000000000000..645b90a557901
--- /dev/null
+++ b/xbmc/music/tags/MusicCodecInfoFFmpeg.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2005-2022 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "MusicCodecInfoFFmpeg.h"
+#include "cores/FFmpeg.h"
+#include "filesystem/File.h"
+
+
+using namespace XFILE;
+
+static int vfs_file_read(void* h, uint8_t* buf, int size)
+{
+ CFile* pFile = static_cast(h);
+ return pFile->Read(buf, size);
+}
+
+static int64_t vfs_file_seek(void* h, int64_t pos, int whence)
+{
+ CFile* pFile = static_cast(h);
+ if (whence == AVSEEK_SIZE)
+ return pFile->GetLength();
+ else
+ return pFile->Seek(pos, whence & ~AVSEEK_FORCE);
+}
+
+bool CMusicCodecInfoFFmpeg::GetMusicCodecInfo(const std::string& strFileName,
+ musicCodecInfo& codec_info)
+{
+ const AVCodec* decoder = nullptr;
+ CFile file;
+ bool haveInfo = false;
+ if (!file.Open(strFileName))
+ return haveInfo;
+
+ int bufferSize = 4096;
+ int blockSize = file.GetChunkSize();
+ if (blockSize > 1)
+ bufferSize = blockSize;
+ uint8_t* buffer = (uint8_t*)av_malloc(bufferSize);
+ AVIOContext* ioctx =
+ avio_alloc_context(buffer, bufferSize, 0, &file, vfs_file_read, NULL, vfs_file_seek);
+
+ AVFormatContext* fctx = avformat_alloc_context();
+ fctx->pb = ioctx;
+
+ if (file.IoControl(IOControl::SEEK_POSSIBLE, NULL) != 1)
+ ioctx->seekable = 0;
+
+ const AVInputFormat* iformat = nullptr;
+ av_probe_input_buffer(ioctx, &iformat, strFileName.c_str(), NULL, 0, 0);
+
+ if (!ioctx)
+ {
+ avformat_close_input(&fctx);
+ if (ioctx)
+ {
+ av_freep(&ioctx->buffer);
+ av_freep(&ioctx);
+ }
+ return haveInfo;
+ }
+
+ AVStream* st = nullptr;
+ if (avformat_open_input(&fctx, strFileName.c_str(), nullptr, nullptr) == 0)
+ {
+ fctx->flags |= AVFMT_FLAG_NOPARSE;
+ if (avformat_find_stream_info(fctx, nullptr) >= 0)
+ {
+ int streamIndex = -1;
+ // Look for the default audio stream first
+ for (unsigned int i = 0; i < fctx->nb_streams; ++i)
+ {
+ if (fctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
+ {
+ if (fctx->streams[i]->disposition & AV_DISPOSITION_DEFAULT)
+ {
+ streamIndex = i;
+ break; // Found the default audio stream, no need to check further
+ }
+ }
+ }
+ if (streamIndex == -1)
+ {
+ for (unsigned int i = 0; i < fctx->nb_streams; ++i)
+ {
+ if (fctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
+ {
+ streamIndex = i;
+ break; // Found the first audio stream
+ }
+ }
+ }
+
+ if (streamIndex == -1)
+ return haveInfo; // didn't find an audio stream so just exit
+ st = fctx->streams[streamIndex];
+ decoder = avcodec_find_decoder(st->codecpar->codec_id);
+ if (decoder)
+ {
+ std::string codec_name = "unknown";
+
+ codec_name = avcodec_get_name(st->codecpar->codec_id);
+ int par_profile = st->codecpar->profile;
+ if (st->codecpar->codec_id == AV_CODEC_ID_DTS)
+ {
+ switch (par_profile)
+ {
+ case AV_PROFILE_DTS_HD_MA_X:
+ codec_name = "dtshd_ma_x";
+ break;
+ case AV_PROFILE_DTS_HD_MA_X_IMAX:
+ codec_name = "dtshd_ma_x_imax";
+ break;
+ case AV_PROFILE_DTS_ES:
+ codec_name = "dts_es";
+ break;
+ case AV_PROFILE_DTS_96_24:
+ codec_name = "dts_96_24";
+ break;
+ case AV_PROFILE_DTS_HD_HRA:
+ codec_name = "dtshd_hra";
+ break;
+ case AV_PROFILE_DTS_EXPRESS:
+ codec_name = "dts_express";
+ break;
+ case AV_PROFILE_DTS_HD_MA:
+ codec_name = "dtshd_ma";
+ break;
+ default:
+ codec_name = "dca";
+ break;
+ }
+ }
+ if (st->codecpar->codec_id == AV_CODEC_ID_EAC3 && par_profile == AV_PROFILE_EAC3_DDP_ATMOS)
+ codec_name = "eac3_ddp_atmos";
+
+ if (st->codecpar->codec_id == AV_CODEC_ID_TRUEHD && par_profile == AV_PROFILE_TRUEHD_ATMOS)
+ codec_name = "truehd_atmos";
+ codec_info.codecName = codec_name;
+ codec_info.bitRate = static_cast(st->codecpar->bit_rate / 1000);
+ codec_info.channels = st->codecpar->ch_layout.nb_channels;
+ codec_info.bitsPerSample = (st->codecpar->bits_per_coded_sample != 0)
+ ? st->codecpar->bits_per_coded_sample
+ : st->codecpar->bits_per_raw_sample;
+ codec_info.sampleRate = st->codecpar->sample_rate;
+ codec_info.duration = st->duration / AV_TIME_BASE;
+ haveInfo = true;
+ }
+ }
+
+ if (fctx)
+ avformat_close_input(&fctx);
+ if (ioctx)
+ {
+ av_free(ioctx->buffer);
+ av_free(ioctx);
+ }
+ }
+ return haveInfo;
+}
diff --git a/xbmc/music/tags/MusicCodecInfoFFmpeg.h b/xbmc/music/tags/MusicCodecInfoFFmpeg.h
new file mode 100644
index 0000000000000..8bd71dd25168f
--- /dev/null
+++ b/xbmc/music/tags/MusicCodecInfoFFmpeg.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2022 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include
+
+struct musicCodecInfo
+{
+public:
+ int bitsPerSample = 0;
+ int sampleRate = 0;
+ int bitRate = 0;
+ int channels = 0;
+ int duration = 0;
+ std::string codecName;
+};
+
+
+class CMusicCodecInfoFFmpeg
+{
+public:
+ static bool GetMusicCodecInfo(const std::string& strFileName, musicCodecInfo& codec_info);
+};
diff --git a/xbmc/music/tags/MusicInfoTag.cpp b/xbmc/music/tags/MusicInfoTag.cpp
index 19e5afa9aa006..44f911cafb2f5 100644
--- a/xbmc/music/tags/MusicInfoTag.cpp
+++ b/xbmc/music/tags/MusicInfoTag.cpp
@@ -270,7 +270,7 @@ const ReplayGain& CMusicInfoTag::GetReplayGain() const
return m_replayGain;
}
-ReleaseType CMusicInfoTag::GetAlbumReleaseType() const
+const AudioType& CMusicInfoTag::GetAlbumReleaseType() const
{
return m_albumReleaseType;
}
@@ -295,6 +295,16 @@ int CMusicInfoTag::GetNoOfChannels() const
return m_channels;
}
+int CMusicInfoTag::GetBitsPerSample() const
+{
+ return m_bitsPerSample;
+}
+
+const std::string& CMusicInfoTag::GetCodec() const
+{
+ return m_codec;
+}
+
const std::string& CMusicInfoTag::GetReleaseDate() const
{
return m_strReleaseDate;
@@ -548,6 +558,16 @@ void CMusicInfoTag::SetComment(std::string_view comment)
m_strComment = comment;
}
+void CMusicInfoTag::SetBitsPerSample(int bitspersample)
+{
+ m_bitsPerSample = bitspersample;
+}
+
+void CMusicInfoTag::SetCodec(const std::string& codec)
+{
+ m_codec = codec;
+}
+
void CMusicInfoTag::SetMood(std::string_view mood)
{
m_strMood = mood;
@@ -761,7 +781,7 @@ void CMusicInfoTag::SetReplayGain(const ReplayGain& aGain)
m_replayGain = aGain;
}
-void CMusicInfoTag::SetAlbumReleaseType(ReleaseType releaseType)
+void CMusicInfoTag::SetAlbumReleaseType(AudioType releaseType)
{
m_albumReleaseType = releaseType;
}
@@ -848,6 +868,11 @@ void CMusicInfoTag::SetAlbum(const CAlbum& album)
SetDatabaseId(album.idAlbum, MediaTypeAlbum);
SetLastPlayed(album.lastPlayed);
SetTotalDiscs(album.iTotalDiscs);
+ SetCodec(album.strCodec);
+ SetBitRate(album.iBitrate);
+ SetNoOfChannels(album.iChannels);
+ SetSampleRate(album.iSampleRate);
+ SetBitsPerSample(album.iBitsPerSample);
SetDuration(album.iAlbumDuration);
SetLoaded();
@@ -901,8 +926,9 @@ void CMusicInfoTag::SetSong(const CSong& song)
SetBitRate(song.iBitRate);
SetSampleRate(song.iSampleRate);
SetNoOfChannels(song.iChannels);
+ SetBitsPerSample(song.iBitsPerSample);
+ SetCodec(song.strCodec);
SetSongVideoURL(song.songVideoURL);
-
if (song.replayGain.Get(ReplayGain::TRACK).Valid())
m_replayGain.Set(ReplayGain::TRACK, song.replayGain.Get(ReplayGain::TRACK));
if (song.replayGain.Get(ReplayGain::ALBUM).Valid())
@@ -977,9 +1003,9 @@ void CMusicInfoTag::Serialize(CVariant& value) const
value["compilationartist"] = m_bCompilation;
value["compilation"] = m_bCompilation;
if (m_type.compare(MediaTypeAlbum) == 0)
- value["releasetype"] = CAlbum::ReleaseTypeToString(m_albumReleaseType);
+ value["releasetype"] = m_albumReleaseType.ToString();
else if (m_type.compare(MediaTypeSong) == 0)
- value["albumreleasetype"] = CAlbum::ReleaseTypeToString(m_albumReleaseType);
+ value["albumreleasetype"] = m_albumReleaseType.ToString();
value["isboxset"] = m_bBoxset;
value["totaldiscs"] = m_iDiscTotal;
value["disctitle"] = m_strDiscSubtitle;
@@ -990,8 +1016,9 @@ void CMusicInfoTag::Serialize(CVariant& value) const
value["bitrate"] = m_bitrate;
value["samplerate"] = m_samplerate;
value["channels"] = m_channels;
- value["songvideourl"] = m_songVideoURL;
-}
+ value["bitspersample"] = m_bitsPerSample;
+ value["codec"] = m_codec;
+ value["songvideourl"] = m_songVideoURL;}
void CMusicInfoTag::ToSortable(SortItem& sortable, Field field) const
{
@@ -1128,11 +1155,13 @@ void CMusicInfoTag::Archive(CArchive& ar)
ar << m_listeners;
ar << m_coverArt;
ar << m_cuesheet;
- ar << static_cast(m_albumReleaseType);
+ ar << static_cast(m_albumReleaseType.GetContent());
ar << m_iBPM;
ar << m_samplerate;
ar << m_bitrate;
ar << m_channels;
+ ar << m_bitsPerSample;
+ ar << m_codec;
ar << m_songVideoURL;
}
else
@@ -1196,11 +1225,13 @@ void CMusicInfoTag::Archive(CArchive& ar)
int albumReleaseType;
ar >> albumReleaseType;
- m_albumReleaseType = static_cast(albumReleaseType);
+ m_albumReleaseType = static_cast(albumReleaseType);
ar >> m_iBPM;
ar >> m_samplerate;
ar >> m_bitrate;
ar >> m_channels;
+ ar >> m_bitsPerSample;
+ ar >> m_codec;
ar >> m_songVideoURL;
}
}
@@ -1245,7 +1276,7 @@ void CMusicInfoTag::Clear()
m_iAlbumId = -1;
m_coverArt.Clear();
m_replayGain = ReplayGain();
- m_albumReleaseType = ReleaseType::Album;
+ m_albumReleaseType = AudioType::Content::Album;
m_listeners = 0;
m_Rating = 0;
m_Userrating = 0;
@@ -1255,6 +1286,8 @@ void CMusicInfoTag::Clear()
m_samplerate = 0;
m_bitrate = 0;
m_channels = 0;
+ m_bitsPerSample = 0;
+ m_codec.clear();
m_stationName.clear();
m_stationArt.clear();
m_songVideoURL.clear();
diff --git a/xbmc/music/tags/MusicInfoTag.h b/xbmc/music/tags/MusicInfoTag.h
index b0d78d805b06c..14de9e718f27a 100644
--- a/xbmc/music/tags/MusicInfoTag.h
+++ b/xbmc/music/tags/MusicInfoTag.h
@@ -11,6 +11,7 @@
#include "ReplayGain.h"
#include "XBDateTime.h"
#include "music/Album.h"
+#include "music/MusicType.h"
#include "music/Song.h"
#include "utils/IArchivable.h"
#include "utils/ISerializable.h"
@@ -83,13 +84,15 @@ class CMusicInfoTag final : public IArchivable, public ISerializable, public ISo
int GetBitRate() const;
int GetNoOfChannels() const;
int GetSampleRate() const;
+ int GetBitsPerSample() const;
+ const std::string& GetCodec() const;
const std::string& GetAlbumReleaseStatus() const;
const std::string& GetStationName() const;
const std::string& GetStationArt() const;
const std::string& GetSongVideoURL() const;
const EmbeddedArtInfo &GetCoverArtInfo() const;
const ReplayGain& GetReplayGain() const;
- ReleaseType GetAlbumReleaseType() const;
+ const AudioType& GetAlbumReleaseType() const;
const std::vector& GetChapterMarks() const;
void SetURL(std::string_view strURL);
@@ -149,7 +152,7 @@ class CMusicInfoTag final : public IArchivable, public ISerializable, public ISo
void SetBoxset(bool boxset);
void SetCoverArtInfo(size_t size, const std::string &mimeType);
void SetReplayGain(const ReplayGain& aGain);
- void SetAlbumReleaseType(ReleaseType releaseType);
+ void SetAlbumReleaseType(AudioType releaseType);
void SetType(MediaType_view mediaType);
void SetDiscSubtitle(std::string_view strDiscSubtitle);
void SetTotalDiscs(int iDiscTotal);
@@ -162,6 +165,8 @@ class CMusicInfoTag final : public IArchivable, public ISerializable, public ISo
void SetStationArt(std::string_view strStationArt);
void SetSongVideoURL(std::string_view songVideoURL); // link to video of song
void SetChapterMarks(const std::vector& chapters);
+ void SetBitsPerSample(int bitspersample);
+ void SetCodec(const std::string& strCodec);
/*! \brief Append a unique artist to the artist list
Checks if we have this artist already added, and if not adds it to the songs artist list.
@@ -254,11 +259,13 @@ class CMusicInfoTag final : public IArchivable, public ISerializable, public ISo
int m_iDiscTotal;
bool m_bBoxset;
int m_iBPM;
- ReleaseType m_albumReleaseType;
+ AudioType m_albumReleaseType;
std::string m_strReleaseStatus;
int m_samplerate;
int m_channels;
int m_bitrate;
+ int m_bitsPerSample;
+ std::string m_codec;
std::string m_stationName;
std::string m_stationArt; // Used to fetch thumb URL for Shoutcasts
std::string m_songVideoURL; // link to a video for a song
diff --git a/xbmc/music/tags/MusicInfoTagLoaderFFmpeg.cpp b/xbmc/music/tags/MusicInfoTagLoaderFFmpeg.cpp
index 0858ab7db856b..8c6bd5358c328 100644
--- a/xbmc/music/tags/MusicInfoTagLoaderFFmpeg.cpp
+++ b/xbmc/music/tags/MusicInfoTagLoaderFFmpeg.cpp
@@ -6,23 +6,26 @@
* See LICENSES/README.md for more information.
*/
-#include "MusicInfoTagLoaderFFmpeg.h"
-
#include "MusicInfoTag.h"
+#include "MusicInfoTagLoaderFFmpeg.h"
#include "cores/FFmpeg.h"
#include "filesystem/File.h"
+#include "ServiceBroker.h"
+#include "music/MusicEmbeddedCoverLoaderFFmpeg.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
#include "utils/StringUtils.h"
using namespace MUSIC_INFO;
using namespace XFILE;
-static int vfs_file_read(void *h, uint8_t* buf, int size)
+static int vfs_file_read(void* h, uint8_t* buf, int size)
{
CFile* pFile = static_cast(h);
return pFile->Read(buf, size);
}
-static int64_t vfs_file_seek(void *h, int64_t pos, int whence)
+static int64_t vfs_file_seek(void* h, int64_t pos, int whence)
{
CFile* pFile = static_cast(h);
if (whence == AVSEEK_SIZE)
@@ -35,7 +38,9 @@ CMusicInfoTagLoaderFFmpeg::CMusicInfoTagLoaderFFmpeg(void) = default;
CMusicInfoTagLoaderFFmpeg::~CMusicInfoTagLoaderFFmpeg() = default;
-bool CMusicInfoTagLoaderFFmpeg::Load(const std::string& strFileName, CMusicInfoTag& tag, EmbeddedArt *art)
+bool CMusicInfoTagLoaderFFmpeg::Load(const std::string& strFileName,
+ CMusicInfoTag& tag,
+ EmbeddedArt* art)
{
tag.SetLoaded(false);
@@ -48,9 +53,8 @@ bool CMusicInfoTagLoaderFFmpeg::Load(const std::string& strFileName, CMusicInfoT
if (blockSize > 1)
bufferSize = blockSize;
uint8_t* buffer = (uint8_t*)av_malloc(bufferSize);
- AVIOContext* ioctx = avio_alloc_context(buffer, bufferSize, 0,
- &file, vfs_file_read, NULL,
- vfs_file_seek);
+ AVIOContext* ioctx =
+ avio_alloc_context(buffer, bufferSize, 0, &file, vfs_file_read, NULL, vfs_file_seek);
AVFormatContext* fctx = avformat_alloc_context();
fctx->pb = ioctx;
@@ -85,77 +89,141 @@ bool CMusicInfoTagLoaderFFmpeg::Load(const std::string& strFileName, CMusicInfoT
Any changes to ID3v2 tag processing in CTagLoaderTagLib need to be
repeated here
*/
- auto&& ParseTag = [&tag](AVDictionaryEntry* avtag)
- {
- if (StringUtils::CompareNoCase(avtag->key, "album") == 0)
- tag.SetAlbum(avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key, "artist") == 0)
- tag.SetArtist(avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key, "album_artist") == 0 ||
- StringUtils::CompareNoCase(avtag->key, "album artist") == 0)
- tag.SetAlbumArtist(avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key, "title") == 0)
- tag.SetTitle(avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key, "genre") == 0)
- tag.SetGenre(avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key, "part_number") == 0 ||
- StringUtils::CompareNoCase(avtag->key, "track") == 0)
- tag.SetTrackNumber(
- static_cast(strtol(avtag->value, nullptr, 10)));
- else if (StringUtils::CompareNoCase(avtag->key, "disc") == 0)
- tag.SetDiscNumber(
- static_cast(strtol(avtag->value, nullptr, 10)));
- else if (StringUtils::CompareNoCase(avtag->key, "date") == 0)
- tag.SetReleaseDate(avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key, "compilation") == 0)
- tag.SetCompilation((strtol(avtag->value, nullptr, 10) == 0) ? false : true);
- else if (StringUtils::CompareNoCase(avtag->key, "encoded_by") == 0) {}
- else if (StringUtils::CompareNoCase(avtag->key, "composer") == 0)
- tag.AddArtistRole("Composer", avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key, "performer") == 0) // Conductor or TPE3 tag
- tag.AddArtistRole("Conductor", avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key, "TEXT") == 0)
- tag.AddArtistRole("Lyricist", avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key, "TPE4") == 0)
- tag.AddArtistRole("Remixer", avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key, "LABEL") == 0 ||
- StringUtils::CompareNoCase(avtag->key, "TPUB") == 0)
- tag.SetRecordLabel(avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key, "copyright") == 0 ||
- StringUtils::CompareNoCase(avtag->key, "TCOP") == 0) {} // Copyright message
- else if (StringUtils::CompareNoCase(avtag->key, "TDRC") == 0)
- tag.SetReleaseDate(avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key, "TDOR") == 0 ||
- StringUtils::CompareNoCase(avtag->key, "TORY") == 0)
- tag.SetOriginalDate(avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key , "TDAT") == 0)
- tag.AddReleaseDate(avtag->value, true); // MMDD part
- else if (StringUtils::CompareNoCase(avtag->key, "TYER") == 0)
- tag.AddReleaseDate(avtag->value); // YYYY part
- else if (StringUtils::CompareNoCase(avtag->key, "TBPM") == 0)
- tag.SetBPM(static_cast(strtol(avtag->value, nullptr, 10)));
- else if (StringUtils::CompareNoCase(avtag->key, "TDTG") == 0) {} // Tagging time
- else if (StringUtils::CompareNoCase(avtag->key, "language") == 0 ||
- StringUtils::CompareNoCase(avtag->key, "TLAN") == 0) {} // Languages
- else if (StringUtils::CompareNoCase(avtag->key, "mood") == 0 ||
- StringUtils::CompareNoCase(avtag->key, "TMOO") == 0)
- tag.SetMood(avtag->value);
- else if (StringUtils::CompareNoCase(avtag->key, "artist-sort") == 0 ||
- StringUtils::CompareNoCase(avtag->key, "TSOP") == 0) {}
- else if (StringUtils::CompareNoCase(avtag->key, "TSO2") == 0) {} // Album artist sort
- else if (StringUtils::CompareNoCase(avtag->key, "TSOC") == 0) {} // composer sort
- else if (StringUtils::CompareNoCase(avtag->key, "TSST") == 0)
- tag.SetDiscSubtitle(avtag->value);
- };
-
- AVDictionaryEntry* avtag=nullptr;
+ std::vector separators{" feat. ", " ft. ", " Feat. ", " Ft. ", ";", ":",
+ "|", "#", "/", " with ", "&"};
+ const std::string musicsep =
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator;
+ if (musicsep.find_first_of(";/,&|#") == std::string::npos)
+ separators.push_back(musicsep);
+ std::vector tagdata;
+ std::string value;
+
+ auto&& ParseTag = [&](AVDictionaryEntry* avtag)
+ {
+ std::string key = StringUtils::ToUpper(avtag->key);
+ std::string value = avtag->value;
+ if (key == "ALBUM")
+ tag.SetAlbum(value);
+ else if (key == "ARTIST")
+ tag.SetArtist(value);
+ else if (key == "ALBUM_ARTIST" || key == "ALBUM ARTIST")
+ tag.SetAlbumArtist(value);
+ else if (key == "TITLE")
+ tag.SetTitle(value);
+ else if (key == "GENRE")
+ tag.SetGenre(value);
+ else if (key == "PART_NUMBER" || key == "TRACK")
+ tag.SetTrackNumber(std::stoi(value));
+ else if (key == "DISC")
+ tag.SetDiscNumber(std::stoi(value));
+ else if (key == "DATE")
+ tag.SetReleaseDate(value);
+ else if (key == "COMPILATION" || key == "TCMP")
+ tag.SetCompilation((std::stoi(value)) ? false : true);
+ else if (key == "ENCODED_BY")
+ {
+ }
+ else if (key == "COMPOSER")
+ tag.AddArtistRole("Composer", value);
+ else if (key == "PERFORMER") // Conductor or TPE3 tag
+ tag.AddArtistRole("Conductor", value);
+ else if (key == "TEXT")
+ tag.AddArtistRole("Lyricist", value);
+ else if (key == "TPE4")
+ tag.AddArtistRole("Remixer", value);
+ else if (key == "LABEL" || key == "TPUB")
+ tag.SetRecordLabel(value);
+ else if (key == "COPYRIGHT" || key == "TCOP")
+ {
+ } // Copyright message
+ else if (key == "TDRC")
+ tag.SetReleaseDate(value);
+ else if (key == "TDOR" || key == "TORY")
+ tag.SetOriginalDate(value);
+ else if (key == "TDAT")
+ tag.AddReleaseDate(value, true); // MMDD part
+ else if (key == "TYER")
+ tag.AddReleaseDate(value); // YYYY part
+ else if (key == "TBPM")
+ tag.SetBPM(std::stoi(value));
+ else if (key == "TDTG")
+ {
+ } // Tagging time
+ else if (key == "LANGUAGE" || key == "TLAN")
+ {
+ } // Languages
+ else if (key == "MOOD" || key == "TMOO")
+ tag.SetMood(value);
+ else if (key == "ARTIST-SORT" || key == "TSOP" || key == "ARTISTSORT" || key == "ARTIST SORT")
+ tag.SetArtistSort(StringUtils::Join(StringUtils::Split(value, separators), musicsep));
+ else if (key == "TSO2" || key == "ALBUMARTISTSORT" || key == "ALBUM ARTIST SORT")
+ tag.SetAlbumArtistSort(StringUtils::Join(StringUtils::Split(value, separators), musicsep));
+ else if (key == "TSOC" || key == "COMPOSERSORT" || key == "COMPOSER SORT")
+ tag.SetComposerSort(StringUtils::Join(StringUtils::Split(value, separators), musicsep));
+ else if (key == "TSST")
+ tag.SetDiscSubtitle(value);
+ // the above values are all id3v2.3/4 frames, we could also have text frames
+ else if (key == "MUSICBRAINZ ARTIST ID")
+ tag.SetMusicBrainzArtistID(StringUtils::Split(value, separators));
+ else if (key == "MUSICBRAINZ ALBUM ID")
+ tag.SetMusicBrainzAlbumID(value);
+ else if (key == "MUSICBRAINZ RELEASEGROUP ID")
+ tag.SetMusicBrainzReleaseGroupID(value);
+ else if (key == "MUSICBRAINZ ALBUM ARTIST ID")
+ tag.SetMusicBrainzAlbumArtistID(StringUtils::Split(value, separators));
+ else if (key == "MUSICBRAINZ ALBUM ARTIST")
+ tag.SetAlbumArtist(value);
+ else if (key == "MUSICBRAINZ ALBUM TYPE")
+ tag.SetMusicBrainzReleaseType(value);
+ else if (key == "MUSICBRAINZ ALBUM STATUS")
+ tag.SetAlbumReleaseStatus(value);
+ else if (key == "ALBUM ARTIST" || key == "ALBUMARTIST")
+ tag.SetAlbumArtist(value);
+ else if (key == "ALBUM ARTIST SORT" || key == "ALBUMARTISTSORT")
+ tag.SetAlbumArtistSort(value);
+ else if (key == "ARTISTS")
+ tag.SetMusicBrainzArtistHints(StringUtils::Split(value, separators));
+ else if (key == "ALBUMARTISTS" || key == "ALBUM ARTISTS")
+ tag.SetMusicBrainzAlbumArtistHints(StringUtils::Split(value, separators));
+ else if (key == "WRITER")
+ tag.AddArtistRole("Writer", StringUtils::Split(value, separators));
+ else if (key == "PERFORMER")
+ {
+ tagdata = StringUtils::Split(avtag->key, separators);
+ AddRole(tagdata, separators, tag);
+ }
+ else if (key == "ARRANGER")
+ {
+ tagdata = StringUtils::Split(avtag->key, separators);
+ AddRole(tagdata, separators, tag);
+ }
+ else if (key == "LYRICIST")
+ tag.AddArtistRole("Lyricist", StringUtils::Split(value, separators));
+ else if (key == "COMPOSER")
+ tag.AddArtistRole("Composer", StringUtils::Split(value, separators));
+ else if (key == "CONDUCTOR")
+ tag.AddArtistRole("Conductor", StringUtils::Split(value, separators));
+ else if (key == "ENGINEER")
+ tag.AddArtistRole("Engineer", StringUtils::Split(value, separators));
+ };
+
+ AVDictionaryEntry* avtag = nullptr;
while ((avtag = av_dict_get(fctx->metadata, "", avtag, AV_DICT_IGNORE_SUFFIX)))
ParseTag(avtag);
- const AVStream* st = fctx->streams[0];
- if (st)
- while ((avtag = av_dict_get(st->metadata, "", avtag, AV_DICT_IGNORE_SUFFIX)))
- ParseTag(avtag);
+ // Look for any embedded cover art
+ CMusicEmbeddedCoverLoaderFFmpeg::GetEmbeddedCover(fctx, tag, art);
+ bool haveFFmpegInfo = false;
+ musicCodecInfo codec_info;
+ haveFFmpegInfo = CMusicCodecInfoFFmpeg::GetMusicCodecInfo(strFileName, codec_info);
+ if (haveFFmpegInfo) // use data from FFmpeg if taglib data missing or not accurate
+ {
+ tag.SetBitRate(codec_info.bitRate);
+ tag.SetSampleRate(codec_info.sampleRate);
+ tag.SetBitsPerSample(codec_info.bitsPerSample);
+ tag.SetCodec(codec_info.codecName);
+ tag.SetNoOfChannels(codec_info.channels);
+ }
if (!tag.GetTitle().empty())
tag.SetLoaded(true);
@@ -166,3 +234,22 @@ bool CMusicInfoTagLoaderFFmpeg::Load(const std::string& strFileName, CMusicInfoT
return true;
}
+
+void CMusicInfoTagLoaderFFmpeg::AddRole(const std::vector& data,
+ const std::vector& separators,
+ MUSIC_INFO::CMusicInfoTag& musictag)
+{
+ if (!data.empty())
+ {
+ for (size_t i = 0; i + 1 < data.size(); i += 2)
+ {
+ std::vector roles = StringUtils::Split(data[i], separators);
+ for (auto& role : roles)
+ {
+ StringUtils::Trim(role);
+ StringUtils::ToCapitalize(role);
+ musictag.AddArtistRole(role, StringUtils::Split(data[i + 1], separators));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/xbmc/music/tags/MusicInfoTagLoaderFFmpeg.h b/xbmc/music/tags/MusicInfoTagLoaderFFmpeg.h
index 272c0659e3eac..7088fe569c70f 100644
--- a/xbmc/music/tags/MusicInfoTagLoaderFFmpeg.h
+++ b/xbmc/music/tags/MusicInfoTagLoaderFFmpeg.h
@@ -9,6 +9,9 @@
#pragma once
#include "ImusicInfoTagLoader.h"
+#include "MusicCodecInfoFFmpeg.h"
+
+#include
namespace MUSIC_INFO
{
@@ -19,5 +22,9 @@ namespace MUSIC_INFO
~CMusicInfoTagLoaderFFmpeg() override;
bool Load(const std::string& strFileName, CMusicInfoTag& tag, EmbeddedArt *art = NULL) override;
+ private:
+ void AddRole(const std::vector& data,
+ const std::vector& separators,
+ MUSIC_INFO::CMusicInfoTag& musictag);
};
}
diff --git a/xbmc/music/tags/MusicInfoTagLoaderFactory.cpp b/xbmc/music/tags/MusicInfoTagLoaderFactory.cpp
index 918442b629db1..19b1f45808e62 100644
--- a/xbmc/music/tags/MusicInfoTagLoaderFactory.cpp
+++ b/xbmc/music/tags/MusicInfoTagLoaderFactory.cpp
@@ -87,7 +87,7 @@ IMusicInfoTagLoader* CMusicInfoTagLoaderFactory::CreateLoader(const CFileItem& i
CMusicInfoTagLoaderSHN *pTagLoader = new CMusicInfoTagLoaderSHN();
return pTagLoader;
}
- else if (strExtension == "mka" || strExtension == "dsf" ||
+ else if (strExtension == "mka" || strExtension == "dsf" || strExtension == "mkv" ||
strExtension == "dff")
return new CMusicInfoTagLoaderFFmpeg();
diff --git a/xbmc/music/tags/TagLoaderTagLib.cpp b/xbmc/music/tags/TagLoaderTagLib.cpp
index c848b9a4bcc71..bcca0f5277e96 100644
--- a/xbmc/music/tags/TagLoaderTagLib.cpp
+++ b/xbmc/music/tags/TagLoaderTagLib.cpp
@@ -59,6 +59,29 @@
#include
#include
#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "TagLibVFSStream.h"
+#include "MusicInfoTag.h"
+#include "MusicCodecInfoFFmpeg.h"
+#include "ReplayGain.h"
+#include "utils/RegExp.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "utils/StringUtils.h"
+#include "ServiceBroker.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
#if TAGLIB_MAJOR_VERSION <= 1 && TAGLIB_MINOR_VERSION < 11
#include "utils/Base64.h"
@@ -1379,54 +1402,150 @@ bool CTagLoaderTagLib::Load(const std::string& strFileName, CMusicInfoTag& tag,
Ogg::XiphComment *xiph = nullptr;
Tag *genericTag = nullptr;
- if (apeFile)
- ape = apeFile->APETag(false);
- else if (asfFile)
- asf = asfFile->tag();
- else if (flacFile)
- {
- xiph = flacFile->xiphComment(false);
- id3v2 = flacFile->ID3v2Tag(false);
- }
- else if (mp4File)
- mp4 = mp4File->tag();
- else if (mpegFile)
+ // bitspersample is file specific and not always available through file->audioProperties(). EG,
+ // mp3 files do not have bitspersample as this is determined by the player whilst decoding so grab
+ // it separately for those files that do contain it.
+
+ try
{
- id3v1 = mpegFile->ID3v1Tag(false);
- id3v2 = mpegFile->ID3v2Tag(false);
- ape = mpegFile->APETag(false);
+ unsigned int bitsPerSample = 0;
+ int mpegLayer = 0;
+ musicCodecInfo codec_info;
+ std::string codec;
+ bool haveFFmpegInfo = false;
+
+ if (apeFile)
+ {
+ ape = apeFile->APETag(false);
+ if (apeFile->audioProperties())
+ bitsPerSample = apeFile->audioProperties()->bitsPerSample();
+ codec = CodecToString(MusicCodecType::CODEC_TYPE_APE);
+ }
+ else if (asfFile)
+ {
+ asf = asfFile->tag();
+ if (asfFile->audioProperties())
+ bitsPerSample = asfFile->audioProperties()->bitsPerSample();
+ codec = asfFile->audioProperties()->codecName().to8Bit(true);
+ }
+ else if (flacFile)
+ {
+ xiph = flacFile->xiphComment(false);
+ if (flacFile->audioProperties())
+ bitsPerSample = flacFile->audioProperties()->bitsPerSample();
+ id3v2 = flacFile->ID3v2Tag(false);
+ codec = CodecToString(MusicCodecType::CODEC_TYPE_FLAC);
+ }
+ else if (mp4File)
+ {
+ mp4 = mp4File->tag();
+ if (mp4File->audioProperties())
+ {
+ bitsPerSample = mp4File->audioProperties()->bitsPerSample();
+ //taglib only recognizes two codecs for mp4 files although mp4 is a container and could contain
+ //any audio codec. If taglib fails to recognize it, use FFmpeg to detect the codec
+ if (mp4File->audioProperties()->codec() != 0)
+ codec = (mp4File->audioProperties()->codec() == 1)
+ ? CodecToString(MusicCodecType::CODEC_TYPE_AAC)
+ : CodecToString(MusicCodecType::CODEC_TYPE_ALAC);
+ else
+ haveFFmpegInfo = CMusicCodecInfoFFmpeg::GetMusicCodecInfo(strFileName, codec_info);
+ }
+ }
+ else if (mpegFile)
+ {
+ id3v1 = mpegFile->ID3v1Tag(false);
+ id3v2 = mpegFile->ID3v2Tag(false);
+ ape = mpegFile->APETag(false);
+ if (mpegFile->audioProperties())
+ {
+ mpegLayer = mpegFile->audioProperties()->layer();
+ codec = CodecToString(MusicCodecType::CODEC_TYPE_MPEG) + std::to_string(mpegLayer);
+ }
+ }
+ else if (oggFlacFile)
+ {
+ xiph = oggFlacFile->tag();
+ if (oggFlacFile->audioProperties())
+ bitsPerSample = oggFlacFile->audioProperties()->bitsPerSample();
+ codec = CodecToString(MusicCodecType::CODEC_TYPE_FLAC);
+ }
+ else if (oggVorbisFile)
+ {
+ xiph = oggVorbisFile->tag();
+ codec = CodecToString(MusicCodecType::CODEC_TYPE_VORBIS);
+ }
+ else if (oggOpusFile)
+ {
+ xiph = oggOpusFile->tag();
+ codec = CodecToString(MusicCodecType::CODEC_TYPE_OPUS);
+ }
+ else if (ttaFile)
+ {
+ id3v2 = ttaFile->ID3v2Tag(false);
+ if (ttaFile->audioProperties())
+ bitsPerSample = ttaFile->audioProperties()->bitsPerSample();
+ codec = CodecToString(MusicCodecType::CODEC_TYPE_TTA);
+ }
+ else if (aiffFile)
+ {
+ id3v2 = aiffFile->tag();
+ if (aiffFile->audioProperties())
+ {
+ bitsPerSample = aiffFile->audioProperties()->bitsPerSample();
+ codec = aiffFile->audioProperties()->compressionName().to8Bit(true);
+ }
+ }
+ else if (wavFile)
+ {
+ id3v2 = wavFile->ID3v2Tag();
+ // taglib doesn't detect the correct codec if the wav file wraps eg DTS as the file will have a
+ // dummy header indicating PCM. Therefore use FFmpeg for wav files so detection doesn't rely on
+ // the header info
+ haveFFmpegInfo = CMusicCodecInfoFFmpeg::GetMusicCodecInfo(strFileName, codec_info);
+ }
+ else if (wvFile)
+ {
+ ape = wvFile->APETag(false);
+ if (wvFile->audioProperties())
+ bitsPerSample = wvFile->audioProperties()->bitsPerSample();
+ codec = CodecToString(MusicCodecType::CODEC_TYPE_WAVPACK);
+ }
+ else if (mpcFile)
+ ape = mpcFile->APETag(false);
+ else // This is a catch all to get generic information for other files types (s3m, xm, it, mod, etc)
+ genericTag = file->tag();
+
+ if (file->audioProperties())
+ {
+ tag.SetDuration(file->audioProperties()->length());
+ tag.SetBitRate(file->audioProperties()->bitrate());
+ tag.SetNoOfChannels(file->audioProperties()->channels());
+ tag.SetSampleRate(file->audioProperties()->sampleRate());
+ }
+
+ if(bitsPerSample)
+ tag.SetBitsPerSample(bitsPerSample);
+ else if (!mpegFile) // skip mp3 files as no bitspersample available but check other filetypes as
+ // taglib returns the wrong bitrate for at least vorbis files (determined
+ // locally using taglib data, mediainfo and ffprobe for comparisons)
+ haveFFmpegInfo = CMusicCodecInfoFFmpeg::GetMusicCodecInfo(strFileName, codec_info);
+ if (!codec.empty())
+ tag.SetCodec(codec);
+
+ if (haveFFmpegInfo) // use data from FFmpeg if taglib data missing or not accurate
+ {
+ tag.SetBitRate(codec_info.bitRate);
+ tag.SetSampleRate(codec_info.sampleRate);
+ tag.SetBitsPerSample(codec_info.bitsPerSample);
+ tag.SetCodec(codec_info.codecName);
+ tag.SetNoOfChannels(codec_info.channels);
+ }
}
- else if (oggFlacFile)
- xiph = oggFlacFile->tag();
- else if (oggVorbisFile)
- xiph = oggVorbisFile->tag();
- else if (oggOpusFile)
- xiph = oggOpusFile->tag();
- else if (ttaFile)
- id3v2 = ttaFile->ID3v2Tag(false);
- else if (aiffFile)
- id3v2 = aiffFile->tag();
- else if (wavFile)
- id3v2 = wavFile->ID3v2Tag();
- else if (wvFile)
- ape = wvFile->APETag(false);
- else if (mpcFile)
- ape = mpcFile->APETag(false);
- else // This is a catch all to get generic information for other files types (s3m, xm, it, mod, etc)
- genericTag = file->tag();
-
- if (file->audioProperties())
+ catch (const std::exception& ex)
{
-#if (TAGLIB_MAJOR_VERSION >= 2)
- tag.SetDuration(file->audioProperties()->lengthInSeconds());
-#else
- tag.SetDuration(file->audioProperties()->length());
-#endif
- tag.SetBitRate(file->audioProperties()->bitrate());
- tag.SetNoOfChannels(file->audioProperties()->channels());
- tag.SetSampleRate(file->audioProperties()->sampleRate());
+ CLog::Log(LOGERROR, "Taglib exception: {}", ex.what());
}
-
if (asf)
ParseTag(asf, art, tag);
if (id3v1)
@@ -1459,3 +1578,30 @@ bool CTagLoaderTagLib::Load(const std::string& strFileName, CMusicInfoTag& tag,
return true;
}
+
+std::string CodecToString(const MusicCodecType& codecType)
+{
+ switch(codecType)
+ {
+ case MusicCodecType::CODEC_TYPE_AAC:
+ return "aac";
+ case MusicCodecType::CODEC_TYPE_ALAC:
+ return "alac";
+ case MusicCodecType::CODEC_TYPE_APE:
+ return "ape";
+ case MusicCodecType::CODEC_TYPE_FLAC:
+ return "flac";
+ case MusicCodecType::CODEC_TYPE_MPEG:
+ return "mp"; // layer is added to the return value to make mp2/mp3
+ case MusicCodecType::CODEC_TYPE_OPUS:
+ return "opus";
+ case MusicCodecType::CODEC_TYPE_TTA:
+ return "tta";
+ case MusicCodecType::CODEC_TYPE_VORBIS:
+ return "vorbis";
+ case MusicCodecType::CODEC_TYPE_WAVPACK:
+ return "wavpack";
+ default:
+ return "";
+ }
+}
diff --git a/xbmc/music/tags/TagLoaderTagLib.h b/xbmc/music/tags/TagLoaderTagLib.h
index 689506985f2fa..c51d12601bec6 100644
--- a/xbmc/music/tags/TagLoaderTagLib.h
+++ b/xbmc/music/tags/TagLoaderTagLib.h
@@ -21,6 +21,19 @@ namespace MUSIC_INFO
class CMusicInfoTag;
};
+enum class MusicCodecType
+{
+ CODEC_TYPE_APE,
+ CODEC_TYPE_FLAC,
+ CODEC_TYPE_AAC,
+ CODEC_TYPE_ALAC,
+ CODEC_TYPE_MPEG,
+ CODEC_TYPE_VORBIS,
+ CODEC_TYPE_OPUS,
+ CODEC_TYPE_TTA,
+ CODEC_TYPE_WAVPACK
+};
+
class CTagLoaderTagLib : public MUSIC_INFO::IMusicInfoTagLoader
{
public:
@@ -57,3 +70,4 @@ class CTagLoaderTagLib : public MUSIC_INFO::IMusicInfoTagLoader
// chapter (if any) extends to the end of the file.
std::chrono::milliseconds totalLenMs = {});
};
+ std::string CodecToString(const MusicCodecType& codecType);
diff --git a/xbmc/playlists/SmartPlayList.cpp b/xbmc/playlists/SmartPlayList.cpp
index e75e7e5a4772b..f984b7ec4f123 100644
--- a/xbmc/playlists/SmartPlayList.cpp
+++ b/xbmc/playlists/SmartPlayList.cpp
@@ -6,14 +6,14 @@
* See LICENSES/README.md for more information.
*/
-#include "SmartPlayList.h"
-
#include "ServiceBroker.h"
+#include "SmartPlayList.h"
#include "Util.h"
#include "XBDateTime.h"
#include "dbwrappers/Database.h"
#include "filesystem/File.h"
#include "filesystem/SmartPlaylistDirectory.h"
+#include "music/MusicType.h"
#include "resources/LocalizeStrings.h"
#include "resources/ResourcesComponent.h"
#include "settings/Settings.h"
@@ -144,6 +144,10 @@ static const auto fields = std::array{
TranslateField{ "hasversions", Field::HAS_VIDEO_VERSIONS, BOOLEAN_FIELD, nullptr, false, 20475 },
TranslateField{ "hasextras", Field::HAS_VIDEO_EXTRAS, BOOLEAN_FIELD, nullptr, false, 20476 },
TranslateField{ "hdrdetail", Field::HDR_DETAIL, TEXTIN_FIELD, nullptr, false, 20478 },
+ TranslateField{ "albumcodec", Field::ALBUM_CODEC, TEXT_FIELD, nullptr, true, 21446 },
+ TranslateField{ "bitspersample", Field::BITS_PER_SAMPLE, TEXT_FIELD, nullptr, true, 612 },
+ TranslateField{ "ismusicconcert", Field::IS_MUSIC_CONCERT, BOOLEAN_FIELD, nullptr, false, 21486},
+ TranslateField{ "isaudiobook", Field::IS_AUDIOBOOK, BOOLEAN_FIELD, nullptr, false, 21487},
};
// clang-format on
@@ -178,7 +182,7 @@ constexpr std::string_view RULE_VALUE_SEPARATOR = " / ";
CSmartPlaylistRule::CSmartPlaylistRule() = default;
-int CSmartPlaylistRule::TranslateField(const char *field) const
+int CSmartPlaylistRule::TranslateField(const char* field) const
{
const auto it = std::ranges::find_if(fields, [field](const auto& f)
{ return StringUtils::EqualsNoCase(field, f.string); });
@@ -192,7 +196,7 @@ std::string CSmartPlaylistRule::TranslateField(int field) const
return it == fields.end() ? "none" : std::string(it->string);
}
-SortBy CSmartPlaylistRule::TranslateOrder(const char *order)
+SortBy CSmartPlaylistRule::TranslateOrder(const char* order)
{
return SortUtils::SortMethodFromString(order);
}
@@ -206,7 +210,7 @@ std::string CSmartPlaylistRule::TranslateOrder(SortBy order)
return sortOrder;
}
-Field CSmartPlaylistRule::TranslateGroup(const char *group)
+Field CSmartPlaylistRule::TranslateGroup(const char* group)
{
const auto it = std::ranges::find_if(groups, [group](const auto& g)
{ return StringUtils::EqualsNoCase(group, g.name); });
@@ -267,7 +271,7 @@ bool CSmartPlaylistRule::Validate(const std::string& input, void* data)
{ return validator(s, data); });
}
-bool CSmartPlaylistRule::ValidateRating(const std::string &input, void *data)
+bool CSmartPlaylistRule::ValidateRating(const std::string& input, void* data)
{
char* end = nullptr;
std::string strRating = input;
@@ -277,7 +281,7 @@ bool CSmartPlaylistRule::ValidateRating(const std::string &input, void *data)
return (end == nullptr || *end == '\0') && rating >= 0.0 && rating <= 10.0;
}
-bool CSmartPlaylistRule::ValidateMyRating(const std::string &input, void *data)
+bool CSmartPlaylistRule::ValidateMyRating(const std::string& input, void* data)
{
std::string strRating = input;
StringUtils::Trim(strRating);
@@ -302,7 +306,7 @@ bool CSmartPlaylistRule::ValidateDate(const std::string& input, void* data)
return dt.SetFromRFC3339FullDate(input);
}
-std::vector CSmartPlaylistRule::GetFields(const std::string &type)
+std::vector CSmartPlaylistRule::GetFields(const std::string& type)
{
std::vector fields;
bool isVideo = false;
@@ -322,7 +326,7 @@ std::vector CSmartPlaylistRule::GetFields(const std::string &type)
Field::ARTIST, Field::ALBUM_ARTIST, Field::TITLE, Field::YEAR,
};
if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
- CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
+ CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
fields.push_back(Field::ORIG_YEAR);
fields.insert(fields.end(), {
Field::TIME,
@@ -342,6 +346,7 @@ std::vector CSmartPlaylistRule::GetFields(const std::string &type)
Field::DATE_ADDED,
Field::DATE_MODIFIED,
Field::DATE_NEW,
+ Field::IS_AUDIOBOOK,
});
}
else if (type == "albums")
@@ -354,7 +359,7 @@ std::vector CSmartPlaylistRule::GetFields(const std::string &type)
Field::YEAR,
};
if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
- CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
+ CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
fields.push_back(Field::ORIG_YEAR);
fields.insert(fields.end(), {
Field::ALBUM_DURATION,
@@ -374,6 +379,9 @@ std::vector CSmartPlaylistRule::GetFields(const std::string &type)
Field::DATE_ADDED,
Field::DATE_MODIFIED,
Field::DATE_NEW,
+ Field::IS_AUDIOBOOK,
+ Field::IS_MUSIC_CONCERT,
+
});
}
else if (type == "artists")
@@ -514,7 +522,7 @@ std::vector CSmartPlaylistRule::GetOrders(const std::string& type)
SortBy::NONE, SortBy::GENRE, SortBy::ALBUM, SortBy::ARTIST, SortBy::TITLE, SortBy::YEAR,
};
if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
- CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
+ CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
orders.push_back(SortBy::ORIG_DATE);
orders.insert(orders.end(), {
SortBy::TIME,
@@ -537,7 +545,7 @@ std::vector CSmartPlaylistRule::GetOrders(const std::string& type)
SortBy::YEAR,
};
if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
- CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
+ CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
orders.push_back(SortBy::ORIG_DATE);
orders.insert(orders.end(), {
SortBy::ALBUM_TYPE,
@@ -608,7 +616,7 @@ std::vector CSmartPlaylistRule::GetOrders(const std::string& type)
return orders;
}
-std::vector CSmartPlaylistRule::GetGroups(const std::string &type)
+std::vector CSmartPlaylistRule::GetGroups(const std::string& type)
{
std::vector groups;
if (type == "artists")
@@ -625,7 +633,7 @@ std::vector CSmartPlaylistRule::GetGroups(const std::string &type)
Field::YEAR,
};
if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
- CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
+ CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
groups.push_back(Field::ORIG_YEAR);
}
if (type == "movies")
@@ -672,7 +680,7 @@ std::string CSmartPlaylistRule::GetLocalizedRule() const
GetLocalizedOperator(m_operator), GetParameter());
}
-std::string CSmartPlaylistRule::GetVideoResolutionQuery(const std::string ¶meter) const
+std::string CSmartPlaylistRule::GetVideoResolutionQuery(const std::string& parameter) const
{
std::string retVal(" IN (SELECT DISTINCT idFile FROM streamdetails WHERE iVideoWidth ");
int iRes = static_cast(std::strtol(parameter.c_str(), nullptr, 10));
@@ -683,10 +691,26 @@ std::string CSmartPlaylistRule::GetVideoResolutionQuery(const std::string ¶m
min = 1921;
max = INT_MAX;
}
- else if (iRes >= 1080) { min = 1281; max = 1920; }
- else if (iRes >= 720) { min = 961; max = 1280; }
- else if (iRes >= 540) { min = 721; max = 960; }
- else { min = 0; max = 720; }
+ else if (iRes >= 1080)
+ {
+ min = 1281;
+ max = 1920;
+ }
+ else if (iRes >= 720)
+ {
+ min = 961;
+ max = 1280;
+ }
+ else if (iRes >= 540)
+ {
+ min = 721;
+ max = 960;
+ }
+ else
+ {
+ min = 0;
+ max = 720;
+ }
switch (m_operator)
{
@@ -710,12 +734,14 @@ std::string CSmartPlaylistRule::GetVideoResolutionQuery(const std::string ¶m
return retVal;
}
-std::string CSmartPlaylistRule::GetBooleanQuery(const std::string &negate, const std::string &strType) const
+std::string CSmartPlaylistRule::GetBooleanQuery(const std::string& negate,
+ const std::string& strType) const
{
if (strType == "movies")
{
if (m_field == static_cast(Field::IN_PROGRESS))
- return "movie_view.idFile " + negate + " IN (SELECT DISTINCT idFile FROM bookmark WHERE type = 1)";
+ return "movie_view.idFile " + negate +
+ " IN (SELECT DISTINCT idFile FROM bookmark WHERE type = 1)";
else if (m_field == static_cast(Field::TRAILER))
return negate + GetField(m_field, strType) + "!= ''";
else if (m_field == static_cast(Field::HAS_VIDEO_VERSIONS) ||
@@ -725,14 +751,16 @@ std::string CSmartPlaylistRule::GetBooleanQuery(const std::string &negate, const
else if (strType == "episodes")
{
if (m_field == static_cast(Field::IN_PROGRESS))
- return "episode_view.idFile " + negate + " IN (SELECT DISTINCT idFile FROM bookmark WHERE type = 1)";
+ return "episode_view.idFile " + negate +
+ " IN (SELECT DISTINCT idFile FROM bookmark WHERE type = 1)";
}
else if (strType == "tvshows")
{
if (m_field == static_cast(Field::IN_PROGRESS))
return negate +
" ("
- "(tvshow_view.watchedcount > 0 AND tvshow_view.watchedcount < tvshow_view.totalCount) "
+ "(tvshow_view.watchedcount > 0 AND tvshow_view.watchedcount < "
+ "tvshow_view.totalCount) "
"OR "
"(tvshow_view.watchedcount = 0 AND EXISTS "
"(SELECT 1 FROM episode_view WHERE episode_view.idShow = " +
@@ -749,6 +777,17 @@ std::string CSmartPlaylistRule::GetBooleanQuery(const std::string &negate, const
return negate + GetField(m_field, strType);
if (m_field == static_cast(Field::IS_BOXSET))
return negate + "albumview.bBoxedSet = 1";
+ if (m_field == static_cast(Field::IS_MUSIC_CONCERT))
+ {
+ std::string SQL;
+ SQL = StringUtils::Format("albumview.strReleaseType like '{}'",
+ AudioType::ToString(AudioType::Content::Concert).c_str());
+ return negate + SQL;
+ }
+ if (m_field == static_cast(Field::IS_AUDIOBOOK))
+ return negate +
+ StringUtils::Format("albumview.strReleaseType like '{}'",
+ AudioType::ToString(AudioType::Content::AudioBook).c_str());
}
return "";
}
@@ -767,7 +806,10 @@ CDatabaseQueryRule::SearchOperator CSmartPlaylistRule::GetOperator(const std::st
return op;
}
-std::string CSmartPlaylistRule::FormatParameter(const std::string &operatorString, const std::string ¶m, const CDatabase &db, const std::string &strType) const
+std::string CSmartPlaylistRule::FormatParameter(const std::string& operatorString,
+ const std::string& param,
+ const CDatabase& db,
+ const std::string& strType) const
{
// special-casing
if (m_field == static_cast(Field::TIME) ||
@@ -779,7 +821,11 @@ std::string CSmartPlaylistRule::FormatParameter(const std::string &operatorStrin
return CDatabaseQueryRule::FormatParameter(operatorString, param, db, strType);
}
-std::string CSmartPlaylistRule::FormatLinkQuery(const char *field, const char *table, const MediaType& mediaType, const std::string& mediaField, const std::string& parameter)
+std::string CSmartPlaylistRule::FormatLinkQuery(const char* field,
+ const char* table,
+ const MediaType& mediaType,
+ const std::string& mediaField,
+ const std::string& parameter)
{
// NOTE: no need for a PrepareSQL here, as the parameter has already been formatted
return StringUtils::Format(
@@ -833,8 +879,11 @@ std::string FormatNullableNumber(const std::string& field,
}
} // namespace
-std::string CSmartPlaylistRule::FormatWhereClause(const std::string &negate, const std::string &oper, const std::string ¶m,
- const CDatabase &db, const std::string &strType) const
+std::string CSmartPlaylistRule::FormatWhereClause(const std::string& negate,
+ const std::string& oper,
+ const std::string& param,
+ const CDatabase& db,
+ const std::string& strType) const
{
std::string parameter = FormatParameter(oper, param, db, strType);
@@ -853,17 +902,23 @@ std::string CSmartPlaylistRule::FormatWhereClause(const std::string &negate, con
GetField(static_cast(Field::ID), strType) +
" AND song_artist.idArtist = artist.idArtist AND artist.strArtist" + parameter + ")";
else if (m_field == static_cast(Field::ALBUM_ARTIST))
- 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 + ")";
+ 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 + ")";
else if (m_field == static_cast(Field::LAST_PLAYED))
query = FormatNullableDate(GetField(m_field, strType), m_operator, parameter);
else if (m_field == static_cast(Field::SOURCE))
- 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 + ")";
+ 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 + ")";
else if (m_field == static_cast(Field::YEAR) ||
m_field == static_cast(Field::ORIG_YEAR))
{
std::string field;
if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
- CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
+ CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE))
field = GetField(static_cast(Field::ORIG_YEAR), strType);
else
field = GetField(m_field, strType);
@@ -904,8 +959,21 @@ std::string CSmartPlaylistRule::FormatWhereClause(const std::string &negate, con
" AND album_source.idSource = source.idSource AND source.strName" + parameter + ")";
else if (m_field == static_cast(Field::DISC_TITLE))
query = negate + " EXISTS (SELECT 1 FROM song WHERE song.idAlbum = " +
+ " EXISTS (SELECT 1 FROM song WHERE song.idAlbum = " +
GetField(static_cast(Field::ID), strType) + " AND song.strDiscSubtitle" +
parameter + ")";
+ else if (m_field == static_cast(Field ::ALBUM_CODEC))
+ query = negate + " EXISTS (SELECT 1 FROM song WHERE song.idAlbum = " +
+ GetField(static_cast(Field::ALBUM_CODEC), strType) + " AND song.strCodec " +
+ parameter + ")";
+ else if (m_field == static_cast(Field::BITS_PER_SAMPLE))
+ query = negate + " EXISTS (SELECT 1 FROM song WHERE song.idAlbum = " +
+ GetField(static_cast(Field::BITS_PER_SAMPLE), strType) +
+ " AND song.iBitsPerSample " + parameter + ")";
+ else if (m_field == static_cast(Field::NUMBER_OF_CHANNELS))
+ query = negate + "EXISTS (SELECT 1 FROM song WHERE song.idAlbum = " +
+ GetField(static_cast(Field::NUMBER_OF_CHANNELS), strType) +
+ " AND song.iChannels " + parameter + ")";
else if (m_field == static_cast(Field::YEAR) ||
m_field == static_cast(Field::ORIG_YEAR))
{
@@ -949,7 +1017,9 @@ std::string CSmartPlaylistRule::FormatWhereClause(const std::string &negate, con
}
else if (m_field == static_cast(Field::PATH))
{
- 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 ";
+ 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 ";
query += "WHERE song_artist.idArtist = " + GetField(static_cast(Field::ID), strType) +
" AND path.strPath" + parameter + "))";
}
@@ -1060,9 +1130,11 @@ std::string CSmartPlaylistRule::FormatWhereClause(const std::string &negate, con
table = "episode_view";
if (m_field == static_cast(Field::GENRE))
- query = negate + FormatLinkQuery("genre", "genre", MediaTypeTvShow, (table + ".idShow").c_str(), parameter);
+ query = negate + FormatLinkQuery("genre", "genre", MediaTypeTvShow,
+ (table + ".idShow").c_str(), parameter);
else if (m_field == static_cast(Field::TAG))
- query = negate + FormatLinkQuery("tag", "tag", MediaTypeTvShow, (table + ".idShow").c_str(), parameter);
+ query = negate + FormatLinkQuery("tag", "tag", MediaTypeTvShow, (table + ".idShow").c_str(),
+ parameter);
else if (m_field == static_cast(Field::DIRECTOR))
query = negate + FormatLinkQuery("director", "actor", MediaTypeEpisode,
GetField(static_cast(Field::ID), strType), parameter);
@@ -1076,30 +1148,48 @@ std::string CSmartPlaylistRule::FormatWhereClause(const std::string &negate, con
m_field == static_cast(Field::DATE_ADDED))
query = FormatNullableDate(GetField(m_field, strType), m_operator, parameter);
else if (m_field == static_cast(Field::STUDIO))
- query = negate + FormatLinkQuery("studio", "studio", MediaTypeTvShow, (table + ".idShow").c_str(), parameter);
+ query = negate + FormatLinkQuery("studio", "studio", MediaTypeTvShow,
+ (table + ".idShow").c_str(), parameter);
else if (m_field == static_cast(Field::MPAA))
- query = negate + " (" + GetField(m_field, strType) + parameter + ")";
+ query = negate + " (" + GetField(m_field, strType) + parameter + ")";
}
if (m_field == static_cast(Field::VIDEO_RESOLUTION))
query = table + ".idFile" + negate + GetVideoResolutionQuery(param);
else if (m_field == static_cast(Field::AUDIO_CHANNELS))
- query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND iAudioChannels " + parameter + ")";
+ query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table +
+ ".idFile AND iAudioChannels " + parameter + ")";
else if (m_field == static_cast(Field::VIDEO_CODEC))
- query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strVideoCodec " + parameter + ")";
+ query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table +
+ ".idFile AND strVideoCodec " + parameter + ")";
else if (m_field == static_cast(Field::AUDIO_CODEC))
- query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strAudioCodec " + parameter + ")";
+ query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table +
+ ".idFile AND strAudioCodec " + parameter + ")";
else if (m_field == static_cast(Field::AUDIO_LANGUAGE))
- query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strAudioLanguage " + parameter + ")";
+ query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table +
+ ".idFile AND strAudioLanguage " + parameter + ")";
else if (m_field == static_cast(Field::SUBTITLE_LANGUAGE))
- query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strSubtitleLanguage " + parameter + ")";
+ query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table +
+ ".idFile AND strSubtitleLanguage " + parameter + ")";
else if (m_field == static_cast(Field::VIDEO_ASPECT_RATIO))
- query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND fVideoAspect " + parameter + ")";
+ query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table +
+ ".idFile AND fVideoAspect " + parameter + ")";
else if (m_field == static_cast(Field::AUDIO_COUNT))
- 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);
+ 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);
else if (m_field == static_cast(Field::SUBTITLE_COUNT))
- 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);
+ 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);
else if (m_field == static_cast(Field::HDR_TYPE))
- query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strHdrType " + parameter + ")";
+ query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table +
+ ".idFile AND strHdrType " + parameter + ")";
else if (m_field == static_cast(Field::HDR_DETAIL))
query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table +
".idFile AND strHdrDetail " + parameter + ")";
@@ -1115,7 +1205,7 @@ std::string CSmartPlaylistRule::FormatWhereClause(const std::string &negate, con
return query;
}
-std::string CSmartPlaylistRule::GetField(int field, const std::string &type) const
+std::string CSmartPlaylistRule::GetField(int field, const std::string& type) const
{
if (field >= static_cast(Field::UNKNOWN) && field < static_cast(Field::MAX))
return DatabaseUtils::GetField(static_cast(field), CMediaTypes::FromString(type),
@@ -1136,7 +1226,8 @@ std::string CSmartPlaylistRuleCombination::GetWhereClause(
{
if (it != combinations.cbegin())
rule += GetType() == CDatabaseQueryRuleCombination::Type::COMBINATION_AND ? " AND " : " OR ";
- std::shared_ptr combo = std::static_pointer_cast(*it);
+ std::shared_ptr combo =
+ std::static_pointer_cast(*it);
if (combo)
rule += "(" + combo->GetWhereClause(db, strType, referencedPlaylists) + ")";
}
@@ -1165,7 +1256,9 @@ std::string CSmartPlaylistRuleCombination::GetWhereClause(
{
std::string playlistQuery;
// only playlists of same type will be part of the query
- if (playlist.GetType() == strType || (playlist.GetType() == "mixed" && (strType == "songs" || strType == "musicvideos")) || playlist.GetType().empty())
+ if (playlist.GetType() == strType ||
+ (playlist.GetType() == "mixed" && (strType == "songs" || strType == "musicvideos")) ||
+ playlist.GetType().empty())
{
playlist.SetType(strType);
playlistQuery = playlist.GetWhereClause(db, referencedPlaylists);
@@ -1193,7 +1286,8 @@ std::string CSmartPlaylistRuleCombination::GetWhereClause(
return rule;
}
-void CSmartPlaylistRuleCombination::GetVirtualFolders(const std::string& strType, std::vector &virtualFolders) const
+void CSmartPlaylistRuleCombination::GetVirtualFolders(
+ const std::string& strType, std::vector& virtualFolders) const
{
for (const auto& combination : GetCombinations())
{
@@ -1234,7 +1328,7 @@ CSmartPlaylist::CSmartPlaylist()
Reset();
}
-bool CSmartPlaylist::OpenAndReadName(const CURL &url)
+bool CSmartPlaylist::OpenAndReadName(const CURL& url)
{
if (readNameFromPath(url) == nullptr)
return false;
@@ -1242,12 +1336,12 @@ bool CSmartPlaylist::OpenAndReadName(const CURL &url)
return !m_playlistName.empty();
}
-const TiXmlNode* CSmartPlaylist::readName(const TiXmlNode *root)
+const TiXmlNode* CSmartPlaylist::readName(const TiXmlNode* root)
{
if (root == nullptr)
return nullptr;
- const TiXmlElement *rootElem = root->ToElement();
+ const TiXmlElement* rootElem = root->ToElement();
if (rootElem == nullptr)
return nullptr;
@@ -1273,7 +1367,7 @@ const TiXmlNode* CSmartPlaylist::readName(const TiXmlNode *root)
return root;
}
-const TiXmlNode* CSmartPlaylist::readNameFromPath(const CURL &url)
+const TiXmlNode* CSmartPlaylist::readNameFromPath(const CURL& url)
{
CFileStream file;
if (!file.Open(url))
@@ -1285,7 +1379,7 @@ const TiXmlNode* CSmartPlaylist::readNameFromPath(const CURL &url)
m_xmlDoc.Clear();
file >> m_xmlDoc;
- const TiXmlNode *root = readName(m_xmlDoc.RootElement());
+ const TiXmlNode* root = readName(m_xmlDoc.RootElement());
if (m_playlistName.empty())
{
m_playlistName = CUtil::GetTitleFromPath(url.Get());
@@ -1296,7 +1390,7 @@ const TiXmlNode* CSmartPlaylist::readNameFromPath(const CURL &url)
return root;
}
-const TiXmlNode* CSmartPlaylist::readNameFromXml(const std::string &xml)
+const TiXmlNode* CSmartPlaylist::readNameFromXml(const std::string& xml)
{
if (xml.empty())
{
@@ -1312,12 +1406,12 @@ const TiXmlNode* CSmartPlaylist::readNameFromXml(const std::string &xml)
return nullptr;
}
- const TiXmlNode *root = readName(m_xmlDoc.RootElement());
+ const TiXmlNode* root = readName(m_xmlDoc.RootElement());
return root;
}
-bool CSmartPlaylist::load(const TiXmlNode *root)
+bool CSmartPlaylist::load(const TiXmlNode* root)
{
if (root == nullptr)
return false;
@@ -1325,18 +1419,18 @@ bool CSmartPlaylist::load(const TiXmlNode *root)
return LoadFromXML(root);
}
-bool CSmartPlaylist::Load(const CURL &url)
+bool CSmartPlaylist::Load(const CURL& url)
{
return load(readNameFromPath(url));
}
-bool CSmartPlaylist::Load(const std::string &path)
+bool CSmartPlaylist::Load(const std::string& path)
{
const CURL pathToUrl(path);
return load(readNameFromPath(pathToUrl));
}
-bool CSmartPlaylist::Load(const CVariant &obj)
+bool CSmartPlaylist::Load(const CVariant& obj)
{
if (!obj.isObject())
return false;
@@ -1366,20 +1460,22 @@ bool CSmartPlaylist::Load(const CVariant &obj)
}
// now any limits
- if (obj.isMember("limit") && (obj["limit"].isInteger() || obj["limit"].isUnsignedInteger()) && obj["limit"].asUnsignedInteger() > 0)
+ if (obj.isMember("limit") && (obj["limit"].isInteger() || obj["limit"].isUnsignedInteger()) &&
+ obj["limit"].asUnsignedInteger() > 0)
m_limit = (unsigned int)obj["limit"].asUnsignedInteger();
// and order
if (obj.isMember("order") && obj["order"].isMember("method") && obj["order"]["method"].isString())
{
- const CVariant &order = obj["order"];
+ const CVariant& order = obj["order"];
if (order.isMember("direction") && order["direction"].isString())
m_orderDirection = StringUtils::EqualsNoCase(order["direction"].asString(), "ascending")
? SortOrder::ASCENDING
: SortOrder::DESCENDING;
if (order.isMember("ignorefolders") && obj["ignorefolders"].isBoolean())
- m_orderAttributes = obj["ignorefolders"].asBoolean() ? SortAttributeIgnoreFolders : SortAttributeNone;
+ m_orderAttributes =
+ obj["ignorefolders"].asBoolean() ? SortAttributeIgnoreFolders : SortAttributeNone;
m_orderField = CSmartPlaylistRule::TranslateOrder(obj["order"]["method"].asString().c_str());
}
@@ -1387,12 +1483,12 @@ bool CSmartPlaylist::Load(const CVariant &obj)
return true;
}
-bool CSmartPlaylist::LoadFromXml(const std::string &xml)
+bool CSmartPlaylist::LoadFromXml(const std::string& xml)
{
return load(readNameFromXml(xml));
}
-bool CSmartPlaylist::LoadFromXML(const TiXmlNode *root, const std::string &encoding)
+bool CSmartPlaylist::LoadFromXML(const TiXmlNode* root, const std::string& encoding)
{
if (!root)
return false;
@@ -1404,7 +1500,7 @@ bool CSmartPlaylist::LoadFromXML(const TiXmlNode *root, const std::string &encod
: CDatabaseQueryRuleCombination::Type::COMBINATION_OR);
// now the rules
- const TiXmlNode *ruleNode = root->FirstChild("rule");
+ const TiXmlNode* ruleNode = root->FirstChild("rule");
while (ruleNode)
{
const auto rule{std::make_shared()};
@@ -1414,7 +1510,7 @@ bool CSmartPlaylist::LoadFromXML(const TiXmlNode *root, const std::string &encod
ruleNode = ruleNode->NextSibling("rule");
}
- const TiXmlElement *groupElement = root->FirstChildElement("group");
+ const TiXmlElement* groupElement = root->FirstChildElement("group");
if (groupElement != nullptr && groupElement->FirstChild() != nullptr)
{
m_group = groupElement->FirstChild()->ValueStr();
@@ -1428,24 +1524,26 @@ bool CSmartPlaylist::LoadFromXML(const TiXmlNode *root, const std::string &encod
// and order
// format is field
- const TiXmlElement *order = root->FirstChildElement("order");
+ const TiXmlElement* order = root->FirstChildElement("order");
if (order && order->FirstChild())
{
- const char *direction = order->Attribute("direction");
+ const char* direction = order->Attribute("direction");
if (direction)
m_orderDirection = StringUtils::EqualsNoCase(direction, "ascending") ? SortOrder::ASCENDING
: SortOrder::DESCENDING;
- const char *ignorefolders = order->Attribute("ignorefolders");
+ const char* ignorefolders = order->Attribute("ignorefolders");
if (ignorefolders != nullptr)
- m_orderAttributes = StringUtils::EqualsNoCase(ignorefolders, "true") ? SortAttributeIgnoreFolders : SortAttributeNone;
+ m_orderAttributes = StringUtils::EqualsNoCase(ignorefolders, "true")
+ ? SortAttributeIgnoreFolders
+ : SortAttributeNone;
m_orderField = CSmartPlaylistRule::TranslateOrder(order->FirstChild()->Value());
}
return true;
}
-bool CSmartPlaylist::LoadFromJson(const std::string &json)
+bool CSmartPlaylist::LoadFromJson(const std::string& json)
{
if (json.empty())
return false;
@@ -1457,15 +1555,15 @@ bool CSmartPlaylist::LoadFromJson(const std::string &json)
return Load(obj);
}
-bool CSmartPlaylist::Save(const std::string &path) const
+bool CSmartPlaylist::Save(const std::string& path) const
{
CXBMCTinyXML doc;
TiXmlDeclaration decl("1.0", "UTF-8", "yes");
doc.InsertEndChild(decl);
TiXmlElement xmlRootElement("smartplaylist");
- xmlRootElement.SetAttribute("type",m_playlistType.c_str());
- TiXmlNode *pRoot = doc.InsertEndChild(xmlRootElement);
+ xmlRootElement.SetAttribute("type", m_playlistType.c_str());
+ TiXmlNode* pRoot = doc.InsertEndChild(xmlRootElement);
if (!pRoot)
return false;
@@ -1511,7 +1609,7 @@ bool CSmartPlaylist::Save(const std::string &path) const
return doc.SaveFile(path);
}
-bool CSmartPlaylist::Save(CVariant &obj, bool full /* = true */) const
+bool CSmartPlaylist::Save(CVariant& obj, bool full /* = true */) const
{
if (obj.type() == CVariant::VariantTypeConstNull)
return false;
@@ -1549,7 +1647,7 @@ bool CSmartPlaylist::Save(CVariant &obj, bool full /* = true */) const
return true;
}
-bool CSmartPlaylist::SaveAsJson(std::string &json, bool full /* = true */) const
+bool CSmartPlaylist::SaveAsJson(std::string& json, bool full /* = true */) const
{
CVariant xsp(CVariant::VariantTypeObject);
if (!Save(xsp, full))
@@ -1570,12 +1668,12 @@ void CSmartPlaylist::Reset()
m_groupMixed = false;
}
-void CSmartPlaylist::SetName(const std::string &name)
+void CSmartPlaylist::SetName(const std::string& name)
{
m_playlistName = name;
}
-void CSmartPlaylist::SetType(const std::string &type)
+void CSmartPlaylist::SetType(const std::string& type)
{
m_playlistType = type;
}
@@ -1590,16 +1688,15 @@ bool CSmartPlaylist::IsMusicType() const
return IsMusicType(m_playlistType);
}
-bool CSmartPlaylist::IsVideoType(const std::string &type)
+bool CSmartPlaylist::IsVideoType(const std::string& type)
{
- return type == "movies" || type == "tvshows" || type == "episodes" ||
- type == "musicvideos" || type == "mixed";
+ return type == "movies" || type == "tvshows" || type == "episodes" || type == "musicvideos" ||
+ type == "mixed";
}
-bool CSmartPlaylist::IsMusicType(const std::string &type)
+bool CSmartPlaylist::IsMusicType(const std::string& type)
{
- return type == "artists" || type == "albums" ||
- type == "songs" || type == "mixed";
+ return type == "artists" || type == "albums" || type == "songs" || type == "mixed";
}
std::string CSmartPlaylist::GetWhereClause(
@@ -1608,7 +1705,7 @@ std::string CSmartPlaylist::GetWhereClause(
return m_ruleCombination.GetWhereClause(db, GetType(), referencedPlaylists);
}
-void CSmartPlaylist::GetVirtualFolders(std::vector &virtualFolders) const
+void CSmartPlaylist::GetVirtualFolders(std::vector& virtualFolders) const
{
m_ruleCombination.GetVirtualFolders(GetType(), virtualFolders);
}
@@ -1623,7 +1720,8 @@ std::string CSmartPlaylist::GetSaveLocation() const
return "video";
}
-void CSmartPlaylist::GetAvailableFields(const std::string &type, std::vector &fieldList)
+void CSmartPlaylist::GetAvailableFields(const std::string& type,
+ std::vector& fieldList)
{
const std::vector typeFields = CSmartPlaylistRule::GetFields(type);
for (const auto& field : typeFields)
@@ -1644,27 +1742,26 @@ bool CSmartPlaylist::IsEmpty(bool ignoreSortAndLimit /* = true */) const
return empty;
}
-bool CSmartPlaylist::CheckTypeCompatibility(const std::string &typeLeft, const std::string &typeRight)
+bool CSmartPlaylist::CheckTypeCompatibility(const std::string& typeLeft,
+ const std::string& typeRight)
{
if (typeLeft == typeRight)
return true;
- if (typeLeft == "mixed" &&
- (typeRight == "songs" || typeRight == "musicvideos"))
+ if (typeLeft == "mixed" && (typeRight == "songs" || typeRight == "musicvideos"))
return true;
- if (typeRight == "mixed" &&
- (typeLeft == "songs" || typeLeft == "musicvideos"))
+ if (typeRight == "mixed" && (typeLeft == "songs" || typeLeft == "musicvideos"))
return true;
return false;
}
-CDatabaseQueryRule *CSmartPlaylist::CreateRule() const
+CDatabaseQueryRule* CSmartPlaylist::CreateRule() const
{
return new CSmartPlaylistRule();
}
-CDatabaseQueryRuleCombination *CSmartPlaylist::CreateCombination() const
+CDatabaseQueryRuleCombination* CSmartPlaylist::CreateCombination() const
{
return new CSmartPlaylistRuleCombination();
}
diff --git a/xbmc/settings/AdvancedSettings.cpp b/xbmc/settings/AdvancedSettings.cpp
index 58a57b5d6907a..ed8e496219429 100644
--- a/xbmc/settings/AdvancedSettings.cpp
+++ b/xbmc/settings/AdvancedSettings.cpp
@@ -513,8 +513,8 @@ void CAdvancedSettings::Initialize()
m_useLocaleCollation = true;
m_pictureExtensions =
- ".png|.jpg|.jpeg|.bmp|.gif|.ico|.tif|.tiff|.tga|.pcx|.cbz|.zip|.rss|.webp|.jp2|.apng|.avif|.heif|.heic";
- m_musicExtensions = ".b4s|.nsv|.m4a|.flac|.aac|.strm|.pls|.rm|.rma|.mpa|.wav|.wma|.ogg|.mp3|.mp2|.m3u|.gdm|.imf|.m15|.sfx|.uni|.ac3|.dts|.cue|.aif|.aiff|.wpl|.xspf|.ape|.mac|.mpc|.mp+|.mpp|.shn|.zip|.wv|.dsp|.xsp|.xwav|.waa|.wvs|.wam|.gcm|.idsp|.mpdsp|.mss|.spt|.rsd|.sap|.cmc|.cmr|.dmc|.mpt|.mpd|.rmt|.tmc|.tm8|.tm2|.oga|.url|.pxml|.tta|.rss|.wtv|.mka|.tak|.opus|.dff|.dsf|.m4b|.dtshd";
+ ".png|.jpg|.jpeg|.bmp|.gif|.ico|.tif|.tiff|.tga|.pcx|.cbz|.zip|.rss|.webp|.jp2|.apng|.avif";
+ m_musicExtensions = ".b4s|.nsv|.m4a|.flac|.aac|.strm|.pls|.rm|.rma|.mpa|.wav|.wma|.ogg|.mp3|.mp2|.m3u|.gdm|.imf|.m15|.sfx|.uni|.ac3|.dts|.cue|.aif|.aiff|.wpl|.xspf|.ape|.mac|.mpc|.mp+|.mpp|.shn|.zip|.wv|.dsp|.xsp|.xwav|.waa|.wvs|.wam|.gcm|.idsp|.mpdsp|.mss|.spt|.rsd|.sap|.cmc|.cmr|.dmc|.mpt|.mpd|.rmt|.tmc|.tm8|.tm2|.oga|.url|.pxml|.tta|.rss|.wtv|.mka|.tak|.opus|.dff|.dsf|.m4b|.dtshd|.mkv|.mp4";
m_videoExtensions = ".m4v|.3g2|.3gp|.nsv|.tp|.ts|.ty|.strm|.pls|.rm|.rmvb|.mpd|.m3u|.m3u8|.ifo|.mov|.qt|.divx|.xvid|.bivx|.vob|.nrg|.img|.iso|.udf|.pva|.wmv|.asf|.asx|.ogm|.m2v|.avi|.bin|.dat|.mpg|.mpeg|.mp4|.mkv|.mk3d|.avc|.vp3|.svq3|.nuv|.viv|.dv|.fli|.flv|.001|.wpl|.xspf|.zip|.vdr|.dvr-ms|.xsp|.mts|.m2t|.m2ts|.evo|.ogv|.sdp|.avs|.rec|.url|.pxml|.vc1|.h264|.rcv|.rss|.mpls|.mpl|.webm|.bdmv|.bdm|.wtv|.trp|.f4v";
m_subtitlesExtensions = ".utf|.utf8|.utf-8|.sub|.srt|.smi|.rt|.txt|.ssa|.text|.ssa|.aqt|.jss|."
"ass|.vtt|.idx|.ifo|.zip|.sup";
diff --git a/xbmc/utils/DatabaseUtils.h b/xbmc/utils/DatabaseUtils.h
index a4b38f3f0e947..947e68f265f7f 100644
--- a/xbmc/utils/DatabaseUtils.h
+++ b/xbmc/utils/DatabaseUtils.h
@@ -153,6 +153,10 @@ enum class Field
USER_PREFERENCE,
HAS_VIDEO_VERSIONS,
HAS_VIDEO_EXTRAS,
+ ALBUM_CODEC,
+ BITS_PER_SAMPLE,
+ IS_MUSIC_CONCERT,
+ IS_AUDIOBOOK,
MAX
};