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 zlib 10from struct import unpack 11 12from ._util import ID3JunkFrameError, ID3EncryptionUnsupportedError, unsynch, \ 13 ID3SaveConfig, error 14from ._specs import BinaryDataSpec, StringSpec, Latin1TextSpec, \ 15 EncodedTextSpec, ByteSpec, EncodingSpec, ASPIIndexSpec, SizedIntegerSpec, \ 16 IntegerSpec, Encoding, VolumeAdjustmentsSpec, VolumePeakSpec, \ 17 VolumeAdjustmentSpec, ChannelSpec, MultiSpec, SynchronizedTextSpec, \ 18 KeyEventSpec, TimeStampSpec, EncodedNumericPartTextSpec, \ 19 EncodedNumericTextSpec, SpecError, PictureTypeSpec, ID3FramesSpec, \ 20 Latin1TextListSpec, CTOCFlagsSpec, FrameIDSpec, RVASpec 21from .._compat import text_type, string_types, swap_to_string, iteritems, \ 22 izip, itervalues 23 24 25def _bytes2key(b): 26 assert isinstance(b, bytes) 27 28 return b.decode("latin1") 29 30 31class Frame(object): 32 """Fundamental unit of ID3 data. 33 34 ID3 tags are split into frames. Each frame has a potentially 35 different structure, and so this base class is not very featureful. 36 """ 37 38 FLAG23_ALTERTAG = 0x8000 39 FLAG23_ALTERFILE = 0x4000 40 FLAG23_READONLY = 0x2000 41 FLAG23_COMPRESS = 0x0080 42 FLAG23_ENCRYPT = 0x0040 43 FLAG23_GROUP = 0x0020 44 45 FLAG24_ALTERTAG = 0x4000 46 FLAG24_ALTERFILE = 0x2000 47 FLAG24_READONLY = 0x1000 48 FLAG24_GROUPID = 0x0040 49 FLAG24_COMPRESS = 0x0008 50 FLAG24_ENCRYPT = 0x0004 51 FLAG24_UNSYNCH = 0x0002 52 FLAG24_DATALEN = 0x0001 53 54 _framespec = [] 55 _optionalspec = [] 56 57 def __init__(self, *args, **kwargs): 58 if len(args) == 1 and len(kwargs) == 0 and \ 59 isinstance(args[0], type(self)): 60 other = args[0] 61 # ask the sub class to fill in our data 62 other._to_other(self) 63 else: 64 for checker, val in izip(self._framespec, args): 65 setattr(self, checker.name, val) 66 for checker in self._framespec[len(args):]: 67 setattr(self, checker.name, 68 kwargs.get(checker.name, checker.default)) 69 for spec in self._optionalspec: 70 if spec.name in kwargs: 71 setattr(self, spec.name, kwargs[spec.name]) 72 else: 73 break 74 75 def __setattr__(self, name, value): 76 for checker in self._framespec: 77 if checker.name == name: 78 self._setattr(name, checker.validate(self, value)) 79 return 80 for checker in self._optionalspec: 81 if checker.name == name: 82 self._setattr(name, checker.validate(self, value)) 83 return 84 super(Frame, self).__setattr__(name, value) 85 86 def _setattr(self, name, value): 87 self.__dict__[name] = value 88 89 def _to_other(self, other): 90 # this impl covers subclasses with the same framespec 91 if other._framespec is not self._framespec: 92 raise ValueError 93 94 for checker in other._framespec: 95 other._setattr(checker.name, getattr(self, checker.name)) 96 97 # this impl covers subclasses with the same optionalspec 98 if other._optionalspec is not self._optionalspec: 99 raise ValueError 100 101 for checker in other._optionalspec: 102 if hasattr(self, checker.name): 103 other._setattr(checker.name, getattr(self, checker.name)) 104 105 def _merge_frame(self, other): 106 # default impl, use the new tag over the old one 107 return other 108 109 def _upgrade_frame(self): 110 """Returns either this instance or a new instance if this is a v2.2 111 frame and an upgrade to a v2.3/4 equivalent is viable. 112 113 If this is a v2.2 instance and there is no upgrade path, returns None. 114 """ 115 116 # turn 2.2 into 2.3/2.4 tags 117 if len(type(self).__name__) == 3: 118 base = type(self).__base__ 119 if base is Frame: 120 return 121 return base(self) 122 else: 123 return self 124 125 def _get_v23_frame(self, **kwargs): 126 """Returns a frame copy which is suitable for writing into a v2.3 tag. 127 128 kwargs get passed to the specs. 129 """ 130 131 new_kwargs = {} 132 for checker in self._framespec: 133 name = checker.name 134 value = getattr(self, name) 135 new_kwargs[name] = checker._validate23(self, value, **kwargs) 136 137 for checker in self._optionalspec: 138 name = checker.name 139 if hasattr(self, name): 140 value = getattr(self, name) 141 new_kwargs[name] = checker._validate23(self, value, **kwargs) 142 143 return type(self)(**new_kwargs) 144 145 @property 146 def HashKey(self): 147 """An internal key used to ensure frame uniqueness in a tag""" 148 149 return self.FrameID 150 151 @property 152 def FrameID(self): 153 """ID3v2 three or four character frame ID""" 154 155 return type(self).__name__ 156 157 def __repr__(self): 158 """Python representation of a frame. 159 160 The string returned is a valid Python expression to construct 161 a copy of this frame. 162 """ 163 kw = [] 164 for attr in self._framespec: 165 # so repr works during __init__ 166 if hasattr(self, attr.name): 167 kw.append('%s=%r' % (attr.name, getattr(self, attr.name))) 168 for attr in self._optionalspec: 169 if hasattr(self, attr.name): 170 kw.append('%s=%r' % (attr.name, getattr(self, attr.name))) 171 return '%s(%s)' % (type(self).__name__, ', '.join(kw)) 172 173 def _readData(self, id3, data): 174 """Raises ID3JunkFrameError; Returns leftover data""" 175 176 for reader in self._framespec: 177 if len(data) or reader.handle_nodata: 178 try: 179 value, data = reader.read(id3, self, data) 180 except SpecError as e: 181 raise ID3JunkFrameError(e) 182 else: 183 raise ID3JunkFrameError("no data left") 184 self._setattr(reader.name, value) 185 186 for reader in self._optionalspec: 187 if len(data) or reader.handle_nodata: 188 try: 189 value, data = reader.read(id3, self, data) 190 except SpecError as e: 191 raise ID3JunkFrameError(e) 192 else: 193 break 194 self._setattr(reader.name, value) 195 196 return data 197 198 def _writeData(self, config=None): 199 """Raises error""" 200 201 if config is None: 202 config = ID3SaveConfig() 203 204 if config.v2_version == 3: 205 frame = self._get_v23_frame(sep=config.v23_separator) 206 else: 207 frame = self 208 209 data = [] 210 for writer in self._framespec: 211 try: 212 data.append( 213 writer.write(config, frame, getattr(frame, writer.name))) 214 except SpecError as e: 215 raise error(e) 216 217 for writer in self._optionalspec: 218 try: 219 data.append( 220 writer.write(config, frame, getattr(frame, writer.name))) 221 except AttributeError: 222 break 223 except SpecError as e: 224 raise error(e) 225 226 return b''.join(data) 227 228 def pprint(self): 229 """Return a human-readable representation of the frame.""" 230 return "%s=%s" % (type(self).__name__, self._pprint()) 231 232 def _pprint(self): 233 return "[unrepresentable data]" 234 235 @classmethod 236 def _fromData(cls, header, tflags, data): 237 """Construct this ID3 frame from raw string data. 238 239 Raises: 240 241 ID3JunkFrameError in case parsing failed 242 NotImplementedError in case parsing isn't implemented 243 ID3EncryptionUnsupportedError in case the frame is encrypted. 244 """ 245 246 if header.version >= header._V24: 247 if tflags & (Frame.FLAG24_COMPRESS | Frame.FLAG24_DATALEN): 248 # The data length int is syncsafe in 2.4 (but not 2.3). 249 # However, we don't actually need the data length int, 250 # except to work around a QL 0.12 bug, and in that case 251 # all we need are the raw bytes. 252 datalen_bytes = data[:4] 253 data = data[4:] 254 if tflags & Frame.FLAG24_UNSYNCH or header.f_unsynch: 255 try: 256 data = unsynch.decode(data) 257 except ValueError: 258 # Some things write synch-unsafe data with either the frame 259 # or global unsynch flag set. Try to load them as is. 260 # https://github.com/quodlibet/mutagen/issues/210 261 # https://github.com/quodlibet/mutagen/issues/223 262 pass 263 if tflags & Frame.FLAG24_ENCRYPT: 264 raise ID3EncryptionUnsupportedError 265 if tflags & Frame.FLAG24_COMPRESS: 266 try: 267 data = zlib.decompress(data) 268 except zlib.error: 269 # the initial mutagen that went out with QL 0.12 did not 270 # write the 4 bytes of uncompressed size. Compensate. 271 data = datalen_bytes + data 272 try: 273 data = zlib.decompress(data) 274 except zlib.error as err: 275 raise ID3JunkFrameError( 276 'zlib: %s: %r' % (err, data)) 277 278 elif header.version >= header._V23: 279 if tflags & Frame.FLAG23_COMPRESS: 280 usize, = unpack('>L', data[:4]) 281 data = data[4:] 282 if tflags & Frame.FLAG23_ENCRYPT: 283 raise ID3EncryptionUnsupportedError 284 if tflags & Frame.FLAG23_COMPRESS: 285 try: 286 data = zlib.decompress(data) 287 except zlib.error as err: 288 raise ID3JunkFrameError('zlib: %s: %r' % (err, data)) 289 290 frame = cls() 291 frame._readData(header, data) 292 return frame 293 294 def __hash__(self): 295 raise TypeError("Frame objects are unhashable") 296 297 298class CHAP(Frame): 299 """Chapter""" 300 301 _framespec = [ 302 Latin1TextSpec("element_id"), 303 SizedIntegerSpec("start_time", 4, default=0), 304 SizedIntegerSpec("end_time", 4, default=0), 305 SizedIntegerSpec("start_offset", 4, default=0xffffffff), 306 SizedIntegerSpec("end_offset", 4, default=0xffffffff), 307 ID3FramesSpec("sub_frames"), 308 ] 309 310 @property 311 def HashKey(self): 312 return '%s:%s' % (self.FrameID, self.element_id) 313 314 def __eq__(self, other): 315 if not isinstance(other, CHAP): 316 return False 317 318 self_frames = self.sub_frames or {} 319 other_frames = other.sub_frames or {} 320 if sorted(self_frames.values()) != sorted(other_frames.values()): 321 return False 322 323 return self.element_id == other.element_id and \ 324 self.start_time == other.start_time and \ 325 self.end_time == other.end_time and \ 326 self.start_offset == other.start_offset and \ 327 self.end_offset == other.end_offset 328 329 __hash__ = Frame.__hash__ 330 331 def _pprint(self): 332 frame_pprint = u"" 333 for frame in itervalues(self.sub_frames): 334 for line in frame.pprint().splitlines(): 335 frame_pprint += "\n" + " " * 4 + line 336 return u"%s time=%d..%d offset=%d..%d%s" % ( 337 self.element_id, self.start_time, self.end_time, 338 self.start_offset, self.end_offset, frame_pprint) 339 340 341class CTOC(Frame): 342 """Table of contents""" 343 344 _framespec = [ 345 Latin1TextSpec("element_id"), 346 CTOCFlagsSpec("flags", default=0), 347 Latin1TextListSpec("child_element_ids"), 348 ID3FramesSpec("sub_frames"), 349 ] 350 351 @property 352 def HashKey(self): 353 return '%s:%s' % (self.FrameID, self.element_id) 354 355 __hash__ = Frame.__hash__ 356 357 def __eq__(self, other): 358 if not isinstance(other, CTOC): 359 return False 360 361 self_frames = self.sub_frames or {} 362 other_frames = other.sub_frames or {} 363 if sorted(self_frames.values()) != sorted(other_frames.values()): 364 return False 365 366 return self.element_id == other.element_id and \ 367 self.flags == other.flags and \ 368 self.child_element_ids == other.child_element_ids 369 370 def _pprint(self): 371 frame_pprint = u"" 372 if getattr(self, "sub_frames", None): 373 frame_pprint += "\n" + "\n".join( 374 [" " * 4 + f.pprint() for f in self.sub_frames.values()]) 375 return u"%s flags=%d child_element_ids=%s%s" % ( 376 self.element_id, int(self.flags), 377 u",".join(self.child_element_ids), frame_pprint) 378 379 380@swap_to_string 381class TextFrame(Frame): 382 """Text strings. 383 384 Text frames support casts to unicode or str objects, as well as 385 list-like indexing, extend, and append. 386 387 Iterating over a TextFrame iterates over its strings, not its 388 characters. 389 390 Text frames have a 'text' attribute which is the list of strings, 391 and an 'encoding' attribute; 0 for ISO-8859 1, 1 UTF-16, 2 for 392 UTF-16BE, and 3 for UTF-8. If you don't want to worry about 393 encodings, just set it to 3. 394 """ 395 396 _framespec = [ 397 EncodingSpec('encoding', default=Encoding.UTF16), 398 MultiSpec('text', EncodedTextSpec('text'), sep=u'\u0000', default=[]), 399 ] 400 401 def __bytes__(self): 402 return text_type(self).encode('utf-8') 403 404 def __str__(self): 405 return u'\u0000'.join(self.text) 406 407 def __eq__(self, other): 408 if isinstance(other, bytes): 409 return bytes(self) == other 410 elif isinstance(other, text_type): 411 return text_type(self) == other 412 return self.text == other 413 414 __hash__ = Frame.__hash__ 415 416 def __getitem__(self, item): 417 return self.text[item] 418 419 def __iter__(self): 420 return iter(self.text) 421 422 def append(self, value): 423 """Append a string.""" 424 425 return self.text.append(value) 426 427 def extend(self, value): 428 """Extend the list by appending all strings from the given list.""" 429 430 return self.text.extend(value) 431 432 def _merge_frame(self, other): 433 # merge in new values 434 for val in other[:]: 435 if val not in self: 436 self.append(val) 437 self.encoding = max(self.encoding, other.encoding) 438 return self 439 440 def _pprint(self): 441 return " / ".join(self.text) 442 443 444class NumericTextFrame(TextFrame): 445 """Numerical text strings. 446 447 The numeric value of these frames can be gotten with unary plus, e.g.:: 448 449 frame = TLEN('12345') 450 length = +frame 451 """ 452 453 _framespec = [ 454 EncodingSpec('encoding', default=Encoding.UTF16), 455 MultiSpec('text', EncodedNumericTextSpec('text'), sep=u'\u0000', 456 default=[]), 457 ] 458 459 def __pos__(self): 460 """Return the numerical value of the string.""" 461 return int(self.text[0]) 462 463 464class NumericPartTextFrame(TextFrame): 465 """Multivalue numerical text strings. 466 467 These strings indicate 'part (e.g. track) X of Y', and unary plus 468 returns the first value:: 469 470 frame = TRCK('4/15') 471 track = +frame # track == 4 472 """ 473 474 _framespec = [ 475 EncodingSpec('encoding', default=Encoding.UTF16), 476 MultiSpec('text', EncodedNumericPartTextSpec('text'), sep=u'\u0000', 477 default=[]), 478 ] 479 480 def __pos__(self): 481 return int(self.text[0].split("/")[0]) 482 483 484@swap_to_string 485class TimeStampTextFrame(TextFrame): 486 """A list of time stamps. 487 488 The 'text' attribute in this frame is a list of ID3TimeStamp 489 objects, not a list of strings. 490 """ 491 492 _framespec = [ 493 EncodingSpec('encoding', default=Encoding.UTF16), 494 MultiSpec('text', TimeStampSpec('stamp'), sep=u',', default=[]), 495 ] 496 497 def __bytes__(self): 498 return text_type(self).encode('utf-8') 499 500 def __str__(self): 501 return u','.join([stamp.text for stamp in self.text]) 502 503 def _pprint(self): 504 return u" / ".join([stamp.text for stamp in self.text]) 505 506 507@swap_to_string 508class UrlFrame(Frame): 509 """A frame containing a URL string. 510 511 The ID3 specification is silent about IRIs and normalized URL 512 forms. Mutagen assumes all URLs in files are encoded as Latin 1, 513 but string conversion of this frame returns a UTF-8 representation 514 for compatibility with other string conversions. 515 516 The only sane way to handle URLs in MP3s is to restrict them to 517 ASCII. 518 """ 519 520 _framespec = [ 521 Latin1TextSpec('url'), 522 ] 523 524 def __bytes__(self): 525 return self.url.encode('utf-8') 526 527 def __str__(self): 528 return self.url 529 530 def __eq__(self, other): 531 return self.url == other 532 533 __hash__ = Frame.__hash__ 534 535 def _pprint(self): 536 return self.url 537 538 539class UrlFrameU(UrlFrame): 540 541 @property 542 def HashKey(self): 543 return '%s:%s' % (self.FrameID, self.url) 544 545 546class TALB(TextFrame): 547 "Album" 548 549 550class TBPM(NumericTextFrame): 551 "Beats per minute" 552 553 554class TCOM(TextFrame): 555 "Composer" 556 557 558class TCON(TextFrame): 559 """Content type (Genre) 560 561 ID3 has several ways genres can be represented; for convenience, 562 use the 'genres' property rather than the 'text' attribute. 563 """ 564 565 from mutagen._constants import GENRES 566 GENRES = GENRES 567 568 def __get_genres(self): 569 genres = [] 570 import re 571 genre_re = re.compile(r"((?:\((?P<id>[0-9]+|RX|CR)\))*)(?P<str>.+)?") 572 for value in self.text: 573 # 255 possible entries in id3v1 574 if value.isdigit() and int(value) < 256: 575 try: 576 genres.append(self.GENRES[int(value)]) 577 except IndexError: 578 genres.append(u"Unknown") 579 elif value == "CR": 580 genres.append(u"Cover") 581 elif value == "RX": 582 genres.append(u"Remix") 583 elif value: 584 newgenres = [] 585 genreid, dummy, genrename = genre_re.match(value).groups() 586 587 if genreid: 588 for gid in genreid[1:-1].split(")("): 589 if gid.isdigit() and int(gid) < len(self.GENRES): 590 gid = text_type(self.GENRES[int(gid)]) 591 newgenres.append(gid) 592 elif gid == "CR": 593 newgenres.append(u"Cover") 594 elif gid == "RX": 595 newgenres.append(u"Remix") 596 else: 597 newgenres.append(u"Unknown") 598 599 if genrename: 600 # "Unescaping" the first parenthesis 601 if genrename.startswith("(("): 602 genrename = genrename[1:] 603 if genrename not in newgenres: 604 newgenres.append(genrename) 605 606 genres.extend(newgenres) 607 608 return genres 609 610 def __set_genres(self, genres): 611 if isinstance(genres, string_types): 612 genres = [genres] 613 self.text = [self.__decode(g) for g in genres] 614 615 def __decode(self, value): 616 if isinstance(value, bytes): 617 enc = EncodedTextSpec._encodings[self.encoding][0] 618 return value.decode(enc) 619 else: 620 return value 621 622 genres = property(__get_genres, __set_genres, None, 623 "A list of genres parsed from the raw text data.") 624 625 def _pprint(self): 626 return " / ".join(self.genres) 627 628 629class TCOP(TextFrame): 630 "Copyright (c)" 631 632 633class TCMP(NumericTextFrame): 634 "iTunes Compilation Flag" 635 636 637class TDAT(TextFrame): 638 "Date of recording (DDMM)" 639 640 641class TDEN(TimeStampTextFrame): 642 "Encoding Time" 643 644 645class TDES(TextFrame): 646 "iTunes Podcast Description" 647 648 649class TKWD(TextFrame): 650 "iTunes Podcast Keywords" 651 652 653class TCAT(TextFrame): 654 "iTunes Podcast Category" 655 656 657class MVNM(TextFrame): 658 "iTunes Movement Name" 659 660 661class MVN(MVNM): 662 "iTunes Movement Name" 663 664 665class MVIN(NumericPartTextFrame): 666 "iTunes Movement Number/Count" 667 668 669class MVI(MVIN): 670 "iTunes Movement Number/Count" 671 672 673class GRP1(TextFrame): 674 "iTunes Grouping" 675 676 677class GP1(GRP1): 678 "iTunes Grouping" 679 680 681class TDOR(TimeStampTextFrame): 682 "Original Release Time" 683 684 685class TDLY(NumericTextFrame): 686 "Audio Delay (ms)" 687 688 689class TDRC(TimeStampTextFrame): 690 "Recording Time" 691 692 693class TDRL(TimeStampTextFrame): 694 "Release Time" 695 696 697class TDTG(TimeStampTextFrame): 698 "Tagging Time" 699 700 701class TENC(TextFrame): 702 "Encoder" 703 704 705class TEXT(TextFrame): 706 "Lyricist" 707 708 709class TFLT(TextFrame): 710 "File type" 711 712 713class TGID(TextFrame): 714 "iTunes Podcast Identifier" 715 716 717class TIME(TextFrame): 718 "Time of recording (HHMM)" 719 720 721class TIT1(TextFrame): 722 "Content group description" 723 724 725class TIT2(TextFrame): 726 "Title" 727 728 729class TIT3(TextFrame): 730 "Subtitle/Description refinement" 731 732 733class TKEY(TextFrame): 734 "Starting Key" 735 736 737class TLAN(TextFrame): 738 "Audio Languages" 739 740 741class TLEN(NumericTextFrame): 742 "Audio Length (ms)" 743 744 745class TMED(TextFrame): 746 "Source Media Type" 747 748 749class TMOO(TextFrame): 750 "Mood" 751 752 753class TOAL(TextFrame): 754 "Original Album" 755 756 757class TOFN(TextFrame): 758 "Original Filename" 759 760 761class TOLY(TextFrame): 762 "Original Lyricist" 763 764 765class TOPE(TextFrame): 766 "Original Artist/Performer" 767 768 769class TORY(NumericTextFrame): 770 "Original Release Year" 771 772 773class TOWN(TextFrame): 774 "Owner/Licensee" 775 776 777class TPE1(TextFrame): 778 "Lead Artist/Performer/Soloist/Group" 779 780 781class TPE2(TextFrame): 782 "Band/Orchestra/Accompaniment" 783 784 785class TPE3(TextFrame): 786 "Conductor" 787 788 789class TPE4(TextFrame): 790 "Interpreter/Remixer/Modifier" 791 792 793class TPOS(NumericPartTextFrame): 794 "Part of set" 795 796 797class TPRO(TextFrame): 798 "Produced (P)" 799 800 801class TPUB(TextFrame): 802 "Publisher" 803 804 805class TRCK(NumericPartTextFrame): 806 "Track Number" 807 808 809class TRDA(TextFrame): 810 "Recording Dates" 811 812 813class TRSN(TextFrame): 814 "Internet Radio Station Name" 815 816 817class TRSO(TextFrame): 818 "Internet Radio Station Owner" 819 820 821class TSIZ(NumericTextFrame): 822 "Size of audio data (bytes)" 823 824 825class TSO2(TextFrame): 826 "iTunes Album Artist Sort" 827 828 829class TSOA(TextFrame): 830 "Album Sort Order key" 831 832 833class TSOC(TextFrame): 834 "iTunes Composer Sort" 835 836 837class TSOP(TextFrame): 838 "Perfomer Sort Order key" 839 840 841class TSOT(TextFrame): 842 "Title Sort Order key" 843 844 845class TSRC(TextFrame): 846 "International Standard Recording Code (ISRC)" 847 848 849class TSSE(TextFrame): 850 "Encoder settings" 851 852 853class TSST(TextFrame): 854 "Set Subtitle" 855 856 857class TYER(NumericTextFrame): 858 "Year of recording" 859 860 861class TXXX(TextFrame): 862 """User-defined text data. 863 864 TXXX frames have a 'desc' attribute which is set to any Unicode 865 value (though the encoding of the text and the description must be 866 the same). Many taggers use this frame to store freeform keys. 867 """ 868 869 _framespec = [ 870 EncodingSpec('encoding'), 871 EncodedTextSpec('desc'), 872 MultiSpec('text', EncodedTextSpec('text'), sep=u'\u0000', default=[]), 873 ] 874 875 @property 876 def HashKey(self): 877 return '%s:%s' % (self.FrameID, self.desc) 878 879 def _pprint(self): 880 return "%s=%s" % (self.desc, " / ".join(self.text)) 881 882 883class WCOM(UrlFrameU): 884 "Commercial Information" 885 886 887class WCOP(UrlFrame): 888 "Copyright Information" 889 890 891class WFED(UrlFrame): 892 "iTunes Podcast Feed" 893 894 895class WOAF(UrlFrame): 896 "Official File Information" 897 898 899class WOAR(UrlFrameU): 900 "Official Artist/Performer Information" 901 902 903class WOAS(UrlFrame): 904 "Official Source Information" 905 906 907class WORS(UrlFrame): 908 "Official Internet Radio Information" 909 910 911class WPAY(UrlFrame): 912 "Payment Information" 913 914 915class WPUB(UrlFrame): 916 "Official Publisher Information" 917 918 919class WXXX(UrlFrame): 920 """User-defined URL data. 921 922 Like TXXX, this has a freeform description associated with it. 923 """ 924 925 _framespec = [ 926 EncodingSpec('encoding', default=Encoding.UTF16), 927 EncodedTextSpec('desc'), 928 Latin1TextSpec('url'), 929 ] 930 931 @property 932 def HashKey(self): 933 return '%s:%s' % (self.FrameID, self.desc) 934 935 936class PairedTextFrame(Frame): 937 """Paired text strings. 938 939 Some ID3 frames pair text strings, to associate names with a more 940 specific involvement in the song. The 'people' attribute of these 941 frames contains a list of pairs:: 942 943 [['trumpet', 'Miles Davis'], ['bass', 'Paul Chambers']] 944 945 Like text frames, these frames also have an encoding attribute. 946 """ 947 948 _framespec = [ 949 EncodingSpec('encoding', default=Encoding.UTF16), 950 MultiSpec('people', 951 EncodedTextSpec('involvement'), 952 EncodedTextSpec('person'), default=[]) 953 ] 954 955 def __eq__(self, other): 956 return self.people == other 957 958 __hash__ = Frame.__hash__ 959 960 961class TIPL(PairedTextFrame): 962 "Involved People List" 963 964 965class TMCL(PairedTextFrame): 966 "Musicians Credits List" 967 968 969class IPLS(TIPL): 970 "Involved People List" 971 972 973class BinaryFrame(Frame): 974 """Binary data 975 976 The 'data' attribute contains the raw byte string. 977 """ 978 979 _framespec = [ 980 BinaryDataSpec('data'), 981 ] 982 983 def __eq__(self, other): 984 return self.data == other 985 986 __hash__ = Frame.__hash__ 987 988 989class MCDI(BinaryFrame): 990 "Binary dump of CD's TOC" 991 992 993class ETCO(Frame): 994 """Event timing codes.""" 995 996 _framespec = [ 997 ByteSpec("format", default=1), 998 KeyEventSpec("events", default=[]), 999 ] 1000 1001 def __eq__(self, other): 1002 return self.events == other 1003 1004 __hash__ = Frame.__hash__ 1005 1006 1007class MLLT(Frame): 1008 """MPEG location lookup table. 1009 1010 This frame's attributes may be changed in the future based on 1011 feedback from real-world use. 1012 """ 1013 1014 _framespec = [ 1015 SizedIntegerSpec('frames', size=2, default=0), 1016 SizedIntegerSpec('bytes', size=3, default=0), 1017 SizedIntegerSpec('milliseconds', size=3, default=0), 1018 ByteSpec('bits_for_bytes', default=0), 1019 ByteSpec('bits_for_milliseconds', default=0), 1020 BinaryDataSpec('data'), 1021 ] 1022 1023 def __eq__(self, other): 1024 return self.data == other 1025 1026 __hash__ = Frame.__hash__ 1027 1028 1029class SYTC(Frame): 1030 """Synchronised tempo codes. 1031 1032 This frame's attributes may be changed in the future based on 1033 feedback from real-world use. 1034 """ 1035 1036 _framespec = [ 1037 ByteSpec("format", default=1), 1038 BinaryDataSpec("data"), 1039 ] 1040 1041 def __eq__(self, other): 1042 return self.data == other 1043 1044 __hash__ = Frame.__hash__ 1045 1046 1047@swap_to_string 1048class USLT(Frame): 1049 """Unsynchronised lyrics/text transcription. 1050 1051 Lyrics have a three letter ISO language code ('lang'), a 1052 description ('desc'), and a block of plain text ('text'). 1053 """ 1054 1055 _framespec = [ 1056 EncodingSpec('encoding', default=Encoding.UTF16), 1057 StringSpec('lang', length=3, default=u"XXX"), 1058 EncodedTextSpec('desc'), 1059 EncodedTextSpec('text'), 1060 ] 1061 1062 @property 1063 def HashKey(self): 1064 return '%s:%s:%s' % (self.FrameID, self.desc, self.lang) 1065 1066 def __bytes__(self): 1067 return self.text.encode('utf-8') 1068 1069 def __str__(self): 1070 return self.text 1071 1072 def __eq__(self, other): 1073 return self.text == other 1074 1075 __hash__ = Frame.__hash__ 1076 1077 def _pprint(self): 1078 return "%s=%s=%s" % (self.desc, self.lang, self.text) 1079 1080 1081@swap_to_string 1082class SYLT(Frame): 1083 """Synchronised lyrics/text.""" 1084 1085 _framespec = [ 1086 EncodingSpec('encoding'), 1087 StringSpec('lang', length=3, default=u"XXX"), 1088 ByteSpec('format', default=1), 1089 ByteSpec('type', default=0), 1090 EncodedTextSpec('desc'), 1091 SynchronizedTextSpec('text'), 1092 ] 1093 1094 @property 1095 def HashKey(self): 1096 return '%s:%s:%s' % (self.FrameID, self.desc, self.lang) 1097 1098 def _pprint(self): 1099 return str(self) 1100 1101 def __eq__(self, other): 1102 return str(self) == other 1103 1104 __hash__ = Frame.__hash__ 1105 1106 def __str__(self): 1107 unit = 'fr' if self.format == 1 else 'ms' 1108 return u"\n".join("[{0}{1}]: {2}".format(time, unit, text) 1109 for (text, time) in self.text) 1110 1111 def __bytes__(self): 1112 return text_type(self).encode("utf-8") 1113 1114 1115class COMM(TextFrame): 1116 """User comment. 1117 1118 User comment frames have a descrption, like TXXX, and also a three 1119 letter ISO language code in the 'lang' attribute. 1120 """ 1121 1122 _framespec = [ 1123 EncodingSpec('encoding'), 1124 StringSpec('lang', length=3, default="XXX"), 1125 EncodedTextSpec('desc'), 1126 MultiSpec('text', EncodedTextSpec('text'), sep=u'\u0000', default=[]), 1127 ] 1128 1129 @property 1130 def HashKey(self): 1131 return '%s:%s:%s' % (self.FrameID, self.desc, self.lang) 1132 1133 def _pprint(self): 1134 return "%s=%s=%s" % (self.desc, self.lang, " / ".join(self.text)) 1135 1136 1137class RVA2(Frame): 1138 """Relative volume adjustment (2). 1139 1140 This frame is used to implemented volume scaling, and in 1141 particular, normalization using ReplayGain. 1142 1143 Attributes: 1144 1145 * desc -- description or context of this adjustment 1146 * channel -- audio channel to adjust (master is 1) 1147 * gain -- a + or - dB gain relative to some reference level 1148 * peak -- peak of the audio as a floating point number, [0, 1] 1149 1150 When storing ReplayGain tags, use descriptions of 'album' and 1151 'track' on channel 1. 1152 """ 1153 1154 _framespec = [ 1155 Latin1TextSpec('desc'), 1156 ChannelSpec('channel', default=1), 1157 VolumeAdjustmentSpec('gain', default=1), 1158 VolumePeakSpec('peak', default=1), 1159 ] 1160 1161 _channels = ["Other", "Master volume", "Front right", "Front left", 1162 "Back right", "Back left", "Front centre", "Back centre", 1163 "Subwoofer"] 1164 1165 @property 1166 def HashKey(self): 1167 return '%s:%s' % (self.FrameID, self.desc) 1168 1169 def __eq__(self, other): 1170 try: 1171 return ((str(self) == other) or 1172 (self.desc == other.desc and 1173 self.channel == other.channel and 1174 self.gain == other.gain and 1175 self.peak == other.peak)) 1176 except AttributeError: 1177 return False 1178 1179 __hash__ = Frame.__hash__ 1180 1181 def __str__(self): 1182 return "%s: %+0.4f dB/%0.4f" % ( 1183 self._channels[self.channel], self.gain, self.peak) 1184 1185 1186class EQU2(Frame): 1187 """Equalisation (2). 1188 1189 Attributes: 1190 method -- interpolation method (0 = band, 1 = linear) 1191 desc -- identifying description 1192 adjustments -- list of (frequency, vol_adjustment) pairs 1193 """ 1194 1195 _framespec = [ 1196 ByteSpec("method", default=0), 1197 Latin1TextSpec("desc"), 1198 VolumeAdjustmentsSpec("adjustments", default=[]), 1199 ] 1200 1201 def __eq__(self, other): 1202 return self.adjustments == other 1203 1204 __hash__ = Frame.__hash__ 1205 1206 @property 1207 def HashKey(self): 1208 return '%s:%s' % (self.FrameID, self.desc) 1209 1210 1211class RVAD(Frame): 1212 """Relative volume adjustment""" 1213 1214 _framespec = [ 1215 RVASpec("adjustments", stereo_only=False), 1216 ] 1217 1218 __hash__ = Frame.__hash__ 1219 1220 def __eq__(self, other): 1221 if not isinstance(other, RVAD): 1222 return False 1223 return self.adjustments == other.adjustments 1224 1225 1226# class EQUA: unsupported 1227 1228 1229class RVRB(Frame): 1230 """Reverb.""" 1231 1232 _framespec = [ 1233 SizedIntegerSpec('left', size=2, default=0), 1234 SizedIntegerSpec('right', size=2, default=0), 1235 ByteSpec('bounce_left', default=0), 1236 ByteSpec('bounce_right', default=0), 1237 ByteSpec('feedback_ltl', default=0), 1238 ByteSpec('feedback_ltr', default=0), 1239 ByteSpec('feedback_rtr', default=0), 1240 ByteSpec('feedback_rtl', default=0), 1241 ByteSpec('premix_ltr', default=0), 1242 ByteSpec('premix_rtl', default=0), 1243 ] 1244 1245 def __eq__(self, other): 1246 return (self.left, self.right) == other 1247 1248 __hash__ = Frame.__hash__ 1249 1250 1251class APIC(Frame): 1252 """Attached (or linked) Picture. 1253 1254 Attributes: 1255 1256 * encoding -- text encoding for the description 1257 * mime -- a MIME type (e.g. image/jpeg) or '-->' if the data is a URI 1258 * type -- the source of the image (3 is the album front cover) 1259 * desc -- a text description of the image 1260 * data -- raw image data, as a byte string 1261 1262 Mutagen will automatically compress large images when saving tags. 1263 """ 1264 1265 _framespec = [ 1266 EncodingSpec('encoding'), 1267 Latin1TextSpec('mime'), 1268 PictureTypeSpec('type'), 1269 EncodedTextSpec('desc'), 1270 BinaryDataSpec('data'), 1271 ] 1272 1273 def __eq__(self, other): 1274 return self.data == other 1275 1276 __hash__ = Frame.__hash__ 1277 1278 @property 1279 def HashKey(self): 1280 return '%s:%s' % (self.FrameID, self.desc) 1281 1282 def _merge_frame(self, other): 1283 other.desc += u" " 1284 return other 1285 1286 def _pprint(self): 1287 type_desc = text_type(self.type) 1288 if hasattr(self.type, "_pprint"): 1289 type_desc = self.type._pprint() 1290 1291 return "%s, %s (%s, %d bytes)" % ( 1292 type_desc, self.desc, self.mime, len(self.data)) 1293 1294 1295class PCNT(Frame): 1296 """Play counter. 1297 1298 The 'count' attribute contains the (recorded) number of times this 1299 file has been played. 1300 1301 This frame is basically obsoleted by POPM. 1302 """ 1303 1304 _framespec = [ 1305 IntegerSpec('count', default=0), 1306 ] 1307 1308 def __eq__(self, other): 1309 return self.count == other 1310 1311 __hash__ = Frame.__hash__ 1312 1313 def __pos__(self): 1314 return self.count 1315 1316 def _pprint(self): 1317 return text_type(self.count) 1318 1319 1320class PCST(Frame): 1321 """iTunes Podcast Flag""" 1322 1323 _framespec = [ 1324 IntegerSpec('value', default=0), 1325 ] 1326 1327 def __eq__(self, other): 1328 return self.value == other 1329 1330 __hash__ = Frame.__hash__ 1331 1332 def __pos__(self): 1333 return self.value 1334 1335 def _pprint(self): 1336 return text_type(self.value) 1337 1338 1339class POPM(Frame): 1340 """Popularimeter. 1341 1342 This frame keys a rating (out of 255) and a play count to an email 1343 address. 1344 1345 Attributes: 1346 1347 * email -- email this POPM frame is for 1348 * rating -- rating from 0 to 255 1349 * count -- number of times the files has been played (optional) 1350 """ 1351 1352 _framespec = [ 1353 Latin1TextSpec('email'), 1354 ByteSpec('rating', default=0), 1355 ] 1356 1357 _optionalspec = [ 1358 IntegerSpec('count', default=0), 1359 ] 1360 1361 @property 1362 def HashKey(self): 1363 return '%s:%s' % (self.FrameID, self.email) 1364 1365 def __eq__(self, other): 1366 return self.rating == other 1367 1368 __hash__ = Frame.__hash__ 1369 1370 def __pos__(self): 1371 return self.rating 1372 1373 def _pprint(self): 1374 return "%s=%r %r/255" % ( 1375 self.email, getattr(self, 'count', None), self.rating) 1376 1377 1378class GEOB(Frame): 1379 """General Encapsulated Object. 1380 1381 A blob of binary data, that is not a picture (those go in APIC). 1382 1383 Attributes: 1384 1385 * encoding -- encoding of the description 1386 * mime -- MIME type of the data or '-->' if the data is a URI 1387 * filename -- suggested filename if extracted 1388 * desc -- text description of the data 1389 * data -- raw data, as a byte string 1390 """ 1391 1392 _framespec = [ 1393 EncodingSpec('encoding'), 1394 Latin1TextSpec('mime'), 1395 EncodedTextSpec('filename'), 1396 EncodedTextSpec('desc'), 1397 BinaryDataSpec('data'), 1398 ] 1399 1400 @property 1401 def HashKey(self): 1402 return '%s:%s' % (self.FrameID, self.desc) 1403 1404 def __eq__(self, other): 1405 return self.data == other 1406 1407 __hash__ = Frame.__hash__ 1408 1409 1410class RBUF(Frame): 1411 """Recommended buffer size. 1412 1413 Attributes: 1414 1415 * size -- recommended buffer size in bytes 1416 * info -- if ID3 tags may be elsewhere in the file (optional) 1417 * offset -- the location of the next ID3 tag, if any 1418 1419 Mutagen will not find the next tag itself. 1420 """ 1421 1422 _framespec = [ 1423 SizedIntegerSpec('size', size=3, default=0), 1424 ] 1425 1426 _optionalspec = [ 1427 ByteSpec('info', default=0), 1428 SizedIntegerSpec('offset', size=4, default=0), 1429 ] 1430 1431 def __eq__(self, other): 1432 return self.size == other 1433 1434 __hash__ = Frame.__hash__ 1435 1436 def __pos__(self): 1437 return self.size 1438 1439 1440@swap_to_string 1441class AENC(Frame): 1442 """Audio encryption. 1443 1444 Attributes: 1445 1446 * owner -- key identifying this encryption type 1447 * preview_start -- unencrypted data block offset 1448 * preview_length -- number of unencrypted blocks 1449 * data -- data required for decryption (optional) 1450 1451 Mutagen cannot decrypt files. 1452 """ 1453 1454 _framespec = [ 1455 Latin1TextSpec('owner'), 1456 SizedIntegerSpec('preview_start', size=2, default=0), 1457 SizedIntegerSpec('preview_length', size=2, default=0), 1458 BinaryDataSpec('data'), 1459 ] 1460 1461 @property 1462 def HashKey(self): 1463 return '%s:%s' % (self.FrameID, self.owner) 1464 1465 def __bytes__(self): 1466 return self.owner.encode('utf-8') 1467 1468 def __str__(self): 1469 return self.owner 1470 1471 def __eq__(self, other): 1472 return self.owner == other 1473 1474 __hash__ = Frame.__hash__ 1475 1476 1477class LINK(Frame): 1478 """Linked information. 1479 1480 Attributes: 1481 1482 * frameid -- the ID of the linked frame 1483 * url -- the location of the linked frame 1484 * data -- further ID information for the frame 1485 """ 1486 1487 _framespec = [ 1488 FrameIDSpec('frameid', length=4), 1489 Latin1TextSpec('url'), 1490 BinaryDataSpec('data'), 1491 ] 1492 1493 @property 1494 def HashKey(self): 1495 return "%s:%s:%s:%s" % ( 1496 self.FrameID, self.frameid, self.url, _bytes2key(self.data)) 1497 1498 def __eq__(self, other): 1499 return (self.frameid, self.url, self.data) == other 1500 1501 __hash__ = Frame.__hash__ 1502 1503 1504class POSS(Frame): 1505 """Position synchronisation frame 1506 1507 Attribute: 1508 1509 * format -- format of the position attribute (frames or milliseconds) 1510 * position -- current position of the file 1511 """ 1512 1513 _framespec = [ 1514 ByteSpec('format', default=1), 1515 IntegerSpec('position', default=0), 1516 ] 1517 1518 def __pos__(self): 1519 return self.position 1520 1521 def __eq__(self, other): 1522 return self.position == other 1523 1524 __hash__ = Frame.__hash__ 1525 1526 1527class UFID(Frame): 1528 """Unique file identifier. 1529 1530 Attributes: 1531 1532 * owner -- format/type of identifier 1533 * data -- identifier 1534 """ 1535 1536 _framespec = [ 1537 Latin1TextSpec('owner'), 1538 BinaryDataSpec('data'), 1539 ] 1540 1541 @property 1542 def HashKey(self): 1543 return '%s:%s' % (self.FrameID, self.owner) 1544 1545 def __eq__(s, o): 1546 if isinstance(o, UFI): 1547 return s.owner == o.owner and s.data == o.data 1548 else: 1549 return s.data == o 1550 1551 __hash__ = Frame.__hash__ 1552 1553 def _pprint(self): 1554 return "%s=%r" % (self.owner, self.data) 1555 1556 1557@swap_to_string 1558class USER(Frame): 1559 """Terms of use. 1560 1561 Attributes: 1562 1563 * encoding -- text encoding 1564 * lang -- ISO three letter language code 1565 * text -- licensing terms for the audio 1566 """ 1567 1568 _framespec = [ 1569 EncodingSpec('encoding'), 1570 StringSpec('lang', length=3, default=u"XXX"), 1571 EncodedTextSpec('text'), 1572 ] 1573 1574 @property 1575 def HashKey(self): 1576 return '%s:%s' % (self.FrameID, self.lang) 1577 1578 def __bytes__(self): 1579 return self.text.encode('utf-8') 1580 1581 def __str__(self): 1582 return self.text 1583 1584 def __eq__(self, other): 1585 return self.text == other 1586 1587 __hash__ = Frame.__hash__ 1588 1589 def _pprint(self): 1590 return "%r=%s" % (self.lang, self.text) 1591 1592 1593@swap_to_string 1594class OWNE(Frame): 1595 """Ownership frame.""" 1596 1597 _framespec = [ 1598 EncodingSpec('encoding'), 1599 Latin1TextSpec('price'), 1600 StringSpec('date', length=8, default=u"19700101"), 1601 EncodedTextSpec('seller'), 1602 ] 1603 1604 def __bytes__(self): 1605 return self.seller.encode('utf-8') 1606 1607 def __str__(self): 1608 return self.seller 1609 1610 def __eq__(self, other): 1611 return self.seller == other 1612 1613 __hash__ = Frame.__hash__ 1614 1615 1616class COMR(Frame): 1617 """Commercial frame.""" 1618 1619 _framespec = [ 1620 EncodingSpec('encoding'), 1621 Latin1TextSpec('price'), 1622 StringSpec('valid_until', length=8, default=u"19700101"), 1623 Latin1TextSpec('contact'), 1624 ByteSpec('format', default=0), 1625 EncodedTextSpec('seller'), 1626 EncodedTextSpec('desc'), 1627 ] 1628 1629 _optionalspec = [ 1630 Latin1TextSpec('mime'), 1631 BinaryDataSpec('logo'), 1632 ] 1633 1634 @property 1635 def HashKey(self): 1636 return '%s:%s' % (self.FrameID, _bytes2key(self._writeData())) 1637 1638 def __eq__(self, other): 1639 return self._writeData() == other._writeData() 1640 1641 __hash__ = Frame.__hash__ 1642 1643 1644@swap_to_string 1645class ENCR(Frame): 1646 """Encryption method registration. 1647 1648 The standard does not allow multiple ENCR frames with the same owner 1649 or the same method. Mutagen only verifies that the owner is unique. 1650 """ 1651 1652 _framespec = [ 1653 Latin1TextSpec('owner'), 1654 ByteSpec('method', default=0x80), 1655 BinaryDataSpec('data'), 1656 ] 1657 1658 @property 1659 def HashKey(self): 1660 return "%s:%s" % (self.FrameID, self.owner) 1661 1662 def __bytes__(self): 1663 return self.data 1664 1665 def __eq__(self, other): 1666 return self.data == other 1667 1668 __hash__ = Frame.__hash__ 1669 1670 1671@swap_to_string 1672class GRID(Frame): 1673 """Group identification registration.""" 1674 1675 _framespec = [ 1676 Latin1TextSpec('owner'), 1677 ByteSpec('group', default=0x80), 1678 BinaryDataSpec('data'), 1679 ] 1680 1681 @property 1682 def HashKey(self): 1683 return '%s:%s' % (self.FrameID, self.group) 1684 1685 def __pos__(self): 1686 return self.group 1687 1688 def __bytes__(self): 1689 return self.owner.encode('utf-8') 1690 1691 def __str__(self): 1692 return self.owner 1693 1694 def __eq__(self, other): 1695 return self.owner == other or self.group == other 1696 1697 __hash__ = Frame.__hash__ 1698 1699 1700@swap_to_string 1701class PRIV(Frame): 1702 """Private frame.""" 1703 1704 _framespec = [ 1705 Latin1TextSpec('owner'), 1706 BinaryDataSpec('data'), 1707 ] 1708 1709 @property 1710 def HashKey(self): 1711 return '%s:%s:%s' % ( 1712 self.FrameID, self.owner, _bytes2key(self.data)) 1713 1714 def __bytes__(self): 1715 return self.data 1716 1717 def __eq__(self, other): 1718 return self.data == other 1719 1720 def _pprint(self): 1721 return "%s=%r" % (self.owner, self.data) 1722 1723 __hash__ = Frame.__hash__ 1724 1725 1726@swap_to_string 1727class SIGN(Frame): 1728 """Signature frame.""" 1729 1730 _framespec = [ 1731 ByteSpec('group', default=0x80), 1732 BinaryDataSpec('sig'), 1733 ] 1734 1735 @property 1736 def HashKey(self): 1737 return '%s:%s:%s' % (self.FrameID, self.group, _bytes2key(self.sig)) 1738 1739 def __bytes__(self): 1740 return self.sig 1741 1742 def __eq__(self, other): 1743 return self.sig == other 1744 1745 __hash__ = Frame.__hash__ 1746 1747 1748class SEEK(Frame): 1749 """Seek frame. 1750 1751 Mutagen does not find tags at seek offsets. 1752 """ 1753 1754 _framespec = [ 1755 IntegerSpec('offset', default=0), 1756 ] 1757 1758 def __pos__(self): 1759 return self.offset 1760 1761 def __eq__(self, other): 1762 return self.offset == other 1763 1764 __hash__ = Frame.__hash__ 1765 1766 1767class ASPI(Frame): 1768 """Audio seek point index. 1769 1770 Attributes: S, L, N, b, and Fi. For the meaning of these, see 1771 the ID3v2.4 specification. Fi is a list of integers. 1772 """ 1773 1774 _framespec = [ 1775 SizedIntegerSpec("S", size=4, default=0), 1776 SizedIntegerSpec("L", size=4, default=0), 1777 SizedIntegerSpec("N", size=2, default=0), 1778 ByteSpec("b", default=0), 1779 ASPIIndexSpec("Fi", default=[]), 1780 ] 1781 1782 def __eq__(self, other): 1783 return self.Fi == other 1784 1785 __hash__ = Frame.__hash__ 1786 1787 1788# ID3v2.2 frames 1789class UFI(UFID): 1790 "Unique File Identifier" 1791 1792 1793class TT1(TIT1): 1794 "Content group description" 1795 1796 1797class TT2(TIT2): 1798 "Title" 1799 1800 1801class TT3(TIT3): 1802 "Subtitle/Description refinement" 1803 1804 1805class TP1(TPE1): 1806 "Lead Artist/Performer/Soloist/Group" 1807 1808 1809class TP2(TPE2): 1810 "Band/Orchestra/Accompaniment" 1811 1812 1813class TP3(TPE3): 1814 "Conductor" 1815 1816 1817class TP4(TPE4): 1818 "Interpreter/Remixer/Modifier" 1819 1820 1821class TCM(TCOM): 1822 "Composer" 1823 1824 1825class TXT(TEXT): 1826 "Lyricist" 1827 1828 1829class TLA(TLAN): 1830 "Audio Language(s)" 1831 1832 1833class TCO(TCON): 1834 "Content Type (Genre)" 1835 1836 1837class TAL(TALB): 1838 "Album" 1839 1840 1841class TPA(TPOS): 1842 "Part of set" 1843 1844 1845class TRK(TRCK): 1846 "Track Number" 1847 1848 1849class TRC(TSRC): 1850 "International Standard Recording Code (ISRC)" 1851 1852 1853class TYE(TYER): 1854 "Year of recording" 1855 1856 1857class TDA(TDAT): 1858 "Date of recording (DDMM)" 1859 1860 1861class TIM(TIME): 1862 "Time of recording (HHMM)" 1863 1864 1865class TRD(TRDA): 1866 "Recording Dates" 1867 1868 1869class TMT(TMED): 1870 "Source Media Type" 1871 1872 1873class TFT(TFLT): 1874 "File Type" 1875 1876 1877class TBP(TBPM): 1878 "Beats per minute" 1879 1880 1881class TCP(TCMP): 1882 "iTunes Compilation Flag" 1883 1884 1885class TCR(TCOP): 1886 "Copyright (C)" 1887 1888 1889class TPB(TPUB): 1890 "Publisher" 1891 1892 1893class TEN(TENC): 1894 "Encoder" 1895 1896 1897class TST(TSOT): 1898 "Title Sort Order key" 1899 1900 1901class TSA(TSOA): 1902 "Album Sort Order key" 1903 1904 1905class TS2(TSO2): 1906 "iTunes Album Artist Sort" 1907 1908 1909class TSP(TSOP): 1910 "Perfomer Sort Order key" 1911 1912 1913class TSC(TSOC): 1914 "iTunes Composer Sort" 1915 1916 1917class TSS(TSSE): 1918 "Encoder settings" 1919 1920 1921class TOF(TOFN): 1922 "Original Filename" 1923 1924 1925class TLE(TLEN): 1926 "Audio Length (ms)" 1927 1928 1929class TSI(TSIZ): 1930 "Audio Data size (bytes)" 1931 1932 1933class TDY(TDLY): 1934 "Audio Delay (ms)" 1935 1936 1937class TKE(TKEY): 1938 "Starting Key" 1939 1940 1941class TOT(TOAL): 1942 "Original Album" 1943 1944 1945class TOA(TOPE): 1946 "Original Artist/Perfomer" 1947 1948 1949class TOL(TOLY): 1950 "Original Lyricist" 1951 1952 1953class TOR(TORY): 1954 "Original Release Year" 1955 1956 1957class TXX(TXXX): 1958 "User-defined Text" 1959 1960 1961class WAF(WOAF): 1962 "Official File Information" 1963 1964 1965class WAR(WOAR): 1966 "Official Artist/Performer Information" 1967 1968 1969class WAS(WOAS): 1970 "Official Source Information" 1971 1972 1973class WCM(WCOM): 1974 "Commercial Information" 1975 1976 1977class WCP(WCOP): 1978 "Copyright Information" 1979 1980 1981class WPB(WPUB): 1982 "Official Publisher Information" 1983 1984 1985class WXX(WXXX): 1986 "User-defined URL" 1987 1988 1989class IPL(IPLS): 1990 "Involved people list" 1991 1992 1993class MCI(MCDI): 1994 "Binary dump of CD's TOC" 1995 1996 1997class ETC(ETCO): 1998 "Event timing codes" 1999 2000 2001class MLL(MLLT): 2002 "MPEG location lookup table" 2003 2004 2005class STC(SYTC): 2006 "Synced tempo codes" 2007 2008 2009class ULT(USLT): 2010 "Unsychronised lyrics/text transcription" 2011 2012 2013class SLT(SYLT): 2014 "Synchronised lyrics/text" 2015 2016 2017class COM(COMM): 2018 "Comment" 2019 2020 2021class RVA(RVAD): 2022 "Relative volume adjustment" 2023 2024 _framespec = [ 2025 RVASpec("adjustments", stereo_only=True), 2026 ] 2027 2028 def _to_other(self, other): 2029 if not isinstance(other, RVAD): 2030 raise TypeError 2031 2032 other.adjustments = list(self.adjustments) 2033 2034# class EQU(EQUA) 2035 2036 2037class REV(RVRB): 2038 "Reverb" 2039 2040 2041class PIC(APIC): 2042 """Attached Picture. 2043 2044 The 'mime' attribute of an ID3v2.2 attached picture must be either 2045 'PNG' or 'JPG'. 2046 """ 2047 2048 _framespec = [ 2049 EncodingSpec('encoding'), 2050 StringSpec('mime', length=3, default="JPG"), 2051 PictureTypeSpec('type'), 2052 EncodedTextSpec('desc'), 2053 BinaryDataSpec('data'), 2054 ] 2055 2056 def _to_other(self, other): 2057 if not isinstance(other, APIC): 2058 raise TypeError 2059 2060 other.encoding = self.encoding 2061 other.mime = self.mime 2062 other.type = self.type 2063 other.desc = self.desc 2064 other.data = self.data 2065 2066 2067class GEO(GEOB): 2068 "General Encapsulated Object" 2069 2070 2071class CNT(PCNT): 2072 "Play counter" 2073 2074 2075class POP(POPM): 2076 "Popularimeter" 2077 2078 2079class BUF(RBUF): 2080 "Recommended buffer size" 2081 2082 2083class CRM(Frame): 2084 """Encrypted meta frame""" 2085 2086 _framespec = [ 2087 Latin1TextSpec('owner'), 2088 Latin1TextSpec('desc'), 2089 BinaryDataSpec('data'), 2090 ] 2091 2092 def __eq__(self, other): 2093 return self.data == other 2094 __hash__ = Frame.__hash__ 2095 2096 2097class CRA(AENC): 2098 "Audio encryption" 2099 2100 2101class LNK(LINK): 2102 """Linked information""" 2103 2104 _framespec = [ 2105 FrameIDSpec('frameid', length=3), 2106 Latin1TextSpec('url'), 2107 BinaryDataSpec('data'), 2108 ] 2109 2110 def _to_other(self, other): 2111 if not isinstance(other, LINK): 2112 raise TypeError 2113 2114 if isinstance(other, LNK): 2115 new_frameid = self.frameid 2116 else: 2117 try: 2118 new_frameid = Frames_2_2[self.frameid].__bases__[0].__name__ 2119 except KeyError: 2120 new_frameid = self.frameid.ljust(4) 2121 2122 # we could end up with invalid IDs here, so bypass the validation 2123 other._setattr("frameid", new_frameid) 2124 2125 other.url = self.url 2126 other.data = self.data 2127 2128 2129Frames = {} 2130"""All supported ID3v2.3/4 frames, keyed by frame name.""" 2131 2132 2133Frames_2_2 = {} 2134"""All supported ID3v2.2 frames, keyed by frame name.""" 2135 2136 2137k, v = None, None 2138for k, v in iteritems(globals()): 2139 if isinstance(v, type) and issubclass(v, Frame): 2140 v.__module__ = "mutagen.id3" 2141 2142 if len(k) == 3: 2143 Frames_2_2[k] = v 2144 elif len(k) == 4: 2145 Frames[k] = v 2146 2147try: 2148 del k 2149 del v 2150except NameError: 2151 pass 2152