Skip to content

Instantly share code, notes, and snippets.

@mzero
Created April 9, 2019 00:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mzero/79677b2dd4128ec05c85574464752d8c to your computer and use it in GitHub Desktop.
Save mzero/79677b2dd4128ec05c85574464752d8c to your computer and use it in GitHub Desktop.
Convert DDP mastered CDs to Kunaki's CUE format
"""Convert DDP mastered CDs to Kunaki's CUE format.
tl;dr:
python ddp-to-kunaki.py my-cool-cd-ddp-dir my-cool-cd-kunaki
This will produce two files:
my-cool-cd-kunaki.CUE <-- the markers
my-cool-cd-kunaki.iso <-- the audio (*not* an ISO format file!)
These are the same as the two files that Kunaki's CD reading software
generates. You upload to them using the "Audio ISO and CUE file" section
of Kunaki's upload page.
Note: The .iso file is a hard link to the audio file in the DDP directory,
so that it dosn't need to copy 700MB for no good reason.
----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----
Details:
In DDP, the file PQ_DESC contains the marker information.
An example:
$ hexdump -v -e '64/1 "%c" "\n"' my-cool-cd-ddp-dir/PQ_DESCR
VVVS00000000000001
VVVS01000000000001 QZ4JJ1688502
VVVS01010000020001 QZ4JJ1688502
VVVS02000004540001 QZ4JJ1688503
VVVS02010004570001 QZ4JJ1688503
VVVS03000013300001 QZ4JJ1688504
VVVS03010013330001 QZ4JJ1688504
VVVS04000018383901 QZ4JJ1688505
VVVS04010018413901 QZ4JJ1688505
VVVS05000023181401 QZ4JJ1688506
VVVS05010023231401 QZ4JJ1688506
VVVS06000030462201 QZ4JJ1688507
VVVS06010030502201 QZ4JJ1688507
VVVS07000043143701 QZ4JJ1688508
VVVS07010043173701 QZ4JJ1688508
VVVS08000047293701 QZ4JJ1688509
VVVS08010047323701 QZ4JJ1688509
VVVS09000055080901 QZ4JJ1688510
VVVS09010055110901 QZ4JJ1688510
VVVS10000061043001 QZ4JJ1688511
VVVS10010061073001 QZ4JJ1688511
VVVS11000065150601 QZ4JJ1688512
VVVS11010065150601 QZ4JJ1688512
VVVS12000070237401 QZ4JJ1688513
VVVS12010070267401 QZ4JJ1688513
VVVSAA010074387401
VVVSAA010074387401
The encoding is actually records of 64 ASCII characters, no newlines.
The format seems to be:
"VVVS" <tt> <ii> "00" <mm><ss><ff> <ee> " " <isrc-code-str> <32 spaces>
tt = track number, "AA" for lead-out
ii = index number
mmssff = minute, second, frame
ee = "01" or "0S" (the later only on audio track markers)
Kunaki's CUE file is binary, and looks like:
$ hexdump -e '"%04_ax :" 8/1 " %02x" "\n"' my-cool-cd-kunaki.CUE
0000 : 01 00 00 01 00 00 00 00
0008 : 01 01 00 00 00 00 00 00
0010 : 21 01 01 00 00 00 02 00
0018 : 21 02 01 00 00 04 39 00
0020 : 21 03 01 00 00 0d 21 00
0028 : 21 04 01 00 00 12 29 27
0030 : 21 05 01 00 00 17 17 0e
0038 : 21 06 01 00 00 1e 32 16
0040 : 21 07 01 00 00 2b 11 25
0048 : 21 08 01 00 00 2f 20 25
0050 : 21 09 01 00 00 37 0b 09
0058 : 21 0a 01 00 00 3d 07 1e
0060 : 21 0b 01 00 00 41 0f 06
0068 : 21 0c 01 00 00 46 1a 4a
0070 : 21 aa 01 01 00 4a 26 4a
0078 : 03
This seems to be similar to the information in a CD's TOC and Q subcode, though
with values in binary, not BCD. The format seems to be:
<ctrl><adr> <tt> <ii> <xx> 00 <mm> <ss> <ff>
ctrl = bits: quad, 0, copy allowed, pre-emphasis
adr = 1 for track info,
3 is isrc code info, but here seems to be an end marker
tt = track number
ii = index number
xx = ???, seems to be 00 for tracks, and 01 for lead-in and lead-out
mmssff = minute, second, frame
"""
import collections
import os
import struct
import sys
TocEntry = collections.namedtuple('TocEntry',
['track', 'index', 'minute', 'second', 'frame'])
track_lead_in = 0
track_lead_out = 0xAA
def read_ddp_manifest(f):
files = {}
while True:
line = f.read(128)
if line == '':
break
head = line[0:4]
data_type = line[4:6]
data_func = line[30:40].strip()
const_017 = line[71:74]
data_file = line[74:86].strip()
if (head != 'VVVM' or data_type not in ['S0', 'D0']
or const_017 != '017'):
print >>sys.stderr, '** Unrecognized line in DDPMS file: "%s"' % line
continue
files[data_func] = data_file
return files
def read_ddp_pq_desc(f):
entries = []
while True:
line = f.read(64)
if line == '':
break
try:
head = line[0:4]
track = line[4:6]
track = track_lead_out if track == 'AA' else int(track)
index = int(line[6:8])
const_00 = line[8:10]
minute = int(line[10:12])
second = int(line[12:14])
frame = int(line[14:16])
ee_code = line[16:18]
const_gap = line[18:20]
isrc = line[20:32]
const_blank = line[32:64]
if (head != 'VVVS' or const_00 != '00' or ee_code not in ['01','0S']
or const_gap != ' ' or any(c != ' ' for c in const_blank)):
print >>sys.stderr, '** Unrecognized line in PQ file: "%s"' % line
continue
except:
print >>sys.stderr, '** Malformed line in PQ file: "%s"' % line
continue
entries.append(TocEntry(track, index, minute, second, frame))
if len(entries) >= 2 and entries[-1] == entries[-2]:
del entries[-1]
return entries
def write_kunaki_cue(f, entries):
for track, index, minute, second, frame in entries:
# if index == 0 and track > 1:
# continue
ctrl = 0 if track == 0 or (track == 1 and index == 0) else 2
adr = 1
xx = 1 if track in (track_lead_in, track_lead_out) else 0
bytes = struct.pack('BBBBBBBB',
ctrl << 4 | adr, track, index, xx,
0, minute, second, frame)
f.write(bytes)
f.write(struct.pack('B', 3))
def main(args):
if len(args) != 3:
print >>sys.stderr, "Usage: %s <ddp-dir> <kunaki-prefix>" % args[0]
sys.exit(1)
ddp_path = args[1]
kunkai_path = args[2]
ddp_manifest = os.path.join(ddp_path, 'DDPMS')
with open(ddp_manifest, 'rb') as f_ms:
files = read_ddp_manifest(f_ms)
image_name = files.get('DA')
if not image_name:
print >>sys.stderr, '** No digital audio found in manifest'
return
pq_desc_name = files.get('PQ DESCR')
if not pq_desc_name:
print >>sys.stderr, '** No PQ DESCR file found in manifest'
return
ddp_image = os.path.join(ddp_path, image_name)
ddp_pq_desc = os.path.join(ddp_path, pq_desc_name)
kunaki_iso = kunkai_path + '.iso'
kunaki_cue = kunkai_path + '.CUE'
with open(ddp_pq_desc, 'rb') as f_pq:
entries = read_ddp_pq_desc(f_pq)
with open(kunaki_cue, 'wb') as f_cue:
write_kunaki_cue(f_cue, entries)
os.link(ddp_image, kunaki_iso)
if __name__ == '__main__':
main(sys.argv)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment