PDA

View Full Version : announcing beta (v0.9) of plugin for Live Music Archive



Dan Aronson
2004-04-09, 12:06
Folks,
Enclosed is a beta ready (somewhat tested) version of a plugin to play
concerts from the Live Music Archive
hosted by the internet archive. The Live Music Archive contains over
10,000 recorded concerts which the bands gratefully support for
non-commercial use. Information on the Live Music Archive can be found at
http://www.archive.org/audio/etree.php.

The Plugin consists of three modes.
Mode 1: Scrolling though the list of bands.
up, down: scroll
right: concerts are indexed by year, get list of years
for displayed artist (mode 2)

Mode 2: Scrolling through list of years
up, down: scroll
right: get list of concerts for given year (mode 3)

Mode 3: Scrolling through list of concerts
up, down: scroll
play: replace current playlist with songs from concert
and start playing songs


Feel free to send feedback. Enclosed is plugin...

--dan aronson
dan at catolabs dot org



# Archive.pm by Dan Aronson (danaronson (AT) yahoo (DOT) com)
#
# 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.
use strict;

###########################################
### Section 1. Change these as required ###
###########################################

package Plugins::Archive;

use Slim::Control::Command;

my @bands;
my %band_index;

my $UNKNOWN = 0;
my $FETCH = 1;
my $PARSE = 2;
my $FETCH_SHOW = 3;
my $PLAYING = 4;
my $OK = 5;
my $ERROR = 6;

my $version_string = "v0.9";

my %bands;
my %band_url;
my %current_artist;
my %current_year;
my %year_index;
my %year_count;
my %years;
my @years;
my %old_year;
my %show_index_by_artist;
my %old_show_index_by_artist;
my %shows;
my @concerts;
my %show_count;
my %show_index;

my %content; # used for http content (see subroutine 'get')
my %error; # error during call to 'get'
my %get_status;

sub getDisplayName() {return "Archive"; };


##################################################
### Section 2. Your variables and code go here ###
##################################################


sub setMode() {
my $client = shift;
$get_status{$client} = $UNKNOWN;
$client->lines(\&lines);
unless (%bands) {
$band_index{$client} = 0;
Slim::Buttons::Block::block($client, "Fetching Archive Band List", "");
get_band_listing($client);
} else {
*bands = $bands{$client};
$current_artist{$client} = $bands[$band_index{$client}];
$client->update();
}


}


my %functions = (
'up' => sub {
my $client = shift;
$band_index{$client} = Slim::Buttons::Common::scroll($client,
-1,
$#bands + 1,
$band_index{$client} || 0);
$current_artist{$client} = $bands[$band_index{$client}];
$client->update();
},
'down' => sub {
my $client = shift;
$band_index{$client} = Slim::Buttons::Common::scroll($client,
1,
$#bands + 1,
$band_index{$client} || 0);
$current_artist{$client} = $bands[$band_index{$client}];
$client->update();
},
'left' => sub {
my $client = shift;
Slim::Buttons::Common::popModeRight($client);
},
'right' => sub {
my $client = shift;
Slim::Buttons::Common::pushModeLeft($client, 'Archive::BandPage');
},
'numberScroll' => sub {
my $client = shift;
my $button = shift;
my $digit = shift;
my $line1 = "button: ${button}";
my $line2 = "digit: ${digit}";
Slim::Display::Animation::showBriefly($client, $line1, $line2);
}
);

sub lines {
my $client = shift;
my ($line1, $line2);
$line1 = "etree band listing";
if (-1 == $#bands) {
$line2 = "couldn't find any bands";
} else {
my $band_index = $band_index{$client};
$line1 .= " \#" . ($band_index+1);
$line2 = $bands[$band_index];
}
return ($line1, $line2);
}


# real code

sub convert_from_hex
{
my $name = shift;
my $new = "";
while ($name =~ s/([^%]*)%(\d\d)//) {
$new .= $1 . chr(eval("0x" . "$2"));
}
$new .= $name;
return $new;
}

my $tmp_file = "/tmp/osa.$$";
my $etree_listing = "http://www.archive.org/audio/etreelisting-browse.php?mode=mp3";
my $etree_details_db_url = "http://www.archive.org/audio/etree-details-db.php?id=";


