Added TagLib (with AUTORS and COPYING files)
[someplayer] / src / taglib / mpeg / id3v2 / id3v2tag.cpp
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
19  *   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 <tfile.h>
27 #include <tdebug.h>
28
29 #include "id3v2tag.h"
30 #include "id3v2header.h"
31 #include "id3v2extendedheader.h"
32 #include "id3v2footer.h"
33 #include "id3v2synchdata.h"
34
35 #include "id3v1genres.h"
36
37 #include "frames/textidentificationframe.h"
38 #include "frames/commentsframe.h"
39
40 using namespace TagLib;
41 using namespace ID3v2;
42
43 class ID3v2::Tag::TagPrivate
44 {
45 public:
46   TagPrivate() : file(0), tagOffset(-1), extendedHeader(0), footer(0), paddingSize(0)
47   {
48     frameList.setAutoDelete(true);
49   }
50   ~TagPrivate()
51   {
52     delete extendedHeader;
53     delete footer;
54   }
55
56   File *file;
57   long tagOffset;
58   const FrameFactory *factory;
59
60   Header header;
61   ExtendedHeader *extendedHeader;
62   Footer *footer;
63
64   int paddingSize;
65
66   FrameListMap frameListMap;
67   FrameList frameList;
68 };
69
70 ////////////////////////////////////////////////////////////////////////////////
71 // public members
72 ////////////////////////////////////////////////////////////////////////////////
73
74 ID3v2::Tag::Tag() : TagLib::Tag()
75 {
76   d = new TagPrivate;
77   d->factory = FrameFactory::instance();
78 }
79
80 ID3v2::Tag::Tag(File *file, long tagOffset, const FrameFactory *factory) :
81   TagLib::Tag()
82 {
83   d = new TagPrivate;
84
85   d->file = file;
86   d->tagOffset = tagOffset;
87   d->factory = factory;
88
89   read();
90 }
91
92 ID3v2::Tag::~Tag()
93 {
94   delete d;
95 }
96
97
98 String ID3v2::Tag::title() const
99 {
100   if(!d->frameListMap["TIT2"].isEmpty())
101     return d->frameListMap["TIT2"].front()->toString();
102   return String::null;
103 }
104
105 String ID3v2::Tag::artist() const
106 {
107   if(!d->frameListMap["TPE1"].isEmpty())
108     return d->frameListMap["TPE1"].front()->toString();
109   return String::null;
110 }
111
112 String ID3v2::Tag::album() const
113 {
114   if(!d->frameListMap["TALB"].isEmpty())
115     return d->frameListMap["TALB"].front()->toString();
116   return String::null;
117 }
118
119 String ID3v2::Tag::comment() const
120 {
121   const FrameList &comments = d->frameListMap["COMM"];
122
123   if(comments.isEmpty())
124     return String::null;
125
126   for(FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it)
127   {
128     CommentsFrame *frame = dynamic_cast<CommentsFrame *>(*it);
129
130     if(frame && frame->description().isEmpty())
131       return (*it)->toString();
132   }
133
134   return comments.front()->toString();
135 }
136
137 String ID3v2::Tag::genre() const
138 {
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 " ".
142
143   if(d->frameListMap["TCON"].isEmpty() ||
144      !dynamic_cast<TextIdentificationFrame *>(d->frameListMap["TCON"].front()))
145   {
146     return String::null;
147   }
148
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
153   // string is built.
154
155   TextIdentificationFrame *f = static_cast<TextIdentificationFrame *>(
156     d->frameListMap["TCON"].front());
157
158   StringList fields = f->fieldList();
159
160   StringList genres;
161
162   for(StringList::Iterator it = fields.begin(); it != fields.end(); ++it) {
163
164     if((*it).isEmpty())
165       continue;
166
167     bool ok;
168     int number = (*it).toInt(&ok);
169     if(ok && number >= 0 && number <= 255) {
170       *it = ID3v1::genre(number);
171     }
172
173     if(std::find(genres.begin(), genres.end(), *it) == genres.end())
174       genres.append(*it);
175   }
176
177   return genres.toString();
178 }
179
180 TagLib::uint ID3v2::Tag::year() const
181 {
182   if(!d->frameListMap["TDRC"].isEmpty())
183     return d->frameListMap["TDRC"].front()->toString().substr(0, 4).toInt();
184   return 0;
185 }
186
187 TagLib::uint ID3v2::Tag::track() const
188 {
189   if(!d->frameListMap["TRCK"].isEmpty())
190     return d->frameListMap["TRCK"].front()->toString().toInt();
191   return 0;
192 }
193
194 void ID3v2::Tag::setTitle(const String &s)
195 {
196   setTextFrame("TIT2", s);
197 }
198
199 void ID3v2::Tag::setArtist(const String &s)
200 {
201   setTextFrame("TPE1", s);
202 }
203
204 void ID3v2::Tag::setAlbum(const String &s)
205 {
206   setTextFrame("TALB", s);
207 }
208
209 void ID3v2::Tag::setComment(const String &s)
210 {
211   if(s.isEmpty()) {
212     removeFrames("COMM");
213     return;
214   }
215
216   if(!d->frameListMap["COMM"].isEmpty())
217     d->frameListMap["COMM"].front()->setText(s);
218   else {
219     CommentsFrame *f = new CommentsFrame(d->factory->defaultTextEncoding());
220     addFrame(f);
221     f->setText(s);
222   }
223 }
224
225 void ID3v2::Tag::setGenre(const String &s)
226 {
227   if(s.isEmpty()) {
228     removeFrames("TCON");
229     return;
230   }
231
232   // iTunes can't handle correctly encoded ID3v2.4 numerical genres.  Just use
233   // strings until iTunes sucks less.
234
235 #ifdef NO_ITUNES_HACKS
236
237   int index = ID3v1::genreIndex(s);
238
239   if(index != 255)
240     setTextFrame("TCON", String::number(index));
241   else
242     setTextFrame("TCON", s);
243
244 #else
245
246   setTextFrame("TCON", s);
247
248 #endif
249 }
250
251 void ID3v2::Tag::setYear(uint i)
252 {
253   if(i <= 0) {
254     removeFrames("TDRC");
255     return;
256   }
257   setTextFrame("TDRC", String::number(i));
258 }
259
260 void ID3v2::Tag::setTrack(uint i)
261 {
262   if(i <= 0) {
263     removeFrames("TRCK");
264     return;
265   }
266   setTextFrame("TRCK", String::number(i));
267 }
268
269 bool ID3v2::Tag::isEmpty() const
270 {
271   return d->frameList.isEmpty();
272 }
273
274 Header *ID3v2::Tag::header() const
275 {
276   return &(d->header);
277 }
278
279 ExtendedHeader *ID3v2::Tag::extendedHeader() const
280 {
281   return d->extendedHeader;
282 }
283
284 Footer *ID3v2::Tag::footer() const
285 {
286   return d->footer;
287 }
288
289 const FrameListMap &ID3v2::Tag::frameListMap() const
290 {
291   return d->frameListMap;
292 }
293
294 const FrameList &ID3v2::Tag::frameList() const
295 {
296   return d->frameList;
297 }
298
299 const FrameList &ID3v2::Tag::frameList(const ByteVector &frameID) const
300 {
301   return d->frameListMap[frameID];
302 }
303
304 void ID3v2::Tag::addFrame(Frame *frame)
305 {
306   d->frameList.append(frame);
307   d->frameListMap[frame->frameID()].append(frame);
308 }
309
310 void ID3v2::Tag::removeFrame(Frame *frame, bool del)
311 {
312   // remove the frame from the frame list
313   FrameList::Iterator it = d->frameList.find(frame);
314   d->frameList.erase(it);
315
316   // ...and from the frame list map
317   it = d->frameListMap[frame->frameID()].find(frame);
318   d->frameListMap[frame->frameID()].erase(it);
319
320   // ...and delete as desired
321   if(del)
322     delete frame;
323 }
324
325 void ID3v2::Tag::removeFrames(const ByteVector &id)
326 {
327     FrameList l = d->frameListMap[id];
328     for(FrameList::Iterator it = l.begin(); it != l.end(); ++it)
329       removeFrame(*it, true);
330 }
331
332 ByteVector ID3v2::Tag::render() const
333 {
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.
338
339   ByteVector tagData;
340
341   // TODO: Render the extended header.
342
343   // Loop through the frames rendering them and adding them to the tagData.
344
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");
349       continue;
350     }
351     if(!(*it)->header()->tagAlterPreservation())
352       tagData.append((*it)->render());
353   }
354
355   // Compute the amount of padding, and append that to tagData.
356
357   uint paddingSize = 0;
358   uint originalSize = d->header.tagSize();
359
360   if(tagData.size() < originalSize)
361     paddingSize = originalSize - tagData.size();
362   else
363     paddingSize = 1024;
364
365   tagData.append(ByteVector(paddingSize, char(0)));
366
367   // Set the tag size.
368   d->header.setTagSize(tagData.size());
369
370   // TODO: This should eventually include d->footer->render().
371   return d->header.render() + tagData;
372 }
373
374 ////////////////////////////////////////////////////////////////////////////////
375 // protected members
376 ////////////////////////////////////////////////////////////////////////////////
377
378 void ID3v2::Tag::read()
379 {
380   if(d->file && d->file->isOpen()) {
381
382     d->file->seek(d->tagOffset);
383     d->header.setData(d->file->readBlock(Header::size()));
384
385     // if the tag size is 0, then this is an invalid tag (tags must contain at
386     // least one frame)
387
388     if(d->header.tagSize() == 0)
389       return;
390
391     parse(d->file->readBlock(d->header.tagSize()));
392   }
393 }
394
395 void ID3v2::Tag::parse(const ByteVector &origData)
396 {
397   ByteVector data = origData;
398
399   if(d->header.unsynchronisation() && d->header.majorVersion() <= 3)
400     data = SynchData::decode(data);
401
402   uint frameDataPosition = 0;
403   uint frameDataLength = data.size();
404
405   // check for extended header
406
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();
414     }
415   }
416
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
419   // size.
420
421   if(d->header.footerPresent() && Footer::size() <= frameDataLength)
422     frameDataLength -= Footer::size();
423
424   // parse frames
425
426   // Make sure that there is at least enough room in the remaining frame data for
427   // a frame header.
428
429   while(frameDataPosition < frameDataLength - Frame::headerSize(d->header.majorVersion())) {
430
431     // If the next data is position is 0, assume that we've hit the padding
432     // portion of the frame data.
433
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.");
437
438       d->paddingSize = frameDataLength - frameDataPosition;
439       return;
440     }
441
442     Frame *frame = d->factory->createFrame(data.mid(frameDataPosition),
443                                            &d->header);
444
445     if(!frame)
446       return;
447
448     // Checks to make sure that frame parsed correctly.
449
450     if(frame->size() <= 0) {
451       delete frame;
452       return;
453     }
454
455     frameDataPosition += frame->size() + Frame::headerSize(d->header.majorVersion());
456     addFrame(frame);
457   }
458 }
459
460 void ID3v2::Tag::setTextFrame(const ByteVector &id, const String &value)
461 {
462   if(value.isEmpty()) {
463     removeFrames(id);
464     return;
465   }
466
467   if(!d->frameListMap[id].isEmpty())
468     d->frameListMap[id].front()->setText(value);
469   else {
470     const String::Type encoding = d->factory->defaultTextEncoding();
471     TextIdentificationFrame *f = new TextIdentificationFrame(id, encoding);
472     addFrame(f);
473     f->setText(value);
474   }
475 }