5 ID3 version 1 module, allows you to retrieve / edit the ID3v1 tag
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.
30 from struct
import pack
, unpack
32 class ID3InvalidTagError(Exception):
36 Is used when some data is invalid(retrieved), from the ID3 in
39 def __init__(self
, msg
):
48 Is handling all reading / writing from / to the mp3 file.
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
):
69 Initialize all the data we will be using throughout
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!
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
):
95 Check to see if the ID3v1 genre, actually is a valid genre.
97 if genre_nr
> 0 and genre_nr
< len(self
.genres
):
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':
113 self
.file.seek(pos
, os
.SEEK_SET
)
116 def __pad_data(self
, str, size
=30):
120 Pads the string with '\0', if size is less then `size'. Also
121 if the len(str) is higher then `size', truncate it!
124 raise ValueError, '`str\' is empty!'
126 raise ValueError, '`size\' <= 0!'
127 str = str[:size
] # truncate to big strings!
128 return str + ('\0' * (size
- len(str)))
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
,\
146 data
= data
+ 'Year: %s\nGenre: %s\nComment: %s' % (self
.year
,\
147 self
.genres
[self
.genre
], self
.comment
)
150 def __setattr__(self
, name
, value
):
151 if name
in ['artist', 'album', 'song', 'year', 'genre', 'comment']:
152 self
.__dict
__['header_changed'] = True
154 if self
.__valid
_genre
(value
):
155 self
.__dict
__[name
] = value
157 self
.__dict
__[name
] = 0
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
):
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
)
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
)
192 # Implement the ability to add ID3 header, to headerless Mp3s