summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README35
-rw-r--r--contrib/exec_vorbisgain38
-rw-r--r--cursesmodule/Makefile372
-rw-r--r--cursesmodule/Makefile.pre274
-rw-r--r--cursesmodule/Makefile.pre.in273
-rw-r--r--cursesmodule/README.cursesmodule422
-rw-r--r--cursesmodule/README.precompiled24
-rw-r--r--cursesmodule/Setup414
-rw-r--r--cursesmodule/config.c197
-rw-r--r--cursesmodule/cursesmodule-1.5b2.patch43
-rw-r--r--cursesmodule/jack_cursesmodule.c2280
-rw-r--r--cursesmodule/lifedemo.py215
-rwxr-xr-xcursesmodule/precompiled/Debian-Potato/cursesmodule.sobin0 -> 116022 bytes
-rwxr-xr-xcursesmodule/precompiled/Debian-Woody/jack_cursesmodule.sobin0 -> 111190 bytes
-rwxr-xr-xcursesmodule/precompiled/RedHat-6.0/cursesmodule.sobin0 -> 115774 bytes
-rw-r--r--cursesmodule/sedscript25
-rw-r--r--doc/CHANGELOG116
-rw-r--r--doc/ChangeLog1193
-rw-r--r--doc/INSTALL87
-rw-r--r--doc/TODO12
-rw-r--r--doc/anim.written.in.vi.gifbin0 -> 5815 bytes
-rw-r--r--doc/download.gifbin0 -> 5729 bytes
-rw-r--r--doc/download.html131
-rw-r--r--doc/examples.html96
-rw-r--r--doc/faq.html75
-rw-r--r--doc/gpl.txt340
-rw-r--r--doc/index.html177
-rw-r--r--doc/install.gifbin0 -> 4340 bytes
-rw-r--r--doc/install.html53
-rw-r--r--doc/jack-curses-screen.gifbin0 -> 13521 bytes
-rw-r--r--doc/jack-logo.jpgbin0 -> 9185 bytes
-rw-r--r--doc/jack-screen.gifbin0 -> 9077 bytes
-rw-r--r--doc/links.gifbin0 -> 3892 bytes
-rw-r--r--doc/links.html81
-rw-r--r--doc/main.gifbin0 -> 3763 bytes
-rw-r--r--doc/mine.css96
-rw-r--r--doc/requirements.gifbin0 -> 5337 bytes
-rw-r--r--doc/requirements.html72
-rw-r--r--doc/screen.gifbin0 -> 5094 bytes
-rw-r--r--doc/screen.html62
-rw-r--r--doc/todo.html43
-rw-r--r--doc/usage.gifbin0 -> 4233 bytes
-rw-r--r--doc/usage.html329
-rw-r--r--example.etc.jackrc41
-rwxr-xr-xjack291
-rw-r--r--jack.man543
-rw-r--r--jack_CDTime.py90
-rw-r--r--jack_TOC.py87
-rw-r--r--jack_TOCentry.py71
-rw-r--r--jack_argv.py215
-rwxr-xr-xjack_checkopts.py205
-rwxr-xr-xjack_children.py20
-rw-r--r--jack_config.py780
-rw-r--r--jack_constants.py37
-rwxr-xr-xjack_display.py121
-rwxr-xr-xjack_encstuff.py21
-rw-r--r--jack_freedb.py809
-rwxr-xr-xjack_functions.py463
-rw-r--r--jack_generic.py66
-rw-r--r--jack_globals.py47
-rw-r--r--jack_helpers.py541
-rw-r--r--jack_init.py68
-rw-r--r--jack_m3u.py44
-rwxr-xr-xjack_main_loop.py481
-rw-r--r--jack_misc.py83
-rw-r--r--jack_mp3.py370
-rw-r--r--jack_playorder.py21
-rw-r--r--jack_plugin_cddb.py10
-rw-r--r--jack_plugin_lame.py32
-rw-r--r--jack_plugins.py45
-rw-r--r--jack_prepare.py760
-rwxr-xr-xjack_progress.py20
-rwxr-xr-xjack_rc.py189
-rwxr-xr-xjack_ripstuff.py78
-rwxr-xr-xjack_status.py66
-rwxr-xr-xjack_t_curses.py276
-rwxr-xr-xjack_t_dumb.py71
-rwxr-xr-xjack_tag.py270
-rw-r--r--jack_targets.py65
-rwxr-xr-xjack_term.py198
-rwxr-xr-xjack_utils.py222
-rw-r--r--jack_version.py29
-rwxr-xr-xjack_workers.py372
-rw-r--r--setup.py31
84 files changed, 15824 insertions, 0 deletions
diff --git a/README b/README
new file mode 100644
index 0000000..0f0ea91
--- /dev/null
+++ b/README
@@ -0,0 +1,35 @@
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2003 Arne Zellentin <zarne@users.sf.net>
+
+### 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.
+
+Requirements:
+
+ * python-2.2 or later
+ * CDDB.py - see doc/INSTALL on how to get/install it
+ * eyeD3 - see doc/INSTALL on how to get/install it
+ * an encoder like lame (MP3) or oggenc for Ogg/Vorbis (default)
+ * a DAE tool like cdparanioa
+
+
+*********************************************************************
+*** Read doc/INSTALL for further installation details. ***
+*** It's very unlikely that it'll run out of the box, you need to ***
+*** install additional software. ***
+*********************************************************************
+
+Install the software as described in doc/INSTALL.
+run jack -h for quick help or read the doc/(umentation).
+Use --save to save frequently uses options to your ~/.jack3rc
+
+--
+Jack has his home at http://www.home.unix-ag.org/arne/jack/
+His creators e-mail address is zarne@users.sf.net
diff --git a/contrib/exec_vorbisgain b/contrib/exec_vorbisgain
new file mode 100644
index 0000000..595b116
--- /dev/null
+++ b/contrib/exec_vorbisgain
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# Example script for exec hooks
+# Copyright (C) 2005 Martin Michlmayr <tbm@cyrius.com>
+# This script may be distributed under the GPL v2 or higher.
+
+# Process tracks with vorbisgain to calculate their "ReplayGain"
+# so they can be played with a uniform sound level.
+
+# Usage: put the following in your ~/.jack3rc file (without any leading
+# hash symbols)
+# exec_when_done:yes
+# exec_no_err:"/usr/share/doc/jack/examples/exec_vorbisgain"
+
+# You have to set (and later restore) $IFS in since $JACK_JUST_ENCODED
+# contains a listing of tracks separated by newlines.
+
+OLDIFS="$IFS"
+IFS="
+"
+
+# There are two alternatives:
+# 1. Process all files at once as an album:
+vorbisgain -a $JACK_JUST_ENCODED
+
+# 2. Process each file individually:
+#for i in $JACK_JUST_ENCODED; do
+# IFS="$OLDIFS";
+# vorbisgain "$i"
+# IFS="
+#"
+#done
+
+# In most real world cases, you will want option 1. However, option two
+# is included to show how you can process individual files.
+
+IFS="$OLDIFS"
+
diff --git a/cursesmodule/Makefile b/cursesmodule/Makefile
new file mode 100644
index 0000000..12723a1
--- /dev/null
+++ b/cursesmodule/Makefile
@@ -0,0 +1,372 @@
+# Generated automatically from Makefile.pre by makesetup.
+# Generated automatically from Makefile.pre.in by sedscript.
+# Universal Unix Makefile for Python extensions
+# =============================================
+
+# Short Instructions
+# ------------------
+
+# 1. Build and install Python (1.4 or newer).
+# 2. "make -f Makefile.pre.in boot"
+# 3. "make"
+# You should now have a shared library.
+
+# Long Instructions
+# -----------------
+
+# Build *and install* the basic Python 1.4 distribution. See the
+# Python README for instructions.
+
+# Create a file Setup.in for your extension. This file follows the
+# format of the Modules/Setup.in file; see the instructions there.
+# For a simple module called "spam" on file "spammodule.c", it can
+# contain a single line:
+# spam spammodule.c
+# You can build as many modules as you want in the same directory --
+# just have a separate line for each of them in the Setup.in file.
+
+# If you want to build your extension as a shared library, insert a
+# line containing just the string
+# *shared*
+# at the top of your Setup.in file.
+
+# Note that the build process copies Setup.in to Setup, and then works
+# with Setup. It doesn't overwrite Setup when Setup.in is changed, so
+# while you're in the process of debugging your Setup.in file, you may
+# want to edit Setup instead, and copy it back to Setup.in later.
+# (All this is done so you can distribute your extension easily and
+# someone else can select the modules they actually want to build by
+# commenting out lines in the Setup file, without editing the
+# original. Editing Setup is also used to specify nonstandard
+# locations for include or library files.)
+
+# Copy this file (Misc/Makefile.pre.in) to the directory containing
+# your extension.
+
+# Run "make -f Makefile.pre.in boot". This creates Makefile
+# (producing Makefile.pre and sedscript as intermediate files) and
+# config.c, incorporating the values for sys.prefix, sys.exec_prefix
+# and sys.version from the installed Python binary. For this to work,
+# the python binary must be on your path. If this fails, try
+# make -f Makefile.pre.in Makefile VERSION=1.4 installdir=<prefix>
+# where <prefix> is the prefix used to install Python for installdir
+# (and possibly similar for exec_installdir=<exec_prefix>).
+
+# If you are building your extension as a shared library (your
+# Setup.in file starts with *shared*), run "make" or "make sharedmods"
+# to build the shared library files. If you are building a statically
+# linked Python binary (the only solution of your platform doesn't
+# support shared libraries, and sometimes handy if you want to
+# distribute or install the resulting Python binary), run "make
+# python".
+
+# Note: Each time you edit Makefile.pre.in or Setup, you must run
+# "make Makefile" before running "make".
+
+# Hint: if you want to use VPATH, you can start in an empty
+# subdirectory and say (e.g.):
+# make -f ../Makefile.pre.in boot srcdir=.. VPATH=..
+
+
+# === Bootstrap variables (edited through "make boot") ===
+
+# The prefix used by "make inclinstall libainstall" of core python
+installdir= /usr/local
+
+# The exec_prefix used by the same
+exec_installdir=/usr/local
+
+# Source directory and VPATH in case you want to use VPATH.
+# (You will have to edit these two lines yourself -- there is no
+# automatic support as the Makefile is not generated by
+# config.status.)
+srcdir= .
+VPATH= .
+
+# === Variables that you may want to customize (rarely) ===
+
+# (Static) build target
+TARGET= python
+
+# Add more -I and -D options here
+CFLAGS= $(OPT) -I$(INCLUDEPY) -I$(LIBPL) $(DEFS)
+
+# These two variables can be set in Setup to merge extensions.
+# See example[23].
+BASELIB=
+BASESETUP=
+
+# === Variables set by makesetup ===
+
+MODOBJS= regexmodule.o regexpr.o pcremodule.o pypcre.o posixmodule.o signalmodule.o readline.o arraymodule.o cmathmodule.o mathmodule.o stropmodule.o structmodule.o timemodule.o operator.o _localemodule.o fcntlmodule.o pwdmodule.o grpmodule.o selectmodule.o socketmodule.o errnomodule.o termios.o resource.o md5module.o md5c.o shamodule.o _tkinter.o tkappinit.o rotormodule.o cursesmodule.o newmodule.o gdbmmodule.o binascii.o parsermodule.o cStringIO.o cPickle.o zlibmodule.o
+MODLIBS= $(LOCALMODLIBS) $(BASEMODLIBS)
+
+# === Definitions added by makesetup ===
+
+LOCALMODLIBS= -lreadline -ltermcap -ltk8.0 -ltcl8.0 -L/usr/X11R6/lib -lX11 -lncurses -ltermcap -L/usr/local/lib -lgdbm -L$(exec_prefix)/lib -lz
+BASEMODLIBS= -lreadline -ltermcap -ltk8.0 -ltcl8.0 -L/usr/X11R6/lib -lX11 -L/usr/local/lib -lgdbm -L$(exec_prefix)/lib -lz
+TKPATH=:lib-tk
+GLHACK=-Dclear=__GLclear
+PYTHONPATH=$(COREPYTHONPATH)
+COREPYTHONPATH=$(DESTPATH)$(SITEPATH)$(TESTPATH)$(MACHDEPPATH)$(STDWINPATH)$(TKPATH)
+MACHDEPPATH=:plat-$(MACHDEP)
+TESTPATH=
+SITEPATH=
+DESTPATH=
+MACHDESTLIB=$(BINLIBDEST)
+DESTLIB=$(LIBDEST)
+TKPATH=:lib-tk
+GLHACK=-Dclear=__GLclear
+PYTHONPATH=$(COREPYTHONPATH)
+COREPYTHONPATH=$(DESTPATH)$(SITEPATH)$(TESTPATH)$(MACHDEPPATH)$(STDWINPATH)$(TKPATH)
+MACHDEPPATH=:plat-$(MACHDEP)
+TESTPATH=
+SITEPATH=
+DESTPATH=
+MACHDESTLIB=$(BINLIBDEST)
+DESTLIB=$(LIBDEST)
+
+
+# === Variables from configure (through sedscript) ===
+
+VERSION= 1.5
+CC= gcc
+OPT= -g -O2
+LDFLAGS=
+DEFS= -DHAVE_CONFIG_H
+LIBS= -lieee -ldl -lpthread
+LIBM= -lm
+LIBC=
+RANLIB= ranlib
+MACHDEP= linux2
+SO= .so
+LDSHARED= gcc -shared
+CCSHARED= -fpic
+LINKFORSHARED= -Xlinker -export-dynamic
+
+# Install prefix for architecture-independent files
+prefix= /usr/local
+
+# Install prefix for architecture-dependent files
+exec_prefix= ${prefix}
+
+# === Fixed definitions ===
+
+# Shell used by make (some versions default to the login shell, which is bad)
+SHELL= /bin/sh
+
+# Expanded directories
+BINDIR= $(exec_installdir)/bin
+LIBDIR= $(exec_prefix)/lib
+MANDIR= $(installdir)/man
+INCLUDEDIR= $(installdir)/include
+SCRIPTDIR= $(prefix)/lib
+
+# Detailed destination directories
+BINLIBDEST= $(LIBDIR)/python$(VERSION)
+LIBDEST= $(SCRIPTDIR)/python$(VERSION)
+INCLUDEPY= $(INCLUDEDIR)/python$(VERSION)
+LIBP= $(exec_installdir)/lib/python$(VERSION)
+
+LIBPL= $(LIBP)/config
+
+PYTHONLIBS= $(LIBPL)/libModules.a \
+ $(LIBPL)/libPython.a \
+ $(LIBPL)/libObjects.a \
+ $(LIBPL)/libParser.a
+
+MAKESETUP= $(LIBPL)/makesetup
+MAKEFILE= $(LIBPL)/Makefile
+CONFIGC= $(LIBPL)/config.c
+CONFIGCIN= $(LIBPL)/config.c.in
+SETUP= $(LIBPL)/Setup
+
+SYSLIBS= $(LIBM) $(LIBC)
+
+ADDOBJS= $(LIBPL)/main.o getpath.o config.o
+
+# === Fixed rules ===
+
+# Default target. This builds shared libraries only
+default: sharedmods
+
+# Build everything
+all: static sharedmods
+
+# Build shared libraries from our extension modules
+sharedmods: $(SHAREDMODS)
+
+# Build a static Python binary containing our extension modules
+static: $(TARGET)
+$(TARGET): $(ADDOBJS) lib.a $(PYTHONLIBS) Makefile $(BASELIB)
+ $(CC) $(LDFLAGS) $(ADDOBJS) lib.a $(PYTHONLIBS) \
+ $(LINKPATH) $(BASELIB) $(MODLIBS) $(LIBS) $(SYSLIBS) \
+ -o $(TARGET)
+
+# Build the library containing our extension modules
+lib.a: $(MODOBJS)
+ -rm -f lib.a
+ ar cr lib.a $(MODOBJS)
+ -$(RANLIB) lib.a || \
+ echo "don't worry if ranlib fails -- probably SYSV or equiv"
+
+# This runs makesetup *twice* to use the BASESETUP definition from Setup
+config.c Makefile: Makefile.pre Setup $(BASESETUP) $(MAKESETUP)
+ $(MAKESETUP) \
+ -m Makefile.pre -c $(CONFIGCIN) Setup -n $(BASESETUP) $(SETUP)
+ $(MAKE) -f Makefile do-it-again
+
+# Internal target to run makesetup for the second time
+do-it-again:
+ $(MAKESETUP) \
+ -m Makefile.pre -c $(CONFIGCIN) Setup -n $(BASESETUP) $(SETUP)
+
+# Make config.o from the config.c created by makesetup
+config.o: config.c
+ $(CC) $(CFLAGS) -c config.c
+
+# Make our own private getpath.o from the installed source and our PYTHONPATH
+getpath.o: $(LIBPL)/getpath.c Makefile
+ $(CC) $(CFLAGS) -DPYTHONPATH=\"$(PYTHONPATH)\" -c $(LIBPL)/getpath.c
+
+# Setup is copied from Setup.in *only* if it doesn't yet exist
+Setup:
+ cp $(srcdir)/Setup.in Setup
+
+# Make the intermediate Makefile.pre from Makefile.pre.in
+Makefile.pre: Makefile.pre.in sedscript
+ sed -f sedscript $(srcdir)/Makefile.pre.in >Makefile.pre
+
+# Shortcuts to make the sed arguments on one line
+P=prefix
+E=exec_prefix
+H=Generated automatically from Makefile.pre.in by sedscript.
+L=LINKFORSHARED
+
+# Make the sed script used to create Makefile.pre from Makefile.pre.in
+sedscript: $(MAKEFILE)
+ sed -n \
+ -e '1s/.*/1i\\/p' \
+ -e '2s%.*%# $H%p' \
+ -e '/^VERSION=/s/^VERSION=[ ]*\(.*\)/s%@VERSION[@]%\1%/p' \
+ -e '/^CC=/s/^CC=[ ]*\(.*\)/s%@CC[@]%\1%/p' \
+ -e '/^OPT=/s/^OPT=[ ]*\(.*\)/s%@OPT[@]%\1%/p' \
+ -e '/^LDFLAGS=/s/^LDFLAGS=[ ]*\(.*\)/s%@LDFLAGS[@]%\1%/p' \
+ -e '/^DEFS=/s/^DEFS=[ ]*\(.*\)/s%@DEFS[@]%\1%/p' \
+ -e '/^LIBS=/s/^LIBS=[ ]*\(.*\)/s%@LIBS[@]%\1%/p' \
+ -e '/^LIBM=/s/^LIBM=[ ]*\(.*\)/s%@LIBM[@]%\1%/p' \
+ -e '/^LIBC=/s/^LIBC=[ ]*\(.*\)/s%@LIBC[@]%\1%/p' \
+ -e '/^RANLIB=/s/^RANLIB=[ ]*\(.*\)/s%@RANLIB[@]%\1%/p' \
+ -e '/^MACHDEP=/s/^MACHDEP=[ ]*\(.*\)/s%@MACHDEP[@]%\1%/p' \
+ -e '/^SO=/s/^SO=[ ]*\(.*\)/s%@SO[@]%\1%/p' \
+ -e '/^LDSHARED=/s/^LDSHARED=[ ]*\(.*\)/s%@LDSHARED[@]%\1%/p' \
+ -e '/^CCSHARED=/s/^CCSHARED=[ ]*\(.*\)/s%@CCSHARED[@]%\1%/p' \
+ -e '/^$L=/s/^$L=[ ]*\(.*\)/s%@$L[@]%\1%/p' \
+ -e '/^$P=/s/^$P=\(.*\)/s%^$P=.*%$P=\1%/p' \
+ -e '/^$E=/s/^$E=\(.*\)/s%^$E=.*%$E=\1%/p' \
+ $(MAKEFILE) >sedscript
+ echo "/^installdir=/s%=.*%= $(installdir)%" >>sedscript
+ echo "/^exec_installdir=/s%=.*%=$(exec_installdir)%" >>sedscript
+ echo "/^srcdir=/s%=.*%= $(srcdir)%" >>sedscript
+ echo "/^VPATH=/s%=.*%= $(VPATH)%" >>sedscript
+ echo "/^LINKPATH=/s%=.*%= $(LINKPATH)%" >>sedscript
+ echo "/^BASELIB=/s%=.*%= $(BASELIB)%" >>sedscript
+ echo "/^BASESETUP=/s%=.*%= $(BASESETUP)%" >>sedscript
+
+# Bootstrap target
+boot: clobber
+ VERSION=`python -c "import sys; print sys.version[:3]"`; \
+ installdir=`python -c "import sys; print sys.prefix"`; \
+ exec_installdir=`python -c "import sys; print sys.exec_prefix"`; \
+ $(MAKE) -f $(srcdir)/Makefile.pre.in VPATH=$(VPATH) srcdir=$(srcdir) \
+ VERSION=$$VERSION \
+ installdir=$$installdir \
+ exec_installdir=$$exec_installdir \
+ Makefile
+
+# Handy target to remove intermediate files and backups
+clean:
+ -rm -f *.o *~
+
+# Handy target to remove everything that is easily regenerated
+clobber: clean
+ -rm -f *.a tags TAGS config.c Makefile.pre python sedscript
+ -rm -f *.so *.sl so_locations
+
+
+# Handy target to remove everything you don't want to distribute
+distclean: clobber
+ -rm -f Makefile Setup
+
+# Rules appended by makedepend
+
+regexmodule.o: $(srcdir)/regexmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/regexmodule.c
+regexpr.o: $(srcdir)/regexpr.c; $(CC) $(CFLAGS) -c $(srcdir)/regexpr.c
+regexmodule$(SO): regexmodule.o regexpr.o; $(LDSHARED) regexmodule.o regexpr.o -o regexmodule$(SO)
+pcremodule.o: $(srcdir)/pcremodule.c; $(CC) $(CFLAGS) -c $(srcdir)/pcremodule.c
+pypcre.o: $(srcdir)/pypcre.c; $(CC) $(CFLAGS) -c $(srcdir)/pypcre.c
+pcre$(SO): pcremodule.o pypcre.o; $(LDSHARED) pcremodule.o pypcre.o -o pcre$(SO)
+posixmodule.o: $(srcdir)/posixmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/posixmodule.c
+posixmodule$(SO): posixmodule.o; $(LDSHARED) posixmodule.o -o posixmodule$(SO)
+signalmodule.o: $(srcdir)/signalmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/signalmodule.c
+signalmodule$(SO): signalmodule.o; $(LDSHARED) signalmodule.o -o signalmodule$(SO)
+readline.o: $(srcdir)/readline.c; $(CC) $(CFLAGS) -c $(srcdir)/readline.c
+readline$(SO): readline.o; $(LDSHARED) readline.o -lreadline -ltermcap -o readline$(SO)
+arraymodule.o: $(srcdir)/arraymodule.c; $(CC) $(CFLAGS) -c $(srcdir)/arraymodule.c
+arraymodule$(SO): arraymodule.o; $(LDSHARED) arraymodule.o -o arraymodule$(SO)
+cmathmodule.o: $(srcdir)/cmathmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/cmathmodule.c
+cmathmodule$(SO): cmathmodule.o; $(LDSHARED) cmathmodule.o -o cmathmodule$(SO)
+mathmodule.o: $(srcdir)/mathmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/mathmodule.c
+mathmodule$(SO): mathmodule.o; $(LDSHARED) mathmodule.o -o mathmodule$(SO)
+stropmodule.o: $(srcdir)/stropmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/stropmodule.c
+stropmodule$(SO): stropmodule.o; $(LDSHARED) stropmodule.o -o stropmodule$(SO)
+structmodule.o: $(srcdir)/structmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/structmodule.c
+structmodule$(SO): structmodule.o; $(LDSHARED) structmodule.o -o structmodule$(SO)
+timemodule.o: $(srcdir)/timemodule.c; $(CC) $(CFLAGS) -c $(srcdir)/timemodule.c
+timemodule$(SO): timemodule.o; $(LDSHARED) timemodule.o -o timemodule$(SO)
+operator.o: $(srcdir)/operator.c; $(CC) $(CFLAGS) -c $(srcdir)/operator.c
+operator$(SO): operator.o; $(LDSHARED) operator.o -o operator$(SO)
+_localemodule.o: $(srcdir)/_localemodule.c; $(CC) $(CFLAGS) -c $(srcdir)/_localemodule.c
+_localemodule$(SO): _localemodule.o; $(LDSHARED) _localemodule.o -o _localemodule$(SO)
+fcntlmodule.o: $(srcdir)/fcntlmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/fcntlmodule.c
+fcntlmodule$(SO): fcntlmodule.o; $(LDSHARED) fcntlmodule.o -o fcntlmodule$(SO)
+pwdmodule.o: $(srcdir)/pwdmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/pwdmodule.c
+pwdmodule$(SO): pwdmodule.o; $(LDSHARED) pwdmodule.o -o pwdmodule$(SO)
+grpmodule.o: $(srcdir)/grpmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/grpmodule.c
+grpmodule$(SO): grpmodule.o; $(LDSHARED) grpmodule.o -o grpmodule$(SO)
+selectmodule.o: $(srcdir)/selectmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/selectmodule.c
+selectmodule$(SO): selectmodule.o; $(LDSHARED) selectmodule.o -o selectmodule$(SO)
+socketmodule.o: $(srcdir)/socketmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/socketmodule.c
+socketmodule$(SO): socketmodule.o; $(LDSHARED) socketmodule.o -o socketmodule$(SO)
+errnomodule.o: $(srcdir)/errnomodule.c; $(CC) $(CFLAGS) -c $(srcdir)/errnomodule.c
+errnomodule$(SO): errnomodule.o; $(LDSHARED) errnomodule.o -o errnomodule$(SO)
+termios.o: $(srcdir)/termios.c; $(CC) $(CFLAGS) -c $(srcdir)/termios.c
+termios$(SO): termios.o; $(LDSHARED) termios.o -o termios$(SO)
+resource.o: $(srcdir)/resource.c; $(CC) $(CFLAGS) -c $(srcdir)/resource.c
+resource$(SO): resource.o; $(LDSHARED) resource.o -o resource$(SO)
+md5module.o: $(srcdir)/md5module.c; $(CC) $(CFLAGS) -c $(srcdir)/md5module.c
+md5c.o: $(srcdir)/md5c.c; $(CC) $(CFLAGS) -c $(srcdir)/md5c.c
+md5module$(SO): md5module.o md5c.o; $(LDSHARED) md5module.o md5c.o -o md5module$(SO)
+shamodule.o: $(srcdir)/shamodule.c; $(CC) $(CFLAGS) -c $(srcdir)/shamodule.c
+shamodule$(SO): shamodule.o; $(LDSHARED) shamodule.o -o shamodule$(SO)
+_tkinter.o: $(srcdir)/_tkinter.c; $(CC) -DWITH_APPINIT $(CFLAGS) -c $(srcdir)/_tkinter.c
+tkappinit.o: $(srcdir)/tkappinit.c; $(CC) -DWITH_APPINIT $(CFLAGS) -c $(srcdir)/tkappinit.c
+_tkinter$(SO): _tkinter.o tkappinit.o; $(LDSHARED) _tkinter.o tkappinit.o -ltk8.0 -ltcl8.0 -L/usr/X11R6/lib -lX11 -o _tkinter$(SO)
+rotormodule.o: $(srcdir)/rotormodule.c; $(CC) $(CFLAGS) -c $(srcdir)/rotormodule.c
+rotormodule$(SO): rotormodule.o; $(LDSHARED) rotormodule.o -o rotormodule$(SO)
+cursesmodule.o: $(srcdir)/cursesmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/cursesmodule.c
+cursesmodule$(SO): cursesmodule.o; $(LDSHARED) cursesmodule.o -lncurses -ltermcap -o cursesmodule$(SO)
+newmodule.o: $(srcdir)/newmodule.c; $(CC) $(CFLAGS) -c $(srcdir)/newmodule.c
+newmodule$(SO): newmodule.o; $(LDSHARED) newmodule.o -o newmodule$(SO)
+gdbmmodule.o: $(srcdir)/gdbmmodule.c; $(CC) -I/usr/local/include $(CFLAGS) -c $(srcdir)/gdbmmodule.c
+gdbmmodule$(SO): gdbmmodule.o; $(LDSHARED) gdbmmodule.o -L/usr/local/lib -lgdbm -o gdbmmodule$(SO)
+binascii.o: $(srcdir)/binascii.c; $(CC) $(CFLAGS) -c $(srcdir)/binascii.c
+binascii$(SO): binascii.o; $(LDSHARED) binascii.o -o binascii$(SO)
+parsermodule.o: $(srcdir)/parsermodule.c; $(CC) $(CFLAGS) -c $(srcdir)/parsermodule.c
+parsermodule$(SO): parsermodule.o; $(LDSHARED) parsermodule.o -o parsermodule$(SO)
+cStringIO.o: $(srcdir)/cStringIO.c; $(CC) $(CFLAGS) -c $(srcdir)/cStringIO.c
+cStringIO$(SO): cStringIO.o; $(LDSHARED) cStringIO.o -o cStringIO$(SO)
+cPickle.o: $(srcdir)/cPickle.c; $(CC) $(CFLAGS) -c $(srcdir)/cPickle.c
+cPickle$(SO): cPickle.o; $(LDSHARED) cPickle.o -o cPickle$(SO)
+zlibmodule.o: $(srcdir)/zlibmodule.c; $(CC) -I$(prefix)/include $(CFLAGS) -c $(srcdir)/zlibmodule.c
+zlibmodule$(SO): zlibmodule.o; $(LDSHARED) zlibmodule.o -L$(exec_prefix)/lib -lz -o zlibmodule$(SO)
diff --git a/cursesmodule/Makefile.pre b/cursesmodule/Makefile.pre
new file mode 100644
index 0000000..03834a9
--- /dev/null
+++ b/cursesmodule/Makefile.pre
@@ -0,0 +1,274 @@
+# Generated automatically from Makefile.pre.in by sedscript.
+# Universal Unix Makefile for Python extensions
+# =============================================
+
+# Short Instructions
+# ------------------
+
+# 1. Build and install Python (1.4 or newer).
+# 2. "make -f Makefile.pre.in boot"
+# 3. "make"
+# You should now have a shared library.
+
+# Long Instructions
+# -----------------
+
+# Build *and install* the basic Python 1.4 distribution. See the
+# Python README for instructions.
+
+# Create a file Setup.in for your extension. This file follows the
+# format of the Modules/Setup.in file; see the instructions there.
+# For a simple module called "spam" on file "spammodule.c", it can
+# contain a single line:
+# spam spammodule.c
+# You can build as many modules as you want in the same directory --
+# just have a separate line for each of them in the Setup.in file.
+
+# If you want to build your extension as a shared library, insert a
+# line containing just the string
+# *shared*
+# at the top of your Setup.in file.
+
+# Note that the build process copies Setup.in to Setup, and then works
+# with Setup. It doesn't overwrite Setup when Setup.in is changed, so
+# while you're in the process of debugging your Setup.in file, you may
+# want to edit Setup instead, and copy it back to Setup.in later.
+# (All this is done so you can distribute your extension easily and
+# someone else can select the modules they actually want to build by
+# commenting out lines in the Setup file, without editing the
+# original. Editing Setup is also used to specify nonstandard
+# locations for include or library files.)
+
+# Copy this file (Misc/Makefile.pre.in) to the directory containing
+# your extension.
+
+# Run "make -f Makefile.pre.in boot". This creates Makefile
+# (producing Makefile.pre and sedscript as intermediate files) and
+# config.c, incorporating the values for sys.prefix, sys.exec_prefix
+# and sys.version from the installed Python binary. For this to work,
+# the python binary must be on your path. If this fails, try
+# make -f Makefile.pre.in Makefile VERSION=1.4 installdir=<prefix>
+# where <prefix> is the prefix used to install Python for installdir
+# (and possibly similar for exec_installdir=<exec_prefix>).
+
+# If you are building your extension as a shared library (your
+# Setup.in file starts with *shared*), run "make" or "make sharedmods"
+# to build the shared library files. If you are building a statically
+# linked Python binary (the only solution of your platform doesn't
+# support shared libraries, and sometimes handy if you want to
+# distribute or install the resulting Python binary), run "make
+# python".
+
+# Note: Each time you edit Makefile.pre.in or Setup, you must run
+# "make Makefile" before running "make".
+
+# Hint: if you want to use VPATH, you can start in an empty
+# subdirectory and say (e.g.):
+# make -f ../Makefile.pre.in boot srcdir=.. VPATH=..
+
+
+# === Bootstrap variables (edited through "make boot") ===
+
+# The prefix used by "make inclinstall libainstall" of core python
+installdir= /usr/local
+
+# The exec_prefix used by the same
+exec_installdir=/usr/local
+
+# Source directory and VPATH in case you want to use VPATH.
+# (You will have to edit these two lines yourself -- there is no
+# automatic support as the Makefile is not generated by
+# config.status.)
+srcdir= .
+VPATH= .
+
+# === Variables that you may want to customize (rarely) ===
+
+# (Static) build target
+TARGET= python
+
+# Add more -I and -D options here
+CFLAGS= $(OPT) -I$(INCLUDEPY) -I$(LIBPL) $(DEFS)
+
+# These two variables can be set in Setup to merge extensions.
+# See example[23].
+BASELIB=
+BASESETUP=
+
+# === Variables set by makesetup ===
+
+MODOBJS= _MODOBJS_
+MODLIBS= _MODLIBS_
+
+# === Definitions added by makesetup ===
+
+# === Variables from configure (through sedscript) ===
+
+VERSION= 1.5
+CC= gcc
+OPT= -g -O2
+LDFLAGS=
+DEFS= -DHAVE_CONFIG_H
+LIBS= -lieee -ldl -lpthread
+LIBM= -lm
+LIBC=
+RANLIB= ranlib
+MACHDEP= linux2
+SO= .so
+LDSHARED= gcc -shared
+CCSHARED= -fpic
+LINKFORSHARED= -Xlinker -export-dynamic
+
+# Install prefix for architecture-independent files
+prefix= /usr/local
+
+# Install prefix for architecture-dependent files
+exec_prefix= ${prefix}
+
+# === Fixed definitions ===
+
+# Shell used by make (some versions default to the login shell, which is bad)
+SHELL= /bin/sh
+
+# Expanded directories
+BINDIR= $(exec_installdir)/bin
+LIBDIR= $(exec_prefix)/lib
+MANDIR= $(installdir)/man
+INCLUDEDIR= $(installdir)/include
+SCRIPTDIR= $(prefix)/lib
+
+# Detailed destination directories
+BINLIBDEST= $(LIBDIR)/python$(VERSION)
+LIBDEST= $(SCRIPTDIR)/python$(VERSION)
+INCLUDEPY= $(INCLUDEDIR)/python$(VERSION)
+LIBP= $(exec_installdir)/lib/python$(VERSION)
+
+LIBPL= $(LIBP)/config
+
+PYTHONLIBS= $(LIBPL)/libModules.a \
+ $(LIBPL)/libPython.a \
+ $(LIBPL)/libObjects.a \
+ $(LIBPL)/libParser.a
+
+MAKESETUP= $(LIBPL)/makesetup
+MAKEFILE= $(LIBPL)/Makefile
+CONFIGC= $(LIBPL)/config.c
+CONFIGCIN= $(LIBPL)/config.c.in
+SETUP= $(LIBPL)/Setup
+
+SYSLIBS= $(LIBM) $(LIBC)
+
+ADDOBJS= $(LIBPL)/main.o getpath.o config.o
+
+# === Fixed rules ===
+
+# Default target. This builds shared libraries only
+default: sharedmods
+
+# Build everything
+all: static sharedmods
+
+# Build shared libraries from our extension modules
+sharedmods: $(SHAREDMODS)
+
+# Build a static Python binary containing our extension modules
+static: $(TARGET)
+$(TARGET): $(ADDOBJS) lib.a $(PYTHONLIBS) Makefile $(BASELIB)
+ $(CC) $(LDFLAGS) $(ADDOBJS) lib.a $(PYTHONLIBS) \
+ $(LINKPATH) $(BASELIB) $(MODLIBS) $(LIBS) $(SYSLIBS) \
+ -o $(TARGET)
+
+# Build the library containing our extension modules
+lib.a: $(MODOBJS)
+ -rm -f lib.a
+ ar cr lib.a $(MODOBJS)
+ -$(RANLIB) lib.a || \
+ echo "don't worry if ranlib fails -- probably SYSV or equiv"
+
+# This runs makesetup *twice* to use the BASESETUP definition from Setup
+config.c Makefile: Makefile.pre Setup $(BASESETUP) $(MAKESETUP)
+ $(MAKESETUP) \
+ -m Makefile.pre -c $(CONFIGCIN) Setup -n $(BASESETUP) $(SETUP)
+ $(MAKE) -f Makefile do-it-again
+
+# Internal target to run makesetup for the second time
+do-it-again:
+ $(MAKESETUP) \
+ -m Makefile.pre -c $(CONFIGCIN) Setup -n $(BASESETUP) $(SETUP)
+
+# Make config.o from the config.c created by makesetup
+config.o: config.c
+ $(CC) $(CFLAGS) -c config.c
+
+# Make our own private getpath.o from the installed source and our PYTHONPATH
+getpath.o: $(LIBPL)/getpath.c Makefile
+ $(CC) $(CFLAGS) -DPYTHONPATH=\"$(PYTHONPATH)\" -c $(LIBPL)/getpath.c
+
+# Setup is copied from Setup.in *only* if it doesn't yet exist
+Setup:
+ cp $(srcdir)/Setup.in Setup
+
+# Make the intermediate Makefile.pre from Makefile.pre.in
+Makefile.pre: Makefile.pre.in sedscript
+ sed -f sedscript $(srcdir)/Makefile.pre.in >Makefile.pre
+
+# Shortcuts to make the sed arguments on one line
+P=prefix
+E=exec_prefix
+H=Generated automatically from Makefile.pre.in by sedscript.
+L=LINKFORSHARED
+
+# Make the sed script used to create Makefile.pre from Makefile.pre.in
+sedscript: $(MAKEFILE)
+ sed -n \
+ -e '1s/.*/1i\\/p' \
+ -e '2s%.*%# $H%p' \
+ -e '/^VERSION=/s/^VERSION=[ ]*\(.*\)/s%@VERSION[@]%\1%/p' \
+ -e '/^CC=/s/^CC=[ ]*\(.*\)/s%@CC[@]%\1%/p' \
+ -e '/^OPT=/s/^OPT=[ ]*\(.*\)/s%@OPT[@]%\1%/p' \
+ -e '/^LDFLAGS=/s/^LDFLAGS=[ ]*\(.*\)/s%@LDFLAGS[@]%\1%/p' \
+ -e '/^DEFS=/s/^DEFS=[ ]*\(.*\)/s%@DEFS[@]%\1%/p' \
+ -e '/^LIBS=/s/^LIBS=[ ]*\(.*\)/s%@LIBS[@]%\1%/p' \
+ -e '/^LIBM=/s/^LIBM=[ ]*\(.*\)/s%@LIBM[@]%\1%/p' \
+ -e '/^LIBC=/s/^LIBC=[ ]*\(.*\)/s%@LIBC[@]%\1%/p' \
+ -e '/^RANLIB=/s/^RANLIB=[ ]*\(.*\)/s%@RANLIB[@]%\1%/p' \
+ -e '/^MACHDEP=/s/^MACHDEP=[ ]*\(.*\)/s%@MACHDEP[@]%\1%/p' \
+ -e '/^SO=/s/^SO=[ ]*\(.*\)/s%@SO[@]%\1%/p' \
+ -e '/^LDSHARED=/s/^LDSHARED=[ ]*\(.*\)/s%@LDSHARED[@]%\1%/p' \
+ -e '/^CCSHARED=/s/^CCSHARED=[ ]*\(.*\)/s%@CCSHARED[@]%\1%/p' \
+ -e '/^$L=/s/^$L=[ ]*\(.*\)/s%@$L[@]%\1%/p' \
+ -e '/^$P=/s/^$P=\(.*\)/s%^$P=.*%$P=\1%/p' \
+ -e '/^$E=/s/^$E=\(.*\)/s%^$E=.*%$E=\1%/p' \
+ $(MAKEFILE) >sedscript
+ echo "/^installdir=/s%=.*%= $(installdir)%" >>sedscript
+ echo "/^exec_installdir=/s%=.*%=$(exec_installdir)%" >>sedscript
+ echo "/^srcdir=/s%=.*%= $(srcdir)%" >>sedscript
+ echo "/^VPATH=/s%=.*%= $(VPATH)%" >>sedscript
+ echo "/^LINKPATH=/s%=.*%= $(LINKPATH)%" >>sedscript
+ echo "/^BASELIB=/s%=.*%= $(BASELIB)%" >>sedscript
+ echo "/^BASESETUP=/s%=.*%= $(BASESETUP)%" >>sedscript
+
+# Bootstrap target
+boot: clobber
+ VERSION=`python -c "import sys; print sys.version[:3]"`; \
+ installdir=`python -c "import sys; print sys.prefix"`; \
+ exec_installdir=`python -c "import sys; print sys.exec_prefix"`; \
+ $(MAKE) -f $(srcdir)/Makefile.pre.in VPATH=$(VPATH) srcdir=$(srcdir) \
+ VERSION=$$VERSION \
+ installdir=$$installdir \
+ exec_installdir=$$exec_installdir \
+ Makefile
+
+# Handy target to remove intermediate files and backups
+clean:
+ -rm -f *.o *~
+
+# Handy target to remove everything that is easily regenerated
+clobber: clean
+ -rm -f *.a tags TAGS config.c Makefile.pre python sedscript
+ -rm -f *.so *.sl so_locations
+
+
+# Handy target to remove everything you don't want to distribute
+distclean: clobber
+ -rm -f Makefile Setup
diff --git a/cursesmodule/Makefile.pre.in b/cursesmodule/Makefile.pre.in
new file mode 100644
index 0000000..32e59d4
--- /dev/null
+++ b/cursesmodule/Makefile.pre.in
@@ -0,0 +1,273 @@
+# Universal Unix Makefile for Python extensions
+# =============================================
+
+# Short Instructions
+# ------------------
+
+# 1. Build and install Python (1.4 or newer).
+# 2. "make -f Makefile.pre.in boot"
+# 3. "make"
+# You should now have a shared library.
+
+# Long Instructions
+# -----------------
+
+# Build *and install* the basic Python 1.4 distribution. See the
+# Python README for instructions.
+
+# Create a file Setup.in for your extension. This file follows the
+# format of the Modules/Setup.in file; see the instructions there.
+# For a simple module called "spam" on file "spammodule.c", it can
+# contain a single line:
+# spam spammodule.c
+# You can build as many modules as you want in the same directory --
+# just have a separate line for each of them in the Setup.in file.
+
+# If you want to build your extension as a shared library, insert a
+# line containing just the string
+# *shared*
+# at the top of your Setup.in file.
+
+# Note that the build process copies Setup.in to Setup, and then works
+# with Setup. It doesn't overwrite Setup when Setup.in is changed, so
+# while you're in the process of debugging your Setup.in file, you may
+# want to edit Setup instead, and copy it back to Setup.in later.
+# (All this is done so you can distribute your extension easily and
+# someone else can select the modules they actually want to build by
+# commenting out lines in the Setup file, without editing the
+# original. Editing Setup is also used to specify nonstandard
+# locations for include or library files.)
+
+# Copy this file (Misc/Makefile.pre.in) to the directory containing
+# your extension.
+
+# Run "make -f Makefile.pre.in boot". This creates Makefile
+# (producing Makefile.pre and sedscript as intermediate files) and
+# config.c, incorporating the values for sys.prefix, sys.exec_prefix
+# and sys.version from the installed Python binary. For this to work,
+# the python binary must be on your path. If this fails, try
+# make -f Makefile.pre.in Makefile VERSION=1.4 installdir=<prefix>
+# where <prefix> is the prefix used to install Python for installdir
+# (and possibly similar for exec_installdir=<exec_prefix>).
+
+# If you are building your extension as a shared library (your
+# Setup.in file starts with *shared*), run "make" or "make sharedmods"
+# to build the shared library files. If you are building a statically
+# linked Python binary (the only solution of your platform doesn't
+# support shared libraries, and sometimes handy if you want to
+# distribute or install the resulting Python binary), run "make
+# python".
+
+# Note: Each time you edit Makefile.pre.in or Setup, you must run
+# "make Makefile" before running "make".
+
+# Hint: if you want to use VPATH, you can start in an empty
+# subdirectory and say (e.g.):
+# make -f ../Makefile.pre.in boot srcdir=.. VPATH=..
+
+
+# === Bootstrap variables (edited through "make boot") ===
+
+# The prefix used by "make inclinstall libainstall" of core python
+installdir= /usr/local
+
+# The exec_prefix used by the same
+exec_installdir=$(installdir)
+
+# Source directory and VPATH in case you want to use VPATH.
+# (You will have to edit these two lines yourself -- there is no
+# automatic support as the Makefile is not generated by
+# config.status.)
+srcdir= .
+VPATH= .
+
+# === Variables that you may want to customize (rarely) ===
+
+# (Static) build target
+TARGET= python
+
+# Add more -I and -D options here
+CFLAGS= $(OPT) -I$(INCLUDEPY) -I$(LIBPL) $(DEFS)
+
+# These two variables can be set in Setup to merge extensions.
+# See example[23].
+BASELIB=
+BASESETUP=
+
+# === Variables set by makesetup ===
+
+MODOBJS= _MODOBJS_
+MODLIBS= _MODLIBS_
+
+# === Definitions added by makesetup ===
+
+# === Variables from configure (through sedscript) ===
+
+VERSION= @VERSION@
+CC= @CC@
+OPT= @OPT@
+LDFLAGS= @LDFLAGS@
+DEFS= @DEFS@
+LIBS= @LIBS@
+LIBM= @LIBM@
+LIBC= @LIBC@
+RANLIB= @RANLIB@
+MACHDEP= @MACHDEP@
+SO= @SO@
+LDSHARED= @LDSHARED@
+CCSHARED= @CCSHARED@
+LINKFORSHARED= @LINKFORSHARED@
+
+# Install prefix for architecture-independent files
+prefix= /usr/local
+
+# Install prefix for architecture-dependent files
+exec_prefix= $(prefix)
+
+# === Fixed definitions ===
+
+# Shell used by make (some versions default to the login shell, which is bad)
+SHELL= /bin/sh
+
+# Expanded directories
+BINDIR= $(exec_installdir)/bin
+LIBDIR= $(exec_prefix)/lib
+MANDIR= $(installdir)/man
+INCLUDEDIR= $(installdir)/include
+SCRIPTDIR= $(prefix)/lib
+
+# Detailed destination directories
+BINLIBDEST= $(LIBDIR)/python$(VERSION)
+LIBDEST= $(SCRIPTDIR)/python$(VERSION)
+INCLUDEPY= $(INCLUDEDIR)/python$(VERSION)
+LIBP= $(exec_installdir)/lib/python$(VERSION)
+
+LIBPL= $(LIBP)/config
+
+PYTHONLIBS= $(LIBPL)/libModules.a \
+ $(LIBPL)/libPython.a \
+ $(LIBPL)/libObjects.a \
+ $(LIBPL)/libParser.a
+
+MAKESETUP= $(LIBPL)/makesetup
+MAKEFILE= $(LIBPL)/Makefile
+CONFIGC= $(LIBPL)/config.c
+CONFIGCIN= $(LIBPL)/config.c.in
+SETUP= $(LIBPL)/Setup
+
+SYSLIBS= $(LIBM) $(LIBC)
+
+ADDOBJS= $(LIBPL)/main.o getpath.o config.o
+
+# === Fixed rules ===
+
+# Default target. This builds shared libraries only
+default: sharedmods
+
+# Build everything
+all: static sharedmods
+
+# Build shared libraries from our extension modules
+sharedmods: $(SHAREDMODS)
+
+# Build a static Python binary containing our extension modules
+static: $(TARGET)
+$(TARGET): $(ADDOBJS) lib.a $(PYTHONLIBS) Makefile $(BASELIB)
+ $(CC) $(LDFLAGS) $(ADDOBJS) lib.a $(PYTHONLIBS) \
+ $(LINKPATH) $(BASELIB) $(MODLIBS) $(LIBS) $(SYSLIBS) \
+ -o $(TARGET)
+
+# Build the library containing our extension modules
+lib.a: $(MODOBJS)
+ -rm -f lib.a
+ ar cr lib.a $(MODOBJS)
+ -$(RANLIB) lib.a || \
+ echo "don't worry if ranlib fails -- probably SYSV or equiv"
+
+# This runs makesetup *twice* to use the BASESETUP definition from Setup
+config.c Makefile: Makefile.pre Setup $(BASESETUP) $(MAKESETUP)
+ $(MAKESETUP) \
+ -m Makefile.pre -c $(CONFIGCIN) Setup -n $(BASESETUP) $(SETUP)
+ $(MAKE) -f Makefile do-it-again
+
+# Internal target to run makesetup for the second time
+do-it-again:
+ $(MAKESETUP) \
+ -m Makefile.pre -c $(CONFIGCIN) Setup -n $(BASESETUP) $(SETUP)
+
+# Make config.o from the config.c created by makesetup
+config.o: config.c
+ $(CC) $(CFLAGS) -c config.c
+
+# Make our own private getpath.o from the installed source and our PYTHONPATH
+getpath.o: $(LIBPL)/getpath.c Makefile
+ $(CC) $(CFLAGS) -DPYTHONPATH=\"$(PYTHONPATH)\" -c $(LIBPL)/getpath.c
+
+# Setup is copied from Setup.in *only* if it doesn't yet exist
+Setup:
+ cp $(srcdir)/Setup.in Setup
+
+# Make the intermediate Makefile.pre from Makefile.pre.in
+Makefile.pre: Makefile.pre.in sedscript
+ sed -f sedscript $(srcdir)/Makefile.pre.in >Makefile.pre
+
+# Shortcuts to make the sed arguments on one line
+P=prefix
+E=exec_prefix
+H=Generated automatically from Makefile.pre.in by sedscript.
+L=LINKFORSHARED
+
+# Make the sed script used to create Makefile.pre from Makefile.pre.in
+sedscript: $(MAKEFILE)
+ sed -n \
+ -e '1s/.*/1i\\/p' \
+ -e '2s%.*%# $H%p' \
+ -e '/^VERSION=/s/^VERSION=[ ]*\(.*\)/s%@VERSION[@]%\1%/p' \
+ -e '/^CC=/s/^CC=[ ]*\(.*\)/s%@CC[@]%\1%/p' \
+ -e '/^OPT=/s/^OPT=[ ]*\(.*\)/s%@OPT[@]%\1%/p' \
+ -e '/^LDFLAGS=/s/^LDFLAGS=[ ]*\(.*\)/s%@LDFLAGS[@]%\1%/p' \
+ -e '/^DEFS=/s/^DEFS=[ ]*\(.*\)/s%@DEFS[@]%\1%/p' \
+ -e '/^LIBS=/s/^LIBS=[ ]*\(.*\)/s%@LIBS[@]%\1%/p' \
+ -e '/^LIBM=/s/^LIBM=[ ]*\(.*\)/s%@LIBM[@]%\1%/p' \
+ -e '/^LIBC=/s/^LIBC=[ ]*\(.*\)/s%@LIBC[@]%\1%/p' \
+ -e '/^RANLIB=/s/^RANLIB=[ ]*\(.*\)/s%@RANLIB[@]%\1%/p' \
+ -e '/^MACHDEP=/s/^MACHDEP=[ ]*\(.*\)/s%@MACHDEP[@]%\1%/p' \
+ -e '/^SO=/s/^SO=[ ]*\(.*\)/s%@SO[@]%\1%/p' \
+ -e '/^LDSHARED=/s/^LDSHARED=[ ]*\(.*\)/s%@LDSHARED[@]%\1%/p' \
+ -e '/^CCSHARED=/s/^CCSHARED=[ ]*\(.*\)/s%@CCSHARED[@]%\1%/p' \
+ -e '/^$L=/s/^$L=[ ]*\(.*\)/s%@$L[@]%\1%/p' \
+ -e '/^$P=/s/^$P=\(.*\)/s%^$P=.*%$P=\1%/p' \
+ -e '/^$E=/s/^$E=\(.*\)/s%^$E=.*%$E=\1%/p' \
+ $(MAKEFILE) >sedscript
+ echo "/^installdir=/s%=.*%= $(installdir)%" >>sedscript
+ echo "/^exec_installdir=/s%=.*%=$(exec_installdir)%" >>sedscript
+ echo "/^srcdir=/s%=.*%= $(srcdir)%" >>sedscript
+ echo "/^VPATH=/s%=.*%= $(VPATH)%" >>sedscript
+ echo "/^LINKPATH=/s%=.*%= $(LINKPATH)%" >>sedscript
+ echo "/^BASELIB=/s%=.*%= $(BASELIB)%" >>sedscript
+ echo "/^BASESETUP=/s%=.*%= $(BASESETUP)%" >>sedscript
+
+# Bootstrap target
+boot: clobber
+ VERSION=`python -c "import sys; print sys.version[:3]"`; \
+ installdir=`python -c "import sys; print sys.prefix"`; \
+ exec_installdir=`python -c "import sys; print sys.exec_prefix"`; \
+ $(MAKE) -f $(srcdir)/Makefile.pre.in VPATH=$(VPATH) srcdir=$(srcdir) \
+ VERSION=$$VERSION \
+ installdir=$$installdir \
+ exec_installdir=$$exec_installdir \
+ Makefile
+
+# Handy target to remove intermediate files and backups
+clean:
+ -rm -f *.o *~
+
+# Handy target to remove everything that is easily regenerated
+clobber: clean
+ -rm -f *.a tags TAGS config.c Makefile.pre python sedscript
+ -rm -f *.so *.sl so_locations
+
+
+# Handy target to remove everything you don't want to distribute
+distclean: clobber
+ -rm -f Makefile Setup
diff --git a/cursesmodule/README.cursesmodule b/cursesmodule/README.cursesmodule
new file mode 100644
index 0000000..406d7d7
--- /dev/null
+++ b/cursesmodule/README.cursesmodule
@@ -0,0 +1,422 @@
+ **********************
+ * Cursesmodule 1.4b1 *
+ **********************
+
+Please read the COPYRIGHT POLICY and the end of this document.
+
+1. General remarks
+1.1 New Features
+1.2 TO DO Lists
+2. List of Functions and Types
+3. Installation
+4. COPYRIGHT POLICY
+5. Email addresses
+
+1. General remarks
+==================
+
+This is a heavily extended version of the cursesmodule 1.2 developed by Lance
+Ellinghaus. This is the first public beta release I release under my
+copyright. I like to thank Lance Ellinghaus and all the other people
+involved in the development of the previous module. I also like to thank Ulf
+Bartelt with whom I have been discussing the future development of this module
+during the last weeks and still do. (That was back in 1996. ;-) Now Andrew
+Kuchling has taken over his part.
+
+This modules was developed using ncurses 4.0, so I can more or less guarantee,
+that the module is fully functional with this implementations.
+
+Tested platforms at the moment:
+* Linux 2.0.28, ncurses 4.0, gcc 2.7.2.1, libc 5.3.12, python 1.4
+ (andrich@fga.de)
+* Linux 1.3.71, ncurses 1.9.8a, gcc 2.7.2, libc 5.2.18, python 1.3
+ (Oliver_Andrich@bammbamm.fido.de)
+* Linux 1.2.13, ncurses 1.9.8a, gcc 2.7.0, libc 5.0.9, python 1.3
+ (Oliver_Andrich@bammbamm.fido.de)
+
+I still think that this module is in beta stadium cause it has only been
+tested on Linux machines yet. The code is stable on these machines and there
+don't seem to exist severe memory leaks or so. (I can't make any statement
+about ncurses anyway. ;-)
+
+I would be very happy if anybody using it under an other platform successfully
+would drop me an email. And anybody, who had some problems with a different
+(n)curses version or something else, should send me an email with the
+description of the problem or with a patch, cause I like to provide a module
+that is widely usable.
+
+1.1 New Features
+================
+
+1.3b2 -> 1.4b1:
+-------------
+* removed soft labels (no slk_functions anymore)
+
+1.3b1 -> 1.3b2:
+---------------
+* fixed a little bug in slk_init and slk_label
+* added argument bound checks
+ => More sophisticated error messages and no wrong return values.
+ No function simply returns "xyz() returns ERR" when simply an
+ argument is instatiated with a wrong value.
+ necessary: color_content, pair_content
+ optional: (can be turned of via -DNO_BOUNDS_CHECK compile option)
+ curs_set, halfdelay, init_color, init_pair, slk_init, slk_label, slk_set
+
+1.2 -> 1.3b1: (Release Date 23.03.1996)
+-------------
+* a lot more functions from the (n)curses are supported
+* full color support
+* soft labels
+* pads (There is only one WINDOW type left. No separate type for pads.)
+* flexible extension of all functions that add chars in one or another
+ way. Now you can also use 1-element strings (= chars) as parameters without
+ having to convert them with ord or so.
+* all necessary global variables of ncurses are exported
+* is fully functional with the panelmodule 1.1
+* made the whole module type safe for future changes in the (n)curses lib
+* begining support for termcap and terminfo functions
+
+1.2 TO DO Lists
+==============
+
+Things that are not yet implemented include:
+
+* implement argument range checks for more sophisticated error messages when
+ calling a method or function with an out of range parameter (high priority)
+* all addchstr functions, cause I haven't managed to develop a convenient way
+ to represent attributed strings in Python yet. (high priority)
+* mouse support (high priority)
+* demo applications (the ncurses test programm especially)
+* the SCREEN stuff, cause it seems as I haven't understood yet, how to use it.
+ (low priority)
+* curs_scr_dmp (very low priority)
+* support for curs_term{info|cap} functions (drop me an email if you need
+ that)
+
+Things that will never be support are (never is a relative term :-):
+
+* menus and forms, cause I extended the module to have a stable basis for
+ developing my own python/curses-based GUI. This thing will be done in
+ conjunction with Ulf Bartelt who has heavily supported me during development
+ of this module.
+* curs_printw and curs_scanw functions, cause I think that there is no need
+ for them. May be curs_scanw functions will implemented, but that has a
+ very low priority at the moment.
+
+2. List of Functions and Types
+==============================
+
+There is 1 basic type exported by this module:
+ window - This is the basic type. This is equivalent to "WINDOW *".
+
+Most of the routines can be looked up using the curses man page. The relevant
+page from the ncurses package is mentioned after each function. But some
+of the return values may differ from the return values of the curses
+functions. But all the return values are listed below.
+
+Description of the various argument types
+
+bool: a standard Python Int with value 0 (False) or not 0 (True)
+ch: a standard Python 1-element string (for intuitive use of the methods)
+int: a attributed character (= a Python int)
+ in order to get an "a" with a blink attribute you have to assign
+ ord("a") | curses.A_BLINK to the parameter
+ (more intuitive calls are also supported, look at addch("a",
+ curses.A_BLINK). That means the same as addch(ord("a") |
+ curses.A_BLINK).
+attr: a Python int representing attributes
+ to get a blinking reversed attribute assign
+ curses.A_BLINK | curses.A_REVERSE to the parameter
+y,x: Python ints representing coordinates
+str: a standard Python string
+
+Here is a list of the currently supported methods and attributes
+in the curses module:
+
+Return Value Func/Attr Description/Manpage
+--------------------------------------------------------------------------
+IntObject baudrate() curs_termattrs
+None beep() curs_beep
+True/FalseObject can_change_colors() curs_color
+None cbreak() curs_inopts
+ cbreak(bool)
+(r,g,b) color_content(int) curs_color
+ returns the RGB values of the color
+ int as a tuple of ints
+IntObject COLORS number of available colors
+ (only available after start_color())
+IntObject COLOR_PAIR(int) curs_color
+ returns the value of the color pair
+ int compiled from the values of for-
+ and backgroundcolor
+IntObject COLOR_PAIRS number of available color pairs
+ (only available aftert start_color())
+IntObject curs_set(int) curs_kernel
+ sets the cursor visibility to value int
+ allowed values 0, 1 and 2
+ returns old value
+None def_prog_mode() curs_kernel
+None def_shell_mode() curs_kernel
+None doupdate() curs_refresh
+None echo() curs_inopts
+ echo(bool)
+None endwin() curs_initscr
+StringObject erasechar() curs_termattrs
+None filter() curs_util
+None flash() curs_beep
+None flushinp() curs_util
+WindowObject getwin(fileobj) curs_util
+True/FalseObject has_colors() curs_color
+True/FalseObject has_ic() curs_termattrs
+True/FalseOBject has_il() curs_termattrs
+None halfdelay(int) curs_inopts
+None init_color(color, r, g, b) curs_color
+None init_pair(pair, fg, bg) curs_color
+WindowObject initscr() curs_initscr
+None intrflush(bool) curs_inopts
+True/FalseObject isendwin() curs_initscr
+StringObject keyname(int) curs_util
+ return the text representation
+ of a key_ value.
+None meta(bool) curs_inopts
+WindowObject newpad(lines, cols) curs_pad
+WindowObject newwin(lines,cols,y,x) curs_window
+ newwin(lines,cols) curs_pad
+ newwin/2 creates a pad, so that newwin
+ behaves similar to newpad/2
+None nl() curs_outopts
+ nl(bool)
+None nocbreak() curs_inopts
+None noecho() curs_inopts
+None nonl() curs_outpots
+None noqiflush() curs_inopts
+None noraw() curs_inopts
+(fg, bg) pair_content(int) curs_color
+ returns fore- and backgroundcolor of
+ color pair int
+IntObject PAIR_NUMBER(int) curs_color
+ returns the number of the colorpair with
+ the value int
+None putp(str) curs_terminfo
+None qiflush() curs_inopts
+ qiflush()
+None raw() curs_inopts
+ raw(bool)
+None reset_prog_mode() curs_kernel
+None reset_shell_mode() curs_kernel
+None start_color() curs_color
+ initializes color support
+IntObject termattrs() curs_termattrs
+StringObject termname() curs_termattrs
+StringObject unctrl(ch) curs_util
+ unctrl(int)
+None ungetch(int) curs_getch
+ Push the int back so next getch()
+ will return it.
+ ungetch(ch) Now also for chars.
+None use_env(bool) curs_util
+StringObject version A string representing the current
+ version of this module.
+
+Here is a list of the currently supported methods and attributes
+in the WindowObject:
+
+Return Value Func/Attr Description/Manpage
+--------------------------------------------------------------------------
+None addch(y,x,int,attr) curs_addch
+ addch(y,x,ch,attr)
+ addch(y,x,int)
+ addch(y,x,ch)
+ addch(int,attr)
+ addch(ch,attr)
+ addch(int)
+ addch(ch)
+None addnstr(y,x,str,n,attr) curs_addstr
+ addnstr(y,x,str,n)
+ addnstr(str,n,attr)
+ addnstr(str,n)
+None addstr(y,x,str,attr) curs_addstr
+ addstr(y,x,str)
+ addstr(str,attr)
+ addstr(str)
+None attron(attr) curs_attr
+None attroff(attr) curs_attr
+None attrset(sttr) curs_attr
+None bkgd(int) curs_bkgd
+ bkgd(ch)
+ bkgd(int, attr)
+ bkgd(ch, attr)
+None bkgdset(int) curs_bkgd
+ bkgdset(ch)
+ bkgdset(int, attr)
+ bkgdset(ch, attr)
+None border(ls,rs,ts,bs,tl,tr,bl,br) curs_border
+ (accepts 0-8 int args)
+None box(vertch,horch) curs_border
+ vertch and horch are INTS
+ box()
+None clear() curs_clear
+None clearok(bool) curs_outopts
+None clrtobot() curs_clear
+None clrtoeol() curs_clear
+None cursyncup() curs_window
+None delch(y,x) curs_delch
+ delch()
+None deleteln() curs_deleteln
+WindowObject derwin(nlines,ncols,begin_y,begin_x) curs_window
+ derwin(begin_y,begin_x)
+None echochar(ch,attr) curs_addch
+ echochar(int,attr)
+ echochar(ch)
+ echochar(int)
+None erase() curs_clear
+(y,x) getbegyx() curs_
+IntObject getbkgd() curs_bkgd
+IntObject getch(y,x) curs_getch
+ getch()
+StringObject getkey(y,x) similar to getch, but returns a char
+ getkey() or the keyname pressed
+(y,x) getmaxyx() curs_getyx
+(y,x) getparyx() curs_getyx
+StringObject getstr(y,x,n) curs_getstr
+ getstr(y,x)
+ getstr(n) n is an int, max chars read
+ getstr()
+(y,x) getyx() curs_getyx
+None hline(y,x,ch,n,attr) curs_border
+ hline(y,x,int,n,attr)
+ hline(y,x,ch,n)
+ hline(y,x,int,n)
+ hline(ch,n,attr)
+ hline(int,n,attr)
+ hline(ch,n)
+ hline(int,n)
+None idlok(bool) curs_outopts
+None idcok(bool) curs_outopts
+None immedok(bool) curs_outopts
+IntObject inch(y,x) curs_inch
+ inch()
+None insch(y,x,ch,attr) curs_insch
+ insch(y,x,int,attr)
+ insch(y,x,ch)
+ insch(y,x,int)
+ insch(ch,attr)
+ insch(int, attr)
+ insch(ch)
+None insdelln(int) curs_deleteln
+None insertln() curs_deleteln
+None insnstr(y,x,str,n,attr) curs_insstr
+ insnstr(y,x,str,n)
+ insnstr(str,n,attr)
+ insnstr(str,n)
+None insstr(y,x,str,attr) curs_insstr
+ insstr(y,x,str)
+ insstr(str,attr)
+ insstr(str)
+StringObject instr(y,x,n) curs_instr
+ instr(y,x)
+ instr(n)
+ instr()
+True/FalseObject is_linetouched(int) curs_touch int = line
+True/FalseObject is_winwouched() curs_touch
+None keypad(bool) curs_inopts
+None leaveok(bool) curs_outopts
+None move(new_y,new_x) curs_move
+ Move Cursor
+None mvwin(new_y,new_x) curs_move
+ Move Window
+None nodelay(bool) curs_inopts
+None noutrefresh() curs_refresh
+ Mark for refresh but wait
+None notimeout(bool) curs_inopts
+None putwin(fileobject) curs_util
+None redrawln(beg, cnt) curs_refresh
+None redrawwin() curs_refresh
+None refresh() curs_refresh
+None scroll() curs_scroll
+ scroll(lines)
+None scrollok(bool) curs_outopts
+None setscrreg(top,bottom) curs_outopts
+None standend() curs_attr
+None standout() curs_attr
+WindowObject subpad(nlines,ncols,begin_y,begin_x) curs_pad
+WindowObject subwin(nlines,ncols,begin_y,begin_x) curs_window
+None syncup() curs_window
+None syncdown() curs_window
+None syncok(bool) curs_window
+None touchline(start,count) curs_touch
+ touchline(start, count, value) (= wtouchln)
+None touchwin() curs_touch
+None vline(y,x,ch,n,attr) curs_border
+ vline(y,x,int,n,attr)
+ vline(y,x,ch,n)
+ vline(y,x,int,n)
+ vline(ch,n,attr)
+ vline(int,n,attr)
+ vline(ch,n)
+ vline(int,n)
+
+3. Installation
+===============
+
+- Specify -DNO_BOUNDS_CHECK on the compile command line, so that all
+ unnecassary boundary checks are left out
+
+a) The easy way:
+
+ If you have installed the Python source distribution, then replace the old
+ cursesmodule.c with the new cursesmodule.c and recompile.
+
+b) If you haven't got a source distribution installed, at hand or don't like
+ to recompile Python, then it depends how you link Python ...
+
+ 1. static linking: get a source distribution and continue with step a). :-)
+
+ 2. dynamic linking: use the Makefile coming along with that distribution,
+ make the the relevant changes, type make and copy the
+ result to a place where python can find the module.
+
+ WARNING! The Makefile functions very well under Linux,
+ I don't know if it functions under a another system.
+ Please mail me the relevant changes.
+
+4. COPYRIGHT POLICY
+===================
+/*
+ * This is a curses implementation for Python.
+ *
+ * Based on a prior work by Lance Ellinghaus
+ * (version 1.2 of this module
+ * Copyright 1994 by Lance Ellinghouse,
+ * Cathedral City, California Republic, United States of America.)
+ * Updated, fixed and heavily extended by Oliver Andrich
+ *
+ * Copyright 1996, 1997 by Oliver Andrich,
+ * Koblenz, Germany
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this source file to use, copy, modify, merge, or publish it
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or in any new file that contains a substantial portion of
+ * this file.
+ *
+ * THE AUTHOR MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF
+ * THE SOFTWARE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" WITHOUT
+ * EXPRESS OR IMPLIED WARRANTY. THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE TO YOU OR ANY OTHER PARTY FOR ANY SPECIAL,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE, STRICT LIABILITY OR
+ * ANY OTHER ACTION ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+5. Email addresses
+==================
+
+andrich@fga.de (Oliver Andrich)
diff --git a/cursesmodule/README.precompiled b/cursesmodule/README.precompiled
new file mode 100644
index 0000000..550b821
--- /dev/null
+++ b/cursesmodule/README.precompiled
@@ -0,0 +1,24 @@
+This is Oliver Andrich's cursesmodule-1.5b2, slightly patched
+by Arne Zellentin <zarne@users.sf.net> so that it includes "termresize".
+
+Note: I renamed it to jack_cursesmodule so that it doesn't conflict with
+ the original cursesmodule. I think it's a valid replacement but I've
+ given up trying to get it into the python distribution.
+
+Note 2: The precompiled versions RedHat-6.0 and Debian-Potato do not reflect
+ this change (yet) because I do not have access to such a system anymore.
+
+The module precompiled/RedHat-6.0/cursesmodule.so has been compiled on a RedHat
+6.0 alike system on an i686 (glibc2); precompiled/Debian-Potato/cursemodule.so
+was done on a (you guessed it) Debian Potato System.
+precompiled/Debian-Woody/jack_cursemodule.so was done on Woody.
+
+If you run something else that is at least Linux/ix86, check if one of them
+works. Do that by running Python and entering
+from curses import *
+or
+from jack_curses import *
+in interactive mode. If it fails, you will be confronted with an error message
+which may be of help.
+
+Have fun!
diff --git a/cursesmodule/Setup b/cursesmodule/Setup
new file mode 100644
index 0000000..3e894ee
--- /dev/null
+++ b/cursesmodule/Setup
@@ -0,0 +1,414 @@
+# -*- makefile -*-
+# The file Setup is used by the makesetup script to construct the files
+# Makefile and config.c, from Makefile.pre and config.c.in,
+# respectively. The file Setup itself is initially copied from
+# Setup.in; once it exists it will not be overwritten, so you can edit
+# Setup to your heart's content. Note that Makefile.pre is created
+# from Makefile.pre.in by the toplevel configure script.
+
+# (VPATH notes: Setup and Makefile.pre are in the build directory, as
+# are Makefile and config.c; the *.in files are in the source
+# directory.)
+
+# Each line in this file describes one or more optional modules.
+# Comment out lines to suppress modules.
+# Lines have the following structure:
+#
+# <module> ... [<sourcefile> ...] [<cpparg> ...] [<library> ...]
+#
+# <sourcefile> is anything ending in .c (.C, .cc, .c++ are C++ files)
+# <cpparg> is anything starting with -I, -D, -U or -C
+# <library> is anything ending in .a or beginning with -l or -L
+# <module> is anything else but should be a valid Python
+# identifier (letters, digits, underscores, beginning with non-digit)
+#
+# (As the makesetup script changes, it may recognize some other
+# arguments as well, e.g. *.so and *.sl as libraries. See the big
+# case statement in the makesetup script.)
+#
+# Lines can also have the form
+#
+# <name> = <value>
+#
+# which defines a Make variable definition inserted into Makefile.in
+#
+# Finally, if a line contains just the word "*shared*" (without the
+# quotes but with the stars), then the following modules will not be
+# included in the config.c file, nor in the list of objects to be
+# added to the library archive, and their linker options won't be
+# added to the linker options, but rules to create their .o files and
+# their shared libraries will still be added to the Makefile, and
+# their names will be collected in the Make variable SHAREDMODS. This
+# is used to build modules as shared libraries. (They can be
+# installed using "make sharedinstall", which is implied by the
+# toplevel "make install" target.) (For compatibility,
+# *noconfig* has the same effect as *shared*.)
+#
+# In addition, *static* reverses this effect (negating a previous
+# *shared* line).
+
+# NOTE: As a standard policy, as many modules as can be supported by a
+# platform should be present. The distribution comes with all modules
+# enabled that are supported by most platforms and don't require you
+# to ftp sources from elsewhere.
+
+
+# Some special rules to define PYTHONPATH.
+# Edit the definitions below to indicate which options you are using.
+# Don't add any whitespace or comments!
+
+# Directories where library files get installed.
+# DESTLIB is for Python modules; MACHDESTLIB for shared libraries.
+DESTLIB=$(LIBDEST)
+MACHDESTLIB=$(BINLIBDEST)
+
+# NOTE: all the paths are now relative to the prefix that is computed
+# at run time!
+
+# Standard path -- don't edit.
+# No leading colon since this is the first entry.
+# Empty since this is now just the runtime prefix.
+DESTPATH=
+
+# Site specific path components -- should begin with : if non-empty
+SITEPATH=
+
+# Standard path components for test modules
+TESTPATH=
+
+# Path components for machine- or system-dependent modules and shared libraries
+MACHDEPPATH=:plat-$(MACHDEP)
+
+COREPYTHONPATH=$(DESTPATH)$(SITEPATH)$(TESTPATH)$(MACHDEPPATH)$(STDWINPATH)$(TKPATH)
+PYTHONPATH=$(COREPYTHONPATH)
+
+
+# The modules listed here can't be built as shared libraries for
+# various reasons; therefore they are listed here instead of in the
+# normal order.
+
+# Some modules that are normally always on:
+
+regex regexmodule.c regexpr.c # Regular expressions, GNU Emacs style
+pcre pcremodule.c pypcre.c # Regular expressions, Perl style (for re.py)
+posix posixmodule.c # posix (UNIX) system calls
+signal signalmodule.c # signal(2)
+
+# The SGI specific GL module:
+
+GLHACK=-Dclear=__GLclear
+#gl glmodule.c cgensupport.c -I$(srcdir) $(GLHACK) -lgl -lX11
+
+# The thread module is now automatically enabled, see Setup.thread.
+
+# Pure module. Cannot be linked dynamically.
+# -DWITH_QUANTIFY, -DWITH_PURIFY, or -DWITH_ALL_PURE
+#WHICH_PURE_PRODUCTS=-DWITH_ALL_PURE
+#PURE_INCLS=-I/usr/local/include
+#PURE_STUBLIBS=-L/usr/local/lib -lpurify_stubs -lquantify_stubs
+#pure puremodule.c $(WHICH_PURE_PRODUCTS) $(PURE_INCLS) $(PURE_STUBLIBS)
+
+# Uncommenting the following line tells makesetup that all following
+# modules are to be built as shared libraries (see above for more
+# detail; also note that *static* reverses this effect):
+
+#*shared*
+
+# GNU readline. Unlike previous Python incarnations, GNU readline is
+# now incorporated in an optional module, configured in the Setup file
+# instead of by a configure script switch. You may have to insert a
+# -L option pointing to the directory where libreadline.* lives,
+# and you may have to change -ltermcap to -ltermlib or perhaps remove
+# it, depending on your system -- see the GNU readline instructions.
+# It's okay for this to be a shared library, too.
+
+readline readline.c -lreadline -ltermcap
+
+
+# Modules that should always be present (non UNIX dependent):
+
+array arraymodule.c # array objects
+cmath cmathmodule.c # -lm # complex math library functions
+math mathmodule.c # -lm # math library functions, e.g. sin()
+strop stropmodule.c # fast string operations implemented in C
+struct structmodule.c # binary structure packing/unpacking
+time timemodule.c # -lm # time operations and variables
+operator operator.c # operator.add() and similar goodies
+
+_locale _localemodule.c # access to ISO C locale support
+
+
+# Modules with some UNIX dependencies -- on by default:
+# (If you have a really backward UNIX, select and socket may not be
+# supported...)
+
+fcntl fcntlmodule.c # fcntl(2) and ioctl(2)
+pwd pwdmodule.c # pwd(3)
+grp grpmodule.c # grp(3)
+select selectmodule.c # select(2); not on ancient System V
+socket socketmodule.c # socket(2); not on ancient System V
+#_socket socketmodule.c # socket(2); use this one for BeOS sockets
+errno errnomodule.c # posix (UNIX) errno values
+
+# The crypt module is now disabled by default because it breaks builds
+# on many systems (where -lcrypt is needed), e.g. Linux (I believe).
+#crypt cryptmodule.c # -lcrypt # crypt(3); needs -lcrypt on some systems
+
+
+# Some more UNIX dependent modules -- off by default, since these
+# are not supported by all UNIX systems:
+
+#nis nismodule.c # Sun yellow pages -- not everywhere
+termios termios.c # Steen Lumholt's termios module
+resource resource.c # Jeremy Hylton's rlimit interface
+
+
+# Multimedia modules -- off by default.
+# These don't work for 64-bit platforms!!!
+# These represent audio samples or images as strings:
+
+#audioop audioop.c # Operations on audio samples
+#imageop imageop.c # Operations on images
+#rgbimg rgbimgmodule.c # Read SGI RGB image files (but coded portably)
+
+
+# The stdwin module provides a simple, portable (between X11 and Mac)
+# windowing interface. You need to ftp the STDWIN library, e.g. from
+# ftp://ftp.cwi.nl/pub/stdwin. (If you get it elsewhere, be sure to
+# get version 1.0 or higher!) The STDWIN variable must point to the
+# STDWIN toplevel directory.
+
+# Uncomment and edit as needed:
+#STDWIN=/ufs/guido/src/stdwin
+
+# Uncomment these lines:
+#STDWINPATH=:lib-stdwin
+#LIBTEXTEDIT=$(STDWIN)/$(MACHDEP)/Packs/textedit/libtextedit.a
+#LIBX11STDWIN=$(STDWIN)/$(MACHDEP)/Ports/x11/libstdwin.a
+#stdwin stdwinmodule.c -I$(STDWIN)/H $(LIBTEXTEDIT) $(LIBX11STDWIN) -lX11
+
+# Use this instead of the last two lines above for alphanumeric stdwin:
+#LIBALFASTDWIN=$(STDWIN)/$(MACHDEP)/Ports/alfa/libstdwin.a
+#stdwin stdwinmodule.c -I$(STDWIN)/H $(LIBTEXTEDIT) $(LIBALFASTDWIN) -ltermcap
+
+
+# The md5 module implements the RSA Data Security, Inc. MD5
+# Message-Digest Algorithm, described in RFC 1321. The necessary files
+# md5c.c and md5.h are included here.
+
+md5 md5module.c md5c.c
+
+
+# The sha module implements the SHA checksum algorithm.
+# (NIST's Secure Hash Algorithm.)
+sha shamodule.c
+
+
+# The mpz module interfaces to the GNU Multiple Precision library.
+# You need to ftp the GNU MP library.
+# The GMP variable must point to the GMP source directory.
+# This was originally written and tested against GMP 1.2 and 1.3.2.
+# It has been modified by Rob Hooft to work with 2.0.2 as well, but I
+# haven't tested it recently.
+
+# A compatible MP library unencombered by the GPL also exists. It was
+# posted to comp.sources.misc in volume 40 and is widely available from
+# FTP archive sites. One URL for it is:
+# ftp://gatekeeper.dec.com/.b/usenet/comp.sources.misc/volume40/fgmp/part01.Z
+
+#GMP=/ufs/guido/src/gmp
+#mpz mpzmodule.c -I$(GMP) $(GMP)/libgmp.a
+
+
+# SGI IRIX specific modules -- off by default.
+
+# These module work on any SGI machine:
+
+# *** gl must be enabled higher up in this file ***
+#fm fmmodule.c $(GLHACK) -lfm -lgl # Font Manager
+#sgi sgimodule.c # sgi.nap() and a few more
+
+# This module requires the header file
+# /usr/people/4Dgifts/iristools/include/izoom.h:
+#imgfile imgfile.c -limage -lgutil -lgl -lm # Image Processing Utilities
+
+
+# These modules require the Multimedia Development Option (I think):
+
+#al almodule.c -laudio # Audio Library
+#cd cdmodule.c -lcdaudio -lds -lmediad # CD Audio Library
+#cl clmodule.c -lcl -lawareaudio # Compression Library
+#sv svmodule.c yuvconvert.c -lsvideo -lXext -lX11 # Starter Video
+
+
+# The FORMS library, by Mark Overmars, implements user interface
+# components such as dialogs and buttons using SGI's GL and FM
+# libraries. You must ftp the FORMS library separately from
+# ftp://ftp.cs.ruu.nl/pub/SGI/FORMS. It was tested with FORMS 2.2a.
+# NOTE: if you want to be able to use FORMS and curses simultaneously
+# (or both link them statically into the same binary), you must
+# compile all of FORMS with the cc option "-Dclear=__GLclear".
+
+# The FORMS variable must point to the FORMS subdirectory of the forms
+# toplevel directory:
+
+#FORMS=/ufs/guido/src/forms/FORMS
+#fl flmodule.c -I$(FORMS) $(GLHACK) $(FORMS)/libforms.a -lfm -lgl
+
+
+# SunOS specific modules -- off by default:
+
+#sunaudiodev sunaudiodev.c
+
+
+# George Neville-Neil's timing module:
+
+#timing timingmodule.c
+
+
+# The _tkinter module.
+#
+# The TKPATH variable is always enabled, to save you the effort.
+TKPATH=:lib-tk
+
+# The command for _tkinter is long and site specific. Please
+# uncomment and/or edit those parts as indicated. If you don't have a
+# specific extension (e.g. Tix or BLT), leave the corresponding line
+# commented out. (Leave the trailing backslashes in! If you
+# experience strange errors, you may want to join all uncommented
+# lines and remove the backslashes -- the backslash interpretation is
+# done by the shell's "read" command and it may not be implemented on
+# every system.
+
+# *** Always uncomment this (leave the leading underscore in!):
+_tkinter _tkinter.c tkappinit.c -DWITH_APPINIT \
+# *** Uncomment and edit to reflect where your Tcl/Tk headers are:
+# -I/usr/local/include \
+#-I/usr/include \
+# *** Uncomment and edit to reflect where your X11 header files are:
+# -I/usr/X11R6/include \
+# *** Or uncomment this for Solaris:
+# -I/usr/openwin/include \
+# *** Uncomment and edit for Tix extension only:
+# -DWITH_TIX -ltix4.1.8.0 \
+# *** Uncomment and edit for BLT extension only:
+# -DWITH_BLT -I/usr/local/blt/blt8.0-unoff/include -lBLT8.0 \
+# *** Uncomment and edit for PIL (TkImaging) extension only:
+# -DWITH_PIL -I../Extensions/Imaging/libImaging tkImaging.c \
+# *** Uncomment and edit for TOGL extension only:
+# -DWITH_TOGL togl.c \
+# *** Uncomment and edit to reflect where your Tcl/Tk libraries are:
+# -L/usr/local/lib \
+# *** Uncomment and edit to reflect your Tcl/Tk versions:
+ -ltk8.0 -ltcl8.0 \
+# *** Uncomment and edit to reflect where your X11 libraries are:
+ -L/usr/X11R6/lib \
+# *** Or uncomment this for Solaris:
+# -L/usr/openwin/lib \
+# *** Uncomment these for TOGL extension only:
+# -lGL -lGLU -lXext -lXmu \
+# *** Uncomment for AIX:
+# -lld \
+# *** Always uncomment this; X11 libraries to link with:
+ -lX11
+
+# Lance Ellinghaus's modules:
+
+rotor rotormodule.c # enigma-inspired encryption
+#syslog syslogmodule.c # syslog daemon interface
+
+
+# Lance's curses module. This requires the System V version of
+# curses, sometimes known as ncurses (e.g. on Linux, link with
+# -lncurses instead of -lcurses; on SunOS 4.1.3, insert -I/usr/5include
+# -L/usr/5lib before -lcurses).
+
+curses cursesmodule.c -lncurses -ltermcap
+
+
+
+# Tommy Burnette's 'new' module (creates new empty objects of certain kinds):
+
+new newmodule.c
+
+
+# Generic (SunOS / SVR4) dynamic loading module.
+# This is not needed for dynamic loading of Python modules --
+# it is a highly experimental and dangerous device for calling
+# *arbitrary* C functions in *arbitrary* shared libraries:
+
+#dl dlmodule.c
+
+
+# Modules that provide persistent dictionary-like semantics. You will
+# probably want to arrange for at least one of them to be available on
+# your machine, though none are defined by default because of library
+# dependencies. The Python module anydbm.py provides an
+# implementation independent wrapper for these; dumbdbm.py provides
+# similar functionality (but slower of course) implemented in Python.
+
+# The standard Unix dbm module:
+
+#dbm dbmmodule.c # dbm(3) may require -lndbm or similar
+
+# Anthony Baxter's gdbm module. GNU dbm(3) will require -lgdbm:
+
+gdbm gdbmmodule.c -I/usr/local/include -L/usr/local/lib -lgdbm
+
+
+# Berkeley DB interface.
+#
+# This requires the Berkeley DB code, see
+# ftp://ftp.cs.berkeley.edu/pub/4bsd/db.1.85.tar.gz
+#
+# Edit the variables DB and DBPORT to point to the db top directory
+# and the subdirectory of PORT where you built it.
+#
+# (See http://www.jenkon-dev.com/~rd/python/ for an interface to
+# BSD DB 2.1.0.)
+
+#DB=/depot/sundry/src/berkeley-db/db.1.85
+#DBPORT=$(DB)/PORT/irix.5.3
+#bsddb bsddbmodule.c -I$(DBPORT)/include -I$(DBPORT) $(DBPORT)/libdb.a
+
+
+
+# David Wayne Williams' soundex module (obsolete -- this will disappear!)
+#soundex soundex.c
+
+# Helper module for various ascii-encoders
+binascii binascii.c
+
+# Fred Drake's interface to the Python parser
+parser parsermodule.c
+
+# Digital Creations' cStringIO and cPickle
+cStringIO cStringIO.c
+cPickle cPickle.c
+
+
+# Lee Busby's SIGFPE modules.
+# The library to link fpectl with is platform specific.
+# Choose *one* of the options below for fpectl:
+
+# For SGI IRIX (tested on 5.3):
+#fpectl fpectlmodule.c -lfpe
+
+# For Solaris with SunPro compiler (tested on Solaris 2.5 with SunPro C 4.2):
+# (Without the compiler you don't have -lsunmath.)
+#fpectl fpectlmodule.c -R/opt/SUNWspro/lib -lsunmath -lm
+
+# For other systems: see instructions in fpectlmodule.c.
+#fpectl fpectlmodule.c ...
+
+# Test module for fpectl. No extra libraries needed.
+#fpetest fpetestmodule.c
+
+# Andrew Kuchling's zlib module.
+# This require zlib 1.1.3 (or later).
+# See http://www.cdrom.com/pub/infozip/zlib/
+zlib zlibmodule.c -I$(prefix)/include -L$(exec_prefix)/lib -lz
+
+
+# Example -- included for reference only:
+# xx xxmodule.c
diff --git a/cursesmodule/config.c b/cursesmodule/config.c
new file mode 100644
index 0000000..be3f82c
--- /dev/null
+++ b/cursesmodule/config.c
@@ -0,0 +1,197 @@
+/* Generated automatically from /usr/local/lib/python1.5/config/config.c.in by makesetup. */
+/* -*- C -*- ***********************************************
+Copyright 1991-1995 by Stichting Mathematisch Centrum, Amsterdam,
+The Netherlands.
+
+ All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the names of Stichting Mathematisch
+Centrum or CWI or Corporation for National Research Initiatives or
+CNRI not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+While CWI is the initial source for this software, a modified version
+is made available by the Corporation for National Research Initiatives
+(CNRI) at the Internet address ftp://ftp.python.org.
+
+STICHTING MATHEMATISCH CENTRUM AND CNRI DISCLAIM ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH
+CENTRUM OR CNRI BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
+DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+
+******************************************************************/
+
+/* Module configuration */
+
+/* !!! !!! !!! This file is edited by the makesetup script !!! !!! !!! */
+
+/* This file contains the table of built-in modules.
+ See init_builtin() in import.c. */
+
+#include "Python.h"
+
+
+extern void initregex();
+extern void initpcre();
+extern void initposix();
+extern void initsignal();
+extern void initreadline();
+extern void initarray();
+extern void initcmath();
+extern void initmath();
+extern void initstrop();
+extern void initstruct();
+extern void inittime();
+extern void initoperator();
+extern void init_locale();
+extern void initfcntl();
+extern void initpwd();
+extern void initgrp();
+extern void initselect();
+extern void initsocket();
+extern void initerrno();
+extern void inittermios();
+extern void initresource();
+extern void initmd5();
+extern void initsha();
+extern void init_tkinter();
+extern void initrotor();
+extern void initcurses();
+extern void initnew();
+extern void initgdbm();
+extern void initbinascii();
+extern void initparser();
+extern void initcStringIO();
+extern void initcPickle();
+extern void initzlib();
+extern void initregex();
+extern void initpcre();
+extern void initposix();
+extern void initsignal();
+extern void initreadline();
+extern void initarray();
+extern void initcmath();
+extern void initmath();
+extern void initstrop();
+extern void initstruct();
+extern void inittime();
+extern void initoperator();
+extern void init_locale();
+extern void initfcntl();
+extern void initpwd();
+extern void initgrp();
+extern void initselect();
+extern void initsocket();
+extern void initerrno();
+extern void inittermios();
+extern void initresource();
+extern void initmd5();
+extern void initsha();
+extern void init_tkinter();
+extern void initrotor();
+extern void initnew();
+extern void initgdbm();
+extern void initbinascii();
+extern void initparser();
+extern void initcStringIO();
+extern void initcPickle();
+extern void initzlib();
+
+/* -- ADDMODULE MARKER 1 -- */
+
+extern void PyMarshal_Init();
+extern void initimp();
+
+struct _inittab _PyImport_Inittab[] = {
+
+ {"regex", initregex},
+ {"pcre", initpcre},
+ {"posix", initposix},
+ {"signal", initsignal},
+ {"readline", initreadline},
+ {"array", initarray},
+ {"cmath", initcmath},
+ {"math", initmath},
+ {"strop", initstrop},
+ {"struct", initstruct},
+ {"time", inittime},
+ {"operator", initoperator},
+ {"_locale", init_locale},
+ {"fcntl", initfcntl},
+ {"pwd", initpwd},
+ {"grp", initgrp},
+ {"select", initselect},
+ {"socket", initsocket},
+ {"errno", initerrno},
+ {"termios", inittermios},
+ {"resource", initresource},
+ {"md5", initmd5},
+ {"sha", initsha},
+ {"_tkinter", init_tkinter},
+ {"rotor", initrotor},
+ {"curses", initcurses},
+ {"new", initnew},
+ {"gdbm", initgdbm},
+ {"binascii", initbinascii},
+ {"parser", initparser},
+ {"cStringIO", initcStringIO},
+ {"cPickle", initcPickle},
+ {"zlib", initzlib},
+ {"regex", initregex},
+ {"pcre", initpcre},
+ {"posix", initposix},
+ {"signal", initsignal},
+ {"readline", initreadline},
+ {"array", initarray},
+ {"cmath", initcmath},
+ {"math", initmath},
+ {"strop", initstrop},
+ {"struct", initstruct},
+ {"time", inittime},
+ {"operator", initoperator},
+ {"_locale", init_locale},
+ {"fcntl", initfcntl},
+ {"pwd", initpwd},
+ {"grp", initgrp},
+ {"select", initselect},
+ {"socket", initsocket},
+ {"errno", initerrno},
+ {"termios", inittermios},
+ {"resource", initresource},
+ {"md5", initmd5},
+ {"sha", initsha},
+ {"_tkinter", init_tkinter},
+ {"rotor", initrotor},
+ {"new", initnew},
+ {"gdbm", initgdbm},
+ {"binascii", initbinascii},
+ {"parser", initparser},
+ {"cStringIO", initcStringIO},
+ {"cPickle", initcPickle},
+ {"zlib", initzlib},
+
+/* -- ADDMODULE MARKER 2 -- */
+
+ /* This module "lives in" with marshal.c */
+ {"marshal", PyMarshal_Init},
+
+ /* This lives it with import.c */
+ {"imp", initimp},
+
+ /* These entries are here for sys.builtin_module_names */
+ {"__main__", NULL},
+ {"__builtin__", NULL},
+ {"sys", NULL},
+
+ /* Sentinel */
+ {0, 0}
+};
diff --git a/cursesmodule/cursesmodule-1.5b2.patch b/cursesmodule/cursesmodule-1.5b2.patch
new file mode 100644
index 0000000..9046971
--- /dev/null
+++ b/cursesmodule/cursesmodule-1.5b2.patch
@@ -0,0 +1,43 @@
+*** x/cursesmodule/cursesmodule.c Wed Mar 24 21:39:59 1999
+--- cursesmodule.c Thu Aug 19 01:22:27 1999
+***************
+*** 1395,1400 ****
+--- 1395,1411 ----
+ Py_INCREF(Py_None); \
+ return Py_None; }
+
++ #define TwoArgNoReturnFunction(X, TYPE, PARSESTR) \
++ static PyObject * PyCurses_ ## X (self,arg) \
++ PyObject * self; \
++ PyObject * arg; \
++ { \
++ TYPE arg1, arg2; \
++ PyCursesInitialised \
++ if (!PyArg_Parse(arg,PARSESTR, &arg1, &arg2)) return NULL; \
++ Py_INCREF(Py_None); \
++ return PyCursesCheckERR(X(arg1, arg2), # X); }
++
+ NoArgNoReturnFunction(beep)
+ NoArgNoReturnFunction(def_prog_mode)
+ NoArgNoReturnFunction(def_shell_mode)
+***************
+*** 1410,1415 ****
+--- 1421,1428 ----
+ NoArgNoReturnFunction(resetty)
+ NoArgNoReturnFunction(savetty)
+
++ TwoArgNoReturnFunction(resizeterm, int, "(ii);y,x")
++
+ NoArgOrFlagNoReturnFunction(cbreak)
+ NoArgOrFlagNoReturnFunction(echo)
+ NoArgOrFlagNoReturnFunction(nl)
+***************
+*** 2087,2092 ****
+--- 2100,2106 ----
+ {"raw", (PyCFunction)PyCurses_raw},
+ {"reset_prog_mode", (PyCFunction)PyCurses_reset_prog_mode},
+ {"reset_shell_mode", (PyCFunction)PyCurses_reset_shell_mode},
++ {"resizeterm", (PyCFunction)PyCurses_resizeterm},
+ {"setsyx", (PyCFunction)PyCurses_setsyx},
+ {"start_color", (PyCFunction)PyCurses_Start_Color},
+ {"termattrs", (PyCFunction)PyCurses_termattrs},
diff --git a/cursesmodule/jack_cursesmodule.c b/cursesmodule/jack_cursesmodule.c
new file mode 100644
index 0000000..60ca656
--- /dev/null
+++ b/cursesmodule/jack_cursesmodule.c
@@ -0,0 +1,2280 @@
+/*
+ * This is a curses implementation for Python.
+ *
+ * Based on a prior work by Lance Ellinghaus
+ * (version 1.2 of this module
+ * Copyright 1994 by Lance Ellinghouse,
+ * Cathedral City, California Republic, United States of America.)
+ * Updated, fixed and heavily extended by Oliver Andrich
+ *
+ * Copyright 1996,1997 by Oliver Andrich,
+ * Koblenz, Germany
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this source file to use, copy, modify, merge, or publish it
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or in any new file that contains a substantial portion of
+ * this file.
+ *
+ * THE AUTHOR MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF
+ * THE SOFTWARE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" WITHOUT
+ * EXPRESS OR IMPLIED WARRANTY. THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE TO YOU OR ANY OTHER PARTY FOR ANY SPECIAL,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE, STRICT LIABILITY OR
+ * ANY OTHER ACTION ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* CVS: $Id: jack_cursesmodule.c,v 1.2 2001/11/08 01:20:01 zarne Exp $ */
+
+/* Release Number */
+
+char *PyCursesVersion = "1.5b1";
+
+/* Includes */
+
+#include "Python.h"
+#include <curses.h>
+
+#ifdef __sgi__
+ /* No attr_t type is available */
+typedef chtype attr_t;
+#endif
+
+/* Definition of exception curses.error */
+
+static PyObject *PyCursesError;
+
+/* general error messages */
+static char *catchall_ERR = "curses function returned ERR";
+static char *catchall_NULL = "curses function returned NULL";
+
+/* Tells whether initscr() has been called to initialise curses. */
+static int initialised = FALSE;
+
+/* Tells whether start_color() has been called to initialise colorusage. */
+static int initialisedcolors = FALSE;
+
+/* Utility Macros */
+#define ARG_COUNT(X) \
+ (((X) == NULL) ? 0 : (PyTuple_Check(X) ? PyTuple_Size(X) : 1))
+
+#define PyCursesInitialised \
+ if (initialised != TRUE) { \
+ PyErr_SetString(PyCursesError, \
+ "must call initscr() first"); \
+ return NULL; }
+
+#define PyCursesInitialisedColor \
+ if (initialisedcolors != TRUE) { \
+ PyErr_SetString(PyCursesError, \
+ "must call start_color() first"); \
+ return NULL; }
+
+/* Utility Functions */
+
+/*
+ * Check the return code from a curses function and return None
+ * or raise an exception as appropriate.
+ */
+
+static PyObject *
+PyCursesCheckERR(code, fname)
+ int code;
+ char *fname;
+{
+ char buf[100];
+
+ if (code != ERR) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ } else {
+ if (fname == NULL) {
+ PyErr_SetString(PyCursesError, catchall_ERR);
+ } else {
+ strcpy(buf, fname);
+ strcat(buf, "() returned ERR");
+ PyErr_SetString(PyCursesError, buf);
+ }
+ return NULL;
+ }
+}
+
+int
+PyCurses_ConvertToChtype(obj, ch)
+ PyObject *obj;
+ chtype *ch;
+{
+ if (PyInt_Check(obj)) {
+ *ch = (chtype) PyInt_AsLong(obj);
+ } else if(PyString_Check(obj) &
+ (PyString_Size(obj) == 1)) {
+ *ch = (chtype) *PyString_AsString(obj);
+ } else {
+ return 0;
+ }
+ return 1;
+}
+
+/*****************************************************************************
+ The Window Object
+******************************************************************************/
+
+/* Definition of the window object and window type */
+
+typedef struct {
+ PyObject_HEAD
+ WINDOW *win;
+} PyCursesWindowObject;
+
+PyTypeObject PyCursesWindow_Type;
+
+#define PyCursesWindow_Check(v) ((v)->ob_type == &PyCursesWindow_Type)
+
+/* Function Prototype Macros - They are ugly but very, very useful. ;-)
+
+ X - function name
+ TYPE - parameter Type
+ ERGSTR - format string for construction of the return value
+ PARSESTR - format string for argument parsing
+ */
+
+#define Window_NoArgNoReturnFunction(X) \
+static PyObject *PyCursesWindow_ ## X (self, arg) \
+ PyCursesWindowObject * self; PyObject * arg; \
+{ if (!PyArg_NoArgs(arg)) return NULL; \
+ return PyCursesCheckERR(X(self->win), # X); }
+
+#define Window_NoArgTrueFalseFunction(X) \
+static PyObject * PyCursesWindow_ ## X (self,arg) \
+ PyCursesWindowObject * self; PyObject * arg; \
+{ \
+ if (!PyArg_NoArgs(arg)) return NULL; \
+ if (X (self->win) == FALSE) { Py_INCREF(Py_False); return Py_False; } \
+ else { Py_INCREF(Py_True); return Py_True; } }
+
+#define Window_NoArgNoReturnVoidFunction(X) \
+static PyObject * PyCursesWindow_ ## X (self,arg) \
+ PyCursesWindowObject * self; \
+ PyObject * arg; \
+{ \
+ if (!PyArg_NoArgs(arg)) return NULL; \
+ X(self->win); Py_INCREF(Py_None); return Py_None; }
+
+#define Window_NoArg2TupleReturnFunction(X, TYPE, ERGSTR) \
+static PyObject * PyCursesWindow_ ## X (self, arg) \
+ PyCursesWindowObject *self; \
+ PyObject * arg; \
+{ \
+ TYPE arg1, arg2; \
+ if (!PyArg_NoArgs(arg)) return NULL; \
+ X(self->win,arg1,arg2); return Py_BuildValue(ERGSTR, arg1, arg2); }
+
+#define Window_OneArgNoReturnVoidFunction(X, TYPE, PARSESTR) \
+static PyObject * PyCursesWindow_ ## X (self, arg) \
+ PyCursesWindowObject *self; \
+ PyObject * arg; \
+{ \
+ TYPE arg1; \
+ if (!PyArg_Parse(arg, PARSESTR, &arg1)) return NULL; \
+ X(self->win,arg1); Py_INCREF(Py_None); return Py_None; }
+
+#define Window_OneArgNoReturnFunction(X, TYPE, PARSESTR) \
+static PyObject * PyCursesWindow_ ## X (self, arg) \
+ PyCursesWindowObject *self; \
+ PyObject * arg; \
+{ \
+ TYPE arg1; \
+ if (!PyArg_Parse(arg,PARSESTR, &arg1)) return NULL; \
+ return PyCursesCheckERR(X(self->win, arg1), # X); }
+
+#define Window_TwoArgNoReturnFunction(X, TYPE, PARSESTR) \
+static PyObject * PyCursesWindow_ ## X (self, arg) \
+ PyCursesWindowObject *self; \
+ PyObject * arg; \
+{ \
+ TYPE arg1, arg2; \
+ if (!PyArg_Parse(arg,PARSESTR, &arg1, &arg2)) return NULL; \
+ return PyCursesCheckERR(X(self->win, arg1, arg2), # X); }
+
+/* ------------- WINDOW routines --------------- */
+
+Window_NoArgNoReturnFunction(untouchwin)
+Window_NoArgNoReturnFunction(touchwin)
+Window_NoArgNoReturnFunction(redrawwin)
+Window_NoArgNoReturnFunction(winsertln)
+Window_NoArgNoReturnFunction(werase)
+Window_NoArgNoReturnFunction(wdeleteln)
+
+Window_NoArgTrueFalseFunction(is_wintouched)
+
+Window_NoArgNoReturnVoidFunction(wsyncup)
+Window_NoArgNoReturnVoidFunction(wsyncdown)
+Window_NoArgNoReturnVoidFunction(wstandend)
+Window_NoArgNoReturnVoidFunction(wstandout)
+Window_NoArgNoReturnVoidFunction(wcursyncup)
+Window_NoArgNoReturnVoidFunction(wclrtoeol)
+Window_NoArgNoReturnVoidFunction(wclrtobot)
+Window_NoArgNoReturnVoidFunction(wclear)
+
+Window_OneArgNoReturnVoidFunction(idcok, int, "i;True(1) or False(0)")
+Window_OneArgNoReturnVoidFunction(immedok, int, "i;True(1) or False(0)")
+
+Window_NoArg2TupleReturnFunction(getyx, int, "(ii)")
+Window_NoArg2TupleReturnFunction(getbegyx, int, "(ii)")
+Window_NoArg2TupleReturnFunction(getmaxyx, int, "(ii)")
+Window_NoArg2TupleReturnFunction(getparyx, int, "(ii)")
+
+Window_OneArgNoReturnFunction(wattron, attr_t, "l;attr")
+Window_OneArgNoReturnFunction(wattroff, attr_t, "l;attr")
+Window_OneArgNoReturnFunction(wattrset, attr_t, "l;attr")
+Window_OneArgNoReturnFunction(clearok, int, "i;True(1) or False(0)")
+Window_OneArgNoReturnFunction(idlok, int, "i;True(1) or False(0)")
+Window_OneArgNoReturnFunction(keypad, int, "i;True(1) or False(0)")
+Window_OneArgNoReturnFunction(leaveok, int, "i;True(1) or False(0)")
+Window_OneArgNoReturnFunction(nodelay, int, "i;True(1) or False(0)")
+Window_OneArgNoReturnFunction(notimeout, int, "i;True(1) or False(0)")
+Window_OneArgNoReturnFunction(scrollok, int, "i;True(1) or False(0)")
+Window_OneArgNoReturnFunction(winsdelln, int, "i;cnt")
+Window_OneArgNoReturnFunction(syncok, int, "i;True(1) or False(0)")
+
+Window_TwoArgNoReturnFunction(mvwin, int, "(ii);y,x")
+Window_TwoArgNoReturnFunction(mvderwin, int, "(ii);y,x")
+Window_TwoArgNoReturnFunction(wmove, int, "(ii);y,x")
+#ifndef __sgi__
+Window_TwoArgNoReturnFunction(wresize, int, "(ii);lines,columns")
+#endif
+
+/* Allocation and Deallocation of Window Objects */
+
+static PyObject *
+PyCursesWindow_New(win)
+ WINDOW *win;
+{
+ PyCursesWindowObject *wo;
+
+ wo = PyObject_NEW(PyCursesWindowObject, &PyCursesWindow_Type);
+ if (wo == NULL) return NULL;
+ wo->win = win;
+ return (PyObject *)wo;
+}
+
+static void
+PyCursesWindow_Dealloc(wo)
+ PyCursesWindowObject *wo;
+{
+ if (wo->win != stdscr) delwin(wo->win);
+ PyMem_DEL(wo);
+}
+
+/* Addch, Addstr, Addnstr */
+
+static PyObject *
+PyCursesWindow_AddCh(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int rtn, x, y, use_xy = FALSE;
+ PyObject *temp;
+ chtype ch = 0;
+ attr_t attr = A_NORMAL;
+
+ switch (ARG_COUNT(arg)) {
+ case 1:
+ if (!PyArg_Parse(arg, "O;ch or int", &temp))
+ return NULL;
+ break;
+ case 2:
+ if (!PyArg_Parse(arg, "(Ol);ch or int,attr", &temp, &attr))
+ return NULL;
+ break;
+ case 3:
+ if (!PyArg_Parse(arg,"(iiO);y,x,ch or int", &y, &x, &temp))
+ return NULL;
+ use_xy = TRUE;
+ break;
+ case 4:
+ if (!PyArg_Parse(arg,"(iiOl);y,x,ch or int, attr",
+ &y, &x, &temp, &attr))
+ return NULL;
+ use_xy = TRUE;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "addch requires 1 or 4 arguments");
+ return NULL;
+ }
+
+ if (!PyCurses_ConvertToChtype(temp, &ch)) {
+ PyErr_SetString(PyExc_TypeError, "argument 1 or 3 must be a ch or an int");
+ return NULL;
+ }
+
+ if (use_xy == TRUE)
+ rtn = mvwaddch(self->win,y,x, ch | attr);
+ else {
+ rtn = waddch(self->win, ch | attr);
+ }
+ return PyCursesCheckERR(rtn, "addch");
+}
+
+static PyObject *
+PyCursesWindow_AddStr(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int rtn;
+ int x, y;
+ char *str;
+ attr_t attr = A_NORMAL , attr_old = A_NORMAL;
+ int use_xy = FALSE, use_attr = FALSE;
+
+ switch (ARG_COUNT(arg)) {
+ case 1:
+ if (!PyArg_Parse(arg,"s;str", &str))
+ return NULL;
+ break;
+ case 2:
+ if (!PyArg_Parse(arg,"(sl);str,attr", &str, &attr))
+ return NULL;
+ use_attr = TRUE;
+ break;
+ case 3:
+ if (!PyArg_Parse(arg,"(iis);int,int,str", &y, &x, &str))
+ return NULL;
+ use_xy = TRUE;
+ break;
+ case 4:
+ if (!PyArg_Parse(arg,"(iisl);int,int,str,attr", &y, &x, &str, &attr))
+ return NULL;
+ use_xy = use_attr = TRUE;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "addstr requires 1 to 4 arguments");
+ return NULL;
+ }
+
+ if (use_attr == TRUE) {
+ attr_old = getattrs(self->win);
+ wattrset(self->win,attr);
+ }
+ if (use_xy == TRUE)
+ rtn = mvwaddstr(self->win,y,x,str);
+ else
+ rtn = waddstr(self->win,str);
+ if (use_attr == TRUE)
+ wattrset(self->win,attr_old);
+ return PyCursesCheckERR(rtn, "addstr");
+}
+
+static PyObject *
+PyCursesWindow_AddNStr(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int rtn, x, y, n;
+ char *str;
+ attr_t attr = A_NORMAL , attr_old = A_NORMAL;
+ int use_xy = FALSE, use_attr = FALSE;
+
+ switch (ARG_COUNT(arg)) {
+ case 2:
+ if (!PyArg_Parse(arg,"(si);str,n", &str, &n))
+ return NULL;
+ break;
+ case 3:
+ if (!PyArg_Parse(arg,"(sil);str,n,attr", &str, &n, &attr))
+ return NULL;
+ use_attr = TRUE;
+ break;
+ case 4:
+ if (!PyArg_Parse(arg,"(iisi);y,x,str,n", &y, &x, &str, &n))
+ return NULL;
+ use_xy = TRUE;
+ break;
+ case 5:
+ if (!PyArg_Parse(arg,"(iisil);y,x,str,n,attr", &y, &x, &str, &n, &attr))
+ return NULL;
+ use_xy = use_attr = TRUE;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "addnstr requires 2 to 5 arguments");
+ return NULL;
+ }
+
+ if (use_attr == TRUE) {
+ attr_old = getattrs(self->win);
+ wattrset(self->win,attr);
+ }
+ if (use_xy == TRUE)
+ rtn = mvwaddnstr(self->win,y,x,str,n);
+ else
+ rtn = waddnstr(self->win,str,n);
+ if (use_attr == TRUE)
+ wattrset(self->win,attr_old);
+ return PyCursesCheckERR(rtn, "addnstr");
+}
+
+static PyObject *
+PyCursesWindow_Bkgd(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ PyObject *temp;
+ chtype bkgd;
+ attr_t attr = A_NORMAL;
+
+ switch (ARG_COUNT(arg)) {
+ case 1:
+ if (!PyArg_Parse(arg, "O;ch or int", &temp))
+ return NULL;
+ break;
+ case 2:
+ if (!PyArg_Parse(arg,"(Ol);ch or int,attr", &temp, &attr))
+ return NULL;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "bkgd requires 1 or 2 arguments");
+ return NULL;
+ }
+
+ if (!PyCurses_ConvertToChtype(temp, &bkgd)) {
+ PyErr_SetString(PyExc_TypeError, "argument 1 or 3 must be a ch or an int");
+ return NULL;
+ }
+
+ return PyCursesCheckERR(wbkgd(self->win, bkgd | A_NORMAL), "bkgd");
+}
+
+static PyObject *
+PyCursesWindow_BkgdSet(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ PyObject *temp;
+ chtype bkgd;
+ attr_t attr = A_NORMAL;
+
+ switch (ARG_COUNT(arg)) {
+ case 1:
+ if (!PyArg_Parse(arg, "O;ch or int", &temp))
+ return NULL;
+ break;
+ case 2:
+ if (!PyArg_Parse(arg,"(Ol);ch or int,attr", &temp, &attr))
+ return NULL;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "bkgdset requires 1 or 2 arguments");
+ return NULL;
+ }
+
+ if (!PyCurses_ConvertToChtype(temp, &bkgd)) {
+ PyErr_SetString(PyExc_TypeError, "argument 1 or 3 must be a ch or an int");
+ return NULL;
+ }
+
+ wbkgdset(self->win, bkgd | attr);
+ return PyCursesCheckERR(0, "bkgdset");
+}
+
+static PyObject *
+PyCursesWindow_Border(self, args)
+ PyCursesWindowObject *self;
+ PyObject *args;
+{
+ chtype ls, rs, ts, bs, tl, tr, bl, br;
+ ls = rs = ts = bs = tl = tr = bl = br = 0;
+ if (!PyArg_Parse(args,"|llllllll;ls,rs,ts,bs,tl,tr,bl,br",
+ &ls, &rs, &ts, &bs, &tl, &tr, &bl, &br))
+ return NULL;
+ wborder(self->win, ls, rs, ts, bs, tl, tr, bl, br);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+PyCursesWindow_Box(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ chtype ch1=0,ch2=0;
+ if (!PyArg_NoArgs(arg)) {
+ PyErr_Clear();
+ if (!PyArg_Parse(arg,"(ll);vertint,horint", &ch1, &ch2))
+ return NULL;
+ }
+ box(self->win,ch1,ch2);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+PyCursesWindow_DelCh(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int rtn;
+ int x, y;
+
+ switch (ARG_COUNT(arg)) {
+ case 0:
+ rtn = wdelch(self->win);
+ break;
+ case 2:
+ if (!PyArg_Parse(arg,"(ii);y,x", &y, &x))
+ return NULL;
+ rtn = mvwdelch(self->win,y,x);
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "delch requires 0 or 2 arguments");
+ return NULL;
+ }
+ return PyCursesCheckERR(rtn, "[mv]wdelch");
+}
+
+static PyObject *
+PyCursesWindow_DerWin(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ WINDOW *win;
+ int nlines, ncols, begin_y, begin_x;
+
+ nlines = 0;
+ ncols = 0;
+ switch (ARG_COUNT(arg)) {
+ case 2:
+ if (!PyArg_Parse(arg,"(ii);begin_y,begin_x",&begin_y,&begin_x))
+ return NULL;
+ break;
+ case 4:
+ if (!PyArg_Parse(arg, "(iiii);nlines,ncols,begin_y,begin_x",
+ &nlines,&ncols,&begin_y,&begin_x))
+ return NULL;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "derwin requires 2 or 4 arguments");
+ return NULL;
+ }
+
+ win = derwin(self->win,nlines,ncols,begin_y,begin_x);
+
+ if (win == NULL) {
+ PyErr_SetString(PyCursesError, catchall_NULL);
+ return NULL;
+ }
+
+ return (PyObject *)PyCursesWindow_New(win);
+}
+
+static PyObject *
+PyCursesWindow_EchoChar(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ PyObject *temp;
+ chtype ch;
+ attr_t attr = A_NORMAL;
+
+ switch (ARG_COUNT(arg)) {
+ case 1:
+ if (!PyArg_Parse(arg,"O;ch or int", &temp))
+ return NULL;
+ break;
+ case 2:
+ if (!PyArg_Parse(arg,"(Ol);ch or int,attr", &temp, &attr))
+ return NULL;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "echochar requires 1 or 2 arguments");
+
+
+ return NULL;
+ }
+
+ if (!PyCurses_ConvertToChtype(temp, &ch)) {
+ PyErr_SetString(PyExc_TypeError, "argument 1 must be a ch or an int");
+ return NULL;
+ }
+
+ if (self->win->_flags & _ISPAD)
+ return PyCursesCheckERR(pechochar(self->win, ch | attr),
+ "echochar");
+ else
+ return PyCursesCheckERR(wechochar(self->win, ch | attr),
+ "echochar");
+}
+
+static PyObject *
+PyCursesWindow_GetBkgd(self, arg)
+ PyCursesWindowObject *self;
+ PyObject *arg;
+{
+ if (!PyArg_NoArgs(arg))
+ return NULL;
+ return PyInt_FromLong((long) getbkgd(self->win));
+}
+
+static PyObject *
+PyCursesWindow_GetCh(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int x, y;
+ chtype rtn;
+
+ switch (ARG_COUNT(arg)) {
+ case 0:
+ rtn = wgetch(self->win);
+ break;
+ case 2:
+ if (!PyArg_Parse(arg,"(ii);y,x",&y,&x))
+ return NULL;
+ rtn = mvwgetch(self->win,y,x);
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "getch requires 0 or 2 arguments");
+ return NULL;
+ }
+ return PyInt_FromLong(rtn);
+}
+
+static PyObject *
+PyCursesWindow_GetKey(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int x, y;
+ chtype rtn;
+
+ switch (ARG_COUNT(arg)) {
+ case 0:
+ rtn = wgetch(self->win);
+ break;
+ case 2:
+ if (!PyArg_Parse(arg,"(ii);y,x",&y,&x))
+ return NULL;
+ rtn = mvwgetch(self->win,y,x);
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "getch requires 0 or 2 arguments");
+ return NULL;
+ }
+ if (rtn<=255)
+ return Py_BuildValue("c", rtn);
+ else
+ return PyString_FromString((char *)keyname(rtn));
+}
+
+static PyObject *
+PyCursesWindow_GetStr(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int x, y, n;
+ char rtn[1024]; /* This should be big enough.. I hope */
+ int rtn2;
+
+ switch (ARG_COUNT(arg)) {
+ case 0:
+ rtn2 = wgetstr(self->win,rtn);
+ break;
+ case 1:
+ if (!PyArg_Parse(arg,"i;n", &n))
+ return NULL;
+ rtn2 = wgetnstr(self->win,rtn,n);
+ break;
+ case 2:
+ if (!PyArg_Parse(arg,"(ii);y,x",&y,&x))
+ return NULL;
+ rtn2 = mvwgetstr(self->win,y,x,rtn);
+ break;
+ case 3:
+ if (!PyArg_Parse(arg,"(iii);y,x,n", &y, &x, &n))
+ return NULL;
+#ifdef __sgi__
+ /* Untested */
+ rtn2 = wmove(self->win,y,x)==ERR ? ERR :
+ wgetnstr(self->win, rtn, n);
+#else
+ rtn2 = mvwgetnstr(self->win, y, x, rtn, n);
+#endif
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "getstr requires 0 to 2 arguments");
+ return NULL;
+ }
+ if (rtn2 == ERR)
+ rtn[0] = 0;
+ return PyString_FromString(rtn);
+}
+
+static PyObject *
+PyCursesWindow_Hline(self, args)
+ PyCursesWindowObject *self;
+ PyObject *args;
+{
+ PyObject *temp;
+ chtype ch;
+ int n, x, y, code = OK;
+ attr_t attr = A_NORMAL;
+
+ switch (ARG_COUNT(args)) {
+ case 2:
+ if (!PyArg_Parse(args, "(Oi);ch or int,n", &temp, &n))
+ return NULL;
+ break;
+ case 3:
+ if (!PyArg_Parse(args, "(Oil);ch or int,n,attr", &temp, &n, &attr))
+ return NULL;
+ break;
+ case 4:
+ if (!PyArg_Parse(args, "(iiOi);y,x,ch o int,n", &y, &x, &temp, &n))
+ return NULL;
+ code = wmove(self->win, y, x);
+ break;
+ case 5:
+ if (!PyArg_Parse(args, "(iiOil); y,x,ch or int,n,attr",
+ &y, &x, &temp, &n, &attr))
+ return NULL;
+ code = wmove(self->win, y, x);
+ default:
+ PyErr_SetString(PyExc_TypeError, "hline requires 2 or 5 arguments");
+ return NULL;
+ }
+
+ if (code != ERR) {
+ if (!PyCurses_ConvertToChtype(temp, &ch)) {
+ PyErr_SetString(PyExc_TypeError,
+ "argument 1 or 3 must be a ch or an int");
+ return NULL;
+ }
+ return PyCursesCheckERR(whline(self->win, ch | attr, n), "hline");
+ } else
+ return PyCursesCheckERR(code, "wmove");
+}
+
+static PyObject *
+PyCursesWindow_InsCh(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int rtn, x, y, use_xy = FALSE;
+ PyObject *temp;
+ chtype ch = 0;
+ attr_t attr = A_NORMAL;
+
+ switch (ARG_COUNT(arg)) {
+ case 1:
+ if (!PyArg_Parse(arg, "O;ch or int", &temp))
+ return NULL;
+ break;
+ case 2:
+ if (!PyArg_Parse(arg, "(Ol);ch or int,attr", &temp, &attr))
+ return NULL;
+ break;
+ case 3:
+ if (!PyArg_Parse(arg,"(iiO);y,x,ch or int", &y, &x, &temp))
+ return NULL;
+ use_xy = TRUE;
+ break;
+ case 4:
+ if (!PyArg_Parse(arg,"(iiOl);y,x,ch or int, attr", &y, &x, &temp, &attr))
+ return NULL;
+ use_xy = TRUE;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "insch requires 1 or 4 arguments");
+ return NULL;
+ }
+
+ if (!PyCurses_ConvertToChtype(temp, &ch)) {
+ PyErr_SetString(PyExc_TypeError,
+ "argument 1 or 3 must be a ch or an int");
+ return NULL;
+ }
+
+ if (use_xy == TRUE)
+ rtn = mvwinsch(self->win,y,x, ch | attr);
+ else {
+ rtn = winsch(self->win, ch | attr);
+ }
+ return PyCursesCheckERR(rtn, "insch");
+}
+
+static PyObject *
+PyCursesWindow_InCh(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int x, y, rtn;
+
+ switch (ARG_COUNT(arg)) {
+ case 0:
+ rtn = winch(self->win);
+ break;
+ case 2:
+ if (!PyArg_Parse(arg,"(ii);y,x",&y,&x))
+ return NULL;
+ rtn = mvwinch(self->win,y,x);
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "inch requires 0 or 2 arguments");
+ return NULL;
+ }
+ return PyInt_FromLong((long) rtn);
+}
+
+static PyObject *
+PyCursesWindow_InStr(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int x, y, n;
+ char rtn[1024]; /* This should be big enough.. I hope */
+ int rtn2;
+
+ switch (ARG_COUNT(arg)) {
+ case 0:
+ rtn2 = winstr(self->win,rtn);
+ break;
+ case 1:
+ if (!PyArg_Parse(arg,"i;n", &n))
+ return NULL;
+ rtn2 = winnstr(self->win,rtn,n);
+ break;
+ case 2:
+ if (!PyArg_Parse(arg,"(ii);y,x",&y,&x))
+ return NULL;
+ rtn2 = mvwinstr(self->win,y,x,rtn);
+ break;
+ case 3:
+ if (!PyArg_Parse(arg, "(iii);y,x,n", &y, &x, &n))
+ return NULL;
+ rtn2 = mvwinnstr(self->win, y, x, rtn, n);
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "instr requires 0 or 3 arguments");
+ return NULL;
+ }
+ if (rtn2 == ERR)
+ rtn[0] = 0;
+ return PyString_FromString(rtn);
+}
+
+static PyObject *
+PyCursesWindow_InsStr(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int rtn;
+ int x, y;
+ char *str;
+ attr_t attr = A_NORMAL , attr_old = A_NORMAL;
+ int use_xy = FALSE, use_attr = FALSE;
+
+ switch (ARG_COUNT(arg)) {
+ case 1:
+ if (!PyArg_Parse(arg,"s;str", &str))
+ return NULL;
+ break;
+ case 2:
+ if (!PyArg_Parse(arg,"(sl);str,attr", &str, &attr))
+ return NULL;
+ use_attr = TRUE;
+ break;
+ case 3:
+ if (!PyArg_Parse(arg,"(iis);y,x,str", &y, &x, &str))
+ return NULL;
+ use_xy = TRUE;
+ break;
+ case 4:
+ if (!PyArg_Parse(arg,"(iisl);y,x,str,attr", &y, &x, &str, &attr))
+ return NULL;
+ use_xy = use_attr = TRUE;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "insstr requires 1 to 4 arguments");
+ return NULL;
+ }
+
+ if (use_attr == TRUE) {
+ attr_old = getattrs(self->win);
+ wattrset(self->win,attr);
+ }
+ if (use_xy == TRUE)
+ rtn = mvwinsstr(self->win,y,x,str);
+ else
+ rtn = winsstr(self->win,str);
+ if (use_attr == TRUE)
+ wattrset(self->win,attr_old);
+ return PyCursesCheckERR(rtn, "insstr");
+}
+
+static PyObject *
+PyCursesWindow_InsNStr(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int rtn, x, y, n;
+ char *str;
+ attr_t attr = A_NORMAL , attr_old = A_NORMAL;
+ int use_xy = FALSE, use_attr = FALSE;
+
+ switch (ARG_COUNT(arg)) {
+ case 2:
+ if (!PyArg_Parse(arg,"(si);str,n", &str, &n))
+ return NULL;
+ break;
+ case 3:
+ if (!PyArg_Parse(arg,"(sil);str,n,attr", &str, &n, &attr))
+ return NULL;
+ use_attr = TRUE;
+ break;
+ case 4:
+ if (!PyArg_Parse(arg,"(iisi);y,x,str,n", &y, &x, &str, &n))
+ return NULL;
+ use_xy = TRUE;
+ break;
+ case 5:
+ if (!PyArg_Parse(arg,"(iisil);y,x,str,n,attr", &y, &x, &str, &n, &attr))
+ return NULL;
+ use_xy = use_attr = TRUE;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "insnstr requires 2 to 5 arguments");
+ return NULL;
+ }
+
+ if (use_attr == TRUE) {
+ attr_old = getattrs(self->win);
+ wattrset(self->win,attr);
+ }
+ if (use_xy == TRUE)
+ rtn = mvwinsnstr(self->win,y,x,str,n);
+ else
+ rtn = winsnstr(self->win,str,n);
+ if (use_attr == TRUE)
+ wattrset(self->win,attr_old);
+ return PyCursesCheckERR(rtn, "insnstr");
+}
+
+static PyObject *
+PyCursesWindow_Is_LineTouched(self,arg)
+ PyCursesWindowObject * self;
+ PyObject * arg;
+{
+ int line, erg;
+ if (!PyArg_Parse(arg,"i;line", &line))
+ return NULL;
+ erg = is_linetouched(self->win, line);
+ if (erg == ERR) {
+ PyErr_SetString(PyExc_TypeError,
+ "is_linetouched: line number outside of boundaries");
+ return NULL;
+ } else
+ if (erg == FALSE) {
+ Py_INCREF(Py_False);
+ return Py_False;
+ } else {
+ Py_INCREF(Py_True);
+ return Py_True;
+ }
+}
+
+static PyObject *
+PyCursesWindow_NoOutRefresh(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int pminrow,pmincol,sminrow,smincol,smaxrow,smaxcol;
+
+ if (self->win->_flags & _ISPAD) {
+ switch(ARG_COUNT(arg)) {
+ case 6:
+ if (!PyArg_Parse(arg,
+ "(iiiiii);" \
+ "pminrow,pmincol,sminrow,smincol,smaxrow,smaxcol",
+ &pminrow, &pmincol, &sminrow,
+ &smincol, &smaxrow, &smaxcol))
+ return NULL;
+ return PyCursesCheckERR(pnoutrefresh(self->win,
+ pminrow, pmincol, sminrow,
+ smincol, smaxrow, smaxcol),
+ "pnoutrefresh");
+ default:
+ PyErr_SetString(PyCursesError,
+ "noutrefresh was called for a pad;" \
+ "requires 6 arguments");
+ return NULL;
+ }
+ } else {
+ if (!PyArg_NoArgs(arg))
+ return NULL;
+ return PyCursesCheckERR(wnoutrefresh(self->win), "wnoutrefresh");
+ }
+}
+
+static PyObject *
+PyCursesWindow_PutWin(self, arg)
+ PyCursesWindowObject *self;
+ PyObject *arg;
+{
+ PyObject *temp;
+
+ if (!PyArg_Parse(arg, "O;fileobj", &temp))
+ return NULL;
+ if (!PyFile_Check(temp)) {
+ PyErr_SetString(PyExc_TypeError, "argument must be a file object");
+ return NULL;
+ }
+ return PyCursesCheckERR(putwin(self->win, PyFile_AsFile(temp)),
+ "putwin");
+}
+
+static PyObject *
+PyCursesWindow_RedrawLine(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int beg, num;
+ if (!PyArg_Parse(arg,"(ii);beg,num", &beg, &num))
+ return NULL;
+ return PyCursesCheckERR(wredrawln(self->win,beg,num), "redrawln");
+}
+
+static PyObject *
+PyCursesWindow_Refresh(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int pminrow,pmincol,sminrow,smincol,smaxrow,smaxcol;
+
+ if (self->win->_flags & _ISPAD) {
+ switch(ARG_COUNT(arg)) {
+ case 6:
+ if (!PyArg_Parse(arg,
+ "(iiiiii);" \
+ "pminrow,pmincol,sminrow,smincol,smaxrow,smaxcol",
+ &pminrow, &pmincol, &sminrow,
+ &smincol, &smaxrow, &smaxcol))
+ return NULL;
+ return PyCursesCheckERR(prefresh(self->win,
+ pminrow, pmincol, sminrow,
+ smincol, smaxrow, smaxcol),
+ "prefresh");
+ default:
+ PyErr_SetString(PyCursesError,
+ "refresh was called for a pad; requires 6 arguments");
+ return NULL;
+ }
+ } else {
+ if (!PyArg_NoArgs(arg))
+ return NULL;
+ return PyCursesCheckERR(wrefresh(self->win), "wrefresh");
+ }
+}
+
+static PyObject *
+PyCursesWindow_SetScrollRegion(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int x, y;
+ if (!PyArg_Parse(arg,"(ii);top, bottom",&y,&x))
+ return NULL;
+ return PyCursesCheckERR(wsetscrreg(self->win,y,x), "wsetscrreg");
+}
+
+static PyObject *
+PyCursesWindow_SubWin(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ WINDOW *win;
+ int nlines, ncols, begin_y, begin_x;
+
+ if (!PyArg_Parse(arg, "(iiii);nlines,ncols,begin_y,begin_x",
+ &nlines,&ncols,&begin_y,&begin_x))
+ return NULL;
+
+ if (self->win->_flags & _ISPAD)
+ win = subpad(self->win, nlines, ncols, begin_y, begin_x);
+ else
+ win = subwin(self->win,nlines,ncols,begin_y,begin_x);
+
+ if (win == NULL) {
+ PyErr_SetString(PyCursesError, catchall_NULL);
+ return NULL;
+ }
+
+ return (PyObject *)PyCursesWindow_New(win);
+}
+
+static PyObject *
+PyCursesWindow_Scroll(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int lines;
+ switch(ARG_COUNT(arg)) {
+ case 0:
+ return PyCursesCheckERR(scroll(self->win), "scroll");
+ break;
+ case 1:
+ if (!PyArg_Parse(arg, "i;lines", &lines))
+ return NULL;
+ return PyCursesCheckERR(wscrl(self->win, lines), "scroll");
+ default:
+ PyErr_SetString(PyExc_TypeError, "scroll requires 0 or 1 arguments");
+ return NULL;
+ }
+}
+
+static PyObject *
+PyCursesWindow_TouchLine(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ int st, cnt, val;
+ switch (ARG_COUNT(arg)) {
+ case 2:
+ if (!PyArg_Parse(arg,"(ii);start,count",&st,&cnt))
+ return NULL;
+ return PyCursesCheckERR(touchline(self->win,st,cnt), "touchline");
+ break;
+ case 3:
+ if (!PyArg_Parse(arg, "(iii);start,count,val", &st, &cnt, &val))
+ return NULL;
+ return PyCursesCheckERR(wtouchln(self->win, st, cnt, val), "touchline");
+ default:
+ PyErr_SetString(PyExc_TypeError, "touchline requires 2 or 3 arguments");
+ return NULL;
+ }
+}
+
+static PyObject *
+PyCursesWindow_Vline(self, args)
+ PyCursesWindowObject *self;
+ PyObject *args;
+{
+ PyObject *temp;
+ chtype ch;
+ int n, x, y, code = OK;
+ attr_t attr = A_NORMAL;
+
+ switch (ARG_COUNT(args)) {
+ case 2:
+ if (!PyArg_Parse(args, "(Oi);ch or int,n", &temp, &n))
+ return NULL;
+ break;
+ case 3:
+ if (!PyArg_Parse(args, "(Oil);ch or int,n,attr", &temp, &n, &attr))
+ return NULL;
+ break;
+ case 4:
+ if (!PyArg_Parse(args, "(iiOi);y,x,ch o int,n", &y, &x, &temp, &n))
+ return NULL;
+ code = wmove(self->win, y, x);
+ break;
+ case 5:
+ if (!PyArg_Parse(args, "(iiOil); y,x,ch or int,n,attr",
+ &y, &x, &temp, &n, &attr))
+ return NULL;
+ code = wmove(self->win, y, x);
+ default:
+ PyErr_SetString(PyExc_TypeError, "vline requires 2 or 5 arguments");
+ return NULL;
+ }
+
+ if (code != ERR) {
+ if (!PyCurses_ConvertToChtype(temp, &ch)) {
+ PyErr_SetString(PyExc_TypeError,
+ "argument 1 or 3 must be a ch or an int");
+ return NULL;
+ }
+ return PyCursesCheckERR(whline(self->win, ch | attr, n), "vline");
+ } else
+ return PyCursesCheckERR(code, "wmove");
+}
+
+static PyMethodDef PyCursesWindow_Methods[] = {
+ {"addch", (PyCFunction)PyCursesWindow_AddCh},
+ {"addnstr", (PyCFunction)PyCursesWindow_AddNStr},
+ {"addstr", (PyCFunction)PyCursesWindow_AddStr},
+ {"attron", (PyCFunction)PyCursesWindow_wattron},
+ {"attr_on", (PyCFunction)PyCursesWindow_wattron},
+ {"attroff", (PyCFunction)PyCursesWindow_wattroff},
+ {"attr_off", (PyCFunction)PyCursesWindow_wattroff},
+ {"attrset", (PyCFunction)PyCursesWindow_wattrset},
+ {"attr_set", (PyCFunction)PyCursesWindow_wattrset},
+ {"bkgd", (PyCFunction)PyCursesWindow_Bkgd},
+ {"bkgdset", (PyCFunction)PyCursesWindow_BkgdSet},
+ {"border", (PyCFunction)PyCursesWindow_Border, METH_VARARGS},
+ {"box", (PyCFunction)PyCursesWindow_Box},
+ {"clear", (PyCFunction)PyCursesWindow_wclear},
+ {"clearok", (PyCFunction)PyCursesWindow_clearok},
+ {"clrtobot", (PyCFunction)PyCursesWindow_wclrtobot},
+ {"clrtoeol", (PyCFunction)PyCursesWindow_wclrtoeol},
+ {"cursyncup", (PyCFunction)PyCursesWindow_wcursyncup},
+ {"delch", (PyCFunction)PyCursesWindow_DelCh},
+ {"deleteln", (PyCFunction)PyCursesWindow_wdeleteln},
+ {"derwin", (PyCFunction)PyCursesWindow_DerWin},
+ {"echochar", (PyCFunction)PyCursesWindow_EchoChar},
+ {"erase", (PyCFunction)PyCursesWindow_werase},
+ {"getbegyx", (PyCFunction)PyCursesWindow_getbegyx},
+ {"getbkgd", (PyCFunction)PyCursesWindow_GetBkgd},
+ {"getch", (PyCFunction)PyCursesWindow_GetCh},
+ {"getkey", (PyCFunction)PyCursesWindow_GetKey},
+ {"getmaxyx", (PyCFunction)PyCursesWindow_getmaxyx},
+ {"getparyx", (PyCFunction)PyCursesWindow_getparyx},
+ {"getstr", (PyCFunction)PyCursesWindow_GetStr},
+ {"getyx", (PyCFunction)PyCursesWindow_getyx},
+ {"hline", (PyCFunction)PyCursesWindow_Hline},
+ {"idlok", (PyCFunction)PyCursesWindow_idlok},
+ {"idcok", (PyCFunction)PyCursesWindow_idcok},
+ {"immedok", (PyCFunction)PyCursesWindow_immedok},
+ {"inch", (PyCFunction)PyCursesWindow_InCh},
+ {"insch", (PyCFunction)PyCursesWindow_InsCh},
+ {"insdelln", (PyCFunction)PyCursesWindow_winsdelln},
+ {"insertln", (PyCFunction)PyCursesWindow_winsertln},
+ {"insnstr", (PyCFunction)PyCursesWindow_InsNStr},
+ {"insstr", (PyCFunction)PyCursesWindow_InsStr},
+ {"instr", (PyCFunction)PyCursesWindow_InStr},
+ {"is_linetouched", (PyCFunction)PyCursesWindow_Is_LineTouched},
+ {"is_wintouched", (PyCFunction)PyCursesWindow_is_wintouched},
+ {"keypad", (PyCFunction)PyCursesWindow_keypad},
+ {"leaveok", (PyCFunction)PyCursesWindow_leaveok},
+ {"move", (PyCFunction)PyCursesWindow_wmove},
+ {"mvwin", (PyCFunction)PyCursesWindow_mvwin},
+ {"mvderwin", (PyCFunction)PyCursesWindow_mvderwin},
+ {"nodelay", (PyCFunction)PyCursesWindow_nodelay},
+ {"noutrefresh", (PyCFunction)PyCursesWindow_NoOutRefresh},
+ {"notimeout", (PyCFunction)PyCursesWindow_notimeout},
+ {"putwin", (PyCFunction)PyCursesWindow_PutWin},
+ {"redrawwin", (PyCFunction)PyCursesWindow_redrawwin},
+ {"redrawln", (PyCFunction)PyCursesWindow_RedrawLine},
+ {"refresh", (PyCFunction)PyCursesWindow_Refresh},
+#ifndef __sgi__
+ {"resize", (PyCFunction)PyCursesWindow_wresize},
+#endif
+ {"scroll", (PyCFunction)PyCursesWindow_Scroll},
+ {"scrollok", (PyCFunction)PyCursesWindow_scrollok},
+ {"setscrreg", (PyCFunction)PyCursesWindow_SetScrollRegion},
+ {"standend", (PyCFunction)PyCursesWindow_wstandend},
+ {"standout", (PyCFunction)PyCursesWindow_wstandout},
+ {"subpad", (PyCFunction)PyCursesWindow_SubWin},
+ {"subwin", (PyCFunction)PyCursesWindow_SubWin},
+ {"syncdown", (PyCFunction)PyCursesWindow_wsyncdown},
+ {"syncok", (PyCFunction)PyCursesWindow_syncok},
+ {"syncup", (PyCFunction)PyCursesWindow_wsyncup},
+ {"touchline", (PyCFunction)PyCursesWindow_TouchLine},
+ {"touchwin", (PyCFunction)PyCursesWindow_touchwin},
+ {"untouchwin", (PyCFunction)PyCursesWindow_untouchwin},
+ {"vline", (PyCFunction)PyCursesWindow_Vline},
+ {NULL, NULL} /* sentinel */
+};
+
+static PyObject *
+PyCursesWindow_GetAttr(self, name)
+ PyCursesWindowObject *self;
+ char *name;
+{
+ return Py_FindMethod(PyCursesWindow_Methods, (PyObject *)self, name);
+}
+
+/* -------------------------------------------------------*/
+
+PyTypeObject PyCursesWindow_Type = {
+ PyObject_HEAD_INIT(&PyType_Type)
+ 0, /*ob_size*/
+ "curses window", /*tp_name*/
+ sizeof(PyCursesWindowObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)PyCursesWindow_Dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ (getattrfunc)PyCursesWindow_GetAttr, /*tp_getattr*/
+ (setattrfunc)0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+};
+
+/*********************************************************************
+ Global Functions
+**********************************************************************/
+
+static PyObject *ModDict;
+
+/* Function Prototype Macros - They are ugly but very, very useful. ;-)
+
+ X - function name
+ TYPE - parameter Type
+ ERGSTR - format string for construction of the return value
+ PARSESTR - format string for argument parsing
+ */
+
+#define NoArgNoReturnFunction(X) \
+static PyObject *PyCurses_ ## X (self, arg) \
+ PyObject * self; \
+ PyObject * arg; \
+{ \
+ PyCursesInitialised \
+ if (!PyArg_NoArgs(arg)) return NULL; \
+ return PyCursesCheckERR(X(), # X); }
+
+#define NoArgOrFlagNoReturnFunction(X) \
+static PyObject *PyCurses_ ## X (self, arg) \
+ PyObject * self; \
+ PyObject * arg; \
+{ \
+ int flag = 0; \
+ PyCursesInitialised \
+ switch(ARG_COUNT(arg)) { \
+ case 0: \
+ return PyCursesCheckERR(X(), # X); \
+ case 1: \
+ if (!PyArg_Parse(arg, "i;True(1) or False(0)", &flag)) return NULL; \
+ if (flag) return PyCursesCheckERR(X(), # X); \
+ else return PyCursesCheckERR(no ## X (), # X); \
+ default: \
+ PyErr_SetString(PyExc_TypeError, # X " requires 0 or 1 argument"); \
+ return NULL; } }
+
+#define NoArgReturnIntFunction(X) \
+static PyObject *PyCurses_ ## X (self, arg) \
+ PyObject * self; \
+ PyObject * arg; \
+{ \
+ PyCursesInitialised \
+ if (!PyArg_NoArgs(arg)) return NULL; \
+ return PyInt_FromLong((long) X()); }
+
+
+#define NoArgReturnStringFunction(X) \
+static PyObject *PyCurses_ ## X (self, arg) \
+ PyObject * self; \
+ PyObject * arg; \
+{ \
+ PyCursesInitialised \
+ if (!PyArg_NoArgs(arg)) return NULL; \
+ return PyString_FromString(X()); }
+
+#define NoArgTrueFalseFunction(X) \
+static PyObject * PyCurses_ ## X (self,arg) \
+ PyObject * self; \
+ PyObject * arg; \
+{ \
+ PyCursesInitialised \
+ if (!PyArg_NoArgs(arg)) return NULL; \
+ if (X () == FALSE) { \
+ Py_INCREF(Py_False); \
+ return Py_False; \
+ } \
+ Py_INCREF(Py_True); \
+ return Py_True; }
+
+#define NoArgNoReturnVoidFunction(X) \
+static PyObject * PyCurses_ ## X (self,arg) \
+ PyObject * self; \
+ PyObject * arg; \
+{ \
+ PyCursesInitialised \
+ if (!PyArg_NoArgs(arg)) return NULL; \
+ X(); \
+ Py_INCREF(Py_None); \
+ return Py_None; }
+
+#define TwoArgNoReturnFunction(X, TYPE, PARSESTR) \
+static PyObject * PyCurses_ ## X (self,arg) \
+ PyObject * self; \
+ PyObject * arg; \
+{ \
+ TYPE arg1, arg2; \
+ PyCursesInitialised \
+ if (!PyArg_Parse(arg,PARSESTR, &arg1, &arg2)) return NULL; \
+ Py_INCREF(Py_None); \
+ return PyCursesCheckERR(X(arg1, arg2), # X); }
+
+NoArgNoReturnFunction(beep)
+NoArgNoReturnFunction(def_prog_mode)
+NoArgNoReturnFunction(def_shell_mode)
+NoArgNoReturnFunction(doupdate)
+NoArgNoReturnFunction(endwin)
+NoArgNoReturnFunction(flash)
+NoArgNoReturnFunction(nocbreak)
+NoArgNoReturnFunction(noecho)
+NoArgNoReturnFunction(nonl)
+NoArgNoReturnFunction(noraw)
+NoArgNoReturnFunction(reset_prog_mode)
+NoArgNoReturnFunction(reset_shell_mode)
+NoArgNoReturnFunction(resetty)
+NoArgNoReturnFunction(savetty)
+
+TwoArgNoReturnFunction(resizeterm, int, "(ii);y,x")
+
+NoArgOrFlagNoReturnFunction(cbreak)
+NoArgOrFlagNoReturnFunction(echo)
+NoArgOrFlagNoReturnFunction(nl)
+NoArgOrFlagNoReturnFunction(raw)
+
+NoArgReturnIntFunction(baudrate)
+NoArgReturnIntFunction(termattrs)
+
+NoArgReturnStringFunction(termname)
+NoArgReturnStringFunction(longname)
+
+NoArgTrueFalseFunction(can_change_color)
+NoArgTrueFalseFunction(has_colors)
+NoArgTrueFalseFunction(has_ic)
+NoArgTrueFalseFunction(has_il)
+NoArgTrueFalseFunction(isendwin)
+
+NoArgNoReturnVoidFunction(filter)
+NoArgNoReturnVoidFunction(flushinp)
+NoArgNoReturnVoidFunction(noqiflush)
+
+static PyObject *
+PyCurses_Color_Content(self, arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ short color,r,g,b;
+
+ PyCursesInitialised
+ PyCursesInitialisedColor
+
+ if (ARG_COUNT(arg) != 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "color_content requires 1 argument");
+ return NULL;
+ }
+
+ if (!PyArg_Parse(arg, "h;color", &color)) return NULL;
+
+ if (color_content(color, &r, &g, &b) != ERR)
+ return Py_BuildValue("(iii)", r, g, b);
+ else {
+ PyErr_SetString(PyCursesError,
+ "Argument 1 was out of range. Check value of COLORS.");
+ return NULL;
+ }
+}
+
+static PyObject *
+PyCurses_COLOR_PAIR(self, arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ int n;
+
+ PyCursesInitialised
+ PyCursesInitialisedColor
+
+ if (ARG_COUNT(arg)!=1) {
+ PyErr_SetString(PyExc_TypeError, "COLOR_PAIR requires 1 argument");
+ return NULL;
+ }
+ if (!PyArg_Parse(arg, "i;number", &n)) return NULL;
+ return PyInt_FromLong((long) (n << 8));
+}
+
+static PyObject *
+PyCurses_Curs_Set(self, arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ int vis,erg;
+
+ PyCursesInitialised
+
+ if (ARG_COUNT(arg)==1) {
+ PyErr_SetString(PyExc_TypeError, "curs_set requires 1 argument");
+ return NULL;
+ }
+
+ if (!PyArg_Parse(arg, "i;int", &vis)) return NULL;
+
+ erg = curs_set(vis);
+ if (erg == ERR) return PyCursesCheckERR(erg, "curs_set");
+
+ return PyInt_FromLong((long) erg);
+}
+
+static PyObject *
+PyCurses_Delay_Output(self,arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ int ms;
+
+ PyCursesInitialised
+
+ if (ARG_COUNT(arg)==1) {
+ PyErr_SetString(PyExc_TypeError, "delay_output requires 1 argument");
+ return NULL;
+ }
+ if (!PyArg_Parse(arg, "i;ms", &ms)) return NULL;
+
+ return PyCursesCheckERR(delay_output(ms), "delay_output");
+}
+
+static PyObject *
+PyCurses_EraseChar(self,arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ char ch;
+
+ PyCursesInitialised
+
+ if (!PyArg_NoArgs(arg)) return NULL;
+
+ ch = erasechar();
+
+ return PyString_FromString(&ch);
+}
+
+static PyObject *
+PyCurses_getsyx(self, arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ int x,y;
+
+ PyCursesInitialised
+
+ if (!PyArg_NoArgs(arg)) return NULL;
+
+ getsyx(y, x);
+
+ return Py_BuildValue("(ii)", y, x);
+}
+
+static PyObject *
+PyCurses_GetWin(self,arg)
+ PyCursesWindowObject *self;
+ PyObject * arg;
+{
+ WINDOW *win;
+ PyObject *temp;
+
+ PyCursesInitialised
+
+ if (!PyArg_Parse(arg, "O;fileobj", &temp)) return NULL;
+
+ if (!PyFile_Check(temp)) {
+ PyErr_SetString(PyExc_TypeError, "argument must be a file object");
+ return NULL;
+ }
+
+ win = getwin(PyFile_AsFile(temp));
+
+ if (win == NULL) {
+ PyErr_SetString(PyCursesError, catchall_NULL);
+ return NULL;
+ }
+
+ return PyCursesWindow_New(win);
+}
+
+static PyObject *
+PyCurses_HalfDelay(self,arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ unsigned char tenths;
+
+ PyCursesInitialised
+
+ switch(ARG_COUNT(arg)) {
+ case 1:
+ if (!PyArg_Parse(arg, "b;tenths", &tenths)) return NULL;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "halfdelay requires 1 argument");
+ return NULL;
+ }
+
+ return PyCursesCheckERR(halfdelay(tenths), "halfdelay");
+}
+
+#ifndef __sgi__
+ /* No has_key! */
+static PyObject * PyCurses_has_key(self,arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ int ch;
+
+ PyCursesInitialised
+
+ if (!PyArg_Parse(arg,"i",&ch)) return NULL;
+
+ if (has_key(ch) == FALSE) {
+ Py_INCREF(Py_False);
+ return Py_False;
+ }
+ Py_INCREF(Py_True);
+ return Py_True;
+}
+#endif
+
+static PyObject *
+PyCurses_Init_Color(self, arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ short color, r, g, b;
+
+ PyCursesInitialised
+ PyCursesInitialisedColor
+
+ switch(ARG_COUNT(arg)) {
+ case 4:
+ if (!PyArg_Parse(arg, "(hhhh);color,r,g,b", &color, &r, &g, &b)) return NULL;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "init_color requires 4 arguments");
+ return NULL;
+ }
+
+ return PyCursesCheckERR(init_color(color, r, g, b), "init_color");
+}
+
+static PyObject *
+PyCurses_Init_Pair(self, arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ short pair, f, b;
+
+ PyCursesInitialised
+ PyCursesInitialisedColor
+
+ if (ARG_COUNT(arg) != 3) {
+ PyErr_SetString(PyExc_TypeError, "init_pair requires 3 arguments");
+ return NULL;
+ }
+
+ if (!PyArg_Parse(arg, "(hhh);pair, f, b", &pair, &f, &b)) return NULL;
+
+ return PyCursesCheckERR(init_pair(pair, f, b), "init_pair");
+}
+
+static PyObject *
+PyCurses_InitScr(self, args)
+ PyObject * self;
+ PyObject * args;
+{
+ WINDOW *win;
+ PyObject *lines, *cols;
+
+ if (!PyArg_NoArgs(args)) return NULL;
+
+ if (initialised == TRUE) {
+ wrefresh(stdscr);
+ return (PyObject *)PyCursesWindow_New(stdscr);
+ }
+
+ win = initscr();
+
+ if (win == NULL) {
+ PyErr_SetString(PyCursesError, catchall_NULL);
+ return NULL;
+ }
+
+ initialised = TRUE;
+
+ lines = PyInt_FromLong((long) LINES);
+ PyDict_SetItemString(ModDict, "LINES", lines);
+ Py_DECREF(lines);
+ cols = PyInt_FromLong((long) COLS);
+ PyDict_SetItemString(ModDict, "COLS", cols);
+ Py_DECREF(cols);
+
+ return (PyObject *)PyCursesWindow_New(win);
+}
+
+
+static PyObject *
+PyCurses_IntrFlush(self,arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ int ch;
+
+ PyCursesInitialised
+
+ switch(ARG_COUNT(arg)) {
+ case 1:
+ if (!PyArg_Parse(arg,"i;True(1), False(0)",&ch)) return NULL;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "intrflush requires 1 argument");
+ return NULL;
+ }
+
+ return PyCursesCheckERR(intrflush(NULL,ch), "intrflush");
+}
+
+static PyObject *
+PyCurses_KeyName(self,arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ const char *knp;
+ int ch;
+
+ PyCursesInitialised
+
+ if (!PyArg_Parse(arg,"i",&ch)) return NULL;
+
+ knp = keyname(ch);
+
+ return PyString_FromString((knp == NULL) ? "" : (char *)knp);
+}
+
+static PyObject *
+PyCurses_KillChar(self,arg)
+PyObject * self;
+PyObject * arg;
+{
+ char ch;
+
+ if (!PyArg_NoArgs(arg)) return NULL;
+
+ ch = killchar();
+
+ return PyString_FromString(&ch);
+}
+
+static PyObject *
+PyCurses_Meta(self,arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ int ch;
+
+ PyCursesInitialised
+
+ switch(ARG_COUNT(arg)) {
+ case 1:
+ if (!PyArg_Parse(arg,"i;True(1), False(0)",&ch)) return NULL;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "meta requires 1 argument");
+ return NULL;
+ }
+
+ return PyCursesCheckERR(meta(stdscr, ch), "meta");
+}
+
+static PyObject *
+PyCurses_NewPad(self,arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ WINDOW *win;
+ int nlines, ncols;
+
+ PyCursesInitialised
+
+ if (!PyArg_Parse(arg,"(ii);nlines,ncols",&nlines,&ncols)) return NULL;
+
+ win = newpad(nlines, ncols);
+
+ if (win == NULL) {
+ PyErr_SetString(PyCursesError, catchall_NULL);
+ return NULL;
+ }
+
+ return (PyObject *)PyCursesWindow_New(win);
+}
+
+static PyObject *
+PyCurses_NewWindow(self,arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ WINDOW *win;
+ int nlines, ncols, begin_y, begin_x;
+
+ PyCursesInitialised
+
+ switch (ARG_COUNT(arg)) {
+ case 2:
+ if (!PyArg_Parse(arg,"(ii);nlines,ncols",&nlines,&ncols))
+ return NULL;
+ win = newpad(nlines, ncols);
+ break;
+ case 4:
+ if (!PyArg_Parse(arg, "(iiii);nlines,ncols,begin_y,begin_x",
+ &nlines,&ncols,&begin_y,&begin_x))
+ return NULL;
+ win = newwin(nlines,ncols,begin_y,begin_x);
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "newwin requires 2 or 4 arguments");
+ return NULL;
+ }
+
+ if (win == NULL) {
+ PyErr_SetString(PyCursesError, catchall_NULL);
+ return NULL;
+ }
+
+ return (PyObject *)PyCursesWindow_New(win);
+}
+
+static PyObject *
+PyCurses_Pair_Content(self, arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ short pair,f,b;
+
+ PyCursesInitialised
+ PyCursesInitialisedColor
+
+ switch(ARG_COUNT(arg)) {
+ case 1:
+ if (!PyArg_Parse(arg, "h;pair", &pair)) return NULL;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "pair_content requires 1 argument");
+ return NULL;
+ }
+
+ if (!pair_content(pair, &f, &b)) {
+ PyErr_SetString(PyCursesError,
+ "Argument 1 was out of range. (1..COLOR_PAIRS-1)");
+ return NULL;
+ }
+
+ return Py_BuildValue("(ii)", f, b);
+}
+
+static PyObject *
+PyCurses_PAIR_NUMBER(self, arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ int n;
+
+ PyCursesInitialised
+ PyCursesInitialisedColor
+
+ switch(ARG_COUNT(arg)) {
+ case 1:
+ if (!PyArg_Parse(arg, "i;pairvalue", &n)) return NULL;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError,
+ "PAIR_NUMBER requires 1 argument");
+ return NULL;
+ }
+
+ return PyInt_FromLong((long) ((n & A_COLOR) >> 8));
+}
+
+static PyObject *
+PyCurses_Putp(self,arg)
+ PyObject *self;
+ PyObject *arg;
+{
+ char *str;
+
+ if (!PyArg_Parse(arg,"s;str", &str)) return NULL;
+ return PyCursesCheckERR(putp(str), "putp");
+}
+
+static PyObject *
+PyCurses_QiFlush(self, arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ int flag = 0;
+
+ PyCursesInitialised
+
+ switch(ARG_COUNT(arg)) {
+ case 0:
+ qiflush();
+ Py_INCREF(Py_None);
+ return Py_None;
+ case 1:
+ if (!PyArg_Parse(arg, "i;True(1) or False(0)", &flag)) return NULL;
+ if (flag) qiflush();
+ else noqiflush();
+ Py_INCREF(Py_None);
+ return Py_None;
+ default:
+ PyErr_SetString(PyExc_TypeError, "nl requires 0 or 1 argument");
+ return NULL;
+ }
+}
+
+static PyObject *
+PyCurses_setsyx(self, arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ int y,x;
+
+ PyCursesInitialised
+
+ if (ARG_COUNT(arg)!=3) {
+ PyErr_SetString(PyExc_TypeError, "curs_set requires 3 argument");
+ return NULL;
+ }
+
+ if (!PyArg_Parse(arg, "(ii);y, x", &y, &x)) return NULL;
+
+ setsyx(y,x);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+PyCurses_Start_Color(self,arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ int code;
+ PyObject *c, *cp;
+
+ PyCursesInitialised
+
+ if (!PyArg_NoArgs(arg)) return NULL;
+
+ code = start_color();
+ if (code != ERR) {
+ initialisedcolors = TRUE;
+ c = PyInt_FromLong((long) COLORS);
+ PyDict_SetItemString(ModDict, "COLORS", c);
+ Py_DECREF(c);
+ cp = PyInt_FromLong((long) COLOR_PAIRS);
+ PyDict_SetItemString(ModDict, "COLOR_PAIRS", cp);
+ Py_DECREF(cp);
+ Py_INCREF(Py_None);
+ return Py_None;
+ } else {
+ PyErr_SetString(PyCursesError, "start_color() returned ERR");
+ return NULL;
+ }
+}
+
+static PyObject *
+PyCurses_UnCtrl(self,arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ PyObject *temp;
+ chtype ch;
+
+ PyCursesInitialised
+
+ if (!PyArg_Parse(arg,"O;ch or int",&temp)) return NULL;
+
+ if (PyInt_Check(temp))
+ ch = (chtype) PyInt_AsLong(temp);
+ else if (PyString_Check(temp))
+ ch = (chtype) *PyString_AsString(temp);
+ else {
+ PyErr_SetString(PyExc_TypeError, "argument must be a ch or an int");
+ return NULL;
+ }
+
+ return PyString_FromString(unctrl(ch));
+}
+
+static PyObject *
+PyCurses_UngetCh(self,arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ PyObject *temp;
+ chtype ch;
+
+ PyCursesInitialised
+
+ if (!PyArg_Parse(arg,"O;ch or int",&temp)) return NULL;
+
+ if (PyInt_Check(temp))
+ ch = (chtype) PyInt_AsLong(temp);
+ else if (PyString_Check(temp))
+ ch = (chtype) *PyString_AsString(temp);
+ else {
+ PyErr_SetString(PyExc_TypeError, "argument must be a ch or an int");
+ return NULL;
+ }
+
+ return PyCursesCheckERR(ungetch(ch), "ungetch");
+}
+
+static PyObject *
+PyCurses_Use_Env(self,arg)
+ PyObject * self;
+ PyObject * arg;
+{
+ int flag;
+
+ PyCursesInitialised
+
+ switch(ARG_COUNT(arg)) {
+ case 1:
+ if (!PyArg_Parse(arg,"i;True(1), False(0)",&flag))
+ return NULL;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "use_env requires 1 argument");
+ return NULL;
+ }
+ use_env(flag);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+/* List of functions defined in the module */
+
+static PyMethodDef PyCurses_methods[] = {
+ {"baudrate", (PyCFunction)PyCurses_baudrate},
+ {"beep", (PyCFunction)PyCurses_beep},
+ {"can_change_color", (PyCFunction)PyCurses_can_change_color},
+ {"cbreak", (PyCFunction)PyCurses_cbreak},
+ {"color_content", (PyCFunction)PyCurses_Color_Content},
+ {"COLOR_PAIR", (PyCFunction)PyCurses_COLOR_PAIR},
+ {"curs_set", (PyCFunction)PyCurses_Curs_Set},
+ {"def_prog_mode", (PyCFunction)PyCurses_def_prog_mode},
+ {"def_shell_mode", (PyCFunction)PyCurses_def_shell_mode},
+ {"delay_output", (PyCFunction)PyCurses_Delay_Output},
+ {"doupdate", (PyCFunction)PyCurses_doupdate},
+ {"echo", (PyCFunction)PyCurses_echo},
+ {"endwin", (PyCFunction)PyCurses_endwin},
+ {"erasechar", (PyCFunction)PyCurses_EraseChar},
+ {"filter", (PyCFunction)PyCurses_filter},
+ {"flash", (PyCFunction)PyCurses_flash},
+ {"flushinp", (PyCFunction)PyCurses_flushinp},
+ {"getsyx", (PyCFunction)PyCurses_getsyx},
+ {"getwin", (PyCFunction)PyCurses_GetWin},
+ {"has_colors", (PyCFunction)PyCurses_has_colors},
+ {"has_ic", (PyCFunction)PyCurses_has_ic},
+ {"has_il", (PyCFunction)PyCurses_has_il},
+#ifndef __sgi__
+ {"has_key", (PyCFunction)PyCurses_has_key},
+#endif
+ {"halfdelay", (PyCFunction)PyCurses_HalfDelay},
+ {"init_color", (PyCFunction)PyCurses_Init_Color},
+ {"init_pair", (PyCFunction)PyCurses_Init_Pair},
+ {"initscr", (PyCFunction)PyCurses_InitScr},
+ {"intrflush", (PyCFunction)PyCurses_IntrFlush},
+ {"isendwin", (PyCFunction)PyCurses_isendwin},
+ {"keyname", (PyCFunction)PyCurses_KeyName},
+ {"killchar", (PyCFunction)PyCurses_KillChar},
+ {"longname", (PyCFunction)PyCurses_longname},
+ {"meta", (PyCFunction)PyCurses_Meta},
+ {"newpad", (PyCFunction)PyCurses_NewPad},
+ {"newwin", (PyCFunction)PyCurses_NewWindow},
+ {"nl", (PyCFunction)PyCurses_nl},
+ {"nocbreak", (PyCFunction)PyCurses_nocbreak},
+ {"noecho", (PyCFunction)PyCurses_noecho},
+ {"nonl", (PyCFunction)PyCurses_nonl},
+ {"noqiflush", (PyCFunction)PyCurses_noqiflush},
+ {"noraw", (PyCFunction)PyCurses_noraw},
+ {"pair_content", (PyCFunction)PyCurses_Pair_Content},
+ {"PAIR_NUMBER", (PyCFunction)PyCurses_PAIR_NUMBER},
+ {"putp", (PyCFunction)PyCurses_Putp},
+ {"qiflush", (PyCFunction)PyCurses_QiFlush},
+ {"raw", (PyCFunction)PyCurses_raw},
+ {"reset_prog_mode", (PyCFunction)PyCurses_reset_prog_mode},
+ {"reset_shell_mode", (PyCFunction)PyCurses_reset_shell_mode},
+ {"resetty", (PyCFunction)PyCurses_resetty},
+ {"savetty", (PyCFunction)PyCurses_savetty},
+ {"resizeterm", (PyCFunction)PyCurses_resizeterm},
+ {"setsyx", (PyCFunction)PyCurses_setsyx},
+ {"start_color", (PyCFunction)PyCurses_Start_Color},
+ {"termattrs", (PyCFunction)PyCurses_termattrs},
+ {"termname", (PyCFunction)PyCurses_termname},
+ {"unctrl", (PyCFunction)PyCurses_UnCtrl},
+ {"ungetch", (PyCFunction)PyCurses_UngetCh},
+ {"use_env", (PyCFunction)PyCurses_Use_Env},
+ {NULL, NULL} /* sentinel */
+};
+
+/* Initialization function for the module */
+
+void
+initjack_curses()
+{
+ PyObject *m, *d, *v;
+
+ /* Create the module and add the functions */
+ m = Py_InitModule("jack_curses", PyCurses_methods);
+
+ /* Add some symbolic constants to the module */
+ d = PyModule_GetDict(m);
+ ModDict = d; /* For PyCurses_InitScr */
+
+ /* For exception curses.error */
+ PyCursesError = PyString_FromString("curses.error");
+ PyDict_SetItemString(d, "error", PyCursesError);
+
+ /* Make the version available */
+ v = PyString_FromString(PyCursesVersion);
+ PyDict_SetItemString(d, "version", v);
+ PyDict_SetItemString(d, "__version__", v);
+ Py_DECREF(v);
+
+ /* Here are some attributes you can add to chars to print */
+
+#define SetDictInt(string,ch) \
+ PyDict_SetItemString(ModDict,string,PyInt_FromLong((long) (ch)));
+
+#ifndef __sgi__
+ /* On IRIX 5.3, the ACS characters aren't available until initscr() has been called. */
+ SetDictInt("ACS_ULCORNER", (ACS_ULCORNER));
+ SetDictInt("ACS_LLCORNER", (ACS_LLCORNER));
+ SetDictInt("ACS_URCORNER", (ACS_URCORNER));
+ SetDictInt("ACS_LRCORNER", (ACS_LRCORNER));
+ SetDictInt("ACS_LTEE", (ACS_LTEE));
+ SetDictInt("ACS_RTEE", (ACS_RTEE));
+ SetDictInt("ACS_BTEE", (ACS_BTEE));
+ SetDictInt("ACS_TTEE", (ACS_TTEE));
+ SetDictInt("ACS_HLINE", (ACS_HLINE));
+ SetDictInt("ACS_VLINE", (ACS_VLINE));
+ SetDictInt("ACS_PLUS", (ACS_PLUS));
+ SetDictInt("ACS_S1", (ACS_S1));
+ SetDictInt("ACS_S9", (ACS_S9));
+ SetDictInt("ACS_DIAMOND", (ACS_DIAMOND));
+ SetDictInt("ACS_CKBOARD", (ACS_CKBOARD));
+ SetDictInt("ACS_DEGREE", (ACS_DEGREE));
+ SetDictInt("ACS_PLMINUS", (ACS_PLMINUS));
+ SetDictInt("ACS_BULLET", (ACS_BULLET));
+ SetDictInt("ACS_LARROW", (ACS_LARROW));
+ SetDictInt("ACS_RARROW", (ACS_RARROW));
+ SetDictInt("ACS_DARROW", (ACS_DARROW));
+ SetDictInt("ACS_UARROW", (ACS_UARROW));
+ SetDictInt("ACS_BOARD", (ACS_BOARD));
+ SetDictInt("ACS_LANTERN", (ACS_LANTERN));
+ SetDictInt("ACS_BLOCK", (ACS_BLOCK));
+#ifndef __sgi__
+ /* The following are never available on IRIX 5.3 */
+ SetDictInt("ACS_S3", (ACS_S3));
+ SetDictInt("ACS_LEQUAL", (ACS_LEQUAL));
+ SetDictInt("ACS_GEQUAL", (ACS_GEQUAL));
+ SetDictInt("ACS_PI", (ACS_PI));
+ SetDictInt("ACS_NEQUAL", (ACS_NEQUAL));
+ SetDictInt("ACS_STERLING", (ACS_STERLING));
+#endif
+ SetDictInt("ACS_BSSB", (ACS_ULCORNER));
+ SetDictInt("ACS_SSBB", (ACS_LLCORNER));
+ SetDictInt("ACS_BBSS", (ACS_URCORNER));
+ SetDictInt("ACS_SBBS", (ACS_LRCORNER));
+ SetDictInt("ACS_SBSS", (ACS_RTEE));
+ SetDictInt("ACS_SSSB", (ACS_LTEE));
+ SetDictInt("ACS_SSBS", (ACS_BTEE));
+ SetDictInt("ACS_BSSS", (ACS_TTEE));
+ SetDictInt("ACS_BSBS", (ACS_HLINE));
+ SetDictInt("ACS_SBSB", (ACS_VLINE));
+ SetDictInt("ACS_SSSS", (ACS_PLUS));
+#endif
+
+ SetDictInt("A_ATTRIBUTES", A_ATTRIBUTES);
+ SetDictInt("A_NORMAL", A_NORMAL);
+ SetDictInt("A_STANDOUT", A_STANDOUT);
+ SetDictInt("A_UNDERLINE", A_UNDERLINE);
+ SetDictInt("A_REVERSE", A_REVERSE);
+ SetDictInt("A_BLINK", A_BLINK);
+ SetDictInt("A_DIM", A_DIM);
+ SetDictInt("A_BOLD", A_BOLD);
+ SetDictInt("A_ALTCHARSET", A_ALTCHARSET);
+ SetDictInt("A_INVIS", A_INVIS);
+ SetDictInt("A_PROTECT", A_PROTECT);
+#ifndef __sgi__
+ SetDictInt("A_HORIZONTAL", A_HORIZONTAL);
+ SetDictInt("A_LEFT", A_LEFT);
+ SetDictInt("A_LOW", A_LOW);
+ SetDictInt("A_RIGHT", A_RIGHT);
+ SetDictInt("A_TOP", A_TOP);
+ SetDictInt("A_VERTICAL", A_VERTICAL);
+#endif
+ SetDictInt("A_CHARTEXT", A_CHARTEXT);
+ SetDictInt("A_COLOR", A_COLOR);
+#ifndef __sgi__
+ SetDictInt("WA_ATTRIBUTES", WA_ATTRIBUTES);
+ SetDictInt("WA_NORMAL", WA_NORMAL);
+ SetDictInt("WA_STANDOUT", WA_STANDOUT);
+ SetDictInt("WA_UNDERLINE", WA_UNDERLINE);
+ SetDictInt("WA_REVERSE", WA_REVERSE);
+ SetDictInt("WA_BLINK", WA_BLINK);
+ SetDictInt("WA_DIM", WA_DIM);
+ SetDictInt("WA_BOLD", WA_BOLD);
+ SetDictInt("WA_ALTCHARSET", WA_ALTCHARSET);
+ SetDictInt("WA_INVIS", WA_INVIS);
+ SetDictInt("WA_PROTECT", WA_PROTECT);
+ SetDictInt("WA_HORIZONTAL", WA_HORIZONTAL);
+ SetDictInt("WA_LEFT", WA_LEFT);
+ SetDictInt("WA_LOW", WA_LOW);
+ SetDictInt("WA_RIGHT", WA_RIGHT);
+ SetDictInt("WA_TOP", WA_TOP);
+ SetDictInt("WA_VERTICAL", WA_VERTICAL);
+#endif
+ SetDictInt("COLOR_BLACK", COLOR_BLACK);
+ SetDictInt("COLOR_RED", COLOR_RED);
+ SetDictInt("COLOR_GREEN", COLOR_GREEN);
+ SetDictInt("COLOR_YELLOW", COLOR_YELLOW);
+ SetDictInt("COLOR_BLUE", COLOR_BLUE);
+ SetDictInt("COLOR_MAGENTA", COLOR_MAGENTA);
+ SetDictInt("COLOR_CYAN", COLOR_CYAN);
+ SetDictInt("COLOR_WHITE", COLOR_WHITE);
+
+ /* Now set everything up for KEY_ variables */
+ {
+ int key;
+ char *key_n;
+ char *key_n2;
+ for (key=KEY_MIN;key < KEY_MAX; key++) {
+ key_n = (char *)keyname(key);
+ if (key_n == NULL || strcmp(key_n,"UNKNOWN KEY")==0)
+ continue;
+ if (strncmp(key_n,"KEY_F(",6)==0) {
+ char *p1, *p2;
+ key_n2 = malloc(strlen(key_n)+1);
+ p1 = key_n;
+ p2 = key_n2;
+ while (*p1) {
+ if (*p1 != '(' && *p1 != ')') {
+ *p2 = *p1;
+ p2++;
+ }
+ p1++;
+ }
+ *p2 = (char)0;
+ } else
+ key_n2 = key_n;
+ PyDict_SetItemString(d,key_n2,PyInt_FromLong((long) key));
+ if (key_n2 != key_n)
+ free(key_n2);
+ }
+ SetDictInt("KEY_MIN", KEY_MIN);
+ SetDictInt("KEY_MAX", KEY_MAX);
+ }
+
+ /* Check for errors */
+ if (PyErr_Occurred())
+ Py_FatalError("can't initialize module curses");
+}
+
+
diff --git a/cursesmodule/lifedemo.py b/cursesmodule/lifedemo.py
new file mode 100644
index 0000000..2a8a69b
--- /dev/null
+++ b/cursesmodule/lifedemo.py
@@ -0,0 +1,215 @@
+#!/usr/bin/env python
+# life.py -- A curses-based version of Conway's Game of Life.
+# Contributed by A.M. Kuchling <amk@magnet.com>
+#
+# An empty board will be displayed, and the following commands are available:
+# E : Erase the board
+# R : Fill the board randomly
+# S : Step for a single generation
+# C : Update continuously until a key is struck
+# Q : Quit
+# Cursor keys : Move the cursor around the board
+# Space or Enter : Toggle the contents of the cursor's position
+#
+# TODO :
+# Support the mouse
+# Use colour if available
+# Make board updates faster
+#
+
+class LifeBoard:
+ """Encapsulates a Life board
+
+ Attributes:
+ M,N : horizontal and vertical size of the board
+
+ Methods:
+ display(update_board) -- If update_board is true, compute the
+ next generation. Then display the state
+ of the board and refresh the screen.
+ erase() -- clear the entire board
+ makeRandom() -- fill the board randomly
+ set(y,x) -- set the given cell to Live; doesn't refresh the screen
+ toggle(y,x) -- change the given cell from live to dead, or vice
+ versa, and refresh the screen display
+
+ """
+ def __init__(self, scr, char=ord('*')):
+ """Return a LifeBoard instance.
+
+ scr -- curses screen object to use for display
+ char -- character used to render live cells (default: '*')
+
+ """
+ self.state={} ; self.scr=scr
+ N, M = self.scr.getmaxyx()
+ self.M, self.N = M-2, N-2-1
+ self.char = char
+ self.scr.clear()
+
+ # Draw a border around the board
+ border_line='+'+(self.M*'-')+'+'
+ self.scr.addstr(0, 0, border_line)
+ self.scr.addstr(self.N+1,0, border_line)
+ for y in range(0, self.N):
+ self.scr.addstr(1+y, 0, '|')
+ self.scr.addstr(1+y, self.M+1, '|')
+ self.scr.refresh()
+ def set(self, y, x):
+ """Set a cell to the live state"""
+ if x<0 or self.M<=x or y<0 or self.N<=y:
+ raise ValueError, "Coordinates out of range %i,%i"% (y,x)
+ self.state[x,y] = 1
+ def toggle(self, y, x):
+ """Toggle a cell's state between live and dead"""
+ if x<0 or self.M<=x or y<0 or self.N<=y:
+ raise ValueError, "Coordinates out of range %i,%i"% (y,x)
+ if self.state.has_key(x,y):
+ del self.state[x,y]
+ self.scr.addch(y+1, x+1, ' ')
+ else:
+ self.state[x,y]=1
+ self.scr.addch(y+1, x+1, self.char)
+ self.scr.refresh()
+ def erase(self):
+ """Clear the entire board and update the board display"""
+ self.state={}
+ self.display(update_board=0)
+ def display(self, update_board=1):
+ """Display the whole board, optionally computing one generation"""
+ M,N = self.M, self.N
+ if not update_board:
+ for i in range(0, M):
+ for j in range(0, N):
+ if self.state.has_key( (i,j) ):
+ self.scr.addch(j+1, i+1, self.char)
+ else:
+ self.scr.addch(j+1, i+1, ' ')
+ self.scr.refresh()
+ return
+
+ d={} ; self.boring=1
+ for i in range(0, M):
+ L=range( max(0, i-1), min(M, i+2) )
+ for j in range(0, N):
+ s=0
+ live=self.state.has_key( (i,j) )
+ for k in range( max(0, j-1), min(N, j+2) ):
+ for l in L:
+ if self.state.has_key( (l,k) ):
+ s=s+1
+ s=s-live
+ if s==3:
+ # Birth
+ d[i,j]=1
+ self.scr.addch(j+1, i+1, self.char)
+ if not live: self.boring=0
+ elif s==2 and live: d[i,j]=1 # Survival
+ elif live:
+ # Death
+ self.scr.addch(j+1, i+1, ' ')
+ self.boring=0
+ self.state=d
+ self.scr.refresh()
+ def makeRandom(self):
+ """Fill the board with a random pattern"""
+ import whrandom
+ self.state={}
+ for i in range(0, self.M):
+ for j in range(0, self.N):
+ if whrandom.random()*10>5.0: self.set(j,i)
+
+
+def erase_menu(stdscr, menu_y):
+ "Clear the space where the menu resides"
+ stdscr.move(menu_y, 0) ; stdscr.clrtoeol()
+ stdscr.move(menu_y+1, 0) ; stdscr.clrtoeol()
+
+def display_menu(stdscr, menu_y):
+ "Display the menu of possible keystroke commands"
+ erase_menu(stdscr, menu_y)
+ stdscr.addstr(menu_y, 4, 'Use the cursor keys to move, and space or Enter to toggle a cell.')
+ stdscr.addstr(menu_y+1, 4, 'E)rase the board, R)andom fill, S)tep once or C)ontinuously, Q)uit')
+
+def main(stdscr):
+ import string, curses
+
+ # Clear the screen and dispaly the menu of keys
+ stdscr.clear()
+ stdscr_y, stdscr_x = stdscr.getmaxyx()
+ menu_y=(stdscr_y-3)-1
+ display_menu(stdscr, menu_y)
+
+ # Allocate a subwindow for the Life board and create the board object
+ subwin=stdscr.subwin(stdscr_y-3, stdscr_x, 0, 0)
+ board=LifeBoard(subwin, char=ord('*'))
+ board.display(update_board=0)
+ # xpos, ypos are the cursor's position
+ xpos, ypos = board.M/2, board.N/2
+
+ # Main loop:
+ while (1):
+ stdscr.move(1+ypos, 1+xpos) # Move the cursor
+ c=stdscr.getch() # Get a keystroke
+ if 0<c<256:
+ c=chr(c)
+ if c in ' \n':
+ board.toggle(ypos, xpos)
+ elif c in 'Cc':
+ erase_menu(stdscr, menu_y)
+ stdscr.addstr(menu_y, 6, ' Hit any key to stop continuously '
+ 'updating the screen.')
+ stdscr.refresh()
+ # Activate nodelay mode; getch() will return -1
+ # if no keystroke is available, instead of waiting.
+ stdscr.nodelay(1)
+ while (1):
+ c=stdscr.getch()
+ if c!=-1: break
+ stdscr.addstr(0,0, '/'); stdscr.refresh()
+ board.display()
+ stdscr.addstr(0,0, '+'); stdscr.refresh()
+
+ stdscr.nodelay(0) # Disable nodelay mode
+ display_menu(stdscr, menu_y)
+
+ elif c in 'Ee': board.erase()
+ elif c in 'Qq': break
+ elif c in 'Rr':
+ board.makeRandom()
+ board.display(update_board=0)
+ elif c in 'Ss':
+ board.display()
+ else: pass # Ignore incorrect keys
+ elif c==curses.key_up and ypos>0: ypos=ypos-1
+ elif c==curses.key_down and ypos<board.N-1: ypos=ypos+1
+ elif c==curses.key_left and xpos>0: xpos=xpos-1
+ elif c==curses.key_right and xpos<board.M-1: xpos=xpos+1
+ else: pass # Ignore incorrect keys
+
+if __name__=='__main__':
+ import curses, traceback
+ try:
+ # Initialize curses
+ stdscr=curses.initscr()
+ # Turn off echoing of keys, and enter cbreak mode,
+ # where no buffering is performed on keyboard input
+ curses.noecho() ; curses.cbreak()
+
+ # In keypad mode, escape sequences for special keys
+ # (like the cursor keys) will be interpreted and
+ # a special value like curses.key_left will be returned
+ stdscr.keypad(1)
+ main(stdscr) # Enter the main loop
+ # Set everything back to normal
+ stdscr.keypad(0)
+ curses.echo() ; curses.nocbreak()
+ curses.endwin() # Terminate curses
+ except:
+ # In the event of an error, restore the terminal
+ # to a sane state.
+ stdscr.keypad(0)
+ curses.echo() ; curses.nocbreak()
+ curses.endwin()
+ traceback.print_exc() # Print the exception
+
diff --git a/cursesmodule/precompiled/Debian-Potato/cursesmodule.so b/cursesmodule/precompiled/Debian-Potato/cursesmodule.so
new file mode 100755
index 0000000..3940863
--- /dev/null
+++ b/cursesmodule/precompiled/Debian-Potato/cursesmodule.so
Binary files differ
diff --git a/cursesmodule/precompiled/Debian-Woody/jack_cursesmodule.so b/cursesmodule/precompiled/Debian-Woody/jack_cursesmodule.so
new file mode 100755
index 0000000..e8922d5
--- /dev/null
+++ b/cursesmodule/precompiled/Debian-Woody/jack_cursesmodule.so
Binary files differ
diff --git a/cursesmodule/precompiled/RedHat-6.0/cursesmodule.so b/cursesmodule/precompiled/RedHat-6.0/cursesmodule.so
new file mode 100755
index 0000000..5a30ef3
--- /dev/null
+++ b/cursesmodule/precompiled/RedHat-6.0/cursesmodule.so
Binary files differ
diff --git a/cursesmodule/sedscript b/cursesmodule/sedscript
new file mode 100644
index 0000000..2e17f3c
--- /dev/null
+++ b/cursesmodule/sedscript
@@ -0,0 +1,25 @@
+1i\
+# Generated automatically from Makefile.pre.in by sedscript.
+s%@VERSION[@]%1.5%
+s%@CC[@]%gcc%
+s%@RANLIB[@]%ranlib%
+s%@OPT[@]%-g -O2%
+s%@LDFLAGS[@]%%
+s%@DEFS[@]%-DHAVE_CONFIG_H%
+s%@LIBS[@]%-lieee -ldl -lpthread%
+s%@LIBM[@]%-lm%
+s%@LIBC[@]%%
+s%@MACHDEP[@]%linux2%
+s%^prefix=.*%prefix= /usr/local%
+s%^exec_prefix=.*%exec_prefix= ${prefix}%
+s%@SO[@]%.so%
+s%@LDSHARED[@]%gcc -shared%
+s%@CCSHARED[@]%-fpic%
+s%@LINKFORSHARED[@]%-Xlinker -export-dynamic%
+/^installdir=/s%=.*%= /usr/local%
+/^exec_installdir=/s%=.*%=/usr/local%
+/^srcdir=/s%=.*%= .%
+/^VPATH=/s%=.*%= .%
+/^LINKPATH=/s%=.*%= %
+/^BASELIB=/s%=.*%= %
+/^BASESETUP=/s%=.*%= %
diff --git a/doc/CHANGELOG b/doc/CHANGELOG
new file mode 100644
index 0000000..632be8b
--- /dev/null
+++ b/doc/CHANGELOG
@@ -0,0 +1,116 @@
+
+###############################################################################
+########################## pre-CVS changes follow: ############################
+###############################################################################
+
+##### This is jack 2.2.2 ...
+ ### changes from version 2.1.1 (internal) include:
+ # added curses frontend
+ # added support for variable bitrate MP3s (currently only for lame)
+ # bugfix: cddb query disc length was 2 seconds too short (thanks to
+ Martin Suess for the hint). I still don't understand where and
+ why I need the MSF OFFSET of 150 blocks, can anyone explain?
+ # bugfix: freedb files with more than one line of ids confused jack,
+ thanks to "casret" for the patch
+ ### changes from version 2.1.0 include:
+ # moved the toc_proc variable a bit lower in the .jackrc 'coz it's confusing
+ # change: in the jack.progress file, track numbers are now %02i
+ # bugfix: sometimes the :-) was not updated in case of an error, now it is
+ # bugfix: pause works now...
+ # bugfix: update interval no longer effects speed on non-Linux (i hope!)
+ ### changes from version 2.0.1 include:
+ # bugfix: the NameError: cd_device bug is gone (I never saw this as I
+ didn't change _my_ .jackrc... sorry)
+ # bugfix: same for "... maybe ... is not installed"
+ # added on-the-fly operation for cdparanoia, lame, mp3enc
+ # added 40 second pause before displaying global ETA so it can stabilize
+ # added option to execute user-defined commands - when done without errors
+ - when done with errors
+ - when ripping is finished
+ # added version check for .jackrc
+ # change: "Various" in freedb data is now treated like "Various Artists"
+ # added check for bogous DAE run without reported error (by checking filesize)
+ # bugfix: ZeroDivisionError when a lonely encoder was killed
+ # added better source code formatting :)
+ ### changes from version 2.0.0 include:
+ # bugfix: jack tried to chdir into newly created dirs twice
+ # bugfix: subdirs were created in the wrong sequence
+ ### changes from version 1.3.5 include:
+ # MANY changes and improvements, let's see if i can remember all:
+ # removed all thread and Queue stuff, much less trouble now
+ # removed resource control (ulimit), jack now quits gracefully
+ # added run-time commands, run jack -h to see details
+ # added directory creation and naming
+ # added directory auto-search magic, if jack doesn't find a toc-file,
+ it scans sub-directories (where and how deep is configurable) for
+ a matching toc-file and continues work
+ # added base_dir option, controls where new dirs are created. If you set
+ your prefs the right way, you only have to change CDs and type
+ "jack" from time to time.
+ # added no-various option to prevent "Various Artists" auto-detection
+ # added option to re-create progress file
+ # added update_interval, status is now updated regularily and no longer
+ in reaction to sub-process output
+ # added total ETA (only for encoders)
+ # changed jack so that it should (finally!) run on FreeBSD and most
+ other unices
+ # performance enhancements, jack only wastes < 0.1 % of _my_ CPU time
+ ### changes from version 1.3.4 include:
+ # added support for tosha(gettoc&DAE) PLEASE help me get jack to work on
+ FreeBSD. Something's wrong with threading I fear.
+ # bugfix: jack reported "WAV lost" when the wav was removed on purpose
+ # bugfix: jack encoded WAVs when resuming even if only_dae was set
+ ### changes from version 1.3.3 include:
+ # bugfix: jack reported "wrong disc" in error
+ # change: track 1 pregap is now stored in "SILENCE" in tocfile. Re-generate
+ your jack.toc files!
+ ### changes from version 1.3.2b include:
+ # better version of cdrdao_gettoc
+ # added resource module auto-detection
+ # added cheesy pause function: touch or rm "jack.pause".
+ # added disc recheck before each DAE is started
+ # added --todo: print what is to be done and exit
+ # bugfix: MP3s are now also checked for not being too large
+ # bugfix: MP3s bitrate is now remembered, too
+ # change: --force now disables check for correct CD inserted
+ # change: --force now disables check for previous encoder run (like when
+ manually encoding)
+ # added support for MP3s not starting at file's start (like RIFFs) (guess_toc)
+ # bugfix: freedb file: DISCID can now be comma-sep. list
+ ### changes from version 1.3.2a include:
+ # rewrote freedb_names
+ # removed sanity_check_freedb_file, freedb_names does the job now
+ # tried to make error messages uniform
+ ### changes from version 1.3.2 include:
+ # added rename_fmt option to .jackrc (use this to change renaming layout)
+ # added rename_underscore option to .jackrc (use this to change " " to "_")
+ ### changes from version 1.3.1 include:
+ # added dae_prog option to .jackrc
+ # added cd_device option to .jackrc (point it to your CD-ROM device)
+ ### changes from version 1.3 include:
+ # announced on freshmeat (famous last words...)
+ # now using Ben Gertzfield's ID3 module for id3tag info setting
+ # added guess mode
+ # added sysload reaction
+ # added (buggy) free space rechecking
+ # added dont-work switch
+ ### changes from version 1.2 include:
+ # public release under GPL
+ # added encoder support: lame, l3enc, mp3enc
+ # rewrote todo detection
+ # added only_dae flag
+ # added dialog which asks before deleting anything
+ # bugfixes
+ ### changes from version 1.1 include:
+ # added stupid function to "rip" from image
+ # added cool function to "rip" from cdrdao toc-file
+ # added toc-file writing
+ # added freedb lookup + renaming + id3tag setting
+ # handles multiple/nonexact freedb matches
+ # added freedb submissions
+ # added xtermset support
+ ### changes from version 1.0 include:
+ # stupid semaphore initialization bug fixed
+ # free space check is no longer braindead
+ # we don't try to remove() nonexistent files anymore
+
diff --git a/doc/ChangeLog b/doc/ChangeLog
new file mode 100644
index 0000000..e4223cd
--- /dev/null
+++ b/doc/ChangeLog
@@ -0,0 +1,1193 @@
+2004-12-12 22:52 zarne
+
+ * jack_version.py, setup.py:
+
+ - bump version for 3.1.1 release
+
+2004-12-12 05:54 zarne
+
+ * jack.man, jack_config.py, jack_functions.py, jack_helpers.py,
+ jack_main_loop.py, jack_t_curses.py:
+
+ - the usage/copyright box stays on top now by default, disable it
+ by pressind '?' or with --usage-win=no
+
+ - pprint_speed() (internal)
+
+ - documentation updates
+
+2004-12-12 03:27 zarne
+
+ * jack_prepare.py:
+
+ - fixed a warning regarding non-audio tracks
+
+2004-12-12 03:03 zarne
+
+ * jack_plugins.py:
+
+ - new plugin architecture (I forgot to include this earlier today)
+
+2004-12-12 03:02 zarne
+
+ * jack, jack.man, jack_checkopts.py, jack_config.py, jack_misc.py,
+ jack_t_curses.py, jack_utils.py:
+
+ - fixed --remove-files
+
+ - fixed --exec
+
+ - help the user if dir_template is longer than scan_dirs
+
+ - documentation updates
+
+ - allow the dir_template to consist of more than two path elements
+
+ - curses: fix dae status display
+
+2004-12-12 01:04 zarne
+
+ * jack.man, jack_config.py, jack_main_loop.py, jack_misc.py,
+ jack_t_curses.py:
+
+ - curses: fix display of the special line
+
+ - --max-load now takes a float argument
+
+ - simplify loadavg()
+
+2004-12-11 23:52 zarne
+
+ * jack_checkopts.py, jack_config.py, jack_main_loop.py,
+ jack_prepare.py:
+
+ - 31_wrong_char_in_status.patch
+
+ - 18_less_verbose_gen_device_warning.patch
+
+ - 28_avoid_empty_USER_LOGNAME.patch
+
+2004-12-11 23:14 zarne
+
+ * setup.py:
+
+ - added the new modules to the setup script
+
+2004-12-11 22:12 zarne
+
+ * example.etc.jackrc:
+
+ - added an example for the global config file /etc/jackrc
+
+2004-12-11 21:57 zarne
+
+ * jack_plugin_cddb.py, jack_plugin_lame.py:
+
+ - added example plugin files
+
+2004-12-11 21:55 zarne
+
+ * jack_checkopts.py, jack_config.py, jack_freedb.py,
+ jack_generic.py, jack_globals.py, jack_helpers.py, jack_prepare.py,
+ jack_rc.py:
+
+ - new plugin architecture for rippers, encoders and freedb-servers
+ (so far)
+
+2004-12-11 19:15 zarne
+
+ * jack_config.py, jack_freedb.py, jack_generic.py,
+ jack_t_curses.py, doc/ChangeLog:
+
+ - fixed a curses bug preventing some from ripping CDs with more
+ tracks than lines in the terminal
+
+ - debug stuff
+
+ - cosmetic stuff
+
+2004-11-23 01:06 zarne
+
+ * jack_helpers.py:
+ - remove debug stuff
+
+2004-11-23 00:45 zarne
+
+ * jack.man, jack_argv.py, jack_checkopts.py, jack_config.py,
+ jack_freedb.py, jack_workers.py:
+ - manpage updates
+
+ - removed --no-various because
+
+ - --various=no now actually works
+
+ - block more options from being written to jackrc
+
+ - --otf works again (but isn't displayed right all the time)
+
+2004-11-22 19:59 zarne
+
+ * jack_helpers.py, jack_main_loop.py, jack_rc.py:
+ - more fixes to cdparanoia status decoding
+
+ - jack_rcversion is written to newly created rc-files
+
+2004-11-22 00:34 zarne
+
+ * jack.man, jack_helpers.py, jack_rc.py:
+ - fix creation of ~/.jack3rc
+
+ - take all debian updates for the manpage (thank you Michael!)
+
+2004-11-22 00:07 zarne
+
+ * jack_helpers.py, jack_version.py, setup.py, doc/INSTALL:
+ - begin jack 3.1 release cycle
+
+ - modest update to INSTALL doc
+
+2004-11-21 23:42 zarne
+
+ * jack_argv.py, jack_checkopts.py, jack_config.py, jack_helpers.py,
+ jack_main_loop.py, jack_prepare.py, jack_rc.py, jack_version.py,
+ jack_workers.py:
+ - update copyright year(s)
+
+ - remove "charset has no effect without a char_filter" warning,
+ charset is used by the ogg tagger
+
+ - disallow saving of the --dont-work option
+
+ - add version-string (and checking) to the rc-files.
+
+2004-11-21 22:44 zarne
+
+ * jack_argv.py, jack_checkopts.py, jack_config.py, jack_helpers.py,
+ jack_main_loop.py, jack_prepare.py, jack_rc.py, jack_workers.py:
+ - allow the argument to --quality to be a float. oggenc is fine
+ with that, lame seems to truncate the float but works.
+
+ - fixed some typos
+
+ - fixed --check-toc
+
+2004-11-21 21:33 zarne
+
+ * jack_argv.py, jack_config.py, jack_helpers.py, jack_main_loop.py,
+ jack_rc.py:
+ - allow for lists (like --unusable-chars) to be --save'd to
+ .jack3rc
+
+ - lame encoder now uses --vbr-new
+
+ - lame encoder now uses --preset instead of --alt-preset
+
+ - fixed the "error decoding status" issue, which I think is
+ actually a bug in cdparanoia which outputs lots of invisible junk
+ after ripping. The fix may break other helpers, I only tested lame,
+ oggenc, cdparanoia and cdda2wav
+
+ - added lame-user which uses -V and uses the quality specified with
+ --quality
+
+ - changed LAME default quality to 6, which will result in 4 being
+ used when using --encoder-name lame-user (ignored otherwise)
+
+ - cdda2wav now uses the --device, not the --raw-device. Seems to
+ work fine.
+
+ - cdda2wav now uses -v toc instead of -v 35 (the latter being
+ obsolete soon)
+
+2004-11-18 03:08 zarne
+
+ * jack, jack_config.py, jack_helpers.py:
+ - added the option "--query-if-needed" which only queries freedb if
+ there is no file called "jack.freed.bak" (which is, among other
+ things, the result of a successful freedb-query).
+
+2004-11-17 22:55 zarne
+
+ * jack_argv.py, jack_rc.py:
+ - changed the UI a bit. Options can now be set with --option=value.
+ Use "yes" or "no" for booleans. --option alone still toggles the
+ current value. Toggeling sounded like a nice idea in the early days
+ of jack but now, with 2 rc-files and the command-line things have
+ gotten out of hand.
+
+ - Booleans are now saved (--save) as option:value in the rc-files.
+ Please use that syntax in /etc/jackrc, too. This will hopefully
+ bring order to the chaos.
+
+2004-11-17 21:26 zarne
+
+ * jack_ripstuff.py, jack_t_curses.py:
+ - fixed the infamous curses addstr bug. Why did it ever work?
+
+2004-11-10 00:42 zarne
+
+ * jack, jack_CDTime.py, jack_TOC.py, jack_TOCentry.py,
+ jack_freedb.py, jack_functions.py, jack_mp3.py, jack_version.py:
+ - re-added those files (I'm running out of brown paper bags)
+
+ - cosmetic stuff
+
+ - one little bug fixed
+
+2004-08-25 04:36 zarne
+
+ * jack_freedb.py:
+ - fixed http_proxy for --submit
+
+2004-08-25 00:53 zarne
+
+ * jack_CDTime.py, jack_TOC.py, jack_TOCentry.py, jack_checkopts.py,
+ jack_display.py, jack_freedb.py, jack_globals.py, jack_mp3.py,
+ jack_prepare.py, jack_t_dumb.py, jack_term.py, jack_workers.py:
+ - removed some unused files
+
+ - --submit works again
+
+ - --mail-submit works again (but use --submit if possible)
+
+ - --silent mode works again
+
+2004-03-25 03:55 zarne
+
+ * jack_freedb.py: - "VA" is now recognized as various
+
+ - fixed another forgotten cf[] (thanks to Stephan Helma for the
+ patch)
+
+2003-12-02 02:49 zarne
+
+ * jack, jack_checkopts.py, jack_config.py, jack_generic.py,
+ jack_globals.py, jack_helpers.py: - fixed cdparanoia status decode
+ bug (I hope)
+
+ - new option --debug, obsoletes DEBUG in jack_generic
+
+ - make --my-mail actually work, better sanity check of the address
+
+2003-12-02 01:51 zarne
+
+ * jack, jack_prepare.py, jack_t_curses.py, jack_version.py: - fix a
+ free diskspace-related bug
+
+ - we're in 2003 now :]
+
+ - expand ~ in base_dir so it can be relative to the user's homedir
+
+2003-11-26 19:16 zarne
+
+ * jack_prepare.py, jack_ripstuff.py: - raw_space was moved but I
+ forgot that in jack_prepare. fixed.
+
+2003-11-26 19:02 zarne
+
+ * setup-cursesmodule.py, setup-jack.py, setup.py, doc/ChangeLog: -
+ new setup.py for jack's modules and the cursesmodule. jack itself
+ still has to be manually installed.
+
+2003-10-12 03:54 zarne
+
+ * README, jack, jack_config.py, jack_freedb.py, jack_functions.py,
+ jack_globals.py, jack_helpers.py, jack_main_loop.py,
+ jack_prepare.py, jack_tag.py, jack_workers.py, doc/INSTALL: - fixed
+ --update-freedb
+
+ - declare encoding (newer python wants this)
+
+ - 17_authenticated_http-proxy_support_patch
+
+ - fixed some deprecation warnings
+
+ - 07_continue_various_if_blank_artist
+
+ - various bug fixes
+
+ - added --strictly-enforce-ISO to lame options
+
+ - fix for new cdparanoia screen output format
+
+ - print dots while tagging (which can be time consuming, e.g. with
+ ID3v2)
+
+ - 19_catch_rename_too_long_fix
+
+ - slight documentation updates (lots of stuff still missing)
+
+2003-04-30 04:35 zarne
+
+ * jack_checkopts.py, jack_config.py, jack_display.py, jack_init.py,
+ jack_tag.py: - ogg tags are now written as utf-8, as required by
+ the standard
+
+ - cosmetic stuff
+
+2003-04-30 03:49 zarne
+
+ * jack, jack_checkopts.py, jack_config.py, jack_init.py,
+ jack_tag.py: - id3v2 support, rejoice! you need
+ http://pyid3lib.sourceforge.net/
+
+ - small stuff
+
+2003-04-19 01:45 zarne
+
+ * jack_helpers.py: - changed default lame command lines: --r3mix ->
+ --alt-preset standard; -h -b <x> -> --alt-preset cbr <x>
+
+2003-04-19 00:55 zarne
+
+ * jack_config.py, jack_freedb.py, jack_functions.py,
+ jack_helpers.py, jack_init.py, jack_playorder.py, jack_prepare.py:
+
+ - small bugfixes
+
+ - nonfunctional (yet) support for the PLAYORDER field in freedb
+ entries
+
+ - toc reading more robust
+
+2003-01-22 20:15 zarne
+
+ * jack_helpers.py, jack_main_loop.py, jack_workers.py: - fixed
+ missing import signal
+
+ - fixed signals for child processes
+
+ - CDDB.py toc-reader now closes rip device (sf.net Bugs item
+ #664344)
+
+2003-01-22 18:40 zarne
+
+ * jack_config.py, jack_m3u.py, jack_tag.py: - added support for
+ creating m3u playlist (as requested)
+
+2003-01-22 18:11 zarne
+
+ * jack, jack_argv.py, jack_checkopts.py, jack_config.py,
+ jack_freedb.py, jack_functions.py, jack_generic.py,
+ jack_main_loop.py, jack_prepare.py, jack_utils.py, jack_workers.py:
+ - quite a number of fixes. Why did nobody complain?
+
+2002-09-05 00:17 zarne
+
+ * jack, jack_checkopts.py, jack_config.py, jack_display.py,
+ jack_freedb.py, jack_functions.py, jack_generic.py,
+ jack_globals.py, jack_helpers.py, jack_main_loop.py,
+ jack_prepare.py, jack_t_curses.py, jack_tag.py, jack_term.py,
+ jack_workers.py:
+
+ - more cleanups and bugfixes
+
+ - out of ignorance I failed to notice that I already use python-2.2
+ features. Who has problems with jack *requiring* python >= 2.2?
+
+2002-09-04 00:34 zarne
+
+ * jack, jack_checkopts.py, jack_config.py, jack_freedb.py,
+ jack_functions.py, jack_t_curses.py, jack_term.py:
+
+ - more cleanups
+
+ - terminal resizing and xtermset both seem to be ok now
+
+2002-09-03 22:45 zarne
+
+ * jack_readprefs.py:
+
+ - removed unused module jack_readprefs
+
+ - jack_generic was not modified, what is CVS trying to tell me?
+
+2002-09-03 21:33 zarne
+
+ * jack, jack_argv.py, jack_freedb.py, jack_functions.py,
+ jack_main_loop.py, jack_utils.py, jack_workers.py:
+
+ - cleanups
+
+ - use error(), info() and warning() more often
+
+2002-09-03 19:56 zarne
+
+ * jack, jack_argv.py, jack_checkopts.py, jack_config.py,
+ jack_constants.py, jack_display.py, jack_encstuff.py,
+ jack_freedb.py, jack_functions.py, jack_generic.py,
+ jack_globals.py, jack_helpers.py, jack_init.py, jack_main_loop.py,
+ jack_misc.py, jack_progress.py, jack_rc.py, jack_ripstuff.py,
+ jack_status.py, jack_t_curses.py, jack_targets.py, jack_term.py,
+ jack_utils.py, jack_workers.py:
+
+ - things are taking form, ripping and renaming seems to work
+
+ - configfile: new format, new filename. save options with --save
+
+ - this is still unstable, beta, don't expect too much (except bugs)
+
+2002-09-03 10:42 zarne
+
+ * jack, jack_argv.py, jack_checkopts.py, jack_children.py,
+ jack_config.py, jack_display.py, jack_freedb.py, jack_functions.py,
+ jack_globals.py, jack_helpers.py, jack_init.py, jack_misc.py,
+ jack_readprefs.py, jack_ripstuff.py, jack_status.py,
+ jack_t_curses.py, jack_t_dumb.py, jack_tag.py, jack_targets.py,
+ jack_term.py, jack_utils.py, jack_version.py, jack_workers.py,
+ doc/ChangeLog:
+
+ - work on version 3.0.0 starts now
+
+ - don't use this, only the bare minimum functionality is there
+
+ - e.g. no config file yet
+
+2002-06-20 14:36 zarne
+
+ * jack:
+
+ - fixed a stupid variable initialisation bug
+
+ - fixed another (hope it was the last one) append() with >1 args
+ bug
+
+2002-06-13 05:43 zarne
+
+ * jack.man:
+
+ - -F and -f clarified
+
+2002-06-13 05:37 zarne
+
+ * jack:
+
+ - strange that nobody noticed it, but jack couldn't rip CDs with
+ only one track on them. fixed.
+
+ - some cleanups
+
+2002-06-09 02:06 zarne
+
+ * jack:
+
+ - only import what we use from the curses module
+
+2002-06-09 01:56 zarne
+
+ * jack:
+
+ - jack can use normal curses now (to the extent possible, i.e.
+ without reacting to SIGWINCH (terminal window resizing)) (inspired
+ by Oleg Broytmann's patch, thanks!)
+
+2002-06-01 17:03 zarne
+
+ * jack:
+
+ * added sloppy ripping mode which is activated by a secret switch
+
+2002-05-25 04:01 zarne
+
+ * jack:
+
+ * to my understanding FLAC produces VBR files. now jack thinks so,
+ too.
+
+2002-05-25 03:42 zarne
+
+ * jack:
+
+ * added support for MPEGplus (encoder: mppenc, extension: .mpc)
+
+2002-05-25 01:46 zarne
+
+ * jack.man:
+
+ * typo
+
+2002-05-25 01:44 zarne
+
+ * jack.man:
+
+ * (hopefully) clarify how the -R / --rename option works
+
+2002-05-25 01:36 zarne
+
+ * jack:
+
+ * nobody complained, but I re-added --rename-only with a
+ deprecation warning
+
+2002-05-25 00:56 zarne
+
+ * jack, jack.man:
+
+ * renamed option --rename-only to --rename -- old name was
+ confusing. I'll revert this if enough people complain.
+
+2002-05-24 15:51 zarne
+
+ * jack:
+
+ * --quality switch for vbr, thanks to Michael Banck for the patch!
+
+2002-05-24 14:17 zarne
+
+ * jack:
+
+ * small changes to the flac patch
+
+2002-05-24 14:15 zarne
+
+ * jack:
+
+ * flac patch from Drew Hess - thanks, great work!
+
+2002-05-24 12:12 zarne
+
+ * jack:
+
+ * hide --charset option from --help if char_filter is not used.
+ having no effect, it would then only confuse people.
+
+2002-05-24 12:10 zarne
+
+ * jack:
+
+ * some small cleanups, options reordered
+
+ * new char_filter, e.g. for upper->lowercase conversion. should
+ work with any character encoding
+
+ * new option --charset, see above, default is latin-1
+
+ * needs python >= 2.2; can anybody tell me how to do this in python
+ < 2.2?
+
+2002-05-04 04:55 zarne
+
+ * jack:
+
+ * fixed Debian Bug#140903 (replacement_chars doesn't work on
+ uppercase umlauts) by documenting it - there's no way to "fix" it
+ but the workaround is simple: just add the umlauts explicitly to
+ unusable_chars and replacement_chars
+
+2002-05-03 00:03 zarne
+
+ * jack:
+
+ * fixed Debian Bug#141781: --upd-progress assumes that the tracks
+ are encoded in mp3 format, even if the encoder is oggenc
+
+2002-04-30 01:36 zarne
+
+ * jack:
+
+ * Debian Bug#144996 (jack uses ogg tag "YEAR" instead of "DATE")
+ fixed.
+
+2002-04-17 11:55 zarne
+
+ * jack:
+
+ * upped version to 2.99.9
+
+ * switch from FCNTL to fcntl module
+
+2002-04-16 09:28 zarne
+
+ * jack.man, doc/ChangeLog:
+
+ * added one example to the manpage
+
+ * ChangeLog isn't auto-updated currently, will fix soon
+
+2002-04-15 04:49 zarne
+
+ * jack:
+
+ * update copyright notice, welcome to the year 2002
+
+2002-04-15 04:40 zarne
+
+ * jack:
+
+ * elaborate on the missing permissions for the device problem many
+ people have
+
+2002-04-15 03:27 zarne
+
+ * jack:
+
+ * when guess-tocing WAVs you now have the option to truncate them
+ if they are noch CDDA-block aligned
+
+ * better defaults for genre and year when pre-tagging
+
+2002-04-15 02:23 zarne
+
+ * jack:
+
+ * (re-)enabled correct processing of mixed mode CDs (which have a
+ data track 1)
+
+ * rewrote and improved toc-reader based on cdda2wav
+
+ * corrected an assumption in CDDB.py's toc-reader which thought the
+ first track is always numbered 01
+
+2002-04-15 00:32 zarne
+
+ * jack:
+
+ * fixed bugs introduced in rev. 1.42
+
+ * made CD-EXTRA really work ;)
+
+2002-04-14 19:36 zarne
+
+ * jack, jack.man:
+
+ * fixed bugs.debian.org/132985 by accepting patch from Martin
+ Michlmayr
+
+2002-04-14 18:23 zarne
+
+ * jack:
+
+ * fixed errors ripping CD-EXTRA by double checking the TOC CDDB.py
+ reports against what the ripper thinks
+
+2002-02-12 17:25 zarne
+
+ * jack:
+ * "various" is now a global variable
+
+ * on VA CDs the artist is now shown while encoding (thanks to
+ Martin Michlmayr)
+
+2002-02-11 03:54 zarne
+
+ * jack:
+ * removed the quality switch for oggenc, people say the default (3)
+ is good. In a quick listening test with a difficult CD I could not
+ spot any obvious flaws. Ogg Vorbis is impressive.
+
+2002-02-06 20:18 zarne
+
+ * README, jack, jack_CDTime.py, jack_TOC.py, jack_TOCentry.py,
+ jack_misc.py, jack_mp3.py, setup-cursesmodule.py, setup-jack.py,
+ cursesmodule/README.precompiled, doc/download.html,
+ doc/examples.html, doc/faq.html, doc/index.html, doc/install.html,
+ doc/links.html, doc/requirements.html, doc/screen.html,
+ doc/todo.html, doc/usage.html:
+ * I prefer jack related email to zarne@users.sf.net now, thanks.
+
+2002-02-06 20:06 zarne
+
+ * jack.man: * corrections
+
+ * missing options added (by Michael Banck, thanks.)
+
+2002-02-06 18:48 zarne
+
+ * jack:
+ * use LC_ALL=C, I hope this helps solve the remaining problems with
+ localization
+
+2002-02-04 01:17 zarne
+
+ * jack.man:
+ * Michael Banck is too modest get have credits for the manpage.
+
+2002-02-03 18:08 zarne
+
+ * jack:
+ * sendmail is now called as /usr/lib/sendmail. Does anyone still
+ use mailsubmit?
+
+2002-02-03 16:57 zarne
+
+ * jack.man:
+ * some edits/corrections/additions
+
+2002-02-03 16:56 zarne
+
+ * jack.man:
+ * initial checkin of the manpage Michael Banck has written for jack
+
+2002-02-03 15:47 zarne
+
+ * jack:
+ * added -QQ which is like -Q but allows you to continue on failed
+ queries.
+
+2002-02-03 15:19 zarne
+
+ * jack:
+ * fixed freedb submission by email (I hope, didn't test it)
+
+2002-02-03 15:04 zarne
+
+ * jack:
+ * pretagging now allows setting of genre (%g) and year (%y)
+
+ * changed mnemonic for generic_device from %g to %D -- check your
+ .jackrc!
+
+2002-02-03 14:31 zarne
+
+ * jack:
+ * workaround for bug in ID3.py which defaults the genre to Blues
+ [0]
+
+2002-02-03 14:09 zarne
+
+ * jack:
+ * jack now creates directories even when reading --from-tocfile
+
+2002-02-03 13:31 zarne
+
+ * jack:
+ * Linux OSTYPE is now recognized if it's called linux-gnu (thank
+ you dme!)
+
+2002-02-03 13:14 zarne
+
+ * jack:
+ * fixed a bug in VA pretagging, thanks to Martin Michlmayr for the
+ patch!
+
+2002-02-03 03:48 zarne
+
+ * jack:
+ * implemented ogg post-tagging for pyvorbis >= 0.5, for real this
+ time ;-)
+
+ * fixes for local freedb_dir by Matthew Mueller
+
+2002-02-03 03:20 zarne
+
+ * jack:
+ * increased status_blocksize from 56 to 64 for oggenc.
+
+ * implemented ogg post-tagging for pyvorbis >= 0.5, thanks to
+ Michael Banck!
+
+ * oggenc is now called with -q 5 instead of a bitrate parameter.
+ True VBR now.
+
+ * [ Sorry, I was terribly busy in the recent past. ]
+
+ * [ There's still a huge backlog.]
+
+2001-11-12 06:36 zarne
+
+ * jack:
+ - small code cleanup (the category chooser is one function now
+ instead of 3 instances)
+
+ - freedb category is now remembered, useful for submitting updated
+ entries
+
+ - bug fixed: entering "0" as category used last category instead of
+ aborting
+
+2001-11-09 19:34 zarne
+
+ * jack:
+ - added option --wait (wait on quit)
+
+ - added option --workdir (where to put files / dirs, == base_dir)
+
+ - added option --search (where to search for workdir, appends to
+ searchdirs)
+
+ - create all dirs leading to workdir
+
+2001-11-08 19:57 zarne
+
+ * jack:
+ This patch - provided by Martin Michlmayr - does this:
+
+ - use float and int instead of atof and atoi. The latter are
+ deprecated (see Python documentation). (The jack_*.py modules need
+ to be overhauled likewise)
+
+ - Check if an input string is really an integer and don't fail if
+ it's not.
+
+ - Check the range of tracks in the -t arguments and ignore invalid
+ tracks.
+
+2001-11-08 02:22 zarne
+
+ * jack:
+ now that 2.99.7 is out, bump version to 2.99.8
+
+2001-11-08 02:20 zarne
+
+ * jack, setup-cursesmodule.py, cursesmodule/jack_cursesmodule.c,
+ doc/ChangeLog, doc/INSTALL:
+ - get rid of compiler warnings with -Wno-strict-prototypes
+
+ - put the two unused functions which gcc complains about to use
+
+2001-11-07 18:52 zarne
+
+ * doc/: ChangeLog, index.html:
+ - index.html points to ChageLog instead of CHANGELOG now
+
+ - ChangeLog will contain the more recent changes now, CHANGELOG the
+ very old ones. ChangeLog will NOT be up-to date in cvs BUT in
+ releases.
+
+2001-11-07 18:43 zarne
+
+ * doc/CHANGELOG: - prepared it for automatic actualization,
+ released versions contain the CVS log from now on. CHANGELOG is now
+ a template from which ChangeLog is generated when releasing, CVS
+ will contain both in a undefined state.
+
+2001-11-07 18:08 zarne
+
+ * jack: - version is now 2.99.7
+
+ - use termios instead of TERMIOS to make python2 happy
+
+ - works with python2 now, I tested/use 2.1.1
+
+ - tell people what to do if modules are missing
+
+ - re-organized .jackrc, unfortunately everybody has to re-gen it
+ now
+
+ - with luck, this is the last time everybody has to re-gen it :)
+
+ - tried xtermset again and found that it still works
+
+ - try it!
+
+ - updated gogo helper, thanks to José Antonio Pérez Sánchez
+
+ - compile helpers, this works in python2. comment out if you have
+ problems.
+
+ - changed the execs to make python2 happy
+
+ - encoders, rippers and freedb servers are listed if specified
+ doesn't exist
+
+ - we're close to 3.0
+
+2001-11-07 18:08 zarne
+
+ * README: removed version, I keep to forget to update it
+
+2001-10-11 00:38 zarne
+
+ * README, jack, doc/INSTALL: - fixed "tag" info for Ogg/Vorbis
+
+ - fall back to CBR if selected encoder does not support VBR
+ (thanks to Michael Banck for finding both issues)
+
+ - removed version info from doc/INSTALL because I keep to forget to
+ update it
+
+2001-10-10 23:21 zarne
+
+ * jack: - changed http Submit-Mode from test to submit (oops)
+
+2001-10-10 21:57 zarne
+
+ * jack: - Version is now (since 2 commits actually) 2.99.6
+
+ - cursesmodule was renamed to jack_cursesmodule to avoid conflicts
+
+ - re-organized .jackrc
+
+ - fixed freedb-sources
+
+ - default encoder is now oggenc (which can only do vbr)
+
+ - default naming scheme has been changed to "Artist - Album - 01
+ - Tracktitle.[ext]"
+
+ - fixed resizing (more precisely I removed a hack which made it
+ work for me on RedHat 6.2)
+
+ - workaround for illegal freedb entries with empty album name
+
+2001-10-10 18:43 zarne
+
+ * doc/TODO: Todo: write get_toc for ogg/vorbis
+
+2001-10-10 18:39 zarne
+
+ * README: - state that oggenc is now the default
+
+2001-10-10 18:34 zarne
+
+ * cursesmodule/: cursesmodule.c, jack_cursesmodule.c: - renamed
+ cursesmodule.c -> jack_cursesmodule.c
+
+ - patched the module to reflect this change
+
+2001-10-10 18:33 zarne
+
+ * cursesmodule/precompiled/Debian-Woody/jack_cursesmodule.so:
+ jack_cursesmodule, precompiled on Debian-Woody
+
+2001-10-10 18:29 zarne
+
+ * setup-cursesmodule.py, cursesmodule/README.precompiled,
+ doc/CHANGELOG, doc/INSTALL, doc/TODO:
+ - Version is now (since 2 commits actually) 2.99.6
+
+ - cursesmodule was renamed to jack_cursesmodule to avoid conflicts
+
+ - the URL in CHANGELOG has been fixed (missing www.)
+
+ - updated TODO
+
+ - re-organized .jackrc
+
+ - fixed freedb-sources
+
+ - default encoder is now oggenc (which can only do vbr)
+
+ - default naming scheme has been changed to "Artist - Album - 01
+ - Tracktitle.[ext]"
+
+ - fixed resizing (more precisely I removed a hack which made it
+ work for RedHat 6.2)
+
+2001-10-06 07:14 zarne
+
+ * jack: fixed the bug which prevented successful ogg tagging
+ (Bender, you really should not program when you're sober!)
+
+2001-10-06 00:14 zarne
+
+ * README, jack:
+ started Ogg Vorbis support which seems to work, there's still a bug
+ concerning the ogg info data. I'll try to fix that one later.
+
+ Jack can now be extended to use any target format.
+
+2001-08-31 22:27 zarne
+
+ * README, doc/INSTALL:
+ clarified (I hope) the installation procedure
+
+2001-08-31 06:29 zarne
+
+ * ID3.py, cursesmodule-1.5b2.patch:
+ ID3 is available separately, the patch was moved into the
+ cursesmodule subdir.
+
+2001-08-31 06:26 zarne
+
+ * setup-cursesmodule.py, setup-jack.py:
+ inital checkin. anyone else I forgot?
+
+2001-08-31 06:20 zarne
+
+ * cursesmodule/: Makefile, Makefile.pre, Makefile.pre.in,
+ README.cursesmodule, README.precompiled, Setup, config.c,
+ cursesmodule-1.5b2.patch, cursesmodule.c, lifedemo.py, sedscript,
+ precompiled/RedHat-6.0/cursesmodule.so:
+ initial checkin, the patched cursesmodule is now part of the
+ standard dist.
+
+2001-08-31 06:16 zarne
+
+ * cursesmodule/precompiled/Debian-Potato/cursesmodule.so:
+ initial checkin
+
+2001-08-31 06:09 zarne
+
+ * jack, jack_TOCentry.py, jack_mp3.py, doc/INSTALL:
+ * bumped version to 2.9.5
+
+ * include my patched cursesmodule now, together with a distutils
+ installer
+
+ * removed ID3.py, this and CDDB.py must be downloaded and installed
+ separately (instructions included)
+
+ * runs on python2 again, don't know whether it works
+
+ * lots of changes, fixes -- I lost track
+
+2001-03-14 07:32 zarne
+
+ * jack_mp3.py: Why hasn't this been in CVS before??? :]
+
+ - huge performance gain
+
+ - better (more accurate) frame syncing (two consecutive rames are
+ searched for now)
+
+ - more robust now
+
+ - VBR bitrates are now returned as a float
+
+ - preliminary ID3v2 support (corrently only the version number is
+ extracted and the tag is skipped)
+
+ - new info: bpf and framesize
+
+ - better detection of Xing tags
+
+2001-03-14 07:23 zarne
+
+ * ID3.py: - small fixes
+
+ - replaced tabs by spaces
+
+ - this is not a official version now, I will sync this with
+ official 1.0 soon.
+
+2001-03-14 07:19 zarne
+
+ * README, jack, doc/INSTALL: - support for freedb files which have
+ track title or artist stored in the EXTT fields
+
+ - toc reading for cdparanois disabled because it's incomplete for
+ CD EXTRA
+
+ - added support for CDDB.py, available at
+ http://cddb-py.sourceforge.net/ (currently only toc reading) this
+ is now the default
+
+ - fixed a bug triggered by tocfiles containing quotes on the file
+ name
+
+ - added support for EXDD=YEAR: ID3G, who can tell me who set this
+ "standard"?
+
+ - CRs are now ignored in freedb files
+
+ - added "Sampler" and "Diverse" to the list of various artists
+ tokens
+
+ - -G none now sets the ID3v1 genre to 255("unknown")
+
+ - print year and genre when finished (if available)
+
+2000-11-21 02:47 zarne
+
+ * jack: *** .jackrc option to disable http_proxy *** replace x -> ×
+ for speed factors *** sys.stdin.flush() removed, impossible :-).
+ What's the "correct" way to flush stdin? fcntl with O_NONBLOCK,
+ then reading (and discarding) until nothing is read? Is there an
+ easy way? *** jack.freedb now has as many spaces in front of the
+ MSF offsets as your favorite CD player *** some reformatting
+
+2000-11-02 20:27 zarne
+
+ * jack: 1) reformatted helpers{} 2) show_time = 1 and show_names =
+ 1 are now defaults 3) it's now called 2.99.4 (actually since the
+ last commit)
+
+2000-11-02 02:08 zarne
+
+ * jack: 1) renamed id -> cd_id; id is a reserved word in python.
+ 2) ignore data tracks, tested with cdparanoia only 3) quit if there
+ are no audio tracks (to do) 4) --abort-on-skip is now the default
+ for cdparanoia, change it if you don't like it 5) call it 2.99.4
+
+ cosmetic changes: 1) more straighforward displays 2) show_time now
+ saves space by reducing track_names to their number. Thus the
+ "normal" line for a track fits into 80 columns again. 3) Show
+ freedb-id and total playing time in Options line 4) try to show
+ Options line when exiting
+
+ "do only one change per commit" is for sissies :-).
+
+2000-08-13 20:16 zarne
+
+ * jack: forgot this one in the previous commit
+
+2000-08-13 20:13 zarne
+
+ * README, doc/CHANGELOG, doc/examples.html: fixed some strings to
+ indicate that freedb now supports entry submission with HTTP POST
+ (option --submit)
+
+2000-08-13 19:26 zarne
+
+ * jack: added a check for an existing destination directory
+
+2000-08-11 03:24 zarne
+
+ * jack: added WAV to the file types --guess-toc recognizes
+ internal: progress now also takes a tuple containing the args
+
+2000-08-11 00:02 zarne
+
+ * jack: changed the short option for --update-freedb from -u to -U
+ because of conflicts. Thanks to C. Marquardt for finding what I do
+ when I program late at night :-).
+
+2000-08-09 04:52 zarne
+
+ * ID3.py, jack: jack can now "update", i.e. re-generate freedb
+ files (new option -u) updated ID3 module so that it supports
+ ID3v1.1 track info jack now sets ID3v1.1 track info
+
+2000-08-08 08:17 zarne
+
+ * jack: added xing vbr tag decoding. Not used much yet, but -g now
+ works on vbr mp3s
+
+2000-08-08 01:32 zarne
+
+ * jack: fixed for new versions of gogo; vbr display enhancements
+ and cosmetics
+
+2000-06-28 02:23 zarne
+
+ * jack: fixed a misleading error message
+
+2000-06-26 16:12 zarne
+
+ * doc/faq.html: Added one FAQ entry, fixed one typo.
+
+2000-06-21 01:23 zarne
+
+ * jack: fixed (cosmetic) display bug when using image-file
+
+2000-05-11 16:51 zarne
+
+ * jack: update to my current development version
+
+2000-05-11 16:32 zarne
+
+ * ID3.py, README, jack, cursesmodule-1.5b2.patch, jack_CDTime.py,
+ jack_TOC.py, jack_TOCentry.py, jack_misc.py, doc/faq.html,
+ doc/gpl.txt, doc/index.html, doc/install.html, doc/screen.html,
+ doc/jack-logo.jpg, doc/requirements.html, doc/usage.html,
+ doc/download.html, doc/examples.html, doc/jack-screen.gif,
+ doc/download.gif, doc/main.gif, doc/requirements.gif,
+ doc/screen.gif, doc/usage.gif, doc/install.gif, doc/links.gif,
+ doc/links.html, doc/CHANGELOG, doc/anim.written.in.vi.gif,
+ doc/mine.css, doc/INSTALL, doc/jack-curses-screen.gif, doc/TODO,
+ doc/todo.html: initial import of jack-2.99.0-pre
+
+2000-05-11 16:32 zarne
+
+ * ID3.py, README, jack, cursesmodule-1.5b2.patch, jack_CDTime.py,
+ jack_TOC.py, jack_TOCentry.py, jack_misc.py, doc/faq.html,
+ doc/gpl.txt, doc/index.html, doc/install.html, doc/screen.html,
+ doc/jack-logo.jpg, doc/requirements.html, doc/usage.html,
+ doc/download.html, doc/examples.html, doc/jack-screen.gif,
+ doc/download.gif, doc/main.gif, doc/requirements.gif,
+ doc/screen.gif, doc/usage.gif, doc/install.gif, doc/links.gif,
+ doc/links.html, doc/CHANGELOG, doc/anim.written.in.vi.gif,
+ doc/mine.css, doc/INSTALL, doc/jack-curses-screen.gif, doc/TODO,
+ doc/todo.html: Initial revision
+
diff --git a/doc/INSTALL b/doc/INSTALL
new file mode 100644
index 0000000..fcc48e7
--- /dev/null
+++ b/doc/INSTALL
@@ -0,0 +1,87 @@
+### This is jack (c) 1999-2004 Arne Zellentin
+### free to use, no warranties, no nothing
+### see COPYING
+
+
+--- Step 1 - install 3rd party modules ---------------------------------------
+
+install the eyeD3 and CDDB.py modules:
+
+* grab them from http://cddb-py.sourceforge.net
+ and http://eyed3.nicfit.net/
+
+Too ease up the installations, install the Python Distutils
+from
+
+ http://www.python.org/sigs/distutils-sig/
+
+if you don't already have them. (If you don't have them you'll get errors like
+"ImportError: No module named distutils.core".)
+
+* if you have the python distutils installed, simply install them (as root)
+ with
+
+ # python setup.py install
+
+* if not, follow the instructions
+
+
+--- Step 2 - install the jack_* modules and my curses module -----------------
+
+* see the end of this file for tips on installing it without distutils!
+
+* if you have the python distutils installed, run (as root if needed)
+
+ # python setup.py install
+
+--- Step 3 - install jack ----------------------------------------------------
+
+Copy it somewhere in your $PATH, e.g.:
+
+ # cp jack $HOME/bin
+
+or (you may have to be root)
+
+ # cp jack /usr/local/bin/
+
+--- That's it -- you're done! Congratulations!
+
+
+
+------------------------------------------------------------------------------
+--- help on installing the cursesmodule manually -----------------------------
+------------------------------------------------------------------------------
+
+(some of this is outdated, the module is now called jack_curses)
+If you want to use the curses mode (belive me, you want to), you have to
+install a special cursesmodule. The one which comes with python has no support
+for pads. The improved version from Oliver Andrich is missing the function
+resizeterm which I patched in. I mailed the patch to the author so it may be
+included in a future version. Get my version on the download page or download
+the original cursesmodule from
+http://andrich.net/python/selfmade.html#ncursesmodule (link broken, sorry)
+and use the patch in the cursesmodule directory (cursesmodule-1.5b2.patch). You
+can either compile the module yourself or use the precompiled version which is
+in my package. I compiled it on a RedHat 6.0 (late I added a binary which runs
+on Debian Potato) alike system on an i686. If you want to (or have to) compile
+it yourself, try the following if you can't get the Makefile to work:
+
+# gcc -g -O2 -I/usr/local/include/python1.5 -I/usr/local/lib/python1.5/config \
+# -DHAVE_CONFIG_H -c ./cursesmodule.c
+# gcc -shared cursesmodule.o -lncurses -ltermcap -o cursesmodule.so
+
+Install the cursesmodule.so to your site-packages.
+
+If you can't get the precompiled cursesmodule to run, make sure that all
+needed libraries are installed on you system. Check this with
+# ldd cursesmodule.so
+which should produce output similar to
+ libncurses.so.4 => /usr/lib/libncurses.so.4 (0x40018000)
+ libtermcap.so.2 => /lib/libtermcap.so.2 (0x4005a000)
+ libc.so.6 => /lib/libc.so.6 (0x4005e000)
+ /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000).
+All links must be satisfied; if a library cannot be found, ldd will tell you.
+In this case install all missing libraries. Some Linux systems don't install
+termcap libraries, fix this; e.g. on RedHat systems, you need
+libtermcap-*.rpm.
+
diff --git a/doc/TODO b/doc/TODO
new file mode 100644
index 0000000..2492f36
--- /dev/null
+++ b/doc/TODO
@@ -0,0 +1,12 @@
+#/\# TODO:
+#\/# - replace df call by something more portable *DONE*
+# - check freedb submit line-length *DONE in -U*
+# - get alternate freedb sites, ... *HALF-DONE, static list*
+# - more DAE tools and encoders *DONE* --- anything else?
+# - gtk-frontend, anyone?
+# - make toc-file reading more robust 1/2 *DONE (uses CDDB.py now)*
+# - make image-reader otf capable
+# - create a man page
+# - a function to query all unqueried rips would be nice... *DONE*
+# - preallocate discspace
+# - write get_toc for ogg/vorbis
diff --git a/doc/anim.written.in.vi.gif b/doc/anim.written.in.vi.gif
new file mode 100644
index 0000000..98289d7
--- /dev/null
+++ b/doc/anim.written.in.vi.gif
Binary files differ
diff --git a/doc/download.gif b/doc/download.gif
new file mode 100644
index 0000000..359c01c
--- /dev/null
+++ b/doc/download.gif
Binary files differ
diff --git a/doc/download.html b/doc/download.html
new file mode 100644
index 0000000..8955d65
--- /dev/null
+++ b/doc/download.html
@@ -0,0 +1,131 @@
+<!--#include virtual="/include/head.html" -->
+<HEAD>
+<TITLE>Download Jack</TITLE>
+<LINK REV=MADE href=mailto:zarne@users.sf.net>
+</HEAD>
+<BODY BGCOLOR=#072387 TEXT=#d0d840 LINK=#f0a840 VLINK=a06820>
+
+<CENTER>
+ <A href=index.html><IMG src="main.gif" height=32></A>
+ <A href=download.html><IMG src="download.gif" height=32></A>
+ <A href=screen.html><IMG src="screen.gif" height=32></A>
+ <A href=requirements.html><IMG src="requirements.gif" height=32></A>
+ <A href=install.html><IMG src="install.gif" height=32></A>
+ <A href=usage.html><IMG src="usage.gif" height=32></A>
+ <A href=links.html><IMG src="links.gif" height=32></A>
+</CENTER>
+
+<TABLE width=100%><TR><TD valign=bottom>
+<H3><U>Download Jack</U></H3>
+<TD valign=bottom>
+<IMG SRC="jack-logo.jpg" align=right alt="Jack" height=50 width=101></TR>
+</TABLE>
+
+<TABLE border cellpadding=5>
+<TR bgcolor=black>
+ <TD>Description
+ <TD>Version
+ <TD>Location
+</TR>
+<TR>
+ <TD>upcoming version without up-to-date documentation, read INSTALL!
+ <TD align=center>2.99.0-pre
+ <TD align=center><A href=jack-2.99.0-pre.tar.gz>jack-2.99.0-pre.gz</A>
+</TR>
+<TR>
+ <TD>upcoming version without up-to-date documentation
+ <TD align=center>2.2.5-pre
+ <TD align=center><A href=jack-2.2.5-pre.tar.gz>jack-2.2.5-pre.tar.gz</A>
+</TR>
+<TR>
+ <TD>buggy version without up-to-date documentation
+ <TD align=center>2.2.4-pre
+ <TD align=center><A href=jack-2.2.4-pre.tar.gz>jack-2.2.4-pre.tar.gz</A>
+</TR>
+<TR>
+ <TD>interim version without up-to-date documentation
+ <TD align=center>2.2.3-pre
+ <TD align=center><A href=jack-2.2.3-pre.tar.gz>jack-2.2.3-pre.tar.gz</A>
+</TR>
+<TR>
+ <TD>old version without up-to-date documentation
+ <TD align=center>2.2.2-pre
+ <TD align=center><A href=jack-2.2.2-pre.tar.gz>jack-2.2.2-pre.tar.gz</A>
+</TR>
+<TR>
+ <TD>current stable
+ <TD align=center>2.2.0
+ <TD align=center><A href=jack-2.2.0.tar.gz>jack-2.2.0.tar.gz</A>
+</TR>
+<TR>
+ <TD>older version, small bugs
+ <TD align=center>2.1.0
+ <TD align=center><A href=jack-2.1.0.tar.gz>jack-2.1.0.tar.gz</A>
+</TR>
+<TR>
+ <TD>old buggy version
+ <TD align=center>2.0.1
+ <TD align=center><A href=jack-2.0.1.tar.gz>jack-2.0.1.tar.gz</A>
+</TR>
+<TR>
+ <TD>buggy version
+ <TD align=center>2.0.0
+ <TD align=center><A href=jack-2.0.0.tar.gz>jack-2.0.0.tar.gz</A>
+</TR>
+<TR>
+ <TD>older version
+ <TD align=center>1.3.5
+ <TD align=center><A href=jack-1.3.5.tar.gz>jack-1.3.5.tar.gz</A>
+</TR>
+<TR>
+ <TD>older version
+ <TD align=center>1.3.4
+ <TD align=center><A href=jack-1.3.4.tar.gz>jack-1.3.4.tar.gz</A>
+</TR>
+<TR>
+ <TD>older version, known bugs.
+ <TD align=center>1.3.3
+ <TD align=center><A href=jack-1.3.3.tar.gz>jack-1.3.3.tar.gz</A>
+</TR>
+<TR>
+ <TD>older version
+ <TD align=center>1.3.2
+ <TD align=center><A href=jack-1.3.2.tar.gz>jack-1.3.2.tar.gz</A>
+</TR>
+<TR>
+ <TD>older version
+ <TD align=center>1.3.1
+ <TD align=center><A href=jack-1.3.1.tar.gz>jack-1.3.1.tar.gz</A>
+</TR>
+<TR>
+ <TD>current BETA
+ <TD align=center>-
+ <TD align=center>-
+</TR>
+<TR>
+ <TD>current ALPHA (just the script)
+ <TD align=center>varies
+ <TD align=center><A href=jack.gz>jack.gz</A>
+</TR>
+<TR bgcolor="#5773d7">
+ <TD>cursesmodule patched for Jack, includes precompiled shared binary
+ <TD align=center>1.5b2
+ <TD align=center><A href=cursesmodule-with-resizeterm-1.5b2.tar.gz>cursesmodule-with-resizeterm-1.5b2.tar.gz</A>
+</TR>
+</TABLE>
+
+<P>
+Wanna go <A href=index.html>home</A> or on to the <A href=install.html>installation information</A>?
+<HR>
+<HRULE>
+<A NAME="BOTTOM"></A>
+<ADDRESS><SMALL>
+&#169; Arne Zellentin,
+<A HREF="mailto:zarne@users.sf.net">zarne@users.sf.net</A>
+</SMALL></ADDRESS>
+<SMALL>(changed: 10-Jan-2000)</SMALL>
+
+</body>
+</html>
+
+
diff --git a/doc/examples.html b/doc/examples.html
new file mode 100644
index 0000000..8d56401
--- /dev/null
+++ b/doc/examples.html
@@ -0,0 +1,96 @@
+<!--#include virtual="/include/head.html" -->
+<HEAD>
+<TITLE>Jack application examples</TITLE>
+<LINK REV=MADE href=mailto:zarne@users.sf.net>
+</HEAD>
+<BODY BGCOLOR=#072387 TEXT=#d0d840 LINK=#f0a840 VLINK=a06820>
+<CENTER>
+ <A href=index.html><IMG src="main.gif" height=32></A>
+ <A href=download.html><IMG src="download.gif" height=32></A>
+ <A href=screen.html><IMG src="screen.gif" height=32></A>
+ <A href=requirements.html><IMG src="requirements.gif" height=32></A>
+ <A href=install.html><IMG src="install.gif" height=32></A>
+ <A href=usage.html><IMG src="usage.gif" height=32></A>
+ <A href=links.html><IMG src="links.gif" height=32></A>
+</CENTER>
+
+<TABLE width=100%><TR><TD valign=bottom>
+<H3><U>Application examples</U></H3>
+<TD valign=bottom>
+<IMG SRC="jack-logo.jpg" align=right alt="Jack" height=50 width=101></TR>
+</TABLE>
+
+<UL>
+<LI>Insert a CD, fire up jack:<PRE>
+$ jack
+</PRE>
+Now watch it work. It's fun for a while. After having finished, you have
+the follwing files on your HD: track_01.mp3, track_02.mp3, ..., track_nn.mp3
+<I> plus </I>jack.toc, jack.freedb, jack.progress. The last three are used to
+store the state jack is in so it can resume work when interrupted.
+<LI>Jack will create a directory called jack-xxxxxxxx for you, there it
+stores all the file for the CD whose id is xxxxxxxx. After a freedb query this
+directory is renamed to something human readable, like "Artist - Title".
+<LI>When jack is interrupted, call it again using the same commandline as before to resume work, in this case:
+<PRE>$ jack</PRE>
+<LI>The WAV files have been deleted. If you want jack to keep them, try
+<PRE>$ jack -k</PRE>
+<LI>Now let's try a freedb query:
+<PRE>$ jack -q</PRE>
+when succesful the files are now renamed to something more readable and have been ID3 tagged accordingly. jack.freedb contains the queried freedb entry, the original file has been backed up to jack.freedb.bak.
+<LI>The query failed? Ok, contribute! edit the freedb template:
+<PRE>$ vi jack.freedb</PRE>
+Note: the DTITLE should be set to
+<PRE>Artist / Name Of Album</PRE>
+or
+<PRE>Various Artist / Name Of Compilation</PRE>
+when adding a compilation, use
+<PRE>Artist - Title Of Track</PRE>
+for the track titles. Do not delete any lines from the template. Do not change the numbers. Yes the TTITLEs start at 0 and end one track too early. Read the freedb documentation.
+<LI>now activate the entries:
+<PRE>$ jack -R</PRE>
+now the files have been renamed and tagged. Check the names two or three times.
+Typo made? No problem, you can alway undo the file renaming with
+<PRE> $ jack -u</PRE>
+Note that the ID3 tags are not undone. Fix the freedb file and again, use
+<PRE>$ jack -R</PRE>
+to activate your changes.
+When you are sure the freedb file is suitable for submission, submit it (via
+e-mail (option -m) or via HTTP POST (option --submit). When using the former,
+sendmail must be installed and working on your machine! If you're on a dial-up,
+you can use the -m option to queue submits, provided sendmail is set up
+accordingly):
+<PRE>$ jack -m</PRE>
+or
+<PRE>$jack --submit</PRE>
+</UL>
+Those were the basics. Now some more advanced examples:
+<UL>
+<LI>All in one: query, rip, encode, cleanup:
+<PRE>$ jack -Q --remove</PRE>
+<LI>query any time while working:
+<PRE>$ jack</PRE>
+and, from another shell:
+<PRE>$ jack -d -Q</PRE>
+<LI>query for unknown MP3s:
+<PRE>$ jack -q -g track_*.mp3</PRE>
+<LI>rip from image, first, create the image:
+<PRE>$ cdrdao read-cd --datafile data.cdr data.toc</PRE>
+then make MP3s from the image:
+<PRE>$ jack -f data.toc</PRE>
+</UL>
+more to follow.
+<P>
+Wanna go <A href=index.html>home</A>?
+
+<HR>
+<HRULE>
+<A NAME="BOTTOM"></A>
+<ADDRESS><SMALL>
+"All trademarks are owned by their owners" or whatever I have to state.<BR>
+&#169; Arne Zellentin,
+<A HREF="mailto:zarne@users.sf.net">zarne@users.sf.net</A>
+</SMALL></ADDRESS>
+<SMALL>(changed: 19-Aug-99)</SMALL>
+</body>
+</html>
diff --git a/doc/faq.html b/doc/faq.html
new file mode 100644
index 0000000..f0ec5b7
--- /dev/null
+++ b/doc/faq.html
@@ -0,0 +1,75 @@
+<!--#include virtual="/include/head.html" -->
+<HEAD>
+<TITLE>FAQ for Jack</TITLE>
+<LINK REV=MADE href=mailto:zarne@users.sf.net>
+</HEAD>
+<BODY BGCOLOR=#072387 TEXT=#d0d840 LINK=#f0a840 VLINK=a06820>
+<CENTER>
+ <A href=index.html><IMG src="main.gif" height=32></A>
+ <A href=download.html><IMG src="download.gif" height=32></A>
+ <A href=screen.html><IMG src="screen.gif" height=32></A>
+ <A href=requirements.html><IMG src="requirements.gif" height=32></A>
+ <A href=install.html><IMG src="install.gif" height=32></A>
+ <A href=usage.html><IMG src="usage.gif" height=32></A>
+ <A href=links.html><IMG src="links.gif" height=32></A>
+</CENTER>
+
+<TABLE width=100%><TR><TD valign=bottom>
+<H3><U>Frequently Asked Questions</U></H3>
+<TD valign=bottom>
+<IMG SRC="jack-logo.jpg" align=right alt="Jack" height=50 width=101></TR>
+</TABLE>
+
+<UL>
+<LI> <B>Q:</B> I'm compiling python 1.5.2 from source. What modules/options do I
+ need to use?
+<P>
+<B>A:</B> Use the '--with-threads' flag to configure, and make sure you include
+ the termios module (which is disabled by default) by uncommenting
+ the line:<BR><TT>
+
+ #termios termios.c # Steen Lumholt's termios module
+</TT><BR>
+ in Modules/Setup. Jack 2.x doesn't need the threads any more, but they
+are still good to have. Only leave them out if they cause trouble. In general, I recommend activating as many modules as possible so you don't have to
+recompile every time a newly installed programm needs another non-default module.
+<SMALL>(from Jason Lunz)</SMALL>
+<P>
+<LI><B>Q:</B> I can't / don't want to install python-1.5.2. Isn't there a
+ way to have jack work with an older version of python?
+<P>
+ <B>A:</B> There are bugs in the python-1.5.1 modules that prevent jack
+ from working flawlessly. From memory, the urllib contained a bug
+ which prevented freedb queries from working from behind a proxy.
+ The wave module did something stupid, too (it is needed for
+ ripping from an image.) If you need neither, try it out and tell
+ me what works. Otherwise you may try to replace these modules
+ by the ones from the python-1.5.2 version. Again, please tell me
+ your experiences.
+<P>
+<LI><B>Q:</B> Jack is encoding with a different bitrate than specified in .jackrc. Why?
+<P>
+<B>A:</B> The bitrate is cached in jack.progress. So if you encode track n with bitrate x1, delete the MP3 and call jack without specifying a bitrate, bitrate x1 is used, regardless what the default in your .jackrc is. Specify the wanted bitrate with -b.
+<P>
+<LI><B>Q:</B> I want to use a flat local cddb directory. Jack wants me to use subfolders like rock, blues, ... . Can you help?
+<P>
+<B>A:</B> As long as nobody complains, use this workaround (sh style):<BR>
+cd [your_cddb_dir]<BR>
+for i in blues classical country data folk \<BR>
+jazz misc newage reggae rock soundtrack ; do<BR>
+ ln -s . "$i"<BR>
+done
+<P>
+</UL>
+Wanna go <A href=index.html>home</A>?
+
+<HR>
+<HRULE>
+<A NAME="BOTTOM"></A>
+<ADDRESS><SMALL>
+&#169; Arne Zellentin,
+<A HREF="mailto:zarne@users.sf.net">zarne@users.sf.net</A>
+</SMALL></ADDRESS>
+<SMALL>(changed: 19-Jul-99)</SMALL>
+</body>
+</html>
diff --git a/doc/gpl.txt b/doc/gpl.txt
new file mode 100644
index 0000000..d104746
--- /dev/null
+++ b/doc/gpl.txt
@@ -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.
diff --git a/doc/index.html b/doc/index.html
new file mode 100644
index 0000000..88dfcef
--- /dev/null
+++ b/doc/index.html
@@ -0,0 +1,177 @@
+<!--#include virtual="/include/head.html" -->
+<HEAD>
+<TITLE>Homepage of Jack</TITLE>
+<LINK REV=MADE href=mailto:zarne@users.sf.net>
+<LINK REL=STYLESHEET HREF="mine.css">
+</HEAD>
+<BODY BGCOLOR=#072387 TEXT=#d0d840 LINK=#f0a840 VLINK=#a06820>
+<CENTER>
+ <A href=index.html><IMG src="main.gif" height=32></A>
+ <A href=download.html><IMG src="download.gif" height=32></A>
+ <A href=screen.html><IMG src="screen.gif" height=32></A>
+ <A href=requirements.html><IMG src="requirements.gif" height=32></A>
+ <A href=install.html><IMG src="install.gif" height=32></A>
+ <A href=usage.html><IMG src="usage.gif" height=32></A>
+ <A href=links.html><IMG src="links.gif" height=32></A>
+</CENTER>
+<HR>
+<CENTER><IMG SRC="jack-logo.jpg" height=172 width=347 alt="Jack Logo"></CENTER>
+<H3>"Why would anyone need <I>another</I> CD ripper / MP3 encoder frontend?"</H3>
+If you just need everything that is new, download it <A href="download.html">here</A>. Be sure to read the <A href=requirements.html>system requirements</A>. It's quite likely that you do not have the correct python version.
+<P>
+<B>News:</B>
+<TABLE cellpadding=3>
+<TR><TD valign=top>10-Jan-2000
+ <TD>New Version (2.99.0-pre) "ain't it dead yet". I'm about to do some major
+ restructuring on jack, i.e. breaking it up into modules, so the
+ installation has changes slightly. Read INSTALL. Cdparanoia 9.7 is now
+ supported, so is xingmp3enc (the latter being untested). You can now use
+ a local freedb dir. Yes, all bugs fixed, especially those concerning
+ the image-reader. TOC file reading has been completely rewritten, it
+ is now 100% bug-free (as if). Please, everybody try this version and
+ gimme some feedback.
+<TR><TD valign=top>05-Nov-1999
+ <TD>New Version (2.2.5-pre). Still no documentation updates. Fixes
+ 2.2.4's renaming bug. Doh.
+<TR><TD valign=top>04-Nov-1999
+ <TD>New Version (2.2.4-pre). No documentation updates (again). Two bugs
+ fixed, one newly introduced. "desperate" mode for those who can't get curses mode to work.
+ Jack now has 3333 lines of code.
+<TR><TD valign=top>04-Nov-1999
+ <TD>New Version (2.2.3-pre). No documentation updates (again). Reintroduced
+ free space checking by calling df for all those (RedHat?) users who
+ had an error with statvfs. Whoever built those rpms should be punished.
+ rename_underscore has been replaced by something more flexible, now you
+ can replace characters by multiple characters (like "&auml;" to "ae"),
+ kill characters from the filename to be used and, of course, replace
+ those spaces by underlines.
+ New DAE tools: cdda2wav (finally!) and dagrab (untested as it doesn't
+ like my Plextor). All bug fixed, of course.
+<TR><TD valign=top>22-Oct-1999
+ <TD>New Version (2.2.2-pre). If you get an error like
+ "AttributeError: TIOCWINSZ", try this version. It is shipped
+ with 2.2.0 documentation as I didn't have time to update it yet.
+ Changes: all bugs fixed :), new features. Wait for the final version
+ for details.
+<TR><TD valign=top>19-Aug-1999
+ <TD>New Version (2.2.0). New curses mode, looks much better, scroll
+ through the status screen if it doesn't fit in your terminal.
+ I recommend not to use XTermset any longer as it is no longer needed.
+ The usual bugfixes are there, too, check the
+ <A href="ChangeLog">changelog</A>.
+<TR><TD valign=top>18-Aug-1999
+ <TD>I really want to add support for the Xing encoder. Does anyone want to pay the registration fee for me? It's only $19 I think.
+<TR><TD valign=top>09-Aug-1999
+ <TD>New Version (2.1.0), all bugs fixed (of course!), new features like
+ on-the-fly operation.
+<TR><TD valign=top>19-Jul-1999
+ <TD>Big OOPS. Version 2.0.0 contained (at least) two "brown paper bag" bugs:
+ first, jack tried to chdir into newly created dirs twice (one time too
+ much), this gets you the "could not create or change to..." error.
+ Second, the dirs were created in the wrong sequence, so instead
+ of "artist/album" you get "album/artist". Upgrade to 2.0.1, get it
+ from the <A href="download.html">download page</A>.
+<TR><TD valign=top>16-Jul-1999
+ <TD>None of the beta testers complained, so I released version 2.0.0.
+ Much has changed, most notably I restructured the beast a bit so that
+ Jack no longer needs threads. It's now much easier to get it to work.
+ Jack should (finally!) run on FreeBSD and most other unices.
+ Jack can now create (and name according to freedb data) directories.
+<TR><TD valign=top>01-Jul-1999
+ <TD>Released version 1.3.5. Support for tosha(gettoc&DAE) has been added,
+ a few non-critical bugs have been fixed.
+<TR><TD valign=top>29-Jun-1999
+ <TD>Released version 1.3.4. Version 1.3.3 contained a bug where it could
+ wrongly claim that you inserted the wrong CD if track 01 doesn't start
+ at block 0. The TOC file format changed, too, so better have them
+ re-generated.
+</TR>
+<TR><TD valign=top>27-Jun-1999
+ <TD>Released version 1.3.3. see the <A href="ChangeLog">changelog</A> for details
+</TR>
+<TR><TD>
+ <TD>rearranged the pages a bit. I know the layout still sux, if <B>you</B> have time to waste, let me know.
+</TR>
+</TABLE>
+<H3>What's it all about?</H3>
+Jack has been developed with one main goal: making MP3s<I> without having to
+worry</I>. There is nearly no way that an incomplete rip goes unnotices, e.g. jack
+compares WAV and MP3 filesizes when continuing from a previous run. Jack also
+checks your HD space before doing anything (even keeps some MB free).
+<P>
+You can take a <A href=screen.html>look</A> at jack working, there are even some explanations.
+<P>
+Jack is different from other such tools in a number of ways:
+<UL>
+<LI>it is written in <A href=http://www.python.org>python</A>
+
+<LI>it is <I>very</I> configurable, maybe too much :)
+
+<LI>it doesn't need X (but is better with it)
+
+<LI>it can "rip" virtual CD images like the ones created by <A
+href=http://www.ping.de/sites/daneb/cdrdao.html>cdrdao</A>
+
+<LI>when using cdparanoia, cdparanoia's status information is displayed for all
+tracks, so you can see if something went wrong (I consider this very important
+- no one wants uncorrected errors or skips to go into one's MP3s. 'Tis the main
+ reason why I don't use something beautiful like <A
+href=http://www.ling.ed.ac.uk/~oliphant/grip/>grip</A>)
+
+<LI>it allows for overlapping ripping / encoding: when the first track has been
+ripped, it's encoder is started as well as the second track's ripping process,
+this "read-ahead" is of course configurable, you can have it read the whole CD
+and start with the next one as the first one is beeing encoded
+
+<LI>in on-the-fly mode, no WAVs have to be created, less space is wasted. I only recommend this if your encoder is nearly as fast as your ripper.
+
+<LI>it uses sophisticated disk space management, i.e. it schedules it's ripping
+/ encoding processes depending on available space. Jack tells you if the batch
+will not fit on your HD. If you want, you can have it choose the optimal
+sequence of tracks - great if you're low on HD space
+
+<LI>freedb query, file renaming and id3 tagging - I know that this alone is not
+special, but jack tries hard to recognize artist and title when working on a
+"Various Artists"-CD, so that the ID3 tag can be set correctly.
+
+<LI>it can resume work after it has been interrupted. If all tracks have been
+ripped, it doesn't even need the CD anymore, even if you want to do a freedb
+query. In practice, this means that you can change the CD and start another
+instance of jack.
+
+<LI>it can do a freedb query based on MP3s alone, like if you don't remember
+from which CD those MP3s came from.
+
+</UL>
+
+Of course, all other "obvious" features are included, too:
+<UL>
+<LI>being <A href=gpl.txt>GPL</A>'ed
+
+<LI>different rippers and encoders are supported
+
+<LI>freedb submissions
+
+<LI>proxy support via http_proxy environment variable (e.g. export http_proxy=http://cache.sld.tld:3128/)
+
+</UL>
+
+You may want to read more on <A href=install.html>intallation</A> and <A href=usage.html>usage</A>.
+<BR>Because it is not just "point-and-click" I put up some <A href=examples.html>examples</A>.
+<BR>Perhaps you want to know what Jack has <A href=TODO>todo</A>.
+<BR>For your amusement, read the <A href=faq.html>FAQ</A>!
+<P>
+Please send feedback to the address below. Yes I wanna hear about all the typos I've made.
+<P>
+<IMG src="anim.written.in.vi.gif" align=right>
+<HR>
+<A NAME="BOTTOM"></A>
+<ADDRESS><SMALL>
+"All trademarks are owned by their owners" or whatever I have to state.<BR>
+&#169; Arne Zellentin,
+<A HREF="mailto:zarne@users.sf.net">zarne@users.sf.net</A>
+</SMALL></ADDRESS>
+<SMALL>(changed: 10-Jan-2000)</SMALL>
+
+</body>
+</html>
diff --git a/doc/install.gif b/doc/install.gif
new file mode 100644
index 0000000..7301a6a
--- /dev/null
+++ b/doc/install.gif
Binary files differ
diff --git a/doc/install.html b/doc/install.html
new file mode 100644
index 0000000..0506460
--- /dev/null
+++ b/doc/install.html
@@ -0,0 +1,53 @@
+<!--#include virtual="/include/head.html" -->
+<HEAD>
+<TITLE>Jack installation instructions</TITLE>
+<LINK REV=MADE href=mailto:zarne@users.sf.net>
+</HEAD>
+<BODY BGCOLOR=#072387 TEXT=#d0d840 LINK=#f0a840 VLINK=a06820>
+
+<CENTER>
+ <A href=index.html><IMG src="main.gif" height=32></A>
+ <A href=download.html><IMG src="download.gif" height=32></A>
+ <A href=screen.html><IMG src="screen.gif" height=32></A>
+ <A href=requirements.html><IMG src="requirements.gif" height=32></A>
+ <A href=install.html><IMG src="install.gif" height=32></A>
+ <A href=usage.html><IMG src="usage.gif" height=32></A>
+ <A href=links.html><IMG src="links.gif" height=32></A>
+</CENTER>
+
+<TABLE width=100%><TR><TD valign=bottom>
+<H3><U>Installation instructions for Jack</U></H3>
+<TD valign=bottom>
+<IMG SRC="jack-logo.jpg" align=right alt="Jack" height=50 width=101></TR>
+</TABLE>
+
+<UL>
+<LI><A href=download.html>download jack</A>.
+<LI>uncompress it: <PRE>$ gunzip jack-*.tar.gz</PRE>
+<LI>untar it: <PRE>$ tar xvf jack-*.tar</PRE>
+<LI>change directory: <PRE>$ cd jack-*</PRE>
+<LI>follow instructions from file "<A href=INSTALL>INSTALL</A>"
+<LI>if this is your first installation of jack, run it once, it will create a new personal preferences file in your homedir
+<LI>customize it using your favorite editor:
+<PRE>$ vi $HOME/.jackrc</PRE>
+<LI>you may want to enable proxy support via the http_proxy environment
+variable
+<PRE>$ http_proxy=http://cache.sld.tld:3128/
+$ export http_proxy</PRE>
+This is a great addition to your .bashrc or whatever you are using!
+</UL>
+Wanna go <A href=index.html>home</A> or on to the <A href=usage.html>usage information</A>?
+
+<HR>
+<HRULE>
+<A NAME="BOTTOM"></A>
+<ADDRESS><SMALL>
+&#169; Arne Zellentin,
+<A HREF="mailto:zarne@users.sf.net">zarne@users.sf.net</A>
+</SMALL></ADDRESS>
+<SMALL>(changed: 19-Aug-99)</SMALL>
+
+</body>
+</html>
+
+
diff --git a/doc/jack-curses-screen.gif b/doc/jack-curses-screen.gif
new file mode 100644
index 0000000..351675e
--- /dev/null
+++ b/doc/jack-curses-screen.gif
Binary files differ
diff --git a/doc/jack-logo.jpg b/doc/jack-logo.jpg
new file mode 100644
index 0000000..ad7c424
--- /dev/null
+++ b/doc/jack-logo.jpg
Binary files differ
diff --git a/doc/jack-screen.gif b/doc/jack-screen.gif
new file mode 100644
index 0000000..c03c864
--- /dev/null
+++ b/doc/jack-screen.gif
Binary files differ
diff --git a/doc/links.gif b/doc/links.gif
new file mode 100644
index 0000000..555d09b
--- /dev/null
+++ b/doc/links.gif
Binary files differ
diff --git a/doc/links.html b/doc/links.html
new file mode 100644
index 0000000..a232fc9
--- /dev/null
+++ b/doc/links.html
@@ -0,0 +1,81 @@
+<!--#include virtual="/include/head.html" -->
+<HEAD>
+<TITLE>Jack-related links</TITLE>
+<LINK REV=MADE href=mailto:zarne@users.sf.net>
+</HEAD>
+<BODY BGCOLOR=#072387 TEXT=#d0d840 LINK=#f0a840 VLINK=a06820>
+<CENTER>
+ <A href=index.html><IMG src="main.gif" height=32></A>
+ <A href=download.html><IMG src="download.gif" height=32></A>
+ <A href=screen.html><IMG src="screen.gif" height=32></A>
+ <A href=requirements.html><IMG src="requirements.gif" height=32></A>
+ <A href=install.html><IMG src="install.gif" height=32></A>
+ <A href=usage.html><IMG src="usage.gif" height=32></A>
+ <A href=links.html><IMG src="links.gif" height=32></A>
+</CENTER>
+
+<TABLE width=100%><TR><TD valign=bottom>
+<H3><U>Jack-related links</U></H3>
+<TD valign=bottom>
+<IMG SRC="jack-logo.jpg" align=right alt="Jack" height=50 width=101></TR>
+</TABLE>
+
+<TABLE border cellpadding=3>
+<TR bgcolor=black>
+ <TD>Description
+ <TD>Location
+</TR>
+<TR>
+ <TD>scripToys - my collection of shell scripts, python hacks and C progs which make my (MP3-) life easier.
+ <TD align=center><A href="../scripToys/">scripToys</A>
+</TR>
+<TR>
+ <TD>Python homepage
+ <TD align=center><A href="http://www.python.org">python</A>
+</TR>
+<TR>
+ <TD>LAME is not MP3 encoder - IMHO the best free MP3 encoder out there, much better than all other ISO-based encoders
+ <TD align=center><A href="http://www.sulaco.org/mp3/">lame</A>
+</TR>
+<TR>
+ <TD>cdparanoia homepage
+ <TD align=center><A href="http://www.xiph.org/paranoia/">cdparanoia</A>
+</TR>
+<TR>
+ <TD>tosha - a DAE tool which works on FreeBSD, Toshiba drives only
+ <TD align=center><A href="http://dorifer.heim3.tu-clausthal.de/~olli/tosha/">tosha</A>
+</TR>
+<TR>
+ <TD>cdrdao - DAO (Disc At Once) mode CD writing software
+ <TD align=center><A href="http://www.ping.de/sites/daneb/cdrdao.html">cdrdao</A>
+</TR>
+<TR>
+ <TD>alternate cursesmodule, use the patched version on the download page!
+ <TD align=center><A href="http://andrich.net/python/selfmade.html#ncursesmodule">cursesmodule</A>
+</TR>
+<TR>
+ <TD>xtermset - control (running!) xterm properties via command line
+ <TD align=center><A href="http://www.cs.vu.nl/~bernsti/xtermset/">xtermset</A>
+</TR>
+<TR>
+ <TD>JeFCo - the Jesterware FreeDB Collector, created to be the solution
+ of all your cddb problems.
+ <TD align=center><A href="http://jesterware.freeservers.com">Jesterware</A>
+</TR>
+
+
+</TABLE>
+
+<P>
+Wanna go <A href=index.html>home</A>?
+<HR>
+<HRULE>
+<A NAME="BOTTOM"></A>
+<ADDRESS><SMALL>
+&#169; Arne Zellentin,
+<A HREF="mailto:zarne@users.sf.net">zarne@users.sf.net</A>
+</SMALL></ADDRESS>
+<SMALL>(changed: 10-Jan-00)</SMALL>
+
+</body>
+</html>
diff --git a/doc/main.gif b/doc/main.gif
new file mode 100644
index 0000000..71c48ae
--- /dev/null
+++ b/doc/main.gif
Binary files differ
diff --git a/doc/mine.css b/doc/mine.css
new file mode 100644
index 0000000..0e8edf6
--- /dev/null
+++ b/doc/mine.css
@@ -0,0 +1,96 @@
+BODY {
+ background: #072387;
+ background-repeat: repeat-y;
+ margin-left: 0;
+ margin-top: 0
+}
+BODY, P, DL, UL {
+ color: #d0d840
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ font-size: normal;
+ line-height: normal;
+ font-family: Helvetica, Arial, Verdana, sans-serif
+}
+IMG {
+ border: 0
+}
+P.info {
+ color: #666666;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ font-size: normal;
+ line-height: normal;
+ font-family: Helvetica, Arial, Verdana, sans-serif
+}
+#header {
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ font-size: small;
+ font-family: Helvetica, Arial, Verdana, sans-serif;
+ text-align: right
+}
+#footer {
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ font-size: small;
+ font-family: Helvetica, Arial, Verdana, sans-serif;
+ text-align: right
+}
+#menu {
+ color: #FFFFFF;
+ font-style: normal;
+ font-weight: bold;
+ font-variant: normal;
+ font-size: 12pt;
+ font-family: Helvetica, Arial, Verdana, sans-serif;
+ text-align: left
+}
+H1 {
+ color: #d0d840
+ font-style: normal;
+ font-weight: bold;
+ font-variant: normal;
+ font-size: large;
+ font-family: Helvetica, Arial, Verdana, sans-serif
+}
+H2 {
+ color: #d0d840
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ font-size: large;
+ font-family: Helvetica, Arial, Verdana, sans-serif
+}
+H3 {
+ color: #d0d840
+ font-style: normal;
+ font-weight: bold;
+ font-variant: normal;
+ font-size: medium;
+ font-family: Helvetica, Arial, Verdana, sans-serif
+}
+H4 {
+ color: #d0d840
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ font-size: medium;
+ font-family: Helvetica, Arial, Verdana, sans-serif
+}
+A:link {
+ #text-decoration: none;
+ color: #f0a840
+}
+A:active {
+ #text-decoration: none;
+ color: #006699
+}
+A:visited {
+ #text-decoration: none;
+ color: #a06820
+}
diff --git a/doc/requirements.gif b/doc/requirements.gif
new file mode 100644
index 0000000..8d53935
--- /dev/null
+++ b/doc/requirements.gif
Binary files differ
diff --git a/doc/requirements.html b/doc/requirements.html
new file mode 100644
index 0000000..8bb8cc5
--- /dev/null
+++ b/doc/requirements.html
@@ -0,0 +1,72 @@
+<!--#include virtual="/include/head.html" -->
+<HEAD>
+<TITLE>Jack's system requirements</TITLE>
+<LINK REV=MADE href=mailto:zarne@users.sf.net>
+</HEAD>
+<BODY BGCOLOR=#072387 TEXT=#d0d840 LINK=#f0a840 VLINK=a06820>
+<CENTER>
+ <A href=index.html><IMG src="main.gif" height=32></A>
+ <A href=download.html><IMG src="download.gif" height=32></A>
+ <A href=screen.html><IMG src="screen.gif" height=32></A>
+ <A href=requirements.html><IMG src="requirements.gif" height=32></A>
+ <A href=install.html><IMG src="install.gif" height=32></A>
+ <A href=usage.html><IMG src="usage.gif" height=32></A>
+ <A href=links.html><IMG src="links.gif" height=32></A>
+</CENTER>
+
+<TABLE width=100%><TR><TD valign=bottom>
+<H3><U>System requirements for Jack</U></H3>
+<TD valign=bottom>
+<IMG SRC="jack-logo.jpg" align=right alt="Jack" height=50 width=101></TR>
+</TABLE>
+
+Jack has been developed using Linux, but I think it should work with other unices, too, as long as it resembles posix (has ptys, ...). If it doesn't work, tell me!
+<P>
+In order to run Jack, you <B>must</B> have the following installed:
+<UL>
+<LI>python 1.5.2: with earlier versions freedb query and ripping-from-image
+will not work. You also need the following modules (they are all part of the
+python source distribution but some may not be installed by default, since
+Version 2 Jack no longer needs threads; it's now a lot easier to get is to
+work.):
+ <UL><LI><B>termios</B>
+ <LI>sndhdr
+ <LI>urllib
+ <LI>pty
+ <LI>wav
+ </UL>
+ If it doesn't run, check these first!
+<LI>Ben Gertzfield's <A href=http://csl.cse.ucsc.edu/~ben/python/>ID3 module</A> WARNING: version 0.5 is buggy, use at least 0.6. No need to download it, I packaged it with jack.
+<LI><A href=http://www.xiph.org/paranoia/>cdparanoia</A> or <A href=http://dorifer.heim3.tu-clausthal.de/~olli/tosha/>tosha</A> for audio extraction
+<LI>df (comes with any unix)
+</UL>
+You will also have to install at least one of these MP3 encoders:
+<UL>
+<LI><A href=http://home8.swipnet.se/~w-82625/encoder/>bladeenc</A>
+<LI><A href=http://www.sulaco.org/mp3/>lame</A>
+<LI>mp3enc
+<LI>l3enc
+</UL>
+
+Optional:
+<UL>
+<LI>a patched <A href=cursesmodule-with-resizeterm-1.5b2.tar.gz>cursesmodule</A>,
+if you want to use the new curses pseudographical mode
+<LI><A href=http://www.cs.vu.nl/~bernsti/xtermset/>xtermset</A>
+<LI><A href=http://www.ping.de/sites/daneb/cdrdao.html>cdrdao</A>
+</UL>
+Wanna go <A href=index.html>home</A>?
+
+<HR>
+<HRULE>
+<A NAME="BOTTOM"></A>
+<ADDRESS><SMALL>
+"All trademarks are owned by their owners" or whatever I have to state.</BR>
+&#169; Arne Zellentin,
+<A HREF="mailto:zarne@users.sf.net">zarne@users.sf.net</A>
+</SMALL></ADDRESS>
+<SMALL>(changed: 19-Aug-99)</SMALL>
+</body>
+</html>
+
+
diff --git a/doc/screen.gif b/doc/screen.gif
new file mode 100644
index 0000000..175ecd9
--- /dev/null
+++ b/doc/screen.gif
Binary files differ
diff --git a/doc/screen.html b/doc/screen.html
new file mode 100644
index 0000000..9eb3e92
--- /dev/null
+++ b/doc/screen.html
@@ -0,0 +1,62 @@
+<!--#include virtual="/include/head.html" -->
+<HEAD>
+<TITLE>Jack's status screen</TITLE>
+<LINK REV=MADE href=mailto:zarne@users.sf.net>
+</HEAD>
+<BODY BGCOLOR=#072387 TEXT=#d0d840 LINK=#f0a840 VLINK=a06820>
+<CENTER>
+ <A href=index.html><IMG src="main.gif" height=32></A>
+ <A href=download.html><IMG src="download.gif" height=32></A>
+ <A href=screen.html><IMG src="screen.gif" height=32></A>
+ <A href=requirements.html><IMG src="requirements.gif" height=32></A>
+ <A href=install.html><IMG src="install.gif" height=32></A>
+ <A href=usage.html><IMG src="usage.gif" height=32></A>
+ <A href=links.html><IMG src="links.gif" height=32></A>
+</CENTER>
+
+<TABLE width=100%><TR><TD valign=bottom>
+<H3><U>A screenshot of Jack working (with comments)</U></H3>
+<TD valign=bottom>
+<IMG SRC="jack-logo.jpg" align=right alt="Jack" height=50 width=101></TR>
+</TABLE>
+
+<BR><BR>
+<CENTER><IMG src=jack-screen.gif alt="screenshot with comments"></CENTER>
+<BR><BR>
+
+(This shows the old 1.3.x version of Jack, 2.x.x look a bit different but nothing major)
+<P>
+Note that the xterm is automatically resized to fit all the tracks of the CD if you have xtermset installed (and enabled).
+<P>
+Jack is a console tool, somewhat in the great tradition of command-line oriented software. When using XTerm and xtermset the xterm's size is automagically changed to match the number of tracks.
+
+This is the meaning of the marked elements:
+<UL>
+<LI>1: the options line: list general options like MP3 bitrate, the WAV read-ahead,...
+<LI>2: the DAE speed: how much faster than real-time the audio data has been extracted
+<LI>3: the DAE status: actually, this is the output of cdparanoia, check cdparanoia -h for details on the used characters, e.g. a "+" means "Unreported loss of streaming/other error in read".
+<LI>4: the encoder status: while encoding, the percentage along with the ETA is displayed, afterwards the speed-factor is given, how much faster (or, like in the exaple, slower) than real-time the encoding was
+<LI>5: DAE in progress: the greater-sign indicates how far the extraction of this track has come, again, this is cdparanoia output.
+<LI>6: the status line: how much disc-space is left, how many WAVs are waiting to be encoded, how many subprocesses are idle, and, finally, the error status.
+</UL>
+
+<H3>Update</H3>
+I made another screenshot of the more current version 2.2.0, using curses.
+You will be able to understand it if you read above descriptions.
+
+<BR><BR>
+<CENTER><IMG src=jack-curses-screen.gif alt="screenshot using curses"></CENTER>
+<BR><BR>
+
+Wanna go <A href=index.html>home</A>?
+
+<HR>
+<HRULE>
+<A NAME="BOTTOM"></A>
+<ADDRESS><SMALL>
+&#169; Arne Zellentin,
+<A HREF="mailto:zarne@users.sf.net">zarne@users.sf.net</A>
+</SMALL></ADDRESS>
+<SMALL>(changed: 19-Aug-99)</SMALL>
+</body>
+</html>
diff --git a/doc/todo.html b/doc/todo.html
new file mode 100644
index 0000000..15b5218
--- /dev/null
+++ b/doc/todo.html
@@ -0,0 +1,43 @@
+<!--#include virtual="/include/head.html" -->
+<HEAD>
+<TITLE>Jack's TODO</TITLE>
+<LINK REV=MADE href=mailto:zarne@users.sf.net>
+</HEAD>
+<BODY BGCOLOR=#072387 TEXT=#d0d840 LINK=#f0a840 VLINK=a06820>
+<CENTER>
+ <A href=index.html><IMG src="main.gif" height=32></A>
+ <A href=download.html><IMG src="download.gif" height=32></A>
+ <A href=screen.html><IMG src="screen.gif" height=32></A>
+ <A href=requirements.html><IMG src="requirements.gif" height=32></A>
+ <A href=install.html><IMG src="install.gif" height=32></A>
+ <A href=usage.html><IMG src="usage.gif" height=32></A>
+ <A href=links.html><IMG src="links.gif" height=32></A>
+</CENTER>
+
+<TABLE width=100%><TR><TD valign=bottom>
+<H3><U>Future additions and other TODOs for Jack</U></H3>
+<TD valign=bottom>
+<IMG SRC="jack-logo.jpg" align=right alt="Jack" height=50 width=101></TR>
+</TABLE>
+
+<UL>
+<LI>curses-"frontend" (MED)
+<LI>add more rippers and encoders (which?) (MED)
+<LI>GTK+ frontend (LOW)
+<LI>get alternate freedb sites, ... (LOW)
+<LI>create a man page (LOW)
+<LI>have Jack schedule encoders in reaction to system load (done but not perfect yet)
+<LI>re-check free space repeatedly, react to changes (done but still buggy)
+</UL>
+Wanna go <A href=index.html>home</A>?
+
+<HR>
+<HRULE>
+<A NAME="BOTTOM"></A>
+<ADDRESS><SMALL>
+&#169; Arne Zellentin,
+<A HREF="mailto:zarne@users.sf.net">zarne@users.sf.net</A>
+</SMALL></ADDRESS>
+<SMALL>(changed: 10-Jun-99)</SMALL>
+</body>
+</html>
diff --git a/doc/usage.gif b/doc/usage.gif
new file mode 100644
index 0000000..724bb12
--- /dev/null
+++ b/doc/usage.gif
Binary files differ
diff --git a/doc/usage.html b/doc/usage.html
new file mode 100644
index 0000000..f920023
--- /dev/null
+++ b/doc/usage.html
@@ -0,0 +1,329 @@
+<!--#include virtual="/include/head.html" -->
+<HEAD>
+<TITLE>Jack Usage</TITLE>
+<LINK REV=MADE href=mailto:zarne@users.sf.net>
+</HEAD>
+<BODY BGCOLOR=#072387 TEXT=#d0d840 LINK=#f0a840 VLINK=a06820>
+<CENTER>
+ <A href=index.html><IMG src="main.gif" height=32></A>
+ <A href=download.html><IMG src="download.gif" height=32></A>
+ <A href=screen.html><IMG src="screen.gif" height=32></A>
+ <A href=requirements.html><IMG src="requirements.gif" height=32></A>
+ <A href=install.html><IMG src="install.gif" height=32></A>
+ <A href=usage.html><IMG src="usage.gif" height=32></A>
+ <A href=links.html><IMG src="links.gif" height=32></A>
+</CENTER>
+
+<TABLE width=100%><TR><TD valign=bottom>
+<H3><U>Usage information for Jack</U></H3>
+<TD valign=bottom>
+<IMG SRC="jack-logo.jpg" align=right alt="Jack" height=50 width=101></TR>
+</TABLE>
+
+Jack is mostly command line-driven, but there are some options which can only
+be set by editing the preferences file, typically named $HOME/.jackrc. This
+file is python code executed at startup so be careful. Invoke Jack with -h for
+a short description of the command line options.
+<P>
+If you get bored reading documentation, check out the <A href=examples.html>application examples</A>!
+
+<P>
+<H4>Important things to configure in this file:</H4>
+<TABLE border cellpadding=3>
+<TR><TD>rename_fmt
+<TD>with this variable you can determine how jack will rename your MP3s after a
+freedb query. Use "%02i" for track number, "%a" for artist, "%l" for album name
+and "%t" for track title. Example: "%02i.%a (%l) - %t" which would result in
+"01.Artist (Album) - Track title.mp3"
+</TR>
+
+<TR><TD>rename_fmt_va
+<TD>same as above for "various artists" CDs. For normal CDs I use "%02i.%t.mp3" while I use ""%02i.%a - %t" for VA CDs.
+</TR>
+
+<TR><TD>dir_template
+<TD>if rename_dir is set, use this format for naming directories. Again, %a is substituted by the artist, %l is the album's name. I use "%a - %l" but "%a/%l"
+is also common, every artist then has his own subdir.
+</TR>
+
+<TR><TD>my_mail<TD>be sure to set your e-mail address if you are planning to submit
+<A href=http://www.freedb.org>freedb</A> entries. Otherwise you won't get
+feedback.
+</TR>
+<TR><TD>xtermset_enable
+<TD>I no longer use this since there is now a curses mode. Only set this if you have <A
+href=http://www.cs.vu.nl/~bernsti/xtermset/>xtermset</A> installed.
+When enabled, Jack can - when running in an xterm - adjust the
+size so all track information is visible. In addition, the xterm is deiconified
+when all is done.
+</TR>
+</TABLE>
+<P>
+
+Everything else is controlled by the command line, try jack --help for a quick summary. You can still change the defaults by editing your .jackrc!
+
+<H4>Command line options:</H4>
+<TABLE table border cellpadding=3>
+<TR><TD>-t, --tracks &lt;tracks&gt;
+<TD>limit ripping and encoding to the specified
+tracks, use comma to seperate track. Ranges are also possible; 5-9 is
+equivalent to 5,6,7,8,9; 12- is like specifying track 12,...,last_track. The
+default is to process the whole CD, I do not know why you would want an
+incomplete rip, but be my guest.
+</TR>
+
+<TR><TD>-b, --bitrate &lt;bitrate&gt;
+<TD>bitrate of the resulting MP3 in kbit/s, default is 160.
+</TR>
+
+<TR><TD>-v, --vbr
+<TD>Generate variable bitrate MP3s, only on encoders which support this. Default is no.
+</TR>
+
+<TR><TD>-e, --encoders &lt;num_encoders&gt;
+<TD>encode how many MP3s in parallel. If
+you have a SMP machine or simply want to stress your system, you can have Jack
+encode several MP3s at once. Default is the number of CPUs I have, which is
+one.
+</TR>
+
+<TR><TD>--otf
+<TD>On-the-fly operation. Only on some encoders/rippers. Do not create WAVs, pipe ripper output through the encoder. Default is no as it's a torture for the CDROM drive (IMHO).
+</TR>
+
+<TR><TD>-E, --encoder-name &lt;encoder_name&gt;
+<TD>choose which MP3 encoder to use,
+valid choices are: blade, lame, l3enc, mp3enc. Note that you may have to install
+the encoder.
+</TR>
+
+<TR><TD>-n, --nice &lt;nice_lvl&gt;
+<TD>nice-level with which the encoders are
+started. Default is 12 which shouldn't hurt your system much.
+</TR>
+
+<TR><TD>-l, --max-load &lt;max_load&gt;
+<TD>only start new encoders if your system's load is below (max_load + num_encoders). If the load is too high, encoding operation is suspended until above criterium is satisfied.
+</TR>
+
+<TR><TD>-a, --read-ahead &lt;read_ahead&gt;
+<TD>read how many WAVs in advance. At most read_ahead +
+num_encoders WAVs are ripped before a track has completely been encoded. Default is 99 which will
+read the whole CD, provided there is enough disk space.
+</TR>
+
+<TR><TD>-r, --reorder
+<TD>optimize track-order for disk space. This can save you some
+peak disk space during the encoding process; this may make it possible to do a
+CD which would otherwise fail to be encoded.
+</TR>
+
+<TR><TD>-s, --space &lt;free_space&gt;
+<TD>forcably set usable diskspace, in bytes. This option lets you
+limit the disk space Jack uses, maybe you need it for something else? Be
+careful: if set too high, ripping and encoding will probably fail. The default
+is to look how much is free and to use this value.
+</TR>
+
+<TR><TD>-o, --overwrite
+<TD>overwrite existing WAVs and MP3s, i.e. do not check if
+already ripped WAVs or an already encoded MP3s seem to be ok. Use this if you
+<I>know</I> something went wrong last time. This is off by default.
+</TR>
+
+<TR><TD>-O, --only-dae
+<TD>only produce WAVs, implies --keep-wavs. This is off by default.
+</TR>
+
+<TR><TD>-k, --keep-wavs
+<TD>do not delete WAVs after encoding them. Maybe you still
+need them.
+</TR>
+
+<TR><TD>-f, --from-tocfile &lt;toc_file&gt;
+<TD>rip from CD image on hd. This option is a
+somewhat special option. There are two reasons for it's existance:
+<UL>
+<LI>When I copy a CD for my car stereo, most of the time I also want to
+have it as MP3s. To copy a CD, I use the exellent tool <A href=http://www.ping.de/sites/daneb/cdrdao.html>cdrdao</A>. This works best
+when having the whole CD in a single WAV file, which I call the image. It also
+needs a so-called toc-file containing the track, pregap, index, CD-TEXT, ...
+information. If I use this image as the source, I do not have to rip the CD a
+second time. My cdrom probably likes that.
+
+<LI>same cdrdao has a great way of doing a bit-wise exact copy of the CD using
+special Plextor-commands. This is superiour to cdparanoia as "bad" samples can
+be detected and - hopefully - repaired.
+</UL>
+The specified toc-file contains the name of the image file.
+</TR>
+
+<TR><TD>-F, --from-image &lt;image_file&gt;
+<TD>read audio data from image file. Like --from-tocfile, but
+the image itself is specified instead of the tocfile. Use this if you do not
+have a toc-file; the TOC is the read from the CD itself.
+</TR>
+
+<TR><TD>-S, --swab
+<TD>swap byteorder from image file. As cdrdao momentarily only
+outputs "raw" .cdr files, you quite likely want to swap the byteorder. Try this
+option if your WAVs and MP3s contain only noise (no, not the noise I call
+music, the pseudo-random one). This is on by default as cdrdao currently
+generates .cdr files that are "wrong".
+</TR>
+
+<TR><TD>-c, --check-toc
+<TD>compare toc-file and cd-toc, then exit. Jack caches the
+TOC of a CD in a file ("jack.toc"). If you want to know if the inserted CD
+matches the toc-file in the current directory, use this option.
+</TR>
+
+<TR><TD>-g, --guess-toc &lt;mp3_files&gt;
+<TD>make up a TOC from the MP3 file given in mp3_files. Format is:
+<PRE>track_01.mp3 ... track_nn.mp3 ;</PRE>
+Note that the trailing ";" is only necessary if you want to append more options
+to your command line. This option makes me especially proud. You can use it to
+do a freedb query based on MP3s alone - no need for the CD. Very useful if you
+have no idea which CD the MP3s are from. The MP3s must be given in the same
+order as they were on their CD. The generated TOC file is similar, but not
+identical to the TOC of the CD - do not
+submit these!
+</TR>
+
+<TR><TD>--various
+<TD>when parsing freedb data, jack assumes that if the disc's
+artist is set to "Various Artists" the track titles have the format "[artist] -
+[title]". If the disc title is set to something else and you still want above
+behaviour, use --various.
+</TR>
+
+<TR><TD>--no-various
+<TD>use this if freedb data says it't "Various Artists" but you want the normal
+renaming scheme, e.g. if Jack can't seperate artist and track title.
+</TR>
+
+<TR><TD>--remove
+<TD>have jack remove it's temp files. See below for details on these
+files. Be careful - don't delete them too early!
+</TR>
+
+<TR><TD>--upd-progress
+<TD>have jack re-create it's temp files. Use this if you deleted them too early.
+</TR>
+
+<TR><TD>-d, --dont-work
+<TD>don't do DAE or encoding. This may be useful if you only want to
+do a freedb query.
+</TR>
+
+<TR><TD>-D, --create-dirs
+<TD>tells Jack to create sub-directories in which Jack puts all the files
+for the current CD. If no freedb data is available, i.e. when not using -Q,
+these directorys will be named "jack-xxxxxxxx" where "xxxxxxxx" stands for the
+CD's freedb-id. Otherwise dir_template (see above) will be used.
+</TR>
+
+<TR><TD>--todo
+<TD>print what would be done and exit.
+</TR>
+
+</TABLE>
+<P>
+<H4>The following options are needed for <A href=www.freedb.org>freedb</A> queries:</H4>
+<TABLE border cellpadding=3>
+<TR><TD>-q, --query
+<TD>do freedb query when all is done. This is useful if Jack was prior run
+without a freedb query. If all tracks are done you don't even have to have a CD
+inserted as the TOC is cached by Jack. After having finished ripping and
+encoding, Jack will rename the MP3s and tag them using Ben Gertzfield's ID3.py
+module
+
+</TR>
+
+<TR><TD>-Q, --query-now
+<TD>do freedb query when starting. Use this if you are
+connected to the internet when starting jack. Know that the query may need
+user-interaction. After having finished ripping and encoding, Jack will rename
+the MP3s and tag them using Ben Gertzfield's ID3.py module
+</TR>
+
+<TR><TD>-R, --rename-only
+<TD>rename and tag files according to freedb file. On startup, Jack
+creates a blank freedb entry file (except if --query-now is used, then the file
+is queried from your freedb server). If you have changed it's contents (e.g.
+because the CD was unknown to freedb) and want to rename and tag your MP3s
+accordingly, use this option.
+</TR>
+
+<TR><TD>-u, --undo-rename
+<TD>undo file renaming and exit. If you don't like how jack renamed your files, use this option to restore the previous state. Note that ID3 tags are not restored.
+</TR>
+
+<TR><TD>-m, --mail-submit
+<TD>submit freedb entry via e-mail. HTTP submission is
+preferred but while freedb only supports submission by e-mail you have to use
+this. You will have to enter the category of the CD.
+</TR>
+
+<TR><TD>-M, --mail-address &lt;submit_addr&gt;
+<TD>when submitting a database entry via e-mail, use this
+option to specify the address the entry is mailed to. Default is
+freedb-submit@freedb.org.
+</TR>
+
+<TR><TD>--my-email &lt;your_mail&gt;
+<TD>use this to specify which e-mail address submission results are
+mailed to. Please use your real e-mail address.
+</TR>
+
+<TR><TD>--submit
+<TD>submit freedb entry via HTTP. You will have to enter the category
+of the CD. At the moment, this does not work with freedb. Or with a buggy
+urllib.
+</TR>
+
+<TR><TD>--server &lt;freedb_server&gt;
+<TD>which freedb server to use. Don't forget to set your proxy.
+Default server is freedb.freedb.org.
+</TR>
+
+<TR><TD>--force
+<TD>do not ask. Like when deleting incomplete (?!) files.
+</TR>
+
+</TABLE>
+<P>
+Jack creates a couple of files when running:
+<TABLE cellpadding=3>
+<TR><TD><LI>jack.toc
+<TD>The CD TOC (Table Of Contents) is cached here. Will be re-created
+if deleted but be careful to insert the matching CD!
+</TR>
+<TR><TD><LI>jack.freedb
+<TD>when you are not telling jack to do a freedb query, this file
+is created as a freedb template file which you can fill out yourself. When
+using --query or --query-now it is filled with the query-result. Existing files
+are backed up as jack.freedb.bak.
+</TR>
+<TR><TD><LI>jack.progress
+<TD>Status (main) screen output is cached here. When resuming
+work, previously finished processes' output is still displayed. Jack also uses
+this file to mark ripping/encoding steps as done, so if you delete this file,
+everything is done again!
+</TR>
+</TABLE>
+Do not delete these files too early, this may case frustration!
+<P>
+Wanna go <A href=index.html>home</A> or do you wanna know <A href=install.html>how to install Jack</A>?
+
+<HR>
+<SMALL>"All trademarks are owned by their owners" or whatever I have to state.</SMALL>
+<A NAME="BOTTOM"></A>
+<ADDRESS><SMALL>
+&#169; Arne Zellentin,
+<A HREF="mailto:zarne@users.sf.net">zarne@users.sf.net</A>
+</SMALL></ADDRESS>
+<SMALL>(changed: 19-Aug-99)</SMALL>
+
+</body>
+</html>
diff --git a/example.etc.jackrc b/example.etc.jackrc
new file mode 100644
index 0000000..04ec871
--- /dev/null
+++ b/example.etc.jackrc
@@ -0,0 +1,41 @@
+# jackrc-version:31
+# Global jackrc file, documenting some of the most important
+# options. The default options are commented out.
+
+# If you want to changes some of these options, add --save to the command-line
+# which sets the respective options. They will be saved in your ~/.jack3rc.
+
+##########################################################################
+
+# The directory where jack puts the files by default.
+# This defaults to the current directory if unset:
+#base_dir:~/jack
+
+# The default ripper. You can choose from 'cdparanoia'(default), 'cdda2wav',
+# 'tosha' or 'dagrab' (The last two ones are not avaiable in Debian).
+#ripper:cdparanoia
+
+# The default encoder. You can choose from 'oggenc'(default), 'xing',
+# 'mppenc', 'lame', 'l3enc', 'gogo', 'mp3enc' or 'flac' (only oggenc is
+# avaiable in Debian).
+#encoder:oggenc
+
+# The VBR-quality of the encoded OGGs. -1 is lowest, 10 is highest.
+#vbr_quality:4
+
+# rename_fmt specifies how the resulting files are named:
+# %n: track number
+# %a: artist
+# %t: track title
+# %l: album title
+# %y: album release year - individual track years are unsupported
+# %g: album genre - individual track genres are unsupported
+#rename_fmt:%a - %l - %n - %t # "Artist - Album - 01 - Tracktitle.[ext]"
+
+# If this is set, query freedb when starting to rip a CD.
+# By default, jack does not query freedb
+#query_if_needed=yes
+
+# If this is set, continue work even if the freedb-query failed.
+# By default, jack stops with an error
+#cont_failed_query
diff --git a/jack b/jack
new file mode 100755
index 0000000..d59c949
--- /dev/null
+++ b/jack
@@ -0,0 +1,291 @@
+#!/usr/bin/env python
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2004 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+### If you want to comment on this program, contact me: zarne@users.sf.net ###
+### Visit the homepage: http://www.home.unix-ag.org/arne/jack/
+
+### see CHANGELOG for recent changes in this program
+### see TODO if you want to see what needs to be implemented
+
+import os
+import sys
+import time
+import wave
+import types
+import posix
+import string
+import select
+import signal
+import pprint
+import traceback
+
+from jack_globals import *
+
+import jack_version
+import jack_misc
+import jack_mp3
+import jack_argv
+import jack_rc
+import jack_helpers
+import jack_targets
+import jack_freedb
+import jack_display
+import jack_term
+import jack_children
+import jack_tag
+import jack_workers
+import jack_utils
+import jack_ripstuff
+import jack_encstuff
+import jack_checkopts
+import jack_status
+import jack_functions
+import jack_main_loop
+import jack_progress
+import jack_prepare
+
+
+##############################################################################
+###################### M A I N ###############################################
+##############################################################################
+
+# say hello...
+print "This is", jack_version.prog_name, jack_version.prog_version, jack_version.prog_copyright, jack_version.prog_devemail
+
+### interpret options
+global_cf = jack_rc.load(cf, cf['global_rc']['val'])
+jack_checkopts.checkopts(cf, global_cf)
+cf.rupdate(global_cf, "global_rc")
+user_cf = jack_rc.load(cf, cf['user_rc']['val'])
+jack_checkopts.checkopts(cf, user_cf)
+cf.rupdate(user_cf, "user_rc")
+help, argv_cf = jack_argv.parse_argv(cf, sys.argv)
+jack_checkopts.checkopts(cf, argv_cf)
+cf.rupdate(argv_cf, "argv")
+if help:
+ jack_argv.show_usage(cf, long=help-1)
+ sys.exit(0)
+debug("global_cf: " + `global_cf`)
+debug("user_cf: " + `user_cf`)
+debug("argv_cf: " + `argv_cf`)
+
+jack_checkopts.consistency_check(cf)
+
+if cf['save_args']['val'] == 1:
+ count = jack_rc.save(cf['user_rc']['val'], cf)
+ info("%d options saved in %s" % (count, cf['user_rc']['val']))
+ sys.exit()
+
+ext = jack_targets.targets[jack_helpers.helpers[cf['_encoder']]['target']]['file_extension']
+
+### (1) search for a dir containing a toc-file or do the multi-mode
+toc_just_read = jack_prepare.find_workdir()
+os.environ["JACK_CUR_DIR"] = os.getcwd()
+os.environ["JACK_BASE_DIR"] = cf['_base_dir']
+# now we are set to go as we know we are in the right dir
+
+### (2) check toc (operation mode)
+if cf['_check_toc']:
+ jack_prepare.check_toc()
+ sys.exit(0)
+
+### (3a) read and interpret toc_file
+is_submittable, track1_offset = jack_prepare.read_toc_file()
+
+### (3b) make sure we have a freedb file
+if not os.path.exists(cf['_freedb_form_file']):
+ jack_freedb.freedb_template(jack_ripstuff.all_tracks)
+
+### (4a) filter out data tracks
+jack_prepare.filter_tracks(toc_just_read)
+
+### (4b) Parse tracks from argv, generate todo
+todo = jack_prepare.gen_todo()
+if len(todo) == 0:
+ error("nothing to do. bye.")
+
+### init status
+jack_status.init(todo)
+
+#XXX## (5) read progress info into status
+
+jack_ripstuff.all_tracks_orig = []
+for i in jack_ripstuff.all_tracks:
+ jack_ripstuff.all_tracks_orig.append(i[:])
+
+status = jack_prepare.init_status()
+
+### (6) update progress file at user's request (operation mode)
+if cf['_upd_progress']:
+ jack_prepare.update_progress(todo)
+ sys.exit(0)
+
+### (7) now read in the progress file
+status = jack_prepare.read_progress(status, todo)
+
+### (8) submit freedb data on user's request
+if cf['_freedb_submit'] or cf['_freedb_mailsubmit']:
+ jack_prepare.freedb_submit()
+ sys.exit(0)
+
+### (9) do query on start
+freedb_rename = 0
+if cf['_query_if_needed']:
+ if not os.path.exists(cf['_freedb_form_file'] + ".bak"):
+ cf['_query_on_start'] = 1
+if cf['_query_on_start']:
+ freedb_rename = jack_prepare.query_on_start()
+
+### (10) update freedb dbfile
+if cf['_update_freedb']:
+ if not jack_tag.track_names:
+ err, jack_tag.track_names, jack_tag.locale_names, freedb_rename, revision = jack_freedb.interpret_db_file(jack_ripstuff.all_tracks, cf['_freedb_form_file'], verb = 1, dirs = 0)
+ jack_freedb.freedb_template(jack_ripstuff.all_tracks, jack_tag.track_names, revision + 1)
+ jack_utils.ex_edit(cf['_freedb_form_file'])
+ info("now submit your changes if you like, using the option --submit (via http POST). Don't forget to activate your changes locally with -R")
+ sys.exit(0)
+
+### (11) undo renaming (operation mode)
+if cf['_undo_rename']:
+ jack_prepare.undo_rename(status, todo)
+ sys.exit(0)
+
+#### Reorder if told so
+if cf['_reorder']:
+ todo.sort(jack_utils.cmp_toc)
+ todo.reverse()
+
+#### check how much bytes we can burn
+if cf['space_from_argv']['history'][-1][0] == "argv":
+ space = jack_ripstuff.raw_space = cf['_space_from_argv']
+else:
+ space = jack_ripstuff.raw_space = jack_functions.df()
+
+#### check what is already there
+space, remove_q, wavs_todo, mp3s_todo, dae_queue, enc_queue = jack_prepare.what_todo(space, todo)
+
+if cf['_todo_exit']: # print what needs to be done, then exit
+ jack_prepare.print_todo(todo, wavs_todo, mp3s_todo)
+ sys.exit(0)
+
+# now mp3s_todo contains the tracks where the wavs only need to be coded and
+# wavs_todo lists those tracks which need to be dae'd end enc'd. The dae_queue
+# has been filled from wavs_todo (todo is superflous now). The main_loop
+# will handle the tracks in mp3s_todo.
+
+### make sure we have enough space to rip the whole thing
+jack_prepare.check_space(space, wavs_todo, mp3s_todo)
+
+cf['_max_load'] = cf['_max_load'] + cf['_encoders'] #XXX
+
+if not cf['_dont_work'] and dae_queue: # check if inserted cd matches toc.
+ jack_prepare.check_cd() # why? paranoia or needed? XXX
+ if cf['_rip_from_device']:
+ all_tracks_on_cd = jack_functions.gettoc(cf['_toc_prog'])
+ if not cf['_force'] and not jack_utils.cmp_toc_cd(jack_ripstuff.all_tracks_orig, all_tracks_on_cd, what=(NUM, LEN)):
+ error("you did not insert the right cd")
+
+### if we have work to do, we may have to remove some files first
+if remove_q:
+ jack_prepare.remove_files(remove_q)
+
+### bail out now if told so
+if cf['_dont_work']:
+ info("quitting now as requested.")
+ sys.exit(0)
+
+### install signal handlers
+signal.signal(signal.SIGTERM, jack_display.sig_handler)
+signal.signal(signal.SIGINT, jack_display.sig_handler)
+signal.signal(signal.SIGQUIT, jack_display.sig_handler)
+signal.signal(signal.SIGHUP, jack_display.sig_handler)
+
+
+ #\ /#
+#########> real work starts here <#############################################
+ #/ \#
+
+global_error = None
+if (wavs_todo or mp3s_todo):
+ jack_ripstuff.gen_printable_names(jack_tag.track_names, todo)
+ jack_term.init(cf['_terminal'], cf['_xtermset_enable'])
+ jack_display.init()
+ try:
+ jack_term.enable()
+ global_error = jack_main_loop.main_loop(mp3s_todo, wavs_todo, space, dae_queue, enc_queue, track1_offset)
+ except SystemExit:
+ jack_term.disable()
+ print jack_display.options_string
+ print "--- Last status: ---------------------------------------------------------------"
+ jack_status.print_status(form = 'short')
+ sys.exit(0)
+ except:
+ jack_term.disable()
+ warning("abnormal exit")
+ traceback.print_exc()
+ sys.exit(1)
+# Set the files we have processed but this may still be overwritten by
+# jack_tag.tag() called below.
+os.environ["JACK_JUST_ENCODED"] = "\n".join([x[NAME] + ext for x in mp3s_todo])
+os.environ["JACK_JUST_RIPPED"] = "\n".join([x[NAME] + ".wav" for x in wavs_todo])
+
+jack_term.disable()
+if cf['_query_when_ready']:
+ info("querying...")
+ if jack_freedb.freedb_query(jack_freedb.freedb_id(jack_ripstuff.all_tracks), jack_ripstuff.all_tracks, cf['_freedb_form_file']):
+ jack_display.exit()
+
+if cf['_query_when_ready'] or cf['_read_freedb_file'] or cf['_query_on_start']:
+ err, jack_tag.track_names, jack_tag.locale_names, freedb_rename, revision = jack_freedb.interpret_db_file(jack_ripstuff.all_tracks, cf['_freedb_form_file'], verb = 1, dirs = 1)
+ if err:
+ error("could not read freedb file")
+
+if jack_term.term_type == "curses":
+ if jack_display.options_string:
+ print jack_display.options_string
+ print "The final status was:"
+ jack_status.print_status(form = 'short')
+
+if global_error:
+ if cf['_exec_when_done']:
+ os.system(cf['_exec_err'])
+ error("aborting because of previous error(s) [%i]." % global_error)
+
+jack_tag.tag(freedb_rename)
+
+if jack_functions.progress_changed:
+ jack_functions.progress("all", "done", time.strftime("%b %2d %H:%M:%S", time.localtime(time.time())))
+
+if cf['_remove_files']:
+ print "cleaning up in", os.getcwd()
+ for i in [cf['_progress_file'], cf['_toc_file'], cf['_def_toc'], cf['_freedb_form_file'], cf['_freedb_form_file'] + ".bak"]:
+ if os.path.exists(i):
+ os.remove(i)
+
+if cf['_exec_when_done']:
+ os.system(cf['_exec_no_err'])
+
+jack_display.exit() # call the cleanup function & exit
+
+
+###############################################################################
+################################## ####################################
+################################## T H E ####################################
+################################## E N D ####################################
+################################## ####################################
+###############################################################################
diff --git a/jack.man b/jack.man
new file mode 100644
index 0000000..1c12ae6
--- /dev/null
+++ b/jack.man
@@ -0,0 +1,543 @@
+.\" Hey, EMACS: -*- nroff -*-
+.\" First parameter, NAME, should be all caps
+.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
+.\" other parameters are allowed: see man(7), man(1)
+.TH JACK 1 "November 22, 2004"
+.\" Please adjust this date whenever revising the manpage.
+.\"
+.\" Some roff macros, for reference:
+.\" .nh disable hyphenation
+.\" .hy enable hyphenation
+.\" .ad l left justify
+.\" .ad b justify to both left and right margins
+.\" .nf disable filling
+.\" .fi enable filling
+.\" .br insert line break
+.\" .sp <n> insert n+1 empty lines
+.\" for manpage-specific macros, see man(7)
+.SH NAME
+Jack \- rip and encode CDs with one command
+.SH SYNOPSIS
+.B jack
+.RI [ options ]
+.SH DESCRIPTION
+.B Jack
+transforms your audio-CDs to FLAC, MP3 or Ogg Vorbis files. It uses several
+helper programs in order to achieve things like ripping, encoding and
+ID3-tagging. Ripping is either done via
+.B cdparanoia
+(in which case the ripping status is displayed by Jack as well) or
+.B cdda2wav.
+Jack works with several encoders, namely
+.B oggenc, flac, lame, gogo, bladeenc, l3enc, mp3enc
+and
+.B xing.
+Any time during operation (and even when everything is finished and
+the original CD lost) you can let Jack look up the track names at
+.B freedb.org
+and rename the tracks accordingly. ID3-tagging of MP3s (or insertion
+of equivalent comment fields in Ogg Vorbis files) is performed
+as well.
+.PP
+If no freedb-lookup has been performed, Jack drops all files in a
+directory
+.BR ~/jack/jack-xxxxxxxx ,
+with
+.B xxxxxxxx
+representing the CD's CDDB/FreeDB disc ID.
+This directory is renamed by Jack when the appropriate information is known.
+.PP
+Most options like ripper, encoder, preferred FreeDB-Server, directory
+and MP3-filename format, etc. can be user defined by changing the
+defaults in
+.B /etc/jackrc
+or by saving them to
+.BR ~/.jack3rc .
+.PP
+While Jack is running, these keyboard commands are available:
+.RS
+.TP
+.BR q " or " Q
+quit
+.TP
+.BR p " or " P
+disable ripping (for example, if you need the CD drive)
+.TP
+.BR p " or " P " (again) or " c " or " C
+resume ripping
+.TP
+.BR e " or " E
+pause/continue all encoders
+.TP
+.BR r " or " R
+pause/continue all rippers.
+.TP
+.BR ?
+toggle the copyright/help box
+.RE
+.SH OPTIONS
+Different options need different data-types as arguments:
+.TP
+.B bool
+can be "yes" or "no", like in
+.B \-\-vbr=yes
+.TP
+.B string
+can be anything, like in
+.B \-\-rename\-fmt
+"%n.%t"
+.TP
+.B int
+an integer number, like in
+.B \-\-bitrate 192
+(or
+.B \-\-bitrate=192
+)
+.TP
+.B list
+multiple strings, delimited by the final ";".
+Example:
+.B \-\-guess\-toc file1.mp3 file2.mp3 ... fileN.mp3
+\\;
+.PP
+Jack understands the following options:
+.TP
+.B \-\-append-year=bool
+if known, append the year to dir in the format " (%y)"
+.TP
+.B \-b, \-\-bitrate int
+target bitrate (in kbit/s, default is 160).
+.TP
+.B \-\-char-filter string
+convert file names using a python method
+.TP
+.B \-\-charset string
+charset of filenames (defaults to your locale).
+.TP
+.B \-c, \-\-check-toc
+compare toc-file and cd-toc, then exit. Jack caches the TOC of a
+CD in a file ("jack.toc"). If you want to know if the inserted CD
+matches the toc-file in the current directory, use this option.
+.TP
+.B \-C, \-\-claim-dir
+rename directories even if they were was not created by Jack.
+.TP
+.B \-\-cont-failed-query
+continue without FreeDB data if query fails.
+.TP
+.B \-D, \-\-create-dirs
+tells Jack to create sub-directories in which Jack puts all the
+files for the current CD. If no FreeDB data is available, i.e.
+when not using
+.B -Q
+, these directories will be named "jack-xxxxxxxx"
+where "xxxxxxxx" stands for the CD's FreeDB ID. Otherwise
+dir_template (see above) will be used. This option is turned on
+by default.
+.TP
+.B \-\-device string
+The device-name of your cdrom-drive. The default is
+.B /dev/cdrom
+.TP
+.B \-\-dir-template string
+if directories are renamed, this is the format used (default "%a/%l")
+.TP
+.B \-d, \-\-dont-work
+don't do DAE, encoding, tagging or renaming. This may be useful if you only
+want to do a FreeDB query, e.g. while another jack is running.
+.TP
+.B \-\-edit-freedb
+open an editor to change the CDDB information which has been obtained
+previously (only useful with -Q).
+.TP
+.B \-\-encoder-name, -E string
+use which encoder (default "oggenc")
+.TP
+.B \-e, \-\-encoders int
+encode how many files in parallel. If you have a SMP machine or
+simply want to stress your system, you can have Jack encode
+several files at once.
+.TP
+.B \-x, \-\-exec
+run predefined command when finished.
+.TP
+.B \-\-extt-is-artist
+the artist is contained in the EXTT fields. The EXTT fields are lines in the
+FreeDB file which contain additional data for each track. As it's the
+submitting user's choice what to use them for, Jack can't determine by itself
+what they were intended for. You have to provide this information.
+.TP
+.B \-\-extt-is-title
+the track title is contained in the EXTT fields.
+.TP
+.B \-\-extt-is-comment
+a track comment is contained in the EXTT fields.
+.TP
+.B \-\-force
+do not ask. Like when deleting incomplete files.
+.TP
+.B \-f, \-\-from-tocfile string
+rip from a cdrdao created CD image on hd. The specified toc-file
+contains the name of the image file. Use
+.B \-F
+if jack can't find the image file.
+.TP
+.B \-F, \-\-from-image string
+read audio data from image file. Like
+.BR \-\-from-tocfile ,
+but the
+image itself is specified instead of the tocfile. If you
+do not have a toc-file (or don't specify a toc-file),
+the TOC is read from the CD itself.
+.TP
+.B \-g, \-\-guess-toc list
+make up a TOC from the MP3 file given in mp3_files. Format is
+.B track_01.mp3 ... track_nn.mp3 ;
+Note that the trailing "
+.B ;
+" is only necessary if you want to
+append more options to your command line.
+You can use it to do a FreeDB query based on
+MP3s alone - no need for the CD. Very useful if you have no idea
+which CD the MP3s are from. The MP3s must be given in the same
+order as they were on their CD. The generated TOC file is
+similar, but not identical to the TOC of the CD - do not submit
+these!
+.TP
+.B \-G, \-\-id3-genre string
+set ID3 genre. Use 'help' to get a list of all known genres. (You can also specify the ID3v1 genre as an int)
+.TP
+.B \-Y, \-\-id3-year int
+set ID3 year.
+.TP
+.B \-h, \-\-help
+Show summary of options.
+.TP
+.B \-k, \-\-keep-wavs
+do not delete WAVs after encoding them.
+.TP
+.B \-m, \-\-mail-submit
+submit FreeDB entry via e-mail. HTTP submission is preferred.
+You will have to enter the category of the CD.
+.TP
+.B \-l, \-\-max-load float
+only start new encoders if your system's load is below the specified value.
+/proc/loadavg must be readable by you for this to work.
+.TP
+.B \-\-multi-mode
+try to query FreeDB for all dirs in searchdirs which
+have no FreeDB data.
+.TP
+.B \-\-my-mail string
+your e-mail address, needed for FreeDB submissions.
+.TP
+.B \-n, \-\-nice int
+nice-level with which the encoders are started. Default is 12
+which shouldn't hurt your system much.
+.TP
+.B \-o, \-\-overwrite
+overwrite existing files, i.e. do not check if already
+ripped WAVs or an already encoded file seem to be OK. Use this if
+you
+.B know
+something went wrong last time. This is off by default.
+.TP
+.B \-O, \-\-only-dae
+only produce WAVs, implies
+.B \-\-keep-wavs.
+This is off by default.
+.TP
+.B \-\-otf=bool
+On-the-fly operation. Only on some encoders/rippers. Do not
+create WAVs, pipe ripper output through the encoder. Default is
+no as it's a torture for the CDROM drive.
+.TP
+.B \-o, \-\-overwrite=bool
+overwrite existing files.
+.\" .TP
+.\" .B \-\-playorder
+.\" use the FreeDB PLAYORDER field to limit the tracks to
+.\" rip (non-functional, sorry)
+.TP
+.B \-\-quality int
+vbr encoding quality. -1 is lowest, 10 highest (default 6). You can also specify a float.
+.TP
+.B \-q, \-\-query
+do FreeDB query when all is done. This is useful if Jack was previously
+run without a FreeDB query. If all tracks are done you don't even
+have to have a CD inserted as the TOC is cached by Jack. After
+having finished ripping and encoding, Jack will rename the files
+and tag them.
+.TP
+.B \-\-query\-if\-needed=bool
+like \-\-query-now, but only if FreeDB data hasn't been successfully
+queried before.
+.TP
+.B \-Q, \-\-query-now
+do FreeDB query when starting. Use this if you are connected to
+the Internet when starting Jack. Know that the query may need
+user-interaction. After having finished ripping and encoding, Jack
+will rename the files and tag them.
+.TP
+.B \-a, \-\-read-ahead int
+read how many WAVs in advance. At most read_ahead + num_encoders
+WAVs are ripped before a track has completely been encoded.
+Default is 99 which will read the whole CD, provided there is
+enough disk space.
+.TP
+.B \-\-remove-files
+have Jack remove its temp jack*-files.
+Be careful - don't delete them too early!
+.TP
+.B \-R, \-\-rename
+rename and tag files according to FreeDB file. On startup, Jack
+creates a blank FreeDB entry file (except if
+.B \-\-query-now
+is used,
+then the file is queried from your FreeDB server). If you have
+changed its contents (e.g. because the CD was unknown to FreeDB)
+and want to rename and tag your MP3s accordingly, use this option.
+Give all other needed options too, like
+.B \-t
+,
+.B \-E
+, ...
+.TP
+.B \-\-rename-dir=bool
+rename directory as well (default).
+.TP
+.B \-\-rename-fmt string
+format of normal files (default "%n - %t")
+.TP
+.B \-\-rename-fmt-va string
+format of Various Artists files (default "%n - %a - %t")
+.TP
+.B \-\-rename-num string
+format of the track number (%n, printf() style) used to rename the files (default "%02d")
+.TP
+.B \-r, \-\-reorder=bool
+optimize track-order for disk space. This can save you some peak
+disk space during the encoding process; this may make it possible
+to do a CD which would otherwise fail to be encoded.
+.TP
+.B \-\-replacement-chars list
+unusable chars are replaced by the corresponding list item (default "%").
+.TP
+.B \-\-ripper string
+which program to use for extracting the audio data (default "cdparanoia").
+.TP
+.B \-\-save
+save options to \fI~/.jack3rc\fP file and exit.
+.TP
+.B \-\-scan-dirs int
+Scan this many
+levels from the current working directory for a matching toc-file (0
+to disable, default 2).
+.TP
+.B \-\-search list
+add these directories to the list of directories searched when looking for the
+workdir (default ".").
+.TP
+.B \-\-server string
+which FreeDB server to use. Don't forget to set your HTTP proxy.
+Currently either "freedb" (default) or "freedb-de".
+.TP
+.B \-\-silent-mode=bool
+be quiet (no screen output).
+.TP
+.B \-s, \-\-space int
+forcibly set usable disk space, in bytes. This option lets you
+limit the disk space Jack uses, maybe you need it for something
+else? Be careful: if set too high or too low, ripping and encoding will
+probably fail. The default is to look how much is free and to use
+this value.
+.TP
+.B \-\-submit
+submit FreeDB entry via HTTP. You will have to enter the category
+of the CD.
+.TP
+.B \-S, \-\-swab=bool
+swap byte order from image file. As cdrdao momentarily only
+outputs "raw" .cdr files, you quite likely want to swap the
+byte order. Try this option if your WAVs and encoded files contain
+only noise.
+This is on by default as cdrdao currently generates .cdr files
+that are "wrong".
+.TP
+.B \-\-todo
+print what would be done and exit.
+.TP
+.B \-t, \-\-tracks string
+limit ripping and encoding to the specified tracks, use comma to
+separate tracks. Ranges are also possible; 5-9 is equivalent to
+5,6,7,8,9; 12- is like specifying track 12,...,last_track. The
+default is to process the whole CD.
+.TP
+.B \-u, \-\-undo-rename
+undo file renaming and exit. If you don't like how Jack renamed
+your files, use this option to restore the previous state.
+Several levels of undo are possible. Note
+that ID3 tags are not restored.
+.TP
+.B \-\-unusable-chars list
+characters which can't be used in filenames (default "/").
+.TP
+.B \-\-upd-progress
+have Jack re-create its temp files. Use this if you deleted them
+too early.
+.TP
+.B \-\-update-freedb, -U
+update the FreeDB info and exit.
+.TP
+.B \-\-usage\-win=bool
+show the help screen while running.
+.TP
+.B \-v, \-\-vbr=bool
+Generate variable bitrate files, only on encoders which support
+this. Default is no.
+.TP
+.B \-\-various=bool
+when parsing FreeDB data, Jack assumes that if the disc\'s artist
+is set to "Various" the track titles have the format
+"[artist] - [title]". If the disc title is set to something else
+and you still want the above behaviour, use
+.B \-\-various.
+.TP
+.B \-\-various-swap
+exchange artist and title, many FreeDB entries have them wrong.
+.TP
+.B \-\-wait=bool
+wait for key press before quitting.
+.TP
+.B \-w, \-\-workdir string
+where to create directories and put the files.
+.TP
+.B \-\-write\-id3v1=bool
+write a smart id3v1 tag to the encoded file.
+.TP
+.B \-\-write\-id3v2=bool
+write an id3v2 tag to the encoded file.
+.TP
+.B \-\-write-m3u
+create a playlist in .m3u format. This has bugs, don't rely on it.
+.SH EXAMPLES
+Insert a CD, fire up jack:
+.RS
+jack
+.RE
+.PP
+Now watch it work. It's fun for a while. After having finished, you have
+the following files on your HD: track_01.mp3, track_02.mp3, ...,
+track_nn.mp3 plus jack.toc, jack.freedb, jack.progress. The last three are
+used to store the state jack is in so it can resume work when interrupted.
+.PP
+Jack will create a directory called jack-xxxxxxxx for you, there it
+stores all the file for the CD whose id is xxxxxxxx. After a FreeDB query
+this directory is renamed to something human readable, like "Artist -
+Title".
+.PP
+When jack is interrupted, call it again using the same command line as
+before to resume work, in this case
+.RS
+jack
+.RE
+.PP
+Now let's try a FreeDB query:
+.RS
+jack -q
+.RE
+If the query is successful the files will be renamed to something more readable
+and will be tagged accordingly using ID3 or Vorbis tags. The file jack.freedb
+will contain the queried FreeDB entry, and the original file will be backed up
+as jack.freedb.bak.
+.PP
+You can use the
+.B \-\-rename\-fmt
+option in order to specify the format of the name which will be given to
+your audio tracks. A list of valid options can be found below. You may
+also want to specify a set of characters which are not usable and should
+be replaced. For example, on Unix systems the slash
+.B (/)
+should most certainly be replaced with something else. The VFAT filesystem
+also does not support double quotes
+.B (").
+In order to replace such characters, you can specify the options
+.B unusable_chars
+together with
+.B replacement_chars.
+For example,
+.RS
+jack -Q --rename-fmt "%n-%t" --unusable-chars A I \; --replacement-chars a i \;
+.RE
+will query the FreeDB server, rip and encode all tracks of the CD and save
+the files in a format which will contain the track number and the title.
+All occurances of the letters
+.B A
+and
+.B I
+will be replaced with their lower-case versions. These options can also be
+put in one's configuration file using the following format:
+.RS
+unusable_chars:[\(aq \(aq, \(aq/\(aq]
+.RE
+.RS
+replacement_chars:[\(aq_\(aq, \(aq_\(aq]
+.RE
+This will replace whitespace and slashes with underscores. If you want to
+convert all characters to lower-case, you don't have to manually specify
+all of them but can use the following option instead:
+.RS
+char_filter:.lower()
+.RE
+.PP
+All in one: query, rip, encode, cleanup:
+.RS
+jack -Q --remove-files
+.RE
+.PP
+Editing / normalizing / stripping the WAV files before encoding:
+.RS
+jack -O --remove-files ; gnoise *wav ; jack -g *wav ; jack
+.RE
+Just replace gnoise by the operation you'd like to perform.
+.SH ENVIRONMENT VARIABLES
+There are several environment variables which can be used in jack's exec
+hooks:
+.IP JACK_BASE_DIR
+lists jack's base directory in which files are stored.
+.IP JACK_CUR_DIR
+lists the current directory of jack in which files of the current album are
+put.
+.IP JACK_JUST_ENCODED
+lists all track names which have just been encoded.
+.IP JACK_JUST_RIPPED
+lists all track names which have just been ripped.
+.SH FORMAT STRINGS
+.IP %n
+Track number
+.IP %a
+Artist
+.IP %t
+Track title
+.IP %l
+Album title
+.IP %y
+Album release year
+.IP %g
+Album genre
+.SH FILES
+.IP \fI/etc/jackrc\fP
+Site-wide configuration file.
+.IP \fI~/.jack3rc\fP
+User-specific configuration file. Use the
+.B \-\-save
+option to save your configuration to this file.
+.SH AUTHOR
+Arne Zellentin <zarne@users.sf.net> is the author of Jack.
+.SH SEE ALSO
+.BR cdparanoia (1),
+.BR cdda2wav (1),
+.BR flac (1),
+.BR oggenc (1)
+and
+.BR lame (1)
diff --git a/jack_CDTime.py b/jack_CDTime.py
new file mode 100644
index 0000000..b5369b6
--- /dev/null
+++ b/jack_CDTime.py
@@ -0,0 +1,90 @@
+### jack_CDTime - various converters between data representation - part of
+### jack - extract audio from a CD and MP3ify it using 3rd party software
+### Copyright (C) 1999,2000 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import string, types
+
+CDDA_BLOCKS_PER_SECOND = 75
+
+def strtoblocks(str):
+ "convert mm:ss:ff to blocks"
+ str = string.split(str, ":")
+ blocks = string.atoi(str[2])
+ blocks = blocks + string.atoi(str[1]) * CDDA_BLOCKS_PER_SECOND
+ blocks = blocks + string.atoi(str[0]) * 60 * CDDA_BLOCKS_PER_SECOND
+ return blocks
+
+def blockstomsf(blocks):
+ "convert blocks to mm, ss, ff"
+ mm = blocks / 60 / CDDA_BLOCKS_PER_SECOND
+ blocks = blocks - mm * 60 * CDDA_BLOCKS_PER_SECOND
+ ss = blocks / CDDA_BLOCKS_PER_SECOND
+ ff = blocks % CDDA_BLOCKS_PER_SECOND
+ return mm, ss, ff, blocks
+
+B_MM, B_SS, B_FF = 0, 1, 2
+def msftostr(msf):
+ "convert msf format to readable string"
+ return "%02i" % msf[B_MM]+":"+"%02i" % msf[B_SS]+":"+"%02i" % msf[B_FF]
+
+
+class CDTime:
+ def __init__(self, any = None):
+ self.__dict__['blocks'] = 0
+ self.__dict__['mm'] = 0
+ self.__dict__['ss'] = 0
+ self.__dict__['ff'] = 0
+ self.__dict__['string'] = "00:00:00"
+ if any:
+ self.any = any
+
+ def __str__(self):
+ return self.string
+
+ def __setattr__(self, name, value):
+ self.__dict__[name] = value
+ if name == 'string' or name == 'any':
+ new_val = self.__dict__[name]
+ if type(new_val) == types.StringType and len(new_val) >=2:
+ if new_val[0] == new_val[-1]:
+ if new_val[0] in ('"', "'"):
+ new_val = new_val[1:-1]
+ try:
+ blocks = string.atoi(new_val)
+ except:
+ if type(new_val) == types.StringType:
+ blocks = strtoblocks(new_val)
+ elif type(new_val) == types.IntType:
+ blocks = new_val
+ else:
+ raise ValueError
+ self.ff = blocks
+ elif name == 'blocks':
+ self.__dict__['mm'] = 0
+ self.__dict__['ss'] = 0
+ self.ff == self.blocks
+ elif name == 'ff':
+ if self.ff >= CDDA_BLOCKS_PER_SECOND:
+ self.ss = self.ss + self.ff / CDDA_BLOCKS_PER_SECOND
+ self.__dict__['ff'] = self.ff % CDDA_BLOCKS_PER_SECOND
+ elif name == 'ss':
+ if self.ss >= 60:
+ self.mm = self.mm + self.ss / 60
+ self.__dict__['ss'] = self.ss % 60
+ self.__dict__['string'] = msftostr((self.mm, self.ss, self.ff,))
+ self.__dict__['blocks'] = strtoblocks(self.string)
+ self.__dict__['any'] = None
diff --git a/jack_TOC.py b/jack_TOC.py
new file mode 100644
index 0000000..0205cf8
--- /dev/null
+++ b/jack_TOC.py
@@ -0,0 +1,87 @@
+### jack_toc - class for CDDA TOCs - part ("module") of
+### jack - extract audio from a CD and MP3ify it using 3rd party software
+### Copyright (C) 1999,2000 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+class TOC:
+ def __init__(self):
+ self.data = []
+ self.image_file = None
+ self.end_pos = 0
+ self.in_need_of_image_name = []
+
+ def __len__(self):
+ return len(self.data)
+
+ def append(self, entry):
+
+## if we don't have a image_name specified, we'll take the first one available
+
+ if entry.image_name == "":
+ self.in_need_of_image_name.append(entry.number)
+ elif self.in_need_of_image_name:
+ for i in self.in_need_of_image_name:
+
+# FIXME: maybe we can't reference a track by it's number?
+
+ self.data[i].image_name = entry.image_name
+ self.in_need_of_image_name = []
+
+## if the entry has a pregap this needs to be added to the previous track and
+## substracted from the current one
+
+ if entry.pregap:
+ self.data[-1].length = self.data[-1].length + entry.pregap
+ self.end_pos = self.end_pos + entry.pregap
+ self.data.append(entry)
+
+ self.end_pos = self.end_pos + entry.length
+
+## update image_file
+
+ self.same_image()
+
+ def export(self):
+ "compatibility"
+ tracks = []
+ for i in self.data:
+ track = i.export()
+ tracks.append(track)
+ return tracks
+
+ def image_filenames(self):
+ "return list of all used filenames"
+ names = []
+ for i in self.data:
+ names.append(i.image_name)
+ return names
+
+ def same_image(self):
+ "check whether all image_files are set to the same file"
+ names = self.image_filenames()[1:]
+ if not names:
+ return 0
+ first = names[0]
+ same = 1
+ for i in names:
+ if not i == first:
+ same = 0
+ break
+ if same:
+ self.image_file = first
+ else:
+ self.image_file = None
+ return same
diff --git a/jack_TOCentry.py b/jack_TOCentry.py
new file mode 100644
index 0000000..8c07ff3
--- /dev/null
+++ b/jack_TOCentry.py
@@ -0,0 +1,71 @@
+### jack_TOCentry - class for CDDA TOCs - part ("module") of
+### jack - extract audio from a CD and MP3ify it using 3rd party software
+### Copyright (C) 1999,2000 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+entry_fields = ['type', 'copy', 'preemphasis', 'channels', 'media',
+ 'filename', 'start', 'length', 'pregap']
+compat_fields = ['number', 'length', 'start', 'copy', 'preemphasis', 'channels', 'rip', 'bitrate', 'rip_name']
+
+class TOCentry:
+
+ def __init__(self, raw_dict={}):
+ self.number = None
+ self.type = None
+ self.copy = None # means no
+ self.preemphasis = None # means no
+ self.channels = None
+ self.media = None # "image" or "cd"
+ self.image_name = None # only for image-reader: name of image
+ self.readable_name = None # name the file is renamed to
+ self.rip_name = None # name of file while ripping / encoding
+ self.pregap = 0
+ self.start = None
+ self.length = None
+ self.bitrate = None # compat?#XXX
+ self.rip = None # compat
+
+ if raw_dict: # for compatibility: allow to read old-style track info
+ num = 1
+ for i in compat_fields:
+ self.__dict__[i] = raw_dict[num]
+ num = num + 1
+
+ def export(self):
+ "compatibility"
+ track = []
+ for i in compat_fields:
+ track.append(self.__dict__[i])
+ return track
+
+ # intercept setting of attributes
+ def __setattr__(self, name, value):
+ if name == 'pregap' and value:
+ self.__dict__['start'] = self.start + (value - self.pregap)
+ self.__dict__['length'] = self.length - (value - self.pregap)
+ self.__dict__['pregap'] = value
+
+ else:
+ self.__dict__[name] = value
+
+ # for debugging purposes only.
+ def initialized(self):
+ ok = 1
+ for i in entry_fields:
+ if not self.__dict__[i]:
+ ok = 0
+ break
+ return ok
diff --git a/jack_argv.py b/jack_argv.py
new file mode 100644
index 0000000..a043e72
--- /dev/null
+++ b/jack_argv.py
@@ -0,0 +1,215 @@
+### jack_argv - argv parser and help printing -- part of
+### jack - extract audio from a CD and MP3ify it using 3rd party software
+### Copyright (C) 2002-2004 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import sys
+import types
+
+import jack_utils
+import jack_generic
+
+from jack_globals import *
+from jack_misc import safe_int
+
+def show_usage(cf, long=0):
+ l = []
+ for i in cf.keys():
+ if not long and not cf[i].has_key('help'):
+ continue
+ s = ""
+ if cf[i].has_key('usage'):
+ if not long and cf[i].has_key('vbr_only') and cf[i]['vbr_only'] != cf['_vbr']:
+ continue
+ if cf[i].has_key('long'):
+ s = " --%s" % cf[i]['long']
+ if cf[i].has_key('short'):
+ s = s + ", -%s" % cf[i]['short']
+ else:
+ error("internal error in show_usage")
+
+ x_char = " "
+ l.append([s, cf[i]['usage'] + jack_utils.yes(cf[i])])
+ max_len = 0
+ for i in l:
+ max_len = max(max_len, len(i[0]))
+
+ l.sort()
+ print "usage: jack [option]..."
+ for i in l:
+ jack_generic.indent(i[0] + " " * (max_len - len(i[0])), i[1])
+
+ if long:
+ print """
+While Jack is running, press q or Q to quit,
+ p or P to disable ripping (you need the CD drive)
+ p or P (again) or c or C to resume,
+ e or E to pause/continue all encoders and
+ r or R to pause/continue all rippers.
+"""
+ else:
+ print "These are the most common options. For a complete list, run jack --longhelp"
+
+def get_next(argv, i, extra_arg = None):
+ if extra_arg != None:
+ return i, extra_arg
+ elif argv[i].find("=") > 0:
+ return i, argv[i].split("=", 1)[1]
+ else:
+ i = i + 1
+ if len(argv) > i:
+ return i, argv[i]
+ else:
+ return i, None
+
+def istrue(x):
+ return x.upper() in ["Y", "YES", "1", "TRUE"]
+
+def parse_option(cf, argv, i, option, alt_arg, origin="argv"):
+ ty = cf[option]['type']
+ if ty == 'toggle':
+ if alt_arg:
+ return i, istrue(alt_arg)
+ else:
+ return i, not cf[option]['val']
+
+ if ty == types.FloatType:
+ i, data = get_next(argv, i, alt_arg)
+ if data != None:
+ try:
+ data = float(data)
+ return i, data
+ except:
+ return None, "option `%s' needs a float argument" % option
+ else:
+ return None, "Option `%s' needs exactly one argument" % option
+
+ if ty == types.IntType:
+ i, data = get_next(argv, i, alt_arg)
+ if data != None:
+ try:
+ data = int(data)
+ return i, data
+ except:
+ return None, "option `%s' needs an integer argument" % option
+
+ return i, safe_int(data, "option `%s' needs an integer argument" % option)
+ else:
+ return None, "Option `%s' needs exactly one argument" % option
+
+ if ty == types.StringType:
+ i, data = get_next(argv, i, alt_arg)
+ if data != None:
+ return i, data
+ else:
+ return None, "Option `%s' needs exactly one string argument" % option
+ if ty == types.ListType:
+ l = []
+ if origin == "argv":
+ while 1:
+ i, data = get_next(argv, i, alt_arg)
+ if data != None:
+ if data == ";":
+ break
+ l.append(data)
+ if alt_arg: # only one option in --opt=val form
+ break
+ else:
+ break
+
+ elif origin == "rcfile":
+ i, data = get_next(argv, i, alt_arg)
+ l = eval(data)
+ if l and type(l) == types.ListType:
+ return i, l
+ else:
+ return None, "option `%s' takes a non-empty list (which may be terminated by \";\")" % `option`
+ # default
+ return None, "unknown argument type for option `%s'." % option
+
+def parse_argv(cf, argv):
+ argv_cf = {}
+ allargs = {}
+ for i in cf.keys():
+ if cf[i].has_key('long'):
+ if len(cf[i]['long']) < 2 or allargs.has_key(cf[i]['long']):
+ print "Hey Arne, don't bullshit me!"
+ print cf[i]
+ sys.exit(1)
+ else:
+ allargs[cf[i]['long']] = i
+ if cf[i].has_key('short'):
+ if len(cf[i]['short']) != 1 or allargs.has_key(cf[i]['short']):
+ print "Hey Arne, don't bullshit me!"
+ print cf[i]
+ sys.exit(1)
+ else:
+ allargs[cf[i]['short']] = i
+ i = 1
+ help = 0
+ while i < len(argv):
+ if argv[i] in ("-h", "--help"):
+ help = 1
+ i = i + 1
+ continue
+
+ if argv[i] in ("--longhelp", "--long-help"):
+ help = 2
+ i = i + 1
+ continue
+
+ option = ""
+ tmp_option = tmp_arg = None
+
+ if argv[i].find("=") >= 2:
+ tmp_option, tmp_arg = argv[i].split("=", 1)
+ else:
+ tmp_option = argv[i]
+
+ if len(tmp_option) == 2 and tmp_option[0] == "-":
+ o = tmp_option[1]
+ if allargs.has_key(o):
+ option = allargs[o]
+
+ elif tmp_option == "--override":
+ i, option = get_next(argv, i, tmp_arg)
+ if option.find("=") > 0:
+ option, tmp_arg = option.split("=", 1)
+ if option == None:
+ print "--override takes two arguments: <VARIABLE> <VALUE>"
+ sys.exit(1)
+
+ elif len(tmp_option) > 2 and tmp_option[0:2] == "--":
+ o = tmp_option[2:]
+ if allargs.has_key(o):
+ option = allargs[o]
+
+ if option:
+ i, value = parse_option(cf, argv, i, option, tmp_arg)
+ if i == None:
+ error(value)
+ if not argv_cf.has_key(option):
+ argv_cf[option] = {}
+ argv_cf[option].update({'val': value})
+ else:
+ print "unknown option `%s'" % argv[i]
+ show_usage(cf)
+ sys.exit(1)
+ if not i:
+ break
+ i = i + 1
+ return help, argv_cf
+# end of parse_argv()
diff --git a/jack_checkopts.py b/jack_checkopts.py
new file mode 100755
index 0000000..79f3f00
--- /dev/null
+++ b/jack_checkopts.py
@@ -0,0 +1,205 @@
+### jack_checkopts: check the options for consistency, a module for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2004 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import signal
+import types
+import sys
+import os
+
+import jack_utils
+import jack_version
+import jack_plugins
+import jack_functions
+
+from jack_globals import *
+import jack_freedb
+import jack_helpers
+
+# special option handling
+def checkopts(cf, cf2):
+ if cf2.has_key('image_file'):
+ cf.rupdate({'rip_from_device': {'val': 0}, 'read_ahead':{'val': 1}}, "check")
+
+ if cf2.has_key('image_toc_file'):
+ cf.rupdate({'rip_from_device': {'val': 0}, 'read_ahead':{'val': 1}}, "check")
+
+ if cf2.has_key('space_from_argv'):
+ cf.rupdate({'space_set_from_argv': {'val': 1}}, "check")
+
+ if cf2.has_key('only_dae') and cf2['only_dae']['val']:
+ cf.rupdate({'encoders': {'val': 0}}, "check")
+
+ if cf2.has_key('query_when_ready') and cf2['query_when_ready']['val']:
+ cf.rupdate({'read_freedb_file': {'val': 1}, 'set_id3tag':{'val': 1}}, "check")
+
+ if cf2.has_key('query_on_start') and cf2['query_on_start']['val']:
+ cf.rupdate({'set_id3tag': {'val': 1}}, "check")
+
+ if cf2.has_key('cont_failed_query') and cf2['cont_failed_query']['val']:
+ cf.rupdate({'query_on_start': {'val': 1}, 'set_id3tag':{'val': 1}}, "check")
+
+ if cf2.has_key('create_dirs') and cf2['create_dirs']['val']:
+ cf.rupdate({'rename_dir': {'val': 1}}, "check")
+
+ if cf2.has_key('freedb_rename') and cf2['freedb_rename']['val']:
+ cf.rupdate({'read_freedb_file': {'val': 1}, 'set_id3tag':{'val': 1}}, "check")
+
+ if cf2.has_key('id3_genre_txt'):
+ genre = jack_functions.check_genre_txt(cf2['id3_genre_txt']['val'])
+ if genre != cf['_id3_genre']:
+ cf.rupdate({'id3_genre': {'val': genre}}, "check")
+ del genre
+
+ for i in cf2.keys():
+ if not cf.has_key(i):
+ error("unknown config item `%s'" % i)
+
+def consistency_check(cf):
+
+ # set plugins path and import freedb_server plugin
+ sys.path.extend(map(expand, cf['_plugin_path']))
+ jack_plugins.import_freedb_servers()
+
+ # check freedb server
+ if cf.has_key('freedb_server'):
+ if not jack_freedb.freedb_servers.has_key(cf['freedb_server']['val']):
+ error("unknown server, choose one: " + `jack_freedb.freedb_servers.keys()`)
+
+ # check dir_template and scan_dirs
+ if len(cf['_dir_template'].split(os.path.sep)) > cf['_scan_dirs']:
+ cf.rupdate({'scan_dirs': {'val': len(cf['_dir_template'].split(os.path.sep))}}, "check")
+ warning("dir-template consists of more sub-paths (%i) than scan-dirs (%i). Jack may not find the workdir next time he is run. (Auto-raised)" % (len(cf['_dir_template'].split(os.path.sep)), cf['_scan_dirs']))
+
+ # check for unsername
+ if cf['username']['val'] == None:
+ if os.environ.has_key('USER') and os.environ['USER'] != "":
+ cf['username']['val'] = os.environ['USER']
+ elif os.environ.has_key('LOGNAME') and os.environ['LOGNAME'] != "":
+ cf['username']['val'] = os.environ['LOGNAME']
+ else:
+ error("can't determine your username, please set it manually.")
+ debug("username is " + cf['_username'])
+
+ # check for hostname
+ if cf['hostname']['val'] == None:
+ cf['hostname']['val'] = os.uname()[1]
+ debug("hostname is " + cf['_hostname'])
+
+ # check for e-mail address
+ if len(jack_freedb.freedb_servers[cf['_freedb_server']]['my_mail']) <= 3 or jack_freedb.freedb_servers[cf['freedb_server']['val']]['my_mail'] == "default":
+ tmp_mail = jack_freedb.freedb_servers[cf['freedb_server']['val']]['my_mail']
+ tmp_mail2 = cf['_my_mail']
+ if len(cf['_my_mail']) <= 3 or cf['_my_mail'] == "default" or cf['_my_mail'].find("@") < 1:
+ env = os.getenv("EMAIL")
+ if env:
+ jack_freedb.freedb_servers[cf['freedb_server']['val']]['my_mail'] = env
+ else:
+ jack_freedb.freedb_servers[cf['freedb_server']['val']]['my_mail'] = cf['username']['val'] + "@" + cf['hostname']['val']
+ if len(cf['my_mail']['history']) > 1:
+ warning("illegal mail address changed to " + jack_freedb.freedb_servers[cf['freedb_server']['val']]['my_mail'])
+ else:
+ jack_freedb.freedb_servers[cf['freedb_server']['val']]['my_mail'] = cf['_my_mail']
+ debug("mail is " + jack_freedb.freedb_servers[cf['freedb_server']['val']]['my_mail'] + ", was " + tmp_mail + " / " + tmp_mail2)
+ del tmp_mail, tmp_mail2
+
+ #if cf.has_key('charset'):
+ # if not cf['char_filter']['val']:
+ # warning("charset has no effect without a char_filter")
+ # this is not true, the ogg tag uses this.
+
+ if len(cf['replacement_chars']['val']) == 0:
+ cf.rupdate({'replacement_chars': {'val': ["",]}}, "check")
+
+ # stretch replacement_chars
+ if len(cf['_unusable_chars']) > len(cf['_replacement_chars']):
+ u, r = cf['_unusable_chars'], cf['_replacement_chars']
+ while len(u) > len(r):
+ if type(r) == types.ListType:
+ r.append(r[-1])
+ elif type(r) == types.StringType:
+ r = r + r[-1]
+ else:
+ error("unsupported type: " + `type(cf['replacement_chars']['val'][-1])`)
+ cf.rupdate({'replacement_chars': {'val': r}}, "check")
+ del u, r
+
+ if cf['silent_mode']['val']:
+ cf['terminal']['val'] = "dumb"
+ cf['xtermset_enable']['val'] = 0
+ out_f = open(cf['_out_file'], "a")
+ err_f = open(cf['_err_file'], "a")
+ os.dup2(out_f.fileno(), STDOUT_FILENO)
+ out_f.close()
+ os.dup2(err_f.fileno(), STDERR_FILENO)
+ err_f.close()
+ signal.signal(signal.SIGTTOU, signal.SIG_IGN)
+
+ # load plugins, compile stuff
+ jack_helpers.init()
+
+ if not jack_helpers.helpers.has_key(cf['_encoder']) or jack_helpers.helpers[cf['_encoder']]['type'] != "encoder":
+ dummy = []
+ for i in jack_helpers.helpers.keys():
+ if jack_helpers.helpers[i]['type'] == "encoder":
+ dummy.append(i)
+ error("Invalid encoder, choose one of " + `dummy`)
+
+ if not jack_helpers.helpers.has_key(cf['_ripper']) or jack_helpers.helpers[cf['_ripper']]['type'] != "ripper":
+ dummy = []
+ for i in jack_helpers.helpers.keys():
+ if jack_helpers.helpers[i]['type'] == "ripper":
+ dummy.append(i)
+ error("Invalid ripper, choose one of " + `dummy`)
+
+ if (cf['vbr_quality']['val'] > 10) or (cf['vbr_quality']['val'] < -1):
+ error("invalid vbr quality, must be between -1 and 10")
+
+ # check for option conflicts:
+ if cf['_otf'] and cf['_only_dae']:
+ warning("disabling on-the-fly operation because we're doing DAE only")
+ cf.rupdate({'otf': {'val': 0}}, "check")
+
+ if cf['_otf'] and cf['_keep_wavs']:
+ warning("disabling on-the-fly operation because we want to keep the wavs")
+ cf.rupdate({'otf': {'val': 0}}, "check")
+
+ if cf['_otf'] and cf['_image_file']:
+ warning("disabling on-the-fly operation as we're reading from image.")
+ cf.rupdate({'otf': {'val': 0}}, "check")
+
+ if cf['_otf']:
+ for i in (cf['_ripper'], cf['_encoder']):
+ if not jack_helpers.helpers[i].has_key(('vbr-' * cf['_vbr'] * (i == cf['_encoder'])) + 'otf-cmd'):
+ error("can't do on-the-fly because " + jack_helpers.helpers[i]['type'] + " " + i + " doesn't support it.")
+
+ if cf['_vbr'] and not jack_helpers.helpers[cf['_encoder']].has_key('vbr-cmd'):
+ warning("disabling VBR because " + cf['_encoder'] + " doesn't support it.")
+ cf.rupdate({'vbr': {'val': 0}}, "check")
+
+ if not cf['_vbr'] and not jack_helpers.helpers[cf['_encoder']].has_key('cmd'):
+ error("can't do CBR because " + cf['encoder']['val'] + " doesn't support it. Use -v")
+
+ if cf['_ripper'] == "cdparanoia" and cf['_sloppy']:
+ jack_helpers.helpers['cdparanoia']['cmd'] = replace(jack_helpers.helpers['cdparanoia']['cmd'], "--abort-on-skip", "")
+ jack_helpers.helpers['cdparanoia']['otf-cmd'] = replace(jack_helpers.helpers['cdparanoia']['otf-cmd'], "--abort-on-skip", "")
+
+ if cf['_query_on_start'] and cf['_query_when_ready']:
+ error("it doesn't make sense to query now _and_ when finished.")
+
+ if cf['_dont_work'] and cf['_query_when_ready']:
+ warning("you want to use --query-now / -Q instead of --query / -q")
diff --git a/jack_children.py b/jack_children.py
new file mode 100755
index 0000000..7ef9082
--- /dev/null
+++ b/jack_children.py
@@ -0,0 +1,20 @@
+### jack_children: subprocess info for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2002 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+# subprocess info
+children = []
diff --git a/jack_config.py b/jack_config.py
new file mode 100644
index 0000000..b09df36
--- /dev/null
+++ b/jack_config.py
@@ -0,0 +1,780 @@
+# -*- coding: iso-8859-15 -*-
+### jack_config.py: default config settings for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 2002-2004 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import types
+import os
+import string
+import jack_misc
+import locale
+
+import jack_version
+from jack_globals import *
+
+# this must be filled manually (done in main)
+
+# config space with attributes
+
+cf = jack_misc.dict2({
+ ### prefs ###
+ 'debug': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'help': 1,
+ 'usage': "show debug information",
+ 'long': 'AUTO',
+ },
+ 'debug_write': {
+ 'type': 'toggle',
+ 'val': 0,
+ #'usage': "write debug information to a file",
+ 'long': 'AUTO',
+ },
+ 'plugin_path': {
+ 'type': types.ListType,
+ 'val': ["~/.jack_plugins",],
+ 'usage': "directories in which jack plugins are searched for",
+ 'long': 'AUTO',
+ },
+ 'ripper': {
+ 'type': types.StringType,
+ 'val': "cdparanoia",
+ 'doc': "use which program to rip: cdparanoia, tosha, cdda2wav, dagrab (untested)",
+ 'usage': "which program to use for extracting the audio data",
+ 'long': 'AUTO',
+ },
+ 'cd_device': {
+ 'type': types.StringType,
+ 'val': "/dev/cdrom",
+ 'usage': "use which device for ripping",
+ 'long': 'device',
+ },
+ 'gen_device': {
+ 'type': types.StringType,
+ 'val': None,
+ 'doc': "cdda2wav may need the scsi generic device",
+ 'long': 'AUTO',
+ },
+ 'encoder': {
+ 'type': types.StringType,
+ 'val': "oggenc",
+ 'doc': "this is a symbolic name (see helpers), NOT the executable's name",
+ 'usage': "use which encoder",
+ 'short': 'E',
+ 'long': 'encoder-name',
+ },
+ 'vbr': {
+ 'type': 'toggle',
+ 'val': 1,
+ 'help': 1,
+ 'doc': "use variable bitrate for encoders which support it",
+ 'usage': "generate variable bitrate files",
+ 'short': 'v',
+ 'long': 'AUTO',
+ },
+ 'vbr_quality': {
+ 'type': types.FloatType,
+ 'val': 6,
+ 'help': 1,
+ 'vbr_only': 1, # only show in --help if vbr is on
+ 'usage': "vbr encoding quality. -1 is lowest, 10 highest.",
+ 'long': 'quality',
+ },
+ 'bitrate': {
+ 'type': types.IntType,
+ 'val': 160,
+ 'help': 1,
+ 'vbr_only': 0,
+ 'doc': "default bitrate",
+ 'usage': "target bitrate in kbit/s",
+ 'short': 'b',
+ 'long': 'AUTO',
+ },
+ 'freedb_server': {
+ 'type': types.StringType,
+ 'val': "freedb",
+ 'doc': "your freedb server, see freedb_servers",
+ 'usage': "use which freedb server",
+ 'long': 'server',
+ },
+ 'disable_http_proxy': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "XXX todo!!! disable default proxy (environment variable \"http_proxy\") for freedb queries",
+ 'long': 'AUTO',
+ },
+ 'my_mail': {
+ 'type': types.StringType,
+ 'val': "@",
+ 'usage': "your e-mail address, needed for freedb submissions",
+ 'long': 'AUTO',
+ },
+ 'rename_fmt': {
+ 'type': types.StringType,
+ 'val': "%a - %l - %n - %t",
+ 'usage': "format of normal files",
+ 'long': 'AUTO',
+ 'doc': """specify how the resulting files are named:
+ %n: track number
+ %a: artist
+ %t: track title
+ %l: album title
+ %y: album release year - individual track years are unsupported
+ %g: album genre - individual track genres are unsupported""",
+ },
+ 'rename_fmt_va': {
+ 'type': types.StringType,
+ 'val': "%l - %n - %a - %t",
+ 'usage': "format of Various Artists files",
+ 'long': 'AUTO',
+ 'doc': """specify how the resulting files are named:
+ %n: track number
+ %a: artist
+ %t: track title
+ %l: album title
+ %y: album release year - individual track years are unsupported
+ %g: album genre - individual track genres are unsupported""",
+ },
+ 'rename_num': {
+ 'type': types.StringType,
+ 'val': "%02d",
+ 'long': 'AUTO',
+ 'usage': "track number format for %n, printf() style",
+ },
+ 'rename_dir': {
+ 'type': 'toggle',
+ 'val': 1,
+ 'usage': "rename directory as well",
+ 'long': 'AUTO',
+ },
+ 'append_year': {
+ 'type': types.StringType,
+ 'val': " (%y)",
+ 'usage': "if known, append the year to dir",
+ 'long': 'AUTO',
+ },
+ 'dir_template': {
+ 'type': types.StringType,
+ 'val': "%a/%l",
+ 'usage': "if directories are renamed, this is the format used",
+ 'doc': """specify how the resulting files are named:
+ %a: artist
+ %l: album title
+ %g: album genre - individual track genres are unsupported""",
+ 'long': 'AUTO',
+ },
+ 'char_filter': {
+ 'type': types.StringType,
+ 'val': "",
+ 'usage': "convert file names using a python method",
+ 'doc': r"""an example which converts to lowercase, even with non-ascii charsets: ".lower()" """,
+ 'long': 'AUTO',
+ },
+ 'charset': {
+ 'type': types.StringType,
+ 'val': locale.getpreferredencoding(),
+ 'usage': 'charset of filenames',
+ 'doc': "examples: latin-1, utf-8, ...",
+ 'long': 'AUTO',
+ },
+ 'unusable_chars': {
+ 'type': types.ListType,
+ 'val': ["/", "\r"],
+ 'usage': "characters which can't be used in filenames",
+ 'doc': """
+put chars which can't be used in filenames here and their replacements
+in replacement_chars.
+
+example 1: replace all " " by "_":
+unusable_chars = " "
+replacement_chars = "_"
+
+example 2: replace umlauts by an alternate representation and kill some
+ special characters:
+unusable_chars = "äöüÄÖÜß?*^()[]{}"
+replacement_chars = ["ae", "oe", "ue", "Ae", "Oe", "Ue", "ss", ""]""",
+ 'long': 'AUTO',
+ },
+ 'replacement_chars': {
+ 'type': types.ListType,
+ 'val': ["%", ""],
+ 'doc': "this is stretched to match unusable_chars' length using the last char as fill",
+ 'usage': "unusable chars are replaced by the corresponding list item",
+ 'long': 'AUTO',
+ },
+ 'show_time': {
+ 'type': 'toggle',
+ 'val': 1,
+ 'doc': "Display the track length in the status screen",
+ },
+ 'show_names': {
+ 'type': 'toggle',
+ 'val': 1,
+ 'doc': "XXX todo: auto id enough term width -- display freedb track names instead if \"track_01\", ... This will not fit in a 80x24 terminal.",
+ },
+ 'scan_dirs': {
+ 'type': types.IntType,
+ 'val': 2,
+ 'usage': "scan in cwd n dir levels deep, e.g. 0 to disable",
+ 'long': 'AUTO',
+ },
+ 'searchdirs': {
+ 'type': types.ListType,
+ 'val': [os.curdir],
+ 'usage': "search which directories",
+ 'long': 'search',
+ },
+ 'base_dir': {
+ 'type': types.StringType,
+ 'val': os.curdir,
+ 'usage': "where to create directories and put the files",
+ 'long': 'workdir',
+ 'short': 'w',
+ },
+ 'update_interval': {
+ 'type': types.FloatType,
+ 'val': 1.0,
+ 'doc': "update status screen every ... seconds",
+ },
+ 'max_load': {
+ 'type': types.FloatType,
+ 'val': 10.0,
+ 'usage': "only start new encoders if load < max_load",
+ 'long': 'AUTO',
+ },
+ 'xtermset_enable': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'doc': "leave disabled if you don't have xtermset installed",
+ },
+ 'restore_xterm_width': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'doc': "XXX not implemented yet! reset xterm width when exiting",
+ },
+ 'terminal': {
+ 'type': types.StringType,
+ 'val': "auto",
+ 'doc': "use what kind of terminal",
+ },
+ 'default_width': {
+ 'type': types.IntType,
+ 'val': 80,
+ 'doc': "XXX unused! your xterm's width (autodetected with curses)",
+ },
+ 'default_height': {
+ 'type': types.IntType,
+ 'val': 24,
+ 'doc': "XXX unused! your xterm's height (autodetected with curses)",
+ },
+ 'usage_win': {
+ 'type': 'toggle',
+ 'val': 1,
+ 'usage': "show the help screen while running",
+ 'long': "AUTO",
+ },
+ 'keep_free': {
+ 'type': types.IntType,
+ 'val': 5*2**20,
+ 'doc': "suspend if less than keep_free bytes are free. Don't set this to zero as encoded file size prediction is always a bit below actual sizes => we might need some extra space.",
+ },
+ 'encoders': {
+ 'type': types.IntType,
+ 'val': 1,
+ 'usage': "encode how many files in parallel",
+ 'short': 'e',
+ 'long': 'AUTO',
+ },
+ 'otf': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "on-the-fly encoding *experimental*",
+ 'long': 'AUTO',
+ },
+ 'create_dirs': {
+ 'type': 'toggle',
+ 'val': 1,
+ 'usage': "create subdir for files",
+ 'short': 'D',
+ 'long': 'AUTO',
+ },
+ 'reorder': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "reorder tracks to save space while encoding",
+ 'short': 'r',
+ 'long': 'AUTO',
+ },
+ 'keep_wavs': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "do not delete WAVs after encoding them",
+ 'short': 'k',
+ 'long': 'AUTO',
+ },
+ 'only_dae': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "only produce WAVs, implies --keep_wavs",
+ 'short': 'O',
+ 'long': 'AUTO',
+ },
+ 'read_ahead': {
+ 'type': types.IntType,
+ 'val': 99,
+ 'usage': "read how many WAVs in advance",
+ 'short': 'a',
+ 'long': 'AUTO',
+ },
+ 'nice_value': {
+ 'type': types.IntType,
+ 'val': 12,
+ 'usage': "nice-level of encoders",
+ 'short': 'n',
+ 'long': 'nice',
+ },
+ 'overwrite': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "overwrite existing files",
+ 'short': 'o',
+ 'long': 'AUTO',
+ },
+ 'remove_files': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "remove jack.* file when done",
+ 'long': 'AUTO',
+ },
+ 'silent_mode': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "be quiet (no screen output)",
+ 'long': 'AUTO',
+ },
+ 'exec_when_done': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "run predefined command when finished",
+ 'long': 'exec',
+ 'short': 'x',
+ },
+ 'exec_rip_done': {
+ 'type': types.StringType,
+ 'val': "eject /dev/cdrom",
+ 'doc': "example: eject the CD when ripping is finished",
+ },
+ 'exec_no_err': {
+ 'type': types.StringType,
+ 'val': "play /usr/local/audio/allok.wav",
+ 'doc': "example: play sound when finished",
+ },
+ 'exec_err': {
+ 'type': types.StringType,
+ 'val': "play /usr/local/audio/error.wav",
+ 'doc': "example: this is played when an error occured",
+ },
+ 'freedb_dir': {
+ 'type': types.StringType,
+ 'val': "",
+ 'doc': "change this to something like \"/var/spool/freedb\" and all queries will be done in this (local) directory; failed local queries will be done via network",
+ },
+ 'freedb_pedantic': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'doc': "don't be pedantic when parsing freedb data, e.g. the ambigous (various artists) TTITLE \"The Artist - Track a Title - Cool Remix\" is split at the first possible separator.",
+ },
+ ### prefs0 ###
+ 'force': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "don't ask.",
+ 'long': 'AUTO',
+ },
+ 'recheck_space': {
+ 'type': 'toggle',
+ 'val': 1,
+ 'doc': "yes we want to react to disk space dropping.",
+ },
+ 'swap_byteorder': {
+ 'type': 'toggle',
+ 'val': 1,
+ 'usage': "swap byteorder when reading from image",
+ 'long': 'swab',
+ 'short': 'S',
+ },
+ 'todo_exit': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "print what would be done and exit",
+ 'long': 'todo',
+ },
+ 'space_from_argv': {
+ 'type': types.IntType,
+ 'val': 0,
+ 'usage': "force usable disk space, in bytes",
+ 'long': 'space',
+ 'short': 's',
+ },
+ 'check_toc': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "compare toc-file and cd-toc, then exit",
+ 'long': 'AUTO',
+ },
+ 'undo_rename': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "undo the last file renaming and exit",
+ 'long': 'AUTO',
+ 'short': 'u',
+ },
+ 'dont_work': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "don't do DAE, encoding, renaming or tagging",
+ 'long': 'AUTO',
+ 'short': 'd',
+ },
+ 'update_freedb': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "update the freedb info and exit",
+ 'long': 'AUTO',
+ 'short': 'U',
+ },
+ 'sloppy': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'doc': "XXX",
+ 'long': 'I-swear-I\'ll-never-give-these-files-to-anyone,-including-hot-babes-TM',
+ },
+ 'tracks': {
+ 'type': types.StringType,
+ 'val': "",
+ 'save': 0,
+ 'usage': "which tracks to process (e.g. 1, 3, 5-9, 12-)",
+ 'long': 'tracks',
+ 'short': 't',
+ },
+ 'name': {
+ 'type': types.StringType,
+ 'val': "track_%02d",
+ 'doc': "filename template (before renaming)",
+ },
+ 'rippers': {
+ 'type': types.IntType,
+ 'val': 1,
+ 'doc': "not implemented: rip in parallel",
+ },
+ 'toc_prog': {
+ 'type': types.StringType,
+ 'val': "CDDB.py",
+ 'doc': "use which helper program to read cd's toc",
+ },
+ ### prefs0 -- FREEDB stuff ###
+ 'query_on_start': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'help': 1,
+ 'usage': "do freedb query when starting",
+ 'long': 'query-now',
+ 'short': 'Q',
+ },
+ 'query_if_needed': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'help': 1,
+ 'usage': "query freedb when starting if not queried already",
+ 'long': 'AUTO',
+ },
+ 'query_when_ready': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'help': 1,
+ 'usage': "do freedb query when all is done",
+ 'long': 'query',
+ 'short': 'q',
+ },
+ 'cont_failed_query': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "continue without freedb data if query fails",
+ 'long': 'AUTO',
+ },
+ 'edit_freedb': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "edit CDDB information before using it",
+ 'long': 'AUTO',
+ },
+ 'various': {
+ 'type': 'toggle',
+ 'val': None,
+ 'save': 0,
+ 'usage': "assume CD has various artists",
+ 'long': 'AUTO',
+ },
+ 'various_swap': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "exchange artist and title",
+ 'long': 'AUTO',
+ },
+ 'extt_is_artist': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "extt contains artist",
+ 'long': 'AUTO',
+ },
+ 'extt_is_title': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "extt contains track title",
+ 'long': 'AUTO',
+ },
+ 'extt_is_comment': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "extt contains track comment",
+ 'long': 'AUTO',
+ },
+ 'freedb_submit': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "http-submit freedb file to server and exit",
+ 'long': 'submit',
+ },
+ 'freedb_mailsubmit': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "submit by e-mail - needs sendmail",
+ 'long': 'mail-submit',
+ 'short': 'm',
+ },
+ 'read_freedb_file': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'doc': "read freedb file",
+ },
+ 'freedb_rename': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'help': 1,
+ 'save': 0,
+ 'usage': "rename according to freedb file, eg. after editing it",
+ 'long': 'rename',
+ 'short': 'R',
+ },
+ 'set_id3tag': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'doc': "set id3 tag info",
+ },
+ 'id3_genre': {
+ 'type': types.IntType,
+ 'val': -1,
+ 'save': 0,
+ 'doc': "set ID3 genre (empty=don't set, help=list)",
+ },
+ 'id3_year': {
+ 'type': types.IntType,
+ 'val': -1,
+ 'save': 0,
+ 'usage': "set ID3 year (0=don't set)",
+ 'long': 'AUTO',
+ 'short': 'Y',
+ },
+ 'username': {
+ 'type': types.StringType,
+ 'val': None,
+ 'doc': "required for freedb query",
+ },
+ 'hostname': {
+ 'type': types.StringType,
+ 'val': None,
+ 'doc': "required for freedb query",
+ },
+ 'image_toc_file': {
+ 'type': types.StringType,
+ 'val': None,
+ 'save': 0,
+ 'usage': "read another toc file which may point to an image-file",
+ 'long': 'from-tocfile',
+ 'short': 'f',
+ },
+ 'image_file': {
+ 'type': types.StringType,
+ 'val': None,
+ 'save': 0,
+ 'usage': "read audio from an image file",
+ 'long': 'from-image',
+ 'short': 'F',
+ },
+ 'rip_from_device': {
+ 'type': 'toggle',
+ 'val': 1,
+ 'save': 0,
+ 'doc': "rip from physical device, not from image_file",
+ },
+ 'toc_file': {
+ 'type': types.StringType,
+ 'val': jack_version.prog_name + ".toc",
+ 'save': 0,
+ 'doc': "the toc file which is actually used",
+ },
+ 'def_toc': {
+ 'type': types.StringType,
+ 'val': jack_version.prog_name + ".toc",
+ 'doc': "the default name of the toc file",
+ },
+ 'freedb_form_file': {
+ 'type': types.StringType,
+ 'val': jack_version.prog_name + ".freedb",
+ 'doc': "name of submission template",
+ },
+ 'out_file': {
+ 'type': types.StringType,
+ 'val': jack_version.prog_name + ".out",
+ 'doc': "in silent-mode, stdout goes here",
+ },
+ 'err_file': {
+ 'type': types.StringType,
+ 'val': jack_version.prog_name + ".err",
+ 'doc': "in silent-mode, stderr here",
+ },
+ 'progress_file': {
+ 'type': types.StringType,
+ 'val': jack_version.prog_name + ".progress",
+ 'doc': "subprocess output is cached here",
+ },
+ 'progr_sep': {
+ 'type': types.StringType,
+ 'val': "/|\\",
+ 'doc': "field separator in progress_file",
+ },
+ 'guess_mp3s': {
+ 'type': types.ListType,
+ 'val': [],
+ 'save': 0,
+ 'usage': "guess TOC from files (until terminating \";\")",
+ 'long': 'guess-toc',
+ 'short': 'g',
+ },
+ 'upd_progress': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "re-generate progress file if \"lost\"",
+ 'long': 'AUTO',
+ },
+ 'multi_mode': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "try to query freedb for all dirs in searchdirs which have no freedb data",
+ 'long': 'AUTO',
+ },
+ 'claim_dir': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "rename the dir even if it was not created by jack",
+ 'long': 'AUTO',
+ 'short': 'C',
+ },
+ 'wait_on_quit': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "wait for key press before quitting",
+ 'long': 'wait',
+ },
+ 'id3_genre_txt': {
+ 'type': types.StringType,
+ 'val': None,
+ 'save': 0,
+ 'usage': "set ID3 genre (empty=don't set, help=list)",
+ 'long': 'id3-genre',
+ 'short': 'G',
+ },
+ 'save_args': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'save': 0,
+ 'usage': "save options to rc file and exit",
+ 'long': 'save',
+ },
+ 'global_rc': {
+ 'type': types.StringType,
+ 'val': "/etc/jackrc",
+ 'save': 0,
+ 'doc': "system-wide config file",
+ },
+ 'user_rc': {
+ 'type': types.StringType,
+ 'val': "~/.jack3rc",
+ 'save': 0,
+ 'doc': "user config file",
+ },
+ 'write_m3u': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "create a playlist in .m3u format",
+ 'long': 'AUTO',
+ },
+ 'write_id3v1': {
+ 'type': 'toggle',
+ 'val': 1,
+ 'usage': "write a smart id3v1 tag to the encoded file",
+ 'long': 'AUTO',
+ },
+ 'write_id3v2': {
+ 'type': 'toggle',
+ 'val': 1,
+ 'usage': "write an id3v2 tag to the encoded file",
+ 'long': 'AUTO',
+ },
+ 'playorder': {
+ 'type': 'toggle',
+ 'val': 0,
+ 'usage': "use the freedb PLAYORDER field to limit the tracks to rip (non-functional, sorry)",
+ 'long': 'AUTO',
+ },
+ })
+
+for i in cf.keys():
+ # expand long options
+ if cf[i].has_key('long') and cf[i]['long'] == "AUTO":
+ cf[i]['long'] = string.replace(i, "_", "-")
+ # init history
+ cf[i]['history'] = [ ["config", cf[i]['val'],],]
diff --git a/jack_constants.py b/jack_constants.py
new file mode 100644
index 0000000..104cc07
--- /dev/null
+++ b/jack_constants.py
@@ -0,0 +1,37 @@
+### jack_constants
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 2002 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+# track representation format
+# LEN and START is in cdda-blocks:
+# [ track#, len, start, copy, pre, ch, unused, bitrate, filename ]
+fields = ["NUM", "LEN", "START", "COPY", "PRE", "CH", "RIP", "RATE", "NAME"]
+NUM, LEN, START, COPY, PRE, CH, RIP, RATE, NAME = 0,1,2,3,4,5,6,7,8
+
+# jack_functions.tracksize() return list format
+ENC, WAV, BOTH, PEAK, AT, CDR, BLOCKS = 0,1,2,3,4,5,6
+
+# more constants
+CDDA_BLOCKSIZE = 2352
+CDDA_BLOCKS_PER_SECOND = 75
+MSF_OFFSET = 150
+CHILD = 0
+CDDA_MAXTRACKS = 100
+STDIN_FILENO = 0
+STDOUT_FILENO = 1
+STDERR_FILENO = 2
+
diff --git a/jack_display.py b/jack_display.py
new file mode 100755
index 0000000..383cf7d
--- /dev/null
+++ b/jack_display.py
@@ -0,0 +1,121 @@
+### jack_display: screen presentation module for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2003 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import termios
+import sys
+import os
+import signal
+
+import jack_ripstuff
+import jack_term
+import jack_children
+import jack_freedb
+import jack_functions
+import jack_globals
+import jack_tag
+
+from jack_globals import *
+
+global_total = None
+options_string = None
+special_line = None
+bottom_line = None
+discname = None
+
+# terminal attributes
+old_tc = None
+
+smile = " :-)"
+
+def init():
+ global global_total
+ global options_string
+ global discname
+ global old_tc
+
+ global_total = jack_functions.tracksize(jack_ripstuff.all_tracks_todo_sorted)[jack_functions.BLOCKS]
+
+ options_string = "Options:" \
+ + (" bitrate=%i" % cf['_bitrate']) * (not cf['_vbr']) + " vbr" * cf['_vbr'] \
+ + " reorder" * cf['_reorder'] \
+ + " read-ahead=" + `cf['_read_ahead']` \
+ + " keep-wavs" * cf['_keep_wavs'] \
+ + " id=" + jack_freedb.freedb_id(jack_ripstuff.all_tracks) \
+ + (" len=%02i:%02i" % (global_total / jack_globals.CDDA_BLOCKS_PER_SECOND \
+ / 60, global_total / jack_globals.CDDA_BLOCKS_PER_SECOND % 60)) \
+ + " | press Q to quit"
+ jack_term.tmod.extra_lines = 2
+ if jack_freedb.names_available:
+ jack_term.tmod.extra_lines = jack_term.tmod.extra_lines + 1
+ if jack_term.term_type == "curses":
+ discname = jack_tag.locale_names[0][0] + " - " + jack_tag.locale_names[0][1]
+ else:
+ options_string = center_line(jack_tag.locale_names[0][0] + " - " + jack_tag.locale_names[0][1], fill = "- ", fill_r = " -", width = jack_term.size_x) + "\n" + center_line(options_string, fill = " ", fill_r = " ", width = jack_term.size_x)
+
+def sig_handler(sig, frame):
+ "signal handler and general cleanup procedure"
+ if frame < 0:
+ exit_code = frame
+ else:
+ exit_code = 0
+
+ jack_term.disable()
+
+ if sig:
+ exit_code = 2
+ info("signal %d caught, exiting." % sig)
+
+ for i in jack_children.children:
+ exit_code = 1
+ if not cf['_silent_mode']:
+ info("killing %s (pid %d)" % (i['type'], i['pid']))
+ os.kill(i['pid'], signal.SIGTERM)
+ i['file'].close()
+
+ if exit_code and cf['_silent_mode']:
+ progress("all", "err", "abnormal exit (code %i), check %s and %s" % (exit_code, cf['_err_file'], cf['_out_file']))
+
+ if cf['_wait_on_quit']:
+ raw_input("press ENTER to exit")
+
+ sys.exit(exit_code)
+
+#/ end of sig_handler /#
+
+def center_line(str, fill = " ", fill_sep = " ", fill_r = "", width = 80):
+ "return str centered, filled with fill chars"
+ width = jack_term.size_x
+ free = width - len(str)
+ if free >= 2:
+ if not fill_r:
+ fill_r = fill
+ length = len(fill)
+ left = free / 2
+ right = free / 2 + (free % 2)
+ left_c = fill * (left / length) + fill_sep * (left % length)
+ right_c = fill_sep * (right % length) + fill_r * (right / length)
+ return left_c + str + right_c
+ else:
+ return str
+
+def exit(why = 0):
+ "call my own cleanum fkt. and exit"
+ if why:
+ why = 0 - why
+ sig_handler(0, why)
+
diff --git a/jack_encstuff.py b/jack_encstuff.py
new file mode 100755
index 0000000..b9d86e5
--- /dev/null
+++ b/jack_encstuff.py
@@ -0,0 +1,21 @@
+### jack_encstuff: container module for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2002 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+# we need this to remove the tracks from wavs_todo and to prevent re-coding
+mp3s_ready = None
+
diff --git a/jack_freedb.py b/jack_freedb.py
new file mode 100644
index 0000000..f53fde7
--- /dev/null
+++ b/jack_freedb.py
@@ -0,0 +1,809 @@
+### jack_freedb: freedb server for use in
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2003 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import urllib2, urllib
+import string
+import sys
+import os
+import locale
+import codecs
+import tempfile
+import shutil
+import re
+
+import jack_playorder
+import jack_functions
+import jack_progress
+import jack_utils
+
+from jack_version import prog_version, prog_name
+from jack_globals import *
+
+names_available = None # freedb info is available
+dir_created = None # dirs are only renamed if we have created them
+NUM, LEN, START, COPY, PRE, CH, RIP, RATE, NAME = range(9)
+
+freedb_servers = {
+ 'freedb': {
+ 'host': "freedb.freedb.org",
+ 'id': prog_name + " " + prog_version,
+ 'mail': "freedb-submit@freedb.org",
+ 'my_mail': "default"
+ },
+ 'freedb-de': {
+ 'host': "de.freedb.org",
+ 'id': prog_name + " " + prog_version,
+ 'mail': "freedb-submit@freedb.org",
+ 'my_mail': "default"
+ },
+}
+
+def interpret_db_file(all_tracks, freedb_form_file, verb, dirs = 0, warn = None):
+ "read freedb file and rename dir(s)"
+ global names_available, dir_created
+ freedb_rename = 0
+ if warn == None:
+ err, track_names, locale_names, cd_id, revision = freedb_names(freedb_id(all_tracks), all_tracks, freedb_form_file, verb = verb)
+ else:
+ err, track_names, locale_names, cd_id, revision = freedb_names(freedb_id(all_tracks), all_tracks, freedb_form_file, verb = verb, warn = warn)
+ if (not err) and dirs:
+ freedb_rename = 1
+
+# The user wants us to use the current dir, unconditionally.
+
+ if cf['_claim_dir']:
+ dir_created = jack_utils.split_dirname(os.getcwd())[-1]
+ jack_functions.progress("all", "mkdir", dir_created)
+ cf['_claim_dir'] = 0
+
+ if cf['_rename_dir'] and dir_created:
+ new_dirs, new_dir = jack_utils.mkdirname(track_names, cf['_dir_template'])
+ old_dir = os.getcwd()
+ old_dirs = jack_utils.split_dirname(old_dir)
+ dirs_created = jack_utils.split_dirname(dir_created)
+
+# only do the following if we are where we think we are and the dir has to be
+# renamed.
+
+ if jack_utils.check_path(dirs_created, old_dirs) and not jack_utils.check_path(dirs_created, new_dirs):
+ jack_utils.rename_path(dirs_created, new_dirs)
+ print "Info: cwd now", os.getcwd()
+ jack_functions.progress("all", 'ren', dir_created + "-->" + unicode(new_dir, cf['_charset']))
+
+ if not err:
+ names_available = 1
+ else:
+ freedb_rename = 0
+ return err, track_names, locale_names, freedb_rename, revision
+#/ end of interpret_db_file /#
+
+def local_freedb(cd_id, freedb_dir, outfile = "/tmp/testfilefreedb"):
+ "Use file from local freedb directory"
+ # Moritz Moeller-Herrmann kindly provided this functionality.
+ if not os.path.isdir(freedb_dir):
+ error("freedb directory not found")
+ if not os.access(freedb_dir, 5):
+ error("freedb directory access not permitted")
+ cat=[freedb_dir] # category listing
+ for entry in os.listdir(freedb_dir):
+ if os.path.isdir(os.path.join(freedb_dir, entry)):
+ cat.append(os.path.join(freedb_dir, entry))
+ for musicdir in cat:
+ for m in os.listdir(musicdir):
+ if m == cd_id:
+ idfile = os.path.join(musicdir, cd_id)
+ inf = open (idfile, "r")
+ outf = open (outfile, "w")
+ buf = inf.readline()
+ while buf:
+ buf = string.replace(buf, "\n", "") # we need trailing spaces
+ if buf != ".":
+ outf.write(buf + "\n")
+ buf = inf.readline()
+ inf.close()
+ outf.close()
+ return 0
+ print "No local matching freedb entry found"
+ return 1
+
+def freedb_sum(n):
+ "belongs to freedb_id"
+ ret = 0
+ while n > 0:
+ ret = ret + (n % 10)
+ n = n / 10
+ return ret
+
+def freedb_id(tracks, warn=0):
+ from jack_globals import START, MSF_OFFSET, CDDA_BLOCKS_PER_SECOND
+ "calculate freedb (aka CDDB) disc-id"
+ cdtoc = []
+ if not tracks:
+ if warn:
+ warning("no tracks! No disc inserted? No/wrong ripper?")
+ return 0
+ for i in tracks:
+ cdtoc.append(jack_functions.blockstomsf(i[START] + MSF_OFFSET))
+ cdtoc.append(jack_functions.blockstomsf(tracks[-1][START] + tracks[-1][LEN]))
+
+ n = t = 0
+ for i in tracks:
+ n = n + freedb_sum((i[START] + MSF_OFFSET) / CDDA_BLOCKS_PER_SECOND)
+ t = (tracks[-1][START] + tracks[-1][LEN]) / CDDA_BLOCKS_PER_SECOND - tracks[0][START] / CDDA_BLOCKS_PER_SECOND
+
+ return "%08x" % ((n % 0xff << long(24)) | (t << 8) | (len(tracks)))
+
+def freedb_split(field, s, max = 78):
+ "split a field into multiple lines of 78 char max."
+ x = ""
+ s = field + "=" + s
+ while len(s) > max:
+ x = x + s[:max] + "\n"
+ s = field + "=" + s[max:]
+ return x + s + "\n"
+
+def freedb_template(tracks, names = "", revision = 0):
+ "generate a freedb submission template"
+ if os.path.exists(cf['_freedb_form_file']):
+ os.rename(cf['_freedb_form_file'], cf['_freedb_form_file'] + ".bak")
+ f = open(cf['_freedb_form_file'], "w")
+ f.write("# xmcd CD database file\n#\n# Track frame offsets:\n")
+ for i in tracks:
+ f.write("# " + `i[START] + MSF_OFFSET` + "\n")
+ f.write("#\n# Disc length: " + `(MSF_OFFSET + tracks[-1][START] + tracks[-1][LEN]) / CDDA_BLOCKS_PER_SECOND`)
+ f.write(" seconds\n#\n# Revision: %i\n" % revision)
+ f.write("# Submitted via: " + prog_name + " " + prog_version + "\n#\n")
+ f.write("DISCID=" + freedb_id(tracks) + "\n")
+ if names:
+ if names[1][0]: # various
+ if string.find(string.upper(names[0][0]), "VARIOUS") >= 0:
+ f.write(freedb_split("DTITLE", "Various / " + names[0][1]))
+ else:
+ f.write(freedb_split("DTITLE", "Various / " + names[0][0] + " - " + names[0][1]))
+ else:
+ f.write(freedb_split("DTITLE", names[0][0] + " / " + names[0][1]))
+ else:
+ f.write("DTITLE=\n")
+ for i in tracks:
+ if names:
+ if names[i[NUM]][0]: # various
+ f.write(
+ freedb_split("TTITLE" + `i[NUM]-1`,
+ names[i[NUM]][0] +
+ " - " +
+ names[i[NUM]][1]
+ )
+ )
+ else:
+ f.write(freedb_split("TTITLE" + `i[NUM]-1`, names[i[NUM]][1]))
+ else:
+ f.write("TTITLE" + `i[NUM]-1` + "=\n")
+ freedb_year, freedb_id3genre = -1, -1
+ if cf['_id3_genre'] >= 0 and cf['_id3_genre'] < len(id3genres) or cf['_id3_genre'] == 255:
+ freedb_id3genre = cf['_id3_genre']
+ elif names and len(names[0]) == 4:
+ freedb_id3genre = names[0][3]
+ if cf['_id3_year'] >= 0:
+ freedb_year = cf['_id3_year']
+ elif names and len(names[0]) == 4:
+ freedb_year = names[0][2]
+ if freedb_year >= 0 or freedb_id3genre >= 0:
+ f.write("EXTD=\\nYEAR: %4s ID3G: %3s\n" % (freedb_year, freedb_id3genre))
+ else:
+ f.write("EXTD=\n")
+ for i in tracks:
+ f.write("EXTT" + `i[NUM]-1` + "=\n")
+ f.write("PLAYORDER=\n")
+
+def freedb_query(cd_id, tracks, file):
+ if cf['_freedb_dir']:
+ if local_freedb(cd_id, cf['_freedb_dir'], file)==0: # use local database (if any)
+ return 0
+
+ qs = "cmd=cddb query " + cd_id + " " + `len(tracks)` + " " # query string
+ for i in tracks:
+ qs = qs + `i[START] + MSF_OFFSET` + " "
+ qs = qs + `(MSF_OFFSET + tracks[-1][START] + tracks[-1][LEN]) / CDDA_BLOCKS_PER_SECOND`
+ hello = "hello=" + cf['_username'] + " " + cf['_hostname'] + " " + freedb_servers[cf['_freedb_server']]['id']
+ qs = urllib.quote_plus(qs + "&" + hello + "&proto=6", "=&")
+ url = "http://" + freedb_servers[cf['_freedb_server']]['host'] + "/~cddb/cddb.cgi?" + qs
+ if cf['_cont_failed_query']:
+ try:
+ f = urllib2.urlopen(url)
+ except IOError:
+ traceback.print_exc()
+ err = 1
+ return err
+ else:
+ f = urllib2.urlopen(url)
+ buf = f.readline()
+ if buf and buf[0:1] == "2":
+ if buf[0:3] in ("210", "211"): # Found inexact or multiple exact matches, list follows
+ print "Found the following matches. Choose one:"
+ num = 1
+ matches = []
+ while 1:
+ buf = f.readline()
+ try:
+ buf = unicode(buf, "utf-8")
+ except UnicodeDecodeError:
+ buf = unicode(buf, "latin-1")
+ if not buf:
+ break
+ buf = string.rstrip(buf)
+ if buf != ".":
+ print "%2i" % num + ".) " + buf.encode(locale.getpreferredencoding(), "replace")
+ matches.append(buf)
+ num = num + 1
+ x = -1
+ while x < 0 or x > num - 1:
+ input = raw_input(" 0.) none of the above: ")
+ if not input:
+ continue
+ try:
+ x = int(input)
+ except ValueError:
+ x = -1 # start the loop again
+ if not x:
+ print "ok, aborting."
+ sys.exit()
+
+ buf = matches[x-1]
+ buf = string.split(buf, " ", 2)
+ freedb_cat = buf[0]
+ cd_id = buf[1]
+ err = 0
+
+ elif buf[0:3] == "200":
+ buf = string.split(buf)
+ freedb_cat = buf[1]
+ elif buf[0:3] == "202":
+ if cf['_cont_failed_query']:
+ warning(buf + f.read() + " How about trying another --server?")
+ err = 1
+ return err
+ else:
+ error(buf + f.read() + " How about trying another --server?")
+ else:
+ if cf['_cont_failed_query']:
+ warning(buf + f.read() + " --don't know what to do, aborting query.")
+ err = 1
+ return err
+ else:
+ error(buf + f.read() + " --don't know what to do, aborting query.")
+
+ cmd = "cmd=cddb read " + freedb_cat + " " + cd_id
+ url = "http://" + freedb_servers[cf['_freedb_server']]['host'] + "/~cddb/cddb.cgi?" + urllib.quote_plus(cmd + "&" + hello + "&proto=6", "=&")
+ f = urllib2.urlopen(url)
+ buf = f.readline()
+ if buf and buf[0:3] == "210": # entry follows
+ if os.path.exists(file):
+ os.rename(file, file + ".bak")
+ of = open(file, "w")
+ buf = f.readline()
+ while buf:
+ buf = string.rstrip(buf)
+ if buf != ".":
+ of.write(buf + "\n")
+ buf = f.readline()
+ of.close()
+ jack_functions.progress("all", "freedb_cat", freedb_cat)
+ jack_progress.status_all['freedb_cat'] = freedb_cat
+ err = 0
+ else:
+ print string.rstrip(buf)
+ print f.read()
+ warning("could not query freedb entry")
+ err = 1
+ f.close()
+ else:
+ print string.rstrip(buf)
+ print f.read()
+ warning("could not check freedb category")
+ err = 2
+ f.close()
+ return err
+
+def freedb_names(cd_id, tracks, name, verb = 0, warn = 1):
+ "returns err, [(artist, albumname), (track_01-artist, track_01-name), ...], cd_id, revision"
+ err = 0
+ tracks_on_cd = tracks[-1][NUM]
+ freedb = {}
+ f = open(name, "r") # first the freedb info is read in...
+ while 1:
+ line = f.readline()
+ if not line:
+ break
+ try:
+ line = unicode(line, "utf-8")
+ except UnicodeDecodeError:
+ line = unicode(line, "latin-1")
+ line = string.replace(line, "\n", "") # cannot use rstrip, we need trailing
+ # spaces
+ line = string.replace(line, "\r", "") # I consider "\r"s as bugs in db info
+ if jack_functions.starts_with(line, "# Revision:"):
+ revision = int(line[11:])
+ for i in ["DISCID", "DTITLE", "TTITLE", "EXTD", "EXTT", "PLAYORDER"]:
+ if jack_functions.starts_with(line, i):
+ buf = line
+ if string.find(buf, "=") != -1:
+ buf = string.split(buf, "=", 1)
+ if buf[1]:
+ if freedb.has_key(buf[0]):
+ if buf[0] == "DISCID":
+ freedb[buf[0]] = freedb[buf[0]] + ',' + buf[1]
+ else:
+ freedb[buf[0]] = freedb[buf[0]] + buf[1]
+ else:
+ freedb[buf[0]] = buf[1]
+ continue
+
+ for i in tracks: # check that info is there for all tracks
+ if not freedb.has_key("TTITLE%i" % (i[NUM] - 1)): # -1 because freedb starts at 0
+ err = 1
+ if verb:
+ warning("no freedb info for track %02i (\"TTITLE%i\")" % (i[NUM], i[NUM] - 1))
+ freedb["TTITLE%i" % (i[NUM] - 1)] = "[not set]"
+
+ for i in freedb.keys():# check that there is no extra info
+ if i[0:6] == "TTITLE":
+ if int(i[6:]) > tracks_on_cd - 1:
+ err = 2
+ if verb:
+ warning("extra freedb info for track %02i (\"%s\"), cd has only %02i tracks." % (int(i[6:]) + 1, i, tracks_on_cd))
+
+ if not freedb.has_key("DTITLE"):
+ err = 3
+ if verb:
+ warning("freedb entry doesn't contain disc title info (\"DTITLE\").")
+ freedb['DTITLE'] = "[not set]"
+
+ if not freedb.has_key("DISCID"):
+ err = 4
+ if verb:
+ warning("freedb entry doesn't contain disc id info (\"DISCID\").")
+ read_id = "00000000"
+ else:
+ read_id = freedb['DISCID']
+ read_ids = string.split(freedb['DISCID'], ",")
+ id_matched = 0
+ for i in read_ids:
+ if i == cd_id:
+ id_matched = 1
+ if not id_matched and warn:
+ print "Warning: calculated id (" + cd_id + ") and id from freedb file"
+ print " :", read_ids
+ print " : do not match, hopefully due to inexact match."
+ for i in read_ids:
+ for j in i:
+ if j not in "0123456789abcdef":
+ if verb:
+ warning("the disc's id is not 8-digit hex (\"DISCID\").")
+ err = 5
+ if len(i) != 8:
+ if verb:
+ warning("the disc's id is not 8-digit hex (\"DISCID\").")
+ err = 5
+
+ if freedb.has_key('PLAYORDER'):
+ jack_playorder.order = freedb('PLAYORDER')
+
+ dtitle = freedb['DTITLE']
+ dtitle = string.replace(dtitle, " / ", "/") # kill superflous slashes
+ dtitle = string.replace(dtitle, "/ ", "/")
+ dtitle = string.replace(dtitle, " /", "/")
+ dtitle = string.replace(dtitle, "(unknown disc title)", "(unknown artist)/(unknown disc title)") # yukk!
+ if not dtitle:
+ dtitle = "(unknown artist)/(unknown disc title)"
+ if string.find(dtitle,"/") == -1:
+ if cf['_various'] == 1:
+ dtitle = "Various/" + dtitle
+ warning("bad disc title, using %s. Please fix and submit." % dtitle)
+ else:
+ dtitle = "(unknown artist)/" + dtitle
+
+ names = [string.split(dtitle,"/",1)]
+ if freedb.has_key('EXTD'):
+ extra_tag_pos = string.find(freedb['EXTD'], "\\nYEAR:")
+ if extra_tag_pos >= 0:
+ try:
+ extd_info = freedb['EXTD'][extra_tag_pos + 7:]
+ extd_year, extd_id3g = string.split(extd_info, "ID3G:", 1)
+ extd_year, extd_id3g = int(extd_year), int(extd_id3g)
+ except:
+ print "can't handle '%s'." % freedb['EXTD']
+ else:
+ names = [string.split(dtitle, "/", 1)]
+ names[0].extend([extd_year, extd_id3g])
+ if names[0][0] == "(unknown artist)":
+ if verb:
+ warning("the disc's title must be set to \"artist / title\" (\"DTITLE\").")
+ err = 6
+
+ if string.upper(names[0][0]) in ("VARIOUS", "VARIOUS ARTISTS", "SAMPLER", "COMPILATION", "DIVERSE", "V.A.", "VA"):
+ if not cf['_various'] and not ['argv', False] in cf['various']['history']:
+ cf['_various'] = 1
+
+# user says additional info is in the EXTT fields
+
+ if cf['_various'] and cf['_extt_is_artist']:
+ for i in range(tracks_on_cd):
+ if freedb['EXTT'+`i`]:
+ names.append([freedb['EXTT'+`i`], freedb['TTITLE'+`i`]])
+ else:
+ err = 8
+ if verb:
+ warning("no EXTT info for track %02i." % i)
+
+ elif cf['_various'] and cf['_extt_is_title']:
+ for i in range(tracks_on_cd):
+ if freedb['EXTT'+`i`]:
+ names.append([freedb['TTITLE'+`i`], freedb['EXTT'+`i`]])
+ else:
+ err = 8
+ if verb:
+ warning("no EXTT info for track %02i." % i)
+
+# we'll try some magic to separate artist and title
+
+ elif cf['_various']:
+ found = [[], [], [], [], [], []]
+ # lenght=3 2 1 , 3 2 1 (secondary)
+ ignore = string.letters + string.digits
+ titles = []
+ braces = [['"', '"'], ["'", "'"], ["(", ")"], ["[", "]"], ["{", "}"]]
+
+# first generate a list of track titles
+
+ for i in range(tracks_on_cd):
+ titles.append(freedb['TTITLE'+`i`])
+
+# now try to find a string common to all titles with length 3...1
+
+ for i in (3,2,1):
+ candidate_found = 0
+ for j in range(len(titles[0])-(i-1)):
+
+# choose a possible candidate
+
+ candidate = titles[0][j:j+i]
+ illegal_letter = 0
+ for k in candidate:
+ if k in ignore:
+
+# candidate must not have characters from ignore
+
+ illegal_letter = 1
+ break
+ if illegal_letter:
+ continue
+ else:
+ candidate_found = 1
+
+# if we have a candidate, check that it occurs in all titles
+
+ if candidate_found:
+ all_matched = 1
+ append_as_secondary = 0
+ for l in titles:
+ matches = 0
+ where = 0
+ brace = 0
+ for b in braces:
+ if b[0] in candidate:
+ brace = 1
+ where2 = string.find(l, candidate) + len(candidate)
+ where = where2
+ while string.find(l, b[1], where) != -1:
+ where = string.find(l, b[1], where) + len(candidate)
+ matches = matches + 1
+ where = where2
+ if not b[1] in candidate:
+ while string.find(l, candidate, where) != -1:
+ where = string.find(l, candidate, where) + len(candidate)
+ matches = matches + 1
+ break # only treat the first pair of braces
+ if not brace:
+ while string.find(l, candidate, where) != -1:
+ matches = matches + 1
+ where = string.find(l, candidate, where) + len(candidate)
+ if matches == 0: # not found
+ all_matched = 0
+ break
+ elif matches == 1: # found exactly once
+ pass
+ else: # found multiple times
+ if cf['_freedb_pedantic']:
+ all_matched = 0
+ break
+ else:
+ append_as_secondary = 1
+ pass
+ if all_matched:
+ if append_as_secondary:
+ found[6-i].append(candidate)
+ else:
+ found[3-i].append(candidate)
+
+# if no candidate has been found, try one with less characters
+
+ else:
+ continue
+
+ tmp = []
+ eliminate = [" "]
+ for i in found:
+ i.sort() # I'm not sure anymore why/if this is needed
+ i.reverse()
+ for j in i:
+ if j not in eliminate:
+ tmp.append(j)
+ found = tmp
+ del tmp
+ if found:
+ # FIXME: when I have time, all candidate should be associated with
+ # a priority. At the moment, fav_seps prefers favorites
+ # over secondary candidates (i.e. candidates occuring multiple
+ # times. EVIL!
+ fav_seps = [" - ", " / "]
+ sep = ""
+ for i in fav_seps:
+ if i in found:
+ sep = i
+ break
+ if not sep:
+ sep = found[0]
+ closing_brace = ""
+ for j in braces:
+ if j[0] in sep:
+ closing_brace = j[1]
+ break
+ for i in titles:
+ buf = string.split(i, sep, 1)
+ if closing_brace:
+ lenbefore = len(buf[0] + buf[1])
+ buf[0] = string.replace(buf[0], closing_brace, "")
+ buf[1] = string.replace(buf[1], closing_brace, "")
+ lenafter = len(buf[0] + buf[1])
+ if lenafter != lenbefore - len(closing_brace):
+ if verb:
+ warning("brace" + `j` + " does not close exactly once.")
+ err = 9
+
+ if cf['_various_swap']:
+ buf = [buf[1], buf[0]]
+ names.append(buf)
+ else:
+ err = 7
+ if verb:
+ warning("could not separate artist and title in all TTITLEs. Try setting freedb_pedantic = 0 or use --no-various Maybe additional information is contained in the EXTT fields. check %s and use either --extt-is-artist or --extt-is-title." % cf['_freedb_form_file'])
+ else:
+ for i in range(tracks_on_cd):
+ buf = freedb['TTITLE'+`i`]
+ names.append(["", buf])
+
+ # append the EXTT fields to the track names
+ if cf['_extt_is_comment']:
+ for i in range(len(names[1:])):
+ if freedb.has_key('EXTT'+`i`) and freedb['EXTT'+`i`]:
+ names[i+1][1] = names[i+1][1] + " (%s)" % freedb['EXTT'+`i`]
+ else:
+ print "Warning: track %i (starting at 0) has no EXTT entry." % i
+
+ locale_names = []
+ # clean up a bit and create names for the appropriate locale:
+ # FIXME: this for loop doesn't actually change the variable names at all!
+ for i in names:
+ t = []
+ for j in [0, 1]:
+ if i[j]:
+ i[j] = string.strip(i[j])
+ while string.find(i[j], " ") != -1:
+ i[j] = string.replace(i[j], " ", " ")
+ while i[j][0] == '"' and i[j][-1] == '"':
+ i[j] = i[j][1:-1]
+ while i[j][0] == '"' and string.find(i[j][1:], '"') != -1:
+ i[j] = string.replace(i[j][1:], '"', '', 1)
+ x = i[j].encode(locale.getpreferredencoding(), "replace")
+ t.append(x)
+ locale_names.append(t)
+ return err, names, locale_names, read_id, revision
+
+def choose_cat(cat = ["blues", "classical", "country", "data", "folk", "jazz", "misc", "newage", "reggae", "rock", "soundtrack"]):
+ print "choose a category:"
+ cat.sort()
+ for i in range(1, len(cat)):
+ print "%2d" % i + ".) " + cat[i]
+
+ x = -1
+ while x < 0 or x > len(cat) - 1:
+ if jack_progress.status_all.has_key('freedb_cat') and jack_progress.status_all['freedb_cat'][-1] in cat:
+ input = raw_input(" 0.) none of the above (default='%s'): " % jack_progress.status_all['freedb_cat'][-1])
+ if not input:
+ x = cat.index(jack_progress.status_all['freedb_cat'][-1])
+ continue
+ else:
+ input = raw_input(" 0.) none of the above: ")
+ try:
+ x = int(input)
+ except ValueError:
+ x = -1 # start the loop again
+
+ if not x:
+ print "ok, aborting."
+ sys.exit(0)
+
+ return cat[x]
+
+def do_freedb_submit(file, cd_id):
+ import httplib
+
+ hello = "hello=" + cf['_username'] + " " + cf['_hostname'] + " " + prog_name + " " + prog_version
+ print "Info: querying categories..."
+ url = "http://" + freedb_servers[cf['_freedb_server']]['host'] + "/~cddb/cddb.cgi?" + urllib.quote_plus("cmd=cddb lscat" + "&" + hello + "&proto=6", "=&")
+ f = urllib2.urlopen(url)
+ buf = f.readline()
+ if buf[0:3] == "500":
+ print "Info: LSCAT failed, using builtin categories..."
+ cat = choose_cat()
+
+ elif buf[0:3] == "210":
+ cat = ["null", ]
+ while 1:
+ buf = f.readline()
+ if not buf:
+ break
+ buf = string.rstrip(buf)
+ if buf != ".":
+ cat.append(buf)
+ f.close()
+ cat = choose_cat(cat)
+
+ else:
+ error("LSCAT failed: " + string.rstrip(buf) + f.read())
+
+ print "OK, using `" + cat + "'."
+ email = freedb_servers[cf['_freedb_server']]['my_mail']
+ print "Your e-mail address is needed to send error messages to you."
+ x = raw_input("enter your e-mail-address [" + email + "]: ")
+ if x:
+ email = x
+
+ info("Submitting...")
+ selector = '/~cddb/submit.cgi'
+ proxy = ""
+ if os.environ.has_key('http_proxy'):
+ proxy = os.environ['http_proxy']
+ def splittype(url):
+ import re
+ _typeprog = re.compile('^([^/:]+):')
+ match = _typeprog.match(url)
+ if match:
+ scheme = match.group(1)
+ return scheme, url[len(scheme) + 1:]
+ return None, url
+
+ def splithost(url):
+ import re
+ _hostprog = re.compile('^//([^/]+)(.*)$')
+ match = _hostprog.match(url)
+ if match: return match.group(1, 2)
+ return None, url
+
+ type, proxy = splittype(proxy)
+ host, selector2 = splithost(proxy)
+ h = httplib.HTTP(host)
+ h.putrequest('POST', 'http://' + freedb_servers[cf['_freedb_server']]['host'] + selector)
+ else:
+ h = httplib.HTTP(freedb_servers[cf['_freedb_server']]['host'])
+ h.putrequest('POST', '/~cddb/submit.cgi')
+ h.putheader('Category', cat)
+ h.putheader('Discid', cd_id)
+ h.putheader('User-Email', email)
+ if cf['_debug']:
+ debug("will submit in test-mode, changes are not applied and you'll get an email which contains the data you submitted.")
+ h.putheader('Submit-Mode', 'test')
+ else:
+ h.putheader('Submit-Mode', 'submit')
+ h.putheader('Charset', 'UTF-8')
+ if cf['_debug']:
+ h.putheader('X-Cddbd-Note', 'Submission will not be applied to database if --debug is on.')
+ else:
+ h.putheader('X-Cddbd-Note', 'data submitted with ' + prog_name + ' (http://jack.sf.net)')
+ h.putheader('Content-Length', str(jack_utils.filesize(file)))
+ h.endheaders()
+ # The user just wrote the file with a text editor so we assume that it
+ # is in their locale.
+ f = codecs.open(file, "r", locale.getpreferredencoding())
+ try:
+ text = f.read()
+ except UnicodeDecodeError:
+ print "The freedb file does not match your current locale. Please convert it"
+ print "to " + locale.getpreferredencoding() + " manually."
+ sys.exit(1)
+ h.send(text.encode("utf-8"))
+ f.close()
+
+ print
+
+ err, msg, headers = h.getreply()
+ f = h.getfile()
+ if proxy:
+ if err != 200:
+ error("proxy: " + `err` + " " + msg + f.read())
+ else:
+ buf = f.readline()
+ err, msg = buf[0:3], buf[4:]
+
+ # lets see if it worked:
+ if err == 404:
+ print "This server doesn't seem to support database submission via http."
+ print "consider submitting via mail (" + progname + " -m). full error:\n"
+ print err, msg
+
+def do_freedb_mailsubmit(file, cd_id):
+ warning("Support for freedb submission via e-mail may be dropped in future versions. Please begin to use HTTP to submit your entries (--submit)")
+ sendmail = '/usr/lib/sendmail -t'
+ #sendmail = 'cat > /tmp/jack.test.mailsubmit'
+ cat = choose_cat()
+ print "OK, using `" + cat + "'."
+ if string.find(freedb_servers[cf['_freedb_server']]['my_mail'], "@") >= 1 and len(freedb_servers[cf['_freedb_server']]['my_mail']) > 3:
+ return os.system("( echo 'To: " + freedb_servers[cf['_freedb_server']]['mail'] + "'; echo From: '" + freedb_servers[cf['_freedb_server']]['my_mail'] + "'; echo 'Subject: cddb " + cat + " " + cd_id + "' ; cat '" + file + "' ) | " + sendmail)
+ else:
+ print "please set your e-mail address. aborting..."
+
+def update_revision(file):
+ "Update the revision (and submitted-via) information in a FreeDB template"
+
+ re_revision = re.compile(r"^#\s*Revision:\s*(\d+)")
+ re_agent = re.compile(r"^#\s*Submitted via:")
+
+ tmp, tmpname = tempfile.mkstemp()
+ freedb_file = open(file, "r")
+ revision = 0
+ comments = 1
+ for line in freedb_file.readlines():
+ rev = re_revision.match(line)
+ agent = re_agent.match(line)
+ if rev:
+ revision = int(rev.group(1)) + 1
+ os.write(tmp, "# Revision: %d\n" % revision)
+ elif agent:
+ os.write(tmp, "# Submitted via: " + prog_name + " " + prog_version + "\n")
+ else:
+ if not line.startswith("#"):
+ # The 'Revisions' field is option but should be set when you
+ # submit a new version. Therefore, after the comments check
+ # whether we've seen a revision and if not write one.
+ if comments:
+ if revision == 0:
+ revision += 1
+ os.write(tmp, "# Revision: %d\n" % revision)
+ comments = 0
+ os.write(tmp, line)
+ os.close(tmp)
+ freedb_file.close()
+ try:
+ shutil.copyfile(tmpname, file)
+ except IOError:
+ print "Cannot copy updated template over existing one."
+ try:
+ os.unlink(tmpname)
+ except IOError:
+ print "Cannot remove temporary file %s." % tmpname
+
diff --git a/jack_functions.py b/jack_functions.py
new file mode 100755
index 0000000..21fe24c
--- /dev/null
+++ b/jack_functions.py
@@ -0,0 +1,463 @@
+# -*- coding: iso-8859-15 -*-
+### jack_functions: functions for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2003 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import codecs
+import traceback
+import sndhdr
+import types
+import string
+import sys
+import os
+
+import jack_TOCentry
+import jack_CDTime
+import jack_utils
+import jack_TOC
+import jack_mp3
+import jack_helpers
+
+from jack_globals import *
+
+progress_changed = None
+
+def df(fs = ".", blocksize = 1024):
+ "returns free space on a filesystem (in bytes)"
+ try:
+ from os import statvfs
+ statvfs_found = 1
+ except:
+ statvfs_found = 0
+
+ if statvfs_found:
+ (f_bsize, f_frsize, f_blocks, f_bfree, f_bavail, f_files, f_ffree, f_favail, f_flag, f_namemax) = statvfs(fs)
+ return long(f_bavail) * long(f_bsize)
+ else:
+ # Not very portable
+ p = os.popen("df " + fs)
+ s = string.split(string.rstrip(p.readline()))
+ for i in range(len(s)):
+ if s[i] == "Available":
+ p.close()
+ s = string.split(string.rstrip(p.readline()))
+ return int(s[i]) * long(blocksize) - long(keep_free)
+
+def get_sysload_linux_proc():
+ "extract sysload from /proc/loadavg, linux only (?)"
+ f = open("/proc/loadavg", "r")
+ loadavg = float(string.split(f.readline())[0])
+ return loadavg
+
+def pprint_i(num, fmt = "%i%s", scale = 2.0**10, max = 4):
+ "return a string describing an int in a more human-readable way"
+ c = ""
+ change = 0
+ for i in ("K", "M", "G", "T"):
+ if abs(num) >= scale:
+ c = i
+ num = num / scale
+ change = 1
+ if change:
+ #num = num + 0.5
+ if num > 999:
+ return fmt % (num, c)
+ elif num >= 100:
+ return string.replace(fmt, "%i", "%s") % (`num`[:3], c)
+ else:
+ return string.replace(fmt, "%i", "%s") % (`num`[:4], c)
+ else:
+ return fmt % (num, c)
+
+def pprint_speed(s, len=4):
+ if len >= 4:
+ if s < 10:
+ return "%4.2f" % s
+ if s < 100:
+ return "%4.1f" % s
+ if s < 1000:
+ return "%4.0f" % s
+ if s < 10000:
+ return "%4d" % (s + 0.5)
+ else:
+ return "9999"
+ elif len == 3:
+ if s < 10:
+ return "%3.1f" % s
+ if s < 100:
+ return "%3.0f" % s
+ if s < 1000:
+ return "%3d" % s + 0.5
+ else:
+ return "999"
+ else:
+ return "X" * len
+
+def gettoc(toc_prog):
+ "Returns track list"
+ if jack_helpers.helpers[toc_prog].has_key('toc_cmd'):
+ cmd = string.replace(jack_helpers.helpers[toc_prog]['toc_cmd'], "%d", cf['_cd_device'])
+ if cf['_gen_device']:
+ cmd = string.replace(cmd, "%D", cf['_gen_device'])
+ p = os.popen(cmd)
+ start = 0
+ erg = []
+ l = p.readline()
+ exec(jack_helpers.helpers[toc_prog]['toc_fkt'])
+ if p.close():
+ if cf['_cd_device']:
+ try:
+ f = open(cf['_cd_device'], "r")
+ except IOError:
+ info("could not open " + cf['_cd_device'] + ". Check permissions and that a disc is inserted.")
+ else:
+ info("maybe " + toc_prog + " is not installed?")
+ else:
+ info("try setting cf['_cd_device'] to your CD device, e.g. /dev/cdrom")
+ error("could not read CD's TOC.")
+ else:
+ return erg
+ else:
+ erg = []
+ try:
+ exec(jack_helpers.helpers[toc_prog]['toc_fkt'])
+ except SystemExit:
+ sys.exit(1)
+ except:
+ traceback.print_exc()
+ error("""%s could not read the disk's TOC. If you already ripped the
+ CD, you'll have to cd into the directory which is either named
+ after the CD's title or still called jack-xxxxxxxx (xxxxxxxx is a
+ hex number).""" % toc_prog)
+ return erg
+
+def guesstoc(names):
+ "Return track list based on guessed lengths"
+ num = 1
+ start = 0
+ erg = []
+ progr = []
+ for i in names:
+ i_name = os.path.basename(i)[:-4]
+ i_ext = string.upper(os.path.basename(i)[-4:])
+ if i_ext == ".MP3":
+ x = jack_mp3.mp3format(i)
+ if not x:
+ error("could not get MP3 info for file \"%x\"" % i)
+ blocks = int(x['length'] * CDDA_BLOCKS_PER_SECOND + 0.5)
+ # NUM, LEN, START, COPY, PRE, CH, RIP, RATE,
+ # NAME
+ erg.append([num, blocks, start, 0, 0, 2, 1, x['bitrate'],
+ i_name])
+ progr.append([num, "dae", " * [ simulated ]"])
+ progr.append([num, "enc", `x['bitrate']`, "[ s i m u l a t e d %3ikbit]" % (x['bitrate'] + 0.5)])
+ if cf['_name'] % num != i_name:
+ progr.append([num, "ren", cf['_name'] % num + "-->" + i_name])
+ elif i_ext == ".WAV":
+ x = sndhdr.whathdr(i)
+ if not x:
+ error("this is not WAV-format: " + i)
+ if x != ('wav', 44100, 2, -1, 16):
+ error("unsupportet format " + `x` + " in " + i)
+ blocks = jack_utils.filesize(i)
+ blocks = blocks - 44 # substract WAV header
+ extra_bytes = blocks % CDDA_BLOCKSIZE
+ if not extra_bytes == 0:
+ warning("this is not CDDA block-aligned: " + `i`)
+ yes = raw_input("May I strip %d bytes (= %.4fseconds) off the end? " % (extra_bytes, extra_bytes / 2352.0 / 75.0))
+ if not string.upper((yes + "x")[0]) == "Y":
+ print "Sorry, I can't process non-aligned files (yet). Bye!"
+ sys.exit()
+ f = open(i, "r+")
+ f.seek(-extra_bytes, 2)
+ f.truncate()
+ f.close()
+ blocks = blocks - extra_bytes
+ blocks = blocks / CDDA_BLOCKSIZE
+ erg.append([num, blocks, start, 0, 0, 2, 1, cf['_bitrate'], i_name])
+ progr.append([num, "dae", " =p [ s i m u l a t e d ]"])
+ if cf['_name'] % num != i_name:
+ progr.append([num, "ren", cf['_name'] % num + "-->" + i_name])
+ elif i_ext == ".OGG":
+ error("you still have to wait for ogg support for this operation, sorry.")
+ elif i_ext == ".FLAC":
+ error("you still have to wait for FLAC support for this ooperation, sorry.")
+ else:
+ error("this is neither .mp3 nor .ogg nor .wav nor .flac: %s" % i)
+ num = num + 1
+ start = start + blocks
+ for i in progr: # this is deferred so that it is only written if no
+ # files fail
+ progress(i)
+ return erg
+
+#XXX will be moved to jack_convert
+def timestrtoblocks(str):
+ "convert mm:ss:ff to blocks"
+ str = string.split(str, ":")
+ blocks = int(str[2])
+ blocks = blocks + int(str[1]) * CDDA_BLOCKS_PER_SECOND
+ blocks = blocks + int(str[0]) * 60 * CDDA_BLOCKS_PER_SECOND
+ return blocks
+
+B_MM, B_SS, B_FF = 0, 1, 2
+def blockstomsf(blocks):
+ from jack_globals import CDDA_BLOCKS_PER_SECOND
+ "convert blocks to mm, ss, ff"
+ mm = blocks / 60 / CDDA_BLOCKS_PER_SECOND
+ blocks = blocks - mm * 60 * CDDA_BLOCKS_PER_SECOND
+ ss = blocks / CDDA_BLOCKS_PER_SECOND
+ ff = blocks % CDDA_BLOCKS_PER_SECOND
+ return mm, ss, ff, blocks
+
+def starts_with(str, with):
+ "checks whether str starts with with"
+ return str[0:len(with)] == with
+
+## #XXX the following will be used if all references to it have been updated.
+## meanwhile the wrapper below is used.
+
+def real_cdrdao_gettoc(tocfile): # get toc from cdrdao-style toc-file
+ "returns TOC object, needs name of toc-file to read"
+ toc = jack_TOC.TOC()
+
+ f = open(tocfile, "r")
+
+ tocpath, tocfiledummy = os.path.split(tocfile)
+
+# a virtual track 0 is introduced which gets all of track 1s pregap.
+# it is removed later if it is too small to contain anything interesting.
+
+ actual_track = jack_TOCentry.TOCentry()
+ actual_track.number = 0
+ actual_track.type = "audio"
+ actual_track.channels = 2
+ actual_track.media = "image"
+ actual_track.start = 0
+ actual_track.length = 0
+ actual_track.rip = 1
+ actual_track.bitrate = cf['_bitrate']
+ actual_track.image_name = ""
+ actual_track.rip_name = cf['_name'] % 0
+
+## tocfile data is read in line by line.
+
+ num = 0
+ while 1:
+ line = f.readline()
+ if not line:
+ if actual_track.channels not in [1,2,4]:
+ debug("track %02d: unknown number of channels, assuming 2" % num)
+ actual_track.channels = 2
+ toc.append(actual_track)
+ break
+ line = string.strip(line)
+
+## everytime we encounter "TRACK" we increment num and append the actual
+## track to the toc.
+
+ if starts_with(line, "TRACK "):
+ num = num + 1
+ new_track = jack_TOCentry.TOCentry()
+ new_track.number = num
+ if actual_track:
+ if actual_track.channels not in [1,2,4]:
+ debug("track %02d: unknown number of channels, assuming 2" % num)
+ actual_track.channels = 2
+ toc.append(actual_track)
+ actual_track = new_track
+ actual_track.rip = 1
+ actual_track.bitrate = cf['_bitrate']
+ actual_track.start = toc.end_pos
+ if line == "TRACK AUDIO":
+ actual_track.type = "audio"
+ else:
+ actual_track.type = "other" # we don't care
+ actual_track.channels = 0
+ actual_track.rip = 0
+ actual_track.bitrate = 0
+
+## check the various track flags.
+## FOUR_CHANNEL_AUDIO is not supported.
+## we have to check for this before ripping. later. much later.
+
+ elif line == "NO COPY":
+ actual_track.copy = 0
+ elif line == "COPY":
+ actual_track.copy = 1
+ elif line == "NO PRE_EMPHASIS":
+ actual_track.preemphasis = 0
+ elif line == "PRE_EMPHASIS":
+ actual_track.preemphasis = 1
+ elif line == "TWO_CHANNEL_AUDIO":
+ actual_track.channels = 2
+ elif line == "FOUR_CHANNEL_AUDIO":
+ actual_track.channels = 4
+
+## example: FILE "data.wav" 08:54:22 04:45:53
+
+ elif starts_with(line, "FILE "):
+ filename = line[string.find(line, "\"") + 1:string.rfind(line, "\"")]
+ offsets = string.strip(line[string.rfind(line, "\"") + 1:])
+ start, length = string.split(offsets)[:2]
+
+## convert time string to blocks(int), update info.
+
+ actual_track.length = jack_CDTime.CDTime(length).blocks
+ actual_track.image_name = os.path.join(tocpath, filename)
+ actual_track.rip_name = cf['_name'] % num
+
+## example: START 00:01:53. This means the actual track starts 1:53s _after_
+## the start given by the FILE statement. This so-called pregap needs to be
+## added to the length of the previous track, added to the start of the
+## actual track and subtracted from its length. This is done automagically
+## by setting the pregap attribute.
+
+ elif starts_with(line, "START "):
+ actual_track.pregap = jack_CDTime.CDTime(string.split(line)[1]).blocks
+
+ f.close()
+ return toc
+
+
+def cdrdao_gettoc(tocfile): # get toc from cdrdao-style toc-file
+ "just a wrapper for real_cdrdao_gettoc."
+ toc = real_cdrdao_gettoc(tocfile)
+ tracks = toc.export()
+ track1_pregap = tracks[0][1]
+ use_filename = toc.image_file
+ # note: toc.image_file is None if different files are specified
+ return tracks[1:], use_filename, track1_pregap
+
+
+##XXX this will be moved to jack_convert
+def msftostr(msf):
+ "convert msf format to readable string"
+ return "%02i" % msf[B_MM]+":"+"%02i" % msf[B_SS]+":"+"%02i" % msf[B_FF]
+
+def cdrdao_puttoc(tocfile, tracks, cd_id): # put toc to cdrdao toc-file
+ "writes toc-file from tracks"
+ f = open(tocfile, "w")
+ f.write("CD_DA\n\n")
+ f.write("// DB-ID=" + cd_id + "\n\n")
+ for i in tracks:
+ f.write("// Track " + `i[NUM]` + "\n") # comments are cool
+ if i[CH] in (2, 4):
+ f.write("TRACK AUDIO\n")
+ if i[CH] == 0:
+ f.write("TRACK MODE1\n")
+ if i[COPY]:
+ f.write("COPY\n")
+ else:
+ f.write("NO COPY\n")
+ if i[PRE]:
+ f.write("PRE_EMPHASIS\n")
+ else:
+ f.write("NO PRE_EMPHASIS\n")
+ if i[CH] == 2:
+ f.write("TWO_CHANNEL_AUDIO\n")
+ elif i[CH] == 4:
+ f.write("FOUR_CHANNEL_AUDIO\n")
+ elif i[CH] == 0:
+ f.write("// not supported by jack!\n")
+ else:
+ error("illegal TOC: channels=%i, aborting." % i[CH])
+ f.write('FILE "' + i[NAME] + '.wav" 0 ')
+ x = i[LEN]
+ if i[NUM] == 1: # add pregap to track, virtually
+ x = x + i[START]
+ x = blockstomsf(x)
+ f.write("%02i" % x[B_MM] + ":" + "%02i" % x[B_SS] + ":" + "%02i" % x[B_FF] + "\n")
+ if i[NUM]==1 and i[START] != 0:
+ f.write("START "+msftostr(blockstomsf(i[START]))+"\n")
+ f.write("\n")
+
+def tracksize(list, dont_dae = [], blocksize = 1024):
+ "Calculates all kind of sizes for a track or a list of tracks."
+ if list and type(list[0]) == types.IntType:
+ list = ((list, ))
+ peak, at, blocks = 0, 0, 0
+ encoded_size = wavsize = cdrsize = 0
+ for track in list:
+ blocks = blocks + track[LEN]
+ encoded_size = encoded_size + track[LEN] / CDDA_BLOCKS_PER_SECOND * track[RATE] * 1000 / 8
+ # files can be a bit shorter, if someone knows a better way of guessing
+ # filesizes, please let me know.
+ count_thiswav = 1
+ for i in dont_dae:
+ if i[NUM] == track[NUM]:
+ count_thiwav = 0
+ thiscdrsize = track[LEN] * CDDA_BLOCKSIZE * count_thiswav
+ wavsize = wavsize + thiscdrsize + 44
+ cdrsize = cdrsize + thiscdrsize
+ now = encoded_size + thiscdrsize + 44
+ if now>peak:
+ at = track[NUM]
+ peak = now
+ return encoded_size, wavsize, encoded_size + wavsize, peak, at, cdrsize, blocks
+
+def progress(track, what="error", data="error", data2 = None):
+ "append a line to the progress file"
+ global progress_changed
+ if type(track) in (types.TupleType, types.ListType):
+ if len(track) == 3:
+ track, what, data = track
+ elif len(track) == 4:
+ track, what, data, data2 = track
+ else:
+ error("illegal progress entry:" + `track` + " (" + `type(track)` + ")")
+
+ if type(track) == types.IntType:
+ first = "%02i" % track
+ elif type(track) == types.StringType:
+ first = track
+ else:
+ error("illegal progress entry:" + `track` + " (" + `type(track)` + ")")
+ progress_changed = 1
+ f = codecs.open (cf['_progress_file'], "a", "utf-8")
+ f.write(first + cf['_progr_sep'] + what + cf['_progr_sep'] + data)
+ if data2:
+ f.write(cf['_progr_sep'] + data2)
+ f.write("\n")
+ f.close()
+
+def check_genre_txt(genre):
+ if isinstance(genre, int):
+ if genre in range(0,256):
+ return genre
+ else:
+ return None
+
+ elif isinstance(genre, str):
+ if string.upper(genre) == "HELP":
+ info("available genres: " + string.join([x for x in eyeD3.genres if x != 'Unknown'], ", "))
+ sys.exit(0)
+ elif string.upper(genre) == "NONE":
+ return 255 # set genre to [unknown]
+ else:
+ try:
+ genre = int(genre)
+ genre = check_genre_txt(genre)
+ if isinstance(genre, int):
+ return genre
+ except:
+ for i in range(len(id3genres)):
+ if genre.upper() == id3genres[i].upper():
+ return i
+
+ import jack_version
+ error("illegal genre. Try '" + jack_version.prog_name + " --id3-genre help' for a list.")
diff --git a/jack_generic.py b/jack_generic.py
new file mode 100644
index 0000000..e0c6ba1
--- /dev/null
+++ b/jack_generic.py
@@ -0,0 +1,66 @@
+### jack_generic: generic functions used (here) for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2002 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import string
+import sys
+import os
+import jack_version
+from jack_config import cf
+
+def indent(pre, msg):
+ print pre,
+
+ msg = string.split(msg)
+ p = len(pre)
+ y = p
+ for i in msg:
+ if len(i) + y > 78:
+ print
+ print " " * p,
+ y = p
+ print i,
+ y = y + len(i) + 1
+ print
+
+def ewprint(pre, msg):
+ pre = " *" + pre + "*"
+ indent(pre, msg)
+
+def error(msg):
+ import jack_term
+ import sys
+ jack_term.disable()
+ ewprint("error", msg)
+ sys.exit(1)
+
+def warning(msg):
+ ewprint("warning", msg)
+
+def info(msg):
+ ewprint("info", msg)
+
+def debug(msg):
+ if cf['_debug']:
+ ewprint("debug", msg)
+ if cf['_debug_write']:
+ tmp = open(jack_version.prog_name + ".debug", "a")
+ tmp.write(msg + "\n")
+ del tmp
+
+def expand(filespec):
+ return(os.path.expanduser(os.path.expandvars(filespec)))
diff --git a/jack_globals.py b/jack_globals.py
new file mode 100644
index 0000000..35fe371
--- /dev/null
+++ b/jack_globals.py
@@ -0,0 +1,47 @@
+# -*- coding: iso-8859-15 -*-
+### jack_globals: Global storage space for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2003 Arne Zellentin <zarne@users.sf.net>
+
+### 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 jack_constants import *
+from jack_config import cf
+from jack_init import eyeD3
+
+from jack_generic import info, warning, debug, error, expand
+#import jack_generic
+#error = jack_generic.error
+
+dummy="""
+def error(x):
+ jack_generic.error(x)
+
+def debug(x):
+ jack_generic.debug(x)
+
+def info(x):
+ jack_generic.info(x)
+
+def debug(x):
+ jack_generic.warning(x)
+"""
+
+# globals
+revision = 0 # initial revision of freedb data
+is_submittable = 0 # well-formed freedb-file?
+
+id3genres = eyeD3.genres
+
diff --git a/jack_helpers.py b/jack_helpers.py
new file mode 100644
index 0000000..e0ba931
--- /dev/null
+++ b/jack_helpers.py
@@ -0,0 +1,541 @@
+# -*- coding: iso-8859-15 -*-
+### jack_helpers: helper applications for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2004 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import string
+import re
+
+import jack_plugins
+
+from jack_globals import *
+
+
+helpers = {
+ 'builtin': {
+ 'type': "dummy",
+ 'status_blocksize': 160
+ },
+
+ 'oggenc': { # based on a patch kindly provided by Bryan Larsen.
+ 'type': "encoder",
+ 'target': "ogg",
+ 'can_tag': 1,
+ 'vbr-cmd': "oggenc -o %o -t %t -a %a -N %n -l %l -G %g -d %y -q %q %i",
+ 'cmd': "oggenc -o %o -t %t -a %a -N %n -l %l -G %g -d %y -b %r %i",
+ 'tags': {
+ 'ogg': {
+ 'track': "-t %s",
+ 'artist': "-a %s",
+ 'number': "-N %s",
+ 'album': "-l %s",
+ 'genre': "-G %s",
+ 'date': "-d %s",
+ },
+ },
+ 'status_blocksize': 64,
+ 'bitrate_factor': 1,
+ 'status_start': "%",
+ 'percent_fkt': r"""
+s = string.split(i['buf'], '\r')
+if len(s) >= 2:
+ s = s[-2]
+if len(s) == 1:
+ s = s[0]
+y0 = string.find(s, "[")
+y1 = string.find(s, "%]")
+if y0 != -1 and y1 != -1:
+ try:
+ percent = float(s[y0 + 1:y1])
+ except ValueError:
+ percent = 0
+else:
+ percent = 0
+""",
+ },
+
+ 'mp3enc': {
+ 'type': "encoder",
+ 'target': "mp3",
+ 'cmd': "mp3enc -v -qual 9 -br %r -if %i -of %o",
+ 'otf-cmd': "mp3enc -v -qual 9 -br %r -sti -be -of %o",
+ 'status_blocksize': 99,
+ 'bitrate_factor': 1000,
+ 'status_start': "%",
+ 'percent_fkt': r"""
+s = string.split(i['buf'], '\r')
+if len(s) >= 4:
+ s = s[-2]
+ if string.find(s, "%") >= 0:
+ y = string.split(s, " ", 3)
+ percent = float(y[0]) / (i['track'][LEN] * CDDA_BLOCKSIZE / 2) * 100.0
+ else:
+ percent = 0
+""",
+ },
+
+ 'l3enc': {
+ 'type': "encoder",
+ 'target': "mp3",
+ 'cmd': "l3enc -hq -br %r %i %o",
+ 'status_blocksize': 99,
+ 'bitrate_factor': 1000,
+ 'status_start': "%",
+ 'percent_fkt': r"""
+s = string.split(i['buf'], '\r')
+if len(s) >= 2: s = s[-2]
+if len(s) == 1: s = s[0]
+if string.find(s, "%") >= 0:
+ y = string.split(s, " / ")
+ y0 = string.split(y[0])[-1]
+ y1 = string.split(y[1])[0]
+ percent=float(y0) / float(y1) * 100.0
+else:
+ percent = 0
+""",
+ },
+
+ 'lame': {
+ 'type': "encoder",
+ 'target': "mp3",
+ 'inverse-quality': 1,
+ 'cmd': "lame --preset cbr %r --strictly-enforce-ISO %i %o",
+ 'vbr-cmd': "lame --preset standard --vbr-new --nohist --strictly-enforce-ISO %i %o",
+ 'otf-cmd': "lame --preset cbr %r --strictly-enforce-ISO - %o",
+ 'vbr-otf-cmd': "lame --preset standard --vbr-new --nohist --strictly-enforce-ISO - %o",
+ 'status_blocksize': 160,
+ 'bitrate_factor': 1,
+ 'status_start': "%",
+ 'percent_fkt': r"""
+s = string.split(i['buf'], '\r')
+if len(s) >= 2: s=s[-2]
+if len(s) == 1: s=s[0]
+if string.find(s, "%") >= 0: # status reporting starts here
+ y = string.split(s, "/")
+ y1 = string.split(y[1], "(")[0]
+ percent = float(y[0]) / float(y1) * 100.0
+elif string.find(s, "Frame:") >= 0: # older versions, like 3.13
+ y = string.split(s, "/")
+ y0 = string.split(y[0], "[")[-1]
+ y1 = string.split(y[1], "]")[0]
+ percent = float(y0) / float(y1) * 100.0
+else:
+ percent = 0
+""",
+ },
+
+ 'gogo': { # Thanks to José Antonio Pérez Sánchez for the vbr and otf presets
+ 'type': "encoder",
+ 'target': "mp3",
+ 'inverse-quality': 1,
+ 'cmd': "gogo %i %o -b %r",
+ 'vbr-cmd': "gogo %i %o -v %q",
+ 'otf-cmd': "gogo stdin %o -b %r",
+ 'vbr-otf-cmd': "gogo stdin %o -v 4",
+ 'status_blocksize': 160,
+ 'bitrate_factor': 1,
+ 'status_start': "%",
+ 'percent_fkt': r"""
+s = string.split(i['buf'], '\r')
+if len(s) >= 2: s=s[-2]
+if len(s) == 1: s=s[0]
+if string.find(s, "%") >= 0: # status reporting starts here
+ s = replace(s, "\000", " ")
+ y = string.split(s, "/")
+ y0 = string.split(y[0], "{")[-1]
+ y1 = string.split(y[1], "}")[0]
+ percent = float(y0) / float(y1) * 100.0
+else:
+ percent = 0
+""",
+ },
+
+ 'bladeenc': {
+ 'type': "encoder",
+ 'target': "mp3",
+ 'cmd': "bladeenc %i %o -br %r",
+ 'status_blocksize': 180,
+ 'bitrate_factor': 1000,
+ 'status_start': "%",
+ 'percent_fkt': r"""
+s = string.split(i['buf'], '\r')
+if len(s) >= 2: s=s[-2]
+if string.find(s, "Status:") != -1:
+ y = string.split(s[8:])
+ percent = float(string.split(y[0], '%')[0])
+else:
+ percent = 0
+""",
+ },
+
+# xing definitions kindly provided by Sebastian Weber
+ 'xing': {
+ 'type': "encoder",
+ 'target': "mp3",
+ 'cmd': "xingmp3enc -B %r %i %o",
+ 'vbr-cmd': "xingmp3enc -V 100 %i %o",
+ 'otf-cmd': "xingmp3enc -b %r -- %o",
+ 'vbr-otf-cmd': "xingmp3enc -V 100 -- %o",
+ 'status_blocksize': 160,
+ 'bitrate_factor': 1,
+ 'status_start': "%",
+ 'percent_fkt': r"""
+s = string.split(i['buf'], '\r')
+if len(s) >= 2: s = s[-2]
+if string.find(s, "ETA:") != -1:
+ y = string.strip(string.split(s, '%')[0])
+ if len(y) == 0:
+ percent = 0
+ else:
+ percent = float(y)
+else:
+ percent = 0
+""",
+ },
+
+ 'flac': {
+ 'type': "encoder",
+ 'target': "flac",
+ 'vbr-cmd': "flac -o %o %i",
+ 'vbr-otf-cmd': "flac -fr -fb -fc 2 -fp 16 -fs 44100 -o %o",
+ 'status_blocksize': 160,
+ 'status_start': "%",
+ 'percent_fkt': r"""
+s = string.split(i['buf'], '\r')
+if len (s) >= 2: s = s[-2]
+if len (s) == 1: s = s[0]
+y0 = string.rfind(s, ": ")
+y1 = string.find (s, "%", y0)
+if y0 != -1 and y1 != -1:
+ try:
+ percent = float(s[y0 + 1:y1])
+ except ValueError:
+ percent = 0
+else:
+ percent = 0
+""",
+ },
+
+ 'mppenc': {
+ 'type': "encoder",
+ 'target': "mpc",
+ 'can_tag': 0,
+ 'vbr-cmd': "mppenc --standard %i %o",
+ #'vbr-otf-cmd': "mppenc --standard - %o", # doesn't work, needs WAVE
+ 'status_blocksize': 160,
+ 'status_start': "-.-",
+ 'percent_fkt': r"""
+s = string.split(i['buf'], '\r')
+if len(s) >= 3:
+ s = s[-3]
+ s = string.split(string.strip(s))
+ if len(s) >= 3 and s[2] == "kbps" and s[0] != "-.-":
+ percent = float(s[0])
+ else:
+ percent = 0
+""",
+ },
+
+ 'cdparanoia': {
+ 'filters': [[r'\n', r'\r'], [r'(\r)+', r'\r'], [r'(Done\.\r)+', r'Done.\r']],
+ 'type': "ripper",
+ 'cmd': "cdparanoia --abort-on-skip -d %d %n %o",
+ 'otf-cmd': "cdparanoia --abort-on-skip -e -d %d %n -R -",
+ 'status_blocksize': 500,
+ 'status_start': "%",
+ 'status_fkt': r"""
+# (== PROGRESS == [ | 013124 00 ] == :^D * ==)
+# (== PROGRESS == [ > .| 011923 00 ] == :-) . ==)
+tmp = string.split(i['buf'], "\r")
+if len(tmp) >= 2:
+ tmp = tmp[-2] + " "
+ new_status = tmp[17:48] + tmp[49:69] # 68->69 because of newer version
+else:
+ new_status = "Cannot parse status"
+""",
+ 'otf-status_fkt': r"""
+buf = i['buf']
+tmp = string.split(buf, "\n")
+new_status = ""
+if len(tmp) >= 2:
+ tmp = string.split(tmp[-2], " @ ")
+ if tmp[0] == "##: -2 [wrote]":
+ percent = (float(tmp[1]) - (i['track'][START] * CDDA_BLOCKSIZE / 2.0)) / (i['track'][LEN] * CDDA_BLOCKSIZE / 2.0) * 100.0
+ new_status = "[otf - reading, %2i%%]" % percent
+""",
+ 'final_status_fkt': r"""
+last_status="0123456789012345 [ -- error decoding status -- ]" # fallback
+if 0 and cf['_debug']: # disabled for now
+ import jack_version
+ tmpf=open("%s.debug.%02d.txt" % (jack_version.prog_name, exited_proc['track'][NUM]), "w")
+ tmpf.write(exited_proc['buf'])
+ del tmpf
+tmps = string.split(exited_proc['buf'], '\r')
+tmps.reverse()
+for tmp in tmps:
+ if string.find(tmp, "PROGRESS") != -1:
+ last_status = tmp
+ break
+final_status = ("%sx" % jack_functions.pprint_speed(speed)) + last_status[16:48] + "]"
+""",
+ 'otf-final_status_fkt': r"""
+final_status = "[otf - done]"
+""",
+ #'toc': 1, # we can't generate correct freedb IDs with cdparanoia.
+ 'toc_cmd': "cdparanoia -d %d -Q 2>&1",
+ 'toc_fkt': r"""
+while l:
+ l = string.rstrip(l)
+ if l and l[0:5] == "TOTAL":
+ start = 0
+ if l and l == '=' * (len(l)):
+ start = 1
+ elif l and start:
+ l = string.split(l, '.', 1)
+ num = int(l[0])
+ l = string.split(l[1])
+ erg.append([num, int(l[0]), int(l[2]), l[4] == 'OK', l[5] == 'yes', int(l[6]), 1, cf['_bitrate'], cf['_name'] % num])
+ l = p.readline()
+""",
+ },
+
+ 'cdda2wav': {
+ 'type': "ripper",
+ 'cmd': "cdda2wav --no-infofile -H -v 1 -D %d -O wav -t %n %o",
+ 'status_blocksize': 200,
+ 'status_start': "percent_done:",
+ 'status_fkt': r"""
+tmp = string.split(i['buf'], "\r")
+if len(tmp) >= 2:
+ if string.find(tmp[-2], '%') != -1:
+ new_status = "ripping: " + string.strip(tmp[-2])
+ else:
+ new_status = "waiting..."
+else:
+ new_status = "Cannot parse status"
+""",
+ 'final_status_fkt': r"""
+final_status = ("%s" % jack_functions.pprint_speed(speed)) + "x [ DAE done with cdda2wav ]"
+""",
+ 'toc': 1,
+ 'toc_cmd': "cdda2wav --no-infofile -D %d -J -v toc --gui 2>&1",
+ 'toc_fkt': r"""
+while 1:
+ l = p.readline()
+ if not l:
+ break
+ if l[0] == "T" and l[1] in string.digits and l[2] in string.digits and l[3] == ":":
+ num, start, length, type, pre, copy, ch, dummy = string.split(l)[:8]
+ if type == "audio":
+ num = int(num[1:3])
+ start = int(start)
+ length = string.replace(length,".", ":")
+ length = timestrtoblocks(length)
+ pre = pre == "pre-emphasized"
+ copy = copy != "copydenied"
+ ch = [ "none", "mono", "stereo", "three", "quad" ].index(ch)
+ erg.append([num, length, start, copy, pre, ch, 1, cf['_bitrate'], cf['_name'] % (num + 1)])
+""",
+ 'toc_cmd_old': "cdda2wav --no-infofile -D %D -J -v 35 2>&1",
+ 'toc_fkt_old': r"""
+new_c2w = 0
+new_toc1 = 0
+new_toc2 = 0
+new_lengths = []
+new_starts = []
+while 1:
+ l = p.readline()
+ if not l:
+ break
+ l = string.strip(l)
+ # new cdda2wav
+ if starts_with(l, "Table of Contents: total tracks"):
+ new_toc1 = 1
+ continue
+
+ if starts_with(l, "Table of Contents: starting sectors"):
+ new_toc2 = 1
+ new_toc1 = 0
+ new_c2w = 1
+ continue
+
+ if new_toc2 and l and l[0] in string.digits:
+ l = string.split(l, "(")[1:]
+ for i in l:
+ x = string.split(i, ")")[0]
+ x = string.strip(x)
+ try:
+ new_starts.append(int(x))
+ except:
+ pass
+ continue
+
+ if new_toc1 and l and l[0] in string.digits:
+ l = string.split(l, "(")[1:]
+ for i in l:
+ if string.find(i, ":") >= 0:
+ x = string.split(i, ")")[0]
+ x = replace(x, ".", ":")
+ new_lengths.append(timestrtoblocks(x))
+ continue
+
+ # old cdda2wav
+ if l and l[0:11] == "Album title":
+ start = 1
+ elif l and start:
+ l = string.split(l)
+ if l[0] == "Leadout:":
+ start = 0
+ else:
+ num = int(l[0][1:3])
+ if l[6] == "stereo":
+ channels = 2
+ elif l[6] == "mono":
+ channels = 1
+ else:
+ channels = 0
+ t_start = int(l[1])
+ msf = string.split(l[2], ":")
+ sf = string.split(msf[1], ".")
+ t_length = int(sf[1]) + int(sf[0]) * 75 + int(msf[0]) * 60 * 75
+ erg.append([num, t_length, t_start, l[5] == "copyallowed", l[4] != "linear", channels, 1, cf['_bitrate'], cf['_name'] % num])
+if new_c2w and len(new_lengths) == len(new_starts) - 1:
+ for i in range(min(len(new_lengths), len(new_starts))): # this provokes an error if the lists are of different length
+ erg.append([i + 1, new_lengths[i], new_starts[i], 0, 0, 2, 1, cf['_bitrate'], cf['_name'] % (i + 1)])
+""",
+ },
+
+ 'dagrab': {
+ 'type': "ripper",
+ 'cmd': "dagrab -d %d -f %o %n",
+ 'status_blocksize': 100,
+ 'status_start': "total:",
+ 'status_fkt': r"""
+tmp = string.split(i['buf'], "\r")
+if len(tmp) >= 2:
+ if string.find(tmp[-2], 'total:') != -1:
+ new_status = string.strip(tmp[-2])
+ else:
+ new_status = "waiting..."
+else:
+ new_status = "Cannot parse status"
+""",
+ 'final_status_fkt': r"""
+final_status = ("%s" % jack_functions.pprint_speed(speed)) + "x [ DAE done with dagrab ]"
+""",
+ 'toc': 1,
+ 'toc_cmd': "dagrab -d %d -i 2>&1",
+ 'toc_fkt': r"""
+while l:
+ l = string.strip(l)
+ if l and l[0:5] == "track":
+ start = 1
+ elif l and start:
+ l = string.split(l)
+ if l[3] == "leadout":
+ start = 0
+ else:
+ num = int(l[0])
+ channels = 2
+ copy = 0
+ pre = 0
+ t_start = int(l[1]) - 150
+ t_length = int(l[2])
+ erg.append([num, t_length, t_start, copy, pre, channels, 1, cf['_bitrate'], cf['_name'] % num])
+ l = p.readline()
+""",
+ },
+
+ 'tosha': {
+ 'type': "ripper",
+ 'cmd': "tosha -d %d -f wav -t %n -o %o",
+ 'status_blocksize': 100,
+ 'status_start': "total:",
+ 'status_fkt': r"""
+x = string.split(i['buf'], '\r')[-2]
+if string.find(x, 'total:') != -1:
+ new_status = string.strip(string.split(i['buf'], '\r')[-2])
+else:
+ new_status = "waiting..."
+""",
+ 'final_status_fkt': r"""
+final_status = ("%s" % jack_functions.pprint_speed(speed)) + "x [ DAE done with tosha ]"
+""",
+ 'toc': 1,
+ 'toc_cmd': "tosha -d %d -iq 2>&1",
+ 'toc_fkt': r"""
+while l:
+ l = string.rstrip(l)
+ if l:
+ l = string.split(l)
+ num = int(l[0])
+ erg.append([num, 1 + int(l[3]) - int(l[2]), int(l[2]), 0, 0, 2, 1, cf['_bitrate'], cf['_name'] % num])
+ l = p.readline()
+""",
+ },
+
+ 'CDDB.py': {
+ 'type': "toc-reader",
+ 'toc': 1,
+ 'toc_fkt': r"""
+import cdrom
+if not os.path.exists(cf['_cd_device']):
+ error("Device %s does not exist!" % cf['_cd_device'])
+if not os.access(cf['_cd_device'], os.R_OK):
+ error("You don't have permission to access device %s!" % cf['_cd_device'])
+try:
+ device = cdrom.open(cf['_cd_device'])
+ (first, last) = cdrom.toc_header(device)
+except cdrom.error, m:
+ error("Access of CD device %s resulted in error: %s" % (cf['_cd_device'], m[1]))
+
+toc = []
+for i in range(first, last + 1):
+ (min, sec, frame) = cdrom.toc_entry(device, i)
+ toc.append(min * 60 * 75 + sec * 75 + frame)
+(min, sec, frame) = cdrom.leadout(device)
+device.close()
+toc.append(min * 60 * 75 + sec * 75 + frame)
+for i in range(first, last + 1):
+ erg.append([i, toc[i - first + 1] - toc[i - first], toc[i - first] - toc[0], 0, 0, 2, 1, cf['_bitrate'], cf['_name'] % i])
+""",
+ }
+}
+
+helpers['lame-user'] = helpers['lame'].copy()
+helpers['lame-user'].update({'cmd': "lame --preset cbr %r --strictly-enforce-ISO %i %o",
+ 'vbr-cmd': "lame -V %q --vbr-new --nohist --strictly-enforce-ISO %i %o",
+ 'otf-cmd': "lame --preset cbr %r --strictly-enforce-ISO - %o",
+ 'vbr-otf-cmd': "lame -V %q --vbr-new --nohist --strictly-enforce-ISO - %o", })
+
+def init():
+ # import plugin
+ jack_plugins.import_helpers()
+
+ # compile exec strings
+ for h in helpers.keys():
+ for i in helpers[h].keys():
+ if i[-4:] == "_fkt":
+ helpers[h][i] = compile(helpers[h][i], '<string>', 'exec')
+
+ # compile filters
+ for h in helpers.keys():
+ if helpers[h].has_key('filters'):
+ newf = []
+ for i in helpers[h]['filters']:
+ newf.append([re.compile(i[0]), i[1]])
+ helpers[h]['filters'] = newf
diff --git a/jack_init.py b/jack_init.py
new file mode 100644
index 0000000..551a6cf
--- /dev/null
+++ b/jack_init.py
@@ -0,0 +1,68 @@
+### jack_init sanity check and initialization of various modules for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 2002-2003 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import string
+import sys
+import os
+from jack_globals import *
+from jack_generic import *
+
+try:
+ from fcntl import F_SETFL
+except:
+ from FCNTL import F_SETFL
+
+try:
+ from os import O_NONBLOCK
+except:
+ from FCNTL import O_NONBLOCK
+
+try:
+ from eyeD3 import eyeD3
+except:
+ print "Please install the eyeD3 module available from http://eyed3.nicfit.net/"
+ sys.exit(1)
+
+try:
+ import cdrom
+except:
+ print "Please install the CDDB module available at http://cddb-py.sourceforge.net"
+ print "Without it, you'll not be able to rip from CDs."
+
+ # want to see my favorite ugly hack of the day?
+ class dummy_cdrom:
+ def __init__(self):
+ pass
+ def open(self, dummy=None):
+ print "Cannot access cdrom device while the CDDB module is not installed. See above."
+ sys.exit(1)
+ cdrom = dummy_cdrom()
+
+try:
+ import ogg.vorbis
+except:
+ class dummy_ogg:
+ def __init__(self):
+ warning("ogg module not installed, ogg support disabled")
+ ogg = dummy_ogg()
+
+try:
+ import flac.metadata
+except ImportError:
+ flac = None
+
diff --git a/jack_m3u.py b/jack_m3u.py
new file mode 100644
index 0000000..7c41726
--- /dev/null
+++ b/jack_m3u.py
@@ -0,0 +1,44 @@
+### jack_m3u: generate a m3u playlist - a module for
+### jack - tag audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2003 Arne Zellentin <zarne@users.sf.net>
+
+### 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 jack_globals import *
+
+m3u = None
+wavm3u = None
+
+def init():
+ global m3u, wavm3u
+ m3u = []
+ wavm3u = []
+
+def add(file):
+ m3u.append(file)
+
+def add_wav(file):
+ wavm3u.append(file)
+ # fixeme - not written to a file yet
+
+def write(file="jack.m3u"):
+ if m3u and cf['_write_m3u']:
+ f = open(file, "w")
+ for i in m3u:
+ f.write(i)
+ f.write("\n")
+ f.close()
+
diff --git a/jack_main_loop.py b/jack_main_loop.py
new file mode 100755
index 0000000..e7d2880
--- /dev/null
+++ b/jack_main_loop.py
@@ -0,0 +1,481 @@
+# -*- coding: iso-8859-15 -*-
+### jack_main_loop: the main encoding loop for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2004 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import string
+import signal
+import select
+import time
+import sys
+import os
+
+import jack_functions
+import jack_ripstuff
+import jack_encstuff
+import jack_children
+import jack_display
+import jack_helpers
+import jack_targets
+import jack_workers
+import jack_status
+import jack_utils
+import jack_misc
+import jack_term
+
+from jack_globals import *
+
+def main_loop(mp3s_todo, wavs_todo, space, dae_queue, enc_queue, track1_offset):
+ global_error = 0 # remember if something went wrong
+ actual_load = -2 # this is always smaller than max_load
+ waiting_load = 0 # are we waiting for the load to drop?
+ waiting_space = 0 # are we waiting for disk space to be freed?
+ space_waiting = 0 # how much space _running_ subprocesses will consume
+ space_adjust = 0 # by how much space has been modified
+ blocked = 0 # we _try_ do detect deadlocks
+ cycles = 0 # it's sort of a timer
+ last_update = 0 # screen updates are done once per second
+ pause = 0 # no new encoders are started if pause==1
+ flags = "[ ]" # runtime controllable flags
+ enc_running = 0 # what is going on?
+ dae_running = 0 # what is going on?
+
+ rotate="/-\\|"
+ rotate_ball=" .o0O0o."
+ rot_cycle = len(rotate)
+ rot_ball_cycle = len(rotate_ball)
+ rot_count = 0
+ global_done = 0
+ first_encoder = 1
+ ext = jack_targets.targets[jack_helpers.helpers[cf['_encoder']]['target']]['file_extension']
+ global_blocks = jack_functions.tracksize(wavs_todo)[BLOCKS] + jack_functions.tracksize(mp3s_todo)[BLOCKS]
+
+
+ #####################
+ ### MAIN LOOP ###
+ #####################
+
+ global_start = time.time()
+ while mp3s_todo or enc_queue or dae_queue or enc_running or dae_running:
+
+ # feed in the WAVs which have been there from the start
+
+ if mp3s_todo and jack_functions.tracksize(mp3s_todo[0])[ENC] < space:
+ waiting_space = 0
+ enc_queue.append(mp3s_todo[0])
+ space = space - jack_functions.tracksize(mp3s_todo[0])[ENC]
+ jack_status.enc_stat_upd(mp3s_todo[0][NUM], "waiting for encoder.")
+ mp3s_todo = mp3s_todo[1:]
+
+ # start new DAE subprocess
+
+ elif (len(enc_queue) + enc_running) < (cf['_read_ahead'] + cf['_encoders']) and dae_queue and dae_running < cf['_rippers'] and ((jack_functions.tracksize(dae_queue[0])[BOTH] < space) or (cf['_only_dae'] and jack_functions.tracksize(dae_queue[0])[WAV] < space) or (cf['_otf'] and jack_functions.tracksize(dae_queue[0])[ENC] < space)):
+ waiting_space = 0
+ this_is_ok = 1
+ if pause:
+ this_is_ok = 0
+ jack_status.dae_stat_upd(dae_queue[0][NUM], "Paused. Press 'c' to continue.")
+ elif cf['_rip_from_device']:
+ all_tracks_on_cd = jack_functions.gettoc(cf['_toc_prog'])
+ if not jack_utils.cmp_toc_cd(jack_ripstuff.all_tracks_orig, all_tracks_on_cd, what=(NUM, LEN)):
+ while dae_queue:
+ track = dae_queue[0]
+ dae_queue = dae_queue[1:]
+ jack_status.dae_stat_upd(track[NUM], "Wrong disc - aborting this track")
+ global_error = global_error + 1
+ this_is_ok = 0
+ if this_is_ok:
+ if cf['_only_dae']:
+ space_waiting = space_waiting + jack_functions.tracksize(dae_queue[0])[WAV]
+ space = space - jack_functions.tracksize(dae_queue[0])[WAV]
+ elif cf['_otf']:
+ space_waiting = space_waiting + jack_functions.tracksize(dae_queue[0])[ENC]
+ space = space - jack_functions.tracksize(dae_queue[0])[ENC]
+ else:
+ space_waiting = space_waiting + jack_functions.tracksize(dae_queue[0])[BOTH]
+ space = space - jack_functions.tracksize(dae_queue[0])[BOTH]
+ dae_running = dae_running + 1
+ track = dae_queue[0]
+ dae_queue = dae_queue[1:]
+ if cf['_otf']:
+ # note: image_reader can't do otf at the moment.
+ jack_status.dae_stat_upd(track[NUM], ":DAE: waiting for status report...")
+ if cf['_encoder'] in ("lame", "gogo", "flac", "mppenc"):
+ jack_status.enc_stat_upd(track[NUM], "[no otf status for %s]" % cf['_encoder'])
+ else:
+ jack_status.enc_stat_upd(track[NUM], "waiting for encoder.")
+ enc_running = enc_running + 1
+ if first_encoder:
+ first_encoder = 0
+ global_start = time.time()
+ data = jack_workers.start_new_otf(track, cf['_ripper'], cf['_encoder'])
+ jack_children.children.append(data['rip'])
+ jack_children.children.append(data['enc'])
+ else:
+ if jack_status.enc_status[track[NUM]]:
+ jack_status.enc_cache[track[NUM]] = jack_status.enc_status[track[NUM]]
+ jack_status.enc_stat_upd(track[NUM], "[...]")
+ jack_status.dae_stat_upd(track[NUM], ":DAE: waiting for status report...")
+ if cf['_rip_from_device']:
+ jack_children.children.append(jack_workers.start_new_ripper(track, cf['_ripper']))
+ elif cf['_image_file']:
+ jack_children.children.append(jack_workers.ripread(track, track1_offset))
+ else:
+ jack_status.dae_stat_upd(track[NUM], ":?AE: don't know how to rip this!")
+
+ # start new encoder subprocess
+
+ if enc_queue and enc_running < cf['_encoders']:
+ if jack_functions.tracksize(enc_queue[0])[ENC] <= space + space_waiting:
+ waiting_space = 0
+ actual_load = jack_misc.loadavg()
+ if actual_load < cf['_max_load']:
+ waiting_load = 0
+ enc_running = enc_running + 1
+ track = enc_queue[0]
+ enc_queue = enc_queue[1:]
+ jack_status.enc_stat_upd(track[NUM], "waiting for encoder...")
+ jack_children.children.append(jack_workers.start_new_encoder(track, cf['_encoder']))
+ if first_encoder:
+ first_encoder = 0
+ global_start = time.time()
+ else:
+ waiting_load = 1
+
+ # check for subprocess output
+
+ readfd=[sys.stdin.fileno()]
+ for i in jack_children.children:
+ readfd.append(i['fd'])
+ try:
+ rfd, wfd, xfd = select.select(readfd, [], [], cf['_update_interval'])
+ except:
+ rfd, wfd, xfd = [], [], []
+ jack_term.tmod.sig_winch_handler(None, None)
+
+ # check for keyboard commands
+
+ if sys.stdin.fileno() in rfd:
+ last_update = last_update - cf['_update_interval']
+ cmd = jack_term.tmod.getkey()
+ sys.stdin.flush()
+ if string.upper(cmd) == "Q":
+ jack_display.exit()
+ elif not pause and string.upper(cmd) == "P":
+ pause = 1
+ flags = flags[:1] + "P" + flags[2:]
+ elif string.upper(cmd) == "C" or pause and string.upper(cmd) == "P":
+ pause = 0
+ flags = flags[:1] + " " + flags[2:]
+ elif not flags[3] == "e" and string.upper(cmd) == "E":
+ for i in jack_children.children:
+ if i['type'] == "encoder":
+ os.kill(i['pid'], signal.SIGSTOP)
+ flags = flags[:3] + "e" + flags[4:]
+ elif flags[3] == "e" and string.upper(cmd) == "E":
+ for i in jack_children.children:
+ if i['type'] == "encoder":
+ os.kill(i['pid'], signal.SIGCONT)
+ flags = flags[:3] + " " + flags[4:]
+ elif not flags[2] == "r" and string.upper(cmd) == "R":
+ for i in jack_children.children:
+ if i['type'] == "ripper":
+ os.kill(i['pid'], signal.SIGSTOP)
+ flags = flags[:2] + "r" + flags[3:]
+ elif flags[2] == "r" and string.upper(cmd) == "R":
+ for i in jack_children.children:
+ if i['type'] == "ripper":
+ os.kill(i['pid'], signal.SIGCONT)
+ flags = flags[:2] + " " + flags[3:]
+ elif string.upper(cmd) == "U":
+ cycles = 29 # do periodic stuff _now_
+ else:
+ jack_term.tmod.move_pad(cmd)
+ if cmd == 'KEY_RESIZE':
+ continue
+ last_update = time.time()
+
+ # read from file with activity
+ for i in jack_children.children:
+ if i['fd'] in rfd:
+ if os.uname()[0] == "Linux" and i['type'] != "image_reader":
+ try:
+ x = i['file'].read()
+ except IOError:
+ pass
+ else:
+ read_chars = 0
+ x = ""
+ while read_chars < jack_helpers.helpers[i['prog']]['status_blocksize']:
+ try:
+ xchar = i['file'].read(1)
+ except IOError:
+ break
+ x = x + xchar
+ read_chars = read_chars + 1
+ try:
+ rfd2, wfd2, xfd2 = select.select([i['fd']], [], [], 0.0)
+ except:
+ rfd2, wfd2, xfd2 = [], [], []
+ jack_term.tmod.sig_winch_handler(None, None)
+ if i['fd'] not in rfd2:
+ break
+ # put read data into child's buffer
+ i['buf'] = i['buf'] + x
+
+ if jack_helpers.helpers[i['prog']].has_key('filters'):
+ for fil in jack_helpers.helpers[i['prog']]['filters']:
+ i['buf'] = fil[0].sub(fil[1], i['buf'])
+
+ i['buf'] = i['buf'][-jack_helpers.helpers[i['prog']]['status_blocksize']:]
+
+ # check for exiting child processes
+ if jack_children.children:
+ respid, res = os.waitpid(-1, os.WNOHANG)
+ if respid != 0:
+ last_update = last_update - cf['_update_interval'] # ensure info is printed
+ new_ch = []
+ exited_proc = []
+ for i in jack_children.children:
+ if i['pid'] == respid:
+ if exited_proc != []:
+ error("pid " + `respid` + " found at multiple child processes")
+ exited_proc = i
+ else:
+ new_ch.append(i)
+ if not exited_proc:
+ error("unknown process (" + `respid` + ") has exited")
+ jack_children.children = new_ch
+ x = ""
+ try:
+ x = exited_proc['file'].read()
+ except IOError:
+ pass
+ except ValueError:
+ pass
+ exited_proc['buf'] = (exited_proc['buf'] + x)[-jack_helpers.helpers[exited_proc['prog']]['status_blocksize']:]
+ exited_proc['file'].close()
+
+ global_error = global_error + res
+ track = exited_proc['track']
+ num = track[NUM]
+ stop_time = time.time()
+ speed = ( track[LEN] / float(CDDA_BLOCKS_PER_SECOND)) / ( stop_time - exited_proc['start_time'] )
+
+ if exited_proc['type'] in ("ripper", "image_reader"):
+ dae_running = dae_running - 1
+ if cf['_exec_when_done'] and exited_proc['type'] == "ripper" and dae_running == 0 and len(dae_queue) == 0:
+ os.system(cf['_exec_rip_done'])
+ if not res:
+ if not exited_proc['otf']:
+ if os.path.exists(track[NAME] + ".wav"):
+ if jack_functions.tracksize(track)[WAV] != jack_utils.filesize(track[NAME] + ".wav"):
+ res = 242
+ jack_status.dae_stat_upd(num, jack_status.get_2_line(exited_proc['buf']))
+ else:
+ jack_status.dae_stat_upd(num, jack_status.get_2_line(exited_proc['buf']))
+ res = 243
+ global_error = global_error + res
+ if res and not cf['_sloppy']:
+ if os.path.exists(track[NAME] + ".wav"):
+ os.remove(track[NAME] + ".wav")
+ space = space + jack_functions.tracksize(track)[WAV]
+ if cf['_otf']:
+ os.kill(exited_proc['otf-pid'], signal.SIGTERM)
+ if os.path.exists(track[NAME] + ext):
+ os.remove(track[NAME] + ext)
+ space = space + jack_functions.tracksize(track)[ENC]
+ if not cf['_otf'] and not cf['_only_dae'] and track not in jack_encstuff.mp3s_ready:
+ space = space + jack_functions.tracksize(track)[ENC]
+ jack_status.dae_stat_upd(num, 'DAE failed with status ' + `res` + ", wav removed.")
+ else:
+ if exited_proc['type'] == "image_reader":
+ jack_status.dae_stat_upd(num, jack_status.get_2_line(exited_proc['buf']))
+ else:
+ if exited_proc['otf'] and jack_helpers.helpers[exited_proc['prog']].has_key('otf-final_status_fkt'):
+ exec(jack_helpers.helpers[exited_proc['prog']]['otf-final_status_fkt']) in globals(), locals()
+ else:
+ last_status = None # (only used in cdparanoia)
+ exec(jack_helpers.helpers[exited_proc['prog']]['final_status_fkt']) in globals(), locals()
+ jack_status.dae_stat_upd(num, final_status)
+ if jack_status.enc_cache[num]:
+ jack_status.enc_stat_upd(num, jack_status.enc_cache[num])
+ jack_status.enc_cache[num] = ""
+ jack_functions.progress(num, "dae", jack_status.dae_status[num])
+ if not cf['_otf'] and not cf['_only_dae'] and track not in jack_encstuff.mp3s_ready:
+ if waiting_space:
+ mp3s_todo.append(track)
+ space = space + jack_functions.tracksize(track)[ENC]
+ else:
+ jack_status.enc_stat_upd(num, 'waiting for encoder.')
+ enc_queue.append(track)
+ space_waiting = space_waiting - jack_functions.tracksize(track)[WAV]
+
+ elif exited_proc['type'] == "encoder":
+ enc_running = enc_running - 1
+ # completed vbr files shouldn't be to small, but this still
+ # caused confusion so again, vbr is an exception:
+ if not cf['_vbr'] and not res and jack_functions.tracksize(track)[ENC] * 0.99 > jack_utils.filesize(track[NAME] + ext):
+ res = 242
+ global_error = global_error + res
+ if res:
+ global_blocks = global_blocks - exited_proc['track'][LEN]
+ global_start = global_start + exited_proc['elapsed'] / (enc_running + 1)
+ if global_start > time.time():
+ global_start = time.time()
+ if os.path.exists(track[NAME] + ext):
+ # mp3enc doesn't report errors when out of disk space...
+ os.remove(track[NAME] + ext)
+ space = space + jack_functions.tracksize(track)[ENC]
+ jack_status.enc_stat_upd(num, 'coding failed, err#' + `res`)
+ else:
+ global_done = global_done + exited_proc['track'][LEN]
+ if cf['_vbr']:
+ jack_status.enc_stat_upd(num, "[coding @" + '%s' % jack_functions.pprint_speed(speed) + "x done, %03.0fkbit]" % ((jack_utils.filesize(track[NAME] + ext) * 0.008) / (track[LEN] / 75.0)))
+ else:
+ jack_status.enc_stat_upd(num, "[coding @" + '%s' % jack_functions.pprint_speed(speed) + "x done, mp3 OK]")
+ if not cf['_otf'] and not cf['_keep_wavs']:
+ os.remove(track[NAME] + ".wav")
+ space = space + jack_functions.tracksize(track)[WAV]
+ jack_functions.progress(num, "enc", `track[RATE]`, jack_status.enc_status[num])
+
+ else:
+ error("child process of unknown type (" + exited_proc['type'] + ") exited")
+ if global_error:
+ jack_display.smile = " :-["
+
+ if last_update + cf['_update_interval'] <= time.time():
+ last_update = time.time()
+
+ # interpret subprocess output
+
+ for i in jack_children.children:
+ if i['type'] == "ripper":
+ if len(i['buf']) == jack_helpers.helpers[i['prog']]['status_blocksize']:
+ if i['otf'] and jack_helpers.helpers[i['prog']].has_key('otf-status_fkt'):
+ exec(jack_helpers.helpers[i['prog']]['otf-status_fkt']) in globals(), locals()
+ else:
+ exec(jack_helpers.helpers[i['prog']]['status_fkt']) in globals(), locals()
+ if new_status:
+ jack_status.dae_stat_upd(i['track'][NUM], ":DAE: " + new_status)
+
+ elif i['type'] == "encoder":
+ if len(i['buf']) == jack_helpers.helpers[i['prog']]['status_blocksize']:
+ tmp_d = {'i': i.copy(), 'percent': 0}
+ try:
+ exec(jack_helpers.helpers[i['prog']]['percent_fkt']) in globals(), tmp_d
+ except:
+ tmp_d['percent'] = 0
+ debug("error in percent_fkt of %s." % `i`)
+ i['percent'] = tmp_d['percent']
+ if i['percent'] > 0:
+ i['elapsed'] = time.time() - i['start_time']
+ speed = ((i['track'][LEN] / float(CDDA_BLOCKS_PER_SECOND)) * ( i['percent'] / 100 )) / i['elapsed']
+ eta = (100 - i['percent']) * i['elapsed'] / i['percent']
+ eta_ms = "%02i:%02i" % (eta / 60, eta % 60)
+ jack_status.enc_stat_upd(i['track'][NUM], '%2i%% done, ETA:%6s, %sx' % (i['percent'], eta_ms, jack_functions.pprint_speed(speed)))
+ #jack_term.tmod.dae_stat_upd(i['track'][NUM], None, i['percent'])
+
+ elif i['type'] == "image_reader":
+ line = string.strip(jack_status.get_2_line(i['buf']))
+ jack_status.dae_stat_upd(i['track'][NUM], line)
+ if line.startswith("Error"):
+ global_error = global_error + 1
+
+ else:
+ error("unknown subprocess type \"" + i['type'] + "\".")
+
+ cycles = cycles + 1
+ if cycles % 30 == 0:
+ if cf['_recheck_space'] and not cf['space_from_argv']['history'][-1][0] == "argv":
+ actual_space = jack_functions.df()
+ if space_adjust:
+ diff = actual_space - space
+ if diff > space_adjust:
+ space = space + space_adjust
+ space_adjust = 0
+ waiting_space = 0
+ else:
+ space = space + diff
+ space_adjust = space_adjust - diff
+ else:
+ if actual_space < space:
+ space_adjust = space - actual_space
+ space = actual_space
+
+ if space_adjust and enc_running == 0 and dae_running == 0:
+ waiting_space = waiting_space + 1
+ if not waiting_space >= 2 and not waiting_load and enc_running == 0 and dae_running == 0:
+ blocked = blocked + 1
+ else:
+ blocked = 0
+
+ total_done = global_done
+ for i in jack_children.children:
+ total_done = total_done + (i['percent'] / 100) * i['track'][LEN]
+ elapsed = time.time() - global_start
+ if global_blocks > 0:
+ percent = total_done / global_blocks
+ else:
+ percent = 0
+ if percent > 0 and elapsed > 40:
+ eta = ((1 - percent) * elapsed / percent)
+ eta_hms = " ETA=%i:%02i:%02i" % (eta / 3600, (eta % 3600) / 60, eta % 60)
+ else:
+ eta_hms = ""
+
+ if string.strip(flags[1:-1]):
+ print_flags = " " + flags
+ else:
+ print_flags = ""
+ if dae_running:
+ rot = rotate_ball[rot_count % rot_ball_cycle]
+ else:
+ rot = rotate[rot_count % rot_cycle]
+ rot_count = rot_count + 1
+
+ # print status
+
+ if blocked > 2:
+ jack_display.special_line = " ...I feel blocked - quit with 'q' if you get bored... "
+ if blocked > 5:
+ space = jack_functions.df() - cf['_keep_free']
+ elif waiting_load and waiting_space >= 2:
+ jack_display.special_line = " ...waiting for load (%.2f)" % actual_load + ") < %.2f" % cf['_max_load'] + " and for " + jack_functions.pprint_i(space_adjust, "%i %sBytes") + " to be freed... "
+ elif waiting_space >= 2:
+ jack_display.special_line = " ...waiting for " + jack_functions.pprint_i(space_adjust, "%i %sBytes") + " to be freed.... "
+ elif waiting_load:
+ jack_display.special_line = " ...waiting for load (%.2f) to drop below %.2f..." % (actual_load, cf['_max_load'])
+ else:
+ jack_display.special_line = None
+
+ jack_display.bottom_line = "(" + rot + ") " \
+ + "SPACE:" * (space_adjust != 0) \
+ + "space:" * (space_adjust == 0) \
+ + jack_functions.pprint_i(space, "%i%sB") \
+ + (" waiting_WAVs:%02i" % len(enc_queue)) \
+ + " DAE:" + `cf['_rippers'] - dae_running` + "+" + `dae_running` \
+ + " ENC:" + `cf['_encoders'] - enc_running` + "+" + `enc_running` \
+ + eta_hms \
+ + " errors: " + `global_error` \
+ + jack_display.smile + print_flags
+
+ jack_term.tmod.update(jack_display.special_line, jack_display.bottom_line)
+
+ return global_error
+
+# end of main loop #########################################################
diff --git a/jack_misc.py b/jack_misc.py
new file mode 100644
index 0000000..c5e8ff5
--- /dev/null
+++ b/jack_misc.py
@@ -0,0 +1,83 @@
+### jack_misc - misc stuff for
+### jack - extract audio from a CD and MP3ify it using 3rd party software
+### Copyright (C) 1999,2000 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import string, types
+import sys
+import os
+
+def id(x):
+ return x
+
+def multi_replace(s, rules, filter = id):
+ "like string.replace but take list (('from0', 'to0'), ('from1', 'to1'))..."
+ # currently all from must be like %x (a percent sign follow by single char.
+ res = ""
+ maybe = 0
+ for i in s:
+ if maybe:
+ maybe = 0
+ found = 0
+ for j in rules:
+ if ("%" + i) == j[0]:
+ res = res[:-1] + filter(j[1])
+ found = 1
+ if found:
+ continue
+ maybe = 0
+ if i == "%":
+ maybe = 1
+ res = res + i
+ return res
+
+def safe_int(number, message):
+ try:
+ return int(number)
+ except ValueError:
+ print message
+ sys.exit(1)
+
+class dict2(dict):
+ def rupdate(self, d2, where):
+ for i in d2.keys():
+ if self.__contains__(i):
+ new = self.__getitem__(i)
+ if new['val'] != d2[i]['val']:
+ new.update(d2[i])
+ new['history'].append([where, new['val']])
+ dict.__setitem__(self, i, new)
+ def __getitem__(self, y):
+ if type(y) == types.StringType and y and y[0] == "_":
+ return dict.__getitem__(self, y[1:])['val']
+ else:
+ return dict.__getitem__(self, y)
+
+ def __setitem__(self, y, x):
+ if type(y) == types.StringType and y and y[0] == "_":
+ self[y[1:]]['val'] = x
+ #return dict.__setitem__(self, y[1:])['val']
+ else:
+ return dict.__setitem__(self, y, x)
+
+def loadavg():
+ "extract sysload from /proc/loadavg, linux only (?)"
+ try:
+ f = open("/proc/loadavg", "r")
+ load = float(string.split(f.readline())[0])
+ return load
+ except:
+ return -1
diff --git a/jack_mp3.py b/jack_mp3.py
new file mode 100644
index 0000000..4b2c1b5
--- /dev/null
+++ b/jack_mp3.py
@@ -0,0 +1,370 @@
+### jack_mp3 - mp3 layer stuff for
+### jack - extract audio from a CD and MP3ify it using 3rd party software
+### Copyright (C) 1999-2001 Arne Zellentin <zarne@users.sf.net>
+### Transformed from mp3info (c) Thorvald Natvig
+
+### 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
+
+DEBUG = 0
+
+import string, types
+from os import stat
+from stat import ST_SIZE
+from StringIO import StringIO
+
+global warnings
+global F, f, xing, id3v2, start_offset
+
+# constants
+mode_names = ["stereo", "j-stereo", "dual-ch", "single-ch", "multi-ch"]
+layer_names = ["I", "II", "III"]
+version_names = ["MPEG-1", "MPEG-2 LSF", "MPEG-2.5"]
+version_nums = ["1", "2", "2.5"]
+bitrates = \
+ [[[ 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448],\
+ [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384],\
+ [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]],\
+ [[0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256],\
+ [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160],\
+ [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]],\
+ [[0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256],\
+ [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160],\
+ [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]]]
+s_freq = \
+ [[44100, 48000, 32000, 0],\
+ [22050, 24000, 16000, 0],\
+ [11025, 8000, 8000, 0]]
+
+bpf_fact = [0, 12000, 144000, 144000]
+
+# Xing stuff
+FRAMES_FLAG = 0x0001
+BYTES_FLAG = 0x0002
+TOC_FLAG = 0x0004
+VBR_SCALE_FLAG= 0x0008
+
+def get_l4 (s):
+ return reduce(lambda a,b: ((a<<8) + b), map(long, map(ord, s)))
+
+def decode_xing():
+ global F, f, xing
+ # defaults:
+ frames = bytes = scale = toc = None
+ where = f.tell()
+ try:
+ if 1:
+ if DEBUG: print "Xing at", where
+ # 32-bit fields; "Xing", flags, frames, bytes, 100 toc
+ flags = get_l4(f.read(4))
+ if flags & FRAMES_FLAG:
+ frames = get_l4(f.read(4))
+ if flags & BYTES_FLAG:
+ bytes = get_l4(f.read(4))
+ if flags & TOC_FLAG:
+ toc = map(lambda x: ord(x), f.read(100))
+ for j in range(len(toc)):
+ toc[j] = int(toc[j] / 256.0 * bytes)
+ if flags & VBR_SCALE_FLAG:
+ scale = get_l4(f.read(4))
+
+ if DEBUG>2:
+ print "Xing: frames=%i" % frames
+ print "Xing: bytes=%i" % bytes
+ print "Xing: toc=", toc
+ print "Xing: scale=%i" % scale
+
+ xing = {}
+ if frames: xing['x_frames'] = frames
+ if bytes: xing['x_bytes'] = bytes
+ if toc: xing['x_toc'] = toc
+ if scale: xing['x_scale'] = scale
+ xing['x_header_at'] = where - 4
+
+ else:
+ return None
+
+ except:
+ if DEBUG:
+ import traceback
+ traceback.print_exc()
+ f.seek(where + 1)
+ return None
+
+def find_xing(framelen):
+ "read (and skip) the Xing header if present"
+ where = f.tell()
+ x = f.read(framelen)
+ pos = string.find(x, "Xing")
+ if pos >= 0:
+ f.seek(where + pos + 4)
+ decode_xing()
+ f.seek(where + framelen)
+ else:
+ f.seek(where)
+
+def reread_f(size):
+ global F, f
+ pos = f.tell()
+ f.seek(F.offset)
+ if DEBUG: print "reread_f: now at", f.tell()
+ F_offset = F.offset
+ F_file = F.file
+ F = StringIO(f.read(F.len + size + 10))
+ F.file = F_file
+ F.offset = F_offset
+ f.seek(pos)
+
+def handle_id3v2(version, flags, data):
+ global id3v2
+ if DEBUG: print "ID3:", `version`, `flags`, len(data)
+ id3v2 = {}
+ id3v2['version'] = tuple(version)
+
+def read_id3v2():
+ #$49 44 33 yy yy xx zz zz zz zz ; yy < $FF; zz < $80
+ # I D 3 yy yy xx zz zz zz zz
+ # 0 1 2 3 4 5 6 7 8 9
+ global F, f
+ where = f.tell()
+ header = f.read(10)
+ if header[:3] != "ID3":
+ if DEBUG: print "no header?", where, `header`
+ f.seek(where)
+ return None
+
+ header = list(header)
+ for i in range(3,10):
+ header[i] = ord(header[i])
+
+ if not (header[3] < 0xff) or not (header[4] < 0xff):
+ f.seek(where)
+ return None
+ if not (header[6] < 0x80) or not (header[7] < 0x80):
+ f.seek(where)
+ return None
+ if not (header[8] < 0x80) or not (header[9] < 0x80):
+ f.seek(where)
+ return None
+
+ size = header[6:]
+ size = list(size)
+ value = 0
+ for i in size:
+ value = value << 7
+ value = value | i
+ size = value
+
+ reread_f(size)
+
+ flags = header[5]
+ version = header[3:5]
+ return handle_id3v2(version, flags, f.read(size))
+
+def decode_header(buffer):
+ "get mp3 info. Transformed from mp3info (c) Thorvald Natvig"
+
+ try:
+ switch = (ord(buffer[1]) >> 3 & 0x3)
+ if switch == 3:
+ version = 0
+ elif switch == 2:
+ version = 1
+ elif switch == 0:
+ version = 2
+ else:
+ return None
+
+ bitrate_index = (ord(buffer[2]) >> 4) & 0x0F
+ sampling_frequency = (ord(buffer[2]) >> 2) & 0x3
+ mode = (ord(buffer[3]) >> 6) & 0x3
+
+ x = {}
+ x['error_protection'] = not (ord(buffer[1]) & 0x1)
+ x['padding'] = (ord(buffer[2]) >> 1) & 0x01
+ x['extension'] = ord(buffer[2]) & 0x01
+ x['mode_ext'] = (ord(buffer[3]) >> 4) & 0x03
+ x['copyright'] = (ord(buffer[3]) >> 3) & 0x01
+ x['original'] = (ord(buffer[3]) >> 2) & 0x1
+ x['emphasis'] = (ord(buffer[3])) & 0x3
+ x['mode_name'] = mode_names[mode]
+ x['lay'] = 4 - ((ord(buffer[1]) >> 1) & 0x3)
+ x['layer_name'] = layer_names[x['lay'] - 1]
+ x['version_name'] = version_names[version]
+ x['version_num'] = version_nums[version]
+ x['bitrate'] = bitrates[version][x['lay'] - 1][bitrate_index]
+ if not x['bitrate']: raise IllegalError_may_be_free_format
+ x['sfreq'] = s_freq[version][sampling_frequency]
+ if not x['sfreq']: raise IllegalError_illegal_sfreq
+
+ except:
+ if DEBUG:
+ import traceback
+ traceback.print_exc()
+ print "Illegal header, so far:", x
+ x = None
+
+ return x
+
+def extend_frameinfo(x):
+ if x['version_num'] == "1":
+ x['samples_per_frame'] = 1152
+ elif x['version_num'] == "2":
+ x['samples_per_frame'] = 576
+ else:
+ print "What is the spf for %s?" % x['version_name'], "(%s)" % x['version_num']
+
+ # calculate BPF
+ x['bpf'] = (x['bitrate'] * bpf_fact[x['lay']]) / x['sfreq']
+ x['framesize'] = (x['bitrate'] * bpf_fact[x['lay']]) / x['sfreq'] + x['padding']
+ #stereo = (mode == MPG_MD_MONO) ? 1 : 2;
+ return x
+
+def syncronize(what = 1):
+ global F, f
+ index = f.tell()
+ x = {}
+ #search intelligently (i.e. fast) for any header
+ fields = ["\377", "I", "X", "R"][:what]
+ if not warnings: fields.remove("R")
+ #while index < len(f.buf) - 4:
+ while index < F.len - 4 + start_offset:
+ #print index
+ if F.buf[index - start_offset] in fields:
+ if F.buf[index - start_offset + 1] >= "\340":
+ # a valid header must start with 11 set bits
+ f.seek(index)
+ x = decode_header(f.read(4))
+ if x:
+ where = f.tell() - 4
+ x['header_at'] = where
+ x = extend_frameinfo(x)
+ next_header = where + x['framesize']
+ if DEBUG: print "MPEG header found at %i, expect next header at %i." % (x['header_at'], next_header)
+ f.seek(next_header)
+ buf = f.read(4)
+ y = None
+ if buf[:2] >= "\377\340":
+ y = decode_header(buf)
+ if y:
+ # two headers, that's certain enough.
+ f.seek(x['header_at'])
+ break
+ else:
+ if DEBUG: print "but no valid header follows at %i." % next_header
+ f.seek(where)
+ index = where
+ x = None
+ elif F.buf[index - start_offset:index - start_offset + 3] == "ID3":
+ if not id3v2:
+ f.seek(index)
+ read_id3v2()
+ if id3v2:
+ if DEBUG: print "ID3v2 found, now at %i" % f.tell(), id3v2
+ index = f.tell()
+ fields.remove("I")
+ continue
+ else:
+ f.seek(index)
+ elif F.buf[index - start_offset:index - start_offset + 4] == "Xing":
+ f.seek(index + 4)
+ decode_xing()
+ if xing:
+ if DEBUG: print "Xing header found without frame sync."
+ index = f.tell()
+ fields.remove("X")
+ continue
+ else:
+ f.seek(index + 4)
+
+ elif F.buf[index - start_offset:index - start_offset + 4] == "RIFF":
+ if warnings: print "skipping RIFF header at %i..." % F.tell()
+ fields.remove("R")
+ index = index + 1
+ return x
+
+def mp3format(file, warn = 1, max_skip = 100000, offset = 0):
+ global F, f, xing, id3v2, warnings, start_offset
+
+ warnings = warn
+ start_offset = offset
+
+ f = xing = id3v2 = None
+ if DEBUG: print "examining", file, "warnings=%i" % warnings, "offset=%i" % start_offset, "max_skip=%i" % max_skip
+
+ # XXX we're using StringIO because it's faster - transitionally.
+ if type(file) == types.StringType:
+ f = open(file, "r")
+ elif hasattr(file, 'seek'):
+ f = file
+ else:
+ print "unknown object:", file
+ return None
+ f.seek(start_offset)
+ search_in = f.read(max_skip)
+ F = StringIO(search_in)
+ F.file = file
+ f.seek(start_offset)
+ F.offset = start_offset
+
+ x = syncronize(what = 4)
+
+ if not x:
+ if warnings: print "Warning: no MP3 header found in the first", F.tell(), "bytes of file\n : \"" + file + "\"."
+ return {}
+
+ # we're quite sure we have a valid header now
+ if warnings > 1:
+ print "Warning: MP3 header found at offset", f.tell() - 4
+
+ # fill in convenience stuff
+ x = extend_frameinfo(x)
+
+ # the xing header is normally embedded in the first valid frame
+ # but sometimes it appears earlier, I don't know why. Use the 1st.
+ if not xing:
+ find_xing(x['framesize'])
+
+ if xing:
+ x.update(xing)
+ if x.has_key('x_frames'):
+ x['length_in_samples'] = x['x_frames'] * x['samples_per_frame']
+ x['length'] = float(x['length_in_samples']) / x['sfreq']
+ # should use the real file size instead of x_bytes.
+ if x.has_key("x_bytes"):
+ x['bitrate'] = int((x['x_bytes'] * 8.0 / x['length']) / 100 + 0.5)
+ x['bitrate'] = x['bitrate'] / 10.0
+ else:
+ print "strange xing=" + `xing`
+ else:
+ x['length'] = stat(file)[ST_SIZE] * 8.0 / (x['bitrate'] * 1000)
+ if id3v2:
+ x['id3v2'] = id3v2
+
+ if DEBUG > 1: print x
+ return x
+
+class MP3:
+ def __init__(self, file, name=None):
+
+ self.file = None
+ self.name = name
+ if type(file) == types.StringType:
+ self.file = open(file, "rb")
+ self.name = file
+ elif hasattr(file, 'seek'):
+ self.file = file
+ else:
+ print "unknown object:", file
+ return None
diff --git a/jack_playorder.py b/jack_playorder.py
new file mode 100644
index 0000000..ea9fc8d
--- /dev/null
+++ b/jack_playorder.py
@@ -0,0 +1,21 @@
+### jack_playorder: use the PLAYORDER field in freedb data for
+### jack - tag audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2003 Arne Zellentin <zarne@users.sf.net>
+
+### 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 jack_globals import *
+
+order = None
diff --git a/jack_plugin_cddb.py b/jack_plugin_cddb.py
new file mode 100644
index 0000000..8dc57fe
--- /dev/null
+++ b/jack_plugin_cddb.py
@@ -0,0 +1,10 @@
+### jack_plugin_cddb: example freedb server plugin
+
+plugin_freedb_servers = {
+ 'plugin_cddb': {
+ 'host': "cddb.cddb.com",
+ 'id': "xmcd 2.6",
+ 'submit_mail': "freedb-submit@freedb.org",
+ 'my_mail': "default"
+ },
+}
diff --git a/jack_plugin_lame.py b/jack_plugin_lame.py
new file mode 100644
index 0000000..35a6248
--- /dev/null
+++ b/jack_plugin_lame.py
@@ -0,0 +1,32 @@
+### jack_plugin_lame: user-editable lame plugin for jack
+
+plugin_helpers = {
+ 'plugin_lame': {
+ 'type': "encoder",
+ 'target': "mp3",
+ 'inverse-quality': 1,
+ 'cmd': "lame --preset cbr %r --strictly-enforce-ISO %i %o",
+ 'vbr-cmd': "lame -V %q --vbr-new --nohist --strictly-enforce-ISO %i %o",
+ 'otf-cmd': "lame --preset cbr %r --strictly-enforce-ISO - %o",
+ 'vbr-otf-cmd': "lame -V %q --vbr-new --nohist --strictly-enforce-ISO - %o",
+ 'status_blocksize': 160,
+ 'bitrate_factor': 1,
+ 'status_start': "%",
+ 'percent_fkt': r"""
+s = string.split(i['buf'], '\r')
+if len(s) >= 2: s=s[-2]
+if len(s) == 1: s=s[0]
+if string.find(s, "%") >= 0: # status reporting starts here
+ y = string.split(s, "/")
+ y1 = string.split(y[1], "(")[0]
+ percent = float(y[0]) / float(y1) * 100.0
+elif string.find(s, "Frame:") >= 0: # older versions, like 3.13
+ y = string.split(s, "/")
+ y0 = string.split(y[0], "[")[-1]
+ y1 = string.split(y[1], "]")[0]
+ percent = float(y0) / float(y1) * 100.0
+else:
+ percent = 0
+""",
+ },
+}
diff --git a/jack_plugins.py b/jack_plugins.py
new file mode 100644
index 0000000..87aa1e5
--- /dev/null
+++ b/jack_plugins.py
@@ -0,0 +1,45 @@
+# -*- coding: iso-8859-15 -*-
+### jack_plugins: plugin architecture for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 2004 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import sys
+
+from jack_globals import *
+import jack_helpers
+import jack_freedb
+
+def load_plugin(name, structure):
+ import_statement = "from jack_%s import %s" % (name, structure)
+ get_statement = "tmp = %s[name]" % structure
+ exec(import_statement) in locals()
+ exec(get_statement) in locals()
+ return tmp
+
+def import_freedb_servers():
+ if cf['_freedb_server'].startswith("plugin_"):
+ tmp = load_plugin(cf['_freedb_server'], "plugin_freedb_servers")
+ jack_freedb.freedb_servers[cf['_freedb_server']] = tmp
+
+def import_helpers():
+ for i in (cf['_encoder'], cf['_ripper']):
+ if i.startswith("plugin_"):
+ tmp = load_plugin(i, "plugin_helpers")
+ if jack_helpers.helpers.has_key(i):
+ warning("plugin %s already loaded, skipping.", i)
+ continue
+ jack_helpers.helpers[i] = tmp
diff --git a/jack_prepare.py b/jack_prepare.py
new file mode 100644
index 0000000..6c6bdc1
--- /dev/null
+++ b/jack_prepare.py
@@ -0,0 +1,760 @@
+### jack_prepare: prepare everything for the main_loop; a module for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2004 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import string
+import types
+import pprint
+import os, sys
+import difflib, shutil
+
+import jack_playorder
+import jack_functions
+import jack_progress
+import jack_version
+import jack_utils
+import jack_ripstuff
+import jack_targets
+import jack_helpers
+import jack_freedb
+import jack_status
+import jack_encstuff
+import jack_misc
+import jack_tag
+
+from jack_globals import *
+
+tracknum = None
+datatracks = []
+
+def find_workdir():
+ "search for a dir containing a toc-file or do the multi-mode"
+ tries = 0
+ toc_just_read = 0
+ debug("multi_mode:" + `cf['_multi_mode']`)
+ while (not os.path.exists(cf['_toc_file'])) or cf['_multi_mode']:
+ tries = tries + 1
+ if tries > 2:
+ break
+ if cf['_guess_mp3s']:
+ jack_ripstuff.all_tracks = jack_functions.guesstoc(cf['_guess_mp3s'])
+ else:
+ if cf['_multi_mode']:
+ debug("multimode all_tracks reset")
+ jack_ripstuff.all_tracks = []
+ else:
+ if cf['_image_toc_file']:
+ jack_ripstuff.all_tracks, dummy, dummy = jack_functions.cdrdao_gettoc(cf['_image_toc_file'])
+ else:
+ jack_ripstuff.all_tracks = jack_functions.gettoc(cf['_toc_prog'])
+ toc_just_read = 1
+ # check that the generic device is usable, too
+ if cf['_gen_device'] and not os.access(cf['_gen_device'], os.R_OK | os.W_OK):
+ warning(r"""could not open generic device %s for reading and writing.""" % cf['_gen_device'])
+
+ if cf['_scan_dirs']:
+ dirs = [os.getcwd()]
+ else:
+ dirs = cf['_searchdirs']
+
+ while cf['_scan_dirs'] > 0:
+ cf['_scan_dirs'] = cf['_scan_dirs'] - 1
+ new_dirs = []
+ for i in dirs:
+ if not i in new_dirs:
+ new_dirs.append(i)
+ try:
+ subdir = os.listdir(i)
+ except OSError, msg:
+ print "skipped %s, %s" % (i, msg)
+ continue
+ for j in subdir:
+ dir = os.path.join(i,j)
+ if os.path.isdir(dir) and not dir in new_dirs:
+ new_dirs.append(dir)
+ dirs = new_dirs
+ possible_dirs = [] # dirs matching inserted CD
+ jack_dirs = [] # dirs containing toc_file
+ for i in dirs:
+ if os.path.exists(os.path.join(i, cf['_toc_file'])):
+ jack_dirs.append(i)
+ file_toc, dummy, dummy = jack_functions.cdrdao_gettoc(os.path.join(i, cf['_toc_file']))
+ if jack_freedb.freedb_id(jack_ripstuff.all_tracks) == jack_freedb.freedb_id(file_toc):
+ possible_dirs.append(i)
+
+ if cf['_multi_mode']:
+ unique_dirs = []
+ for i in range(len(jack_dirs)):
+ found = 0
+ for j in range(i + 1,len(jack_dirs)):
+ if os.path.samefile(jack_dirs[i], jack_dirs[j]):
+ found = 1
+ if not found:
+ unique_dirs.append(jack_dirs[i])
+ for i in unique_dirs:
+ jack_ripstuff.all_tracks, dummy, track1_offset = jack_functions.cdrdao_gettoc(os.path.join(i, cf['_toc_file']))
+ err, jack_tag.track_names, jack_tag.locale_names, cd_id, revision = freedb_names(jack_freedb.freedb_id(jack_ripstuff.all_tracks), jack_ripstuff.all_tracks, os.path.join(i, cf['_freedb_form_file']), verb = 0, warn = 0)
+ if err or cf['_force']:# this means freedb data is not there yet
+ info("matching dir found: %d" % i)
+ pid = os.fork()
+ if pid == CHILD:
+ os.chdir(i)
+ ch_args = sys.argv
+ for killarg in ('--force', '--multi-mode'):
+ if killarg in ch_args:
+ ch_args.remove(killarg)
+ info("running" + `ch_args`)
+ os.execvp(ch_args[0], ch_args)
+ else:
+ respid, res = os.waitpid(pid, 0)
+ sys.exit()
+
+ unique_dirs = []
+ for i in range(len(possible_dirs)):
+ found = 0
+ for j in range(i + 1,len(possible_dirs)):
+ if os.path.samefile(possible_dirs[i], possible_dirs[j]):
+ found = 1
+ if not found:
+ unique_dirs.append(possible_dirs[i])
+ info("matching dir found: " + possible_dirs[i])
+ if len(unique_dirs) > 1:
+ error("found more than one workdir, change to the correct one.")
+ elif len(unique_dirs) == 1:
+ os.chdir(unique_dirs[0])
+ else:
+ if cf['_create_dirs']:
+ cf['_base_dir'] = expand(cf['_base_dir'])
+ if not os.path.exists(cf['_base_dir']):
+ os.makedirs(cf['_base_dir'])
+ os.chdir(cf['_base_dir'])
+ dir_name = jack_version.prog_name + "-" + jack_freedb.freedb_id(jack_ripstuff.all_tracks, warn=0)
+ if not os.path.exists(dir_name) and not os.path.isdir(dir_name):
+ os.mkdir(dir_name)
+ os.chdir(dir_name)
+ jack_freedb.dir_created = dir_name
+ jack_functions.progress("all", "mkdir", jack_freedb.dir_created)
+
+ if not cf['_multi_mode']:
+ if not os.path.exists(cf['_toc_file']):
+ jack_functions.cdrdao_puttoc(cf['_toc_file'], jack_ripstuff.all_tracks, jack_freedb.freedb_id(jack_ripstuff.all_tracks))
+ jack_freedb.freedb_template(jack_ripstuff.all_tracks) # generate freedb form if tocfile is created
+ if not os.path.exists(cf['_freedb_form_file']):
+ jack_freedb.freedb_template(jack_ripstuff.all_tracks)
+ else:
+ break
+ return toc_just_read
+
+def check_toc():
+ "compare CD toc to tocfile"
+
+ if cf['_check_toc']:
+ cd_toc = jack_functions.gettoc(cf['_toc_prog'])
+ if os.path.exists(cf['_toc_file']):
+ file_toc, dummy, dummy = jack_functions.cdrdao_gettoc(cf['_toc_file'])
+ else:
+ print "no toc-file named " + cf['_toc_file'] + " found, exiting."
+ jack_display.exit()
+ print "This is the inserted CD:"
+ pprint.pprint(cd_toc)
+ print
+ print "And This is what we expect:"
+ pprint.pprint(file_toc)
+ print
+ if cmp(cd_toc, file_toc) == 0:
+ print 'Yes, toc-file ("' + cf['_toc_file'] + '") matches inserted CD.'
+ else:
+ print 'No, toc-file ("' + cf['_toc_file'] + '") *DOES NOT* match inserted CD.'
+
+def read_toc_file():
+ "read and interpret toc_file"
+ global is_submittable
+
+ is_submittable = 0
+ if os.path.exists(cf['_toc_file']):
+ if cf['_image_toc_file']:
+ cf['_toc_file'] = cf['_image_toc_file']
+
+ jack_ripstuff.all_tracks, new_image_file, track1_offset = jack_functions.cdrdao_gettoc(cf['_toc_file'])
+
+ if not os.path.exists(cf['_def_toc']):
+ jack_functions.cdrdao_puttoc(cf['_def_toc'], jack_ripstuff.all_tracks, jack_freedb.freedb_id(jack_ripstuff.all_tracks))
+
+ # if image_file is not set (-F), we can guess it from image_toc_file
+ if not cf['_image_file'] and not cf['_rip_from_device']:
+ cf['_image_file'] = new_image_file
+
+ if cf['_rip_from_device']:
+ cf['_image_file'] = ""
+
+ is_submittable = 1
+ return is_submittable, track1_offset
+
+def filter_tracks(toc_just_read):
+ "filter out data tracks"
+
+ if toc_just_read and jack_helpers.helpers[cf['_ripper']].has_key("toc_cmd") and cf['_ripper'] != cf['_toc_prog']:
+ ripper_tracks = jack_functions.gettoc(cf['_ripper'])
+ if ripper_tracks != jack_ripstuff.all_tracks:
+ for i in range(len(jack_ripstuff.all_tracks)):
+ rtn = jack_utils.has_track(ripper_tracks, jack_ripstuff.all_tracks[i][NUM])
+ if rtn >= 0:
+ for j in range(6):
+ # "NUM LEN START COPY PRE CH" (not: "RIP RATE NAME")
+ if ripper_tracks[rtn][j] != jack_ripstuff.all_tracks[i][j]:
+ jack_functions.progress(i + 1, "patch", "%s %d -> %d" % (fields[j], jack_ripstuff.all_tracks[i][j], ripper_tracks[rtn][j]))
+ debug("Track %02d %s" % (i + 1, fields[j]) + `jack_ripstuff.all_tracks[i][j]` + " != " + `ripper_tracks[rtn][j]` + " (trusting %s; to the right)" % cf['_ripper'])
+ else:
+ jack_functions.progress(i + 1, "off", "non-audio")
+ datatracks.append(i + 1)
+ info("Track %02d not found by %s. Treated as non-audio." % (i + 1, cf['_ripper']))
+
+
+def gen_todo():
+ "parse tracks from argv, generate todo"
+ global tracknum
+
+ tracknum = {}
+ for i in jack_ripstuff.all_tracks:
+ tracknum[i[NUM]] = i
+
+ if not cf['_tracks'] and not jack_playorder.order:
+ todo = []
+ for i in jack_ripstuff.all_tracks:
+ if i[CH] == 2:
+ todo.append(i)
+ else:
+ info("can't handle non audio track %i" % i[NUM])
+
+ else:
+ # example: "1,2,4-8,12-" -> [ 1,2,4,5,6,7,8,12,13,...,n ]
+ tlist = []
+ if cf['_tracks']:
+ tracks = string.split(cf['_tracks'], ",")
+ else:
+ tracks = string.split(jack_playorder.order, ",")
+ for k in tracks:
+ if string.find(k, '-') >= 0:
+ k = string.split(k, '-')
+ lower_limit = jack_misc.safe_int(k[0], "Track '%s' is not a number." % k[0])
+ if k[1]:
+ upper_limit = jack_misc.safe_int(k[1], "Track '%s' is not a number." % k[1])
+ else:
+ upper_limit = len(jack_ripstuff.all_tracks)
+ for j in range(lower_limit, upper_limit + 1):
+ tlist.append(j)
+ else:
+ track = jack_misc.safe_int(k, "Track '%s' is not a number." % k)
+ tlist.append(track)
+
+ # uniq the track list
+ tlist.sort()
+ k = 0
+ while k < len(tlist) - 1:
+ if tlist[k] == tlist[k+1]:
+ del tlist[k]
+ else:
+ k = k + 1
+
+ # generate todo
+ todo = []
+ audiotracks = []
+ for i in jack_ripstuff.all_tracks:
+ if i[NUM] in datatracks:
+ continue
+ audiotracks.append(i[NUM])
+
+ if audiotracks != range(1, audiotracks[-1] + 1):
+ info("strange audio track layout " + `audiotracks`)
+ continuous = 0
+ else:
+ continuous = 1
+
+ for k in tlist:
+ if continuous:
+ if k < 1 or k > len(audiotracks):
+ warning('This CD has audio tracks 1-%d. Ignoring request for track %d.' % (len(audiotracks), k))
+ continue
+ else:
+ if k < 1 or k > len(jack_ripstuff.all_tracks):
+ warning('This CD has tracks 1-%d. Ignoring request for track %d.' % (len(jack_ripstuff.all_tracks), k))
+ continue
+ if jack_ripstuff.all_tracks[k-1][CH] == 2:
+ todo.append(jack_ripstuff.all_tracks[k-1])
+ else:
+ warning("can't handle non audio track %i" % k[NUM])
+
+ for i in todo:
+ jack_ripstuff.all_tracks_todo_sorted.append(i)
+
+ return todo
+
+def init_status():
+ status = {}
+ for i in jack_ripstuff.all_tracks:
+ num = i[NUM]
+ status[num] = {}
+ status[num]['dae'] = []
+ status[num]['enc'] = []
+ status[num]['ren'] = []
+ status[num]['names'] = [i[NAME],]
+ status[num]['patch'] = []
+ status[num]['off'] = []
+
+ status['all'] = {}
+ status['all']['mkdir'] = status['all']['names'] = [[],]
+ status['all']['dae'] = []
+ status['all']['enc'] = []
+ status['all']['ren'] = []
+ status['all']['patch'] = []
+ status['all']['off'] = []
+ status['all']['id3_genre'] = ["-1",]
+ status['all']['id3_year'] = ["-1",]
+ return status
+
+def update_progress(todo):
+ ext = jack_targets.targets[jack_helpers.helpers[cf['_encoder']]['target']]['file_extension']
+ "update progress file at user's request (operation mode)"
+
+ if cf['_upd_progress']:
+ for i in todo:
+ num = i[NUM]
+ if not status[num]['dae']:
+ if os.path.exists(i[NAME] + ".wav"):
+ status[num]['dae'] = " * [ simulated ]"
+ jack_functions.progress(num, "dae", status[num]['dae'])
+ if not status[num]['enc']:
+ if os.path.exists(i[NAME] + ext):
+ if ext == ".mp3":
+ x = jack_mp3.mp3format(i[NAME] + ext)
+ temp_rate = x['bitrate']
+ elif ext == ".ogg":
+ x = ogg.vorbis.VorbisFile(i[NAME] + ext)
+ temp_rate = int(x.raw_total(0) * 8 / x.time_total(0) / 1000 + 0.5)
+ else:
+ error("don't know how to handle %s files." % ext)
+ status[num]['enc'] = `temp_rate` + cf['_progr_sep'] + "[simulated]"
+ jack_functions.progress(num, "enc", status[num]['enc'])
+
+def read_progress(status, todo):
+ "now read in the progress file"
+
+ if os.path.exists(cf['_progress_file']):
+ f = open(cf['_progress_file'], "r")
+ while 1:
+ buf = f.readline()
+ if not buf:
+ break
+
+ # strip doesn't work here as we may have trailing spaces
+ buf = string.replace(buf, "\n", "")
+
+ # ignore empty lines
+ if not buf:
+ continue
+
+ buf = string.split(buf, cf['_progr_sep'], 3)
+ try:
+ num = int(buf[0])
+ except ValueError:
+ num = buf[0]
+ if buf[1] == 'undo': # this needs special treatment as
+ # the correct sequence is important
+
+ status[num]['ren'].append(('Undo',))
+ elif buf[1] == 'ren':
+ status[num][buf[1]].append(buf[2:])
+ else:
+ status[num][buf[1]] = buf[2:]
+ f.close()
+
+ # names for 'all' can't be initialized earlier...
+ status['all']['names'] = [status['all']['mkdir'][-1],]
+
+ # extract names from renaming
+ for i in status.keys():
+ for j in status[i]['ren']:
+ if j == ('Undo',):
+ if len(status[i]['names']) > 1:
+ del status[i]['names'][-1]
+ else:
+ error("more undos than renames, exit.")
+ else:
+ names = string.split(j[0], '-->', 1)
+ if status[i]['names'][-1] == names[0]:
+ status[i]['names'].append(names[1])
+ if type(i) == types.IntType:
+ tracknum[i][NAME] = status[i]['names'][-1]
+ del status[i]['ren']
+
+ # status info for the whole CD is treated separately
+ jack_progress.status_all = status['all']
+
+ del status['all']
+
+ # now clean up a little
+ for i in status.keys():
+ if len(status[i]['dae']) > 1 or len(status[i]['enc']) > 2:
+ error("malformed progress file")
+ sys.exit()
+ if len(status[i]['enc']) == 2:
+ tracknum[i][RATE] = int(float(status[i]['enc'][0]) + 0.5)
+ status[i]['enc'] = status[i]['enc'][1]
+ elif status[i]['enc'] and len(status[i]['enc']) == 1:
+ tracknum[i][RATE] = cf['_bitrate']
+ if status[i]['dae'] and len(status[i]['dae']) == 1:
+ status[i]['dae'] = status[i]['dae'][0]
+
+ if status[i]['patch']:
+ for j in status[i]['patch']:
+ p_what, p_from, dummy, p_to = string.split(j)
+ p_from = int(p_from)
+ p_to = int(p_to)
+ if tracknum[i][fields.index(p_what)] == p_from:
+ tracknum[i][fields.index(p_what)] = p_to
+ else:
+ error("illegal patch %s. " % j, + "Track %02d: %s is %d" % (i, p_what, todo[jack_utils.has_track(todo, i)][fields.index(p_what)]))
+
+ if status[i]['off']:
+ if jack_utils.has_track(todo, i) >= 0:
+ del todo[jack_utils.has_track(todo, i)]
+ if jack_utils.has_track(jack_ripstuff.all_tracks_todo_sorted, i) >= 0:
+ del jack_ripstuff.all_tracks_todo_sorted[jack_utils.has_track(jack_ripstuff.all_tracks_todo_sorted, i)]
+
+ # extract status from read progress data
+ jack_status.extract(status)
+
+ jack_freedb.dir_created = jack_progress.status_all['names'][-1]
+
+ jack_progress.status_all['id3_genre'] = int(jack_progress.status_all['id3_genre'][-1])
+ jack_progress.status_all['id3_year'] = int(jack_progress.status_all['id3_year'][-1])
+
+ if cf['_id3_genre'] == -1:
+ cf['_id3_genre'] = jack_progress.status_all['id3_genre']
+ else:
+ if cf['_id3_genre'] != jack_progress.status_all['id3_genre']:
+ jack_functions.progress("all", "id3_genre", `cf['_id3_genre']`)
+
+ jack_tag.genretxt = ""
+ if cf['_id3_genre'] >= 0 and cf['_id3_genre'] < len(id3genres):
+ jack_tag.genretxt = id3genres[cf['_id3_genre']]
+
+ if cf['_id3_year'] == -1:
+ cf['_id3_year'] = jack_progress.status_all['id3_year']
+ else:
+ if cf['_id3_year'] != jack_progress.status_all['id3_year']:
+ jack_functions.progress("all", "id3_year", `cf['_id3_year']`)
+
+ return status
+
+def freedb_submit():
+ "submit freedb data on user's request"
+ if not is_submittable:
+ error("can't submit in current state, please fix jack.freedb")
+
+ err, jack_tag.track_names, jack_tag.locale_names, cd_id, revision = jack_freedb.freedb_names(jack_freedb.freedb_id(jack_ripstuff.all_tracks), jack_ripstuff.all_tracks, cf['_freedb_form_file'], verb = 1)
+ if err:
+ error("invalid freedb file")
+ else:
+ if cf['_freedb_mailsubmit']:
+ jack_freedb.do_freedb_mailsubmit(cf['_freedb_form_file'], cd_id)
+ else:
+ jack_freedb.do_freedb_submit(cf['_freedb_form_file'], cd_id)
+
+ ### (9) do query on start
+
+def query_on_start():
+ info("querying...")
+ if jack_freedb.freedb_query(jack_freedb.freedb_id(jack_ripstuff.all_tracks), jack_ripstuff.all_tracks, cf['_freedb_form_file']):
+ if cf['_cont_failed_query']:
+
+ x = raw_input("\nfreedb search failed, continue? ") + "x"
+ if string.upper(x[0]) != "Y":
+ sys.exit(0)
+ cf['_query_on_start'] = 0
+ else:
+ jack_display.exit()
+
+ if cf['_edit_freedb']:
+ file = cf['_freedb_form_file']
+ bakfile = file + ".bak"
+ if os.path.exists(file):
+ try:
+ shutil.copyfile(file, bakfile)
+ except IOError:
+ pass
+ jack_utils.ex_edit(cf['_freedb_form_file'])
+ if os.path.exists(bakfile):
+ try:
+ f = open(file, "r")
+ b = open(bakfile, "r")
+ except IOError:
+ print "Could not open jack.freedb or jack.freedb.bak for comparison"
+ else:
+ pdiff = "".join(difflib.unified_diff(b.readlines(), f.readlines(), bakfile, file))
+ f.close()
+ b.close()
+ if pdiff:
+ print
+ print "You made the following changes to the FreeDB file:"
+ print
+ print pdiff
+ x = raw_input("Would you like to submit these changes to the FreeDB server? (y/N) ")
+ if string.upper(x[0]) == "Y":
+ jack_freedb.update_revision(file)
+ freedb_submit()
+
+ if cf['_query_on_start']:
+ err, jack_tag.track_names, jack_tag.locale_names, freedb_rename, revision = jack_freedb.interpret_db_file(jack_ripstuff.all_tracks, cf['_freedb_form_file'], verb = cf['_query_on_start'], dirs = 1)
+ if err:
+ error("query on start failed to give a good freedb file, aborting.")
+ else:
+ err, jack_tag.track_names, jack_tag.locale_names, freedb_rename, revision = jack_freedb.interpret_db_file(jack_ripstuff.all_tracks, cf['_freedb_form_file'], verb = cf['_query_on_start'], warn = cf['_query_on_start'])
+ return freedb_rename
+
+def undo_rename(status, todo):
+ ext = jack_targets.targets[jack_helpers.helpers[cf['_encoder']]['target']]['file_extension']
+ "undo renaming (operation mode)"
+ maxnames = max(map(lambda x: len(x['names']), status.values()))
+ if len(jack_progress.status_all['names']) >= maxnames:
+ dir_too = 1
+ else:
+ dir_too = 0
+ maxnames = max(maxnames, len(jack_progress.status_all['names']))
+ if maxnames > 1:
+
+ # undo dir renaming
+ cwd = os.getcwd()
+ if jack_freedb.dir_created and jack_utils.check_path(jack_freedb.dir_created, cwd) and dir_too:
+ new_name, old_name = jack_progress.status_all['names'][-2:]
+ jack_utils.rename_path(old_name, new_name) # this changes cwd!
+ info("cwd now " + os.getcwd())
+ jack_functions.progress("all", "undo", "dir")
+
+ else:
+ maxnames = max(map(lambda x: len(x['names']), status.values()))
+
+ # undo file renaming
+ for i in todo:
+ if maxnames < 2:
+ break
+ act_names = status[i[NUM]]['names']
+ if len(act_names) == maxnames:
+ for j in (ext, '.wav'):
+ new_name, old_name = act_names[-2:]
+ new_name, old_name = new_name + j, old_name + j
+ if not os.path.exists(old_name):
+ if j == ext:
+ print 'NOT renaming "' + old_name + '": it doesn\'t exist.'
+ else:
+ if os.path.exists(new_name):
+ print 'NOT renaming "' + old_name + '" to "' + new_name + '" because dest. exists.'
+ else:
+ jack_functions.progress(i[NUM], "undo", "-")
+ os.rename(old_name, new_name)
+ else:
+ info("nothing to do.")
+
+def what_todo(space, todo):
+ #### check what is already there
+ wavs_todo = []
+ mp3s_todo = []
+ remove_q = []
+ ext = jack_targets.targets[jack_helpers.helpers[cf['_encoder']]['target']]['file_extension']
+
+ for track in todo:
+ wavs_todo.append(track)
+ mp3s_todo.append(track)
+
+ jack_encstuff.mp3s_ready = []
+ for track in todo:
+ mp3 = track[NAME] + ext
+ if os.path.exists(mp3):
+ if cf['_overwrite']:
+ space = space + jack_utils.filesize(mp3)
+ remove_q.append(mp3)
+ jack_status.enc_status[track[NUM]] = "will o/w file."
+ elif not cf['_force'] and not jack_status.enc_status[track[NUM]]:
+ space = space + jack_utils.filesize(mp3)
+ remove_q.append(mp3)
+ jack_status.enc_status[track[NUM]] = "no encoder run."
+ # with vbr encoded files can't legally be too small
+ # but to reduce confusion, the check is then removed:
+ elif not cf['_vbr'] and jack_utils.filesize(mp3) <= jack_functions.tracksize(track)[ENC] * 0.99: # found by trial'n'err
+ space = space + jack_utils.filesize(mp3)
+ remove_q.append(mp3)
+ jack_status.enc_status[track[NUM]] = "encoded file too small by " + jack_functions.pprint_i(jack_functions.tracksize(track)[ENC] - jack_utils.filesize(mp3)) + "."
+ elif not cf['_vbr'] and jack_utils.filesize(mp3) >= jack_functions.tracksize(track)[ENC] * 1.05: # found by trial'n'err
+ space = space + jack_utils.filesize(mp3)
+ remove_q.append(mp3)
+ jack_status.enc_status[track[NUM]] = "enc. file too large by " + jack_functions.pprint_i(jack_utils.filesize(mp3) - jack_functions.tracksize(track)[ENC]) + "."
+ else:
+ mp3s_todo.remove(track)
+ jack_encstuff.mp3s_ready.append(track)
+ else:
+ if jack_status.enc_status[track[NUM]]:
+ jack_status.enc_status[track[NUM]] = "[file lost-doing again]"
+
+ jack_ripstuff.wavs_ready = []
+ for track in todo:
+ wav = track[NAME] + ".wav"
+ if os.path.exists(wav):
+ if cf['_overwrite']:
+ space = space + jack_utils.filesize(wav)
+ remove_q.append(wav)
+ jack_status.dae_status[track[NUM]] = "Existing WAV will be overwritten."
+ elif jack_utils.filesize(wav) == jack_functions.tracksize(track)[WAV] and jack_status.dae_status[track[NUM]]:
+ wavs_todo.remove(track)
+ jack_ripstuff.wavs_ready.append(track)
+ elif jack_utils.filesize(wav) == jack_functions.tracksize(track)[WAV]:
+ space = space + jack_utils.filesize(wav)
+ remove_q.append(wav)
+ jack_status.dae_status[track[NUM]] = " ---- [Existing WAV not done by jack.]"
+ if jack_status.enc_status[track[NUM]] == "[file lost-doing again]":
+ jack_status.enc_status[track[NUM]] = ""
+ else:
+ space = space + jack_utils.filesize(wav)
+ remove_q.append(wav)
+ jack_status.dae_status[track[NUM]] = " ---- [Existing WAV was not complete.]"
+ if jack_status.enc_status[track[NUM]] == "[file lost-doing again]":
+ jack_status.enc_status[track[NUM]] = ""
+ else:
+ if jack_status.dae_status[track[NUM]]:
+ if jack_status.enc_status[track[NUM]] == "[file lost-doing again]":
+ jack_status.dae_status[track[NUM]] = " ---- [ both lost, doing again ]"
+ jack_status.enc_status[track[NUM]] = ""
+ elif cf['_keep_wavs'] or track not in jack_encstuff.mp3s_ready:
+ jack_status.dae_status[track[NUM]] = " ---- [ WAV lost, doing again ]"
+
+ if cf['_only_dae']:
+ cf['_keep_wavs'] = 1
+
+ if not cf['_keep_wavs']:
+ for track in todo:
+ if track in jack_encstuff.mp3s_ready and track in wavs_todo:
+ wavs_todo.remove(track)
+
+ if cf['_reorder']:
+ mp3s_todo.sort(jack_utils.cmp_toc)
+
+ dae_queue = [] # This stores the tracks to rip
+ enc_queue = [] # WAVs go here to get some codin'
+
+ for track in wavs_todo:
+ dae_queue.append(track) # copy track to dae + code in queue
+ if track in mp3s_todo:
+ mp3s_todo.remove(track) # remove mp3s which are not there yet
+
+ if cf['_only_dae']: # if only_dae nothing is encoded _at_all_.
+ mp3s_todo = []
+
+ # overwrite cached bitrates with those from argv
+ if cf['bitrate']['history'][-1][0] == "argv":
+ for i in wavs_todo:
+ i[RATE] = cf['_bitrate']
+ for i in mp3s_todo:
+ i[RATE] = cf['_bitrate']
+
+ return space, remove_q, wavs_todo, mp3s_todo, dae_queue, enc_queue
+#
+#
+#
+
+def print_todo(todo, wavs_todo, mp3s_todo):
+ "print what needs to be done"
+ for i in jack_ripstuff.all_tracks:
+ print "%02i" % i[NUM],
+ if jack_utils.has_track(todo, i[NUM]) >= 0:
+ print "*",
+ else:
+ print "-",
+ if i in wavs_todo:
+ print ":DAE:",
+ # FIXME!
+ if jack_status.dae_status[i[NUM]] != "[simulated]": print jack_status.dae_status[i[NUM]],
+ if not cf['_only_dae']:
+ print ":ENC:",
+ if jack_status.enc_status[i[NUM]] != "[simulated]": print jack_status.enc_status[i[NUM]],
+ if i in mp3s_todo:
+ print ":ENC:",
+ if jack_status.enc_status[i[NUM]] != "[simulated]": print jack_status.enc_status[i[NUM]],
+ print
+
+# overwrite cached bitrates from argv
+if cf['bitrate']['history'][-1][0] == "argv":
+ for i in wavs_todo:
+ i[RATE] = cf['_bitrate']
+ for i in mp3s_todo:
+ i[RATE] = cf['_bitrate']
+
+def check_space(space, wavs_todo, mp3s_todo):
+ # check free space
+ will_work = 1
+ freeable_space = 0
+ if cf['_keep_wavs']:
+ space_needed = jack_functions.tracksize(wavs_todo)[BOTH]
+ elif cf['_otf']:
+ space_needed = jack_functions.tracksize(wavs_todo)[ENC]
+ else:
+ space_needed = jack_functions.tracksize(wavs_todo)[PEAK]
+ if cf['_only_dae']:
+ space_needed = jack_functions.tracksize(wavs_todo)[WAV]
+ else:
+ for i in mp3s_todo:
+ if space + freeable_space>jack_functions.tracksize(i)[ENC]:
+ if not cf['_keep_wavs']:
+ freeable_space = freeable_space + jack_functions.tracksize(i)[WAV] - jack_functions.tracksize(i)[ENC]
+ else:
+ will_work = 0
+ # this is quite dirty
+ space_needed = jack_functions.tracksize(i)[ENC] - space + freeable_space
+ break
+
+ if (space + freeable_space < space_needed or not will_work) and not cf['_dont_work']:
+ error(("insufficient disk space (%sBytes needed), " + "try reorder or " * (not cf['_reorder']) + "free %sBytes.") % (jack_functions.pprint_i(space_needed - freeable_space, "%i %s"), jack_functions.pprint_i(space_needed - freeable_space - jack_ripstuff.raw_space, "%i %s")))
+
+
+def check_cd():
+ if cf['_rip_from_device']:
+ all_tracks_on_cd = jack_functions.gettoc(cf['_toc_prog'])
+ if not cf['_force'] and not jack_utils.cmp_toc_cd(jack_ripstuff.all_tracks_orig, all_tracks_on_cd, what=(NUM, LEN)):
+ error("you did not insert the right cd")
+
+def remove_files(remove_q):
+ if cf['_silent_mode'] or cf['_dont_work']:
+ print "remove these files before going on:"
+ for i in remove_q:
+ print i
+ print "### . ###"
+
+ if cf['_silent_mode']:
+ sys.exit(3)
+
+ else:
+ print "/\\" * 40
+ for i in remove_q:
+ print i
+ x = raw_input("These files will be deleted, continue? ") + "x"
+ if cf['_force']:
+ info("(forced)")
+ else:
+ if string.upper(x[0]) != "Y":
+ sys.exit(0)
+
+ for i in remove_q:
+ os.remove(i)
+
diff --git a/jack_progress.py b/jack_progress.py
new file mode 100755
index 0000000..e475d57
--- /dev/null
+++ b/jack_progress.py
@@ -0,0 +1,20 @@
+### jack_progress
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2002 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+status_all = {} # progress info common for all track
+
diff --git a/jack_rc.py b/jack_rc.py
new file mode 100755
index 0000000..7234f50
--- /dev/null
+++ b/jack_rc.py
@@ -0,0 +1,189 @@
+### jack_rc: read/write config file, a module for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2004 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import os
+import sys
+import string
+import types
+
+import jack_argv
+import jack_version
+
+from jack_globals import *
+
+# exported functions:
+# load
+# save
+
+def read(file):
+ read_rc = []
+
+ try:
+ f = open(file)
+ except:
+ return read_rc
+
+ lineno = 0
+ while 1:
+ x = f.readline()
+ if not x:
+ break
+ opt = val = com = None
+ lineno = lineno + 1
+
+ x = string.strip(x)
+ x = string.split(x, "#", 1)
+ if len(x) > 1:
+ opt, com = x
+ else:
+ opt = x[0]
+ if opt and com:
+ opt = string.strip(opt)
+ if opt:
+ x = string.split(opt, ":", 1)
+ if len(x) > 1:
+ opt, val = x
+ else:
+ opt = x[0]
+ else:
+ opt = None
+ read_rc.append([opt, val, com, lineno])
+ version = get_version(read_rc)
+ if version != jack_version.prog_rcversion:
+ warning("config file %s is of unknown version %s." % (file, `version`))
+ return read_rc
+
+def get_version(rc):
+ if not rc:
+ return None
+ if len(rc[0]) != 4:
+ return None
+ opt, val, com, lineno = rc[0]
+ if opt == None and val == None and lineno == 1:
+ vers = com.strip().split(":", 1)
+ if len(vers) != 2:
+ return None
+ if vers[0] != "jackrc-version":
+ return None
+ ver = int(vers[1])
+ return ver
+ else:
+ return None
+
+def load(cf, file):
+ rc = read(expand(file))
+ rc_cf = {}
+ for i in rc:
+ if i[0] != None:
+ if cf.has_key(i[0]):
+ ret, val = jack_argv.parse_option(cf, i[0:2], 0, i[0], None, origin="rcfile")
+ if ret != None:
+ rc_cf[i[0]] = {'val': val}
+ else:
+ warning(file + ":%s: " % i[3] + val)
+ else:
+ warning(file + ":%s: unknown option `%s'" % (i[3], i[0]))
+ return rc_cf
+
+def merge(old, new):
+ old = old[:]
+ new = new[:]
+ append = []
+ remove = []
+ old.reverse()
+ for i in range(len(new)):
+ found = 0
+ for j in range(len(old)):
+ if new[i][0] and new[i][0] == old[j][0]:
+ old[j][:2] = new[i][:2]
+ found = 1
+ break
+ if not found:
+ append.append(new[i][:2] + [None,])
+ else:
+ if new[i][2] == 'toggle':
+ remove.append(old[j])
+ for i in remove:
+ if i[2] != None:
+ x = old.index(i)
+ old[x] = [None, None, old[x][2]]
+ else:
+ old.remove(i)
+ old.reverse()
+ return old + append
+
+def write(file, rc, rcfile_exists = True):
+ f = open(file, "w")
+ if not rcfile_exists:
+ f.write("# jackrc-version:%d\n" % jack_version.prog_rcversion)
+
+ for i in rc:
+ if i[0]:
+ f.write(i[0])
+ if i[1] != None:
+ f.write(":" + i[1])
+ if i[2] != None:
+ if i[0] or i[1]:
+ f.write(" ")
+ f.write("#" + i[2])
+ f.write("\n")
+
+def write_yes(x):
+ if x:
+ return "yes"
+ else:
+ return "no"
+
+def convert(cf):
+ rc = []
+ for i in cf.keys():
+ if cf[i]['type'] == types.StringType:
+ rc.append([i, cf[i]['val'], None])
+ elif cf[i]['type'] == types.FloatType:
+ rc.append([i, `cf[i]['val']`, None])
+ elif cf[i]['type'] == types.IntType:
+ rc.append([i, `cf[i]['val']`, None])
+ elif cf[i]['type'] == 'toggle':
+ rc.append([i, write_yes(cf[i]['val']), 'toggle'])
+ elif cf[i]['type'] == types.ListType:
+ rc.append([i, `cf[i]['val']`, None])
+ else:
+ error("don't know how to handle " + `cf[i]['type']`)
+ return rc
+
+def save(file, cf):
+ file = expand(file)
+ rc_cf = {}
+ for i in cf.keys():
+ if cf[i].has_key('save') and not cf[i]['save']:
+ continue
+ opt = cf[i]
+ if opt['history'][-1][0] == "argv":
+ rc_cf[i] = opt
+ oldrc = read(file)
+ argvrc = convert(rc_cf)
+ newrc = merge(oldrc, argvrc)
+ rcfile_exists = os.path.exists(file)
+ try:
+ write(file + ".tmp", newrc, rcfile_exists)
+ except:
+ error("can't write config file")
+ if os.path.exists(file):
+ os.rename(file, file + "~")
+ os.rename(file + ".tmp", file)
+ return len(rc_cf)
diff --git a/jack_ripstuff.py b/jack_ripstuff.py
new file mode 100755
index 0000000..82a2c7b
--- /dev/null
+++ b/jack_ripstuff.py
@@ -0,0 +1,78 @@
+### jack_ripstuff: container module for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2004 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import jack_freedb
+
+import locale
+
+from jack_globals import *
+
+all_tracks_orig = []
+all_tracks_todo_sorted = []
+all_tracks = []
+
+wavs_ready = None
+
+printable_names = None # these are displayed for the track names
+max_name_len = None # max len of printable_names[]
+
+raw_space = None # free diskspace
+
+def gen_printable_names(track_names, todo):
+ global printable_names
+ global max_name_len
+
+ printable_names=[]
+ for i in range(CDDA_MAXTRACKS):
+ printable_names.append("")
+
+ if jack_freedb.names_available and cf['_show_names']:
+ if cf['_various']:
+ max_name_len = max(map(lambda x: len(track_names[x[NUM]][0] + " - " + track_names[x[NUM]][1]), todo))
+ else:
+ max_name_len = max(map(lambda x: len(track_names[x[NUM]][1]), todo))
+ max_name_len = len("01 ") + max_name_len
+ if cf['_show_time']:
+ max_name_len = max_name_len + 6
+ else:
+ max_name_len = len("01")
+ if cf['_show_time']:
+ max_name_len = max_name_len + len(" 01:23")
+
+ for i in todo:
+ if cf['_show_time']:
+ len_tmp = i[LEN] / CDDA_BLOCKS_PER_SECOND
+ len_tmp = ("%02i:%02i") % (len_tmp / 60, len_tmp % 60)
+
+ if jack_freedb.names_available and cf['_show_names']:
+ if cf['_show_time']:
+ tmp = "%02i %5s " % (i[NUM], len_tmp)
+ else:
+ tmp = "%02i " % i[NUM]
+ if cf['_various']:
+ tmp = tmp + track_names[i[NUM]][0] + " - " + track_names[i[NUM]][1]
+ else:
+ tmp = tmp + track_names[i[NUM]][1]
+ p_tmp = tmp.encode(locale.getpreferredencoding(), "replace")
+ printable_names[i[NUM]] = p_tmp + "." * (max_name_len - len(tmp))
+ else:
+ if cf['_show_time']:
+ printable_names[i[NUM]] = ("%02i " % i[NUM]) + len_tmp + "." * (max_name_len - len(i[NAME]) - 6)
+ else:
+ printable_names[i[NUM]] = i[NAME] + "." * (max_name_len - len(i[NAME]))
+
diff --git a/jack_status.py b/jack_status.py
new file mode 100755
index 0000000..70ee5f8
--- /dev/null
+++ b/jack_status.py
@@ -0,0 +1,66 @@
+### jack_status: module which holds the ripping and encoding status for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2002 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import string
+
+from jack_globals import NUM, cf
+import jack_term
+import jack_ripstuff
+
+enc_status = {} # status messages are stored here
+enc_cache = {} # sometimes status messages are stored here
+dae_status = {} # status messages are stored here
+
+def init(todo):
+ global enc_status, enc_cache, dae_status
+ for i in todo:
+ dae_status[i[NUM]] = ""
+ enc_status[i[NUM]] = ""
+ enc_cache[i[NUM]] = ""
+
+def extract(status):
+ for i in status.keys():
+ if status[i]['dae']:
+ dae_status[i] = status[i]['dae']
+ if status[i]['enc']:
+ enc_status[i] = status[i]['enc']
+
+def enc_stat_upd(num, string):
+ global enc_status
+ enc_status[num] = string
+ jack_term.tmod.enc_stat_upd(num, string)
+
+def dae_stat_upd(num, string):
+ global enc_status
+ dae_status[num] = string
+ jack_term.tmod.dae_stat_upd(num, string)
+
+def print_status(form = 'normal'):
+ for i in jack_ripstuff.all_tracks_todo_sorted:
+ if form != 'normal' or not jack_ripstuff.printable_names:
+ print cf['_name'] % i[NUM] + ":", dae_status[i[NUM]], enc_status[i[NUM]]
+ else:
+ print jack_ripstuff.printable_names[i[NUM]] + ":", dae_status[i[NUM]], enc_status[i[NUM]]
+
+def get_2_line(buf, default="A failure occured"):
+ tmp = string.split(buf, "\n")
+ if len(tmp) >= 2:
+ return string.strip(tmp[-2])
+ else:
+ return default
+
diff --git a/jack_t_curses.py b/jack_t_curses.py
new file mode 100755
index 0000000..f178390
--- /dev/null
+++ b/jack_t_curses.py
@@ -0,0 +1,276 @@
+### jack_t_curses: dumb terminal functions for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2004 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import termios
+import sys
+import signal
+import types
+
+import jack_status
+import jack_ripstuff
+import jack_display
+import jack_term
+import jack_globals
+import jack_version
+
+from jack_globals import *
+
+enabled = None
+
+try:
+ from jack_curses import endwin, resizeterm, A_REVERSE, newwin, newpad, initscr, noecho, cbreak, echo, nocbreak
+except ImportError:
+ warning("jack_curses module not found, trying normal curses...")
+ try:
+ from curses import endwin, A_REVERSE, newwin, newpad, initscr, noecho, cbreak, echo, nocbreak
+ def resizeterm(y, x):
+ pass
+ except ImportError:
+ print "curses module not found or too old, please install it (see README)"
+
+
+# screen objects
+stdscr = status_pad = usage_win = None
+
+# status pad geometry
+pad_x = pad_y = pad_start_y = pad_start_x = pad_end_y = pad_end_x = None
+pad_height = pad_width = None
+pad_disp_start_y = pad_disp_start_x = 0
+
+# usage win geometry
+usage_win_y = usage_win_x = 0
+usage_win_height, usage_win_width = 7, 49
+
+# reserve lines for the copyright/help box
+splash_reserve = 0
+if cf['_usage_win']:
+ splash_reserve = usage_win_height
+
+curses_sighandler = None
+map_track_num = None # list track number -> line number
+extra_lines = None
+had_special = None
+
+def enable():
+ # Initialize curses
+ global stdscr
+ global status_pad
+ global usage_win
+ global curses_sighandler
+ global map_track_num
+ global enabled
+ global pad_height, pad_width
+ global had_special
+ global extra_lines
+
+ if enabled:
+ return
+
+ had_special = 0
+ extra_lines = 2 # top + bottom status lines
+ if jack_display.discname:
+ extra_lines = extra_lines + 1
+ stdscr = initscr()
+ enabled = 1
+ jack_term.sig_winch_cache = signal.signal(signal.SIGWINCH, signal.SIG_IGN)
+ # Turn off echoing of keys, and enter cbreak mode,
+ # where no buffering is performed on keyboard input
+ noecho() ; cbreak()
+
+ # In keypad mode, escape sequences for special keys
+ # (like the cursor keys) will be interpreted and
+ # a special value like KEY_LEFT will be returned
+ stdscr.keypad(1)
+ stdscr.leaveok(0)
+
+ # build the pad
+ pad_height, pad_width = len(jack_ripstuff.all_tracks_todo_sorted), jack_ripstuff.max_name_len + 72
+ status_pad = newpad(pad_height, pad_width)
+ usage_win = newwin(usage_win_height, usage_win_width, 0, 0)
+ map_track_num = {}
+ for i in range(len(jack_ripstuff.all_tracks_todo_sorted)):
+ map_track_num[jack_ripstuff.all_tracks_todo_sorted[i][NUM]] = i
+
+ sig_winch_handler(None, None)
+
+def disable():
+ global enabled
+ if enabled:
+ # Set everything back to normal
+ stdscr.keypad(0)
+ echo() ; nocbreak()
+ # Terminate curses, back to normal screen
+ endwin()
+ # re-install previous sighandler
+ #signal.signal(signal.SIGWINCH, jack_term.sig_winch_cache)
+ signal.signal(signal.SIGWINCH, signal.SIG_IGN)
+ enabled = 0
+
+def sig_winch_handler(sig, frame):
+ global staus_pad, stdscr, usage_win
+ global pad_y, pad_x, pad_start_y, pad_start_x, pad_end_y, pad_end_x
+ global usage_win_y, usage_win_x
+
+ if not enabled:
+ return
+
+ signal.signal(signal.SIGWINCH, signal.SIG_IGN)
+ if type(curses_sighandler) == types.FunctionType:
+ curses_sighandler(sig, frame)
+
+ jack_term.resize()
+
+ old_y, old_x = stdscr.getmaxyx()
+ resizeterm(jack_term.size_y, jack_term.size_x)
+ pad_y, pad_x = pad_disp_start_y, pad_disp_start_x
+ pad_start_y, pad_start_x = extra_lines - 1, 0
+ pad_end_y = min(extra_lines - 1 + pad_height, jack_term.size_y - 2 - splash_reserve)
+ pad_end_x = min(jack_term.size_x, pad_width) - 1
+ pad_missing_y = max(pad_height - (jack_term.size_y - extra_lines - splash_reserve), 0)
+ pad_missing_x = max(pad_width - jack_term.size_x, 0)
+ if pad_missing_y >= pad_height:
+ pad_start_y = 0
+ stdscr.clear()
+ status_pad.clear()
+ scroll_keys = ""
+ if pad_disp_start_x:
+ scroll_keys = scroll_keys + "h"
+ else:
+ scroll_keys = scroll_keys + " "
+ if pad_missing_y and not pad_disp_start_y > pad_missing_y - 1:
+ scroll_keys = scroll_keys + "j"
+ else:
+ scroll_keys = scroll_keys + " "
+ if pad_disp_start_y:
+ scroll_keys = scroll_keys + "k"
+ else:
+ scroll_keys = scroll_keys + " "
+ if pad_missing_x and not pad_disp_start_x > pad_missing_x - 1:
+ scroll_keys = scroll_keys + "l"
+ else:
+ scroll_keys = scroll_keys + " "
+ if extra_lines < jack_term.size_y:
+ if jack_display.discname:
+ stdscr.addstr(0, 0, (jack_display.center_line(jack_display.options_string + " [" + scroll_keys + "]", fill = " ", width = jack_term.size_x))[:jack_term.size_x], A_REVERSE)
+ stdscr.addstr(1, 0, jack_display.center_line(jack_display.discname, fill = "- ", fill_r = " -", width = jack_term.size_x)[:jack_term.size_x], A_REVERSE)
+ else:
+ stdscr.addstr(0, 0, (jack_display.options_string + " " * (jack_term.size_x - len(jack_display.options_string) - (0 + 4)) + scroll_keys)[:jack_term.size_x], A_REVERSE)
+
+ if jack_display.special_line:
+ spec_pos = 1
+ if jack_display.discname:
+ spec_pos = 2
+ stdscr.addstr(spec_pos, 0, jack_display.center_line(jack_display.special_line, fill = " ", width = jack_term.size_x)[:jack_term.size_x], A_REVERSE)
+
+ if jack_display.bottom_line:
+ stdscr.addstr(jack_term.size_y - 1, 0, (jack_display.bottom_line + " " * (jack_term.size_x - len(jack_display.bottom_line) - 1 ))[:jack_term.size_x - 1], A_REVERSE)
+
+ stdscr.refresh()
+
+ usage_win_y, usage_win_x = jack_term.size_y - usage_win_height - 1, (jack_term.size_x - usage_win_width) / 2
+ if usage_win_y > extra_lines and usage_win_x > 0 and jack_term.size_y > extra_lines + 2 + usage_win_height and jack_term.size_x > usage_win_width:
+ del usage_win
+ usage_win = newwin(usage_win_height, usage_win_width, usage_win_y, usage_win_x)
+ usage_win.box()
+ usage_win.addstr(1, 2, "* * * " + jack_version.prog_name + " " + jack_version.prog_version + " " + jack_version.prog_copyright + " * * *")
+ usage_win.addstr(2, 2, "use cursor keys or hjkl to scroll status info")
+ usage_win.addstr(3, 2, "press P to disable/continue ripping,")
+ usage_win.addstr(4, 2, " E to pause/continue all encoders or")
+ usage_win.addstr(5, 2, " R to pause/continue all rippers.")
+ usage_win.refresh()
+
+ for i in jack_ripstuff.all_tracks_todo_sorted:
+ dae_stat_upd(i[NUM], jack_status.dae_status[i[NUM]])
+ enc_stat_upd(i[NUM], jack_status.enc_status[i[NUM]])
+
+ if pad_start_y < pad_end_y:
+ status_pad.refresh(pad_y, pad_x, pad_start_y, pad_start_x, pad_end_y, pad_end_x)
+ signal.signal(signal.SIGWINCH, sig_winch_handler)
+#/ end of sig_winch_handler(sig, frame) /#
+
+def move_pad(cmd):
+ global pad_disp_start_y, pad_disp_start_x, splash_reserve
+ if cmd in ("j", 'KEY_DOWN') and pad_disp_start_y < pad_height - 1:
+ pad_disp_start_y = pad_disp_start_y + 1
+ elif cmd in ("k", 'KEY_UP') and pad_disp_start_y > 0:
+ pad_disp_start_y = pad_disp_start_y - 1
+ elif cmd in ("l", 'KEY_RIGHT') and pad_disp_start_x < pad_width - 1:
+ pad_disp_start_x = pad_disp_start_x + 1
+ elif cmd in ("h", 'KEY_LEFT') and pad_disp_start_x > 0:
+ pad_disp_start_x = pad_disp_start_x - 1
+ elif cmd in ("?"):
+ if splash_reserve:
+ splash_reserve = 0
+ else:
+ splash_reserve = usage_win_height
+ sig_winch_handler(None, None)
+
+def disp_bottom_line(bottom_line):
+ stdscr.addstr(jack_term.size_y - 1, 0, (bottom_line + " " * (jack_term.size_x - len(bottom_line)))[:jack_term.size_x - 1], A_REVERSE)
+ if pad_start_y < pad_end_y:
+ status_pad.refresh(pad_y, pad_x, pad_start_y, pad_start_x, pad_end_y, pad_end_x)
+ stdscr.refresh()
+
+def getkey():
+ return stdscr.getkey()
+
+def update(special_line, bottom_line):
+ global had_special
+ global extra_lines
+
+ if special_line and not had_special:
+ had_special = 1
+ extra_lines = extra_lines + 1
+ sig_winch_handler(None, None)
+ elif had_special and not special_line:
+ had_special = 0
+ extra_lines = extra_lines - 1
+ sig_winch_handler(None, None)
+ if 1 < jack_term.size_y:
+ disp_bottom_line(bottom_line)
+
+def enc_stat_upd(num, string):
+ status_pad.addstr(map_track_num[num], jack_ripstuff.max_name_len + 40, " " + jack_status.enc_status[num])
+ status_pad.clrtoeol()
+
+def dae_stat_upd(num, string, reverse=-1):
+ track = jack_ripstuff.all_tracks[num-1]
+ if reverse >= 0:
+ split_point = int(6.5 + reverse / 100.0 * 32)
+ front = jack_ripstuff.printable_names[num] + ": " + jack_status.dae_status[num][:6]
+ middle = jack_status.dae_status[num][6:split_point]
+ end = jack_status.dae_status[num][split_point:] + " " + jack_status.enc_status[num]
+ status_pad.addstr(map_track_num[num], 0, front)
+ status_pad.addstr(map_track_num[num], len(front), middle, A_REVERSE)
+ status_pad.addstr(map_track_num[num], len(front + middle), end)
+ else:
+ status_pad.addstr(map_track_num[num], 0, (jack_ripstuff.printable_names[num] + ": " + jack_status.dae_status[num] + " " + jack_status.enc_status[num]))
+
+ dummy = """
+ if ripper == "cdparanoia" and track in dae_tracks or (track in enc_queue and track not in mp3s_done):
+ status_pad.addstr(map_track_num[num], 0, jack_ripstuff.printable_names[num] + ": " + jack_status.dae_status[num][:7])
+ pos = find(jack_status.dae_status[num], ">")
+ if pos < 7:
+ pos = 37
+ status_pad.addstr(jack_status.dae_status[num][7:pos], A_REVERSE)
+ status_pad.addstr(jack_status.dae_status[num][pos:])
+ else:
+ status_pad.addstr(map_track_num[num], 0, jack_ripstuff.printable_names[num] + ": " + jack_status.dae_status[num] + " " + jack_status.enc_status[num])
+"""
+
+ status_pad.clrtoeol()
diff --git a/jack_t_dumb.py b/jack_t_dumb.py
new file mode 100755
index 0000000..c3f19d6
--- /dev/null
+++ b/jack_t_dumb.py
@@ -0,0 +1,71 @@
+### jack_t_dumb: dumb terminal functions for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2002 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import termios
+import sys
+
+import jack_display
+import jack_status
+import jack_ripstuff
+from jack_globals import NUM
+
+old_tc = None
+
+def init():
+ pass
+
+def enable():
+ global old_tc
+ # set terminal attributes
+ new = termios.tcgetattr(sys.stdin.fileno())
+ if old_tc == None:
+ old_tc = new[:]
+ new[3] = new[3] & ~termios.ECHO
+ new[3] = new[3] & ~termios.ICANON
+ termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, new)
+ del new
+
+def disable():
+ if not old_tc:
+ return
+ termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_tc)
+
+def move_pad(cmd):
+ pass
+
+def getkey():
+ return sys.stdin.read(1)
+
+def sig_winch_handler(sig, frame):
+ pass
+
+def update(special_line, bottom_line):
+ print
+ print
+ if special_line:
+ print jack_display.center_line(special_line, fill = "#")
+ print jack_display.options_string
+ for i in jack_ripstuff.all_tracks_todo_sorted:
+ print jack_ripstuff.printable_names[i[NUM]] + ": " + jack_status.dae_status[i[NUM]], jack_status.enc_status[i[NUM]]
+ print bottom_line
+
+def enc_stat_upd(num, string):
+ pass
+
+def dae_stat_upd(num, string):
+ pass
diff --git a/jack_tag.py b/jack_tag.py
new file mode 100755
index 0000000..825c828
--- /dev/null
+++ b/jack_tag.py
@@ -0,0 +1,270 @@
+### jack_tag: name information (ID3 among others) stuff for
+### jack - tag audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2003 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import string
+import os, sys
+import locale
+
+import jack_functions
+import jack_ripstuff
+import jack_targets
+import jack_helpers
+import jack_freedb
+import jack_utils
+import jack_misc
+import jack_m3u
+
+from jack_init import ogg
+from jack_init import eyeD3
+from jack_init import flac
+from jack_globals import *
+
+track_names = None
+locale_names = None
+genretxt = None
+
+a_artist = None
+a_title = None
+
+def tag(freedb_rename):
+ global a_artist, a_title
+
+ ext = jack_targets.targets[jack_helpers.helpers[cf['_encoder']]['target']]['file_extension']
+
+ if cf['_vbr'] and not cf['_only_dae']:
+ total_length = 0
+ total_size = 0
+ for i in jack_ripstuff.all_tracks_todo_sorted:
+ total_length = total_length + i[LEN]
+ total_size = total_size + jack_utils.filesize(i[NAME] + ext)
+
+ if cf['_set_id3tag'] and not jack_targets.targets[jack_helpers.helpers[cf['_encoder']]['target']]['can_posttag']:
+ cf['_set_id3tag'] = 0
+
+ # maybe export?
+ if jack_freedb.names_available:
+ a_artist = track_names[0][0] # unicode
+ a_title = track_names[0][1] # unicode
+ p_artist = locale_names[0][0] # string
+ p_title = locale_names[0][1] # string
+
+ if cf['_set_id3tag'] or freedb_rename:
+ jack_m3u.init()
+ if len(track_names[0]) == 4:
+ # use freedb year and genre data if available
+ if cf['_id3_genre'] == -1:
+ cf['_id3_genre'] = track_names[0][3]
+ if cf['_id3_year'] == -1:
+ cf['_id3_year'] = track_names[0][2]
+
+ print "Tagging",
+ for i in jack_ripstuff.all_tracks_todo_sorted:
+ sys.stdout.write(".") ; sys.stdout.flush()
+ mp3name = i[NAME] + ext
+ wavname = i[NAME] + ".wav"
+ t_artist = track_names[i[NUM]][0]
+ t_name = track_names[i[NUM]][1]
+ t_comm = ""
+ if not cf['_only_dae'] and cf['_set_id3tag']:
+ if len(t_name) > 30:
+ if string.find(t_name, "(") != -1 and string.find(t_name, ")") != -1:
+ # we only use the last comment
+ t_comm = string.split(t_name, "(")[-1]
+ if t_comm[-1] == ")":
+ t_comm = t_comm[:-1]
+ if t_comm[-1] == " ":
+ t_comm = t_comm[:-1]
+ t_name2 = string.replace(t_name, " (" + t_comm + ") ", "")
+ t_name2 = string.replace(t_name2, " (" + t_comm + ")", "")
+ t_name2 = string.replace(t_name2, "(" + t_comm + ") ", "")
+ t_name2 = string.replace(t_name2, "(" + t_comm + ")", "")
+ else:
+ t_comm = ""
+ if jack_helpers.helpers[cf['_encoder']]['target'] == "mp3":
+ if cf['_write_id3v2']:
+ mp3file = file(mp3name, "rw")
+ tag = eyeD3.Tag()
+ tag.link(mp3file)
+ tag.header.setVersion(eyeD3.ID3_V2_4)
+ tag.setTextEncoding(eyeD3.UTF_8_ENCODING)
+ tag.setAlbum(a_title)
+ tag.setTitle(t_name)
+ tag.setTrackNum((i[NUM],len(jack_ripstuff.all_tracks_orig)))
+ tag.setTitle(t_name)
+ if t_artist:
+ tag.setArtist(t_artist)
+ else:
+ tag.setArtist(a_artist)
+ if cf['_id3_genre'] != -1:
+ tag.setGenre("(%d)" % (cf['_id3_genre']))
+ if cf['_id3_year'] != -1:
+ tag.setDate(cf['_id3_year'])
+ tag.setPlayCount(int(i[LEN] * 1000.0 / 75 + 0.5))
+ tag.update()
+ mp3file.close()
+ if cf['_write_id3v1']:
+ mp3file = file(mp3name, "rw")
+ tag = eyeD3.Tag()
+ tag.link(mp3file)
+
+ tag.header.setVersion(eyeD3.ID3_V1_1)
+ tag.setTextEncoding(eyeD3.LATIN1_ENCODING)
+ old_genre = tag.getGenre()
+
+ tag.setAlbum(a_title)
+ if t_comm:
+ tag.addComment(t_comm)
+ tag.setTitle(t_name2)
+ else:
+ tag.setTitle(t_name)
+ tag.setTrackNum((i[NUM],len(jack_ripstuff.all_tracks_orig)))
+ tag.setTitle(t_name)
+ if t_artist:
+ tag.setArtist(t_artist)
+ else:
+ tag.setArtist(a_artist)
+ if cf['_id3_genre'] != -1:
+ tag.setGenre("(%d)" % (cf['_id3_genre']))
+ elif old_genre == None:
+ tag.setGenre("(255)") # unknown
+ if cf['_id3_year'] != -1:
+ tag.setDate(cf['_id3_year'])
+ try:
+ tag.update()
+ except UnicodeEncodeError:
+ if not cf['_write_id3v2']:
+ print
+ print "Track %02d contains data not supported by id3v1; please use --write-id3v2" % i[NUM]
+ mp3file.close()
+ elif jack_helpers.helpers[cf['_encoder']]['target'] == "flac":
+ if flac:
+ chain = flac.metadata.Chain()
+ chain.read(mp3name)
+ it = flac.metadata.Iterator()
+ it.init(chain)
+ while 1:
+ if it.get_block_type() == flac.metadata.VORBIS_COMMENT:
+ block = it.get_block()
+ vc = flac.metadata.VorbisComment(block)
+ break
+ if not it.next():
+ break
+ if vc:
+ vc.comments['ALBUM'] = a_title.encode("utf-8")
+ vc.comments['TRACKNUMBER'] = `i[NUM]`
+ vc.comments['TITLE'] = t_name.encode("utf-8")
+ if t_artist:
+ vc.comments['ARTIST'] = t_artist.encode("utf-8")
+ else:
+ vc.comments['ARTIST'] = a_artist.encode("utf-8")
+ if cf['_id3_genre'] != -1:
+ vc.comments['GENRE'] = id3genres[cf['_id3_genre']]
+ if cf['_id3_year'] != -1:
+ vc.comments['DATE'] = `cf['_id3_year']`
+ chain.write(True, True)
+ else:
+ print
+ print "Please install the pyflac module available at"
+ print "http://www.sacredchao.net/quodlibet/wiki/Download"
+ print "Without it, you'll not be able to tag FLAC tracks."
+ elif jack_helpers.helpers[cf['_encoder']]['target'] == "ogg":
+ vf = ogg.vorbis.VorbisFile(mp3name)
+ oggi = vf.comment()
+ oggi.clear()
+ oggi.add_tag('ALBUM', a_title.encode("utf-8"))
+ oggi.add_tag('TRACKNUMBER', `i[NUM]`)
+ oggi.add_tag('TITLE', t_name.encode("utf-8"))
+ if t_artist:
+ oggi.add_tag('ARTIST', t_artist.encode("utf-8"))
+ else:
+ oggi.add_tag('ARTIST', a_artist.encode("utf-8"))
+ if cf['_id3_genre'] != -1:
+ oggi.add_tag('GENRE', id3genres[cf['_id3_genre']])
+ if cf['_id3_year'] != -1:
+ oggi.add_tag('DATE', `cf['_id3_year']`)
+ oggi.write_to(mp3name)
+ if freedb_rename:
+ if t_artist: # 'Various Artists'
+ replacelist = (("%n", cf['_rename_num'] % i[NUM]), ("%a", t_artist), ("%t", t_name), ("%l", a_title), ("%y", `cf['_id3_year']`), ("%g", genretxt))
+ newname = jack_misc.multi_replace(cf['_rename_fmt_va'], replacelist)
+
+ else:
+ replacelist = (("%n", cf['_rename_num'] % i[NUM]), ("%a", a_artist), ("%t", t_name), ("%l", a_title))
+ newname = jack_misc.multi_replace(cf['_rename_fmt'], replacelist)
+ exec("newname = newname" + cf['_char_filter'])
+ for char_i in range(len(cf['_unusable_chars'])):
+ newname = string.replace(newname, cf['_unusable_chars'][char_i], cf['_replacement_chars'][char_i])
+ try:
+ i[NAME] = unicode(i[NAME], "utf-8")
+ except UnicodeDecodeError:
+ i[NAME] = unicode(i[NAME], "latin-1")
+ if i[NAME] != newname:
+ p_newname = newname.encode(locale.getpreferredencoding(), "replace")
+ u_newname = newname
+ newname = newname.encode(cf['_charset'], "replace")
+ p_mp3name = i[NAME].encode(locale.getpreferredencoding(), "replace") + ext
+ p_wavname = i[NAME].encode(locale.getpreferredencoding(), "replace") + ".wav"
+ ok = 1
+ if os.path.exists(newname + ext):
+ ok = 0
+ print 'NOT renaming "' + p_mp3name + '" to "' + p_newname + ext + '" because dest. exists.'
+ if cf['_keep_wavs']:
+ print 'NOT renaming "' + p_wavname + '" to "' + p_newname + ".wav" + '" because dest. exists.'
+ elif cf['_keep_wavs'] and os.path.exists(newname + ".wav"):
+ ok = 0
+ print 'NOT renaming "' + p_wavname + '" to "' + p_newname + ".wav" + '" because dest. exists.'
+ print 'NOT renaming "' + p_mp3name + '" to "' + p_newname + ext + '" because WAV dest. exists.'
+ if ok:
+ if not cf['_only_dae']:
+ try:
+ os.rename(mp3name, newname + ext)
+ except OSError:
+ error('Cannot rename "%s" to "%s" (Filename is too long or has unusable characters)' % (p_mp3name, p_newname + ext))
+ jack_m3u.add(newname + ext)
+ if cf['_keep_wavs']:
+ os.rename(wavname, newname + ".wav")
+ jack_m3u.add_wav(newname + ".wav")
+ jack_functions.progress(i[NUM], "ren", "%s-->%s" % (i[NAME], u_newname))
+ elif cf['_silent_mode']:
+ jack_functions.progress(i[NUM], "err", "while renaming track")
+ print
+
+ if not cf['_silent_mode']:
+ if jack_freedb.names_available:
+ print "Done with \"" + p_artist + " - " + p_title + "\"."
+ else:
+ print "All done.",
+ if cf['_set_id3tag'] and cf['_id3_year'] != -1:
+ print "Year: %4i" % cf['_id3_year'],
+ if cf['_id3_genre'] == -1: print
+ if cf['_set_id3tag'] and cf['_id3_genre'] != -1:
+ if cf['_id3_genre'] <0 or cf['_id3_genre'] > len(id3genres):
+ print "Genre: [unknown]"
+ else:
+ print "Genre: %s" % id3genres[cf['_id3_genre']]
+ if cf['_vbr'] and not cf['_only_dae']:
+ print "Avg. bitrate: %03.0fkbit" % ((total_size * 0.008) / (total_length / 75))
+ else:
+ print
+
+ if jack_m3u.m3u:
+ os.environ["JACK_JUST_ENCODED"] = "\n".join(jack_m3u.m3u)
+ if jack_m3u.wavm3u:
+ os.environ["JACK_JUST_RIPPED"] = "\n".join(jack_m3u.wavm3u)
+ jack_m3u.write()
+
diff --git a/jack_targets.py b/jack_targets.py
new file mode 100644
index 0000000..0286ac7
--- /dev/null
+++ b/jack_targets.py
@@ -0,0 +1,65 @@
+### jack_targets.py: supportet target formats for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 2002 Arne Zellentin <zarne@users.sf.net>
+
+### 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 jack_globals import *
+
+try:
+ import ogg.vorbis
+ has_ogg_vorbis = 1
+ if ogg.vorbis.__version__ < "0.5":
+ has_ogg_vorbis = 0
+ debug("ogg.vorbis tagging disabled, version is too low.")
+except:
+ has_ogg_vorbis = 0
+ debug("ogg.vorbis tagging disabled.")
+
+# supported target file fomats
+targets = {
+ 'mp3': {
+ 'can_cbr': 1, # can do CBR = constant bitrate
+ 'can_vbr': 1, # can do VBR = variable bitrate
+ 'can_id3': 1, # can set a ID3 tag
+ 'can_pretag': 0, # can set tag info before encoding
+ 'can_posttag': 1, # can set tag info after encoding
+ 'file_extension': ".mp3" # the extension for this target format
+ },
+ 'ogg': {
+ 'can_cbr': 0,
+ 'can_vbr': 1,
+ 'can_id3': 1,
+ 'can_pretag': 1,
+ 'can_posttag': (1 & has_ogg_vorbis),
+ 'file_extension': ".ogg"
+ },
+ 'flac': {
+ 'can_cbr': 0,
+ 'can_vbr': 1,
+ 'can_id3': 1,
+ 'can_pretag': 0,
+ 'can_posttag': 1,
+ 'file_extension': ".flac"
+ },
+ 'mpc': {
+ 'can_cbr': 0,
+ 'can_vbr': 1,
+ 'can_id3': 0,
+ 'can_pretag': 0,
+ 'can_posttag': 0,
+ 'file_extension': ".mpc"
+ }
+ }
diff --git a/jack_term.py b/jack_term.py
new file mode 100755
index 0000000..f180499
--- /dev/null
+++ b/jack_term.py
@@ -0,0 +1,198 @@
+### jack_term: terminal specific stuff for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2002 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+# terminal is one of dumb, curses
+
+import array
+import traceback
+import fcntl
+import sys
+
+import jack_ripstuff
+import jack_freedb
+
+from jack_globals import *
+
+# exported functions:
+#init
+#enable
+#disable
+
+# expected functions in tmod:
+#update
+#getkey
+#sig_winch_handler
+
+# exported variables:
+enabled = None
+initialized = None
+size_x, size_y = None, None
+orig_size_x, orig_size_y = None, None
+term_type = None
+sig_winch_cache = None
+
+# variables
+xtermset = None
+can_getsize = None
+geom_changed = None
+
+
+def init(arg_type="auto", arg_xtermset = 0):
+ global initialized
+ global geom_changed
+ global tmod
+ global xtermset
+ global term_type
+ global size_x, size_y
+ global orig_size_x, orig_size_y
+ size_x, size_y = None, None
+
+ if initialized:
+ return
+
+ # import the terminal specific module
+ if arg_type == "auto":
+ try:
+ import jack_t_curses as tmod
+ term_type = "curses"
+ except:
+ import jack_t_dumb as tmod
+ term_type = "dump"
+ elif arg_type == "dumb":
+ import jack_t_dumb as tmod
+ term_type = "dump"
+ elif arg_type == "curses":
+ import jack_t_curses as tmod
+ term_type = "curses"
+
+ if not tmod:
+ error("invalid terminal type `%s'" % term_type)
+
+ xtermset = arg_xtermset
+
+ initialized = 1
+ geom_changed = 0
+ size_x, size_y = 80, 24 # fallback value
+
+ oldsize = getsize()
+ if oldsize != (None, None):
+ orig_size_x, orig_size_y = oldsize
+ size_x, size_y = oldsize
+ del oldsize
+
+def xtermset_enable():
+ global xtermset
+ global geom_changed
+ if xtermset:
+ import os
+ want_x = 80 - len("track_00") + jack_ripstuff.max_name_len
+ want_y = len(jack_ripstuff.all_tracks_todo_sorted) + 3
+ if term_type == "curses":
+ want_y = want_y - 1
+ if jack_freedb.names_available:
+ want_y = want_y + 1
+ want_y = max(want_y, 7)
+ if (size_x, size_y) != (want_x, want_y):
+ try:
+ os.system("xtermset -geom %dx%d" % (want_x, want_y))
+ geom_changed = 1
+ resize()
+ except:
+ warning("failed to call xtermset, is it really installed?")
+ xtermset = 0
+ del want_x, want_y
+
+def xtermset_disable():
+ import os
+ global geom_changed
+ if xtermset and geom_changed:
+ try:
+ os.system("xtermset -restore -geom %dx%d" % (orig_size_x, orig_size_y))
+ geom_changed = 0
+ except:
+ pass
+
+def getsize():
+ global can_getsize
+ if can_getsize == 0:
+ return None, None
+
+ if can_getsize == None:
+ try:
+ from IOCTLS import TIOCGWINSZ
+ except ImportError:
+ try:
+ from termios import TIOCGWINSZ
+ except ImportError:
+ # TIOCGWINSZ = 0x5413 # linux, ix86. Anyone else?
+ can_getsize = 0
+ warning("""could not find a module which exports
+TIOCGWINSZ. This means I can't determine your terminal's geometry, so please
+don't resize it. Use Tools/scripts/h2py.py from the Python source distribution
+to convert /usr/include/asm/ioctls.h to IOCTLS.py and install it.""")
+ return None, None
+ try:
+ # to get the size, we will have to do an ioctl which will return a
+ # struct winsize {
+ # unsigned short ws_row;
+ # unsigned short ws_col;
+ # unsigned short ws_xpixel;
+ # unsigned short ws_ypixel;
+ # };
+ # (according to _I386_TERMIOS_H, /usr/include/asm/termios.h)
+
+ winsize = array.array("H")
+ data = " " * (winsize.itemsize * 4)
+ data = fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ, data)
+ # unpack the data, I hope this is portable:
+ winsize.fromstring(data)
+ new_y, new_x, xpixel, ypixel = winsize.tolist()
+ except:
+ can_getsize = 0
+ return None, None
+ return new_x, new_y
+
+def resize():
+ global size_x, size_y
+ x, y = getsize()
+ if (x, y) != (None, None):
+ size_x, size_y = x, y
+
+def enable():
+ global enabled
+
+ if not initialized:
+ return
+
+ if enabled:
+ return
+
+ xtermset_enable()
+ tmod.enable()
+ enabled = 1
+
+def disable():
+ global enabled
+ import os
+
+ if not enabled or not initialized:
+ return
+
+ tmod.disable()
+ xtermset_disable()
+ enabled = 0
diff --git a/jack_utils.py b/jack_utils.py
new file mode 100755
index 0000000..4d332a4
--- /dev/null
+++ b/jack_utils.py
@@ -0,0 +1,222 @@
+### jack_utils: utility functions for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2002 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import sys
+import signal
+import types
+import os
+import stat
+import string
+
+import jack_functions
+import jack_globals
+import jack_misc
+import jack_term
+
+from jack_globals import *
+
+def all_paths(p):
+ "return all path leading to and including p"
+ if type(p) == types.StringType:
+ p = split_dirname(p)
+ all = []
+ x = ""
+ for i in p:
+ x = os.path.join(x, i)
+ all.append(x)
+ return all
+
+def check_path(p1, p2):
+ "check if p1 and p2 are equal or sub/supersets"
+ if type(p1) == types.StringType:
+ p1 = split_dirname(p1)
+ if type(p2) == types.StringType:
+ p2 = split_dirname(p2)
+ for i in p1, p2:
+ if type(i) != types.ListType:
+ error("invalid type for check_path" + `i`)
+ if len(p1) > len(p2): # make sure p1 is shorter or as long as p2
+ p1, p2 = p2, p1
+ ok = 1
+ for i in range(1, len(p1) + 1):
+ if p1[-i] != p2[-i]:
+ ok = 0
+ return ok
+
+def rename_path(old, new):
+ "this is complicated."
+ cwd = os.getcwd()
+ print cwd
+ cwds = split_dirname(cwd)
+ if type(old) == types.StringType:
+ old = split_dirname(old)
+ if type(new) == types.StringType:
+ new = split_dirname(new)
+ for i in old, new, cwds:
+ if type(i) != types.ListType:
+ error("invalid type for rename_path: " + `i`)
+
+ # weed out empty dirs (which are technically illegal on freedb but exist)
+ tmp = []
+ for i in new:
+ if i:
+ tmp.append(i)
+ new = tmp
+ del tmp
+
+ for i in old:
+ os.chdir(os.pardir)
+ for i in new[:-1]:
+ if not os.path.exists(i):
+ try:
+ os.mkdir(i)
+ except OSError:
+ error('Cannot create directory "%s" (Filename is too long or has unusable characters)' % i)
+ if os.path.isdir(i):
+ os.chdir(i)
+ else:
+ error("could not create or change to " + i + " from " + os.getcwd())
+
+ last_of_new = new[-1]
+ if os.path.exists(last_of_new):
+ error("destination directory already exists: " + last_of_new)
+ try:
+ os.rename(cwd, last_of_new)
+ except OSError:
+ error('Cannot rename "%s" to "%s" (Filename is too long or has unusable characters)' % (cwd, last_of_new))
+ os.chdir(last_of_new)
+ # now remove empty "orphan" dirs
+
+ old_dirs = all_paths(cwds)
+ old_dirs.reverse()
+ for i in old_dirs[:len(old)][1:]:
+ try:
+ os.rmdir(i)
+ except OSError:
+ pass
+
+def cmp_toc(x, y):
+ "compare two track's length"
+ x, y = x[LEN], y[LEN]
+ if x > y: return 1
+ elif x == y: return 0
+ elif x < y: return -1
+
+NUM, LEN, START, COPY, PRE, CH, RIP, RATE, NAME = range(9)
+def cmp_toc_cd(x, y, what=(NUM, LEN, START)):
+ "compare the relevant parts of two TOCs"
+ if len(x) == len(y):
+ for i in range(len(x)):
+ for j in what:
+ if x[i][j] != y[i][j]:
+ return 0
+ else:
+ return 0
+ return 1
+
+def filesize(name):
+ return os.stat(name)[stat.ST_SIZE]
+
+def yes(what):
+ if what.has_key('save') and what['save'] == 0:
+ return ""
+
+ if what['type'] == 'toggle':
+ if what['val']:
+ s = "yes"
+ else:
+ s = "no"
+ elif what['type'] == types.StringType:
+ s = "'%s'" % what['val']
+ else:
+ s = str(what['val'])
+
+ s = " [%s]" % s
+ if what['history'][-1][0] == "global_rc":
+ s = s + "*"
+ if what.has_key('doc'):
+ s = s + " +"
+ return s
+
+def safe_float(number, message):
+ try:
+ return float(number)
+ except ValueError:
+ print message
+ sys.exit(1)
+
+def unusable_charmap(x):
+ for i in range(len(cf['_unusable_chars'])):
+ x = string.replace(x, cf['_unusable_chars'][i], cf['_replacement_chars'][i])
+ return x
+
+def mkdirname(names, template):
+ "generate mkdir-able directory name(s)"
+ dirs = template.split(os.path.sep)
+
+ dirs2 = []
+ for i in dirs:
+ replace_list = (("%a", names[0][0].encode(cf['_charset'], "replace")),
+ ("%l", names[0][1].encode(cf['_charset'], "replace")),
+ ("%y", `cf['_id3_year']`), ("%g", cf['_id3_genre_txt']))
+ x = jack_misc.multi_replace(i, replace_list, unusable_charmap)
+ exec("x = x" + cf['_char_filter'])
+ dirs2.append(x)
+ if cf['_append_year'] and len(`cf['_id3_year']`) == 4: # Y10K bug!
+ dirs2[-1] = dirs2[-1] + jack_misc.multi_replace(cf['_append_year'], replace_list)
+ name = ""
+ for i in dirs2:
+ name = os.path.join(name, i)
+ return dirs2, name
+
+def split_dirname(name):
+ "split path in components"
+ names = []
+ while 1:
+ base, sub = os.path.split(name)
+ if not base or base == os.sep:
+ names.append(os.path.join(base, sub))
+ break
+ names.append(sub)
+ name = base
+ names.reverse()
+ return names
+
+def split_path(path, num):
+ "split given path in num parts"
+ new_path = []
+ for i in range(1, num):
+ base, end = os.path.split(path)
+ path = base
+ new_path.append(end)
+ new_path.append(base)
+ new_path.reverse()
+
+def ex_edit(file):
+ editor = "/usr/bin/sensible-editor"
+ if os.environ.has_key("EDITOR"):
+ editor = os.environ['EDITOR']
+ print "invoking your editor,", editor, "..."
+ os.system(string.split(editor)[0] + " " + file)
+
+def has_track(l, num):
+ for i in range(len(l)):
+ if l[i][NUM] == num:
+ return i
+ return -1.5
+
diff --git a/jack_version.py b/jack_version.py
new file mode 100644
index 0000000..37f60dd
--- /dev/null
+++ b/jack_version.py
@@ -0,0 +1,29 @@
+### jack_version: define program version and name for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 2002 Arne Zellentin <zarne@users.sf.net>
+
+### 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 string import split
+from sys import version
+
+prog_version = "3.1.1"
+prog_name = "jack"
+prog_rcversion = 31
+prog_copyright = "(C)2004 Arne Zellentin"
+prog_devemail = "<zarne@users.sf.net>"
+py_version = split(version)[0]
+
+
diff --git a/jack_workers.py b/jack_workers.py
new file mode 100755
index 0000000..b341745
--- /dev/null
+++ b/jack_workers.py
@@ -0,0 +1,372 @@
+### jack_workers: worker functions for
+### jack - extract audio from a CD and encode it using 3rd party software
+### Copyright (C) 1999-2004 Arne Zellentin <zarne@users.sf.net>
+
+### 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
+
+import sndhdr
+import signal
+import string
+import posix
+import array
+import fcntl
+import wave
+import time
+import pty
+import sys
+import os
+
+import jack_functions
+import jack_ripstuff
+import jack_helpers
+import jack_targets
+import jack_utils
+import jack_tag
+
+from jack_globals import *
+from jack_helpers import helpers
+from jack_init import F_SETFL, O_NONBLOCK
+
+def default_signals():
+ signal.signal(signal.SIGTERM, signal.SIG_DFL)
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+ signal.signal(signal.SIGQUIT, signal.SIG_DFL)
+ signal.signal(signal.SIGHUP, signal.SIG_DFL)
+ signal.signal(signal.SIGWINCH, signal.SIG_DFL)
+
+def start_new_process(args, nice_value = 0):
+ "start a new process in a pty and renice it"
+ data = {}
+ data['start_time'] = time.time()
+ pid, master_fd = pty.fork()
+ if pid == CHILD:
+ default_signals()
+ if nice_value:
+ os.nice(nice_value)
+ os.execvp(args[0], [a.encode(cf['_charset'], "replace") for a in args])
+ else:
+ data['pid'] = pid
+ if os.uname()[0] == "Linux":
+ fcntl.fcntl(master_fd, F_SETFL, O_NONBLOCK)
+ data['fd'] = master_fd
+ data['file'] = os.fdopen(master_fd)
+ data['cmd'] = args
+ data['buf'] = ""
+ data['otf'] = 0
+ data['percent'] = 0
+ data['elapsed'] = 0
+ return data
+
+def start_new_ripper(track, ripper):
+ "start a new DAE process"
+ helper = helpers[cf['_ripper']]
+ cmd = string.split(helper['cmd'])
+ args = []
+ for i in cmd:
+ if i == "%n": args.append(`track[NUM]`)
+ elif i == "%o": args.append(track[NAME].decode(cf['_charset'], "replace") + ".wav")
+ elif i == "%d": args.append(cf['_cd_device'])
+ elif i == "%D": args.append(cf['_gen_device'])
+ else: args.append(i)
+ data = start_new_process(args)
+ data['type'] = "ripper"
+ data['prog'] = cf['_ripper']
+ data['track'] = track
+ return data
+
+def start_new_encoder(track, encoder):
+ "start a new encoder process"
+ helper = helpers[cf['_encoder']]
+ if cf['_vbr']:
+ cmd = string.split(helper['vbr-cmd'])
+ else:
+ cmd = string.split(helper['cmd'])
+
+ args = []
+ for i in cmd:
+ if i == "%r":
+ args.append(`track[RATE] * helper['bitrate_factor']`)
+ elif i == "%q":
+ if helper.has_key('inverse-quality') and helper['inverse-quality']:
+ quality = min(9, 10 - cf['_vbr_quality'])
+ else:
+ quality = cf['_vbr_quality']
+ args.append("%.3f" % quality)
+ elif i == "%i":
+ args.append(track[NAME].decode(cf['_charset'], "replace") + ".wav")
+ elif i == "%o":
+ args.append(track[NAME].decode(cf['_charset'], "replace") + jack_targets.targets[jack_helpers.helpers[cf['_encoder']]['target']]['file_extension'])
+ else:
+ if jack_targets.targets[helper['target']]['can_pretag']:
+ if i == "%t":
+ if jack_tag.track_names:
+ args.append(jack_tag.track_names[track[NUM]][1])
+ else:
+ args.append("")
+ elif i == "%a":
+ if jack_tag.track_names:
+ if jack_tag.track_names[track[NUM]][0]:
+ args.append(jack_tag.track_names[track[NUM]][0])
+ else:
+ args.append(jack_tag.track_names[0][0])
+ else:
+ args.append("")
+ elif i == "%n":
+ args.append(`track[NUM]`)
+ elif i == "%l":
+ if jack_tag.track_names:
+ args.append(jack_tag.track_names[0][1])
+ else:
+ args.append("")
+ elif i == "%G":
+ if cf['_id3_genre'] >= 0: args.append(cf['_id3_genre'])
+ else: args.append('255')
+ elif i == "%g":
+ if cf['_id3_genre'] >= 0: args.append(jack_tag.genretxt)
+ else: args.append('Unknown')
+ elif i == "%y":
+ if cf['_id3_year'] > 0:
+ args.append(`cf['_id3_year']`)
+ else:
+ args.append('0')
+ else:
+ args.append(i)
+ else:
+ args.append(i)
+ data = start_new_process(args, cf['_nice_value'])
+ data['type'] = "encoder"
+ data['prog'] = cf['_encoder']
+ data['track'] = track
+ return data
+
+def start_new_otf(track, ripper, encoder):
+ "start a new ripper/encoder pair for on-the-fly encoding"
+ data = {}
+ data['rip'] = {}
+ data['enc'] = {}
+ data['rip']['otf'] = 1
+ data['enc']['otf'] = 1
+ enc_in, rip_out = os.pipe()
+ data['rip']['fd'], rip_err = os.pipe()
+ data['enc']['fd'], enc_err = os.pipe()
+ args = []
+ for i in string.split(helpers[ripper]['otf-cmd']):
+ if i == "%n": args.append(`track[NUM]`)
+ elif i == "%d": args.append(cf['_cd_device'])
+ elif i == "%D": args.append(cf['_gen_device'])
+ else: args.append(i)
+ data['rip']['start_time'] = time.time()
+ pid = os.fork()
+ if pid == CHILD:
+ default_signals()
+ os.dup2(rip_out, STDOUT_FILENO)
+ os.dup2(rip_err, STDERR_FILENO)
+ os.close(rip_out)
+ os.close(rip_err)
+ os.execvp(args[0], args)
+ # child won't see anything below...
+ os.close(rip_out)
+ os.close(rip_err)
+ data['rip']['pid'] = pid
+ data['rip']['cmd'] = helpers[cf['_ripper']]['otf-cmd']
+ data['rip']['buf'] = ""
+ data['rip']['percent'] = 0
+ data['rip']['elapsed'] = 0
+ data['rip']['type'] = "ripper"
+ data['rip']['prog'] = cf['_ripper']
+ data['rip']['track'] = track
+ if cf['_vbr']:
+ cmd = string.split(helpers[cf['_encoder']]['vbr-otf-cmd'])
+ else:
+ cmd = string.split(helpers[cf['_encoder']]['otf-cmd'])
+ args = []
+ for i in cmd:
+ if i == "%r":
+ args.append(`track[RATE] * helpers[cf['_encoder']]['bitrate_factor']`)
+ elif i == "%q":
+ if helper.has_key('inverse-quality') and helper['inverse-quality']:
+ quality = min(9, 10 - cf['_vbr_quality'])
+ else:
+ quality = cf['_vbr_quality']
+ args.append("%.3f" % quality)
+ elif i == "%o":
+ args.append(track[NAME].decode(cf['_charset'], "replace") + jack_targets.targets[jack_helpers.helpers[cf['_encoder']]['target']]['file_extension'])
+ elif i == "%d":
+ args.append(cf['_cd_device'])
+ elif i == "%D":
+ args.append(cf['_gen_device'])
+ else:
+ args.append(i)
+ data['enc']['start_time'] = time.time()
+ pid = os.fork()
+ if pid == CHILD:
+ default_signals()
+ if cf['_nice_value']:
+ os.nice(cf['_nice_value'])
+ os.dup2(enc_in, STDIN_FILENO)
+ os.dup2(enc_err, STDERR_FILENO)
+ os.close(enc_in)
+ os.close(enc_err)
+ os.execvp(args[0], args)
+ # child won't see anything below...
+ os.close(enc_in)
+ os.close(enc_err)
+ data['enc']['pid'] = pid
+ data['enc']['otf-pid'] = data['rip']['pid']
+ data['enc']['cmd'] = cmd
+ data['enc']['buf'] = ""
+ data['enc']['percent'] = 0
+ data['enc']['elapsed'] = 0
+ data['enc']['type'] = "encoder"
+ data['enc']['prog'] = cf['_encoder']
+ data['enc']['track'] = track
+ data['rip']['otf-pid'] = data['enc']['pid']
+
+ if os.uname()[0] == "Linux":
+ fcntl.fcntl(data['rip']['fd'], F_SETFL, O_NONBLOCK)
+ fcntl.fcntl(data['enc']['fd'], F_SETFL, O_NONBLOCK)
+ data['rip']['file'] = os.fdopen(data['rip']['fd'])
+ data['enc']['file'] = os.fdopen(data['enc']['fd'])
+ return data
+
+def ripread(track, offset = 0):
+ "rip one track from an image file."
+ data = {}
+ start_time = time.time()
+ pid, master_fd = pty.fork() # this could also be done with a pipe, anyone?
+ if pid == CHILD:
+ #debug:
+ #so=open("/tmp/stdout", "w")
+ #sys.stdout = so
+ #se=open("/tmp/stderr", "w+")
+ #sys.stderr = se
+ default_signals()
+
+# FIXME: all this offset stuff has to go, track 0 support has to come.
+
+ print ":fAE: waiting for status report..."
+ sys.stdout.flush()
+ hdr = sndhdr.whathdr(cf['_image_file'])
+ my_swap_byteorder = cf['_swap_byteorder']
+ my_offset = offset
+ if hdr:
+
+## I guess most people use cdparanoia 1- (instead of 0- if applicable)
+## for image creation, so for a wav file use:
+
+ image_offset = -offset
+
+ else:
+ if string.upper(cf['_image_file'])[-4:] == ".CDR":
+ hdr = ('cdr', 44100, 2, -1, 16) # Unknown header, assuming cdr
+#
+## assume old cdrdao which started at track 1, not at block 0
+ image_offset = -offset
+
+ elif string.upper(cf['_image_file'])[-4:] == ".BIN":
+ hdr = ('bin', 44100, 2, -1, 16) # Unknown header, assuming bin
+#
+## assume new cdrdao which starts at block 0, byteorder is reversed.
+ my_swap_byteorder = not my_swap_byteorder
+ image_offset = 0
+
+ elif string.upper(cf['_image_file'])[-4:] == ".RAW":
+ hdr = ('bin', 44100, 2, -1, 16) # Unknown header, assuming raw
+ image_offset = 0
+
+ else:
+ debug("unsupported image file " + cf['_image_file'])
+ posix._exit(4)
+
+ expected_filesize = jack_functions.tracksize(jack_ripstuff.all_tracks)[CDR] + CDDA_BLOCKSIZE * offset
+#
+## WAVE header is 44 Bytes for normal PCM files...
+#
+ if hdr[0] == 'wav':
+ expected_filesize = expected_filesize + 44
+
+ if abs(jack_utils.filesize(cf['_image_file']) - expected_filesize) > CDDA_BLOCKSIZE:
+ # we *do* allow a difference of one frame
+ debug("image file size mismatch, aborted. %d != %d" % (jack_utils.filesize(cf['_image_file']), expected_filesize))
+ posix._exit(1)
+
+ elif hdr[0] == 'wav' and (hdr[1], hdr[2], hdr[4]) != (44100, 2, 16):
+ debug("unsupported WAV, need CDDA_fmt, aborted.")
+ posix._exit(2)
+
+ elif hdr[0] not in ('wav', 'cdr', 'bin'):
+ debug("unsupported: " + hdr[0] + ", aborted.")
+ posix._exit(3)
+
+ else:
+ f = open(cf['_image_file'], 'r')
+#
+## set up output wav file:
+#
+ wav = wave.open(track[NAME].decode(cf['_charset'], "replace") + ".wav", 'w')
+ wav.setnchannels(2)
+ wav.setsampwidth(2)
+ wav.setframerate(44100)
+ wav.setnframes(0)
+ wav.setcomptype('NONE', 'not compressed')
+#
+## calculate (and seek to) position in image file
+#
+ track_start = (track[START] + image_offset) * CDDA_BLOCKSIZE
+ if hdr[0] == 'wav':
+ track_start = track_start + 44
+ f.seek(track_start)
+#
+## copy / convert the stuff
+#
+ for i in range(0, track[LEN]):
+ buf = array.array("h")
+ buf.fromfile(f, 1176) # CDDA_BLOCKSIZE / 2
+ if not my_swap_byteorder: # this is inverted as WAVE swabs them anyway.
+ buf.byteswap()
+ wav.writeframesraw(buf.tostring())
+ if i % 1000 == 0:
+ print ":fAE: Block " + `i` + "/" + `track[LEN]` + (" (%2i%%)" % (i * 100 / track[LEN]))
+ sys.stdout.flush()
+ wav.close()
+ f.close()
+
+ stop_time = time.time()
+ read_speed = track[LEN] / CDDA_BLOCKS_PER_SECOND / ( stop_time - start_time )
+ if read_speed < 100:
+ print "[%2.0fx]" % read_speed,
+ else:
+ print "[99x]",
+ if hdr[0] in ('bin', 'wav'):
+ print "[ - read from image - ]"
+ else:
+ print "[cdr-WARNING, check byteorder !]"
+ sys.stdout.flush()
+ posix._exit(0)
+ else: # we are not the child
+ data['start_time'] = start_time
+ data['pid'] = pid
+ data['fd'] = master_fd
+ data['file'] = os.fdopen(master_fd)
+ data['cmd'] = ""
+ data['buf'] = ""
+ data['type'] = "image_reader"
+ data['prog'] = "builtin"
+ data['track'] = track
+ data['percent'] = 0
+ data['otf'] = 0
+ data['elapsed'] = 0
+ return data
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..92fd2ce
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+"""Setup script for the jack module distribution."""
+
+from distutils.core import setup, Extension
+
+setup( # Distribution meta-data
+ name = "jack",
+ version = "3.1.1",
+ description = "A frontend for several cd-rippers and mp3 encoders",
+ author = "Arne Zellentin",
+ author_email = "zarne@users.sf.net",
+ url = "http://www.home.unix-ag.org/arne/jack/",
+
+ # Description of the modules and packages in the distribution
+ ext_modules = [ Extension('jack_cursesmodule',
+ ['cursesmodule/jack_cursesmodule.c'], libraries=["ncurses"],
+ extra_compile_args=["-Wno-strict-prototypes"]) ],
+
+ py_modules = [ 'jack_CDTime', 'jack_TOC', 'jack_TOCentry', 'jack_argv',
+ 'jack_checkopts', 'jack_children', 'jack_config', 'jack_constants',
+ 'jack_display', 'jack_encstuff', 'jack_freedb', 'jack_functions',
+ 'jack_generic', 'jack_globals', 'jack_helpers', 'jack_init', 'jack_m3u',
+ 'jack_main_loop', 'jack_misc', 'jack_mp3', 'jack_playorder',
+ 'jack_plugins', 'jack_prepare', 'jack_progress', 'jack_rc',
+ 'jack_ripstuff', 'jack_status', 'jack_t_curses', 'jack_t_dumb', 'jack_tag',
+ 'jack_targets', 'jack_term', 'jack_utils', 'jack_version', 'jack_workers']
+)
+
+print "If you have installed the modules, copy jack to some place in your $PATH,"
+print "like /usr/local/bin/."