Commits

J.A. Roberts Tunney committed 34ac13e

initial import

  • Participants
  • Tags 0.9.1

Comments (0)

Files changed (10)

+syntax: glob
+*.o
+*.so
+*~
+*.rej
+*.orig
+.depend
+
+syntax: regexp
+(.*/)?\#[^/]*\#$
+J.A. Roberts Tunney <jtunney@lobstertech.com>
+
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
+2006-08-18  J.A. Roberts Tunney  <jtunney@lobstertech.com>
+
+	* jukebox 0.9.1
+
+	* INSTALL: Instructions for make start/stop/restart
+
+2006-08-17  J.A. Roberts Tunney  <jtunney@lobstertech.com>
+
+	* app_jukebox.c: Added factor scale F() option
+
+	* app_mp3_2.c: Created app_mp3_2.c which is clone of app_mp3.c
+	with factor scale support
+
+	* Makefile: Added uninstall, stop, start and restart targets
+
+2006-08-16  J.A. Roberts Tunney  <jtunney@lobstertech.com>
+
+	* Makefile: Make dist now /works/ and includes a binary build
+
+	* INSTALL: Created INSTALL
+
+	* README: Created README
+	
+	* COPYING: Created COPYING
+
+2006-08-15  J.A. Roberts Tunney  <jtunney@lobstertech.com>
+
+	* jukebox 0.9
+
+Copyright (c) 2006 J.A. Roberts Tunney
+Copying and distribution of this file, with or without modification, are
+permitted provided the copyright notice and this notice are preserved.
+              .--.                              .--.
+            .'(`                               /  ..\
+         __.>\ '.  _.---,._,'             ____.'  _o/
+       /.--.  : |/' _.--.<                '--.     |.__
+    _..-'    `\     /'     `'             _.-'     /--'
+     >_.-``-. `Y  /' _.---._____     _.--'        /
+    '` .-''. \|:  \.'   ___, .-'`   ~'--....___.-'
+     .'--._ `-:  \/   /'    \\
+         /.'`\ :;    /'       `-.
+        -`    |     |
+              :.; : |               Asterisk Jukebox
+              |:    |                 Version o.9.1
+              |     |
+              :. :  |        Copyright (c) 2006 J.A. Roberts Tunney
+            .jgs    ;            Keep it open source pigs
+            /:::.    `\
+----------------------------------------------------------------------
+
+J.A. Roberts Tunney <jtunney@lobstertech.com>
+http://www.lobstertech.com/code/jukebox/
+
+----------------------------------------------------------------------
+REQUIREMENTS
+----------------------------------------------------------------------
+
+ - Asterisk
+ - Festival
+ - Asterisk headers (If you choose to compile from source)
+
+----------------------------------------------------------------------
+BINARY INSTALLATION
+----------------------------------------------------------------------
+
+Run the following commands while inside the source directory for this
+program.
+
+	make installbin
+
+All the above command does is copies the module library files to the
+default Asterisk module dir: /usr/lib/asterisk/modules
+
+----------------------------------------------------------------------
+INSTALLATION FROM SOURCE
+----------------------------------------------------------------------
+
+Run the following commands while inside the source directory for this
+program.
+
+	make
+	make install
+
+If that worked, the module should now be loaded in to Asterisk next
+time it is restarted.  If you want to load the Jukebox application in
+to a running instance of Asterisk without restart, then issue this
+command:
+
+	make start
+
+If you make a change any of the code, you can recompile, intall, and
+reload all the modules in to Asterisk with one command:
+
+	make restart
+
+To unload the modules:
+
+	make stop
+
+----------------------------------------------------------------------
+
+               __                          ___           _aaa
+              d8888a,_                  a8888888a  __a8888888b
+             d8P   `88ba.               8P'~~~~Y88888P""~~~~88b
+            d8P     ~"Y88a____aaaa_____aP        88         Y88
+           d8P         ~Y88"8~~~~~~88888          8g         88
+          d8P                         88     ____ 88y__      88b
+          88                         a88   _a88~888"8M88a____888
+          88                         88P   88  a8"     `88888888b_
+          8P                         88    88 a88        88b    Y8,
+          88a                        Y8b     888L        888.    88P
+         aP                           Y8     _888      _a8P 8   a88
+        _8                             ~88a888~888_   a888yg'  a88'
+        88                               ~~~~    ~"8888       a88P
+       d8'                                           Y8,     888L
+       8E                                             88a___8"888
+      d8P                                              ~Y888   88L
+      88                                                        88
+      88                                                        88b
+  ____88a_      a8a                                           __881
+88""P~888       888b                              _         8888888888
+      888       888P                             d8b            88
+     _888b       ~           aaa.                888           d8P
+ a888~"Y88                  88888                "P         8aa888_
+        Y8                  Y88P"                             88""888a
+        _8g8                 ~~                              a88    ~~
+    __a8"88_                                              a_a88
+   88"'   "88g                                             "88g_
+   ~        `88a_                                        _a8'"Y88gg,
+               "888a_.                                _a88"'     ~88
+                  ~~"8888aaa_____               ___a888P'
+                          ~~""""""888888888888888888""~~~
+                                   ~~~~~~~~~~~
+#
+# Asterisk Jukebox Makefile
+#
+# Copyright (C) 2006 J.A. Roberts Tunney
+#
+# J.A. Roberts Tunney <jtunney@lobstertech.com>
+#
+# This program is free software, distributed under the terms of
+# the GNU General Public License
+#
+
+.EXPORT_ALL_VARIABLES:
+
+MODS=app_jukebox.so app_mp3_2.so
+
+CC=cc
+CFLAGS=-O -g -D_GNU_SOURCE -shared -fpic
+
+PREFIX=/usr
+MODULES_DIR=$(PREFIX)/lib/asterisk/modules
+
+NAME=$(shell basename `pwd`)
+OSARCH=$(shell uname -s)
+
+ifeq (${OSARCH},Darwin)
+SOLINK=-dynamic -bundle -undefined suppress -force_flat_namespace
+else
+SOLINK=-fpic -shared -Xlinker -x -pthread
+endif
+ifeq (${OSARCH},SunOS)
+SOLINK=-shared -fpic -L/usr/local/ssl/lib
+endif
+
+all: depend $(MODS)
+
+install: all
+	for x in $(MODS); do install -m 755 $$x $(MODULES_DIR) ; done
+
+installbin:
+	for x in $(MODS); do install -m 755 $$x $(MODULES_DIR) ; done
+
+uninstall:
+	for x in $(MODS); do rm -f $(MODULES_DIR)/$$x ; done
+
+clean:
+	rm -f *.so *.o .depend $(NAME).tar.gz
+
+objclean:
+	rm -f *.o .depend $(NAME).tar.gz
+
+dist: all objclean
+	tar -C .. -cvzf /tmp/$(NAME).tar.gz $(NAME)
+	mv /tmp/$(NAME).tar.gz .
+
+start: install
+	for x in $(MODS); do asterisk -rx "load $$x" ; done
+
+stop:
+	for x in $(MODS); do asterisk -rx "unload $$x" ; done
+
+restart: stop start
+
+%.so : %.o
+	$(CC) $(SOLINK) -o $@ $<
+
+ifneq ($(wildcard .depend),)
+include .depend
+endif
+
+depend: .depend
+
+.depend:
+	./mkdep $(CFLAGS) $(shell ls *.c)
+              .--.                              .--.
+            .'(`                               /  ..\
+         __.>\ '.  _.---,._,'             ____.'  _o/
+       /.--.  : |/' _.--.<                '--.     |.__
+    _..-'    `\     /'     `'             _.-'     /--'
+     >_.-``-. `Y  /' _.---._____     _.--'        /
+    '` .-''. \|:  \.'   ___, .-'`   ~'--....___.-'
+     .'--._ `-:  \/   /'    \\
+         /.'`\ :;    /'       `-.
+        -`    |     |
+              :.; : |               Asterisk Jukebox
+              |:    |                 Version o.9.1
+              |     |
+              :. :  |        Copyright (c) 2006 J.A. Roberts Tunney
+            .jgs    ;            Keep it open source pigs
+            /:::.    `\
+----------------------------------------------------------------------
+
+J.A. Roberts Tunney <jtunney@lobstertech.com>
+http://www.lobstertech.com/code/jukebox/
+
+----------------------------------------------------------------------
+DESCRIPTION
+----------------------------------------------------------------------
+
+Asterisk Jukebox is an IVR application written for Asterisk, an open
+source PBX application. Asterisk Jukebox allows a caller to browse
+your music collection. All you have to do is tell Jukebox where your
+music is and callers will be able to browse the collection. For
+example a caller might hear: "Press 1 for Sisters Of Mercy", (Caller
+presses 1), "Press 1 for Marian, Press 2 for This Corrosion, etc." But
+that's not all! You can also tell the Jukebox to automatically pick
+random songs to play.
+
+At the moment, the jukebox supports all the standard Asterisk formats
+such as GSM and uLaw. There is also MP3 support if you have CPU to
+spare and mpg123 installed via app_mp3. You must also have Festival
+installed on your system so the Jukebox can generate text to speech.
+
+----------------------------------------------------------------------
+USAGE INSTRUCTIONS
+----------------------------------------------------------------------
+
+Press '*' to go up a directory. If you are in the root music folder
+you will be exited from the script unless the 't' option is set.
+
+If you have a really long list of files, you can filter the list at
+any time by pressing '#' and spelling out a few letters you expect the
+files to start with. For example, if you wanted to know what extension
+'Requiem For A Dream' was, you'd type: '#737'. Note: Phone keypads
+don't include Q and Z. Q is 7 and Z is 9. Star may be used as a
+character wildcard. To clear your filter after it's been applied, just
+press '#'.
+
+Also remember that you may terminate any digit input by pressing the
+pound key. This helps to avoid the 5 second timeout.
+
+----------------------------------------------------------------------
+
+               __                          ___           _aaa
+              d8888a,_                  a8888888a  __a8888888b
+             d8P   `88ba.               8P'~~~~Y88888P""~~~~88b
+            d8P     ~"Y88a____aaaa_____aP        88         Y88
+           d8P         ~Y88"8~~~~~~88888          8g         88
+          d8P                         88     ____ 88y__      88b
+          88                         a88   _a88~888"8M88a____888
+          88                         88P   88  a8"     `88888888b_
+          8P                         88    88 a88        88b    Y8,
+          88a                        Y8b     888L        888.    88P
+         aP                           Y8     _888      _a8P 8   a88
+        _8                             ~88a888~888_   a888yg'  a88'
+        88                               ~~~~    ~"8888       a88P
+       d8'                                           Y8,     888L
+       8E                                             88a___8"888
+      d8P                                              ~Y888   88L
+      88                                                        88
+      88                                                        88b
+  ____88a_      a8a                                           __881
+88""P~888       888b                              _         8888888888
+      888       888P                             d8b            88
+     _888b       ~           aaa.                888           d8P
+ a888~"Y88                  88888                "P         8aa888_
+        Y8                  Y88P"                             88""888a
+        _8g8                 ~~                              a88    ~~
+    __a8"88_                                              a_a88
+   88"'   "88g                                             "88g_
+   ~        `88a_                                        _a8'"Y88gg,
+               "888a_.                                _a88"'     ~88
+                  ~~"8888aaa_____               ___a888P'
+                          ~~""""""888888888888888888""~~~
+                                   ~~~~~~~~~~~

File app_jukebox.c

+/*
+ * Jukebox for Asterisk 1.2
+ * Version 0.9
+ *
+ * Copyright (C) 2006 J.A. Roberts Tunney
+ *
+ * J.A. Roberts Tunney <jtunney@lobstertech.com>
+ *
+ * Keep it Open Source Pigs
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License version 2.0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <asterisk/file.h>
+#include <asterisk/frame.h>
+#include <asterisk/options.h>
+#include <asterisk/logger.h>
+#include <asterisk/channel.h>
+#include <asterisk/pbx.h>
+#include <asterisk/module.h>
+#include <asterisk/lock.h>
+#include <asterisk/md5.h>
+#include <asterisk/app.h>
+#include <asterisk/say.h>
+
+#define JUKEBOX_CONFIG "jukebox.conf"
+#define FLOCK_BUF_SIZE 256
+
+#define AST_JUKEBOX_RECACHE	(1 << 0)
+#define AST_JUKEBOX_HANGUP	(1 << 1)
+#define AST_JUKEBOX_GREETING	(1 << 2)
+#define AST_JUKEBOX_INSTRUCT	(1 << 3)
+#define AST_JUKEBOX_RANDOM	(1 << 4)
+#define AST_JUKEBOX_NOTIMEOUT	(1 << 5)
+#define AST_JUKEBOX_NOIVR	(1 << 6)
+#define AST_JUKEBOX_SCALE	(1 << 7)
+
+#define IVR_HANGUP -1
+#define IVR_OK 0
+#define IVR_TIMEOUT 1
+#define IVR_MOVEUP 2
+#define IVR_INVALID 3
+
+static char *app = "Jukebox";
+static char *synopsis = "IVR app to browse a hierarchy of music files";
+static char *tdesc = "Jukebox Music Player";
+static char *descrip = ""
+"JUKEBOX([dir[|options]])\n"
+"\n"
+"Description:\n"
+"  This jukebox application allows a caller to browse your music\n"
+"  collection.  Essentially what happens here is you point this\n"
+"  application in the direction of a folder containing a bunch of\n"
+"  music.  The caller will be read off the files and folders using\n"
+"  text2wave, which is provided by Festival.\n"
+"\n"
+"  At the moment, the jukebox supports all the standard Asterisk\n"
+"  formats such as GSM and uLaw.  There is also MP3 support if\n"
+"  you have CPU to spare and mpg123 installed via app_mp3.\n"
+"\n"
+"Usage Instructions:\n"
+"  Press '*' to go up a directory.  If you are in the root music\n"
+"  folder you will be exited from the script.\n"
+"\n"
+"  If you have a really long list of files, you can filter the list\n"
+"  at any time by pressing '#' and spelling out a few letters you\n"
+"  expect the files to start with.  For example, if you wanted to\n"
+"  know what extension 'Requiem For A Dream' was, you'd type:\n"
+"  '#737'.  Note: Phone keypads don't include Q and Z.  Q is 7 and\n"
+"  Z is 9.  Star may be used as a character wildcard.  To clear\n"
+"  your filter after it's been applied, just press '#'.\n"
+"\n"
+"  Also remember that you may terminate any digit input by pressing\n"
+"  the pound key.  This helps to avoid the 5 second timeout.\n"
+"\n"
+"Options:\n"
+"  R(x) -- Re-cache all text to speech files using `x' number of\n"
+"          threads\n"
+"  F(x) -- Changes volume scale factor; see MP3Player2() for info.\n"
+"          TIP: F(4096) makes mp3 files play quieter\n"
+"  h -- Hangup after playing song\n"
+"  g -- Play the greeting message\n"
+"  i -- Play the instructions\n"
+"  r -- Start with random song\n"
+"  t -- Play random song on timeout\n"
+"  n -- No IVR.  Must specify with 'r'\n"
+"\n"
+"Returns:\n"
+"  The JUKEBOX() application always returns 0 unless it has trouble\n"
+"  parsing the arguments you pass it.  Upon completion, a JUKESTATUS\n"
+"  variable is set to one of the following values:\n"
+"\n"
+"   - HANGUP (User hungup or error occurred)\n"
+"   - TIMEOUT (User didn't enter anything at a prompt)\n"
+"   - MOVEUP (User hit '*' at base directory)\n"
+"   - OK (Should only return if you specify 'n' w/o 'r')\n"
+"\n"
+"Examples:\n"
+"    exten => 666,1,Jukebox(default|gi)   ; Normal invokation\n"
+"    exten => 666,1,Jukebox(default|rn)   ; Just play random songs\n"
+"    exten => 666,1,Jukebox(default|rh)   ; Play one song and hangup\n"
+"    exten => 666,1,Jukebox(default|R(6)) ; Recache all the TTS\n"
+"\n"
+"Advanced Configuration:\n"
+"\n"
+"  /etc/asterisk/jukebox.conf ** OPTIONAL **\n"
+"\n"
+"  [jukebox]\n"
+"  cachedir=/var/cache/jukebox\n"
+"  text2wave=text2wave                      ; and optional switches\n"
+"  defaultmusicdir=/var/lib/asterisk/music\n"
+;
+
+STANDARD_LOCAL_USER;
+LOCAL_USER_DECL;
+
+enum {
+	OPT_ARG_RECACHE = 0,
+	OPT_ARG_SCALE,
+	/* note: this entry _MUST_ be the last one in the enum */
+	OPT_ARG_ARRAY_SIZE
+} jukebox_option_args;
+
+AST_APP_OPTIONS(jukebox_options, {
+	AST_APP_OPTION('h', AST_JUKEBOX_HANGUP),
+	AST_APP_OPTION('g', AST_JUKEBOX_GREETING),
+	AST_APP_OPTION('i', AST_JUKEBOX_INSTRUCT),
+	AST_APP_OPTION('r', AST_JUKEBOX_RANDOM),
+	AST_APP_OPTION('t', AST_JUKEBOX_NOTIMEOUT),
+	AST_APP_OPTION('n', AST_JUKEBOX_NOIVR),
+	AST_APP_OPTION_ARG('R', AST_JUKEBOX_RECACHE, OPT_ARG_RECACHE),
+	AST_APP_OPTION_ARG('F', AST_JUKEBOX_SCALE, OPT_ARG_SCALE),
+});
+
+struct jukebox_ops {
+	struct ast_channel *chan;
+	char *musicdir;
+	int threads;
+	int options;
+	int scale;
+
+	/* only needed for cache thread thing */
+	ast_mutex_t mutex;
+	int work_done;
+	int hangup;
+	int threadcnt;
+	char **flocks;
+};
+
+struct menuitem {
+	int type;
+	int num;
+	char *dir;
+	char *name;
+	struct menuitem *next;
+};
+
+struct thread_ops {
+	int threadid;
+	struct jukebox_ops *ops;
+};
+
+static int browse_dir(struct jukebox_ops *ops, const char *dir);
+
+char jukop_text2wave[128];
+char jukop_cachedir[128];
+char jukop_defaultmusicdir[128];
+
+static char hex(int n)
+{
+	switch (n) {
+	case  0: return '0';
+	case  1: return '1';
+	case  2: return '2';
+	case  3: return '3';
+	case  4: return '4';
+	case  5: return '5';
+	case  6: return '6';
+	case  7: return '7';
+	case  8: return '8';
+	case  9: return '9';
+	case 10: return 'a';
+	case 11: return 'b';
+	case 12: return 'c';
+	case 13: return 'd';
+	case 14: return 'e';
+	case 15: return 'f';
+	default: return '0';
+	}
+}
+
+static void inline bhex(char res[2], int n)
+{
+	res[0] = hex((n >> 4) & 0xF);
+	res[1] = hex((n >> 0) & 0xF);
+}
+
+static void md5(char res[33], unsigned char *data, unsigned len)
+{
+	unsigned char digest[16];
+	struct MD5Context md5;
+
+	MD5Init(&md5);
+	MD5Update(&md5, data, len);
+	MD5Final(digest, &md5);
+
+	bhex((char *)&res[0], digest[0]);
+	bhex((char *)&res[2], digest[1]);
+	bhex((char *)&res[4], digest[2]);
+	bhex((char *)&res[6], digest[3]);
+	bhex((char *)&res[8], digest[4]);
+	bhex((char *)&res[10], digest[5]);
+	bhex((char *)&res[12], digest[6]);
+	bhex((char *)&res[14], digest[7]);
+	bhex((char *)&res[16], digest[8]);
+	bhex((char *)&res[18], digest[9]);
+	bhex((char *)&res[20], digest[10]);
+	bhex((char *)&res[22], digest[11]);
+	bhex((char *)&res[24], digest[12]);
+	bhex((char *)&res[26], digest[13]);
+	bhex((char *)&res[28], digest[14]);
+	bhex((char *)&res[30], digest[15]);
+
+	res[32] = '\0';
+}
+
+static void inline clean_filename(char *s)
+{
+	while (*s) {
+		if ((*s >= '0' && *s <= '9') ||
+		    (*s >= 'a' && *s <= 'z') ||
+		    (*s >= 'A' && *s <= 'Z'))
+			s++;
+		else
+			*s++ = ' ';
+	}
+}
+
+/*
+ * When passing the filename to this function, please remove the
+ * extension first so asterisk can determine the least cost format.
+ *
+ * Returns:
+ *  -1 - Hangup or Error
+ *   0 - No input
+ *  >0 - Ascii DTMF Input
+ */
+static int say_file(struct ast_channel *chan, const char *filename, const char *ints)
+{
+	int res;
+
+	if (ast_streamfile(chan, filename, chan->language) == -1)
+		return IVR_HANGUP;
+	res = ast_waitstream(chan, ints);
+	ast_stopstream(chan);
+
+	return res;
+}
+
+/*
+ * Returns:
+ *  -1 - Hangup or Error
+ *   0 - No input
+ *  >0 - Ascii DTMF Input
+ */
+static int inline say_digits(struct ast_channel *chan, int number, const char *ints)
+{
+	return ast_say_digits(chan, number, ints, chan->language);
+}
+
+/*
+ * Returns:
+ *  -1 - Hangup or Error
+ *   0 - No input
+ *  >0 - Ascii DTMF Input
+ */
+static int inline say_number(struct ast_channel *chan, int number, const char *ints)
+{
+	return ast_say_number(chan, number, ints, chan->language, "n");
+}
+
+/*
+ * When passing the filename to this function, please do not truncate
+ * the extension.
+ *
+ * Returns:
+ *  -1 - Hangup or Error
+ *   0 - Success
+ */
+static int play_music(struct jukebox_ops *ops, const char *filename)
+{
+	struct ast_app *app;
+	char *pos, *s;
+
+	pos = strrchr(filename, '.');
+	if (pos == NULL || strcasecmp(pos + 1, "mp3") != 0) {
+		s = strdupa(filename);
+		pos = strrchr(s, '.');
+		if (pos)
+			*pos = '\0';
+		if (say_file(ops->chan, s, "1234567890*#") == IVR_HANGUP)
+			return IVR_HANGUP;
+		else
+			return IVR_OK;
+	} else {
+		if (option_verbose > 2)
+			ast_verbose(VERBOSE_PREFIX_3 "Playing: %s\n", filename);
+		if (!(app = pbx_findapp("MP3Player2"))) {
+			ast_log(LOG_WARNING, "Could not find application (MP3Player2)\n");
+			return IVR_HANGUP;
+		}
+		s = alloca(strlen(filename) + 16);
+		if ((ops->options & AST_JUKEBOX_SCALE) == AST_JUKEBOX_SCALE)
+			snprintf(s, strlen(filename) + 16, "%s|F(%d)", filename, ops->scale);
+		else
+			snprintf(s, strlen(filename) + 16, "%s", filename);
+		ast_verbose(s);
+		if (pbx_exec(ops->chan, app, s, 1) == 0)
+			return IVR_OK;
+		else
+			return IVR_HANGUP;
+	}
+}
+
+/*
+ * Fills your buffer with a string of DTMF digits that are entered by
+ * the caller.  Uses default timeout.  For the max digits effect, just
+ * say the length of your buffer is max digits + 1.
+ *
+ * You may specify a string of interupt characters.  If any are
+ * entered, it will not be included in the result.  I generally just
+ * set # as an interupt here.
+ *
+ * If return is success and string is empty, that means user waited
+ * whole timeout.
+ *
+ * Returns:
+ *  -1 - Error
+ *   0 - Success
+ */
+static int get_digits(struct ast_channel *chan, char *res, int len, const char *ints)
+{
+	int r;
+	if (len <= 1) {
+		*res = '\0';
+		return IVR_OK;
+	}
+	for (;;) {
+		r = ast_waitfordigit(chan, chan->pbx ? chan->pbx->dtimeout * 1000 : 5000);
+		if (r <= 0) {
+			*res = '\0';
+			return r;
+		}
+		if (strchr(ints, r)) {
+			*res = '\0';
+			return IVR_OK;
+		}
+		*res++ = (char)r;
+		if (--len == 1) {
+			*res = '\0';
+			return IVR_OK;
+		}
+	}
+}
+
+/*
+ * Creates a TTS file of some text
+ *
+ * This will create the u-Law file in the the designated cache dir.
+ * The name of the file will be the juk_<md5 of text>.ul.  It will not
+ * recreate the file if it already exists.
+ *
+ * The name of the cache file will be stored in fnout
+ *
+ * Returns:
+ *  -1 = error
+ *   0 = success
+ */
+static int cache_text(char *fnout, int len, const char *fmt, ...)
+{
+	va_list ap;
+	char digest[33], txt[512], cmd[512];
+	FILE *pf;
+
+	va_start(ap, fmt);
+	vsnprintf(txt, sizeof(txt), fmt, ap);
+	va_end(ap);
+	clean_filename(txt);
+
+	md5(digest, (unsigned char *)txt, (unsigned)strlen(txt));
+
+	snprintf(fnout, len, "%s/juk_%s.ul", jukop_cachedir, digest);
+
+	if (access(fnout, F_OK) == 0)
+		return IVR_OK;
+
+	snprintf(cmd, sizeof(cmd), "%s -o %s -otype ulaw -", jukop_text2wave, fnout);
+	if (option_verbose > 3)
+		ast_verbose(VERBOSE_PREFIX_4 "echo '%s' | %s\n", txt, cmd);
+	pf = popen(cmd, "w");
+	if (!pf) {
+		ast_log(LOG_WARNING, "Failed to open pipe to %s\n", jukop_text2wave);
+		return IVR_HANGUP;
+	}
+	if (fwrite(txt, strlen(txt), 1, pf) != 1) {
+		ast_log(LOG_WARNING, "Failed to write data to pipe\n");
+		pclose(pf);
+		return IVR_HANGUP;
+	}
+	pclose(pf);
+
+	if (access(fnout, F_OK) != 0) {
+		ast_log(LOG_WARNING, "Failed to create TTS file using %s\n", jukop_text2wave);
+		return IVR_HANGUP;
+	}
+
+	return IVR_OK;
+}
+
+/*
+ * Returns file/dir count or -1 on error
+ */
+static int count_files(const char *dir)
+{
+	DIR *d;
+	struct dirent *e;
+	int count = 0;
+
+	if (!(d = opendir(dir))) {
+		ast_log(LOG_WARNING, "opendir() Failed: %s\n", dir);
+		return -1;
+	}
+	while ((e = readdir(d))) {
+		if (e->d_name[0] == '.')
+			continue;
+		switch (e->d_type) {
+		case DT_REG:
+			count++;
+			break;
+		case DT_DIR: {
+			char *subdir;
+			int len, n;
+			count++;
+			len = strlen(dir) + 1 + strlen(e->d_name) + 1;
+			subdir = alloca(len);
+			snprintf(subdir, len, "%s/%s", dir, e->d_name);
+			n = count_files(subdir);
+			if (n == -1)
+				return -1;
+			count += n;
+			break;
+		}
+		}
+	}
+	closedir(d);
+
+	return count;
+}
+
+/*
+ * Although directories appear in the count, they will not be chosen.
+ * In the event that 'n' points to a directory, the next file will be
+ * chosen.
+ *
+ * Returns:
+ *  -1 = Error
+ *   0 = OK
+ *   1 = Not found
+ */
+static int get_file_n(const char *dir, char *buf, int len, int nfile, int *curcount)
+{
+	DIR *d;
+	struct dirent *e;
+
+	if (!(d = opendir(dir))) {
+		ast_log(LOG_WARNING, "opendir() Failed: %s\n", dir);
+		return -1;
+	}
+	while ((e = readdir(d))) {
+		if (e->d_name[0] == '.')
+			continue;
+		switch (e->d_type) {
+		case DT_REG:
+			if (++*curcount >= nfile) {
+				snprintf(buf, len, "%s/%s", dir, e->d_name);
+				return 0;
+			}
+			break;
+		case DT_DIR: {
+			char *subdir;
+			int l, rc;
+			(*curcount)++;
+			l = strlen(dir) + 1 + strlen(e->d_name) + 1;
+			subdir = alloca(l);
+			snprintf(subdir, l, "%s/%s", dir, e->d_name);
+			rc = get_file_n(subdir, buf, len, nfile, curcount);
+			if (rc <= 0)
+				return rc;
+			break;
+		}
+		}
+	}
+	closedir(d);
+
+	return 1;
+}
+
+static int thread_cache_text(struct jukebox_ops *ops, int threadid, char *file)
+{
+	int n;
+	char *s, *pos;
+	char cachefile[512];
+
+	ast_mutex_lock(&ops->mutex);
+	for (n = 0; n < ops->threadcnt; n++) {
+		if (strcmp(ops->flocks[n], file) == 0) {
+			ast_mutex_unlock(&ops->mutex);
+			return IVR_OK;
+		}
+	}
+	ast_mutex_unlock(&ops->mutex);
+
+	ast_mutex_lock(&ops->mutex);
+	snprintf(ops->flocks[threadid], FLOCK_BUF_SIZE, "%s", file);
+	ast_mutex_unlock(&ops->mutex);
+
+	s = strdupa(file);
+	pos = strrchr(s, '.');
+	if (pos)
+		*pos = '\0';
+	cache_text(cachefile, sizeof(cachefile), s);
+
+	ast_mutex_lock(&ops->mutex);
+	ops->flocks[threadid][0] = '\0';
+	ops->work_done++;
+	ast_mutex_unlock(&ops->mutex);
+
+	return IVR_OK;
+}
+
+/*
+ * This function will go through a directory recursively and cache a
+ * TTS recording of the file name without extension.
+ *
+ * For each file that is generated, ops->work_done will be locked by
+ * ops->mutex and incremented.
+ */
+static int cache_dir(struct jukebox_ops *ops, int threadid, const char *dir)
+{
+	DIR *d;
+	struct dirent *e;
+	int terminated;
+
+	if (!(d = opendir(dir))) {
+		ast_log(LOG_WARNING, "opendir() Failed: %s\n", dir);
+		return IVR_HANGUP;
+	}
+	while ((e = readdir(d))) {
+		ast_mutex_lock(&ops->mutex);
+		terminated = ops->hangup;
+		ast_mutex_unlock(&ops->mutex);
+		if (terminated) {
+			closedir(d);
+			return IVR_HANGUP;
+		}
+
+		if (e->d_name[0] == '.')
+			continue;
+
+		switch (e->d_type) {
+		case DT_REG:
+			thread_cache_text(ops, threadid, e->d_name);
+			break;
+
+		case DT_DIR: {
+			char *subdir;
+			int len;
+
+			thread_cache_text(ops, threadid, e->d_name);
+
+			len = strlen(dir) + 1 + strlen(e->d_name) + 1;
+			subdir = alloca(len);
+			snprintf(subdir, len, "%s/%s", dir, e->d_name);
+			if (cache_dir(ops, threadid, subdir) != 0) {
+				closedir(d);
+				return IVR_HANGUP;
+			}
+			break;
+		}
+		}
+	}
+	closedir(d);
+
+	return IVR_OK;
+}
+
+static void *cache_worker(void *tid)
+{
+	struct jukebox_ops *ops;
+	int threadid;
+
+	ops = ((struct thread_ops *)tid)->ops;
+	threadid = ((struct thread_ops *)tid)->threadid;
+	free(tid);
+
+	cache_dir(ops, threadid, ops->musicdir);
+
+	pthread_exit(NULL);
+}
+
+static int recache_wait(struct jukebox_ops *ops)
+{
+	struct ast_channel *chan;
+	struct ast_frame *f;
+	time_t t;
+	int ms, res = IVR_HANGUP;
+	int done, total;
+
+	t = time(NULL);
+	chan = ops->chan;
+	total = count_files(ops->musicdir) * ops->threads;
+
+	for (;;) {
+		ast_mutex_lock(&ops->mutex);
+		done = ops->work_done;
+		ast_mutex_unlock(&ops->mutex);
+		if (done >= total) {
+			res = IVR_OK;
+			break;
+		}
+
+		if (time(NULL) >= t + 20) {
+			t = time(NULL);
+			if (option_verbose > 2)
+				ast_verbose(VERBOSE_PREFIX_3 "%d%% Complete\n", (int)((float)done / (float)total * 100.0));
+			ms = say_number(chan, (int)((float)done / (float)total * 100.0), "1234567890*#");
+			if (ms < 0) { res = IVR_HANGUP; break; }
+			if (ms > 0) { res = IVR_OK; break; }
+			/* todo : play 'percent' */
+		}
+
+		ms = ast_waitfor(chan, 100);
+		if (ms < 0)
+			break;
+		if (ms) {
+			f = ast_read(chan);
+			if (!f)
+				break;
+			if (f->frametype == AST_FRAME_DTMF) {
+				ast_log(LOG_DEBUG, "User pressed a key\n");
+				ast_frfree(f);
+				res = IVR_OK;
+				break;
+			}
+			ast_frfree(f);
+		}
+	}
+
+	ast_mutex_lock(&ops->mutex);
+	ops->hangup = 1;
+	ast_mutex_unlock(&ops->mutex);
+
+	return res;
+}
+
+/*
+ * Returns:
+ *  -1 - Hangup or Error
+ *   0 - Success
+ */
+static int recache_tts(struct jukebox_ops *ops)
+{
+	pthread_t *threads;
+	pthread_attr_t attr;
+	struct thread_ops *tops;
+	int n, r;
+
+	ast_mutex_init(&ops->mutex);
+	pthread_attr_init(&attr);
+	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+	threads = alloca(sizeof(pthread_t) * ops->threads);
+	ops->flocks = malloc(sizeof(char *) * ops->threads);
+	ops->work_done = 0;
+	ops->hangup = 0;
+	ops->threadcnt = ops->threads;
+	for (n = 0; n < ops->threads; n++) {
+		ops->flocks[n] = malloc(FLOCK_BUF_SIZE);
+		ops->flocks[n][0] = '\0';
+	}
+
+	for (n = 0; n < ops->threads; n++) {
+		tops = malloc(sizeof(tops));
+		if (!tops)
+			ast_log(LOG_WARNING, "Out of memory, failure imminent\n");
+		tops->threadid = n;
+		tops->ops = ops;
+		if (pthread_create(&threads[n], &attr, cache_worker, (void *)tops)) {
+			ast_log(LOG_WARNING, "Failed to create thread\n");
+			return IVR_HANGUP;
+		}
+	}
+
+	r = recache_wait(ops);
+
+	for (n = 0; n < ops->threads; n++) {
+		pthread_join(threads[n], NULL);
+	}
+
+	for (n = 0; n < ops->threads; n++)
+		free(ops->flocks[n]);
+	free(ops->flocks);
+	ast_mutex_destroy(&ops->mutex);
+	pthread_attr_destroy(&attr);
+
+	return r;
+}
+
+/*
+ * Returns:
+ *  -1 - Hangup or Error
+ *   0 - Ok, check res for input
+ */
+static int prompt_menuitem(struct jukebox_ops *ops, char *res, int len, struct menuitem *item)
+{
+	int r;
+	char *s, *pos;
+	char cachefile[512];
+
+	r = say_file(ops->chan, "vm-press", "1234567890*#");
+	if (r == IVR_HANGUP) { *res = '\0'; return IVR_HANGUP; }
+	if (r > 0) {
+		*res = (char)r;
+		if ((char)r == '*') { *++res = '\0'; return IVR_OK; }
+		return get_digits(ops->chan, res + 1, len - 1, "#");
+	}
+	r = say_digits(ops->chan, item->num, "1234567890*#");
+	if (r == IVR_HANGUP) { *res = '\0'; return IVR_HANGUP; }
+	if (r > 0) {
+		*res = (char)r;
+		if ((char)r == '*') { *++res = '\0'; return IVR_OK; }
+		return get_digits(ops->chan, res + 1, len - 1, "#");
+	}
+	r = say_file(ops->chan, "vm-for", "1234567890*#");
+	if (r == IVR_HANGUP) { *res = '\0'; return IVR_HANGUP; }
+	if (r > 0) {
+		*res = (char)r;
+		if ((char)r == '*') { *++res = '\0'; return IVR_OK; }
+		return get_digits(ops->chan, res + 1, len - 1, "#");
+	}
+
+	s = strdupa(item->name);
+	pos = strrchr(s, '.');
+	if (pos)
+		*pos = '\0';
+	if (cache_text(cachefile, sizeof(cachefile), s) != 0)
+		return IVR_HANGUP;
+
+	pos = strrchr(cachefile, '.');
+	if (pos)
+		*pos = '\0';
+	r = say_file(ops->chan, cachefile, "1234567890*#");
+	if (r == IVR_HANGUP) { *res = '\0'; return IVR_HANGUP; }
+	if (r > 0) {
+		*res = (char)r;
+		if ((char)r == '*') { *++res = '\0'; return IVR_OK; }
+		return get_digits(ops->chan, res + 1, len - 1, "#");
+	}
+
+	*res = '\0';
+	return IVR_OK;
+}
+
+static void free_menu(struct menuitem *menu)
+{
+	struct menuitem *item;
+	do {
+		item = menu->next;
+		free(menu->dir);
+		free(menu->name);
+		free(menu);
+	} while ((menu = item));
+}
+
+static int sort_menu_cmp(const void *left, const void *right)
+{
+	return strcmp((*(struct menuitem **)left)->name, (*(struct menuitem **)right)->name);
+}
+
+static struct menuitem *sort_menu(struct menuitem *menu)
+{
+	struct menuitem *item, **list;
+	int cnt = 0, n;
+
+	for (item = menu; item; item = item->next)
+		cnt++;
+	list = alloca(sizeof(struct menuitem *) * cnt);
+	for (item = menu, n = 0; item; item = item->next, n++)
+		list[n] = item;
+
+	qsort((void *)list, cnt, sizeof(struct menuitem *), sort_menu_cmp);
+
+	for (n = 0; n < cnt - 1; n++) {
+		list[n]->next = list[n + 1];
+		list[n]->num = n + 1;
+	}
+	list[cnt - 1]->next = NULL;
+	list[cnt - 1]->num = cnt;
+
+	return list[0];
+}
+
+static struct menuitem *build_menu(const char *dir)
+{
+	DIR *d;
+	struct dirent *e;
+	struct menuitem *menu = NULL, *item, *last = NULL;
+
+	if (!(d = opendir(dir))) {
+		ast_log(LOG_WARNING, "opendir() Failed: %s\n", dir);
+		return NULL;
+	}
+	while ((e = readdir(d))) {
+		if (e->d_name[0] == '.')
+			continue;
+		item = malloc(sizeof(struct menuitem));
+		if (!item) {
+			if (menu)
+				free_menu(menu);
+			return NULL;
+		}
+		item->type = e->d_type;
+		item->dir = strdup(dir);
+		if (!item->dir) {
+			free(item);
+			if (menu)
+				free_menu(menu);
+			return NULL;
+		}
+		item->name = strdup(e->d_name);
+		if (!item->name) {
+			free(item->dir);
+			free(item);
+			if (menu)
+				free_menu(menu);
+			return NULL;
+		}
+		item->next = NULL;
+		if (menu == NULL) {
+			menu = item;
+			last = item;
+		} else {
+			last->next = item;
+			last = item;
+		}
+	}
+	closedir(d);
+
+	return sort_menu(menu);
+}
+
+/*
+ * Returns:
+ *  -1: Hangup or error
+ *   1: timeout
+ *   2: re-browse menu
+ *   3: invalid
+ */
+static int process_selection(struct jukebox_ops *ops, struct menuitem *menu, const char *sel)
+{
+	int nsel;
+	struct menuitem *item;
+
+	sscanf(sel, "%d", &nsel);
+	if (option_verbose > 2)
+		ast_verbose(VERBOSE_PREFIX_3 "User entered: %s\n", sel);
+
+	for (item = menu; item; item = item->next) {
+		if (item->num != nsel)
+			continue;
+		switch (item->type) {
+		case DT_REG: {
+			char *file;
+			int res, len;
+			len = strlen(item->dir) + 1 + strlen(item->name) + 1;
+			file = alloca(len);
+			snprintf(file, len, "%s/%s", item->dir, item->name);
+			res = play_music(ops, file);
+			if (res == IVR_HANGUP || (ops->options & AST_JUKEBOX_HANGUP) == AST_JUKEBOX_HANGUP)
+				return IVR_HANGUP;
+			else
+				return IVR_MOVEUP;
+		}
+		case DT_DIR: {
+			char *subdir;
+			int len;
+			len = strlen(item->dir) + 1 + strlen(item->name) + 1;
+			subdir = alloca(len);
+			snprintf(subdir, len, "%s/%s", item->dir, item->name);
+			return browse_dir(ops, subdir);
+		}
+		default:
+			ast_log(LOG_NOTICE, "wtf\n");
+			return IVR_HANGUP;
+		}
+	}
+
+	return IVR_INVALID;
+}
+
+static void stripcpy(char *to, const char *from)
+{
+	while (*from) {
+		if ((*from >= '0' && *from <= '9') ||
+		    (*from >= 'a' && *from <= 'z')) {
+			*to++ = *from;
+		} else if (*from >= 'A' && *from <= 'Z') {
+			*to++ = *from + ('a' - 'A');
+		}
+		from++;
+	}
+	*to = '\0';
+}
+
+/*
+ * Returns:
+ *   0 - doesn't match
+ *   1 - matches
+ */
+static int match_filter(const char *name, const char *filter)
+{
+	char *aname;
+	int n;
+	int len, dlen;
+
+	aname = alloca(strlen(name) + 1);
+	stripcpy(aname, name);
+	len = strlen(aname);
+	dlen = strlen(filter);
+
+	for (n = 0; n < len && n < dlen; n++) {
+		switch (filter[n]) {
+		case '0':
+			if (aname[n] != '0')
+				return 0;
+			break;
+		case '1':
+			if (aname[n] != '1')
+				return 0;
+			break;
+		case '2':
+			if (aname[n] != '2' &&
+			    aname[n] != 'a' &&
+			    aname[n] != 'b' &&
+			    aname[n] != 'c')
+				return 0;
+			break;
+		case '3':
+			if (aname[n] != '3' &&
+			    aname[n] != 'd' &&
+			    aname[n] != 'e' &&
+			    aname[n] != 'f')
+				return 0;
+			break;
+		case '4':
+			if (aname[n] != '4' &&
+			    aname[n] != 'g' &&
+			    aname[n] != 'h' &&
+			    aname[n] != 'i')
+				return 0;
+			break;
+		case '5':
+			if (aname[n] != '5' &&
+			    aname[n] != 'j' &&
+			    aname[n] != 'k' &&
+			    aname[n] != 'l')
+				return 0;
+			break;
+		case '6':
+			if (aname[n] != '6' &&
+			    aname[n] != 'm' &&
+			    aname[n] != 'n' &&
+			    aname[n] != 'o')
+				return 0;
+			break;
+		case '7':
+			if (aname[n] != '7' &&
+			    aname[n] != 'p' &&
+			    aname[n] != 'q' &&
+			    aname[n] != 'r' &&
+			    aname[n] != 's')
+				return 0;
+			break;
+		case '8':
+			if (aname[n] != '8' &&
+			    aname[n] != 't' &&
+			    aname[n] != 'u' &&
+			    aname[n] != 'v')
+				return 0;
+			break;
+		case '9':
+			if (aname[n] != '9' &&
+			    aname[n] != 'w' &&
+			    aname[n] != 'x' &&
+			    aname[n] != 'y' &&
+			    aname[n] != 'z')
+				return 0;
+			break;
+		case '*':
+			break;
+		}
+	}
+
+	return 1;
+}
+
+/*
+ * Returns:
+ *  -1 - error or hangup
+ *   1 - timeout
+ *   2 - move up a dir
+ */
+static int browse_menu(struct jukebox_ops *ops, struct menuitem *menu)
+{
+	char inp[16], filter[16];
+	struct menuitem *item;
+	int res, found;
+
+	for (;;) {
+		inp[0] = '\0';
+		for (item = menu; item; item = item->next) {
+			if (strlen(filter) && !match_filter(item->name, filter))
+				continue;
+			res = prompt_menuitem(ops, inp, sizeof(inp), item);
+			if (res == IVR_HANGUP)
+				return IVR_HANGUP;
+			if (!strlen(inp))
+				continue;
+			if (inp[0] == '*')
+				return IVR_MOVEUP;
+			break;
+		}
+
+		if (inp[0] == '#') {
+			snprintf(filter, sizeof(filter), "%s", &inp[1]);
+			if (!strlen(filter)) {
+				if (option_verbose > 2)
+					ast_verbose(VERBOSE_PREFIX_3 "Removing filter\n");
+				continue;
+			}
+			found = 0;
+			for (item = menu; item; item = item->next) {
+				if (match_filter(item->name, filter)) {
+					found = 1;
+					break;
+				}
+			}
+			if (!found) {
+				/* todo : play 'nothing matches' */
+				if (option_verbose > 2)
+					ast_verbose(VERBOSE_PREFIX_3 "Filter matches nothing: %s\n", filter);
+				filter[0] = '\0';
+			} else {
+				if (option_verbose > 2)
+					ast_verbose(VERBOSE_PREFIX_3 "Applying filter: %s\n", filter);
+			}
+			continue;
+		}
+
+		if (!strlen(inp)) {
+			res = get_digits(ops->chan, inp, sizeof(inp), "#");
+			if (res == IVR_HANGUP)
+				return IVR_HANGUP;
+			if (!strlen(inp))
+				return IVR_TIMEOUT;
+		}
+
+		for (;;) {
+			res = process_selection(ops, menu, inp);
+			if (res == IVR_HANGUP || res == IVR_TIMEOUT)
+				return res;
+			else if (res == IVR_MOVEUP) {
+				filter[0] = '\0';
+				break;
+			} else if (res == IVR_INVALID) {
+				res = say_file(ops->chan, "invalid", "1234567890*#");
+				if (res == IVR_HANGUP)
+					return IVR_HANGUP;
+				if (res == IVR_OK)
+					break;
+				*inp = (char)res;
+				if (get_digits(ops->chan, inp + 1, sizeof(inp) - 1, "#") == IVR_HANGUP)
+					return IVR_HANGUP;
+			} else {
+				ast_log(LOG_NOTICE, "wtf\n");
+				return IVR_HANGUP;
+			}
+		}
+	}
+}
+
+/*
+ * Returns:
+ *  -1 - error or hangup
+ *   0 - success
+ *   1 - timeout
+ *   2 - move up a dir
+ */
+static int browse_dir(struct jukebox_ops *ops, const char *dir)
+{
+	struct menuitem *menu;
+	int res;
+
+	if (!(menu = build_menu(dir)))
+		return IVR_HANGUP;
+	res = browse_menu(ops, menu);
+	free_menu(menu);
+
+	return res;
+}
+
+/*
+ * Returns:
+ *  -1 - error or hangup
+ *   0 - success
+ */
+static int play_random(struct jukebox_ops *ops)
+{
+	int n, c = 0;
+	int rc, total;
+	char file[512];
+
+	total = count_files(ops->musicdir);
+	n = (rand() % total) + 1;
+	rc = get_file_n(ops->musicdir, file, sizeof(file), n, &c);
+	if (rc == -1)
+		return IVR_HANGUP;
+	else if (rc == 1) {
+		return IVR_HANGUP;
+	}
+	rc = play_music(ops, file);
+	if (rc == IVR_HANGUP || (ops->options & AST_JUKEBOX_HANGUP) == AST_JUKEBOX_HANGUP)
+		return IVR_HANGUP;
+	else
+		return IVR_OK;
+}
+
+/*
+ * Returns:
+ *  -1 - error or hangup
+ *   0 - success
+ *   1 - timeout
+ *   2 - move up a dir
+ */
+static int jukebox_main(struct jukebox_ops *ops)
+{
+	int rc;
+
+	if ((ops->options & AST_JUKEBOX_NOIVR) != AST_JUKEBOX_NOIVR) {
+		if ((ops->options & AST_JUKEBOX_RECACHE) == AST_JUKEBOX_RECACHE) {
+			if (recache_tts(ops) != 0)
+				return IVR_HANGUP;
+		}
+		if ((ops->options & AST_JUKEBOX_GREETING) == AST_JUKEBOX_GREETING) {
+			/* todo : play greeting */
+		}
+		if ((ops->options & AST_JUKEBOX_INSTRUCT) == AST_JUKEBOX_INSTRUCT) {
+			/* todo : play instructions */
+		}
+	}
+
+	for (;;) {
+		if ((ops->options & AST_JUKEBOX_NOIVR) == AST_JUKEBOX_NOIVR &&
+		    (ops->options & AST_JUKEBOX_RANDOM) != AST_JUKEBOX_NOIVR)
+			break;
+
+		if ((ops->options & AST_JUKEBOX_RANDOM) == AST_JUKEBOX_RANDOM) {
+			rc = play_random(ops);
+			if (rc == IVR_HANGUP)
+				return IVR_HANGUP;
+		}
+
+		if ((ops->options & AST_JUKEBOX_NOIVR) != AST_JUKEBOX_NOIVR) {
+			rc = browse_dir(ops, ops->musicdir);
+			if (rc == IVR_HANGUP)
+				return IVR_HANGUP;
+			if ((ops->options & AST_JUKEBOX_NOTIMEOUT) != AST_JUKEBOX_NOTIMEOUT)
+				return rc;
+
+			rc = play_random(ops);
+			if (rc == IVR_HANGUP)
+				return IVR_HANGUP;
+		}
+	}
+
+	return IVR_OK;
+}
+
+static int jukebox(struct jukebox_ops *ops)
+{
+	struct localuser *u;
+	struct ast_channel *chan;
+	char *status;
+
+	chan = ops->chan;
+
+	if (access(ops->musicdir, F_OK) != 0) {
+		ast_log(LOG_NOTICE, "Musicdir doesn't exist: %s\n", ops->musicdir);
+		return -1;
+	}
+
+	LOCAL_USER_ADD(u);
+
+	switch (jukebox_main(ops)) {
+	case IVR_HANGUP:  status = "HANGUP"; break;
+	case IVR_OK:      status = "OK"; break;
+	case IVR_MOVEUP:  status = "MOVEUP"; break;
+	case IVR_TIMEOUT: status = "TIMEOUT"; break;
+	default:          status = "UNKNOWN"; break;
+	}
+	pbx_builtin_setvar_helper(chan, "JUKESTATUS", status);
+	ast_log(LOG_DEBUG, "Exiting with JUKESTATUS=%s\n", status);
+
+	LOCAL_USER_REMOVE(u);
+
+	return 0;
+}
+
+static int jukebox_app_exec(struct ast_channel *chan, void *data)
+{
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(musicdir);
+		AST_APP_ARG(options);
+	);
+	char *parse;
+	struct ast_flags opts = { 0, };
+	char *opt_args[OPT_ARG_ARRAY_SIZE];
+	struct jukebox_ops ops;
+
+	memset(&ops, '\0', sizeof(ops));
+	ops.chan = chan;
+
+	if (ast_strlen_zero(data)) {
+		ops.musicdir = strdupa(jukop_defaultmusicdir);
+	} else {
+		parse = ast_strdupa(data);
+		AST_STANDARD_APP_ARGS(args, parse);
+
+		if (ast_strlen_zero(args.musicdir)) {
+			ast_log(LOG_WARNING, "Jukebox requires a music directory argument\n");
+			return -1;
+		}
+		if (!ast_strlen_zero(args.options)) {
+			if (ast_app_parse_options(jukebox_options, &opts, opt_args, args.options))
+				return -1;
+		}
+		if (strcasecmp(args.musicdir, "default") == 0)
+			ops.musicdir = "/var/lib/asterisk/music";
+		else
+			ops.musicdir = args.musicdir;
+		if (ast_test_flag(&opts, AST_JUKEBOX_RECACHE) && !ast_strlen_zero(opt_args[OPT_ARG_RECACHE])) {
+			ops.options |= AST_JUKEBOX_RECACHE;
+			ops.threads = strtol(opt_args[OPT_ARG_RECACHE], NULL, 10);
+		}
+		if (ast_test_flag(&opts, AST_JUKEBOX_SCALE) && !ast_strlen_zero(opt_args[OPT_ARG_SCALE])) {
+			ops.options |= AST_JUKEBOX_SCALE;
+			ops.scale = strtol(opt_args[OPT_ARG_SCALE], NULL, 10);
+		}
+		ops.options |= ast_test_flag(&opts, AST_JUKEBOX_HANGUP) ? AST_JUKEBOX_HANGUP : 0;
+		ops.options |= ast_test_flag(&opts, AST_JUKEBOX_GREETING) ? AST_JUKEBOX_GREETING : 0;
+		ops.options |= ast_test_flag(&opts, AST_JUKEBOX_INSTRUCT) ? AST_JUKEBOX_INSTRUCT : 0;
+		ops.options |= ast_test_flag(&opts, AST_JUKEBOX_RANDOM) ? AST_JUKEBOX_RANDOM : 0;
+		ops.options |= ast_test_flag(&opts, AST_JUKEBOX_NOTIMEOUT) ? AST_JUKEBOX_NOTIMEOUT : 0;
+		ops.options |= ast_test_flag(&opts, AST_JUKEBOX_NOIVR) ? AST_JUKEBOX_NOIVR : 0;
+	}
+
+	return jukebox(&ops);
+}
+
+static int load_config(void)
+{
+	char *s;
+	struct ast_config *conf;
+
+	conf = ast_config_load(JUKEBOX_CONFIG);
+	if (!conf) {
+		ast_log(LOG_NOTICE, "Failed to open " JUKEBOX_CONFIG ", using defaults\n");
+		snprintf(jukop_cachedir, sizeof(jukop_cachedir), "/var/cache/jukebox");
+		snprintf(jukop_text2wave, sizeof(jukop_text2wave), "text2wave");
+		snprintf(jukop_defaultmusicdir, sizeof(jukop_defaultmusicdir), "/var/lib/asterisk/music");
+	} else {
+		s = ast_variable_retrieve(conf, "jukebox", "cachedir");
+		if (s)
+			snprintf(jukop_cachedir, sizeof(jukop_cachedir), "%s", s);
+		else
+			snprintf(jukop_cachedir, sizeof(jukop_text2wave), "/var/cache/jukebox");
+		s = ast_variable_retrieve(conf, "jukebox", "text2wave");
+		if (s)
+			snprintf(jukop_text2wave, sizeof(jukop_text2wave), "%s", s);
+		else
+			snprintf(jukop_text2wave, sizeof(jukop_text2wave), "text2wave");
+		s = ast_variable_retrieve(conf, "jukebox", "defaultmusicdir");
+		if (s)
+			snprintf(jukop_defaultmusicdir, sizeof(jukop_defaultmusicdir), "%s", s);
+		else
+			snprintf(jukop_defaultmusicdir, sizeof(jukop_defaultmusicdir), "/var/lib/asterisk/music");
+		ast_config_destroy(conf);
+	}
+
+	if (access(jukop_cachedir, F_OK) != 0) {
+		if (mkdir(jukop_cachedir, 0755) != 0)
+			ast_log(LOG_WARNING, "Cachedir doesn't exist and failed to create: %s\n", jukop_cachedir);
+	}
+
+	return 0;
+}
+
+int load_module(void)
+{
+	int r;
+	srand(time(NULL));
+	r = load_config();
+	if (r)
+		return r;
+	return ast_register_application(app, jukebox_app_exec, synopsis, descrip);
+}
+
+int unload_module(void)
+{
+	int res;
+	res = ast_unregister_application(app);
+	STANDARD_HANGUP_LOCALUSERS;
+	return res;
+}
+
+int reload(void)
+{
+	return load_config();
+}
+
+char *description(void)
+{
+	return tdesc;
+}
+
+int usecount(void)
+{
+	int res;
+	STANDARD_USECOUNT(res);
+	return res;
+}
+
+char *key()
+{
+	return ASTERISK_GPL_KEY;
+}
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Silly application to play an MP3 file -- uses mpg123
+ *
+ * This application was modified by J.A. Roberts Tunney to allow volume
+ * adjustments on 2006-08-17.  These changes are also subject to the
+ * terms of the General Public License v2.
+ * 
+ * \ingroup applications
+ */
+ 
+#include <string.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/frame.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/options.h"
+#include <asterisk/app.h>
+
+#define LOCAL_MPG_123 "/usr/local/bin/mpg123"
+#define MPG_123 "/usr/bin/mpg123"
+
+static char *tdesc = "Silly MP3 Application";
+
+static char *app = "MP3Player2";
+
+static char *synopsis = "Play an MP3 file or stream";
+
+static char *descrip = 
+"  MP3Player2(location[|options]) Executes mpg123 to play the given location,\n"
+"which typically would be a filename or a URL. User can exit by pressing\n"
+"any key on the dialpad, or by hanging up.\n"
+"\n"
+"Options:\n"
+"\n"
+"  F(x) - Adjust scale factor to set -f option in mpg123.  Defaust is 8192\n"
+; 
+
+#define AST_MP3_SCALE (1 << 0)
+
+enum {
+	OPT_ARG_SCALE = 0,
+	/* note: this entry _MUST_ be the last one in the enum */
+	OPT_ARG_ARRAY_SIZE
+} mp3_option_args;
+
+AST_APP_OPTIONS(mp3_options, {
+	AST_APP_OPTION_ARG('F', AST_MP3_SCALE, OPT_ARG_SCALE),
+});
+
+STANDARD_LOCAL_USER;
+
+LOCAL_USER_DECL;
+
+static int mp3play(char *filename, int fd, int scale)
+{
+	int res;
+	int x;
+	char ss[32];
+	res = fork();
+	if (res < 0) 
+		ast_log(LOG_WARNING, "Fork failed\n");
+	if (res)
+		return res;
+	if (option_highpriority)
+		ast_set_priority(0);
+	dup2(fd, STDOUT_FILENO);
+	for (x=0;x<256;x++) {
+		if (x != STDOUT_FILENO)
+			close(x);
+	}
+	snprintf(ss, sizeof(ss), "%d", scale);
+	/* Execute mpg123, but buffer if it's a net connection */
+	if (!strncasecmp(filename, "http://", 7)) {
+		/* Most commonly installed in /usr/local/bin */
+		execl(LOCAL_MPG_123, "mpg123", "-q", "-s", "-b", "1024", "-f", ss, "--mono", "-r", "8000", filename, (char *)NULL);
+		/* But many places has it in /usr/bin */
+		execl(MPG_123, "mpg123", "-q", "-s", "-b", "1024","-f", ss, "--mono", "-r", "8000", filename, (char *)NULL);
+		/* As a last-ditch effort, try to use PATH */
+		execlp("mpg123", "mpg123", "-q", "-s", "-b", "1024",  "-f", ss, "--mono", "-r", "8000", filename, (char *)NULL);
+	}
+	else {
+		/* Most commonly installed in /usr/local/bin */
+		execl(MPG_123, "mpg123", "-q", "-s", "-f", ss, "--mono", "-r", "8000", filename, (char *)NULL);
+		/* But many places has it in /usr/bin */
+		execl(LOCAL_MPG_123, "mpg123", "-q", "-s", "-f", ss, "--mono", "-r", "8000", filename, (char *)NULL);
+		/* As a last-ditch effort, try to use PATH */
+		execlp("mpg123", "mpg123", "-q", "-s", "-f", ss, "--mono", "-r", "8000", filename, (char *)NULL);
+	}
+	ast_log(LOG_WARNING, "Execute of mpg123 failed\n");
+	return -1;
+}
+
+static int timed_read(int fd, void *data, int datalen, int timeout)
+{
+	int res;
+	struct pollfd fds[1];
+	fds[0].fd = fd;
+	fds[0].events = POLLIN;
+	res = poll(fds, 1, timeout);
+	if (res < 1) {
+		ast_log(LOG_NOTICE, "Poll timed out/errored out with %d\n", res);
+		return -1;
+	}
+	return read(fd, data, datalen);
+	
+}
+
+static int mp3_exec(struct ast_channel *chan, void *data)
+{
+	int res=0;
+	struct localuser *u;
+	int fds[2];
+	int ms = -1;
+	int pid = -1;
+	int owriteformat;
+	int timeout = 2000;
+	struct timeval next;
+	struct ast_frame *f;
+	struct myframe {
+		struct ast_frame f;
+		char offset[AST_FRIENDLY_OFFSET];
+		short frdata[160];
+	} myf;
+	int scale = 8192;
+
+	char *parse;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(file);
+		AST_APP_ARG(options);
+	);
+	struct ast_flags opts = { 0, };
+	char *opt_args[OPT_ARG_ARRAY_SIZE];
+	
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "MP3 Playback requires an argument (filename)\n");
+		return -1;
+	}
+	parse = ast_strdupa(data);
+	AST_STANDARD_APP_ARGS(args, parse);
+	if (ast_strlen_zero(args.file)) {
+		ast_log(LOG_WARNING, "MP3 Playback requires an argument (filename)\n");
+		return -1;
+	}
+	if (!ast_strlen_zero(args.options)) {
+		if (ast_app_parse_options(mp3_options, &opts, opt_args, args.options)) {
+			ast_log(LOG_WARNING, "ast_app_parse_options() failed!\n");
+			return -1;
+		}
+		if (ast_test_flag(&opts, AST_MP3_SCALE) && !ast_strlen_zero(opt_args[OPT_ARG_SCALE])) {
+			scale = strtol(opt_args[OPT_ARG_SCALE], NULL, 10);
+		}
+	}
+
+	LOCAL_USER_ADD(u);
+
+	if (pipe(fds)) {
+		ast_log(LOG_WARNING, "Unable to create pipe\n");
+		LOCAL_USER_REMOVE(u);
+		return -1;
+	}
+
+	ast_stopstream(chan);
+
+	owriteformat = chan->writeformat;
+	res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
+	if (res < 0) {
+		ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
+		LOCAL_USER_REMOVE(u);
+		return -1;
+	}
+	
+	res = mp3play(args.file, fds[1], scale);
+	if (!strncasecmp(args.file, "http://", 7)) {
+		timeout = 10000;
+	}
+	/* Wait 1000 ms first */
+	next = ast_tvnow();
+	next.tv_sec += 1;
+	if (res >= 0) {
+		pid = res;
+		/* Order is important -- there's almost always going to be mp3...  we want to prioritize the
+		   user */