Anyone knows of an easy fix for this? Or should we just wait and see?
Could you please give this version a try? Rename it to Plugin.pm and move
it to the Plugin/Biography folder. Then restart your server.
I can't test thoroughly right now as I'm on my laptop without my music
collection at hand. If you could give me a thumbs up I'd upload a new
release. Thanks for the feedback!
--
Michael
-----------------------------------------------------------
Help translate SlimServer by using the
StringEditor Plugin (http://www.herger.net/slim/)
# Biography::Plugin.pm by mh 2005
#
# This code is derived from code with the following copyright message:
#
# SliMP3 Server Copyright (C) 2001 Sean Adams, Slim Devices Inc.
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
# version 2.
#
# Changelog
# 1.1 - improved ignore list behaviour for artists like "Trio" or "The The"
# - added first level cache (memory) to reduce disk acces
# 1.0 - fixed name guess with online streams
# 0.9 - Dan adapted the plugin to using non blocking web requests - thanks a
lot!
# - first release for SlimServer v6
# - added user configurable $ignoreList: words like 'the band group quartet
trio'
# that are not to be respected in the artist's name
# - fixed caching duration
# 0.2 - added support for proxy server (thanks to Jaimes Craig for pointing
this out)
# - added _blank target to non relative external links
# - added configuration options for slideshow mode, web interface refresh,
cache purge
# - added regular cache purge
# - fixed crash when there's no player available
# - added some more informational texts (nothing found, gathering
information etc.)
# 0.1 - first release
package Plugins::Biography::Plugin;
use strict;
use vars qw($VERSION);
$VERSION = substr(q$Revision: 1.0 $,10);
use File::Spec;
use File::Spec;
use FindBin qw($Bin);
use lib(File::Spec->catdir($Bin, 'Plugins','MHCPAN'));
use HTML::FormatText;
use Slim::Buttons::Common;
use Slim::Display::Animation;
use Slim::Music::Info;
use Slim::Player::Playlist;
use Slim::Player::Source;
use Slim::Utils::Prefs;
use Slim::Utils::Strings qw(string);
my $baseURL = 'http://www.allmusic.com';
my $searchURL = "$baseURL/cg/amg.dll";
# TODO mh: back to "my" after debugging...
my ($cacheFolder, %status);
sub initPlugin {
$cacheFolder = initCacheFolder();
}
sub getDisplayName {
return 'PLUGIN_BIOGRAPHY';
}
sub setMode {
my $client = shift;
$client->lines( \&lines );
# Please wait...
$status{$client}{status} ||= -1;
$client->update();
checkArtistBio($client);
$client->update();
}
sub lines {
my $client = shift;
my $line1 = '';
my $line2 = '';
if ($status{$client}{status} eq "0") {
$line1 = $client->string('PLUGIN_BIOGRAPHY_COLLECTINGINFO');
}
elsif ($status{$client}{status} eq "-1") {
$line1 = $client->string('PLUGIN_BIOGRAPHY');
$line2 = $client->string('PLUGIN_BIOGRAPHY_NOTHINGFOUND');
}
else {
$line1 = $client->string('PLUGIN_BIOGRAPHY') . ' - ' .
$status{$client}{status};
$line2 = $status{$client}{playerBio}[$status{$client}{line}];
}
return ($line1, $line2);
}
sub getFunctions {
my %functions = (
'up' => sub {
my $client = shift;
$status{$client}{line} =
Slim::Buttons::Common::scroll($client, -1, $#{ $status{$client}{playerBio} } +
1, $status{$client}{line} || 0);
$client->update();
},
'down' => sub {
my $client = shift;
$status{$client}{line} =
Slim::Buttons::Common::scroll($client, 1, $#{ $status{$client}{playerBio} } +
1, $status{$client}{line} || 0);
$client->update();
},
'left' => sub {
my $client = shift;
Slim::Buttons::Common::popModeRight($client);
},
'right' => sub {
my $client = shift;
Slim::Display::Animation::bumpRight($client);
}
);
return \%functions;
}
sub setupGroup {
my %setupGroup = (
PrefOrder => ['plugin_biography_slideshow',
'plugin_biography_refresh', 'plugin_biography_cache',
'plugin_biography_purge_cache', 'plugin_biography_ignorelist'],
GroupHead => string('PLUGIN_BIOGRAPHY'),
GroupLine => 1,
GroupSub => 0,
Suppress_PrefSub => 0,
Suppress_PrefLine => 1
);
my %setupPrefs = (
'plugin_biography_slideshow' => {
'validate' => \&Slim::Web::Setup::validateInt,
'validateArgs' => [0,undef,1]
},
'plugin_biography_refresh' => {
'validate' => \&Slim::Web::Setup::validateInt,
'validateArgs' => [0,undef,1]
},
'plugin_biography_cache' => {
'validate' => \&Slim::Web::Setup::validateInt,
'validateArgs' => [1,undef,1],
'onChange' => sub {
my ($client,$changeref,$paramref,$pageref) = @_;
Slim::Utils::Prefs::set('plugin_biography_cache',
$changeref->{'plugin_biography_cache'}->{'new'});
initCacheFolder();
}
},
'plugin_biography_ignorelist' => {
'PrefSize' => 'large'
}
);
return (\%setupGroup, \%setupPrefs);
}
sub webPages {
my %pages = ("index\.htm" => \&handleWebIndex);
return (\%pages, "index.html");
}
sub handleWebIndex {
my ($client, $params, $callback, $httpClient, $response) = @_;
if (!$client) {
$params->{'artist'} = string('NO_PLAYER_FOUND');
$params->{'artistbio'} = string('NO_PLAYER_DETAILS');
}
elsif (checkArtistBio($client)) {
if (Slim::Utils::Prefs::get('plugin_biography_refresh')) {
$params->{'refresh'} =
Slim::Utils::Prefs::get('plugin_biography_refresh') * 1000;
}
# Try memory cache first
$params->{'artist'} = $status{$client}{status};
$params->{'artistbio'} = $status{$client}{htmlBio};
$params->{'artistimages'} = $status{$client}{images};
$params->{'artistlinks'} = $status{$client}{links};
} else {
$params->{'artist'} = string('PLUGIN_BIOGRAPHY_NOTHINGFOUND');
}
return
Slim::Web::HTTP::filltemplatefile('plugins/Biography/index.html', $params);
}
sub initStatus {
my $client = shift;
$status{$client}{status} = shift;
$status{$client}{playerBio} = shift;
$status{$client}{line} = 0;
$status{$client}{htmlBio} = shift;
$status{$client}{images} = shift;
$status{$client}{links} = shift;
}
sub checkArtistBio {
my $client = shift;
# check whether there's actually a song!
if (my $url = Slim::Player::Playlist::song($client)) {
my $artist = getArtistFromSongURL($url);
# only read bio if artist changed
if ($artist ne $status{$client}{status}) {
my $artistHTML = getArtistInfo($client, $artist);
# initialize status hash
$status{$client}{status} = $artist;
$status{$client}{playerBio} = ();
$status{$client}{line} = 0;
$status{$client}{htmlBio} = '';
$status{$client}{images} = '';
$status{$client}{links} = '';
# Some error when downloading data
if (!$artistHTML) {
$status{$client}{htmlBio} =
string('PLUGIN_BIOGRAPHY_NOTHINGFOUND');
$status{$client}{playerBio} =
[$client->string('PLUGIN_BIOGRAPHY_NOTHINGFOUND')];
}
# nothing (unique) found on allmusic -> display
selection
elsif ($artistHTML =~ /^<!-- NOT FOUND -->/) {
$status{$client}{htmlBio} = $artistHTML;
$status{$client}{playerBio} =
[$client->string('PLUGIN_BIOGRAPHY_NOTHINGFOUND')];
}
# we're fine!
else {
# cache values for web interface
$status{$client}{htmlBio} =
getArtistBio($artistHTML, $artist);
$status{$client}{images} =
getArtistImages($artistHTML);
$status{$client}{links} =
getArtistLinks($artistHTML);
# create player's content
my $bioFormatter =
HTML::FormatText->new(leftmargin => 0, rightmargin => 39);
my $formattedBio = $status{$client}{htmlBio};
$formattedBio =~ s/Read More\.\.\./(Read more
on the web)/i;
$formattedBio =
$bioFormatter->format_string($formattedBio);
$formattedBio =~ s/\n+/\n/g;
@{$status{$client}{playerBio}} = split(/\n/,
$formattedBio);
if (@{$status{$client}{playerBio}} == 0) {
push(@{$status{$client}{playerBio}},
$client->string('PLUGIN_BIOGRAPHY_NOTEXTFOUND'));
}
else {
push(@{$status{$client}{playerBio}},
'----------------------------------------');
}
}
}
else {
$::d_plugins && Slim::Utils::Misc::msg("Biography:
$artist was in memory cache\n");
}
}
else {
# initialize status hash
$status{$client}{status} = '-1';
$status{$client}{playerBio} =
[string('PLUGIN_BIOGRAPHY_NOTHINGFOUND')];
$status{$client}{line} = 0;
$status{$client}{htmlBio} = '';
$status{$client}{images} = '';
$status{$client}{links} = '';
return 0;
}
return 1;
}
sub getArtistFromSongURL {
my ($url, $params) = @_;
my $ds = Slim::Music::Info::getCurrentDataStore();
my $track = $ds->objectForUrl($url);
$params->{'artist'} = $track->artist();
$::d_plugins && Slim::Utils::Misc::msg("Biography: Let's guess the
artists name: '$params->{artist}'\n");
# We didn't get an artist - try to extract one from the title.
unless ($params->{'artist'} && ($params->{'artist'} ne
string('NO_ARTIST'))) {
$::d_plugins && Slim::Utils::Misc::msg("Biography: artist's
name is either empty or 'No artist'\n");
$params->{'artist'} = Slim::Music::Info::getCurrentTitle('',
$url);
$::d_plugins && Slim::Utils::Misc::msg("Biography: give the
track's title a try (for streams): '$params->{artist}'\n");
$params->{'artist'} =~ /([^\-]+?) -/;
if ($1) {
$params->{'artist'} = $1;
$::d_plugins && Slim::Utils::Misc::msg("Biography:
tried to match 'artist - song': '$params->{artist}'\n");
}
}
return $params->{'artist'};
}
sub getArtistInfo {
my $client = shift;
my $searchArtist = shift;
$::d_plugins && Slim::Utils::Misc::msg("Biography: Looking up
information for artist '$searchArtist'\n");
if (my $ignoreList =
Slim::Utils::Prefs::get('plugin_biography_ignorelist')) {
my $originalArtist = $searchArtist;
for (split(/ /, $ignoreList)) {
$searchArtist =~ s/\b$_\b//sig;
}
# remove leading/trailing spaces
$searchArtist =~ s/^\s*//g;
$searchArtist =~ s/\s*$//g;
# if the artist's name is empty (as for eg. "Trio" and "The
The"), use the original instead
$searchArtist = $originalArtist if not $searchArtist =~ /\w/;
}
# Try and use the locally cached copy first
my $cachedFile = "$cacheFolder/$searchArtist.html";
my $cacheAge = Slim::Utils::Prefs::get('plugin_biography_cache') || 30;
if (-e $cachedFile && -M $cachedFile < $cacheAge) {
$::d_plugins && Slim::Utils::Misc::msg("Biography:
$searchArtist.html was in disk cache\n");
local $/ = undef;
open CACHE, $cachedFile;
my $cachedContent = <CACHE>;
close CACHE;
# change timestamp to keep it a little longer...
utime time(), time(), $cachedFile;
return $cachedContent;
}
# ...ok, no cached copy, we'll have to go through this...
$::d_plugins && Slim::Utils::Misc::msg("Biography: artist after
ignore-cleanup '$searchArtist'\n");
# This makes a non-blocking request to the remote server.
my $http = Slim::Player::Protocols::HTTP->new({
'url' => $searchURL,
'post' => "SQL=$searchArtist&OPT1=1&submit=Go&P=amg",
'create' => 0,
'timeout' => 10
});
my $html;
if (defined $http) {
$html = $http->content();
$http->close();
}
if (not $html) {
$::d_plugins && Slim::Utils::Misc::msg("Biography: timeout problem?\n");
return 'timeout?';
}
# direct hit?
if (($html =~ /Overview/si) && ($html =~ /Biography/si) && ($html =~
/Discography/si)) {
$::d_plugins && Slim::Utils::Misc::msg("Biography: Match!\n");
storeCachedFile($cachedFile, $html);
return $html;
} else {
$html =~ /<a
href="(\/cg\/amg.dll\?p=amg\&sql=\d{1,2}:\w+)">(.*?)<\/a>/si;
my $artistURL = $1;
my $artistFound = $2;
my $ok = 1;
if (defined $artistURL && defined $artistFound) {
$::d_plugins && Slim::Utils::Misc::msg("Biography: URL
found ($artistURL, $artistFound)\n");
# verify if we really match the search expression
for (split(/[\W]/, $searchArtist)) {
$::d_plugins &&
Slim::Utils::Misc::msg("Biography: verify name part '$_'\n");
if ($_ && $artistFound !~ /$_/i) {
$ok = 0;
last;
}
}
}
# give the first entry a try
if ($ok || ($searchArtist =~ $artistFound)) {
my $http =
Slim::Player::Source::openRemoteStream($baseURL . $artistURL);
my $html = $http->content();
$http->close();
storeCachedFile($cachedFile, $html);
return $html;
} else {
# show list of possible matches
my $htmlList = '';
while ($html =~ /<a
(href="\/cg\/amg.dll\?p=amg\&sql=\d{1,2}:\w+")>(.*?)<\/a>(<\/strong>|)<\/td><td.*?>(.*?)<\/td>/sig)
{
$htmlList .= "<li><a " . fixBaseURL($1) . ">$2
($4)</a></li>\n";
}
return "<!-- NOT FOUND --><ul>$htmlList</ul>\n";
}
}
}
sub storeCachedFile {
my $cachedFile = shift;
my $cachedContent = shift;
open(CACHE, ">$cachedFile") || Slim::Utils::Misc::msg("Could not open
$cachedFile for writing: $!\n");
print CACHE $cachedContent;
close(CACHE);
}
sub getArtistLinks {
my $artistHTML = shift;
# if ($artistHTML =~
/<hr[^>]*?><table.*?(<table.*?)<\/td><\/tr><\/table><\/td><!--End Center
Content-->/si) {
if ($artistHTML =~ /Begin Center
Content-->.*?<\/table><hr.*?>(<table.*?)<\/td><\/tr><\/table><\/td><!--End
Center Content-->/si) {
my $html = $1;
$html =~ s/<[\/]?(span|div)[^>]*?>//sig;
return fixBaseURL($html);
} else {
return '';
}
}
sub getArtistBio {
my $artistHTML = shift;
my $searchArtist = shift;
if ($artistHTML =~ /<div id="artistminibio">.*?<p.*?>(.*?)<\/p.*?>/si) {
return fixBaseURL($1);
}
return string('PLUGIN_BIOGRAPHY_NOTHINGFOUND');
}
sub getArtistImages {
my $artistHTML = shift;
my $html = '<script type="text/javascript" language="javascript" src="'
. $baseURL . '/js/pictures.js"></script>';
if ($artistHTML !~ /Begin Page Photo-->(.*?)<!--End Page Photo/si) {
return '';
}
$1 =~ /<table[^>]*?>(.*?)<\/table[^>]*?>/i;
$html .= "\n\n$1\n\n";
$html = "<table cellpadding=\"0\" cellspacing=\"0\">\n$html</table>";
# ...the picture list...
if ($artistHTML =~ /^(Pictures.*?\n*^Credits.*?)$/msi) {
my $imgList = $1;
$imgList =~ /Pictures = new Array\(([^\)]*?)\);/si;
my @images = split /,/, $1;
my $startImg = int rand @images;
$imgList .= "\nvar i = $startImg;\n";
if (Slim::Utils::Prefs::get('plugin_biography_slideshow')) {
$imgList .=
"nextPicture();\nsetInterval(\"nextPicture()\", " .
Slim::Utils::Prefs::get('plugin_biography_slideshow') * 1000 . ");\n";
}
$html .= "<script type=\"text/javascript\"
language=\"javascript\">\n<!--\n$imgList\n-->\n</script>\n";
}
return $html;
}
sub fixBaseURL {
my $html = shift;
$html =~ s/href="(.*?allmovie.com.*?)"/href="$1" target="_blank"/sig;
$html =~ s/href="\/(.*?)"/href="$baseURL\/$1" target="_blank"/sig;
return $html;
}
sub initCacheFolder {
my $cacheFolder = Slim::Utils::Prefs::get('cachedir');
mkdir($cacheFolder) unless (-d $cacheFolder);
$cacheFolder .= "/.slimserver-biography-cache";
mkdir($cacheFolder) unless (-d $cacheFolder);
my $cacheAge = Slim::Utils::Prefs::get('plugin_biography_cache') || 30;
# purge the cache
if (opendir(DIR, $cacheFolder)) {
while (defined(my $cachedFile = readdir(DIR))) {
$cachedFile = "$cacheFolder/$cachedFile";
if (-M $cachedFile > $cacheAge) {
unlink $cachedFile;
}
}
}
else {
$::d_plugins && Slim::Utils::Misc::msg("can't opendir
$cacheFolder: $!\n");
}
closedir(DIR);
# set timer to purge cache once a day
Slim::Utils::Timers::setTimer(0, Time::HiRes::time() + 60*60*24,
\&initCacheFolder);
return $cacheFolder;
}
sub strings {
return q^PLUGIN_BIOGRAPHY
DE Biographie
EN Biography
BIOGRAPHY
DE Biographie
EN Biography
PLUGIN_BIOGRAPHY_RELOAD
DE Aktualisieren
EN Refresh
PLUGIN_BIOGRAPHY_NOTHINGFOUND
DE Konnte keine Informationen finden!
EN Couldn't find any information!
PLUGIN_BIOGRAPHY_NOTEXTFOUND
DE Konnte keinen Biographie-Text finden!
EN Couldn't find any biography text!
PLUGIN_BIOGRAPHY_COLLECTINGINFO
DE Suche Informationen...
EN Gathering information...
SETUP_PLUGIN_BIOGRAPHY_SLIDESHOW
DE Dia-Show
EN Slide show mode
ES Modo Show de Diapositivas
SETUP_PLUGIN_BIOGRAPHY_SLIDESHOW_CHOOSE
DE Bildwechsel-Intervall in Sekunden:
EN Image change interval in seconds:
ES Intervalo para cambio de imágenes en segundos:
SETUP_PLUGIN_BIOGRAPHY_SLIDESHOW_DESC
DE Falls mehrere Bilder gefunden werden, so kann mit einem
Mausklick auf das Bild zwischen diesen gewechselt werden. Sie können diesen
Wechsel aber auch wie bei einer Diashow automatisieren. Geben Sie ein
Zeitinterval an, in welchem die Bilder gewechselt werden sollen. "0"
deaktiviert den automatischen Wechsel. (Vorgabe: 0)
EN There may be more than one picture for an interpret. You can
change between them by clicking on the image or automatically by defining a
change interval. Put 0 if you don't want the pictures to flick automatically or
any other value for the change interval in seconds. (Default: 0)
SETUP_PLUGIN_BIOGRAPHY_REFRESH
DE Aktualisierungs Intervall
EN Refresh interval
ES Intervalo de recarga
SETUP_PLUGIN_BIOGRAPHY_REFRESH_CHOOSE
DE Aktualisierungs-Intervall in Sekunden:
EN Refresh interval in seconds:
ES Intervalo de recarga en segundos:
SETUP_PLUGIN_BIOGRAPHY_REFRESH_DESC
DE Sie können die Biographie im Web-Interface automatisch
aktualisieren lassen, um sie mit der aktuell gespielten Musik zu
synchronisieren. Geben Sie ein Zeitinterval an, in welchem die Biographie
aktualisiert werden soll. "0" deaktiviert die automatische Aktualisierung.
(Vorgabe: 0)
EN You can have the biography in your web interface update
automatically to stay in sync with the currently playing song. Define the
refresh interval in seconds or 0 if you don't want it to be updated
automatically. (Default: 0)
SETUP_PLUGIN_BIOGRAPHY_CACHE
DE Biographie Cache
EN Biography cache
ES Cache de BiografÃas
SETUP_PLUGIN_BIOGRAPHY_CACHE_CHOOSE
DE Anzahl Tage, die die Biographien lokal gespeichert bleiben
sollen:
EN Days to keep local copies of biographies:
SETUP_PLUGIN_BIOGRAPHY_CACHE_DESC
DE Die Biographie-Daten werden im SlimServer Cache-Verzeichnis
zwischengespeichert, um den Zugriff zu beschleunigen. Definieren Sie hier,
wieviele Tage die Daten lokal gespeichert werden sollen.
EN The artist's biographies are cached locally in SlimServer's
cache folder to speed up the display. Define for how many days you want to keep
the local copies. (Default: 30)
SETUP_PLUGIN_BIOGRAPHY_IGNORELIST
DE Liste zu ignorierender Ausdrücke
EN List of strings to be ignored in artist search
SETUP_PLUGIN_BIOGRAPHY_IGNORELIST_CHOOSE
DE Ausdrücke, die bei der Artistensuche ignoriert werden sollen:
EN Strings to be ignored during artist search:
SETUP_PLUGIN_BIOGRAPHY_IGNORELIST_DESC
DE Namen von Musikgruppen sind oft etwas unterschiedlich erfasst
("The Beatles", "Beatles") oder aber nach einem der Musiker benannt. Dies kann
dazu führen, dass die Gruppe nicht gefunden wird, obwohl der Artist sehr wohl
bekannt ist (z.B. "Tony Levin Band"). Definieren Sie hier eine Liste von
Ausdrücken, die bei der Biographie-Suche ignoriert werden sollen, um die Suche
zu vereinfachen (z.B. "Band Trio Quartet The"). Trennen Sie die Ausdrücke mit
einem Leerzeichen.
EN Some names of music groups are often edited in several slightly
different ways ("The Beatles", "Beatles"), or contain the main artist's name.
This can hamper the search for eg. little known or short-lived groups but who's
main artist is well known (eg. "Tony Levin Band"). You can define a list of
strings which are to be ignored when looking for biographies. Please define
those strings space separated (eg. "Band Trio Quartet The").^;
}
1;_______________________________________________
plugins mailing list
[email protected]
http://lists.slimdevices.com/lists/listinfo/plugins