Build a Live365 station using Linux – Part 2 (Song Data)
In the first part of this series I explained how to build and configure the Music Player Daemon (MPD) for use with Live365. In this installment I’ll describe how to send the song metadata (Title, Artist, Album). This is a requirement for ‘live’ mode broadcasters; if you do not send this data your station will not be listed in the directory.
When you run a Live365 station in ‘basic’ mode they pull the song metadata directly from the ID3 tags in your MP3 files. Most MP3 streams include this info. within the stream itself so that it can be displayed by the player software. Live365 does not extract this data from the stream you feed to them. Live365 provides an API (application program interface) that allows you to feed the data to them separate from the music stream.
Feeding the Metadata
The tricky bit is synchronizing the metadata feed with the music feed. Ideally, this would be done from within the music player directly but unfortunately MPD has no ability to do this; we’ll have to write something ourselves. The process will involve extracting the current track information from MPD and then relaying that to Live365 via the ‘add-song’ API. I’ve written a PERL script to handle this; you can find the complete script here.
You’ll need a few PERL modules to accomplish the task at hand (all are available via CPAN):
Audio::MPD – This is the interface to the MPD player
URI::Escape – Encodes text suitable for inclusion in a URL
Data::Dumper – Useful for debugging
Dissect the Code (please, be kind)
First off, let’s get the necessary modules called in:
#!/usr/bin/perl -w #---------------------------------------------- # MPDFeed - Update Live365 with track info as it is # played by MPD. # # 2009-dec-6 - TimC v0.1 # First Go # TODO: Need to check return values from Login and AddSong and act appropriately. #---------------------------------------------- use Audio::MPD; use LWP; use URI::Escape; use Data::Dumper; use strict;
Next, we’ll define some global variables and constants:
#------------------- # G L O B A L S #------------------- my $MPDHOST = 'localhost'; # hostname or IP of machine running MPD my $VOLUME = 90; # Playback volume my $REPEAT = 1; # repeat is ACTIVE my $RANDOM = 1; # random is ACTIVE my $XFADE = 5; # crossfade time is 5s my $mpd; my $L365_LOGIN_URI = 'http://www.live365.com/cgi-bin/api_login.cgi'; my $L365_NP_URI = 'http://www.live365.com/cgi-bin/add_song.cgi'; my $L365_USER = 'YOUR_LIVE365_USERNAME'; my $L365_PW = 'YOUR_LIVE365_PASSWORD'; my $DEBUG = 0; my $browser; # LWP::UserAgent; my $session_id; # Live365 API login session ID
You’ll need to update $L365_USER and $L365_PW appropriatly for your Live365 account. These are the same values you use when you login directly to the Live365 website.
If you wish to see the nitty-gritty details of the scripts operation you can enable the DEBUG flag by setting it to 1;
Let’s look at the song info. update routine:
#---------------------
sub Live365NowPlaying($$$$$)
#---------------------
{
my $title = shift;
my $artist = shift;
my $album = shift;
my $duration = shift;
my $filename = shift;
my $url = '';
my $resp = '';
unless ($browser) {
$browser = LWP::UserAgent->new;
$browser->agent("MPDFeed/0.1 ");
}
if ($DEBUG) {
print 'Tit: [' . $title . '] Art:[' . $artist . '] Alb:[' . $album . '] Dur:[' . $duration . '] FN:[' . $filename . "]\n";
}
$url = $L365_NP_URI . '?version=2&charset=UTF-8'. '&member_name=' . $L365_USER . '&password=' . $L365_PW;
if ($title) { $url .= '&title=' . uri_escape($title); }
if ($artist) { $url .= '&artist=' . uri_escape($artist); }
if ($album) { $url .= '&album=' . uri_escape($album); }
if ($filename) { $url .= '&fileName=' . uri_escape($filename); }
if ($duration) { $url .= '&seconds=' . $duration; } else { $url .= '&seconds=120'; }
if ($DEBUG) { print 'L365: [' . $url . "]\n"; }
$resp = $browser->get($url);
if ($DEBUG) { print 'L365 resp:[' . $resp->content . "]\n"; }
die "Couldn't get $url". ':' . $resp->status_line . "\n" unless $resp->is_success;
if ($DEBUG) { print 'Headers:[ ' . $resp->headers_as_string() . "]\n"; }
return 1;
}
First, it creates $browser object if necessary. It then assembles the API URL from the available info. It starts with the basic API URL and then adds on the track title, artist, album, filename and duration. The duration data is important as it is provided to the Live365 listener’s player to tell it when to update the current song display (album art, title, etc.). If the duration data isn’t available I send along a default of 2 minutes so that the player will check back for the next track.
The importance of being Ernest (who the heck is ‘Ernest’ anyway?)
It is important to supply track title, artist and album info to Live365 as this is what they use to determine if your stream is DMCA compliant. If any of these fields are missing or incorrect the result may be the dreaded ‘de-listing’ notice. We get this data from MPD a bit later in the script, but MPD gets it from the ID3 tags within the audio files so it is vitally important that you review and correct these tags before you begin streaming.
I’m going to skip describing the DisplayStatus() routine — it’s operation is obvious.
Let’s jump down to the mainline of the script:
#-------------------
# M A I N
#-------------------
my $ver = '';
my $mpd_status;
my $mpd_time;
my $mpd_song;
my $time_remain;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
$mpd = Audio::MPD->new(host=>$MPDHOST);
unless ($mpd) { die "*E* Unable to connect to MPD running on $MPDHOST.\n"; }
$ver = $mpd->version();
print "MPD Version is: [$ver]\n";
# - set the correct volume
$mpd->volume($VOLUME);
# - set the playback parms
$mpd->repeat($REPEAT);
# - set the 'shuffle' mode
$mpd->random($RANDOM);
# - set the crossfade parms
$mpd->fade($XFADE);
# - Get the current status
$mpd_status = $mpd->status();
DisplayStatus($mpd_status);
Loopty-Loop
We connect to the MPD daemon and then set several playback options. The ‘volume’ doesn’t impact the stream but it does set the level for local playback. We then grab the current status and display it on the console. We then enter into an endless loop:
# - Loop forever
while (1) {
$mpd_status = $mpd->status();
$mpd_song = $mpd->song();
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
printf "%4d-%02d-%02d %02d:%02d:%02d: ",$year+1900,$mon+1,$mday,$hour,$min,$sec;
if ($mpd_status->state() eq 'play') {
print "Playing: [" . $mpd_song->title() . "]:[" . $mpd_song->artist() . "]:[" . $mpd_song->album() . "]:[" . $mpd_song->file()
. "]:[" . $mpd_status->audio() . "]\n";
$mpd_time = $mpd_status->time();
$time_remain = abs( $mpd_time->seconds_left() ); # abs() to avoid negative values
if ($DEBUG) { print "time remaining: [" . $time_remain . "] seconds]\n"; }
Live365NowPlaying($mpd_song->title(), $mpd_song->artist(), $mpd_song->album(), $time_remain, $mpd_song->file());
} else {
print "Player Stopped: Waiting 2 minutes...\n";
$time_remain = 120;
}
sleep ($time_remain + 1);
$mpd = Audio::MPD->new(host=>$MPDHOST); # the damn thing doesn't maintain a connection -- DOH!
}
Within the loop we grab the current player status and currently playing song data. If the stream is active (the daemon is in ‘play’ state) the current song, along with a timestamp is displayed on the console. We then pass the song data to the update routine for posting to Live365. The script then sleeps for the remainder of the current song.
If the stream is not active, the script simply waits 2 minutes before trying again.
Using this method we can run the script independently of the MPD daemon and it will automatically synchronize itself with the playback.
In the next installment I’ll describe the setup of your live station on Live365.
As always, you can drop questions into the comments or directly to me at dreamlandblues at frontierdigital dot com.
UPDATED 10-jan-10: Fixed the code line numbering.
