Skip to content

Instantly share code, notes, and snippets.

@m66n
Last active February 17, 2017 15:35
Show Gist options
  • Save m66n/7f6e976f9e77f5f669b17861c18ea826 to your computer and use it in GitHub Desktop.
Save m66n/7f6e976f9e77f5f669b17861c18ea826 to your computer and use it in GitHub Desktop.
Extract CDDA data from NRG file
#!/usr/bin/env python3
import argparse
import os
import sys
CHUNK_TYPES = [b'SINF', b'ETN2', b'DAOX', b'CUEX', b'RELO', b'TOCT', b'DINF',
b'CDTX', b'MTYP', b'END!']
b2i = lambda f, n=4: int.from_bytes(f.read(n), byteorder='big')
def read_iff_offset(f):
f.seek(-12, os.SEEK_END)
if f.read(4) != b'NER5':
raise RuntimeError('Nero NRG v2 footer was not found.')
return b2i(f, 8)
def read_end_chunk(f, end_of_chunk, chunks):
pass
def read_mtyp_chunk(f, end_of_chunk, chunks):
chunks[b'MTYP'] = { 'type': b2i(f) }
def read_sinf_chunk(f, end_of_chunk, chunks):
chunks[b'SINF'] = { 'num_tracks': b2i(f) }
def read_daox_chunk(f, end_of_chunk, chunks):
chunks[b'DAOX'] = {
'le_size': b2i(f),
'upc': f.read(13),
'null': b2i(f, 1),
'toc_type': b2i(f, 2),
'first_track': b2i(f, 1),
'last_track': b2i(f, 1)
}
tracks = []
while f.tell() < end_of_chunk:
tracks.append({
'isrc': f.read(12),
'sector_size': b2i(f, 2),
'mode': b2i(f, 2),
'unknown': b2i(f, 2),
'index0': b2i(f, 8),
'index1': b2i(f, 8),
'next_index': b2i(f, 8)
})
chunks[b'DAOX']['tracks'] = tracks
def read_cuex_chunk(f, end_of_chunk, chunks):
tracks = []
while f.tell() < end_of_chunk:
tracks.append({
'mode': b2i(f, 1),
'track': b2i(f, 1),
'index': b2i(f, 1),
'null': b2i(f, 1),
'lba': b2i(f)
})
chunks[b'CUEX'] = { 'tracks': tracks }
def chunk_dispatch(chunk_type):
def not_implemented(f, end_of_chunk, _):
print('WARNING: {0} chunk processing not implemented yet.'
.format(str(chunk_type, 'utf-8')))
f.seek(end_of_chunk, os.SEEK_SET)
return {
b'CUEX': read_cuex_chunk,
b'DAOX': read_daox_chunk,
b'SINF': read_sinf_chunk,
b'MTYP': read_mtyp_chunk,
b'END!': read_end_chunk
}.get(chunk_type, not_implemented)
def read_iff(f):
chunks = {}
done = False
while not done:
offset = f.tell()
chunk_type = f.read(4)
if chunk_type not in CHUNK_TYPES:
raise RuntimeError('Unknown chunk type: {0}.'.format(chunk_type))
chunk_size = b2i(f)
# print('Found {0} chunk => size: {1}, offset: 0x{2:x} ({2}).'
# .format(str(chunk_type, 'utf-8'), chunk_size, offset))
end_of_chunk = f.tell() + chunk_size
chunk_dispatch(chunk_type)(f, end_of_chunk, chunks)
if f.tell() != end_of_chunk:
raise RuntimeError('Chunk alignment error.')
done = (chunk_type == b'END!')
return chunks
def read_file(f):
iff_offset = read_iff_offset(f)
f.seek(iff_offset, os.SEEK_SET)
return read_iff(f)
def extract_cdda_tracks(filename, outdir):
cdda_template = os.path.join(outdir, 'track-{0}.cdda')
with open(filename, 'rb') as f:
chunks = read_file(f)
index = 1
for track in chunks[b'DAOX']['tracks']:
print('Extracting track {0:2d}... '.format(index), end='', flush=True)
f.seek(track['index1'], os.SEEK_SET)
b = f.read(track['next_index'] - track['index1'])
with open(cdda_template.format(index), 'wb') as w:
w.write(b)
index += 1
print('Done.')
def main():
parser = argparse.ArgumentParser()
parser.add_argument('file', help='Nero NRG v2 file with CDDA audio tracks')
parser.add_argument('--outdir', help='directory for CDDA files')
args = parser.parse_args()
outdir = os.path.expanduser(args.outdir) if args.outdir else os.getcwd()
extract_cdda_tracks(args.file, outdir)
return 0
if __name__ == '__main__':
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment