1# -*- coding: utf-8 -*- 2# Copyright (C) 2005 Michael Urman 3# 4# This program is free software; you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation; either version 2 of the License, or 7# (at your option) any later version. 8 9import struct 10import codecs 11from struct import unpack, pack 12 13from .._compat import text_type, chr_, PY3, swap_to_string, string_types, \ 14 xrange 15from .._util import total_ordering, decode_terminated, enum, izip, flags, \ 16 cdata, encode_endian, intround 17from ._util import BitPaddedInt, is_valid_frame_id 18 19 20@enum 21class PictureType(object): 22 """Enumeration of image types defined by the ID3 standard for the APIC 23 frame, but also reused in WMA/FLAC/VorbisComment. 24 """ 25 26 OTHER = 0 27 """Other""" 28 29 FILE_ICON = 1 30 """32x32 pixels 'file icon' (PNG only)""" 31 32 OTHER_FILE_ICON = 2 33 """Other file icon""" 34 35 COVER_FRONT = 3 36 """Cover (front)""" 37 38 COVER_BACK = 4 39 """Cover (back)""" 40 41 LEAFLET_PAGE = 5 42 """Leaflet page""" 43 44 MEDIA = 6 45 """Media (e.g. label side of CD)""" 46 47 LEAD_ARTIST = 7 48 """Lead artist/lead performer/soloist""" 49 50 ARTIST = 8 51 """Artist/performer""" 52 53 CONDUCTOR = 9 54 """Conductor""" 55 56 BAND = 10 57 """Band/Orchestra""" 58 59 COMPOSER = 11 60 """Composer""" 61 62 LYRICIST = 12 63 """Lyricist/text writer""" 64 65 RECORDING_LOCATION = 13 66 """Recording Location""" 67 68 DURING_RECORDING = 14 69 """During recording""" 70 71 DURING_PERFORMANCE = 15 72 """During performance""" 73 74 SCREEN_CAPTURE = 16 75 """Movie/video screen capture""" 76 77 FISH = 17 78 """A bright coloured fish""" 79 80 ILLUSTRATION = 18 81 """Illustration""" 82 83 BAND_LOGOTYPE = 19 84 """Band/artist logotype""" 85 86 PUBLISHER_LOGOTYPE = 20 87 """Publisher/Studio logotype""" 88 89 def _pprint(self): 90 return text_type(self).split(".", 1)[-1].lower().replace("_", " ") 91 92 93@flags 94class CTOCFlags(object): 95 96 TOP_LEVEL = 0x2 97 """Identifies the CTOC root frame""" 98 99 ORDERED = 0x1 100 """Child elements are ordered""" 101 102 103class SpecError(Exception): 104 pass 105 106 107class Spec(object): 108 109 handle_nodata = False 110 """If reading empty data is possible and writing it back will again 111 result in no data. 112 """ 113 114 def __init__(self, name, default): 115 self.name = name 116 self.default = default 117 118 def __hash__(self): 119 raise TypeError("Spec objects are unhashable") 120 121 def _validate23(self, frame, value, **kwargs): 122 """Return a possibly modified value which, if written, 123 results in valid id3v2.3 data. 124 """ 125 126 return value 127 128 def read(self, header, frame, data): 129 """ 130 Returns: 131 (value: object, left_data: bytes) 132 Raises: 133 SpecError 134 """ 135 136 raise NotImplementedError 137 138 def write(self, config, frame, value): 139 """ 140 Returns: 141 bytes: The serialized data 142 Raises: 143 SpecError 144 """ 145 raise NotImplementedError 146 147 def validate(self, frame, value): 148 """ 149 Returns: 150 the validated value 151 Raises: 152 ValueError 153 TypeError 154 """ 155 156 raise NotImplementedError 157 158 159class ByteSpec(Spec): 160 161 def __init__(self, name, default=0): 162 super(ByteSpec, self).__init__(name, default) 163 164 def read(self, header, frame, data): 165 return bytearray(data)[0], data[1:] 166 167 def write(self, config, frame, value): 168 return chr_(value) 169 170 def validate(self, frame, value): 171 if value is not None: 172 chr_(value) 173 return value 174 175 176class PictureTypeSpec(ByteSpec): 177 178 def __init__(self, name, default=PictureType.COVER_FRONT): 179 super(PictureTypeSpec, self).__init__(name, default) 180 181 def read(self, header, frame, data): 182 value, data = ByteSpec.read(self, header, frame, data) 183 return PictureType(value), data 184 185 def validate(self, frame, value): 186 value = ByteSpec.validate(self, frame, value) 187 if value is not None: 188 return PictureType(value) 189 return value 190 191 192class CTOCFlagsSpec(ByteSpec): 193 194 def read(self, header, frame, data): 195 value, data = ByteSpec.read(self, header, frame, data) 196 return CTOCFlags(value), data 197 198 def validate(self, frame, value): 199 value = ByteSpec.validate(self, frame, value) 200 if value is not None: 201 return CTOCFlags(value) 202 return value 203 204 205class IntegerSpec(Spec): 206 def read(self, header, frame, data): 207 return int(BitPaddedInt(data, bits=8)), b'' 208 209 def write(self, config, frame, value): 210 return BitPaddedInt.to_str(value, bits=8, width=-1) 211 212 def validate(self, frame, value): 213 return value 214 215 216class SizedIntegerSpec(Spec): 217 218 def __init__(self, name, size, default): 219 self.name, self.__sz = name, size 220 self.default = default 221 222 def read(self, header, frame, data): 223 return int(BitPaddedInt(data[:self.__sz], bits=8)), data[self.__sz:] 224 225 def write(self, config, frame, value): 226 return BitPaddedInt.to_str(value, bits=8, width=self.__sz) 227 228 def validate(self, frame, value): 229 return value 230 231 232@enum 233class Encoding(object): 234 """Text Encoding""" 235 236 LATIN1 = 0 237 """ISO-8859-1""" 238 239 UTF16 = 1 240 """UTF-16 with BOM""" 241 242 UTF16BE = 2 243 """UTF-16BE without BOM""" 244 245 UTF8 = 3 246 """UTF-8""" 247 248 249class EncodingSpec(ByteSpec): 250 251 def __init__(self, name, default=Encoding.UTF16): 252 super(EncodingSpec, self).__init__(name, default) 253 254 def read(self, header, frame, data): 255 enc, data = super(EncodingSpec, self).read(header, frame, data) 256 if enc not in (Encoding.LATIN1, Encoding.UTF16, Encoding.UTF16BE, 257 Encoding.UTF8): 258 raise SpecError('Invalid Encoding: %r' % enc) 259 return Encoding(enc), data 260 261 def validate(self, frame, value): 262 if value is None: 263 raise TypeError 264 if value not in (Encoding.LATIN1, Encoding.UTF16, Encoding.UTF16BE, 265 Encoding.UTF8): 266 raise ValueError('Invalid Encoding: %r' % value) 267 return Encoding(value) 268 269 def _validate23(self, frame, value, **kwargs): 270 # only 0, 1 are valid in v2.3, default to utf-16 271 if value not in (Encoding.LATIN1, Encoding.UTF16): 272 value = Encoding.UTF16 273 return value 274 275 276class StringSpec(Spec): 277 """A fixed size ASCII only payload.""" 278 279 def __init__(self, name, length, default=None): 280 if default is None: 281 default = u" " * length 282 super(StringSpec, self).__init__(name, default) 283 self.len = length 284 285 def read(s, header, frame, data): 286 chunk = data[:s.len] 287 try: 288 ascii = chunk.decode("ascii") 289 except UnicodeDecodeError: 290 raise SpecError("not ascii") 291 else: 292 if PY3: 293 chunk = ascii 294 295 return chunk, data[s.len:] 296 297 def write(self, config, frame, value): 298 if PY3: 299 value = value.encode("ascii") 300 return (bytes(value) + b'\x00' * self.len)[:self.len] 301 302 def validate(self, frame, value): 303 if value is None: 304 raise TypeError 305 if PY3: 306 if not isinstance(value, str): 307 raise TypeError("%s has to be str" % self.name) 308 value.encode("ascii") 309 else: 310 if not isinstance(value, bytes): 311 value = value.encode("ascii") 312 313 if len(value) == self.len: 314 return value 315 316 raise ValueError('Invalid StringSpec[%d] data: %r' % (self.len, value)) 317 318 319class RVASpec(Spec): 320 321 def __init__(self, name, stereo_only, default=[0, 0]): 322 # two_chan: RVA has only 2 channels, while RVAD has 6 channels 323 super(RVASpec, self).__init__(name, default) 324 self._max_values = 4 if stereo_only else 12 325 326 def read(self, header, frame, data): 327 # inc/dec flags 328 spec = ByteSpec("flags", 0) 329 flags, data = spec.read(header, frame, data) 330 if not data: 331 raise SpecError("truncated") 332 333 # how many bytes per value 334 bits, data = spec.read(header, frame, data) 335 if bits == 0: 336 # not allowed according to spec 337 raise SpecError("bits used has to be > 0") 338 bytes_per_value = (bits + 7) // 8 339 340 values = [] 341 while len(data) >= bytes_per_value and len(values) < self._max_values: 342 v = BitPaddedInt(data[:bytes_per_value], bits=8) 343 data = data[bytes_per_value:] 344 values.append(v) 345 346 if len(values) < 2: 347 raise SpecError("First two values not optional") 348 349 # if the respective flag bit is zero, take as decrement 350 for bit, index in enumerate([0, 1, 4, 5, 8, 10]): 351 if not cdata.test_bit(flags, bit): 352 try: 353 values[index] = -values[index] 354 except IndexError: 355 break 356 357 return values, data 358 359 def write(self, config, frame, values): 360 if len(values) < 2 or len(values) > self._max_values: 361 raise SpecError( 362 "at least two volume change values required, max %d" % 363 self._max_values) 364 365 spec = ByteSpec("flags", 0) 366 367 flags = 0 368 values = list(values) 369 for bit, index in enumerate([0, 1, 4, 5, 8, 10]): 370 try: 371 if values[index] < 0: 372 values[index] = -values[index] 373 else: 374 flags |= (1 << bit) 375 except IndexError: 376 break 377 378 buffer_ = bytearray() 379 buffer_.extend(spec.write(config, frame, flags)) 380 381 # serialized and make them all the same size (min 2 bytes) 382 byte_values = [ 383 BitPaddedInt.to_str(v, bits=8, width=-1, minwidth=2) 384 for v in values] 385 max_bytes = max([len(v) for v in byte_values]) 386 byte_values = [v.ljust(max_bytes, b"\x00") for v in byte_values] 387 388 bits = max_bytes * 8 389 buffer_.extend(spec.write(config, frame, bits)) 390 391 for v in byte_values: 392 buffer_.extend(v) 393 394 return bytes(buffer_) 395 396 def validate(self, frame, values): 397 if len(values) < 2 or len(values) > self._max_values: 398 raise ValueError("needs list of length 2..%d" % self._max_values) 399 return values 400 401 402class FrameIDSpec(StringSpec): 403 404 def __init__(self, name, length): 405 super(FrameIDSpec, self).__init__(name, length, u"X" * length) 406 407 def validate(self, frame, value): 408 value = super(FrameIDSpec, self).validate(frame, value) 409 if not is_valid_frame_id(value): 410 raise ValueError("Invalid frame ID") 411 return value 412 413 414class BinaryDataSpec(Spec): 415 416 handle_nodata = True 417 418 def __init__(self, name, default=b""): 419 super(BinaryDataSpec, self).__init__(name, default) 420 421 def read(self, header, frame, data): 422 return data, b'' 423 424 def write(self, config, frame, value): 425 if isinstance(value, bytes): 426 return value 427 value = text_type(value).encode("ascii") 428 return value 429 430 def validate(self, frame, value): 431 if value is None: 432 raise TypeError 433 if isinstance(value, bytes): 434 return value 435 elif PY3: 436 raise TypeError("%s has to be bytes" % self.name) 437 438 value = text_type(value).encode("ascii") 439 return value 440 441 442def iter_text_fixups(data, encoding): 443 """Yields a series of repaired text values for decoding""" 444 445 yield data 446 if encoding == Encoding.UTF16BE: 447 # wrong termination 448 yield data + b"\x00" 449 elif encoding == Encoding.UTF16: 450 # wrong termination 451 yield data + b"\x00" 452 # utf-16 is missing BOM, content is usually utf-16-le 453 yield codecs.BOM_UTF16_LE + data 454 # both cases combined 455 yield codecs.BOM_UTF16_LE + data + b"\x00" 456 457 458class EncodedTextSpec(Spec): 459 460 _encodings = { 461 Encoding.LATIN1: ('latin1', b'\x00'), 462 Encoding.UTF16: ('utf16', b'\x00\x00'), 463 Encoding.UTF16BE: ('utf_16_be', b'\x00\x00'), 464 Encoding.UTF8: ('utf8', b'\x00'), 465 } 466 467 def __init__(self, name, default=u""): 468 super(EncodedTextSpec, self).__init__(name, default) 469 470 def read(self, header, frame, data): 471 enc, term = self._encodings[frame.encoding] 472 err = None 473 for data in iter_text_fixups(data, frame.encoding): 474 try: 475 value, data = decode_terminated(data, enc, strict=False) 476 except ValueError as e: 477 err = e 478 else: 479 # Older id3 did not support multiple values, but we still 480 # read them. To not missinterpret zero padded values with 481 # a list of empty strings, stop if everything left is zero. 482 # https://github.com/quodlibet/mutagen/issues/276 483 if header.version < header._V24 and not data.strip(b"\x00"): 484 data = b"" 485 return value, data 486 raise SpecError(err) 487 488 def write(self, config, frame, value): 489 enc, term = self._encodings[frame.encoding] 490 try: 491 return encode_endian(value, enc, le=True) + term 492 except UnicodeEncodeError as e: 493 raise SpecError(e) 494 495 def validate(self, frame, value): 496 return text_type(value) 497 498 499class MultiSpec(Spec): 500 def __init__(self, name, *specs, **kw): 501 super(MultiSpec, self).__init__(name, default=kw.get('default')) 502 self.specs = specs 503 self.sep = kw.get('sep') 504 505 def read(self, header, frame, data): 506 values = [] 507 while data: 508 record = [] 509 for spec in self.specs: 510 value, data = spec.read(header, frame, data) 511 record.append(value) 512 if len(self.specs) != 1: 513 values.append(record) 514 else: 515 values.append(record[0]) 516 return values, data 517 518 def write(self, config, frame, value): 519 data = [] 520 if len(self.specs) == 1: 521 for v in value: 522 data.append(self.specs[0].write(config, frame, v)) 523 else: 524 for record in value: 525 for v, s in izip(record, self.specs): 526 data.append(s.write(config, frame, v)) 527 return b''.join(data) 528 529 def validate(self, frame, value): 530 if self.sep and isinstance(value, string_types): 531 value = value.split(self.sep) 532 if isinstance(value, list): 533 if len(self.specs) == 1: 534 return [self.specs[0].validate(frame, v) for v in value] 535 else: 536 return [ 537 [s.validate(frame, v) for (v, s) in izip(val, self.specs)] 538 for val in value] 539 raise ValueError('Invalid MultiSpec data: %r' % value) 540 541 def _validate23(self, frame, value, **kwargs): 542 if len(self.specs) != 1: 543 return [[s._validate23(frame, v, **kwargs) 544 for (v, s) in izip(val, self.specs)] 545 for val in value] 546 547 spec = self.specs[0] 548 549 # Merge single text spec multispecs only. 550 # (TimeStampSpec beeing the exception, but it's not a valid v2.3 frame) 551 if not isinstance(spec, EncodedTextSpec) or \ 552 isinstance(spec, TimeStampSpec): 553 return value 554 555 value = [spec._validate23(frame, v, **kwargs) for v in value] 556 if kwargs.get("sep") is not None: 557 return [spec.validate(frame, kwargs["sep"].join(value))] 558 return value 559 560 561class EncodedNumericTextSpec(EncodedTextSpec): 562 pass 563 564 565class EncodedNumericPartTextSpec(EncodedTextSpec): 566 pass 567 568 569class Latin1TextSpec(Spec): 570 571 def __init__(self, name, default=u""): 572 super(Latin1TextSpec, self).__init__(name, default) 573 574 def read(self, header, frame, data): 575 if b'\x00' in data: 576 data, ret = data.split(b'\x00', 1) 577 else: 578 ret = b'' 579 return data.decode('latin1'), ret 580 581 def write(self, config, data, value): 582 return value.encode('latin1') + b'\x00' 583 584 def validate(self, frame, value): 585 return text_type(value) 586 587 588class ID3FramesSpec(Spec): 589 590 handle_nodata = True 591 592 def __init__(self, name, default=[]): 593 super(ID3FramesSpec, self).__init__(name, default) 594 595 def read(self, header, frame, data): 596 from ._tags import ID3Tags 597 598 tags = ID3Tags() 599 return tags, tags._read(header, data) 600 601 def _validate23(self, frame, value, **kwargs): 602 from ._tags import ID3Tags 603 604 v = ID3Tags() 605 for frame in value.values(): 606 v.add(frame._get_v23_frame(**kwargs)) 607 return v 608 609 def write(self, config, frame, value): 610 return bytes(value._write(config)) 611 612 def validate(self, frame, value): 613 from ._tags import ID3Tags 614 615 if isinstance(value, ID3Tags): 616 return value 617 618 tags = ID3Tags() 619 for v in value: 620 tags.add(v) 621 622 return tags 623 624 625class Latin1TextListSpec(Spec): 626 627 def __init__(self, name, default=[]): 628 super(Latin1TextListSpec, self).__init__(name, default) 629 self._bspec = ByteSpec("entry_count", default=0) 630 self._lspec = Latin1TextSpec("child_element_id") 631 632 def read(self, header, frame, data): 633 count, data = self._bspec.read(header, frame, data) 634 entries = [] 635 for i in xrange(count): 636 entry, data = self._lspec.read(header, frame, data) 637 entries.append(entry) 638 return entries, data 639 640 def write(self, config, frame, value): 641 b = self._bspec.write(config, frame, len(value)) 642 for v in value: 643 b += self._lspec.write(config, frame, v) 644 return b 645 646 def validate(self, frame, value): 647 return [self._lspec.validate(frame, v) for v in value] 648 649 650@swap_to_string 651@total_ordering 652class ID3TimeStamp(object): 653 """A time stamp in ID3v2 format. 654 655 This is a restricted form of the ISO 8601 standard; time stamps 656 take the form of: 657 YYYY-MM-DD HH:MM:SS 658 Or some partial form (YYYY-MM-DD HH, YYYY, etc.). 659 660 The 'text' attribute contains the raw text data of the time stamp. 661 """ 662 663 import re 664 665 def __init__(self, text): 666 if isinstance(text, ID3TimeStamp): 667 text = text.text 668 elif not isinstance(text, text_type): 669 if PY3: 670 raise TypeError("not a str") 671 text = text.decode("utf-8") 672 673 self.text = text 674 675 __formats = ['%04d'] + ['%02d'] * 5 676 __seps = ['-', '-', ' ', ':', ':', 'x'] 677 678 def get_text(self): 679 parts = [self.year, self.month, self.day, 680 self.hour, self.minute, self.second] 681 pieces = [] 682 for i, part in enumerate(parts): 683 if part is None: 684 break 685 pieces.append(self.__formats[i] % part + self.__seps[i]) 686 return u''.join(pieces)[:-1] 687 688 def set_text(self, text, splitre=re.compile('[-T:/.]|\\s+')): 689 year, month, day, hour, minute, second = \ 690 splitre.split(text + ':::::')[:6] 691 for a in 'year month day hour minute second'.split(): 692 try: 693 v = int(locals()[a]) 694 except ValueError: 695 v = None 696 setattr(self, a, v) 697 698 text = property(get_text, set_text, doc="ID3v2.4 date and time.") 699 700 def __str__(self): 701 return self.text 702 703 def __bytes__(self): 704 return self.text.encode("utf-8") 705 706 def __repr__(self): 707 return repr(self.text) 708 709 def __eq__(self, other): 710 return isinstance(other, ID3TimeStamp) and self.text == other.text 711 712 def __lt__(self, other): 713 return self.text < other.text 714 715 __hash__ = object.__hash__ 716 717 def encode(self, *args): 718 return self.text.encode(*args) 719 720 721class TimeStampSpec(EncodedTextSpec): 722 def read(self, header, frame, data): 723 value, data = super(TimeStampSpec, self).read(header, frame, data) 724 return self.validate(frame, value), data 725 726 def write(self, config, frame, data): 727 return super(TimeStampSpec, self).write(config, frame, 728 data.text.replace(' ', 'T')) 729 730 def validate(self, frame, value): 731 try: 732 return ID3TimeStamp(value) 733 except TypeError: 734 raise ValueError("Invalid ID3TimeStamp: %r" % value) 735 736 737class ChannelSpec(ByteSpec): 738 (OTHER, MASTER, FRONTRIGHT, FRONTLEFT, BACKRIGHT, BACKLEFT, FRONTCENTRE, 739 BACKCENTRE, SUBWOOFER) = xrange(9) 740 741 742class VolumeAdjustmentSpec(Spec): 743 def read(self, header, frame, data): 744 value, = unpack('>h', data[0:2]) 745 return value / 512.0, data[2:] 746 747 def write(self, config, frame, value): 748 number = intround(value * 512) 749 # pack only fails in 2.7, do it manually in 2.6 750 if not -32768 <= number <= 32767: 751 raise SpecError("not in range") 752 return pack('>h', number) 753 754 def validate(self, frame, value): 755 if value is not None: 756 try: 757 self.write(None, frame, value) 758 except SpecError: 759 raise ValueError("out of range") 760 return value 761 762 763class VolumePeakSpec(Spec): 764 def read(self, header, frame, data): 765 # http://bugs.xmms.org/attachment.cgi?id=113&action=view 766 peak = 0 767 data_array = bytearray(data) 768 bits = data_array[0] 769 vol_bytes = min(4, (bits + 7) >> 3) 770 # not enough frame data 771 if vol_bytes + 1 > len(data): 772 raise SpecError("not enough frame data") 773 shift = ((8 - (bits & 7)) & 7) + (4 - vol_bytes) * 8 774 for i in xrange(1, vol_bytes + 1): 775 peak *= 256 776 peak += data_array[i] 777 peak *= 2 ** shift 778 return (float(peak) / (2 ** 31 - 1)), data[1 + vol_bytes:] 779 780 def write(self, config, frame, value): 781 number = intround(value * 32768) 782 # pack only fails in 2.7, do it manually in 2.6 783 if not 0 <= number <= 65535: 784 raise SpecError("not in range") 785 # always write as 16 bits for sanity. 786 return b"\x10" + pack('>H', number) 787 788 def validate(self, frame, value): 789 if value is not None: 790 try: 791 self.write(None, frame, value) 792 except SpecError: 793 raise ValueError("out of range") 794 return value 795 796 797class SynchronizedTextSpec(EncodedTextSpec): 798 def read(self, header, frame, data): 799 texts = [] 800 encoding, term = self._encodings[frame.encoding] 801 while data: 802 try: 803 value, data = decode_terminated(data, encoding) 804 except ValueError: 805 raise SpecError("decoding error") 806 807 if len(data) < 4: 808 raise SpecError("not enough data") 809 time, = struct.unpack(">I", data[:4]) 810 811 texts.append((value, time)) 812 data = data[4:] 813 return texts, b"" 814 815 def write(self, config, frame, value): 816 data = [] 817 encoding, term = self._encodings[frame.encoding] 818 for text, time in value: 819 try: 820 text = encode_endian(text, encoding, le=True) + term 821 except UnicodeEncodeError as e: 822 raise SpecError(e) 823 data.append(text + struct.pack(">I", time)) 824 return b"".join(data) 825 826 def validate(self, frame, value): 827 return value 828 829 830class KeyEventSpec(Spec): 831 def read(self, header, frame, data): 832 events = [] 833 while len(data) >= 5: 834 events.append(struct.unpack(">bI", data[:5])) 835 data = data[5:] 836 return events, data 837 838 def write(self, config, frame, value): 839 return b"".join(struct.pack(">bI", *event) for event in value) 840 841 def validate(self, frame, value): 842 return list(value) 843 844 845class VolumeAdjustmentsSpec(Spec): 846 # Not to be confused with VolumeAdjustmentSpec. 847 def read(self, header, frame, data): 848 adjustments = {} 849 while len(data) >= 4: 850 freq, adj = struct.unpack(">Hh", data[:4]) 851 data = data[4:] 852 freq /= 2.0 853 adj /= 512.0 854 adjustments[freq] = adj 855 adjustments = sorted(adjustments.items()) 856 return adjustments, data 857 858 def write(self, config, frame, value): 859 value.sort() 860 return b"".join(struct.pack(">Hh", int(freq * 2), int(adj * 512)) 861 for (freq, adj) in value) 862 863 def validate(self, frame, value): 864 return list(value) 865 866 867class ASPIIndexSpec(Spec): 868 869 def read(self, header, frame, data): 870 if frame.b == 16: 871 format = "H" 872 size = 2 873 elif frame.b == 8: 874 format = "B" 875 size = 1 876 else: 877 raise SpecError("invalid bit count in ASPI (%d)" % frame.b) 878 879 indexes = data[:frame.N * size] 880 data = data[frame.N * size:] 881 try: 882 return list(struct.unpack(">" + format * frame.N, indexes)), data 883 except struct.error as e: 884 raise SpecError(e) 885 886 def write(self, config, frame, values): 887 if frame.b == 16: 888 format = "H" 889 elif frame.b == 8: 890 format = "B" 891 else: 892 raise SpecError("frame.b must be 8 or 16") 893 try: 894 return struct.pack(">" + format * frame.N, *values) 895 except struct.error as e: 896 raise SpecError(e) 897 898 def validate(self, frame, values): 899 return list(values) 900