~ubuntu-branches/ubuntu/oneiric/eyed3/oneiric

« back to all changes in this revision

Viewing changes to src/eyeD3/frames.py

  • Committer: Bazaar Package Importer
  • Author(s): Alexander Wirt
  • Date: 2006-11-18 01:47:38 UTC
  • mfrom: (1.2.1 upstream) (2.1.5 feisty)
  • Revision ID: james.westby@ubuntu.com-20061118014738-bcwcoe9gywrgorz2
New upstream version  

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
################################################################################
2
2
#
3
 
#  Copyright (C) 2002-2005  Travis Shirk <travis@pobox.com>
 
3
#  Copyright (C) 2002-2006  Travis Shirk <travis@pobox.com>
4
4
#  Copyright (C) 2001  Ryan Finne <ryan@finnie.org>
5
5
#
6
6
#  This program is free software; you can redistribute it and/or modify
44
44
CONTENT_TITLE_FID  = "TIT1";
45
45
TITLE_FIDS         = [TITLE_FID, SUBTITLE_FID, CONTENT_TITLE_FID];
46
46
COMMENT_FID        = "COMM";
 
47
LYRICS_FID         = "USLT";
47
48
GENRE_FID          = "TCON";
48
49
TRACKNUM_FID       = "TRCK";
 
50
DISCNUM_FID        = "TPOS";
49
51
USERTEXT_FID       = "TXXX";
50
52
CDID_FID           = "MCDI";
51
53
IMAGE_FID          = "APIC";
 
54
OBJECT_FID         = "GEOB";
52
55
URL_COMMERCIAL_FID = "WCOM";
53
56
URL_COPYRIGHT_FID  = "WCOP";
54
57
URL_AUDIOFILE_FID  = "WOAF";
64
67
USERURL_FID        = "WXXX";
65
68
PLAYCOUNT_FID      = "PCNT";
66
69
UNIQUE_FILE_ID_FID = "UFID";
 
70
BPM_FID            = "TBPM";
 
71
PUBLISHER_FID      = "TPUB";
67
72
 
68
73
obsoleteFrames = {"EQUA": "Equalisation",
69
74
                  "IPLS": "Involved people list",
80
85
OBSOLETE_ORIG_RELEASE_FID    = "TORY";
81
86
OBSOLETE_RECORDING_DATE_FID  = "TRDA";
82
87
 
 
88
DATE_FIDS          = ["TDRL", "TDOR", "TDRC", OBSOLETE_YEAR_FID,
 
89
                      OBSOLETE_DATE_FID];
 
90
 
83
91
frameDesc = { "AENC": "Audio encryption",
84
92
              "APIC": "Attached picture",
85
93
              "ASPI": "Audio seek point index",
176
184
              "WPUB": "Publishers official webpage",
177
185
              "WXXX": "User defined URL link frame" };
178
186
 
 
187
 
 
188
# mapping of 2.2 frames to 2.3/2.4
 
189
TAGS2_2_TO_TAGS_2_3_AND_4 = {
 
190
    "TT1" : "TIT1", # CONTENTGROUP content group description
 
191
    "TT2" : "TIT2", # TITLE title/songname/content description
 
192
    "TT3" : "TIT3", # SUBTITLE subtitle/description refinement
 
193
    "TP1" : "TPE1", # ARTIST lead performer(s)/soloist(s)
 
194
    "TP2" : "TPE2", # BAND band/orchestra/accompaniment
 
195
    "TP3" : "TPE3", # CONDUCTOR conductor/performer refinement
 
196
    "TP4" : "TPE4", # MIXARTIST interpreted, remixed, modified by
 
197
    "TCM" : "TCOM", # COMPOSER composer
 
198
    "TXT" : "TEXT", # LYRICIST lyricist/text writer
 
199
    "TLA" : "TLAN", # LANGUAGE language(s)
 
200
    "TCO" : "TCON", # CONTENTTYPE content type
 
201
    "TAL" : "TALB", # ALBUM album/movie/show title
 
202
    "TRK" : "TRCK", # TRACKNUM track number/position in set
 
203
    "TPA" : "TPOS", # PARTINSET part of set
 
204
    "TRC" : "TSRC", # ISRC international standard recording code
 
205
    "TDA" : "TDAT", # DATE date
 
206
    "TYE" : "TYER", # YEAR year
 
207
    "TIM" : "TIME", # TIME time
 
208
    "TRD" : "TRDA", # RECORDINGDATES recording dates
 
209
    "TOR" : "TORY", # ORIGYEAR original release year
 
210
    "TBP" : "TBPM", # BPM beats per minute
 
211
    "TMT" : "TMED", # MEDIATYPE media type
 
212
    "TFT" : "TFLT", # FILETYPE file type
 
213
    "TCR" : "TCOP", # COPYRIGHT copyright message
 
214
    "TPB" : "TPUB", # PUBLISHER publisher
 
215
    "TEN" : "TENC", # ENCODEDBY encoded by
 
216
    "TSS" : "TSSE", # ENCODERSETTINGS software/hardware + settings for encoding
 
217
    "TLE" : "TLEN", # SONGLEN length (ms)
 
218
    "TSI" : "TSIZ", # SIZE size (bytes)
 
219
    "TDY" : "TDLY", # PLAYLISTDELAY playlist delay
 
220
    "TKE" : "TKEY", # INITIALKEY initial key
 
221
    "TOT" : "TOAL", # ORIGALBUM original album/movie/show title
 
222
    "TOF" : "TOFN", # ORIGFILENAME original filename
 
223
    "TOA" : "TOPE", # ORIGARTIST original artist(s)/performer(s)
 
224
    "TOL" : "TOLY", # ORIGLYRICIST original lyricist(s)/text writer(s)
 
225
    "TXX" : "TXXX", # USERTEXT user defined text information frame
 
226
    "WAF" : "WOAF", # WWWAUDIOFILE official audio file webpage
 
227
    "WAR" : "WOAR", # WWWARTIST official artist/performer webpage
 
228
    "WAS" : "WOAS", # WWWAUDIOSOURCE official audion source webpage
 
229
    "WCM" : "WCOM", # WWWCOMMERCIALINFO commercial information
 
230
    "WCP" : "WCOP", # WWWCOPYRIGHT copyright/legal information
 
231
    "WPB" : "WPUB", # WWWPUBLISHER publishers official webpage
 
232
    "WXX" : "WXXX", # WWWUSER user defined URL link frame
 
233
    "IPL" : "IPLS", # INVOLVEDPEOPLE involved people list
 
234
    "ULT" : "USLT", # UNSYNCEDLYRICS unsynchronised lyrics/text transcription
 
235
    "COM" : "COMM", # COMMENT comments
 
236
    "UFI" : "UFID", # UNIQUEFILEID unique file identifier
 
237
    "MCI" : "MCDI", # CDID music CD identifier
 
238
    "ETC" : "ETCO", # EVENTTIMING event timing codes
 
239
    "MLL" : "MLLT", # MPEGLOOKUP MPEG location lookup table
 
240
    "STC" : "SYTC", # SYNCEDTEMPO synchronised tempo codes
 
241
    "SLT" : "SYLT", # SYNCEDLYRICS synchronised lyrics/text
 
242
    "RVA" : "RVAD", # VOLUMEADJ relative volume adjustment
 
243
    "EQU" : "EQUA", # EQUALIZATION equalization
 
244
    "REV" : "RVRB", # REVERB reverb
 
245
    "PIC" : "APIC", # PICTURE attached picture
 
246
    "GEO" : "GEOB", # GENERALOBJECT general encapsulated object
 
247
    "CNT" : "PCNT", # PLAYCOUNTER play counter
 
248
    "POP" : "POPM", # POPULARIMETER popularimeter
 
249
    "BUF" : "RBUF", # BUFFERSIZE recommended buffer size
 
250
    "CRA" : "AENC", # AUDIOCRYPTO audio encryption
 
251
    "LNK" : "LINK", # LINKEDINFO linked information
 
252
    # Extension workarounds i.e., ignore them
 
253
    "TCP" : "TCP ", # iTunes "extension" for compilation marking
 
254
    "CM1" : "CM1 "  # Seems to be some script kiddie tagging the tag.
 
255
                    # For example, [rH] join #rH on efnet [rH]
 
256
}
 
257
 
 
258
 
179
259
NULL_FRAME_FLAGS = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
180
260
 
181
261
TEXT_FRAME_RX = re.compile("^T[A-Z0-9][A-Z0-9][A-Z0-9]$");
183
263
URL_FRAME_RX = re.compile("^W[A-Z0-9][A-Z0-9][A-Z0-9]$");
184
264
USERURL_FRAME_RX = re.compile("^" + USERURL_FID + "$");
185
265
COMMENT_FRAME_RX = re.compile("^" + COMMENT_FID + "$");
 
266
LYRICS_FRAME_RX = re.compile("^" + LYRICS_FID + "$");
186
267
CDID_FRAME_RX = re.compile("^" + CDID_FID + "$");
187
268
IMAGE_FRAME_RX = re.compile("^" + IMAGE_FID + "$");
 
269
OBJECT_FRAME_RX = re.compile("^" + OBJECT_FID + "$");
188
270
PLAYCOUNT_FRAME_RX = re.compile("^" + PLAYCOUNT_FID + "$");
189
271
UNIQUE_FILE_ID_FRAME_RX = re.compile("^" + UNIQUE_FILE_ID_FID + "$");
190
272
 
199
281
    "\x00MP",
200
282
    " MP",
201
283
    "MP3",
202
 
    "COM "
 
284
    "COM ",
 
285
    "TCP ", # iTunes
 
286
    "CM1 "  # Script kiddie
203
287
]
204
288
 
