class ID3v1Comment(MetaData, list): """A complete ID3v1 tag.""" ID3v1 = Con.Struct("id3v1", Con.Const(Con.String("identifier", 3), 'TAG'), Con.String("song_title", 30), Con.String("artist", 30), Con.String("album", 30), Con.String("year", 4), Con.String("comment", 28), Con.Padding(1), Con.Byte("track_number"), Con.Byte("genre")) ID3v1_NO_TRACKNUMBER = Con.Struct( "id3v1_notracknumber", Con.Const(Con.String("identifier", 3), 'TAG'), Con.String("song_title", 30), Con.String("artist", 30), Con.String("album", 30), Con.String("year", 4), Con.String("comment", 30), Con.Byte("genre")) ATTRIBUTES = [ 'track_name', 'artist_name', 'album_name', 'year', 'comment', 'track_number' ] @classmethod def read_id3v1_comment(cls, mp3filename): """Reads a ID3v1Comment data from an MP3 filename. Returns a (song title, artist, album, year, comment, track number) tuple. If no ID3v1 tag is present, returns a tuple with those fields blank. All text is in unicode. If track number is -1, the id3v1 comment could not be found. """ mp3file = file(mp3filename, "rb") try: mp3file.seek(-128, 2) try: id3v1 = ID3v1Comment.ID3v1.parse(mp3file.read()) except Con.adapters.PaddingError: mp3file.seek(-128, 2) id3v1 = ID3v1Comment.ID3v1_NO_TRACKNUMBER.parse(mp3file.read()) id3v1.track_number = 0 except Con.ConstError: return tuple([u""] * 5 + [-1]) field_list = (id3v1.song_title, id3v1.artist, id3v1.album, id3v1.year, id3v1.comment) return tuple( map(lambda t: t.rstrip('\x00').decode('ascii', 'replace'), field_list) + [id3v1.track_number]) finally: mp3file.close() @classmethod def build_id3v1(cls, song_title, artist, album, year, comment, track_number): """Turns fields into a complete ID3v1 binary tag string. All fields are unicode except for track_number, an int.""" def __s_pad__(s, length): if (len(s) < length): return s + chr(0) * (length - len(s)) else: s = s[0:length].rstrip() return s + chr(0) * (length - len(s)) c = Con.Container() c.identifier = 'TAG' c.song_title = __s_pad__(song_title.encode('ascii', 'replace'), 30) c.artist = __s_pad__(artist.encode('ascii', 'replace'), 30) c.album = __s_pad__(album.encode('ascii', 'replace'), 30) c.year = __s_pad__(year.encode('ascii', 'replace'), 4) c.comment = __s_pad__(comment.encode('ascii', 'replace'), 28) c.track_number = int(track_number) c.genre = 0 return ID3v1Comment.ID3v1.build(c) def __init__(self, metadata): """Initialized with a read_id3v1_comment tuple. Fields are (title,artist,album,year,comment,tracknum)""" list.__init__(self, metadata) def supports_images(self): """Returns False.""" return False #if an attribute is updated (e.g. self.track_name) #make sure to update the corresponding list item def __setattr__(self, key, value): if (key in self.ATTRIBUTES): if (key != 'track_number'): self[self.ATTRIBUTES.index(key)] = value else: self[self.ATTRIBUTES.index(key)] = int(value) elif (key in MetaData.__FIELDS__): pass else: self.__dict__[key] = value def __delattr__(self, key): if (key == 'track_number'): setattr(self, key, 0) elif (key in self.ATTRIBUTES): setattr(self, key, u"") def __getattr__(self, key): if (key in self.ATTRIBUTES): return self[self.ATTRIBUTES.index(key)] elif (key in MetaData.__INTEGER_FIELDS__): return 0 elif (key in MetaData.__FIELDS__): return u"" else: raise AttributeError(key) @classmethod def converted(cls, metadata): """Converts a MetaData object to an ID3v1Comment object.""" if ((metadata is None) or (isinstance(metadata, ID3v1Comment))): return metadata return ID3v1Comment( (metadata.track_name, metadata.artist_name, metadata.album_name, metadata.year, metadata.comment, int(metadata.track_number))) def __comment_name__(self): return u'ID3v1' def __comment_pairs__(self): return zip(('Title', 'Artist', 'Album', 'Year', 'Comment', 'Tracknum'), self) def build_tag(self): """Returns a binary string of this tag's data.""" return self.build_id3v1(self.track_name, self.artist_name, self.album_name, self.year, self.comment, self.track_number) def images(self): """Returns an empty list of Image objects.""" return []
stream.write(data) def _sizeof(self, context): return self.sub_atom.sizeof(context) + 8 ATOM_FTYP = Con.Struct("ftyp", Con.String("major_brand", 4), Con.UBInt32("major_brand_version"), Con.GreedyRepeater(Con.String("compatible_brands", 4))) ATOM_MVHD = Con.Struct( "mvhd", Con.Byte("version"), Con.String("flags", 3), VersionLength("created_mac_UTC_date"), VersionLength("modified_mac_UTC_date"), Con.UBInt32("time_scale"), VersionLength("duration"), Con.UBInt32("playback_speed"), Con.UBInt16("user_volume"), Con.Padding(10), Con.Struct("windows", Con.UBInt32("geometry_matrix_a"), Con.UBInt32("geometry_matrix_b"), Con.UBInt32("geometry_matrix_u"), Con.UBInt32("geometry_matrix_c"), Con.UBInt32("geometry_matrix_d"), Con.UBInt32("geometry_matrix_v"), Con.UBInt32("geometry_matrix_x"), Con.UBInt32("geometry_matrix_y"), Con.UBInt32("geometry_matrix_w")), Con.UBInt64("quicktime_preview"), Con.UBInt32("quicktime_still_poster"), Con.UBInt64("quicktime_selection_time"), Con.UBInt32("quicktime_current_time"), Con.UBInt32("next_track_id")) ATOM_IODS = Con.Struct( "iods", Con.Byte("version"), Con.String("flags", 3), Con.Byte("type_tag"),