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 <algorithm>
27 
28 #include <tfile.h>
29 #include <tbytevector.h>
30 #include <tpropertymap.h>
31 #include <tdebug.h>
32 
33 #include "id3v2tag.h"
34 #include "id3v2header.h"
35 #include "id3v2extendedheader.h"
36 #include "id3v2footer.h"
37 #include "id3v2synchdata.h"
38 #include "id3v1genres.h"
39 
40 #include "frames/textidentificationframe.h"
41 #include "frames/commentsframe.h"
42 #include "frames/urllinkframe.h"
43 #include "frames/uniquefileidentifierframe.h"
44 #include "frames/unsynchronizedlyricsframe.h"
45 #include "frames/unknownframe.h"
46 
47 using namespace TagLib;
48 using namespace ID3v2;
49 
50 namespace
51 {
52   const ID3v2::Latin1StringHandler defaultStringHandler;
53   const ID3v2::Latin1StringHandler *stringHandler = &defaultStringHandler;
54 
55   const long MinPaddingSize = 1024;
56   const long MaxPaddingSize = 1024 * 1024;
57 }
58 
59 class ID3v2::Tag::TagPrivate
60 {
61 public:
TagPrivate()62   TagPrivate() :
63     factory(0),
64     file(0),
65     tagOffset(0),
66     extendedHeader(0),
67     footer(0)
68   {
69     frameList.setAutoDelete(true);
70   }
71 
~TagPrivate()72   ~TagPrivate()
73   {
74     delete extendedHeader;
75     delete footer;
76   }
77 
78   const FrameFactory *factory;
79 
80   File *file;
81   long tagOffset;
82 
83   Header header;
84   ExtendedHeader *extendedHeader;
85   Footer *footer;
86 
87   FrameListMap frameListMap;
88   FrameList frameList;
89 };
90 
91 ////////////////////////////////////////////////////////////////////////////////
92 // StringHandler implementation
93 ////////////////////////////////////////////////////////////////////////////////
94 
Latin1StringHandler()95 Latin1StringHandler::Latin1StringHandler()
96 {
97 }
98 
~Latin1StringHandler()99 Latin1StringHandler::~Latin1StringHandler()
100 {
101 }
102 
parse(const ByteVector & data) const103 String Latin1StringHandler::parse(const ByteVector &data) const
104 {
105   return String(data, String::Latin1);
106 }
107 
108 ////////////////////////////////////////////////////////////////////////////////
109 // public members
110 ////////////////////////////////////////////////////////////////////////////////
111 
Tag()112 ID3v2::Tag::Tag() :
113   TagLib::Tag(),
114   d(new TagPrivate())
115 {
116   d->factory = FrameFactory::instance();
117 }
118 
Tag(File * file,long tagOffset,const FrameFactory * factory)119 ID3v2::Tag::Tag(File *file, long tagOffset, const FrameFactory *factory) :
120   TagLib::Tag(),
121   d(new TagPrivate())
122 {
123   d->factory = factory;
124   d->file = file;
125   d->tagOffset = tagOffset;
126 
127   read();
128 }
129 
~Tag()130 ID3v2::Tag::~Tag()
131 {
132   delete d;
133 }
134 
title() const135 String ID3v2::Tag::title() const
136 {
137   if(!d->frameListMap["TIT2"].isEmpty())
138     return d->frameListMap["TIT2"].front()->toString();
139   return String();
140 }
141 
artist() const142 String ID3v2::Tag::artist() const
143 {
144   if(!d->frameListMap["TPE1"].isEmpty())
145     return d->frameListMap["TPE1"].front()->toString();
146   return String();
147 }
148 
album() const149 String ID3v2::Tag::album() const
150 {
151   if(!d->frameListMap["TALB"].isEmpty())
152     return d->frameListMap["TALB"].front()->toString();
153   return String();
154 }
155 
comment() const156 String ID3v2::Tag::comment() const
157 {
158   const FrameList &comments = d->frameListMap["COMM"];
159 
160   if(comments.isEmpty())
161     return String();
162 
163   for(FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it)
164   {
165     CommentsFrame *frame = dynamic_cast<CommentsFrame *>(*it);
166 
167     if(frame && frame->description().isEmpty())
168       return (*it)->toString();
169   }
170 
171   return comments.front()->toString();
172 }
173 
genre() const174 String ID3v2::Tag::genre() const
175 {
176   // TODO: In the next major version (TagLib 2.0) a list of multiple genres
177   // should be separated by " / " instead of " ".  For the moment to keep
178   // the behavior the same as released versions it is being left with " ".
179 
180   if(d->frameListMap["TCON"].isEmpty() ||
181      !dynamic_cast<TextIdentificationFrame *>(d->frameListMap["TCON"].front()))
182   {
183     return String();
184   }
185 
186   // ID3v2.4 lists genres as the fields in its frames field list.  If the field
187   // is simply a number it can be assumed that it is an ID3v1 genre number.
188   // Here was assume that if an ID3v1 string is present that it should be
189   // appended to the genre string.  Multiple fields will be appended as the
190   // string is built.
191 
192   TextIdentificationFrame *f = static_cast<TextIdentificationFrame *>(
193     d->frameListMap["TCON"].front());
194 
195   StringList fields = f->fieldList();
196 
197   StringList genres;
198 
199   for(StringList::Iterator it = fields.begin(); it != fields.end(); ++it) {
200 
201     if((*it).isEmpty())
202       continue;
203 
204     bool ok;
205     int number = (*it).toInt(&ok);
206     if(ok && number >= 0 && number <= 255) {
207       *it = ID3v1::genre(number);
208     }
209 
210     if(std::find(genres.begin(), genres.end(), *it) == genres.end())
211       genres.append(*it);
212   }
213 
214   return genres.toString();
215 }
216 
year() const217 unsigned int ID3v2::Tag::year() const
218 {
219   if(!d->frameListMap["TDRC"].isEmpty())
220     return d->frameListMap["TDRC"].front()->toString().substr(0, 4).toInt();
221   return 0;
222 }
223 
track() const224 unsigned int ID3v2::Tag::track() const
225 {
226   if(!d->frameListMap["TRCK"].isEmpty())
227     return d->frameListMap["TRCK"].front()->toString().toInt();
228   return 0;
229 }
230 
setTitle(const String & s)231 void ID3v2::Tag::setTitle(const String &s)
232 {
233   setTextFrame("TIT2", s);
234 }
235 
setArtist(const String & s)236 void ID3v2::Tag::setArtist(const String &s)
237 {
238   setTextFrame("TPE1", s);
239 }
240 
setAlbum(const String & s)241 void ID3v2::Tag::setAlbum(const String &s)
242 {
243   setTextFrame("TALB", s);
244 }
245 
setComment(const String & s)246 void ID3v2::Tag::setComment(const String &s)
247 {
248   if(s.isEmpty()) {
249     removeFrames("COMM");
250     return;
251   }
252 
253   const FrameList &comments = d->frameListMap["COMM"];
254 
255   if(!comments.isEmpty()) {
256     for(FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it) {
257       CommentsFrame *frame = dynamic_cast<CommentsFrame *>(*it);
258       if(frame && frame->description().isEmpty()) {
259         (*it)->setText(s);
260         return;
261       }
262     }
263 
264     comments.front()->setText(s);
265     return;
266   }
267 
268   CommentsFrame *f = new CommentsFrame(d->factory->defaultTextEncoding());
269   addFrame(f);
270   f->setText(s);
271 }
272 
setGenre(const String & s)273 void ID3v2::Tag::setGenre(const String &s)
274 {
275   if(s.isEmpty()) {
276     removeFrames("TCON");
277     return;
278   }
279 
280   // iTunes can't handle correctly encoded ID3v2.4 numerical genres.  Just use
281   // strings until iTunes sucks less.
282 
283 #ifdef NO_ITUNES_HACKS
284 
285   int index = ID3v1::genreIndex(s);
286 
287   if(index != 255)
288     setTextFrame("TCON", String::number(index));
289   else
290     setTextFrame("TCON", s);
291 
292 #else
293 
294   setTextFrame("TCON", s);
295 
296 #endif
297 }
298 
setYear(unsigned int i)299 void ID3v2::Tag::setYear(unsigned int i)
300 {
301   if(i == 0) {
302     removeFrames("TDRC");
303     return;
304   }
305   setTextFrame("TDRC", String::number(i));
306 }
307 
setTrack(unsigned int i)308 void ID3v2::Tag::setTrack(unsigned int i)
309 {
310   if(i == 0) {
311     removeFrames("TRCK");
312     return;
313   }
314   setTextFrame("TRCK", String::number(i));
315 }
316 
isEmpty() const317 bool ID3v2::Tag::isEmpty() const
318 {
319   return d->frameList.isEmpty();
320 }
321 
header() const322 Header *ID3v2::Tag::header() const
323 {
324   return &(d->header);
325 }
326 
extendedHeader() const327 ExtendedHeader *ID3v2::Tag::extendedHeader() const
328 {
329   return d->extendedHeader;
330 }
331 
footer() const332 Footer *ID3v2::Tag::footer() const
333 {
334   return d->footer;
335 }
336 
frameListMap() const337 const FrameListMap &ID3v2::Tag::frameListMap() const
338 {
339   return d->frameListMap;
340 }
341 
frameList() const342 const FrameList &ID3v2::Tag::frameList() const
343 {
344   return d->frameList;
345 }
346 
frameList(const ByteVector & frameID) const347 const FrameList &ID3v2::Tag::frameList(const ByteVector &frameID) const
348 {
349   return d->frameListMap[frameID];
350 }
351 
addFrame(Frame * frame)352 void ID3v2::Tag::addFrame(Frame *frame)
353 {
354   d->frameList.append(frame);
355   d->frameListMap[frame->frameID()].append(frame);
356 }
357 
removeFrame(Frame * frame,bool del)358 void ID3v2::Tag::removeFrame(Frame *frame, bool del)
359 {
360   // remove the frame from the frame list
361   FrameList::Iterator it = d->frameList.find(frame);
362   d->frameList.erase(it);
363 
364   // ...and from the frame list map
365   it = d->frameListMap[frame->frameID()].find(frame);
366   d->frameListMap[frame->frameID()].erase(it);
367 
368   // ...and delete as desired
369   if(del)
370     delete frame;
371 }
372 
removeFrames(const ByteVector & id)373 void ID3v2::Tag::removeFrames(const ByteVector &id)
374 {
375   FrameList l = d->frameListMap[id];
376   for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
377     removeFrame(*it, true);
378 }
379 
properties() const380 PropertyMap ID3v2::Tag::properties() const
381 {
382   PropertyMap properties;
383   for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it) {
384     PropertyMap props = (*it)->asProperties();
385     properties.merge(props);
386   }
387   return properties;
388 }
389 
removeUnsupportedProperties(const StringList & properties)390 void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties)
391 {
392   for(StringList::ConstIterator it = properties.begin(); it != properties.end(); ++it){
393     if(it->startsWith("UNKNOWN/")) {
394       String frameID = it->substr(String("UNKNOWN/").size());
395       if(frameID.size() != 4)
396         continue; // invalid specification
397       ByteVector id = frameID.data(String::Latin1);
398       // delete all unknown frames of given type
399       FrameList l = frameList(id);
400       for(FrameList::ConstIterator fit = l.begin(); fit != l.end(); fit++)
401         if (dynamic_cast<const UnknownFrame *>(*fit) != 0)
402           removeFrame(*fit);
403     }
404     else if(it->size() == 4){
405       ByteVector id = it->data(String::Latin1);
406       removeFrames(id);
407     }
408     else {
409       ByteVector id = it->substr(0,4).data(String::Latin1);
410       if(it->size() <= 5)
411         continue; // invalid specification
412       String description = it->substr(5);
413       Frame *frame = 0;
414       if(id == "TXXX")
415         frame = UserTextIdentificationFrame::find(this, description);
416       else if(id == "WXXX")
417         frame = UserUrlLinkFrame::find(this, description);
418       else if(id == "COMM")
419         frame = CommentsFrame::findByDescription(this, description);
420       else if(id == "USLT")
421         frame = UnsynchronizedLyricsFrame::findByDescription(this, description);
422       else if(id == "UFID")
423         frame = UniqueFileIdentifierFrame::findByOwner(this, description);
424       if(frame)
425         removeFrame(frame);
426     }
427   }
428 }
429 
setProperties(const PropertyMap & origProps)430 PropertyMap ID3v2::Tag::setProperties(const PropertyMap &origProps)
431 {
432   FrameList framesToDelete;
433   // we split up the PropertyMap into the "normal" keys and the "complicated" ones,
434   // which are those according to TIPL or TMCL frames.
435   PropertyMap properties;
436   PropertyMap tiplProperties;
437   PropertyMap tmclProperties;
438   Frame::splitProperties(origProps, properties, tiplProperties, tmclProperties);
439   for(FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it){
440     for(FrameList::ConstIterator lit = it->second.begin(); lit != it->second.end(); ++lit){
441       PropertyMap frameProperties = (*lit)->asProperties();
442       if(it->first == "TIPL") {
443         if (tiplProperties != frameProperties)
444           framesToDelete.append(*lit);
445         else
446           tiplProperties.erase(frameProperties);
447       } else if(it->first == "TMCL") {
448         if (tmclProperties != frameProperties)
449           framesToDelete.append(*lit);
450         else
451           tmclProperties.erase(frameProperties);
452       } else if(!properties.contains(frameProperties))
453         framesToDelete.append(*lit);
454       else
455         properties.erase(frameProperties);
456     }
457   }
458   for(FrameList::ConstIterator it = framesToDelete.begin(); it != framesToDelete.end(); ++it)
459     removeFrame(*it);
460 
461   // now create remaining frames:
462   // start with the involved people list (TIPL)
463   if(!tiplProperties.isEmpty())
464       addFrame(TextIdentificationFrame::createTIPLFrame(tiplProperties));
465   // proceed with the musician credit list (TMCL)
466   if(!tmclProperties.isEmpty())
467       addFrame(TextIdentificationFrame::createTMCLFrame(tmclProperties));
468   // now create the "one key per frame" frames
469   for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it)
470     addFrame(Frame::createTextualFrame(it->first, it->second));
471   return PropertyMap(); // ID3 implements the complete PropertyMap interface, so an empty map is returned
472 }
473 
render() const474 ByteVector ID3v2::Tag::render() const
475 {
476   return render(ID3v2::v4);
477 }
478 
downgradeFrames(FrameList * frames,FrameList * newFrames) const479 void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
480 {
481 #ifdef NO_ITUNES_HACKS
482   const char *unsupportedFrames[] = {
483     "ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG",
484     "TMOO", "TPRO", "TSOA", "TSOT", "TSST", "TSOP", 0
485   };
486 #else
487   // iTunes writes and reads TSOA, TSOT, TSOP to ID3v2.3.
488   const char *unsupportedFrames[] = {
489     "ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG",
490     "TMOO", "TPRO", "TSST", 0
491   };
492 #endif
493   ID3v2::TextIdentificationFrame *frameTDOR = 0;
494   ID3v2::TextIdentificationFrame *frameTDRC = 0;
495   ID3v2::TextIdentificationFrame *frameTIPL = 0;
496   ID3v2::TextIdentificationFrame *frameTMCL = 0;
497   for(FrameList::ConstIterator it = d->frameList.begin(); it != d->frameList.end(); it++) {
498     ID3v2::Frame *frame = *it;
499     ByteVector frameID = frame->header()->frameID();
500     for(int i = 0; unsupportedFrames[i]; i++) {
501       if(frameID == unsupportedFrames[i]) {
502         debug("A frame that is not supported in ID3v2.3 \'"
503           + String(frameID) + "\' has been discarded");
504         frame = 0;
505         break;
506       }
507     }
508     if(frame && frameID == "TDOR") {
509       frameTDOR = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame);
510       frame = 0;
511     }
512     if(frame && frameID == "TDRC") {
513       frameTDRC = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame);
514       frame = 0;
515     }
516     if(frame && frameID == "TIPL") {
517       frameTIPL = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame);
518       frame = 0;
519     }
520     if(frame && frameID == "TMCL") {
521       frameTMCL = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame);
522       frame = 0;
523     }
524     if(frame) {
525       frames->append(frame);
526     }
527   }
528   if(frameTDOR) {
529     String content = frameTDOR->toString();
530     if(content.size() >= 4) {
531       ID3v2::TextIdentificationFrame *frameTORY = new ID3v2::TextIdentificationFrame("TORY", String::Latin1);
532       frameTORY->setText(content.substr(0, 4));
533       frames->append(frameTORY);
534       newFrames->append(frameTORY);
535     }
536   }
537   if(frameTDRC) {
538     String content = frameTDRC->toString();
539     if(content.size() >= 4) {
540       ID3v2::TextIdentificationFrame *frameTYER = new ID3v2::TextIdentificationFrame("TYER", String::Latin1);
541       frameTYER->setText(content.substr(0, 4));
542       frames->append(frameTYER);
543       newFrames->append(frameTYER);
544       if(content.size() >= 10 && content[4] == '-' && content[7] == '-') {
545         ID3v2::TextIdentificationFrame *frameTDAT = new ID3v2::TextIdentificationFrame("TDAT", String::Latin1);
546         frameTDAT->setText(content.substr(8, 2) + content.substr(5, 2));
547         frames->append(frameTDAT);
548         newFrames->append(frameTDAT);
549         if(content.size() >= 16 && content[10] == 'T' && content[13] == ':') {
550           ID3v2::TextIdentificationFrame *frameTIME = new ID3v2::TextIdentificationFrame("TIME", String::Latin1);
551           frameTIME->setText(content.substr(11, 2) + content.substr(14, 2));
552           frames->append(frameTIME);
553           newFrames->append(frameTIME);
554         }
555       }
556     }
557   }
558   if(frameTIPL || frameTMCL) {
559     ID3v2::TextIdentificationFrame *frameIPLS = new ID3v2::TextIdentificationFrame("IPLS", String::Latin1);
560     StringList people;
561     if(frameTMCL) {
562       StringList v24People = frameTMCL->fieldList();
563       for(unsigned int i = 0; i + 1 < v24People.size(); i += 2) {
564         people.append(v24People[i]);
565         people.append(v24People[i+1]);
566       }
567     }
568     if(frameTIPL) {
569       StringList v24People = frameTIPL->fieldList();
570       for(unsigned int i = 0; i + 1 < v24People.size(); i += 2) {
571         people.append(v24People[i]);
572         people.append(v24People[i+1]);
573       }
574     }
575     frameIPLS->setText(people);
576     frames->append(frameIPLS);
577     newFrames->append(frameIPLS);
578   }
579 }
580 
render(int version) const581 ByteVector ID3v2::Tag::render(int version) const
582 {
583   return render(version == 3 ? v3 : v4);
584 }
585 
render(Version version) const586 ByteVector ID3v2::Tag::render(Version version) const
587 {
588   // We need to render the "tag data" first so that we have to correct size to
589   // render in the tag's header.  The "tag data" -- everything that is included
590   // in ID3v2::Header::tagSize() -- includes the extended header, frames and
591   // padding, but does not include the tag's header or footer.
592 
593   // TODO: Render the extended header.
594 
595   // Downgrade the frames that ID3v2.3 doesn't support.
596 
597   FrameList newFrames;
598   newFrames.setAutoDelete(true);
599 
600   FrameList frameList;
601   if(version == v4) {
602     frameList = d->frameList;
603   }
604   else {
605     downgradeFrames(&frameList, &newFrames);
606   }
607 
608   // Reserve a 10-byte blank space for an ID3v2 tag header.
609 
610   ByteVector tagData(Header::size(), '\0');
611 
612   // Loop through the frames rendering them and adding them to the tagData.
613 
614   for(FrameList::ConstIterator it = frameList.begin(); it != frameList.end(); it++) {
615     (*it)->header()->setVersion(version == v3 ? 3 : 4);
616     if((*it)->header()->frameID().size() != 4) {
617       debug("An ID3v2 frame of unsupported or unknown type \'"
618           + String((*it)->header()->frameID()) + "\' has been discarded");
619       continue;
620     }
621     if(!(*it)->header()->tagAlterPreservation()) {
622       const ByteVector frameData = (*it)->render();
623       if(frameData.size() == Frame::headerSize((*it)->header()->version())) {
624         debug("An empty ID3v2 frame \'"
625           + String((*it)->header()->frameID()) + "\' has been discarded");
626         continue;
627       }
628       tagData.append(frameData);
629     }
630   }
631 
632   // Compute the amount of padding, and append that to tagData.
633 
634   long originalSize = d->header.tagSize();
635   long paddingSize = originalSize - (tagData.size() - Header::size());
636 
637   if(paddingSize <= 0) {
638     paddingSize = MinPaddingSize;
639   }
640   else {
641     // Padding won't increase beyond 1% of the file size or 1MB.
642 
643     long threshold = d->file ? d->file->length() / 100 : 0;
644     threshold = std::max(threshold, MinPaddingSize);
645     threshold = std::min(threshold, MaxPaddingSize);
646 
647     if(paddingSize > threshold)
648       paddingSize = MinPaddingSize;
649   }
650 
651   tagData.resize(static_cast<unsigned int>(tagData.size() + paddingSize), '\0');
652 
653   // Set the version and data size.
654   d->header.setMajorVersion(version);
655   d->header.setTagSize(tagData.size() - Header::size());
656 
657   // TODO: This should eventually include d->footer->render().
658   const ByteVector headerData = d->header.render();
659   std::copy(headerData.begin(), headerData.end(), tagData.begin());
660 
661   return tagData;
662 }
663 
latin1StringHandler()664 Latin1StringHandler const *ID3v2::Tag::latin1StringHandler()
665 {
666   return stringHandler;
667 }
668 
setLatin1StringHandler(const Latin1StringHandler * handler)669 void ID3v2::Tag::setLatin1StringHandler(const Latin1StringHandler *handler)
670 {
671   if(handler)
672     stringHandler = handler;
673   else
674     stringHandler = &defaultStringHandler;
675 }
676 
677 ////////////////////////////////////////////////////////////////////////////////
678 // protected members
679 ////////////////////////////////////////////////////////////////////////////////
680 
read()681 void ID3v2::Tag::read()
682 {
683   if(!d->file)
684     return;
685 
686   if(!d->file->isOpen())
687     return;
688 
689   d->file->seek(d->tagOffset);
690   d->header.setData(d->file->readBlock(Header::size()));
691 
692   // If the tag size is 0, then this is an invalid tag (tags must contain at
693   // least one frame)
694 
695   if(d->header.tagSize() != 0)
696     parse(d->file->readBlock(d->header.tagSize()));
697 
698   // Look for duplicate ID3v2 tags and treat them as an extra blank of this one.
699   // It leads to overwriting them with zero when saving the tag.
700 
701   // This is a workaround for some faulty files that have duplicate ID3v2 tags.
702   // Unfortunately, TagLib itself may write such duplicate tags until v1.10.
703 
704   unsigned int extraSize = 0;
705 
706   while(true) {
707 
708     d->file->seek(d->tagOffset + d->header.completeTagSize() + extraSize);
709 
710     const ByteVector data = d->file->readBlock(Header::size());
711     if(data.size() < Header::size() || !data.startsWith(Header::fileIdentifier()))
712       break;
713 
714     extraSize += Header(data).completeTagSize();
715   }
716 
717   if(extraSize != 0) {
718     debug("ID3v2::Tag::read() - Duplicate ID3v2 tags found.");
719     d->header.setTagSize(d->header.tagSize() + extraSize);
720   }
721 }
722 
parse(const ByteVector & origData)723 void ID3v2::Tag::parse(const ByteVector &origData)
724 {
725   ByteVector data = origData;
726 
727   if(d->header.unsynchronisation() && d->header.majorVersion() <= 3)
728     data = SynchData::decode(data);
729 
730   unsigned int frameDataPosition = 0;
731   unsigned int frameDataLength = data.size();
732 
733   // check for extended header
734 
735   if(d->header.extendedHeader()) {
736     if(!d->extendedHeader)
737       d->extendedHeader = new ExtendedHeader();
738     d->extendedHeader->setData(data);
739     if(d->extendedHeader->size() <= data.size()) {
740       frameDataPosition += d->extendedHeader->size();
741       frameDataLength -= d->extendedHeader->size();
742     }
743   }
744 
745   // check for footer -- we don't actually need to parse it, as it *must*
746   // contain the same data as the header, but we do need to account for its
747   // size.
748 
749   if(d->header.footerPresent() && Footer::size() <= frameDataLength)
750     frameDataLength -= Footer::size();
751 
752   // parse frames
753 
754   // Make sure that there is at least enough room in the remaining frame data for
755   // a frame header.
756 
757   while(frameDataPosition < frameDataLength - Frame::headerSize(d->header.majorVersion())) {
758 
759     // If the next data is position is 0, assume that we've hit the padding
760     // portion of the frame data.
761 
762     if(data.at(frameDataPosition) == 0) {
763       if(d->header.footerPresent()) {
764         debug("Padding *and* a footer found.  This is not allowed by the spec.");
765       }
766 
767       break;
768     }
769 
770     Frame *frame = d->factory->createFrame(data.mid(frameDataPosition),
771                                            &d->header);
772 
773     if(!frame)
774       return;
775 
776     // Checks to make sure that frame parsed correctly.
777 
778     if(frame->size() <= 0) {
779       delete frame;
780       return;
781     }
782 
783     frameDataPosition += frame->size() + Frame::headerSize(d->header.majorVersion());
784     addFrame(frame);
785   }
786 
787   d->factory->rebuildAggregateFrames(this);
788 }
789 
setTextFrame(const ByteVector & id,const String & value)790 void ID3v2::Tag::setTextFrame(const ByteVector &id, const String &value)
791 {
792   if(value.isEmpty()) {
793     removeFrames(id);
794     return;
795   }
796 
797   if(!d->frameListMap[id].isEmpty())
798     d->frameListMap[id].front()->setText(value);
799   else {
800     const String::Type encoding = d->factory->defaultTextEncoding();
801     TextIdentificationFrame *f = new TextIdentificationFrame(id, encoding);
802     addFrame(f);
803     f->setText(value);
804   }
805 }
806