Fixed write() -- part of it at least, still need
[ID3v1.git] / ID3v1.py
blob8152b1b97af5a026d08baec4984795cc94da7957
1 #!/usr/bin/env python
2 '''
3 ID3v1
5 ID3 version 1 module, allows you to retrieve / edit the ID3v1 tag
6 on mp3 files.
7 '''
9 # ID3v1 Python Library
10 # Copyright (C) 2008 Alexander Borgerth <alex.borgert@gmail.com>
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # This is just a rough draft, it is a half working version, i have yet
26 # to implement writing abilitiy to the ID3 class.
27 # Most(if not all of this) will be refactored later.
29 import os
30 from struct import pack, unpack
32 class ID3InvalidTagError(Exception):
33 '''
34 Invalid Tag Error
36 Is used when some data is invalid(retrieved), from the ID3 in
37 a file.
38 '''
39 def __init__(self, msg):
40 self.msg = msg
41 def __str__(self):
42 return self.msg
44 class ID3(object):
45 '''
46 ID3
48 Is handling all reading / writing from / to the mp3 file.
49 '''
50 genres = ['Blues', 'Classic Rock', 'Country', 'Dance', 'Disco', 'Funk',
51 'Grunge', 'Hip-Hop', 'Jazz', 'Metal', 'New Age', 'Oldies', 'Other',
52 'Pop', 'R&B', 'Rap', 'Reggae', 'Rock', 'Techno', 'Industrial',
53 'Alternative', 'Ska', 'Death Metal', 'Pranks', 'Soundtrack', 'Euro-Techno',
54 'Ambient', 'Trip-Hop', 'Vocal', 'Jazz+Funk', 'Fusion', 'Trance',
55 'Classical', 'Instrumental', 'Acid', 'House', 'Game', 'Sound Clip',
56 'Gospel', 'Noise', 'AlternRock', 'Bass', 'Soul', 'Punk', 'Space',
57 'Meditative', 'Instrumental Pop', 'Instrumental Rock', 'Ethnic', 'Gothic',
58 'Darkwave', 'Techno-Industrial', 'Electronic', 'Pop-Folk', 'Eurodance',
59 'Dream', 'Southern Rock', 'Comedy', 'Cult', 'Gangsta', 'Top 40',
60 'Christian Rap', 'Pop/Funk', 'Jungle', 'Native American', 'Cabaret',
61 'New Wave', 'Psychadelic', 'Rave', 'Showtunes', 'Trailer', 'Lo-Fi',
62 'Tribal', 'Acid Punk', 'Acid Jazz', 'Polka', 'Retro', 'Musical',
63 'Rock & Roll', 'Hard Rock']
65 def __init__(self, filename):
66 '''
67 Initialize the data.
69 Initialize all the data we will be using throughout
70 the class.
71 '''
72 self.__TAG_SIZE = 128
73 self.song = None
74 self.artist = None
75 self.album = None
76 self.year = None
77 self.comment = None
78 self.genre = None
80 self.header_changed = False
82 if len(filename) <= 0:
83 raise ValueError, 'filename is empty!'
84 self.filename = filename
85 # re-open with `r+b' only if we need to actually write any data!
86 try:
87 self.file = open(self.filename, 'rb')
88 except IOError, value:
89 print 'Couldn\'t open file: `%s\''
91 def __valid_genre(self, genre_nr):
92 '''
93 Valid Genre
95 Check to see if the ID3v1 genre, actually is a valid genre.
96 '''
97 if genre_nr > 0 and genre_nr < len(self.genres):
98 return True
99 return False
101 def __has_tag(self):
103 Check for tag.
105 Check if the loaded file has an ID3v1 tag.
107 pos = self.file.tell()
108 if os.stat(self.filename).st_size < self.__TAG_SIZE:
109 raise ValueError, 'File size < %db!' % (self.__TAG_SIZE)
110 self.file.seek(-128, os.SEEK_END)
111 if self.file.read(3) != 'TAG':
112 return False
113 self.file.seek(pos, os.SEEK_SET)
114 return True
116 def __pad_data(self, str, size=30):
118 Pad data.
120 Pads the string with '\0', if size is less then `size'. Also
121 if the len(str) is higher then `size', truncate it!
123 if len(str) <= 0:
124 raise ValueError, '`str\' is empty!'
125 if size <= 0:
126 raise ValueError, '`size\' <= 0!'
127 str = str[:size] # truncate to big strings!
128 return str + ('\0' * (size - len(str)))
130 def read(self):
132 Read the ID3 tag from the file.
134 Reads the ID3 tag from the file, and fill our data!
136 if not self.__has_tag():
137 raise ID3InvalidTagError, 'No ID3 tag found in file!'
138 self.file.seek(-125, os.SEEK_END)
139 raw = self.file.read(125)
140 (self.song, self.artist, self.album, self.year, self.comment,\
141 self.genre) = unpack('>30s30s30s4s30sB', raw)
143 def __str__(self): # Used for debugging right now!
144 data = 'Artist: %s\nAlbum: %s\nSong: %s\n' % (self.artist, self.album,\
145 self.song)
146 data = data + 'Year: %s\nGenre: %s\nComment: %s' % (self.year,\
147 self.genres[self.genre], self.comment)
148 return data
150 def __setattr__(self, name, value):
151 if name in ['artist', 'album', 'song', 'year', 'genre', 'comment']:
152 self.__dict__['header_changed'] = True
153 if name == 'genre':
154 if self.__valid_genre(value):
155 self.__dict__[name] = value
156 else:
157 self.__dict__[name] = 0
158 else:
159 self.__dict__[name] = value
160 self.__dict__[name] = value
162 def __validate_data(self):
163 if len(self.song) > 0 and len(self.artist) > 0 and\
164 len(self.album) > 0 and len(self.year) > 0 and\
165 len(self.comment) > 0:
166 if self.__valid_genre(self.genre):
167 return True
168 return false
170 def write(self): # FIXME: broken!
171 if self.header_changed:
172 self.song = self.__pad_data(self.song, 30)
173 self.artist = self.__pad_data(self.artist, 30)
174 self.album = self.__pad_data(self.album, 30)
175 self.year = self.__pad_data(self.year, 4)
176 self.comment = self.__pad_data(self.comment, 30)
177 self.genre = chr(self.genre)
178 if self.__has_tag():
179 try:
180 self.file.close()
181 self.file = open(self.filename, 'r+b')
182 except IOError, value:
183 print 'Couldn\'t open file(r+b): %s' % (value)
184 self.file.seek(-125, os.SEEK_END)
185 self.file.write(self.song)
186 self.file.write(self.artist)
187 self.file.write(self.album)
188 self.file.write(self.year)
189 self.file.write(self.comment)
190 self.file.write(self.genre)
191 else:
192 # Implement the ability to add ID3 header, to headerless Mp3s
193 pass