Created
July 13, 2009 15:18
-
-
Save dennyhalim/146196 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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