File indexing completed on 2024-05-12 04:55:38

0001 /**
0002  * \file mp3file.cpp
0003  * Handling of tagged MP3 files.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 9 Jan 2003
0008  *
0009  * Copyright (C) 2003-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "mp3file.h"
0028 
0029 #include <QDir>
0030 #include <QString>
0031 #if QT_VERSION >= 0x060000
0032 #include <QStringConverter>
0033 #else
0034 #include <QTextCodec>
0035 #endif
0036 #include <QByteArray>
0037 #include <QtEndian>
0038 
0039 #include <cstring>
0040 #include <id3/tag.h>
0041 #ifdef Q_OS_WIN32
0042 #include <id3.h>
0043 #endif
0044 
0045 #include "id3libconfig.h"
0046 #include "genres.h"
0047 #include "attributedata.h"
0048 
0049 #ifdef Q_OS_WIN32
0050 /**
0051  * This will be set for id3lib versions with Unicode bugs.
0052  * ID3LIB_ symbols cannot be found on Windows ?!
0053  */
0054 #define UNICODE_SUPPORT_BUGGY 1
0055 #else
0056 /** This will be set for id3lib versions with Unicode bugs. */
0057 #define UNICODE_SUPPORT_BUGGY ((((ID3LIB_MAJOR_VERSION) << 16) + \
0058   ((ID3LIB_MINOR_VERSION) << 8) + (ID3LIB_PATCH_VERSION)) <= 0x030803)
0059 #endif
0060 
0061 #if defined __GNUC__ && (__GNUC__ * 100 + __GNUC_MINOR__) >= 407
0062 /** Defined if GCC is used and supports diagnostic pragmas */
0063 #define GCC_HAS_DIAGNOSTIC_PRAGMA
0064 #endif
0065 
0066 namespace {
0067 
0068 #if QT_VERSION >= 0x060000
0069 /** String decoder for ID3v1 tags, default is ISO 8859-1 */
0070 QStringDecoder s_decoderV1;
0071 QStringEncoder s_encoderV1;
0072 #else
0073 /** Text codec for ID3v1 tags, 0 to use default (ISO 8859-1) */
0074 const QTextCodec* s_textCodecV1 = nullptr;
0075 #endif
0076 
0077 /** Default text encoding */
0078 ID3_TextEnc s_defaultTextEncoding = ID3TE_ISO8859_1;
0079 
0080 /**
0081  * Get the default text encoding.
0082  * @return default text encoding.
0083  */
0084 ID3_TextEnc getDefaultTextEncoding() { return s_defaultTextEncoding; }
0085 
0086 }
0087 
0088 /**
0089  * Constructor.
0090  *
0091  * @param idx index in tagged file system model
0092  */
0093 Mp3File::Mp3File(const QPersistentModelIndex& idx)
0094   : TaggedFile(idx)
0095 {
0096 }
0097 
0098 /**
0099  * Destructor.
0100  */
0101 Mp3File::~Mp3File()
0102 {
0103   // Must not be inline because of forwared declared QScopedPointer.
0104 }
0105 
0106 /**
0107  * Get key of tagged file format.
0108  * @return "Id3libMetadata".
0109  */
0110 QString Mp3File::taggedFileKey() const
0111 {
0112   return QLatin1String("Id3libMetadata");
0113 }
0114 
0115 /**
0116  * Get features supported.
0117  * @return bit mask with Feature flags set.
0118  */
0119 int Mp3File::taggedFileFeatures() const
0120 {
0121   return TF_ID3v11 | TF_ID3v23;
0122 }
0123 
0124 /**
0125  * Read tags from file.
0126  *
0127  * @param force true to force reading even if tags were already read.
0128  */
0129 void Mp3File::readTags(bool force)
0130 {
0131   bool priorIsTagInformationRead = isTagInformationRead();
0132   QByteArray fn = QFile::encodeName(currentFilePath());
0133 
0134   if (force && m_tagV1) {
0135     m_tagV1->Clear();
0136     m_tagV1->Link(fn, ID3TT_ID3V1);
0137     markTagUnchanged(Frame::Tag_1);
0138   }
0139   if (!m_tagV1) {
0140     m_tagV1.reset(new ID3_Tag);
0141     m_tagV1->Link(fn, ID3TT_ID3V1);
0142     markTagUnchanged(Frame::Tag_1);
0143   }
0144 
0145   if (force && m_tagV2) {
0146     m_tagV2->Clear();
0147     m_tagV2->Link(fn, ID3TT_ID3V2);
0148     markTagUnchanged(Frame::Tag_2);
0149   }
0150   if (!m_tagV2) {
0151     m_tagV2.reset(new ID3_Tag);
0152     m_tagV2->Link(fn, ID3TT_ID3V2);
0153     markTagUnchanged(Frame::Tag_2);
0154   }
0155 
0156   if (force) {
0157     setFilename(currentFilename());
0158   }
0159 
0160   notifyModelDataChanged(priorIsTagInformationRead);
0161 }
0162 
0163 /**
0164  * Write tags to file and rename it if necessary.
0165  *
0166  * @param force   true to force writing even if file was not changed.
0167  * @param renamed will be set to true if the file was renamed,
0168  *                i.e. the file name is no longer valid, else *renamed
0169  *                is left unchanged
0170  * @param preserve true to preserve file time stamps
0171  *
0172  * @return true if ok, false if the file could not be written or renamed.
0173  */
0174 bool Mp3File::writeTags(bool force, bool* renamed, bool preserve)
0175 {
0176   QString fnStr(currentFilePath());
0177   if (isChanged() && !QFileInfo(fnStr).isWritable()) {
0178     revertChangedFilename();
0179     return false;
0180   }
0181 
0182   // store time stamp if it has to be preserved
0183   quint64 actime = 0, modtime = 0;
0184   if (preserve) {
0185     getFileTimeStamps(fnStr, actime, modtime);
0186   }
0187 
0188   // There seems to be a bug in id3lib: The V1 genre is not
0189   // removed. So we check here and strip the whole header
0190   // if there are no frames.
0191   if (m_tagV1 && (force || isTagChanged(Frame::Tag_1)) && m_tagV1->NumFrames() == 0) {
0192     m_tagV1->Strip(ID3TT_ID3V1);
0193     markTagUnchanged(Frame::Tag_1);
0194   }
0195   // Even after removing all frames, HasV2Tag() still returns true,
0196   // so we strip the whole header.
0197   if (m_tagV2 && (force || isTagChanged(Frame::Tag_2)) && m_tagV2->NumFrames() == 0) {
0198     m_tagV2->Strip(ID3TT_ID3V2);
0199     markTagUnchanged(Frame::Tag_2);
0200   }
0201   // There seems to be a bug in id3lib: If I update an ID3v1 and then
0202   // strip the ID3v2 the ID3v1 is removed too and vice versa, so I
0203   // first make any stripping and then the updating.
0204   if (m_tagV1 && (force || isTagChanged(Frame::Tag_1)) && m_tagV1->NumFrames() > 0) {
0205     m_tagV1->Update(ID3TT_ID3V1);
0206     markTagUnchanged(Frame::Tag_1);
0207   }
0208   if (m_tagV2 && (force || isTagChanged(Frame::Tag_2)) && m_tagV2->NumFrames() > 0) {
0209     m_tagV2->Update(ID3TT_ID3V2);
0210     markTagUnchanged(Frame::Tag_2);
0211   }
0212 
0213   // restore time stamp
0214   if (actime || modtime) {
0215     setFileTimeStamps(fnStr, actime, modtime);
0216   }
0217 
0218   if (isFilenameChanged()) {
0219     if (!renameFile()) {
0220       return false;
0221     }
0222     markFilenameUnchanged();
0223     // link tags to new file name
0224     readTags(true);
0225     *renamed = true;
0226   }
0227   return true;
0228 }
0229 
0230 /**
0231  * Free resources allocated when calling readTags().
0232  *
0233  * @param force true to force clearing even if the tags are modified
0234  */
0235 void Mp3File::clearTags(bool force)
0236 {
0237   if (isChanged() && !force)
0238     return;
0239 
0240   bool priorIsTagInformationRead = isTagInformationRead();
0241   if (m_tagV1) {
0242     m_tagV1.reset();
0243     markTagUnchanged(Frame::Tag_1);
0244   }
0245   if (m_tagV2) {
0246     m_tagV2.reset();
0247     markTagUnchanged(Frame::Tag_2);
0248   }
0249   notifyModelDataChanged(priorIsTagInformationRead);
0250 }
0251 
0252 namespace {
0253 
0254 /**
0255  * Fix up a unicode string from id3lib.
0256  *
0257  * @param str      unicode string
0258  * @param numChars number of characters in str
0259  *
0260  * @return string as QString.
0261  */
0262 QString fixUpUnicode(const unicode_t* str, size_t numChars)
0263 {
0264   QString text;
0265   if (numChars > 0 && str && *str) {
0266     auto qcarray = new QChar[numChars];
0267     // Unfortunately, Unicode support in id3lib is rather buggy
0268     // in the current version: The codes are mirrored.
0269     // In the hope that my patches will be included, I try here
0270     // to work around these bugs.
0271     size_t numZeroes = 0;
0272     for (size_t i = 0; i < numChars; i++) {
0273       qcarray[i] = UNICODE_SUPPORT_BUGGY
0274           ? static_cast<ushort>(((str[i] & 0x00ff) << 8) |
0275                                 ((str[i] & 0xff00) >> 8))
0276           : static_cast<ushort>(str[i]);
0277       if (qcarray[i].isNull()) { ++numZeroes; }
0278     }
0279     // remove a single trailing zero character
0280     if (numZeroes == 1 && qcarray[numChars - 1].isNull()) {
0281       --numChars;
0282     }
0283     text = QString(qcarray, numChars);
0284     delete [] qcarray;
0285   }
0286   return text;
0287 }
0288 
0289 /**
0290  * Get string from text field.
0291  *
0292  * @param field field
0293  * @param codec text codec to use, 0 for default
0294  *
0295  * @return string,
0296  *         "" if the field does not exist.
0297  */
0298 QString getString(ID3_Field* field,
0299 #if QT_VERSION >= 0x060000
0300                   QStringDecoder* decoder = nullptr
0301 #else
0302                   const QTextCodec* codec = nullptr
0303 #endif
0304     )
0305 {
0306   QString text(QLatin1String(""));
0307   if (field != nullptr) {
0308     if (ID3_TextEnc enc = field->GetEncoding();
0309         enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
0310       if (size_t numItems = field->GetNumTextItems(); numItems <= 1) {
0311         text = fixUpUnicode(field->GetRawUnicodeText(),
0312                             field->Size() / sizeof(unicode_t));
0313       } else {
0314         // if there are multiple items, put them into one string
0315         // separated by a special separator.
0316         // ID3_Field::GetRawUnicodeTextItem() returns a pointer to a temporary
0317         // object, so I do not use it.
0318         text = fixUpUnicode(field->GetRawUnicodeText(),
0319                             field->Size() / sizeof(unicode_t));
0320         text = Frame::joinStringList(text.split(QLatin1Char('\0')));
0321       }
0322     } else {
0323       // (ID3TE_IS_SINGLE_BYTE_ENC(enc))
0324       // (enc == ID3TE_ISO8859_1 || enc == ID3TE_UTF8)
0325       if (size_t numItems = field->GetNumTextItems(); numItems <= 1) {
0326 #if QT_VERSION >= 0x060000
0327         text = decoder ? decoder->decode(QByteArray(field->GetRawText(), field->Size()))
0328                        : QString::fromLatin1(field->GetRawText());
0329 #else
0330         text = codec ? codec->toUnicode(field->GetRawText(), field->Size())
0331                      : QString::fromLatin1(field->GetRawText());
0332 #endif
0333       } else {
0334         // if there are multiple items, put them into one string
0335         // separated by a special separator.
0336         QStringList strs;
0337         strs.reserve(numItems);
0338         for (size_t itemNr = 0; itemNr < numItems; ++itemNr) {
0339           strs.append(QString::fromLatin1(field->GetRawTextItem(itemNr)));
0340         }
0341         text = Frame::joinStringList(strs);
0342       }
0343     }
0344   }
0345   return text;
0346 }
0347 
0348 /**
0349  * Get text field.
0350  *
0351  * @param tag ID3 tag
0352  * @param id  frame ID
0353  * @param codec text codec to use, 0 for default
0354  * @return string,
0355  *         "" if the field does not exist,
0356  *         QString::null if the tags do not exist.
0357  */
0358 QString getTextField(const ID3_Tag* tag, ID3_FrameID id,
0359 #if QT_VERSION >= 0x060000
0360                      QStringDecoder* decoder = nullptr
0361 #else
0362                      const QTextCodec* codec = nullptr
0363 #endif
0364     )
0365 {
0366   if (!tag) {
0367     return QString();
0368   }
0369   QString str(QLatin1String(""));
0370   ID3_Field* fld;
0371   if (ID3_Frame* frame = tag->Find(id);
0372       frame && (fld = frame->GetField(ID3FN_TEXT)) != nullptr) {
0373 #if QT_VERSION >= 0x060000
0374     str = getString(fld, decoder);
0375 #else
0376     str = getString(fld, codec);
0377 #endif
0378   }
0379   return str;
0380 }
0381 
0382 /**
0383  * Get year.
0384  *
0385  * @param tag ID3 tag
0386  * @return number,
0387  *         0 if the field does not exist,
0388  *         -1 if the tags do not exist.
0389  */
0390 int getYear(const ID3_Tag* tag)
0391 {
0392   QString str = getTextField(tag, ID3FID_YEAR);
0393   if (str.isNull()) return -1;
0394   if (str.isEmpty()) return 0;
0395   return str.toInt();
0396 }
0397 
0398 /**
0399  * Get track.
0400  *
0401  * @param tag ID3 tag
0402  * @return number,
0403  *         0 if the field does not exist,
0404  *         -1 if the tags do not exist.
0405  */
0406 int getTrackNum(const ID3_Tag* tag)
0407 {
0408   QString str = getTextField(tag, ID3FID_TRACKNUM);
0409   if (str.isNull()) return -1;
0410   if (str.isEmpty()) return 0;
0411   // handle "track/total number of tracks" format
0412   if (int slashPos = str.indexOf(QLatin1Char('/')); slashPos != -1) {
0413     str.truncate(slashPos);
0414   }
0415   return str.toInt();
0416 }
0417 
0418 /**
0419  * Get genre.
0420  *
0421  * @param tag ID3 tag
0422  * @return number,
0423  *         0xff if the field does not exist,
0424  *         -1 if the tags do not exist.
0425  */
0426 int getGenreNum(const ID3_Tag* tag)
0427 {
0428   QString str = getTextField(tag, ID3FID_CONTENTTYPE);
0429   if (str.isNull()) return -1;
0430   if (str.isEmpty()) return 0xff;
0431   int n = 0xff;
0432   if (int cpPos = 0;
0433       str[0] == QLatin1Char('(') &&
0434       (cpPos = str.indexOf(QLatin1Char(')'), 2)) > 1) {
0435     bool ok;
0436 #if QT_VERSION >= 0x060000
0437     n = str.mid(1, cpPos - 1).toInt(&ok);
0438 #else
0439     n = str.midRef(1, cpPos - 1).toInt(&ok);
0440 #endif
0441     if (!ok || n > 0xff) {
0442       n = 0xff;
0443     }
0444   } else {
0445     // ID3v2 genres can be stored as "(9)", "(9)Metal" or "Metal".
0446     // If the string does not start with '(', try to get the genre number
0447     // from a string containing a genre text.
0448     n = Genres::getNumber(str);
0449   }
0450   return n;
0451 }
0452 
0453 /**
0454  * Allocate a fixed up a unicode string for id3lib.
0455  *
0456  * @param text string
0457  *
0458  * @return new allocated unicode string, has to be freed with delete [].
0459  */
0460 unicode_t* newFixedUpUnicode(const QString& text)
0461 {
0462   // Unfortunately, Unicode support in id3lib is rather buggy in the
0463   // current version: The codes are mirrored, a second different
0464   // BOM may be added, if the LSB >= 0x80, the MSB is set to 0xff.
0465   // If iconv is used (id3lib on Linux), the character do not come
0466   // back mirrored, but with a second (different)! BOM 0xfeff and
0467   // they are still written in the wrong order (big endian).
0468   // In the hope that my patches will be included, I try here to
0469   // work around these bugs, but there is no solution for the
0470   // LSB >= 0x80 bug.
0471   const QChar* qcarray = text.unicode();
0472   int unicode_size = text.length();
0473   auto unicode = new unicode_t[unicode_size + 1];
0474   for (int i = 0; i < unicode_size; ++i) {
0475     unicode[i] = qcarray[i].unicode();
0476     if (UNICODE_SUPPORT_BUGGY) {
0477       unicode[i] = static_cast<ushort>(((unicode[i] & 0x00ff) << 8) |
0478                                        ((unicode[i] & 0xff00) >> 8));
0479     }
0480   }
0481   unicode[unicode_size] = 0;
0482   return unicode;
0483 }
0484 
0485 /**
0486  * Set string list in text field.
0487  *
0488  * @param field field
0489  * @param lst   string list to set
0490  */
0491 void setStringList(ID3_Field* field, const QStringList& lst)
0492 {
0493   ID3_TextEnc enc = field->GetEncoding();
0494   bool first = true;
0495   for (auto it = lst.constBegin(); it != lst.constEnd(); ++it) {
0496     if (first) {
0497       if (enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
0498         if (unicode_t* unicode = newFixedUpUnicode(*it)) {
0499           field->Set(unicode);
0500           delete [] unicode;
0501         }
0502       } else if (enc == ID3TE_UTF8) {
0503         field->Set(it->toUtf8().data());
0504       } else {
0505         // enc == ID3TE_ISO8859_1
0506         field->Set(it->toLatin1().data());
0507       }
0508       first = false;
0509     } else {
0510       // This will not work with buggy id3lib. A BOM 0xfffe is written before
0511       // the first string, but not before the subsequent strings. Prepending a
0512       // BOM or changing the byte order does not help when id3lib rewrites
0513       // this field when another frame is changed. So you cannot use
0514       // string lists with Unicode encoding.
0515       if (enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
0516         if (unicode_t* unicode = newFixedUpUnicode(*it)) {
0517           field->Add(unicode);
0518           delete [] unicode;
0519         }
0520       } else if (enc == ID3TE_UTF8) {
0521         field->Add(it->toUtf8().data());
0522       } else {
0523         // enc == ID3TE_ISO8859_1
0524         field->Add(it->toLatin1().data());
0525       }
0526     }
0527   }
0528 }
0529 
0530 /**
0531  * Set string in text field.
0532  *
0533  * @param field        field
0534  * @param text         text to set
0535  * @param codec        text codec to use, 0 for default
0536  */
0537 void setString(ID3_Field* field, const QString& text,
0538 #if QT_VERSION >= 0x060000
0539                QStringEncoder* encoder = nullptr
0540 #else
0541                const QTextCodec* codec = nullptr
0542 #endif
0543     )
0544 {
0545   if (text.indexOf(Frame::stringListSeparator()) == -1) {
0546     // (ID3TE_IS_DOUBLE_BYTE_ENC(enc))
0547     if (ID3_TextEnc enc = field->GetEncoding();
0548         enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
0549       if (unicode_t* unicode = newFixedUpUnicode(text)) {
0550         field->Set(unicode);
0551         delete [] unicode;
0552       }
0553     } else if (enc == ID3TE_UTF8) {
0554       field->Set(text.toUtf8().data());
0555     } else {
0556       // enc == ID3TE_ISO8859_1
0557 #if QT_VERSION >= 0x060000
0558       field->Set(encoder ? static_cast<QByteArray>(encoder->encode(text)).constData()
0559                        : text.toLatin1().constData());
0560 #else
0561       field->Set(codec ? codec->fromUnicode(text).constData()
0562                        : text.toLatin1().constData());
0563 #endif
0564     }
0565   } else {
0566     setStringList(field, Frame::splitStringList(text));
0567   }
0568 }
0569 
0570 /**
0571  * Set text field.
0572  *
0573  * @param tag          ID3 tag
0574  * @param id           frame ID
0575  * @param text         text to set
0576  * @param allowUnicode true to allow setting of Unicode encoding if necessary
0577  * @param replace      true to replace an existing field
0578  * @param removeEmpty  true to remove a field if text is empty
0579  * @param codec        text codec to use, 0 for default
0580  *
0581  * @return true if the field was changed.
0582  */
0583 bool setTextField(ID3_Tag* tag, ID3_FrameID id, const QString& text,
0584                   bool allowUnicode = false, bool replace = true,
0585                   bool removeEmpty = true,
0586 #if QT_VERSION >= 0x060000
0587                   QStringEncoder* encoder = nullptr
0588 #else
0589                   const QTextCodec* codec = nullptr
0590 #endif
0591     )
0592 {
0593   bool changed = false;
0594   if (tag && !text.isNull()) {
0595     ID3_Frame* frame = nullptr;
0596     bool removeOnly = removeEmpty && text.isEmpty();
0597     if (replace || removeOnly) {
0598       if (id == ID3FID_COMMENT && tag->HasV2Tag()) {
0599         frame = tag->Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, "");
0600       } else {
0601         frame = tag->Find(id);
0602       }
0603       if (frame) {
0604         frame = tag->RemoveFrame(frame);
0605         delete frame;
0606         changed = true;
0607       }
0608     }
0609     if (!removeOnly && (replace || tag->Find(id) == nullptr)) {
0610       frame = new ID3_Frame(id);
0611       if (frame) {
0612         if (ID3_Field* fld = frame->GetField(ID3FN_TEXT)) {
0613           ID3_TextEnc enc = tag->HasV2Tag() ?
0614             getDefaultTextEncoding() : ID3TE_ISO8859_1;
0615           if (allowUnicode && enc == ID3TE_ISO8859_1) {
0616             // check if information is lost if the string is not unicode
0617             int unicode_size = text.length();
0618             const QChar* qcarray = text.unicode();
0619             for (int i = 0; i < unicode_size; ++i) {
0620               if (char ch = qcarray[i].toLatin1();
0621                   ch == 0 || (ch & 0x80) != 0) {
0622                 enc = ID3TE_UTF16;
0623                 break;
0624               }
0625             }
0626           }
0627           if (ID3_Field* encfld = frame->GetField(ID3FN_TEXTENC)) {
0628             encfld->Set(enc);
0629           }
0630           fld->SetEncoding(enc);
0631 #if QT_VERSION >= 0x060000
0632           setString(fld, text, encoder);
0633 #else
0634           setString(fld, text, codec);
0635 #endif
0636           tag->AttachFrame(frame);
0637         }
0638       }
0639       changed = true;
0640     }
0641   }
0642   return changed;
0643 }
0644 
0645 /**
0646  * Set year.
0647  *
0648  * @param tag ID3 tag
0649  * @param num number to set, 0 to remove field.
0650  *
0651  * @return true if the field was changed.
0652  */
0653 bool setYear(ID3_Tag* tag, int num)
0654 {
0655   bool changed = false;
0656   if (num >= 0) {
0657     QString str;
0658     if (num != 0) {
0659       str.setNum(num);
0660     } else {
0661       str.clear();
0662     }
0663     changed = getTextField(tag, ID3FID_YEAR) != str &&
0664               setTextField(tag, ID3FID_YEAR, str);
0665   }
0666   return changed;
0667 }
0668 
0669 }
0670 
0671 /**
0672  * Set track.
0673  *
0674  * @param tag ID3 tag
0675  * @param num number to set, 0 to remove field.
0676  * @param numTracks total number of tracks, <=0 to ignore
0677  *
0678  * @return true if the field was changed.
0679  */
0680 bool Mp3File::setTrackNum(ID3_Tag* tag, int num, int numTracks) const
0681 {
0682   bool changed = false;
0683   if (num >= 0 && getTrackNum(tag) != num) {
0684     QString str = trackNumberString(num, numTracks);
0685     changed = getTextField(tag, ID3FID_TRACKNUM) != str &&
0686               setTextField(tag, ID3FID_TRACKNUM, str);
0687   }
0688   return changed;
0689 }
0690 
0691 namespace {
0692 
0693 /**
0694  * Set genre.
0695  *
0696  * @param tag ID3 tag
0697  * @param num number to set, 0xff to remove field.
0698  *
0699  * @return true if the field was changed.
0700  */
0701 bool setGenreNum(ID3_Tag* tag, int num)
0702 {
0703   bool changed = false;
0704   if (num >= 0) {
0705     QString str;
0706     if (num != 0xff) {
0707       str = QString(QLatin1String("(%1)")).arg(num);
0708     } else {
0709       str = QLatin1String("");
0710     }
0711     changed = getTextField(tag, ID3FID_CONTENTTYPE) != str &&
0712               setTextField(tag, ID3FID_CONTENTTYPE, str);
0713   }
0714   return changed;
0715 }
0716 
0717 }
0718 
0719 /**
0720  * Check if tag information has already been read.
0721  *
0722  * @return true if information is available,
0723  *         false if the tags have not been read yet, in which case
0724  *         hasTag() does not return meaningful information.
0725  */
0726 bool Mp3File::isTagInformationRead() const
0727 {
0728   return m_tagV1 || m_tagV2;
0729 }
0730 
0731 /**
0732  * Check if tags are supported by the format of this file.
0733  *
0734  * @param tagNr tag number
0735  * @return true.
0736  */
0737 bool Mp3File::isTagSupported(Frame::TagNumber tagNr) const
0738 {
0739   return tagNr == Frame::Tag_1 || tagNr == Frame::Tag_2;
0740 }
0741 
0742 /**
0743  * Check if file has a tag.
0744  *
0745  * @param tagNr tag number
0746  * @return true if a tag is available.
0747  * @see isTagInformationRead()
0748  */
0749 bool Mp3File::hasTag(Frame::TagNumber tagNr) const
0750 {
0751   return (tagNr == Frame::Tag_1 && m_tagV1 && m_tagV1->HasV1Tag()) ||
0752          (tagNr == Frame::Tag_2 && m_tagV2 && m_tagV2->HasV2Tag());
0753 }
0754 
0755 /**
0756  * Get technical detail information.
0757  *
0758  * @param info the detail information is returned here
0759  */
0760 void Mp3File::getDetailInfo(DetailInfo& info) const
0761 {
0762   if (getFilename().right(4).toLower() == QLatin1String(".aac")) {
0763     info.valid = true;
0764     info.format = QLatin1String("AAC");
0765     return;
0766   }
0767 
0768   const Mp3_Headerinfo* headerInfo = nullptr;
0769   if (m_tagV2) {
0770     headerInfo = m_tagV2->GetMp3HeaderInfo();
0771   }
0772   if (!headerInfo && m_tagV1) {
0773     headerInfo = m_tagV1->GetMp3HeaderInfo();
0774   }
0775   if (headerInfo) {
0776     info.valid = true;
0777     switch (headerInfo->version) {
0778       case MPEGVERSION_1:
0779         info.format = QLatin1String("MPEG 1 ");
0780         break;
0781       case MPEGVERSION_2:
0782         info.format = QLatin1String("MPEG 2 ");
0783         break;
0784       case MPEGVERSION_2_5:
0785         info.format = QLatin1String("MPEG 2.5 ");
0786         break;
0787       default:
0788         ; // nothing
0789     }
0790     switch (headerInfo->layer) {
0791       case MPEGLAYER_I:
0792         info.format += QLatin1String("Layer 1");
0793         break;
0794       case MPEGLAYER_II:
0795         info.format += QLatin1String("Layer 2");
0796         break;
0797       case MPEGLAYER_III:
0798         info.format += QLatin1String("Layer 3");
0799         break;
0800       default:
0801         ; // nothing
0802     }
0803     info.bitrate = headerInfo->bitrate / 1000;
0804 #ifndef HAVE_NO_ID3LIB_VBR
0805     if (headerInfo->vbr_bitrate > 1000) {
0806       info.vbr = true;
0807       info.bitrate = headerInfo->vbr_bitrate / 1000;
0808     }
0809 #endif
0810     info.sampleRate = headerInfo->frequency;
0811     switch (headerInfo->channelmode) {
0812       case MP3CHANNELMODE_STEREO:
0813         info.channelMode = DetailInfo::CM_Stereo;
0814         info.channels = 2;
0815         break;
0816       case MP3CHANNELMODE_JOINT_STEREO:
0817         info.channelMode = DetailInfo::CM_JointStereo;
0818         info.channels = 2;
0819         break;
0820       case MP3CHANNELMODE_DUAL_CHANNEL:
0821         info.channels = 2;
0822         break;
0823       case MP3CHANNELMODE_SINGLE_CHANNEL:
0824         info.channels = 1;
0825         break;
0826       default:
0827         ; // nothing
0828     }
0829     info.duration = headerInfo->time;
0830   } else {
0831     info.valid = false;
0832   }
0833 }
0834 
0835 /**
0836  * Get duration of file.
0837  *
0838  * @return duration in seconds,
0839  *         0 if unknown.
0840  */
0841 unsigned Mp3File::getDuration() const
0842 {
0843   unsigned duration = 0;
0844   const Mp3_Headerinfo* info = nullptr;
0845   if (m_tagV2) {
0846     info = m_tagV2->GetMp3HeaderInfo();
0847   }
0848   if (!info && m_tagV1) {
0849     info = m_tagV1->GetMp3HeaderInfo();
0850   }
0851   if (info && info->time > 0) {
0852     duration = info->time;
0853   }
0854   return duration;
0855 }
0856 
0857 /**
0858  * Get file extension including the dot.
0859  *
0860  * @return file extension ".mp3".
0861  */
0862 QString Mp3File::getFileExtension() const
0863 {
0864   if (QString ext(getFilename().right(4).toLower());
0865       ext == QLatin1String(".aac") || ext == QLatin1String(".mp2"))
0866     return ext;
0867   return QLatin1String(".mp3");
0868 }
0869 
0870 /**
0871  * Get the format of a tag.
0872  *
0873  * @param tagNr tag number
0874  * @return string describing format of tag,
0875  *         e.g. "ID3v1.1".
0876  */
0877 QString Mp3File::getTagFormat(Frame::TagNumber tagNr) const
0878 {
0879   if (tagNr == Frame::Tag_1 && m_tagV1 && m_tagV1->HasV1Tag()) {
0880     return QLatin1String("ID3v1.1");
0881   }
0882   if (tagNr == Frame::Tag_2 && m_tagV2 && m_tagV2->HasV2Tag()) {
0883     switch (m_tagV2->GetSpec()) {
0884     case ID3V2_3_0:
0885       return QLatin1String("ID3v2.3.0");
0886     case ID3V2_4_0:
0887       return QLatin1String("ID3v2.4.0");
0888     case ID3V2_2_0:
0889       return QLatin1String("ID3v2.2.0");
0890     case ID3V2_2_1:
0891       return QLatin1String("ID3v2.2.1");
0892     default:
0893       break;
0894     }
0895   }
0896   return QString();
0897 }
0898 
0899 namespace {
0900 
0901 /** Types and descriptions for id3lib frame IDs */
0902 const struct TypeStrOfId {
0903   Frame::Type type;
0904   const char* str;
0905 } typeStrOfId[] = {
0906   { Frame::FT_UnknownFrame,   nullptr },                                                                                 /* ???? */
0907   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "AENC - Audio encryption") },                                /* AENC */
0908   { Frame::FT_Picture,        QT_TRANSLATE_NOOP("@default", "APIC - Attached picture") },                                /* APIC */
0909   { Frame::FT_Other,          nullptr },                                                                                 /* ASPI */
0910   { Frame::FT_Comment,        QT_TRANSLATE_NOOP("@default", "COMM - Comments") },                                        /* COMM */
0911   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "COMR - Commercial") },                                      /* COMR */
0912   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "ENCR - Encryption method registration") },                  /* ENCR */
0913   { Frame::FT_Other,          nullptr },                                                                                 /* EQU2 */
0914   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "EQUA - Equalization") },                                    /* EQUA */
0915   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "ETCO - Event timing codes") },                              /* ETCO */
0916   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "GEOB - General encapsulated object") },                     /* GEOB */
0917   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "GRID - Group identification registration") },               /* GRID */
0918   { Frame::FT_Arranger,       QT_TRANSLATE_NOOP("@default", "IPLS - Involved people list") },                            /* IPLS */
0919   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "LINK - Linked information") },                              /* LINK */
0920   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "MCDI - Music CD identifier") },                             /* MCDI */
0921   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "MLLT - MPEG location lookup table") },                      /* MLLT */
0922   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "OWNE - Ownership frame") },                                 /* OWNE */
0923   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "PRIV - Private frame") },                                   /* PRIV */
0924   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "PCNT - Play counter") },                                    /* PCNT */
0925   { Frame::FT_Rating,         QT_TRANSLATE_NOOP("@default", "POPM - Popularimeter") },                                   /* POPM */
0926   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "POSS - Position synchronisation frame") },                  /* POSS */
0927   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "RBUF - Recommended buffer size") },                         /* RBUF */
0928   { Frame::FT_Other,          nullptr },                                                                                 /* RVA2 */
0929   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "RVAD - Relative volume adjustment") },                      /* RVAD */
0930   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "RVRB - Reverb") },                                          /* RVRB */
0931   { Frame::FT_Other,          nullptr },                                                                                 /* SEEK */
0932   { Frame::FT_Other,          nullptr },                                                                                 /* SIGN */
0933   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "SYLT - Synchronized lyric/text") },                         /* SYLT */
0934   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "SYTC - Synchronized tempo codes") },                        /* SYTC */
0935   { Frame::FT_Album,          QT_TRANSLATE_NOOP("@default", "TALB - Album/Movie/Show title") },                          /* TALB */
0936   { Frame::FT_Bpm,            QT_TRANSLATE_NOOP("@default", "TBPM - BPM (beats per minute)") },                          /* TBPM */
0937   { Frame::FT_Composer,       QT_TRANSLATE_NOOP("@default", "TCOM - Composer") },                                        /* TCOM */
0938   { Frame::FT_Genre,          QT_TRANSLATE_NOOP("@default", "TCON - Content type") },                                    /* TCON */
0939   { Frame::FT_Copyright,      QT_TRANSLATE_NOOP("@default", "TCOP - Copyright message") },                               /* TCOP */
0940   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TDAT - Date") },                                            /* TDAT */
0941   { Frame::FT_Other,          nullptr },                                                                                 /* TDEN */
0942   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TDLY - Playlist delay") },                                  /* TDLY */
0943   { Frame::FT_Other,          nullptr },                                                                                 /* TDOR */
0944   { Frame::FT_Other,          nullptr },                                                                                 /* TDRC */
0945   { Frame::FT_Other,          nullptr },                                                                                 /* TDRL */
0946   { Frame::FT_Other,          nullptr },                                                                                 /* TDTG */
0947   { Frame::FT_Other,          nullptr },                                                                                 /* TIPL */
0948   { Frame::FT_EncodedBy,      QT_TRANSLATE_NOOP("@default", "TENC - Encoded by") },                                      /* TENC */
0949   { Frame::FT_Lyricist,       QT_TRANSLATE_NOOP("@default", "TEXT - Lyricist/Text writer") },                            /* TEXT */
0950   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TFLT - File type") },                                       /* TFLT */
0951   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TIME - Time") },                                            /* TIME */
0952   { Frame::FT_Work,           QT_TRANSLATE_NOOP("@default", "TIT1 - Content group description") },                       /* TIT1 */
0953   { Frame::FT_Title,          QT_TRANSLATE_NOOP("@default", "TIT2 - Title/songname/content description") },              /* TIT2 */
0954   { Frame::FT_Description,    QT_TRANSLATE_NOOP("@default", "TIT3 - Subtitle/Description refinement") },                 /* TIT3 */
0955   { Frame::FT_InitialKey,     QT_TRANSLATE_NOOP("@default", "TKEY - Initial key") },                                     /* TKEY */
0956   { Frame::FT_Language,       QT_TRANSLATE_NOOP("@default", "TLAN - Language(s)") },                                     /* TLAN */
0957   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TLEN - Length") },                                          /* TLEN */
0958   { Frame::FT_Other,          nullptr },                                                                                 /* TMCL */
0959   { Frame::FT_Media,          QT_TRANSLATE_NOOP("@default", "TMED - Media type") },                                      /* TMED */
0960   { Frame::FT_Other,          nullptr },                                                                                 /* TMOO */
0961   { Frame::FT_OriginalAlbum,  QT_TRANSLATE_NOOP("@default", "TOAL - Original album/movie/show title") },                 /* TOAL */
0962   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TOFN - Original filename") },                               /* TOFN */
0963   { Frame::FT_Author,         QT_TRANSLATE_NOOP("@default", "TOLY - Original lyricist(s)/text writer(s)") },             /* TOLY */
0964   { Frame::FT_OriginalArtist, QT_TRANSLATE_NOOP("@default", "TOPE - Original artist(s)/performer(s)") },                 /* TOPE */
0965   { Frame::FT_OriginalDate,   QT_TRANSLATE_NOOP("@default", "TORY - Original release year") },                           /* TORY */
0966   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TOWN - File owner/licensee") },                             /* TOWN */
0967   { Frame::FT_Artist,         QT_TRANSLATE_NOOP("@default", "TPE1 - Lead performer(s)/Soloist(s)") },                    /* TPE1 */
0968   { Frame::FT_AlbumArtist,    QT_TRANSLATE_NOOP("@default", "TPE2 - Band/orchestra/accompaniment") },                    /* TPE2 */
0969   { Frame::FT_Conductor,      QT_TRANSLATE_NOOP("@default", "TPE3 - Conductor/performer refinement") },                  /* TPE3 */
0970   { Frame::FT_Remixer,        QT_TRANSLATE_NOOP("@default", "TPE4 - Interpreted, remixed, or otherwise modified by") },  /* TPE4 */
0971   { Frame::FT_Disc,           QT_TRANSLATE_NOOP("@default", "TPOS - Part of a set") },                                   /* TPOS */
0972   { Frame::FT_Other,          nullptr },                                                                                 /* TPRO */
0973   { Frame::FT_Publisher,      QT_TRANSLATE_NOOP("@default", "TPUB - Publisher") },                                       /* TPUB */
0974   { Frame::FT_Track,          QT_TRANSLATE_NOOP("@default", "TRCK - Track number/Position in set") },                    /* TRCK */
0975   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TRDA - Recording dates") },                                 /* TRDA */
0976   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TRSN - Internet radio station name") },                     /* TRSN */
0977   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TRSO - Internet radio station owner") },                    /* TRSO */
0978   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TSIZ - Size") },                                            /* TSIZ */
0979   { Frame::FT_Other,          nullptr },                                                                                 /* TSOA */
0980   { Frame::FT_Other,          nullptr },                                                                                 /* TSOP */
0981   { Frame::FT_Other,          nullptr },                                                                                 /* TSOT */
0982   { Frame::FT_Isrc,           QT_TRANSLATE_NOOP("@default", "TSRC - ISRC (international standard recording code)") },    /* TSRC */
0983   { Frame::FT_EncoderSettings,QT_TRANSLATE_NOOP("@default", "TSSE - Software/Hardware and settings used for encoding") },/* TSSE */
0984   { Frame::FT_Subtitle,       nullptr },                                                                                 /* TSST */
0985   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "TXXX - User defined text information") },                   /* TXXX */
0986   { Frame::FT_Date,           QT_TRANSLATE_NOOP("@default", "TYER - Year") },                                            /* TYER */
0987   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "UFID - Unique file identifier") },                          /* UFID */
0988   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "USER - Terms of use") },                                    /* USER */
0989   { Frame::FT_Lyrics,         QT_TRANSLATE_NOOP("@default", "USLT - Unsynchronized lyric/text transcription") },         /* USLT */
0990   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "WCOM - Commercial information") },                          /* WCOM */
0991   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "WCOP - Copyright/Legal information") },                     /* WCOP */
0992   { Frame::FT_WWWAudioFile,   QT_TRANSLATE_NOOP("@default", "WOAF - Official audio file webpage") },                     /* WOAF */
0993   { Frame::FT_Website,        QT_TRANSLATE_NOOP("@default", "WOAR - Official artist/performer webpage") },               /* WOAR */
0994   { Frame::FT_WWWAudioSource, QT_TRANSLATE_NOOP("@default", "WOAS - Official audio source webpage") },                   /* WOAS */
0995   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "WORS - Official internet radio station homepage") },        /* WORS */
0996   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "WPAY - Payment") },                                         /* WPAY */
0997   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "WPUB - Official publisher webpage") },                      /* WPUB */
0998   { Frame::FT_Other,          QT_TRANSLATE_NOOP("@default", "WXXX - User defined URL link") }                            /* WXXX */
0999 };
1000 Q_STATIC_ASSERT(std::size(typeStrOfId) == ID3FID_WWWUSER + 1);
1001 
1002 /**
1003  * Get type and description of frame.
1004  *
1005  * @param id ID of frame
1006  * @param type the type is returned here
1007  * @param str  the description is returned here, 0 if not available
1008  */
1009 void getTypeStringForId3libFrameId(ID3_FrameID id, Frame::Type& type,
1010                                    const char*& str)
1011 {
1012   const auto& [t, s] = typeStrOfId[id <= ID3FID_WWWUSER ? id : 0];
1013   type = t;
1014   str = s;
1015 }
1016 
1017 /**
1018  * Get id3lib frame ID for frame type.
1019  *
1020  * @param type frame type
1021  *
1022  * @return id3lib frame ID or ID3FID_NOFRAME if type not found.
1023  */
1024 ID3_FrameID getId3libFrameIdForType(Frame::Type type)
1025 {
1026   // IPLS is mapped to FT_Arranger and FT_Performer
1027   if (type == Frame::FT_Performer) {
1028     return ID3FID_INVOLVEDPEOPLE;
1029   }
1030   if (type == Frame::FT_CatalogNumber ||
1031       type == Frame::FT_ReleaseCountry ||
1032       type == Frame::FT_Grouping ||
1033       type == Frame::FT_Subtitle ||
1034       Frame::isCustomFrameType(type)) {
1035     return ID3FID_USERTEXT;
1036   }
1037 
1038   static int typeIdMap[Frame::FT_LastFrame + 1] = { -1, };
1039   if (typeIdMap[0] == -1) {
1040     // first time initialization
1041     for (int i = 0; i <= ID3FID_WWWUSER; ++i) {
1042       if (int t = typeStrOfId[i].type; t <= Frame::FT_LastFrame) {
1043         typeIdMap[t] = i;
1044       }
1045     }
1046   }
1047   return type <= Frame::FT_LastFrame ?
1048     static_cast<ID3_FrameID>(typeIdMap[type]) : ID3FID_NOFRAME;
1049 }
1050 
1051 /**
1052  * Get id3lib frame ID for frame name.
1053  *
1054  * @param name frame name
1055  *
1056  * @return id3lib frame ID or ID3FID_NOFRAME if name not found.
1057  */
1058 ID3_FrameID getId3libFrameIdForName(const QString& name)
1059 {
1060   if (name.length() >= 4) {
1061     QByteArray nameBytes = name.toLatin1();
1062     const char* nameStr = nameBytes.constData();
1063     for (int i = 0; i <= ID3FID_WWWUSER; ++i) {
1064       if (const char* s = typeStrOfId[i].str;
1065           s && ::strncmp(s, nameStr, 4) == 0) {
1066         return static_cast<ID3_FrameID>(i);
1067       }
1068     }
1069   }
1070   return ID3FID_NOFRAME;
1071 }
1072 
1073 /**
1074  * Convert the binary field of a SYLT frame to a list of alternating
1075  * time stamps and strings.
1076  * @param bytes binary field
1077  * @param enc encoding
1078  * @return list containing time, text, time, text, ...
1079  */
1080 QVariantList syltBytesToList(const QByteArray& bytes, ID3_TextEnc enc)
1081 {
1082   QVariantList timeEvents;
1083   const int numBytes = bytes.size();
1084   int textBegin = 0, textEnd;
1085   while (textBegin < numBytes) {
1086     if (enc == ID3TE_ISO8859_1 || enc == ID3TE_UTF8) {
1087       textEnd = bytes.indexOf('\0', textBegin);
1088       if (textEnd != -1) {
1089         ++textEnd;
1090       }
1091     } else {
1092       auto unicode =
1093           reinterpret_cast<const ushort*>(bytes.constData() + textBegin);
1094       textEnd = textBegin;
1095       while (textEnd < numBytes) {
1096         textEnd += 2;
1097         if (*unicode++ == 0) {
1098           break;
1099         }
1100       }
1101     }
1102     if (textEnd < 0 || textEnd >= numBytes)
1103       break;
1104 
1105     QString str;
1106     QByteArray text = bytes.mid(textBegin, textEnd - textBegin);
1107     switch (enc) {
1108     case ID3TE_UTF16BE:
1109       text.prepend(0xff);
1110       text.prepend(0xfe);
1111       // starting with FEFF BOM
1112       // fallthrough
1113     case ID3TE_UTF16:
1114 #if QT_VERSION >= 0x060000
1115       str = QString::fromUtf16(reinterpret_cast<const char16_t*>(text.constData()));
1116 #else
1117       str = QString::fromUtf16(reinterpret_cast<const ushort*>(text.constData()));
1118 #endif
1119       break;
1120     case ID3TE_UTF8:
1121       str = QString::fromUtf8(text.constData());
1122       break;
1123     case ID3TE_ISO8859_1:
1124     default:
1125       str = QString::fromLatin1(text.constData());
1126     }
1127     textBegin = textEnd + 4;
1128     if (textBegin > numBytes)
1129       break;
1130 
1131     auto time = qFromBigEndian<quint32>(
1132           reinterpret_cast<const uchar*>(bytes.constData()) + textEnd);
1133     timeEvents.append(time);
1134     timeEvents.append(str);
1135   }
1136   return timeEvents;
1137 }
1138 
1139 /**
1140  * Convert a list of alternating time stamps and strings to the binary field of
1141  * a SYLT frame.
1142  * @param synchedData list containing time, text, time, text, ...
1143  * @param enc text encoding
1144  * @return binary field bytes.
1145  */
1146 QByteArray syltListToBytes(const QVariantList& synchedData, ID3_TextEnc enc)
1147 {
1148   QByteArray bytes;
1149   QListIterator it(synchedData);
1150   while (it.hasNext()) {
1151     quint32 milliseconds = it.next().toUInt();
1152     if (!it.hasNext())
1153       break;
1154 
1155     QString str = it.next().toString();
1156     switch (enc) {
1157     case ID3TE_UTF16:
1158       bytes.append(0xff);
1159       bytes.append(0xfe);
1160       // starting with FFFE BOM
1161       // fallthrough
1162     case ID3TE_UTF16BE:
1163     {
1164       const ushort* unicode = str.utf16();
1165       do {
1166         uchar lsb = *unicode & 0xff;
1167         uchar msb = *unicode >> 8;
1168         if (enc == ID3TE_UTF16) {
1169           bytes.append(static_cast<char>(lsb));
1170           bytes.append(static_cast<char>(msb));
1171         } else {
1172           bytes.append(static_cast<char>(msb));
1173           bytes.append(static_cast<char>(lsb));
1174         }
1175       } while (*unicode++);
1176       break;
1177     }
1178     case ID3TE_UTF8:
1179       bytes.append(str.toUtf8());
1180       bytes.append('\0');
1181       break;
1182     case ID3TE_ISO8859_1:
1183     default:
1184       bytes.append(str.toLatin1());
1185       bytes.append('\0');
1186     }
1187     uchar timeStamp[4];
1188     qToBigEndian(milliseconds, timeStamp);
1189     bytes.append(reinterpret_cast<const char*>(timeStamp), sizeof(timeStamp));
1190   }
1191   if (bytes.isEmpty()) {
1192     // id3lib bug: Empty binary fields are not written, so add a minimal field
1193     bytes = QByteArray(4 + (enc == ID3TE_UTF16 ||
1194                             enc == ID3TE_UTF16BE ? 2 : 1),
1195                        '\0');
1196   }
1197   return bytes;
1198 }
1199 
1200 /**
1201  * Convert the binary field of an ETCO frame to a list of alternating
1202  * time stamps and codes.
1203  * @param bytes binary field
1204  * @return list containing time, code, time, code, ...
1205  */
1206 QVariantList etcoBytesToList(const QByteArray& bytes)
1207 {
1208   QVariantList timeEvents;
1209   const int numBytes = bytes.size();
1210   // id3lib bug: There is only a single data field for ETCO frames,
1211   // but it should be preceded by an ID_TimestampFormat field.
1212   // Start with the second byte.
1213   int pos = 1;
1214   while (pos < numBytes) {
1215     int code = static_cast<uchar>(bytes.at(pos));
1216     ++pos;
1217     if (pos + 4 > numBytes)
1218       break;
1219 
1220     auto time = qFromBigEndian<quint32>(
1221           reinterpret_cast<const uchar*>(bytes.constData()) + pos);
1222     pos += 4;
1223     timeEvents.append(time);
1224     timeEvents.append(code);
1225   }
1226   return timeEvents;
1227 }
1228 
1229 /**
1230  * Convert a list of alternating time stamps and codes to the binary field of
1231  * an ETCO frame.
1232  * @param synchedData list containing time, code, time, code, ...
1233  * @return binary field bytes.
1234  */
1235 QByteArray etcoListToBytes(const QVariantList& synchedData)
1236 {
1237   QByteArray bytes;
1238   QListIterator it(synchedData);
1239   while (it.hasNext()) {
1240     quint32 milliseconds = it.next().toUInt();
1241     if (!it.hasNext())
1242       break;
1243 
1244     int code = it.next().toInt();
1245     bytes.append(static_cast<char>(code));
1246     uchar timeStamp[4];
1247     qToBigEndian(milliseconds, timeStamp);
1248     bytes.append(reinterpret_cast<const char*>(timeStamp), sizeof(timeStamp));
1249   }
1250   return bytes;
1251 }
1252 
1253 /**
1254  * Get the fields from an ID3v2 tag.
1255  *
1256  * @param id3Frame frame
1257  * @param fields   the fields are appended to this list
1258  *
1259  * @return text representation of fields (Text or URL).
1260  */
1261 QString getFieldsFromId3Frame(ID3_Frame* id3Frame, Frame::FieldList& fields)
1262 {
1263   QString text;
1264   ID3_Frame::Iterator* iter = id3Frame->CreateIterator();
1265   ID3_FrameID id3Id = id3Frame->GetID();
1266   ID3_TextEnc enc = ID3TE_NONE;
1267   ID3_Field* id3Field;
1268   Frame::Field field;
1269   while ((id3Field = iter->GetNext()) != nullptr) {
1270     ID3_FieldID id = id3Field->GetID();
1271     ID3_FieldType type = id3Field->GetType();
1272     field.m_id = id;
1273     if (type == ID3FTY_INTEGER) {
1274       uint32 intVal = id3Field->Get();
1275       field.m_value = intVal;
1276       if (id == ID3FN_TEXTENC) {
1277         enc = static_cast<ID3_TextEnc>(intVal);
1278       }
1279     }
1280     else if (type == ID3FTY_BINARY) {
1281       QByteArray ba(reinterpret_cast<const char*>(id3Field->GetRawBinary()),
1282                     static_cast<int>(id3Field->Size()));
1283       if (id3Id == ID3FID_SYNCEDLYRICS) {
1284         field.m_value = syltBytesToList(ba, enc);
1285       } else if (id3Id == ID3FID_EVENTTIMING) {
1286         field.m_value = etcoBytesToList(ba);
1287       } else {
1288         field.m_value = ba;
1289       }
1290     }
1291     else if (type == ID3FTY_TEXTSTRING) {
1292       if (id == ID3FN_TEXT || id == ID3FN_DESCRIPTION || id == ID3FN_URL) {
1293         text = getString(id3Field);
1294         if (id3Id == ID3FID_CONTENTTYPE) {
1295           text = Genres::getNameString(text);
1296         }
1297         field.m_value = text;
1298       } else {
1299         field.m_value = getString(id3Field);
1300       }
1301     } else {
1302       field.m_value.clear();
1303     }
1304     fields.push_back(field);
1305   }
1306 #ifdef Q_OS_WIN32
1307   /* allocated in Windows DLL => must be freed in the same DLL */
1308   ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
1309 #else
1310 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1311 #pragma GCC diagnostic push
1312 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
1313 /* Is safe because iterator implementation has default destructor */
1314 #endif
1315   delete iter;
1316 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1317 #pragma GCC diagnostic pop
1318 #endif
1319 #endif
1320   return text;
1321 }
1322 
1323 /**
1324  * Get ID3v2 frame by index.
1325  *
1326  * @param tag   ID3v2 tag
1327  * @param index index
1328  *
1329  * @return frame, 0 if index invalid.
1330  */
1331 ID3_Frame* getId3v2Frame(ID3_Tag* tag, int index)
1332 {
1333   ID3_Frame* result = nullptr;
1334   if (tag) {
1335     ID3_Frame* frame;
1336     ID3_Tag::Iterator* iter = tag->CreateIterator();
1337     int i = 0;
1338     while ((frame = iter->GetNext()) != nullptr) {
1339       if (i == index) {
1340         result = frame;
1341         break;
1342       }
1343       ++i;
1344     }
1345 #ifdef Q_OS_WIN32
1346     /* allocated in Windows DLL => must be freed in the same DLL */
1347     ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
1348 #else
1349 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1350 #pragma GCC diagnostic push
1351 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
1352 /* Is safe because iterator implementation has default destructor */
1353 #endif
1354     delete iter;
1355 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1356 #pragma GCC diagnostic pop
1357 #endif
1358 #endif
1359   }
1360   return result;
1361 }
1362 
1363 }
1364 
1365 /**
1366  * Set the fields in an id3lib frame from the field in the frame.
1367  *
1368  * @param id3Frame id3lib frame
1369  * @param frame    frame with fields
1370  */
1371 void Mp3File::setId3v2Frame(ID3_Frame* id3Frame, const Frame& frame) const
1372 {
1373   ID3_Frame::Iterator* iter = id3Frame->CreateIterator();
1374   ID3_FrameID id3Id = id3Frame->GetID();
1375   ID3_TextEnc enc = ID3TE_NONE;
1376   for (auto fldIt = frame.getFieldList().constBegin();
1377        fldIt != frame.getFieldList().constEnd();
1378        ++fldIt) {
1379     ID3_Field* id3Field = iter->GetNext();
1380     if (!id3Field) {
1381       qDebug("early end of ID3 fields");
1382       break;
1383     }
1384     const Frame::Field& fld = *fldIt;
1385 #if QT_VERSION >= 0x060000
1386     switch (fld.m_value.typeId()) {
1387       case QMetaType::Int:
1388       case QMetaType::UInt:
1389 #else
1390     switch (fld.m_value.type()) {
1391       case QVariant::Int:
1392       case QVariant::UInt:
1393 #endif
1394       {
1395         int intVal = fld.m_value.toInt();
1396         if (fld.m_id == ID3FN_TEXTENC) {
1397           if (intVal == ID3TE_UTF8) intVal = ID3TE_UTF16;
1398           enc = static_cast<ID3_TextEnc>(intVal);
1399         }
1400         id3Field->Set(intVal);
1401         break;
1402       }
1403 
1404 #if QT_VERSION >= 0x060000
1405       case QMetaType::QString:
1406 #else
1407       case QVariant::String:
1408 #endif
1409       {
1410         if (enc != ID3TE_NONE) {
1411           id3Field->SetEncoding(enc);
1412         }
1413         QString value(fld.m_value.toString());
1414         if (id3Id == ID3FID_CONTENTTYPE) {
1415           if (!TagConfig::instance().genreNotNumeric() ||
1416               value.contains(Frame::stringListSeparator())) {
1417             value = Genres::getNumberString(value, true);
1418           }
1419         } else if (id3Id == ID3FID_TRACKNUM) {
1420           formatTrackNumberIfEnabled(value, true);
1421         }
1422         setString(id3Field, value);
1423         break;
1424       }
1425 
1426 #if QT_VERSION >= 0x060000
1427       case QMetaType::QByteArray:
1428 #else
1429       case QVariant::ByteArray:
1430 #endif
1431       {
1432         const QByteArray& ba = fld.m_value.toByteArray();
1433         id3Field->Set(reinterpret_cast<const unsigned char*>(ba.data()),
1434                       static_cast<size_t>(ba.size()));
1435         break;
1436       }
1437 
1438 #if QT_VERSION >= 0x060000
1439       case QMetaType::QVariantList:
1440 #else
1441       case QVariant::List:
1442 #endif
1443       {
1444         if (id3Id == ID3FID_SYNCEDLYRICS) {
1445           QByteArray ba = syltListToBytes(fld.m_value.toList(), enc);
1446           id3Field->Set(reinterpret_cast<const unsigned char*>(ba.data()),
1447                         static_cast<size_t>(ba.size()));
1448         } else if (id3Id == ID3FID_EVENTTIMING) {
1449           QByteArray ba = etcoListToBytes(fld.m_value.toList());
1450           // id3lib bug: There is only a single data field for ETCO frames,
1451           // but it should be preceded by an ID_TimestampFormat field.
1452           ba.prepend(2);
1453           id3Field->Set(reinterpret_cast<const unsigned char*>(ba.data()),
1454                         static_cast<size_t>(ba.size()));
1455         } else {
1456           qDebug("Unexpected QVariantList in field %d", fld.m_id);
1457         }
1458         break;
1459       }
1460 
1461       default:
1462 #if QT_VERSION >= 0x060000
1463         qDebug("Unknown type %d in field %d", fld.m_value.typeId(), fld.m_id);
1464 #else
1465         qDebug("Unknown type %d in field %d", fld.m_value.type(), fld.m_id);
1466 #endif
1467     }
1468   }
1469 #ifdef Q_OS_WIN32
1470   /* allocated in Windows DLL => must be freed in the same DLL */
1471   ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
1472 #else
1473 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1474 #pragma GCC diagnostic push
1475 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
1476 /* Is safe because iterator implementation has default destructor */
1477 #endif
1478   delete iter;
1479 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1480 #pragma GCC diagnostic pop
1481 #endif
1482 #endif
1483 }
1484 
1485 /**
1486  * Get a specific frame from the tags.
1487  *
1488  * @param tagNr tag number
1489  * @param type  frame type
1490  * @param frame the frame is returned here
1491  *
1492  * @return true if ok.
1493  */
1494 bool Mp3File::getFrame(Frame::TagNumber tagNr, Frame::Type type, Frame& frame) const
1495 {
1496   if (type < Frame::FT_FirstFrame || type > Frame::FT_LastV1Frame)
1497     return false;
1498 
1499   ID3_FrameID frameId = getId3libFrameIdForType(type);
1500   if (frameId == ID3FID_NOFRAME)
1501     return false;
1502 
1503   const ID3_Tag* tag;
1504 #if QT_VERSION >= 0x060000
1505   QStringDecoder* decoder;
1506 #else
1507   const QTextCodec* codec;
1508 #endif
1509   if (tagNr == Frame::Tag_1) {
1510     tag = m_tagV1.data();
1511 #if QT_VERSION >= 0x060000
1512     decoder = &s_decoderV1;
1513 #else
1514     codec = s_textCodecV1;
1515 #endif
1516   } else if (tagNr == Frame::Tag_2) {
1517     tag = m_tagV2.data();
1518 #if QT_VERSION >= 0x060000
1519     decoder = nullptr;
1520 #else
1521     codec = nullptr;
1522 #endif
1523   } else {
1524     return false;
1525   }
1526 
1527   switch (type) {
1528   case Frame::FT_Album:
1529   case Frame::FT_Artist:
1530   case Frame::FT_Comment:
1531   case Frame::FT_Title:
1532 #if QT_VERSION >= 0x060000
1533     frame.setValue(getTextField(tag, frameId, decoder));
1534 #else
1535     frame.setValue(getTextField(tag, frameId, codec));
1536 #endif
1537     break;
1538   case Frame::FT_Track:
1539     if (tagNr == Frame::Tag_1) {
1540       frame.setValueAsNumber(getTrackNum(tag));
1541     } else {
1542       frame.setValue(getTextField(tag, frameId));
1543     }
1544     break;
1545   case Frame::FT_Date:
1546     frame.setValueAsNumber(getYear(tag));
1547     break;
1548   case Frame::FT_Genre:
1549   {
1550     int num = getGenreNum(tag);
1551     if (tagNr == Frame::Tag_1) {
1552       frame.setValue(num == -1
1553                      ? QString()
1554                      : num == 0xff
1555                        ? QLatin1String("")
1556                        : QString::fromLatin1(Genres::getName(num)));
1557     } else {
1558       frame.setValue(num != 0xff && num != -1
1559           ? QString::fromLatin1(Genres::getName(num))
1560           : getTextField(tag, frameId));
1561     }
1562     break;
1563   }
1564   default:
1565     return false;
1566   }
1567   frame.setType(type);
1568   return true;
1569 }
1570 
1571 /**
1572  * Set a frame in the tags.
1573  *
1574  * @param tagNr tag number
1575  * @param frame frame to set
1576  *
1577  * @return true if ok.
1578  */
1579 bool Mp3File::setFrame(Frame::TagNumber tagNr, const Frame& frame)
1580 {
1581   if (tagNr == Frame::Tag_2) {
1582     // If the frame has an index, change that specific frame
1583     if (int index = frame.getIndex(); index != -1 && m_tagV2) {
1584       if (ID3_Frame* id3Frame = getId3v2Frame(m_tagV2.data(), index)) {
1585         // If value is changed or field list is empty,
1586         // set from value, else from FieldList.
1587         if (frame.isValueChanged() || frame.getFieldList().empty()) {
1588           QString value(frame.getValue());
1589           if (ID3_Field* fld;
1590               (fld = id3Frame->GetField(ID3FN_URL)) != nullptr) {
1591             if (getString(fld) != value) {
1592               fld->Set(value.toLatin1().data());
1593               markTagChanged(Frame::Tag_2, frame.getExtendedType());
1594             }
1595             return true;
1596           } else {
1597             if ((fld = id3Frame->GetField(ID3FN_TEXT)) != nullptr ||
1598               (fld = id3Frame->GetField(ID3FN_DESCRIPTION)) != nullptr) {
1599               ID3_FrameID id = id3Frame->GetID();
1600               if (id == ID3FID_CONTENTTYPE) {
1601                 if (!TagConfig::instance().genreNotNumeric() ||
1602                   value.contains(Frame::stringListSeparator())) {
1603                   value = Genres::getNumberString(value, true);
1604                 }
1605               } else if (id == ID3FID_TRACKNUM) {
1606                 formatTrackNumberIfEnabled(value, true);
1607               }
1608               bool hasEnc;
1609               const ID3_TextEnc enc = fld->GetEncoding();
1610               auto newEnc = static_cast<ID3_TextEnc>(
1611                 frame.getFieldValue(Frame::ID_TextEnc).toInt(&hasEnc));
1612               if (!hasEnc) {
1613                 newEnc = enc;
1614               }
1615               if (newEnc != ID3TE_ISO8859_1 && newEnc != ID3TE_UTF16) {
1616                 // only ISO-8859-1 and UTF16 are allowed for ID3v2.3.0
1617                 newEnc = ID3TE_UTF16;
1618               }
1619               if (newEnc == ID3TE_ISO8859_1) {
1620                 // check if information is lost if the string is not unicode
1621                 int unicode_size = value.length();
1622                 const QChar* qcarray = value.unicode();
1623                 for (int i = 0; i < unicode_size; i++) {
1624                   if (char ch = qcarray[i].toLatin1();
1625                     ch == 0 || (ch & 0x80) != 0) {
1626                     newEnc = ID3TE_UTF16;
1627                     break;
1628                   }
1629                 }
1630               }
1631               if (enc != newEnc && id != ID3FID_SYNCEDLYRICS) {
1632                 if (ID3_Field* encfld = id3Frame->GetField(ID3FN_TEXTENC)) {
1633                   encfld->Set(newEnc);
1634                 }
1635                 fld->SetEncoding(newEnc);
1636                 markTagChanged(Frame::Tag_2, frame.getExtendedType());
1637               }
1638               if (getString(fld) != value) {
1639                 setString(fld, value);
1640                 markTagChanged(Frame::Tag_2, frame.getExtendedType());
1641               }
1642               return true;
1643             }
1644             if (id3Frame->GetID() == ID3FID_PRIVATE &&
1645               (fld = id3Frame->GetField(ID3FN_DATA)) != nullptr) {
1646               ID3_Field* ownerFld = id3Frame->GetField(ID3FN_OWNER);
1647               QByteArray newData;
1648               if (QString owner;
1649                 ownerFld && !(owner = getString(ownerFld)).isEmpty() &&
1650                 AttributeData(owner).toByteArray(value, newData)) {
1651                 if (auto oldData =
1652                     QByteArray(reinterpret_cast<const char*>(fld->GetRawBinary()),
1653                                static_cast<int>(fld->Size()));
1654                     newData != oldData) {
1655                   fld->Set(reinterpret_cast<const unsigned char*>(newData.data()),
1656                            newData.size());
1657                   markTagChanged(Frame::Tag_2, frame.getExtendedType());
1658                 }
1659                 return true;
1660               }
1661             } else if (id3Frame->GetID() == ID3FID_CDID &&
1662               (fld = id3Frame->GetField(ID3FN_DATA)) != nullptr) {
1663               QByteArray newData;
1664               if (AttributeData::isHexString(value, 'F', QLatin1String("+")) &&
1665                 AttributeData(AttributeData::Utf16).toByteArray(value, newData)) {
1666                 if (auto oldData =
1667                     QByteArray(reinterpret_cast<const char*>(fld->GetRawBinary()),
1668                                static_cast<int>(fld->Size()));
1669                     newData != oldData) {
1670                   fld->Set(reinterpret_cast<const unsigned char*>(newData.data()),
1671                            static_cast<size_t>(newData.size()));
1672                   markTagChanged(Frame::Tag_2, frame.getExtendedType());
1673                 }
1674                 return true;
1675               }
1676             } else if (id3Frame->GetID() == ID3FID_UNIQUEFILEID &&
1677                        (fld = id3Frame->GetField(ID3FN_DATA)) != nullptr) {
1678               QByteArray newData;
1679               if (AttributeData::isHexString(value, 'Z', QLatin1String("-"))) {
1680                 newData = (value + QLatin1Char('\0')).toLatin1();
1681                 if (auto oldData =
1682                     QByteArray(reinterpret_cast<const char*>(fld->GetRawBinary()),
1683                                static_cast<int>(fld->Size()));
1684                     newData != oldData) {
1685                   fld->Set(reinterpret_cast<const unsigned char*>(newData.data()),
1686                            static_cast<size_t>(newData.size()));
1687                   markTagChanged(Frame::Tag_2, frame.getExtendedType());
1688                 }
1689                 return true;
1690               }
1691             } else if (id3Frame->GetID() == ID3FID_POPULARIMETER &&
1692                        (fld = id3Frame->GetField(ID3FN_RATING)) != nullptr) {
1693               if (getString(fld) != value) {
1694                 fld->Set(value.toInt());
1695                 markTagChanged(Frame::Tag_2, frame.getExtendedType());
1696               }
1697               return true;
1698             }
1699           }
1700         } else {
1701           setId3v2Frame(id3Frame, frame);
1702           markTagChanged(Frame::Tag_2, frame.getExtendedType());
1703           return true;
1704         }
1705       }
1706     }
1707   }
1708 
1709   // Try the basic method
1710   Frame::Type type = frame.getType();
1711   if (type < Frame::FT_FirstFrame || type > Frame::FT_LastV1Frame)
1712     return false;
1713 
1714   ID3_FrameID frameId = getId3libFrameIdForType(type);
1715   if (frameId == ID3FID_NOFRAME)
1716     return false;
1717 
1718   ID3_Tag* tag;
1719 #if QT_VERSION >= 0x060000
1720   QStringDecoder* decoder;
1721   QStringEncoder* encoder;
1722 #else
1723   const QTextCodec* codec;
1724 #endif
1725   bool allowUnicode;
1726   if (tagNr == Frame::Tag_1) {
1727     tag = m_tagV1.data();
1728 #if QT_VERSION >= 0x060000
1729     decoder = &s_decoderV1;
1730     encoder = &s_encoderV1;
1731 #else
1732     codec = s_textCodecV1;
1733 #endif
1734     allowUnicode = false;
1735   } else if (tagNr == Frame::Tag_2) {
1736     tag = m_tagV2.data();
1737 #if QT_VERSION >= 0x060000
1738     decoder = nullptr;
1739     encoder = nullptr;
1740 #else
1741     codec = nullptr;
1742 #endif
1743     allowUnicode = true;
1744   } else {
1745     return false;
1746   }
1747 
1748   switch (type) {
1749   case Frame::FT_Album:
1750   case Frame::FT_Artist:
1751   case Frame::FT_Comment:
1752   case Frame::FT_Title:
1753   {
1754     QString str = frame.getValue();
1755 #if QT_VERSION >= 0x060000
1756     if (getTextField(tag, frameId, decoder) != str &&
1757         setTextField(tag, frameId, str, allowUnicode, true, true, encoder)) {
1758 #else
1759     if (getTextField(tag, frameId, codec) != str &&
1760         setTextField(tag, frameId, str, allowUnicode, true, true, codec)) {
1761 #endif
1762       markTagChanged(tagNr, Frame::ExtendedType(type));
1763       if (QString s = checkTruncation(tagNr, str, 1ULL << type,
1764                                       type == Frame::FT_Comment ? 28 : 30);
1765           !s.isNull())
1766 #if QT_VERSION >= 0x060000
1767         setTextField(tag, frameId, s, allowUnicode, true, true, encoder);
1768 #else
1769         setTextField(tag, frameId, s, allowUnicode, true, true, codec);
1770 #endif
1771     }
1772     break;
1773   }
1774   case Frame::FT_Date:
1775   {
1776     if (int num = frame.getValueAsNumber(); setYear(tag, num)) {
1777       markTagChanged(tagNr, Frame::ExtendedType(type));
1778     }
1779     break;
1780   }
1781   case Frame::FT_Genre:
1782   {
1783     QString str = frame.getValue();
1784     if (tagNr == Frame::Tag_1) {
1785       if (!str.isNull()) {
1786         const auto genres = Frame::splitStringList(str);
1787         int num = 0xff;
1788         for (const auto& genre : genres) {
1789           num = Genres::getNumber(genre);
1790           if (num != 0xff) {
1791             break;
1792           }
1793         }
1794         if (setGenreNum(tag, num)) {
1795           markTagChanged(tagNr, Frame::ExtendedType(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, Frame::ExtendedType(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, Frame::ExtendedType(type));
1823           }
1824         }
1825       }
1826     }
1827     break;
1828   }
1829   case Frame::FT_Track:
1830   {
1831     if (tagNr == Frame::Tag_1) {
1832       if (int num = frame.getValueAsNumber(); setTrackNum(tag, num)) {
1833         markTagChanged(tagNr, Frame::ExtendedType(type));
1834         if (int n = checkTruncation(tagNr, num, 1ULL << type); n != -1)
1835           setTrackNum(tag, n);
1836       }
1837     } else {
1838       QString str = frame.getValue();
1839       int numTracks;
1840       if (int num = splitNumberAndTotal(str, &numTracks);
1841           setTrackNum(tag, num, numTracks)) {
1842         markTagChanged(tagNr, Frame::ExtendedType(type));
1843       }
1844     }
1845     break;
1846   }
1847   default:
1848     return false;
1849   }
1850   return true;
1851 }
1852 
1853 /**
1854  * Create an id3lib frame from a frame.
1855  * @param frame frame
1856  * @return id3lib frame, 0 if invalid.
1857  */
1858 ID3_Frame* Mp3File::createId3FrameFromFrame(Frame& frame) const
1859 {
1860   ID3_Frame* id3Frame = nullptr;
1861   ID3_FrameID id;
1862   if (!Frame::isCustomFrameTypeOrOther(frame.getType())) {
1863     id = getId3libFrameIdForType(frame.getType());
1864   } else {
1865     id = getId3libFrameIdForName(frame.getName());
1866     if (id == ID3FID_NOFRAME) {
1867       if (frame.getName() == QLatin1String("AverageLevel") ||
1868           frame.getName() == QLatin1String("PeakValue") ||
1869           frame.getName().startsWith(QLatin1String("WM/"))) {
1870         id = ID3FID_PRIVATE;
1871       } else if (frame.getName().startsWith(QLatin1String("iTun"))) {
1872         id = ID3FID_COMMENT;
1873       } else {
1874         id = ID3FID_USERTEXT;
1875       }
1876     }
1877   }
1878   if (id != ID3FID_NOFRAME && id != ID3FID_SETSUBTITLE) {
1879     id3Frame = new ID3_Frame(id);
1880     ID3_Field* fld = id3Frame->GetField(ID3FN_TEXT);
1881     if (fld) {
1882       ID3_TextEnc enc = getDefaultTextEncoding();
1883       if (ID3_Field* encfld = id3Frame->GetField(ID3FN_TEXTENC)) {
1884         encfld->Set(enc);
1885       }
1886       fld->SetEncoding(enc);
1887     }
1888     if (id == ID3FID_USERTEXT &&
1889         !frame.getName().startsWith(QLatin1String("TXXX"))) {
1890       fld = id3Frame->GetField(ID3FN_DESCRIPTION);
1891       if (fld) {
1892         QString description;
1893         if (frame.getType() == Frame::FT_CatalogNumber) {
1894           description = QLatin1String("CATALOGNUMBER");
1895         } else if (frame.getType() == Frame::FT_ReleaseCountry) {
1896           description = QLatin1String("RELEASECOUNTRY");
1897         } else if (frame.getType() == Frame::FT_Grouping) {
1898           description = QLatin1String("GROUPING");
1899         } else if (frame.getType() == Frame::FT_Subtitle) {
1900           description = QLatin1String("SUBTITLE");
1901         } else if (Frame::isCustomFrameType(frame.getType())) {
1902           description = QString::fromLatin1(
1903                 Frame::getNameForCustomFrame(frame.getType()));
1904         } else {
1905           description = frame.getName();
1906         }
1907         setString(fld, description);
1908       }
1909     } else if (id == ID3FID_COMMENT) {
1910       fld = id3Frame->GetField(ID3FN_LANGUAGE);
1911       if (fld) {
1912         setString(fld, QLatin1String("eng"));
1913       }
1914       if (frame.getType() == Frame::FT_Other) {
1915         fld = id3Frame->GetField(ID3FN_DESCRIPTION);
1916         if (fld) {
1917           setString(fld, frame.getName());
1918         }
1919       }
1920     } else if (id == ID3FID_PRIVATE &&
1921                !frame.getName().startsWith(QLatin1String("PRIV"))) {
1922       fld = id3Frame->GetField(ID3FN_OWNER);
1923       if (fld) {
1924         setString(fld, frame.getName());
1925         QByteArray data;
1926         if (AttributeData(frame.getName()).toByteArray(frame.getValue(), data)) {
1927           fld = id3Frame->GetField(ID3FN_DATA);
1928           if (fld) {
1929             fld->Set(reinterpret_cast<const unsigned char*>(data.data()),
1930                      static_cast<size_t>(data.size()));
1931           }
1932         }
1933       }
1934     } else if (id == ID3FID_UNIQUEFILEID) {
1935       fld = id3Frame->GetField(ID3FN_OWNER);
1936       if (fld) {
1937         setString(fld, QLatin1String("http://www.id3.org/dummy/ufid.html"));
1938       }
1939       QByteArray data;
1940       if (AttributeData::isHexString(frame.getValue(), 'Z', QLatin1String("-"))) {
1941         data = (frame.getValue() + QLatin1Char('\0')).toLatin1();
1942         fld = id3Frame->GetField(ID3FN_DATA);
1943         if (fld) {
1944           fld->Set(reinterpret_cast<const unsigned char*>(data.data()),
1945                    static_cast<size_t>(data.size()));
1946         }
1947       }
1948     } else if (id == ID3FID_PICTURE) {
1949       fld = id3Frame->GetField(ID3FN_MIMETYPE);
1950       if (fld) {
1951         setString(fld, QLatin1String("image/jpeg"));
1952       }
1953       fld = id3Frame->GetField(ID3FN_PICTURETYPE);
1954       if (fld) {
1955         fld->Set(ID3PT_COVERFRONT);
1956       }
1957     } else if (id == ID3FID_SYNCEDLYRICS) {
1958       fld = id3Frame->GetField(ID3FN_LANGUAGE);
1959       if (fld) {
1960         setString(fld, QLatin1String("eng"));
1961       }
1962       fld = id3Frame->GetField(ID3FN_TIMESTAMPFORMAT);
1963       if (fld) {
1964         fld->Set(ID3TSF_MS);
1965       }
1966       fld = id3Frame->GetField(ID3FN_CONTENTTYPE);
1967       if (fld) {
1968         fld->Set(ID3CT_LYRICS);
1969       }
1970     } else if (id == ID3FID_UNSYNCEDLYRICS || id == ID3FID_TERMSOFUSE) {
1971       fld = id3Frame->GetField(ID3FN_LANGUAGE);
1972       if (fld) {
1973         setString(fld, QLatin1String("eng"));
1974       }
1975     } else if (id == ID3FID_POPULARIMETER) {
1976       fld = id3Frame->GetField(ID3FN_EMAIL);
1977       if (fld) {
1978         setString(fld, TagConfig::instance().defaultPopmEmail());
1979       }
1980     }
1981     if (!frame.fieldList().empty()) {
1982       setId3v2Frame(id3Frame, frame);
1983     }
1984     Frame::Type type;
1985     const char* name;
1986     getTypeStringForId3libFrameId(id, type, name);
1987     if (type == Frame::FT_Other) {
1988       type = Frame::getTypeFromCustomFrameName(QByteArray(id3Frame->GetTextID()));
1989     }
1990     frame.setExtendedType(Frame::ExtendedType(type, QString::fromLatin1(name)));
1991   }
1992   return id3Frame;
1993 }
1994 
1995 /**
1996  * Add a frame in the tags.
1997  *
1998  * @param tagNr tag number
1999  * @param frame frame to add, a field list may be added by this method
2000  *
2001  * @return true if ok.
2002  */
2003 bool Mp3File::addFrame(Frame::TagNumber tagNr, Frame& frame)
2004 {
2005   if (tagNr == Frame::Tag_2) {
2006     // Add a new frame.
2007     if (ID3_Frame* id3Frame;
2008         m_tagV2 && (id3Frame = createId3FrameFromFrame(frame)) != nullptr) {
2009       m_tagV2->AttachFrame(id3Frame);
2010       frame.setIndex(m_tagV2->NumFrames() - 1);
2011       if (frame.fieldList().empty()) {
2012         // add field list to frame
2013         getFieldsFromId3Frame(id3Frame, frame.fieldList());
2014         frame.setFieldListFromValue();
2015       }
2016       markTagChanged(Frame::Tag_2, frame.getExtendedType());
2017       return true;
2018     }
2019   }
2020 
2021   // Try the superclass method
2022   return TaggedFile::addFrame(tagNr, frame);
2023 }
2024 
2025 /**
2026  * Delete a frame from the tags.
2027  *
2028  * @param tagNr tag number
2029  * @param frame frame to delete.
2030  *
2031  * @return true if ok.
2032  */
2033 bool Mp3File::deleteFrame(Frame::TagNumber tagNr, const Frame& frame)
2034 {
2035   if (tagNr == Frame::Tag_2) {
2036     // If the frame has an index, delete that specific frame
2037     if (int index = frame.getIndex(); index != -1 && m_tagV2) {
2038       if (ID3_Frame* id3Frame = getId3v2Frame(m_tagV2.data(), index)) {
2039         m_tagV2->RemoveFrame(id3Frame);
2040         markTagChanged(Frame::Tag_2, frame.getExtendedType());
2041         return true;
2042       }
2043     }
2044   }
2045 
2046   // Try the superclass method
2047   return TaggedFile::deleteFrame(tagNr, frame);
2048 }
2049 
2050 namespace {
2051 
2052 /**
2053  * Create a frame from an id3lib frame.
2054  * @param id3Frame id3lib frame
2055  * @param index -1 if not used
2056  * @return frame.
2057  */
2058 Frame createFrameFromId3libFrame(ID3_Frame* id3Frame, int index)
2059 {
2060   Frame::Type type;
2061   const char* name;
2062   getTypeStringForId3libFrameId(id3Frame->GetID(), type, name);
2063   if (type == Frame::FT_Other) {
2064     type = Frame::getTypeFromCustomFrameName(QByteArray(id3Frame->GetTextID()));
2065   }
2066 
2067   Frame frame(type, QLatin1String(""), QString::fromLatin1(name), index);
2068   frame.setValue(getFieldsFromId3Frame(id3Frame, frame.fieldList()));
2069   if (id3Frame->GetID() == ID3FID_USERTEXT ||
2070       id3Frame->GetID() == ID3FID_WWWUSER ||
2071       id3Frame->GetID() == ID3FID_COMMENT) {
2072     if (QVariant fieldValue = frame.getFieldValue(Frame::ID_Description);
2073         fieldValue.isValid()) {
2074       if (QString description = fieldValue.toString(); !description.isEmpty()) {
2075         if (description == QLatin1String("CATALOGNUMBER")) {
2076           frame.setType(Frame::FT_CatalogNumber);
2077         } else if (description == QLatin1String("RELEASECOUNTRY")) {
2078           frame.setType(Frame::FT_ReleaseCountry);
2079         } else if (description == QLatin1String("GROUPING")) {
2080           frame.setType(Frame::FT_Grouping);
2081         } else if (description == QLatin1String("SUBTITLE")) {
2082           frame.setType(Frame::FT_Subtitle);
2083         } else {
2084           frame.setExtendedType(Frame::ExtendedType(
2085               Frame::getTypeFromCustomFrameName(description.toLatin1()),
2086               frame.getInternalName() + QLatin1Char('\n') + description));
2087         }
2088       }
2089     }
2090   } else if (id3Frame->GetID() == ID3FID_PRIVATE) {
2091     const Frame::FieldList& fields = frame.getFieldList();
2092     QString owner;
2093     QByteArray data;
2094     for (auto it = fields.constBegin(); it != fields.constEnd(); ++it) {
2095       if (it->m_id == Frame::ID_Owner) {
2096         owner = it->m_value.toString();
2097         if (!owner.isEmpty()) {
2098           frame.setExtendedType(Frame::ExtendedType(Frame::FT_Other,
2099                     frame.getInternalName() + QLatin1Char('\n') + owner));
2100         }
2101       } else if (it->m_id == Frame::ID_Data) {
2102         data = it->m_value.toByteArray();
2103       }
2104     }
2105     if (!owner.isEmpty() && !data.isEmpty()) {
2106       if (QString str; AttributeData(owner).toString(data, str)) {
2107         frame.setValue(str);
2108       }
2109     }
2110   } else if (id3Frame->GetID() == ID3FID_CDID) {
2111     if (QVariant fieldValue = frame.getFieldValue(Frame::ID_Data);
2112         fieldValue.isValid()) {
2113       if (QString str; AttributeData(AttributeData::Utf16)
2114           .toString(fieldValue.toByteArray(), str) &&
2115           AttributeData::isHexString(str, 'F', QLatin1String("+"))) {
2116         frame.setValue(str);
2117       }
2118     }
2119   } else if (id3Frame->GetID() == ID3FID_UNIQUEFILEID) {
2120     if (QVariant fieldValue = frame.getFieldValue(Frame::ID_Data);
2121         fieldValue.isValid()) {
2122       QByteArray ba(fieldValue.toByteArray());
2123       if (QString str(QString::fromLatin1(ba));
2124           ba.size() - str.length() <= 1 &&
2125           AttributeData::isHexString(str, 'Z', QLatin1String("-"))) {
2126         frame.setValue(str);
2127       }
2128     }
2129   } else if (id3Frame->GetID() == ID3FID_POPULARIMETER) {
2130     if (QVariant fieldValue = frame.getFieldValue(Frame::ID_Rating);
2131         fieldValue.isValid()) {
2132       if (QString str(fieldValue.toString()); !str.isEmpty()) {
2133         frame.setValue(str);
2134       }
2135     }
2136   }
2137   return frame;
2138 }
2139 
2140 }
2141 
2142 /**
2143  * Remove frames.
2144  *
2145  * @param tagNr tag number
2146  * @param flt filter specifying which frames to remove
2147  */
2148 void Mp3File::deleteFrames(Frame::TagNumber tagNr, const FrameFilter& flt)
2149 {
2150   if (tagNr == Frame::Tag_1) {
2151     if (m_tagV1) {
2152       if (flt.areAllEnabled()) {
2153         ID3_Tag::Iterator* iter = m_tagV1->CreateIterator();
2154         ID3_Frame* frame;
2155         while ((frame = iter->GetNext()) != nullptr) {
2156           m_tagV1->RemoveFrame(frame);
2157         }
2158 #ifdef Q_OS_WIN32
2159         /* allocated in Windows DLL => must be freed in the same DLL */
2160         ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
2161 #else
2162 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2163 #pragma GCC diagnostic push
2164 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
2165 /* Is safe because iterator implementation has default destructor */
2166 #endif
2167         delete iter;
2168 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2169 #pragma GCC diagnostic pop
2170 #endif
2171 #endif
2172         markTagChanged(Frame::Tag_1, Frame::ExtendedType());
2173         clearTrunctionFlags(Frame::Tag_1);
2174       } else {
2175         TaggedFile::deleteFrames(Frame::Tag_1, flt);
2176       }
2177     }
2178   } else if (tagNr == Frame::Tag_2) {
2179     if (m_tagV2) {
2180       if (flt.areAllEnabled()) {
2181         ID3_Tag::Iterator* iter = m_tagV2->CreateIterator();
2182         ID3_Frame* frame;
2183         while ((frame = iter->GetNext()) != nullptr) {
2184           m_tagV2->RemoveFrame(frame);
2185         }
2186 #ifdef Q_OS_WIN32
2187         /* allocated in Windows DLL => must be freed in the same DLL */
2188         ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
2189 #else
2190 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2191 #pragma GCC diagnostic push
2192 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
2193 /* Is safe because iterator implementation has default destructor */
2194 #endif
2195         delete iter;
2196 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2197 #pragma GCC diagnostic pop
2198 #endif
2199 #endif
2200         markTagChanged(Frame::Tag_2, Frame::ExtendedType());
2201       } else {
2202         ID3_Tag::Iterator* iter = m_tagV2->CreateIterator();
2203         ID3_Frame* id3Frame;
2204         while ((id3Frame = iter->GetNext()) != nullptr) {
2205           if (Frame frame(createFrameFromId3libFrame(id3Frame, -1));
2206               flt.isEnabled(frame.getType(), frame.getName())) {
2207             m_tagV2->RemoveFrame(id3Frame);
2208           }
2209         }
2210   #ifdef Q_OS_WIN32
2211         /* allocated in Windows DLL => must be freed in the same DLL */
2212         ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
2213   #else
2214   #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2215   #pragma GCC diagnostic push
2216   #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
2217   /* Is safe because iterator implementation has default destructor */
2218   #endif
2219         delete iter;
2220   #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2221   #pragma GCC diagnostic pop
2222   #endif
2223   #endif
2224         markTagChanged(Frame::Tag_2, Frame::ExtendedType());
2225       }
2226     }
2227   }
2228 }
2229 
2230 /**
2231  * Get all frames in tag.
2232  *
2233  * @param tagNr tag number
2234  * @param frames frame collection to set.
2235  */
2236 void Mp3File::getAllFrames(Frame::TagNumber tagNr, FrameCollection& frames)
2237 {
2238   if (tagNr == Frame::Tag_2) {
2239     frames.clear();
2240     if (m_tagV2) {
2241       ID3_Tag::Iterator* iter = m_tagV2->CreateIterator();
2242       ID3_Frame* id3Frame;
2243       int i = 0;
2244       while ((id3Frame = iter->GetNext()) != nullptr) {
2245         Frame frame(createFrameFromId3libFrame(id3Frame, i++));
2246         frames.insert(frame);
2247       }
2248 #ifdef Q_OS_WIN32
2249       /* allocated in Windows DLL => must be freed in the same DLL */
2250       ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
2251 #else
2252 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2253 #pragma GCC diagnostic push
2254 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
2255 /* Is safe because iterator implementation has default destructor */
2256 #endif
2257       delete iter;
2258 #ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2259 #pragma GCC diagnostic pop
2260 #endif
2261 #endif
2262     }
2263     updateMarkedState(tagNr, frames);
2264     frames.addMissingStandardFrames();
2265     return;
2266   }
2267 
2268   TaggedFile::getAllFrames(tagNr, frames);
2269 }
2270 
2271 /**
2272  * Add a suitable field list for the frame if missing.
2273  * If a frame is created, its field list is empty. This method will create
2274  * a field list appropriate for the frame type and tagged file type if no
2275  * field list exists.
2276  * @param tagNr tag number
2277  * @param frame frame where field list is added
2278  */
2279 void Mp3File::addFieldList(Frame::TagNumber tagNr, Frame& frame) const
2280 {
2281   if (tagNr == Frame::Tag_2 && frame.fieldList().isEmpty()) {
2282     if (ID3_Frame* id3Frame = createId3FrameFromFrame(frame)) {
2283       getFieldsFromId3Frame(id3Frame, frame.fieldList());
2284       frame.setFieldListFromValue();
2285       delete id3Frame;
2286     }
2287   }
2288 }
2289 
2290 /**
2291  * Get a list of frame IDs which can be added.
2292  * @param tagNr tag number
2293  * @return list with frame IDs.
2294  */
2295 QStringList Mp3File::getFrameIds(Frame::TagNumber tagNr) const
2296 {
2297   if (tagNr != Frame::Tag_2)
2298     return QStringList();
2299 
2300   QStringList lst;
2301   for (int type = Frame::FT_FirstFrame; type <= Frame::FT_LastFrame; ++type) {
2302     if (auto name = Frame::ExtendedType(static_cast<Frame::Type>(type),
2303                                         QLatin1String("")).getName();
2304         !name.isEmpty()) {
2305       lst.append(name);
2306     }
2307   }
2308   for (int i = 0; i <= ID3FID_WWWUSER; ++i) {
2309     if (typeStrOfId[i].type == Frame::FT_Other) {
2310       if (const char* s = typeStrOfId[i].str) {
2311         lst.append(QString::fromLatin1(s));
2312       }
2313     }
2314   }
2315   return lst;
2316 }
2317 
2318 /**
2319  * Set the encoding to be used for tag 1.
2320  *
2321  * @param name of encoding, default is ISO 8859-1
2322  */
2323 void Mp3File::setTextEncodingV1(const QString& name)
2324 {
2325 #if QT_VERSION >= 0x060000
2326   auto encoding = QStringConverter::encodingForName(name.toLatin1());
2327   s_decoderV1 = QStringDecoder(encoding.value_or(QStringConverter::Latin1));
2328   s_encoderV1 = QStringEncoder(encoding.value_or(QStringConverter::Latin1));
2329 #else
2330   s_textCodecV1 = name != QLatin1String("ISO-8859-1")
2331       ? QTextCodec::codecForName(name.toLatin1().data()) : nullptr;
2332 #endif
2333 }
2334 
2335 /**
2336  * Set the default text encoding.
2337  *
2338  * @param textEnc default text encoding
2339  */
2340 void Mp3File::setDefaultTextEncoding(TagConfig::TextEncoding textEnc)
2341 {
2342   // UTF8 encoding is buggy, so UTF16 is used when UTF8 is configured
2343   s_defaultTextEncoding = textEnc == TagConfig::TE_ISO8859_1 ?
2344     ID3TE_ISO8859_1 : ID3TE_UTF16;
2345 }
2346 
2347 /**
2348  * Notify about configuration change.
2349  * This method shall be called when the configuration changes.
2350  */
2351 void Mp3File::notifyConfigurationChange()
2352 {
2353   setDefaultTextEncoding(
2354     static_cast<TagConfig::TextEncoding>(TagConfig::instance().textEncoding()));
2355   setTextEncodingV1(TagConfig::instance().textEncodingV1());
2356 }