~ubuntu-branches/ubuntu/utopic/mutagen/utopic-proposed

« back to all changes in this revision

Viewing changes to mutagen/_id3specs.py

  • Committer: Package Import Robot
  • Author(s): Daniel T Chen
  • Date: 2013-11-27 22:10:48 UTC
  • mfrom: (8.1.17 sid)
  • Revision ID: package-import@ubuntu.com-20131127221048-ae2f5j42ak2ox3kw
Tags: 1.22-1ubuntu1
* Merge from Debian unstable.  Remaining changes:
  - debian/control: Drop faad and oggz-tools build dependencies (in
    universe).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005  Michael Urman
 
2
#
 
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.
 
6
 
 
7
import struct
 
8
from struct import unpack, pack
 
9
from warnings import warn
 
10
 
 
11
from mutagen._id3util import ID3JunkFrameError, ID3Warning, BitPaddedInt
 
12
 
 
13
 
 
14
class Spec(object):
 
15
    def __init__(self, name):
 
16
        self.name = name
 
17
 
 
18
    def __hash__(self):
 
19
        raise TypeError("Spec objects are unhashable")
 
20
 
 
21
    def _validate23(self, frame, value, **kwargs):
 
22
        """Return a possibly modified value which, if written,
 
23
        results in valid id3v2.3 data.
 
24
        """
 
25
 
 
26
        return value
 
27
 
 
28
 
 
29
class ByteSpec(Spec):
 
30
    def read(self, frame, data):
 
31
        return ord(data[0]), data[1:]
 
32
 
 
33
    def write(self, frame, value):
 
34
        return chr(value)
 
35
 
 
36
    def validate(self, frame, value):
 
37
        if value is not None:
 
38
            chr(value)
 
39
        return value
 
40
 
 
41
 
 
42
class IntegerSpec(Spec):
 
43
    def read(self, frame, data):
 
44
        return int(BitPaddedInt(data, bits=8)), ''
 
45
 
 
46
    def write(self, frame, value):
 
47
        return BitPaddedInt.to_str(value, bits=8, width=-1)
 
48
 
 
49
    def validate(self, frame, value):
 
50
        return value
 
51
 
 
52
 
 
53
class SizedIntegerSpec(Spec):
 
54
    def __init__(self, name, size):
 
55
        self.name, self.__sz = name, size
 
56
 
 
57
    def read(self, frame, data):
 
58
        return int(BitPaddedInt(data[:self.__sz], bits=8)), data[self.__sz:]
 
59
 
 
60
    def write(self, frame, value):
 
61
        return BitPaddedInt.to_str(value, bits=8, width=self.__sz)
 
62
 
 
63
    def validate(self, frame, value):
 
64
        return value
 
65
 
 
66
 
 
67
class EncodingSpec(ByteSpec):
 
68
    def read(self, frame, data):
 
69
        enc, data = super(EncodingSpec, self).read(frame, data)
 
70
        if enc < 16:
 
71
            return enc, data
 
72
        else:
 
73
            return 0, chr(enc)+data
 
74
 
 
75
    def validate(self, frame, value):
 
76
        if 0 <= value <= 3:
 
77
            return value
 
78
        if value is None:
 
79
            return None
 
80
        raise ValueError('Invalid Encoding: %r' % value)
 
81
 
 
82
    def _validate23(self, frame, value, **kwargs):
 
83
        # only 0, 1 are valid in v2.3, default to utf-16
 
84
        return min(1, value)
 
85
 
 
86
 
 
87
class StringSpec(Spec):
 
88
    def __init__(self, name, length):
 
89
        super(StringSpec, self).__init__(name)
 
90
        self.len = length
 
91
 
 
92
    def read(s, frame, data):
 
93
        return data[:s.len], data[s.len:]
 
94
 
 
95
    def write(s, frame, value):
 
96
        if value is None:
 
97
            return '\x00' * s.len
 
98
        else:
 
99
            return (str(value) + '\x00' * s.len)[:s.len]
 
100
 
 
101
    def validate(s, frame, value):
 
102
        if value is None:
 
103
            return None
 
104
        if isinstance(value, basestring) and len(value) == s.len:
 
105
            return value
 
106
        raise ValueError('Invalid StringSpec[%d] data: %r' % (s.len, value))
 
107
 
 
108
 
 
109
class BinaryDataSpec(Spec):
 
110
    def read(self, frame, data):
 
111
        return data, ''
 
112
 
 
113
    def write(self, frame, value):
 
114
        return str(value)
 
115
 
 
116
    def validate(self, frame, value):
 
117
        return str(value)
 
118
 
 
119
 
 
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.
 
124
    _encodings = (
 
125
        ('latin1', '\x00'),
 
126
        ('utf16', '\x00\x00'),
 
127
        ('utf_16_be', '\x00\x00'),
 
128
        ('utf8', '\x00')
 
129
    )
 
130
 
 
131
    def read(self, frame, data):
 
132
        enc, term = self._encodings[frame.encoding]
 
133
        ret = ''
 
134
        if len(term) == 1:
 
135
            if term in data:
 
136
                data, ret = data.split(term, 1)
 
