diff --git a/Slim/Control/Queries.pm b/Slim/Control/Queries.pm index 937cc78abfd..5623b55029f 100644 --- a/Slim/Control/Queries.pm +++ b/Slim/Control/Queries.pm @@ -4740,6 +4740,8 @@ sub titlesQuery { my $ignoreWorkTracks = $request->getParam('ignore_work_tracks'); my $performance = $request->getParam('performance'); my $onlyAlbumYears = $request->getParam('only_album_years'); + my $remoteAlbumId = $request->getParam('remote_album_id'); + my $onlineService = $request->getParam('service'); # did we have override on the defaults? # note that this is not equivalent to @@ -4795,6 +4797,8 @@ sub titlesQuery { workId => $workID, libraryId => $libraryID, onlyAlbumYears=> $onlyAlbumYears, + remoteAlbumId => $remoteAlbumId, + onlineService => $onlineService, limit => sub { $count = shift; @@ -4813,26 +4817,67 @@ sub titlesQuery { $count += 0; - my $loopname = 'titles_loop'; - # this is the count of items in this part of the request (e.g., menu 100 200) - # not to be confused with $count, which is the count of the entire list - my $chunkCount = 0; + # is it a remote album that's not in the database? + my $handler; + if ( !scalar @{$itemOrder} && $remoteAlbumId && $onlineService ) { + my $url = $onlineService . ':album:' . $remoteAlbumId; + $handler = Slim::Player::ProtocolHandlers->handlerForURL($url); + } + #--- + # this change gets rendered by git diff in a very confusing way. All we're doing is adding this "if" and indenting + # the existing code in an "else". + if ( $handler && $handler->can('getAlbumTracks') ) { + # we have an online service handler that can return album tracks in the expected format + + $request->setStatusProcessing(); + $handler->getAlbumTracks(sub{ + ($items, $itemOrder, $totalCount) = @_; - if ( scalar @{$itemOrder} ) { + my $loopname = 'titles_loop'; + my $chunkCount = 0; - for my $trackId ( @{$itemOrder} ) { - my $item = $items->{$trackId}; + if ( scalar @{$itemOrder} ) { - _addSong($request, $loopname, $chunkCount, $item, $tags); + for my $trackId ( @{$itemOrder} ) { + my $item = $items->{$trackId}; - $chunkCount++; - } + _addSong($request, $loopname, $chunkCount, $item, $tags); + + $chunkCount++; + } + + } + + $request->addResult('count', $totalCount); + + $request->setStatusDone(); + }, $request->client, $remoteAlbumId); } + #--- + else { - $request->addResult('count', $totalCount); + my $loopname = 'titles_loop'; + # this is the count of items in this part of the request (e.g., menu 100 200) + # not to be confused with $count, which is the count of the entire list + my $chunkCount = 0; - $request->setStatusDone(); + if ( scalar @{$itemOrder} ) { + + for my $trackId ( @{$itemOrder} ) { + my $item = $items->{$trackId}; + + _addSong($request, $loopname, $chunkCount, $item, $tags); + + $chunkCount++; + } + + } + + $request->addResult('count', $totalCount); + + $request->setStatusDone(); + } } @@ -5823,17 +5868,33 @@ sub _songData { $song = $client->currentSongForUrl($url); } + my $service; if ( $isRemote ) { my $handler = Slim::Player::ProtocolHandlers->handlerForURL($url); if ( $handler && $handler->can('getMetadataFor') ) { + $service = (split(/:/, $url))[0]; # Don't modify source data $remoteMeta = Storable::dclone( $handler->getMetadataFor( $request->client, $url ) ); + # if the artist is in the database, use their local id. If not, use the remote service id multiplied by -1 + # so clients can distinguish between the two possibilities. + #------------------------------------------------------------------------------------- + ### !!! Needs extra thought here: what if the remote service artist id is not numeric? + #------------------------------------------------------------------------------------- + my @extArtistIds = split /,/, $remoteMeta->{artistId}; + my @artistIds; + foreach (@extArtistIds) { + my $artistObj = Slim::Schema->rs("Contributor")->search( extid => "$service:artist:$_" )->single; + push @artistIds, $artistObj ? $artistObj->id : $_ * -1; + } + $remoteMeta->{artistId} = join ',', @artistIds; + $remoteMeta->{a} = $remoteMeta->{artist}; $remoteMeta->{A} = $remoteMeta->{artist}; + $remoteMeta->{e} = $remoteMeta->{albumId}; $remoteMeta->{E} = $remoteMeta->{extid}; $remoteMeta->{l} = $remoteMeta->{album}; $remoteMeta->{i} = $remoteMeta->{disc}; @@ -5877,6 +5938,7 @@ sub _songData { $returnHash{'id'} = $track->id; $returnHash{'title'} = $remoteMeta->{title} || $track->title; + $returnHash{'service_id'} = $service if $service; my %seen; # loop so that stuff is returned in the order given... @@ -5906,6 +5968,15 @@ sub _songData { elsif ($tag eq 'b') { $returnHash{work} = $remoteMeta->{$tag}; $returnHash{composer} = $remoteMeta->{composer} if $remoteMeta->{composer}; + # if the composer is in the database, use their local id. If not, use the remote service id multiplied by -1 + # so clients can distinguish between the two possibilities. + #------------------------------------------------------------------------------------- + ### !!! Needs extra thought here: what if the remote service composer id is not numeric? + #------------------------------------------------------------------------------------- + if ( $remoteMeta->{composerId} ) { + my $composerObj = Slim::Schema->rs("Contributor")->search( extid => "$service:artist:" . $remoteMeta->{composerId} )->single; + $returnHash{composer_ids} = $composerObj ? $composerObj->id . "" : $remoteMeta->{composerId} * -1 . ""; + } } # Special case for 2: at track level, triggers addition of the play queue context $addedFromWork @@ -5917,6 +5988,7 @@ sub _songData { elsif ($tag eq 'A' || $tag eq 'S') { if ( my $meta = $remoteMeta->{$tag} ) { $returnHash{artist} = $meta; + $returnHash{artist_ids} = $remoteMeta->{artistId} if $remoteMeta->{artistId}; next; } elsif ( $track->isa('Slim::Schema::RemoteTrack')) { @@ -6005,6 +6077,7 @@ sub _songData { } # we might need to proxy the image request to resize it elsif ($tag eq 'K' && $value) { + $returnHash{baseImage} = URI::Escape::uri_escape_utf8($value); $value = proxiedImage($value); } @@ -6289,12 +6362,6 @@ sub _getTagDataForTracks { } } - if ( my $libraryId = Slim::Music::VirtualLibraries->getRealId($args->{libraryId}) ) { - $sql .= 'JOIN library_track ON library_track.track = tracks.id '; - push @{$w}, 'library_track.library = ?'; - push @{$p}, $libraryId; - } - # Some helper functions to setup joins with less code my $join_genre_track = sub { if ( $sql !~ /JOIN genre_track/ ) { @@ -6336,6 +6403,19 @@ sub _getTagDataForTracks { } }; + if ( $args->{remoteAlbumId} && $args->{onlineService} ) { + # allow retrieval of track by remote album id. + my $remoteAlbumId = $args->{remoteAlbumId}; + my $onlineService = $args->{onlineService}; + $join_albums->(); + push @{$w}, 'albums.extid = ?'; + push @{$p}, $onlineService . ':album:' . $remoteAlbumId; + } elsif ( my $libraryId = Slim::Music::VirtualLibraries->getRealId($args->{libraryId}) ) { + $sql .= 'JOIN library_track ON library_track.track = tracks.id '; + push @{$w}, 'library_track.library = ?'; + push @{$p}, $libraryId; + } + if ( my $year = $args->{year} ) { push @{$w}, 'tracks.year = ?'; push @{$p}, $year; diff --git a/Slim/Control/XMLBrowser.pm b/Slim/Control/XMLBrowser.pm index 29f91ef8c5c..0c037d999ed 100644 --- a/Slim/Control/XMLBrowser.pm +++ b/Slim/Control/XMLBrowser.pm @@ -38,6 +38,25 @@ use constant CACHE_TIME => 3600; # how long to cache browse sessions my $log = logger('formats.xml'); my $prefs = preferences('server'); +# variable of same name in Slim::Control::Queries is source of truth +# used to return raw metadata for clients who request tags +my %colMap = ( + # "publisher" is used in Podcasts + a => ['artist','publisher'], + A => 'artists', + b => ['work','composer'], + d => ['secs','duration'], + g => 'genre', + G => 'genres', + i => 'discnum', + k => 'description', + l => ['album','version','remoteAlbumId'], + q => 'disccount', + t => ['tracknum','title','titleFlags'], + # "date" is being used in Podcast episodes + 'y' => ['year','date'], +); + sub cliQuery { my ( $query, $feed, $request, $expires, $forceTitle ) = @_; @@ -310,6 +329,7 @@ sub _cliQuery_done { my $menu = $request->getParam('menu'); my $url = $request->getParam('url'); my $trackId = $request->getParam('track_id'); + my $tags = $request->getParam('tags'); # menu/jive mgmt my $menuMode = defined $menu; @@ -496,6 +516,8 @@ sub _cliQuery_done { my $pt = $subFeed->{passthrough} || []; my %args = (params => $feed->{'query'}, isControl => 1); + $args{'tags'} = $tags if $tags; + if (defined $search && $subFeed->{type} && ($subFeed->{type} eq 'search' || defined $subFeed->{'searchParam'})) { $args{'search'} = $search; } @@ -549,7 +571,7 @@ sub _cliQuery_done { ($subFeed->{'type'} && $subFeed->{'type'} eq 'audio') || $subFeed->{'enclosure'} || # Bug 17385 - rss feeds include description at non leaf levels - ($subFeed->{'description'} && $subFeed->{'type'} && $subFeed->{'type'} ne 'rss') + ($subFeed->{'description'} && $subFeed->{'type'} && $subFeed->{'type'} ne 'rss' && ($subFeed->{'hasMetadata'} || '') ne 'podcast') ) ) { @@ -673,7 +695,7 @@ sub _cliQuery_done { && (defined $subFeed->{'name'} || defined $subFeed->{'title'}) && ($method !~ /all/)) { - my $title = $subFeed->{'name'} || $subFeed->{'title'}; + my $title = $subFeed->{'album'} && $subFeed->{'artist'} ? $subFeed->{'title'} : $subFeed->{'name'}; my $url = $subFeed->{'url'}; # Podcast enclosures @@ -699,6 +721,8 @@ sub _cliQuery_done { bitrate => $subFeed->{'bitrate'}, cover => $subFeed->{'cover'} || $subFeed->{'image'} || $subFeed->{'icon'} || $request->getParam('icon'), year => $subFeed->{'year'}, + album => $subFeed->{'album'}, + artist => $subFeed->{'artist'}, } ); $client->execute([ 'playlist', $method, $url ]); @@ -1372,6 +1396,35 @@ sub _cliQuery_done { delete $hash{'style'} if $hash{'style'} && $hash{'style'} eq 'itemNoAction'; } + if ( $item->{hasMetadata} && (my $tags = $request->getParam('tags')) ) { + my $metadata = { + type => $item->{hasMetadata}, + }; + + foreach my $tag (split(//, $tags)) { + if (my $mapping = $colMap{$tag}) { + $mapping = [$mapping] unless ref $mapping; + foreach my $map (@$mapping) { + if (my $value = $item->{$map}) { + $metadata->{$map} = $value; + } + } + } + } + + # some itmes (basically line1, line2) we add always, if available + $metadata->{'name'} ||= $item->{'name'} if defined $item->{'name'} && !$metadata->{'title'}; + $metadata->{'description'} ||= $item->{'description'} if defined $item->{'description'}; + + # convert unix timestamps to human readable time + $metadata->{'date'} = localtime($metadata->{'date'}) if $metadata->{'date'} =~ /\d{10}/; + + # add formatted duration + $metadata->{'duration'} ||= Slim::Utils::DateTime::secsToMMSS($metadata->{'secs'}) if $metadata->{'secs'}; + + $hash{'metadata'} = $metadata; + } + $hash{'textkey'} = $item->{textkey} if defined $item->{textkey}; $request->setResultLoopHash($loopname, $cnt, \%hash); @@ -1410,7 +1463,6 @@ sub _cliQuery_done { $hash{hasitems} = $hasItems; } - $request->setResultLoopHash($loopname, $cnt, \%hash); } $cnt++; @@ -1421,6 +1473,16 @@ sub _cliQuery_done { $request->addResult('count', $totalCount); + if ( my $meta = $subFeed->{'hasMetadata'} ) { + $request->addResult('hasMetadata', $meta); + if ( $meta eq 'album' ) { + $request->addResult('year', $subFeed->{'year'}); + $request->addResult('album', $subFeed->{'album'}); + $request->addResult('artist', $subFeed->{'artist'}); + $request->addResult('genre', $subFeed->{'genre'}); + } + } + if ($menuMode) { if ($request->getResult('base')) { diff --git a/Slim/Formats/XML.pm b/Slim/Formats/XML.pm index cd0f458e435..61731c0d11e 100644 --- a/Slim/Formats/XML.pm +++ b/Slim/Formats/XML.pm @@ -610,7 +610,8 @@ sub _parseOPMLOutline { my $url = $itemXML->{'url'} || $itemXML->{'URL'} || $itemXML->{'xmlUrl'}; - next if $url && $url =~ IS_TUNEIN_RE && $itemXML && ref $itemXML && $itemXML->{key} && $itemXML->{key} eq 'unavailable'; + my $isTuneIn = $url && $url =~ IS_TUNEIN_RE; + next if $isTuneIn && $itemXML && ref $itemXML && $itemXML->{key} && $itemXML->{key} eq 'unavailable'; # Some programs, such as OmniOutliner put garbage in the URL. if ($url) { @@ -620,12 +621,33 @@ sub _parseOPMLOutline { # Pull in all attributes we find my %attrs; for my $attr ( keys %{$itemXML} ) { - next if $attr =~ /^(?:text|type|URL|xmlUrl|outline)$/i; - $attrs{$attr} = $itemXML->{$attr}; - } + next if $attr =~ /^(?:text|type|URL|xmlUrl|outline)$/i; + $attrs{$attr} = $itemXML->{$attr}; + } - push @items, { + if ( $isTuneIn && $itemXML->{type} ) { + my $type = $itemXML->{type} || ''; + my $item = $itemXML->{item} || ''; + + my $defaults = sub { + $attrs{'hasMetadata'} = $_[0]; + $attrs{'title'} = unescapeAndTrim($itemXML->{'text'}), + $attrs{'description'} = unescapeAndTrim($itemXML->{'subtext'}), + }; + + if ($type eq 'audio' && $item eq 'topic' && ($itemXML->{'stream_type'} || '') eq 'download') { + $defaults->('episode'); + $attrs{'secs'} = $itemXML->{'topic_duration'} || 0; + } + elsif ($type eq 'audio') { + $defaults->('station'); + } + elsif ($type eq 'link' && $item eq 'show') { + $defaults->('podcast'); + } + } + push @items, { # compatable with INPUT.Choice, which expects 'name' and 'value' 'name' => unescapeAndTrim( $itemXML->{'text'} ), 'value' => $url || $itemXML->{'text'}, diff --git a/Slim/Menu/BrowseLibrary.pm b/Slim/Menu/BrowseLibrary.pm index 95286a0e94b..4eea097f579 100644 --- a/Slim/Menu/BrowseLibrary.pm +++ b/Slim/Menu/BrowseLibrary.pm @@ -1856,7 +1856,7 @@ sub _tracks { $_->{'ct'} = $_->{'type'}; if (my $secs = $_->{'duration'}) { $_->{'secs'} = $secs; - $_->{'duration'} = sprintf('%d:%02d', int($secs / 60), $secs % 60); + $_->{'duration'} = Slim::Utils::DateTime::secsToMMSS($secs); } $_->{'discc'} = delete $_->{'disccount'} if defined $_->{'disccount'}; $_->{'fs'} = $_->{'filesize'}; @@ -2241,7 +2241,7 @@ sub _playlistTracks { $_->{'ct'} = $_->{'type'}; if (my $secs = $_->{'duration'}) { $_->{'secs'} = $secs; - $_->{'duration'} = sprintf('%d:%02d', int($secs / 60), $secs % 60); + $_->{'duration'} = Slim::Utils::DateTime::secsToMMSS($secs); } $_->{'discc'} = delete $_->{'disccount'} if defined $_->{'disccount'}; $_->{'fs'} = $_->{'filesize'}; diff --git a/Slim/Plugin/InternetRadio/Plugin.pm b/Slim/Plugin/InternetRadio/Plugin.pm index 847ff111e8f..0fdfb7d66cc 100644 --- a/Slim/Plugin/InternetRadio/Plugin.pm +++ b/Slim/Plugin/InternetRadio/Plugin.pm @@ -204,7 +204,6 @@ sub setFeed { \$localFeed = \$_[1] } $subclass->initPlugin(); } -# Some TuneIn-specific code to add formats param if Alien is installed sub radiotimeFeed { my ( $class, $feed, $client ) = @_; diff --git a/Slim/Plugin/Podcast/GPodder.pm b/Slim/Plugin/Podcast/GPodder.pm index 3523b388510..607b3252271 100644 --- a/Slim/Plugin/Podcast/GPodder.pm +++ b/Slim/Plugin/Podcast/GPodder.pm @@ -26,6 +26,9 @@ sub getFeedsIterator { image => $feed->{$image}, description => $feed->{description}, author => $feed->{author}, + hasMetadata => 'podcast', + title => $feed->{title}, + publisher => $feed->{author}, }; }; } diff --git a/Slim/Plugin/Podcast/Parser.pm b/Slim/Plugin/Podcast/Parser.pm index e8e84fb80ba..e2ab4e44b0e 100644 --- a/Slim/Plugin/Podcast/Parser.pm +++ b/Slim/Plugin/Podcast/Parser.pm @@ -137,6 +137,10 @@ sub parse { elsif ($duration) { $item->{line2} = $item->{line2} ? $item->{line2} . ' (' . $duration . ')' : $duration; } + + $item->{hasMetadata} = 'episode'; + $item->{secs} ||= $item->{duration}; + $item->{'date'} = $item->{pubdate}; } $feed->{nocache} = 1; diff --git a/Slim/Plugin/Podcast/Plugin.pm b/Slim/Plugin/Podcast/Plugin.pm index c1b4455f956..79f66cb9dd8 100644 --- a/Slim/Plugin/Podcast/Plugin.pm +++ b/Slim/Plugin/Podcast/Plugin.pm @@ -183,6 +183,8 @@ sub handleFeed { parser => 'Slim::Plugin::Podcast::Parser', image => $image || __PACKAGE__->_pluginDataFor('icon'), playlist => $url, + hasMetadata => 'podcast', + title => $_->{name}, }; # if pre-cached feed data is missing, initiate retrieval diff --git a/Slim/Plugin/Podcast/PodcastIndex.pm b/Slim/Plugin/Podcast/PodcastIndex.pm index 3f5029f476e..e8003d291a6 100644 --- a/Slim/Plugin/Podcast/PodcastIndex.pm +++ b/Slim/Plugin/Podcast/PodcastIndex.pm @@ -75,6 +75,9 @@ sub getFeedsIterator { description => $feed->{description}, author => $feed->{author}, language => $feed->{language}, + hasMetadata => 'podcast', + title => $feed->{title}, + publisher => $feed->{author}, }; }; } @@ -124,6 +127,10 @@ sub newsHandler { image => $item->{image} || $item->{feedImage}, date => $item->{datePublished}, type => 'audio', + hasMetadata => 'episode', + title => $item->{title}, + description => $item->{description}, + secs => $item->{duration}, }; } diff --git a/Slim/Plugin/SongScanner/Plugin.pm b/Slim/Plugin/SongScanner/Plugin.pm index 9ec97a7640b..49961d8c81a 100644 --- a/Slim/Plugin/SongScanner/Plugin.pm +++ b/Slim/Plugin/SongScanner/Plugin.pm @@ -102,15 +102,11 @@ my %modeParams = ( sub _formatTime { my $seconds = shift; - my $hrs = int($seconds / 3600); - my $mins = int(($seconds % 3600) / 60); - my $secs = $seconds % 60; - - if ($hrs) { - return sprintf("%d:%02d:%02d", $hrs, $mins, $secs); + if (int($seconds / 3600)) { + return Slim::Utils::DateTime::timeFormat($seconds); } else { - return sprintf("%02d:%02d", $mins, $secs); + return Slim::Utils::DateTime::secsToMMSS($seconds); } } diff --git a/Slim/Schema.pm b/Slim/Schema.pm index b7a57d6827e..2c656d15e59 100644 --- a/Slim/Schema.pm +++ b/Slim/Schema.pm @@ -2824,13 +2824,15 @@ sub _preCheckAttributes { # since the tag may need to be split. See bugs #295 and #4584. # # Push these back until we have a Track object. - for my $tag (Slim::Schema::Contributor->contributorRoles, (map { $_ . 'SORT' } Slim::Schema::Contributor->contributorRoles()), + for my $tag (Slim::Schema::Contributor->contributorRoles, + (map { $_ . 'SORT' } Slim::Schema::Contributor->contributorRoles()), + (map { $_ . '_EXTID' } Slim::Schema::Contributor->contributorRoles()), qw( COMMENT GENRE PIC APIC ALBUM ALBUMSORT DISCC COMPILATION REPLAYGAIN_ALBUM_PEAK REPLAYGAIN_ALBUM_GAIN MUSICBRAINZ_ARTIST_ID MUSICBRAINZ_ALBUMARTIST_ID MUSICBRAINZ_ALBUM_ID MUSICBRAINZ_ALBUM_TYPE MUSICBRAINZ_ALBUM_STATUS RELEASETYPE - ALBUM_EXTID ARTIST_EXTID WORK WORKSORT + ALBUM_EXTID WORK WORKSORT )) { @@ -3114,6 +3116,7 @@ sub _mergeAndCreateContributors { # Bug: 6507 - use any ARTISTSORT tag for this contributor $attributes->{'TRACKARTISTSORT'} = delete $attributes->{'ARTISTSORT'}; $attributes->{'MUSICBRAINZ_TRACKARTIST_ID'} = delete $attributes->{'MUSICBRAINZ_ARTIST_ID'} if $attributes->{'MUSICBRAINZ_ARTIST_ID'}; + $attributes->{'TRACKARTIST_EXTID'} = delete $attributes->{'ARTIST_EXTID'}; main::DEBUGLOG && $isDebug && $log->debug(sprintf("-- Contributor '%s' of role 'ARTIST' transformed to role 'TRACKARTIST'", $attributes->{'TRACKARTIST'}, @@ -3138,8 +3141,7 @@ sub _mergeAndCreateContributors { 'artist' => $contributor, 'brainzID' => $attributes->{"MUSICBRAINZ_${tag}_ID"}, 'sortBy' => $attributes->{$tag.'SORT'}, - # only store EXTID for track artist, as we don't have it for other roles - 'extid' => $tag eq 'ARTIST' && $attributes->{'ARTIST_EXTID'}, + 'extid' => $attributes->{"${tag}_EXTID"}, }); main::DEBUGLOG && $isDebug && $log->is_debug && $log->debug(sprintf("-- Track has contributor '$contributor' of role '$tag'")); diff --git a/Slim/Schema/RemoteTrack.pm b/Slim/Schema/RemoteTrack.pm index d3cb989f5f6..e9819007808 100644 --- a/Slim/Schema/RemoteTrack.pm +++ b/Slim/Schema/RemoteTrack.pm @@ -13,6 +13,7 @@ use strict; use base qw(Slim::Utils::Accessor); use Scalar::Util qw(blessed); +use Digest::MD5 qw(md5_hex); use Tie::Cache::LRU; use Slim::Utils::Cache; @@ -63,6 +64,7 @@ my @allAttributes = (qw( performance discsubtitle added_from_work + artistid )); { @@ -110,7 +112,7 @@ sub init { # Emulate absent methods - hopefully these can be retired at some time sub artists {return ();} -sub artistid {} +#sub artistid {} sub genres {return ();} sub genrename {} sub genreid {} @@ -349,6 +351,21 @@ my $separator; sub setAttributes { my ($self, $attributes) = @_; +#-------------- +# leaving this here as a reminder, commented out for now, because something like this is required to make sure that the play queue +# is formatted correctly after a restart in default/classic skins. But it needs amendment because it causes problems +# for plugins (eg BBC Sounds) where getMetadataFor fails without a client. +#-------------- +# my $url = $self->_url; +# if ( $url !~ "^http" && Slim::Music::Info::isRemoteURL($url) ) { +# my $handler = Slim::Player::ProtocolHandlers->handlerForURL( $url ); +# if ( $handler && $handler->can('getMetadataFor') ) { +# if ( my $meta = $handler->getMetadataFor( undef, $url ) ) {; +# $attributes = $meta; +# } +# } +# } + %availableTags = map { $_ => 1 } @allAttributes unless keys %availableTags; main::DEBUGLOG && $log->is_debug && $log->debug($self->url . " => ", Data::Dump::dump($attributes)); @@ -375,7 +392,7 @@ sub setAttributes { main::DEBUGLOG && $log->is_debug && defined $self->$key() && $self->$key() ne $value && $log->debug("$key: ", $self->$key(), "=>$value"); - $self->$key($value); + $self->$key($value) if !$self->$key || $value; } } diff --git a/Slim/Utils/DateTime.pm b/Slim/Utils/DateTime.pm index 0813372eb3d..035c7280b3d 100644 --- a/Slim/Utils/DateTime.pm +++ b/Slim/Utils/DateTime.pm @@ -131,6 +131,11 @@ sub timeFormat { ); } +sub secsToMMSS { + my $secs = shift || 0; + return sprintf('%d:%02d', int($secs / 60), $secs % 60); +} + =head2 fracSecToMinSec( $seconds ) Turns seconds into min:sec diff --git a/SlimBrowse Metadata.md b/SlimBrowse Metadata.md new file mode 100644 index 00000000000..6e7dfbe45ae --- /dev/null +++ b/SlimBrowse Metadata.md @@ -0,0 +1,54 @@ +# SlimBrowse Metadata + +The following is a summary of the metadata returned by the XMLBrowser based CLI commands - if requested and available. +In order to request raw metadata, add the `tags:...` parameter to those SlimBrowse queries. The results may vary +depending on the service providing the data. Eg. Podcasts on TuneIn don't have a publishing date, but it's embedded +in the description. And they don't provide an author or publisher. + +## Albums +* hasMetadata `album` +* album (name) +* artist +* artists +* genre +* year + +## Artists +* hasMetadata `artist` +* artist + +## Tracks +* hasMetadata `track` +* title +* album (name) +* artist (name) +* artists +* tracknum +* duration +* year + +## Playlists +* hasMetadata: `playlist` +* title +* description + +## Radio Station +* hasMetadata: `station` +* name +* description + +## Podcasts +* hasMetadata `podcast` +* title +* publisher +* description + +## Podcast Episodes +* hasMetadata `episode` +* title +* podcast (the show's name if available) +* description +* duration +* date (of the episode's publishing) + +