michael wrote:
>
> Please pass it along. Most of the conversion scripts posted so far
> have assumed one track per file. And the more the merrier anyway. :)
>
Since you asked, here's my version of flac-split as a python script.
This is truly a version 0.1, very raw and messy. I do use it to convert
my single-album flac files to single-track Ogg Vorbis files for me to
take to work. If anyone wants to polish it up and take it over it
certainly won't hurt my feelings.
D.L.
#!/usr/bin/python
# Simple utility to convert my single-album FLAC's to single-track
# Ogg Vorbis files preserving Vorbis Comments and trying to do
# something reasonable with the file names. It is raw, uncommented
# and taylored pretty specificly to me, however I have built a bit
# of flexibility into the base classes so they should be easily
# extended (and/or parameterized). This script makes the assumption
# that the FLAC file being split has an embedded cuesheet, it also
# assumes a Vorbis style comment block, but probably won't crash if
# that is missing.
#
# Version 0.1 (The IWFM release!)
# Copyright 2005 David Lutz ([EMAIL PROTECTED])
# Released under the Gnu Public License (GPL) version 2.
#
# Basic usage: flac-split album.flac [album.flac ...]
# Detailed usage: Read the Source...
import codecs, sys, re, os, os.path
# Why isn't this part of the standard Python lib???
def flatten(S):
if isinstance(S,tuple): S = list(S)
if not isinstance(S,list): return [S]
if S == []: return S
return flatten(S[0]) + flatten(S[1:])
class alist(list):
'Automagically extending list'
def __setitem__(self, idx, val):
try:
return super(alist,self).__setitem__(idx, val)
except IndexError:
if idx >= 0:
self.extend((1 + idx - len(self))*[None])
return super(alist,self).__setitem__(idx, val)
class CueSheetIndex(object):
def __init__(self):
offset = None
class CueSheetTrack(alist):
def __init__(self):
self.irsc = None
def get_offset01(self):
if len(self) == 0:
return self.offset
elif len(self) == 1:
return self.offset + self[0].offset
else:
return self.offset + self[1].offset
offset01 = property(get_offset01)
class CueSheet(alist):
def __init__(self,fh=None):
self.catalog = None
if fh:
self.parse(fh)
def parse(self, fh):
r_track = re.compile(r'track\[(\d+)\]')
r_index = re.compile(r'index\[(\d+)\]')
r_offset = re.compile(r'offset: (\d+)')
r_catalog = re.compile(r'media catalog number: (\w+)')
r_irsc = re.compile(r'ISRC: (\w+)')
lines = map(lambda x:x.rstrip(), fh.readlines())
for line in lines:
s = r_track.search(line)
if s:
mode = "track"
curtrack = CueSheetTrack()
self[int(s.group(1))] = curtrack
s = r_index.search(line)
if s:
mode = "index"
curindex = CueSheetIndex()
curtrack[int(s.group(1))] = curindex
s = r_offset.search(line)
if s and mode == "track":
curtrack.offset = int(s.group(1))
elif s and mode == "index":
curindex.offset = int(s.group(1))
s = r_catalog.search(line)
if s:
self.catalog = s.group(1)
s = r_irsc.search(line)
if s:
curtrack.irsc = s.group(1)
def dump(self):
if self.catalog: print "CATALOG:", self.catalog
for t in self:
if t.irsc: print " IRSC:", t.irsc
print " Offset:", t.offset
for i in t:
print " Offset:", i.offset
class VorbisComment(object):
def __init__(self, name, value):
self.name = name
self.value = value
class VorbisCommentList(object):
def __init__(self,parent=None):
self.parent = parent
self._list = list()
self._dict = dict()
def __setitem__(self,key,val):
key = str(key)
self._list.append(VorbisComment(key,val))
self._dict[key.upper()] = val
def __getitem__(self,key):
key = str(key)
try:
return self._dict[key.upper()]
except KeyError:
if self.parent != None:
return self.parent[key]
else:
raise KeyError
def __iter__(self):
if self.parent != None:
for vc in self.parent:
if vc.name not in map(lambda x:x.name, self._list):
yield vc
for vc in self._list:
yield vc
def __contains__(self,key):
if key.upper() in self._dict:
return True
elif self.parent != None:
return key in self.parent
else:
return False
class VorbisCommentBlock(alist):
def __init__(self,fh=None):
self.master = VorbisCommentList()
if fh:
self.parse(fh)
def __getitem__(self, idx):
'Autoextend lookups, creating VorbisCommentList() entries'
try:
return super(VorbisCommentBlock,self).__getitem__(idx)
except IndexError:
if idx >= 0:
# Do this loop by hand to create seperate VCL instances
for i in xrange(len(self),idx+1):
self[i] = VorbisCommentList(self.master)
return super(VorbisCommentBlock,self).__getitem__(idx)
def parse(self,fh):
r_general = re.compile(r'\A(\w+)=(.*)\Z', re.UNICODE)
r_specific = re.compile(r'\A(\w+)\[(\d+)\]=(.*)\Z', re.UNICODE)
lines = map(lambda x:x.decode('utf-8').rstrip(), fh.readlines())
for line in lines:
s = r_general.search(line)
if s:
self.master[s.group(1)] = s.group(2)
s = r_specific.search(line)
if s:
self[int(s.group(2))][s.group(1)] = s.group(3)
def dump(self):
for vc in self.master: print vc
for i in xrange(len(self)):
for vc in self[i]:
print "%02d %s=%s" % (i, vc.name, vc.value)
class Track(object):
ext = "wav"
def __init__(self,infile,num,start,end,vcl,outdir=None):
self.infile = infile
self.num = num
self.start = start
self.end = end
self.vcl = vcl
try:
self.title = vcl['TITLE']
except KeyError:
self.title = "track%02d" % num
if outdir == None:
# Use the flac file name as the directory name by default
self.outdir = os.path.splitext(os.path.basename(infile))[0]
else:
self.outdir = outdir
def get_outfile(self):
'A cleaned up, latin-1, filesystem safe version of the name'
badchars = re.compile(r'[\x00-\x19/\\]', re.UNICODE)
title = badchars.sub(u'_', self.title)
fname = ("%02d-%s.%s" % (self.num, title, self.ext)).encode('latin-1')
return os.path.normpath(os.path.join(self.outdir, fname))
outfile = property(get_outfile)
def decode_fh(self):
return pread("flac", "-dcs",
"--skip=%d" % self.start,
"--until=%d" % self.end,
self.infile)
def make_outdir(self):
nd = os.path.normpath(self.outdir)
if not os.path.isdir(nd):
os.makedirs(nd)
def encode_fh(self):
self.make_outdir()
return open(self.outfile, "w")
def tagfile(self): pass
def convert(self):
r = self.decode_fh()
w = self.encode_fh(dir)
buf = r.read(8192)
while buf:
w.write(buf)
buf = r.read(8192)
r.close()
w.close()
self.tagfile()
def dump(self,verbose=False):
print "%s (%d-%d)" % (self.outfile, self.start, self.end)
if verbose:
for vc in self.vcl:
print (" %s=%s" % (vc.name, vc.value)).encode('latin-1')
class OggTrack(Track):
ext = "ogg"
def encode_fh(self,dir=None):
self.make_outdir()
return pwrite("oggenc", "-", "--quality", "6",
"--output", self.outfile)
def tagfile(self):
fh = pwrite("vorbiscomment", "-w", "--raw", "-c", "-", self.outfile)
for vc in self.vcl:
fh.write(("%s=%s\n" % (vc.name, vc.value)).encode('utf-8'))
fh.close()
def pread(cmd, *args):
# This much more complex than using popen because it
# was easier for me to code this than deal with the
# shell metacharacter quoting for popen.
class pread_file(object):
def __init__(self,pid,fh):
self.pid = pid
self.fh = fh
def read(self,size=8192):
return self.fh.read(size)
def readlines(self,size=-1):
return self.fh.readlines(size)
def close(self):
self.fh.close()
return os.waitpid(self.pid,0)
# Create the pipe
(rpipe, wpipe) = os.pipe()
# Fork the child process
pid = os.fork()
if pid == 0:
# Child: Do plumbing
os.close(rpipe)
os.dup2(wpipe,sys.stdout.fileno())
os.close(sys.stdin.fileno())
# Exec metaflac
os.execvp(cmd, [cmd]+flatten(args))
# Just in case...
return None
else:
# Parent: Do plumbing, return read end of pipe
os.close(wpipe)
return pread_file(pid, os.fdopen(rpipe,'r'))
def pwrite(cmd, *args):
# This much more complex than using popen because it
# was easier for me to code this than deal with the
# shell metacharacter quoting for popen.
class pwrite_file(object):
def __init__(self,pid,fh):
self.pid = pid
self.fh = fh
def write(self,str):
return self.fh.write(str)
def close(self):
self.fh.close()
return os.waitpid(self.pid,0)
# Create the pipe
(rpipe, wpipe) = os.pipe()
# Fork the child process
pid = os.fork()
if pid == 0:
# Child: Do plumbing
os.close(wpipe)
os.dup2(rpipe,sys.stdin.fileno())
os.close(sys.stdout.fileno())
# Exec metaflac
os.execvp(cmd, [cmd]+flatten(args))
# Just in case...
return None
else:
# Parent: Do plumbing, return write end of pipe
os.close(rpipe)
return pwrite_file(pid, os.fdopen(wpipe,'w'))
# Main Program Starts Here, processes every arg as if it was a valid
# flac file.
for arg in sys.argv[1:]:
fh = pread("metaflac", "--list",
"--block-type=CUESHEET", arg)
cuesheet = CueSheet(fh)
fh.close()
#cuesheet.dump()
fh = pread("metaflac", "--no-utf8-convert",
"--export-tags-to=-", arg)
vorbis = VorbisCommentBlock(fh)
fh.close()
#vorbis.dump()
for i in xrange(len(cuesheet)-1):
track = OggTrack(arg, i+1,
cuesheet[i].offset01,
cuesheet[i+1].offset01,
vorbis[i+1])
#track.dump(True)
track.convert()
_______________________________________________
Discuss mailing list
[email protected]
http://lists.slimdevices.com/lists/listinfo/discuss