summaryrefslogtreecommitdiff
path: root/dbpl.py
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2021-04-20 22:22:19 +0300
committerEvgeny Zinoviev <me@ch1p.io>2021-04-20 22:22:19 +0300
commite490743960fddc0fa253232cb127c5ebf02df6bc (patch)
tree8bcffcb4b414a4e4ccd1fb1e2b57adc737c8d6e6 /dbpl.py
initial
Diffstat (limited to 'dbpl.py')
-rw-r--r--dbpl.py169
1 files changed, 169 insertions, 0 deletions
diff --git a/dbpl.py b/dbpl.py
new file mode 100644
index 0000000..dddd192
--- /dev/null
+++ b/dbpl.py
@@ -0,0 +1,169 @@
+import os
+import struct
+
+from enum import Enum
+
+
+class Flag(Enum):
+ DDB_IS_SUBTRACK = 1 << 0 # file is not single-track, might have metainfo in external file
+ DDB_IS_READONLY = 1 << 1 # check this flag to block tag writing (e.g. in iso.wv)
+ DDB_HAS_EMBEDDED_CUESHEET = 1 << 2
+
+ DDB_TAG_ID3V1 = 1 << 8
+ DDB_TAG_ID3V22 = 1 << 9
+ DDB_TAG_ID3V23 = 1 << 10
+ DDB_TAG_ID3V24 = 1 << 11
+ DDB_TAG_APEV2 = 1 << 12
+ DDB_TAG_VORBISCOMMENTS = 1 << 13
+ DDB_TAG_CUESHEET = 1 << 14
+ DDB_TAG_ICY = 1 << 15
+ DDB_TAG_ITUNES = 1 << 16
+
+ DDB_TAG_MASK = 0x000fff00
+
+
+class Track:
+ def __init__(self):
+ self.uri = None
+ self.decoder = None
+ self.num = None
+ self.startsample = None
+ self.endsample = None
+ self.duration = None
+ self.filetype = None
+ self.replaygain_albumgain = None
+ self.replaygain_albumpeak = None
+ self.replaygain_trackgain = None
+ self.replaygain_trackpeak = None
+ self.flags = 0
+ self.meta = {}
+
+
+class Playlist:
+ def __init__(self):
+ self.major_version = None
+ self.minor_version = None
+ self.tracks_count = None
+ self.tracks = []
+ self.meta = {}
+
+
+def read(file):
+ playlist = Playlist()
+
+ with open(file, 'rb') as f:
+ magic = f.read(4)
+ if magic != b'DBPL':
+ raise ValueError('invalid magic value')
+
+ # uint8_t
+ playlist.major_version, playlist.minor_version = struct.unpack('BB', f.read(2))
+
+ if playlist.major_version != 1:
+ raise ValueError('invalid major version')
+
+ if playlist.minor_version < 1:
+ raise ValueError('invalid minor version')
+
+ # uint32_t
+ tracks_count = struct.unpack('I', f.read(4))[0]
+
+ for i in range(tracks_count):
+ track = Track()
+
+ if playlist.minor_version <= 2:
+ # uint16_t
+ uri_len = struct.unpack('H', f.read(2))[0]
+ track.uri = f.read(uri_len)
+
+ # uint8_t
+ decoder_len = struct.unpack('B', f.read(1))[0]
+ if decoder_len >= 20:
+ raise ValueError('invalid decoder length')
+
+ if decoder_len:
+ track.decoder = f.read(decoder_len)
+
+ # int16_t
+ track.num = struct.unpack('h', f.read(2))[0]
+
+ # int32_t
+ track.startsample, track.endsample = struct.unpack('ii', f.read(8))
+
+ # float
+ track.duration = struct.unpack('f', f.read(4))[0]
+
+ if playlist.minor_version <= 2:
+ # legacy filetype support, they say
+ # uint8_t
+ filetype_len = struct.unpack('B', f.read(1))[0]
+ if filetype_len:
+ track.filetype = f.read(filetype_len)
+
+ # floats
+ ag, ap, tg, tp = struct.unpack('ffff', f.read(16))
+ if ag != 0:
+ track.replaygain_albumgain = ag
+ if ap != 0 and ap != 1:
+ track.replaygain_albumpeak = ap
+ if tg != 0:
+ track.replaygain_trackgain = tg
+ if tp != 0 and tp != 1:
+ track.replaygain_trackpeak = tp
+
+ if playlist.minor_version >= 2:
+ # uint32_t
+ track.flags = struct.unpack('I', f.read(4))[0]
+ elif track.startsample > 0 or track.endsample > 0 or track.num > 0:
+ track.flags |= Flag.DDB_IS_SUBTRACK
+
+ # int16_t
+ nm = struct.unpack('h', f.read(2))[0]
+ for j in range(nm):
+ # uint16_t
+ value_len = struct.unpack('H', f.read(2))[0]
+ if value_len >= 20000:
+ raise ValueError('invalid key length')
+
+ key = f.read(value_len)
+
+ value_len = struct.unpack('H', f.read(2))[0]
+ if value_len >= 20000:
+ f.seek(value_len, os.SEEK_CUR)
+ else:
+ value = f.read(value_len)
+ if key[0] == ':':
+ value = int(value)
+ if key == ':STARTSAMPLE':
+ track.startsample = value
+ elif key == ':ENDSAMPLE':
+ track.endsample = value
+ else:
+ track.meta[key] = value
+ else:
+ track.meta[key] = value
+
+ playlist.tracks.append(track)
+
+ assert tracks_count == len(playlist.tracks)
+
+ # playlist metadata
+ # int16_t
+ nm = struct.unpack('H', f.read(2))[0]
+ for i in range(nm):
+ # int16_t
+ key_len = struct.unpack('h', f.read(2))[0]
+ if key_len < 0 or key_len >= 20000:
+ raise ValueError('invalid length')
+
+ key = f.read(key_len)
+
+ # int16_t
+ value_len = struct.unpack('h', f.read(2))[0]
+ if value_len < 0 or value_len >= 20000:
+ f.seek(value_len, os.SEEK_CUR)
+ else:
+ value = f.read(value_len)
+ playlist.meta[key] = value
+
+ return playlist \ No newline at end of file