205
289
LATIN1_ENCODING   = "\x00";
212
296
DEFAULT_ID3_MINOR_VERSION = 4;
213
297
DEFAULT_LANG = "eng";
214
298
 
 
299
def cleanNulls(s):
 
300
   return "/".join([x for x in s.split('\x00') if x])
 
301
 
215
302
def id3EncodingToString(encoding):
216
303
    if encoding == LATIN1_ENCODING:
217
304
        return "latin_1";
222
309
    elif encoding == UTF_16BE_ENCODING:
223
310
        return "utf_16_be";
224
311
    else:
225
 
        raise ValueError;
 
312
        if strictID3():
 
313
            raise ValueError;
 
314
        else:
 
315
            return "latin_1";
226
316
 
227
317
################################################################################
228
 
class FrameException:
229
 
   msg = "";
230
 
 
231
 
   def __init__(self, msg):
232
 
      self.msg = msg;
233
 
 
234
 
   def __str__(self):
235
 
      return self.msg;
 
318
class FrameException(Exception):
 
319
    '''Thrown by invalid frames'''
 
320
    pass;
236
321
 
237
322
################################################################################
238
323
class FrameHeader:
258
343
 
259
344
   # 2.4 not only added flag bits, but also reordered the previously defined
260
345
   # flags.  So these are mapped once we know the version.
261
 
   TAG_ALTER   = None;   
262
 
   FILE_ALTER  = None;   
263
 
   READ_ONLY   = None;   
264
 
   COMPRESSION = None;   
265
 
   ENCRYPTION  = None;   
266
 
   GROUPING    = None;   
267
 
   UNSYNC      = None;   
268
 
   DATA_LEN    = None;   
 
346
   TAG_ALTER   = None;
 
347
   FILE_ALTER  = None;
 
348
   READ_ONLY   = None;
 
349
   COMPRESSION = None;
 
350
   ENCRYPTION  = None;
 
351
   GROUPING    = None;
 
352
   UNSYNC      = None;
 
353
   DATA_LEN    = None;
269
354
 
270
355
   # Constructor.
271
356
   def __init__(self, tagHeader = None):
283
368
      else:
284
369
         self.majorVersion = tagHeader.majorVersion;
285
370
         self.minorVersion = tagHeader.minorVersion;
 
371
      # Correctly set size of header
 
372
      if self.minorVersion == 2:
 
373
         self.FRAME_HEADER_SIZE = 6;
 
374
      else:
 
375
         self.FRAME_HEADER_SIZE = 10;
286
376
      self.setBitMask();
287
377
 
288
378
   def setBitMask(self):
291
381
 
292
382
      # 1.x tags are converted to 2.4 frames internally.  These frames are
293
383
      # created with frame flags \x00.
294
 
      if (major == 2 and minor == 3):
 
384
      if (major == 2 and minor == 2):
 
385
          # no flags for 2.2 frames
 
386
          pass;
 
387
      elif (major == 2 and minor == 3):
295
388
         self.TAG_ALTER   = 0;   
296
389
         self.FILE_ALTER  = 1;   
297
390
         self.READ_ONLY   = 2;   
342
435
 
343
436
      return data;
344
437
 
 
438
   def parse2_2(self, f):
 
439
      frameId_22 = f.read(3);
 
