1 /**
2  * \file mp3file.cpp
3  * Handling of tagged MP3 files.
4  *
5  * \b Project: Kid3
6  * \author Urs Fleisch
7  * \date 9 Jan 2003
8  *
9  * Copyright (C) 2003-2018  Urs Fleisch
10  *
11  * This file is part of Kid3.
12  *
13  * Kid3 is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * Kid3 is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #include "mp3file.h"
28 
29 #include <QDir>
30 #include <QString>
31 #if QT_VERSION >= 0x060000
32 #include <QStringConverter>
33 #else
34 #include <QTextCodec>
35 #endif
36 #include <QByteArray>
37 #include <QtEndian>
38 
39 #include <cstring>
40 #include <id3/tag.h>
41 #ifdef Q_OS_WIN32
42 #include <id3.h>
43 #endif
44 
45 #include "id3libconfig.h"
46 #include "genres.h"
47 #include "attributedata.h"
48 
49 #ifdef Q_OS_WIN32
50 /**
51  * This will be set for id3lib versions with Unicode bugs.
52  * ID3LIB_ symbols cannot be found on Windows ?!
53  */
54 #define UNICODE_SUPPORT_BUGGY 1
55 #else
56 /** This will be set for id3lib versions with Unicode bugs. */
57 #define UNICODE_SUPPORT_BUGGY ((((ID3LIB_MAJOR_VERSION) << 16) + \
58   ((ID3LIB_MINOR_VERSION) << 8) + (ID3LIB_PATCH_VERSION)) <= 0x030803)
59 #endif
60 
61 #if defined __GNUC__ && (__GNUC__ * 100 + __GNUC_MINOR__) >= 407
62 /** Defined if GCC is used and supports diagnostic pragmas */
63 #define GCC_HAS_DIAGNOSTIC_PRAGMA
64 #endif
65 
66 namespace {
67 
68 #if QT_VERSION >= 0x060000
69 /** String decoder for ID3v1 tags, default is ISO 8859-1 */
70 QStringDecoder s_decoderV1;
71 QStringEncoder s_encoderV1;
72 #else
73 /** Text codec for ID3v1 tags, 0 to use default (ISO 8859-1) */
74 const QTextCodec* s_textCodecV1 = nullptr;
75 #endif
76 
77 /** Default text encoding */
78 ID3_TextEnc s_defaultTextEncoding = ID3TE_ISO8859_1;
79 
80 /**
81  * Get the default text encoding.
82  * @return default text encoding.
83  */
getDefaultTextEncoding()84 ID3_TextEnc getDefaultTextEncoding() { return s_defaultTextEncoding; }
85 
86 }
87 
88 /**
89  * Constructor.
90  *
91  * @param idx index in file proxy model
92  */
Mp3File(const QPersistentModelIndex & idx)93 Mp3File::Mp3File(const QPersistentModelIndex& idx)
94   : TaggedFile(idx)
95 {
96 }
97 
98 /**
99  * Destructor.
100  */
~Mp3File()101 Mp3File::~Mp3File()
102 {
103   // Must not be inline because of forwared declared QScopedPointer.
104 }
105 
106 /**
107  * Get key of tagged file format.
108  * @return "Id3libMetadata".
109  */
taggedFileKey() const110 QString Mp3File::taggedFileKey() const
111 {
112   return QLatin1String("Id3libMetadata");
113 }
114 
115 /**
116  * Get features supported.
117  * @return bit mask with Feature flags set.
118  */
taggedFileFeatures() const119 int Mp3File::taggedFileFeatures() const
120 {
121   return TF_ID3v11 | TF_ID3v23;
122 }
123 
124 /**
125  * Read tags from file.
126  *
127  * @param force true to force reading even if tags were already read.
128  */
readTags(bool force)129 void Mp3File::readTags(bool force)
130 {
131   bool priorIsTagInformationRead = isTagInformationRead();
132   QByteArray fn = QFile::encodeName(currentFilePath());
133 
134   if (force && m_tagV1) {
135     m_tagV1->Clear();
136     m_tagV1->Link(fn, ID3TT_ID3V1);
137     markTagUnchanged(Frame::Tag_1);
138   }
139   if (!m_tagV1) {
140     m_tagV1.reset(new ID3_Tag);
141     m_tagV1->Link(fn, ID3TT_ID3V1);
142     markTagUnchanged(Frame::Tag_1);
143   }
144 
145   if (force && m_tagV2) {
146     m_tagV2->Clear();
147     m_tagV2->Link(fn, ID3TT_ID3V2);
148     markTagUnchanged(Frame::Tag_2);
149   }
150   if (!m_tagV2) {
151     m_tagV2.reset(new ID3_Tag);
152     m_tagV2->Link(fn, ID3TT_ID3V2);
153     markTagUnchanged(Frame::Tag_2);
154   }
155 
156   if (force) {
157     setFilename(currentFilename());
158   }
159 
160   notifyModelDataChanged(priorIsTagInformationRead);
161 }
162 
163 /**
164  * Write tags to file and rename it if necessary.
165  *
166  * @param force   true to force writing even if file was not changed.
167  * @param renamed will be set to true if the file was renamed,
168  *                i.e. the file name is no longer valid, else *renamed
169  *                is left unchanged
170  * @param preserve true to preserve file time stamps
171  *
172  * @return true if ok, false if the file could not be written or renamed.
173  */
writeTags(bool force,bool * renamed,bool preserve)174 bool Mp3File::writeTags(bool force, bool* renamed, bool preserve)
175 {
176   QString fnStr(currentFilePath());
177   if (isChanged() && !QFileInfo(fnStr).isWritable()) {
178     revertChangedFilename();
179     return false;
180   }
181 
182   // store time stamp if it has to be preserved
183   quint64 actime = 0, modtime = 0;
184   if (preserve) {
185     getFileTimeStamps(fnStr, actime, modtime);
186   }
187 
188   // There seems to be a bug in id3lib: The V1 genre is not
189   // removed. So we check here and strip the whole header
190   // if there are no frames.
191   if (m_tagV1 && (force || isTagChanged(Frame::Tag_1)) && (m_tagV1->NumFrames() == 0)) {
192     m_tagV1->Strip(ID3TT_ID3V1);
193     markTagUnchanged(Frame::Tag_1);
194   }
195   // Even after removing all frames, HasV2Tag() still returns true,
196   // so we strip the whole header.
197   if (m_tagV2 && (force || isTagChanged(Frame::Tag_2)) && (m_tagV2->NumFrames() == 0)) {
198     m_tagV2->Strip(ID3TT_ID3V2);
199     markTagUnchanged(Frame::Tag_2);
200   }
201   // There seems to be a bug in id3lib: If I update an ID3v1 and then
202   // strip the ID3v2 the ID3v1 is removed too and vice versa, so I
203   // first make any stripping and then the updating.
204   if (m_tagV1 && (force || isTagChanged(Frame::Tag_1)) && (m_tagV1->NumFrames() > 0)) {
205     m_tagV1->Update(ID3TT_ID3V1);
206     markTagUnchanged(Frame::Tag_1);
207   }
208   if (m_tagV2 && (force || isTagChanged(Frame::Tag_2)) && (m_tagV2->NumFrames() > 0)) {
209     m_tagV2->Update(ID3TT_ID3V2);
210     markTagUnchanged(Frame::Tag_2);
211   }
212 
213   // restore time stamp
214   if (actime || modtime) {
215     setFileTimeStamps(fnStr, actime, modtime);
216   }
217 
218   if (isFilenameChanged()) {
219     if (!renameFile()) {
220       return false;
221     }
222     markFilenameUnchanged();
223     // link tags to new file name
224     readTags(true);
225     *renamed = true;
226   }
227   return true;
228 }
229 
230 /**
231  * Free resources allocated when calling readTags().
232  *
233  * @param force true to force clearing even if the tags are modified
234  */
clearTags(bool force)235 void Mp3File::clearTags(bool force)
236 {
237   if (isChanged() && !force)
238     return;
239 
240   bool priorIsTagInformationRead = isTagInformationRead();
241   if (m_tagV1) {
242     m_tagV1.reset();
243     markTagUnchanged(Frame::Tag_1);
244   }
245   if (m_tagV2) {
246     m_tagV2.reset();
247     markTagUnchanged(Frame::Tag_2);
248   }
249   notifyModelDataChanged(priorIsTagInformationRead);
250 }
251 
252 namespace {
253 
254 /**
255  * Fix up a unicode string from id3lib.
256  *
257  * @param str      unicode string
258  * @param numChars number of characters in str
259  *
260  * @return string as QString.
261  */
fixUpUnicode(const unicode_t * str,size_t numChars)262 QString fixUpUnicode(const unicode_t* str, size_t numChars)
263 {
264   QString text;
265   if (numChars > 0 && str && *str) {
266     auto qcarray = new QChar[numChars];
267     // Unfortunately, Unicode support in id3lib is rather buggy
268     // in the current version: The codes are mirrored.
269     // In the hope that my patches will be included, I try here
270     // to work around these bugs.
271     size_t numZeroes = 0;
272     for (size_t i = 0; i < numChars; i++) {
273       qcarray[i] = UNICODE_SUPPORT_BUGGY
274           ? static_cast<ushort>(((str[i] & 0x00ff) << 8) |
275                                 ((str[i] & 0xff00) >> 8))
276           : static_cast<ushort>(str[i]);
277       if (qcarray[i].isNull()) { ++numZeroes; }
278     }
279     // remove a single trailing zero character
280     if (numZeroes == 1 && qcarray[numChars - 1].isNull()) {
281       --numChars;
282     }
283     text = QString(qcarray, numChars);
284     delete [] qcarray;
285   }
286   return text;
287 }
288 
289 /**
290  * Get string from text field.
291  *
292  * @param field field
293  * @param codec text codec to use, 0 for default
294  *
295  * @return string,
296  *         "" if the field does not exist.
297  */
getString(ID3_Field * field,QStringDecoder * decoder=nullptr)298 QString getString(ID3_Field* field,
299 #if QT_VERSION >= 0x060000
300                   QStringDecoder* decoder = nullptr
301 #else
302                   const QTextCodec* codec = nullptr
303 #endif
304     )
305 {
306   QString text(QLatin1String(""));
307   if (field != nullptr) {
308     ID3_TextEnc enc = field->GetEncoding();
309     if (enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
310       size_t numItems = field->GetNumTextItems();
311       if (numItems <= 1) {
312         text = fixUpUnicode(field->GetRawUnicodeText(),
313                             field->Size() / sizeof(unicode_t));
314       } else {
315         // if there are multiple items, put them into one string
316         // separated by a special separator.
317         // ID3_Field::GetRawUnicodeTextItem() returns a pointer to a temporary
318         // object, so I do not use it.
319         text = fixUpUnicode(field->GetRawUnicodeText(),
320                             field->Size() / sizeof(unicode_t));
321         text.replace(QLatin1Char('\0'), Frame::stringListSeparator());
322       }
323     } else {
324       // (ID3TE_IS_SINGLE_BYTE_ENC(enc))
325       // (enc == ID3TE_ISO8859_1 || enc == ID3TE_UTF8)
326       size_t numItems = field->GetNumTextItems();
327       if (numItems <= 1) {
328 #if QT_VERSION >= 0x060000
329         text = decoder ? decoder->decode(QByteArray(field->GetRawText(), field->Size()))
330                        : QString::fromLatin1(field->GetRawText());
331 #else
332         text = codec ? codec->toUnicode(field->GetRawText(), field->Size())
333                      : QString::fromLatin1(field->GetRawText());
334 #endif
335       } else {
336         // if there are multiple items, put them into one string
337         // separated by a special separator.
338         for (size_t itemNr = 0; itemNr < numItems; ++itemNr) {
339           if (itemNr == 0) {
340             text = QString::fromLatin1(field->GetRawTextItem(0));
341           } else {
342             text += Frame::stringListSeparator();
343             text += QString::fromLatin1(field->GetRawTextItem(itemNr));
344           }
345         }
346       }
347     }
348   }
349   return text;
350 }
351 
352 /**
353  * Get text field.
354  *
355  * @param tag ID3 tag
356  * @param id  frame ID
357  * @param codec text codec to use, 0 for default
358  * @return string,
359  *         "" if the field does not exist,
360  *         QString::null if the tags do not exist.
361  */
getTextField(const ID3_Tag * tag,ID3_FrameID id,QStringDecoder * decoder=nullptr)362 QString getTextField(const ID3_Tag* tag, ID3_FrameID id,
363 #if QT_VERSION >= 0x060000
364                      QStringDecoder* decoder = nullptr
365 #else
366                      const QTextCodec* codec = nullptr
367 #endif
368     )
369 {
370   if (!tag) {
371     return QString();
372   }
373   QString str(QLatin1String(""));
374   ID3_Field* fld;
375   ID3_Frame* frame = tag->Find(id);
376   if (frame && ((fld = frame->GetField(ID3FN_TEXT)) != nullptr)) {
377 #if QT_VERSION >= 0x060000
378     str = getString(fld, decoder);
379 #else
380     str = getString(fld, codec);
381 #endif
382   }
383   return str;
384 }
385 
386 /**
387  * Get year.
388  *
389  * @param tag ID3 tag
390  * @return number,
391  *         0 if the field does not exist,
392  *         -1 if the tags do not exist.
393  */
getYear(const ID3_Tag * tag)394 int getYear(const ID3_Tag* tag)
395 {
396   QString str = getTextField(tag, ID3FID_YEAR);
397   if (str.isNull()) return -1;
398   if (str.isEmpty()) return 0;
399   return str.toInt();
400 }
401 
402 /**
403  * Get track.
404  *
405  * @param tag ID3 tag
406  * @return number,
407  *         0 if the field does not exist,
408  *         -1 if the tags do not exist.
409  */
getTrackNum(const ID3_Tag * tag)410 int getTrackNum(const ID3_Tag* tag)
411 {
412   QString str = getTextField(tag, ID3FID_TRACKNUM);
413   if (str.isNull()) return -1;
414   if (str.isEmpty()) return 0;
415   // handle "track/total number of tracks" format
416   int slashPos = str.indexOf(QLatin1Char('/'));
417   if (slashPos != -1) {
418     str.truncate(slashPos);
419   }
420   return str.toInt();
421 }
422 
423 /**
424  * Get genre.
425  *
426  * @param tag ID3 tag
427  * @return number,
428  *         0xff if the field does not exist,
429  *         -1 if the tags do not exist.
430  */
getGenreNum(const ID3_Tag * tag)431 int getGenreNum(const ID3_Tag* tag)
432 {
433   QString str = getTextField(tag, ID3FID_CONTENTTYPE);
434   if (str.isNull()) return -1;
435   if (str.isEmpty()) return 0xff;
436   int cpPos = 0, n = 0xff;
437   if ((str[0] == QLatin1Char('(')) &&
438       ((cpPos = str.indexOf(QLatin1Char(')'), 2)) > 1)) {
439     bool ok;
440 #if QT_VERSION >= 0x060000
441     n = str.mid(1, cpPos - 1).toInt(&ok);
442 #else
443     n = str.midRef(1, cpPos - 1).toInt(&ok);
444 #endif
445     if (!ok || n > 0xff) {
446       n = 0xff;
447     }
448   } else {
449     // ID3v2 genres can be stored as "(9)", "(9)Metal" or "Metal".
450     // If the string does not start with '(', try to get the genre number
451     // from a string containing a genre text.
452     n = Genres::getNumber(str);
453   }
454   return n;
455 }
456 
457 /**
458  * Allocate a fixed up a unicode string for id3lib.
459  *
460  * @param text string
461  *
462  * @return new allocated unicode string, has to be freed with delete [].
463  */
newFixedUpUnicode(const QString & text)464 unicode_t* newFixedUpUnicode(const QString& text)
465 {
466   // Unfortunately, Unicode support in id3lib is rather buggy in the
467   // current version: The codes are mirrored, a second different
468   // BOM may be added, if the LSB >= 0x80, the MSB is set to 0xff.
469   // If iconv is used (id3lib on Linux), the character do not come
470   // back mirrored, but with a second (different)! BOM 0xfeff and
471   // they are still written in the wrong order (big endian).
472   // In the hope that my patches will be included, I try here to
473   // work around these bugs, but there is no solution for the
474   // LSB >= 0x80 bug.
475   const QChar* qcarray = text.unicode();
476   int unicode_size = text.length();
477   auto unicode = new unicode_t[unicode_size + 1];
478   for (int i = 0; i < unicode_size; ++i) {
479     unicode[i] = qcarray[i].unicode();
480     if (UNICODE_SUPPORT_BUGGY) {
481       unicode[i] = static_cast<ushort>(((unicode[i] & 0x00ff) << 8) |
482                                        ((unicode[i] & 0xff00) >> 8));
483     }
484   }
485   unicode[unicode_size] = 0;
486   return unicode;
487 }
488 
489 /**
490  * Set string list in text field.
491  *
492  * @param field field
493  * @param lst   string list to set
494  */
setStringList(ID3_Field * field,const QStringList & lst)495 void setStringList(ID3_Field* field, const QStringList& lst)
496 {
497   ID3_TextEnc enc = field->GetEncoding();
498   bool first = true;
499   for (auto it = lst.constBegin(); it != lst.constEnd(); ++it) {
500     if (first) {
501       if (enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
502         unicode_t* unicode = newFixedUpUnicode(*it);
503         if (unicode) {
504           field->Set(unicode);
505           delete [] unicode;
506         }
507       } else if (enc == ID3TE_UTF8) {
508         field->Set((*it).toUtf8().data());
509       } else {
510         // enc == ID3TE_ISO8859_1
511         field->Set((*it).toLatin1().data());
512       }
513       first = false;
514     } else {
515       // This will not work with buggy id3lib. A BOM 0xfffe is written before
516       // the first string, but not before the subsequent strings. Prepending a
517       // BOM or changing the byte order does not help when id3lib rewrites
518       // this field when another frame is changed. So you cannot use
519       // string lists with Unicode encoding.
520       if (enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
521         unicode_t* unicode = newFixedUpUnicode(*it);
522         if (unicode) {
523           field->Add(unicode);
524           delete [] unicode;
525         }
526       } else if (enc == ID3TE_UTF8) {
527         field->Add((*it).toUtf8().data());
528       } else {
529         // enc == ID3TE_ISO8859_1
530         field->Add((*it).toLatin1().data());
531       }
532     }
533   }
534 }
535 
536 /**
537  * Set string in text field.
538  *
539  * @param field        field
540  * @param text         text to set
541  * @param codec        text codec to use, 0 for default
542  */
setString(ID3_Field * field,const QString & text,QStringEncoder * encoder=nullptr)543 void setString(ID3_Field* field, const QString& text,
544 #if QT_VERSION >= 0x060000
545                QStringEncoder* encoder = nullptr
546 #else
547                const QTextCodec* codec = nullptr
548 #endif
549     )
550 {
551   if (text.indexOf(Frame::stringListSeparator()) == -1) {
552     ID3_TextEnc enc = field->GetEncoding();
553     // (ID3TE_IS_DOUBLE_BYTE_ENC(enc))
554     if (enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
555       unicode_t* unicode = newFixedUpUnicode(text);
556       if (unicode) {
557         field->Set(unicode);
558         delete [] unicode;
559       }
560     } else if (enc == ID3TE_UTF8) {
561       field->Set(text.toUtf8().data());
562     } else {
563       // enc == ID3TE_ISO8859_1
564 #if QT_VERSION >= 0x060000
565       field->Set(encoder ? static_cast<QByteArray>(encoder->encode(text)).constData()
566                        : text.toLatin1().constData());
567 #else
568       field->Set(codec ? codec->fromUnicode(text).constData()
569                        : text.toLatin1().constData());
570 #endif
571     }
572   } else {
573     setStringList(field, text.split(Frame::stringListSeparator()));
574   }
575 }
576 
577 /**
578  * Set text field.
579  *
580  * @param tag          ID3 tag
581  * @param id           frame ID
582  * @param text         text to set
583  * @param allowUnicode true to allow setting of Unicode encoding if necessary
584  * @param replace      true to replace an existing field
585  * @param removeEmpty  true to remove a field if text is empty
586  * @param codec        text codec to use, 0 for default
587  *
588  * @return true if the field was changed.
589  */
setTextField(ID3_Tag * tag,ID3_FrameID id,const QString & text,bool allowUnicode=false,bool replace=true,bool removeEmpty=true,QStringEncoder * encoder=nullptr)590 bool setTextField(ID3_Tag* tag, ID3_FrameID id, const QString& text,
591                   bool allowUnicode = false, bool replace = true,
592                   bool removeEmpty = true,
593 #if QT_VERSION >= 0x060000
594                   QStringEncoder* encoder = nullptr
595 #else
596                   const QTextCodec* codec = nullptr
597 #endif
598     )
599 {
600   bool changed = false;
601   if (tag && !text.isNull()) {
602     ID3_Frame* frame = nullptr;
603     bool removeOnly = removeEmpty && text.isEmpty();
604     if (replace || removeOnly) {
605       if (id == ID3FID_COMMENT && tag->HasV2Tag()) {
606         frame = tag->Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, "");
607       } else {
608         frame = tag->Find(id);
609       }
610       if (frame) {
611         frame = tag->RemoveFrame(frame);
612         delete frame;
613         changed = true;
614       }
615     }
616     if (!removeOnly && (replace || tag->Find(id) == nullptr)) {
617       frame = new ID3_Frame(id);
618       if (frame) {
619         ID3_Field* fld = frame->GetField(ID3FN_TEXT);
620         if (fld) {
621           ID3_TextEnc enc = tag->HasV2Tag() ?
622             getDefaultTextEncoding() : ID3TE_ISO8859_1;
623           if (allowUnicode && enc == ID3TE_ISO8859_1) {
624             // check if information is lost if the string is not unicode
625             int i, unicode_size = text.length();
626             const QChar* qcarray = text.unicode();
627             for (i = 0; i < unicode_size; ++i) {
628               char ch = qcarray[i].toLatin1();
629               if (ch == 0 || (ch & 0x80) != 0) {
630                 enc = ID3TE_UTF16;
631                 break;
632               }
633             }
634           }
635           ID3_Field* encfld = frame->GetField(ID3FN_TEXTENC);
636           if (encfld) {
637             encfld->Set(enc);
638           }
639           fld->SetEncoding(enc);
640 #if QT_VERSION >= 0x060000
641           setString(fld, text, encoder);
642 #else
643           setString(fld, text, codec);
644 #endif
645           tag->AttachFrame(frame);
646         }
647       }
648       changed = true;
649     }
650   }
651   return changed;
652 }
653 
654 /**
655  * Set year.
656  *
657  * @param tag ID3 tag
658  * @param num number to set, 0 to remove field.
659  *
660  * @return true if the field was changed.
661  */
setYear(ID3_Tag * tag,int num)662 bool setYear(ID3_Tag* tag, int num)
663 {
664   bool changed = false;
665   if (num >= 0) {
666     QString str;
667     if (num != 0) {
668       str.setNum(num);
669     } else {
670       str.clear();
671     }
672     changed = getTextField(tag, ID3FID_YEAR) != str &&
673               setTextField(tag, ID3FID_YEAR, str);
674   }
675   return changed;
676 }
677 
678 }
679 
680 /**
681  * Set track.
682  *
683  * @param tag ID3 tag
684  * @param num number to set, 0 to remove field.
685  * @param numTracks total number of tracks, <=0 to ignore
686  *
687  * @return true if the field was changed.
688  */
setTrackNum(ID3_Tag * tag,int num,int numTracks) const689 bool Mp3File::setTrackNum(ID3_Tag* tag, int num, int numTracks) const
690 {
691   bool changed = false;
692   if (num >= 0 && getTrackNum(tag) != num) {
693     QString str = trackNumberString(num, numTracks);
694     changed = getTextField(tag, ID3FID_TRACKNUM) != str &&
695               setTextField(tag, ID3FID_TRACKNUM, str);
696   }
697   return changed;
698 }
699 
700 namespace {
701 
702 /**
703  * Set genre.
704  *
705  * @param tag ID3 tag
706  * @param num number to set, 0xff to remove field.
707  *
708  * @return true if the field was changed.
709  */
setGenreNum(ID3_Tag * tag,int num)710 bool setGenreNum(ID3_Tag* tag, int num)
711 {
712   bool changed = false;
713   if (num >= 0) {
714     QString str;
715     if (num != 0xff) {
716       str = QString(QLatin1String("(%1)")).arg(num);
717     } else {
718       str = QLatin1String("");
719     }
720     changed = getTextField(tag, ID3FID_CONTENTTYPE) != str &&
721               setTextField(tag, ID3FID_CONTENTTYPE, str);
722   }
723   return changed;
724 }
725 
726 }
727 
728 /**
729  * Check if tag information has already been read.
730  *
731  * @return true if information is available,
732  *         false if the tags have not been read yet, in which case
733  *         hasTag() does not return meaningful information.
734  */
isTagInformationRead() const735 bool Mp3File::isTagInformationRead() const
736 {
737   return m_tagV1 || m_tagV2;
738 }
739 
740 /**
741  * Check if tags are supported by the format of this file.
742  *
743  * @param tagNr tag number
744  * @return true.
745  */
isTagSupported(Frame::TagNumber tagNr) const746 bool Mp3File::isTagSupported(Frame::TagNumber tagNr) const
747 {
748   return tagNr == Frame::Tag_1 || tagNr == Frame::Tag_2;
749 }
750 
751 /**
752  * Check if file has a tag.
753  *
754  * @param tagNr tag number
755  * @return true if a tag is available.
756  * @see isTagInformationRead()
757  */
hasTag(Frame::TagNumber tagNr) const758 bool Mp3File::hasTag(Frame::TagNumber tagNr) const
759 {
760   return (tagNr == Frame::Tag_1 && m_tagV1 && m_tagV1->HasV1Tag()) ||
761          (tagNr == Frame::Tag_2 && m_tagV2 && m_tagV2->HasV2Tag());
762 }
763 
764 /**
765  * Get technical detail information.
766  *
767  * @param info the detail information is returned here
768  */
getDetailInfo(DetailInfo & info) const769 void Mp3File::getDetailInfo(DetailInfo& info) const
770 {
771   if (getFilename().right(4).toLower() == QLatin1String(".aac")) {
772     info.valid = true;
773     info.format = QLatin1String("AAC");
774     return;
775   }
776 
777   const Mp3_Headerinfo* headerInfo = nullptr;
778   if (m_tagV2) {
779     headerInfo = m_tagV2->GetMp3HeaderInfo();
780   }
781   if (!headerInfo && m_tagV1) {
782     headerInfo = m_tagV1->GetMp3HeaderInfo();
783   }
784   if (headerInfo) {
785     info.valid = true;
786     switch (headerInfo->version) {
787       case MPEGVERSION_1:
788         info.format = QLatin1String("MPEG 1 ");
789         break;
790       case MPEGVERSION_2:
791         info.format = QLatin1String("MPEG 2 ");
792         break;
793       case MPEGVERSION_2_5:
794         info.format = QLatin1String("MPEG 2.5 ");
795         break;
796       default:
797         ; // nothing
798     }
799     switch (headerInfo->layer) {
800       case MPEGLAYER_I:
801         info.format += QLatin1String("Layer 1");
802         break;
803       case MPEGLAYER_II:
804         info.format += QLatin1String("Layer 2");
805         break;
806       case MPEGLAYER_III:
807         info.format += QLatin1String("Layer 3");
808         break;
809       default:
810         ; // nothing
811     }
812     info.bitrate = headerInfo->bitrate / 1000;
813 #ifndef HAVE_NO_ID3LIB_VBR
814     if (headerInfo->vbr_bitrate > 1000) {
815       info.vbr = true;
816       info.bitrate = headerInfo->vbr_bitrate / 1000;
817     }
818 #endif
819     info.sampleRate = headerInfo->frequency;
820     switch (headerInfo->channelmode) {
821       case MP3CHANNELMODE_STEREO:
822         info.channelMode = DetailInfo::CM_Stereo;
823         info.channels = 2;
824         break;
825       case MP3CHANNELMODE_JOINT_STEREO:
826         info.channelMode = DetailInfo::CM_JointStereo;
827         info.channels = 2;
828         break;
829       case MP3CHANNELMODE_DUAL_CHANNEL:
830         info.channels = 2;
831         break;
832       case MP3CHANNELMODE_SINGLE_CHANNEL:
833         info.channels = 1;
834         break;
835       default:
836         ; // nothing
837     }
838     info.duration = headerInfo->time;
839   } else {
840     info.valid = false;
841   }
842 }
843 
844 /**
845  * Get duration of file.
846  *
847  * @return duration in seconds,
848  *         0 if unknown.
849  */
getDuration() const850 unsigned Mp3File::getDuration() const
851 {
852   unsigned duration = 0;
853   const Mp3_Headerinfo* info = nullptr;
854   if (m_tagV2) {
855     info = m_tagV2->GetMp3HeaderInfo();
856   }
857   if (!info && m_tagV1) {
858     info = m_tagV1->GetMp3HeaderInfo();
859   }
860   if (info && info->time > 0) {
861     duration = info->time;
862   }
863   return duration;
864 }
865 
866 /**
867  * Get file extension including the dot.
868  *
869  * @return file extension ".mp3".
870  */
getFileExtension() const871 QString Mp3File::getFileExtension() const
872 {
873   QString ext(getFilename().right(4).toLower());
874   if (ext == QLatin1String(".aac") || ext == QLatin1String(".mp2"))
875     return ext;
876   else
877     return QLatin1String(".mp3");
878 }
879 
880 /**
881  * Get the format of a tag.
882  *
883  * @param tagNr tag number
884  * @return string describing format of tag,
885  *         e.g. "ID3v1.1".
886  */
getTagFormat(Frame::TagNumber tagNr) const887 QString Mp3File::getTagFormat(Frame::TagNumber tagNr) const
888 {
889   if (tagNr == Frame::Tag_1 && m_tagV1 && m_tagV1->HasV1Tag()) {
890     return QLatin1String("ID3v1.1");
891   } else if (tagNr == Frame::Tag_2 && m_tagV2 && m_tagV2->HasV2Tag()) {
892     switch (m_tagV2->GetSpec()) {
893       case ID3V2_3_0:
894         return QLatin1String("ID3v2.3.0");
895       case ID3V2_4_0:
896         return QLatin1String("ID3v2.4.0");
897       case ID3V2_2_0:
898         return QLatin1String("ID3v2.2.0");
899       case ID3V2_2_1:
900         return QLatin1String("ID3v2.2.1");
901       default:
902         break;
903     }
904   }
905   return QString();
906 }
907 
908 namespace {
909 
910 /** Types and descriptions for id3lib frame IDs */
911 const struct TypeStrOfId {
912   Frame::Type type;
913   const char* str;
914 } typeStrOfId[] = {
915   { Frame::FT_UnknownFrame,   nullptr },                                                                                 /* ???? */
916   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "AENC - Audio encryption") },                                /* AENC */
917   { Frame::FT_Picture,        QT_TRANSLATE_NOOP("@default", "APIC - Attached picture") },                                /* APIC */
918   { Frame::FT_Other,          nullptr },                                                                                 /* ASPI */
919   { Frame::FT_Comment,        QT_TRANSLATE_NOOP("@default", "COMM - Comments") },                                        /* COMM */
920   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "COMR - Commercial") },                                      /* COMR */
921   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "ENCR - Encryption method registration") },                  /* ENCR */
922   { Frame::FT_Other,          nullptr },                                                                                 /* EQU2 */
923   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "EQUA - Equalization") },                                    /* EQUA */
924   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "ETCO - Event timing codes") },                              /* ETCO */
925   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "GEOB - General encapsulated object") },                     /* GEOB */
926   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "GRID - Group identification registration") },               /* GRID */
927   { Frame::FT_Arranger,       QT_TRANSLATE_NOOP("@default", "IPLS - Involved people list") },                            /* IPLS */
928   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "LINK - Linked information") },                              /* LINK */
929   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "MCDI - Music CD identifier") },                             /* MCDI */
930   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "MLLT - MPEG location lookup table") },                      /* MLLT */
931   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "OWNE - Ownership frame") },                                 /* OWNE */
932   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "PRIV - Private frame") },                                   /* PRIV */
933   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "PCNT - Play counter") },                                    /* PCNT */
934   { Frame::FT_Rating,         QT_TRANSLATE_NOOP("@default", "POPM - Popularimeter") },                                   /* POPM */
935   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "POSS - Position synchronisation frame") },                  /* POSS */
936   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "RBUF - Recommended buffer size") },                         /* RBUF */
937   { Frame::FT_Other,          nullptr },                                                                                 /* RVA2 */
938   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "RVAD - Relative volume adjustment") },                      /* RVAD */
939   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "RVRB - Reverb") },                                          /* RVRB */
940   { Frame::FT_Other,          nullptr },                                                                                 /* SEEK */
941   { Frame::FT_Other,          nullptr },                                                                                 /* SIGN */
942   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "SYLT - Synchronized lyric/text") },                         /* SYLT */
943   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "SYTC - Synchronized tempo codes") },                        /* SYTC */
944   { Frame::FT_Album,          QT_TRANSLATE_NOOP("@default", "TALB - Album/Movie/Show title") },                          /* TALB */
945   { Frame::FT_Bpm,            QT_TRANSLATE_NOOP("@default", "TBPM - BPM (beats per minute)") },                          /* TBPM */
946   { Frame::FT_Composer,       QT_TRANSLATE_NOOP("@default", "TCOM - Composer") },                                        /* TCOM */
947   { Frame::FT_Genre,          QT_TRANSLATE_NOOP("@default", "TCON - Content type") },                                    /* TCON */
948   { Frame::FT_Copyright,      QT_TRANSLATE_NOOP("@default", "TCOP - Copyright message") },                               /* TCOP */
949   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TDAT - Date") },                                            /* TDAT */
950   { Frame::FT_Other,          nullptr },                                                                                 /* TDEN */
951   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TDLY - Playlist delay") },                                  /* TDLY */
952   { Frame::FT_Other,          nullptr },                                                                                 /* TDOR */
953   { Frame::FT_Other,          nullptr },                                                                                 /* TDRC */
954   { Frame::FT_Other,          nullptr },                                                                                 /* TDRL */
955   { Frame::FT_Other,          nullptr },                                                                                 /* TDTG */
956   { Frame::FT_Other,          nullptr },                                                                                 /* TIPL */
957   { Frame::FT_EncodedBy,      QT_TRANSLATE_NOOP("@default", "TENC - Encoded by") },                                      /* TENC */
958   { Frame::FT_Lyricist,       QT_TRANSLATE_NOOP("@default", "TEXT - Lyricist/Text writer") },                            /* TEXT */
959   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TFLT - File type") },                                       /* TFLT */
960   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TIME - Time") },                                            /* TIME */
961   { Frame::FT_Work,           QT_TRANSLATE_NOOP("@default", "TIT1 - Content group description") },                       /* TIT1 */
962   { Frame::FT_Title,          QT_TRANSLATE_NOOP("@default", "TIT2 - Title/songname/content description") },              /* TIT2 */
963   { Frame::FT_Description,    QT_TRANSLATE_NOOP("@default", "TIT3 - Subtitle/Description refinement") },                 /* TIT3 */
964   { Frame::FT_InitialKey,     QT_TRANSLATE_NOOP("@default", "TKEY - Initial key") },                                     /* TKEY */
965   { Frame::FT_Language,       QT_TRANSLATE_NOOP("@default", "TLAN - Language(s)") },                                     /* TLAN */
966   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TLEN - Length") },                                          /* TLEN */
967   { Frame::FT_Other,          nullptr },                                                                                 /* TMCL */
968   { Frame::FT_Media,          QT_TRANSLATE_NOOP("@default", "TMED - Media type") },                                      /* TMED */
969   { Frame::FT_Other,          nullptr },                                                                                 /* TMOO */
970   { Frame::FT_OriginalAlbum,  QT_TRANSLATE_NOOP("@default", "TOAL - Original album/movie/show title") },                 /* TOAL */
971   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TOFN - Original filename") },                               /* TOFN */
972   { Frame::FT_Author,         QT_TRANSLATE_NOOP("@default", "TOLY - Original lyricist(s)/text writer(s)") },             /* TOLY */
973   { Frame::FT_OriginalArtist, QT_TRANSLATE_NOOP("@default", "TOPE - Original artist(s)/performer(s)") },                 /* TOPE */
974   { Frame::FT_OriginalDate,   QT_TRANSLATE_NOOP("@default", "TORY - Original release year") },                           /* TORY */
975   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TOWN - File owner/licensee") },                             /* TOWN */
976   { Frame::FT_Artist,         QT_TRANSLATE_NOOP("@default", "TPE1 - Lead performer(s)/Soloist(s)") },                    /* TPE1 */
977   { Frame::FT_AlbumArtist,    QT_TRANSLATE_NOOP("@default", "TPE2 - Band/orchestra/accompaniment") },                    /* TPE2 */
978   { Frame::FT_Conductor,      QT_TRANSLATE_NOOP("@default", "TPE3 - Conductor/performer refinement") },                  /* TPE3 */
979   { Frame::FT_Remixer,        QT_TRANSLATE_NOOP("@default", "TPE4 - Interpreted, remixed, or otherwise modified by") },  /* TPE4 */
980   { Frame::FT_Disc,           QT_TRANSLATE_NOOP("@default", "TPOS - Part of a set") },                                   /* TPOS */
981   { Frame::FT_Other,          nullptr },                                                                                 /* TPRO */
982   { Frame::FT_Publisher,      QT_TRANSLATE_NOOP("@default", "TPUB - Publisher") },                                       /* TPUB */
983   { Frame::FT_Track,          QT_TRANSLATE_NOOP("@default", "TRCK - Track number/Position in set") },                    /* TRCK */
984   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TRDA - Recording dates") },                                 /* TRDA */
985   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TRSN - Internet radio station name") },                     /* TRSN */
986   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TRSO - Internet radio station owner") },                    /* TRSO */
987   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TSIZ - Size") },                                            /* TSIZ */
988   { Frame::FT_Other,          nullptr },                                                                                 /* TSOA */
989   { Frame::FT_Other,          nullptr },                                                                                 /* TSOP */
990   { Frame::FT_Other,          nullptr },                                                                                 /* TSOT */
991   { Frame::FT_Isrc,           QT_TRANSLATE_NOOP("@default", "TSRC - ISRC (international standard recording code)") },    /* TSRC */
992   { Frame::FT_EncoderSettings,QT_TRANSLATE_NOOP("@default", "TSSE - Software/Hardware and settings used for encoding") },/* TSSE */
993   { Frame::FT_Subtitle,       nullptr },                                                                                 /* TSST */
994   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TXXX - User defined text information") },                   /* TXXX */
995   { Frame::FT_Date,           QT_TRANSLATE_NOOP("@default", "TYER - Year") },                                            /* TYER */
996   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "UFID - Unique file identifier") },                          /* UFID */
997   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "USER - Terms of use") },                                    /* USER */
998   { Frame::FT_Lyrics,         QT_TRANSLATE_NOOP("@default", "USLT - Unsynchronized lyric/text transcription") },         /* USLT */
999   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "WCOM - Commercial information") },                          /* WCOM */
1000   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "WCOP - Copyright/Legal information") },                     /* WCOP */
1001   { Frame::FT_WWWAudioFile,   QT_TRANSLATE_NOOP("@default", "WOAF - Official audio file webpage") },                     /* WOAF */
1002   { Frame::FT_Website,        QT_TRANSLATE_NOOP("@default", "WOAR - Official artist/performer webpage") },               /* WOAR */
1003   { Frame::FT_WWWAudioSource, QT_TRANSLATE_NOOP("@default", "WOAS - Official audio source webpage") },                   /* WOAS */
1004   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "WORS - Official internet radio station homepage") },        /* WORS */
1005   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "WPAY - Payment") },                                         /* WPAY */
1006   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "WPUB - Official publisher webpage") },                      /* WPUB */
1007   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "WXXX - User defined URL link") }                            /* WXXX */
1008 };
1009 Q_STATIC_ASSERT(sizeof(typeStrOfId) / sizeof(typeStrOfId[0]) == ID3FID_WWWUSER + 1);
1010 
1011 /**
1012  * Get type and description of frame.
1013  *
1014  * @param id ID of frame
1015  * @param type the type is returned here
1016  * @param str  the description is returned here, 0 if not available
1017  */
getTypeStringForId3libFrameId(ID3_FrameID id,Frame::Type & type,const char * & str)1018 void getTypeStringForId3libFrameId(ID3_FrameID id, Frame::Type& type,
1019                                    const char*& str)
1020 {
1021   const TypeStrOfId& ts = typeStrOfId[id <= ID3FID_WWWUSER ? id : 0];
1022   type = ts.type;
1023   str = ts.str;
1024 }
1025 
1026 /**
1027  * Get id3lib frame ID for frame type.
1028  *
1029  * @param type frame type
1030  *
1031  * @return id3lib frame ID or ID3FID_NOFRAME if type not found.
1032  */
getId3libFrameIdForType(Frame::Type type)1033 ID3_FrameID getId3libFrameIdForType(Frame::Type type)
1034 {
1035   // IPLS is mapped to FT_Arranger and FT_Performer
1036   if (type == Frame::FT_Performer) {
1037     return ID3FID_INVOLVEDPEOPLE;
1038   } else if (type == Frame::FT_CatalogNumber ||
1039              type == Frame::FT_ReleaseCountry ||
1040              type == Frame::FT_Grouping ||
1041              type == Frame::FT_Subtitle) {
1042     return ID3FID_USERTEXT;
1043   }
1044 
1045   static int typeIdMap[Frame::FT_LastFrame + 1] = { -1, };
1046   if (typeIdMap[0] == -1) {
1047     // first time initialization
1048     for (int i = 0; i <= ID3FID_WWWUSER; ++i) {
1049       int t = typeStrOfId[i].type;
1050       if (t <= Frame::FT_LastFrame) {
1051         typeIdMap[t] = i;
1052       }
1053     }
1054   }
1055   return type <= Frame::FT_LastFrame ?
1056     static_cast<ID3_FrameID>(typeIdMap[type]) : ID3FID_NOFRAME;
1057 }
1058 
1059 /**
1060  * Get id3lib frame ID for frame name.
1061  *
1062  * @param name frame name
1063  *
1064  * @return id3lib frame ID or ID3FID_NOFRAME if name not found.
1065  */
getId3libFrameIdForName(const QString & name)1066 ID3_FrameID getId3libFrameIdForName(const QString& name)
1067 {
1068   if (name.length() >= 4) {
1069     QByteArray nameBytes = name.toLatin1();
1070     const char* nameStr = nameBytes.constData();
1071     for (int i = 0; i <= ID3FID_WWWUSER; ++i) {
1072       const char* s = typeStrOfId[i].str;
1073       if (s && ::strncmp(s, nameStr, 4) == 0) {
1074         return static_cast<ID3_FrameID>(i);
1075       }
1076     }
1077   }
1078   return ID3FID_NOFRAME;
1079 }
1080 
1081 /**
1082  * Convert the binary field of a SYLT frame to a list of alternating
1083  * time stamps and strings.
1084  * @param bytes binary field
1085  * @param enc encoding
1086  * @return list containing time, text, time, text, ...
1087  */
syltBytesToList(const QByteArray & bytes,ID3_TextEnc enc)1088 QVariantList syltBytesToList(const QByteArray& bytes, ID3_TextEnc enc)
1089 {
1090   QVariantList timeEvents;
1091   const int numBytes = bytes.size();
1092   int textBegin = 0, textEnd;
1093   while (textBegin < numBytes) {
1094     if (enc == ID3TE_ISO8859_1 || enc == ID3TE_UTF8) {
1095       textEnd = bytes.indexOf('\0', textBegin);
1096       if (textEnd != -1) {
1097         ++textEnd;
1098       }
1099     } else {
1100       const ushort* unicode =
1101           reinterpret_cast<const ushort*>(bytes.constData() + textBegin);
1102       textEnd = textBegin;
1103       while (textEnd < numBytes) {
1104         textEnd += 2;
1105         if (*unicode++ == 0) {
1106           break;
1107         }
1108       }
1109     }
1110     if (textEnd < 0 || textEnd >= numBytes)
1111       break;
1112 
1113     QString str;
1114     QByteArray text = bytes.mid(textBegin, textEnd - textBegin);
1115     switch (enc) {
1116     case ID3TE_UTF16BE:
1117       text.prepend(0xff);
1118       text.prepend(0xfe);
1119       // starting with FEFF BOM
1120       // fallthrough
1121     case ID3TE_UTF16:
1122 #if QT_VERSION >= 0x060000
1123       str = QString::fromUtf16(reinterpret_cast<const char16_t*>(text.constData()));
1124 #else
1125       str = QString::fromUtf16(reinterpret_cast<const ushort*>(text.constData()));
1126 #endif
1127       break;
1128     case ID3TE_UTF8:
1129       str = QString::fromUtf8(text.constData());
1130       break;
1131     case ID3TE_ISO8859_1:
1132     default:
1133       str = QString::fromLatin1(text.constData());
1134     }
1135     textBegin = textEnd + 4;
1136     if (textBegin > numBytes)
1137       break;
1138 
1139     auto time = qFromBigEndian<quint32>(
1140           reinterpret_cast<const uchar*>(bytes.constData()) + textEnd);
1141     timeEvents.append(time);
1142     timeEvents.append(str);
1143   }
1144   return timeEvents;
1145 }
1146 
1147 /**
1148  * Convert a list of alternating time stamps and strings to the binary field of
1149  * a SYLT frame.
1150  * @param synchedData list containing time, text, time, text, ...
1151  * @return binary field bytes.
1152  */
syltListToBytes(const QVariantList & synchedData,ID3_TextEnc enc)1153 QByteArray syltListToBytes(const QVariantList& synchedData, ID3_TextEnc enc)
1154 {
1155   QByteArray bytes;
1156   QListIterator<QVariant> it(synchedData);
1157   while (it.hasNext()) {
1158     quint32 milliseconds = it.next().toUInt();
1159     if (!it.hasNext())
1160       break;
1161 
1162     QString str = it.next().toString();
1163     switch (enc) {
1164     case ID3TE_UTF16:
1165       bytes.append(0xff);
1166       bytes.append(0xfe);
1167       // starting with FFFE BOM
1168       // fallthrough
1169     case ID3TE_UTF16BE:
1170     {
1171       const ushort* unicode = str.utf16();
1172       do {
1173         uchar lsb = *unicode & 0xff;
1174         uchar msb = *unicode >> 8;
1175         if (enc == ID3TE_UTF16) {
1176           bytes.append(static_cast<char>(lsb));
1177           bytes.append(static_cast<char>(msb));
1178         } else {
1179           bytes.append(static_cast<char>(msb));
1180           bytes.append(static_cast<char>(lsb));
1181         }
1182       } while (*unicode++);
1183       break;
1184     }
1185     case ID3TE_UTF8:
1186       bytes.append(str.toUtf8());
1187       bytes.append('\0');
1188       break;
1189     case ID3TE_ISO8859_1:
1190     default:
1191       bytes.append(str.toLatin1());
1192       bytes.append('\0');
1193     }
1194     uchar timeStamp[4];
1195     qToBigEndian(milliseconds, timeStamp);
1196     bytes.append(reinterpret_cast<const char*>(timeStamp), sizeof(timeStamp));
1197   }
1198   if (bytes.isEmpty()) {
1199     // id3lib bug: Empty binary fields are not written, so add a minimal field
1200     bytes = QByteArray(4 + (enc == ID3TE_UTF16 ||
1201                             enc == ID3TE_UTF16BE ? 2 : 1),
1202                        '\0');
1203   }
1204   return bytes;
1205 }
1206 
1207 /**
1208  * Convert the binary field of an ETCO frame to a list of alternating
1209  * time stamps and codes.
1210  * @param bytes binary field
1211  * @return list containing time, code, time, code, ...
1212  */
etcoBytesToList(const QByteArray & bytes)1213 QVariantList etcoBytesToList(const QByteArray& bytes)
1214 {
1215   QVariantList timeEvents;
1216   const int numBytes = bytes.size();
1217   // id3lib bug: There is only a single data field for ETCO frames,
1218   // but it should be preceded by an ID_TimestampFormat field.
1219   // Start with the second byte.
1220   int pos = 1;
1221   while (pos < numBytes) {
1222     int code = static_cast<uchar>(bytes.at(pos));
1223     ++pos;
1224     if (pos + 4 > numBytes)
1225       break;
1226 
1227     auto time = qFromBigEndian<quint32>(
1228           reinterpret_cast<const uchar*>(bytes.constData()) + pos);
1229     pos += 4;
1230     timeEvents.append(time);
1231     timeEvents.append(code);
1232   }
1233   return timeEvents;
1234 }
1235 
1236 /**
1237  * Convert a list of alternating time stamps and codes to the binary field of
1238  * an ETCO frame.
1239  * @param synchedData list containing time, code, time, code, ...
1240  * @return binary field bytes.
1241  */
etcoListToBytes(const QVariantList & synchedData)1242 QByteArray etcoListToBytes(const QVariantList& synchedData)
1243 {
1244   QByteArray bytes;
1245   QListIterator<QVariant> it(synchedData);
1246   while (it.hasNext()) {
1247     quint32 milliseconds = it.next().toUInt();
1248     if (!it.hasNext())
1249       break;
1250 
1251     int code = it.next().toInt();
1252     bytes.append(static_cast<char>(code));
1253     uchar timeStamp[4];
1254     qToBigEndian(milliseconds, timeStamp);
1255     bytes.append(reinterpret_cast<const char*>(timeStamp), sizeof(timeStamp));
1256   }
1257   return bytes;
1258 }
1259 
1260 /**
1261  * Get the fields from an ID3v2 tag.
1262  *
1263  * @param id3Frame frame
1264  * @param fields   the fields are appended to this list
1265  *
1266  * @return text representation of fields (Text or URL).
1267  */
getFieldsFromId3Frame(ID3_Frame * id3Frame,Frame::FieldList & fields)1268 QString getFieldsFromId3Frame(ID3_Frame* id3Frame, Frame::FieldList& fields)
1269 {
1270   QString text;
1271   ID3_Frame::Iterator* iter = id3Frame->CreateIterator();
1272   ID3_FrameID id3Id = id3Frame->GetID();
1273   ID3_TextEnc enc = ID3TE_NONE;
1274   ID3_Field* id3Field;
1275   Frame::Field field;
1276   while ((id3Field = iter->GetNext()) != nullptr) {
1277     ID3_FieldID id = id3Field->GetID();
1278     ID3_FieldType type = id3Field->GetType();
1279     field.m_id = id;
1280     if (type == ID3FTY_INTEGER) {
1281       uint32 intVal = id3Field->Get();
1282       field.m_value = intVal;
1283       if (id == ID3FN_TEXTENC) {
1284         enc = static_cast<ID3_TextEnc>(intVal);
1285       }
1286     }
1287     else if (type == ID3FTY_BINARY) {
1288       QByteArray ba(reinterpret_cast<const char*>(id3Field->GetRawBinary()),
1289                     static_cast<int>(id3Field->Size()));
1290       if (id3Id == ID3FID_SYNCEDLYRICS) {
1291         field.m_value = syltBytesToList(ba, enc);
1292       } else if (id3Id == ID3FID_EVENTTIMING) {
1293         field.m_value = etcoBytesToList(ba);
1294       } else {
1295         field.m_value = ba;
1296       }
1297     }
1298     else if (type == ID3FTY_TEXTSTRING) {
1299       if (id == ID3FN_TEXT || id == ID3FN_DESCRIPTION || id == ID3FN_URL) {
1300         text = getString(id3Field);
1301         if (id3Id == ID3FID_CONTENTTYPE) {
1302           text = Genres::getNameString(text);
1303         }
1304         field.m_value = text;
1305       } else {
1306         field.m_value = getString(id3Field);
1307       }
1308     } else {
1309       field.m_value.clear();
1310     }
1311     fields.push_back(field);
1312   }
1313 #ifdef Q_OS_WIN32
1314   /* allocated in Windows DLL => must be freed in the same DLL */
1315   ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
1316 #else
1317 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1318 #pragma GCC diagnostic push
1319 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
1320 /* Is safe because iterator implementation has default destructor */
1321 #endif
1322   delete iter;
1323 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1324 #pragma GCC diagnostic pop
1325 #endif
1326 #endif
1327   return text;
1328 }
1329 
1330 /**
1331  * Get ID3v2 frame by index.
1332  *
1333  * @param tag   ID3v2 tag
1334  * @param index index
1335  *
1336  * @return frame, 0 if index invalid.
1337  */
getId3v2Frame(ID3_Tag * tag,int index)1338 ID3_Frame* getId3v2Frame(ID3_Tag* tag, int index)
1339 {
1340   ID3_Frame* result = nullptr;
1341   if (tag) {
1342     ID3_Frame* frame;
1343     ID3_Tag::Iterator* iter = tag->CreateIterator();
1344     int i = 0;
1345     while ((frame = iter->GetNext()) != nullptr) {
1346       if (i == index) {
1347         result = frame;
1348         break;
1349       }
1350       ++i;
1351     }
1352 #ifdef Q_OS_WIN32
1353     /* allocated in Windows DLL => must be freed in the same DLL */
1354     ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
1355 #else
1356 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1357 #pragma GCC diagnostic push
1358 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
1359 /* Is safe because iterator implementation has default destructor */
1360 #endif
1361     delete iter;
1362 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1363 #pragma GCC diagnostic pop
1364 #endif
1365 #endif
1366   }
1367   return result;
1368 }
1369 
1370 }
1371 
1372 /**
1373  * Set the fields in an id3lib frame from the field in the frame.
1374  *
1375  * @param id3Frame id3lib frame
1376  * @param frame    frame with fields
1377  */
setId3v2Frame(ID3_Frame * id3Frame,const Frame & frame) const1378 void Mp3File::setId3v2Frame(ID3_Frame* id3Frame, const Frame& frame) const
1379 {
1380   ID3_Frame::Iterator* iter = id3Frame->CreateIterator();
1381   ID3_FrameID id3Id = id3Frame->GetID();
1382   ID3_TextEnc enc = ID3TE_NONE;
1383   for (auto fldIt = frame.getFieldList().constBegin();
1384        fldIt != frame.getFieldList().constEnd();
1385        ++fldIt) {
1386     ID3_Field* id3Field = iter->GetNext();
1387     if (!id3Field) {
1388       qDebug("early end of ID3 fields");
1389       break;
1390     }
1391     const Frame::Field& fld = *fldIt;
1392 #if QT_VERSION >= 0x060000
1393     switch (fld.m_value.typeId()) {
1394       case QMetaType::Int:
1395       case QMetaType::UInt:
1396 #else
1397     switch (fld.m_value.type()) {
1398       case QVariant::Int:
1399       case QVariant::UInt:
1400 #endif
1401       {
1402         int intVal = fld.m_value.toInt();
1403         if (fld.m_id == ID3FN_TEXTENC) {
1404           if (intVal == ID3TE_UTF8) intVal = ID3TE_UTF16;
1405           enc = static_cast<ID3_TextEnc>(intVal);
1406         }
1407         id3Field->Set(intVal);
1408         break;
1409       }
1410 
1411 #if QT_VERSION >= 0x060000
1412       case QMetaType::QString:
1413 #else
1414       case QVariant::String:
1415 #endif
1416       {
1417         if (enc != ID3TE_NONE) {
1418           id3Field->SetEncoding(enc);
1419         }
1420         QString value(fld.m_value.toString());
1421         if (id3Id == ID3FID_CONTENTTYPE) {
1422           if (!TagConfig::instance().genreNotNumeric() ||
1423               value.contains(Frame::stringListSeparator())) {
1424             value = Genres::getNumberString(value, true);
1425           }
1426         } else if (id3Id == ID3FID_TRACKNUM) {
1427           formatTrackNumberIfEnabled(value, true);
1428         }
1429         setString(id3Field, value);
1430         break;
1431       }
1432 
1433 #if QT_VERSION >= 0x060000
1434       case QMetaType::QByteArray:
1435 #else
1436       case QVariant::ByteArray:
1437 #endif
1438       {
1439         const QByteArray& ba = fld.m_value.toByteArray();
1440         id3Field->Set(reinterpret_cast<const unsigned char*>(ba.data()),
1441                       static_cast<size_t>(ba.size()));
1442         break;
1443       }
1444 
1445 #if QT_VERSION >= 0x060000
1446       case QMetaType::QVariantList:
1447 #else
1448       case QVariant::List:
1449 #endif
1450       {
1451         if (id3Id == ID3FID_SYNCEDLYRICS) {
1452           QByteArray ba = syltListToBytes(fld.m_value.toList(), enc);
1453           id3Field->Set(reinterpret_cast<const unsigned char*>(ba.data()),
1454                         static_cast<size_t>(ba.size()));
1455         } else if (id3Id == ID3FID_EVENTTIMING) {
1456           QByteArray ba = etcoListToBytes(fld.m_value.toList());
1457           // id3lib bug: There is only a single data field for ETCO frames,
1458           // but it should be preceded by an ID_TimestampFormat field.
1459           ba.prepend(2);
1460           id3Field->Set(reinterpret_cast<const unsigned char*>(ba.data()),
1461                         static_cast<size_t>(ba.size()));
1462         } else {
1463           qDebug("Unexpected QVariantList in field %d", fld.m_id);
1464         }
1465         break;
1466       }
1467 
1468       default:
1469 #if QT_VERSION >= 0x060000
1470         qDebug("Unknown type %d in field %d", fld.m_value.typeId(), fld.m_id);
1471 #else
1472         qDebug("Unknown type %d in field %d", fld.m_value.type(), fld.m_id);
1473 #endif
1474     }
1475   }
1476 #ifdef Q_OS_WIN32
1477   /* allocated in Windows DLL => must be freed in the same DLL */
1478   ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
1479 #else
1480 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1481 #pragma GCC diagnostic push
1482 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
1483 /* Is safe because iterator implementation has default destructor */
1484 #endif
1485   delete iter;
1486 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1487 #pragma GCC diagnostic pop
1488 #endif
1489 #endif
1490 }
1491 
1492 /**
1493  * Get a specific frame from the tags.
1494  *
1495  * @param tagNr tag number
1496  * @param type  frame type
1497  * @param frame the frame is returned here
1498  *
1499  * @return true if ok.
1500  */
1501 bool Mp3File::getFrame(Frame::TagNumber tagNr, Frame::Type type, Frame& frame) const
1502 {
1503   if (type < Frame::FT_FirstFrame || type > Frame::FT_LastV1Frame)
1504     return false;
1505 
1506   ID3_FrameID frameId = getId3libFrameIdForType(type);
1507   if (frameId == ID3FID_NOFRAME)
1508     return false;
1509 
1510   const ID3_Tag* tag;
1511 #if QT_VERSION >= 0x060000
1512   QStringDecoder* decoder;
1513 #else
1514   const QTextCodec* codec;
1515 #endif
1516   if (tagNr == Frame::Tag_1) {
1517     tag = m_tagV1.data();
1518 #if QT_VERSION >= 0x060000
1519     decoder = &s_decoderV1;
1520 #else
1521     codec = s_textCodecV1;
1522 #endif
1523   } else if (tagNr == Frame::Tag_2) {
1524     tag = m_tagV2.data();
1525 #if QT_VERSION >= 0x060000
1526     decoder = nullptr;
1527 #else
1528     codec = nullptr;
1529 #endif
1530   } else {
1531     return false;
1532   }
1533 
1534   switch (type) {
1535   case Frame::FT_Album:
1536   case Frame::FT_Artist:
1537   case Frame::FT_Comment:
1538   case Frame::FT_Title:
1539 #if QT_VERSION >= 0x060000
1540     frame.setValue(getTextField(tag, frameId, decoder));
1541 #else
1542     frame.setValue(getTextField(tag, frameId, codec));
1543 #endif
1544     break;
1545   case Frame::FT_Track:
1546     if (tagNr == Frame::Tag_1) {
1547       frame.setValueAsNumber(getTrackNum(tag));
1548     } else {
1549       frame.setValue(getTextField(tag, frameId));
1550     }
1551     break;
1552   case Frame::FT_Date:
1553     frame.setValueAsNumber(getYear(tag));
1554     break;
1555   case Frame::FT_Genre:
1556   {
1557     int num = getGenreNum(tag);
1558     if (tagNr == Frame::Tag_1) {
1559       frame.setValue(num == -1
1560                      ? QString()
1561                      : num == 0xff
1562                        ? QLatin1String("")
1563                        : QString::fromLatin1(Genres::getName(num)));
1564     } else {
1565       frame.setValue(num != 0xff && num != -1
1566           ? QString::fromLatin1(Genres::getName(num))
1567           : getTextField(tag, frameId));
1568     }
1569     break;
1570   }
1571   default:
1572     return false;
1573   }
1574   frame.setType(type);
1575   return true;
1576 }
1577 
1578 /**
1579  * Set a frame in the tags.
1580  *
1581  * @param tagNr tag number
1582  * @param frame frame to set
1583  *
1584  * @return true if ok.
1585  */
1586 bool Mp3File::setFrame(Frame::TagNumber tagNr, const Frame& frame)
1587 {
1588   if (tagNr == Frame::Tag_2) {
1589     // If the frame has an index, change that specific frame
1590     int index = frame.getIndex();
1591     if (index != -1 && m_tagV2) {
1592       ID3_Frame* id3Frame = getId3v2Frame(m_tagV2.data(), index);
1593       if (id3Frame) {
1594         // If value is changed or field list is empty,
1595         // set from value, else from FieldList.
1596         if (frame.isValueChanged() || frame.getFieldList().empty()) {
1597           QString value(frame.getValue());
1598           ID3_Field* fld;
1599           if ((fld = id3Frame->GetField(ID3FN_URL)) != nullptr) {
1600             if (getString(fld) != value) {
1601               fld->Set(value.toLatin1().data());
1602               markTagChanged(Frame::Tag_2, frame.getType());
1603             }
1604             return true;
1605           } else if ((fld = id3Frame->GetField(ID3FN_TEXT)) != nullptr ||
1606               (fld = id3Frame->GetField(ID3FN_DESCRIPTION)) != nullptr) {
1607             ID3_FrameID id = id3Frame->GetID();
1608             if (id == ID3FID_CONTENTTYPE) {
1609               if (!TagConfig::instance().genreNotNumeric() ||
1610                   value.contains(Frame::stringListSeparator())) {
1611                 value = Genres::getNumberString(value, true);
1612               }
1613             } else if (id == ID3FID_TRACKNUM) {
1614               formatTrackNumberIfEnabled(value, true);
1615             }
1616             bool hasEnc;
1617             const ID3_TextEnc enc = fld->GetEncoding();
1618             ID3_TextEnc newEnc = static_cast<ID3_TextEnc>(
1619                   frame.getFieldValue(Frame::ID_TextEnc).toInt(&hasEnc));
1620             if (!hasEnc) {
1621               newEnc = enc;
1622             }
1623             if (newEnc != ID3TE_ISO8859_1 && newEnc != ID3TE_UTF16) {
1624               // only ISO-8859-1 and UTF16 are allowed for ID3v2.3.0
1625               newEnc = ID3TE_UTF16;
1626             }
1627             if (newEnc == ID3TE_ISO8859_1) {
1628               // check if information is lost if the string is not unicode
1629               int i, unicode_size = value.length();
1630               const QChar* qcarray = value.unicode();
1631               for (i = 0; i < unicode_size; i++) {
1632                 char ch = qcarray[i].toLatin1();
1633                 if (ch == 0 || (ch & 0x80) != 0) {
1634                   newEnc = ID3TE_UTF16;
1635                   break;
1636                 }
1637               }
1638             }
1639             if (enc != newEnc && id != ID3FID_SYNCEDLYRICS) {
1640               ID3_Field* encfld = id3Frame->GetField(ID3FN_TEXTENC);
1641               if (encfld) {
1642                 encfld->Set(newEnc);
1643               }
1644               fld->SetEncoding(newEnc);
1645               markTagChanged(Frame::Tag_2, frame.getType());
1646             }
1647             if (getString(fld) != value) {
1648               setString(fld, value);
1649               markTagChanged(Frame::Tag_2, frame.getType());
1650             }
1651             return true;
1652           } else if (id3Frame->GetID() == ID3FID_PRIVATE &&
1653                      (fld = id3Frame->GetField(ID3FN_DATA)) != nullptr) {
1654             ID3_Field* ownerFld = id3Frame->GetField(ID3FN_OWNER);
1655             QString owner;
1656             QByteArray newData, oldData;
1657             if (ownerFld && !(owner = getString(ownerFld)).isEmpty() &&
1658                 AttributeData(owner).toByteArray(value, newData)) {
1659               oldData =
1660                   QByteArray(reinterpret_cast<const char*>(fld->GetRawBinary()),
1661                              static_cast<int>(fld->Size()));
1662               if (newData != oldData) {
1663                 fld->Set(reinterpret_cast<const unsigned char*>(newData.data()),
1664                          newData.size());
1665                 markTagChanged(Frame::Tag_2, frame.getType());
1666               }
1667               return true;
1668             }
1669           } else if (id3Frame->GetID() == ID3FID_CDID &&
1670                      (fld = id3Frame->GetField(ID3FN_DATA)) != nullptr) {
1671             QByteArray newData, oldData;
1672             if (AttributeData::isHexString(value, 'F', QLatin1String("+")) &&
1673                 AttributeData(AttributeData::Utf16).toByteArray(value, newData)) {
1674               oldData = QByteArray(reinterpret_cast<const char*>(fld->GetRawBinary()),
1675                                    static_cast<int>(fld->Size()));
1676               if (newData != oldData) {
1677                 fld->Set(reinterpret_cast<const unsigned char*>(newData.data()),
1678                          static_cast<size_t>(newData.size()));
1679                 markTagChanged(Frame::Tag_2, frame.getType());
1680               }
1681               return true;
1682             }
1683           } else if (id3Frame->GetID() == ID3FID_UNIQUEFILEID &&
1684                      (fld = id3Frame->GetField(ID3FN_DATA)) != nullptr) {
1685             QByteArray newData, oldData;
1686             if (AttributeData::isHexString(value, 'Z', QLatin1String("-"))) {
1687               newData = (value + QLatin1Char('\0')).toLatin1();
1688               oldData =
1689                   QByteArray(reinterpret_cast<const char*>(fld->GetRawBinary()),
1690                              static_cast<int>(fld->Size()));
1691               if (newData != oldData) {
1692                 fld->Set(reinterpret_cast<const unsigned char*>(newData.data()),
1693                          static_cast<size_t>(newData.size()));
1694                 markTagChanged(Frame::Tag_2, frame.getType());
1695               }
1696               return true;
1697             }
1698           } else if (id3Frame->GetID() == ID3FID_POPULARIMETER &&
1699                      (fld = id3Frame->GetField(ID3FN_RATING)) != nullptr) {
1700             if (getString(fld) != value) {
1701               fld->Set(value.toInt());
1702               markTagChanged(Frame::Tag_2, frame.getType());
1703             }
1704             return true;
1705           }
1706         } else {
1707           setId3v2Frame(id3Frame, frame);
1708           markTagChanged(Frame::Tag_2, frame.getType());
1709           return true;
1710         }
1711       }
1712     }
1713   }
1714 
1715   // Try the basic method
1716   Frame::Type type = frame.getType();
1717   if (type < Frame::FT_FirstFrame || type > Frame::FT_LastV1Frame)
1718     return false;
1719 
1720   ID3_FrameID frameId = getId3libFrameIdForType(type);
1721   if (frameId == ID3FID_NOFRAME)
1722     return false;
1723 
1724   ID3_Tag* tag;
1725 #if QT_VERSION >= 0x060000
1726   QStringDecoder* decoder;
1727   QStringEncoder* encoder;
1728 #else
1729   const QTextCodec* codec;
1730 #endif
1731   bool allowUnicode;
1732   if (tagNr == Frame::Tag_1) {
1733     tag = m_tagV1.data();
1734 #if QT_VERSION >= 0x060000
1735     decoder = &s_decoderV1;
1736     encoder = &s_encoderV1;
1737 #else
1738     codec = s_textCodecV1;
1739 #endif
1740     allowUnicode = false;
1741   } else if (tagNr == Frame::Tag_2) {
1742     tag = m_tagV2.data();
1743 #if QT_VERSION >= 0x060000
1744     decoder = nullptr;
1745     encoder = nullptr;
1746 #else
1747     codec = nullptr;
1748 #endif
1749     allowUnicode = true;
1750   } else {
1751     return false;
1752   }
1753 
1754   switch (type) {
1755   case Frame::FT_Album:
1756   case Frame::FT_Artist:
1757   case Frame::FT_Comment:
1758   case Frame::FT_Title:
1759   {
1760     QString str = frame.getValue();
1761 #if QT_VERSION >= 0x060000
1762     if (getTextField(tag, frameId, decoder) != str &&
1763         setTextField(tag, frameId, str, allowUnicode, true, true, encoder)) {
1764 #else
1765     if (getTextField(tag, frameId, codec) != str &&
1766         setTextField(tag, frameId, str, allowUnicode, true, true, codec)) {
1767 #endif
1768       markTagChanged(tagNr, type);
1769       QString s = checkTruncation(tagNr, str, 1ULL << type,
1770                                   type == Frame::FT_Comment ? 28 : 30);
1771       if (!s.isNull())
1772 #if QT_VERSION >= 0x060000
1773         setTextField(tag, frameId, s, allowUnicode, true, true, encoder);
1774 #else
1775         setTextField(tag, frameId, s, allowUnicode, true, true, codec);
1776 #endif
1777     }
1778     break;
1779   }
1780   case Frame::FT_Date:
1781   {
1782     int num = frame.getValueAsNumber();
1783     if (setYear(tag, num)) {
1784       markTagChanged(tagNr, type);
1785     }
1786     break;
1787   }
1788   case Frame::FT_Genre:
1789   {
1790     QString str = frame.getValue();
1791     if (tagNr == Frame::Tag_1) {
1792       if (!str.isNull()) {
1793         int num = Genres::getNumber(str);
1794         if (setGenreNum(tag, num)) {
1795           markTagChanged(tagNr, type);
1796         }
1797         // if the string cannot be converted to a number, set the truncation flag
1798         checkTruncation(tagNr, num == 0xff && !str.isEmpty() ? 1 : 0,
1799                         1ULL << type, 0);
1800       }
1801     } else {
1802       if (!str.isNull()) {
1803         int num = 0xff;
1804         if (str.contains(Frame::stringListSeparator())) {
1805           str = Genres::getNumberString(str, true);
1806         } else if (!TagConfig::instance().genreNotNumeric()) {
1807           num = Genres::getNumber(str);
1808         }
1809         if (num >= 0 && num != 0xff) {
1810           if (getGenreNum(tag) != num &&
1811               setGenreNum(tag, num)) {
1812             markTagChanged(tagNr, type);
1813           }
1814         } else {
1815 #if QT_VERSION >= 0x060000
1816           if (getTextField(tag, frameId, decoder) != str &&
1817               setTextField(tag, frameId, str, allowUnicode, true, true, encoder)) {
1818 #else
1819           if (getTextField(tag, frameId, codec) != str &&
1820               setTextField(tag, frameId, str, allowUnicode, true, true, codec)) {
1821 #endif
1822             markTagChanged(tagNr, type);
1823           }
1824         }
1825       }
1826     }
1827     break;
1828   }
1829   case Frame::FT_Track:
1830   {
1831     if (tagNr == Frame::Tag_1) {
1832       int num = frame.getValueAsNumber();
1833       if (setTrackNum(tag, num)) {
1834         markTagChanged(tagNr, type);
1835         int n = checkTruncation(tagNr, num, 1ULL << type);
1836         if (n != -1) setTrackNum(tag, n);
1837       }
1838     } else {
1839       QString str = frame.getValue();
1840       int numTracks;
1841       int num = splitNumberAndTotal(str, &numTracks);
1842       if (setTrackNum(tag, num, numTracks)) {
1843         markTagChanged(tagNr, type);
1844       }
1845     }
1846     break;
1847   }
1848   default:
1849     return false;
1850   }
1851   return true;
1852 }
1853 
1854 /**
1855  * Create an id3lib frame from a frame.
1856  * @param frame frame
1857  * @return id3lib frame, 0 if invalid.
1858  */
1859 ID3_Frame* Mp3File::createId3FrameFromFrame(Frame& frame) const
1860 {
1861   ID3_Frame* id3Frame = nullptr;
1862   ID3_FrameID id;
1863   if (frame.getType() != Frame::FT_Other) {
1864     id = getId3libFrameIdForType(frame.getType());
1865   } else {
1866     id = getId3libFrameIdForName(frame.getName());
1867     if (id == ID3FID_NOFRAME) {
1868       if (frame.getName() == QLatin1String("AverageLevel") ||
1869           frame.getName() == QLatin1String("PeakValue") ||
1870           frame.getName().startsWith(QLatin1String("WM/"))) {
1871         id = ID3FID_PRIVATE;
1872       } else if (frame.getName().startsWith(QLatin1String("iTun"))) {
1873         id = ID3FID_COMMENT;
1874       } else {
1875         id = ID3FID_USERTEXT;
1876       }
1877     }
1878   }
1879   if (id != ID3FID_NOFRAME && id != ID3FID_SETSUBTITLE) {
1880     id3Frame = new ID3_Frame(id);
1881     ID3_Field* fld = id3Frame->GetField(ID3FN_TEXT);
1882     if (fld) {
1883       ID3_TextEnc enc = getDefaultTextEncoding();
1884       ID3_Field* encfld = id3Frame->GetField(ID3FN_TEXTENC);
1885       if (encfld) {
1886         encfld->Set(enc);
1887       }
1888       fld->SetEncoding(enc);
1889     }
1890     if (id == ID3FID_USERTEXT &&
1891         !frame.getName().startsWith(QLatin1String("TXXX"))) {
1892       fld = id3Frame->GetField(ID3FN_DESCRIPTION);
1893       if (fld) {
1894         QString description;
1895         if (frame.getType() == Frame::FT_CatalogNumber) {
1896           description = QLatin1String("CATALOGNUMBER");
1897         } else if (frame.getType() == Frame::FT_ReleaseCountry) {
1898           description = QLatin1String("RELEASECOUNTRY");
1899         } else if (frame.getType() == Frame::FT_Grouping) {
1900           description = QLatin1String("GROUPING");
1901         } else if (frame.getType() == Frame::FT_Subtitle) {
1902           description = QLatin1String("SUBTITLE");
1903         } else {
1904           description = frame.getName();
1905         }
1906         setString(fld, description);
1907       }
1908     } else if (id == ID3FID_COMMENT) {
1909       fld = id3Frame->GetField(ID3FN_LANGUAGE);
1910       if (fld) {
1911         setString(fld, QLatin1String("eng"));
1912       }
1913       if (frame.getType() == Frame::FT_Other) {
1914         fld = id3Frame->GetField(ID3FN_DESCRIPTION);
1915         if (fld) {
1916           setString(fld, frame.getName());
1917         }
1918       }
1919     } else if (id == ID3FID_PRIVATE &&
1920                !frame.getName().startsWith(QLatin1String("PRIV"))) {
1921       fld = id3Frame->GetField(ID3FN_OWNER);
1922       if (fld) {
1923         setString(fld, frame.getName());
1924         QByteArray data;
1925         if (AttributeData(frame.getName()).toByteArray(frame.getValue(), data)) {
1926           fld = id3Frame->GetField(ID3FN_DATA);
1927           if (fld) {
1928             fld->Set(reinterpret_cast<const unsigned char*>(data.data()),
1929                      static_cast<size_t>(data.size()));
1930           }
1931         }
1932       }
1933     } else if (id == ID3FID_UNIQUEFILEID) {
1934       fld = id3Frame->GetField(ID3FN_OWNER);
1935       if (fld) {
1936         setString(fld, QLatin1String("http://www.id3.org/dummy/ufid.html"));
1937       }
1938       QByteArray data;
1939       if (AttributeData::isHexString(frame.getValue(), 'Z', QLatin1String("-"))) {
1940         data = (frame.getValue() + QLatin1Char('\0')).toLatin1();
1941         fld = id3Frame->GetField(ID3FN_DATA);
1942         if (fld) {
1943           fld->Set(reinterpret_cast<const unsigned char*>(data.data()),
1944                    static_cast<size_t>(data.size()));
1945         }
1946       }
1947     } else if (id == ID3FID_PICTURE) {
1948       fld = id3Frame->GetField(ID3FN_MIMETYPE);
1949       if (fld) {
1950         setString(fld, QLatin1String("image/jpeg"));
1951       }
1952       fld = id3Frame->GetField(ID3FN_PICTURETYPE);
1953       if (fld) {
1954         fld->Set(ID3PT_COVERFRONT);
1955       }
1956     } else if (id == ID3FID_SYNCEDLYRICS) {
1957       fld = id3Frame->GetField(ID3FN_LANGUAGE);
1958       if (fld) {
1959         setString(fld, QLatin1String("eng"));
1960       }
1961       fld = id3Frame->GetField(ID3FN_TIMESTAMPFORMAT);
1962       if (fld) {
1963         fld->Set(ID3TSF_MS);
1964       }
1965       fld = id3Frame->GetField(ID3FN_CONTENTTYPE);
1966       if (fld) {
1967         fld->Set(ID3CT_LYRICS);
1968       }
1969     } else if (id == ID3FID_UNSYNCEDLYRICS || id == ID3FID_TERMSOFUSE) {
1970       fld = id3Frame->GetField(ID3FN_LANGUAGE);
1971       if (fld) {
1972         setString(fld, QLatin1String("eng"));
1973       }
1974     } else if (id == ID3FID_POPULARIMETER) {
1975       fld = id3Frame->GetField(ID3FN_EMAIL);
1976       if (fld) {
1977         setString(fld, TagConfig::instance().defaultPopmEmail());
1978       }
1979     }
1980     if (!frame.fieldList().empty()) {
1981       setId3v2Frame(id3Frame, frame);
1982     }
1983     Frame::Type type;
1984     const char* name;
1985     getTypeStringForId3libFrameId(id, type, name);
1986     frame.setExtendedType(Frame::ExtendedType(type, QString::fromLatin1(name)));
1987   }
1988   return id3Frame;
1989 }
1990 
1991 /**
1992  * Add a frame in the tags.
1993  *
1994  * @param tagNr tag number
1995  * @param frame frame to add, a field list may be added by this method
1996  *
1997  * @return true if ok.
1998  */
1999 bool Mp3File::addFrame(Frame::TagNumber tagNr, Frame& frame)
2000 {
2001   if (tagNr == Frame::Tag_2) {
2002     // Add a new frame.
2003     ID3_Frame* id3Frame;
2004     if (m_tagV2 && (id3Frame = createId3FrameFromFrame(frame)) != nullptr) {
2005       m_tagV2->AttachFrame(id3Frame);
2006       frame.setIndex(m_tagV2->NumFrames() - 1);
2007       if (frame.fieldList().empty()) {
2008         // add field list to frame
2009         getFieldsFromId3Frame(id3Frame, frame.fieldList());
2010         frame.setFieldListFromValue();
2011       }
2012       markTagChanged(Frame::Tag_2, frame.getType());
2013       return true;
2014     }
2015   }
2016 
2017   // Try the superclass method
2018   return TaggedFile::addFrame(tagNr, frame);
2019 }
2020 
2021 /**
2022  * Delete a frame from the tags.
2023  *
2024  * @param tagNr tag number
2025  * @param frame frame to delete.
2026  *
2027  * @return true if ok.
2028  */
2029 bool Mp3File::deleteFrame(Frame::TagNumber tagNr, const Frame& frame)
2030 {
2031   if (tagNr == Frame::Tag_2) {
2032     // If the frame has an index, delete that specific frame
2033     int index = frame.getIndex();
2034     if (index != -1 && m_tagV2) {
2035       ID3_Frame* id3Frame = getId3v2Frame(m_tagV2.data(), index);
2036       if (id3Frame) {
2037         m_tagV2->RemoveFrame(id3Frame);
2038         markTagChanged(Frame::Tag_2, frame.getType());
2039         return true;
2040       }
2041     }
2042   }
2043 
2044   // Try the superclass method
2045   return TaggedFile::deleteFrame(tagNr, frame);
2046 }
2047 
2048 namespace {
2049 
2050 /**
2051  * Create a frame from an id3lib frame.
2052  * @param id3Frame id3lib frame
2053  * @param index, -1 if not used
2054  * @return frame.
2055  */
2056 Frame createFrameFromId3libFrame(ID3_Frame* id3Frame, int index)
2057 {
2058   Frame::Type type;
2059   const char* name;
2060   getTypeStringForId3libFrameId(id3Frame->GetID(), type, name);
2061   Frame frame(type, QLatin1String(""), QString::fromLatin1(name), index);
2062   frame.setValue(getFieldsFromId3Frame(id3Frame, frame.fieldList()));
2063   if (id3Frame->GetID() == ID3FID_USERTEXT ||
2064       id3Frame->GetID() == ID3FID_WWWUSER ||
2065       id3Frame->GetID() == ID3FID_COMMENT) {
2066     QVariant fieldValue = frame.getFieldValue(Frame::ID_Description);
2067     if (fieldValue.isValid()) {
2068       QString description = fieldValue.toString();
2069       if (!description.isEmpty()) {
2070         if (description == QLatin1String("CATALOGNUMBER")) {
2071           frame.setType(Frame::FT_CatalogNumber);
2072         } else if (description == QLatin1String("RELEASECOUNTRY")) {
2073           frame.setType(Frame::FT_ReleaseCountry);
2074         } else if (description == QLatin1String("GROUPING")) {
2075           frame.setType(Frame::FT_Grouping);
2076         } else if (description == QLatin1String("SUBTITLE")) {
2077           frame.setType(Frame::FT_Subtitle);
2078         } else {
2079           frame.setExtendedType(Frame::ExtendedType(Frame::FT_Other,
2080               frame.getInternalName() + QLatin1Char('\n') + description));
2081         }
2082       }
2083     }
2084   } else if (id3Frame->GetID() == ID3FID_PRIVATE) {
2085     const Frame::FieldList& fields = frame.getFieldList();
2086     QString owner;
2087     QByteArray data;
2088     for (auto it = fields.constBegin(); it != fields.constEnd(); ++it) {
2089       if ((*it).m_id == Frame::ID_Owner) {
2090         owner = (*it).m_value.toString();
2091         if (!owner.isEmpty()) {
2092           frame.setExtendedType(Frame::ExtendedType(Frame::FT_Other,
2093                     frame.getInternalName() + QLatin1Char('\n') + owner));
2094         }
2095       } else if ((*it).m_id == Frame::ID_Data) {
2096         data = (*it).m_value.toByteArray();
2097       }
2098     }
2099     if (!owner.isEmpty() && !data.isEmpty()) {
2100       QString str;
2101       if (AttributeData(owner).toString(data, str)) {
2102         frame.setValue(str);
2103       }
2104     }
2105   } else if (id3Frame->GetID() == ID3FID_CDID) {
2106     QVariant fieldValue = frame.getFieldValue(Frame::ID_Data);
2107     if (fieldValue.isValid()) {
2108       QString str;
2109       if (AttributeData(AttributeData::Utf16)
2110           .toString(fieldValue.toByteArray(), str) &&
2111           AttributeData::isHexString(str, 'F', QLatin1String("+"))) {
2112         frame.setValue(str);
2113       }
2114     }
2115   } else if (id3Frame->GetID() == ID3FID_UNIQUEFILEID) {
2116     QVariant fieldValue = frame.getFieldValue(Frame::ID_Data);
2117     if (fieldValue.isValid()) {
2118       QByteArray ba(fieldValue.toByteArray());
2119       QString str(QString::fromLatin1(ba));
2120       if (ba.size() - str.length() <= 1 &&
2121           AttributeData::isHexString(str, 'Z', QLatin1String("-"))) {
2122         frame.setValue(str);
2123       }
2124     }
2125   } else if (id3Frame->GetID() == ID3FID_POPULARIMETER) {
2126     QVariant fieldValue = frame.getFieldValue(Frame::ID_Rating);
2127     if (fieldValue.isValid()) {
2128       QString str(fieldValue.toString());
2129       if (!str.isEmpty()) {
2130         frame.setValue(str);
2131       }
2132     }
2133   }
2134   return frame;
2135 }
2136 
2137 }
2138 
2139 /**
2140  * Remove frames.
2141  *
2142  * @param tagNr tag number
2143  * @param flt filter specifying which frames to remove
2144  */
2145 void Mp3File::deleteFrames(Frame::TagNumber tagNr, const FrameFilter& flt)
2146 {
2147   if (tagNr == Frame::Tag_1) {
2148     if (m_tagV1) {
2149       if (flt.areAllEnabled()) {
2150         ID3_Tag::Iterator* iter = m_tagV1->CreateIterator();
2151         ID3_Frame* frame;
2152         while ((frame = iter->GetNext()) != nullptr) {
2153           m_tagV1->RemoveFrame(frame);
2154         }
2155 #ifdef Q_OS_WIN32
2156         /* allocated in Windows DLL => must be freed in the same DLL */
2157         ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
2158 #else
2159 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2160 #pragma GCC diagnostic push
2161 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
2162 /* Is safe because iterator implementation has default destructor */
2163 #endif
2164         delete iter;
2165 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2166 #pragma GCC diagnostic pop
2167 #endif
2168 #endif
2169         markTagChanged(Frame::Tag_1, Frame::FT_UnknownFrame);
2170         clearTrunctionFlags(Frame::Tag_1);
2171       } else {
2172         TaggedFile::deleteFrames(Frame::Tag_1, flt);
2173       }
2174     }
2175   } else if (tagNr == Frame::Tag_2) {
2176     if (m_tagV2) {
2177       if (flt.areAllEnabled()) {
2178         ID3_Tag::Iterator* iter = m_tagV2->CreateIterator();
2179         ID3_Frame* frame;
2180         while ((frame = iter->GetNext()) != nullptr) {
2181           m_tagV2->RemoveFrame(frame);
2182         }
2183 #ifdef Q_OS_WIN32
2184         /* allocated in Windows DLL => must be freed in the same DLL */
2185         ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
2186 #else
2187 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2188 #pragma GCC diagnostic push
2189 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
2190 /* Is safe because iterator implementation has default destructor */
2191 #endif
2192         delete iter;
2193 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2194 #pragma GCC diagnostic pop
2195 #endif
2196 #endif
2197         markTagChanged(Frame::Tag_2, Frame::FT_UnknownFrame);
2198       } else {
2199         ID3_Tag::Iterator* iter = m_tagV2->CreateIterator();
2200         ID3_Frame* id3Frame;
2201         while ((id3Frame = iter->GetNext()) != nullptr) {
2202           Frame frame(createFrameFromId3libFrame(id3Frame, -1));
2203           if (flt.isEnabled(frame.getType(), frame.getName())) {
2204             m_tagV2->RemoveFrame(id3Frame);
2205           }
2206         }
2207   #ifdef Q_OS_WIN32
2208         /* allocated in Windows DLL => must be freed in the same DLL */
2209         ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
2210   #else
2211   #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2212   #pragma GCC diagnostic push
2213   #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
2214   /* Is safe because iterator implementation has default destructor */
2215   #endif
2216         delete iter;
2217   #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2218   #pragma GCC diagnostic pop
2219   #endif
2220   #endif
2221         markTagChanged(Frame::Tag_2, Frame::FT_UnknownFrame);
2222       }
2223     }
2224   }
2225 }
2226 
2227 /**
2228  * Get all frames in tag.
2229  *
2230  * @param tagNr tag number
2231  * @param frames frame collection to set.
2232  */
2233 void Mp3File::getAllFrames(Frame::TagNumber tagNr, FrameCollection& frames)
2234 {
2235   if (tagNr == Frame::Tag_2) {
2236     frames.clear();
2237     if (m_tagV2) {
2238       ID3_Tag::Iterator* iter = m_tagV2->CreateIterator();
2239       ID3_Frame* id3Frame;
2240       int i = 0;
2241       while ((id3Frame = iter->GetNext()) != nullptr) {
2242         Frame frame(createFrameFromId3libFrame(id3Frame, i++));
2243         frames.insert(frame);
2244       }
2245 #ifdef Q_OS_WIN32
2246       /* allocated in Windows DLL => must be freed in the same DLL */
2247       ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
2248 #else
2249 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2250 #pragma GCC diagnostic push
2251 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
2252 /* Is safe because iterator implementation has default destructor */
2253 #endif
2254       delete iter;
2255 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2256 #pragma GCC diagnostic pop
2257 #endif
2258 #endif
2259     }
2260     updateMarkedState(tagNr, frames);
2261     frames.addMissingStandardFrames();
2262     return;
2263   }
2264 
2265   TaggedFile::getAllFrames(tagNr, frames);
2266 }
2267 
2268 /**
2269  * Add a suitable field list for the frame if missing.
2270  * If a frame is created, its field list is empty. This method will create
2271  * a field list appropriate for the frame type and tagged file type if no
2272  * field list exists.
2273  * @param tagNr tag number
2274  * @param frame frame where field list is added
2275  */
2276 void Mp3File::addFieldList(Frame::TagNumber tagNr, Frame& frame) const
2277 {
2278   if (tagNr == Frame::Tag_2 && frame.fieldList().isEmpty()) {
2279     ID3_Frame* id3Frame = createId3FrameFromFrame(frame);
2280     getFieldsFromId3Frame(id3Frame, frame.fieldList());
2281     frame.setFieldListFromValue();
2282     delete id3Frame;
2283   }
2284 }
2285 
2286 /**
2287  * Get a list of frame IDs which can be added.
2288  * @param tagNr tag number
2289  * @return list with frame IDs.
2290  */
2291 QStringList Mp3File::getFrameIds(Frame::TagNumber tagNr) const
2292 {
2293   if (tagNr != Frame::Tag_2)
2294     return QStringList();
2295 
2296   QStringList lst;
2297   for (int type = Frame::FT_FirstFrame; type <= Frame::FT_LastFrame; ++type) {
2298     lst.append(Frame::ExtendedType(static_cast<Frame::Type>(type),
2299                                    QLatin1String("")).getName());
2300   }
2301   for (int i = 0; i <= ID3FID_WWWUSER; ++i) {
2302     if (typeStrOfId[i].type == Frame::FT_Other) {
2303       const char* s = typeStrOfId[i].str;
2304       if (s) {
2305         lst.append(QString::fromLatin1(s));
2306       }
2307     }
2308   }
2309   return lst;
2310 }
2311 
2312 /**
2313  * Set the encoding to be used for tag 1.
2314  *
2315  * @param name of encoding, default is ISO 8859-1
2316  */
2317 void Mp3File::setTextEncodingV1(const QString& name)
2318 {
2319 #if QT_VERSION >= 0x060000
2320   auto encoding = QStringConverter::encodingForName(name.toLatin1());
2321   s_decoderV1 = QStringDecoder(encoding ? encoding.value()
2322                                         : QStringConverter::Latin1);
2323   s_encoderV1 = QStringEncoder(encoding ? encoding.value()
2324                                         : QStringConverter::Latin1);
2325 #else
2326   s_textCodecV1 = name != QLatin1String("ISO-8859-1")
2327       ? QTextCodec::codecForName(name.toLatin1().data()) : nullptr;
2328 #endif
2329 }
2330 
2331 /**
2332  * Set the default text encoding.
2333  *
2334  * @param textEnc default text encoding
2335  */
2336 void Mp3File::setDefaultTextEncoding(TagConfig::TextEncoding textEnc)
2337 {
2338   // UTF8 encoding is buggy, so UTF16 is used when UTF8 is configured
2339   s_defaultTextEncoding = textEnc == TagConfig::TE_ISO8859_1 ?
2340     ID3TE_ISO8859_1 : ID3TE_UTF16;
2341 }
2342 
2343 /**
2344  * Notify about configuration change.
2345  * This method shall be called when the configuration changes.
2346  */
2347 void Mp3File::notifyConfigurationChange()
2348 {
2349   setDefaultTextEncoding(
2350     static_cast<TagConfig::TextEncoding>(TagConfig::instance().textEncoding()));
2351   setTextEncodingV1(TagConfig::instance().textEncodingV1());
2352 }
2353