Skip to content

Instantly share code, notes, and snippets.

@dennyhalim
Created July 13, 2009 15:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dennyhalim/146196 to your computer and use it in GitHub Desktop.
Save dennyhalim/146196 to your computer and use it in GitHub Desktop.
#!/usr/bin/python
'''
Program to allow secure ripping using CD paranoia, in the spirit of
EAC and rubyripper.
An important property that I want it to be a drop in replacement
to "cdparanoia [options] <output file>", that is probably used by many
rippers, in particular it has to be compatible with grip and jack.
Note that it assumes that the last argument is the name of a output
sound file.
I am licensing this file under the same licence cdparanoia has.
COPYRIGHT: Paulo Jose da Silva e Silva, email: pssilva at ime dot usp dot br
History:
2nd April 2002. First public release
2nd April 2002. Small fixes from R. Bernstein
'''
# TODO: Better deal with options, letting some options define the
# constants below (and pass the other to cdparanoia)
import sys, md5, os, copy
# Length of a CD-ROM sector, in bytes
SECTORLEN = 2352
# Size of a chunk. I am using the size of a CD-ROM sector
CHUNKSIZE = SECTORLEN
# Number of times the chunks must match. It must be >= 2 to make sense
MINMATCHES = 2
# Number of bytes per second in a PCM (used to compute the position of
# of a possibly bad chunk in seconds)
BYTESPERSECOND = SECTORLEN*75
# Number of times the program should try ripping the track.
NUMBEROFTRIALS = 10
class Chunk(object):
def __init__(self, pos, digest):
self.pos = pos
self.digest = digest
self.rep = MINMATCHES
self.saved = True
class Track(object):
def __init__(self, maxTrials, cdparanoiaOptions, output):
self.maxTrials = max(2, maxTrials)
self.cdparanoiaOptions = cdparanoiaOptions
self.output = output
self.chunks = []
self.badChunks = []
def _callParanoia(self, output):
retCode = os.system('cdparanoia %s %s' %
(self.cdparanoiaOptions, output))
if retCode != 0:
sys.exit(retCode)
def _initializeChunkList(self):
pos = 0
originalData = file(self.output)
chunk = originalData.read(CHUNKSIZE)
while chunk != '':
m = md5.new()
m.update(chunk)
self.chunks.append(Chunk(pos, m.digest()))
pos += 1
chunk = originalData.read(CHUNKSIZE)
originalData.close()
def _updateChunks(self, otherChunk):
m = md5.new()
m.update(otherChunk)
otherDigest = m.digest()
chunk = self.chunks.pop(0)
if otherDigest == chunk.digest:
if chunk.rep <= 2:
if not chunk.saved:
originalData = os.open(self.output, os.O_WRONLY)
os.lseek(originalData, chunk.pos*CHUNKSIZE, 0)
os.write(originalData, otherChunk)
os.close(originalData)
else:
chunk.rep -= 1
self.chunks.append(chunk)
else:
chunk.digest = otherDigest
chunk.rep = MINMATCHES
chunk.saved = False
self.chunks.append(chunk)
def _saveBadChunks(self):
self.badChunks = self.chunks[:]
def _problemsLog(self):
log = file(self.output + '-rip.log', 'w')
for c in self.badChunks:
log.write('Original Difference at chunk %d (%ds)\n' %
(CHUNKSIZE*c.pos, CHUNKSIZE*c.pos/BYTESPERSECOND))
for c in self.chunks:
log.write('*Uncorrected* chunk at %d (%ds)\n' %
(CHUNKSIZE*c.pos, CHUNKSIZE*c.pos/BYTESPERSECOND))
log.close()
def rip(self):
# Rip for the first time
nTrials = 1
self._callParanoia(self.output)
self._initializeChunkList()
while len(self.chunks) > 0 and nTrials < self.maxTrials:
print '# Chunks to go:', len(self.chunks), '- Trial', nTrials + 1
tempName = self.output + '.other'
self._callParanoia(tempName)
pos = 0
other = file(tempName)
chunk = other.read(CHUNKSIZE)
while len(self.chunks) > 0 and chunk != '':
if pos == self.chunks[0].pos:
self._updateChunks(chunk)
pos += 1
chunk = other.read(CHUNKSIZE)
nTrials += 1
os.unlink(tempName)
if nTrials == MINMATCHES: self._saveBadChunks()
# Create a list of bad chunks
if len(self.badChunks) > 0:
self._problemsLog()
if len(self.chunks) > 0:
print '***************** BAD RIP *****************'
if __name__ == '__main__':
cdparanoiaOptions, output = ' '.join(sys.argv[1:-1]), sys.argv[-1]
t = Track(NUMBEROFTRIALS, cdparanoiaOptions, output)
t.rip()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment