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

Reply via email to