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 ***************************************************************************/
34 #include "id3v2framefactory.h"
35 #include "id3v2synchdata.h"
36 #include "id3v1genres.h"
38 #include "frames/attachedpictureframe.h"
39 #include "frames/commentsframe.h"
40 #include "frames/relativevolumeframe.h"
41 #include "frames/textidentificationframe.h"
42 #include "frames/uniquefileidentifierframe.h"
43 #include "frames/unknownframe.h"
44 #include "frames/generalencapsulatedobjectframe.h"
45 #include "frames/urllinkframe.h"
46 #include "frames/unsynchronizedlyricsframe.h"
47 #include "frames/popularimeterframe.h"
48 #include "frames/privateframe.h"
50 using namespace TagLib;
51 using namespace ID3v2;
53 class FrameFactory::FrameFactoryPrivate
56 FrameFactoryPrivate() :
57 defaultEncoding(String::Latin1),
58 useDefaultEncoding(false) {}
60 String::Type defaultEncoding;
61 bool useDefaultEncoding;
63 template <class T> void setTextEncoding(T *frame)
65 if(useDefaultEncoding)
66 frame->setTextEncoding(defaultEncoding);
70 FrameFactory *FrameFactory::factory = 0;
72 ////////////////////////////////////////////////////////////////////////////////
74 ////////////////////////////////////////////////////////////////////////////////
76 FrameFactory *FrameFactory::instance()
79 factory = new FrameFactory;
83 Frame *FrameFactory::createFrame(const ByteVector &data, bool synchSafeInts) const
85 return createFrame(data, uint(synchSafeInts ? 4 : 3));
88 Frame *FrameFactory::createFrame(const ByteVector &data, uint version) const
91 tagHeader.setMajorVersion(version);
92 return createFrame(data, &tagHeader);
95 Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) const
97 ByteVector data = origData;
98 uint version = tagHeader->majorVersion();
99 Frame::Header *header = new Frame::Header(data, version);
100 ByteVector frameID = header->frameID();
102 // A quick sanity check -- make sure that the frameID is 4 uppercase Latin1
103 // characters. Also make sure that there is data in the frame.
105 if(!frameID.size() == (version < 3 ? 3 : 4) ||
106 header->frameSize() <= uint(header->dataLengthIndicator() ? 4 : 0) ||
107 header->frameSize() > data.size())
113 for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) {
114 if( (*it < 'A' || *it > 'Z') && (*it < '1' || *it > '9') ) {
120 if(version > 3 && (tagHeader->unsynchronisation() || header->unsynchronisation())) {
121 // Data lengths are not part of the encoded data, but since they are synch-safe
122 // integers they will be never actually encoded.
123 ByteVector frameData = data.mid(Frame::Header::size(version), header->frameSize());
124 frameData = SynchData::decode(frameData);
125 data = data.mid(0, Frame::Header::size(version)) + frameData;
128 // TagLib doesn't mess with encrypted frames, so just treat them
129 // as unknown frames.
132 if(header->compression()) {
133 debug("Compressed frames are currently not supported.");
134 return new UnknownFrame(data, header);
137 if(header->encryption()) {
138 debug("Encrypted frames are currently not supported.");
139 return new UnknownFrame(data, header);
142 if(!updateFrame(header)) {
143 header->setTagAlterPreservation(true);
144 return new UnknownFrame(data, header);
147 // updateFrame() might have updated the frame ID.
149 frameID = header->frameID();
151 // This is where things get necissarily nasty. Here we determine which
152 // Frame subclass (or if none is found simply an Frame) based
153 // on the frame ID. Since there are a lot of possibilities, that means
154 // a lot of if blocks.
156 // Text Identification (frames 4.2)
158 if(frameID.startsWith("T")) {
160 TextIdentificationFrame *f = frameID != "TXXX"
161 ? new TextIdentificationFrame(data, header)
162 : new UserTextIdentificationFrame(data, header);
164 d->setTextEncoding(f);
166 if(frameID == "TCON")
172 // Comments (frames 4.10)
174 if(frameID == "COMM") {
175 CommentsFrame *f = new CommentsFrame(data, header);
176 d->setTextEncoding(f);
180 // Attached Picture (frames 4.14)
182 if(frameID == "APIC") {
183 AttachedPictureFrame *f = new AttachedPictureFrame(data, header);
184 d->setTextEncoding(f);
188 // ID3v2.2 Attached Picture
190 if(frameID == "PIC") {
191 AttachedPictureFrame *f = new AttachedPictureFrameV22(data, header);
192 d->setTextEncoding(f);
196 // Relative Volume Adjustment (frames 4.11)
198 if(frameID == "RVA2")
199 return new RelativeVolumeFrame(data, header);
201 // Unique File Identifier (frames 4.1)
203 if(frameID == "UFID")
204 return new UniqueFileIdentifierFrame(data, header);
206 // General Encapsulated Object (frames 4.15)
208 if(frameID == "GEOB") {
209 GeneralEncapsulatedObjectFrame *f = new GeneralEncapsulatedObjectFrame(data, header);
210 d->setTextEncoding(f);
214 // URL link (frames 4.3)
216 if(frameID.startsWith("W")) {
217 if(frameID != "WXXX") {
218 return new UrlLinkFrame(data, header);
221 UserUrlLinkFrame *f = new UserUrlLinkFrame(data, header);
222 d->setTextEncoding(f);
227 // Unsynchronized lyric/text transcription (frames 4.8)
229 if(frameID == "USLT") {
230 UnsynchronizedLyricsFrame *f = new UnsynchronizedLyricsFrame(data, header);
231 if(d->useDefaultEncoding)
232 f->setTextEncoding(d->defaultEncoding);
236 // Popularimeter (frames 4.17)
238 if(frameID == "POPM")
239 return new PopularimeterFrame(data, header);
241 // Private (frames 4.27)
243 if(frameID == "PRIV")
244 return new PrivateFrame(data, header);
246 return new UnknownFrame(data, header);
249 String::Type FrameFactory::defaultTextEncoding() const
251 return d->defaultEncoding;
254 void FrameFactory::setDefaultTextEncoding(String::Type encoding)
256 d->useDefaultEncoding = true;
257 d->defaultEncoding = encoding;
260 ////////////////////////////////////////////////////////////////////////////////
262 ////////////////////////////////////////////////////////////////////////////////
264 FrameFactory::FrameFactory()
266 d = new FrameFactoryPrivate;
269 FrameFactory::~FrameFactory()
274 bool FrameFactory::updateFrame(Frame::Header *header) const
276 TagLib::ByteVector frameID = header->frameID();
278 switch(header->version()) {
282 if(frameID == "CRM" ||
290 debug("ID3v2.4 no longer supports the frame type " + String(frameID) +
291 ". It will be discarded from the tag.");
295 // ID3v2.2 only used 3 bytes for the frame ID, so we need to convert all of
296 // the frames to their 4 byte ID3v2.4 equivalent.
298 convertFrame("BUF", "RBUF", header);
299 convertFrame("CNT", "PCNT", header);
300 convertFrame("COM", "COMM", header);
301 convertFrame("CRA", "AENC", header);
302 convertFrame("ETC", "ETCO", header);
303 convertFrame("GEO", "GEOB", header);
304 convertFrame("IPL", "TIPL", header);
305 convertFrame("MCI", "MCDI", header);
306 convertFrame("MLL", "MLLT", header);
307 convertFrame("POP", "POPM", header);
308 convertFrame("REV", "RVRB", header);
309 convertFrame("SLT", "SYLT", header);
310 convertFrame("STC", "SYTC", header);
311 convertFrame("TAL", "TALB", header);
312 convertFrame("TBP", "TBPM", header);
313 convertFrame("TCM", "TCOM", header);
314 convertFrame("TCO", "TCON", header);
315 convertFrame("TCR", "TCOP", header);
316 convertFrame("TDY", "TDLY", header);
317 convertFrame("TEN", "TENC", header);
318 convertFrame("TFT", "TFLT", header);
319 convertFrame("TKE", "TKEY", header);
320 convertFrame("TLA", "TLAN", header);
321 convertFrame("TLE", "TLEN", header);
322 convertFrame("TMT", "TMED", header);
323 convertFrame("TOA", "TOAL", header);
324 convertFrame("TOF", "TOFN", header);
325 convertFrame("TOL", "TOLY", header);
326 convertFrame("TOR", "TDOR", header);
327 convertFrame("TOT", "TOAL", header);
328 convertFrame("TP1", "TPE1", header);
329 convertFrame("TP2", "TPE2", header);
330 convertFrame("TP3", "TPE3", header);
331 convertFrame("TP4", "TPE4", header);
332 convertFrame("TPA", "TPOS", header);
333 convertFrame("TPB", "TPUB", header);
334 convertFrame("TRC", "TSRC", header);
335 convertFrame("TRD", "TDRC", header);
336 convertFrame("TRK", "TRCK", header);
337 convertFrame("TSS", "TSSE", header);
338 convertFrame("TT1", "TIT1", header);
339 convertFrame("TT2", "TIT2", header);
340 convertFrame("TT3", "TIT3", header);
341 convertFrame("TXT", "TOLY", header);
342 convertFrame("TXX", "TXXX", header);
343 convertFrame("TYE", "TDRC", header);
344 convertFrame("UFI", "UFID", header);
345 convertFrame("ULT", "USLT", header);
346 convertFrame("WAF", "WOAF", header);
347 convertFrame("WAR", "WOAR", header);
348 convertFrame("WAS", "WOAS", header);
349 convertFrame("WCM", "WCOM", header);
350 convertFrame("WCP", "WCOP", header);
351 convertFrame("WPB", "WPUB", header);
352 convertFrame("WXX", "WXXX", header);
359 if(frameID == "EQUA" ||
366 debug("ID3v2.4 no longer supports the frame type " + String(frameID) +
367 ". It will be discarded from the tag.");
371 convertFrame("TORY", "TDOR", header);
372 convertFrame("TYER", "TDRC", header);
379 // This should catch a typo that existed in TagLib up to and including
380 // version 1.1 where TRDC was used for the year rather than TDRC.
382 convertFrame("TRDC", "TDRC", header);
389 ////////////////////////////////////////////////////////////////////////////////
391 ////////////////////////////////////////////////////////////////////////////////
393 void FrameFactory::convertFrame(const char *from, const char *to,
394 Frame::Header *header) const
396 if(header->frameID() != from)
399 // debug("ID3v2.4 no longer supports the frame type " + String(from) + " It has" +
400 // "been converted to the type " + String(to) + ".");
402 header->setFrameID(to);
405 void FrameFactory::updateGenre(TextIdentificationFrame *frame) const
407 StringList fields = frame->fieldList();
408 StringList newfields;
410 for(StringList::Iterator it = fields.begin(); it != fields.end(); ++it) {
412 int end = s.find(")");
414 if(s.startsWith("(") && end > 0) {
416 String text = s.substr(end + 1);
418 int number = s.substr(1, end - 1).toInt(&ok);
419 if(ok && number >= 0 && number <= 255 && !(ID3v1::genre(number) == text))
420 newfields.append(s.substr(1, end - 1));
422 newfields.append(text);
430 if(newfields.isEmpty())
431 fields.append(String::null);
433 frame->setText(newfields);