1 /***************************************************************************
2     copyright            : (C) 2002 - 2008 by Scott Wheeler
3     email                : wheeler@kde.org
4  ***************************************************************************/
5 
6 /***************************************************************************
7  *   This library is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU Lesser General Public License version   *
9  *   2.1 as published by the Free Software Foundation.                     *
10  *                                                                         *
11  *   This library is distributed in the hope that it will be useful, but   *
12  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
14  *   Lesser General Public License for more details.                       *
15  *                                                                         *
16  *   You should have received a copy of the GNU Lesser General Public      *
17  *   License along with this library; if not, write to the Free Software   *
18  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA         *
19  *   02110-1301  USA                                                       *
20  *                                                                         *
21  *   Alternatively, this file is available under the Mozilla Public        *
22  *   License Version 1.1.  You may obtain a copy of the License at         *
23  *   http://www.mozilla.org/MPL/                                           *
24  ***************************************************************************/
25 
26 #include <tdebug.h>
27 #include <tfile.h>
28 
29 #include "id3v1tag.h"
30 #include "id3v1genres.h"
31 
32 using namespace TagLib;
33 using namespace ID3v1;
34 
35 namespace
36 {
37   const ID3v1::StringHandler defaultStringHandler;
38   const ID3v1::StringHandler *stringHandler = &defaultStringHandler;
39 }
40 
41 class ID3v1::Tag::TagPrivate
42 {
43 public:
TagPrivate()44   TagPrivate() :
45     file(0),
46     tagOffset(0),
47     track(0),
48     genre(255) {}
49 
50   File *file;
51   long tagOffset;
52 
53   String title;
54   String artist;
55   String album;
56   String year;
57   String comment;
58   unsigned char track;
59   unsigned char genre;
60 };
61 
62 ////////////////////////////////////////////////////////////////////////////////
63 // StringHandler implementation
64 ////////////////////////////////////////////////////////////////////////////////
65 
StringHandler()66 StringHandler::StringHandler()
67 {
68 }
69 
parse(const ByteVector & data) const70 String ID3v1::StringHandler::parse(const ByteVector &data) const
71 {
72   return String(data, String::Latin1).stripWhiteSpace();
73 }
74 
render(const String & s) const75 ByteVector ID3v1::StringHandler::render(const String &s) const
76 {
77   if(s.isLatin1())
78     return s.data(String::Latin1);
79   else
80     return ByteVector();
81 }
82 
83 ////////////////////////////////////////////////////////////////////////////////
84 // public methods
85 ////////////////////////////////////////////////////////////////////////////////
86 
Tag()87 ID3v1::Tag::Tag() :
88   TagLib::Tag(),
89   d(new TagPrivate())
90 {
91 }
92 
Tag(File * file,long tagOffset)93 ID3v1::Tag::Tag(File *file, long tagOffset) :
94   TagLib::Tag(),
95   d(new TagPrivate())
96 {
97   d->file = file;
98   d->tagOffset = tagOffset;
99 
100   read();
101 }
102 
~Tag()103 ID3v1::Tag::~Tag()
104 {
105   delete d;
106 }
107 
render() const108 ByteVector ID3v1::Tag::render() const
109 {
110   ByteVector data;
111 
112   data.append(fileIdentifier());
113   data.append(stringHandler->render(d->title).resize(30));
114   data.append(stringHandler->render(d->artist).resize(30));
115   data.append(stringHandler->render(d->album).resize(30));
116   data.append(stringHandler->render(d->year).resize(4));
117   data.append(stringHandler->render(d->comment).resize(28));
118   data.append(char(0));
119   data.append(char(d->track));
120   data.append(char(d->genre));
121 
122   return data;
123 }
124 
fileIdentifier()125 ByteVector ID3v1::Tag::fileIdentifier()
126 {
127   return ByteVector::fromCString("TAG");
128 }
129 
title() const130 String ID3v1::Tag::title() const
131 {
132   return d->title;
133 }
134 
artist() const135 String ID3v1::Tag::artist() const
136 {
137   return d->artist;
138 }
139 
album() const140 String ID3v1::Tag::album() const
141 {
142   return d->album;
143 }
144 
comment() const145 String ID3v1::Tag::comment() const
146 {
147   return d->comment;
148 }
149 
genre() const150 String ID3v1::Tag::genre() const
151 {
152   return ID3v1::genre(d->genre);
153 }
154 
year() const155 unsigned int ID3v1::Tag::year() const
156 {
157   return d->year.toInt();
158 }
159 
track() const160 unsigned int ID3v1::Tag::track() const
161 {
162   return d->track;
163 }
164 
setTitle(const String & s)165 void ID3v1::Tag::setTitle(const String &s)
166 {
167   d->title = s;
168 }
169 
setArtist(const String & s)170 void ID3v1::Tag::setArtist(const String &s)
171 {
172   d->artist = s;
173 }
174 
setAlbum(const String & s)175 void ID3v1::Tag::setAlbum(const String &s)
176 {
177   d->album = s;
178 }
179 
setComment(const String & s)180 void ID3v1::Tag::setComment(const String &s)
181 {
182   d->comment = s;
183 }
184 
setGenre(const String & s)185 void ID3v1::Tag::setGenre(const String &s)
186 {
187   d->genre = ID3v1::genreIndex(s);
188 }
189 
setYear(unsigned int i)190 void ID3v1::Tag::setYear(unsigned int i)
191 {
192   d->year = i > 0 ? String::number(i) : String();
193 }
194 
setTrack(unsigned int i)195 void ID3v1::Tag::setTrack(unsigned int i)
196 {
197   d->track = i < 256 ? i : 0;
198 }
199 
genreNumber() const200 unsigned int ID3v1::Tag::genreNumber() const
201 {
202   return d->genre;
203 }
204 
setGenreNumber(unsigned int i)205 void ID3v1::Tag::setGenreNumber(unsigned int i)
206 {
207   d->genre = i < 256 ? i : 255;
208 }
209 
setStringHandler(const StringHandler * handler)210 void ID3v1::Tag::setStringHandler(const StringHandler *handler)
211 {
212   if(handler)
213     stringHandler = handler;
214   else
215     stringHandler = &defaultStringHandler;
216 }
217 
218 ////////////////////////////////////////////////////////////////////////////////
219 // protected methods
220 ////////////////////////////////////////////////////////////////////////////////
221 
read()222 void ID3v1::Tag::read()
223 {
224   if(d->file && d->file->isValid()) {
225     d->file->seek(d->tagOffset);
226     // read the tag -- always 128 bytes
227     const ByteVector data = d->file->readBlock(128);
228 
229     // some initial sanity checking
230     if(data.size() == 128 && data.startsWith("TAG"))
231       parse(data);
232     else
233       debug("ID3v1 tag is not valid or could not be read at the specified offset.");
234   }
235 }
236 
parse(const ByteVector & data)237 void ID3v1::Tag::parse(const ByteVector &data)
238 {
239   int offset = 3;
240 
241   d->title = stringHandler->parse(data.mid(offset, 30));
242   offset += 30;
243 
244   d->artist = stringHandler->parse(data.mid(offset, 30));
245   offset += 30;
246 
247   d->album = stringHandler->parse(data.mid(offset, 30));
248   offset += 30;
249 
250   d->year = stringHandler->parse(data.mid(offset, 4));
251   offset += 4;
252 
253   // Check for ID3v1.1 -- Note that ID3v1 *does not* support "track zero" -- this
254   // is not a bug in TagLib.  Since a zeroed byte is what we would expect to
255   // indicate the end of a C-String, specifically the comment string, a value of
256   // zero must be assumed to be just that.
257 
258   if(data[offset + 28] == 0 && data[offset + 29] != 0) {
259     // ID3v1.1 detected
260 
261     d->comment = stringHandler->parse(data.mid(offset, 28));
262     d->track   = static_cast<unsigned char>(data[offset + 29]);
263   }
264   else
265     d->comment = data.mid(offset, 30);
266 
267   offset += 30;
268 
269   d->genre = static_cast<unsigned char>(data[offset]);
270 }
271