137
        else:
 
138
            offset = -1
 
139
            try:
 
140
                while True:
 
141
                    offset = data.index(term, offset+1)
 
142
                    if offset & 1:
 
143
                        continue
 
144
                    data, ret = data[0:offset], data[offset+2:]
 
145
                    break
 
146
            except ValueError:
 
147
                pass
 
148
 
 
149
        if len(data) < len(term):
 
150
            return u'', ret
 
151
        return data.decode(enc), ret
 
152
 
 
153
    def write(self, frame, value):
 
154
        enc, term = self._encodings[frame.encoding]
 
155
        return value.encode(enc) + term
 
156
 
 
157
    def validate(self, frame, value):
 
158
        return unicode(value)
 
159
 
 
160
 
 
161
class MultiSpec(Spec):
 
162
    def __init__(self, name, *specs, **kw):
 
163
        super(MultiSpec, self).__init__(name)
 
164
        self.specs = specs
 
165
        self.sep = kw.get('sep')
 
166
 
 
167
    def read(self, frame, data):
 
168
        values = []
 
169
        while data:
 
170
            record = []
 
171
            for spec in self.specs:
 
172
                value, data = spec.read(frame, data)
 
173
                record.append(value)
 
174
            if len(self.specs) != 1:
 
175
                values.append(record)
 
176
            else:
 
177
                values.append(record[0])
 
178
        return values, data
 
179
 
 
180
    def write(self, frame, value):
 
181
        data = []
 
182
        if len(self.specs) == 1:
 
183
            for v in value:
 
184
                data.append(self.specs[0].write(frame, v))
 
185
        else:
 
186
            for record in value:
 
187
                for v, s in zip(record, self.specs):
 
188
                    data.append(s.write(frame, v))
 
189
        return ''.join(data)
 
190
 
 
191
    def validate(self, frame, value):
 
192
        if value is None:
 
193
            return []
 
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]
 
199
            else:
 
200
                return [
 
201
                    [s.validate(frame, v) for (v, s) in zip(val, self.specs)]
 
202
                    for val in value]
 
203
        raise ValueError('Invalid MultiSpec data: %r' % value)
 
204
 
 
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)]
 
209
                    for val in value]
 
210
 
 
211
        spec = self.specs[0]
 
212
 
 
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):
 
217
            return value
 
218
 
 
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))]
 
222
        return value
 
223
 
 
224
 
 
225
class EncodedNumericTextSpec(EncodedTextSpec):
 
226
    pass
 
227
 
 
228
 
 
229
class EncodedNumericPartTextSpec(EncodedTextSpec):
 
230
    pass
 
231
 
 
232
 
 
233
class Latin1TextSpec(EncodedTextSpec):
 
234
    def read(self, frame, data):
 
235
        if '\x00' in data:
 
236
            data, ret = data.split('\x00', 1)
 
237
        else:
 
238
            ret = ''
 
239
        return data.decode('latin1'), ret
 
240
 
 
241
    def write(self, data, value):
 
242
        return value.encode('latin1') + '\x00'
 
243
 
 
244
    def validate(self, frame, value):
 
245
        return unicode(value)
 
246
 
 
247
 
 
248
class ID3TimeStamp(object):
 
249
    """A time stamp in ID3v2 format.
 
250
 
 
251
    This is a restricted form of the ISO 8601 standard; time stamps
 
252
    take the form of:
 
253
        YYYY-MM-DD HH:MM:SS
 
254
    Or some partial form (YYYY-MM-DD HH, YYYY, etc.).
 
255
 
 
256
    The 'text' attribute contains the raw text data of the time stamp.
 
257
    """
 
258
 
 
259
    import re
 
260
 
 
261
    def __init__(self, text):
 
262
        if isinstance(text, ID3TimeStamp):
 
263
            text = text.text
 
264
        self.text = text
 
265
 
 
266
    __formats = ['%04d'] + ['%02d'] * 5
 
267
    __seps = ['-', '-', ' ', ':', ':', 'x']
 
268
 
 
269
    def get_text(self):
 
270
        parts = [self.year, self.month, self.day,
 
271
                 self.hour, self.minute, self.second]
 
272
        pieces = []
 
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]
 
276
 
 
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():
 
281
            try:
 
282
                v = int(locals()[a])
 
283
            except ValueError:
 
284
                v = None
 
285
            setattr(self, a, v)
 
286
 
 
287
    text = property(get_text, set_text, doc="ID3v2.4 date and time.")
 
288
 
 
289
    def __str__(self):
 
290
        return self.text
 
291
 
 
292
    def __repr__(self):
 
293
        return repr(self.text)
 
294
 
 
295
    def __cmp__(self, other):
 
296
        return cmp(self.text, other.text)
 
297
 
 
298
    __hash__ = object.__hash__
 
299
 
 
300
    def encode(self, *args):
 
301
        return self.text.encode(*args)
 
302
 
 
303
 
 
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
 
308
 
 
309
    def write(self, frame, data):
 
310
        return super(TimeStampSpec, self).write(frame,
 
311
                                                data.text.replace(' ', 'T'))
 
