changeset 0:aa5f022eac8a 1.49

Use upstream cplay-1.49 as a start
author markus schnalke <meillo@marmaro.de>
date Wed, 27 Sep 2017 09:22:32 +0200
parents
children 7db0dc9d32d3
files ChangeLog LICENSE Makefile README TODO cplay cplay.1 cplay.list cplayrc lircrc po/Makefile po/da.po po/de.po po/hu.po po/pl.po
diffstat 15 files changed, 4044 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ChangeLog	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,649 @@
+2003-12-05  Ulf Betlehem  <flu@iki.fi>
+
+	*** 1.49 released ***
+
+	* README, cplay.1: - document restricted mode
+
+2003-11-08  Ulf Betlehem  <flu@iki.fi>
+
+	*** 1.49pre4 ***
+
+	* cplay:
+	- restricted mode (suggested by Yoann AUBINEAU)
+	- connect player stdin to a pipe
+	- removed sleep(1) if player exec failed
+	- combined pause/unpause -> toggle_pause
+	- no parse_buf() if stopped or seeking
+	- removed --no-tty-control from madplay (stdin no longer tty)
+	- reduced codesize
+
+2003-11-06  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay:
+	- use 'm' for bookmarking instead of 'b'
+	- minor code clean-up
+	- modified help page
+
+2003-11-02  Ulf Betlehem  <flu@iki.fi>
+
+	*** 1.49pre3 ***
+
+	* cplay.list:
+	- ESP Package Manager support (http://www.easysw.com/epm/)
+
+	* cplay:
+	- removed excessive update() from get_bookmark()
+	- rewritten delete and move commands for speed
+
+2003-11-01  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay:
+	- move active status support from ListEntry to PlaylistEntry
+
+2003-10-04  Ulf Betlehem  <flu@iki.fi>
+
+	* cplayrc:
+	- removed execute permissions
+
+2003-10-01  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay:
+	- possible bugfix for increasing CPU usage
+
+2003-10-01  Ulf Betlehem  <flu@iki.fi>
+
+	*** 1.49pre2 ***
+	
+	* cplay:
+	- possible bugfix for increasing CPU usage
+
+2003-09-28  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay:
+	- use curses.KEY_ENTER for xwsh (wave++)
+
+2003-09-13  Ulf Betlehem  <flu@iki.fi>
+
+	*** 1.49pre1 ***
+	
+	* cplay:
+	- support and prefer ossaudiodev (dorphell)
+
+2003-09-01  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay:
+	- fixed playlist identification for 1.48 (Jean-Nicolas Kuttler)
+
+2003-08-28  Ulf Betlehem  <flu@iki.fi>
+
+	* Makefile:
+	- cplayrc generation
+
+	* cplay.1:
+	- execute both /etc/cplayrc and ~/.cplayrc
+	- ignore /etc/cplayrc if ~/.cplayrc exists
+	- speex
+	- xmp
+
+	* README:
+	- speex
+	- ~/.cplayrc
+
+2003-08-26  Ulf Betlehem  <flu@iki.fi>
+
+	*** 1.48 released ***
+
+	* cplay:
+	- xmp regexp (Yuri D'Elia)
+	- URL support in mpg123 regexp (Martin Michlmayr)
+	- rudimentary /etc/cplayrc support (Toni Timonen)
+
+	* cplay.1:
+	- xmp, play and cplayrc references
+
+	* cplayrc:
+	- new file
+
+2003-08-20  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay(1.48pre1):
+	- discontinue python1.5 support
+	- mixer/volume control using python-oss module
+	- horizontal scrolling with < and >
+	- show tail (was head) of long input lines
+	- import random instead of whrandom
+	- minor progress parsing modification
+	- NoOffsetPlayer simply counts seconds (Martin Michlmayr)
+	- TimeOffsetPlayer with full madplay support
+	- added partial xmp and play (sox) support
+
+2003-08-17  Ulf Betlehem  <flu@iki.fi>
+
+	* po/hu.po:
+	- new file (Gergely Nagy)
+	
+	* po/pl.po:
+	- new file (Perry)
+	- fixed help text not showing
+
+	* po/da.po:
+	- new file (Christian Storgaard)
+	- specified charset/encoding
+
+2003-05-13  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay:
+	- display "Adding tagged files" instead of a separate message for each file (Martin Michlmayr)
+	- avoid error-messages when interrupting cplay when started via xargs (Moshe Zadka)
+
+2003-04-13  Ulf Betlehem  <flu@iki.fi>
+
+	*** 1.47 released ***
+
+	* README:
+	- mp3info and ogginfo modules are both required
+
+	* TODO: *** empty log message ***
+
+	* cplay.1:
+	- mention help window
+	- shell command and positional parameters
+	- document control_fifo in FILES section
+	- BUGS section
+
+2003-04-11  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay(1.47rc4):
+	- removed "quit silently" command-line option (use Q instead)
+	- fixed missing ": " for isearch-prompt
+	- always add absolute paths to playlist (args and stdin)
+
+2003-04-10  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay(1.47rc3):
+	- uses glob.glob() instead of fnmatch.filter()
+
+2003-04-08  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay(1.47rc2):
+	- bugfix
+
+	* cplay(1.47rc1):
+	- status and title now use viewpoints (l)
+	- hide cursor after shell command
+	- help window updates
+
+2003-04-07  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay(1.47pre5):
+	- '!' shell command with positional args
+	- TAB completion
+	- kill word/line with C-w/C-u
+	- invert tags command 'i'
+	- removed hide partial pathnames feature
+	- renamed 'X' from [manual] to [stop]
+	- bookmarks
+	- fixed .. -> ../
+	- actually chdir in filelist
+	- fixed seek/stop/pause crash
+	- minor code cleanup
+
+2003-03-02  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay(1.47pre4):
+	- X toggles manual/automatic playlist advancement  (Väinö Järvelä)
+	- C-s C-s now remembers previous isearch string
+	- minor code cleanup here and there
+	- absolute seeking with C-a and ^ for bof, C-e and $ for eof
+	- HelpWindow includes "Undocumented commands"
+	- seeking now yield similar results when stopped and paused
+	- fixed byteorder issues with mixer on different architectures?
+
+2003-02-09  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay(1.47pre3):
+	- The "Quit? (y/N)" prompt no longer requires Enter.
+	- The number of dirs to hide can be adjusted with < and > for the
+	  pathname viewpoint. However, this might still change.
+	- Sorting is now done according to viewpoint, which means that 'S'
+	  no longer toggles sorting methods.
+	- Minor help window updates.
+
+2003-01-30  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay(1.47pre2):
+	- command line option to quit silently without confirmation
+	- isearch speedup (suggested by Eric Max Francis)
+	- viewpoint speedup
+
+2003-01-25  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay(1.47pre1):
+	- added os.path.exists check to get_tag()
+
+2002-12-16  Ulf Betlehem  <flu@iki.fi>
+
+	* lircrc:
+	- new file (Pugo)
+
+	* cplay:
+	- documented @ command
+	- get_tag improvement (Martin Michlmayr)
+
+	* cplay.1:
+	- combined v and V options into one.
+
+2002-12-16  Ulf Betlehem  <flu@iki.fi>
+
+	*** 1.46 released ***
+	
+	* cplay:
+	- documented @ command
+	- get_tag improvement (Martin Michlmayr)
+
+	* cplay.1:
+	- combined v and V options into one.
+
+2002-12-04  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay (1.46rc1):
+	- includes latest version of Martin's get_tag
+
+2002-11-30  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay (1.46pre9):
+	- alternative metadata support through get_tag (Martin Michlmayr)
+	- misc refactoring: TagListWindow, PlaylistEntry, etc.
+	- scrollable Help Window
+	- fixed keybinding for toggle counter mode
+	- new @ command that jumps to the active playlist entry
+	- removed V command and option, v toggles MASTER/PCM instead
+	- removed custom normpath
+
+2002-11-08  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay.1:
+	- Use minuses instead of hyphens for command line options. (Martin)
+
+2002-10-27  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay (1.46pre8)
+	- modified keymap!
+	- updated help window
+	- filelist tagging support (based on a patch by Jason M. Felice)
+	- improved status message behavior
+	- added retry if resize failed
+	- show cursor in input mode
+
+2002-10-24  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay (1.46pre7)
+	- a couple of status message changes
+	- faster delete when not in random mode
+	- rudimentary .pls playlist support
+	- improved streaming support
+	- advance playlist if player not found
+	- changed player priority order
+
+2002-10-21  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay (1.46pre6)
+	- new and improved random mode (Radu)
+
+2002-10-20  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay:
+	- refactoring
+	- list mode (l = toggle viewpoints)
+	- q = quit (y/n) and Q = Quit immediately
+	- isearch turnaround change
+	- input cursor position
+	- recursive search duplicates fix
+	- case insensitive regex marking
+	- regex marking matches viewpoint
+	- VALID_SONG regex matches basename
+	- playlist sorting by filename or pathname
+	- don't move empty list of marked entries
+	- SIGTERM -> SIGINT (again)
+	- updated mikmod switches
+
+2002-10-15  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay:
+	- pad input with space for cursor position
+
+2002-10-11  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay (1.46pre5)
+	- string.punctuation kludge for python 1.5
+	- recursive search in filelist!
+        - include 669|mtm|it in mikmod regex (Samium Gromoff)
+
+2002-08-28  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay (1.46pre4)
+	- bugfix
+
+2002-08-28  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay (1.46pre3)
+	- LIRC support via control FIFO (Pugo)
+
+2002-08-21  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay (1.46pre2)
+	- allow printable chars as input
+	- alias commands: Q for q and = for +
+	- grid bug removed from line number display
+	- keep current position after auto filelist updates
+	- quiet auto filelist updates (Martin Michlmayr)
+	- select child in filelist after a parent command
+	- parse player output only once every second
+	- PCM/MASTER volume commands show current volume
+
+	* LICENSE: new file
+
+2002-03-31  Ulf Betlehem  <flu@iki.fi>
+	
+	* cplay (1.46pre1)
+	- remember playlist filename (Patrice Neff)
+	
+2002-03-24  Ulf Betlehem  <flu@iki.fi>
+	
+	*** 1.45 released ***
+	
+2002-03-19  Ulf Betlehem  <flu@iki.fi>
+	
+	* cplay (1.45pre5):
+	- emulate insstr() for python1.5
+	- new commands m/M = move after/before
+	- new command D = delete current (Jay Felice)
+	- line numbers
+	
+2002-01-19  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay (1.45pre4):
+	- added options -v and -V to control either PCM or MASTER volume
+	- increase and decrease volume in steps of 3% (kludge)
+
+2002-01-13  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay (1.45pre3):
+	- progressbar cosmetics
+	- tilde expansion (Patrice Neff)
+	
+2001-12-27  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay (1.45pre2):
+	- added "--no-tty-control" option for madplay
+	- removed "-d oss" option from ogg123 (Han)
+	- use insstr instead of addstr to work around a classical curses-
+	  problem with writing the rightmost character without scrolling.
+
+2001-12-01  Ulf Betlehem  <flu@iki.fi>
+
+	*** 1.44 released ***
+	
+	* cplay:
+	- partial support for madplay
+	- partial support for mikmod (yason)
+	- removed sox support - unless someone needs it
+	- toggle counter mode: time done / time left
+	- seek acceleration based on song length
+	- avoid listing dot-files (Martin Michlmayr)
+	- remove ".." entry from root (Martin Michlmayr)
+	- show playlist upon startup if playing (Patrice Neff)
+	- removed TERMIOS warning with recent python versions
+	- add directories from command line (Han)
+	- fixed x-bug (Chris Liechti)
+	- changed write_playlist key from 'o' to 'w'
+	- changed goto command key from 'g' to 'o'
+	- added 'g' (home) and 'G' (end) keys
+	- added '/' and '?' keys for searching
+	- misc tweaks
+	
+	* cplay.1:
+	- update
+	
+	* README:
+	- update
+	
+2001-03-15  Ulf Betlehem  <flu@iki.fi>
+
+	*** 1.43 released ***
+	
+	* cplay:
+	- partial support for splay
+	- commandline arguments: repeat/random (Gerald Stieglbauer)
+	- volume fine tuning via +/- (Martin Michlmayr)
+	- simplified player framework
+	- mark/clear regexp
+	
+2001-01-18  Ulf Betlehem  <flu@iki.fi>
+
+	*** 1.42 released ***
+	
+	* cplay:
+	- ignore bogus gettext module
+	- correct devfs paths
+	- use seconds instead of frames
+	- shuffle speedup (Martin Persson)
+	- changed player hierarchy
+	- improved ogg123 support
+
+2000-12-08  Ulf Betlehem  <flu@iki.fi>
+
+	*** 1.41 released ***
+
+	* README: a few words about mpg123 and streaming
+	
+	* po/de.po, cplay.1: updated (Martin Michlmayr)
+
+	* po/Makefile, Makefile: use "install -c" for compatibility
+
+	* cplay:
+	  - autoplay initial playlist
+	  - is now a front-end for various audio players
+	  - ogg123 support (Martin Michlmayr)
+	  - devfs paths (Martin Michlmayr)
+	  - playlist url support (Charl P. Botha)
+	  - fixed signalling bug
+	  - minor code cleanup
+
+2000-10-19  Ulf Betlehem  <flu@iki.fi>
+
+	*** v1.40 released ***
+
+	* README: added instructions on how to change player options
+
+	* cplay: new versioning scheme
+	fixed locale setting
+	python 2.0 compatible
+	prefers standard gettext to fintl
+	delayed warnings for missing players and unknown fileformats
+	fixed hline with zero length in progressline
+	set title to xterm in cleanup
+	better support for mpg123 buffers by signalling progress groups
+
+	* README: modified usage
+
+	* Makefile: install man page
+
+	* cplay.1: man page (Martin Michlmayr)
+
+	* ChangeLog, TODO: new entry
+
+	* po/de.po: update (Martin Michlmayr)
+
+2000-09-06  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: Python 1.6 compatible
+
+2000-08-09  Ulf Betlehem  <flu@iki.fi>
+
+	* po/de.po: new file
+
+	* po/Makefile: new Makefile
+
+	* killpgmodule.c: *** empty log message ***
+
+	* README: new README
+
+	* Makefile: new Makefile
+
+2000-07-31  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: added i18n support by Martin Michlmayr
+	fixed locale support
+
+2000-07-25  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay:
+	added support for sox to play .wav, .au and other sound formats
+
+	* cplay: shows status in titlebar under X -- thanks Chmouel Boudjnah
+
+2000-05-23  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: doesn't stat() cwd when idle
+
+	* cplay: supports both pyncurses and the old cursesmodule
+
+2000-04-24  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: - restores terminal settings on exceptions
+	- global mp3 and m3u regexps
+	- new and improved keymap class
+	- removed a possible "division by zero" bug
+
+2000-03-24  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: translate evil characters to '?'
+
+2000-02-07  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: fixed a bug in FilelistWindow.add_dir()
+
+2000-01-20  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay:
+	- changed the player class so that one can hold down 'n' or 'p' when
+	  changing tracks without cplay crashing ;)
+
+2000-01-19  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: Enter now plays files bypassing the playlist
+	Space adds files to playlist
+	a adds recursively
+	z toggles pause
+	x toggles stop
+	m3u lines beginning with '#' are now silently discarded
+
+1999-12-22  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: - lot's of small changes
+
+1999-12-13  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: handles SIGWINCH correctly
+	automatically rereads current dir when modified
+	lot's of minor changes
+
+1999-10-26  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: Added two commands:
+	 R = random play order (keeps your playlist intact)
+	 S = sort playlist by filename
+
+	Removed a seldom used (also undocumented) command:
+	 N = previous track
+
+1999-05-10  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: catches os.error if os.listfiles() fails.
+
+1999-02-13  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: Added error-checking to prevent manipulating empty playlists.
+	Raised default seek speed from Pi to 4.
+
+1999-02-07  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: Corrected a feature that caused automatic loading of playlists
+	upon entering a directory where the cursor was over a .m3u file.
+
+1999-01-29  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay:
+	Uses frames_done and frames_left instead of time_done and time_left.
+	Minor code clean-up.
+
+	* cplay: Now supports at least mpg123 v0.59o through v0.59q
+
+1999-01-19  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: o  Is now "pure Python", which means there is no need for the
+	   killpgmodule.so anymore. Oh, joy!
+	o  mpg123 is now automatically located in the PATH if not specified absolutely.
+	o  Moved mark() to 'space' and pause_or_unpause() to 'p' and stop_or_unstop()
+	   to 'k'.
+	o  Playlists are now always saved with the extension .m3u.
+
+1998-12-11  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: now consumes anything written on stdout
+
+1998-11-29  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: select() now only timeouts when necessary.
+
+1998-11-20  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: added PlaylistWindow.command_mark_all()
+
+1998-11-12  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: SIGTERM -> SIGINT
+
+	* cplay: fixed sigchld bug
+	added help window
+
+1998-11-11  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: random -> whrandom
+
+	* cplay: Too many changes!
+	Reorganization
+	Change of policy
+
+1998-10-29  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: separated PLAYER and COMMAND
+	checks if the PLAYER is valid before it continues
+
+1998-10-27  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: kludged mixed case in curses constants
+
+1998-10-12  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: support for curses module versions with different key-case.
+
+1998-10-05  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: changed progress bar
+
+	* killpgmodule.c: New file.
+
+1998-04-29  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: remember bufptr of directories
+
+1998-04-20  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: code cleanup
+
+1998-04-18  Ulf Betlehem  <flu@iki.fi>
+
+	* cplay: New file.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LICENSE	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,340 @@
+		    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) <year>  <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) year 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.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,21 @@
+PREFIX = /usr/local
+ENV = PREFIX=$(PREFIX)
+
+SUBDIRS = po
+
+all: recursive-all
+
+install: recursive-install
+	install -c -m 755 cplay $(PREFIX)/bin
+	install -c -m 644 cplay.1 $(PREFIX)/man/man1
+
+clean: recursive-clean
+
+recursive-all recursive-install recursive-clean:
+	@target=$@; \
+	for i in $(SUBDIRS); do \
+		(cd $$i && make $(ENV) $${target#recursive-}); \
+	done
+
+cplayrc: cplay
+	awk '/^PLAYERS/ {p=1} /^$$/ {p=0} {if (p==1) {print}}' cplay > cplayrc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,59 @@
+Description:
+
+        cplay is a curses front-end for various audio players. It aims
+        to provide a power-user-friendly interface with simple filelist
+        and playlist control. cplay is written in Python and can use
+        either pyncurses or the standard curses module.
+
+Requirements:
+
+        - cplay                   http://www.tf.hut.fi/~flu/cplay/
+        - python                  http://www.python.org/
+        - python-oss  (optional)  http://net.indra.com/~tim/ossmodule/
+        - mpg321      (optional)  http://sourceforge.net/projects/mpg321/
+        - ogg123      (optional)  http://www.vorbis.com/
+        - mpg123      (optional)  http://www.mpg123.org/
+        - splay       (optional)  http://splay.sourceforge.net/
+        - madplay     (optional)  http://www.mars.org/home/rob/proj/mpeg/
+        - mikmod      (optional)  http://www.mikmod.org/
+        - fintl       (optional)  http://www.python.org/sigs/i18n-sig/
+        - pyncurses   (optional)  http://pyncurses.sourceforge.net/
+        - ID3-py      (optional)  http://id3-py.sourceforge.net/
+        - pyvorbis    (optional)  http://www.duke.edu/~ahc4/pyogg/
+        - xmp         (optional)  http://xmp.sf.net/
+        - sox         (optional)  http://sox.sf.net/
+        - speex       (optional)  http://www.speex.org/
+
+Installation:
+
+        make install
+
+Usage:
+
+        cplay [-nrRv] [ file | dir | playlist ] ...
+
+        When in doubt, press 'h' for a friendly help page.
+
+Configuration:
+
+        If you would like to change options passed to the actual players
+        just edit the PLAYERS list in the cplay script or put the PLAYERS
+        definition in either ~/.cplayrc or /etc/cplayrc. If one of these
+        files is available, it is executed by cplay before initialization.
+
+        Note, currently there is no version control for the rc-file!
+
+Miscellaneous:
+
+        In order for either mp3info (ID3) or ogginfo to work,
+        both corresponding python modules have to be installed.
+        This will probably change someday.
+
+        A playlist can contain URLs, but the playlist itself will
+        have to be local. For mpeg streaming, splay is recommended.
+
+        It is also possible to pipe a playlist to cplay, as stdin
+        will be reopened on startup unless it is attached to a tty.
+
+        Remote control via /var/tmp/cplay_control; see lircrc.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TODO	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,126 @@
+Keymap
+------------------------------------------
+Filelist: _b_def___________r____w_y_ ABCDEF_HI__LMNOP_RS__VWXYZ
+Playlist: ab__ef________o_________y_ ABC_EF_HI__L_NOP_____VW_YZ 
+Overload: ____________m_____s_______ __________________________
+
+TODO
+------------------------------------------
+x - external mixer framework (MIXERS list?)
+x - select player based on magic?
+1 - use 00:00:00 instead of 00h 00m 00s ?
+x - remove inc_volume and dec_volume when control fifo takes args
+4 - support wma (via mplayer?)
+3 - support musepack
+3 - support midi players
+3 - support flac (command line player?)
+3 - support xmms?
+x - replace current playlist (useful when streaming?)
+x - browse bookmarks?
+3 - use "file" to both get mime-type and bitrate info?
+2 - backspace does not work on gentoo?
+x - index-mode: group/sort/search by ctime, filename, metadata, etc.
+x - screen title patch
+x - tree view patch: expand 2 or more dirs mode for filelist?
+4 - don't require both ID3 and pyogg for one of them to work
+    - how to enable the metadata viewpoint?
+x - get_tag() is messy
+5 - don't try to write m3u to search results path
+4 - don't write dir/.m3u files for w + enter
+x - search results mode vs path
+x - shell from playlist? (requires new tab-completion?)
+3 - incremental filter command 'f' like *amp 'j'
+4 - optionally show all files in a dir
+2 - show alternatives in filelist for ambiguous completion?
+1 - :commands?
+x - macros / bind user commands
+3 - center current line (requires one-line scrolling)
+x - shell prompt could show number of args? (tagged entries)
+5 - change cwd for shell commands (crash if "search results"!?)
+x - embed search command in pathname to view in filelist? (bookmark results)
+    - /path/name?search=args or /path/name?index=args
+    - will allow bookmarks, except for refined searches?
+    - don't allow bookmarking in search results (mode)
+x - command-line history? (now cursor-up cancels)
+x - persistence: save state on exit?
+x - replace help window with generic show file window (move help -> file)
+x - shuffle & sort tagged entries only?
+x - searching for empty string not currently possible with /
+x - make isearch use regexes?
+x - sanity check: (pause or stopped) and seek => play?
+4 - add status message for unbound keys / commands
+x - change number of parent dirs to show
+    .. makes pathname viewpoint obsolete?
+x - search/tag regex/... on str(entry) or entry.vp() ?
+    .. currently sorting by vp, tagging by str and searching by str
+2 - wait 0.5s between subsequent next/prev. NOT before starting player!
+    .. or pause current player immediately on next/prev commands?
+    .. compare with repeating enter?
+3 - rc-file with
+    1. player configs
+    2. metadata viewpoint format
+    3. pathname viewpoint number of dirs to show (command also?)
+    4. persistent bookmarks
+4 - enter opens playlists as virtual dir in filelist
+5 + restricted mode (bang, write m3u, open path, soft chroot)
+4 - pls-playlist TITLE and LENGTH support
+3 - programmable delay between songs (useful for synchronized recording)
+1 - improve isearch using bookmarks for previous position?
+4 - error msg when following broken links (what?)
+4 - don't block on slow commands (eg. listdir / add dir / recursive search)
+2 - fast Esc (not possible with keypad?)
+2 - hide cursor after SUSP & CONT cycle
+4 - metadata info page
+4 - try small screen layout patch
+2 - check against old ogg.and.mp3.info.patch for missing features
+3 - use i to toggle *info window?
+5 - recursive search should save bufptr of prev dir
+3 - clean up app.player.is_stopped() and app.kludge mess
+2 - which -> WHICH (upcase global functions?)
+3 - howto select player for streaming http://ip:port urls?
+
+
+misc thoughts
+------------------------------------------
+- use & for delayed commands?
+- import on demand wrapper?
+- mark or delete duplicate or no-longer existing playlist entries
+- locate and jump to (show) current playlist entry in the filelist
+- fork and exec /bin/sh and select on pipe for command completion?
+- use C as a shortcut for untag/clear all?
+- suspend + resize + continue -> should resize
+- sort search results
+- dynamic help page?
+- better input support (readline?)
+- toggle header / small screen mode (treshold?)
+- randomly play albums (groups / hierarchical playlist)
+- delwin before/in/after resize?
+- different marks/tags for filelist and playlist?
+- dust off id3 patch?
+- dust off cddb patch?
+- filtered point of view (obsoleted by recursive searching?)
+- mark filelist entries already in playlist
+- alternative scrolling method
+- metadata editor?
+- reread dir => search for current entry
+- support alsaplayer, xmp, others?
+- support cd-players?
+- delayed play or mark as next command (play after current is finished)
+- use 'N' to mark as next?
+- reset progress at end of playlist? (or when changing song?)
+- restore xterm title (not possible with aterm, rxvt, etc?)
+- mpg123 gives jumpy progress info for vbr files (averaging filter?)
+- should there be another way to start playing the playlist
+  from the beginning than "next track"? (and Tab+Enter)
+- more vi-friendly keymap?
+- numerical arguments?
+- n and N could correspond to next and previous
+- f and b could select next and previous track?
+- a/A = Append mp3/dir
+- i/I = Insert mp3/dir
+- Meta-<, Meta-> commands? (avoid meta)
+- show progress-counter while streaming?
+- support slang?
+- fade in/out mode? (a la repeat/random)
+- crossfade mode
+- is Enter and cursor position logical / intuitive? (absolutely)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cplay	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,1655 @@
+#!/usr/bin/env python
+# -*- python -*-
+
+__version__ = "cplay 1.49"
+
+"""
+cplay - A curses front-end for various audio players
+Copyright (C) 1998-2003 Ulf Betlehem <flu@iki.fi>
+
+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.
+"""
+
+# ------------------------------------------
+from types import *
+
+import os
+import sys
+import time
+import getopt
+import signal
+import string
+import select
+import re
+
+try: from ncurses import curses
+except ImportError: import curses
+
+try: import tty
+except ImportError: tty = None
+
+try: import locale; locale.setlocale(locale.LC_ALL, "")
+except: pass
+
+# ------------------------------------------
+_locale_domain = "cplay"
+_locale_dir = "/usr/local/share/locale"
+
+try:
+    import gettext  # python 2.0
+    gettext.install(_locale_domain, _locale_dir)
+except ImportError:
+    try:
+        import fintl
+        fintl.bindtextdomain(_locale_domain, _locale_dir)
+        fintl.textdomain(_locale_domain)
+        _ = fintl.gettext
+    except ImportError:
+        def _(s): return s
+except:
+    def _(s): return s
+
+# ------------------------------------------
+XTERM = re.search("rxvt|xterm", os.environ["TERM"])
+CONTROL_FIFO = "/var/tmp/cplay_control"
+
+# ------------------------------------------
+def which(program):
+    for path in string.split(os.environ["PATH"], ":"):
+        if os.path.exists(os.path.join(path, program)):
+            return os.path.join(path, program)
+
+# ------------------------------------------
+def cut(s, n, left=0):
+    if left: return len(s) > n and "<%s" % s[-n+1:] or s
+    else: return len(s) > n and "%s>" % s[:n-1] or s
+
+# ------------------------------------------
+class Stack:
+    def __init__(self):
+        self.items = ()
+
+    def push(self, item):
+        self.items = (item,) + self.items
+
+    def pop(self):
+        self.items, item = self.items[1:], self.items[0]
+        return item
+
+# ------------------------------------------
+class KeymapStack(Stack):
+    def process(self, code):
+        for keymap in self.items:
+            if keymap and keymap.process(code):
+                break
+
+# ------------------------------------------
+class Keymap:
+    def __init__(self):
+        self.methods = [None] * curses.KEY_MAX
+
+    def bind(self, key, method, args=None):
+        if type(key) in (TupleType, ListType):
+            for i in key: self.bind(i, method, args)
+            return
+        if type(key) is StringType:
+            key = ord(key)
+        self.methods[key] = (method, args)
+
+    def process(self, key):
+        if self.methods[key] is None: return 0
+        method, args = self.methods[key]
+        if args is None:
+            apply(method, (key,))
+        else:
+            apply(method, args)
+        return 1
+
+# ------------------------------------------
+class Window:
+    chars = string.letters+string.digits+string.punctuation+string.whitespace
+
+    t = ['?'] * 256
+    for c in chars: t[ord(c)] = c
+    translationTable = string.join(t, ""); del t
+
+    def __init__(self, parent):
+        self.parent = parent
+        self.children = []
+        self.name = None
+        self.keymap = None
+        self.visible = 1
+        self.resize()
+        if parent: parent.children.append(self)
+
+    def insstr(self, s):
+        if not s: return
+        self.w.addstr(s[:-1])
+        self.w.hline(ord(s[-1]), 1)  # insch() work-around
+
+    def __getattr__(self, name):
+        return getattr(self.w, name)
+
+    def getmaxyx(self):
+        y, x = self.w.getmaxyx()
+        try: curses.version  # tested with 1.2 and 1.6
+        except AttributeError:
+            # pyncurses - emulate traditional (silly) behavior
+            y, x = y+1, x+1
+        return y, x
+
+    def touchwin(self):
+        try: self.w.touchwin()
+        except AttributeError: self.touchln(0, self.getmaxyx()[0])
+
+    def attron(self, attr):
+        try: self.w.attron(attr)
+        except AttributeError: self.w.attr_on(attr)
+
+    def attroff(self, attr):
+        try: self.w.attroff(attr)
+        except AttributeError: self.w.attr_off(attr)
+
+    def newwin(self):
+        return curses.newwin(0, 0, 0, 0)
+
+    def resize(self):
+        self.w = self.newwin()
+        self.ypos, self.xpos = self.getbegyx()
+        self.rows, self.cols = self.getmaxyx()
+        self.keypad(1)
+        self.leaveok(0)
+        self.scrollok(0)
+        for child in self.children:
+            child.resize()
+
+    def update(self):
+        self.clear()
+        self.refresh()
+        for child in self.children:
+            child.update()
+
+# ------------------------------------------
+class ProgressWindow(Window):
+    def __init__(self, parent):
+        Window.__init__(self, parent)
+        self.value = 0
+
+    def newwin(self):
+        return curses.newwin(1, self.parent.cols, self.parent.rows-2, 0)
+
+    def update(self):
+        self.move(0, 0)
+        self.hline(ord('-'), self.cols)
+        if self.value > 0:
+            self.move(0, 0)
+            x = int(self.value * self.cols)  # 0 to cols-1
+            x and self.hline(ord('='), x)
+            self.move(0, x)
+            self.insstr('|')
+        self.touchwin()
+        self.refresh()
+
+    def progress(self, value):
+        self.value = min(value, 0.99)
+        self.update()
+
+# ------------------------------------------
+class StatusWindow(Window):
+    def __init__(self, parent):
+        Window.__init__(self, parent)
+        self.default_message = ''
+        self.current_message = ''
+        self.tid = None
+
+    def newwin(self):
+        return curses.newwin(1, self.parent.cols-12, self.parent.rows-1, 0)
+
+    def update(self):
+        msg = string.translate(self.current_message, Window.translationTable)
+        self.move(0, 0)
+        self.clrtoeol()
+        self.insstr(cut(msg, self.cols))
+        self.touchwin()
+        self.refresh()
+
+    def status(self, message, duration = 0):
+        self.current_message = str(message)
+        if self.tid: app.timeout.remove(self.tid)
+        if duration: self.tid = app.timeout.add(duration, self.timeout)
+        else: self.tid = None
+        self.update()
+
+    def timeout(self):
+        self.tid = None
+        self.restore_default_status()
+
+    def set_default_status(self, message):
+        if self.current_message == self.default_message: self.status(message)
+        self.default_message = message
+        XTERM and sys.stderr.write("\033]0;%s\a" % (message or "cplay"))
+
+    def restore_default_status(self):
+        self.status(self.default_message)
+
+# ------------------------------------------
+class CounterWindow(Window):
+    def __init__(self, parent):
+        Window.__init__(self, parent)
+        self.values = [0, 0]
+        self.mode = 1
+
+    def newwin(self):
+        return curses.newwin(1, 11, self.parent.rows-1, self.parent.cols-11)
+
+    def update(self):
+        h, s = divmod(self.values[self.mode], 3600)
+        m, s = divmod(s, 60)
+        self.move(0, 0)
+        self.attron(curses.A_BOLD)
+        self.insstr("%02dh %02dm %02ds" % (h, m, s))
+        self.attroff(curses.A_BOLD)
+        self.touchwin()
+        self.refresh()
+
+    def counter(self, values):
+        self.values = values
+        self.update()
+
+    def toggle_mode(self):
+        self.mode = not self.mode
+        tmp = [_("elapsed"), _("remaining")][self.mode]
+        app.status(_("Counting %s time") % tmp, 1)
+        self.update()
+
+# ------------------------------------------
+class RootWindow(Window):
+    def __init__(self, parent):
+        Window.__init__(self, parent)
+        keymap = Keymap()
+        app.keymapstack.push(keymap)
+        self.win_progress = ProgressWindow(self)
+        self.win_status = StatusWindow(self)
+        self.win_counter = CounterWindow(self)
+        self.win_tab = TabWindow(self)
+        keymap.bind(12, self.update, ()) # C-l
+        keymap.bind([curses.KEY_LEFT, 2], app.seek, (-1, 1)) # C-b
+        keymap.bind([curses.KEY_RIGHT, 6], app.seek, (1, 1)) # C-f
+        keymap.bind([1, '^'], app.seek, (0, 0)) # C-a
+        keymap.bind([5, '$'], app.seek, (-1, 0)) # C-e
+        keymap.bind(range(48,58), app.key_volume) # 0123456789
+        keymap.bind(['+', '='], app.inc_volume, ())
+        keymap.bind('-', app.dec_volume, ())
+        keymap.bind('n', app.next_song, ())
+        keymap.bind('p', app.prev_song, ())
+        keymap.bind('z', app.toggle_pause, ())
+        keymap.bind('x', app.toggle_stop, ())
+        keymap.bind('c', self.win_counter.toggle_mode, ())
+        keymap.bind('Q', app.quit, ())
+        keymap.bind('q', self.command_quit, ())
+        keymap.bind('v', app.mixer, ("toggle",))
+
+    def command_quit(self):
+        app.do_input_hook = self.do_quit
+        app.start_input(_("Quit? (y/N)"))
+        
+    def do_quit(self, ch):
+        if chr(ch) == 'y': app.quit()
+        app.stop_input()
+
+# ------------------------------------------
+class TabWindow(Window):
+    def __init__(self, parent):
+        Window.__init__(self, parent)
+        self.active_child = 0
+
+        self.win_filelist = self.add(FilelistWindow)
+        self.win_playlist = self.add(PlaylistWindow)
+        self.win_help     = self.add(HelpWindow)
+
+        keymap = Keymap()
+        keymap.bind('\t', self.change_window, ()) # tab
+        keymap.bind('h', self.help, ())
+        app.keymapstack.push(keymap)
+        app.keymapstack.push(self.children[self.active_child].keymap)
+
+    def newwin(self):
+        return curses.newwin(self.parent.rows-2, self.parent.cols, 0, 0)
+
+    def update(self):
+        self.update_title()
+        self.move(1, 0)
+        self.hline(ord('-'), self.cols)
+        self.move(2, 0)
+        self.clrtobot()
+        self.refresh()
+        child = self.children[self.active_child]
+        child.visible = 1
+        child.update()
+
+    def update_title(self, refresh = 1):
+        child = self.children[self.active_child]
+        self.move(0, 0)
+        self.clrtoeol()
+        self.attron(curses.A_BOLD)
+        self.insstr(child.get_title())
+        self.attroff(curses.A_BOLD)
+        if refresh: self.refresh()
+
+    def add(self, Class):
+        win = Class(self)
+        win.visible = 0
+        return win
+
+    def change_window(self, window = None):
+        app.keymapstack.pop()
+        self.children[self.active_child].visible = 0
+        if window:
+            self.active_child = self.children.index(window)
+        else:
+            # toggle windows 0 and 1
+            self.active_child = not self.active_child
+        app.keymapstack.push(self.children[self.active_child].keymap)
+        self.update()
+
+    def help(self):
+        if self.children[self.active_child] == self.win_help:
+            self.change_window(self.win_last)
+        else:
+            self.win_last = self.children[self.active_child]
+            self.change_window(self.win_help)
+            app.status(__version__, 2)
+
+# ------------------------------------------
+class ListWindow(Window):
+    def __init__(self, parent):
+        Window.__init__(self, parent)
+        self.buffer = []
+        self.bufptr = self.scrptr = 0
+        self.search_direction = 0
+        self.last_search = ""
+        self.hoffset = 0
+        self.keymap = Keymap()
+        self.keymap.bind(['k', curses.KEY_UP, 16], self.cursor_move, (-1,))
+        self.keymap.bind(['j', curses.KEY_DOWN, 14], self.cursor_move, (1,))
+        self.keymap.bind(['K', curses.KEY_PPAGE], self.cursor_ppage, ())
+        self.keymap.bind(['J', curses.KEY_NPAGE], self.cursor_npage, ())
+        self.keymap.bind(['g', curses.KEY_HOME], self.cursor_home, ())
+        self.keymap.bind(['G', curses.KEY_END], self.cursor_end, ())
+        self.keymap.bind(['?', 18], self.start_search,
+                         (_("backward-isearch"), -1))
+        self.keymap.bind(['/', 19], self.start_search,
+                         (_("forward-isearch"), 1))
+        self.keymap.bind(['>'], self.hscroll, (8,))
+        self.keymap.bind(['<'], self.hscroll, (-8,))
+
+    def newwin(self):
+        return curses.newwin(self.parent.rows-2, self.parent.cols,
+                             self.parent.ypos+2, self.parent.xpos)
+
+    def update(self, force = 1):
+        self.bufptr = max(0, min(self.bufptr, len(self.buffer) - 1))
+        scrptr = (self.bufptr / self.rows) * self.rows
+        if force or self.scrptr != scrptr:
+            self.scrptr = scrptr
+            self.move(0, 0)
+            self.clrtobot()
+            i = 0
+            for entry in self.buffer[self.scrptr:]:
+                self.move(i, 0)
+                i = i + 1
+                self.putstr(entry)
+                if self.getyx()[0] == self.rows - 1: break
+            if self.visible:
+                self.refresh()
+                self.parent.update_title()
+        self.update_line(curses.A_REVERSE)
+
+    def update_line(self, attr = None, refresh = 1):
+        if not self.buffer: return
+        ypos = self.bufptr - self.scrptr
+        if attr: self.attron(attr)
+        self.move(ypos, 0)
+        self.hline(ord(' '), self.cols)
+        self.putstr(self.current())
+        if attr: self.attroff(attr)
+        if self.visible and refresh: self.refresh()
+
+    def get_title(self, data=""):
+        pos = "%s-%s/%s" % (self.scrptr+min(1, len(self.buffer)),
+                            min(self.scrptr+self.rows, len(self.buffer)),
+                            len(self.buffer))
+        width = self.cols-len(pos)-2
+        data = cut(data, width-len(self.name), 1)
+        return "%-*s  %s" % (width, cut(self.name+data, width), pos)
+
+    def putstr(self, entry, *pos):
+        s = string.translate(str(entry), Window.translationTable)
+        pos and apply(self.move, pos)
+        if self.hoffset: s = "<%s" % s[self.hoffset+1:]
+        self.insstr(cut(s, self.cols))
+
+    def current(self):
+        if self.bufptr >= len(self.buffer): self.bufptr = len(self.buffer) - 1
+        return self.buffer[self.bufptr]
+
+    def cursor_move(self, ydiff):
+        if app.input_mode: app.cancel_input()
+        if not self.buffer: return
+        self.update_line(refresh = 0)
+        self.bufptr = (self.bufptr + ydiff) % len(self.buffer)
+        self.update(force = 0)
+
+    def cursor_ppage(self):
+        tmp = self.bufptr % self.rows
+        if tmp == self.bufptr:
+            self.cursor_move(-(tmp+(len(self.buffer) % self.rows) or self.rows))
+        else:
+            self.cursor_move(-(tmp+self.rows))
+
+    def cursor_npage(self):
+        tmp = self.rows - self.bufptr % self.rows
+        if self.bufptr + tmp > len(self.buffer):
+            self.cursor_move(len(self.buffer) - self.bufptr)
+        else:
+            self.cursor_move(tmp)
+
+    def cursor_home(self): self.cursor_move(-self.bufptr)
+
+    def cursor_end(self): self.cursor_move(-self.bufptr - 1)
+
+    def start_search(self, type, direction):
+        self.search_direction = direction
+        self.not_found = 0
+        if app.input_mode:
+            app.input_prompt = "%s: " % type
+            self.do_search(advance = direction)
+        else:
+            app.do_input_hook = self.do_search
+            app.stop_input_hook = self.stop_search
+            app.start_input(type)
+
+    def stop_search(self):
+        self.last_search = app.input_string
+        app.status(_("ok"), 1)
+
+    def do_search(self, ch = None, advance = 0):
+        if ch in [8, 127]: app.input_string = app.input_string[:-1]
+        elif ch: app.input_string = "%s%c" % (app.input_string, ch)
+        else: app.input_string = app.input_string or self.last_search
+        index = self.bufptr + advance
+        while 1:
+            if not 0 <= index < len(self.buffer):
+                app.status(_("Not found: %s ") % app.input_string)
+                self.not_found = 1
+                break
+            line = string.lower(str(self.buffer[index]))
+            if string.find(line, string.lower(app.input_string)) != -1:
+                app.show_input()
+                self.update_line(refresh = 0)
+                self.bufptr = index
+                self.update(force = 0)
+                self.not_found = 0
+                break
+            if self.not_found:
+                app.status(_("Not found: %s ") % app.input_string)
+                break
+            index = index + self.search_direction
+
+    def hscroll(self, value):
+        self.hoffset = max(0, self.hoffset + value)
+        self.update()
+
+# ------------------------------------------
+class HelpWindow(ListWindow):
+    def __init__(self, parent):
+        ListWindow.__init__(self, parent)
+        self.name = _("Help")
+        self.keymap.bind('q', self.parent.help, ())
+        self.buffer = string.split(_("""\
+  Global                               t, T  : tag current/regex
+  ------                               u, U  : untag current/regex
+  Up, Down, k, j, C-p, C-n,            Sp, i : invert current/all
+  PgUp, PgDn, K, J,                    !     : shell ($@ = tagged or current)
+  Home, End, g, G : movement
+  Enter           : chdir or play      Filelist
+  Tab             : filelist/playlist  --------
+  n, p            : next/prev track    a     : add (tagged) to playlist
+  z, x            : toggle pause/stop  s     : recursive search
+                                       BS, o : goto parent/specified dir
+  Left, Right,                         m, '  : set/get bookmark
+  C-f, C-b    : seek forward/backward  
+  C-a, C-e    : restart/end track      Playlist
+  C-s, C-r, / : isearch                --------
+  C-g, Esc    : cancel                 d, D  : delete (tagged) tracks/playlist
+  1..9, +, -  : volume control         m, M  : move tagged tracks after/before
+  c, v        : counter/volume mode    r, R  : toggle repeat/Random mode
+  <, >        : horizontal scrolling   s, S  : shuffle/Sort playlist
+  C-l, l      : refresh, list mode     w, @  : write playlist, jump to active
+  h, q, Q     : help, quit?, Quit!     X     : stop playlist after each track
+"""), "\n")
+
+# ------------------------------------------
+class ListEntry:
+    def __init__(self, pathname, dir=0):
+        self.filename = os.path.basename(pathname)
+        self.pathname = pathname
+        self.slash = dir and "/" or ""
+        self.tagged = 0
+
+    def set_tagged(self, value):
+        self.tagged = value
+
+    def is_tagged(self):
+        return self.tagged == 1
+
+    def __str__(self):
+        mark = self.is_tagged() and "#" or " "
+        return "%s %s%s" % (mark, self.vp(), self.slash)
+
+    def vp(self):
+        return self.vps[0][1](self)
+
+    def vp_filename(self):
+        return self.filename or self.pathname
+
+    def vp_pathname(self):
+        return self.pathname
+
+    vps = [[_("filename"), vp_filename],
+           [_("pathname"), vp_pathname]]
+
+# ------------------------------------------
+class PlaylistEntry(ListEntry):
+    def __init__(self, pathname):
+        ListEntry.__init__(self, pathname)
+        self.metadata = None
+        self.active = 0
+
+    def set_active(self, value):
+        self.active = value
+
+    def is_active(self):
+        return self.active == 1
+
+    def vp_metadata(self):
+        return self.metadata or self.read_metadata()
+
+    def read_metadata(self):
+        self.metadata = get_tag(self.pathname)
+        return self.metadata
+
+    vps = ListEntry.vps[:]
+
+# ------------------------------------------
+class TagListWindow(ListWindow):
+    def __init__(self, parent):
+        ListWindow.__init__(self, parent)
+        self.keymap.bind(' ', self.command_tag_untag, ())
+        self.keymap.bind('i', self.command_invert_tags, ())
+        self.keymap.bind('t', self.command_tag, (1,))
+        self.keymap.bind('u', self.command_tag, (0,))
+        self.keymap.bind('T', self.command_tag_regexp, (1,))
+        self.keymap.bind('U', self.command_tag_regexp, (0,))
+        self.keymap.bind('l', self.command_change_viewpoint, ())
+
+    def command_change_viewpoint(self, klass=ListEntry):
+        klass.vps.append(klass.vps.pop(0))
+        app.status(_("Listing %s") % klass.vps[0][0], 1)
+        app.player.update_status()
+        self.update()
+
+    def command_invert_tags(self):
+        for i in self.buffer:
+            i.set_tagged(not i.is_tagged())
+        self.update()
+
+    def command_tag_untag(self):
+        if not self.buffer: return
+        tmp = self.buffer[self.bufptr]
+        tmp.set_tagged(not tmp.is_tagged())
+        self.cursor_move(1)
+
+    def command_tag(self, value):
+        if not self.buffer: return
+        self.buffer[self.bufptr].set_tagged(value)
+        self.cursor_move(1)
+
+    def command_tag_regexp(self, value):
+        self.tag_value = value
+        app.stop_input_hook = self.stop_tag_regexp
+        app.start_input(value and _("Tag regexp") or _("Untag regexp"))
+
+    def stop_tag_regexp(self):
+        try:
+            r = re.compile(app.input_string, re.I)
+            for entry in self.buffer:
+                if r.search(str(entry)):
+                    entry.set_tagged(self.tag_value)
+            self.update()
+            app.status(_("ok"), 1)
+        except re.error, e:
+            app.status(e, 2)
+
+    def get_tagged(self):
+        return filter(lambda x: x.is_tagged(), self.buffer)
+
+    def not_tagged(self, l):
+        return filter(lambda x: not x.is_tagged(), l)
+
+# ------------------------------------------
+class FilelistWindow(TagListWindow):
+    def __init__(self, parent):
+        TagListWindow.__init__(self, parent)
+        self.oldposition = {}
+        try: self.chdir(os.getcwd())
+        except OSError: self.chdir(os.environ['HOME'])
+        self.startdir = self.cwd
+        self.mtime_when = 0
+        self.mtime = None
+        self.keymap.bind(['\n', curses.KEY_ENTER],
+                         self.command_chdir_or_play, ())
+        self.keymap.bind(['.', curses.KEY_BACKSPACE],
+                         self.command_chparentdir, ())
+        self.keymap.bind('a', self.command_add_recursively, ())
+        self.keymap.bind('o', self.command_goto, ())
+        self.keymap.bind('s', self.command_search_recursively, ())
+        self.keymap.bind('m', self.command_set_bookmark, ())
+        self.keymap.bind("'", self.command_get_bookmark, ())
+        self.keymap.bind('!', self.command_shell, ())
+        self.bookmarks = { 39: [self.cwd, 0] }
+
+    def command_shell(self):
+        if app.restricted: return
+        app.stop_input_hook = self.stop_shell
+        app.complete_input_hook = self.complete_shell
+        app.start_input(_("shell$ "), colon=0)
+
+    def stop_shell(self):
+        s = app.input_string
+        curses.endwin()
+        sys.stderr.write("\n")
+        argv = map(lambda x: x.pathname, self.get_tagged() or [self.current()])
+        argv = ["/bin/sh", "-c", s, "--"] + argv
+        pid = os.fork()
+        if pid == 0:
+            try: os.execv(argv[0], argv)
+            except: os._exit(1)
+        pid, r = os.waitpid(pid, 0)
+        sys.stderr.write("\nshell returned %s, press return!\n" % r)
+        sys.stdin.readline()
+        app.win_root.update()
+        app.restore_default_status()
+        app.cursor(0)
+
+    def complete_shell(self, line):
+        return self.complete_generic(line, quote=1)
+
+    def complete_generic(self, line, quote=0):
+        import glob
+        if quote:
+            s = re.sub('.*[^\\\\][ \'"()\[\]{}$`]', '', line)
+            s, part = re.sub('\\\\', '', s), line[:len(line)-len(s)]
+        else:
+            s, part = line, ""
+        results = glob.glob(os.path.expanduser(s)+"*")
+        if len(results) == 0:
+            return line
+        if len(results) == 1:
+            lm = results[0]
+            lm = lm + (os.path.isdir(lm) and "/" or "")
+        else:
+            lm = results[0]
+            for result in results:
+                for i in range(min(len(result), len(lm))):
+                   if result[i] != lm[i]:
+                        lm = lm[:i]
+                        break
+        if quote: lm = re.sub('([ \'"()\[\]{}$`])', '\\\\\\1', lm)
+        return part + lm
+
+    def command_get_bookmark(self):
+        app.do_input_hook = self.do_get_bookmark
+        app.start_input(_("bookmark"))
+
+    def do_get_bookmark(self, ch):
+        app.input_string = ch
+        bookmark = self.bookmarks.get(ch)
+        if bookmark:
+            self.bookmarks[39] = [self.cwd, self.bufptr]
+            dir, pos = bookmark
+            self.chdir(dir)
+            self.listdir()
+            self.bufptr = pos
+            self.update()
+            app.status(_("ok"), 1)
+        else:
+            app.status(_("Not found!"), 1)
+        app.stop_input()
+
+    def command_set_bookmark(self):
+        app.do_input_hook = self.do_set_bookmark
+        app.start_input(_("set bookmark"))
+        
+    def do_set_bookmark(self, ch):
+        app.input_string = ch
+        self.bookmarks[ch] = [self.cwd, self.bufptr]
+        ch and app.status(_("ok"), 1) or app.stop_input()
+
+    def command_search_recursively(self):
+        app.stop_input_hook = self.stop_search_recursively
+        app.start_input(_("search"))
+
+    def stop_search_recursively(self):
+        try: re_tmp = re.compile(app.input_string, re.I)
+        except re.error, e:
+            app.status(e, 2)
+            return
+        app.status(_("Searching..."))
+        results = []
+        for entry in self.buffer:
+            if entry.filename == "..":
+                continue
+            if re_tmp.search(entry.filename):
+                results.append(entry)
+            elif os.path.isdir(entry.pathname):
+                try: self.search_recursively(re_tmp, entry.pathname, results)
+                except: pass
+        if not self.search_mode:
+            self.chdir(os.path.join(self.cwd,_("search results")))
+            self.search_mode = 1
+        self.buffer = results
+        self.bufptr = 0
+        self.parent.update_title()
+        self.update()
+        app.restore_default_status()
+
+    def search_recursively(self, re_tmp, dir, results):
+        for filename in os.listdir(dir):
+            pathname = os.path.join(dir, filename)
+            if re_tmp.search(filename):
+                if os.path.isdir(pathname):
+                    results.append(ListEntry(pathname, 1))
+                elif VALID_PLAYLIST(filename) or VALID_SONG(filename):
+                    results.append(ListEntry(pathname))
+            elif os.path.isdir(pathname):
+                self.search_recursively(re_tmp, pathname, results)
+
+    def get_title(self):
+        self.name = _("Filelist: ")
+        return ListWindow.get_title(self, re.sub("/?$", "/", self.cwd))
+
+    def listdir_maybe(self, now=0):
+        if now < self.mtime_when+2: return
+        self.mtime_when = now
+        self.oldposition[self.cwd] = self.bufptr
+        try: self.mtime == os.stat(self.cwd)[8] or self.listdir(quiet=1)
+        except os.error: pass
+
+    def listdir(self, quiet=0, prevdir=None):
+        quiet or app.status(_("Reading directory..."))
+        self.search_mode = 0
+        dirs = []
+        files = []
+        try:
+            self.mtime = os.stat(self.cwd)[8]
+            self.mtime_when = time.time()
+            filenames = os.listdir(self.cwd)
+            filenames.sort()
+            for filename in filenames:
+                if filename[0] == ".": continue
+                pathname = os.path.join(self.cwd, filename)
+                if os.path.isdir(pathname): dirs.append(pathname)
+                elif VALID_SONG(filename): files.append(pathname)
+                elif VALID_PLAYLIST(filename): files.append(pathname)
+        except os.error: pass
+        dots = ListEntry(os.path.join(self.cwd, ".."), 1)
+        self.buffer = [[dots], []][self.cwd == "/"]
+        for i in dirs: self.buffer.append(ListEntry(i, 1))
+        for i in files: self.buffer.append(ListEntry(i))
+        if prevdir:
+            for self.bufptr in range(len(self.buffer)):
+                if self.buffer[self.bufptr].filename == prevdir: break
+            else: self.bufptr = 0
+        elif self.oldposition.has_key(self.cwd):
+            self.bufptr = self.oldposition[self.cwd]
+        else: self.bufptr = 0
+        self.parent.update_title()
+        self.update()
+        quiet or app.restore_default_status()
+
+    def chdir(self, dir):
+        if hasattr(self, "cwd"): self.oldposition[self.cwd] = self.bufptr
+        self.cwd = os.path.normpath(dir)
+        try: os.chdir(self.cwd)
+        except: pass
+
+    def command_chdir_or_play(self):
+        if not self.buffer: return
+        if self.current().filename == "..":
+            self.command_chparentdir()
+        elif os.path.isdir(self.current().pathname):
+            self.chdir(self.current().pathname)
+            self.listdir()
+        elif VALID_SONG(self.current().filename):
+            app.play(self.current())
+
+    def command_chparentdir(self):
+        if app.restricted and self.cwd == self.startdir: return
+        dir = os.path.basename(self.cwd)
+        self.chdir(os.path.dirname(self.cwd))
+        self.listdir(prevdir=dir)
+
+    def command_goto(self):
+        if app.restricted: return
+        app.stop_input_hook = self.stop_goto
+        app.complete_input_hook = self.complete_generic
+        app.start_input(_("goto"))
+
+    def stop_goto(self):
+        dir = os.path.expanduser(app.input_string)
+        if dir[0] != '/': dir = os.path.join(self.cwd, dir)
+        if not os.path.isdir(dir):
+            app.status(_("Not a directory!"), 1)
+            return
+        self.chdir(dir)
+        self.listdir()
+
+    def command_add_recursively(self):
+        l = self.get_tagged()
+        if not l:
+            app.win_playlist.add(self.current().pathname)
+            self.cursor_move(1)
+            return
+        app.status(_("Adding tagged files"), 1)
+        for entry in l:
+            app.win_playlist.add(entry.pathname, quiet=1)
+            entry.set_tagged(0)
+        self.update()
+
+# ------------------------------------------
+class PlaylistWindow(TagListWindow):
+    def __init__(self, parent):
+        TagListWindow.__init__(self, parent)
+        self.pathname = None
+        self.repeat = 0
+        self.random = 0
+        self.random_prev = []
+        self.random_next = []
+        self.random_left = []
+        self.stop = 0
+        self.keymap.bind(['\n', curses.KEY_ENTER],
+                         self.command_play, ())
+        self.keymap.bind('d', self.command_delete, ())
+        self.keymap.bind('D', self.command_delete_all, ())
+        self.keymap.bind('m', self.command_move, (1,))
+        self.keymap.bind('M', self.command_move, (0,))
+        self.keymap.bind('s', self.command_shuffle, ())
+        self.keymap.bind('S', self.command_sort, ())
+        self.keymap.bind('r', self.command_toggle_repeat, ())
+        self.keymap.bind('R', self.command_toggle_random, ())
+        self.keymap.bind('X', self.command_toggle_stop, ())
+        self.keymap.bind('w', self.command_save_playlist, ())
+        self.keymap.bind('@', self.command_jump_to_active, ())
+
+    def command_change_viewpoint(self, klass=PlaylistEntry):
+        if not globals().get("ID3"):
+            try:
+                global ID3, ogg, codecs
+                import ID3, ogg.vorbis, codecs
+                klass.vps.append([_("metadata"), klass.vp_metadata])
+            except ImportError: pass
+        TagListWindow.command_change_viewpoint(self, klass)
+
+    def get_title(self):
+        space_out = lambda value, s: value and s or " "*len(s)
+        self.name = _("Playlist %s %s %s") % (
+            space_out(self.repeat, _("[repeat]")),
+            space_out(self.random, _("[random]")),
+            space_out(self.stop, _("[stop]")))
+        return ListWindow.get_title(self)
+
+    def append(self, item):
+        self.buffer.append(item)
+        if self.random: self.random_left.append(item)
+
+    def add_dir(self, dir):
+        filenames = os.listdir(dir)
+        filenames.sort()
+        subdirs = []
+        for filename in filenames:
+            pathname = os.path.join(dir, filename)
+            if VALID_SONG(filename):
+                self.append(PlaylistEntry(pathname))
+            if os.path.isdir(pathname):
+                subdirs.append(pathname)
+        map(self.add_dir, subdirs)
+
+    def add_m3u(self, line):
+        if re.match("^(#.*)?$", line): return
+        if re.match("^(/|http://)", line):
+            self.append(PlaylistEntry(self.fix_url(line)))
+        else:
+            dirname = os.path.dirname(self.pathname)
+            self.append(PlaylistEntry(os.path.join(dirname, line)))
+
+    def add_pls(self, line):
+        # todo - support title & length
+        m = re.match("File(\d+)=(.*)", line)
+        if m: self.append(PlaylistEntry(self.fix_url(m.group(2))))
+
+    def add_playlist(self, pathname):
+        self.pathname = pathname
+        if re.search("\.m3u$", pathname, re.I): f = self.add_m3u
+        if re.search("\.pls$", pathname, re.I): f = self.add_pls
+        file = open(pathname)
+        map(f, map(string.strip, file.readlines()))
+        file.close()
+    
+    def add(self, pathname, quiet=0):
+        try:
+            if os.path.isdir(pathname):
+                app.status(_("Working..."))
+                self.add_dir(pathname)
+            elif VALID_PLAYLIST(pathname):
+                self.add_playlist(pathname)
+            else:
+                pathname = self.fix_url(pathname)
+                self.append(PlaylistEntry(pathname))
+            # todo - refactor
+            filename = os.path.basename(pathname) or pathname
+            quiet or app.status(_("Added: %s") % filename, 1)
+        except Exception, e:
+            app.status(e, 2)
+
+    def fix_url(self, url):
+        return re.sub("(http://[^/]+)/?(.*)", "\\1/\\2", url)
+
+    def putstr(self, entry, *pos):
+        if entry.is_active(): self.attron(curses.A_BOLD)
+        apply(ListWindow.putstr, (self, entry) + pos)
+        if entry.is_active(): self.attroff(curses.A_BOLD)
+
+    def change_active_entry(self, direction):
+        if not self.buffer: return
+        old = self.get_active_entry()
+        new = None
+        if self.random:
+            if direction > 0:
+                if self.random_next: new = self.random_next.pop()
+                elif self.random_left: pass
+                elif self.repeat: self.random_left = self.buffer[:]
+                else: return
+                if not new:
+                    import random
+                    new = random.choice(self.random_left)
+                    self.random_left.remove(new)
+                try: self.random_prev.remove(new)
+                except ValueError: pass
+                self.random_prev.append(new)
+            else:
+                if len(self.random_prev) > 1:
+                    self.random_next.append(self.random_prev.pop())
+                    new = self.random_prev[-1]
+                else: return
+            old and old.set_active(0)
+        elif old:
+            index = self.buffer.index(old)+direction
+            if not (0 <= index < len(self.buffer) or self.repeat): return
+            old.set_active(0)
+            new = self.buffer[index % len(self.buffer)]
+        else:
+            new = self.buffer[0]
+        new.set_active(1)
+        self.update()
+        return new
+
+    def get_active_entry(self):
+        for entry in self.buffer:
+            if entry.is_active(): return entry
+
+    def command_jump_to_active(self):
+        entry = self.get_active_entry()
+        if not entry: return
+        self.bufptr = self.buffer.index(entry)
+        self.update()
+
+    def command_play(self):
+        if not self.buffer: return
+        entry = self.get_active_entry()
+        entry and entry.set_active(0)
+        entry = self.current()
+        entry.set_active(1)
+        self.update()
+        app.play(entry)
+
+    def command_delete(self):
+        if not self.buffer: return
+        current_entry, n = self.current(), len(self.buffer)
+        self.buffer = self.not_tagged(self.buffer)
+        if n > len(self.buffer):
+            try: self.bufptr = self.buffer.index(current_entry)
+            except ValueError: pass
+        else:
+            current_entry.set_tagged(1)
+            del self.buffer[self.bufptr]
+        if self.random:
+            self.random_prev = self.not_tagged(self.random_prev)
+            self.random_next = self.not_tagged(self.random_next)
+            self.random_left = self.not_tagged(self.random_left)
+        self.update()
+
+    def command_delete_all(self):
+        self.buffer = []
+        self.random_prev = []
+        self.random_next = []
+        self.random_left = []
+        app.status(_("Deleted playlist"), 1)
+        self.update()
+
+    def command_move(self, after):
+        if not self.buffer: return
+        current_entry, l = self.current(), self.get_tagged()
+        if not l or current_entry.is_tagged(): return
+        self.buffer = self.not_tagged(self.buffer)
+        self.bufptr = self.buffer.index(current_entry)+after
+        self.buffer[self.bufptr:self.bufptr] = l
+        self.update()
+
+    def command_shuffle(self):
+        import random
+        l = []
+        n = len(self.buffer)
+        while n > 0:
+            n = n-1
+            r = random.randint(0, n)
+            l.append(self.buffer[r])
+            del self.buffer[r]
+        self.buffer = l
+        self.bufptr = 0
+        self.update()
+        app.status(_("Shuffled playlist... Oops?"), 1)
+
+    def command_sort(self):
+        app.status(_("Working..."))
+        self.buffer.sort(lambda x, y: x.vp() > y.vp() or -1)
+        self.bufptr = 0
+        self.update()
+        app.status(_("Sorted playlist"), 1)
+
+    def command_toggle_repeat(self):
+        self.toggle("repeat", _("Repeat: %s"))
+
+    def command_toggle_random(self):
+        self.toggle("random", _("Random: %s"))
+        self.random_prev = []
+        self.random_next = []
+        self.random_left = self.buffer[:]
+
+    def command_toggle_stop(self):
+        self.toggle("stop", _("Stop playlist: %s"))
+
+    def toggle(self, attr, format):
+        setattr(self, attr, not getattr(self, attr))
+        app.status(format % (getattr(self, attr) and _("on") or _("off")), 1)
+        self.parent.update_title()
+
+    def command_save_playlist(self):
+        if app.restricted: return
+        default = self.pathname or "%s/" % app.win_filelist.cwd
+        app.stop_input_hook = self.stop_save_playlist
+        app.start_input(_("Save playlist"), default)
+
+    def stop_save_playlist(self):
+        pathname = app.input_string
+        if pathname[0] != '/':
+            pathname = os.path.join(app.win_filelist.cwd, pathname)
+        if not re.search("\.m3u$", pathname, re.I):
+            pathname = "%s%s" % (pathname, ".m3u")
+        try:
+            file = open(pathname, "w")
+            for entry in self.buffer:
+                file.write("%s\n" % entry.pathname)
+            file.close()
+            self.pathname = pathname
+            app.status(_("ok"), 1)
+        except IOError, e:
+            app.status(e, 2)
+
+# ------------------------------------------
+def get_tag(pathname):
+    if re.compile("^http://").match(pathname) or not os.path.exists(pathname):
+        return pathname
+    tags = {}
+    # FIXME: use magic instead of file extensions to identify OGGs and MP3s
+    if re.compile(".*\.ogg$", re.I).match(pathname):
+        try:
+            vf = ogg.vorbis.VorbisFile(pathname)
+            vc = vf.comment()
+            tags = vc.as_dict()
+        except NameError: pass
+        except (IOError, UnicodeError): return os.path.basename(pathname)
+    elif re.compile(".*\.mp3$", re.I).match(pathname):
+        try:
+            vc = ID3.ID3(pathname, as_tuple=1)
+            tags = vc.as_dict()
+        except NameError: pass
+        except (IOError, ID3.InvalidTagError): return os.path.basename(pathname)
+    else:
+        return os.path.basename(pathname)
+
+    artist = tags.get("ARTIST", [""])[0]
+    title = tags.get("TITLE", [""])[0]
+    tag = os.path.basename(pathname)
+    try:
+        if artist and title:
+            tag = codecs.latin_1_encode(artist)[0] + " - " + codecs.latin_1_encode(title)[0]
+        elif artist:
+            tag = artist
+        elif title:
+            tag = title
+        return codecs.latin_1_encode(tag)[0]
+    except (NameError, UnicodeError): return tag
+
+# ------------------------------------------
+class Player:
+    def __init__(self, commandline, files, fps=1):
+        self.commandline = commandline
+        self.re_files = re.compile(files, re.I)
+        self.fps = fps
+        self.stdin_r, self.stdin_w = os.pipe()
+        self.stdout_r, self.stdout_w = os.pipe()
+        self.stderr_r, self.stderr_w = os.pipe()
+        self.entry = None
+        self.stopped = 0
+        self.paused = 0
+        self.time_setup = None
+        self.buf = ''
+        self.tid = None
+
+    def setup(self, entry, offset):
+        self.argv = string.split(self.commandline)
+        self.argv[0] = which(self.argv[0])
+        for i in range(len(self.argv)):
+            if self.argv[i] == "%s": self.argv[i] = entry.pathname
+            if self.argv[i] == "%d": self.argv[i] = str(offset*self.fps)
+        self.entry = entry
+        if offset == 0:
+            app.progress(0)
+            self.offset = 0
+            self.length = 0
+            self.values = [0, 0]
+        self.time_setup = time.time()
+        return self.argv[0]
+
+    def play(self):
+        self.pid = os.fork()
+        if self.pid == 0:
+            os.dup2(self.stdin_w, sys.stdin.fileno())
+            os.dup2(self.stdout_w, sys.stdout.fileno())
+            os.dup2(self.stderr_w, sys.stderr.fileno())
+            os.setpgrp()
+            try: os.execv(self.argv[0], self.argv)
+            except: os._exit(1)
+        self.stopped = 0
+        self.paused = 0
+        self.step = 0
+        self.update_status()
+
+    def stop(self, quiet=0):
+        self.paused and self.toggle_pause(quiet)
+        try:
+            while 1:
+                try: os.kill(-self.pid, signal.SIGINT)
+                except os.error: pass
+                os.waitpid(self.pid, os.WNOHANG)
+        except Exception: pass
+        self.stopped = 1
+        quiet or self.update_status()
+
+    def toggle_pause(self, quiet=0):
+        try: os.kill(-self.pid, [signal.SIGSTOP, signal.SIGCONT][self.paused])
+        except os.error: return
+        self.paused = not self.paused
+        quiet or self.update_status()
+
+    def parse_progress(self):
+        if self.stopped or self.step: self.tid = None
+        else:
+            self.parse_buf()
+            self.tid = app.timeout.add(1.0, self.parse_progress)
+
+    def read_fd(self, fd):
+        self.buf = os.read(fd, 512)
+        self.tid or self.parse_progress()
+
+    def poll(self):
+        try: os.waitpid(self.pid, os.WNOHANG)
+        except:
+            # something broken? try again
+            if self.time_setup and (time.time() - self.time_setup) < 2.0:
+                self.play()
+                return 0
+            app.set_default_status("")
+            app.counter([0,0])
+            app.progress(0)
+            return 1
+
+    def seek(self, offset, relative):
+        if relative:
+            d = offset * self.length * 0.002
+            self.step = self.step * (self.step * d > 0) + d
+            self.offset = min(self.length, max(0, self.offset+self.step))
+        else:
+            self.step = 1
+            self.offset = (offset < 0) and self.length+offset or offset
+        self.show_position()
+
+    def set_position(self, offset, length, values):
+        self.offset = offset
+        self.length = length
+        self.values = values
+        self.show_position()
+
+    def show_position(self):
+        app.counter(self.values)
+        app.progress(self.length and (float(self.offset) / self.length))
+
+    def update_status(self):
+        if not self.entry:
+            app.set_default_status("")
+        elif self.stopped:
+            app.set_default_status(_("Stopped: %s") % self.entry.vp())
+        elif self.paused:
+            app.set_default_status(_("Paused: %s") % self.entry.vp())
+        else:
+            app.set_default_status(_("Playing: %s") % self.entry.vp())
+
+# ------------------------------------------
+class FrameOffsetPlayer(Player):
+    re_progress = re.compile("Time.*\s(\d+):(\d+).*\[(\d+):(\d+)")
+
+    def parse_buf(self):
+        match = self.re_progress.search(self.buf)
+        if match:
+            m1, s1, m2, s2 = map(string.atoi, match.groups())
+            head, tail = m1*60+s1, m2*60+s2
+            self.set_position(head, head+tail, [head, tail])
+
+# ------------------------------------------
+class TimeOffsetPlayer(Player):
+    re_progress = re.compile("(\d+):(\d+):(\d+)")
+
+    def parse_buf(self):
+        match = self.re_progress.search(self.buf)
+        if match:
+            h, m, s = map(string.atoi, match.groups())
+            tail = h*3600+m*60+s
+            head = max(self.length, tail) - tail
+            self.set_position(head, head+tail, [head, tail])
+
+# ------------------------------------------
+class NoOffsetPlayer(Player):
+
+    def parse_buf(self):
+        head = self.offset+1
+        self.set_position(head, 0, [head, head])
+
+    def seek(self, *dummy):
+        return 1
+
+# ------------------------------------------
+class Timeout:
+    def __init__(self):
+        self.next = 0
+        self.dict = {}
+
+    def add(self, timeout, func, args=()):
+        tid = self.next = self.next + 1
+        self.dict[tid] = (func, args, time.time() + timeout)
+        return tid
+
+    def remove(self, tid):
+        del self.dict[tid]
+
+    def check(self, now):
+        for tid, (func, args, timeout) in self.dict.items():
+            if now >= timeout:
+                self.remove(tid)
+                apply(func, args)
+        return len(self.dict) and 0.2 or None
+
+# ------------------------------------------
+class FIFOControl:
+    def __init__(self):
+        try: self.fd = open(CONTROL_FIFO, "rb+", 0)
+        except: self.fd = None
+        self.commands = {"pause" : app.toggle_pause,
+                         "next" : app.next_song,
+                         "prev" : app.prev_song,
+                         "forward" : self.forward,
+                         "backward" : self.backward,
+                         "play" : app.toggle_stop,
+                         "stop" : app.toggle_stop,
+                         "volup" : app.inc_volume,
+                         "voldown" : app.dec_volume,
+                         "quit" : app.quit}
+
+    def handle_command(self):
+        command = string.strip(self.fd.readline())
+        if command in self.commands.keys():
+            self.commands[command]()
+
+    def forward(self):
+        app.seek(1, 1)
+
+    def backward(self):
+        app.seek(-1, 1)
+
+# ------------------------------------------
+class Application:
+    def __init__(self):
+        self.keymapstack = KeymapStack()
+        self.input_mode = 0
+        self.input_prompt = ""
+        self.input_string = ""
+        self.do_input_hook = None
+        self.stop_input_hook = None
+        self.complete_input_hook = None
+        self.channels = []
+        self.restricted = 0
+        self.input_keymap = Keymap()
+        self.input_keymap.bind(list(Window.chars), self.do_input)
+        self.input_keymap.bind(curses.KEY_BACKSPACE, self.do_input, (8,))
+        self.input_keymap.bind([21, 23], self.do_input)
+        self.input_keymap.bind(['\a', 27], self.cancel_input, ())
+        self.input_keymap.bind(['\n', curses.KEY_ENTER],
+                               self.stop_input, ())
+
+    def setup(self):
+        if tty:
+            self.tcattr = tty.tcgetattr(sys.stdin.fileno())
+            tcattr = tty.tcgetattr(sys.stdin.fileno())
+            tcattr[0] = tcattr[0] & ~(tty.IXON)
+            tty.tcsetattr(sys.stdin.fileno(), tty.TCSANOW, tcattr)
+        self.w = curses.initscr()
+        curses.cbreak()
+        curses.noecho()
+        try: curses.meta(1)
+        except: pass
+        self.cursor(0)
+        signal.signal(signal.SIGCHLD, signal.SIG_IGN)
+        signal.signal(signal.SIGHUP, self.handler_quit)
+        signal.signal(signal.SIGINT, self.handler_quit)
+        signal.signal(signal.SIGTERM, self.handler_quit)
+        signal.signal(signal.SIGWINCH, self.handler_resize)
+        self.win_root = RootWindow(None)
+        self.win_root.update()
+        self.win_tab = self.win_root.win_tab
+        self.win_filelist = self.win_root.win_tab.win_filelist
+        self.win_playlist = self.win_root.win_tab.win_playlist
+        self.win_status = self.win_root.win_status
+        self.status = self.win_status.status
+        self.set_default_status = self.win_status.set_default_status
+        self.restore_default_status = self.win_status.restore_default_status
+        self.counter = self.win_root.win_counter.counter
+        self.progress = self.win_root.win_progress.progress
+        self.player = PLAYERS[0]
+        self.timeout = Timeout()
+        self.play_tid = None
+        self.kludge = 0
+        self.win_filelist.listdir()
+        self.control = FIFOControl()
+
+    def cleanup(self):
+        try: curses.endwin()
+        except curses.error: return
+        XTERM and sys.stderr.write("\033]0;%s\a" % "xterm")
+        tty and tty.tcsetattr(sys.stdin.fileno(), tty.TCSADRAIN, self.tcattr)
+        print
+
+    def run(self):
+        while 1:
+            now = time.time()
+            timeout = self.timeout.check(now)
+            self.win_filelist.listdir_maybe(now)
+            if not self.player.stopped:
+                timeout = 0.5
+                if self.kludge and self.player.poll():
+                    self.player.stopped = 1  # end of playlist hack
+                    if not self.win_playlist.stop:
+                        entry = self.win_playlist.change_active_entry(1)
+                        entry and self.play(entry)
+            R = [sys.stdin, self.player.stdout_r, self.player.stderr_r]
+            self.control.fd and R.append(self.control.fd)
+            try: r, w, e = select.select(R, [], [], timeout)
+            except select.error: continue
+            self.kludge = 1
+            # user
+            if sys.stdin in r:
+                c = self.win_root.getch()
+                self.keymapstack.process(c)
+            # player
+            if self.player.stderr_r in r:
+                self.player.read_fd(self.player.stderr_r)
+            # player
+            if self.player.stdout_r in r:
+                self.player.read_fd(self.player.stdout_r)
+            # remote
+            if self.control.fd in r:
+                self.control.handle_command()
+
+    def play(self, entry, offset = 0):
+        self.kludge = 0
+        self.play_tid = None
+        if entry is None or offset is None: return
+        self.player.stop(quiet=1)
+        for self.player in PLAYERS:
+            if self.player.re_files.search(entry.pathname):
+                if self.player.setup(entry, offset): break
+        else:
+            app.status(_("Player not found!"), 1)
+            self.player.stopped = 0  # keep going
+            return
+        self.player.play()
+
+    def delayed_play(self, entry, offset):
+        if self.play_tid: self.timeout.remove(self.play_tid)
+        self.play_tid = self.timeout.add(0.5, self.play, (entry, offset))
+
+    def next_song(self):
+        self.delayed_play(self.win_playlist.change_active_entry(1), 0)
+
+    def prev_song(self):
+        self.delayed_play(self.win_playlist.change_active_entry(-1), 0)
+
+    def seek(self, offset, relative):
+        if not self.player.entry: return
+        self.player.seek(offset, relative)
+        self.delayed_play(self.player.entry, self.player.offset)
+
+    def toggle_pause(self):
+        if not self.player.entry: return
+        if not self.player.stopped: self.player.toggle_pause()
+
+    def toggle_stop(self):
+        if not self.player.entry: return
+        if not self.player.stopped: self.player.stop()
+        else: self.play(self.player.entry, self.player.offset)
+
+    def inc_volume(self):
+        self.mixer("cue", 1)
+
+    def dec_volume(self):
+        self.mixer("cue", -1)
+
+    def key_volume(self, ch):
+        self.mixer("set", (ch & 0x0f)*10)
+
+    def mixer(self, cmd=None, arg=None):
+        try: self._mixer(cmd, arg)
+        except Exception, e: app.status(e, 2)
+
+    def _mixer(self, cmd, arg):
+        try:
+            import ossaudiodev
+            mixer = ossaudiodev.openmixer()
+            get, set = mixer.get, mixer.set
+            self.channels = self.channels or \
+                [['MASTER', ossaudiodev.SOUND_MIXER_VOLUME],
+                 ['PCM', ossaudiodev.SOUND_MIXER_PCM]]
+        except ImportError:
+            import oss
+            mixer = oss.open_mixer()
+            get, set = mixer.read_channel, mixer.write_channel
+            self.channels = self.channels or \
+                [['MASTER', oss.SOUND_MIXER_VOLUME],
+                 ['PCM', oss.SOUND_MIXER_PCM]]
+        if cmd is "toggle": self.channels.insert(0, self.channels.pop())
+        name, channel = self.channels[0]
+        if cmd is "cue": arg = min(100, max(0, get(channel)[0] + arg))
+        if cmd in ["set", "cue"]: set(channel, (arg, arg))
+        app.status(_("%s volume %s%%") % (name, get(channel)[0]), 1)
+        mixer.close()
+
+    def show_input(self):
+        n = len(self.input_prompt)+1
+        s = cut(self.input_string, self.win_status.cols-n, left=1)
+        app.status("%s%s " % (self.input_prompt, s))
+
+    def start_input(self, prompt="", data="", colon=1):
+        self.input_mode = 1
+        self.cursor(1)
+        app.keymapstack.push(self.input_keymap)
+        self.input_prompt = prompt + (colon and ": " or "")
+        self.input_string = data
+        self.show_input()
+
+    def do_input(self, *args):
+        if self.do_input_hook:
+            return apply(self.do_input_hook, args)
+        ch = args and args[0] or None
+        if ch in [8, 127]: # backspace
+            self.input_string = self.input_string[:-1]
+        elif ch == 9 and self.complete_input_hook:
+            self.input_string = self.complete_input_hook(self.input_string)
+        elif ch == 21: # C-u
+            self.input_string = ""
+        elif ch == 23: # C-w
+            self.input_string = re.sub("((.* )?)\w.*", "\\1", self.input_string)
+        elif ch:
+            self.input_string = "%s%c" % (self.input_string, ch)
+        self.show_input()
+
+    def stop_input(self, *args):
+        self.input_mode = 0
+        self.cursor(0)
+        app.keymapstack.pop()
+        if not self.input_string:
+            app.status(_("cancel"), 1)
+        elif self.stop_input_hook:
+            apply(self.stop_input_hook, args)
+        self.do_input_hook = None
+        self.stop_input_hook = None
+        self.complete_input_hook = None
+
+    def cancel_input(self):
+        self.input_string = ""
+        self.stop_input()
+
+    def cursor(self, visibility):
+        try: curses.curs_set(visibility)
+        except: pass
+
+    def quit(self):
+        self.player.stop(quiet=1)
+        sys.exit(0)
+
+    def handler_resize(self, sig, frame):
+        # curses trickery
+        while 1:
+            try: curses.endwin(); break
+            except: time.sleep(1)
+        self.w.refresh()
+        self.win_root.resize()
+        self.win_root.update()
+
+    def handler_quit(self, sig, frame):
+        self.quit()
+
+# ------------------------------------------
+def main():
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "nrRv")
+    except:
+        usage = _("Usage: %s [-nrRv] [ file | dir | playlist ] ...\n")
+        sys.stderr.write(usage % sys.argv[0])
+        sys.exit(1)
+
+    global app
+    app = Application()
+
+    playlist = []
+    if not sys.stdin.isatty():
+        playlist = map(string.strip, sys.stdin.readlines())
+        os.close(0)
+        os.open("/dev/tty", 0)
+    try:
+        app.setup()
+        for opt, optarg in opts:
+            if opt == "-n": app.restricted = 1
+            if opt == "-r": app.win_playlist.command_toggle_repeat()
+            if opt == "-R": app.win_playlist.command_toggle_random()
+            if opt == "-v": app.mixer("toggle")
+        if args or playlist:
+            for i in args or playlist:
+                app.win_playlist.add(os.path.abspath(i))
+            app.win_tab.change_window()
+        app.run()
+    except SystemExit:
+        app.cleanup()
+    except Exception:
+        app.cleanup()
+        import traceback
+        traceback.print_exc()
+
+# ------------------------------------------
+PLAYERS = [
+    FrameOffsetPlayer("ogg123 -q -v -k %d %s", "\.ogg$"),
+    FrameOffsetPlayer("splay -f -k %d %s", "(^http://|\.mp[123]$)", 38.28),
+    FrameOffsetPlayer("mpg123 -q -v -k %d %s", "(^http://|\.mp[123]$)", 38.28),
+    FrameOffsetPlayer("mpg321 -q -v -k %d %s", "(^http://|\.mp[123]$)", 38.28),
+    TimeOffsetPlayer("madplay -v --display-time=remaining -s %d %s", "\.mp[123]$"),
+    NoOffsetPlayer("mikmod -q -p0 %s", "\.(mod|xm|fm|s3m|med|col|669|it|mtm)$"),
+    NoOffsetPlayer("xmp -q %s", "\.(mod|xm|fm|s3m|med|col|669|it|mtm|stm)$"),
+    NoOffsetPlayer("play %s", "\.(aiff|au|cdr|mp3|ogg|wav)$"),
+    NoOffsetPlayer("speexdec %s", "\.spx$")
+    ]
+
+def VALID_SONG(name):
+    for player in PLAYERS:
+        if player.re_files.search(name):
+            return 1
+
+def VALID_PLAYLIST(name):
+    if re.search("\.(m3u|pls)$", name, re.I):
+        return 1
+
+for rc in [os.path.expanduser("~/.cplayrc"), "/etc/cplayrc"]:
+    try: execfile(rc); break
+    except IOError: pass
+
+# ------------------------------------------
+if __name__ == "__main__": main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cplay.1	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,80 @@
+.\" Copyright (C) 2000, 2002 Martin Michlmayr <tbm@cyrius.com>
+.\" This manual is freely distributable under the terms of the GPL.
+.\" It was originally written for Debian GNU/Linux (but may be used
+.\" by others).
+.\"
+.TH CPLAY 1 "November 2002"
+
+.SH NAME
+.PP
+cplay \- a front-end for various audio players
+
+.SH SYNOPSIS
+.PP
+\fBcplay\fR [\-options] [ \fIfile\fP | \fIdir\fP | \fIplaylist\fP ] ...
+
+.SH DESCRIPTION
+.PP
+.B cplay
+is a front-end for various audio players. It aims to provide a
+user-friendly interface with simple filelist and playlist
+control.  Invoking
+.B cplay
+without any options puts you in the filelist in which you
+can navigate around looking for audio files.  When you
+specify an audio file,
+.B cplay
+automatically adds it to the playlist which can be accessed
+by pressing the tabulator key.
+.PP
+Use 'h' to display the help window.
+.PP
+Shell command-lines are executed with tagged or current entries
+as positional parameters, which means that "$@" might be useful.
+.PP
+Currently, the following audio formats are supported: MP3 (through
+mpg321, splay, mpg123 or madplay), Ogg Vorbis (through ogg123), and
+various module-formats (through mikmod and xmp).
+
+.SH OPTIONS
+.IP \fB\-n
+Enable \fIrestricted\fI mode
+.IP \fB\-r
+Toggles playlist \fIrepeat\fP mode
+.IP \fB\-R
+Toggles playlist \fIrandom\fP mode
+.IP \fB\-v
+Toggles PCM and MASTER (default) volume control
+
+.SH SEE ALSO
+.PP
+.BR ogg123 (1),
+.BR mpg123 (1),
+.BR mpg321 (1),
+.BR splay (1),
+.BR madplay (1),
+.BR mikmod (1),
+.BR xmp (1),
+.BR play (1),
+.BR speexdec (1)
+
+.SH FILES
+.PP
+.I /var/tmp/cplay_control 
+- Optional remote control fifo.
+
+.I /etc/cplayrc
+- Optional configuration file.
+
+.I ~/.cplayrc
+- Optional configuration file.
+
+.SH AUTHOR
+.PP
+Ulf Betlehem <flu@iki.fi>
+
+.SH BUGS
+.PP
+In order for either mp3info (ID3) or ogginfo to work,
+both corresponding python modules have to be installed.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cplay.list	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,35 @@
+# cplay list file
+# tested with epm 2.5 and cplay 1.48
+
+# Manpage extensions...
+$MAN1EXT=1
+
+# Directories...
+$prefix=/usr/local
+$bindir=${prefix}/bin
+$confdir=/etc
+$docdir=${prefix}/doc/cplay
+$mandir=${prefix}/man/man1
+
+# Product information
+%product cplay
+%copyright GPL
+%vendor Ulf Betlehem <flu@iki.fi>
+%license COPYING
+%readme README
+%description cplay, a curses front-end for various audio players. 
+%version 1.48
+%packager Giuseppe "Cowo" Corbelli <cowo@lugbs.linux.it>
+
+# Executables
+%system all
+f 0555 root sys ${bindir}/cplay cplay
+# Doc
+f 0444 root sys ${docdir}/README README
+f 0444 root sys ${docdir}/TODO TODO
+
+# Man pages
+f 0444 root sys ${mandir}/cplay.${MAN1EXT} cplay.1
+
+#Configuration
+f 0644 root sys ${confdir}/cplayrc cplayrc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cplayrc	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,12 @@
+PLAYERS = [
+    FrameOffsetPlayer("ogg123 -q -v -k %d %s", "\.ogg$"),
+    FrameOffsetPlayer("splay -f -k %d %s", "(^http://|\.mp[123]$)", 38.28),
+    FrameOffsetPlayer("mpg123 -q -v -k %d %s", "(^http://|\.mp[123]$)", 38.28),
+    FrameOffsetPlayer("mpg321 -q -v -k %d %s", "(^http://|\.mp[123]$)", 38.28),
+    TimeOffsetPlayer("madplay -v --no-tty-control --display-time=remaining -s %d %s", "\.mp[123]$"),
+    NoOffsetPlayer("mikmod -q -p0 %s", "\.(mod|xm|fm|s3m|med|col|669|it|mtm)$"),
+    NoOffsetPlayer("xmp -q %s", "\.(mod|xm|fm|s3m|med|col|669|it|mtm|stm)$"),
+    NoOffsetPlayer("play %s", "\.(aiff|au|cdr|mp3|ogg|wav)$"),
+    NoOffsetPlayer("speexdec %s", "\.spx$")
+    ]
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lircrc	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,71 @@
+begin
+    remote = ANIMAX
+    button = SKIP_FORWARD_DOWN
+    prog   = irexec
+    repeat = 0
+    config = echo "next" > /var/tmp/cplay_control
+end
+
+begin
+    remote = ANIMAX
+    button = SKIP_BACKWARD_DOWN
+    prog   = irexec
+    repeat = 0
+    config = echo "prev" > /var/tmp/cplay_control
+end
+
+begin
+    remote = ANIMAX
+    button = REWIND_DOWN
+    prog   = irexec
+    repeat = 1
+    config = echo "backward" > /var/tmp/cplay_control
+end
+
+begin
+    remote = ANIMAX
+    button = FORWARD_DOWN
+    prog   = irexec
+    repeat = 1
+    config = echo "forward" > /var/tmp/cplay_control
+end
+
+begin
+    remote = ANIMAX
+    button = PLAY_DOWN
+    prog   = irexec
+    repeat = 0
+    config = echo "play" > /var/tmp/cplay_control
+end
+
+begin
+    remote = ANIMAX
+    button = STOP_DOWN
+    prog   = irexec
+    repeat = 0
+    config = echo "stop" > /var/tmp/cplay_control
+end
+
+begin
+    remote = ANIMAX
+    button = VOLUME_UP_DOWN
+    prog   = irexec
+    repeat = 1
+    config = echo "volup" > /var/tmp/cplay_control
+end
+
+begin
+    remote = ANIMAX
+    button = VOLUME_DOWN_DOWN
+    prog   = irexec
+    repeat = 1
+    config = echo "voldown" > /var/tmp/cplay_control
+end
+
+begin
+    remote = ANIMAX
+    button = POWER_DOWN
+    prog   = irexec
+    repeat = 0
+    config = echo "quit" > /var/tmp/cplay_control
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/po/Makefile	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,16 @@
+POFILES = $(wildcard *.po)
+MOFILES = $(POFILES:.po=.mo)
+
+$(MOFILES): $(POFILES)
+	for i in $(POFILES); do msgfmt $$i -o $${i%.po}.mo; done
+
+all: $(MOFILES)
+
+install: all
+	for i in $(MOFILES); do \
+		mkdir -p $(PREFIX)/share/locale/$${i%.mo}/LC_MESSAGES; \
+		install -c -m 644 $$i $(PREFIX)/share/locale/$${i%.mo}/LC_MESSAGES/cplay.mo; \
+	done
+
+clean:
+	rm -f $(MOFILES) *~
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/po/da.po	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,278 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR ORGANIZATION
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: Tue Jul 29 17:01:48 2003\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=iso-8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.4\n"
+
+
+#: cplay:287
+msgid "elapsed"
+msgstr "spillet"
+
+#: cplay:287
+msgid "remaining"
+msgstr "tilbage"
+
+#: cplay:288
+msgid "Counting %s time"
+msgstr "Tćller %s gange"
+
+#: cplay:320
+msgid "Quit? (y/N)"
+msgstr "Afslut? (y/N)"
+
+#: cplay:405
+msgid "backward-isearch"
+msgstr "baglćns-isřgning"
+
+#: cplay:407
+msgid "forward-isearch"
+msgstr "forlćns-isřgning"
+
+#: cplay:496 cplay:657 cplay:748 cplay:760 cplay:1148
+msgid "ok"
+msgstr "ok"
+
+#: cplay:505 cplay:517
+msgid "Not found: %s "
+msgstr "%s ikke fundet "
+
+#: cplay:525
+msgid "Help"
+msgstr "Hjćlp"
+
+#: cplay:527
+msgid ""
+"\n"
+"  Global                               Space : tag/untag current\n"
+"  ------                               t, T  : tag current/regex\n"
+"  Up, Down, k, j, C-p, C-n,            u, U  : untag current/regex\n"
+"  PgUp, PgDn, K, J,                    i     : invert tags\n"
+"  Home, End, g, G : movement\n"
+"  Enter           : chdir or play      Filelist\n"
+"  Tab             : filelist/playlist  --------\n"
+"  n, p            : next/prev track    BkSpc : parent dir\n"
+"  z, x            : toggle pause/stop  o, s  : goto, search recursively\n"
+"  Left, Right,                         a     : add (tagged) to playlist\n"
+"  C-f, C-b    : seek forward/backward\n"
+"  c           : counter mode           Playlist\n"
+"  C-s, C-r, / : isearch                --------\n"
+"  C-g, Esc    : cancel                 d, D  : delete (tagged) tracks/playlist\n"
+"  1..9, +, -  : volume control         m, M  : move tagged tracks after/before\n"
+"  v           : PCM or MASTER volume   r, R  : toggle repeat/Random mode\n"
+"  C-l, l      : refresh, list mode     s, S  : shuffle/Sort playlist\n"
+"  h, q, Q     : help, quit?, Quit!     w, @  : write playlist, jump to current\n"
+"\n"
+"\n"
+"\n"
+"  Goodies\n"
+"  -------\n"
+"  b, '   : set/get filelist bookmark\n"
+"  X      : stop playlist after each track\n"
+"  C-a, ^ : seek to beginning of current track (restart)\n"
+"  C-e, $ : seek to end of current track (useless?)\n"
+"  !      : shell command ($@ = tagged or current)\n"
+"  "
+msgstr ""
+"\n"
+"  Globalt                                  Space : vćlg/fravćlg nuvćrende\n"
+"  ------                                   t, T  : vćlg nuvćrende/regex\n"
+"  Up, Ned, k, j, C-p, C-n,                 u, U  : fravćlg nuvćrede/regex\n"
+"  PgUp, PgDn, K, J,                        i     : vend vćlg om\n"
+"  Home, End, g, G : bevćgelse\n"
+"  Enter           : chdir eller afspil     Filliste\n"
+"  Tab             : filliste/spilleliste   --------\n"
+"  n, p            : nćste/forrige nummer   BkSpc : et filkatalog op\n"
+"  z, x            : pause/stop fra og til  o, s  : gĺ til, sřg gennemgĺende\n"
+"  Venstre, Hřjre,                          a     : tilfřj (valgte) til spilleliste\n"
+"  C-f, C-b    : sřg forlćns/baglćns\n"
+"  c           : tćller-type                Spilleliste\n"
+"  C-s, C-r, / : isřgning                   --------\n"
+"  C-g, Esc    : afbryd                     d, D  : fjern (valgte) numre/spillelist\n"
+"  1..9, +, -  : lydstyrke                  m, M  : flyt valgte numre efter/fřr\n"
+"  v           : PCM eller MASTER volume    r, R  : repeat/Random fra og til\n"
+"  C-l, l      : opfrisk, vis liste         s, S  : bland/Sortér spillelist\n"
+"  h, q, Q     : hjćlp, afslut?, Afslut!    w, @  : gem spillelist, gĺ til nuvćrende\n"
+"\n"
+"\n"
+"\n"
+"  Godbider\n"
+"  -------\n"
+"  b, '   : sćt/hent filliste-bogmćrke\n"
+"  X      : stop spilleiste efter hvert nummer\n"
+"  C-a, ^ : gĺ til begyndelsen af det nyvćrende nummer (start om)\n"
+"  C-e, $ : gĺ til slutningen af det nuvćrende nummer (ubrugelig?)\n"
+"  !      : skal-kommando ($@ = valgte eller nuvćrende)\n"
+"  "
+
+#: cplay:593
+msgid "filename"
+msgstr "filnavn"
+
+#: cplay:594
+msgid "pathname"
+msgstr "stinavn"
+
+#: cplay:625
+msgid "Listing %s"
+msgstr "Sććter %s pĺ liste"
+
+#: cplay:648
+msgid "Tag regexp"
+msgstr "Vćlg regexp"
+
+#: cplay:648
+msgid "Untag regexp"
+msgstr "Fravćlg regexp"
+
+#: cplay:687
+msgid "shell$ "
+msgstr "skal$ "
+
+#: cplay:734
+msgid "bookmark"
+msgstr "bogmćrke"
+
+#: cplay:750
+msgid "Not found!"
+msgstr "Ikke fundet!"
+
+#: cplay:755
+msgid "set bookmark"
+msgstr "sćt bogmćrke"
+
+#: cplay:764
+msgid "search"
+msgstr "sřg"
+
+#: cplay:771
+msgid "Searching..."
+msgstr "Sřger..."
+
+#: cplay:782
+msgid "search results"
+msgstr "sřgnings-resultater"
+
+#: cplay:802
+msgid "Filelist: "
+msgstr "Filliste: "
+
+#: cplay:813
+msgid "Reading directory..."
+msgstr "Lćser filkatalog..."
+
+#: cplay:868
+msgid "goto"
+msgstr "gĺ til"
+
+#: cplay:874
+msgid "Not a directory!"
+msgstr "Ikke et filkatalog"
+
+#: cplay:919
+msgid "metadata"
+msgstr "metadata"
+
+#: cplay:925
+msgid "Playlist %s %s %s"
+msgstr "Spilleliste %s %s %s"
+
+#: cplay:926
+msgid "[repeat]"
+msgstr "[gentag]"
+
+#: cplay:927
+msgid "[random]"
+msgstr "[tilfćldig]"
+
+#: cplay:928
+msgid "[stop]"
+msgstr "[stop]"
+
+#: cplay:981 cplay:1108
+msgid "Working..."
+msgstr "Arbejder..."
+
+#: cplay:990
+msgid "Added: %s"
+msgstr "Tilfřjede: %s"
+
+#: cplay:1079
+msgid "Deleted playlist"
+msgstr "Fjernede spilleliste"
+
+#: cplay:1105
+msgid "Shuffled playlist... Oops?"
+msgstr "Blandede spilleliste... Hovsa?"
+
+#: cplay:1112
+msgid "Sorted playlist"
+msgstr "Sortérede spilleliste"
+
+#: cplay:1115
+msgid "Repeat: %s"
+msgstr "Gentag: %s"
+
+#: cplay:1118
+msgid "Random: %s"
+msgstr "Tilfćldig: %s"
+
+#: cplay:1124
+msgid "Stop playlist: %s"
+msgstr "Stop spilleliste"
+
+#: cplay:1128
+msgid "off"
+msgstr "fra"
+
+#: cplay:1128
+msgid "on"
+msgstr "til"
+
+#: cplay:1134
+msgid "Save playlist"
+msgstr "Gem spilleliste"
+
+#: cplay:1310
+msgid "Stopped: %s"
+msgstr "Stoppet: %s"
+
+#: cplay:1312
+msgid "Paused: %s"
+msgstr "Sat pĺ pause: %s"
+
+#: cplay:1314
+msgid "Playing: %s"
+msgstr "Spiller: %s"
+
+#: cplay:1491
+msgid "Player not found!"
+msgstr "Afspiller ikke fundet!"
+
+#: cplay:1548
+msgid "Volume %s%%"
+msgstr "Lydstyrke %s%"
+
+#: cplay:1556
+msgid "%s volume %s%%"
+msgstr "%s lydstyrke %s%"
+
+#: cplay:1587
+msgid "cancel"
+msgstr "afbryd"
+
+#: cplay:1623
+msgid ""
+"Usage: %s [-rRv] [ file | dir | playlist ] ...\n"
+msgstr ""
+"Brug: %s [-rRv] [ fil | filkatalog | spilleliste ] ...\n"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/po/de.po	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,193 @@
+# Copyright (C) 2000 Martin Michlmayr <tbm@cyrius.com>
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 1.0\n"
+"PO-Revision-Date: Sat Jul 22 00:21:05 2000\n"
+"Last-Translator: Martin Michlmayr <tbm@cyrius.com>\n"
+"Language-Team: Martin Michlmayr <tbm@cyrius.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=iso-8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: cplay:828
+msgid "Almost there..."
+msgstr "Fast fertig..."
+
+#: cplay:834
+msgid "Cleared all marks..."
+msgstr "Lösche alle Markierungen..."
+
+#: cplay:746
+msgid "IOError"
+msgstr "I/O-Fehler"
+
+#: cplay:882 cplay:888
+msgid "off"
+msgstr "aus"
+
+#: cplay:918
+msgid "Cannot write playlist!"
+msgstr "Kann Playliste nicht schreiben!"
+
+#: cplay:412
+msgid "forward-isearch"
+msgstr "Vorwärts-isuche"
+
+#: cplay:539
+msgid "Not found: %s"
+msgstr "Nicht gefunden: %s"
+
+#: cplay:727
+msgid "Playlist %s %s"
+msgstr "Playliste %s %s"
+
+#: cplay:411
+msgid "backward-isearch"
+msgstr "Rückwärts-isuche"
+
+#: cplay:878
+msgid "Sorted playlist..."
+msgstr "Sortiere Playliste..."
+
+#: cplay:205
+msgid "Help"
+msgstr "Hilfe"
+
+#: cplay:419 cplay:916
+msgid "ok"
+msgstr "ok"
+
+#: cplay:958
+msgid "Paused: %s"
+msgstr "Pause: %s"
+
+#: cplay:882
+msgid "Repeat %s"
+msgstr "Wiederholung %s"
+
+#: cplay:882 cplay:888
+msgid "on"
+msgstr "ein"
+
+#: cplay:742 cplay:749
+msgid "Added: %s"
+msgstr "Hinzugefügt: %s"
+
+#: cplay:728 cplay:729
+msgid "[random]"
+msgstr "[zufällig]"
+
+#: cplay:872
+msgid "Shuffled playlist... Oops?"
+msgstr "Durchwühle Playliste... Oops?"
+
+#: cplay:418 cplay:639 cplay:904
+msgid "cancel"
+msgstr "Abbruch"
+
+#: cplay:463
+msgid "probably not ok"
+msgstr "wahrscheinlich nicht ok"
+
+#: cplay:575
+msgid "Reading directory..."
+msgstr "Lese Verzeichnis..."
+
+#: cplay:1162
+msgid "Player not found!"
+msgstr "Player nicht gefunden!"
+
+#: cplay:611
+msgid "Filelist: "
+msgstr "Dateiliste: "
+
+#: cplay:1240
+msgid "Usage: %s [ filename | playlist.m3u ] ...\n"
+msgstr "Verwendung: %s [ datei | playliste.m3u ] ...\n"
+
+#: cplay:1217
+msgid "Volume %d%%"
+msgstr "Lautstärke %d%%"
+
+#: cplay:941 cplay:964
+msgid "Playing: %s"
+msgstr "Spiele: %s"
+
+#: cplay:181
+msgid ""
+"\n"
+"  Global                            Filelist\n"
+"  ------                            --------\n"
+"  Up, C-p, k,                       Space     : add to playlist\n"
+"  Down, C-n, j,                     a         : add recursively\n"
+"  PgUp, PgDown,                     g         : goto\n"
+"  Home, End : movement              Enter     : chdir or play\n"
+"  Tab       : filelist/playlist     Backspace : parent dir\n"
+"  n,p       : next/prev track       \n"
+"  z,x       : toggle pause/stop     Playlist\n"
+"  Left, Right,                      --------\n"
+"  C-b, C-f  : seek                  Enter : play track\n"
+"  C-s       : forward-isearch       Space : mark\n"
+"  C-r       : backward-isearch      a,c   : mark/clear all\n"
+"  C-g, Esc  : cancel                m     : move marked tracks\n"
+"  1..9, +-  : volume                d     : delete marked tracks\n"
+"  C-l       : refresh screen        r, R  : toggle repeat/Random mode\n"
+"  h         : help                  s, S  : shuffle/Sort playlist\n"
+"  q         : quit                  o     : save playlist (to .m3u file)\n"
+msgstr ""
+"\n"
+"  Global                            Dateiliste\n"
+"  ------                            ----------\n"
+"  Up, C-p, k,                       Space     : Zu Playliste hinzufügen\n"
+"  Down, C-n, j,                     a         : Füge rekursiv hinzu\n"
+"  PgUp, PgDown,                     g         : Gehzu\n"
+"  Home, End : Bewegung              Enter     : Wechsle Verzeichnis oder "
+"spiele\n"
+"  Tab       : Dateiliste/Spielliste Backspace : Übergeordnetes Verzeichnis\n"
+"  n,p       : nächster/vorheriger Track\n"
+"  z,x       : schalte Pause/Stop    Playliste\n"
+"  Left, Right,                      ---------\n"
+"  C-b, C-f  : Suche                 Enter : Spiele Track\n"
+"  C-s       : Vorwärts-isuche       Space : markiere\n"
+"  C-r       : Rückwärts-isuche      a,c   : markiere/lösche alle\n"
+"  C-g, Esc  : Abbruch               m     : verschiebe markierte Tracks\n"
+"  1..9, +-  : Lautstärke            d     : lösche markierte Tracks\n"
+"  C-l       : stelle Bildschirm her r, R  : schalte widerhole/zufällig "
+"Modus\n"
+"  h         : Hilfe                 s, S  : durchwühle/ordne Playliste\n"
+"  q         : Ende                  o     : speichere Playliste (als .m3u "
+"Datei)\n"
+
+#: cplay:727 cplay:728
+msgid "[repeat]"
+msgstr "[wiederhole]"
+
+#: cplay:888
+msgid "Random %s"
+msgstr "Zufall %s"
+
+#: cplay:894
+msgid "Save playlist"
+msgstr "Speichere Playliste"
+
+#: cplay:629
+msgid "goto"
+msgstr "Gehzu"
+
+#: cplay:975
+msgid "Stopped: %s"
+msgstr "Gestoppt: %s"
+
+#: cplay:1209 cplay:1219
+msgid "Cannot open mixer device %s"
+msgstr "Kann Mixer-Device %s nicht öffnen."
+
+#: cplay:710
+msgid "Playlist"
+msgstr "Playliste"
+
+#: cplay:644
+msgid "Not a directory!"
+msgstr "Kein Verzeichnis!"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/po/hu.po	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,189 @@
+# Copyright (C) 2001 Gergely Nagy <8@free.bsd.hu>
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 1.0\n"
+"PO-Revision-Date: Thu Jun  7 12:29:48 CEST 2001\n"
+"Last-Translator: Gergely Nagy <8@free.bsd.hu>\n"
+"Language-Team: Gergely Nagy <8@free.bsd.hu>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=iso-8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: cplay:828
+msgid "Almost there..."
+msgstr "Majdnem kész..."
+
+#: cplay:834
+msgid "Cleared all marks..."
+msgstr "Minden jel törölve..."
+
+#: cplay:746
+msgid "IOError"
+msgstr "I/O-hiba"
+
+#: cplay:882 cplay:888
+msgid "off"
+msgstr "ki"
+
+#: cplay:918
+msgid "Cannot write playlist!"
+msgstr "Nem tudom menteni a számlistát!"
+
+#: cplay:412
+msgid "forward-isearch"
+msgstr "keresés előre"
+
+#: cplay:539
+msgid "Not found: %s"
+msgstr "Nem található: %s"
+
+#: cplay:727
+msgid "Playlist %s %s"
+msgstr "Számlista %s %s"
+
+#: cplay:411
+msgid "backward-isearch"
+msgstr "keresés hátra"
+
+#: cplay:878
+msgid "Sorted playlist..."
+msgstr "Rendezett számlista..."
+
+#: cplay:205
+msgid "Help"
+msgstr "Segítség"
+
+#: cplay:419 cplay:916
+msgid "ok"
+msgstr "ok"
+
+#: cplay:958
+msgid "Paused: %s"
+msgstr "Szünetel: %s"
+
+#: cplay:882
+msgid "Repeat %s"
+msgstr "Ismétel %s"
+
+#: cplay:882 cplay:888
+msgid "on"
+msgstr "be"
+
+#: cplay:742 cplay:749
+msgid "Added: %s"
+msgstr "Hozzáadva: %s"
+
+#: cplay:728 cplay:729
+msgid "[random]"
+msgstr "[véletlenszerű]"
+
+#: cplay:872
+msgid "Shuffled playlist... Oops?"
+msgstr "Kevert számlista... Hopsz?"
+
+#: cplay:418 cplay:639 cplay:904
+msgid "cancel"
+msgstr "mégsem"
+
+#: cplay:463
+msgid "probably not ok"
+msgstr "valószinűleg nem jó"
+
+#: cplay:575
+msgid "Reading directory..."
+msgstr "Könyvtár olvasása..."
+
+#: cplay:1162
+msgid "Player not found!"
+msgstr "Lejátszó nem található!"
+
+#: cplay:611
+msgid "Filelist: "
+msgstr "Állománylista: "
+
+#: cplay:1240
+msgid "Usage: %s [ filename | playlist.m3u ] ...\n"
+msgstr "Használat: %s [ állományok | számlista.m3u ] ...\n"
+
+#: cplay:1217
+msgid "Volume %d%%"
+msgstr "Hangerő %d%%"
+
+#: cplay:941 cplay:964
+msgid "Playing: %s"
+msgstr "Lejátszás: %s"
+
+#: cplay:181
+msgid ""
+"\n"
+"  Global                            Filelist\n"
+"  ------                            --------\n"
+"  Up, C-p, k,                       Space     : add to playlist\n"
+"  Down, C-n, j,                     a         : add recursively\n"
+"  PgUp, PgDown,                     g         : goto\n"
+"  Home, End : movement              Enter     : chdir or play\n"
+"  Tab       : filelist/playlist     Backspace : parent dir\n"
+"  n,p       : next/prev track       \n"
+"  z,x       : toggle pause/stop     Playlist\n"
+"  Left, Right,                      --------\n"
+"  C-b, C-f  : seek                  Enter : play track\n"
+"  C-s       : forward-isearch       Space : mark\n"
+"  C-r       : backward-isearch      a,c   : mark/clear all\n"
+"  C-g, Esc  : cancel                m     : move marked tracks\n"
+"  1..9, +-  : volume                d     : delete marked tracks\n"
+"  C-l       : refresh screen        r, R  : toggle repeat/Random mode\n"
+"  h         : help                  s, S  : shuffle/Sort playlist\n"
+"  q         : quit                  o     : save playlist (to .m3u file)\n"
+msgstr ""
+"\n"
+"  Általános                         Állománylista\n"
+"  ---------                         -------------\n"
+"  Fel, C-p, k,                      Szóköz    : hozzáadás a számlistához\n"
+"  Le, C-n, j,                       a         : rekurzív hozzáadás\n"
+"  PgUp, PgDown,                     g         : ugrás\n"
+"  Home, End : mozgás                Enter     : könyvtárváltás vagy lejátszás\n"
+"  Tab       : állomány/számlista    Backspace : előző könyvtár\n"
+"  n,p       : előző/következp szám  \n"
+"  z,x       : szünet/megállítás     Számlista\n"
+"  Baéra, Jobbra,                    ---------\n"
+"  C-b, C-f  : tekerés               Enter  : szám lejátszása\n"
+"  C-s       : keresés előre         Szóköz : megjelőlés\n"
+"  C-r       : keresés hátra         a,c    : összes megjelőlése/jelölés törlése\n"
+"  C-g, Esc  : mégse                 m      : kijelölt számok mozgatása\n"
+"  1..9, +-  : hangerő               d      : kijelölt számok törlése\n"
+"  C-l       : képernyő frissítés    r, R   : ismétlő/kevert mód váltása\n"
+"  h         : segítség              s, S   : számlista keverése/rendezése\n"
+"  q         : kilépés               o      : számlista mentése (.m3u állományba)\n"
+
+#: cplay:727 cplay:728
+msgid "[repeat]"
+msgstr "[ismétlés]"
+
+#: cplay:888
+msgid "Random %s"
+msgstr "Véletlenszerű %s"
+
+#: cplay:894
+msgid "Save playlist"
+msgstr "Számlista mentése"
+
+#: cplay:629
+msgid "goto"
+msgstr "ugrás"
+
+#: cplay:975
+msgid "Stopped: %s"
+msgstr "Megállítva: %s"
+
+#: cplay:1209 cplay:1219
+msgid "Cannot open mixer device %s"
+msgstr "A %s keverő-eszköz nem nyitható meg"
+
+#: cplay:710
+msgid "Playlist"
+msgstr "Számlista"
+
+#: cplay:644
+msgid "Not a directory!"
+msgstr "Nem könyvtár!"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/po/pl.po	Wed Sep 27 09:22:32 2017 +0200
@@ -0,0 +1,320 @@
+# Copyright (C) 2003 Marcin D. Mikielewicz <perry@ry.pl>
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 1.0\n"
+"PO-Revision-Date: Mon Apr 14 02:36:52 2003\n"
+"Last-Translator: Marcin D. Mikielewicz <perry@ry.pl>\n"
+"Language-Team: Marcin D. Mikielewicz <perry@ry.pl>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=iso-8859-2\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: cplay:287
+msgid "elapsed"
+msgstr "odegranego"
+
+#: cplay:287
+msgid "remaining"
+msgstr "pozostałego"
+
+#: cplay:288
+msgid "Counting %s time"
+msgstr "Licznik %s czasu"
+
+#: cplay:320
+msgid "Quit? (y/N)"
+msgstr "Wyj¶ć (y/N)"
+
+#: cplay:405
+msgid "backward-isearch"
+msgstr "szukanie-wstecz"
+
+#: cplay:407
+msgid "forward-isearch"
+msgstr "szukanie-do przodu"
+
+#: cplay:496 cplay:657 cplay:748 cplay:760 cplay:1148
+msgid "ok"
+msgstr "ok"
+
+#: cplay:505 cplay:517
+msgid "Not found: %s "
+msgstr "Nie znaleziono: %s"
+
+#: cplay:525
+msgid "Help"
+msgstr "Pomoc"
+
+#: cplay:528
+msgid ""
+"\n"
+"  Global                               Space : tag/untag current\n"
+"  ------                               t, T  : tag current/regex\n"
+"  Up, Down, k, j, C-p, C-n,            u, U  : untag current/regex\n"
+"  PgUp, PgDn, K, J,                    i     : invert tags\n"
+"  Home, End, g, G : movement\n"
+"  Enter           : chdir or play      Filelist\n"
+"  Tab             : filelist/playlist  --------\n"
+"  n, p            : next/prev track    BkSpc : parent dir\n"
+"  z, x            : toggle pause/stop  o, s  : goto, search recursively\n"
+"  Left, Right,                         a     : add (tagged) to playlist\n"
+"  C-f, C-b    : seek forward/backward\n"
+"  c           : counter mode           Playlist\n"
+"  C-s, C-r, / : isearch                --------\n"
+"  C-g, Esc    : cancel                 d, D  : delete (tagged) tracks/playlist\n"
+"  1..9, +, -  : volume control         m, M  : move tagged tracks after/before\n"
+"  v           : PCM or MASTER volume   r, R  : toggle repeat/Random mode\n"
+"  C-l, l      : refresh, list mode     s, S  : shuffle/Sort playlist\n"
+"  h, q, Q     : help, quit?, Quit!     w, @  : write playlist, jump to current\n"
+"\n"
+"\n"
+"\n"
+"  Goodies\n"
+"  -------\n"
+"  b, '   : set/get filelist bookmark\n"
+"  X      : stop playlist after each track\n"
+"  C-a, ^ : seek to beginning of current track (restart)\n"
+"  C-e, $ : seek to end of current track (useless?)\n"
+"  !      : shell command ($@ = tagged or current)\n"
+"  "
+msgstr ""
+"\n"
+"  Globalne                             Spacja: zaznacz/odznacz bież±cy\n"
+"  ------                               t, T  : zaznacz bież±cy/regex\n"
+"  Góra, Dół, k, j, C-p, C-n,           u, U  : odznacz bież±cy/regex\n"
+"  PgUp, PgDn, K, J,                    i     : odwróć zaznaczenia\n"
+"  Home, End, g, G : poruszanie się\n"
+"  Enter           : chdir or play      Lista plików\n"
+"  Tab             : filelist/playlist  --------\n"
+"  n, p       : następny/poprzedniutwór BkSpc : katalog macierzysty\n"
+"  z, x       : przeł±cznik pauza/stop  o, s  : idĽ do, szukaj rekursywnie\n"
+"  Lewo, Prawo,                         a     : dodaj (zazanaczone) do listy odgrywania\n"
+"  C-f, C-b    : szukaj w przód/wstecz\n"
+"  c           : tryb licznika          Lista odgrywania\n"
+"  C-s, C-r, / : szukanie               --------\n"
+"  C-g, Esc    : anuluj                 d, D  : delete (tagged) tracks/playlist\n"
+"  1..9, +, -  : kontrola gło¶no¶ci     m, M  : move tagged tracks after/before\n"
+"  v           : gło¶no¶ć PCM lub MASTER r, R  : przeł±cznik trybu powtarzania/losowo¶ci\n"
+"  C-l, l      : od¶wież, tryb listy    s, S  : przetasuj/Sortuj listę odtwarzania\n"
+"  h, q, Q     : pomoc,wyj¶cie?,Wyj¶cie! w, @  : zapisz listę odtwarzania, skocz do bież±cego\n"
+"\n"
+"\n"
+"\n"
+"  Dobrodziejstwa\n"
+"  --------------\n"
+"  b, '   : ustaw/pobierz listę plików z zakładki\n"
+"  X      : zatrzymaj listę utworów po każdym utworze\n"
+"  C-a, ^ : szukaj do pocz±tku od bież±cego utworu (restart)\n"
+"  C-e, $ : szukaj do końca bież±cego utworu (użyteczne?)\n"
+"  !      : komenda powłoki ($@ = zaznaczone lub bież±ce)\n"
+"  "
+
+#: cplay:593
+msgid "filename"
+msgstr "plików"
+
+#: cplay:594
+msgid "pathname"
+msgstr "¶cieżek"
+
+#: cplay:625
+msgid "Listing %s"
+msgstr "Lista %s"
+
+#: cplay:648
+msgid "Tag regexp"
+msgstr "Zaznacz wyrażenie"
+
+#: cplay:648
+msgid "Untag regexp"
+msgstr "Odznacz wyrażenie"
+
+#: cplay:687
+msgid "shell$ "
+msgstr "powłoka$ "
+
+#: cplay:700
+msgid "\nshell returned %s, press return!\n"
+msgstr "\npowłoka zwróciła %s, wci¶nij enter!\n"
+
+#: cplay:734
+msgid "bookmark"
+msgstr "zakładka"
+
+#: cplay:750
+msgid "Not found!"
+msgstr "Nie znaleziono!"
+
+#: cplay:755
+msgid "set bookmark"
+msgstr "ustaw zakładkę"
+
+#: cplay:764
+msgid "search"
+msgstr "szukaj"
+
+#: cplay:771
+msgid "Searching..."
+msgstr "Wyszukiwanie..."
+
+#: cplay:782
+msgid "search results"
+msgstr "wyniki wyszukiwania"
+
+#: cplay:802
+msgid "Filelist: "
+msgstr "Lista plików: "
+
+#: cplay:813
+msgid "Reading directory..."
+msgstr "Wczytywanie katalogu..."
+
+#: cplay:868
+msgid "goto"
+msgstr "idĽ do"
+
+#: cplay:874
+msgid "Not a directory!"
+msgstr "To nie jest katalog!"
+
+#: cplay:919
+msgid "metadata"
+msgstr "metadata"
+
+#: cplay:925
+msgid "Playlist %s %s %s"
+msgstr "Lista utworów %s %s %s"
+
+#: cplay:926
+msgid "[repeat]"
+msgstr "[powtarzanie]"
+
+#: cplay:927
+msgid "[random]"
+msgstr "[losowo]"
+
+#: cplay:928
+msgid "[stop]"
+msgstr "[zatrzymano]"
+
+#: cplay:967
+msgid "File(\\d+)=(.*)"
+msgstr "Plik(\\d+)=(.*)"
+
+#: cplay:981 cplay:1108
+msgid "Working..."
+msgstr "Zajety..."
+
+#: cplay:990
+msgid "Added: %s"
+msgstr "Dodano: %s"
+
+#: cplay:990
+msgid "Deleted playlist"
+msgstr "Skasowano listę utworów"
+
+#: cplay:1105
+msgid "Shuffled playlist... Oops?"
+msgstr "Przetasowano listę utworów... Oops?"
+
+#: cplay:1112
+msgid "Sorted playlist"
+msgstr "Posortowano listę utworów"
+
+#: cplay:1115
+msgid "repeat"
+msgstr "powtarzanie"
+
+#: cplay:1115
+msgid "Repeat: %s"
+msgstr "Powtarzanie: %s"
+
+#: cplay:1118
+msgid "random"
+msgstr "losowanie"
+
+#: cplay:1118
+msgid "Random: %s"
+msgstr "Losowo¶ć: %s"
+
+#: cplay:1124 cplay:1375
+msgid "stop"
+msgstr "zatrzymanie"
+
+#: cplay:1124
+msgid "Stop playlist: %s"
+msgstr "Zatrzymano listę utworów: %s"
+
+#: cplay:1128
+msgid "on"
+msgstr "wł±czone"
+
+#: cplay:1128
+msgid "off"
+msgstr "wył±czone"
+
+#: cplay:1134
+msgid "Save playlist"
+msgstr "Zapisz listę utworów"
+
+#: cplay:1310
+msgid "Stopped: %s"
+msgstr "Zatrzymano: %s"
+
+#: cplay:1312
+msgid "Paused: %s"
+msgstr "Pauza: %s"
+
+#: cplay:1314
+msgid "Playing: %s"
+msgstr "Odtwarzanie: %s"
+
+#: cplay:1318
+msgid "Time.*\\s(\\d+):(\\d+).*\\[(\\d+):(\\d+)"
+msgstr "Czas.*\\s(\\d+):(\\d+).*\\[(\\d+):(\\d+)"
+
+#: cplay:1369
+msgid "pause"
+msgstr "pauza"
+
+#: cplay:1370
+msgid "next"
+msgstr "następny"
+
+#: cplay:1371
+msgid "prev"
+msgstr "poprzedni"
+
+#: cplay:1372
+msgid "forward"
+msgstr "do przodu"
+
+#: cplay:1373
+msgid "backward"
+msgstr "wstecz"
+
+#: cplay:1374
+msgid "play"
+msgstr "odtwarzaj"
+
+#: cplay:1491
+msgid "Player not found!"
+msgstr "Nie znaleziono programu odtwarzaj±cego!"
+
+#: cplay:1548
+msgid "Volume %s%%"
+msgstr "Gło¶no¶ć %s%%"
+
+#: cplay:1556
+msgid "%s volume %s%%"
+msgstr "%s gło¶no¶ć %s%%"
+
+#: cplay:1587
+msgid "cancel"
+msgstr "anulowanie"
+
+#: cplay:1375
+msgid "Usage: %s [-rRv] [ file | dir | playlist ] ...\n"
+msgstr "Użycie: %s [-rRv] [ plik | katalog | lista utworów ] ...\n"
+