5 # A music manager for Asterisk.
7 # Copyright (C) 2005-2006, Justin Tunney
9 # Justin Tunney <jesuscyborg@gmail.com>
11 # This program is free software, distributed under the terms of the
12 # GNU General Public License v2.
14 # Keep it open source pigs
16 # --------------------------------------------------------------------
18 # Uses festival to list off all your MP3 music files over a channel in
19 # a hierarchical fashion. Put this file in your agi-bin folder which
20 # is located at: /var/lib/asterisk/agi-bin Be sure to chmod +x it!
23 # exten => 68742,1,Answer()
24 # exten => 68742,2,agi,jukebox.agi|/home/justin/Music
25 # exten => 68742,3,Hangup()
27 # exten => 68742,1,Answer()
28 # exten => 68742,2,agi,jukebox.agi|/home/justin/Music|pm
29 # exten => 68742,3,Hangup()
32 # p - Precache text2wave outputs for every possible filename.
33 # It is much better to set this option because if a caller
34 # presses a key during a cache operation, it will be ignored.
35 # m - Go back to menu after playing song
36 # g - Do not play the greeting message
39 # - Press '*' to go up a directory. If you are in the root music
40 # folder you will be exitted from the script.
41 # - If you have a really long list of files, you can filter the list
42 # at any time by pressing '#' and spelling out a few letters you
43 # expect the files to start with. For example, if you wanted to
44 # know what extension 'Requiem For A Dream' was, you'd type:
45 # '#737'. Note, phone keypads don't include Q and Z. Q is 7 and
49 # - This AGI script uses the MP3Player command which uses the
50 # mpg123 Program. Grab yourself a copy of this program by
51 # going to http://www.mpg123.de/cgi-bin/sitexplorer.cgi?/mpg123/
52 # Be sure to download mpg123-0.59r.tar.gz because it is known to
53 # work with Asterisk and hopefully isn't the release with that
54 # awful security problem. If you're using Fedora Core 3 with
55 # Alsa like me, make linux-alsa isn't going to work. Do make
56 # linux-devel and you're peachy keen.
58 # - You won't get nifty STDERR debug messages if you're using a
59 # remote asterisk shell.
61 # - For some reason, caching certain files will generate the
62 # error: 'using default diphone ax-ax for y-pau'. Example:
63 # # echo "Depeche Mode - CUW - 05 - The Meaning of Love" | text2wave -o /var/jukeboxcache/jukeboxcache/Depeche_Mode/Depeche_Mode_-_CUW_-_05_-_The_Meaning_of_Love.mp3.ul -otype ulaw -
64 # The temporary work around is to just touch these files.
66 # - The background app doesn't like to get more than 2031 chars
74 # Setup some variables
75 my %AGI; my $tests = 0; my $fail = 0; my $pass = 0;
76 my @masterCacheList = ();
81 last unless length($_);
82 if (/^agi_(\w+)\:\s+(.*)$/) {
90 my $MENUAFTERSONG = 0;
92 $PRECACHE = 1 if $ARGV[1] =~ /p/;
93 $MENUAFTERSONG = 1 if $ARGV[1] =~ /m/;
94 $SHOWGREET = 0 if $ARGV[1] =~ /g/;
98 $MUSIC = &rmts
($MUSIC);
99 my $FESTIVALCACHE = "/var/jukeboxcache";
100 if (! -e
$FESTIVALCACHE) {
101 `mkdir -p -m0776 $FESTIVALCACHE`;
104 # make sure we have some essential files
105 if (! -e
"$FESTIVALCACHE/jukebox_greet.ul") {
106 `echo "Welcome to the Asterisk Jukebox" | text2wave -o $FESTIVALCACHE/jukebox_greet.ul -otype ulaw -`;
108 if (! -e
"$FESTIVALCACHE/jukebox_press.ul") {
109 `echo "Press" | text2wave -o $FESTIVALCACHE/jukebox_press.ul -otype ulaw -`;
111 if (! -e
"$FESTIVALCACHE/jukebox_for.ul") {
112 `echo "For" | text2wave -o $FESTIVALCACHE/jukebox_for.ul -otype ulaw -`;
114 if (! -e
"$FESTIVALCACHE/jukebox_toplay.ul") {
115 `echo "To play" | text2wave -o $FESTIVALCACHE/jukebox_toplay.ul -otype ulaw -`;
117 if (! -e
"$FESTIVALCACHE/jukebox_nonefound.ul") {
118 `echo "There were no music files found in this folder" | text2wave -o $FESTIVALCACHE/jukebox_nonefound.ul -otype ulaw -`;
120 if (! -e
"$FESTIVALCACHE/jukebox_percent.ul") {
121 `echo "Percent" | text2wave -o $FESTIVALCACHE/jukebox_percent.ul -otype ulaw -`;
123 if (! -e
"$FESTIVALCACHE/jukebox_generate.ul") {
124 `echo "Please wait while Astrisk Jukebox cashes the files of your music collection" | text2wave -o $FESTIVALCACHE/jukebox_generate.ul -otype ulaw -`;
126 if (! -e
"$FESTIVALCACHE/jukebox_invalid.ul") {
127 `echo "You have entered an invalid selection" | text2wave -o $FESTIVALCACHE/jukebox_invalid.ul -otype ulaw -`;
129 if (! -e
"$FESTIVALCACHE/jukebox_thankyou.ul") {
130 `echo "Thank you for using Astrisk Jukebox, Goodbye" | text2wave -o $FESTIVALCACHE/jukebox_thankyou.ul -otype ulaw -`;
135 print "EXEC Playback \"$FESTIVALCACHE/jukebox_greet\"\n";
136 my $result = <STDIN
>; &check_result
($result);
139 # go through the directories
140 music_dir_cache
() if $PRECACHE;
145 ##########################################################################
150 # generate a list of mp3's and directories and assign each one it's
151 # own selection number. Then make sure that we've got a sound clip
153 if (!opendir(THEDIR
, rmts
($MUSIC.$dir))) {
154 print STDERR
"Failed to open music directory: $dir\n";
157 my @files = sort readdir THEDIR
;
159 my @masterBgList = ();
161 foreach my $file (@files) {
163 if ($file ne '.' && $file ne '..' && $file ne 'festivalcache') { # ignore special files
164 my $real_version = &rmts
($MUSIC.$dir).'/'.$file;
165 my $cache_version = &rmts
($FESTIVALCACHE.$dir).'/'.$file.'.ul';
166 my $cache_version2 = &rmts
($FESTIVALCACHE.$dir).'/'.$file;
167 my $cache_version_esc = &clean_file
($cache_version);
168 my $cache_version2_esc = &clean_file
($cache_version2);
170 if (-d
$real_version) {
171 # 0:id 1:type 2:text2wav-file 3:for-filtering 4:the-directory 5:text2wav echo
172 push(@masterBgList, [$cnt++, 1, $cache_version2_esc, &remove_special_chars
($file), $file, "for the $file folder"]);
173 } elsif ($real_version =~ /\.mp3$/) {
174 # 0:id 1:type 2:text2wav-file 3:for-filtering 4:the-mp3
175 push(@masterBgList, [$cnt++, 2, $cache_version2_esc, &remove_special_chars
($file), $real_version, "to play $file"]);
181 my @filterList = @masterBgList;
183 if (@filterList == 0) {
184 print "EXEC Playback \"$FESTIVALCACHE/jukebox_nonefound\"\n";
185 my $result = <STDIN
>; &check_result
($result);
192 # play bg selections and figure out their selection
195 for (my $n=0; $n<@filterList; $n++) {
196 &cache_speech
(&remove_file_extension
($filterList[$n][5]), "$filterList[$n][2].ul") if ! -e
"$filterList[$n][2].ul";
197 &cache_speech
("Press $filterList[$n][0]", "$FESTIVALCACHE/jukebox_$filterList[$n][0].ul") if ! -e
"$FESTIVALCACHE/jukebox_$filterList[$n][0].ul";
198 print "EXEC Background \"$filterList[$n][2]&$FESTIVALCACHE/jukebox_$filterList[$n][0]\"\n";
199 my $result = <STDIN
>;
200 $digit = &check_result
($result);
202 $digitstr .= chr($digit);
207 print "WAIT FOR DIGIT 3000\n";
208 my $result = <STDIN
>;
209 $digit = &check_result
($result);
211 $digitstr .= chr($digit);
214 # see if it's a valid selection
215 print STDERR
"Digits Entered: '$digitstr'\n";
216 exit 0 if $digitstr eq '';
218 goto EXITSUB
if $digitstr =~ /\*/;
221 if ($digitstr =~ /^\#\d+/) {
223 for (my $n=1; $n<length($digitstr); $n++) {
224 my $d = substr($digitstr, $n, 1);
244 for (my $n=1; $n<@masterBgList; $n++) {
245 push(@filterList, $masterBgList[$n]) if $masterBgList[$n][3] =~ /^$regexp/i;
250 for (my $n=0; $n<@masterBgList; $n++) {
251 if ($digitstr == $masterBgList[$n][0]) {
252 if ($masterBgList[$n][1] == 1) { # a folder
253 &music_dir_menu
(rmts
($dir).'/'.$masterBgList[$n][4]);
254 @filterList = @masterBgList;
256 } elsif ($masterBgList[$n][1] == 2) { # a file
257 # because *'s scripting language is crunk and won't allow us to escape
258 # funny filenames, we need to create a temporary symlink to the mp3
260 my $mp3 = &escape_file
($masterBgList[$n][4]);
264 print STDERR
"ln -s $mp3 $link\n";
265 my $cmdr = `ln -s $mp3 $link`;
267 print "Failed to create symlink to mp3: $cmdr\n" if $cmdr ne '';
269 print "EXEC MP3Player \"$link\"\n";
270 my $result = <STDIN
>; &check_result
($result);
274 if (!$MENUAFTERSONG) {
275 print "EXEC Playback \"$FESTIVALCACHE/jukebox_thankyou\"\n";
276 my $result = <STDIN
>; &check_result
($result);
284 print "EXEC Playback \"$FESTIVALCACHE/jukebox_invalid\"\n";
285 my $result = <STDIN
>; &check_result
($result);
294 my $theDir = extract_file_dir
($file);
295 `mkdir -p -m0776 $theDir`;
297 print STDERR
"echo \"$speech\" | text2wave -o $file -otype ulaw -\n";
298 my $cmdr = `echo "$speech" | text2wave -o $file -otype ulaw -`;
300 if ($cmdr =~ /using default diphone/) {
301 # temporary bug work around....
303 } elsif ($cmdr ne '') {
304 print STDERR
"Command Failed\n";
309 sub music_dir_cache
{
310 # generate list of text2speech files to generate
311 if (!music_dir_cache_genlist
('/')) {
312 print STDERR
"Horrible Dreadful Error: No Music Found in $MUSIC!";
316 # add to list how many 'number' files we have to generate. We can't
317 # use the SayNumber app in Asterisk because we want to chain all
318 # talking in one Background command. We also want a consistent
320 for (my $n=1; $n<=$maxNumber; $n++) {
321 push(@masterCacheList, [3, "Press $n", "$FESTIVALCACHE/jukebox_$n.ul"]) if ! -e
"$FESTIVALCACHE/jukebox_$n.ul";
324 # now generate all these darn text2speech files
325 if (@masterCacheList > 5) {
326 print "EXEC Playback \"$FESTIVALCACHE/jukebox_generate\"\n";
327 my $result = <STDIN
>; &check_result
($result);
329 my $theTime = time();
330 for (my $n=0; $n < @masterCacheList; $n++) {
332 if ($masterCacheList[$n][0] == 1) { # directory
333 &cache_speech
("for folder $masterCacheList[$n][1]", $masterCacheList[$n][2]);
334 } elsif ($masterCacheList[$n][0] == 2) { # file
335 &cache_speech
("to play $masterCacheList[$n][1]", $masterCacheList[$n][2]);
336 } elsif ($masterCacheList[$n][0] == 3) { # number
337 &cache_speech
($masterCacheList[$n][1], $masterCacheList[$n][2]);
339 if (time() >= $theTime + 30) {
340 my $percent = int($n / @masterCacheList * 100);
341 print "SAY NUMBER $percent \"\"\n";
342 my $result = <STDIN
>; &check_result
($result);
343 print "EXEC Playback \"$FESTIVALCACHE/jukebox_percent\"\n";
344 my $result = <STDIN
>; &check_result
($result);
350 # this function will fill the @masterCacheList of all the files that
351 # need to have text2speeced ulaw files of their names generated
352 sub music_dir_cache_genlist
{
354 if (!opendir(THEDIR
, rmts
($MUSIC.$dir))) {
355 print STDERR
"Failed to open music directory: $dir\n";
358 my @files = sort readdir THEDIR
;
361 foreach my $file (@files) {
363 if ($file ne '.' && $file ne '..' && $file ne 'festivalcache') { # ignore special files
364 my $real_version = &rmts
($MUSIC.$dir).'/'.$file;
365 my $cache_version = &rmts
($FESTIVALCACHE.$dir).'/'.$file.'.ul';
366 my $cache_version2 = &rmts
($FESTIVALCACHE.$dir).'/'.$file;
367 my $cache_version_esc = &clean_file
($cache_version);
368 my $cache_version2_esc = &clean_file
($cache_version2);
370 if (-d
$real_version) {
371 if (music_dir_cache_genlist
(rmts
($dir).'/'.$file)) {
373 $maxNumber = $tmpMaxNum if $tmpMaxNum > $maxNumber;
374 push(@masterCacheList, [1, $file, $cache_version_esc]) if ! -e
$cache_version_esc;
377 } elsif ($real_version =~ /\.mp3$/) {
379 $maxNumber = $tmpMaxNum if $tmpMaxNum > $maxNumber;
380 push(@masterCacheList, [2, &remove_file_extension
($file), $cache_version_esc]) if ! -e
$cache_version_esc;
389 sub rmts
{ # remove trailing slash
395 sub extract_file_name
{
397 $hog =~ /\/?
([^\
/]+)$/;
401 sub extract_file_dir
{
403 return $hog if ! ($hog =~ /\//);
404 $hog =~ /(.*)\/[^\
/]*$/;
408 sub remove_file_extension
{
410 return $hog if ! ($hog =~ /\./);
411 $hog =~ /(.*)\.[^.]*$/;
433 sub remove_special_chars
{
474 if ($res =~ /^200/) {
475 $res =~ /result=(-?\d+)/;
477 print STDERR
"FAIL ($res)\n";
481 print STDERR
"PASS ($1)\n";
485 print STDERR
"FAIL (unexpected result '$res')\n";