312
 
 
313
    def validate(self, frame, value):
 
314
        try:
 
315
            return ID3TimeStamp(value)
 
316
        except TypeError:
 
317
            raise ValueError("Invalid ID3TimeStamp: %r" % value)
 
318
 
 
319
 
 
320
class ChannelSpec(ByteSpec):
 
321
    (OTHER, MASTER, FRONTRIGHT, FRONTLEFT, BACKRIGHT, BACKLEFT, FRONTCENTRE,
 
322
     BACKCENTRE, SUBWOOFER) = range(9)
 
323
 
 
324
 
 
325
class VolumeAdjustmentSpec(Spec):
 
326
    def read(self, frame, data):
 
327
        value, = unpack('>h', data[0:2])
 
328
        return value/512.0, data[2:]
 
329
 
 
330
    def write(self, frame, value):
 
331
        return pack('>h', int(round(value * 512)))
 
332
 
 
333
    def validate(self, frame, value):
 
334
        if value is not None:
 
335
            try:
 
336
                self.write(frame, value)
 
337
            except struct.error:
 
338
                raise ValueError("out of range")
 
339
        return value
 
340
 
 
341
 
 
342
class VolumePeakSpec(Spec):
 
343
    def read(self, frame, data):
 
344
        # http://bugs.xmms.org/attachment.cgi?id=113&action=view
 
345
        peak = 0
 
346
        bits = ord(data[0])
 
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):
 
353
            peak *= 256
 
354
            peak += ord(data[i])
 
355
        peak *= 2 ** shift
 
356
        return (float(peak) / (2**31-1)), data[1+bytes:]
 
357
 
 
358
    def write(self, frame, value):
 
359
        # always write as 16 bits for sanity.
 
360
        return "\x10" + pack('>H', int(round(value * 32768)))
 
361
 
 
362
    def validate(self, frame, value):
 
363
        if value is not None:
 
364
            try:
 
365
                self.write(frame, value)
 
366
            except struct.error:
 
367
                raise ValueError("out of range")
 
368
        return value
 
369
 
 
370
 
 
371
class SynchronizedTextSpec(EncodedTextSpec):
 
372
    def read(self, frame, data):
 
373
        texts = []
 
374
        encoding, term = self._encodings[frame.encoding]
 
375
        while data:
 
376
            l = len(term)
 
377
            try:
 
378
                value_idx = data.index(term)
 
379
            except ValueError:
 
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:]
 
387
        return texts, ""
 
388
 
 
389
    def write(self, frame, value):
 
390
        data = []
 
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))
 
395
        return "".join(data)
 
396
 
 
397
    def validate(self, frame, value):
 
398
        return value
 
399
 
 
400
 
 
401
class KeyEventSpec(Spec):
 
402
    def read(self, frame, data):
 
403
        events = []
 
404
        while len(data) >= 5:
 
405
            events.append(struct.unpack(">bI", data[:5]))
 
406
            data = data[5:]
 
407
        return events, data
 
408
 
 
409
    def write(self, frame, value):
 
410
        return "".join([struct.pack(">bI", *event) for event in value])
 
411
 
 
412
    def validate(self, frame, value):
 
413
        return value
 
414
 
 
415
 
 
416
class VolumeAdjustmentsSpec(Spec):
 
417
    # Not to be confused with VolumeAdjustmentSpec.
 
418
    def read(self, frame, data):
 
419
        adjustments = {}
 
420
        while len(data) >= 4:
 
421
            freq, adj = struct.unpack(">Hh", data[:4])
 
422
            data = data[4:]
 
423
            freq /= 2.0
 
424
            adj /= 512.0
 
425
            adjustments[freq] = adj
 
426
        adjustments = adjustments.items()
 
427
        adjustments.sort()
 
428
        return adjustments, data
 
429
 
 
430
    def write(self, frame, value):
 
431
        value.sort()
 
432
        return "".join([struct.pack(">Hh", int(freq * 2), int(adj * 512))
 
433
                        for (freq, adj) in value])
 
434
 
 
435
    def validate(self, frame, value):
 
436
        return value
 
437
 
 
438
 
 
439
class ASPIIndexSpec(Spec):
 
440
    def read(self, frame, data):
 
441
        if frame.b == 16:
 
442
            format = "H"
 
443
            size = 2
 
444
        elif frame.b == 8:
 
445
            format = "B"
 
446
            size = 1
 
447
        else:
 
448
            warn("invalid bit count in ASPI (%d)" % frame.b, ID3Warning)
 
449
            return [], data
 
450
 
 
451
        indexes = data[:frame.N * size]
 
452
        data = data[frame.N * size:]
 
453
        return list(struct.unpack(">" + format * frame.N, indexes)), data
 
454
 
 
455
    def write(self, frame, values):
 
456
        if frame.b == 16:
 
457
            format = "H"
 
458
        elif frame.b == 8:
 
459
            format = "B"
 
460
        else:
 
461
            raise ValueError("frame.b must be 8 or 16")
 
462
        return struct.pack(">" + format * frame.N, *values)
 
463
 
 
464
    def validate(self, frame, values):
 
465
        return values