1 /***************************************************************************
2 copyright : (C) 2002 - 2008 by Scott Wheeler
3 email : wheeler@kde.org
4 ***************************************************************************/
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. *
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. *
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
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 ***************************************************************************/
30 #include "id3v2header.h"
31 #include "id3v2extendedheader.h"
32 #include "id3v2footer.h"
33 #include "id3v2synchdata.h"
35 #include "id3v1genres.h"
37 #include "frames/textidentificationframe.h"
38 #include "frames/commentsframe.h"
40 using namespace TagLib;
41 using namespace ID3v2;
43 class ID3v2::Tag::TagPrivate
46 TagPrivate() : file(0), tagOffset(-1), extendedHeader(0), footer(0), paddingSize(0)
48 frameList.setAutoDelete(true);
52 delete extendedHeader;
58 const FrameFactory *factory;
61 ExtendedHeader *extendedHeader;
66 FrameListMap frameListMap;
70 ////////////////////////////////////////////////////////////////////////////////
72 ////////////////////////////////////////////////////////////////////////////////
74 ID3v2::Tag::Tag() : TagLib::Tag()
77 d->factory = FrameFactory::instance();
80 ID3v2::Tag::Tag(File *file, long tagOffset, const FrameFactory *factory) :
86 d->tagOffset = tagOffset;
98 String ID3v2::Tag::title() const
100 if(!d->frameListMap["TIT2"].isEmpty())
101 return d->frameListMap["TIT2"].front()->toString();
105 String ID3v2::Tag::artist() const
107 if(!d->frameListMap["TPE1"].isEmpty())
108 return d->frameListMap["TPE1"].front()->toString();
112 String ID3v2::Tag::album() const
114 if(!d->frameListMap["TALB"].isEmpty())
115 return d->frameListMap["TALB"].front()->toString();
119 String ID3v2::Tag::comment() const
121 const FrameList &comments = d->frameListMap["COMM"];
123 if(comments.isEmpty())
126 for(FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it)
128 CommentsFrame *frame = dynamic_cast<CommentsFrame *>(*it);
130 if(frame && frame->description().isEmpty())
131 return (*it)->toString();
134 return comments.front()->toString();
137 String ID3v2::Tag::genre() const
139 // TODO: In the next major version (TagLib 2.0) a list of multiple genres
140 // should be separated by " / " instead of " ". For the moment to keep
141 // the behavior the same as released versions it is being left with " ".
143 if(d->frameListMap["TCON"].isEmpty() ||
144 !dynamic_cast<TextIdentificationFrame *>(d->frameListMap["TCON"].front()))
149 // ID3v2.4 lists genres as the fields in its frames field list. If the field
150 // is simply a number it can be assumed that it is an ID3v1 genre number.
151 // Here was assume that if an ID3v1 string is present that it should be
152 // appended to the genre string. Multiple fields will be appended as the
155 TextIdentificationFrame *f = static_cast<TextIdentificationFrame *>(
156 d->frameListMap["TCON"].front());
158 StringList fields = f->fieldList();
162 for(StringList::Iterator it = fields.begin(); it != fields.end(); ++it) {
168 int number = (*it).toInt(&ok);
169 if(ok && number >= 0 && number <= 255) {
170 *it = ID3v1::genre(number);
173 if(std::find(genres.begin(), genres.end(), *it) == genres.end())
177 return genres.toString();
180 TagLib::uint ID3v2::Tag::year() const
182 if(!d->frameListMap["TDRC"].isEmpty())
183 return d->frameListMap["TDRC"].front()->toString().substr(0, 4).toInt();
187 TagLib::uint ID3v2::Tag::track() const
189 if(!d->frameListMap["TRCK"].isEmpty())
190 return d->frameListMap["TRCK"].front()->toString().toInt();
194 void ID3v2::Tag::setTitle(const String &s)
196 setTextFrame("TIT2", s);
199 void ID3v2::Tag::setArtist(const String &s)
201 setTextFrame("TPE1", s);
204 void ID3v2::Tag::setAlbum(const String &s)
206 setTextFrame("TALB", s);
209 void ID3v2::Tag::setComment(const String &s)
212 removeFrames("COMM");
216 if(!d->frameListMap["COMM"].isEmpty())
217 d->frameListMap["COMM"].front()->setText(s);
219 CommentsFrame *f = new CommentsFrame(d->factory->defaultTextEncoding());
225 void ID3v2::Tag::setGenre(const String &s)
228 removeFrames("TCON");
232 // iTunes can't handle correctly encoded ID3v2.4 numerical genres. Just use
233 // strings until iTunes sucks less.
235 #ifdef NO_ITUNES_HACKS
237 int index = ID3v1::genreIndex(s);
240 setTextFrame("TCON", String::number(index));
242 setTextFrame("TCON", s);
246 setTextFrame("TCON", s);
251 void ID3v2::Tag::setYear(uint i)
254 removeFrames("TDRC");
257 setTextFrame("TDRC", String::number(i));
260 void ID3v2::Tag::setTrack(uint i)
263 removeFrames("TRCK");
266 setTextFrame("TRCK", String::number(i));
269 bool ID3v2::Tag::isEmpty() const
271 return d->frameList.isEmpty();
274 Header *ID3v2::Tag::header() const
279 ExtendedHeader *ID3v2::Tag::extendedHeader() const
281 return d->extendedHeader;
284 Footer *ID3v2::Tag::footer() const
289 const FrameListMap &ID3v2::Tag::frameListMap() const
291 return d->frameListMap;
294 const FrameList &ID3v2::Tag::frameList() const
299 const FrameList &ID3v2::Tag::frameList(const ByteVector &frameID) const
301 return d->frameListMap[frameID];
304 void ID3v2::Tag::addFrame(Frame *frame)
306 d->frameList.append(frame);
307 d->frameListMap[frame->frameID()].append(frame);
310 void ID3v2::Tag::removeFrame(Frame *frame, bool del)
312 // remove the frame from the frame list
313 FrameList::Iterator it = d->frameList.find(frame);
314 d->frameList.erase(it);
316 // ...and from the frame list map
317 it = d->frameListMap[frame->frameID()].find(frame);
318 d->frameListMap[frame->frameID()].erase(it);
320 // ...and delete as desired
325 void ID3v2::Tag::removeFrames(const ByteVector &id)
327 FrameList l = d->frameListMap[id];
328 for(FrameList::Iterator it = l.begin(); it != l.end(); ++it)
329 removeFrame(*it, true);
332 ByteVector ID3v2::Tag::render() const
334 // We need to render the "tag data" first so that we have to correct size to
335 // render in the tag's header. The "tag data" -- everything that is included
336 // in ID3v2::Header::tagSize() -- includes the extended header, frames and
337 // padding, but does not include the tag's header or footer.
341 // TODO: Render the extended header.
343 // Loop through the frames rendering them and adding them to the tagData.
345 for(FrameList::Iterator it = d->frameList.begin(); it != d->frameList.end(); it++) {
346 if((*it)->header()->frameID().size() != 4) {
347 debug("A frame of unsupported or unknown type \'"
348 + String((*it)->header()->frameID()) + "\' has been discarded");
351 if(!(*it)->header()->tagAlterPreservation())
352 tagData.append((*it)->render());
355 // Compute the amount of padding, and append that to tagData.
357 uint paddingSize = 0;
358 uint originalSize = d->header.tagSize();
360 if(tagData.size() < originalSize)
361 paddingSize = originalSize - tagData.size();
365 tagData.append(ByteVector(paddingSize, char(0)));
368 d->header.setTagSize(tagData.size());
370 // TODO: This should eventually include d->footer->render().
371 return d->header.render() + tagData;
374 ////////////////////////////////////////////////////////////////////////////////
376 ////////////////////////////////////////////////////////////////////////////////
378 void ID3v2::Tag::read()
380 if(d->file && d->file->isOpen()) {
382 d->file->seek(d->tagOffset);
383 d->header.setData(d->file->readBlock(Header::size()));
385 // if the tag size is 0, then this is an invalid tag (tags must contain at
388 if(d->header.tagSize() == 0)
391 parse(d->file->readBlock(d->header.tagSize()));
395 void ID3v2::Tag::parse(const ByteVector &origData)
397 ByteVector data = origData;
399 if(d->header.unsynchronisation() && d->header.majorVersion() <= 3)
400 data = SynchData::decode(data);
402 uint frameDataPosition = 0;
403 uint frameDataLength = data.size();
405 // check for extended header
407 if(d->header.extendedHeader()) {
408 if(!d->extendedHeader)
409 d->extendedHeader = new ExtendedHeader;
410 d->extendedHeader->setData(data);
411 if(d->extendedHeader->size() <= data.size()) {
412 frameDataPosition += d->extendedHeader->size();
413 frameDataLength -= d->extendedHeader->size();
417 // check for footer -- we don't actually need to parse it, as it *must*
418 // contain the same data as the header, but we do need to account for its
421 if(d->header.footerPresent() && Footer::size() <= frameDataLength)
422 frameDataLength -= Footer::size();
426 // Make sure that there is at least enough room in the remaining frame data for
429 while(frameDataPosition < frameDataLength - Frame::headerSize(d->header.majorVersion())) {
431 // If the next data is position is 0, assume that we've hit the padding
432 // portion of the frame data.
434 if(data.at(frameDataPosition) == 0) {
435 if(d->header.footerPresent())
436 debug("Padding *and* a footer found. This is not allowed by the spec.");
438 d->paddingSize = frameDataLength - frameDataPosition;
442 Frame *frame = d->factory->createFrame(data.mid(frameDataPosition),
448 // Checks to make sure that frame parsed correctly.
450 if(frame->size() <= 0) {
455 frameDataPosition += frame->size() + Frame::headerSize(d->header.majorVersion());
460 void ID3v2::Tag::setTextFrame(const ByteVector &id, const String &value)
462 if(value.isEmpty()) {
467 if(!d->frameListMap[id].isEmpty())
468 d->frameListMap[id].front()->setText(value);
470 const String::Type encoding = d->factory->defaultTextEncoding();
471 TextIdentificationFrame *f = new TextIdentificationFrame(id, encoding);