Controlling iTunes with Perl
Pages: 1, 2
iTunes, Perl, and Apache
I have been using Apple's AirPort for a while. We swear by it in my household, and my guests like to bring their laptops and wireless cards when they visit. The Airport has raised our computer expectations -- we want to be able to do any task from anywhere in the house. However, when it comes to playing music, we have a problem. Which computer is hooked up to the stereo? I do not like listening to music on the built-in speakers of my laptop, so I have another Mac hooked up to my stereo and a very large external hard drive filled with MP3s.
With all of that, I cannot carry that computer around my apartment. Even if I could, I want it to just play music and perhaps perform other silent tasks. I should not have to interrupt my music because I decide to change something on the Mac I am working on. I want the music to keep playing even if I restart the iTunes on my laptop, which I do frequently while developing Mac::iTunes.
I need to control this central MP3 player remotely. I could create a command line tool to control iTunes and then log in the machine with ssh, but not everyone who wants to control iTunes likes using the Terminal. I need a more pleasing interface. Since Mac OS X comes with the Apache Web server (which runs by default), I can write a CGI script to control iTunes.
#!/usr/bin/perl
# $Id: iTunes.cgi,v 1.3 2002/09/30 05:09:02 comdog Exp $
use strict;
use CGI qw(:standard);
use Mac::iTunes;
use Text::Template;
my $Template = '/Users/brian/Dev/MacOSX/iTunes/html/iTunes.html';
=head1 NAME
iTunes.cgi - control iTunes from the web
=head1 SYNOPSIS
=head1 DESCRIPTION
This is only a proof-of-concept script.
=head1 AUTHOR
brian d foy, E<lt>bdfoy@cpan.orgE<gt>
=head1 COPYRIGHT
Copyright 2002 brian d foy, All rights reserved
=cut
my $controller = Mac::iTunes->new()->controller;
my $command = param('command');
my $playlist = param('playlist') || 'Library';
my $set_playlist = param('set_playlist');
if( $command )
{
my %Commands = map { $_, 1 }
qw( play stop pause back_track);
$controller->$command
if exists $Commands{$command};
}
elsif( $set_playlist )
{
$controller->_set_playlist( $set_playlist );
$playlist = $set_playlist;
}
my %var;
$var{base}
= 'http://10.0.1.2:8080/cgi-bin/iTunes.cgi';
$var{state} = $controller->player_state;
$var{current} = $controller->current_track_name;
$var{playlist} = $playlist;
$var{playlists} = $controller->get_playlists;
$var{tracks}
= $controller->get_track_names_in_playlist(
$playlist );
my $html = Text::Template::fill_in_file(
$Template, HASH => \%var );
print header(), $html, "\n";
On the first run without input, the script creates an iTunes controller object, sets the starting playlist to Library (the iTunes virtual playlist that has everything iTunes knows about), then asks iTunes for a lot of state information, including the names of tracks in the playlists, the names of the playlists, and what iTunes is currently doing (e.g. playing or stopped). The script uses Text::Template to turn all of this into HTML, which it sends back to a Web browser. The template file I use is in the html directory of the Mac::iTunes distribution, and those with any sort of design skills will surely want to change it to something more pleasing. The code is separated from the presentation.
|
|
I have a small problem with this approach. To tell an application to do something through AppleScript, the telling program has to be running as a logged-in user. The Web server is set up to run as the unprivileged pseudo-user "nobody," so this CGI script will not work from the stock Apache configuration. This is not much of a problem, since I can make Apache run under my user. On my machine, I run a second Apache server with the same configuration file save for a couple of changes.
|
Related Reading Mac OS X in a Nutshell |
First, I have to make the Web server run as my user, so I change the User directive. Along with that, I have to choose another port, since only root can use port numbers below 1024, and Apache expects to use port 80. I choose port 8080 instead. I will have to pass this non-standard port along in any URLs, but my CGI script already does that. As long as I use the Web interface without typing into the Web browser's location box, I will not have to worry about that.
User brian
Port 8080
I also have to change any file paths that Apache expects to write to. Since Apache runs as my user, it can only create files were I can create files.
PidFile "/Users/brian/httpd-brian.pid"
Once everything is set up, I access the CGI script from any computer in my home network, Mac or not, and I can control my central iTunes.
iTunes, Perl, Apache, and mod_perl
CGI scripts are slow. Every time I run a CGI script, the Web server has to launch the script and the script has to load all of the modules that it needs to do its work. I have another problem with Mac::iTunes, though. The first call to Mac::AppleScript's RunAppleScript() seems to be slower than subsequent calls. I pay a first-use penalty for that. To get around that, I want to keep my iTunes controller running so I do not have to pay this overhead over and over again.
I created Apache::iTunes to do just that. I could run my
CGI script under Apache::Registry, but I like the native
Apache interface better. I configured my Web server to hand
off any requests of a URL starting with /iTunes to my
module. I use PerlSetEnv directives to configure the literal
data I had in the CGI version.
<Location /iTunes>
SetHandler perl-script
PerlHandler Apache::iTunes
PerlModule Mac::iTunes
PerlInitHandler Apache::StatINC
PerlSetEnv APACHE_ITUNES_HTML /web/templates/iTunes.html
PerlSetEnv APACHE_ITUNES_URL http://www.example.com:8080/iTunes
PerlSetEnv APACHE_ITUNES 1
</Location>
The output looks a little different from the CGI version because I used a different template that included more features. I can change the look-and-feel without touching the code.
|
|
I tend to like the mod_perl interface more. Instead of passing variables around in the query string, the URL itself is the command and is simple, short, and without funny-looking characters.
http://www.example.com/iTunes/play
http://www.example.com/iTunes/stop
iTunes, Perl, and Tk
As I was working on Apache::iTunes, I was also working on a different project that needed Tk. I was programming things on FreeBSD, but I like to work on my Mac. That's easy enough, since I have XDarwin and OrobosX installed. Under Mac OS X 10.2 these work without a problem, although if you use 10.1 you have to perform a little bit of surgery on your system, following Steve Lidie's instructions. Since I had been away from the Tk world for awhile, I was referring to Mastering Perl/Tk quite a bit. As I was flipping through the pages on my way to the next thing I needed to read, I noticed a screen shot of iTunes. It was not really iTunes though -- Steve Lidie had taken the iTunes look-and-feel as a front end for his MP3 player example.
I already had all of the back-end stuff to control iTunes and none of it was tied to a particular interface. Even my CGI script could output something other than HTML, like plain text or even a huge image. I could easily add a Tk interface to the same thing -- or so I thought.
Controlling iTunes is easy. Controlling it from a Web page is easy. Controlling it from Tk, which has a persistent connection to whatever it hooks up to, was harder. Since I had the persistent connection, I could reflect changes in iTunes instantaneously. In the Web versions, if somebody else changed the state, like changing the song or muting the volume, the Web page would not show that until I reloaded. The Tk interface could show it almost instantaneously. In reality, I could only get the Tk interface to poll iTunes for its state every 3 1/2 seconds or so before it took a big drop in performance, but that is good enough for me.
|
|
The tk-itunes.pl script comes with Mac::iTunes. Someday I might develop a skins mechanism for it -- all I, or somebody else, needs to do is make the colors configurable. The script already uses a configuration file, although I can only configure a few things at the moment.
Final Thoughts
Perl can interact with Aqua applications through AppleScript. With Mac::iTunes as a back end, I can create multiple interfaces to iTunes that I can use on the same computer or on other computers on the same network. Everyone in my house, or within range of my AirPort, can control my iTunes.
brian d foy is a Perl trainer for Stonehenge Consulting Services and is the publisher of The Perl Review.
Return to the Mac DevCenter.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 20 of 20.
-
Perfect
2006-06-11 08:16:11 cetaae [Reply | View]
Thanks, this works very nice, no problems at all with installation, and it works like a charm.
-
Installation Dies
2003-09-08 01:43:19 anonymous2 [Reply | View]
I am using CPAN to install the module, and I get these errors:
.
.
.
Checking if your kit is complete...
Looks good
dyld: /usr/bin/perl Undefined symbols:
_Perl_sv_2pv
_perl_get_sv
Running make test
Make had some problems, maybe interrupted? Won't test
Running make install
Make had some problems, maybe interrupted? Won't install
.
.
.
Perl 5.8
OS 10.2.6
it looks as if my perl installation is bjorked.. any advice would be helpful.
-
Installation Dies
2003-09-08 01:43:15 anonymous2 [Reply | View]
I am using CPAN to install the module, and I get these errors:
.
.
.
Checking if your kit is complete...
Looks good
dyld: /usr/bin/perl Undefined symbols:
_Perl_sv_2pv
_perl_get_sv
Running make test
Make had some problems, maybe interrupted? Won't test
Running make install
Make had some problems, maybe interrupted? Won't install
.
.
.
Perl 5.8
OS 10.2.6
it looks as if my perl installation is bjorked.. any advice would be helpful.
-
it almost works...
2003-07-13 19:47:37 anonymous2 [Reply | View]
Did a version change in itunes mangle the perl module?
Any help in getting this going again would be much appreciated.
Thanks,
Keith
I get this in the apache logs:
[Sun Jul 13 19:33:04 2003] [error] [client 127.0.0.1] Premature end of script headers: itunes.cgi
[Sun Jul 13 19:33:04 2003] [error] [client 127.0.0.1] -1753 at /usr/local/apache2.0.47/cgi-bin/itunes.cgi line 59
and an Error 500 on the web page.
I'm running these versions:
Apache 2.0.47
iTunes 4.0.1 (117)
If I run on the command line I get this:
[goomba:/usr/local/apache2.0.47] danya% cgi-bin/iTunes.cgi
-1753 at cgi-bin/iTunes.cgi line 59
Content-Type: text/html; charset=ISO-8859-1
<html>
<head><title>iTunes Web Interface</title></head>
<body>
<h1>iTunes Web Interface</h1>
player state is stopped.
current track is p$+.
current playlist is Library.
||
Play ||
Stop ||
Pause ||
Restart ||
<table>
<tr>
<td bgcolor="#FFFF00">Playlists</td>
<td bgcolor="#FFFF00">Tracks</td>
</tr>
</tr>
<tr><td valign="top">
<table>
<tr><td bgcolor="#00ccff">Library</td></tr>
<tr><td>Natasha's Songs</td></tr>
<tr><td>Recently Played</td></tr>
<tr><td>Top 25 Most Played</td></tr>
<tr><td>Top N Least Played</td></tr>
<tr><td>Funkytown</td></tr>
<tr><td>Olde Lady Songs</td></tr>
</table>
</td>
<td valign="top">
<table>
</table>
</td></tr>
</table>
</body>
</html>
[goomba:/usr/local/apache2.0.47] danya%
-
iTunes as nobody?
2003-02-10 23:50:47 anonymous2 [Reply | View]
Hello.
Is it at all possible to run iTunes as nobody instead of running Apache as me? I tried the command "sudo -u nobody /Applications/iTunes/Contents/MacOS/iTunes", but it spit out a long list of errors that appeared to mean that iTunes could not connect to a display. Is there any other way to accomplish this?
-
Guide to CPAN for first timers?
2002-11-27 13:29:38 anonymous2 [Reply | View]
I ask because I'm running into heaps of trouble.
I think I've got cpan.pm working.
For those who are like me 24 hours ago: CPAN (cpan.org) is a resource of perl stuff, cpan.pm is a handy download / installer.
I'm running into dependency issues like crazy. I think there may be something wrong with my install as 'install bundle::libnet' fails.
Thanks,
kdavis@uvic.ca
-
Great article, great module.
2002-11-26 20:55:04 anonymous2 [Reply | View]
Hey Brian, great article, you've inspired me :) I think I'll go and write Mac::iCal now, if someone hasn't done it yet (Checking on cpan now).
For the cgi script, wouldn't it be better to run it with the suid wrapper, though? I know you can't do that for the mod_perl bit but...
-
If only I could install it
2002-11-25 13:49:20 hondo77 [Reply | View]
I love the idea of Mac::iTunes. However, as someone who tried to install the thing just a few days ago, it's not ready for prime time. There are so many freaking dependencies, some on modules that must exist only on Brian's personal box, that I couldn't get it working. Did I say "working"? I meant "installed". Personally, I just want to have an easy way to parse the XML files iTunes produces and have a nice object or two to play with. I shouldn't have to download half of CPAN and sync up with Brian's box to do that. I've seen this mentioned elsewhere on the web (an Apple mailing list, as I recall) so this isn't news.
I'd love to install it and test it out, Brian, but I don't have the time to resolve the excessive dependencies and hunt down unreleased modules. -
If only I could install it
2002-11-25 23:10:33 brian d foy |
[Reply | View]
As you say, Mac::ITunes is not ready for prime time. It is a work in progress.
However, all of the modules it needs are on CPAN. The easiest way to find any module is to go directly to http://search.cpan.org. For some reason CPAN.pm does not find Mac::Path::Util, but it is on CPAN. Everything else is in the Perl modules list.
Some of the tests for the AppleScript portion seem to depend on the state of iTunes (e.g. docked or not). However, you can install the module even if the controller tests fail and the controller portion should still work. The parts of the module that deal with the library parsing do not depend on the other parts.
If you want to parse the XML music library, then you do not want Mac::iTunes anyway. The module will parse teh binary format, which, by the way, has more information about each track. The XML format does not expose everything.
If you send me any error messages you get I would be happy to help you sort it out. -
If only I could install it
2002-11-26 15:46:19 hondo77 [Reply | View]
Good news about Mac::iTunes reading more than the XML file. Actually, I want the information however I can get it. I assumed the XMl file had all of it.
Okay, so I tried the installation again. Mac::Path::Util was missing, of course, so I installed it (I thought I had done that last week...hmmm...). Then I had to install Test::Prereq. Now I get the following error messages:
CHECK failed--call queue aborted.
B::Module::Info,modules_used use failed with 255 saying:
Can't locate object method "ARRAY" via package "B::begin_av" at /Library/Perl/B/Module/Info.pm line 49.
I decide to just go for it and force an install of Test::Prereq. That done, I now try and install Mac::iTunes. It looks like (after a LONG time testing) I need to install Test::Data. Make that force an install of Test::Data (I don't know what that B::Module message above is all about but it sure keeps coming up).
Now to try to install Mac::iTunes again. After hours of things like this:
t/controller.......NOK 12-1753 at t/controller.t line 43
# Failed test (t/controller.t at line 43)
# got: undef
# expected: 'true'
I finally fail with this:
Failed Test Stat Wstat Total Fail Failed List of Failed
-------------------------------------------------------------------------------
t/controller.t 39 9984 44 39 88.64% 2 7-44
t/position.t 5 1280 6 5 83.33% 2-6
t/track_info.t 3 768 4 3 75.00% 2-4
4 subtests skipped.
Failed 3/17 test scripts, 82.35% okay. 47/448 subtests failed, 89.51% okay.
I'll have to do a forced install tomorrow as there aren't enough hours left today to go through all those tests today.
Anyway, you see what I mean. It's a rough install and it takes forever. -
If only I could install it
2002-11-26 20:18:09 brian d foy |
[Reply | View]
I've uploaded new versions of Mac::iTunes and Mac::Path::Util.
If you have more problems, please send the complete output from make directly to me.
Thanks. -
If only I could install it
2002-11-25 22:13:41 timct [Reply | View]
I hunted down Mac::Path::Util via google installed the alpha that I found. However even after successfully compiling Mac::iTunes it failes many of the controller tests and thus refuses to install without forcing it to. Any ideas? I am running Perl 5.8.0 on Jaguar. -
If only I could install it
2002-11-25 23:13:58 brian d foy |
[Reply | View]
Some of the controller tests seem to depend on the state of iTunes. If you change iTunes during the test, like minimizing it, some tests can fail.
As for Mac::Path::Util, or any Perl module, your first stop should be http://search.cpan.org.
If you send me any error messages you get, I would be happy to help you fix them. :) -
Apple Events in iTunes
2005-09-06 03:25:50 Bill_Palmer [Reply | View]
Hi Brian
Do you know how to access iTunes Events from Applescript or Perl on Mac OS X.
On the Windows version, the COM object emits events such as OnPlayerPlayEvent and OnDatabaseChangedEvent but I can't find any equivalent in the documentation of the Mac version.
I'm using your Mac::iTunes module to interface with iTunes but the project needs to know when tracks are deleted from iTunes. With events this would be easy but with regular polling of iTunes state it is too processor intensive. -
If only I could install it
2002-11-26 11:15:16 timct [Reply | View]
Brian, when interacting with the controller, I keep getting these type of messages. Is this normal?
## Component Manager: attempting to find symbols in a component alias of type (regR/carP/x!bt)
-
If only I could install it
2003-10-25 18:25:14 anonymous2 [Reply | View]
It's a common problem caused by Toast: See if you have a file called Toast Video CD Support.qtx in the main /Library/QuickTime/ directory on your system. If so, delete it or move it to some other location (unless you really want to have Video CD support in Toast!). -
If only I could install it
2002-11-26 20:22:02 brian d foy |
[Reply | View]
That does not look like a Perl error. That looks like something else is wrong.










Nice article! Do you know if its possible to pass perl variables to an applescript via osascript in perl?
Cant seem to find any answers on that