Apache::MP3 - Generate streamable directories of MP3 and Ogg Vorbis files
# httpd.conf or srm.conf AddType audio/mpeg mp3 MP3 AddType audio/playlist m3u M3U AddType audio/x-scpls pls PLS AddType application/x-ogg ogg OGG
# httpd.conf or access.conf <Location /songs> SetHandler perl-script PerlHandler Apache::MP3 </Location>
# Or use the Apache::MP3::Sorted subclass to get sortable directory listings <Location /songs> SetHandler perl-script PerlHandler Apache::MP3::Sorted </Location>
# Or use the Apache::MP3::Playlist subclass to get persistent playlists <Location /songs> SetHandler perl-script PerlHandler Apache::MP3::Playlist </Location>
A demo version can be browsed at http://www.modperl.com/Songs/.
This module makes it possible to browse a directory hierarchy containing MP3, Ogg Vorbis, or Wave files, sort them on various fields, download them, stream them to an MP3 decoder like WinAmp, and construct playlists. The display is configurable and subclassable.
NOTE: This version of Apache::MP3 is substantially different from the pre-2.0 version described in The Perl Journal. Specifically, the format to use for HREF links has changed. See Linking for details.
This section describes the installation process.
1. Prequisites
The module will automatically adjust for the absence of one or more of the MP3::Info, Ogg::Vorbis or Audio::Wav modules by inhibiting the display of the corresponding file type.
2. Configure MIME types
AddType audio/mpeg mp3 MP3 AddType audio/playlist m3u M3U AddType audio/x-scpls pls PLS AddType application/x-ogg ogg OGG AddType audio/wav wav WAV
Note that you need extemely large amounts of bandwidth to stream Wav files, and that few audio file players currently support this type of streaming. Wav file support is primarily intended to allow for convenient downloads.
3. Install icons and stylesheet
You may change the location of this directory by setting the BaseDir configuration variable. See the Customizing section for more details.
4. Set Apache::MP3 to be the handler for the MP3 directory
<Location /Songs>
SetHandler perl-script
PerlHandler Apache::MP3
</Location>
If you would prefer an MP3 file listing that allows the user to sort it in various ways, set the handler to use the Apache::MP3::Sorted subclass instead. A further elaboration is Apache::MP3::Playlist, which uses cookies to manage a persistent playlist for the user.
5. Load MP3::Info in the Perl Startup file (optional)
use MP3::Info; use Apache::MP3;
6. Set up MP3 directory
If you place a file named "cover.jpg" in any of the directories, that image will be displayed at the top of the directory listing. You can use this to display cover art.
If you place a list of .mp3 file names in a file with the .m3u extension, it will be treated as a playlist and displayed to the user with a distinctive icon. Selecting the playlist icon will download the playlist and stream its contents. The playlist must contain relative file names, but may refer to subdirectories, as in this example:
# file: folk_favorites.m3u Never_a_Moment_s_Thought_v2.mp3 Peter Paul & Mary - Leaving On A Jet Plane.mp3 Simon and Garfunkel/Simon And Garfunkel - April Come She Will.mp3
Likewise, if you place a list of shoutcast URLs into a file with the .pls extension, it will be treated as a playlist and displayed to the user with a distinctive icon. Selecting the playlist icon will contact the shoutcast servers in the playlist and stream their contents. The playlist syntax is as in this example:
[playlist] numberofentries=2 File1=http://205.188.245.132:8038 Title1=Monkey Radio: Grooving. Sexy. Beats. Length1=-1 File2=http://205.188.234.67:8052 Title2=SmoothJazz Length2=-1 Version=2
Likewise, if you place a list of shoutcast URLs into a file with the .pls extension, it will be treated as a playlist and displayed to the user with a distinctive icon. Selecting the playlist icon will contact the shoutcast servers in the playlist and stream their contents. The playlist syntax is as in this example:
[playlist] numberofentries=2 File1=http://205.188.245.132:8038 Title1=Monkey Radio: Grooving. Sexy. Beats. Length1=-1 File2=http://205.188.234.67:8052 Title2=SmoothJazz Length2=-1 Version=2
7. Set up an information cache directory (optional)
To configure this, choose a directory that the Web server has write access for, such as /usr/tmp. Then add a configuration variable like the following to the <Location> directive:
PerlSetVar CacheDir /usr/tmp/mp3_cache
If the designated directory does not exist, Apache::MP3 will attempt to create it, limited of course by the Web server's privileges. You may need to create the mp3_cache directory yourself if /usr/tmp is not world writable.
Open up the MP3 URL in your favorite browser. You should be able to see directory listings, and download and stream your songs. If things don't seem to be working, checking the server error log for messages.
Apache::MP3 can be customized in three ways: (1) by changing per-directory variables; (2) changing settings in the Apache::MP3 cascading stylesheet; and (3) subclassing Apache::MP3 or Apache::MP3::Sorted.
Per-directory variables are set by PerlSetVar directives in the Apache::MP3 <Location> or <Directory> section. For example, to change the icon displayed next to subdirectories of MP3s, you would use PerlSetVar to change the DirectoryIcon variable:
PerlSetVar DirectoryIcon big_cd.gif
This following table summarizes the configuration variables. A more detailed explanation of each follows in the subsequent sections.
Table 1: Configuration Variables
Name Value Default ---- ----- ------- GENERAL OPTIONS AllowDownload yes|no yes AllowStream yes|no yes AllowPlayLocally yes|no yes CheckStreamClient yes|no no ReadMP3Info yes|no yes StreamTimeout integer 0
DIRECTORY OPTOINS BaseDir URL /apache_mp3 CacheDir path -none- HelpURL URL apache_mp3_help.gif:614x498 StreamBase URL -none-
DISPLAY OPTIONS ArrowIcon URL right_arrow.gif CoverImage filename cover.jpg CoverImageSmall filename cover_small.jpg PlaylistImage filename playlist.jpg DescriptionFormat string -see below- DirectoryIcon URL cd_icon_small.gif PlaylistIcon URL playlist.gif Fields list title,artist,duration,bitrate HomeLabel string "Home" LongList integer 10 MissingComment string "unknown" PathStyle Staircase|Arrows Staircase SongIcon URL sound.gif SubdirColumns integer 3 Stylesheet URL apache_mp3.css TitleIcon URL cd_icon.gif
AllowDownload yes|no
The module recognizes the arguments "yes", "no", "true" and "false". The default is "yes".
Note that this setting only affects MP3 files. Other files, including cover art and playlists, can still be downloaded.
AllowStream yes|no
AllowPlayLocally yes|no
CheckStreamClient yes|no
The default is "no".
ReadMP3Info yes|no
If "no" is specified, all fields in the directory listing will be blank except for filename and description, which will both be set to the physical filename of the MP3 file.
StreamTimeout integer
BaseDir URL
The default is "/apache_mp3."
CacheDir path
HelpURL URL:widthxheight
Default: apache_mp3_help.gif:614x498
Note: I prepared this image on an airplane, so it isn't as clean as I would like. Volunteers to make a better help page are welcomed!
StreamBase URL
Example:
If the song requested is http://www.foobar.com/Songs/Madonna_live.m3u?stream=1
and StreamBase is set to http://streamer.myhost.net, then the URL placed in the playlist will be
http://streamer.myhost.net/Songs/Madonna_live.m3u?stream=1
The path part of the URL is simply appended to StreamBase. If you want to do more sophisticated URL processing, use mod_rewrite or equivalent.
ArrowIcon URL
CoverImage filename
CoverImageSmall filename
DescriptionFormat string
You can customize this behavior by providing a DescriptionFormat string. These strings combine constant characters with %x format codes in much the way that sprintf() does. For example, the directive shown below will create descriptions similar to [Madonna] Like a Virgin (1980).
PerlSetVar DescriptionFormat "[%a] %t (%y)"
The full list of format codes follows:
Table 2: DescriptionFormat Field Codes
Code Description ---- -----------
%a Artist name %c Comment %d Duration, in format 00m00s %f Name of physical file (minus path) %g Genre %l Album name %m Minutes portion of duration, usually used with %s %n Track number %q Sample rate, in kHz %r Bitrate, in kbps %s Seconds portion of duration, usually used with %m %S Duration, expressed as total seconds %t Title %y Year
DirectoryIcon URL
PlaylistIcon URL
PlaylistImage filename
Fields title,artist,duration,bitrate
The following are valid fields:
Table 3: Field Names For use with the Fields Configuration Variable
Field Description
----- -----------
album The album
artist The artist
bitrate The bitrate, expressed in kbps
comment The comment field
duration Duration of the song in hour, minute, second format
description Description as specified by DescriptionFormat
filename The physical name of the .mp3 file
genre The genre
min The minutes portion of the duration
seconds Total duration of the song in seconds
sec The seconds portion of the duration
samplerate The sampling rate, in KHz
title The title of the song
track The track number
year The album year
Note that MP3 rip and encoding software differ in what fields they capture and the exact format of such fields as the title and album. Field names are case insensitive.
Previous versions of this module used "kbps" instead of "bitrate". This has been changed.
HomeLabel string
LongList integer
MissingComment string
PerlSetVar MissingComment off
PathStyle Staircase|Arrows
SongIcon URL
SubdirColumns integer
Stylesheet URL
TitleIcon URL
You can change the appearance of the page by changing the cascading stylesheet that accompanies this module, apache_mp3.css. The following table describes the tags that can be customized:
Table 4: Stylesheet Class Names
Class Name Description ---------- ----------
BODY General defaults H1 Current directory path H2 "CD Directories" and "Song List" headings TR.title Style for the top line of the song listing TR.normal Style for odd-numbered song listing lines TR.highlight Style for even-numbered song listing lines .directory Style for the title of the current directory .subdirectory Style for the title of subdirectories P Ordinary paragraphs A Links INPUT Fill-out form fields
For more extensive customization, you can subclass this module. The Apache::MP3::Sorted module illustrates how to do this.
Briefly, your module should inherit from Apache::MP3 (or
Apache::MP3::Sorted) either by setting the @ISA package global or,
in Perl 5.6 and higher, with the use base directive. Your module
can then override existing methods and define new ones.
This module uses the mod_perl method invocation syntax for handler invocation. Because of this, if you override the handler() method, be sure to give it a prototype of ($$). If you override new(), be sure to place the Apache::Request object in an instance variable named 'r'. See the MP3.pm module for details.
One implication of using the method invocation syntax is that the Apache::MP3 object is created at server configuration time. This means that you cannot tweak the code and simply restart the server, but must formally stop and relaunch the server every time you change the code or install a new version. This disadvantage is balanced by a savings in memory consumption and performance.
See The Apache::MP3 API below for more information on overriding Apache::MP3 methods.
You may wish to create links to MP3 files and directories manually. The rules for creating HREFs are different from those used in earlier versions of Apache::MP3, a decision forced by the fact that the playlist format used by popular MP3 decoders has changed.
The following rules apply:
Download an MP3 file
<a href="/Songs/Madonna/like_a_virgin.mp3">Like a Virgin</a>
Stream an MP3 file
<a href="/Songs/Madonna/like_a_virgin.m3u?play=1">
Like a streaming Virgin</a>
Stream a directory
<a href="/Songs/Madonna/playlist.m3u?Play+All=1">Madonna Lives!</a>
The capitalization of "Play All" is significant. Apache::Mp3 will generate a playlist containing all MP3 files within the directory.
Stream a directory heirarchy recursively
<a href="/Songs/HipHop/playlist.m3u?Play+All+Recursive=1">Rock me</a>
The capitalization of "Play All Recursive" is significant. Apache::MP3 will generate a playlist containing all MP3 files within the directory and all its subdirectories.
Shuffle and stream a directory
<a href="/Songs/HipHop/playlist.m3u?Shuffle+All">Rock me</a>
Apache::MP3 will generate a playlist containing all MP3 files within the directory and all its subdirectories, and then randomize its order.
Shuffle an entire directory heirarchy recursively
<a href="/Songs/HipHop/playlist.m3u?Shuffle+All+Recursive=1">Rock me</a>
Apache::MP3 will generate a playlist containing all MP3 files within the directory and all its subdirectories, and then randomize its order.
Play a set of MP3s within a directory
<a href="/Songs/Madonna/playlist.m3u?Play+Selected=1;file=like_a_virgin.mp3;file=evita.mp3"> Two favorites</a>
Again, the capitalization of "Play Selected" counts.
Display a sorted directory
<a href="/Songs/Madonna/?sort=duration">Madonna lives!</a>
The Apache::MP3 object is a blessed hash containing a single key,
r, which points at the current request object. This can be
retrieved conveniently using the r() method.
Apache::MP3 builds up its directory listing pages in pieces, using a hierarchical scheme. The following diagram summarizes which methods are responsible for generating the various parts. It might help to study it alongside a representative HTML page:
list_directory()
------------------------- page top --------------------------------
page_top()
directory_top()
<CDICON> <DIRECTORY> -> <DIRECTORY> -> <DIRECTORY>
[Shuffle All] [Stream All]
list_subdirs()
subdir_list_top()
------------------------------------------------------------
<CD Directories (6)>
subdir_list()
<cdicon> <title> <cdicon> <title> <cdicon> <title>
<cdicon> <title> <cdicon> <title> <cdicon> <title>
subdir_list_bottom() # does nothing
------------------------------------------------------------
list_playlists()
playlist_list_top()
------------------------------------------------------------
<CD Playlists (6)>
playlist_list()
<cdicon> <title> <cdicon> <title> <cdicon> <title>
<cdicon> <title> <cdicon> <title> <cdicon> <title>
playlist_list_bottom() # does nothing
------------------------------------------------------------
list_mp3s()
mp3_list_top()
------------------------------------------------------------
<Song List (4)>
mp3_list()
mp3_list_top()
mp3_table_header()
<Select> Title Kbps
format_song() # called for each row
<icon>[] [fetch][stream] Like a virgin 128
<icon>[] [fetch][stream] American Pie 128
<icon>[] [fetch][stream] Santa Evita 96
<icon>[] [fetch][stream] Boy Toy 168
mp3_list_bottom()
[Play Selected] [Shuffle All] [Play All]
directory_bottom() ------------------------- page bottom -----------------------------
This section lists each of the Apache::MP3 method calls briefly.
$response_code = handler($request)
$mp3 = Apache::MP3->new(@args)
$request = $mp3->r()
$boolean = $mp3->is_local()
$response_code = $mp3->run()
$response_code = $mp3->process_directory($dir)
$response_code = $mp3->download_file($file)
$response_code = $mp3->stream($file)
$fh = $mp3->open_file($file)
$mp3->send_playlist($urls,$shuffle)
$urls is an array reference containing
the MP3 URLs to incorporate into the playlist, and $shuffle is a
flag indicating that the order of the playlist should be randomized
prior to sending it. No return value is returned.
$mp3_info = $mp3->find_mp3s($recurse)
$recurse, if true, causes the method to recurse through
all subdirectories. The return value is a hashref in which the keys
are the URLs of the found MP3s, and the values are hashrefs containing
the MP3 tag fields recovered from the files ("title", etc.).
@urls = $mp3->sort_mp3s($mp3_info)
@mp3s = $mp3->load_playlist($playlist)
$mp3->playlist_list_bottom($playlists)
$playlists. Currently it does nothing.
$mp3->playlist_list($playlists)
$playlists in a nicely
formatted table.
$html = $mp3->format_playlist($playlist)
$response_code = $mp3->list_directory($dir)
$mp3->page_top($dir)
$mp3->directory_top($dir)
$mp3->generate_navpath_staircase($dir)
$mp3->generate_navpath_arrows($dir)
$mp3->directory_bottom($dir)
$mp3->subdir_list_top($directories)
$directories is an arrayref containing the
subdirectories to display.
$mp3->subdir_list_bottom($directories)
$directories. Currently it does nothing.
$mp3->subdir_list($directories)
$directories and displays them in a nicely-formatted table.
@directories = $mp3->sort_subdirs($directories)
$directories and
returns a sorted list (not an arrayref).
$html = $mp3->format_subdir($directory)
$mp3->get_help
$mp3->list_subdirs($subdirectories)
$subdirectories
is an array reference containing the subdirectories to display
$mp3->list_playlists($playlists)
$playlists
is an array reference containing the playlists to display.
$mp3->list_mp3s($mp3s)
$mp3s is a
hashref in which the key is the path of the MP3 file and the value is
a hashref containing MP3 tag info about it. This generates the
buttons at the top of the table and then calls mp3_table_header() and
mp3_list_bottom().
$mp3->mp3_table_header
$mp3->mp3_list_bottom($mp3s)
$mp3s is a hashref containing information about each file.
$mp3->mp3_list($mp3s)
$mp3s and invokes
format_song() to format it for the table.
@buttons = $mp3->control_buttons
$arrayref = $mp3->format_song($song,$info,$count)
$song is the path to
the MP3 file, $info is a hashref containing tag information from
the song, and $count is an integer containing the song's position
in the list (which currently is unusued). The method invokes
format_song_controls() and format_song_fields() to generate a list of
elements to be incorporated into cells of the table, and returns an
array reference.
@array = $mp3->format_song_controls($song,$info,$count)
@array = $mp3->format_song_fields($song,$info,$count)
($directories,$mp3s) = $mp3->read_directory($dir)
$dir, generating an arrayref
containing the subdirectories and a hashref containing the MP3 files
and their information, which are returned as a two-element list.
$hashref = $mp3->fetch_info($file)
$file and returns a
hashref containing the MP3 tag information as well as some synthesized
fields. The synthesized fields are track, which contains the same
information as tracknum; description, which contains the title,
album and artist merged together; and duration, which contains the
duration of the song expressed as hours, minutes and seconds. Other
fields are taken directly from the MP3 tag, but are downcased (for
convenience to other routines).
Apache::MP3->path_escape($scalarref)
@fields = $mp3->fields
$hashref = $mp3->read_cache($file)
$boolean = $mp3->write_cache($file,$info)
$file and $info are the path
to the file and its MP3 tag information, respectively. Returns a
boolean indicating the success of the operation.
$result_code = $mp3->send_stream($file,$uri)
$file and URI $uri) and
streams it to the client. It returns an Apache result code indicating
the success of the operation.
$boolean = $mp3->download_ok
$boolean = $mp3->stream_ok
$boolean = $mp3->check_stream_client
$boolean = $mp3->is_stream_client
$boolean = $mp3->read_mp3_info
$seconds = $mp3->stream_timeout
$lines = $mp3->file_list_is_long
$html = $mp3->home_label
$style = $mp3->path_style
$path = $mp3->cache_dir
$int = $mp3->subdir_columns
$dir = $mp3->default_dir
miscellaneous directories and files
stylesheet() URI to the stylesheet file
parent_icon() URI to the icon to use to move up in directory
hierarchy (no longer used)
cd_icon URI for the big CD icon printed in the upper left corner
cd_list_icon URI for the little CD icons in the subdirectory listing
playlist_icon URI for the playlist icon
song_icon URI for the music note icons printed for each MP3 file
arrow_icon URI for the arrow used in the navigation bar
help_url URI of the document to display when user asks for help
$boolean = $mp3->skip_directory($dir)
Although it is pure Perl, this module relies on an unusual number of compiled modules. Perhaps for this reason, it appears to be sensitive to certain older versions of modules.
Before upgrading to Apache/1.3.6 mod_perl/1.24, I would see random segfaults in the httpd children when using this module. This problem disappeared when I installed a newer mod_perl.
If you experience this problem, I have found that one workaround is to load the MP3::Info module at server startup time using the mod_perl perl.startup script made the problem go away. This is an excerpt from my perl.startup file:
# the !/usr/local/bin/perl ... use Apache::Registry (); use Apache::Constants(); use MP3::Info; use Apache::MP3; use CGI(); use CGI::Carp ();
Versions of mod_perl prior to 1.22 crash when using the idiom -d $r->finfo (or any other idiom). Since there are many older versions still out there, I have replaced $r->finfo with $r->filename and marked their locations in comments. To get increased performance, change back to $r->finfo.
In the directory display, the alignment of subdirectory icon with the subdirectory title is a little bit off. I want to move the title a bit lower using some stylesheet magic. Can anyone help?
Apache::MP3::Sorted, Apache::MP3::Playlist, MP3::Info, Apache
Tim Ayers <tayers@bridge.com> found and fixed a misfeature in the way that playlists were sorted.
Chris Nandor identified various bugs in the module and provided patches.
Copyright 2000, Lincoln Stein <lstein@cshl.org>.
This module is distributed under the same terms as Perl itself. Feel free to use, modify and redistribute it as long as you retain the correct attribution.