440
      frameId = map2_2FrameId(frameId_22);
 
441
      if self.isFrameIdValid(frameId):
 
442
         TRACE_MSG("FrameHeader [id]: %s (0x%x%x%x)" % (frameId_22,
 
443
                                                        ord(frameId_22[0]),
 
444
                                                        ord(frameId_22[1]),
 
445
                                                        ord(frameId_22[2])));
 
446
         self.id = frameId;
 
447
         # dataSize corresponds to the size of the data segment after
 
448
         # encryption, compression, and unsynchronization.
 
449
         sz = f.read(3);
 
450
         self.dataSize = bin2dec(bytes2bin(sz, 8));
 
451
         TRACE_MSG("FrameHeader [data size]: %d (0x%X)" % (self.dataSize,
 
452
                                                           self.dataSize));
 
453
      elif frameId == '\x00\x00\x00':
 
454
         TRACE_MSG("FrameHeader: Null frame id found at byte " +\
 
455
                   str(f.tell()));
 
456
         return 0;
 
457
      elif not strictID3() and frameId in KNOWN_BAD_FRAMES:
 
458
         TRACE_MSG("FrameHeader: Illegal but known "\
 
459
                   "(possibly created by the shitty mp3ext) frame found; "\
 
460
                   "Happily ignoring!" + str(f.tell()));
 
461
         return 0;
 
462
      else:
 
463
         raise FrameException("FrameHeader: Illegal Frame ID: " + frameId);
 
464
      return 1;
 
465
      
 
466
 
345
467
   # Returns 1 on success and 0 when a null tag (marking the beginning of
346
468
   # padding).  In the case of an invalid frame header, a FrameException is 
347
469
   # thrown.
348
470
   def parse(self, f):
349
471
      TRACE_MSG("FrameHeader [start byte]: %d (0x%X)" % (f.tell(),
350
472
                                                         f.tell()));
 
473
      if self.minorVersion == 2:
 
474
          return self.parse2_2(f)
 
475
      
351
476
      frameId = f.read(4);
352
477
      if self.isFrameIdValid(frameId):
