diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 1a89437d96ba1..1ffb443e0e9ba 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -15167,7 +15167,26 @@ msgctxt "#21485" msgid "Supported file extensions and media types" msgstr "" -#empty strings from id 21486 to 21601 +#: xbmc/dialogs/GUIDialogMediaFilter.cpp +#: xbmc/playlist/SmartPlayList.cpp +msgctxt "#21486" +msgid "Music concerts" +msgstr "" + +#. Boolean value to include/exclude audiobooks in the filter or smartplaylist +#: xbmc/dialogs/GUIDialogMediaFilter.cpp +#: xbmc/playlist/SmartPlayList.cpp +msgctxt "#21487" +msgid "Audiobook" +msgstr "" + +#. Plural of #21487 - for display on screen in the associated node name +#: system/library/music/audiobook.xml +msgctxt "#21488" +msgid "Audiobooks" +msgstr "" + +#empty strings from id 21489 to 21601 #: xbmc/Util.cpp msgctxt "#21602" diff --git a/addons/skin.estuary/xml/Includes.xml b/addons/skin.estuary/xml/Includes.xml index 07db7dd6963f8..8f0163433c462 100644 --- a/addons/skin.estuary/xml/Includes.xml +++ b/addons/skin.estuary/xml/Includes.xml @@ -403,6 +403,21 @@ 9 1900 true + + 115 + !String.IsEmpty(ListItem.MusicCodec) + + 110 + 60 + center + center + + font_flag + + + + + @@ -517,6 +532,82 @@ + + !String.IsEmpty(ListItem.Samplerate) + 115 + + 110 + 60 + center + center + + font_flag + + + + + + + !String.IsEmpty(ListItem.BitRate) + 115 + + 110 + 60 + center + center + + font_flag + + + + + + + !String.IsEmpty(ListItem.MusicBitsPerSample) + 115 + + 110 + 60 + center + center + + font_flag + + + + + + + !String.IsEmpty(ListItem.BPM) + 115 + + 110 + 60 + center + center + + font_flag + + + + + + + 115 + !String.IsEmpty($PARAM[infolabel_prefix]ListItem.Duration) + Container.Content(albums) + ![Window.IsVisible(songinformation) | Window.IsVisible(musicinformation)] + + 110 + 60 + center + center + + font_flag + + + + + diff --git a/system/library/music/audiobooks.xml b/system/library/music/audiobooks.xml new file mode 100644 index 0000000000000..44b27886c903f --- /dev/null +++ b/system/library/music/audiobooks.xml @@ -0,0 +1,7 @@ + + + + albums + album + + diff --git a/system/library/music/musicconcerts.xml b/system/library/music/musicconcerts.xml new file mode 100644 index 0000000000000..187554a112da9 --- /dev/null +++ b/system/library/music/musicconcerts.xml @@ -0,0 +1,6 @@ + + + + albums + + diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 828fc5961b4c4..2eace3b04add3 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -830,6 +830,21 @@ bool CFileItem::IsDeleted() const return false; } +bool CFileItem::IsAudioBook() const +{ + return IsType(".m4b"); +} + +bool CFileItem::IsMatroskaAudio() const +{ + return IsType(".mka|.mp4"); +} + +bool CFileItem::IsMatroskaVideo() const +{ + return IsType(".mkv"); +} + bool CFileItem::IsGame() const { if (HasGameInfoTag()) @@ -898,8 +913,8 @@ bool CFileItem::IsFileFolder(FileFolderType types) const if (PLAYLIST::IsSmartPlayList(*this) || (PLAYLIST::IsPlayList(*this) && CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders) || - IsAPK() || IsZIP() || IsRAR() || IsRSS() || URIUtils::IsArchive(GetURL()) || - MUSIC::IsAudioBook(*this) || + IsAPK() || IsZIP() || IsRAR() || IsRSS() || MUSIC::IsAudioBook(*this) || IsMatroskaAudio() || IsMatroskaVideo() || IsType(".mp4") || + #if defined(TARGET_ANDROID) IsType(".apk") || #endif diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index 3f626dd6b8cdb..d1d54d11d3caa 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -154,6 +154,14 @@ class CFileItem : public CGUIListItem, public IArchivable, public ISerializable, */ bool IsDeleted() const; + /*! + \brief Check whether an item is an audio book item. + \return true if item is audiobook, false otherwise. + */ + bool IsAudioBook() const; + bool IsMatroskaAudio() const; + bool IsMatroskaVideo() const; + bool IsGame() const; bool IsLibraryFolder() const; bool IsPythonScript() const; diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 95e002b8ed59a..e76152fefdad7 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -844,7 +844,8 @@ constexpr std::array 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 };