sub get_band_listing
{
my $client = shift;
my $ret = get($client, $etree_listing);
if (0 == $ret) {
Slim::Utils::Timers::setTimer($client, time() + 1, \&get_band_listing, [ $client ]);
} elsif (-1 == $ret) {
Slim::Buttons::Block::unblock($client);
print STDERR "Error when calling get: ", $error{$client};
} else {
# get the real data
my $content = $content{$client};
if ($content) {
while ($content =~ s/collection=etree&mode=mp3&cat=([^&]*)&PHPS//)
{
my $band_name = convert_from_hex($1);
$band_url{$band_name} = $1;
push(@bands, $band_name);
}
}
$bands{$client} = @bands;
$current_artist{$client} = $bands[0];
Slim::Buttons::Block::unblock($client);
$client->update();
}
}




sub get_band_page_listing
{
my $client = shift;
my $url = shift;
my $ret = get($client, $url);
if (0 == $ret) {
Slim::Utils::Timers::setTimer($client, time() + 1, \&get_band_page_listing, [ $client, $url ]);
} elsif (-1 == $ret) {
Slim::Buttons::Block::unblock($client);
print STDERR "Error when calling get: ", $error{$client};
} else {
my $content = $content{$client};
my $band_url = $band_url{$current_artist{$client}};
my $search_string = "collection=etree&mode=mp3&cat=${band_url}%3A%20(\\d\\d\\d\\d)";
while ($content =~ s/collection=etree&mode=mp3&cat=${band_url}%3A%20(\d\d\d\d)//)
{
push(@years, $1);
}
$years{$current_artist{$client}} = join(':', reverse(@years));
@years = split(':', $years{$current_artist{$client}});
$year_count{$client} = $#years;
Slim::Buttons::Block::unblock($client);
$client->update();
}
}



sub band_page_mode_sub {
my $client = shift;
$year_index{$client} = $old_year{$current_artist{$client}} || 0;
$client->lines(\&BandPageLines);
@years = ();
if (!(exists $years{$current_artist{$client}})) {
my $band_url = $band_url{$current_artist{$client}};
my $url = $etree_listing . "&cat=" . $band_url;
Slim::Buttons::Block::block($client, "Fetching year list", $current_artist{$client});
get_band_page_listing($client, $url);
} else {
@years = split(':', $years{$current_artist{$client}});
$year_count{$client} = $#years;
$client->update();
}
};


my $band_page_mode_sub = *band_page_mode_sub;

sub BandPageLines() {
my $client = shift;
my ($line1, $line2);
$line1 = "Year listing for " . $current_artist{$client};
if (-1 != $year_index{$client}) {
$line2 = "Year: " . $years[$year_index{$client}];
} else {
$line2 = "";
}
return($line1, $line2);
};

my %BandPageFunctions =
(
'up' => sub {
my $client = shift;
$year_index{$client} = Slim::Buttons::Common::scroll($client,
-1,
$year_count{$client} + 1,
$year_index{$client});
$client->update();
},
'down' => sub {
my $client = shift;
$year_index{$client} = Slim::Buttons::Common::scroll($client,
1,
$year_count{$client} + 1,
$year_index{$client});
$client->update();
},
'left' => sub {
my $client = shift;
$old_year{$current_artist{$client}} = $year_index{$client};
Slim::Buttons::Common::popModeRight($client);
},
'right' => sub {
my $client = shift;
$current_year{$client} = $years[$year_index{$client}];
Slim::Buttons::Common::pushModeLeft($client, 'Archive::YearPage');
}
);



# Band listing mode
Slim::Buttons::Common::addMode('Archive::BandPage' , \%BandPageFunctions, $band_page_mode_sub);



my $ID_CONCERT_SEPARATOR = "ID_CONCERT_SEPARATOR";
my $CONCERT_LIST_SEPARATOR = "CONCERT_LIST_SEPARATOR";
my $SHOW_LIST_SEPARATOR = "SHOW_LIST_SEPARATOR";

sub year_page_mode_listing
{
my $client = shift;
my $url = shift;
my $ret = get($client, $url);
if (0 == $ret) {
Slim::Utils::Timers::setTimer($client, time() + 1, \&year_page_mode_listing, [ $client, $url ]);
} elsif (-1 == $ret) {
Slim::Buttons::Block::unblock($client);
print STDERR "Error when calling get: ", $error{$client};
} else {
my $content = $content{$client};
my $search_string = "etree-details-db.php\\?id=(\\d+).*<strong>(.*)</strong>";
while ($content =~ s/$search_string//)
{
push(@concerts, join($ID_CONCERT_SEPARATOR, $1, $2));
}
$shows{$current_artist{$client}}{$current_year{$cl ient}} = join($CONCERT_LIST_SEPARATOR, @concerts);
@concerts = split($CONCERT_LIST_SEPARATOR, $shows{$current_artist{$client}}{$current_year{$cl ient}});
$show_count{$client} = $#concerts;
Slim::Buttons::Block::unblock($client);
$client->update();
}
}


sub year_page_mode_sub {
my $client = shift;
my $key = $current_artist{$client} . $SHOW_LIST_SEPARATOR . $current_year{$client};
my @show_list;
$show_index{$client} = $old_show_index_by_artist{$current_artist{$client} }{$current_year{$client}} || 0;
$client->lines(\&YearPageLines);
@concerts = ();
if (!(exists $shows{$current_artist{$client}}{$current_year{$cl ient}})) {
my $year_url = $band_url{$current_artist{$client}} . "%3A%20" .
$current_year{$client};
my $url = $etree_listing . "&cat=" . $year_url;
Slim::Buttons::Block::block($client, "Fetching Concert list from "
. $current_year{$client}, $current_artist{$client});
year_page_mode_listing($client, $url);
} else {
@concerts = split($CONCERT_LIST_SEPARATOR, $shows{$current_artist{$client}}{$current_year{$cl ient}});
$show_count{$client} = $#concerts;
$client->update();
}
};

my $year_page_mode_sub = *year_page_mode_sub;



sub YearPageLines() {
my $client = shift;
my ($line1, $line2);
if (-1 != $show_index{$client}) {
my ($id, $concert) = split($ID_CONCERT_SEPARATOR, $concerts[$show_index{$client}]);
$line1 = $id;
$line2 = $concert;
} else {
$line1 = "Current Artist = " . $current_artist{$client} ."\n";
$line2 = "";
}
return($line1, $line2);
};


sub play_show
{
my $client = shift;
my $url = shift;
my $ret = get($client, $url);
if (0 == $ret) {
Slim::Utils::Timers::setTimer($client, time() + 1, \&play_show, [ $client, $url ]);
} elsif (-1 == $ret) {
Slim::Buttons::Block::unblock($client);
print STDERR "Error when calling get: ", $error{$client};
} else {
my $content = $content{$client};
my @playlist;
if ($content) {
my $song;
my $index = 0;
@playlist = parse_playlist($content);
if (-1 != $#playlist) {
foreach $song (@playlist) {
if (0 == $index) {
Slim::Control::Command::execute($client, ['playlist', 'play', $song]);
} else {
Slim::Control::Command::execute($client, ['playlist', 'add', $song]);
}
$index++;
}
Slim::Control::Command::execute($client, ['play']);
Slim::Buttons::Common::setMode($client, 'playlist');
}
}
Slim::Buttons::Block::unblock($client);
$client->update();
}
}

my %YearPageFunctions =
(
'up' => sub {
my $client = shift;
$show_index{$client} = Slim::Buttons::Common::scroll($client,
-1,
$show_count{$client} + 1,
$show_index{$client});
$client->update();
},
'down' => sub {
my $client = shift;
$show_index{$client} = Slim::Buttons::Common::scroll($client,
1,
$show_count{$client} + 1,
$show_index{$client});
$client->update();
},
'left' => sub {
my $client = shift;
$old_show_index_by_artist{$current_artist{$client} }{$current_year{$client}} = $show_index{$client};
Slim::Buttons::Common::popModeRight($client);
},
'right' => sub {
my $client = shift;
Slim::Display::Animation::bumpRight($client);
},
'play' => sub {
my $client = shift;
my ($id, $concert) = split($ID_CONCERT_SEPARATOR, $concerts[$show_index{$client}]);
my $details_url = $etree_details_db_url . $id;
Slim::Buttons::Block::block($client, "Adding songs to playlist", $concert);
play_show($client, $details_url);
}
);


my $playlist_search_string = ".*<td class=\"fileTitleHeader\">Audio Files(.*?)</table>";
my $song_search_string = ".*<td class=\"fileTitle\">(.*?)</td>.*\?href=\"(.*?vbr.mp3)\?";
my $archive_prefix = "http://www.archive.org";

sub parse_playlist {
$_ = shift;
my $pls_data = "";
my $count = 1;
my @items;
if ( /$playlist_search_string/s ) {
# found song playlist lines, time to build playlist
# we could do real parsing via HTML::TreeBuilder, but this should be faster and ok
my $song_playlist_lines = $1;
while ( $song_playlist_lines =~ s/$song_search_string// ) {
my $name = $1;
my $url = $2;
# this is the whole list of urls, let's find the last one
my $href_string = "href=\"";

my $href_pos = rindex($url, $href_string);
if (-1 == $href_pos) {
# print STDERR "could not parse songs";
next;
} else {
$url = substr($url, $href_pos + length($href_pos) + 3);
}
# print STDERR "found song $name, url: $url\n";
# $pls_data .= "File" . $count . "=" . $archive_prefix . $url . "\n";
# $pls_data .= "Title" . $count . "=" . $name . "\n";
$url = $archive_prefix . $url;
push @items, $url;
Slim::Music::Info::setTitle($url, $name);
$count++;
}
}
return @items;
}
sub parse_playlist_old {
$_ = shift;
my $pls_data = "";
my $count = 1;
if ( /$playlist_search_string/s ) {
# found song playlist lines, time to build playlist
# we could do real parsing via HTML::TreeBuilder, but this should be faster and ok
my $song_playlist_lines = $1;
while ( $song_playlist_lines =~ s/$song_search_string// ) {
my $name = $1;
my $url = $2;
# this is the whole list of urls, let's find the last one
my $href_string = "href=\"";

my $href_pos = rindex($url, $href_string);
if (-1 == $href_pos) {
# print STDERR "could not parse songs";
return "";
} else {
$url = substr($url, $href_pos + length($href_pos) + 3);
}
# print STDERR "found song $name, url: $url\n";
$pls_data .= "File" . $count . "=" . $archive_prefix . $url . "\n";
$pls_data .= "Title" . $count . "=" . $name . "\n";
$count++;
}
}
$pls_data = "[playlist]\n\nnumberofentries=" . $count . "\n\n" . $pls_data;
# print STDERR "pls_data is: ${pls_data}\n";
return $pls_data;
}


# Band listing mode
Slim::Buttons::Common::addMode('Archive::YearPage' , \%YearPageFunctions, $year_page_mode_sub);

# HTTP routine
use strict;
use Errno qw(EINPROGRESS EALREADY EAGAIN);
use Socket;
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
use English;

# this version of get implements a non-blocking http request as an FSM. The first argument
# is an index into some hash tables that keep the state. Calling it returns either:
# 1 if the content has been received, in which case it is in the hash table 'content',
# indexed by the first argument
#
# 0 keep calling, not done yet
#
# -1 error, error string returned in hash table 'error' indexed by the first argument

my $OPENING = 1;
my $OPENED = 2;
my $GETTING = 3;
my $READ = 4;
my $READ_RESPONSE = 5;
my %socket;
my %paddr;
my %request;
my %response;

# some code borrowed

# nonblocking get, returns 0 if it should be called again,
# 1 if the data has been recieved and -1 if there is an error

my $CRLF = "\015\012";

sub get
{
my $client = shift;
my $url = shift;
# print STDERR "calling get, status is: ", $get_status{$client}, "client is: " , $client, "\n";
if ($UNKNOWN == $get_status{$client}) {
$content{$client} = $response{$client} = "";
# parse the request, setup the socket and start the open
if ($url =~ m,^http://([^/:\@]+)(?::(\d+))?(/\S*)?$,) {
my $host = $1;
my $port = $2 || 80;
my $path = $3;
my $iaddr = inet_aton($host);
$path = "/" unless defined($path);
my $paddr = sockaddr_in($port, $iaddr);
# set the socket to non blocking
socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp'));
fcntl(SOCK, F_SETFL, fcntl(SOCK, F_GETFL, 0) | O_NONBLOCK);
my $ret = connect(SOCK, $paddr);
my $netloc = $host;
$netloc .= ":$port" if $port != 80;
$request{$client} = join($CRLF =>
"GET $path HTTP/1.0",
"Host: $netloc",
"User-Agent: SlimServer/Archive",
"", "");

# print STDERR "url = ${url}\n";
# print STDERR "request = ", $request{$client}, "\n";
if ($ret) {
# wow, it connected
$get_status{$client} = $OPENED;
return(get($client));
} else {
#ok if in progress
if (EINPROGRESS == $ERRNO) {
# print STDERR "setting status to opening(" , $OPENING, ")\n";
$get_status{$client} = $OPENING;
$paddr{$client} = $paddr;
return(0);
}
else {
$error{$client} = "Cannot connect to '${url}', $ERRNO";
close(SOCK);
$get_status{$client} = $UNKNOWN;
return(-1);
}
}
}
$error{$client} = "Couldn't parse '${url}'";
return(-1);
} elsif ($OPENING == $get_status{$client}) {
my $ret = connect(SOCK, $paddr{$client});
if ($ret) {
$get_status{$client} = $OPENED;
return(get($client));
} else {
#ok if in progress
if (EALREADY == $ERRNO) {
return(0);
}
else {
$error{$client} = "Cannot connect to '${url}', $ERRNO";
close(SOCK);
$get_status{$client} = $UNKNOWN;
return(-1);
}
}
} elsif ($OPENED == $get_status{$client}) {
# connected, now send the request
my $byte_count = syswrite(SOCK,$request{$client});
my $length = length($request{$client});
if ($byte_count) {
if ($length == $byte_count) {
#all bytes are written, can now change to read mode
$get_status{$client} = $READ_RESPONSE;
return(get($client));
} else {
$request{$client} = substring($request{$client}, $byte_count);
return(0);
}
} else {
if (EAGAIN == $ERRNO) {
return(0);
} else {
$error{$client} = "Cannot write to socket";
close(SOCK);
$get_status{$client} = $UNKNOWN;
return(-1);
}
}
} elsif ($READ_RESPONSE == $get_status{$client}) {
my $data;
my $byte_count = sysread(SOCK, $data, 100000);
if (defined($byte_count)) {
if (0 == $byte_count) {
# end of file, this is an error when it happens here
$error{$client} = "End of file before any real data read...";
close(SOCK);
$get_status{$client} = $UNKNOWN;
return(-1);
} else {
$response{$client} .= $data;
my $pos = index($response{$client}, $CRLF . $CRLF);
if (-1 != $pos) {
$response{$client} =~ / (\d\d\d)/;
my $response_code = $1;
if (200 != $response_code) {
$error{$client} = "HTTP response wasn't 200, was: ${response_code}";
close(SOCK);
$get_status{$client} = $UNKNOWN;
return(-1);
}
$content{$client} = substr($response{$client}, $pos + length($CRLF . $CRLF));
$get_status{$client} = $READ;
}
return(get($client));
}
} else {
if (EAGAIN == $ERRNO) {
return(0);
} else {
$error{$client} = "Error $! while trying to read from socket";
close(SOCK);
$get_status{$client} = $UNKNOWN;
return(-1);
}
}
} elsif ($READ = $get_status{$client}) {
# reading data

my $data;
my $byte_count = sysread(SOCK, $data, 100000);
# print "while processing READ, got ${byte_count} bytes\n";
if (defined($byte_count)) {
if (0 == $byte_count) {
# end of file, finally
close(SOCK);
$get_status{$client} = $UNKNOWN;
return(1);
} else {
$content{$client} .= $data;
return(get($client));
}
} else {
if (EAGAIN == $ERRNO) {
return(0);
} else {
$error{$client} = "Error $! while trying to read from socket";
close(SOCK);
$get_status{$client} = $UNKNOWN;
return(-1);
}
}
} else {
$error{$client} = "Illegal status set, bug in function";
$get_status{$client} = $UNKNOWN;
return(-1);
}
}



################################################
### End of Section 2. ###
################################################

################################
### Ignore from here onwards ###
################################

sub getFunctions() {
return \%functions;
}


1;

Rob Funk
2004-04-09, 12:51
Dan Aronson wrote:
> Enclosed is a beta ready (somewhat tested) version of a plugin to play
> concerts from the Live Music Archive
> hosted by the internet archive. The Live Music Archive contains over
> 10,000 recorded concerts which the bands gratefully support for
> non-commercial use. Information on the Live Music Archive can be found at
> http://www.archive.org/audio/etree.php.

That's a really cool plugin!

I do have some suggestions to improve it a bit, but that doesn't take away
from the usefulness.

It would be nice if the name in the plugin listing were more descriptive
than just "Archive". "Live Music" or "Live Concert" seems to be an
important thing to include there.

There seems to be a bug in hex-decoding. I'm getting band listings like:
Benevento%2FRusso
Deutsch-Vergn%FCgen
Robert Randolph 5Band the Family %Band%5D
The Unbelievable Meltdown 5Bformerly %A440%5D

Ideally, articles ("The") should probably be removed from the sort like they
are in the server itself. (See Info.pm.)

Also, would it be possible for the each listing to include the total number
of items in that list, rather than just "band listing #42"? (42 out of how
many?) The usual "(42/97)" style might fit better.
Could the listings also show how many concerts are available within that
category? It looks like the archive.org web pages include this.

The concert listing part includes a number in the top line that doesn't mean
much to the user. Could the specific date be included there? And the
second line might be more useful with the city added (first) and the venue
coming before the band name, since the band has already been selected.


This is one of those plugins, like the Shoutcast browser, that could greatly
add to the value of the Slim/Squeeze by expanding the amount of music
available. (Especially since the archive isn't just Dead/Phish style jam
bands. :-)

--
==============================| "A slice of life isn't the whole cake
Rob Funk <rfunk (AT) funknet (DOT) net> | One tooth will never make a full grin"
http://www.funknet.net/rfunk | -- Chris Mars, "Stuck in Rewind"

dean
2004-04-09, 13:08
This is totally great!

Rob's feedback is all good. I'd like to see it use the same UI model
and feel as the rest of the SlimServer interface. Once it's closer to
completion, let's ship it with SlimServer so everybody can benefit!

-dean

On Apr 9, 2004, at 12:06 PM, Dan Aronson wrote:

> Folks,
> Enclosed is a beta ready (somewhat tested) version of a plugin to
> play concerts from the Live Music Archive
> hosted by the internet archive. The Live Music Archive contains over
> 10,000 recorded concerts which the bands gratefully support for
> non-commercial use. Information on the Live Music Archive can be
> found at
> http://www.archive.org/audio/etree.php.
>
> The Plugin consists of three modes.
> Mode 1: Scrolling though the list of bands.
> up, down: scroll
> right: concerts are indexed by year, get list of years
> for displayed artist (mode 2)
>
> Mode 2: Scrolling through list of years
> up, down: scroll
> right: get list of concerts for given year (mode 3)
>
> Mode 3: Scrolling through list of concerts
> up, down: scroll
> play: replace current playlist with songs from concert
> and start playing songs
>
>
> Feel free to send feedback. Enclosed is plugin...
>
> --dan aronson
> dan at catolabs dot org
>
>
> # Archive.pm by Dan Aronson (danaronson (AT) yahoo (DOT) com)
> #
> # 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.
> use strict;
>
> ###########################################
> ### Section 1. Change these as required ###
> ###########################################
>
> package Plugins::Archive;
>
> use Slim::Control::Command;
>
> my @bands;
> my %band_index;
>
> my $UNKNOWN = 0;
> my $FETCH = 1;
> my $PARSE = 2;
> my $FETCH_SHOW = 3;
> my $PLAYING = 4;
> my $OK = 5;
> my $ERROR = 6;
>
> my $version_string = "v0.9";
>
> my %bands;
> my %band_url;
> my %current_artist;
> my %current_year;
> my %year_index;
> my %year_count;
> my %years;
> my @years;
> my %old_year;
> my %show_index_by_artist;
> my %old_show_index_by_artist;
> my %shows;
> my @concerts;
> my %show_count;
> my %show_index;
>
> my %content; # used for http content (see subroutine 'get')
> my %error; # error during call to 'get'
> my %get_status;
>
> sub getDisplayName() {return "Archive"; };
>
>
> ##################################################
> ### Section 2. Your variables and code go here ###
> ##################################################
>
>
> sub setMode() {
> my $client = shift;
> $get_status{$client} = $UNKNOWN;
> $client->lines(\&lines);
> unless (%bands) {
> $band_index{$client} = 0;
> Slim::Buttons::Block::block($client, "Fetching Archive Band
> List", "");
> get_band_listing($client);
> } else {
> *bands = $bands{$client};
> $current_artist{$client} = $bands[$band_index{$client}];
> $client->update();
> }
>
>
> }
>
>
> my %functions = (
> 'up' => sub {
> my $client = shift;
> $band_index{$client} = Slim::Buttons::Common::scroll($client,
> -1,
> $#bands + 1,
> $band_index{$client} || 0);
> $current_artist{$client} = $bands[$band_index{$client}];
> $client->update();
> },
> 'down' => sub {
> my $client = shift;
> $band_index{$client} = Slim::Buttons::Common::scroll($client,
> 1,
> $#bands + 1,
> $band_index{$client} || 0);
> $current_artist{$client} = $bands[$band_index{$client}];
> $client->update();
> },
> 'left' => sub {
> my $client = shift;
> Slim::Buttons::Common::popModeRight($client);
> },
> 'right' => sub {
> my $client = shift;
> Slim::Buttons::Common::pushModeLeft($client, 'Archive::BandPage');
> },
> 'numberScroll' => sub {
> my $client = shift;
> my $button = shift;
> my $digit = shift;
> my $line1 = "button: ${button}";
> my $line2 = "digit: ${digit}";
> Slim::Display::Animation::showBriefly($client, $line1, $line2);
> }
> );
>
> sub lines {
> my $client = shift;
> my ($line1, $line2);
> $line1 = "etree band listing";
> if (-1 == $#bands) {
> $line2 = "couldn't find any bands";
> } else {
> my $band_index = $band_index{$client};
> $line1 .= " \#" . ($band_index+1);
> $line2 = $bands[$band_index];
> }
> return ($line1, $line2);
> }
>
>
> # real code
>
> sub convert_from_hex
> {
> my $name = shift;
> my $new = "";
> while ($name =~ s/([^%]*)%(\d\d)//) {
> $new .= $1 . chr(eval("0x" . "$2"));
> }
> $new .= $name;
> return $new;
> }
>
> my $tmp_file = "/tmp/osa.$$";
> my $etree_listing =
> "http://www.archive.org/audio/etreelisting-browse.php?mode=mp3";
> my $etree_details_db_url =
> "http://www.archive.org/audio/etree-details-db.php?id=";
>
>
> sub get_band_listing
> {
> my $client = shift;
> my $ret = get($client, $etree_listing);
> if (0 == $ret) {
> Slim::Utils::Timers::setTimer($client, time() + 1,
> \&get_band_listing, [ $client ]);
> } elsif (-1 == $ret) {
> Slim::Buttons::Block::unblock($client);
> print STDERR "Error when calling get: ", $error{$client};
> } else {
> # get the real data
> my $content = $content{$client};
> if ($content) {
> while ($content =~ s/collection=etree&mode=mp3&cat=([^&]*)&PHPS//)
> {
> my $band_name = convert_from_hex($1);
> $band_url{$band_name} = $1;
> push(@bands, $band_name);
> }
> }
> $bands{$client} = @bands;
> $current_artist{$client} = $bands[0];
> Slim::Buttons::Block::unblock($client);
> $client->update();
> }
> }
>
>
>
>
> sub get_band_page_listing
> {
> my $client = shift;
> my $url = shift;
> my $ret = get($client, $url);
> if (0 == $ret) {
> Slim::Utils::Timers::setTimer($client, time() + 1,
> \&get_band_page_listing, [ $client, $url ]);
> } elsif (-1 == $ret) {
> Slim::Buttons::Block::unblock($client);
> print STDERR "Error when calling get: ", $error{$client};
> } else {
> my $content = $content{$client};
> my $band_url = $band_url{$current_artist{$client}};
> my $search_string =
> "collection=etree&mode=mp3&cat=${band_url}%3A%20(\\d\\d\\d\\d)";
> while ($content =~
> s/collection=etree&mode=mp3&cat=${band_url}%3A%20(\d\d\d\d)//)
> {
> push(@years, $1);
> }
> $years{$current_artist{$client}} = join(':', reverse(@years));
> @years = split(':', $years{$current_artist{$client}});
> $year_count{$client} = $#years;
> Slim::Buttons::Block::unblock($client);
> $client->update();
> }
> }
>
>
>
> sub band_page_mode_sub {
> my $client = shift;
> $year_index{$client} = $old_year{$current_artist{$client}} || 0;
> $client->lines(\&BandPageLines);
> @years = ();
> if (!(exists $years{$current_artist{$client}})) {
> my $band_url = $band_url{$current_artist{$client}};
> my $url = $etree_listing . "&cat=" . $band_url;
> Slim::Buttons::Block::block($client, "Fetching year list",
> $current_artist{$client});
> get_band_page_listing($client, $url);
> } else {
> @years = split(':', $years{$current_artist{$client}});
> $year_count{$client} = $#years;
> $client->update();
> }
> };
>
>
> my $band_page_mode_sub = *band_page_mode_sub;
>
> sub BandPageLines() {
> my $client = shift;
> my ($line1, $line2);
> $line1 = "Year listing for " . $current_artist{$client};
> if (-1 != $year_index{$client}) {
> $line2 = "Year: " . $years[$year_index{$client}];
> } else {
> $line2 = "";
> }
> return($line1, $line2);
> };
>
> my %BandPageFunctions =
> (
> 'up' => sub {
> my $client = shift;
> $year_index{$client} = Slim::Buttons::Common::scroll($client,
> -1,
> $year_count{$client} + 1,
> $year_index{$client});
> $client->update();
> },
> 'down' => sub {
> my $client = shift;
> $year_index{$client} = Slim::Buttons::Common::scroll($client,
> 1,
> $year_count{$client} + 1,
> $year_index{$client});
> $client->update();
> },
> 'left' => sub {
> my $client = shift;
> $old_year{$current_artist{$client}} = $year_index{$client};
> Slim::Buttons::Common::popModeRight($client);
> },
> 'right' => sub {
> my $client = shift;
> $current_year{$client} = $years[$year_index{$client}];
> Slim::Buttons::Common::pushModeLeft($client, 'Archive::YearPage');
> }
> );
>
>
>
> # Band listing mode
> Slim::Buttons::Common::addMode('Archive::BandPage' ,
> \%BandPageFunctions, $band_page_mode_sub);
>
>
>
> my $ID_CONCERT_SEPARATOR = "ID_CONCERT_SEPARATOR";
> my $CONCERT_LIST_SEPARATOR = "CONCERT_LIST_SEPARATOR";
> my $SHOW_LIST_SEPARATOR = "SHOW_LIST_SEPARATOR";
>
> sub year_page_mode_listing
> {
> my $client = shift;
> my $url = shift;
> my $ret = get($client, $url);
> if (0 == $ret) {
> Slim::Utils::Timers::setTimer($client, time() + 1,
> \&year_page_mode_listing, [ $client, $url ]);
> } elsif (-1 == $ret) {
> Slim::Buttons::Block::unblock($client);
> print STDERR "Error when calling get: ", $error{$client};
> } else {
> my $content = $content{$client};
> my $search_string =
> "etree-details-db.php\\?id=(\\d+).*<strong>(.*)</strong>";
> while ($content =~ s/$search_string//)
> {
> push(@concerts, join($ID_CONCERT_SEPARATOR, $1, $2));
> }
> $shows{$current_artist{$client}}{$current_year{$cl ient}} =
> join($CONCERT_LIST_SEPARATOR, @concerts);
> @concerts = split($CONCERT_LIST_SEPARATOR,
> $shows{$current_artist{$client}}{$current_year{$cl ient}});
> $show_count{$client} = $#concerts;
> Slim::Buttons::Block::unblock($client);
> $client->update();
> }
> }
>
>
> sub year_page_mode_sub {
> my $client = shift;
> my $key = $current_artist{$client} . $SHOW_LIST_SEPARATOR .
> $current_year{$client};
> my @show_list;
> $show_index{$client} =
> $old_show_index_by_artist{$current_artist{$client} }{$current_year{$clie
> nt}} || 0;
> $client->lines(\&YearPageLines);
> @concerts = ();
> if (!(exists
> $shows{$current_artist{$client}}{$current_year{$cl ient}})) {
> my $year_url = $band_url{$current_artist{$client}} . "%3A%20" .
> $current_year{$client};
> my $url = $etree_listing . "&cat=" . $year_url;
> Slim::Buttons::Block::block($client, "Fetching Concert list from "
> . $current_year{$client}, $current_artist{$client});
> year_page_mode_listing($client, $url);
> } else {
> @concerts = split($CONCERT_LIST_SEPARATOR,
> $shows{$current_artist{$client}}{$current_year{$cl ient}});
> $show_count{$client} = $#concerts;
> $client->update();
> }
> };
>
> my $year_page_mode_sub = *year_page_mode_sub;
>
>
>
> sub YearPageLines() {
> my $client = shift;
> my ($line1, $line2);
> if (-1 != $show_index{$client}) {
> my ($id, $concert) = split($ID_CONCERT_SEPARATOR,
> $concerts[$show_index{$client}]);
> $line1 = $id;
> $line2 = $concert;
> } else {
> $line1 = "Current Artist = " . $current_artist{$client} ."\n";
> $line2 = "";
> }
> return($line1, $line2);
> };
>
>
> sub play_show
> {
> my $client = shift;
> my $url = shift;
> my $ret = get($client, $url);
> if (0 == $ret) {
> Slim::Utils::Timers::setTimer($client, time() + 1, \&play_show, [
> $client, $url ]);
> } elsif (-1 == $ret) {
> Slim::Buttons::Block::unblock($client);
> print STDERR "Error when calling get: ", $error{$client};
> } else {
> my $content = $content{$client};
> my @playlist;
> if ($content) {
> my $song;
> my $index = 0;
> @playlist = parse_playlist($content);
> if (-1 != $#playlist) {
> foreach $song (@playlist) {
> if (0 == $index) {
> Slim::Control::Command::execute($client, ['playlist', 'play',
> $song]);
> } else {
> Slim::Control::Command::execute($client, ['playlist', 'add',
> $song]);
> }
> $index++;
> }
> Slim::Control::Command::execute($client, ['play']);
> Slim::Buttons::Common::setMode($client, 'playlist');
> }
> }
> Slim::Buttons::Block::unblock($client);
> $client->update();
> }
> }
>
> my %YearPageFunctions =
> (
> 'up' => sub {
> my $client = shift;
> $show_index{$client} = Slim::Buttons::Common::scroll($client,
> -1,
> $show_count{$client} + 1,
> $show_index{$client});
> $client->update();
> },
> 'down' => sub {
> my $client = shift;
> $show_index{$client} = Slim::Buttons::Common::scroll($client,
> 1,
> $show_count{$client} + 1,
> $show_index{$client});
> $client->update();
> },
> 'left' => sub {
> my $client = shift;
>
> $old_show_index_by_artist{$current_artist{$client} }{$current_year{$clie
> nt}} = $show_index{$client};
> Slim::Buttons::Common::popModeRight($client);
> },
> 'right' => sub {
> my $client = shift;
> Slim::Display::Animation::bumpRight($client);
> },
> 'play' => sub {
> my $client = shift;
> my ($id, $concert) = split($ID_CONCERT_SEPARATOR,
> $concerts[$show_index{$client}]);
> my $details_url = $etree_details_db_url . $id;
> Slim::Buttons::Block::block($client, "Adding songs to playlist",
> $concert);
> play_show($client, $details_url);
> }
> );
>
>
> my $playlist_search_string = ".*<td class=\"fileTitleHeader\">Audio
> Files(.*?)</table>";
> my $song_search_string = ".*<td
> class=\"fileTitle\">(.*?)</td>.*\?href=\"(.*?vbr.mp3)\?";
> my $archive_prefix = "http://www.archive.org";
>
> sub parse_playlist {
> $_ = shift;
> my $pls_data = "";
> my $count = 1;
> my @items;
> if ( /$playlist_search_string/s ) {
> # found song playlist lines, time to build playlist
> # we could do real parsing via HTML::TreeBuilder, but this should be
> faster and ok
> my $song_playlist_lines = $1;
> while ( $song_playlist_lines =~ s/$song_search_string// ) {
> my $name = $1;
> my $url = $2;
> # this is the whole list of urls, let's find the last one
> my $href_string = "href=\"";
>
> my $href_pos = rindex($url, $href_string);
> if (-1 == $href_pos) {
> # print STDERR "could not parse songs";
> next;
> } else {
> $url = substr($url, $href_pos + length($href_pos) + 3);
> }
> # print STDERR "found song $name, url: $url\n";
> # $pls_data .= "File" . $count . "=" . $archive_prefix . $url .
> "\n";
> # $pls_data .= "Title" . $count . "=" . $name . "\n";
> $url = $archive_prefix . $url;
> push @items, $url;
> Slim::Music::Info::setTitle($url, $name);
> $count++;
> }
> }
> return @items;
> }
> sub parse_playlist_old {
> $_ = shift;
> my $pls_data = "";
> my $count = 1;
> if ( /$playlist_search_string/s ) {
> # found song playlist lines, time to build playlist
> # we could do real parsing via HTML::TreeBuilder, but this should be
> faster and ok
> my $song_playlist_lines = $1;
> while ( $song_playlist_lines =~ s/$song_search_string// ) {
> my $name = $1;
> my $url = $2;
> # this is the whole list of urls, let's find the last one
> my $href_string = "href=\"";
>
> my $href_pos = rindex($url, $href_string);
> if (-1 == $href_pos) {
> # print STDERR "could not parse songs";
> return "";
> } else {
> $url = substr($url, $href_pos + length($href_pos) + 3);
> }
> # print STDERR "found song $name, url: $url\n";
> $pls_data .= "File" . $count . "=" . $archive_prefix . $url .
> "\n";
> $pls_data .= "Title" . $count . "=" . $name . "\n";
> $count++;
> }
> }
> $pls_data = "[playlist]\n\nnumberofentries=" . $count . "\n\n" .
> $pls_data;
> # print STDERR "pls_data is: ${pls_data}\n";
> return $pls_data;
> }
>
>
> # Band listing mode
> Slim::Buttons::Common::addMode('Archive::YearPage' ,
> \%YearPageFunctions, $year_page_mode_sub);
>
> # HTTP routine
> use strict;
> use Errno qw(EINPROGRESS EALREADY EAGAIN);
> use Socket;
> use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
> use English;
>
> # this version of get implements a non-blocking http request as an
> FSM. The first argument
> # is an index into some hash tables that keep the state. Calling it
> returns either:
> # 1 if the content has been received, in which case it is in the
> hash table 'content',
> # indexed by the first argument
> #
> # 0 keep calling, not done yet
> #
> # -1 error, error string returned in hash table 'error' indexed by
> the first argument
>
> my $OPENING = 1;
> my $OPENED = 2;
> my $GETTING = 3;
> my $READ = 4;
> my $READ_RESPONSE = 5;
> my %socket;
> my %paddr;
> my %request;
> my %response;
>
> # some code borrowed
>
> # nonblocking get, returns 0 if it should be called again,
> # 1 if the data has been recieved and -1 if there is an error
>
> my $CRLF = "\015\012";
>
> sub get
> {
> my $client = shift;
> my $url = shift;
> # print STDERR "calling get, status is: ", $get_status{$client},
> "client is: " , $client, "\n";
> if ($UNKNOWN == $get_status{$client}) {
> $content{$client} = $response{$client} = "";
> # parse the request, setup the socket and start the open
> if ($url =~ m,^http://([^/:\@]+)(?::(\d+))?(/\S*)?$,) {
> my $host = $1;
> my $port = $2 || 80;
> my $path = $3;
> my $iaddr = inet_aton($host);
> $path = "/" unless defined($path);
> my $paddr = sockaddr_in($port, $iaddr);
> # set the socket to non blocking
> socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp'));
> fcntl(SOCK, F_SETFL, fcntl(SOCK, F_GETFL, 0) | O_NONBLOCK);
> my $ret = connect(SOCK, $paddr);
> my $netloc = $host;
> $netloc .= ":$port" if $port != 80;
> $request{$client} = join($CRLF =>
> "GET $path HTTP/1.0",
> "Host: $netloc",
> "User-Agent: SlimServer/Archive",
> "", "");
>
> # print STDERR "url = ${url}\n";
> # print STDERR "request = ", $request{$client}, "\n";
> if ($ret) {
> # wow, it connected
> $get_status{$client} = $OPENED;
> return(get($client));
> } else {
> #ok if in progress
> if (EINPROGRESS == $ERRNO) {
> # print STDERR "setting status to opening(" , $OPENING, ")\n";
> $get_status{$client} = $OPENING;
> $paddr{$client} = $paddr;
> return(0);
> }
> else {
> $error{$client} = "Cannot connect to '${url}', $ERRNO";
> close(SOCK);
> $get_status{$client} = $UNKNOWN;
> return(-1);
> }
> }
> }
> $error{$client} = "Couldn't parse '${url}'";
> return(-1);
> } elsif ($OPENING == $get_status{$client}) {
> my $ret = connect(SOCK, $paddr{$client});
> if ($ret) {
> $get_status{$client} = $OPENED;
> return(get($client));
> } else {
> #ok if in progress
> if (EALREADY == $ERRNO) {
> return(0);
> }
> else {
> $error{$client} = "Cannot connect to '${url}', $ERRNO";
> close(SOCK);
> $get_status{$client} = $UNKNOWN;
> return(-1);
> }
> }
> } elsif ($OPENED == $get_status{$client}) {
> # connected, now send the request
> my $byte_count = syswrite(SOCK,$request{$client});
> my $length = length($request{$client});
> if ($byte_count) {
> if ($length == $byte_count) {
> #all bytes are written, can now change to read mode
> $get_status{$client} = $READ_RESPONSE;
> return(get($client));
> } else {
> $request{$client} = substring($request{$client}, $byte_count);
> return(0);
> }
> } else {
> if (EAGAIN == $ERRNO) {
> return(0);
> } else {
> $error{$client} = "Cannot write to socket";
> close(SOCK);
> $get_status{$client} = $UNKNOWN;
> return(-1);
> }
> }
> } elsif ($READ_RESPONSE == $get_status{$client}) {
> my $data;
> my $byte_count = sysread(SOCK, $data, 100000);
> if (defined($byte_count)) {
> if (0 == $byte_count) {
> # end of file, this is an error when it happens here
> $error{$client} = "End of file before any real data read...";
> close(SOCK);
> $get_status{$client} = $UNKNOWN;
> return(-1);
> } else {
> $response{$client} .= $data;
> my $pos = index($response{$client}, $CRLF . $CRLF);
> if (-1 != $pos) {
> $response{$client} =~ / (\d\d\d)/;
> my $response_code = $1;
> if (200 != $response_code) {
> $error{$client} = "HTTP response wasn't 200, was: ${response_code}";
> close(SOCK);
> $get_status{$client} = $UNKNOWN;
> return(-1);
> }
> $content{$client} = substr($response{$client}, $pos +
> length($CRLF . $CRLF));
> $get_status{$client} = $READ;
> }
> return(get($client));
> }
> } else {
> if (EAGAIN == $ERRNO) {
> return(0);
> } else {
> $error{$client} = "Error $! while trying to read from socket";
> close(SOCK);
> $get_status{$client} = $UNKNOWN;
> return(-1);
> }
> }
> } elsif ($READ = $get_status{$client}) {
> # reading data
>
> my $data;
> my $byte_count = sysread(SOCK, $data, 100000);
> # print "while processing READ, got ${byte_count} bytes\n";
> if (defined($byte_count)) {
> if (0 == $byte_count) {
> # end of file, finally
> close(SOCK);
> $get_status{$client} = $UNKNOWN;
> return(1);
> } else {
> $content{$client} .= $data;
> return(get($client));
> }
> } else {
> if (EAGAIN == $ERRNO) {
> return(0);
> } else {
> $error{$client} = "Error $! while trying to read from socket";
> close(SOCK);
> $get_status{$client} = $UNKNOWN;
> return(-1);
> }
> }
> } else {
> $error{$client} = "Illegal status set, bug in function";
> $get_status{$client} = $UNKNOWN;
> return(-1);
> }
> }
>
>
>
> ################################################
> ### End of Section 2. ###
> ################################################
>
> ################################
> ### Ignore from here onwards ###
> ################################
>
> sub getFunctions() {
> return \%functions;
> }
>
>
> 1;
>

Rob Funk
2004-04-09, 14:19
Some more suggestions for the UI. These are just ideas.


Band listing has the meaningless (to the user) word "etree", maybe should
include the plugin name? Maybe something like "Live Concerts Archive
(1/123)".

Year selection redundantly has the word "Year" in both lines.
Maybe top line "Concert Years (1/4)", bottom line just the year.

In concert list mode, maybe put year/concert#/total#, e.g. "2004 concerts
(1/5)" in top line, date/city/venue (in that order) in second line. Band
name gets in the way in the second line, though it wouldn't hurt in the top
line other than crowding out other info there.


Going from tweaks to wishlist....

Maybe add another mode that allows browsing the artist, date, venue, and
concert notes. (Like when you hit the right arrow from a selected song
elsewhere, and can scroll throough Artist, Title, Album, Track, Year,
Genre, Format, Length, Rate, etc.)

I'm not sure if this is possible with streaming playlist entries, but it
would be nice if the information in the constructed playlist could include
the artist information without making it part of the title.


Again, great plugin.
--
==============================| "A slice of life isn't the whole cake
Rob Funk <rfunk (AT) funknet (DOT) net> | One tooth will never make a full grin"
http://www.funknet.net/rfunk | -- Chris Mars, "Stuck in Rewind"

Dan Aronson
2004-04-09, 14:25
Rob Funk wrote:

>Some more suggestions for the UI. These are just ideas.
>
>
>Band listing has the meaningless (to the user) word "etree", maybe should
>include the plugin name? Maybe something like "Live Concerts Archive
>(1/123)".
>
>
yup, working on it.

>Year selection redundantly has the word "Year" in both lines.
>Maybe top line "Concert Years (1/4)", bottom line just the year.
>
>
trying to figure out the right thing here.

>In concert list mode, maybe put year/concert#/total#, e.g. "2004 concerts
>(1/5)" in top line, date/city/venue (in that order) in second line. Band
>name gets in the way in the second line, though it wouldn't hurt in the top
>line other than crowding out other info there.
>
>
sigh, that requires some more detailed parsing then I'm doind now and I
would prefer not
to have to relay on any CPAN modules, since that may make installation
harder. This is one that I need to think about more.

>
>Going from tweaks to wishlist....
>
>Maybe add another mode that allows browsing the artist, date, venue, and
>concert notes. (Like when you hit the right arrow from a selected song
>elsewhere, and can scroll throough Artist, Title, Album, Track, Year,
>Genre, Format, Length, Rate, etc.)
>
>
Hopefully the magic of open source, it would be great if once this
really gets out there, someonw wants to takle this.

>I'm not sure if this is possible with streaming playlist entries, but it
>would be nice if the information in the constructed playlist could include
>the artist information without making it part of the title.
>
>
I agree, I think this sounds like a feature request for streaming
playlist entries in the server itself.

--dan