353
478
         TRACE_MSG("FrameHeader [id]: %s (0x%x%x%x%x)" % (frameId,
441
566
       assert(isinstance(frameHeader, FrameHeader));
442
567
       self.header = frameHeader;
443
568
 
444
 
   def __repr__(self):
 
569
   def __str__(self):
445
570
      desc = self.getFrameDesc();
446
571
      return '<%s Frame (%s)>' % (desc, self.header.id);
447
572
 
467
592
      return data;
468
593
 
469
594
   def decrypt(self, data):
470
 
      raise FrameException("Ecnyption Not Supported");
 
595
      raise FrameException("Encryption not supported");
471
596
 
472
597
   def encrypt(self, data):
473
 
      raise FrameException("Ecnyption Not Supported");
 
598
      raise FrameException("Encryption not supported");
474
599
 
475
600
   def disassembleFrame(self, data):
476
601
      # Format flags in the frame header may add extra data to the
554
679
         except KeyError:
555
680
            return "UNKOWN FRAME";
556
681
 
 
682
   def getTextDelim(self):
 
683
       if self.encoding == UTF_16_ENCODING or \
 
684
          self.encoding == UTF_16BE_ENCODING:
 
685
           return "\x00\x00";
 
686
       else:
 
687
           return "\x00";
 
688
 
557
689
################################################################################
558
690
class TextFrame(Frame):
559
691
   text = u"";
583
715
      TRACE_MSG("TextFrame encoding: %s" % id3EncodingToString(self.encoding));
584
716
      try:
585
717
          self.text = unicode(data[1:], id3EncodingToString(self.encoding));
 
718
          if not strictID3():
 
719
              self.text = cleanNulls(self.text)
586
720
      except TypeError, excArg:
587
721
          # if data is already unicode, just copy it
588
722
          if excArg.args == ("decoding Unicode is not supported",):
589
723
              self.text = data[1:];
 
724
              if not strictID3():
 
725
                 self.text = cleanNulls(self.text)
590
726
          else:
591
727
              raise;
592
728
      TRACE_MSG("TextFrame text: %s" % self.text);
593
729
 
594
 
   def __repr__(self):
595
 
      return '<%s (%s): %s>' % (self.getFrameDesc(), self.header.id, self.text);
 
730
   def __unicode__(self):
 
731
      return u'<%s (%s): %s>' % (self.getFrameDesc(), self.header.id,
 
732
                                 self.text);
596
733
   
597
734
   def render(self):
598
735
       if self.header.minorVersion == 4 and self.header.id == "TSIZ":
621
758
 
622
759
   def _set(self, data, frameHeader):
623
760
      TextFrame._set(self, data, frameHeader);
624
 
      if self.header.id[:2] != "TD" and self.header.minorVersion != 3:
 
761
      if self.header.id[:2] != "TD" and self.header.minorVersion >= 4:
625
762
         raise FrameException("Invalid frame id for DateFrame: " + \
626
763
                              self.header.id);
627
764
   
638
775
               self.date = d;
639
776
            else:
640
777
               assert(isinstance(d, unicode));
 
778
               # Witnessed oddball tags with NULL bytes (ozzy.tag from id3lib)
641
779
               d = d.strip("\x00");
642
780
 
643
 
               # Witnessed oddball tags with NULL bytes (ozzy.tag from id3lib)
644
 
               strippedDate = "";
645
 
               for c in d:
646
 
                  if ord(c) != 0:
647
 
                     strippedDate += c;
648
 
               d = strippedDate;
649
 
 
650
781
               try:
651
782
                  self.date = time.strptime(d, fmt);
652
783
               except TypeError, ex:
766
897
      self.description = unicode(d, id3EncodingToString(self.encoding));
767
898
      TRACE_MSG("UserTextFrame description: %s" % self.description);
768
899
      self.text = unicode(t, id3EncodingToString(self.encoding));
 
900
      if not strictID3():
 
901
          self.text = cleanNulls(self.text)
769
902
      TRACE_MSG("UserTextFrame text: %s" % self.text);
770
903
 
771
904
   def render(self):
772
905
      data = self.encoding +\
773
906
             self.description.encode(id3EncodingToString(self.encoding)) +\
774
 
             "\x00" +\
 
907
             self.getTextDelim() +\
775
908
             self.text.encode(id3EncodingToString(self.encoding));
776
909
      return self.assembleFrame(data);
777
910
 
778
 
   def __repr__(self):
779
 
       return '<%s (%s): {Desc: %s} %s>' % (self.getFrameDesc(),
780
 
                                           self.header.id,
781
 
                                           self.description, self.text);
 
911
   def __unicode__(self):
 
912
       return u'<%s (%s): {Desc: %s} %s>' % (self.getFrameDesc(),
 
913
                                            self.header.id,
 
914
                                            self.description, self.text);
782
915
 
783
916
################################################################################
784
917
class URLFrame(Frame):
802
935
         raise FrameException("Invalid frame id for URLFrame: " + fid);
803
936
      data = self.disassembleFrame(data);
804
937
      self.url = data;
 
938
      if not strictID3():
 
939
          self.url = cleanNulls(self.url)
805
940
 
806
941
   def render(self):
807
942
      data = str(self.url);
808
943
      return self.assembleFrame(data);
809
944
 
810
 
   def __repr__(self):
 
945
   def __str__(self):
811
946
      return '<%s (%s): %s>' % (self.getFrameDesc(), self.header.id,
812
947
                                self.url);
813
948
 
842
977
      self.encoding = data[0];
843
978
      TRACE_MSG("UserURLFrame encoding: %s" %\
844
979
                id3EncodingToString(self.encoding));
845
 
      (d, u) = splitUnicode(data[1:], self.encoding);
 
980
      try:
 
981
          (d, u) = splitUnicode(data[1:], self.encoding);
 
982
      except ValueError, ex:
 
983
          if strictID3():
 
984
              raise FrameException("Invalid WXXX frame, no null byte")
 
985
          d = data[1:]
 
986
          u = ""
846
987
      self.description = unicode(d, id3EncodingToString(self.encoding));
847
988
      TRACE_MSG("UserURLFrame description: %s" % self.description);
848
989
      self.url = u;
 
990
      if not strictID3():
 
991
          self.url = cleanNulls(self.url)
849
992
      TRACE_MSG("UserURLFrame text: %s" % self.url);
850
993
 
851
994
   def render(self):
852
995
      data = self.encoding +\
853
996
             self.description.encode(id3EncodingToString(self.encoding)) +\
854
 
             "\x00" + self.url;
 
997
             self.getTextDelim() + self.url;
855
998
      return self.assembleFrame(data);
856
999
 
857
 
   def __repr__(self):
858
 
      return '<%s (%s): %s [Encoding: %s] [Desc: %s]>' %\
 
1000
   def __unicode__(self):
 
1001
      return u'<%s (%s): %s [Encoding: %s] [Desc: %s]>' %\
859
1002
             (self.getFrameDesc(), self.header.id,
860
1003
              self.url, self.encoding, self.description)
861
1004
 
862
1005
################################################################################
863
1006
class CommentFrame(Frame):
864
 
   lang = u"";
 
1007
   lang = "";
865
1008
   description = u"";
866
1009
   comment = u"";
867
1010
 
868
1011
   # Data string format:
869
1012
   # encoding (one byte) + lang (three byte code) + description + "\x00" +
870
1013
   # text
871
 
   def __init__(self, frameHeader, data = None, lang = u"",
 
1014
   def __init__(self, frameHeader, data = None, lang = "",
872
1015
                description = u"", comment = u"", encoding = DEFAULT_ENCODING):
873
1016
       Frame.__init__(self, frameHeader);
874
1017
       if data != None:
919
1062
          else:
920
1063
              self.description = u"";
921
1064
              self.comment = u"";
 
1065
      if not strictID3():
 
1066
          self.description = cleanNulls(self.description)
 
1067
          self.comment = cleanNulls(self.comment)
922
1068
 
923
1069
   def render(self):
924
1070
      lang = self.lang.encode("ascii");
928
1074
          lang = lang + ('\x00' * (3 - len(lang)));
929
1075
      data = self.encoding + lang +\
930
1076
             self.description.encode(id3EncodingToString(self.encoding)) +\
931
 
             "\x00" +\
 
1077
             self.getTextDelim() +\
932
1078
             self.comment.encode(id3EncodingToString(self.encoding));
933
1079
      return self.assembleFrame(data);
934
1080
 
935
 
   def __repr__(self):
936
 
      return "<%s (%s): %s [Lang: %s] [Desc: %s]>" %\
 
1081
   def __unicode__(self):
 
1082
      return u"<%s (%s): %s [Lang: %s] [Desc: %s]>" %\
937
1083
             (self.getFrameDesc(), self.header.id, self.comment,
938
1084
              self.lang, self.description);
939
1085
 
940
1086
################################################################################
 
1087
class LyricsFrame(Frame):
 
1088
   lang = "";
 
1089
   description = u"";
 
1090
   lyrics = u"";
 
1091
 
 
1092
   # Data string format:
 
1093
   # encoding (one byte) + lang (three byte code) + description + "\x00" +
 
1094
   # text
 
1095
   def __init__(self, frameHeader, data = None, lang = "",
 
1096
                description = u"", lyrics = u"", encoding = DEFAULT_ENCODING):
 
1097
       Frame.__init__(self, frameHeader);
 
1098
       if data != None:
 
1099
           self._set(data, frameHeader);
 
1100
       else:
 
1101
           assert(isinstance(description, unicode));
 
1102
           assert(isinstance(lyrics, unicode));
 
1103
           assert(isinstance(lang, str));
 
1104
           self.encoding = encoding;
 
1105
           self.lang = lang;
 
1106
           self.description = description;
 
1107
           self.lyrics = lyrics;
 
1108
 
 
1109
   # Data string format:
 
1110
   # encoding (one byte) + lang (three byte code) + description + "\x00" +
 
1111
   # text
 
1112
   def _set(self, data, frameHeader = None):
 
1113
      assert(frameHeader);
 
1114
      if not LYRICS_FRAME_RX.match(frameHeader.id):
 
1115
         raise FrameException("Invalid frame id for LyricsFrame: " +\
 
1116
                              frameHeader.id);
 
1117
 
 
1118
      data = self.disassembleFrame(data);
 
1119
      self.encoding = data[0];
 
1120
      TRACE_MSG("LyricsFrame encoding: " + id3EncodingToString(self.encoding));
 
1121
      try:
 
1122
          self.lang = str(data[1:4]).strip("\x00");
 
1123
          # Test ascii encoding
 
1124
          temp_lang = unicode(self.lang, "ascii");
 
1125
          if self.lang and \
 
1126
             not re.compile("[A-Z][A-Z][A-Z]", re.IGNORECASE).match(self.lang):
 
1127
             if strictID3():
 
1128
                 raise FrameException("[LyricsFrame] Invalid language "\
 
1129
                                       "code: %s" % self.lang);
 
1130
      except UnicodeDecodeError, ex:
 
1131
          if strictID3():
 
1132
              raise FrameException("[LyricsFrame] Invalid language code: "\
 
1133
                                   "[%s] %s" % (ex.object, ex.reason));
 
1134
          else:
 
1135
              self.lang = "";
 
1136
      try:
 
1137
         (d, c) = splitUnicode(data[4:], self.encoding);
 
1138
         self.description = unicode(d, id3EncodingToString(self.encoding));
 
1139
         self.lyrics = unicode(c, id3EncodingToString(self.encoding));
 
1140
      except ValueError:
 
1141
          if strictID3():
 
1142
              raise FrameException("Invalid lyrics; no description/lyrics");
 
1143
          else:
 
1144
              self.description = u"";
 
1145
              self.lyrics = u"";
 
1146
      if not strictID3():
 
1147
          self.description = cleanNulls(self.description)
 
1148
          self.lyrics = cleanNulls(self.lyrics)
 
1149
 
 
1150
   def render(self):
 
1151
      lang = self.lang.encode("ascii");
 
1152
      if len(lang) > 3:
 
1153
          lang = lang[0:3];
 
1154
      elif len(lang) < 3:
 
1155
          lang = lang + ('\x00' * (3 - len(lang)));
 
1156
      data = self.encoding + lang +\
 
1157
             self.description.encode(id3EncodingToString(self.encoding)) +\
 
1158
             self.getTextDelim() +\
 
1159
             self.lyrics.encode(id3EncodingToString(self.encoding));
 
1160
      return self.assembleFrame(data);
 
1161
 
 
1162
   def __unicode__(self):
 
1163
      return u"<%s (%s): %s [Lang: %s] [Desc: %s]>" %\
 
1164
             (self.getFrameDesc(), self.header.id, self.lyrics,
 
1165
              self.lang, self.description);
 
1166
 
 
1167
################################################################################
941
1168
# This class refers to the APIC frame, otherwise known as an "attached
942
1169
# picture".
943
1170
class ImageFrame(Frame):
1042
1269
      self.encoding = input.read(1);
1043
1270
      TRACE_MSG("APIC encoding: " + id3EncodingToString(self.encoding));
1044
1271
 
 
1272
      # Mime type
1045
1273
      self.mimeType = "";
1046
 
      ch = input.read(1);
1047
 
      while ch != "\x00":
1048
 
         self.mimeType += ch;
1049
 
         ch = input.read(1);
 
1274
      if self.header.minorVersion != 2:
 
1275
          ch = input.read(1);
 
1276
          while ch != "\x00":
 
1277
              self.mimeType += ch;
 
1278
              ch = input.read(1);
 
1279
      else:
 
1280
          # v2.2 (OBSOLETE) special case
 
1281
          self.mimeType = input.read(3);
1050
1282
      TRACE_MSG("APIC mime type: " + self.mimeType);
1051
1283
      if strictID3() and not self.mimeType:
1052
1284
         raise FrameException("APIC frame does not contain a mime type");
1070
1302
      TRACE_MSG("APIC picture type: " + str(self.pictureType));
1071
1303
 
1072
1304
      self.desciption = u"";
1073
 
      buffer = "";
1074
 
      ch = input.read(1);
1075
 
      while ch != "\x00":
1076
 
         buffer += ch;
1077
 
         ch = input.read(1);
1078
 
      self.description = unicode(buffer, id3EncodingToString(self.encoding));
 
1305
 
 
1306
      # Remaining data is a NULL separated description and image data
 
1307
      buffer = input.read();
 
1308
      input.close();
 
1309
 
 
1310
      (desc, img) = splitUnicode(buffer, self.encoding);
 
1311
      TRACE_MSG("description len: %d" % len(desc));
 
1312
      TRACE_MSG("description len: %d" % len(img));
 
1313
      self.description = unicode(desc, id3EncodingToString(self.encoding));
1079
1314
      TRACE_MSG("APIC description: " + self.description);
1080
1315
 
1081
1316
      if self.mimeType.find("-->") != -1:
1082
1317
         self.imageData = None;
1083
 
         self.imageURL = input.read();
 
1318
         self.imageURL = img;
1084
1319
      else:
1085
 
         self.imageData = input.read();
 
1320
         self.imageData = img;
1086
1321
         self.imageURL = None;
1087
1322
      TRACE_MSG("APIC image data: " + str(len(self.imageData)) + " bytes");
1088
1323
      if strictID3() and not self.imageData and not self.imageURL:
1089
1324
         raise FrameException("APIC frame does not contain any image data");
1090
1325
 
1091
 
      input.close();
1092
1326
 
1093
1327
   def writeFile(self, path = "./", name = None):
1094
1328
      if not self.imageData:
1101
1335
      f.write(self.imageData);
1102
1336
      f.flush();
1103
1337
      f.close();
1104
 
   def getDefaultFileName(self):
 
1338
   def getDefaultFileName(self, suffix = ""):
1105
1339
      nameStr = self.picTypeToString(self.pictureType);
 
1340
      if suffix:
 
1341
          nameStr += suffix;
1106
1342
      nameStr = nameStr +  "." + self.mimeType.split("/")[1];
1107
1343
      return nameStr;
1108
1344
 
1110
1346
      data = self.encoding + self.mimeType + "\x00" +\
1111
1347
             bin2bytes(dec2bin(self.pictureType, 8)) +\
1112
1348
             self.description.encode(id3EncodingToString(self.encoding)) +\
1113
 
             "\x00";
 
1349
             self.getTextDelim();
1114
1350
      if self.imageURL:
1115
1351
          data += self.imageURL.encode("ascii");
1116
1352
      else:
1211
1447
         raise FrameException("Invalid APIC picture type: %d" % t);
1212
1448
   picTypeToString = staticmethod(picTypeToString);
1213
1449
 
 
1450
################################################################################
 
1451
# This class refers to the GEOB frame
 
1452
class ObjectFrame(Frame):
 
1453
   mimeType = None;
 
1454
   description = u"";
 
1455
   filename = u"";
 
1456
   objectData = None;
 
1457
 
 
1458
   def __init__(self, frameHeader, data = None,
 
1459
                desc = u"", filename = u"",
 
1460
                objectData = None, mimeType = None,
 
1461
                encoding = DEFAULT_ENCODING):
 
1462
       Frame.__init__(self, frameHeader);
 
1463
       if data != None:
 
1464
           self._set(data, frameHeader);
 
1465
       else:
 
1466
           assert(isinstance(desc, unicode));
 
1467
           self.description = desc;
 
1468
           assert(isinstance(filename, unicode));
 
1469
           self.filename = filename;
 
1470
           self.encoding = encoding;
 
1471
           assert(mimeType);
 
1472
           self.mimeType = mimeType;
 
1473
           assert(objectData);
 
1474
           self.objectData = objectData;
 
1475
 
 
1476
   # Factory method
 
1477
   def create(objFile, mime = u"", desc = u"", filename = None,
 
1478
              encoding = DEFAULT_ENCODING):
 
1479
       if filename == None:
 
1480
           filename = unicode(os.path.basename(objFile));
 
1481
       if not isinstance(desc, unicode) or \
 
1482
          (not isinstance(filename, unicode) and filename != ""):
 
1483
           raise FrameException("Wrong description and/or filename type.");
 
1484
       # Load file
 
1485
       fp = file(objFile, "rb");
 
1486
       objData = fp.read();
 
1487
       if mime:
 
1488
           print("Using specified mime type %s" % mime);
 
1489
       else:
 
1490
           mt = mimetypes.guess_type(objFile);
 
1491
           if not mt[0]:
 
1492
               raise FrameException("Unable to guess mime-type for %s" %
 
1493
                                    objFile);
 
1494
           mime = mt[0];
 
1495
           print("Guessing mime type %s" % mime);
 
1496
 
 
1497
       frameData = DEFAULT_ENCODING;
 
1498
       frameData += mime + "\x00";
 
1499
       frameData += filename.encode(id3EncodingToString(encoding)) + "\x00";
 
1500
       frameData += desc.encode(id3EncodingToString(encoding)) + "\x00";
 
1501
       frameData += objData;
 
1502
 
 
1503
       frameHeader = FrameHeader();
 
1504
       frameHeader.id = OBJECT_FID;
 
1505
       return ObjectFrame(frameHeader, data = frameData);
 
1506
   # Make create a static method.  Odd....
 
1507
   create = staticmethod(create);
 
1508
 
 
1509
   # Data string format:
 
1510
   # <Header for 'General encapsulated object', ID: "GEOB">
 
1511
   #  Text encoding          $xx
 
1512
   #  MIME type              <text string> $00
 
1513
   #  Filename               <text string according to encoding> $00 (00)
 
1514
   #  Content description    <text string according to encoding> $00 (00)
 
1515
   #  Encapsulated object    <binary data>
 
1516
   def _set(self, data, frameHeader = None):
 
1517
      assert(frameHeader);
 
1518
      if not OBJECT_FRAME_RX.match(frameHeader.id):
 
1519
         raise FrameException("Invalid frame id for ObjectFrame: " +\
 
1520
                              frameHeader.id);
 
1521
 
 
1522
      data = self.disassembleFrame(data);
 
1523
 
 
1524
      input = StringIO(data);
 
1525
      TRACE_MSG("GEOB frame data size: " + str(len(data)));
 
1526
      self.encoding = input.read(1);
 
1527
      TRACE_MSG("GEOB encoding: " + id3EncodingToString(self.encoding));
 
1528
 
 
1529
      # Mime type
 
1530
      self.mimeType = "";
 
1531
      if self.header.minorVersion != 2:
 
1532
          ch = input.read(1);
 
1533
          while ch != "\x00":
 
1534
              self.mimeType += ch;
 
1535
              ch = input.read(1);
 
1536
      else:
 
1537
          # v2.2 (OBSOLETE) special case
 
1538
          self.mimeType = input.read(3);
 
1539
      TRACE_MSG("GEOB mime type: " + self.mimeType);
 
1540
      if strictID3() and not self.mimeType:
 
1541
         raise FrameException("GEOB frame does not contain a mime type");
 
1542
      if strictID3() and self.mimeType.find("/") == -1:
 
1543
         raise FrameException("GEOB frame does not contain a valid mime type");
 
1544
 
 
1545
      self.filename = u"";
 
1546
      self.description = u"";
 
1547
 
 
1548
      # Remaining data is a NULL separated filename, description and object data
 
1549
      buffer = input.read();
 
1550
      input.close();
 
1551
 
 
1552
      (filename, buffer) = splitUnicode(buffer, self.encoding);
 
1553
      (desc, obj) = splitUnicode(buffer, self.encoding);
 
1554
      TRACE_MSG("filename len: %d" % len(filename));
 
1555
      TRACE_MSG("description len: %d" % len(desc));
 
1556
      TRACE_MSG("data len: %d" % len(obj));
 
1557
      self.filename = unicode(filename, id3EncodingToString(self.encoding));
 
1558
      self.description = unicode(desc, id3EncodingToString(self.encoding));
 
1559
      TRACE_MSG("GEOB filename: " + self.filename);
 
1560
      TRACE_MSG("GEOB description: " + self.description);
 
1561
 
 
1562
      self.objectData = obj;
 
1563
      TRACE_MSG("GEOB data: " + str(len(self.objectData)) + " bytes");
 
1564
      if strictID3() and not self.objectData:
 
1565
         raise FrameException("GEOB frame does not contain any data");
 
1566
 
 
1567
 
 
1568
   def writeFile(self, path = "./", name = None):
 
1569
      if not self.objectData:
 
1570
         raise IOError("Fetching remote object files is not implemented.");
 
1571
      if not name:
 
1572
         name = self.getDefaultFileName();
 
1573
      objectFile = os.path.join(path, name);
 
1574
 
 
1575
      f = file(objectFile, "wb");
 
1576
      f.write(self.objectData);
 
1577
      f.flush();
 
1578
      f.close();
 
1579
   def getDefaultFileName(self, suffix = ""):
 
1580
      nameStr = self.filename;
 
1581
      if suffix:
 
1582
          nameStr += suffix;
 
1583
      nameStr = nameStr +  "." + self.mimeType.split("/")[1];
 
1584
      return nameStr;
 
1585
 
 
1586
   def render(self):
 
1587
      data = self.encoding + self.mimeType + "\x00" +\
 
1588
             self.filename.encode(id3EncodingToString(self.encoding)) +\
 
1589
             self.getTextDelim() +\
 
1590
             self.description.encode(id3EncodingToString(self.encoding)) +\
 
1591
             self.getTextDelim() +\
 
1592
             self.objectData;
 
1593
      return self.assembleFrame(data);
 
1594
 
1214
1595
class PlayCountFrame(Frame):
1215
1596
    count = None;
1216
1597
 
1225
1606
        assert(frameHeader);
1226
1607
        assert(len(data) >= 4);
1227
1608
        self.count = long(bytes2dec(data));
1228
 
        
 
1609
 
1229
1610
    def render(self):
1230
1611
        data = dec2bytes(self.count, 32);
1231
1612
        return self.assembleFrame(data);
1255
1636
        if strictID3() and (len(self.owner_id) == 0 or
1256
1637
                            len(self.id) == 0 or len(self.id) > 64):
1257
1638
            raise FrameException("Invalid UFID frame");
1258
 
        
 
1639
 
1259
1640
    def render(self):
1260
1641
        data = self.owner_id + "\x00" + self.id;
1261
1642
        return self.assembleFrame(data);
1265
1646
   data = "";
1266
1647
 
1267
1648
   def __init__(self, frameHeader, data):
1268
 
       Frame.__init__(self, frameHeader);
1269
1649
       assert(frameHeader and data);
 
1650
       Frame.__init__(self, frameHeader);
1270
1651
       self._set(data, frameHeader);
1271
1652
 
1272
1653
   def _set(self, data, frameHeader):
1273
 
      data = self.disassembleFrame(data);
1274
 
      self.data = data;
 
1654
      self.data = self.disassembleFrame(data);
1275
1655
 
1276
1656
   def render(self):
1277
1657
      return self.assembleFrame(self.data)
1370
1750
 
1371
1751
         self.addFrame(createFrame(frameHeader, data));
1372
1752
 
1373
 
         # Each frame contains dataSize + headerSize(10) bytes.
 
1753
         # Each frame contains dataSize + headerSize bytes.
1374
1754
         sizeLeft -= (frameHeader.FRAME_HEADER_SIZE + frameHeader.dataSize);
1375
1755
 
1376
1756
      return paddingSize;
1381
1761
      for f in self:
1382
1762
         sz += len(f.render());
1383
1763
      return sz;
1384
 
   
 
1764
 
1385
1765
   def setTagHeader(self, tagHeader):
1386
1766
      self.tagHeader = tagHeader;
1387
1767
      for f in self:
1395
1775
      # No multiples except for TXXX which must have unique descriptions.
1396
1776
      if strictID3() and TEXT_FRAME_RX.match(fid) and self[fid]:
1397
1777
         if not USERTEXT_FRAME_RX.match(fid):
1398
 
            raise FrameException("Multiple %s frames now allowed." % fid);
 
1778
            raise FrameException("Multiple %s frames not allowed." % fid);
1399
1779
         userTextFrames = self[fid];
1400
1780
         for frm in userTextFrames:
1401
1781
            if frm.description == frame.description:
1402
1782
               raise FrameException("Multiple %s frames with the same\
1403
 
                                     description now allowed." % fid);
 
1783
                                     description not allowed." % fid);
1404
1784
 
1405
1785
      # Comment frame restrictions.
1406
1786
      # Multiples must have a unique description/language combination.
1410
1790
            if frm.description == frame.description and\
1411
1791
               frm.lang == frame.lang:
1412
1792
               raise FrameException("Multiple %s frames with the same\
1413
 
                                     language and description now allowed." %\
 
1793
                                     language and description not allowed." %\
 
1794
                                     fid);
 
1795
 
 
1796
      # Lyrics frame restrictions.
 
1797
      # Multiples must have a unique description/language combination.
 
1798
      if strictID3() and LYRICS_FRAME_RX.match(fid) and self[fid]:
 
1799
         lyricsFrames = self[fid];
 
1800
         for frm in lyricsFrames:
 
1801
            if frm.description == frame.description and\
 
1802
               frm.lang == frame.lang:
 
1803
               raise FrameException("Multiple %s frames with the same\
 
1804
                                     language and description not allowed." %\
1414
1805
                                     fid);
1415
1806
 
1416
1807
      # URL frame restrictions.
1417
1808
      # No multiples except for TXXX which must have unique descriptions.
1418
1809
      if strictID3() and URL_FRAME_RX.match(fid) and self[fid]:
1419
1810
         if not USERURL_FRAME_RX.match(fid):
1420
 
            raise FrameException("Multiple %s frames now allowed." % fid);
 
1811
            raise FrameException("Multiple %s frames not allowed." % fid);
1421
1812
         userUrlFrames = self[fid];
1422
1813
         for frm in userUrlFrames:
1423
1814
            if frm.description == frame.description:
1424
1815
               raise FrameException("Multiple %s frames with the same\
1425
 
                                     description now allowed." % fid);
 
1816
                                     description not allowed." % fid);
1426
1817
 
1427
1818
      # Music CD ID restrictions.
1428
1819
      # No multiples.
1429
1820
      if strictID3() and CDID_FRAME_RX.match(fid) and self[fid]:
1430
 
         raise FrameException("Multiple %s frames now allowed." % fid);
 
1821
         raise FrameException("Multiple %s frames not allowed." % fid);
1431
1822
 
1432
1823
      # Image (attached picture) frame restrictions.
1433
1824
      # Multiples must have a unique content desciptor.  I'm assuming that
1434
1825
      # the spec means the picture type.....
1435
 
      if IMAGE_FRAME_RX.match(fid) and self[fid]:
 
1826
      if IMAGE_FRAME_RX.match(fid) and self[fid] and strictID3():
1436
1827
         imageFrames = self[fid];
1437
1828
         for frm in imageFrames:
1438
1829
            if frm.pictureType == frame.pictureType:
1439
 
               raise FrameException("Multiple %s frames with the same\
1440
 
                                     content descriptor now allowed." % fid);
 
1830
               raise FrameException("Multiple %s frames with the same "\
 
1831
                                    "content descriptor not allowed." % fid);
 
1832
 
 
1833
      # Object (GEOB) frame restrictions.
 
1834
      # Multiples must have a unique content desciptor.
 
1835
      if OBJECT_FRAME_RX.match(fid) and self[fid] and strictID3():
 
1836
         objectFrames = self[fid];
 
1837
         for frm in objectFrames:
 
1838
            if frm.description == frame.description:
 
1839
               raise FrameException("Multiple %s frames with the same "\
 
1840
                                    "content descriptor not allowed." % fid);
1441
1841
 
1442
1842
      # Play count frame (PCNT).  There may be only one
1443
1843
      if PLAYCOUNT_FRAME_RX.match(fid) and self[fid]:
1444
 
         raise FrameException("Multiple %s frames now allowed." % fid);
 
1844
         raise FrameException("Multiple %s frames not allowed." % fid);
1445
1845
 
1446
1846
      # Unique File identifier frame.  There may be only one with the same
1447
1847
      # owner_id
1449
1849
          ufid_frames = self[fid];
1450
1850
          for frm in ufid_frames:
1451
1851
              if frm.owner_id == frame.owner_id:
1452
 
                  raise FrameException("Multiple %s frames now allowed with "\
 
1852
                  raise FrameException("Multiple %s frames not allowed with "\
1453
1853
                                       "the same owner ID (%s)" %\
1454
1854
                                       (fid, frame.owner_id));
1455
1855
 
1480
1880
          h.id = frameId;
1481
1881
          if not encoding:
1482
1882
              encoding = DEFAULT_ENCODING;
1483
 
          self.addFrame(TextFrame(h, encoding = encoding, text = text));
 
1883
          if frameId in DATE_FIDS:
 
1884
              self.addFrame(DateFrame(h, encoding = encoding, date_str = text));
 
1885
          else:
 
1886
              self.addFrame(TextFrame(h, encoding = encoding, text = text));
1484
1887
 
1485
1888
   # If a user text frame with the same description exists then
1486
1889
   # the frame text is replaced, otherwise the frame is added.
1514
1917
                                   description = description,
1515
1918
                                   comment = comment));
1516
1919
 
 
1920
   # If a user text frame with the same description exists then
 
1921
   # the frame text is replaced, otherwise the frame is added.
 
1922
   def setLyricsFrame(self, lyrics, description, lang = DEFAULT_LANG,
 
1923
                       encoding = None):
 
1924
      assert(isinstance(lyrics, unicode) and isinstance(description, unicode));
 
1925
 
 
1926
      if self[LYRICS_FID]:
 
1927
         found = 0;
 
1928
         for f in self[LYRICS_FID]:
 
1929
            if f.lang == lang and f.description == description:
 
1930
               f.lyrics = lyrics;
 
1931
               if encoding:
 
1932
                   f.encoding = encoding;
 
1933
               found = 1;
 
1934
               break;
 
1935
         if not found:
 
1936
            h = FrameHeader(self.tagHeader);
 
1937
            h.id = LYRICS_FID;
 
1938
            if not encoding:
 
1939
                encoding = DEFAULT_ENCODING;
 
1940
            self.addFrame(LyricsFrame(h, encoding = encoding, lang = lang,
 
1941
                                       description = description,
 
1942
                                       lyrics = lyrics));
 
1943
      else:
 
1944
        if not encoding:
 
1945
            encoding = DEFAULT_ENCODING;
 
1946
        h = FrameHeader(self.tagHeader);
 
1947
        h.id = LYRICS_FID;
 
1948
        self.addFrame(LyricsFrame(h, encoding = encoding, lang = lang,
 
1949
                                   description = description,
 
1950
                                   lyrics = lyrics));
 
1951
 
1517
1952
   def setUniqueFileIDFrame(self, owner_id, id):
1518
1953
      assert(isinstance(owner_id, str) and isinstance(id, str));
1519
1954
 
1564
1999
          self.addFrame(UserTextFrame(h, encoding = encoding,
1565
2000
                                      description = description,
1566
2001
                                      text = txt));
1567
 
         
 
2002
 
1568
2003
   # This method removes all frames with the matching frame ID.
1569
2004
   # The number of frames removed is returned.
1570
2005
   # Note that calling this method with a key like "COMM" may remove more
1618
2053
      else:
1619
2054
         raise TypeError("FrameSet key must be type int or string");
1620
2055
 
1621
 
#  Mmmmm!  Cheesy!
1622
2056
def splitUnicode(data, encoding):
1623
 
    if encoding == LATIN1_ENCODING or encoding == UTF_8_ENCODING or\
1624
 
       encoding == UTF_16BE_ENCODING:
 
2057
    if encoding == LATIN1_ENCODING or encoding == UTF_8_ENCODING:
1625
2058
        return data.split("\x00", 1);
1626
 
    elif encoding == UTF_16_ENCODING:
1627
 
        (d, t) = data.split("\x00\x00\x00", 1);
1628
 
        d += "\x00";
1629
 
        t += "\x00";
 
2059
    elif encoding == UTF_16_ENCODING or encoding == UTF_16BE_ENCODING:
 
2060
        # Two null bytes split, but since each utf16 char is also two 
 
2061
        # bytes we need to ensure we found a proper boundary.
 
2062
        (d, t) = data.split("\x00\x00", 1);
 
2063
        if (len(d) % 2) != 0:
 
2064
            (d, t) = data.split("\x00\x00\x00", 1);
 
2065
            d += "\x00";
1630
2066
        return (d, t);
1631
2067
 
1632
 
 
1633
2068
#######################################################################
1634
2069
# Create and return the appropriate frame.
1635
2070
# Exceptions: ....
1651
2086
  # Comment Frames.
1652
2087
  elif COMMENT_FRAME_RX.match(frameHeader.id):
1653
2088
     f = CommentFrame(frameHeader, data = data);
 
2089
  # Lyrics Frames.
 
2090
  elif LYRICS_FRAME_RX.match(frameHeader.id):
 
2091
     f = LyricsFrame(frameHeader, data = data);
1654
2092
  # URL Frames.
1655
2093
  elif URL_FRAME_RX.match(frameHeader.id):
1656
2094
     if USERURL_FRAME_RX.match(frameHeader.id):
1663
2101
  # Attached picture
1664
2102
  elif IMAGE_FRAME_RX.match(frameHeader.id):
1665
2103
     f = ImageFrame(frameHeader, data = data);
 
2104
  # Encapsulated object
 
2105
  elif OBJECT_FRAME_RX.match(frameHeader.id):
 
2106
     f = ObjectFrame(frameHeader, data = data);
1666
2107
  # Play count
1667
2108
  elif PLAYCOUNT_FRAME_RX.match(frameHeader.id):
1668
2109
     f = PlayCountFrame(frameHeader, data = data);
1674
2115
     f = UnknownFrame(frameHeader, data);
1675
2116
 
1676
2117
  return f;
 
2118
 
 
2119
 
 
2120
def map2_2FrameId(originalId):
 
2121
    if not TAGS2_2_TO_TAGS_2_3_AND_4.has_key(originalId):
 
2122
        return originalId
 
2123
    return TAGS2_2_TO_TAGS_2_3_AND_4[originalId]
 
2124