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