Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 62 additions & 2 deletions Slim/Control/XMLBrowser.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
q => 'disccount',
t => ['tracknum','title','titleFlags'],
# "date" is being used in Podcast episodes
y => ['year','date'],
);

sub cliQuery {
my ( $query, $feed, $request, $expires, $forceTitle ) = @_;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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')
)
) {

Expand Down Expand Up @@ -1372,6 +1394,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);
Expand Down Expand Up @@ -1410,7 +1461,6 @@ sub _cliQuery_done {

$hash{hasitems} = $hasItems;
}

$request->setResultLoopHash($loopname, $cnt, \%hash);
}
$cnt++;
Expand All @@ -1421,6 +1471,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')) {
Expand Down
32 changes: 27 additions & 5 deletions Slim/Formats/XML.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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'},
Expand Down
4 changes: 2 additions & 2 deletions Slim/Menu/BrowseLibrary.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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'};
Expand Down Expand Up @@ -2232,7 +2232,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'};
Expand Down
1 change: 0 additions & 1 deletion Slim/Plugin/InternetRadio/Plugin.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) = @_;

Expand Down
3 changes: 3 additions & 0 deletions Slim/Plugin/Podcast/GPodder.pm
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ sub getFeedsIterator {
image => $feed->{$image},
description => $feed->{description},
author => $feed->{author},
hasMetadata => 'podcast',
title => $feed->{title},
publisher => $feed->{author},
};
};
}
Expand Down
4 changes: 4 additions & 0 deletions Slim/Plugin/Podcast/Parser.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions Slim/Plugin/Podcast/Plugin.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions Slim/Plugin/Podcast/PodcastIndex.pm
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ sub getFeedsIterator {
description => $feed->{description},
author => $feed->{author},
language => $feed->{language},
hasMetadata => 'podcast',
title => $feed->{title},
publisher => $feed->{author},
};
};
}
Expand Down Expand Up @@ -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},
};
}

Expand Down
10 changes: 3 additions & 7 deletions Slim/Plugin/SongScanner/Plugin.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
5 changes: 5 additions & 0 deletions Slim/Utils/DateTime.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 54 additions & 0 deletions SlimBrowse Metadata.md
Original file line number Diff line number Diff line change
@@ -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)