oidua-0.16.1/0040777000104000000220000000000007722737527014075 5ustar AdministratörerSYSTEMoidua-0.16.1/audiodir.py0100644000104000010010000001530307722512445016242 0ustar AdministratörerIngen# -*- coding: iso-8859-1 -*- # # A module for gathering information about a directory of audio files # # This program is under GPL license. See COPYING file for details. # # Copyright 2003 Sylvester Johansson (sylvestor@telia.com) # Mattias Päivärinta (mpa99001@student.mdh.se) import re, os, string, time import audiotype, conf __version__ = "0.16.1" def _is_audio_file(file): return os.path.isfile(file) and re.search("(?i)\.(?:mp3|mpc|ogg|flac)$", file) def uniq(list): """make a list with all duplicate elements removed""" list[0] = [ list[0] ] return reduce(lambda A,x: x in A and A or A+[x], list) def map_dict(func, dict): for key in dict.keys(): dict[key] = func(dict[key]) return dict def dir_test(dir): """check if it's a readable directory""" if not os.path.isdir(dir) or not os.access(dir, os.R_OK): return 0 # does os.access(file, os.R_OK) not work for windows? try: os.chdir(dir) return 1 except OSError: return 0 def to_minutes(value): return "%i:%02i" % (value / 60, value % 60) def to_human(value, radix=1024.0): i = 0 while value >= radix: value /= radix i += 1 suffix = " kMG"[i] if value > 100: return "%d%s" % (value, suffix) elif value < 10: return "%.2f%s" % (value, suffix) else: return "%.1f%s" % (value, suffix) class Dir: def __init__(self, filename, depth=0): self.path = filename self.depth = depth self._children = None self._streams = None self._num_streams = None self._subdirs = None self._types = None self._size = None self._length = None self._lengths = None self._bitrate = None self._br_list = [] self._brtype = None self._profile = None self._bad = None self._date = None def name(self): return os.path.basename(self.path) or self.path def path(self): return os.path.dirname(self.path) def children(self): if self._children: return self._children self._children = map(lambda x: os.path.join(self.path, x), os.listdir(self.path)) return self._children def subdirs(self): if self._subdirs != None: return self._subdirs self._subdirs = filter(dir_test, self.children()) return self._subdirs def streams(self): if self._streams: return self._streams self._streams = [] self._bad = [] self._num_streams = 0 list = filter(_is_audio_file, self.children()) for child in list: self._num_streams += 1 try: self._streams.append(audiotype.openstream(child)) except KeyboardInterrupt: raise KeyboardInterrupt except audiotype.SpacerError: continue except Exception, msg: self._bad.append(child) return self._streams def bad_streams(self): self.streams() return self._bad def num_files(self): return len(filter(_is_audio_file, self.children())) def types(self): if self._types != None: return self._types types = map(lambda x: x.type(), self.streams()) self._types = uniq(types) self._types.sort() return self._types def type(self): if not self.types(): return "?" elif len(self.types()) == 1: return self.types()[0] else: return "Mixed" def size(self, type="all"): """report size in bytes Note: The size reported is the total audio file size, not the total directory size.""" if self._size != None: return self._size[type] self._size = {} self._size["all"] = 0 for file in self.streams(): if file.type() in self._size: self._size[file.type()] += file.streamsize() else: self._size[file.type()] = file.streamsize() self._size["all"] += file.streamsize() return self._size[type] def length(self, type="all"): if self._length != None: return self._length[type] tot = 0 self._length = {} self._length["all"] = 0 for file in self.streams(): if file.type() in self._length: self._length[file.type()] += file.time else: self._length[file.type()] = file.time self._length["all"] += file.time self._length = map_dict(int, self._length) return self._length[type] def _variable_bitrate(self): if self.length() == 0: self._bitrate = 0 else: self._bitrate = int(self.size() * 8.0 / self.length()) def _constant_bitrate(self): for file in self.streams(): br = file.bitrate() if self._bitrate == None: self._bitrate = br elif self._bitrate != br: self._brtype = "~" self._variable_bitrate() self._bitrate = int(self._bitrate) def brtype(self): """report the bitrate type If multiple types are found "~" is returned. If no audio is found the empty string is returned.""" if self._brtype: return self._brtype self._brtype = "" if self.type() == "Mixed": self._brtype = "~" return self._brtype for file in self.streams(): type = file.brtype() if self._brtype == "": self._brtype = type elif self._brtype != type: self._brtype = "~" break if self._brtype == "C": self._constant_bitrate() return self._brtype def bitrate(self): """report average bitrate in bits per second If no audio is found zero is returned.""" if self._bitrate: return self._bitrate if self.brtype() != "C": self._variable_bitrate() return self._bitrate def profile(self): if self._profile != None: return self._profile if self.brtype() == "~": self._profile = "" return self._profile for file in self.streams(): p = file.profile() if not self._profile: self._profile = p if not p or p != self._profile: self._profile = "" break return self._profile def quality(self): if self.profile(): return self.profile() return "%s %s" % (self.bitrate() / 1000, self.brtype()) def audiolist_format(self): if self.brtype() == "V": return "VBR" list = [] for stream in self.streams(): if stream.brtype() == "C": br = stream.bitrate() / 1000 else: table = {"V": "VBR", "L": "LL"} br = table[stream.brtype()] if br not in list: list.append(br) list.sort() return string.join(map(str, list), ", ") def modified(self): if self._date: return self._date dates = map(lambda x: x.modified(), self.streams()) dates.append(os.path.getmtime(self.path)) self._date = max(dates) return self._date def get(self, id): table = { "a": lambda: self.audiolist_format(), "b": lambda: to_human(self.bitrate(), 1000.0), "B": lambda: self.bitrate(), "D": lambda: self.depth, "f": lambda: self.num_files(), "l": lambda: to_minutes(self.length()), "L": lambda: self.length(), "m": lambda: time.ctime(self.modified()), "M": lambda: self.modified(), "n": lambda: " " * conf.conf.Indent * self.depth + self.name(), "N": lambda: self.name(), "p": lambda: self.profile(), "P": lambda: self.path, "q": lambda: self.quality(), "s": lambda: to_human(self.size()), "S": lambda: self.size(), "t": lambda: self.type(), "T": lambda: self.brtype() } return table[id]() oidua-0.16.1/audiotype.py0100644000104000010010000003772307722513665016464 0ustar AdministratörerIngen# -*- coding: iso-8859-1 -*- # # A module for gathering information about an audio file # # This program is under GPL license. See COPYING file for details. # # Copyright 2003 Sylvester Johansson (sylvestor@telia.com) # Mattias Päivärinta (mpa99001@student.mdh.se) # TODO: Finish the API for type classes # TODO: Make type classes lazy (later) # TODO: Implement *.audiosize() that also excludes vorbis comments and such? import os, re, string, struct, sys, warnings __version__ = "0.16" class SpacerError(Exception): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) class AudioType: def __init__(self, file): self._filename = file self._f = open(self._filename, 'rb') self._begin = None self._end = None self._meta = [] def name(self): return os.path.basename(self._filename) def path(self): return os.path.dirname(self._filename) def modified(self): return os.path.getmtime(self._filename) def bitrate(self): return int(self.streamsize() * 8.0 / self.length()) def filesize(self): return os.path.getsize(self._filename) def streamsize(self): return self.stream_end() - self.stream_begin() def stream_begin(self): if self._begin != None: return self._begin mark = self._f.tell() self._begin = 0 # check for prepended ID3v2 self._f.seek(0) if self._f.read(3) == "ID3": self._meta.append((0, "ID3v2")) data = struct.unpack("2x5B", self._f.read(7)) self._begin += 10 + unpack_bits(data[-4:]) if data[0] & 0x40: extsize = struct.unpack("4B", self._f.read(4)) self._begin += unpack_bits(extsize) if data[0] & 0x10: self._begin += 10 self._f.seek(mark) return self._begin def stream_end(self): if self._end != None: return self._end mark = self._f.tell() self._end = os.path.getsize(self._filename) # check for ID3v1 self._f.seek(-128, 2) if self._f.read(3) == "TAG": self._end -= 128 self._meta.append((self._end, "ID3v1")) # check for appended ID3v2 self._f.seek(self._end - 10) if self._f.read(3) == "3DI": data = struct.unpack("2x5B", self._f.read(7)) self._end -= 20 + unpack_bits(data[-4:]) if data[0] & 0x40: extsize = struct.unpack("4B", self._f.read(4)) self._end -= unpack_bits(extsize) self._meta.append((end, "ID3v2")) self._f.seek(mark) return self._end def get(self, id, width=None): table = { "n": lambda: self.name(), "p": lambda: self.path(), "t": lambda: self.type(), "d": lambda: self.modified(), "s": lambda: self.filesize(), "S": lambda: self.streamsize(), "T": lambda: self.brtype(), "P": lambda: self.profile(), "b": lambda: self.bitrate(), "f": lambda: self.freq(), "c": lambda: self.channels(), "l": lambda: self.time, "v": lambda: self.vendor, "V": lambda: self.version, "q": lambda: (self.o.profile() or "%i %s" % (self.o.bitrate, self.o.brtype)) } data = table[id]() if width != None: data = "%*s" % (width, str(data)[:width]) return data class Ogg(AudioType): def __init__(self, file): AudioType.__init__(self, file) self.header = self.getheader() self.version = self.header[1] self._channels = self.header[2] self._freq = self.header[3] self.maxbitrate = self.header[4] self.nombitrate = self.header[5] self.minbitrate = self.header[6] #self.blocksize1 = self.header[7] #self.blocksize2 = self.header[8] self.audiosamples = self.lastgranule()[-1] self.time = float(self.audiosamples) / self._freq def type(self): return "Ogg" def brtype(self): return "V" def freq(self): return self._freq def channels(self): return self._channels def length(self): return self.time def getheader(self): # Setup header and sync stuff syncpattern = '\x01vorbis' overlap = len(syncpattern) - 1 headerformat = ' overlap: # Look for sync sync = chunk.find(syncpattern) if sync != -1: # Read the header self._f.seek(start + sync) return struct.unpack(headerformat, self._f.read(headersize)) # Read next chunk start = start + 1024 self._f.seek(start + overlap) chunk = chunk[-overlap:] + self._f.read(1024) def lastgranule(self): # The setup header and sync stuff syncpattern = 'OggS' overlap = len(syncpattern) - 1 headerformat = '<4s2xl' headersize = struct.calcsize(headerformat) # Read last chunk self._f.seek(-1024, 2) start = self._f.tell() chunk = self._f.read(1024) # Do all file if we have to while len(chunk) > overlap: # Look for sync sync = chunk.find(syncpattern) if sync != -1: # Read the header self._f.seek(start + sync) return struct.unpack(headerformat, self._f.read(headersize)) # Read next block start = start - 1024 self._f.seek(start) chunk = self._f.read(1024) + chunk[:overlap] def profile(self): xiph = [80001,96001,112001,128003,160003,192003,224003,256006,320017,499821] gt3 = [128005,180003,212003,244003,276006,340017,519821] if self.nombitrate in xiph: return "-q" + str(xiph.index(self.nombitrate) + 1) if self.nombitrate in gt3: return "-q" + str(gt3.index(self.nombitrate) + 4) class MP3(AudioType): brtable = [ [ #MPEG2 & 2.5 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0], #Layer III [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0], #Layer II [0, 32, 48, 56, 64, 80, 96,112,128,144,160,176,192,224,256,0] #Layer I ], [ #MPEG1 [0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,0], #Layer III [0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384,0], #Layer II [0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448,0] #Layer I ] ] fqtable = [ [32000, 16000, 8000], #MPEG 2.5 [ 0, 0, 0], #reserved [22050, 24000, 16000], #MPEG 2 [44100, 48000, 32000] #MPEG 1 ] def __init__(self,file): AudioType.__init__(self, file) self.brtable = [ [ #MPEG2 & 2.5 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0], #Layer III [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0], #Layer II [0, 32, 48, 56, 64, 80, 96,112,128,144,160,176,192,224,256,0] #Layer I ], [ #MPEG1 [0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,0], #Layer III [0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384,0], #Layer II [0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448,0] #Layer I ] ] self.fqtable = [ [32000, 16000, 8000], #MPEG 2.5 [ 0, 0, 0], #reserved [22050, 24000, 16000], #MPEG 2 [44100, 48000, 32000] #MPEG 1 ] #try: self.mp3header = self.getheader(self.stream_begin()) self._brtype = "CV"[self.mp3header[1]=='Xing'] self.framesync = (self.mp3header[0]>>21 & 2047) self.versionindex = (self.mp3header[0]>>19 & 3) self.layerindex = (self.mp3header[0]>>17 & 3) self.protectionbit = (self.mp3header[0]>>16 & 1) bitrateindex = (self.mp3header[0]>>12 & 15) frequencyindex = (self.mp3header[0]>>10 & 3) self.paddingbit = (self.mp3header[0]>>9 & 1) self.privatebit = (self.mp3header[0]>>8 & 1) self.modeindex = (self.mp3header[0]>>6 & 3) self.modeextindex = (self.mp3header[0]>>4 & 3) self.copyrightbit = (self.mp3header[0]>>3 & 1) self.originalbit = (self.mp3header[0]>>2 & 1) self.emphasisindex = (self.mp3header[0] & 3) #self.framecount = (self.mp3header[3]) self.framesize = (self.mp3header[4]) self.vendor = self.mp3header[6] self._freq = self.fqtable[self.versionindex][frequencyindex] self._channels = [2,2,2,1][self.modeindex] if self._brtype == "V": if self.framesize <= 0: self.framesize = AudioType.streamsize(self) self.framecount = self.mp3header[3] + 1 self._bitrate = int(1000.0 * self.framesize * self._freq / float(self.modificator() * self.framecount)) else: self._bitrate = int(1000.0 * self.brtable[self.versionindex & 1][self.layerindex-1][bitrateindex]) self.framecount = int(self.streamsize() * self._freq / float(self.modificator() * self._bitrate) * 1000) #print self.framecount #self.time = (float(1 * 576 * (bool(self.versionindex>>1)+ 1)) / self.freq) * self.framecount self.time = self.streamsize() * 8.0 / self._bitrate def type(self): return "MP3" def freq(self): return self._freq def brtype(self): return self._brtype def channels(self): return self._channels def length(self): return self.time def bitrate(self): return self._bitrate def streamsize(self): if self.brtype() == "V": return self.framesize else: return AudioType.streamsize(self) def getheader(self, offset = 0): # Setup header and sync stuff syncre = re.compile('\xff[\xe0-\xff]') overlap = 1 pattern = '>l32x4s3l100xL9s2B8x2B' patternsize = struct.calcsize(pattern) # Read first block self._f.seek(offset) start = self._f.tell() chunk = self._f.read(1024 + overlap) # Do all file if we have to while len(chunk) > overlap: # Look for sync sync = syncre.search(chunk) while sync: # Read header self._f.seek(start + sync.start()) header = struct.unpack(pattern,self._f.read(patternsize)) #if header[0] >= 0x54414700 and header[0] <= 0x544147ff: #raise SpacerError("Spacer found") # raise IOError # Return the header if it's valid if self.valid(header[0]): return header # How about next sync in this block? sync = syncre.search(chunk, sync.start() + 1) # Read next chunk start = start + 1024 self._f.seek(start + overlap) chunk = chunk[-overlap:] + self._f.read(1024) if offset == self.filesize(): raise SpacerError("Spacer found %s" % self._f.name) self._f.seek(offset) tag = struct.unpack(">3s",self._f.read(struct.calcsize(">3s"))) if tag[0] == "TAG": raise SpacerError("Spacer found %s" % self._f.name) def modificator(self): if self.layerindex == 3: return 12000 else: return 144000 def valid(self, header): return (((header>>21 & 2047) == 2047) and ((header>>19 & 3) != 1) and ((header>>17 & 3) != 0) and ((header>>12 & 15) != 0) and ((header>>12 & 15) != 15) and ((header>>10 & 3) != 3) and ((header & 3) != 2)) def profile(self): if self.mp3header[6][:4] == "LAME": try: version = string.atof(self.mp3header[6][4:8]) except ValueError: return "" vbrmethod = self.mp3header[7] & 15 lowpass = self.mp3header[8] ath = self.mp3header[9] & 15 if version < 3.90: #lame version if vbrmethod == 8: #unknown if lowpass in [97, 98]: if ath == 0: return "-r3mix" if version >= 3.90: #lame version if vbrmethod == 3: #vbr-old / vbr-rh if lowpass in [195, 196]: if ath == 2: return "-ape" if lowpass == 190: if ath == 4: return "-aps" if vbrmethod == 4: #vbr-mtrh if lowpass == 190: if ath == 4: return "-apfs" if lowpass in [195, 196]: if ath == 2: return "-apfe" if ath == 3: return "-r3mix" if vbrmethod == 2: #abr if lowpass == 206: if ath == 2: return "-api" return "" class MPC(AudioType): def __init__(self,file): AudioType.__init__(self,file) self.profiletable = [ 'NoProfile', 'Unstable', 'Unspec.', 'Unspec.', 'Unspec.', 'BelowTel.', 'BelowTel.', 'Telephone', 'Thumb', 'Radio', 'Standard', 'Xtreme', 'Insane', 'Braindead', 'AbvBrnded', 'AbvBrnded' ] fqtable = [44100,48000,37800,32000] # self.profile = self.profiletable[self.getheader()[3]>>20 & 15] self._freq = fqtable[self.getheader()[3]>>16 & 4] self.framecount= self.getheader()[2] self._bitrate = int(self.streamsize() * 144000.0 / float(self.framecount * self._freq)) self.time = (float(self.framecount) * 1.150 / float(44.1) + float(0.5)) def type(self): return "MPC" def brtype(self): return "V" def channels(self): return 2 def freq(self): return self._freq def bitrate(self): return self._bitrate def headerstart(self): self._f.seek(0) for x in range(self.filesize() / 1024): buffer = self._f.read(1024) if re.search('MP+',buffer): return (x * 1024) + string.find(buffer,'MP+') def getheader(self): # Setup header and sync stuff syncre = re.compile('MP+') overlap = 1 pattern = '3sb2i4h' patternsize = struct.calcsize(pattern) # Read first block self._f.seek(0) start = self._f.tell() chunk = self._f.read(1024 + overlap) # Do all file if we have to while len(chunk) > overlap: # Look for sync sync = syncre.search(chunk) while sync: # Read header self._f.seek(start + sync.start()) header = struct.unpack(pattern,self._f.read(patternsize)) # Return the header if it's valid if header[1]==7: return header # How about next sync in this block? sync = syncre.search(chunk, sync.start() + 1) # Read next chunk start = start + 1024 self._f.seek(start + overlap) chunk = chunk[-overlap:] + self._f.read(1024) def profile(self): return self.profiletable[self.getheader()[3] >> 20 & 0xF] class FLAC(AudioType): def __init__(self, file): AudioType.__init__(self, file) # [(sample number, byte offset, samples in frame), ...] self.seekpoints = [] # vorbis comments self.comments = [] self.streaminfo = None # 0 minimum blocksize # 1 maximum blocksize # 2 minimum framesize # 3 maximum framesize # 4 sample rate in Hz # 5 number of channels # 6 bits per sample # 7 total samples in stream # 8 MD5 sum of unencoded audio self._f.seek(self.stream_begin()) self.parse() self.samples = self.streaminfo[7] self._freq = self.streaminfo[4] self.time = self.samples / self._freq self._bitrate = self.streamsize() * 8 / self.time self._channels = self.streaminfo[5] samplebits = self.streaminfo[6] self.compression = self.streamsize() / (self.samples * samplebits * self._channels / 8) self.encoding = "%.1f%%" % self.compression def type(self): return "FLAC" def brtype(self): return "L" def profile(self): return "" def freq(self): return self._freq def channels(self): return self._channels def parse(self): # STREAM if struct.unpack('>4s', self._f.read(4))[0] != 'fLaC': return last = 0 while not last: # METADATA_BLOCK_HEADER data = struct.unpack('>I', self._f.read(4))[0] last = data >> 31 type = data >> 24 & 0x7F length = data & 0x00FFFFFF if type == 0: # Stream Info data = struct.unpack('>3HI3Q', self._f.read(34)) self.streaminfo = ( data[0], data[1], (data[2] << 8) | (data[3] >> 24), data[3] & 0x00FFFFFF, data[4] >> 44, (data[4] >> 41 & 0x07) + 1, (data[4] >> 36 & 0x1F) + 1, data[4] & 0x0000000FFFFFFFFFL) elif type == 3: # Seektable for i in range(length/18): self.seekpoints.append(struct.unpack('2QH', self._f.read(18))) elif type == 4: # Vorbis Comment self.commentvendor, self.comments = self.readOggCommentHeader() #print "framing", framing else: # Padding or unknown self._f.seek(length, 1) def readLength(self): len = struct.unpack('= self.streamsize(): raise ValueError return len def readString(self): return self._f.read(self.readLength()) def readOggCommentHeader(self): list = [] vendor = self.readString() for i in range(self.readLength()): list.append(self.readString()) return vendor, list #, struct.unpack('B', fd.read(1)) def has_suffix(str, suffix): """check string for suffix""" return suffix == string.lower(str[-len(suffix):]) def unpack_bits(bits): """Unpack ID3's syncsafe 7bit number format.""" value = 0 for chunk in bits: value = value << 7 value = value | chunk return value def openstream(filename): if has_suffix(filename, ".mp3"): return MP3(filename) elif has_suffix(filename, ".mpc"): return MPC(filename) elif has_suffix(filename, ".ogg"): return Ogg(filename) elif has_suffix(filename, ".flac"): return FLAC(filename) else: return None oidua-0.16.1/buildexe.py0100744000104000000220000000113107722450240016216 0ustar AdministratörerSYSTEM#!/usr/bin/python # -*- coding: iso-8859-1 -*- # # Script for building a win32 executable from Oidua. # # This program is under GPL license. See COPYING file for details. # # Copyright 2003 Sylvester Johansson (sylvestor@telia.com) # Mattias Päivärinta (mpa99001@student.mdh.se) # buildexe.py # Use this script to build a oidua win32 exe-file. # Needs the py2exe module (http://starship.python.net/crew/theller/py2exe/) # Invoke with "buildexe.py py2exe --console -d [destination path]" from distutils.core import setup import py2exe setup(name="oidua", scripts=["oidua.py"] ) oidua-0.16.1/conf.py0100644000104000010010000001467007722512051015366 0ustar AdministratörerIngen# -*- coding: iso-8859-1 -*- # # Configuration module for Oidua # # This program is under GPL license. See COPYING file for details. # # Copyright 2003 Sylvester Johansson (sylvestor@telia.com) # Mattias Päivärinta (mpa99001@student.mdh.se) # TODO: dir_test is duplicated in audiodir.py __version__ = "0.16.1" import getopt, glob, os, re, string, sys class Settings: def __init__(self): # error messages are the only thing Settings should ever print sys.stdout = sys.__stderr__ self.BGColor = "white" self.IgnoreCase = 0 self.Indent = 4 self.Debug = 0 self.Delayed = 1 self.DispVersion = 0 self.DispTime = 0 self.DispHelp = 0 self.DispDate = 0 self.DispResult = 0 self.ExcludePaths = [] self.Folders = {} self.ListBad = 1 self.Merge = 0 self.OutputFormat = "plain" self.OutStream = sys.__stdout__ self.Quiet = 0 self.TextColor = "black" self.Wildcards = 0 self.RawOutputString = "[n,-52]| [s,5] | [t,-4] | [q]" self.Fields = [] self.OutputString = "" self.Stripped = 0 # parse the command line if not self.parse(): print "Type 'oidua.py -h' for help." sys.exit(2) # format outputstring self.process_outputstring() # direct stdout to the correct stream sys.stdout = self.OutStream def parse(self): shortopts = "B:T:p:e:W:f:I:o:DhHimqStVwcs" + "gp:W:" longopts = [ "bg=", "exclude=", "date", "debug", "file=", "help", "ignore-bad", "ignore-case", "indent=", "merge", "output=" "quiet", "stats", "strip", "text=", "time", "version", "wildcards"] + ["global-sort", "preset=", "width="] try: opts, args = (getopt.getopt(sys.argv[1:], shortopts, longopts)) except getopt.GetoptError, (msg, opt): print "Invalid option '%s': %s" % (opt, msg) return 0 # parse option pairs for o, a in opts: if o in ("-B", "--bg"): self.BGColor = a elif o in ("-D", "--date"): self.DispDate = 1 elif o == "--debug": self.Debug = 1 elif o in ("-e", "--exclude"): self.exclude_dir(a) elif o in ("-f", "--file"): self.set_outstream(a) elif o in ("-H", "--html"): self.OutputFormat = "HTML" elif o in ("-h", "--help"): self.DispHelp = 1 elif o == "--ignore-bad": self.ListBad = 0 elif o in ("-i", "--ignore-case"): self.IgnoreCase = 1 elif o in ("-I", "--indent"): self.Indent = string.atoi(a) elif o in ("-m", "--merge"): self.Merge = 1 elif o in ("-q", "--quiet"): self.Quiet = 1 elif o in ("-s", "--strip"): self.Stripped = 1 elif o in ("-S", "--stats"): self.DispResult = 1 elif o in ("-T", "--text"): self.TextColor = a elif o in ("-t", "--time"): self.DispTime = 1 elif o in ("-V", "--version"): self.DispVersion = 1 elif o in ("-w", "--wildcards"): self.Wildcards = 1 elif o in ("-o", "--output"): self.RawOutputString = a elif o in ("-g", "--global-sort", "-p", "--preset", "-W", "--width"): print "The '%s' option is no longer supported." % o return 0 else: print "This should never happen!" print "Unknown option", (o, a) return 0 # add basedirs to both self.Folder and self.ExcludePaths self.paircount = 0 for glob_dir in args: dirs = self.expand(glob_dir) self.ExcludePaths += dirs for key, dir in map(self.add_key, dirs): self.add_basedir(key, dir) del self.paircount # reject "no operation" configurations if (not self.Folders and not self.DispVersion and not self.DispHelp): print "No folders to process." return 0 # options overriding eachother if self.Debug or self.OutStream.isatty(): self.Quiet = 1 if self.Debug: self.ListBad = 1 return 1 def add_key(self, dir): """make a (sortkey, value) pair from a path""" if self.Merge: key = os.path.basename(dir) or dir else: self.paircount += 1 key = "%06d" % self.paircount return (key, dir) def set_outstream(self, file): """open output stream for writing""" try: self.OutStream = open(file, 'w') except IOError, (errno, errstr): print "I/O Error(%s): %s" % (errno, errstr) print "Cannot open '%s' for writing" % file sys.exit(2) def exclude_dir(self, dir): """add a directory to exclude-list""" if dir[-1] == os.sep: dir = dir[:-1] if os.path.isdir(dir): self.ExcludePaths.append(dir) else: print "There is no directory '%s'" % dir sys.exit(2) def expand(self, dir): """translate a basedir to a list of absolute paths""" if self.Wildcards and re.search("[*?]|(?:\[.*\])", dir): list = map(os.path.abspath, self.sort(glob.glob(dir))) else: list = [ os.path.abspath(dir) ] return filter(self.dir_test, list) def add_basedir(self, key, dir): """add directory with sortkey to self.Folders""" if self.Folders.has_key(key): self.Folders[key].append(dir) else: self.Folders[key] = [ dir ] def process_outputstring(self): parts = re.split(r"(? Copyright (C) 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. , 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. oidua-0.16.1/NEWS0100644000104000000220000001026207722507371014556 0ustar AdministratörerSYSTEMVersion 0.16.1 - 2003-08-25 * fixed the multiple basedirs and --ignore-case bug * fixed recognition of --indent and --ignore-case long options * fixed oidua to actually use the --ignore-bad option * fixed "bad files" logic for --debug * fixed case insensitive sorting of wildcard expansions * fixed the broken unescaping of escaped brackets in the format string * documented the --merge option * improved error handling in the conf module * all files now refer to COPYING instead of including a long legal text of their own Version 0.16 - 2003-08-15 * added customizable output. obsoleting --preset and --width * added audiodir module (brand new) * added conf module (abstracted from oidua.py) * added --merge option to merge output of (partially) identical directories * added --strip option to strip empty directories from output trees * added --ignore-bad option to not list audiotype failures * added support for mp3 spacer files * added support for id3-tags in flac-files, thanks to Nathan Owen * fixed directory duplication in output. they are never listed twice * fixed listing of root directories as nameless * fixed a bug in the mp3 profile dectection * fixed a compatibility issue with python 2.1.3, thanks to Ford Prefect * removed --global-sort option in favour of --merge * removed --preset option. use --output instead * removed --width option. use --output instead * changed the magic line to look for python in the entire path * better progress indication * more error checking in options parser * major code rewrites Version 0.15.1 - 2003-06-17 * fixed a crashbug introduced in 0.15. oidua crashed when audiotype failed on the first file in a directory * turned wildcard expansion off by default * added -w/--wildcards option to reenable wildcard expansion * removed some error message crippling code * added a workaround for win32 NTFS drives * removed an obsolete workaround for Real(tm) mp3 files with id3v2 Version 0.15 - 2003-06-16 * added CTRL-C handling * added wildcard support for base directories * added base directories sorting option -g/--global-sort * added output-file option --file * added audiotype failure reports * added empty directories omission * added progress indication and --quiet * added legend to README * added --debug option - non-developers can safely ignore it * added short/long names for all options (except --debug) * fixed a crash bug in the id3v2 code * fixed statistics corruption due to audiotype failure * changed -p semantics * changed -C option to -W * changed all error messages to go to stderr * new command line parser * output has separators between all fields * better error handling * lots of code factoring and clean up Version 0.14.1 - 2003-06-12 * fixed bug in -C * fixed bug with -e and trailing directory separators Version 0.14 - 2003-06-12 * added FLAC support * added Ogg preset guessing * added MusePack preset guessing (This actually was in 0.13 already, we just forgot to mention it. Doh!) * added statistics output option -S * added HTML output options -H, --text and --bg * added case insensitive directory sorting option -i * new command line option parser * improved flexibility of the preset guessing * fixed division by zero for empty directories * fixed division by zero for "non measurable" execution times * moved Changelog to NEWS * moved gpl.txt to COPYING * added README * added buildexe.py script for building win32 binaries * changed the output format again * more code clean up Version 0.13 - 2003-06-06 * LAME preset guessing implemented. Use with -p * major speed up for mp3 files with id3v2 tags * small changes to output format * minor code clean up Version 0.12 - 2003-05-30 * added Changelog * added #!-tags * added encodings tags * updated licence info in audiotype.py Version 0.11 - 2003-03-25 * mpc is fixed, although it probably won't work that well on non-SV7 mpc's * "special character" problem fixed Version 0.10 - 2003-03-25 * initial release oidua-0.16.1/oidua.py0100744000104000010010000002444507722511513015546 0ustar AdministratörerIngen#!/usr/bin/env python # -*- coding: iso-8859-1 -*- # # Script gathering information about directory trees of audio files # # This program is under GPL license. See COPYING file for details. # # Copyright 2003 Sylvester Johansson (sylvestor@telia.com) # Mattias Päivärinta (mpa99001@student.mdh.se) # TODO: Use list comprehension instead of bulky map and filter calls (later) # TODO: Make specification for smoke tests (later) # TODO: Actually create smoke tests (later) # TODO: Consider custom character escaping (later) # TODO: Installation guides? (later) # TODO: [s,5] looks bad mixing 98.2 and 100M for instance (later) # TODO: Customize metadata output? (later) # TODO: to_human is duplicated in audiodir.py (later) r"""Usage: oidua.py [options] ... Options: -B, --bg COLOR Set HTML background color -D, --date Display datestamp header --debug Output debug trace to stderr -e, --exclude DIR Exclude dir from search -f, --file FILE Write output to FILE -h, --help Display this message -H, --html HTML output --ignore-bad Don't list files that cause Audiotype failure -i, --ignore-case Case-insensitive directory sorting -I, --indent N Set indent to N -m, --merge Merge identical directories Basedirs with identical names are merged. This Means that all their subdirs are considered being subdirs of a single directory, and therefore sorted and displayed together. If there are duplicate names among the subdirs then those are also merged. -o, --output STRING Set output format to STRING Anything enclosed by brackets is considered a field. A field must have the following syntax: [TAG] [TAG,WIDTH] [TAG,WIDTH,SUFFIX] [TAG,,SUFFIX] TAG is any of the following characters: a list of bitrates in Audiolist compatible format b bitrate with suffix (i.e. 192k) B bitrate in bps d depth; distance from respective basedir f number of audio files (including spacers) l length in minutes and seconds L length in seconds m time of last change M time of last change in seconds since the epoch n directory name (indented) N directory name p profile P full path q quality s size with suffix (i.e. 65.4M) S size in bytes t file type T bitrate type: ~ mixed files C constant bitrate L lossless compression V variable bitrate WIDTH defines the exact width of the field. The output is cropped to this width if needed. Negative values will give left aligned output. Cropping is always done on the right. SUFFIX lets you specify a unit to be concatenated to all non-empty data. Other interpreted sequences are: \[ [ \] ] \n new line \t tab character Unescaped brackets are forbidden unless they define a field. Note: If you have any whitespace in your output string you must put it inside quotes or otherwise it will not get parsed right. -q, --quiet Omit progress indication -s, --strip Strip output of field headers and empty directories -S, --stats Display statistics results -t, --time Display elapsed time footer -T, --text COLOR Set HTML text color -V, --version Display version -w, --wildcards Expand wildcards in basedirs """ __version__ = "0.16.1" import os, re, string, sys, time import audiotype, audiodir, conf class Data: def __init__(self): self.BadFiles = [] self.Base = 0 self.PathStack = [] self.Start = 0 self.Size = { "Total": 0.0, "FLAC": 0.0, "Ogg": 0.0, "MP3": 0.0, "MPC": 0.0} self.TimeTotal = 0.0 def to_human(value, radix=1024.0): i = 0 while value >= radix: value /= radix i += 1 suffix = " kMG"[i] if value > 100: return "%d%s" % (value, suffix) elif value < 10: return "%.2f%s" % (value, suffix) else: return "%.1f%s" % (value, suffix) def update_progress(): """indicate progress""" if sys.stdout.isatty() or conf.conf.Quiet: return print >> sys.stderr, "\r%sb processed" % to_human(globals.Size["Total"]), def clear_progress(): """terminate progress indication""" if not sys.stdout.isatty() and not conf.conf.Quiet: print >> sys.stderr, "\r \r", def eval_fields(fields, obj, suffixes=1): """project an object through a field list into a tuple of strings""" list = [] for field in fields: try: data, width, suffix = str(obj.get(field[0])), field[1], field[2] except KeyError: print >> sys.stderr, "Unknown field <%s> in format string" % field[0] sys.exit(1) if not data: suffix = " " * len(suffix) if suffixes: data += suffix if width != None: data = "%*.*s" % (width, abs(width), data) list.append(data) return tuple(list) def main(): if conf.conf.DispHelp: print >> sys.stderr, __doc__ return 0 if conf.conf.OutputFormat == "HTML": htmlheader() if conf.conf.DispDate: headers("date") globals.Start = time.clock() if conf.conf.Folders: headers("header") keys = conf.conf.Folders.keys() conf.conf.sort(keys) for key in keys: smash(conf.conf.Folders[key], 0) if globals.BadFiles: print "" print "Audiotype failed on the following files:" print string.join(globals.BadFiles, "\n") globals.ElapsedTime = time.clock() - globals.Start if conf.conf.DispTime: print "" print "Generation time: %8.2f s" % globals.ElapsedTime if conf.conf.DispResult: statistics = [ ["Ogg", globals.Size["Ogg"]], ["MP3", globals.Size["MP3"]], ["MPC", globals.Size["MPC"]], ["FLAC", globals.Size["FLAC"]]] line = "+-----------------------+-----------+" print "" print line print "| Format Amount (Mb) | Ratio (%) |" print line for x in statistics: if x[1]: print "| %-8s %12.2f | %9.2f |" % ( x[0], x[1] / (1024 * 1024), x[1] * 100 / globals.Size["Total"]) print line totalMegs = globals.Size["Total"] / (1024 * 1024) print "| Total %10.2f Mb |" % totalMegs print "| Speed %10.2f Mb/s |" % (totalMegs / globals.ElapsedTime) print line[:25] if conf.conf.DispVersion: print "" print "oidua version: ", __version__ print "audiotype version:", audiotype.__version__ if conf.conf.OutputFormat == "HTML": htmlfooter() def htmlheader(): """output HTML header""" # XXX Should we _always_ use this charset? print """ Music List
""" % (__version__, conf.conf.TextColor, conf.conf.BGColor)


def htmlfooter():
	"""output HTML footer"""
	print"
" #print "

" #print "\"Valid

" print"" def set_toggle(set, element): """toggle occurance of element in set""" if element in set: set.remove(element) else: set.append(element) def headers(token): if token == "header" and not conf.conf.Stripped: #top header line = conf.conf.OutputString % eval_fields(conf.conf.Fields, HeaderObject(), 0) print line print "=" * len(line) elif token == "date": #date print time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()) def has_suffix(str, suffix): """check string for suffix""" return suffix == string.lower(str[-len(suffix):]) def debug(msg): """print debug message""" if conf.conf.Debug: print >> sys.stderr, "?? " + msg class HeaderObject: def __init__(self): pass def get(self, id): dict = { "a": "Bitrate(s)", "b": "Bitrate", "B": "Bitrate", "c": "Channels", "d": "Dir", "D": "Depth", "f": "Files", "l": "Length", "L": "Length", "m": "Modified", "n": "Album/Artist", "N": "Album/Artist", "p": "Profile", "P": "Path", "q": "Quality", "r": "Sample Rate", "s": "Size", "S": "Size", "t": "Type", "T": "BR Type" #"v": "Vendor", #"V": "Version", } return dict[id] class EmptyDir: def __init__(self, dir): self.dir = dir def get(self, id): if id in "nNmdPD": return self.dir.get(id) else: return "" def grab(dir): debug("enter grab %s %s" % (dir.depth, dir.name())) update_progress() if len(dir.streams()): for type in dir.types(): globals.Size[type] += dir.size(type) globals.Size["Total"] += dir.size() # delayed output for ddir, ddepth in globals.PathStack: fields = eval_fields(conf.conf.Fields, ddir) print conf.conf.OutputString % fields globals.PathStack = [] # evaluate the fields and output string, and output fields = eval_fields(conf.conf.Fields, dir) print conf.conf.OutputString % fields # take care of bad files if conf.conf.Debug: for badfile in dir.bad_streams(): print >> sys.stderr, "Audiotype failed for:", badfile elif conf.conf.ListBad: globals.BadFiles += dir.bad_streams() debug("exit grab %s %s" % (dir.depth, dir.name())) return len(dir.streams()) != 0 def subdirectories(dirs): dirdict = {} for dir in dirs: for path in dir.subdirs(): key = os.path.basename(path) if dirdict.has_key(key): dirdict[key].append(path) else: dirdict[key] = [ path ] return dirdict def smash(pathlist, depth): debug("enter smash %s %s" % (depth, pathlist)) displayed = 0 # create Dir objects for all paths dirs = map(lambda x: audiodir.Dir(x, depth), pathlist) # grab all Dirs for dir in dirs: displayed += grab(dir) # create an EmptyDir for the Dir and delay its output if not conf.conf.Stripped and not displayed: globals.PathStack.append((EmptyDir(dir), depth)) # create a common dictionary over the subdirectories of all Dirs subdir_dict = subdirectories(dirs) # sort keys and traverse the dictionary keys = subdir_dict.keys() conf.conf.sort(keys) for key in keys: # weed out base and excluded directories dirs = filter(lambda x: x not in conf.conf.ExcludePaths, subdir_dict[key]) # anything left? if dirs: smash(dirs, depth + 1) # forget this directory unless its already printed if globals.PathStack: globals.PathStack = globals.PathStack[:-1] debug("exit smash %s %s" % (depth, pathlist)) if __name__ == "__main__": globals = Data() conf.init() try: main() except KeyboardInterrupt: print >> sys.stderr, "Aborted by user" sys.exit(1) clear_progress() oidua-0.16.1/README0100644000104000000220000000214007722450237014731 0ustar AdministratörerSYSTEMOIDUA ~~~~~ OIDUA is an Audiolist[1] clone. It makes a catalog from a directory tree of audio files. OIDUA does not look much like its predecessor as it is a command line tool while Audiolist only sports a graphical user interface. Apart from that OIDUA's functionality is a complete superset of Audiolist's. There is a Windows GUI available called GUIDUA, courtesy of Brandon West. See project website for link. Please feel free to contact us if you would like to make a graphical frontend for any platform. We will be happy to help you out. Purpose ~~~~~~~ There are a few reasons why we started this project. * There were no programs like Audiolist outside the Windows platform * Audiolist is dead. There has been no sign of the guy who made it for a very long time * I guess we wanted a project like this anyway Authors ~~~~~~~ Sylvester Johansson Mattias Päivärinta The project website: http://mds.mdh.se/~dal99mpa/oidua.html References ~~~~~~~~~~ [1] http://www.etek.chalmers.se/~e9and/audiolist/ Mattais Päivärinta mpa99001@student.mdh.se