414 lines
9.1 KiB
Python
Executable File
414 lines
9.1 KiB
Python
Executable File
#! /usr/bin/env python
|
|
|
|
# XXX This only works on SGIs running IRIX 4.0 or higher
|
|
|
|
# JUKEBOX: browse directories full of sampled sound files.
|
|
#
|
|
# One or more "list windows" display the files and subdirectories of
|
|
# the arguments. Double-clicking on a subdirectory opens a new window
|
|
# displaying its contents (and so on recursively). Double clicking
|
|
# on a file plays it as a sound file (assuming it is one).
|
|
#
|
|
# Playing is asynchronous: the application keeps listening for events
|
|
# while the sample is playing, so you can cancel playing or start a
|
|
# new sample right away. Synchronous playing is available through the
|
|
# -s option.
|
|
#
|
|
# The control window displays a "stop button" that cancel the current
|
|
# play request.
|
|
#
|
|
# Most sound file formats recognized by SOX or SFPLAY are recognized.
|
|
# Since conversion is costly, converted files are cached in
|
|
# /usr/tmp/@j* until the user quits or changes the sampling rate via
|
|
# the Rate menu.
|
|
|
|
import commands
|
|
import getopt
|
|
import os
|
|
from stat import *
|
|
import rand
|
|
import stdwin
|
|
from stdwinevents import *
|
|
import sys
|
|
import tempfile
|
|
import sndhdr
|
|
|
|
from WindowParent import WindowParent
|
|
from Buttons import PushButton
|
|
|
|
# Pathnames
|
|
|
|
DEF_DB = '/usr/local/sounds' # Default directory of sounds
|
|
SOX = '/usr/local/bin/sox' # Sound format conversion program
|
|
SFPLAY = '/usr/sbin/sfplay' # Sound playing program
|
|
|
|
|
|
# Global variables
|
|
|
|
class struct: pass # Class to define featureless structures
|
|
|
|
G = struct() # Holds writable global variables
|
|
|
|
|
|
# Main program
|
|
|
|
def main():
|
|
G.synchronous = 0 # If set, use synchronous audio.write()
|
|
G.debug = 0 # If set, print debug messages
|
|
G.busy = 0 # Set while asynchronous playing is active
|
|
G.windows = [] # List of open windows, except control
|
|
G.mode = '' # File type (default any that sfplay knows)
|
|
G.rate = 0 # Sampling rate (default " " " ")
|
|
G.tempprefix = tempfile.mktemp()
|
|
#
|
|
try:
|
|
optlist, args = getopt.getopt(sys.argv[1:], 'dr:st:')
|
|
except getopt.error, msg:
|
|
sys.stdout = sys.stderr
|
|
print msg
|
|
print 'usage: jukebox [-d] [-s] [-t type] [-r rate]'
|
|
print ' -d debugging (-dd event debugging)'
|
|
print ' -s synchronous playing'
|
|
print ' -t type file type'
|
|
print ' -r rate sampling rate'
|
|
sys.exit(2)
|
|
#
|
|
for optname, optarg in optlist:
|
|
if optname == '-d':
|
|
G.debug = G.debug + 1
|
|
elif optname == '-r':
|
|
G.rate = int(eval(optarg))
|
|
elif optname == '-s':
|
|
G.synchronous = 1
|
|
elif optname == '-t':
|
|
G.mode = optarg
|
|
#
|
|
if G.debug:
|
|
for name in G.__dict__.keys():
|
|
print 'G.' + name, '=', `G.__dict__[name]`
|
|
#
|
|
if not args:
|
|
args = [DEF_DB]
|
|
#
|
|
G.cw = opencontrolwindow()
|
|
for dirname in args:
|
|
G.windows.append(openlistwindow(dirname))
|
|
#
|
|
#
|
|
try:
|
|
maineventloop()
|
|
finally:
|
|
clearcache()
|
|
killchild()
|
|
|
|
# Entries in Rate menu:
|
|
rates = ['default', '7350', \
|
|
'8000', '11025', '16000', '22050', '32000', '41000', '48000']
|
|
|
|
def maineventloop():
|
|
mouse_events = WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP
|
|
while G.windows:
|
|
try:
|
|
type, w, detail = event = stdwin.getevent()
|
|
except KeyboardInterrupt:
|
|
killchild()
|
|
continue
|
|
if w == G.cw.win:
|
|
if type == WE_CLOSE:
|
|
return
|
|
if type == WE_TIMER:
|
|
checkchild()
|
|
if G.busy:
|
|
G.cw.win.settimer(1)
|
|
elif type == WE_MENU:
|
|
menu, item = detail
|
|
if menu is G.ratemenu:
|
|
clearcache()
|
|
if item == 0:
|
|
G.rate = 0
|
|
else:
|
|
G.rate = eval(rates[item])
|
|
for i in range(len(rates)):
|
|
menu.check(i, (i == item))
|
|
else:
|
|
G.cw.dispatch(event)
|
|
else:
|
|
if type == WE_DRAW:
|
|
w.drawproc(w, detail)
|
|
elif type in mouse_events:
|
|
w.mouse(w, type, detail)
|
|
elif type == WE_CLOSE:
|
|
w.close(w)
|
|
del w, event
|
|
else:
|
|
if G.debug > 1: print type, w, detail
|
|
|
|
def checkchild():
|
|
if G.busy:
|
|
waitchild(1)
|
|
|
|
def killchild():
|
|
if G.busy:
|
|
os.kill(G.busy, 9)
|
|
waitchild(0)
|
|
|
|
def waitchild(options):
|
|
pid, sts = os.waitpid(G.busy, options)
|
|
if pid == G.busy:
|
|
G.busy = 0
|
|
G.stop.enable(0)
|
|
|
|
|
|
# Control window -- to set gain and cancel play operations in progress
|
|
|
|
def opencontrolwindow():
|
|
stdwin.setdefscrollbars(0, 0)
|
|
cw = WindowParent().create('Jukebox', (0, 0))
|
|
#
|
|
stop = PushButton().definetext(cw, ' Stop ')
|
|
stop.hook = stop_hook
|
|
stop.enable(0)
|
|
G.stop = stop
|
|
#
|
|
cw.realize()
|
|
#
|
|
G.ratemenu = cw.win.menucreate('Rate')
|
|
for r in rates:
|
|
G.ratemenu.additem(r)
|
|
if G.rate == 0:
|
|
G.ratemenu.check(0, 1)
|
|
else:
|
|
for i in len(range(rates)):
|
|
if rates[i] == `G.rate`:
|
|
G.ratemenu.check(i, 1)
|
|
#
|
|
return cw
|
|
|
|
def stop_hook(self):
|
|
killchild()
|
|
|
|
|
|
# List windows -- to display list of files and subdirectories
|
|
|
|
def openlistwindow(dirname):
|
|
list = os.listdir(dirname)
|
|
list.sort()
|
|
i = 0
|
|
while i < len(list):
|
|
if list[i][0] == '.':
|
|
del list[i]
|
|
else:
|
|
i = i+1
|
|
for i in range(len(list)):
|
|
fullname = os.path.join(dirname, list[i])
|
|
if os.path.isdir(fullname):
|
|
info = '/'
|
|
else:
|
|
try:
|
|
size = os.stat(fullname)[ST_SIZE]
|
|
info = `(size + 1023)/1024` + 'k'
|
|
except IOError:
|
|
info = '???'
|
|
info = '(' + info + ')'
|
|
list[i] = list[i], info
|
|
width = maxwidth(list)
|
|
# width = width + stdwin.textwidth(' ') # XXX X11 stdwin bug workaround
|
|
height = len(list) * stdwin.lineheight()
|
|
stdwin.setdefwinsize(width, min(height, 500))
|
|
stdwin.setdefscrollbars(0, 1)
|
|
w = stdwin.open(dirname)
|
|
stdwin.setdefwinsize(0, 0)
|
|
w.setdocsize(width, height)
|
|
w.drawproc = drawlistwindow
|
|
w.mouse = mouselistwindow
|
|
w.close = closelistwindow
|
|
w.dirname = dirname
|
|
w.list = list
|
|
w.selected = -1
|
|
return w
|
|
|
|
def maxwidth(list):
|
|
width = 1
|
|
for name, info in list:
|
|
w = stdwin.textwidth(name + ' ' + info)
|
|
if w > width: width = w
|
|
return width
|
|
|
|
def drawlistwindow(w, area):
|
|
## (left, top), (right, bottom) = area
|
|
d = w.begindrawing()
|
|
d.erase((0, 0), (1000, 10000))
|
|
lh = d.lineheight()
|
|
h, v = 0, 0
|
|
for name, info in w.list:
|
|
if info == '/':
|
|
text = name + '/'
|
|
else:
|
|
text = name + ' ' + info
|
|
d.text((h, v), text)
|
|
v = v + lh
|
|
showselection(w, d)
|
|
d.close()
|
|
|
|
def hideselection(w, d):
|
|
if w.selected >= 0:
|
|
invertselection(w, d)
|
|
|
|
def showselection(w, d):
|
|
if w.selected >= 0:
|
|
invertselection(w, d)
|
|
|
|
def invertselection(w, d):
|
|
lh = d.lineheight()
|
|
h1, v1 = p1 = 0, w.selected*lh
|
|
h2, v2 = p2 = 1000, v1 + lh
|
|
d.invert(p1, p2)
|
|
|
|
def mouselistwindow(w, type, detail):
|
|
(h, v), clicks, button = detail[:3]
|
|
d = w.begindrawing()
|
|
lh = d.lineheight()
|
|
if 0 <= v < lh*len(w.list):
|
|
i = v / lh
|
|
else:
|
|
i = -1
|
|
if w.selected <> i:
|
|
hideselection(w, d)
|
|
w.selected = i
|
|
showselection(w, d)
|
|
d.close()
|
|
if type == WE_MOUSE_DOWN and clicks >= 2 and i >= 0:
|
|
setcursors('watch')
|
|
name, info = w.list[i]
|
|
fullname = os.path.join(w.dirname, name)
|
|
if info == '/':
|
|
if clicks == 2:
|
|
G.windows.append(openlistwindow(fullname))
|
|
else:
|
|
playfile(fullname)
|
|
setcursors('cross')
|
|
|
|
def closelistwindow(w):
|
|
G.windows.remove(w)
|
|
|
|
def setcursors(cursor):
|
|
for w in G.windows:
|
|
w.setwincursor(cursor)
|
|
G.cw.win.setwincursor(cursor)
|
|
|
|
|
|
# Playing tools
|
|
|
|
cache = {}
|
|
|
|
def clearcache():
|
|
for x in cache.keys():
|
|
cmd = 'rm -f ' + cache[x]
|
|
if G.debug: print cmd
|
|
sts = os.system(cmd)
|
|
if sts:
|
|
print cmd
|
|
print 'Exit status', sts
|
|
del cache[x]
|
|
|
|
validrates = (8000, 11025, 16000, 22050, 32000, 44100, 48000)
|
|
|
|
def playfile(filename):
|
|
killchild()
|
|
try:
|
|
tuple = sndhdr.what(filename)
|
|
except IOError, msg:
|
|
print 'Can\'t open', filename, msg
|
|
stdwin.fleep()
|
|
return
|
|
raw = 0
|
|
if tuple:
|
|
mode, rate = tuple[:2]
|
|
if rate == 0:
|
|
rate = G.rate
|
|
if rate == 0:
|
|
rate = 8000
|
|
else:
|
|
mode = G.mode
|
|
rate = G.rate
|
|
if G.debug: print 'mode =', mode, 'rate =', rate
|
|
if mode in ('au', 'aiff', 'wav', 'aifc', 'ul', 'ub', 'sb') and \
|
|
rate in validrates:
|
|
tempname = filename
|
|
if mode in ('ul', 'ub', 'sb'):
|
|
raw = 1
|
|
elif cache.has_key(filename):
|
|
tempname = cache[filename]
|
|
else:
|
|
tempname = G.tempprefix + `rand.rand()` + '.aiff'
|
|
cmd = SOX
|
|
if G.debug:
|
|
cmd = cmd + ' -V'
|
|
if mode <> '':
|
|
cmd = cmd + ' -t ' + mode
|
|
cmd = cmd + ' ' + commands.mkarg(filename)
|
|
cmd = cmd + ' -t aiff'
|
|
if rate not in validrates:
|
|
rate = 32000
|
|
if rate:
|
|
cmd = cmd + ' -r ' + `rate`
|
|
cmd = cmd + ' ' + tempname
|
|
if G.debug: print cmd
|
|
sts = os.system(cmd)
|
|
if sts:
|
|
print cmd
|
|
print 'Exit status', sts
|
|
stdwin.fleep()
|
|
try:
|
|
os.unlink(tempname)
|
|
except:
|
|
pass
|
|
return
|
|
cache[filename] = tempname
|
|
if raw:
|
|
pid = sfplayraw(tempname, tuple)
|
|
else:
|
|
pid = sfplay(tempname, [])
|
|
if G.synchronous:
|
|
sts = os.wait(pid, 0)
|
|
else:
|
|
G.busy = pid
|
|
G.stop.enable(1)
|
|
G.cw.win.settimer(1)
|
|
|
|
def sfplayraw(filename, tuple):
|
|
args = ['-i']
|
|
type, rate, channels, frames, bits = tuple
|
|
if type == 'ul':
|
|
args.append('mulaw')
|
|
elif type == 'ub':
|
|
args = args + ['integer', '8', 'unsigned']
|
|
elif type == 'sb':
|
|
args = args + ['integer', '8', '2scomp']
|
|
else:
|
|
print 'sfplayraw: warning: unknown type in', tuple
|
|
if channels > 1:
|
|
args = args + ['channels', `channels`]
|
|
if not rate:
|
|
rate = G.rate
|
|
if rate:
|
|
args = args + ['rate', `rate`]
|
|
args.append('end')
|
|
return sfplay(filename, args)
|
|
|
|
def sfplay(filename, args):
|
|
if G.debug:
|
|
args = ['-p'] + args
|
|
args = [SFPLAY, '-r'] + args + [filename]
|
|
if G.debug: print 'sfplay:', args
|
|
pid = os.fork()
|
|
if pid == 0:
|
|
# Child
|
|
os.execv(SFPLAY, args)
|
|
# NOTREACHED
|
|
else:
|
|
# Parent
|
|
return pid
|
|
|
|
main()
|