#!/usr/bin/perl -w # player.pl - a script to query SlimServer's TCP CLI interface for information # (C) 2004, Ralph Bolton. Licensed as GPL (http://www.gnu.org/copyleft/gpl.html) # # This script connects to SlimServer's TCP CLI port (usually port 9090) and asks # it for information. It gets the artist, album and title of the what ever the # server is playing on each player. It also checks to see if the player is actually # playing, or is paused, stopped etc. # # The first player that is actually playing is used for output. If no player is # playing, then "not listening to anything at the moment" is output. If a player # is playing a track, then that track's info is output. If the player is playing # ShoutCast Internet Radio, then only the title is available, not album or artist, # so just the title is displayed. # # This script is written for my personal setup. It'll work elsewhere, but you may # want to customise it so that it does exactly what you want. # Correct these for where ever your slimserver is... use constant SERVER => "localhost"; use constant PORT => "9090"; # Set this to 1 for extra output about what the server's being asked and what # it's saying back. Probably not required 99% of the time! use constant SERVER_DEBUG => 0; use IO::Socket; use Socket; use Fcntl; use strict; # Incredibly simple, probably not really required in a sub! sub connect_to_server { my $socket=IO::Socket::INET->new(SERVER . ":" . PORT) or return undef; return $socket; } # ask_server_something # "generic" sub that sends a request to the server and gets the response. # The responses are always prefixed with the request, so that is stripped # from the reply. It'll start to screw up if the request gets mangled by the # server (the server always returns things URI encoded). Ask for stuff as # you get it given to you and you'll be fine! sub ask_server_something { my ($socket,$request)=@_; print "Sending request >$request<\n" if(SERVER_DEBUG); print $socket "$request\n\r"; print "Waiting for response\n" if(SERVER_DEBUG); my $response=<$socket>; return undef if(!defined($response)); print "response is >$response<\n" if(SERVER_DEBUG); $response=~s/[\n\r]//g; return undef if($response eq ""); $request=~s/\?*$//; $response=~s/^$request\s*//; return $response; } # Simple sub that returns the number of players attached to the server. # (0 = no players, unlike a Perl list which would be -1 for no entries!) sub ask_for_players { my ($socket)=@_; my $response=&ask_server_something($socket,"player count"); return undef if(!defined($response)); $response=~s/\D//g; return $response; } # get_player_info # Get all the details from the server for a given player number. The ID # (hardware address) is fetched so that we can get the rest of the info. # This is pretty intollerant of any variations in output. If there's ever # anything a bit weird, nothing gets returned at all. sub get_player_info { my ($socket,$playernum)=@_; my %data=(); $data{'playernum'}=$playernum; $data{'name'}=&ask_server_something($socket,"player name $playernum"); return () if(!defined($data{'name'})); $data{'name'}=~ s/%(..)/pack("c",hex($1))/ge; # URI decode %20 into " " $data{'id'}=&ask_server_something($socket,"player id $playernum"); return () if(!defined($data{'id'})); $data{'artist'}=&ask_server_something($socket,$data{'id'} . " playlist artist"); return () if(!defined($data{'artist'})); $data{'artist'} =~ s/%(..)/pack("c",hex($1))/ge; $data{'album'}=&ask_server_something($socket,$data{'id'} . " playlist album"); return () if(!defined($data{'album'})); $data{'album'} =~ s/%(..)/pack("c",hex($1))/ge; $data{'index'}=&ask_server_something($socket,$data{'id'} . " playlist index ?"); return () if(!defined($data{'index'})); $data{'title'}=&ask_server_something($socket,$data{'id'} . " playlist title " . $data{'index'}); return () if(!defined($data{'title'})); $data{'title'} =~ s/%(..)/pack("c",hex($1))/ge; $data{'mode'}=&ask_server_something($socket,$data{'id'} . " mode ?"); return () if(!defined($data{'mode'})); return %data; } MAIN: { # This is for future enhancements where output gets redirected to # a file. Not implemented here though. my $out=*STDOUT; # If we get called like a CGI, then we have to send a content type. # Even though we're text, we say HTML so that we can include hyperlinks # later (if I ever find a decent site to hyperlink to ;-) if(defined($ENV{'REQUEST_METHOD'})) { print $out "Content-Type: text/html\n\n"; } # Attempt to connect to the server. my $socket=&connect_to_server; die "Couldn't connect to " . SERVER . " on port " . PORT . ": $!\n" if(!defined($socket)); # Get the number of players. This returns 0 for no players my $playercount=&ask_for_players($socket); # Stop if we had a problem getting the player count, or if no players were # on the server. if((!defined($playercount)) || ($playercount eq "") || ($playercount eq "0")) { die "Couldn't get player count on " . SERVER . "\n"; } # Convert the playercount into a Perl list type number. Ie. playercount # 0 is the first in the list, as opposed to player number 1. This is the # way the server works too - player 0 is the first player, player 1 being # the second. Two players are counted as "2", so the player numbers and # the count are one out. We just adjust that... $playercount--; my $i; my @players=(); # Step through each player, getting it's info. We could stop as soon as # we have what we want, but since the connection is open, we might as well # get everthing. Might get inefficient if you have 100 squeezeboxes or something! for($i=0; $i<=$playercount; $i++) { # Ask for each player's name, it's ID and # get what it's playing, and its mode my %player=&get_player_info($socket,$i); # If this isn't set, then nothing's set, so skip this player # as we had some problem getting it's info. next if(!defined($player{'mode'})); # Add the associative array to the list... (by reference) push @players, \%player; } # All done with the server close($socket); # Now sift through and see what's what... my $player; # This is default output if no players are actually playing anything my $output="Not listening to anything at the moment."; foreach $player (@players) { # The first player that's actually playing something gets used... if($$player{'mode'} eq "play") { # This one's playing... # Build the output. album and artist are skipped if they're "0", which # happens if you're listening to ShoutCast radio. THe player's name # (as found in the Slimserver Web GUI) is used as a sort of location. # My two squeezeboxes are called "Living Room" and "bedroom", so it works # for me. Your situation might be different, so adjust the output as required. $output="Listening to \"" . $$player{'title'} . "\""; $output.=" on \"" . $$player{'album'} . "\"" if($$player{'album'} ne "0"); $output.=" by \"" . $$player{'artist'} . "\"" if($$player{'artist'} ne "0"); $output.=" in the " . $$player{'name'}; # Found a playing server, we don't go any further. You might want to change # this, and perhaps have it output what all the playing boxes are doing? last; } } # Now output the result. We could open a file here and output to that...? print $out "$output\n"; } # End of script.