diff options
author | Evgeny Zinoviev <me@ch1p.io> | 2021-04-20 22:22:19 +0300 |
---|---|---|
committer | Evgeny Zinoviev <me@ch1p.io> | 2021-04-20 22:22:19 +0300 |
commit | e490743960fddc0fa253232cb127c5ebf02df6bc (patch) | |
tree | 8bcffcb4b414a4e4ccd1fb1e2b57adc737c8d6e6 /dbpl.py |
initial
Diffstat (limited to 'dbpl.py')
-rw-r--r-- | dbpl.py | 169 |
1 files changed, 169 insertions, 0 deletions
@@ -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 |