191 lines
6.3 KiB
Python
191 lines
6.3 KiB
Python
#!/usr/bin/python
|
|
|
|
#Audio Tools, a module and set of tools for manipulating audio data
|
|
#Copyright (C) 2007-2011 Brian Langenberger
|
|
|
|
#This program is free software; you can redistribute it and/or modify
|
|
#it under the terms of the GNU General Public License as published by
|
|
#the Free Software Foundation; either version 2 of the License, or
|
|
#(at your option) any later version.
|
|
|
|
#This program is distributed in the hope that it will be useful,
|
|
#but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
#GNU General Public License for more details.
|
|
|
|
#You should have received a copy of the GNU General Public License
|
|
#along with this program; if not, write to the Free Software
|
|
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
from audiotools import MetaData, Con, os
|
|
|
|
|
|
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)
|
|
|
|
@classmethod
|
|
def supports_images(cls):
|
|
"""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 []
|