1
# Copyright (C) 2005 Michael Urman
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of version 2 of the GNU General Public License as
5
# published by the Free Software Foundation.
8
from struct import unpack, pack
9
from warnings import warn
11
from mutagen._id3util import ID3JunkFrameError, ID3Warning, BitPaddedInt
15
def __init__(self, name):
19
raise TypeError("Spec objects are unhashable")
21
def _validate23(self, frame, value, **kwargs):
22
"""Return a possibly modified value which, if written,
23
results in valid id3v2.3 data.
30
def read(self, frame, data):
31
return ord(data[0]), data[1:]
33
def write(self, frame, value):
36
def validate(self, frame, value):
42
class IntegerSpec(Spec):
43
def read(self, frame, data):
44
return int(BitPaddedInt(data, bits=8)), ''
46
def write(self, frame, value):
47
return BitPaddedInt.to_str(value, bits=8, width=-1)
49
def validate(self, frame, value):
53
class SizedIntegerSpec(Spec):
54
def __init__(self, name, size):
55
self.name, self.__sz = name, size
57
def read(self, frame, data):
58
return int(BitPaddedInt(data[:self.__sz], bits=8)), data[self.__sz:]
60
def write(self, frame, value):
61
return BitPaddedInt.to_str(value, bits=8, width=self.__sz)
63
def validate(self, frame, value):
67
class EncodingSpec(ByteSpec):
68
def read(self, frame, data):
69
enc, data = super(EncodingSpec, self).read(frame, data)
73
return 0, chr(enc)+data
75
def validate(self, frame, value):
80
raise ValueError('Invalid Encoding: %r' % value)
82
def _validate23(self, frame, value, **kwargs):
83
# only 0, 1 are valid in v2.3, default to utf-16
87
class StringSpec(Spec):
88
def __init__(self, name, length):
89
super(StringSpec, self).__init__(name)
92
def read(s, frame, data):
93
return data[:s.len], data[s.len:]
95
def write(s, frame, value):
99
return (str(value) + '\x00' * s.len)[:s.len]
101
def validate(s, frame, value):
104
if isinstance(value, basestring) and len(value) == s.len:
106
raise ValueError('Invalid StringSpec[%d] data: %r' % (s.len, value))
109
class BinaryDataSpec(Spec):
110
def read(self, frame, data):
113
def write(self, frame, value):
116
def validate(self, frame, value):
120
class EncodedTextSpec(Spec):
121
# Okay, seriously. This is private and defined explicitly and
122
# completely by the ID3 specification. You can't just add
123
# encodings here however you want.
126
('utf16', '\x00\x00'),
127
('utf_16_be', '\x00\x00'),
131
def read(self, frame, data):
132
enc, term = self._encodings[frame.encoding]
136
data, ret = data.split(term, 1)
141
offset = data.index(term, offset+1)
144
data, ret = data[0:offset], data[offset+2:]
149
if len(data) < len(term):
151
return data.decode(enc), ret
153
def write(self, frame, value):
154
enc, term = self._encodings[frame.encoding]
155
return value.encode(enc) + term
157
def validate(self, frame, value):
158
return unicode(value)
161
class MultiSpec(Spec):
162
def __init__(self, name, *specs, **kw):
163
super(MultiSpec, self).__init__(name)
165
self.sep = kw.get('sep')
167
def read(self, frame, data):
171
for spec in self.specs:
172
value, data = spec.read(frame, data)
174
if len(self.specs) != 1:
175
values.append(record)
177
values.append(record[0])
180
def write(self, frame, value):
182
if len(self.specs) == 1:
184
data.append(self.specs[0].write(frame, v))
187
for v, s in zip(record, self.specs):
188
data.append(s.write(frame, v))
191
def validate(self, frame, value):
194
if self.sep and isinstance(value, basestring):
195
value = value.split(self.sep)
196
if isinstance(value, list):
197
if len(self.specs) == 1:
198
return [self.specs[0].validate(frame, v) for v in value]
201
[s.validate(frame, v) for (v, s) in zip(val, self.specs)]
203
raise ValueError('Invalid MultiSpec data: %r' % value)
205
def _validate23(self, frame, value, **kwargs):
206
if len(self.specs) != 1:
207
return [[s._validate23(frame, v, **kwargs)
208
for (v, s) in zip(val, self.specs)]
213
# Merge single text spec multispecs only.
214
# (TimeStampSpec beeing the exception, but it's not a valid v2.3 frame)
215
if not isinstance(spec, EncodedTextSpec) or \
216
isinstance(spec, TimeStampSpec):
219
value = [spec._validate23(frame, v, **kwargs) for v in value]
220
if kwargs.get("sep") is not None:
221
return [spec.validate(frame, kwargs["sep"].join(value))]
225
class EncodedNumericTextSpec(EncodedTextSpec):
229
class EncodedNumericPartTextSpec(EncodedTextSpec):
233
class Latin1TextSpec(EncodedTextSpec):
234
def read(self, frame, data):
236
data, ret = data.split('\x00', 1)
239
return data.decode('latin1'), ret
241
def write(self, data, value):
242
return value.encode('latin1') + '\x00'
244
def validate(self, frame, value):
245
return unicode(value)
248
class ID3TimeStamp(object):
249
"""A time stamp in ID3v2 format.
251
This is a restricted form of the ISO 8601 standard; time stamps
254
Or some partial form (YYYY-MM-DD HH, YYYY, etc.).
256
The 'text' attribute contains the raw text data of the time stamp.
261
def __init__(self, text):
262
if isinstance(text, ID3TimeStamp):
266
__formats = ['%04d'] + ['%02d'] * 5
267
__seps = ['-', '-', ' ', ':', ':', 'x']
270
parts = [self.year, self.month, self.day,
271
self.hour, self.minute, self.second]
273
for i, part in enumerate(iter(iter(parts).next, None)):
274
pieces.append(self.__formats[i] % part + self.__seps[i])
275
return u''.join(pieces)[:-1]
277
def set_text(self, text, splitre=re.compile('[-T:/.]|\s+')):
278
year, month, day, hour, minute, second = \
279
splitre.split(text + ':::::')[:6]
280
for a in 'year month day hour minute second'.split():
287
text = property(get_text, set_text, doc="ID3v2.4 date and time.")
293
return repr(self.text)
295
def __cmp__(self, other):
296
return cmp(self.text, other.text)
298
__hash__ = object.__hash__
300
def encode(self, *args):
301
return self.text.encode(*args)
304
class TimeStampSpec(EncodedTextSpec):
305
def read(self, frame, data):
306
value, data = super(TimeStampSpec, self).read(frame, data)
307
return self.validate(frame, value), data
309
def write(self, frame, data):
310
return super(TimeStampSpec, self).write(frame,
311
data.text.replace(' ', 'T'))
313
def validate(self, frame, value):
315
return ID3TimeStamp(value)
317
raise ValueError("Invalid ID3TimeStamp: %r" % value)
320
class ChannelSpec(ByteSpec):
321
(OTHER, MASTER, FRONTRIGHT, FRONTLEFT, BACKRIGHT, BACKLEFT, FRONTCENTRE,
322
BACKCENTRE, SUBWOOFER) = range(9)
325
class VolumeAdjustmentSpec(Spec):
326
def read(self, frame, data):
327
value, = unpack('>h', data[0:2])
328
return value/512.0, data[2:]
330
def write(self, frame, value):
331
return pack('>h', int(round(value * 512)))
333
def validate(self, frame, value):
334
if value is not None:
336
self.write(frame, value)
338
raise ValueError("out of range")
342
class VolumePeakSpec(Spec):
343
def read(self, frame, data):
344
# http://bugs.xmms.org/attachment.cgi?id=113&action=view
347
bytes = min(4, (bits + 7) >> 3)
348
# not enough frame data
349
if bytes + 1 > len(data):
350
raise ID3JunkFrameError
351
shift = ((8 - (bits & 7)) & 7) + (4 - bytes) * 8
352
for i in range(1, bytes+1):
356
return (float(peak) / (2**31-1)), data[1+bytes:]
358
def write(self, frame, value):
359
# always write as 16 bits for sanity.
360
return "\x10" + pack('>H', int(round(value * 32768)))
362
def validate(self, frame, value):
363
if value is not None:
365
self.write(frame, value)
367
raise ValueError("out of range")
371
class SynchronizedTextSpec(EncodedTextSpec):
372
def read(self, frame, data):
374
encoding, term = self._encodings[frame.encoding]
378
value_idx = data.index(term)
380
raise ID3JunkFrameError
381
value = data[:value_idx].decode(encoding)
382
if len(data) < value_idx + l + 4:
383
raise ID3JunkFrameError
384
time, = struct.unpack(">I", data[value_idx+l:value_idx+l+4])
385
texts.append((value, time))
386
data = data[value_idx+l+4:]
389
def write(self, frame, value):
391
encoding, term = self._encodings[frame.encoding]
392
for text, time in frame.text:
393
text = text.encode(encoding) + term
394
data.append(text + struct.pack(">I", time))
397
def validate(self, frame, value):
401
class KeyEventSpec(Spec):
402
def read(self, frame, data):
404
while len(data) >= 5:
405
events.append(struct.unpack(">bI", data[:5]))
409
def write(self, frame, value):
410
return "".join([struct.pack(">bI", *event) for event in value])
412
def validate(self, frame, value):
416
class VolumeAdjustmentsSpec(Spec):
417
# Not to be confused with VolumeAdjustmentSpec.
418
def read(self, frame, data):
420
while len(data) >= 4:
421
freq, adj = struct.unpack(">Hh", data[:4])
425
adjustments[freq] = adj
426
adjustments = adjustments.items()
428
return adjustments, data
430
def write(self, frame, value):
432
return "".join([struct.pack(">Hh", int(freq * 2), int(adj * 512))
433
for (freq, adj) in value])
435
def validate(self, frame, value):
439
class ASPIIndexSpec(Spec):
440
def read(self, frame, data):
448
warn("invalid bit count in ASPI (%d)" % frame.b, ID3Warning)
451
indexes = data[:frame.N * size]
452
data = data[frame.N * size:]
453
return list(struct.unpack(">" + format * frame.N, indexes)), data
455
def write(self, frame, values):
461
raise ValueError("frame.b must be 8 or 16")
462
return struct.pack(">" + format * frame.N, *values)
464
def validate(self, frame, values):