File indexing completed on 2024-05-19 04:56:36

0001 /**
0002  * \file taglibfile.cpp
0003  * Handling of tagged files using TagLib.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 12 Sep 2006
0008  *
0009  * Copyright (C) 2006-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 "taglibfile.h"
0028 #include <QDir>
0029 #include <QString>
0030 #if QT_VERSION >= 0x060000
0031 #include <QStringConverter>
0032 #else
0033 #include <QTextCodec>
0034 #endif
0035 #include <QByteArray>
0036 #include <QVarLengthArray>
0037 #include <QScopedPointer>
0038 #include <QMimeDatabase>
0039 #include "genres.h"
0040 #include "attributedata.h"
0041 #include "pictureframe.h"
0042 
0043 // Just using include <oggfile.h>, include <flacfile.h> as recommended in the
0044 // TagLib documentation does not work, as there are files with these names
0045 // in this directory.
0046 #include <mpegfile.h>
0047 #include <oggfile.h>
0048 #include <vorbisfile.h>
0049 #include <flacfile.h>
0050 #include <mpcfile.h>
0051 #include <id3v1tag.h>
0052 #include <id3v1genres.h>
0053 #include <id3v2tag.h>
0054 #include <id3v2header.h>
0055 #include <apetag.h>
0056 #include <textidentificationframe.h>
0057 #include <commentsframe.h>
0058 #include <attachedpictureframe.h>
0059 #include <uniquefileidentifierframe.h>
0060 #include <generalencapsulatedobjectframe.h>
0061 #include <urllinkframe.h>
0062 #include <unsynchronizedlyricsframe.h>
0063 #include <speexfile.h>
0064 #include <trueaudiofile.h>
0065 #include <wavpackfile.h>
0066 #include <oggflacfile.h>
0067 #include <relativevolumeframe.h>
0068 #include <mp4file.h>
0069 #include <asffile.h>
0070 #include <aifffile.h>
0071 #include <wavfile.h>
0072 #include <popularimeterframe.h>
0073 #include <privateframe.h>
0074 #include <apefile.h>
0075 #include <ownershipframe.h>
0076 #include <modfile.h>
0077 #include <s3mfile.h>
0078 #include <itfile.h>
0079 #include <tfilestream.h>
0080 #include <xmfile.h>
0081 #include <opusfile.h>
0082 #if TAGLIB_VERSION >= 0x020000
0083 #include <dsffile.h>
0084 #include <dsdifffile.h>
0085 #else
0086 #include "taglibext/dsf/dsffiletyperesolver.h"
0087 #include "taglibext/dsf/dsffile.h"
0088 #include "taglibext/dsdiff/dsdifffiletyperesolver.h"
0089 #include "taglibext/dsdiff/dsdifffile.h"
0090 #endif
0091 
0092 #if TAGLIB_VERSION >= 0x010a00
0093 #include <synchronizedlyricsframe.h>
0094 #include <eventtimingcodesframe.h>
0095 #include <chapterframe.h>
0096 #include <tableofcontentsframe.h>
0097 #else
0098 #include "taglibext/synchronizedlyricsframe.h"
0099 #include "taglibext/eventtimingcodesframe.h"
0100 #endif
0101 
0102 #if TAGLIB_VERSION >= 0x010b00
0103 #include <podcastframe.h>
0104 #endif
0105 #if TAGLIB_VERSION < 0x020000
0106 #include "taglibext/aac/aacfiletyperesolver.h"
0107 #include "taglibext/mp2/mp2filetyperesolver.h"
0108 #endif
0109 
0110 /** for loop through all supported tag number values. */
0111 #define FOR_TAGLIB_TAGS(variable) \
0112   for (Frame::TagNumber variable = Frame::Tag_1; \
0113        variable < NUM_TAGS; \
0114        variable = static_cast<Frame::TagNumber>(variable + 1))
0115 
0116 /** for loop through all supported tag number values in reverse order. */
0117 #define FOR_TAGLIB_TAGS_REVERSE(variable) \
0118   for (Frame::TagNumber variable = static_cast<Frame::TagNumber>(NUM_TAGS - 1); \
0119        variable >= Frame::Tag_1; \
0120        variable = static_cast<Frame::TagNumber>(variable - 1))
0121 
0122 #if defined TAGLIB_WITH_OFFSET_TYPE || TAGLIB_VERSION >= 0x020000
0123 typedef TagLib::offset_t taglib_offset_t;
0124 typedef TagLib::offset_t taglib_uoffset_t;
0125 #else
0126 typedef long taglib_offset_t;
0127 typedef ulong taglib_uoffset_t;
0128 #endif
0129 
0130 namespace {
0131 
0132 /** Convert QString @a s to a TagLib::String. */
0133 TagLib::String toTString(const QString& s)
0134 {
0135   int len = s.length();
0136   QVarLengthArray<wchar_t> a(len + 1);
0137   wchar_t* const ws = a.data();
0138   // Do not use `len = s.toWCharArray(ws); ws[len] = 0;`, this would construct
0139   // an array with UCS-4 encoded wide characters (if not on Windows), which
0140   // is not compatible with TagLib, which expects only 16 bit characters.
0141   // This works for Basic Multilingual Plane only, but not for surrogate pairs.
0142   wchar_t* wsPtr = ws;
0143   for (auto it = s.constBegin(); it != s.constEnd(); ++it) {
0144     *wsPtr++ = it->unicode();
0145   }
0146   *wsPtr = 0;
0147   return TagLib::String(ws);
0148 }
0149 
0150 /** Convert TagLib::String @a s to a QString. */
0151 QString toQString(const TagLib::String& s)
0152 {
0153   return QString::fromWCharArray(s.toCWString(), s.size());
0154 }
0155 
0156 /**
0157  * Convert TagLib::StringList @a tstrs to QString joining with
0158  * Frame::stringListSeparator().
0159  */
0160 QString joinToQString(const TagLib::StringList &tstrs)
0161 {
0162   QStringList strs;
0163   strs.reserve(tstrs.size());
0164   for (const TagLib::String &tstr : tstrs) {
0165     strs.append(toQString(tstr));
0166   }
0167   return Frame::joinStringList(strs);
0168 }
0169 
0170 /**
0171  * Convert QString @a s to a TagLib::StringList splitting with
0172  * Frame::stringListSeparator().
0173  */
0174 TagLib::StringList splitToTStringList(const QString &s)
0175 {
0176   const QStringList qstrs = Frame::splitStringList(s);
0177   TagLib::StringList tstrs;
0178   for (const QString &qstr : qstrs) {
0179     tstrs.append(toTString(qstr));
0180   }
0181   return tstrs;
0182 }
0183 
0184 /**
0185  * Set a picture frame from a FLAC picture.
0186  *
0187  * @param pic FLAC picture
0188  * @param frame the picture frame is returned here
0189  */
0190 void flacPictureToFrame(const TagLib::FLAC::Picture* pic, Frame& frame)
0191 {
0192   TagLib::ByteVector picData(pic->data());
0193   QByteArray ba(picData.data(), picData.size());
0194   PictureFrame::ImageProperties imgProps(
0195         pic->width(), pic->height(), pic->colorDepth(),
0196         pic->numColors(), ba);
0197   PictureFrame::setFields(
0198     frame, Frame::TE_ISO8859_1, QLatin1String("JPG"), toQString(pic->mimeType()),
0199     static_cast<PictureFrame::PictureType>(pic->type()),
0200     toQString(pic->description()),
0201     ba, &imgProps);
0202 }
0203 
0204 /**
0205  * Set a FLAC picture from a frame.
0206  *
0207  * @param frame picture frame
0208  * @param pic the FLAC picture to set
0209  */
0210 void frameToFlacPicture(const Frame& frame, TagLib::FLAC::Picture* pic)
0211 {
0212   Frame::TextEncoding enc;
0213   QString imgFormat;
0214   QString mimeType;
0215   PictureFrame::PictureType pictureType;
0216   QString description;
0217   QByteArray data;
0218   PictureFrame::ImageProperties imgProps;
0219   PictureFrame::getFields(frame, enc, imgFormat, mimeType, pictureType,
0220                           description, data, &imgProps);
0221   pic->setType(static_cast<TagLib::FLAC::Picture::Type>(pictureType));
0222   pic->setMimeType(toTString(mimeType));
0223   pic->setDescription(toTString(description));
0224   pic->setData(TagLib::ByteVector(data.data(), data.size()));
0225   if (!imgProps.isValidForImage(data)) {
0226     imgProps = PictureFrame::ImageProperties(data);
0227   }
0228   pic->setWidth(imgProps.width());
0229   pic->setHeight(imgProps.height());
0230   pic->setColorDepth(imgProps.depth());
0231   pic->setNumColors(imgProps.numColors());
0232 }
0233 
0234 
0235 /**
0236  * TagLib::RIFF::WAV::File subclass with additional method for id3 chunk name.
0237  */
0238 class WavFile : public TagLib::RIFF::WAV::File {
0239 public:
0240   /**
0241    * Constructor.
0242    * @param stream stream to open
0243    */
0244   explicit WavFile(TagLib::IOStream *stream);
0245   ~WavFile() override;
0246 
0247   /**
0248    * Replace the "ID3 " chunk with a lowercase named "id3 " chunk.
0249    * This method has to be called after successfully calling save() to use
0250    * lowercase "id3 " chunk names.
0251    */
0252   void changeToLowercaseId3Chunk();
0253 };
0254 
0255 /**
0256  * Destructor.
0257  */
0258 WavFile::~WavFile()
0259 {
0260   // not inline or default to silence weak-vtables warning
0261 }
0262 
0263 WavFile::WavFile(TagLib::IOStream *stream) : TagLib::RIFF::WAV::File(stream)
0264 {
0265 }
0266 
0267 void WavFile::changeToLowercaseId3Chunk()
0268 {
0269   if (readOnly() || !isValid())
0270     return;
0271 
0272   int i;
0273   for (i = chunkCount() - 1; i >= 0; --i) {
0274     if (chunkName(i) == "ID3 ") {
0275       break;
0276     }
0277   }
0278   if (i >= 0) {
0279     TagLib::ByteVector data = chunkData(i);
0280     removeChunk(i);
0281     setChunkData("id3 ", data);
0282   }
0283 }
0284 
0285 }
0286 
0287 /**
0288  * Wrapper around TagLib::FileStream which reduces the number of open file
0289  * descriptors.
0290  *
0291  * Using streams, closing the file descriptor is also possible for modified
0292  * files because the TagLib file does not have to be deleted just to close the
0293  * file descriptor.
0294  */
0295 class FileIOStream : public TagLib::IOStream {
0296 public:
0297   /**
0298    * Constructor.
0299    * @param fileName path to file
0300    */
0301   explicit FileIOStream(const QString& fileName);
0302 
0303   /**
0304    * Destructor.
0305    */
0306   ~FileIOStream() override;
0307 
0308   FileIOStream(const FileIOStream&) = delete;
0309   FileIOStream& operator=(const FileIOStream&) = delete;
0310 
0311   /**
0312    * Close the file handle.
0313    * The file will automatically be opened again if needed.
0314    */
0315   void closeFileHandle();
0316 
0317   /**
0318    * Change the file name.
0319    * Can be used to modify the file name when it has changed because a path
0320    * component was renamed.
0321    * @param fileName path to file
0322    */
0323   void setName(const QString& fileName);
0324 
0325   // Reimplemented from TagLib::IOStream, delegate to TagLib::FileStream.
0326   /** File name in local file system encoding. */
0327   TagLib::FileName name() const override;
0328 #if TAGLIB_VERSION >= 0x020000
0329   /** Read block of size @a length at current pointer. */
0330   TagLib::ByteVector readBlock(size_t length) override;
0331   /**
0332    * Insert @a data at position @a start in the file overwriting @a replace
0333    * bytes of the original content.
0334    */
0335   void insert(const TagLib::ByteVector &data,
0336               taglib_uoffset_t start = 0, size_t replace = 0) override;
0337   /** Remove block starting at @a start for @a length bytes. */
0338   void removeBlock(taglib_uoffset_t start = 0, size_t length = 0) override;
0339 #else
0340   /** Read block of size @a length at current pointer. */
0341   TagLib::ByteVector readBlock(ulong length) override;
0342   /**
0343    * Insert @a data at position @a start in the file overwriting @a replace
0344    * bytes of the original content.
0345    */
0346   void insert(const TagLib::ByteVector &data,
0347               taglib_uoffset_t start = 0, ulong replace = 0) override;
0348   /** Remove block starting at @a start for @a length bytes. */
0349   void removeBlock(taglib_uoffset_t start = 0, ulong length = 0) override;
0350 #endif
0351   /** Write block @a data at current pointer. */
0352   void writeBlock(const TagLib::ByteVector &data) override;
0353   /** True if the file is read only. */
0354   bool readOnly() const override;
0355   /** Check if open in constructor succeeded. */
0356   bool isOpen() const override;
0357   /** Move I/O pointer to @a offset in the file from position @a p. */
0358   void seek(taglib_offset_t offset, Position p = Beginning) override;
0359   /** Reset the end-of-file and error flags on the file. */
0360   void clear() override;
0361   /** Current offset within the file. */
0362   taglib_offset_t tell() const override;
0363   /** Length of the file. */
0364   taglib_offset_t length() override;
0365   /** Truncate the file to @a length. */
0366   void truncate(taglib_offset_t length) override;
0367 
0368   /**
0369    * Create a TagLib file for a stream.
0370    * TagLib::FileRef::create() adapted for IOStream.
0371    * @param stream stream with name() of which the extension is used to deduce
0372    * the file type
0373    * @return file, 0 if not supported.
0374    */
0375   static TagLib::File* create(IOStream* stream);
0376 
0377 private:
0378   /**
0379    * Open file handle, is called by operations which need a file handle.
0380    *
0381    * @return true if file is open.
0382    */
0383   bool openFileHandle() const;
0384 
0385   /**
0386    * Create a TagLib file for a stream.
0387    * @param stream stream with name() of which the extension is used to deduce
0388    * the file type
0389    * @return file, 0 if not supported.
0390    */
0391   static TagLib::File* createFromExtension(IOStream* stream);
0392 
0393   /**
0394    * Create a TagLib file for a stream.
0395    * @param stream stream
0396    * @param ext uppercase extension used to deduce the file type
0397    * @return file, 0 if not supported.
0398    */
0399   static TagLib::File* createFromExtension(TagLib::IOStream* stream,
0400                                            const TagLib::String& ext);
0401 
0402   /**
0403    * Create a TagLib file for a stream.
0404    * @param stream stream where the contents are used to deduce the file type
0405    * @return file, 0 if not supported.
0406    */
0407   static TagLib::File* createFromContents(IOStream* stream);
0408 
0409   /**
0410    * Register open files, so that the number of open files can be limited.
0411    * If the number of open files exceeds a limit, files are closed.
0412    *
0413    * @param stream new open file to be registered
0414    */
0415   static void registerOpenFile(FileIOStream* stream);
0416 
0417   /**
0418    * Deregister open file.
0419    *
0420    * @param stream file which is no longer open
0421    */
0422   static void deregisterOpenFile(FileIOStream* stream);
0423 
0424 #ifdef Q_OS_WIN32
0425   wchar_t* m_fileName;
0426 #else
0427   char* m_fileName;
0428 #endif
0429   TagLib::FileStream* m_fileStream;
0430   long m_offset;
0431 
0432   /** list of file streams with open file descriptor */
0433   static QList<FileIOStream*> s_openFiles;
0434 };
0435 
0436 QList<FileIOStream*> FileIOStream::s_openFiles;
0437 
0438 FileIOStream::FileIOStream(const QString& fileName)
0439   : m_fileName(nullptr), m_fileStream(nullptr), m_offset(0)
0440 {
0441   setName(fileName);
0442 }
0443 
0444 FileIOStream::~FileIOStream()
0445 {
0446   deregisterOpenFile(this);
0447   delete m_fileStream;
0448   delete [] m_fileName;
0449 }
0450 
0451 bool FileIOStream::openFileHandle() const
0452 {
0453   if (!m_fileStream) {
0454     auto self = const_cast<FileIOStream*>(this);
0455     self->m_fileStream =
0456         new TagLib::FileStream(TagLib::FileName(m_fileName));
0457     if (!self->m_fileStream->isOpen()) {
0458       delete self->m_fileStream;
0459       self->m_fileStream = nullptr;
0460       return false;
0461     }
0462     if (m_offset > 0) {
0463       m_fileStream->seek(m_offset);
0464     }
0465     registerOpenFile(self);
0466   }
0467   return true;
0468 }
0469 
0470 void FileIOStream::closeFileHandle()
0471 {
0472   if (m_fileStream) {
0473     m_offset = m_fileStream->tell();
0474     delete m_fileStream;
0475     m_fileStream = nullptr;
0476     deregisterOpenFile(this);
0477   }
0478 }
0479 
0480 void FileIOStream::setName(const QString& fileName)
0481 {
0482   delete m_fileName;
0483 #ifdef Q_OS_WIN32
0484   int fnLen = fileName.length();
0485   m_fileName = new wchar_t[fnLen + 1];
0486   m_fileName[fnLen] = 0;
0487   fileName.toWCharArray(m_fileName);
0488 #else
0489   QByteArray fn = QFile::encodeName(fileName);
0490   m_fileName = new char[fn.size() + 1];
0491   qstrcpy(m_fileName, fn.data());
0492 #endif
0493 }
0494 
0495 TagLib::FileName FileIOStream::name() const
0496 {
0497   if (m_fileStream) {
0498     return m_fileStream->name();
0499   }
0500   return TagLib::FileName(m_fileName);
0501 }
0502 
0503 TagLib::ByteVector FileIOStream::readBlock(
0504 #if TAGLIB_VERSION >= 0x020000
0505     size_t length
0506 #else
0507     ulong length
0508 #endif
0509 )
0510 {
0511   if (openFileHandle()) {
0512     return m_fileStream->readBlock(length);
0513   }
0514   return TagLib::ByteVector();
0515 }
0516 
0517 void FileIOStream::writeBlock(const TagLib::ByteVector &data)
0518 {
0519   if (openFileHandle()) {
0520     m_fileStream->writeBlock(data);
0521   }
0522 }
0523 
0524 void FileIOStream::insert(const TagLib::ByteVector &data,
0525                           taglib_uoffset_t start,
0526 #if TAGLIB_VERSION >= 0x020000
0527                           size_t replace
0528 #else
0529                           ulong replace
0530 #endif
0531 )
0532 {
0533   if (openFileHandle()) {
0534     m_fileStream->insert(data, start, replace);
0535   }
0536 }
0537 
0538 void FileIOStream::removeBlock(taglib_uoffset_t start,
0539 #if TAGLIB_VERSION >= 0x020000
0540                                size_t length
0541 #else
0542                                ulong length
0543 #endif
0544 )
0545 {
0546   if (openFileHandle()) {
0547     m_fileStream->removeBlock(start, length);
0548   }
0549 }
0550 
0551 bool FileIOStream::readOnly() const
0552 {
0553   if (openFileHandle()) {
0554     return m_fileStream->readOnly();
0555   }
0556   return true;
0557 }
0558 
0559 bool FileIOStream::isOpen() const
0560 {
0561   if (m_fileStream) {
0562     return m_fileStream->isOpen();
0563   }
0564   return true;
0565 }
0566 
0567 void FileIOStream::seek(taglib_offset_t offset, Position p)
0568 {
0569   if (openFileHandle()) {
0570     m_fileStream->seek(offset, p);
0571   }
0572 }
0573 
0574 void FileIOStream::clear()
0575 {
0576   if (openFileHandle()) {
0577     m_fileStream->clear();
0578   }
0579 }
0580 
0581 taglib_offset_t FileIOStream::tell() const
0582 {
0583   if (openFileHandle()) {
0584     return m_fileStream->tell();
0585   }
0586   return 0;
0587 }
0588 
0589 taglib_offset_t FileIOStream::length()
0590 {
0591   if (openFileHandle()) {
0592     return m_fileStream->length();
0593   }
0594   return 0;
0595 }
0596 
0597 void FileIOStream::truncate(taglib_offset_t length)
0598 {
0599   if (openFileHandle()) {
0600     m_fileStream->truncate(length);
0601   }
0602 }
0603 
0604 TagLib::File* FileIOStream::create(TagLib::IOStream* stream)
0605 {
0606   TagLib::File* file = createFromExtension(stream);
0607   if (file && !file->isValid()) {
0608     delete file;
0609     file = nullptr;
0610   }
0611   if (!file) {
0612     file = createFromContents(stream);
0613   }
0614   return file;
0615 }
0616 
0617 TagLib::File* FileIOStream::createFromExtension(TagLib::IOStream* stream)
0618 {
0619 #ifdef Q_OS_WIN32
0620   TagLib::String fn = stream->name().toString();
0621 #else
0622   TagLib::String fn = stream->name();
0623 #endif
0624   const int extPos = fn.rfind(".");
0625   return extPos != -1
0626       ? createFromExtension(stream, fn.substr(extPos + 1).upper())
0627       : nullptr;
0628 }
0629 
0630 TagLib::File* FileIOStream::createFromExtension(TagLib::IOStream* stream,
0631                                                 const TagLib::String& ext)
0632 {
0633   if (ext == "MP3" || ext == "MP2" || ext == "AAC")
0634 #if TAGLIB_VERSION >= 0x020000
0635     return new TagLib::MPEG::File(stream);
0636 #else
0637     return new TagLib::MPEG::File(stream,
0638                                   TagLib::ID3v2::FrameFactory::instance());
0639 #endif
0640   if (ext == "OGG") {
0641     TagLib::File* file = new TagLib::Vorbis::File(stream);
0642     if (!file->isValid()) {
0643       delete file;
0644       file = new TagLib::Ogg::FLAC::File(stream);
0645     }
0646     return file;
0647   }
0648   if (ext == "OGA") {
0649     TagLib::File* file = new TagLib::Ogg::FLAC::File(stream);
0650     if (!file->isValid()) {
0651       delete file;
0652       file = new TagLib::Vorbis::File(stream);
0653     }
0654     return file;
0655   }
0656   if (ext == "FLAC")
0657 #if TAGLIB_VERSION >= 0x020000
0658     return new TagLib::FLAC::File(stream);
0659 #else
0660     return new TagLib::FLAC::File(stream,
0661                                   TagLib::ID3v2::FrameFactory::instance());
0662 #endif
0663   if (ext == "MPC")
0664     return new TagLib::MPC::File(stream);
0665   if (ext == "WV")
0666     return new TagLib::WavPack::File(stream);
0667   if (ext == "SPX")
0668     return new TagLib::Ogg::Speex::File(stream);
0669   if (ext == "OPUS")
0670     return new TagLib::Ogg::Opus::File(stream);
0671   if (ext == "TTA")
0672     return new TagLib::TrueAudio::File(stream);
0673   if (ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" ||
0674       ext == "M4R" || ext == "MP4" || ext == "3G2" || ext == "M4V" ||
0675       ext == "MP4V")
0676     return new TagLib::MP4::File(stream);
0677   if (ext == "WMA" || ext == "ASF" || ext == "WMV")
0678     return new TagLib::ASF::File(stream);
0679   if (ext == "AIF" || ext == "AIFF")
0680     return new TagLib::RIFF::AIFF::File(stream);
0681   if (ext == "WAV")
0682     return new WavFile(stream);
0683   if (ext == "APE")
0684     return new TagLib::APE::File(stream);
0685   if (ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW")
0686     return new TagLib::Mod::File(stream);
0687   if (ext == "S3M")
0688     return new TagLib::S3M::File(stream);
0689   if (ext == "IT")
0690     return new TagLib::IT::File(stream);
0691   if (ext == "XM")
0692     return new TagLib::XM::File(stream);
0693   if (ext == "DSF")
0694 #if TAGLIB_VERSION >= 0x020000
0695     return new TagLib::DSF::File(stream);
0696 #else
0697     return new DSFFile(stream, TagLib::ID3v2::FrameFactory::instance());
0698 #endif
0699   if (ext == "DFF")
0700 #if TAGLIB_VERSION >= 0x020000
0701     return new TagLib::DSDIFF::File(stream);
0702 #else
0703     return new DSDIFFFile(stream, TagLib::ID3v2::FrameFactory::instance());
0704 #endif
0705   return nullptr;
0706 }
0707 
0708 TagLib::File* FileIOStream::createFromContents(TagLib::IOStream* stream)
0709 {
0710   static const struct ExtensionForMimeType {
0711     const char* mime;
0712     const char* ext;
0713   } extensionForMimeType[] = {
0714     { "application/ogg", "OGG" },
0715     { "application/vnd.ms-asf", "WMA" },
0716     { "audio/aac", "AAC" },
0717     { "audio/flac", "FLAC" },
0718     { "audio/mp4", "MP4" },
0719     { "audio/mpeg", "MP3" },
0720     { "audio/x-aiff", "AIFF" },
0721     { "audio/x-ape", "APE" },
0722     { "audio/x-flac+ogg", "OGG" },
0723     { "audio/x-it", "IT" },
0724     { "audio/x-musepack", "MPC" },
0725     { "audio/x-opus+ogg", "OPUS" },
0726     { "audio/x-s3m", "S3M" },
0727     { "audio/x-speex+ogg", "SPX" },
0728     { "audio/x-tta", "TTA" },
0729     { "audio/x-vorbis+ogg", "OGG" },
0730     { "audio/x-wav", "WAV" },
0731     { "audio/x-wavpack", "WV" },
0732     { "audio/x-xm", "XM" },
0733     { "video/mp4", "MP4" }
0734 };
0735 
0736   static QMap<QString, TagLib::String> mimeExtMap;
0737   if (mimeExtMap.empty()) {
0738     // first time initialization
0739     for (const auto& [mime, ext] : extensionForMimeType) {
0740       mimeExtMap.insert(QString::fromLatin1(mime), ext);
0741     }
0742   }
0743 
0744   stream->seek(0);
0745   TagLib::ByteVector bv = stream->readBlock(4096);
0746   stream->seek(0);
0747   QMimeDatabase mimeDb;
0748   auto mimeType =
0749       mimeDb.mimeTypeForData(QByteArray(bv.data(), static_cast<int>(bv.size())));
0750   if (TagLib::String ext = mimeExtMap.value(mimeType.name()); !ext.isEmpty()) {
0751     return createFromExtension(stream, ext);
0752   }
0753   return nullptr;
0754 }
0755 
0756 void FileIOStream::registerOpenFile(FileIOStream* stream)
0757 {
0758   if (s_openFiles.contains(stream))
0759     return;
0760 
0761   if (int numberOfFilesToClose = s_openFiles.size() - 15;
0762       numberOfFilesToClose > 5) {
0763     for (auto it = s_openFiles.begin(); it != s_openFiles.end(); ++it) { // clazy:exclude=detaching-member
0764       (*it)->closeFileHandle();
0765       if (--numberOfFilesToClose <= 0) {
0766         break;
0767       }
0768     }
0769   }
0770   s_openFiles.append(stream);
0771 }
0772 
0773 /**
0774  * Deregister open file.
0775  *
0776  * @param stream file which is no longer open
0777  */
0778 void FileIOStream::deregisterOpenFile(FileIOStream* stream)
0779 {
0780   s_openFiles.removeAll(stream);
0781 }
0782 
0783 namespace {
0784 
0785 /**
0786  * Data encoding in ID3v1 tags.
0787  */
0788 class TextCodecStringHandler : public TagLib::ID3v1::StringHandler {
0789 public:
0790   /**
0791    * Constructor.
0792    */
0793   TextCodecStringHandler() = default;
0794 
0795   /**
0796    * Destructor.
0797    */
0798   virtual ~TextCodecStringHandler() = default;
0799 
0800   TextCodecStringHandler(const TextCodecStringHandler&) = delete;
0801   TextCodecStringHandler& operator=(const TextCodecStringHandler&) = delete;
0802 
0803   /**
0804    * Decode a string from data.
0805    *
0806    * @param data data to decode
0807    */
0808   TagLib::String parse(const TagLib::ByteVector& data) const override;
0809 
0810   /**
0811    * Encode a byte vector with the data from a string.
0812    *
0813    * @param s string to encode
0814    */
0815   TagLib::ByteVector render(const TagLib::String& s) const override;
0816 
0817 #if QT_VERSION >= 0x060000
0818   /**
0819    * Set string decoder.
0820    * @param encodingName encoding, empty for default behavior (ISO 8859-1)
0821    */
0822   static void setStringDecoder(const QString& encodingName) {
0823     if (auto encoding = QStringConverter::encodingForName(encodingName.toLatin1())) {
0824       s_encoder = QStringEncoder(*encoding);
0825       s_decoder = QStringDecoder(*encoding);
0826     } else {
0827       s_encoder = QStringEncoder();
0828       s_decoder = QStringDecoder();
0829     }
0830   }
0831 #else
0832   /**
0833    * Set text codec.
0834    * @param codec text codec, 0 for default behavior (ISO 8859-1)
0835    */
0836   static void setTextCodec(const QTextCodec* codec) { s_codec = codec; }
0837 #endif
0838 
0839 private:
0840 #if QT_VERSION >= 0x060000
0841   static QStringDecoder s_decoder;
0842   static QStringEncoder s_encoder;
0843 #else
0844   static const QTextCodec* s_codec;
0845 #endif
0846 };
0847 
0848 #if QT_VERSION >= 0x060000
0849 QStringDecoder TextCodecStringHandler::s_decoder;
0850 QStringEncoder TextCodecStringHandler::s_encoder;
0851 #else
0852 const QTextCodec* TextCodecStringHandler::s_codec = nullptr;
0853 #endif
0854 
0855 /**
0856  * Decode a string from data.
0857  *
0858  * @param data data to decode
0859  */
0860 TagLib::String TextCodecStringHandler::parse(const TagLib::ByteVector& data) const
0861 {
0862 #if QT_VERSION >= 0x060000
0863   return s_decoder.isValid()
0864       ? toTString(s_decoder(QByteArray(data.data(), data.size()))).stripWhiteSpace()
0865       : TagLib::String(data, TagLib::String::Latin1).stripWhiteSpace();
0866 #else
0867   return s_codec
0868       ? toTString(s_codec->toUnicode(data.data(), data.size())).stripWhiteSpace()
0869       : TagLib::String(data, TagLib::String::Latin1).stripWhiteSpace();
0870 #endif
0871 }
0872 
0873 /**
0874  * Encode a byte vector with the data from a string.
0875  *
0876  * @param s string to encode
0877  */
0878 TagLib::ByteVector TextCodecStringHandler::render(const TagLib::String& s) const
0879 {
0880 #if QT_VERSION >= 0x060000
0881   if (s_encoder.isValid()) {
0882     QByteArray ba = s_encoder(toQString(s));
0883     return TagLib::ByteVector(ba.data(), ba.size());
0884   } else {
0885     return s.data(TagLib::String::Latin1);
0886   }
0887 #else
0888   if (s_codec) {
0889     QByteArray ba(s_codec->fromUnicode(toQString(s)));
0890     return TagLib::ByteVector(ba.data(), ba.size());
0891   }
0892   return s.data(TagLib::String::Latin1);
0893 #endif
0894 }
0895 
0896 }
0897 
0898 /** Default text encoding */
0899 TagLib::String::Type TagLibFile::s_defaultTextEncoding = TagLib::String::Latin1;
0900 
0901 
0902 /**
0903  * Constructor.
0904  *
0905  * @param idx index in tagged file system model
0906  */
0907 TagLibFile::TagLibFile(const QPersistentModelIndex& idx)
0908   : TaggedFile(idx),
0909     m_tagInformationRead(false), m_fileRead(false),
0910     m_stream(nullptr),
0911     m_id3v2Version(0),
0912     m_activatedFeatures(0), m_duration(0)
0913 {
0914   FOR_TAGLIB_TAGS(tagNr) {
0915     m_hasTag[tagNr] = false;
0916     m_isTagSupported[tagNr] = tagNr == Frame::Tag_2;
0917     m_tag[tagNr] = nullptr;
0918     m_tagType[tagNr] = TT_Unknown;
0919   }
0920 }
0921 
0922 /**
0923  * Destructor.
0924  */
0925 TagLibFile::~TagLibFile()
0926 {
0927   closeFile(true);
0928 }
0929 
0930 /**
0931  * Get key of tagged file format.
0932  * @return "TaglibMetadata".
0933  */
0934 QString TagLibFile::taggedFileKey() const
0935 {
0936   return QLatin1String("TaglibMetadata");
0937 }
0938 
0939 /**
0940  * Get features supported.
0941  * @return bit mask with Feature flags set.
0942  */
0943 int TagLibFile::taggedFileFeatures() const
0944 {
0945   return TF_ID3v11 | TF_ID3v22 |
0946       TF_OggFlac |
0947       TF_OggPictures |
0948       TF_ID3v23 |
0949       TF_ID3v24;
0950 }
0951 
0952 /**
0953  * Get currently active tagged file features.
0954  * @return active tagged file features (TF_ID3v23, TF_ID3v24, or 0).
0955  * @see setActiveTaggedFileFeatures()
0956  */
0957 int TagLibFile::activeTaggedFileFeatures() const
0958 {
0959   return m_activatedFeatures;
0960 }
0961 
0962 /**
0963  * Activate some features provided by the tagged file.
0964  * TagLibFile provides the TF_ID3v23 and TF_ID3v24 features, which determine
0965  * the ID3v2 version used in writeTags() (the overload without id3v2Version).
0966  * If 0 is set, the default behavior applies, i.e. for new files,
0967  * TagConfig::id3v2Version() is used, else the existing version.
0968  *
0969  * @param features TF_ID3v23, TF_ID3v24, or 0
0970  */
0971 void TagLibFile::setActiveTaggedFileFeatures(int features)
0972 {
0973   m_activatedFeatures = features;
0974 }
0975 
0976 /**
0977  * Free resources allocated when calling readTags().
0978  *
0979  * @param force true to force clearing even if the tags are modified
0980  */
0981 void TagLibFile::clearTags(bool force)
0982 {
0983   if (isChanged() && !force)
0984     return;
0985 
0986   bool priorIsTagInformationRead = isTagInformationRead();
0987   closeFile(true);
0988   m_pictures.clear();
0989   m_pictures.setRead(false);
0990   m_tagInformationRead = false;
0991   FOR_TAGLIB_TAGS(tagNr) {
0992     m_hasTag[tagNr] = false;
0993     m_tagFormat[tagNr].clear();
0994     m_tagType[tagNr] = TT_Unknown;
0995   }
0996   FOR_TAGLIB_TAGS(tagNr) {
0997     markTagUnchanged(tagNr);
0998   }
0999   notifyModelDataChanged(priorIsTagInformationRead);
1000 }
1001 
1002 /**
1003  * Read tags from file.
1004  *
1005  * @param force true to force reading even if tags were already read.
1006  */
1007 void TagLibFile::readTags(bool force)
1008 {
1009   bool priorIsTagInformationRead = isTagInformationRead();
1010   QString fileName = currentFilePath();
1011 
1012   if (force || m_fileRef.isNull()) {
1013     delete m_stream;
1014     m_stream = new FileIOStream(fileName);
1015     m_fileRef = TagLib::FileRef(FileIOStream::create(m_stream));
1016     FOR_TAGLIB_TAGS(tagNr) {
1017       m_tag[tagNr] = nullptr;
1018     }
1019     FOR_TAGLIB_TAGS(tagNr) {
1020       markTagUnchanged(tagNr);
1021     }
1022     m_fileRead = true;
1023 
1024     m_pictures.clear();
1025     m_pictures.setRead(false);
1026   }
1027 
1028   if (TagLib::File* file;
1029       !m_fileRef.isNull() && (file = m_fileRef.file()) != nullptr) {
1030     m_fileExtension = QLatin1String(".mp3");
1031     m_isTagSupported[Frame::Tag_1] = false;
1032     if (TagLib::MPEG::File* mpegFile;
1033         (mpegFile = dynamic_cast<TagLib::MPEG::File*>(file)) != nullptr) {
1034       QString ext(fileName.right(4).toLower());
1035       m_fileExtension =
1036           ext == QLatin1String(".aac") || ext == QLatin1String(".mp2")
1037           ? ext : QLatin1String(".mp3");
1038       m_isTagSupported[Frame::Tag_1] = true;
1039       m_isTagSupported[Frame::Tag_3] = true;
1040       if (!m_tag[Frame::Tag_1]) {
1041         m_tag[Frame::Tag_1] = mpegFile->ID3v1Tag();
1042         markTagUnchanged(Frame::Tag_1);
1043       }
1044       if (!m_tag[Frame::Tag_2]) {
1045         TagLib::ID3v2::Tag* id3v2Tag = mpegFile->ID3v2Tag();
1046         setId3v2VersionFromTag(id3v2Tag);
1047         m_tag[Frame::Tag_2] = id3v2Tag;
1048         markTagUnchanged(Frame::Tag_2);
1049       }
1050       if (!m_tag[Frame::Tag_3]) {
1051         m_tag[Frame::Tag_3] = mpegFile->APETag();
1052         markTagUnchanged(Frame::Tag_3);
1053       }
1054     } else if (TagLib::FLAC::File* flacFile;
1055                (flacFile = dynamic_cast<TagLib::FLAC::File*>(file)) != nullptr) {
1056       m_fileExtension = QLatin1String(".flac");
1057       m_isTagSupported[Frame::Tag_1] = true;
1058       m_isTagSupported[Frame::Tag_3] = true;
1059       if (!m_tag[Frame::Tag_1]) {
1060         m_tag[Frame::Tag_1] = flacFile->ID3v1Tag();
1061         markTagUnchanged(Frame::Tag_1);
1062       }
1063       if (!m_tag[Frame::Tag_2]) {
1064         m_tag[Frame::Tag_2] = flacFile->xiphComment();
1065         markTagUnchanged(Frame::Tag_2);
1066       }
1067       if (!m_tag[Frame::Tag_3]) {
1068         m_tag[Frame::Tag_3] = flacFile->ID3v2Tag();
1069         markTagUnchanged(Frame::Tag_3);
1070       }
1071       if (!m_pictures.isRead()) {
1072         const TagLib::List pics(flacFile->pictureList());
1073         int i = 0;
1074         for (auto it = pics.begin(); it != pics.end(); ++it) {
1075           PictureFrame frame;
1076           flacPictureToFrame(*it, frame);
1077           frame.setIndex(Frame::toNegativeIndex(i++));
1078           m_pictures.append(frame);
1079         }
1080         m_pictures.setRead(true);
1081       }
1082 #if TAGLIB_VERSION >= 0x010b00
1083     } else if (TagLib::MPC::File* mpcFile;
1084                (mpcFile = dynamic_cast<TagLib::MPC::File*>(file)) != nullptr) {
1085       m_fileExtension = QLatin1String(".mpc");
1086       m_isTagSupported[Frame::Tag_1] = true;
1087       if (!m_tag[Frame::Tag_1]) {
1088         m_tag[Frame::Tag_1] = mpcFile->ID3v1Tag();
1089         markTagUnchanged(Frame::Tag_1);
1090       }
1091       if (!m_tag[Frame::Tag_2]) {
1092         m_tag[Frame::Tag_2] = mpcFile->APETag();
1093         markTagUnchanged(Frame::Tag_2);
1094       }
1095     } else if (TagLib::WavPack::File* wvFile;
1096                (wvFile = dynamic_cast<TagLib::WavPack::File*>(file)) != nullptr) {
1097       m_fileExtension = QLatin1String(".wv");
1098       m_isTagSupported[Frame::Tag_1] = true;
1099       if (!m_tag[Frame::Tag_1]) {
1100         m_tag[Frame::Tag_1] = wvFile->ID3v1Tag();
1101         markTagUnchanged(Frame::Tag_1);
1102       }
1103       if (!m_tag[Frame::Tag_2]) {
1104         m_tag[Frame::Tag_2] = wvFile->APETag();
1105         markTagUnchanged(Frame::Tag_2);
1106       }
1107 #endif
1108     } else if (TagLib::TrueAudio::File* ttaFile;
1109                (ttaFile = dynamic_cast<TagLib::TrueAudio::File*>(file)) != nullptr) {
1110       m_fileExtension = QLatin1String(".tta");
1111       m_isTagSupported[Frame::Tag_1] = true;
1112       if (!m_tag[Frame::Tag_1]) {
1113         m_tag[Frame::Tag_1] = ttaFile->ID3v1Tag();
1114         markTagUnchanged(Frame::Tag_1);
1115       }
1116       if (!m_tag[Frame::Tag_2]) {
1117         m_tag[Frame::Tag_2] = ttaFile->ID3v2Tag();
1118         markTagUnchanged(Frame::Tag_2);
1119       }
1120     } else if (TagLib::APE::File* apeFile;
1121                (apeFile = dynamic_cast<TagLib::APE::File*>(file)) != nullptr) {
1122       m_fileExtension = QLatin1String(".ape");
1123       m_isTagSupported[Frame::Tag_1] = true;
1124       if (!m_tag[Frame::Tag_1]) {
1125         m_tag[Frame::Tag_1] = apeFile->ID3v1Tag();
1126         markTagUnchanged(Frame::Tag_1);
1127       }
1128       if (!m_tag[Frame::Tag_2]) {
1129         m_tag[Frame::Tag_2] = apeFile->APETag();
1130         markTagUnchanged(Frame::Tag_2);
1131       }
1132     } else if (TagLib::RIFF::WAV::File* wavFile;
1133                (wavFile = dynamic_cast<TagLib::RIFF::WAV::File*>(file)) != nullptr) {
1134       m_fileExtension = QLatin1String(".wav");
1135       m_tag[Frame::Tag_1] = nullptr;
1136       markTagUnchanged(Frame::Tag_1);
1137 #if TAGLIB_VERSION >= 0x010a00
1138       m_isTagSupported[Frame::Tag_3] = true;
1139       if (!m_tag[Frame::Tag_2]) {
1140         TagLib::ID3v2::Tag* id3v2Tag = wavFile->ID3v2Tag();
1141         setId3v2VersionFromTag(id3v2Tag);
1142         m_tag[Frame::Tag_2] = id3v2Tag;
1143         markTagUnchanged(Frame::Tag_2);
1144       }
1145       if (!m_tag[Frame::Tag_3]) {
1146         m_tag[Frame::Tag_3] = wavFile->InfoTag();
1147         markTagUnchanged(Frame::Tag_3);
1148       }
1149 #else
1150       if (!m_tag[Frame::Tag_2]) {
1151         m_tag[Frame::Tag_2] = wavFile->tag();
1152         markTagUnchanged(Frame::Tag_2);
1153       }
1154 #endif
1155 #if TAGLIB_VERSION >= 0x020000
1156     } else if (TagLib::DSF::File* dsfFile;
1157                (dsfFile = dynamic_cast<TagLib::DSF::File*>(file)) != nullptr) {
1158 #else
1159     } else if (DSFFile* dsfFile;
1160                (dsfFile = dynamic_cast<DSFFile*>(file)) != nullptr) {
1161 #endif
1162       m_fileExtension = QLatin1String(".dsf");
1163       m_tag[Frame::Tag_1] = nullptr;
1164       markTagUnchanged(Frame::Tag_1);
1165       if (!m_tag[Frame::Tag_2]) {
1166 #if TAGLIB_VERSION >= 0x020000
1167         TagLib::ID3v2::Tag* id3v2Tag = dsfFile->tag();
1168 #else
1169         TagLib::ID3v2::Tag* id3v2Tag = dsfFile->ID3v2Tag();
1170 #endif
1171         setId3v2VersionFromTag(id3v2Tag);
1172         m_tag[Frame::Tag_2] = id3v2Tag;
1173         markTagUnchanged(Frame::Tag_2);
1174       }
1175 #if TAGLIB_VERSION >= 0x020000
1176     } else if (TagLib::DSDIFF::File* dffFile;
1177                (dffFile = dynamic_cast<TagLib::DSDIFF::File*>(file)) != nullptr) {
1178 #else
1179     } else if (DSDIFFFile* dffFile;
1180                (dffFile = dynamic_cast<DSDIFFFile*>(file)) != nullptr) {
1181 #endif
1182       m_fileExtension = QLatin1String(".dff");
1183       m_tag[Frame::Tag_1] = nullptr;
1184       markTagUnchanged(Frame::Tag_1);
1185       if (!m_tag[Frame::Tag_2]) {
1186         TagLib::ID3v2::Tag* id3v2Tag = dffFile->ID3v2Tag();
1187         setId3v2VersionFromTag(id3v2Tag);
1188         m_tag[Frame::Tag_2] = id3v2Tag;
1189         markTagUnchanged(Frame::Tag_2);
1190       }
1191     } else {
1192       if (dynamic_cast<TagLib::Vorbis::File*>(file) != nullptr) {
1193         m_fileExtension = QLatin1String(".ogg");
1194       } else if (dynamic_cast<TagLib::Ogg::Speex::File*>(file) != nullptr) {
1195         m_fileExtension = QLatin1String(".spx");
1196 #if TAGLIB_VERSION < 0x010b00
1197       } else if (dynamic_cast<TagLib::MPC::File*>(file) != 0) {
1198         m_fileExtension = QLatin1String(".mpc");
1199       } else if (dynamic_cast<TagLib::WavPack::File*>(file) != 0) {
1200         m_fileExtension = QLatin1String(".wv");
1201 #endif
1202       } else if (dynamic_cast<TagLib::MP4::File*>(file) != nullptr) {
1203         m_fileExtension = QLatin1String(".m4a");
1204       } else if (dynamic_cast<TagLib::ASF::File*>(file) != nullptr) {
1205         m_fileExtension = QLatin1String(".wma");
1206       } else if (dynamic_cast<TagLib::RIFF::AIFF::File*>(file) != nullptr) {
1207         m_fileExtension = QLatin1String(".aiff");
1208       } else if (dynamic_cast<TagLib::Mod::File*>(file) != nullptr) {
1209         m_fileExtension = QLatin1String(".mod");
1210       } else if (dynamic_cast<TagLib::S3M::File*>(file) != nullptr) {
1211         m_fileExtension = QLatin1String(".s3m");
1212       } else if (dynamic_cast<TagLib::IT::File*>(file) != nullptr) {
1213         m_fileExtension = QLatin1String(".it");
1214       } else if (dynamic_cast<TagLib::XM::File*>(file) != nullptr) {
1215         m_fileExtension = QLatin1String(".xm");
1216       } else if (dynamic_cast<TagLib::Ogg::Opus::File*>(file) != nullptr) {
1217         m_fileExtension = QLatin1String(".opus");
1218       }
1219       m_tag[Frame::Tag_1] = nullptr;
1220       markTagUnchanged(Frame::Tag_1);
1221       if (!m_tag[Frame::Tag_2]) {
1222         m_tag[Frame::Tag_2] = m_fileRef.tag();
1223         markTagUnchanged(Frame::Tag_2);
1224       }
1225       if (!m_pictures.isRead()) {
1226 #if TAGLIB_VERSION >= 0x010b00
1227         if (auto xiphComment =
1228             dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[Frame::Tag_2])) {
1229           const TagLib::List pics(xiphComment->pictureList());
1230           int i = 0;
1231           for (auto it = pics.begin(); it != pics.end(); ++it) {
1232             PictureFrame frame;
1233             flacPictureToFrame(*it, frame);
1234             frame.setIndex(Frame::toNegativeIndex(i++));
1235             m_pictures.append(frame);
1236           }
1237           m_pictures.setRead(true);
1238         } else
1239 #endif
1240         if (auto mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[Frame::Tag_2])) {
1241 #if TAGLIB_VERSION >= 0x010a00
1242           const auto& itemMap = mp4Tag->itemMap();
1243           auto it = itemMap.find("covr");
1244           const TagLib::MP4::CoverArtList pics = it != itemMap.end()
1245               ? it->second.toCoverArtList() : TagLib::MP4::CoverArtList();
1246 #else
1247           const TagLib::MP4::CoverArtList pics(mp4Tag->itemListMap()["covr"].toCoverArtList());
1248 #endif
1249           int i = 0;
1250           for (auto pit = pics.begin(); pit != pics.end(); ++pit) {
1251             const TagLib::MP4::CoverArt& coverArt = *pit;
1252             TagLib::ByteVector bv = coverArt.data();
1253             QString mimeType, imgFormat;
1254             switch (coverArt.format()) {
1255             case TagLib::MP4::CoverArt::PNG:
1256               mimeType = QLatin1String("image/png");
1257               imgFormat = QLatin1String("PNG");
1258               break;
1259             case TagLib::MP4::CoverArt::BMP:
1260               mimeType = QLatin1String("image/bmp");
1261               imgFormat = QLatin1String("BMP");
1262               break;
1263             case TagLib::MP4::CoverArt::GIF:
1264               mimeType = QLatin1String("image/gif");
1265               imgFormat = QLatin1String("GIF");
1266               break;
1267             case TagLib::MP4::CoverArt::JPEG:
1268             case TagLib::MP4::CoverArt::Unknown:
1269             default:
1270               mimeType = QLatin1String("image/jpeg");
1271               imgFormat = QLatin1String("JPG");
1272             }
1273             PictureFrame frame(
1274                   QByteArray(bv.data(), static_cast<int>(bv.size())),
1275                   QLatin1String(""), PictureFrame::PT_CoverFront, mimeType,
1276                   Frame::TE_ISO8859_1, imgFormat);
1277             frame.setIndex(Frame::toNegativeIndex(i++));
1278             frame.setExtendedType(Frame::ExtendedType(Frame::FT_Picture,
1279                                                       QLatin1String("covr")));
1280             m_pictures.append(frame);
1281           }
1282           m_pictures.setRead(true);
1283         }
1284       }
1285     }
1286   }
1287 
1288   // Cache information, so that it is available after file is closed.
1289   m_tagInformationRead = true;
1290   FOR_TAGLIB_TAGS(tagNr) {
1291     m_hasTag[tagNr] = m_tag[tagNr] && !m_tag[tagNr]->isEmpty();
1292     m_tagFormat[tagNr] = getTagFormat(m_tag[tagNr], m_tagType[tagNr]);
1293   }
1294   readAudioProperties();
1295 
1296   if (force) {
1297     setFilename(currentFilename());
1298   }
1299 
1300   closeFile(false);
1301 
1302   notifyModelDataChanged(priorIsTagInformationRead);
1303 }
1304 
1305 /**
1306  * Close file handle.
1307  * TagLib keeps the file handle open until the FileRef is destroyed.
1308  * This causes problems when the operating system has a limited number of
1309  * open file handles. This method closes the file by assigning a new file
1310  * reference. Note that this will also invalidate the tag pointers.
1311  * The file is only closed if there are no unsaved tag changes or if the
1312  * @a force parameter is set.
1313  *
1314  * @param force true to close the file even if tags are changed
1315  */
1316 void TagLibFile::closeFile(bool force)
1317 {
1318   if (force) {
1319     m_fileRef = TagLib::FileRef();
1320     delete m_stream;
1321     m_stream = nullptr;
1322     FOR_TAGLIB_TAGS(tagNr) {
1323       m_tag[tagNr] = nullptr;
1324     }
1325     m_fileRead = false;
1326   } else if (m_stream) {
1327     m_stream->closeFileHandle();
1328   }
1329 }
1330 
1331 /**
1332  * Make sure that file is open.
1333  * This method should be called before accessing m_fileRef, m_tag.
1334  *
1335  * @param force true to force reopening of file even if it is already open
1336  */
1337 void TagLibFile::makeFileOpen(bool force) const
1338 {
1339   if (!m_fileRead || force) {
1340     const_cast<TagLibFile*>(this)->readTags(force);
1341   }
1342 }
1343 
1344 /**
1345  * Write tags to file and rename it if necessary.
1346  *
1347  * @param force    true to force writing even if file was not changed.
1348  * @param renamed  will be set to true if the file was renamed,
1349  *                 i.e. the file name is no longer valid, else *renamed
1350  *                 is left unchanged
1351  * @param preserve true to preserve file time stamps
1352  *
1353  * @return true if ok, false if the file could not be written or renamed.
1354  */
1355 bool TagLibFile::writeTags(bool force, bool* renamed, bool preserve)
1356 {
1357   int id3v2Version;
1358   if (m_activatedFeatures & TF_ID3v24)
1359     id3v2Version = 4;
1360   else if (m_activatedFeatures & TF_ID3v23)
1361     id3v2Version = 3;
1362   else
1363     id3v2Version = 0;
1364   return writeTags(force, renamed, preserve, id3v2Version);
1365 }
1366 
1367 /**
1368  * Write tags to file and rename it if necessary.
1369  *
1370  * @param force    true to force writing even if file was not changed.
1371  * @param renamed  will be set to true if the file was renamed,
1372  *                 i.e. the file name is no longer valid, else *renamed
1373  *                 is left unchanged
1374  * @param preserve true to preserve file time stamps
1375  * @param id3v2Version ID3v2 version to use, 0 to use existing or preferred,
1376  *                     3 to force ID3v2.3.0, 4 to force ID3v2.4.0. Is ignored
1377  *                     if TagLib version is less than 1.8.0.
1378  *
1379  * @return true if ok, false if the file could not be written or renamed.
1380  */
1381 bool TagLibFile::writeTags(bool force, bool* renamed, bool preserve,
1382                            int id3v2Version)
1383 {
1384   QString fnStr(currentFilePath());
1385   if (isChanged() && !QFileInfo(fnStr).isWritable()) {
1386     closeFile(false);
1387     revertChangedFilename();
1388     return false;
1389   }
1390 
1391   // store time stamp if it has to be preserved
1392   quint64 actime = 0, modtime = 0;
1393   if (preserve) {
1394     getFileTimeStamps(fnStr, actime, modtime);
1395   }
1396 
1397   bool fileChanged = false;
1398   if (TagLib::File* file;
1399       !m_fileRef.isNull() && (file = m_fileRef.file()) != nullptr) {
1400     if (m_stream) {
1401 #ifndef Q_OS_WIN32
1402       QString fileName = QFile::decodeName(m_stream->name());
1403 #else
1404       QString fileName = toQString(m_stream->name().toString());
1405 #endif
1406       if (fnStr != fileName) {
1407         qDebug("TagLibFile: Fix file name mismatch, should be '%s', not '%s'",
1408                qPrintable(fnStr), qPrintable(fileName));
1409         m_stream->setName(fnStr);
1410       }
1411     }
1412     if (auto mpegFile = dynamic_cast<TagLib::MPEG::File*>(file)) {
1413       static constexpr int tagTypes[NUM_TAGS] = {
1414         TagLib::MPEG::File::ID3v1, TagLib::MPEG::File::ID3v2,
1415         TagLib::MPEG::File::APE
1416       };
1417       int saveMask = 0;
1418       // We iterate through the tags in reverse order to work around
1419       // a TagLib bug: When stripping the APE tag after the ID3v1 tag,
1420       // the ID3v1 tag is not removed.
1421       FOR_TAGLIB_TAGS_REVERSE(tagNr) {
1422         if (m_tag[tagNr] && (force || isTagChanged(tagNr))) {
1423           if (m_tag[tagNr]->isEmpty()) {
1424             mpegFile->strip(tagTypes[tagNr]);
1425             fileChanged = true;
1426             m_tag[tagNr] = nullptr;
1427             markTagUnchanged(tagNr);
1428           } else {
1429             saveMask |= tagTypes[tagNr];
1430           }
1431         }
1432       }
1433       if (saveMask != 0) {
1434         setId3v2VersionOrDefault(id3v2Version);
1435         if (
1436 #if TAGLIB_VERSION >= 0x010c00
1437             mpegFile->save(
1438               saveMask, TagLib::File::StripNone,
1439               m_id3v2Version == 4 ? TagLib::ID3v2::v4 : TagLib::ID3v2::v3,
1440               TagLib::File::DoNotDuplicate)
1441 #else
1442             mpegFile->save(saveMask, false, m_id3v2Version, false)
1443 #endif
1444             ) {
1445           fileChanged = true;
1446           FOR_TAGLIB_TAGS(tagNr) {
1447             if (saveMask & tagTypes[tagNr]) {
1448               markTagUnchanged(tagNr);
1449             }
1450           }
1451         }
1452       }
1453     } else {
1454       bool needsSave = false;
1455       FOR_TAGLIB_TAGS(tagNr) {
1456         if (m_tag[tagNr] && (force || isTagChanged(tagNr))) {
1457           needsSave = true;
1458           break;
1459         }
1460       }
1461       if (needsSave) {
1462         if (auto ttaFile =
1463             dynamic_cast<TagLib::TrueAudio::File*>(file)) {
1464           static constexpr int tagTypes[NUM_TAGS] = {
1465             TagLib::MPEG::File::ID3v1, TagLib::MPEG::File::ID3v2,
1466             TagLib::MPEG::File::NoTags
1467           };
1468           FOR_TAGLIB_TAGS(tagNr) {
1469             if (m_tag[tagNr] && (force || isTagChanged(tagNr)) && m_tag[tagNr]->isEmpty()) {
1470               ttaFile->strip(tagTypes[tagNr]);
1471               fileChanged = true;
1472               m_tag[tagNr] = nullptr;
1473               markTagUnchanged(tagNr);
1474             }
1475           }
1476         } else if (auto mpcFile =
1477                    dynamic_cast<TagLib::MPC::File*>(file)) {
1478 #if TAGLIB_VERSION >= 0x010b00
1479           static constexpr int tagTypes[NUM_TAGS] = {
1480             TagLib::MPC::File::ID3v1 | TagLib::MPC::File::ID3v2,
1481             TagLib::MPC::File::APE, TagLib::MPC::File::NoTags
1482           };
1483           FOR_TAGLIB_TAGS(tagNr) {
1484             if (m_tag[tagNr] && (force || isTagChanged(tagNr)) &&
1485                 m_tag[tagNr]->isEmpty()) {
1486               mpcFile->strip(tagTypes[tagNr]);
1487               fileChanged = true;
1488               m_tag[tagNr] = nullptr;
1489               markTagUnchanged(tagNr);
1490             }
1491           }
1492 #else
1493           // it does not work if there is also an ID3 tag (bug in TagLib)
1494           mpcFile->remove(TagLib::MPC::File::ID3v1 | TagLib::MPC::File::ID3v2);
1495           fileChanged = true;
1496 #endif
1497         } else if (auto wvFile =
1498                    dynamic_cast<TagLib::WavPack::File*>(file)) {
1499 #if TAGLIB_VERSION >= 0x010b00
1500           static constexpr int tagTypes[NUM_TAGS] = {
1501             TagLib::WavPack::File::ID3v1, TagLib::WavPack::File::APE,
1502             TagLib::WavPack::File::NoTags
1503           };
1504           FOR_TAGLIB_TAGS(tagNr) {
1505             if (m_tag[tagNr] && (force || isTagChanged(tagNr)) &&
1506                 m_tag[tagNr]->isEmpty()) {
1507               wvFile->strip(tagTypes[tagNr]);
1508               fileChanged = true;
1509               m_tag[tagNr] = nullptr;
1510               markTagUnchanged(tagNr);
1511             }
1512           }
1513 #else
1514           // it does not work if there is also an ID3 tag (bug in TagLib)
1515           wvFile->strip(TagLib::WavPack::File::ID3v1);
1516           fileChanged = true;
1517 #endif
1518         }
1519         else if (auto apeFile =
1520                  dynamic_cast<TagLib::APE::File*>(file)) {
1521           static constexpr int tagTypes[NUM_TAGS] = {
1522             TagLib::MPEG::File::ID3v1, TagLib::APE::File::APE,
1523             TagLib::APE::File::NoTags
1524           };
1525           FOR_TAGLIB_TAGS(tagNr) {
1526             if (m_tag[tagNr] && (force || isTagChanged(tagNr)) && m_tag[tagNr]->isEmpty()) {
1527               apeFile->strip(tagTypes[tagNr]);
1528               fileChanged = true;
1529               m_tag[tagNr] = nullptr;
1530               markTagUnchanged(tagNr);
1531             }
1532           }
1533         }
1534         else if (auto flacFile =
1535             dynamic_cast<TagLib::FLAC::File*>(file)) {
1536 #if TAGLIB_VERSION >= 0x010b00
1537           static constexpr int tagTypes[NUM_TAGS] = {
1538             TagLib::FLAC::File::ID3v1, TagLib::FLAC::File::XiphComment,
1539             TagLib::FLAC::File::ID3v2
1540           };
1541           FOR_TAGLIB_TAGS(tagNr) {
1542             if (m_tag[tagNr] && (force || isTagChanged(tagNr)) && m_tag[tagNr]->isEmpty()) {
1543               flacFile->strip(tagTypes[tagNr]);
1544               fileChanged = true;
1545               m_tag[tagNr] = nullptr;
1546               markTagUnchanged(tagNr);
1547             }
1548           }
1549 #endif
1550           flacFile->removePictures();
1551           const auto frames = m_pictures;
1552           for (const Frame& frame : frames) {
1553             auto pic = new TagLib::FLAC::Picture;
1554             frameToFlacPicture(frame, pic);
1555             flacFile->addPicture(pic);
1556           }
1557         }
1558         else if (auto wavFile = dynamic_cast<WavFile*>(file)) {
1559           static constexpr TagLib::RIFF::WAV::File::TagTypes tagTypes[NUM_TAGS] = {
1560             TagLib::RIFF::WAV::File::NoTags, TagLib::RIFF::WAV::File::ID3v2,
1561 #if TAGLIB_VERSION >= 0x010a00
1562             TagLib::RIFF::WAV::File::Info
1563 #else
1564             TagLib::RIFF::WAV::File::NoTags
1565 #endif
1566           };
1567           int saveTags = 0;
1568           FOR_TAGLIB_TAGS(tagNr) {
1569             if (m_tag[tagNr] && (force || isTagChanged(tagNr)) &&
1570                 m_tag[tagNr]->isEmpty()) {
1571               m_tag[tagNr] = nullptr;
1572             } else {
1573               saveTags |= tagTypes[tagNr];
1574             }
1575           }
1576           setId3v2VersionOrDefault(id3v2Version);
1577           if (
1578 #if TAGLIB_VERSION >= 0x010c00
1579               wavFile->save(
1580                 static_cast<TagLib::RIFF::WAV::File::TagTypes>(saveTags),
1581                 TagLib::File::StripOthers,
1582                 m_id3v2Version == 4 ? TagLib::ID3v2::v4 : TagLib::ID3v2::v3)
1583 #else
1584               wavFile->save(static_cast<TagLib::RIFF::WAV::File::TagTypes>(
1585                               saveTags), true, m_id3v2Version)
1586 #endif
1587               ) {
1588             if (TagConfig::instance().lowercaseId3RiffChunk()) {
1589               wavFile->changeToLowercaseId3Chunk();
1590             }
1591             fileChanged = true;
1592             FOR_TAGLIB_TAGS(tagNr) {
1593               markTagUnchanged(tagNr);
1594             }
1595             needsSave = false;
1596           }
1597         }
1598 #if TAGLIB_VERSION >= 0x020000
1599         else if (auto dsfFile = dynamic_cast<TagLib::DSF::File*>(file)) {
1600           setId3v2VersionOrDefault(id3v2Version);
1601           if (dsfFile->save(m_id3v2Version == 4 ? TagLib::ID3v2::v4
1602                                                 : TagLib::ID3v2::v3)) {
1603 #else
1604         else if (auto dsfFile = dynamic_cast<DSFFile*>(file)) {
1605           setId3v2VersionOrDefault(id3v2Version);
1606           if (dsfFile->save(m_id3v2Version)) {
1607 #endif
1608             fileChanged = true;
1609             FOR_TAGLIB_TAGS(tagNr) {
1610               markTagUnchanged(tagNr);
1611             }
1612             needsSave = false;
1613           }
1614         }
1615 #if TAGLIB_VERSION >= 0x020000
1616         else if (auto dffFile = dynamic_cast<TagLib::DSDIFF::File*>(file)) {
1617           int saveMask = 0;
1618           if (m_tag[Frame::Tag_2] && (force || isTagChanged(Frame::Tag_2))) {
1619             if (m_tag[Frame::Tag_2]->isEmpty()) {
1620               dffFile->strip(TagLib::DSDIFF::File::ID3v2);
1621               fileChanged = true;
1622               m_tag[Frame::Tag_2] = nullptr;
1623               markTagUnchanged(Frame::Tag_2);
1624               needsSave = false;
1625             } else {
1626               saveMask = TagLib::DSDIFF::File::ID3v2;
1627             }
1628           }
1629           setId3v2VersionOrDefault(id3v2Version);
1630           if (saveMask != 0 && dffFile->save(saveMask,
1631                             TagLib::File::StripNone,
1632                             m_id3v2Version == 4 ? TagLib::ID3v2::v4 : TagLib::ID3v2::v3)) {
1633 #else
1634         else if (auto dffFile = dynamic_cast<DSDIFFFile*>(file)) {
1635           setId3v2VersionOrDefault(id3v2Version);
1636           if (dffFile->save(m_id3v2Version)) {
1637 #endif
1638             fileChanged = true;
1639             FOR_TAGLIB_TAGS(tagNr) {
1640               markTagUnchanged(tagNr);
1641             }
1642             needsSave = false;
1643           }
1644         }
1645 #if TAGLIB_VERSION >= 0x010b00
1646         else if (auto xiphComment =
1647                  dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[Frame::Tag_2])) {
1648           xiphComment->removeAllPictures();
1649           const auto frames = m_pictures;
1650           for (const Frame& frame : frames) {
1651             auto pic = new TagLib::FLAC::Picture;
1652             frameToFlacPicture(frame, pic);
1653             xiphComment->addPicture(pic);
1654           }
1655         }
1656 #endif
1657         else if (auto mp4Tag =
1658                  dynamic_cast<TagLib::MP4::Tag*>(m_tag[Frame::Tag_2])) {
1659           if (!m_pictures.isEmpty()) {
1660             TagLib::MP4::CoverArtList coverArtList;
1661             const auto frames = m_pictures;
1662             for (const Frame& frame : frames) {
1663               QByteArray ba;
1664               TagLib::MP4::CoverArt::Format format = TagLib::MP4::CoverArt::JPEG;
1665               if (PictureFrame::getData(frame, ba)) {
1666                 if (QString mimeType;
1667                     PictureFrame::getMimeType(frame, mimeType)) {
1668                   if (mimeType == QLatin1String("image/png")) {
1669                     format = TagLib::MP4::CoverArt::PNG;
1670                   } else if (mimeType == QLatin1String("image/bmp")) {
1671                     format = TagLib::MP4::CoverArt::BMP;
1672                   } else if (mimeType == QLatin1String("image/gif")) {
1673                     format = TagLib::MP4::CoverArt::GIF;
1674                   }
1675                 }
1676               }
1677               coverArtList.append(TagLib::MP4::CoverArt(
1678                           format,
1679                           TagLib::ByteVector(
1680                             ba.data(), static_cast<unsigned int>(ba.size()))));
1681             }
1682 #if TAGLIB_VERSION >= 0x010a00
1683             mp4Tag->setItem("covr", coverArtList);
1684 #else
1685             mp4Tag->itemListMap()["covr"] = coverArtList;
1686 #endif
1687           } else {
1688 #if TAGLIB_VERSION >= 0x010a00
1689             mp4Tag->removeItem("covr");
1690 #else
1691             mp4Tag->itemListMap().erase("covr");
1692 #endif
1693           }
1694 #if TAGLIB_VERSION >= 0x010d00
1695           if (TagLib::MP4::File* mp4File;
1696               (force || isTagChanged(Frame::Tag_2)) && mp4Tag->isEmpty() &&
1697               (mp4File = dynamic_cast<TagLib::MP4::File*>(file)) != nullptr) {
1698             mp4File->strip();
1699             fileChanged = true;
1700             m_tag[Frame::Tag_2] = nullptr;
1701             markTagUnchanged(Frame::Tag_2);
1702             needsSave = false;
1703           }
1704 #endif
1705         }
1706         if (needsSave && m_fileRef.save()) {
1707           fileChanged = true;
1708           FOR_TAGLIB_TAGS(tagNr) {
1709             markTagUnchanged(tagNr);
1710           }
1711         }
1712       }
1713     }
1714   }
1715 
1716   // If the file was changed, make sure it is written to disk.
1717   // This is done when the file is closed. Later the file is opened again.
1718   // If the file is not properly closed, doubled tags can be
1719   // written if the file is finally closed!
1720   // This can be reproduced with an untagged MP3 file, then add
1721   // an ID3v2 title, save, add an ID3v2 artist, save, reload
1722   // => double ID3v2 tags.
1723   // On Windows it is necessary to close the file before renaming it,
1724   // so it is done even if the file is not changed.
1725 #ifndef Q_OS_WIN32
1726   closeFile(fileChanged);
1727 #else
1728   closeFile(true);
1729 #endif
1730 
1731   // restore time stamp
1732   if (actime || modtime) {
1733     setFileTimeStamps(fnStr, actime, modtime);
1734   }
1735 
1736   if (isFilenameChanged()) {
1737     if (!renameFile()) {
1738       return false;
1739     }
1740     markFilenameUnchanged();
1741     *renamed = true;
1742   }
1743 
1744 #ifndef Q_OS_WIN32
1745   if (fileChanged)
1746 #endif
1747     makeFileOpen(true);
1748   return true;
1749 }
1750 
1751 namespace {
1752 
1753 /**
1754  * Get a genre string from a string which can contain the genre itself,
1755  * or only the genre number or the genre number in parenthesis.
1756  *
1757  * @param str genre string
1758  *
1759  * @return genre.
1760  */
1761 QString getGenreString(const TagLib::String& str)
1762 {
1763 #if TAGLIB_VERSION < 0x010b01
1764   if (str.isNull()) {
1765     return QLatin1String("");
1766   }
1767 #endif
1768   QString qs = toQString(str);
1769   int n = 0xff;
1770   bool ok = false;
1771   if (int cpPos = 0;
1772       !qs.isEmpty() && qs[0] == QLatin1Char('(') &&
1773       (cpPos = qs.indexOf(QLatin1Char(')'), 2)) > 1) {
1774 #if QT_VERSION >= 0x060000
1775     n = qs.mid(1, cpPos - 1).toInt(&ok);
1776 #else
1777     n = qs.midRef(1, cpPos - 1).toInt(&ok);
1778 #endif
1779     if (!ok || n > 0xff) {
1780       n = 0xff;
1781     }
1782     return QString::fromLatin1(Genres::getName(n));
1783   }
1784   if ((n = qs.toInt(&ok)) >= 0 && n <= 0xff && ok) {
1785     return QString::fromLatin1(Genres::getName(n));
1786   }
1787   return qs;
1788 }
1789 
1790 }
1791 
1792 /**
1793  * Create tag if it does not already exist so that it can be set.
1794  *
1795  * @return true if tag can be set.
1796  */
1797 bool TagLibFile::makeTagSettable(Frame::TagNumber tagNr)
1798 {
1799   if (tagNr >= NUM_TAGS)
1800     return false;
1801 
1802   makeFileOpen();
1803   if (!m_tag[tagNr]) {
1804     if (TagLib::File* file;
1805         !m_fileRef.isNull() && (file = m_fileRef.file()) != nullptr) {
1806       TagLib::MPEG::File* mpegFile;
1807       TagLib::FLAC::File* flacFile;
1808       TagLib::MPC::File* mpcFile;
1809       TagLib::WavPack::File* wvFile;
1810       TagLib::TrueAudio::File* ttaFile;
1811       TagLib::APE::File* apeFile;
1812       if (tagNr == Frame::Tag_1) {
1813         if ((mpegFile = dynamic_cast<TagLib::MPEG::File*>(file)) != nullptr) {
1814           m_tag[tagNr] = mpegFile->ID3v1Tag(true);
1815         } else if ((flacFile = dynamic_cast<TagLib::FLAC::File*>(file)) != nullptr) {
1816           m_tag[tagNr] = flacFile->ID3v1Tag(true);
1817 #if TAGLIB_VERSION >= 0x010b00
1818         } else if ((mpcFile = dynamic_cast<TagLib::MPC::File*>(file)) != nullptr) {
1819           m_tag[tagNr] = mpcFile->ID3v1Tag(true);
1820         } else if ((wvFile = dynamic_cast<TagLib::WavPack::File*>(file)) != nullptr) {
1821           m_tag[tagNr] = wvFile->ID3v1Tag(true);
1822 #endif
1823         } else if ((ttaFile = dynamic_cast<TagLib::TrueAudio::File*>(file)) != nullptr) {
1824           m_tag[tagNr] = ttaFile->ID3v1Tag(true);
1825         } else if ((apeFile = dynamic_cast<TagLib::APE::File*>(file)) != nullptr) {
1826           m_tag[tagNr] = apeFile->ID3v1Tag(true);
1827         }
1828       } else if (tagNr == Frame::Tag_2) {
1829         if ((mpegFile = dynamic_cast<TagLib::MPEG::File*>(file)) != nullptr) {
1830           m_tag[tagNr] = mpegFile->ID3v2Tag(true);
1831         } else if ((flacFile = dynamic_cast<TagLib::FLAC::File*>(file)) != nullptr) {
1832           m_tag[tagNr] = flacFile->xiphComment(true);
1833         } else if ((mpcFile = dynamic_cast<TagLib::MPC::File*>(file)) != nullptr) {
1834           m_tag[tagNr] = mpcFile->APETag(true);
1835         } else if ((wvFile = dynamic_cast<TagLib::WavPack::File*>(file)) != nullptr) {
1836           m_tag[tagNr] = wvFile->APETag(true);
1837         } else if ((ttaFile = dynamic_cast<TagLib::TrueAudio::File*>(file)) != nullptr) {
1838           m_tag[tagNr] = ttaFile->ID3v2Tag(true);
1839         } else if ((apeFile = dynamic_cast<TagLib::APE::File*>(file)) != nullptr) {
1840           m_tag[tagNr] = apeFile->APETag(true);
1841         } else if (auto wavFile =
1842                    dynamic_cast<TagLib::RIFF::WAV::File*>(file)) {
1843           m_tag[tagNr] = wavFile->ID3v2Tag();
1844         }
1845       } else if (tagNr == Frame::Tag_3) {
1846         if ((mpegFile = dynamic_cast<TagLib::MPEG::File*>(file)) != nullptr) {
1847           m_tag[tagNr] = mpegFile->APETag(true);
1848         } else if ((flacFile = dynamic_cast<TagLib::FLAC::File*>(file)) != nullptr) {
1849           m_tag[tagNr] = flacFile->ID3v2Tag(true);
1850 #if TAGLIB_VERSION >= 0x010a00
1851         } else if (auto wavFile =
1852                    dynamic_cast<TagLib::RIFF::WAV::File*>(file)) {
1853           m_tag[tagNr] = wavFile->InfoTag();
1854 #endif
1855         }
1856       }
1857     }
1858   }
1859   return m_tag[tagNr] != nullptr;
1860 }
1861 
1862 namespace {
1863 
1864 /**
1865  * Check if string needs Unicode encoding.
1866  *
1867  * @return true if Unicode needed,
1868  *         false if Latin-1 sufficient.
1869  */
1870 bool needsUnicode(const QString& qstr)
1871 {
1872   bool result = false;
1873   uint unicodeSize = qstr.length();
1874   const QChar* qcarray = qstr.unicode();
1875   for (uint i = 0; i < unicodeSize; ++i) {
1876     if (char ch = qcarray[i].toLatin1();
1877         ch == 0 || (ch & 0x80) != 0) {
1878       result = true;
1879       break;
1880     }
1881   }
1882   return result;
1883 }
1884 
1885 /**
1886  * Get the configured text encoding.
1887  *
1888  * @param unicode true if unicode is required
1889  *
1890  * @return text encoding.
1891  */
1892 TagLib::String::Type getTextEncodingConfig(bool unicode)
1893 {
1894   TagLib::String::Type enc = TagLibFile::getDefaultTextEncoding();
1895   if (unicode && enc == TagLib::String::Latin1) {
1896     enc = TagLib::String::UTF8;
1897   }
1898   return enc;
1899 }
1900 
1901 /**
1902  * Remove the first COMM frame with an empty description.
1903  *
1904  * @param id3v2Tag ID3v2 tag
1905  */
1906 void removeCommentFrame(TagLib::ID3v2::Tag* id3v2Tag)
1907 {
1908   const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList("COMM");
1909   for (auto it = frameList.begin();
1910        it != frameList.end();
1911        ++it) {
1912     if (auto id3Frame =
1913           dynamic_cast<TagLib::ID3v2::CommentsFrame*>(*it);
1914         id3Frame && id3Frame->description().isEmpty()) {
1915       id3v2Tag->removeFrame(id3Frame, true);
1916       break;
1917     }
1918   }
1919 }
1920 
1921 void addTagLibFrame(TagLib::ID3v2::Tag* id3v2Tag, TagLib::ID3v2::Frame* frame)
1922 {
1923 #ifdef Q_OS_WIN32
1924   // freed in Windows DLL => must be allocated in the same DLL
1925 #if TAGLIB_VERSION >= 0x020000
1926   TagLib::ID3v2::Header tagHeader;
1927   tagHeader.setMajorVersion(4);
1928   TagLib::ID3v2::Frame* dllAllocatedFrame =
1929       TagLib::ID3v2::FrameFactory::instance()->createFrame(frame->render(),
1930                                                            &tagHeader);
1931 #else
1932   TagLib::ID3v2::Frame* dllAllocatedFrame =
1933       TagLib::ID3v2::FrameFactory::instance()->createFrame(frame->render());
1934 #endif
1935   if (dllAllocatedFrame) {
1936     id3v2Tag->addFrame(dllAllocatedFrame);
1937   }
1938   delete frame;
1939 #else
1940   id3v2Tag->addFrame(frame);
1941 #endif
1942 }
1943 
1944 /**
1945  * Write a Unicode field if the tag is ID3v2 and Latin-1 is not sufficient.
1946  *
1947  * @param tag     tag
1948  * @param qstr    text as QString
1949  * @param tstr    text as TagLib::String
1950  * @param frameId ID3v2 frame ID
1951  *
1952  * @return true if an ID3v2 Unicode field was written.
1953  */
1954 bool setId3v2Unicode(TagLib::Tag* tag, const QString& qstr,
1955                      const TagLib::String& tstr, const char* frameId)
1956 {
1957   if (TagLib::ID3v2::Tag* id3v2Tag;
1958       tag && (id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(tag)) != nullptr) {
1959     // first check if this string needs to be stored as unicode
1960     TagLib::String::Type enc = getTextEncodingConfig(needsUnicode(qstr));
1961     if (TagLib::ByteVector id(frameId);
1962         enc != TagLib::String::Latin1 || id == "COMM" || id == "TDRC") {
1963       if (id == "COMM") {
1964         removeCommentFrame(id3v2Tag);
1965       } else {
1966         id3v2Tag->removeFrames(id);
1967       }
1968       if (!tstr.isEmpty()) {
1969         TagLib::ID3v2::Frame* frame;
1970         if (frameId[0] != 'C') {
1971           frame = new TagLib::ID3v2::TextIdentificationFrame(id, enc);
1972         } else {
1973           auto commFrame =
1974               new TagLib::ID3v2::CommentsFrame(enc);
1975           frame = commFrame;
1976           commFrame->setLanguage("eng"); // for compatibility with iTunes
1977         }
1978         frame->setText(tstr);
1979         addTagLibFrame(id3v2Tag, frame);
1980       }
1981       return true;
1982     }
1983   }
1984   return false;
1985 }
1986 
1987 }
1988 
1989 /**
1990  * Check if tag information has already been read.
1991  *
1992  * @return true if information is available,
1993  *         false if the tags have not been read yet, in which case
1994  *         hasTag() does not return meaningful information.
1995  */
1996 bool TagLibFile::isTagInformationRead() const
1997 {
1998   return m_tagInformationRead;
1999 }
2000 
2001 /**
2002  * Check if tags are supported by the format of this file.
2003  *
2004  * @param tagNr tag number
2005  * @return true.
2006  */
2007 bool TagLibFile::isTagSupported(Frame::TagNumber tagNr) const
2008 {
2009   return tagNr < NUM_TAGS ? m_isTagSupported[tagNr] : false;
2010 }
2011 
2012 /**
2013  * Check if file has a tag.
2014  *
2015  * @param tagNr tag number
2016  * @return true if tag is available.
2017  * @see isTagInformationRead()
2018  */
2019 bool TagLibFile::hasTag(Frame::TagNumber tagNr) const
2020 {
2021   return tagNr < NUM_TAGS ? m_hasTag[tagNr] : false;
2022 }
2023 
2024 /**
2025  * Get technical detail information.
2026  *
2027  * @param info the detail information is returned here
2028  */
2029 void TagLibFile::getDetailInfo(DetailInfo& info) const
2030 {
2031   info = m_detailInfo;
2032 }
2033 
2034 /**
2035  * Cache technical detail information.
2036  */
2037 void TagLibFile::readAudioProperties()
2038 {
2039   if (TagLib::AudioProperties* audioProperties;
2040       !m_fileRef.isNull() &&
2041       (audioProperties = m_fileRef.audioProperties()) != nullptr) {
2042     m_detailInfo.valid = true;
2043     if (TagLib::MPEG::Properties* mpegProperties;
2044         (mpegProperties =
2045          dynamic_cast<TagLib::MPEG::Properties*>(audioProperties)) != nullptr) {
2046 #if TAGLIB_VERSION < 0x020000
2047       if (getFilename().right(4).toLower() == QLatin1String(".aac")) {
2048         m_detailInfo.format = QLatin1String("AAC");
2049         return;
2050       }
2051 #endif
2052       switch (mpegProperties->version()) {
2053         case TagLib::MPEG::Header::Version1:
2054           m_detailInfo.format = QLatin1String("MPEG 1 ");
2055           break;
2056         case TagLib::MPEG::Header::Version2:
2057           m_detailInfo.format = QLatin1String("MPEG 2 ");
2058           break;
2059         case TagLib::MPEG::Header::Version2_5:
2060           m_detailInfo.format = QLatin1String("MPEG 2.5 ");
2061           break;
2062 #if TAGLIB_VERSION >= 0x020000
2063         case TagLib::MPEG::Header::Version4:
2064           m_detailInfo.format = QLatin1String("MPEG 4 ");
2065           break;
2066 #endif
2067       }
2068       if (int layer = mpegProperties->layer(); layer >= 1 && layer <= 3) {
2069         m_detailInfo.format += QLatin1String("Layer ");
2070         m_detailInfo.format += QString::number(layer);
2071       }
2072       switch (mpegProperties->channelMode()) {
2073         case TagLib::MPEG::Header::Stereo:
2074           m_detailInfo.channelMode = DetailInfo::CM_Stereo;
2075           m_detailInfo.channels = 2;
2076           break;
2077         case TagLib::MPEG::Header::JointStereo:
2078           m_detailInfo.channelMode = DetailInfo::CM_JointStereo;
2079           m_detailInfo.channels = 2;
2080           break;
2081         case TagLib::MPEG::Header::DualChannel:
2082           m_detailInfo.channels = 2;
2083           break;
2084         case TagLib::MPEG::Header::SingleChannel:
2085           m_detailInfo.channels = 1;
2086           break;
2087       }
2088 #if TAGLIB_VERSION >= 0x020000
2089       if (mpegProperties->isADTS()) {
2090         m_detailInfo.format += QLatin1String("ADTS");
2091         m_detailInfo.channels = mpegProperties->channels();
2092       }
2093 #endif
2094     } else if (dynamic_cast<TagLib::Vorbis::Properties*>(audioProperties) !=
2095                nullptr) {
2096       m_detailInfo.format = QLatin1String("Ogg Vorbis");
2097     } else if (auto flacProperties =
2098                 dynamic_cast<TagLib::FLAC::Properties*>(audioProperties)) {
2099       m_detailInfo.format = QLatin1String("FLAC");
2100 #if TAGLIB_VERSION >= 0x010a00
2101       if (int bits = flacProperties->bitsPerSample(); bits > 0) {
2102         m_detailInfo.format += QLatin1Char(' ');
2103         m_detailInfo.format += QString::number(bits);
2104         m_detailInfo.format += QLatin1String(" bit");
2105       }
2106 #endif
2107     } else if (dynamic_cast<TagLib::MPC::Properties*>(audioProperties) != nullptr) {
2108       m_detailInfo.format = QLatin1String("MPC");
2109     } else if (TagLib::Ogg::Speex::Properties* speexProperties;
2110                (speexProperties =
2111                 dynamic_cast<TagLib::Ogg::Speex::Properties*>(audioProperties)) != nullptr) {
2112       m_detailInfo.format = QString(QLatin1String("Speex %1")).arg(speexProperties->speexVersion());
2113     } else if (TagLib::TrueAudio::Properties* ttaProperties;
2114                (ttaProperties =
2115                 dynamic_cast<TagLib::TrueAudio::Properties*>(audioProperties)) != nullptr) {
2116       m_detailInfo.format = QLatin1String("True Audio ");
2117       m_detailInfo.format += QString::number(ttaProperties->ttaVersion());
2118       m_detailInfo.format += QLatin1Char(' ');
2119       m_detailInfo.format += QString::number(ttaProperties->bitsPerSample());
2120       m_detailInfo.format += QLatin1String(" bit");
2121     } else if (TagLib::WavPack::Properties* wvProperties;
2122                (wvProperties =
2123                 dynamic_cast<TagLib::WavPack::Properties*>(audioProperties)) != nullptr) {
2124       m_detailInfo.format = QLatin1String("WavPack ");
2125       m_detailInfo.format += QString::number(wvProperties->version(), 16);
2126       m_detailInfo.format += QLatin1Char(' ');
2127       m_detailInfo.format += QString::number(wvProperties->bitsPerSample());
2128       m_detailInfo.format += QLatin1String(" bit");
2129     } else if (auto mp4Properties =
2130                 dynamic_cast<TagLib::MP4::Properties*>(audioProperties)) {
2131       m_detailInfo.format = QLatin1String("MP4");
2132 #if TAGLIB_VERSION >= 0x010a00
2133       switch (mp4Properties->codec()) {
2134       case TagLib::MP4::Properties::AAC:
2135         m_detailInfo.format += QLatin1String(" AAC");
2136         break;
2137       case TagLib::MP4::Properties::ALAC:
2138         m_detailInfo.format += QLatin1String(" ALAC");
2139         break;
2140       case TagLib::MP4::Properties::Unknown:
2141         ;
2142       }
2143       if (int bits = mp4Properties->bitsPerSample(); bits > 0) {
2144         m_detailInfo.format += QLatin1Char(' ');
2145         m_detailInfo.format += QString::number(bits);
2146         m_detailInfo.format += QLatin1String(" bit");
2147       }
2148 #endif
2149     } else if (dynamic_cast<TagLib::ASF::Properties*>(audioProperties) != nullptr) {
2150       m_detailInfo.format = QLatin1String("ASF");
2151     } else if (auto aiffProperties =
2152                dynamic_cast<TagLib::RIFF::AIFF::Properties*>(audioProperties)) {
2153       m_detailInfo.format = QLatin1String("AIFF");
2154 #if TAGLIB_VERSION >= 0x010a00
2155       if (int bits = aiffProperties->bitsPerSample(); bits > 0) {
2156         m_detailInfo.format += QLatin1Char(' ');
2157         m_detailInfo.format += QString::number(bits);
2158         m_detailInfo.format += QLatin1String(" bit");
2159       }
2160 #endif
2161     } else if (auto wavProperties =
2162                dynamic_cast<TagLib::RIFF::WAV::Properties*>(audioProperties)) {
2163       m_detailInfo.format = QLatin1String("WAV");
2164 #if TAGLIB_VERSION >= 0x010a00
2165       if (int format = wavProperties->format(); format > 0) {
2166         // https://tools.ietf.org/html/rfc2361#appendix-A
2167         static const struct {
2168           int code;
2169           const char* name;
2170         } codeToName[] = {
2171         {0x0001, "PCM"}, {0x0002, "ADPCM"}, {0x003, "IEEE Float"},
2172         {0x0004, "VSELP"}, {0x0005, "IBM CVSD"}, {0x0006, "ALAW"},
2173         {0x0007, "MULAW"}, {0x0010, "OKI ADPCM"}, {0x0011, "DVI ADPCM"},
2174         {0x0012, "MediaSpace ADPCM"}, {0x0013, "Sierra ADPCM"},
2175         {0x0014, "G.723 ADPCM"}, {0x0015, "DIGISTD"}, {0x0016, "DIGIFIX"},
2176         {0x0017, "OKI ADPCM"}, {0x0018, "MediaVision ADPCM"}, {0x0019, "CU"},
2177         {0x0020, "Yamaha ADPCM"}, {0x0021, "Sonarc"}, {0x0022, "True Speech"},
2178         {0x0023, "EchoSC1"}, {0x0024, "AF36"}, {0x0025, "APTX"},
2179         {0x0026, "AF10"}, {0x0027, "Prosody 1612"}, {0x0028, "LRC"},
2180         {0x0030, "Dolby AC2"}, {0x0031, "GSM610"}, {0x0032, "MSNAudio"},
2181         {0x0033, "Antex ADPCME"}, {0x0034, "Control Res VQLPC"}, {0x0035, "Digireal"},
2182         {0x0036, "DigiADPCM"}, {0x0037, "Control Res CR10"}, {0x0038, "NMS VBXADPCM"},
2183         {0x0039, "Roland RDAC"}, {0x003a, "EchoSC3"}, {0x003b, "Rockwell ADPCM"},
2184         {0x003c, "Rockwell DIGITALK"}, {0x003d, "Xebec"}, {0x0040, "G.721 ADPCM"},
2185         {0x0041, "G.728 CELP"}, {0x0042, "MSG723"}, {0x0050, "MPEG"},
2186         {0x0052, "RT24"}, {0x0053, "PAC"}, {0x0055, "MPEG Layer 3"},
2187         {0x0059, "Lucent G.723"}, {0x0060, "Cirrus"}, {0x0061, "ESPCM"},
2188         {0x0062, "Voxware"}, {0x0063, "Canopus Atrac"}, {0x0064, "G.726 ADPCM"},
2189         {0x0065, "G.722 ADPCM"}, {0x0066, "DSAT"}, {0x0067, "DSAT Display"},
2190         {0x0069, "Voxware Byte Aligned"}, {0x0070, "Voxware AC8"}, {0x0071, "Voxware AC10"},
2191         {0x0072, "Voxware AC16"}, {0x0073, "Voxware AC20"}, {0x0074, "Voxware MetaVoice"},
2192         {0x0075, "Voxware MetaSound"}, {0x0076, "Voxware RT29HW"}, {0x0077, "Voxware VR12"},
2193         {0x0078, "Voxware VR18"}, {0x0079, "Voxware TQ40"}, {0x0080, "Softsound"},
2194         {0x0081, "Voxware TQ60"}, {0x0082, "MSRT24"}, {0x0083, "G.729A"},
2195         {0x0084, "MVI MV12"}, {0x0085, "DF G.726"}, {0x0086, "DF GSM610"},
2196         {0x0088, "ISIAudio"}, {0x0089, "Onlive"}, {0x0091, "SBC24"},
2197         {0x0092, "Dolby AC3 SPDIF"}, {0x0097, "ZyXEL ADPCM"}, {0x0098, "Philips LPCBB"},
2198         {0x0099, "Packed"}, {0x0100, "Rhetorex ADPCM"}, {0x0101, "IRAT"},
2199         {0x0111, "Vivo G.723"}, {0x0112, "Vivo Siren"}, {0x0123, "Digital G.723"},
2200         {0x0200, "Creative ADPCM"}, {0x0202, "Creative FastSpeech8"}, {0x0203, "Creative FastSpeech10"},
2201         {0x0220, "Quarterdeck"}, {0x0300, "FM Towns Snd"}, {0x0400, "BTV Digital"},
2202         {0x0680, "VME VMPCM"}, {0x1000, "OLIGSM"}, {0x1001, "OLIADPCM"},
2203         {0x1002, "OLICELP"}, {0x1003, "OLISBC"}, {0x1004, "OLIOPR"},
2204         {0x1100, "LH Codec"}, {0x1400, "Norris"}, {0x1401, "ISIAudio"},
2205         {0x1500, "Soundspace Music Compression"}, {0x2000, "DVM"}
2206         };
2207         for (const auto& [code, name] : codeToName) {
2208           if (format == code) {
2209             m_detailInfo.format += QLatin1Char(' ');
2210             m_detailInfo.format += QString::fromLatin1(name);
2211             break;
2212           }
2213         }
2214       }
2215       if (int bits = wavProperties->bitsPerSample(); bits > 0) {
2216         m_detailInfo.format += QLatin1Char(' ');
2217         m_detailInfo.format += QString::number(bits);
2218         m_detailInfo.format += QLatin1String(" bit");
2219       }
2220 #endif
2221     } else if (TagLib::APE::Properties* apeProperties;
2222                (apeProperties =
2223                 dynamic_cast<TagLib::APE::Properties*>(audioProperties)) != nullptr) {
2224       m_detailInfo.format = QString(QLatin1String("APE %1.%2 %3 bit"))
2225         .arg(apeProperties->version() / 1000)
2226         .arg(apeProperties->version() % 1000)
2227         .arg(apeProperties->bitsPerSample());
2228     } else if (TagLib::Mod::Properties* modProperties;
2229                (modProperties =
2230                 dynamic_cast<TagLib::Mod::Properties*>(audioProperties)) != nullptr) {
2231       m_detailInfo.format = QString(QLatin1String("Mod %1 %2 Instruments"))
2232           .arg(getTrackerName())
2233           .arg(modProperties->instrumentCount());
2234     } else if (TagLib::S3M::Properties* s3mProperties;
2235                (s3mProperties =
2236                 dynamic_cast<TagLib::S3M::Properties*>(audioProperties)) != nullptr) {
2237       m_detailInfo.format = QString(QLatin1String("S3M %1 V%2 T%3"))
2238           .arg(getTrackerName())
2239           .arg(s3mProperties->fileFormatVersion())
2240           .arg(s3mProperties->trackerVersion(), 0, 16);
2241       m_detailInfo.channelMode = s3mProperties->stereo()
2242           ? DetailInfo::CM_Stereo : DetailInfo::CM_None;
2243     } else if (TagLib::IT::Properties* itProperties;
2244                (itProperties =
2245                 dynamic_cast<TagLib::IT::Properties*>(audioProperties)) != nullptr) {
2246       m_detailInfo.format = QString(QLatin1String("IT %1 V%2 %3 Instruments"))
2247           .arg(getTrackerName())
2248           .arg(itProperties->version(), 0, 16)
2249           .arg(itProperties->instrumentCount());
2250       m_detailInfo.channelMode = itProperties->stereo()
2251           ? DetailInfo::CM_Stereo : DetailInfo::CM_None;
2252     } else if (TagLib::XM::Properties* xmProperties;
2253                (xmProperties =
2254                 dynamic_cast<TagLib::XM::Properties*>(audioProperties)) != nullptr) {
2255       m_detailInfo.format = QString(QLatin1String("XM %1 V%2 %3 Instruments"))
2256           .arg(getTrackerName())
2257           .arg(xmProperties->version(), 0, 16)
2258           .arg(xmProperties->instrumentCount());
2259     } else if (TagLib::Ogg::Opus::Properties* opusProperties;
2260                (opusProperties =
2261           dynamic_cast<TagLib::Ogg::Opus::Properties*>(audioProperties)) != nullptr) {
2262       m_detailInfo.format = QString(QLatin1String("Opus %1"))
2263           .arg(opusProperties->opusVersion());
2264 #if TAGLIB_VERSION >= 0x020000
2265     } else if (TagLib::DSF::Properties* dsfProperties;
2266                (dsfProperties =
2267                 dynamic_cast<TagLib::DSF::Properties*>(audioProperties)) != nullptr) {
2268       m_detailInfo.format = QString(QLatin1String("DSF %1"))
2269                                 .arg(dsfProperties->formatVersion());
2270     } else if (dynamic_cast<TagLib::DSDIFF::Properties*>(audioProperties) != nullptr) {
2271       m_detailInfo.format = QString(QLatin1String("DFF"));
2272 #else
2273     } else if (DSFProperties* dsfProperties;
2274                (dsfProperties =
2275               dynamic_cast<DSFProperties*>(audioProperties)) != nullptr) {
2276       m_detailInfo.format = QString(QLatin1String("DSF %1"))
2277                                 .arg(dsfProperties->version());
2278     } else if (dynamic_cast<DSDIFFProperties*>(audioProperties) != nullptr) {
2279       m_detailInfo.format = QString(QLatin1String("DFF"));
2280 #endif
2281     }
2282 
2283     m_detailInfo.bitrate = audioProperties->bitrate();
2284     m_detailInfo.sampleRate = audioProperties->sampleRate();
2285     if (audioProperties->channels() > 0) {
2286       m_detailInfo.channels = audioProperties->channels();
2287     }
2288 #if TAGLIB_VERSION >= 0x020000
2289     m_detailInfo.duration = audioProperties->lengthInSeconds();
2290 #else
2291     // lengthInSeconds() does not work for DSF with TagLib 1.x, because
2292     // it is not virtual.
2293     m_detailInfo.duration = audioProperties->length();
2294 #endif
2295   } else {
2296     m_detailInfo.valid = false;
2297   }
2298 }
2299 
2300 /**
2301  * Get tracker name of a module file.
2302  *
2303  * @return tracker name, null if not found.
2304  */
2305 QString TagLibFile::getTrackerName() const
2306 {
2307   QString trackerName;
2308   if (auto modTag = dynamic_cast<TagLib::Mod::Tag*>(m_tag[Frame::Tag_2])) {
2309     trackerName = toQString(modTag->trackerName()).trimmed();
2310   }
2311   return trackerName;
2312 }
2313 
2314 
2315 /**
2316  * Set m_id3v2Version to 3 or 4 from tag if it exists, else to 0.
2317  * @param id3v2Tag ID3v2 tag
2318  */
2319 void TagLibFile::setId3v2VersionFromTag(const TagLib::ID3v2::Tag* id3v2Tag)
2320 {
2321   m_id3v2Version = 0;
2322   if (TagLib::ID3v2::Header* header;
2323       id3v2Tag && (header = id3v2Tag->header()) != nullptr) {
2324     if (!id3v2Tag->isEmpty()) {
2325       m_id3v2Version = header->majorVersion();
2326     } else {
2327       header->setMajorVersion(TagConfig::instance().id3v2Version() ==
2328                               TagConfig::ID3v2_3_0 ? 3 : 4);
2329     }
2330   }
2331 }
2332 
2333 /**
2334  * Set m_id3v2Version from given value (3 or 4) or use default from
2335  * configuration if not already set to 3 or 4.
2336  * @param id3v2Version 3 or 4 to force version, 0 to use existing version
2337  * or default
2338  */
2339 void TagLibFile::setId3v2VersionOrDefault(int id3v2Version)
2340 {
2341   if (id3v2Version == 3 || id3v2Version == 4) {
2342     m_id3v2Version = id3v2Version;
2343   }
2344   if (m_id3v2Version != 3 && m_id3v2Version != 4) {
2345     m_id3v2Version = TagConfig::instance().id3v2Version() ==
2346         TagConfig::ID3v2_3_0 ? 3 : 4;
2347   }
2348 }
2349 
2350 /**
2351  * Get duration of file.
2352  *
2353  * @return duration in seconds,
2354  *         0 if unknown.
2355  */
2356 unsigned TagLibFile::getDuration() const
2357 {
2358   return m_detailInfo.valid ? m_detailInfo.duration : 0;
2359 }
2360 
2361 /**
2362  * Get file extension including the dot.
2363  *
2364  * @return file extension ".mp3".
2365  */
2366 QString TagLibFile::getFileExtension() const
2367 {
2368   return m_fileExtension;
2369 }
2370 
2371 /**
2372  * Get the format of a tag.
2373  *
2374  * @param tag tag, 0 if no tag available
2375  * @param type the tag type is returned here
2376  *
2377  * @return string describing format of tag,
2378  *         e.g. "ID3v1.1", "ID3v2.3", "Vorbis", "APE",
2379  *         QString::null if unknown.
2380  */
2381 QString TagLibFile::getTagFormat(const TagLib::Tag* tag, TagType& type)
2382 {
2383   if (tag && !tag->isEmpty()) {
2384     if (dynamic_cast<const TagLib::ID3v1::Tag*>(tag) != nullptr) {
2385       type = TT_Id3v1;
2386       return QLatin1String("ID3v1.1");
2387     }
2388     if (const TagLib::ID3v2::Tag* id3v2Tag;
2389         (id3v2Tag = dynamic_cast<const TagLib::ID3v2::Tag*>(tag)) != nullptr) {
2390       type = TT_Id3v2;
2391       if (TagLib::ID3v2::Header* header = id3v2Tag->header()) {
2392         uint majorVersion = header->majorVersion();
2393         uint revisionNumber = header->revisionNumber();
2394         return QString(QLatin1String("ID3v2.%1.%2"))
2395           .arg(majorVersion).arg(revisionNumber);
2396       }
2397       return QLatin1String("ID3v2");
2398     }
2399     if (dynamic_cast<const TagLib::Ogg::XiphComment*>(tag) != nullptr) {
2400       type = TT_Vorbis;
2401       return QLatin1String("Vorbis");
2402     }
2403     if (dynamic_cast<const TagLib::APE::Tag*>(tag) != nullptr) {
2404       type = TT_Ape;
2405       return QLatin1String("APE");
2406     }
2407     if (dynamic_cast<const TagLib::MP4::Tag*>(tag) != nullptr) {
2408       type = TT_Mp4;
2409       return QLatin1String("MP4");
2410     }
2411     if (dynamic_cast<const TagLib::ASF::Tag*>(tag) != nullptr) {
2412       type = TT_Asf;
2413       return QLatin1String("ASF");
2414 #if TAGLIB_VERSION >= 0x010a00
2415     }
2416     if (dynamic_cast<const TagLib::RIFF::Info::Tag*>(tag) != nullptr) {
2417       type = TT_Info;
2418       return QLatin1String("RIFF INFO");
2419 #endif
2420     }
2421   }
2422   type = TT_Unknown;
2423   return QString();
2424 }
2425 
2426 /**
2427  * Get the format of tag.
2428  *
2429  * @param tagNr tag number
2430  * @return string describing format of tag,
2431  *         e.g. "ID3v1.1", "ID3v2.3", "Vorbis", "APE",
2432  *         QString::null if unknown.
2433  */
2434 QString TagLibFile::getTagFormat(Frame::TagNumber tagNr) const
2435 {
2436   return tagNr < NUM_TAGS ? m_tagFormat[tagNr] : QString();
2437 }
2438 
2439 
2440 namespace TagLibFileInternal {
2441 
2442 /**
2443  * Fix up the format of the value if needed for an ID3v2 frame.
2444  *
2445  * @param self this TagLibFile instance
2446  * @param frameType type of frame
2447  * @param value the value to be set for frame, will be modified if needed
2448  */
2449 void fixUpTagLibFrameValue(const TagLibFile* self,
2450                            Frame::Type frameType, QString& value)
2451 {
2452   if (frameType == Frame::FT_Genre) {
2453     if (const bool useId3v23 = self->m_id3v2Version == 3;
2454         !TagConfig::instance().genreNotNumeric() ||
2455         (useId3v23 && value.contains(Frame::stringListSeparator()))) {
2456       value = Genres::getNumberString(value, useId3v23);
2457     }
2458   } else if (frameType == Frame::FT_Track) {
2459     self->formatTrackNumberIfEnabled(value, true);
2460   } else if ((frameType == Frame::FT_Arranger ||
2461               frameType == Frame::FT_Performer) &&
2462              !value.isEmpty() &&
2463              !value.contains(Frame::stringListSeparator())) {
2464     // When using TIPL or TMCL and writing an ID3v2.3.0 tag, TagLib
2465     // needs in ID3v2::Tag::downgradeFrames() a string list with at
2466     // least two elements, otherwise it will not take the value over
2467     // to an IPLS frame. If there is a single value in such a case,
2468     // add a second element.
2469     value = Frame::joinStringList({value, QLatin1String("")});
2470   }
2471 }
2472 
2473 }
2474 
2475 
2476 using namespace TagLibFileInternal;
2477 
2478 namespace {
2479 
2480 /** Types and descriptions for id3lib frame IDs */
2481 const struct TypeStrOfId {
2482   const char* str;
2483   Frame::Type type;
2484   bool supported;
2485 } typeStrOfId[] = {
2486   { QT_TRANSLATE_NOOP("@default", "AENC - Audio encryption"), Frame::FT_Other, false },
2487   { QT_TRANSLATE_NOOP("@default", "APIC - Attached picture"), Frame::FT_Picture, true },
2488   { QT_TRANSLATE_NOOP("@default", "ASPI - Audio seek point index"), Frame::FT_Other, false },
2489 #if TAGLIB_VERSION >= 0x010a00
2490   { QT_TRANSLATE_NOOP("@default", "CHAP - Chapter"), Frame::FT_Other, true },
2491 #endif
2492   { QT_TRANSLATE_NOOP("@default", "COMM - Comments"), Frame::FT_Comment, true },
2493   { QT_TRANSLATE_NOOP("@default", "COMR - Commercial"), Frame::FT_Other, false },
2494 #if TAGLIB_VERSION >= 0x010a00
2495   { QT_TRANSLATE_NOOP("@default", "CTOC - Table of contents"), Frame::FT_Other, true },
2496 #endif
2497   { QT_TRANSLATE_NOOP("@default", "ENCR - Encryption method registration"), Frame::FT_Other, false },
2498   { QT_TRANSLATE_NOOP("@default", "EQU2 - Equalisation (2)"), Frame::FT_Other, false },
2499   { QT_TRANSLATE_NOOP("@default", "ETCO - Event timing codes"), Frame::FT_Other, true },
2500   { QT_TRANSLATE_NOOP("@default", "GEOB - General encapsulated object"), Frame::FT_Other, true },
2501   { QT_TRANSLATE_NOOP("@default", "GRID - Group identification registration"), Frame::FT_Other, false },
2502 #if TAGLIB_VERSION >= 0x010c00
2503   { QT_TRANSLATE_NOOP("@default", "GRP1 - Grouping"), Frame::FT_Other, true },
2504 #endif
2505   { QT_TRANSLATE_NOOP("@default", "LINK - Linked information"), Frame::FT_Other, false },
2506   { QT_TRANSLATE_NOOP("@default", "MCDI - Music CD identifier"), Frame::FT_Other, false },
2507   { QT_TRANSLATE_NOOP("@default", "MLLT - MPEG location lookup table"), Frame::FT_Other, false },
2508 #if TAGLIB_VERSION >= 0x010c00
2509   { QT_TRANSLATE_NOOP("@default", "MVIN - Movement Number"), Frame::FT_Other, true },
2510   { QT_TRANSLATE_NOOP("@default", "MVNM - Movement Name"), Frame::FT_Other, true },
2511 #endif
2512   { QT_TRANSLATE_NOOP("@default", "OWNE - Ownership frame"), Frame::FT_Other, true },
2513   { QT_TRANSLATE_NOOP("@default", "PRIV - Private frame"), Frame::FT_Other, true },
2514   { QT_TRANSLATE_NOOP("@default", "PCNT - Play counter"), Frame::FT_Other, false },
2515 #if TAGLIB_VERSION >= 0x010b00
2516   { QT_TRANSLATE_NOOP("@default", "PCST - Podcast"), Frame::FT_Other, true },
2517 #endif
2518   { QT_TRANSLATE_NOOP("@default", "POPM - Popularimeter"), Frame::FT_Rating, true },
2519   { QT_TRANSLATE_NOOP("@default", "POSS - Position synchronisation frame"), Frame::FT_Other, false },
2520   { QT_TRANSLATE_NOOP("@default", "RBUF - Recommended buffer size"), Frame::FT_Other, false },
2521   { QT_TRANSLATE_NOOP("@default", "RVA2 - Relative volume adjustment (2)"), Frame::FT_Other, true },
2522   { QT_TRANSLATE_NOOP("@default", "RVRB - Reverb"), Frame::FT_Other, false },
2523   { QT_TRANSLATE_NOOP("@default", "SEEK - Seek frame"), Frame::FT_Other, false },
2524   { QT_TRANSLATE_NOOP("@default", "SIGN - Signature frame"), Frame::FT_Other, false },
2525   { QT_TRANSLATE_NOOP("@default", "SYLT - Synchronized lyric/text"), Frame::FT_Other, true },
2526   { QT_TRANSLATE_NOOP("@default", "SYTC - Synchronized tempo codes"), Frame::FT_Other, false },
2527   { QT_TRANSLATE_NOOP("@default", "TALB - Album/Movie/Show title"), Frame::FT_Album, true },
2528   { QT_TRANSLATE_NOOP("@default", "TBPM - BPM (beats per minute)"),  Frame::FT_Bpm, true },
2529 #if TAGLIB_VERSION >= 0x010b00
2530   { QT_TRANSLATE_NOOP("@default", "TCAT - Podcast category"), Frame::FT_Other, true },
2531 #endif
2532   { QT_TRANSLATE_NOOP("@default", "TCMP - iTunes compilation flag"), Frame::FT_Compilation, true },
2533   { QT_TRANSLATE_NOOP("@default", "TCOM - Composer"), Frame::FT_Composer, true },
2534   { QT_TRANSLATE_NOOP("@default", "TCON - Content type"), Frame::FT_Genre, true },
2535   { QT_TRANSLATE_NOOP("@default", "TCOP - Copyright message"), Frame::FT_Copyright, true },
2536   { QT_TRANSLATE_NOOP("@default", "TDEN - Encoding time"), Frame::FT_EncodingTime, true },
2537 #if TAGLIB_VERSION >= 0x010b00
2538   { QT_TRANSLATE_NOOP("@default", "TDES - Podcast description"), Frame::FT_Other, true },
2539 #endif
2540   { QT_TRANSLATE_NOOP("@default", "TDLY - Playlist delay"), Frame::FT_Other, true },
2541   { QT_TRANSLATE_NOOP("@default", "TDOR - Original release time"), Frame::FT_OriginalDate, true },
2542   { QT_TRANSLATE_NOOP("@default", "TDRC - Recording time"), Frame::FT_Date, true },
2543   { QT_TRANSLATE_NOOP("@default", "TDRL - Release time"), Frame::FT_ReleaseDate, true },
2544   { QT_TRANSLATE_NOOP("@default", "TDTG - Tagging time"), Frame::FT_Other, true },
2545   { QT_TRANSLATE_NOOP("@default", "TENC - Encoded by"), Frame::FT_EncodedBy, true },
2546   { QT_TRANSLATE_NOOP("@default", "TEXT - Lyricist/Text writer"), Frame::FT_Lyricist, true },
2547   { QT_TRANSLATE_NOOP("@default", "TFLT - File type"), Frame::FT_Other, true },
2548 #if TAGLIB_VERSION >= 0x010b00
2549   { QT_TRANSLATE_NOOP("@default", "TGID - Podcast identifier"), Frame::FT_Other, true },
2550 #endif
2551   { QT_TRANSLATE_NOOP("@default", "TIPL - Involved people list"), Frame::FT_Arranger, true },
2552   { QT_TRANSLATE_NOOP("@default", "TIT1 - Content group description"), Frame::FT_Work, true },
2553   { QT_TRANSLATE_NOOP("@default", "TIT2 - Title/songname/content description"), Frame::FT_Title, true },
2554   { QT_TRANSLATE_NOOP("@default", "TIT3 - Subtitle/Description refinement"), Frame::FT_Description, true },
2555   { QT_TRANSLATE_NOOP("@default", "TKEY - Initial key"), Frame::FT_InitialKey, true },
2556 #if TAGLIB_VERSION >= 0x010b00
2557   { QT_TRANSLATE_NOOP("@default", "TKWD - Podcast keywords"), Frame::FT_Other, true },
2558 #endif
2559   { QT_TRANSLATE_NOOP("@default", "TLAN - Language(s)"), Frame::FT_Language, true },
2560   { QT_TRANSLATE_NOOP("@default", "TLEN - Length"), Frame::FT_Other, true },
2561   { QT_TRANSLATE_NOOP("@default", "TMCL - Musician credits list"), Frame::FT_Performer, true },
2562   { QT_TRANSLATE_NOOP("@default", "TMED - Media type"), Frame::FT_Media, true },
2563   { QT_TRANSLATE_NOOP("@default", "TMOO - Mood"), Frame::FT_Mood, true },
2564   { QT_TRANSLATE_NOOP("@default", "TOAL - Original album/movie/show title"), Frame::FT_OriginalAlbum, true },
2565   { QT_TRANSLATE_NOOP("@default", "TOFN - Original filename"), Frame::FT_Other, true },
2566   { QT_TRANSLATE_NOOP("@default", "TOLY - Original lyricist(s)/text writer(s)"), Frame::FT_Author, true },
2567   { QT_TRANSLATE_NOOP("@default", "TOPE - Original artist(s)/performer(s)"), Frame::FT_OriginalArtist, true },
2568   { QT_TRANSLATE_NOOP("@default", "TOWN - File owner/licensee"), Frame::FT_Other, true },
2569   { QT_TRANSLATE_NOOP("@default", "TPE1 - Lead performer(s)/Soloist(s)"), Frame::FT_Artist, true },
2570   { QT_TRANSLATE_NOOP("@default", "TPE2 - Band/orchestra/accompaniment"), Frame::FT_AlbumArtist, true },
2571   { QT_TRANSLATE_NOOP("@default", "TPE3 - Conductor/performer refinement"), Frame::FT_Conductor, true },
2572   { QT_TRANSLATE_NOOP("@default", "TPE4 - Interpreted, remixed, or otherwise modified by"), Frame::FT_Remixer, true },
2573   { QT_TRANSLATE_NOOP("@default", "TPOS - Part of a set"), Frame::FT_Disc, true },
2574   { QT_TRANSLATE_NOOP("@default", "TPRO - Produced notice"), Frame::FT_Other, true },
2575   { QT_TRANSLATE_NOOP("@default", "TPUB - Publisher"), Frame::FT_Publisher, true },
2576   { QT_TRANSLATE_NOOP("@default", "TRCK - Track number/Position in set"), Frame::FT_Track, true },
2577   { QT_TRANSLATE_NOOP("@default", "TRSN - Internet radio station name"), Frame::FT_Other, true },
2578   { QT_TRANSLATE_NOOP("@default", "TRSO - Internet radio station owner"), Frame::FT_Other, true },
2579   { QT_TRANSLATE_NOOP("@default", "TSO2 - Album artist sort order"), Frame::FT_SortAlbumArtist, true },
2580   { QT_TRANSLATE_NOOP("@default", "TSOA - Album sort order"), Frame::FT_SortAlbum, true },
2581   { QT_TRANSLATE_NOOP("@default", "TSOC - Composer sort order"), Frame::FT_SortComposer, true },
2582   { QT_TRANSLATE_NOOP("@default", "TSOP - Performer sort order"), Frame::FT_SortArtist, true },
2583   { QT_TRANSLATE_NOOP("@default", "TSOT - Title sort order"), Frame::FT_SortName, true },
2584   { QT_TRANSLATE_NOOP("@default", "TSRC - ISRC (international standard recording code)"), Frame::FT_Isrc, true },
2585   { QT_TRANSLATE_NOOP("@default", "TSSE - Software/Hardware and settings used for encoding"), Frame::FT_EncoderSettings, true },
2586   { QT_TRANSLATE_NOOP("@default", "TSST - Set subtitle"), Frame::FT_Subtitle, true },
2587   { QT_TRANSLATE_NOOP("@default", "TXXX - User defined text information"), Frame::FT_Other, true },
2588   { QT_TRANSLATE_NOOP("@default", "UFID - Unique file identifier"), Frame::FT_Other, true },
2589   { QT_TRANSLATE_NOOP("@default", "USER - Terms of use"), Frame::FT_Other, false },
2590   { QT_TRANSLATE_NOOP("@default", "USLT - Unsynchronized lyric/text transcription"), Frame::FT_Lyrics, true },
2591   { QT_TRANSLATE_NOOP("@default", "WCOM - Commercial information"), Frame::FT_Other, true },
2592   { QT_TRANSLATE_NOOP("@default", "WCOP - Copyright/Legal information"), Frame::FT_Other, true },
2593 #if TAGLIB_VERSION >= 0x010b00
2594   { QT_TRANSLATE_NOOP("@default", "WFED - Podcast feed"), Frame::FT_Other, true },
2595 #endif
2596   { QT_TRANSLATE_NOOP("@default", "WOAF - Official audio file webpage"), Frame::FT_WWWAudioFile, true },
2597   { QT_TRANSLATE_NOOP("@default", "WOAR - Official artist/performer webpage"), Frame::FT_Website, true },
2598   { QT_TRANSLATE_NOOP("@default", "WOAS - Official audio source webpage"), Frame::FT_WWWAudioSource, true },
2599   { QT_TRANSLATE_NOOP("@default", "WORS - Official internet radio station homepage"), Frame::FT_Other, true },
2600   { QT_TRANSLATE_NOOP("@default", "WPAY - Payment"), Frame::FT_Other, true },
2601   { QT_TRANSLATE_NOOP("@default", "WPUB - Official publisher webpage"), Frame::FT_Other, true },
2602   { QT_TRANSLATE_NOOP("@default", "WXXX - User defined URL link"), Frame::FT_Other, true }
2603 };
2604 
2605 /**
2606  * Get type and description of frame.
2607  *
2608  * @param id   ID of frame
2609  * @param type the type is returned here
2610  * @param str  the description is returned here
2611  */
2612 void getTypeStringForFrameId(const TagLib::ByteVector& id, Frame::Type& type,
2613                              const char*& str)
2614 {
2615   static TagLib::Map<TagLib::ByteVector, unsigned> idIndexMap;
2616   if (idIndexMap.isEmpty()) {
2617     for (unsigned i = 0; i < std::size(typeStrOfId); ++i) {
2618       idIndexMap.insert(TagLib::ByteVector(typeStrOfId[i].str, 4), i);
2619     }
2620   }
2621   if (idIndexMap.contains(id)) {
2622     const auto& [s, t, supported] = typeStrOfId[idIndexMap[id]];
2623     type = t;
2624     str = s;
2625     if (type == Frame::FT_Other) {
2626       type = Frame::getTypeFromCustomFrameName(
2627             QByteArray(id.data(), id.size()));
2628     }
2629   } else {
2630     type = Frame::FT_UnknownFrame;
2631     str = "????";
2632   }
2633 }
2634 
2635 /**
2636  * Get string description starting with 4 bytes ID.
2637  *
2638  * @param type type of frame
2639  *
2640  * @return string.
2641  */
2642 const char* getStringForType(Frame::Type type)
2643 {
2644   if (type != Frame::FT_Other) {
2645     for (const auto& [s, t, supported] : typeStrOfId) {
2646       if (t == type) {
2647         return s;
2648       }
2649     }
2650   }
2651   return "????";
2652 }
2653 
2654 /**
2655  * Get the fields from a text identification frame.
2656  *
2657  * @param tFrame text identification frame
2658  * @param fields the fields are appended to this list
2659  * @param type   frame type
2660  *
2661  * @return text representation of fields (Text or URL).
2662  */
2663 QString getFieldsFromTextFrame(
2664   const TagLib::ID3v2::TextIdentificationFrame* tFrame,
2665   Frame::FieldList& fields, Frame::Type type)
2666 {
2667   QString text;
2668   Frame::Field field;
2669   field.m_id = Frame::ID_TextEnc;
2670   field.m_value = tFrame->textEncoding();
2671   fields.push_back(field);
2672 
2673   if (const TagLib::ID3v2::UserTextIdentificationFrame* txxxFrame;
2674       (txxxFrame =
2675        dynamic_cast<const TagLib::ID3v2::UserTextIdentificationFrame*>(tFrame))
2676       != nullptr) {
2677     field.m_id = Frame::ID_Description;
2678     field.m_value = toQString(txxxFrame->description());
2679     fields.push_back(field);
2680 
2681     TagLib::StringList slText = tFrame->fieldList();
2682     text = slText.size() > 1 ? toQString(slText[1]) : QLatin1String("");
2683   } else {
2684     // if there are multiple items, put them into one string
2685     // separated by a special separator.
2686     text = joinToQString(tFrame->fieldList());
2687   }
2688   field.m_id = Frame::ID_Text;
2689   if (type == Frame::FT_Genre) {
2690     text = Genres::getNameString(text);
2691   }
2692   field.m_value = text;
2693   fields.push_back(field);
2694 
2695   return text;
2696 }
2697 
2698 /**
2699  * Get the fields from an attached picture frame.
2700  *
2701  * @param apicFrame attached picture frame
2702  * @param fields the fields are appended to this list
2703  *
2704  * @return text representation of fields (Text or URL).
2705  */
2706 QString getFieldsFromApicFrame(
2707   const TagLib::ID3v2::AttachedPictureFrame* apicFrame,
2708   Frame::FieldList& fields)
2709 {
2710   QString text;
2711   Frame::Field field;
2712   field.m_id = Frame::ID_TextEnc;
2713   field.m_value = apicFrame->textEncoding();
2714   fields.push_back(field);
2715 
2716   // for compatibility with ID3v2.3 id3lib
2717   field.m_id = Frame::ID_ImageFormat;
2718   field.m_value = QString(QLatin1String(""));
2719   fields.push_back(field);
2720 
2721   field.m_id = Frame::ID_MimeType;
2722   field.m_value = toQString(apicFrame->mimeType());
2723   fields.push_back(field);
2724 
2725   field.m_id = Frame::ID_PictureType;
2726   field.m_value = apicFrame->type();
2727   fields.push_back(field);
2728 
2729   field.m_id = Frame::ID_Description;
2730   text = toQString(apicFrame->description());
2731   field.m_value = text;
2732   fields.push_back(field);
2733 
2734   field.m_id = Frame::ID_Data;
2735   TagLib::ByteVector pic = apicFrame->picture();
2736   QByteArray ba;
2737   ba = QByteArray(pic.data(), pic.size());
2738   field.m_value = ba;
2739   fields.push_back(field);
2740 
2741   return text;
2742 }
2743 
2744 /**
2745  * Get the fields from a comments frame.
2746  *
2747  * @param commFrame comments frame
2748  * @param fields the fields are appended to this list
2749  *
2750  * @return text representation of fields (Text or URL).
2751  */
2752 QString getFieldsFromCommFrame(
2753   const TagLib::ID3v2::CommentsFrame* commFrame, Frame::FieldList& fields)
2754 {
2755   QString text;
2756   Frame::Field field;
2757   field.m_id = Frame::ID_TextEnc;
2758   field.m_value = commFrame->textEncoding();
2759   fields.push_back(field);
2760 
2761   field.m_id = Frame::ID_Language;
2762   TagLib::ByteVector bvLang = commFrame->language();
2763   field.m_value = QString::fromLatin1(QByteArray(bvLang.data(), bvLang.size()));
2764   fields.push_back(field);
2765 
2766   field.m_id = Frame::ID_Description;
2767   field.m_value = toQString(commFrame->description());
2768   fields.push_back(field);
2769 
2770   field.m_id = Frame::ID_Text;
2771   text = toQString(commFrame->toString());
2772   field.m_value = text;
2773   fields.push_back(field);
2774 
2775   return text;
2776 }
2777 
2778 /**
2779  * Get the fields from a unique file identifier frame.
2780  *
2781  * @param ufidFrame unique file identifier frame
2782  * @param fields the fields are appended to this list
2783  *
2784  * @return text representation of fields (Text or URL).
2785  */
2786 QString getFieldsFromUfidFrame(
2787   const TagLib::ID3v2::UniqueFileIdentifierFrame* ufidFrame,
2788   Frame::FieldList& fields)
2789 {
2790   Frame::Field field;
2791   field.m_id = Frame::ID_Owner;
2792   field.m_value = toQString(ufidFrame->owner());
2793   fields.push_back(field);
2794 
2795   field.m_id = Frame::ID_Id;
2796   TagLib::ByteVector id = ufidFrame->identifier();
2797   auto ba = QByteArray(id.data(), id.size());
2798   field.m_value = ba;
2799   fields.push_back(field);
2800 
2801   if (!ba.isEmpty()) {
2802     if (QString text(QString::fromLatin1(ba));
2803         ba.size() - text.length() <= 1 &&
2804         AttributeData::isHexString(text, 'Z', QLatin1String("-"))) {
2805       return text;
2806     }
2807   }
2808   return QString();
2809 }
2810 
2811 /**
2812  * Get the fields from a general encapsulated object frame.
2813  *
2814  * @param geobFrame general encapsulated object frame
2815  * @param fields the fields are appended to this list
2816  *
2817  * @return text representation of fields (Text or URL).
2818  */
2819 QString getFieldsFromGeobFrame(
2820   const TagLib::ID3v2::GeneralEncapsulatedObjectFrame* geobFrame,
2821   Frame::FieldList& fields)
2822 {
2823   QString text;
2824   Frame::Field field;
2825   field.m_id = Frame::ID_TextEnc;
2826   field.m_value = geobFrame->textEncoding();
2827   fields.push_back(field);
2828 
2829   field.m_id = Frame::ID_MimeType;
2830   field.m_value = toQString(geobFrame->mimeType());
2831   fields.push_back(field);
2832 
2833   field.m_id = Frame::ID_Filename;
2834   field.m_value = toQString(geobFrame->fileName());
2835   fields.push_back(field);
2836 
2837   field.m_id = Frame::ID_Description;
2838   text = toQString(geobFrame->description());
2839   field.m_value = text;
2840   fields.push_back(field);
2841 
2842   field.m_id = Frame::ID_Data;
2843   TagLib::ByteVector obj = geobFrame->object();
2844   QByteArray ba;
2845   ba = QByteArray(obj.data(), obj.size());
2846   field.m_value = ba;
2847   fields.push_back(field);
2848 
2849   return text;
2850 }
2851 
2852 /**
2853  * Get the fields from a URL link frame.
2854  *
2855  * @param wFrame URL link frame
2856  * @param fields the fields are appended to this list
2857  *
2858  * @return text representation of fields (Text or URL).
2859  */
2860 QString getFieldsFromUrlFrame(
2861   const TagLib::ID3v2::UrlLinkFrame* wFrame, Frame::FieldList& fields)
2862 {
2863   Frame::Field field;
2864   field.m_id = Frame::ID_Url;
2865   QString text = toQString(wFrame->url());
2866   field.m_value = text;
2867   fields.push_back(field);
2868 
2869   return text;
2870 }
2871 
2872 /**
2873  * Get the fields from a user URL link frame.
2874  *
2875  * @param wxxxFrame user URL link frame
2876  * @param fields the fields are appended to this list
2877  *
2878  * @return text representation of fields (Text or URL).
2879  */
2880 QString getFieldsFromUserUrlFrame(
2881   const TagLib::ID3v2::UserUrlLinkFrame* wxxxFrame, Frame::FieldList& fields)
2882 {
2883   Frame::Field field;
2884   field.m_id = Frame::ID_TextEnc;
2885   field.m_value = wxxxFrame->textEncoding();
2886   fields.push_back(field);
2887 
2888   field.m_id = Frame::ID_Description;
2889   field.m_value = toQString(wxxxFrame->description());
2890   fields.push_back(field);
2891 
2892   field.m_id = Frame::ID_Url;
2893   QString text = toQString(wxxxFrame->url());
2894   field.m_value = text;
2895   fields.push_back(field);
2896 
2897   return text;
2898 }
2899 
2900 /**
2901  * Get the fields from an unsynchronized lyrics frame.
2902  * This is copy-pasted from editCommFrame().
2903  *
2904  * @param usltFrame unsynchronized frame
2905  * @param fields the fields are appended to this list
2906  *
2907  * @return text representation of fields (Text or URL).
2908  */
2909 QString getFieldsFromUsltFrame(
2910   const TagLib::ID3v2::UnsynchronizedLyricsFrame* usltFrame,
2911   Frame::FieldList& fields)
2912 {
2913   QString text;
2914   Frame::Field field;
2915   field.m_id = Frame::ID_TextEnc;
2916   field.m_value = usltFrame->textEncoding();
2917   fields.push_back(field);
2918 
2919   field.m_id = Frame::ID_Language;
2920   TagLib::ByteVector bvLang = usltFrame->language();
2921   field.m_value = QString::fromLatin1(QByteArray(bvLang.data(), bvLang.size()));
2922   fields.push_back(field);
2923 
2924   field.m_id = Frame::ID_Description;
2925   field.m_value = toQString(usltFrame->description());
2926   fields.push_back(field);
2927 
2928   field.m_id = Frame::ID_Text;
2929   text = toQString(usltFrame->toString());
2930   field.m_value = text;
2931   fields.push_back(field);
2932 
2933   return text;
2934 }
2935 
2936 /**
2937  * Get the fields from a synchronized lyrics frame.
2938  *
2939  * @param syltFrame synchronized lyrics frame
2940  * @param fields the fields are appended to this list
2941  *
2942  * @return text representation of fields (Text or URL).
2943  */
2944 QString getFieldsFromSyltFrame(
2945   const TagLib::ID3v2::SynchronizedLyricsFrame* syltFrame,
2946   Frame::FieldList& fields)
2947 {
2948   QString text;
2949   Frame::Field field;
2950   field.m_id = Frame::ID_TextEnc;
2951   field.m_value = syltFrame->textEncoding();
2952   fields.push_back(field);
2953 
2954   field.m_id = Frame::ID_Language;
2955   TagLib::ByteVector bvLang = syltFrame->language();
2956   field.m_value = QString::fromLatin1(QByteArray(bvLang.data(), bvLang.size()));
2957   fields.push_back(field);
2958 
2959   field.m_id = Frame::ID_TimestampFormat;
2960   field.m_value = syltFrame->timestampFormat();
2961   fields.push_back(field);
2962 
2963   field.m_id = Frame::ID_ContentType;
2964   field.m_value = syltFrame->type();
2965   fields.push_back(field);
2966 
2967   field.m_id = Frame::ID_Description;
2968   text = toQString(syltFrame->description());
2969   field.m_value = text;
2970   fields.push_back(field);
2971 
2972   field.m_id = Frame::ID_Data;
2973   QVariantList synchedData;
2974   const TagLib::ID3v2::SynchronizedLyricsFrame::SynchedTextList stl =
2975       syltFrame->synchedText();
2976   for (auto it = stl.begin(); it != stl.end(); ++it) {
2977     synchedData.append(static_cast<quint32>(it->time));
2978     synchedData.append(toQString(it->text));
2979   }
2980   field.m_value = synchedData;
2981   fields.push_back(field);
2982 
2983   return text;
2984 }
2985 
2986 /**
2987  * Get the fields from an event timing codes frame.
2988  *
2989  * @param etcoFrame event timing codes frame
2990  * @param fields the fields are appended to this list
2991  *
2992  * @return text representation of fields (Text or URL).
2993  */
2994 QString getFieldsFromEtcoFrame(
2995   const TagLib::ID3v2::EventTimingCodesFrame* etcoFrame,
2996   Frame::FieldList& fields)
2997 {
2998   Frame::Field field;
2999   field.m_id = Frame::ID_TimestampFormat;
3000   field.m_value = etcoFrame->timestampFormat();
3001   fields.push_back(field);
3002 
3003   field.m_id = Frame::ID_Data;
3004   QVariantList synchedData;
3005   const TagLib::ID3v2::EventTimingCodesFrame::SynchedEventList sel =
3006       etcoFrame->synchedEvents();
3007   for (auto it = sel.begin(); it != sel.end(); ++it) {
3008     synchedData.append(static_cast<quint32>(it->time));
3009     synchedData.append(static_cast<int>(it->type));
3010   }
3011   field.m_value = synchedData;
3012   fields.push_back(field);
3013 
3014   return QString();
3015 }
3016 
3017 /**
3018  * Get the fields from a private frame.
3019  *
3020  * @param privFrame private frame
3021  * @param fields the fields are appended to this list
3022  *
3023  * @return text representation of fields (Text or URL).
3024  */
3025 QString getFieldsFromPrivFrame(
3026   const TagLib::ID3v2::PrivateFrame* privFrame,
3027   Frame::FieldList& fields)
3028 {
3029   Frame::Field field;
3030   field.m_id = Frame::ID_Owner;
3031   QString owner = toQString(privFrame->owner());
3032   field.m_value = owner;
3033   fields.push_back(field);
3034 
3035   field.m_id = Frame::ID_Data;
3036   TagLib::ByteVector data = privFrame->data();
3037   auto ba = QByteArray(data.data(), data.size());
3038   field.m_value = ba;
3039   fields.push_back(field);
3040 
3041   if (!owner.isEmpty() && !ba.isEmpty()) {
3042     if (QString str; AttributeData(owner).toString(ba, str)) {
3043       return str;
3044     }
3045   }
3046   return QString();
3047 }
3048 
3049 /**
3050  * Get the fields from a popularimeter frame.
3051  *
3052  * @param popmFrame popularimeter frame
3053  * @param fields the fields are appended to this list
3054  *
3055  * @return text representation of fields (Text or URL).
3056  */
3057 QString getFieldsFromPopmFrame(
3058   const TagLib::ID3v2::PopularimeterFrame* popmFrame,
3059   Frame::FieldList& fields)
3060 {
3061   Frame::Field field;
3062   field.m_id = Frame::ID_Email;
3063   field.m_value = toQString(popmFrame->email());
3064   fields.push_back(field);
3065 
3066   field.m_id = Frame::ID_Rating;
3067   field.m_value = popmFrame->rating();
3068   QString text(field.m_value.toString());
3069   fields.push_back(field);
3070 
3071   field.m_id = Frame::ID_Counter;
3072   field.m_value = popmFrame->counter();
3073   fields.push_back(field);
3074 
3075   return text;
3076 }
3077 
3078 /**
3079  * Get the fields from an ownership frame.
3080  *
3081  * @param owneFrame ownership frame
3082  * @param fields the fields are appended to this list
3083  *
3084  * @return text representation of fields (Text or URL).
3085  */
3086 QString getFieldsFromOwneFrame(
3087   const TagLib::ID3v2::OwnershipFrame* owneFrame,
3088   Frame::FieldList& fields)
3089 {
3090   Frame::Field field;
3091   field.m_id = Frame::ID_TextEnc;
3092   field.m_value = owneFrame->textEncoding();
3093   fields.push_back(field);
3094 
3095   field.m_id = Frame::ID_Date;
3096   field.m_value = toQString(owneFrame->datePurchased());
3097   fields.push_back(field);
3098 
3099   field.m_id = Frame::ID_Price;
3100   field.m_value = toQString(owneFrame->pricePaid());
3101   fields.push_back(field);
3102 
3103   field.m_id = Frame::ID_Seller;
3104   QString text(toQString(owneFrame->seller()));
3105   field.m_value = text;
3106   fields.push_back(field);
3107 
3108   return text;
3109 }
3110 
3111 /**
3112  * Get a string representation of the data in an RVA2 frame.
3113  * @param rva2Frame RVA2 frame
3114  * @return string containing lines with space separated values for
3115  * type of channel, volume adjustment, bits representing peak,
3116  * peak volume. The peak volume is a hex byte array, the other values
3117  * are integers, the volume adjustment is signed. Bits representing peak
3118  * and peak volume are omitted if they have zero bits.
3119  */
3120 QString rva2FrameToString(
3121     const TagLib::ID3v2::RelativeVolumeFrame* rva2Frame)
3122 {
3123   QString text;
3124   const TagLib::List<TagLib::ID3v2::RelativeVolumeFrame::ChannelType> channels =
3125       rva2Frame->channels();
3126   for (auto it = channels.begin(); it != channels.end(); ++it) {
3127     TagLib::ID3v2::RelativeVolumeFrame::ChannelType type = *it;
3128     if (!text.isEmpty()) {
3129       text += QLatin1Char('\n');
3130     }
3131     short adj = rva2Frame->volumeAdjustmentIndex(type);
3132     TagLib::ID3v2::RelativeVolumeFrame::PeakVolume peak =
3133         rva2Frame->peakVolume(type);
3134     text += QString::number(type);
3135     text += QLatin1Char(' ');
3136     text += QString::number(adj);
3137     if (peak.bitsRepresentingPeak > 0) {
3138       text += QLatin1Char(' ');
3139       text += QString::number(peak.bitsRepresentingPeak);
3140       text += QLatin1Char(' ');
3141       text += QString::fromLatin1(
3142             QByteArray(peak.peakVolume.data(), peak.peakVolume.size()).toHex());
3143     }
3144   }
3145   return text;
3146 }
3147 
3148 /**
3149  * Set the data in an RVA2 frame from a string representation.
3150  * @param rva2Frame RVA2 frame to set
3151  * @param text string representation
3152  * @see rva2FrameToString()
3153  */
3154 void rva2FrameFromString(TagLib::ID3v2::RelativeVolumeFrame* rva2Frame,
3155                          const TagLib::String& text)
3156 {
3157   // Unfortunately, it is not possible to remove data for a specific channel.
3158   // Only the whole frame could be deleted and a new one created.
3159   const auto lines = toQString(text).split(QLatin1Char('\n'));
3160   for (const QString& line : lines) {
3161     if (QStringList strs = line.split(QLatin1Char(' ')); strs.size() > 1) {
3162       bool ok;
3163       if (int typeInt = strs.at(0).toInt(&ok);
3164           ok && typeInt >= 0 && typeInt <= 8) {
3165         short adj = strs.at(1).toShort(&ok);
3166         if (ok) {
3167           auto type =
3168               static_cast<TagLib::ID3v2::RelativeVolumeFrame::ChannelType>(
3169                 typeInt);
3170           rva2Frame->setVolumeAdjustmentIndex(adj, type);
3171           if (strs.size() > 3) {
3172             int bitsInt = strs.at(2).toInt(&ok);
3173             if (QByteArray ba = QByteArray::fromHex(strs.at(3).toLatin1());
3174                 ok && bitsInt > 0 && bitsInt <= 255 &&
3175                 bitsInt <= ba.size() * 8) {
3176               TagLib::ID3v2::RelativeVolumeFrame::PeakVolume peak;
3177               peak.bitsRepresentingPeak = bitsInt;
3178               peak.peakVolume.setData(ba.constData(), ba.size());
3179               rva2Frame->setPeakVolume(peak, type);
3180             }
3181           }
3182         }
3183       }
3184     }
3185   }
3186 }
3187 
3188 /**
3189  * Get the fields from a relative volume frame.
3190  *
3191  * @param rva2Frame relative volume frame
3192  * @param fields the fields are appended to this list
3193  *
3194  * @return text representation of fields (Text or URL).
3195  */
3196 QString getFieldsFromRva2Frame(
3197   const TagLib::ID3v2::RelativeVolumeFrame* rva2Frame,
3198   Frame::FieldList& fields)
3199 {
3200   Frame::Field field;
3201   field.m_id = Frame::ID_Id;
3202   field.m_value = toQString(rva2Frame->identification());
3203   fields.push_back(field);
3204 
3205   QString text = rva2FrameToString(rva2Frame);
3206   field.m_id = Frame::ID_Text;
3207   field.m_value = text;
3208   fields.push_back(field);
3209   return text;
3210 }
3211 
3212 #if TAGLIB_VERSION >= 0x010a00
3213 Frame createFrameFromId3Frame(const TagLib::ID3v2::Frame* id3Frame, int index);
3214 
3215 /**
3216  * Get the fields from a chapter frame.
3217  *
3218  * @param chapFrame chapter frame
3219  * @param fields the fields are appended to this list
3220  *
3221  * @return text representation of fields (Text or URL).
3222  */
3223 QString getFieldsFromChapFrame(
3224   const TagLib::ID3v2::ChapterFrame* chapFrame,
3225   Frame::FieldList& fields)
3226 {
3227   Frame::Field field;
3228   field.m_id = Frame::ID_Id;
3229   QString text = toQString(
3230         TagLib::String(chapFrame->elementID(), TagLib::String::Latin1));
3231   field.m_value = text;
3232   fields.push_back(field);
3233 
3234   field.m_id = Frame::ID_Data;
3235   QVariantList data;
3236   data.append(chapFrame->startTime());
3237   data.append(chapFrame->endTime());
3238   data.append(chapFrame->startOffset());
3239   data.append(chapFrame->endOffset());
3240   field.m_value = data;
3241   fields.push_back(field);
3242 
3243   field.m_id = Frame::ID_Subframe;
3244   const TagLib::ID3v2::FrameList& frameList = chapFrame->embeddedFrameList();
3245   for (auto it = frameList.begin();
3246        it != frameList.end();
3247        ++it) {
3248     Frame frame(createFrameFromId3Frame(*it, -1));
3249     field.m_value = frame.getExtendedType().getName();
3250     fields.push_back(field);
3251     fields.append(frame.getFieldList());
3252   }
3253 
3254   return text;
3255 }
3256 
3257 /**
3258  * Get the fields from a table of contents frame.
3259  *
3260  * @param ctocFrame table of contents frame
3261  * @param fields the fields are appended to this list
3262  *
3263  * @return text representation of fields (Text or URL).
3264  */
3265 QString getFieldsFromCtocFrame(
3266   const TagLib::ID3v2::TableOfContentsFrame* ctocFrame,
3267   Frame::FieldList& fields)
3268 {
3269   Frame::Field field;
3270   field.m_id = Frame::ID_Id;
3271   QString text = toQString(
3272         TagLib::String(ctocFrame->elementID(), TagLib::String::Latin1));
3273   field.m_value = text;
3274   fields.push_back(field);
3275 
3276   field.m_id = Frame::ID_Data;
3277   QVariantList data;
3278   data.append(ctocFrame->isTopLevel());
3279   data.append(ctocFrame->isOrdered());
3280   QStringList elements;
3281   const TagLib::ByteVectorList childElements = ctocFrame->childElements();
3282   for (auto it = childElements.begin(); it != childElements.end(); ++it) {
3283     elements.append(toQString(TagLib::String(*it, TagLib::String::Latin1)));
3284   }
3285   data.append(elements);
3286   field.m_value = data;
3287   fields.push_back(field);
3288 
3289   field.m_id = Frame::ID_Subframe;
3290   const TagLib::ID3v2::FrameList& frameList = ctocFrame->embeddedFrameList();
3291   for (auto it = frameList.begin();
3292        it != frameList.end();
3293        ++it) {
3294     Frame frame(createFrameFromId3Frame(*it, -1));
3295     field.m_value = frame.getExtendedType().getName();
3296     fields.push_back(field);
3297     fields.append(frame.getFieldList());
3298   }
3299 
3300   return text;
3301 }
3302 #endif
3303 
3304 /**
3305  * Get the fields from an unknown frame.
3306  *
3307  * @param unknownFrame unknown frame
3308  * @param fields the fields are appended to this list
3309  *
3310  * @return text representation of fields (Text or URL).
3311  */
3312 QString getFieldsFromUnknownFrame(
3313   const TagLib::ID3v2::Frame* unknownFrame, Frame::FieldList& fields)
3314 {
3315   Frame::Field field;
3316   field.m_id = Frame::ID_Data;
3317   TagLib::ByteVector dat = unknownFrame->render();
3318   auto ba = QByteArray(dat.data(), dat.size());
3319   field.m_value = ba;
3320   fields.push_back(field);
3321   return QString();
3322 }
3323 
3324 /**
3325  * Get the fields from an ID3v2 tag.
3326  *
3327  * @param frame  frame
3328  * @param fields the fields are appended to this list
3329  * @param type   frame type
3330  *
3331  * @return text representation of fields (Text or URL).
3332  */
3333 QString getFieldsFromId3Frame(const TagLib::ID3v2::Frame* frame,
3334                               Frame::FieldList& fields, Frame::Type type)
3335 {
3336   if (frame) {
3337     if (const TagLib::ID3v2::TextIdentificationFrame* tFrame;
3338         (tFrame =
3339           dynamic_cast<const TagLib::ID3v2::TextIdentificationFrame*>(frame)) !=
3340           nullptr) {
3341       return getFieldsFromTextFrame(tFrame, fields, type);
3342     }
3343     if (const TagLib::ID3v2::AttachedPictureFrame* apicFrame;
3344         (apicFrame =
3345           dynamic_cast<const TagLib::ID3v2::AttachedPictureFrame*>(frame))
3346           != nullptr) {
3347       return getFieldsFromApicFrame(apicFrame, fields);
3348     }
3349     if (const TagLib::ID3v2::CommentsFrame* commFrame;
3350         (commFrame = dynamic_cast<const TagLib::ID3v2::CommentsFrame*>(
3351            frame)) != nullptr) {
3352       return getFieldsFromCommFrame(commFrame, fields);
3353     }
3354     if (const TagLib::ID3v2::UniqueFileIdentifierFrame* ufidFrame;
3355         (ufidFrame =
3356            dynamic_cast<const TagLib::ID3v2::UniqueFileIdentifierFrame*>(
3357              frame)) != nullptr) {
3358       return getFieldsFromUfidFrame(ufidFrame, fields);
3359     }
3360     if (const TagLib::ID3v2::GeneralEncapsulatedObjectFrame* geobFrame;
3361         (geobFrame =
3362            dynamic_cast<const TagLib::ID3v2::GeneralEncapsulatedObjectFrame*>(
3363              frame)) != nullptr) {
3364       return getFieldsFromGeobFrame(geobFrame, fields);
3365     }
3366     if (const TagLib::ID3v2::UserUrlLinkFrame* wxxxFrame;
3367         (wxxxFrame = dynamic_cast<const TagLib::ID3v2::UserUrlLinkFrame*>(
3368            frame)) != nullptr) {
3369       return getFieldsFromUserUrlFrame(wxxxFrame, fields);
3370     }
3371     if (const TagLib::ID3v2::UrlLinkFrame* wFrame;
3372         (wFrame = dynamic_cast<const TagLib::ID3v2::UrlLinkFrame*>(
3373            frame)) != nullptr) {
3374       return getFieldsFromUrlFrame(wFrame, fields);
3375     }
3376     if (const TagLib::ID3v2::UnsynchronizedLyricsFrame* usltFrame;
3377         (usltFrame = dynamic_cast<const TagLib::ID3v2::UnsynchronizedLyricsFrame*>(
3378            frame)) != nullptr) {
3379       return getFieldsFromUsltFrame(usltFrame, fields);
3380     }
3381     if (const TagLib::ID3v2::SynchronizedLyricsFrame* syltFrame;
3382         (syltFrame = dynamic_cast<const TagLib::ID3v2::SynchronizedLyricsFrame*>(
3383            frame)) != nullptr) {
3384       return getFieldsFromSyltFrame(syltFrame, fields);
3385     }
3386     if (const TagLib::ID3v2::EventTimingCodesFrame* etcoFrame;
3387         (etcoFrame = dynamic_cast<const TagLib::ID3v2::EventTimingCodesFrame*>(
3388            frame)) != nullptr) {
3389       return getFieldsFromEtcoFrame(etcoFrame, fields);
3390     }
3391     if (const TagLib::ID3v2::PrivateFrame* privFrame;
3392         (privFrame = dynamic_cast<const TagLib::ID3v2::PrivateFrame*>(
3393            frame)) != nullptr) {
3394       return getFieldsFromPrivFrame(privFrame, fields);
3395     }
3396     if (const TagLib::ID3v2::PopularimeterFrame* popmFrame;
3397         (popmFrame = dynamic_cast<const TagLib::ID3v2::PopularimeterFrame*>(
3398            frame)) != nullptr) {
3399       return getFieldsFromPopmFrame(popmFrame, fields);
3400     }
3401     if (const TagLib::ID3v2::OwnershipFrame* owneFrame;
3402         (owneFrame = dynamic_cast<const TagLib::ID3v2::OwnershipFrame*>(
3403            frame)) != nullptr) {
3404       return getFieldsFromOwneFrame(owneFrame, fields);
3405     }
3406     if (const TagLib::ID3v2::RelativeVolumeFrame* rva2Frame;
3407         (rva2Frame = dynamic_cast<const TagLib::ID3v2::RelativeVolumeFrame*>(
3408            frame)) != nullptr) {
3409       return getFieldsFromRva2Frame(rva2Frame, fields);
3410     }
3411 #if TAGLIB_VERSION >= 0x010a00
3412     if (const TagLib::ID3v2::ChapterFrame* chapFrame;
3413         (chapFrame = dynamic_cast<const TagLib::ID3v2::ChapterFrame*>(
3414            frame)) != nullptr) {
3415       return getFieldsFromChapFrame(chapFrame, fields);
3416     }
3417     if (const TagLib::ID3v2::TableOfContentsFrame* ctocFrame;
3418         (ctocFrame = dynamic_cast<const TagLib::ID3v2::TableOfContentsFrame*>(
3419            frame)) != nullptr) {
3420       return getFieldsFromCtocFrame(ctocFrame, fields);
3421     }
3422 #endif
3423     TagLib::ByteVector id = frame->frameID();
3424 #if TAGLIB_VERSION < 0x010a00
3425     if (id.startsWith("SYLT")) {
3426       TagLib::ID3v2::SynchronizedLyricsFrame syltFrm(frame->render());
3427       return getFieldsFromSyltFrame(&syltFrm, fields);
3428     }
3429     if (id.startsWith("ETCO")) {
3430       TagLib::ID3v2::EventTimingCodesFrame etcoFrm(frame->render());
3431       return getFieldsFromEtcoFrame(&etcoFrm, fields);
3432     }
3433 #endif
3434     return getFieldsFromUnknownFrame(frame, fields);
3435   }
3436   return QString();
3437 }
3438 
3439 /**
3440  * Convert a string to a language code byte vector.
3441  *
3442  * @param str string containing language code.
3443  *
3444  * @return 3 byte vector with language code.
3445  */
3446 TagLib::ByteVector languageCodeByteVector(QString str)
3447 {
3448   if (uint len = str.length(); len > 3) {
3449     str.truncate(3);
3450   } else if (len < 3) {
3451     for (uint i = len; i < 3; ++i) {
3452       str += QLatin1Char(' ');
3453     }
3454   }
3455   return TagLib::ByteVector(str.toLatin1().data(), str.length());
3456 }
3457 
3458 /**
3459  * The following template functions are used to uniformly set fields
3460  * in the different ID3v2 frames.
3461  */
3462 //! @cond
3463 template <class T>
3464 void setTextEncoding(T*, TagLib::String::Type) {}
3465 
3466 template <>
3467 void setTextEncoding(TagLib::ID3v2::TextIdentificationFrame* f,
3468                      TagLib::String::Type enc)
3469 {
3470   f->setTextEncoding(enc);
3471 }
3472 
3473 template <>
3474 void setTextEncoding(TagLib::ID3v2::UserTextIdentificationFrame* f,
3475                      TagLib::String::Type enc)
3476 {
3477   f->setTextEncoding(enc);
3478 }
3479 
3480 template <>
3481 void setTextEncoding(TagLib::ID3v2::AttachedPictureFrame* f,
3482                      TagLib::String::Type enc)
3483 {
3484   f->setTextEncoding(enc);
3485 }
3486 
3487 template <>
3488 void setTextEncoding(TagLib::ID3v2::CommentsFrame* f,
3489                      TagLib::String::Type enc)
3490 {
3491   f->setTextEncoding(enc);
3492 }
3493 
3494 template <>
3495 void setTextEncoding(TagLib::ID3v2::GeneralEncapsulatedObjectFrame* f,
3496                      TagLib::String::Type enc)
3497 {
3498   f->setTextEncoding(enc);
3499 }
3500 
3501 template <>
3502 void setTextEncoding(TagLib::ID3v2::UserUrlLinkFrame* f,
3503                      TagLib::String::Type enc)
3504 {
3505   f->setTextEncoding(enc);
3506 }
3507 
3508 template <>
3509 void setTextEncoding(TagLib::ID3v2::UnsynchronizedLyricsFrame* f,
3510                      TagLib::String::Type enc)
3511 {
3512   f->setTextEncoding(enc);
3513 }
3514 
3515 template <>
3516 void setTextEncoding(TagLib::ID3v2::SynchronizedLyricsFrame* f,
3517                      TagLib::String::Type enc)
3518 {
3519   f->setTextEncoding(enc);
3520 }
3521 
3522 
3523 template <class T>
3524 void setDescription(T*, const Frame::Field&) {}
3525 
3526 template <>
3527 void setDescription(TagLib::ID3v2::UserTextIdentificationFrame* f,
3528                     const Frame::Field& fld)
3529 {
3530   f->setDescription(toTString(fld.m_value.toString()));
3531 }
3532 
3533 template <>
3534 void setDescription(TagLib::ID3v2::AttachedPictureFrame* f,
3535                     const Frame::Field& fld)
3536 {
3537   f->setDescription(toTString(fld.m_value.toString()));
3538 }
3539 
3540 template <>
3541 void setDescription(TagLib::ID3v2::CommentsFrame* f, const Frame::Field& fld)
3542 {
3543   f->setDescription(toTString(fld.m_value.toString()));
3544 }
3545 
3546 template <>
3547 void setDescription(TagLib::ID3v2::GeneralEncapsulatedObjectFrame* f,
3548                     const Frame::Field& fld)
3549 {
3550   f->setDescription(toTString(fld.m_value.toString()));
3551 }
3552 
3553 template <>
3554 void setDescription(TagLib::ID3v2::UserUrlLinkFrame* f, const Frame::Field& fld)
3555 {
3556   f->setDescription(toTString(fld.m_value.toString()));
3557 }
3558 
3559 template <>
3560 void setDescription(TagLib::ID3v2::UnsynchronizedLyricsFrame* f,
3561                     const Frame::Field& fld)
3562 {
3563   f->setDescription(toTString(fld.m_value.toString()));
3564 }
3565 
3566 template <>
3567 void setDescription(TagLib::ID3v2::SynchronizedLyricsFrame* f,
3568                     const Frame::Field& fld)
3569 {
3570   f->setDescription(toTString(fld.m_value.toString()));
3571 }
3572 
3573 template <class T>
3574 void setMimeType(T*, const Frame::Field&) {}
3575 
3576 template <>
3577 void setMimeType(TagLib::ID3v2::AttachedPictureFrame* f,
3578                  const Frame::Field& fld)
3579 {
3580   f->setMimeType(toTString(fld.m_value.toString()));
3581 }
3582 
3583 template <>
3584 void setMimeType(TagLib::ID3v2::GeneralEncapsulatedObjectFrame* f,
3585                  const Frame::Field& fld)
3586 {
3587   f->setMimeType(toTString(fld.m_value.toString()));
3588 }
3589 
3590 template <class T>
3591 void setPictureType(T*, const Frame::Field&) {}
3592 
3593 template <>
3594 void setPictureType(TagLib::ID3v2::AttachedPictureFrame* f,
3595                     const Frame::Field& fld)
3596 {
3597   f->setType(
3598     static_cast<TagLib::ID3v2::AttachedPictureFrame::Type>(
3599       fld.m_value.toInt()));
3600 }
3601 
3602 template <class T>
3603 void setData(T*, const Frame::Field&) {}
3604 
3605 template <>
3606 void setData(TagLib::ID3v2::Frame* f, const Frame::Field& fld)
3607 {
3608   QByteArray ba(fld.m_value.toByteArray());
3609   f->setData(TagLib::ByteVector(ba.data(), ba.size()));
3610 }
3611 
3612 template <>
3613 void setData(TagLib::ID3v2::AttachedPictureFrame* f, const Frame::Field& fld)
3614 {
3615   QByteArray ba(fld.m_value.toByteArray());
3616   f->setPicture(TagLib::ByteVector(ba.data(), ba.size()));
3617 }
3618 
3619 template <>
3620 void setData(TagLib::ID3v2::GeneralEncapsulatedObjectFrame* f,
3621              const Frame::Field& fld)
3622 {
3623   QByteArray ba(fld.m_value.toByteArray());
3624   f->setObject(TagLib::ByteVector(ba.data(), ba.size()));
3625 }
3626 
3627 template <>
3628 void setData(TagLib::ID3v2::UniqueFileIdentifierFrame* f,
3629              const Frame::Field& fld)
3630 {
3631   QByteArray ba(fld.m_value.toByteArray());
3632   f->setIdentifier(TagLib::ByteVector(ba.data(), ba.size()));
3633 }
3634 
3635 template <>
3636 void setData(TagLib::ID3v2::SynchronizedLyricsFrame* f,
3637              const Frame::Field& fld)
3638 {
3639   TagLib::ID3v2::SynchronizedLyricsFrame::SynchedTextList stl;
3640   QVariantList synchedData(fld.m_value.toList());
3641   QListIterator it(synchedData);
3642   while (it.hasNext()) {
3643     quint32 time = it.next().toUInt();
3644     if (!it.hasNext())
3645       break;
3646 
3647     TagLib::String text = toTString(it.next().toString());
3648     stl.append(TagLib::ID3v2::SynchronizedLyricsFrame::SynchedText(time, text));
3649   }
3650   f->setSynchedText(stl);
3651 }
3652 
3653 template <>
3654 void setData(TagLib::ID3v2::EventTimingCodesFrame* f,
3655              const Frame::Field& fld)
3656 {
3657   TagLib::ID3v2::EventTimingCodesFrame::SynchedEventList sel;
3658   QVariantList synchedData(fld.m_value.toList());
3659   QListIterator it(synchedData);
3660   while (it.hasNext()) {
3661     quint32 time = it.next().toUInt();
3662     if (!it.hasNext())
3663       break;
3664 
3665     auto type =
3666         static_cast<TagLib::ID3v2::EventTimingCodesFrame::EventType>(
3667           it.next().toInt());
3668     sel.append(TagLib::ID3v2::EventTimingCodesFrame::SynchedEvent(time, type));
3669   }
3670   f->setSynchedEvents(sel);
3671 }
3672 
3673 template <class T>
3674 void setLanguage(T*, const Frame::Field&) {}
3675 
3676 template <>
3677 void setLanguage(TagLib::ID3v2::CommentsFrame* f, const Frame::Field& fld)
3678 {
3679   f->setLanguage(languageCodeByteVector(fld.m_value.toString()));
3680 }
3681 
3682 template <>
3683 void setLanguage(TagLib::ID3v2::UnsynchronizedLyricsFrame* f,
3684                  const Frame::Field& fld)
3685 {
3686   f->setLanguage(languageCodeByteVector(fld.m_value.toString()));
3687 }
3688 
3689 template <>
3690 void setLanguage(TagLib::ID3v2::SynchronizedLyricsFrame* f,
3691                  const Frame::Field& fld)
3692 {
3693   f->setLanguage(languageCodeByteVector(fld.m_value.toString()));
3694 }
3695 
3696 template <class T>
3697 void setOwner(T*, const Frame::Field&) {}
3698 
3699 template <>
3700 void setOwner(TagLib::ID3v2::UniqueFileIdentifierFrame* f,
3701               const Frame::Field& fld)
3702 {
3703   f->setOwner(toTString(fld.m_value.toString()));
3704 }
3705 
3706 template <>
3707 void setOwner(TagLib::ID3v2::PrivateFrame* f,
3708               const Frame::Field& fld)
3709 {
3710   f->setOwner(toTString(fld.m_value.toString()));
3711 }
3712 
3713 template <>
3714 void setData(TagLib::ID3v2::PrivateFrame* f,
3715              const Frame::Field& fld)
3716 {
3717   QByteArray ba(fld.m_value.toByteArray());
3718   f->setData(TagLib::ByteVector(ba.data(), ba.size()));
3719 }
3720 
3721 template <class T>
3722 void setIdentifier(T*, const Frame::Field&) {}
3723 
3724 template <>
3725 void setIdentifier(TagLib::ID3v2::UniqueFileIdentifierFrame* f,
3726                    const Frame::Field& fld)
3727 {
3728   QByteArray ba(fld.m_value.toByteArray());
3729   f->setIdentifier(TagLib::ByteVector(ba.data(), ba.size()));
3730 }
3731 
3732 template <class T>
3733 void setFilename(T*, const Frame::Field&) {}
3734 
3735 template <>
3736 void setFilename(TagLib::ID3v2::GeneralEncapsulatedObjectFrame* f,
3737                  const Frame::Field& fld)
3738 {
3739   f->setFileName(toTString(fld.m_value.toString()));
3740 }
3741 
3742 template <class T>
3743 void setUrl(T*, const Frame::Field&) {}
3744 
3745 template <>
3746 void setUrl(TagLib::ID3v2::UrlLinkFrame* f, const Frame::Field& fld)
3747 {
3748   f->setUrl(toTString(fld.m_value.toString()));
3749 }
3750 
3751 template <>
3752 void setUrl(TagLib::ID3v2::UserUrlLinkFrame* f, const Frame::Field& fld)
3753 {
3754   f->setUrl(toTString(fld.m_value.toString()));
3755 }
3756 
3757 template <class T>
3758 void setValue(T* f, const TagLib::String& text)
3759 {
3760   f->setText(text);
3761 }
3762 
3763 template <>
3764 void setValue(TagLib::ID3v2::AttachedPictureFrame* f, const TagLib::String& text)
3765 {
3766   f->setDescription(text);
3767 }
3768 
3769 template <>
3770 void setValue(TagLib::ID3v2::GeneralEncapsulatedObjectFrame* f,
3771               const TagLib::String& text)
3772 {
3773   f->setDescription(text);
3774 }
3775 
3776 void setStringOrList(TagLib::ID3v2::TextIdentificationFrame* f,
3777                      const TagLib::String& text)
3778 {
3779   if (text.find(Frame::stringListSeparator().toLatin1()) == -1) {
3780     f->setText(text);
3781   } else {
3782     f->setText(splitToTStringList(toQString(text)));
3783   }
3784 }
3785 
3786 template <>
3787 void setValue(TagLib::ID3v2::TextIdentificationFrame* f, const TagLib::String& text)
3788 {
3789   setStringOrList(f, text);
3790 }
3791 
3792 template <>
3793 void setValue(TagLib::ID3v2::UniqueFileIdentifierFrame* f, const TagLib::String& text)
3794 {
3795   if (AttributeData::isHexString(toQString(text), 'Z', QLatin1String("-"))) {
3796     TagLib::ByteVector data(text.data(TagLib::String::Latin1));
3797     data.append('\0');
3798     f->setIdentifier(data);
3799   }
3800 }
3801 
3802 template <>
3803 void setValue(TagLib::ID3v2::SynchronizedLyricsFrame* f, const TagLib::String& text)
3804 {
3805   f->setDescription(text);
3806 }
3807 
3808 template <>
3809 void setValue(TagLib::ID3v2::PrivateFrame* f, const TagLib::String& text)
3810 {
3811   QByteArray newData;
3812   if (TagLib::String owner = f->owner(); !owner.isEmpty() &&
3813       AttributeData(toQString(owner))
3814       .toByteArray(toQString(text), newData)) {
3815     f->setData(TagLib::ByteVector(newData.data(), newData.size()));
3816   }
3817 }
3818 
3819 template <>
3820 void setValue(TagLib::ID3v2::PopularimeterFrame* f, const TagLib::String& text)
3821 {
3822   f->setRating(text.toInt());
3823 }
3824 
3825 template <class T>
3826 void setText(T* f, const TagLib::String& text)
3827 {
3828   f->setText(text);
3829 }
3830 
3831 template <>
3832 void setText(TagLib::ID3v2::TextIdentificationFrame* f, const TagLib::String& text)
3833 {
3834   setStringOrList(f, text);
3835 }
3836 
3837 template <class T>
3838 void setEmail(T*, const Frame::Field&) {}
3839 
3840 template <>
3841 void setEmail(TagLib::ID3v2::PopularimeterFrame* f, const Frame::Field& fld)
3842 {
3843   f->setEmail(toTString(fld.m_value.toString()));
3844 }
3845 
3846 template <class T>
3847 void setRating(T*, const Frame::Field&) {}
3848 
3849 template <>
3850 void setRating(TagLib::ID3v2::PopularimeterFrame* f, const Frame::Field& fld)
3851 {
3852   f->setRating(fld.m_value.toInt());
3853 }
3854 
3855 template <class T>
3856 void setCounter(T*, const Frame::Field&) {}
3857 
3858 template <>
3859 void setCounter(TagLib::ID3v2::PopularimeterFrame* f, const Frame::Field& fld)
3860 {
3861   f->setCounter(fld.m_value.toUInt());
3862 }
3863 
3864 template <class T>
3865 void setDate(T*, const Frame::Field&) {}
3866 
3867 template <>
3868 void setDate(TagLib::ID3v2::OwnershipFrame* f, const Frame::Field& fld)
3869 {
3870   // The date string must have exactly 8 characters (should be YYYYMMDD)
3871   QString date(fld.m_value.toString().leftJustified(8, QLatin1Char(' '), true));
3872   f->setDatePurchased(toTString(date));
3873 }
3874 
3875 template <class T>
3876 void setPrice(T*, const Frame::Field&) {}
3877 
3878 template <>
3879 void setPrice(TagLib::ID3v2::OwnershipFrame* f, const Frame::Field& fld)
3880 {
3881   f->setPricePaid(toTString(fld.m_value.toString()));
3882 }
3883 
3884 template <class T>
3885 void setSeller(T*, const Frame::Field&) {}
3886 
3887 template <>
3888 void setSeller(TagLib::ID3v2::OwnershipFrame* f, const Frame::Field& fld)
3889 {
3890   f->setSeller(toTString(fld.m_value.toString()));
3891 }
3892 
3893 template <>
3894 void setTextEncoding(TagLib::ID3v2::OwnershipFrame* f,
3895                      TagLib::String::Type enc)
3896 {
3897   f->setTextEncoding(enc);
3898 }
3899 
3900 template <>
3901 void setValue(TagLib::ID3v2::OwnershipFrame* f, const TagLib::String& text)
3902 {
3903   f->setSeller(text);
3904 }
3905 
3906 template <class T>
3907 void setTimestampFormat(T*, const Frame::Field&) {}
3908 
3909 template <>
3910 void setTimestampFormat(TagLib::ID3v2::SynchronizedLyricsFrame* f,
3911                         const Frame::Field& fld)
3912 {
3913   f->setTimestampFormat(
3914         static_cast<TagLib::ID3v2::SynchronizedLyricsFrame::TimestampFormat>(
3915           fld.m_value.toInt()));
3916 }
3917 
3918 template <>
3919 void setTimestampFormat(TagLib::ID3v2::EventTimingCodesFrame* f,
3920                         const Frame::Field& fld)
3921 {
3922   f->setTimestampFormat(
3923         static_cast<TagLib::ID3v2::EventTimingCodesFrame::TimestampFormat>(
3924           fld.m_value.toInt()));
3925 }
3926 
3927 template <class T>
3928 void setContentType(T*, const Frame::Field&) {}
3929 
3930 template <>
3931 void setContentType(TagLib::ID3v2::SynchronizedLyricsFrame* f,
3932                     const Frame::Field& fld)
3933 {
3934   f->setType(static_cast<TagLib::ID3v2::SynchronizedLyricsFrame::Type>(
3935                fld.m_value.toInt()));
3936 }
3937 
3938 template <>
3939 void setIdentifier(TagLib::ID3v2::RelativeVolumeFrame* f,
3940                    const Frame::Field& fld)
3941 {
3942   f->setIdentification(toTString(fld.m_value.toString()));
3943 }
3944 
3945 template <>
3946 void setText(TagLib::ID3v2::RelativeVolumeFrame* f, const TagLib::String& text)
3947 {
3948   rva2FrameFromString(f, text);
3949 }
3950 
3951 template <>
3952 void setValue(TagLib::ID3v2::RelativeVolumeFrame* f, const TagLib::String& text)
3953 {
3954   rva2FrameFromString(f, text);
3955 }
3956 
3957 #if TAGLIB_VERSION >= 0x010a00
3958 TagLib::ID3v2::Frame* createId3FrameFromFrame(const TagLibFile* self,
3959                                               Frame& frame);
3960 
3961 template <>
3962 void setIdentifier(TagLib::ID3v2::ChapterFrame* f,
3963                    const Frame::Field& fld)
3964 {
3965   QByteArray id = fld.m_value.toString().toLatin1();
3966   f->setElementID(TagLib::ByteVector(id.constData(), id.size()));
3967 }
3968 
3969 template <>
3970 void setIdentifier(TagLib::ID3v2::TableOfContentsFrame* f,
3971                    const Frame::Field& fld)
3972 {
3973   QByteArray id = fld.m_value.toString().toLatin1();
3974   f->setElementID(TagLib::ByteVector(id.constData(), id.size()));
3975 }
3976 
3977 template <>
3978 void setValue(TagLib::ID3v2::ChapterFrame* f, const TagLib::String& text)
3979 {
3980   f->setElementID(text.data(TagLib::String::Latin1));
3981 }
3982 
3983 template <>
3984 void setValue(TagLib::ID3v2::TableOfContentsFrame* f, const TagLib::String& text)
3985 {
3986   f->setElementID(text.data(TagLib::String::Latin1));
3987 }
3988 
3989 template <>
3990 void setData(TagLib::ID3v2::ChapterFrame* f,
3991              const Frame::Field& fld)
3992 {
3993   if (QVariantList data(fld.m_value.toList()); data.size() == 4) {
3994     f->setStartTime(data.at(0).toUInt());
3995     f->setEndTime(data.at(1).toUInt());
3996     f->setStartOffset(data.at(2).toUInt());
3997     f->setEndOffset(data.at(3).toUInt());
3998   }
3999   // The embedded frames are deleted here because frames without subframes
4000   // do not have an ID_Subframe field and setSubframes() is not called.
4001   while (!f->embeddedFrameList().isEmpty()) {
4002     f->removeEmbeddedFrame(f->embeddedFrameList()[0]);
4003   }
4004   // f->removeEmbeddedFrame() calls erase() thereby invalidating an iterator
4005   // on f->embeddedFrameList(). The uncommented code below will therefore crash.
4006   // const TagLib::ID3v2::FrameList l = f->embeddedFrameList();
4007   // for (auto it = l.begin(); it != l.end(); ++it) {
4008   //   f->removeEmbeddedFrame(*it, true);
4009   // }
4010 }
4011 
4012 template <>
4013 void setData(TagLib::ID3v2::TableOfContentsFrame* f,
4014              const Frame::Field& fld)
4015 {
4016   if (QVariantList data(fld.m_value.toList()); data.size() >= 3) {
4017     f->setIsTopLevel(data.at(0).toBool());
4018     f->setIsOrdered(data.at(1).toBool());
4019     QStringList elementStrings = data.at(2).toStringList();
4020     TagLib::ByteVectorList elements;
4021     for (auto it = elementStrings.constBegin();
4022          it != elementStrings.constEnd();
4023          ++it) {
4024       QByteArray id = it->toLatin1();
4025       elements.append(TagLib::ByteVector(id.constData(), id.size()));
4026     }
4027     f->setChildElements(elements);
4028   }
4029   // The embedded frames are deleted here because frames without subframes
4030   // do not have an ID_Subframe field and setSubframes() is not called.
4031   while (!f->embeddedFrameList().isEmpty()) {
4032     f->removeEmbeddedFrame(f->embeddedFrameList()[0]);
4033   }
4034   // f->removeEmbeddedFrame() calls erase() thereby invalidating an iterator
4035   // on f->embeddedFrameList(). The uncommented code below will therefore crash.
4036   // const TagLib::ID3v2::FrameList l = f->embeddedFrameList();
4037   // for (auto it = l.begin(); it != l.end(); ++it) {
4038   //   f->removeEmbeddedFrame(*it, true);
4039   // }
4040 }
4041 
4042 template <class T>
4043 void setSubframes(const TagLibFile*, T*, Frame::FieldList::const_iterator, // clazy:exclude=function-args-by-ref
4044                   Frame::FieldList::const_iterator) {}
4045 
4046 template <>
4047 void setSubframes(const TagLibFile* self, TagLib::ID3v2::ChapterFrame* f,
4048                   Frame::FieldList::const_iterator begin, // clazy:exclude=function-args-by-ref
4049                   Frame::FieldList::const_iterator end) // clazy:exclude=function-args-by-ref
4050 {
4051   FrameCollection frames = FrameCollection::fromSubframes(begin, end);
4052   for (auto it = frames.begin(); it != frames.end(); ++it) {
4053     f->addEmbeddedFrame(createId3FrameFromFrame(self, const_cast<Frame&>(*it)));
4054   }
4055 }
4056 
4057 template <>
4058 void setSubframes(const TagLibFile* self, TagLib::ID3v2::TableOfContentsFrame* f,
4059                   Frame::FieldList::const_iterator begin, // clazy:exclude=function-args-by-ref
4060                   Frame::FieldList::const_iterator end) // clazy:exclude=function-args-by-ref
4061 {
4062   FrameCollection frames = FrameCollection::fromSubframes(begin, end);
4063   for (auto it = frames.begin(); it != frames.end(); ++it) {
4064     f->addEmbeddedFrame(createId3FrameFromFrame(self, const_cast<Frame&>(*it)));
4065   }
4066 }
4067 #endif
4068 
4069 //! @endcond
4070 
4071 /**
4072  * Set the fields in a TagLib ID3v2 frame.
4073  *
4074  * @param self   this TagLibFile instance
4075  * @param tFrame TagLib frame to set
4076  * @param frame  frame with field values
4077  */
4078 template <class T>
4079 void setTagLibFrame(const TagLibFile* self, T* tFrame, const Frame& frame)
4080 {
4081   // If value is changed or field list is empty,
4082   // set from value, else from FieldList.
4083   if (const Frame::FieldList& fieldList = frame.getFieldList();
4084       frame.isValueChanged() || fieldList.empty()) {
4085     QString text(frame.getValue());
4086     fixUpTagLibFrameValue(self, frame.getType(), text);
4087     setValue(tFrame, toTString(text));
4088     setTextEncoding(tFrame, getTextEncodingConfig(needsUnicode(text)));
4089   } else {
4090     for (auto fldIt = fieldList.constBegin(); fldIt != fieldList.constEnd(); ++fldIt) {
4091       switch (const Frame::Field& fld = *fldIt; fld.m_id) {
4092         case Frame::ID_Text:
4093         {
4094           QString value(fld.m_value.toString());
4095           fixUpTagLibFrameValue(self, frame.getType(), value);
4096           setText(tFrame, toTString(value));
4097           break;
4098         }
4099         case Frame::ID_TextEnc:
4100           setTextEncoding(tFrame, static_cast<TagLib::String::Type>(
4101                             fld.m_value.toInt()));
4102           break;
4103         case Frame::ID_Description:
4104           setDescription(tFrame, fld);
4105           break;
4106         case Frame::ID_MimeType:
4107           setMimeType(tFrame, fld);
4108           break;
4109         case Frame::ID_PictureType:
4110           setPictureType(tFrame, fld);
4111           break;
4112         case Frame::ID_Data:
4113           setData(tFrame, fld);
4114           break;
4115         case Frame::ID_Language:
4116           setLanguage(tFrame, fld);
4117           break;
4118         case Frame::ID_Owner:
4119           setOwner(tFrame, fld);
4120           break;
4121         case Frame::ID_Id:
4122           setIdentifier(tFrame, fld);
4123           break;
4124         case Frame::ID_Filename:
4125           setFilename(tFrame, fld);
4126           break;
4127         case Frame::ID_Url:
4128           setUrl(tFrame, fld);
4129           break;
4130         case Frame::ID_Email:
4131           setEmail(tFrame, fld);
4132           break;
4133         case Frame::ID_Rating:
4134           setRating(tFrame, fld);
4135           break;
4136         case Frame::ID_Counter:
4137           setCounter(tFrame, fld);
4138           break;
4139         case Frame::ID_Price:
4140           setPrice(tFrame, fld);
4141           break;
4142         case Frame::ID_Date:
4143           setDate(tFrame, fld);
4144           break;
4145         case Frame::ID_Seller:
4146           setSeller(tFrame, fld);
4147           break;
4148         case Frame::ID_TimestampFormat:
4149           setTimestampFormat(tFrame, fld);
4150           break;
4151         case Frame::ID_ContentType:
4152           setContentType(tFrame, fld);
4153           break;
4154 #if TAGLIB_VERSION >= 0x010a00
4155         case Frame::ID_Subframe:
4156           setSubframes(self, tFrame, fldIt, fieldList.end());
4157           return;
4158 #endif
4159       }
4160     }
4161   }
4162 }
4163 
4164 /**
4165  * Modify an ID3v2 frame.
4166  *
4167  * @param self     this TagLibFile instance
4168  * @param id3Frame original ID3v2 frame
4169  * @param frame    frame with fields to set in new frame
4170  */
4171 void setId3v2Frame(const TagLibFile* self,
4172   TagLib::ID3v2::Frame* id3Frame, const Frame& frame)
4173 {
4174   if (id3Frame) {
4175     if (TagLib::ID3v2::TextIdentificationFrame* tFrame;
4176         (tFrame =
4177          dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(id3Frame))
4178         != nullptr) {
4179       if (auto txxxFrame =
4180             dynamic_cast<TagLib::ID3v2::UserTextIdentificationFrame*>(id3Frame)) {
4181         setTagLibFrame(self, txxxFrame, frame);
4182       } else {
4183         setTagLibFrame(self, tFrame, frame);
4184       }
4185     } else if (TagLib::ID3v2::AttachedPictureFrame* apicFrame;
4186                (apicFrame =
4187                 dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(id3Frame))
4188                != nullptr) {
4189       setTagLibFrame(self, apicFrame, frame);
4190     } else if (TagLib::ID3v2::CommentsFrame* commFrame;
4191                (commFrame = dynamic_cast<TagLib::ID3v2::CommentsFrame*>(
4192                   id3Frame)) != nullptr) {
4193       setTagLibFrame(self, commFrame, frame);
4194     } else if (TagLib::ID3v2::UniqueFileIdentifierFrame* ufidFrame;
4195                (ufidFrame =
4196                 dynamic_cast<TagLib::ID3v2::UniqueFileIdentifierFrame*>(
4197                   id3Frame)) != nullptr) {
4198       setTagLibFrame(self, ufidFrame, frame);
4199     } else if (TagLib::ID3v2::GeneralEncapsulatedObjectFrame* geobFrame;
4200                (geobFrame =
4201                 dynamic_cast<TagLib::ID3v2::GeneralEncapsulatedObjectFrame*>(
4202                   id3Frame)) != nullptr) {
4203       setTagLibFrame(self, geobFrame, frame);
4204     } else if (TagLib::ID3v2::UserUrlLinkFrame* wxxxFrame;
4205                (wxxxFrame = dynamic_cast<TagLib::ID3v2::UserUrlLinkFrame*>(
4206                   id3Frame)) != nullptr) {
4207       setTagLibFrame(self, wxxxFrame, frame);
4208     } else if (TagLib::ID3v2::UrlLinkFrame* wFrame;
4209                (wFrame = dynamic_cast<TagLib::ID3v2::UrlLinkFrame*>(
4210                   id3Frame)) != nullptr) {
4211       setTagLibFrame(self, wFrame, frame);
4212     } else if (TagLib::ID3v2::UnsynchronizedLyricsFrame* usltFrame;
4213                (usltFrame =
4214                 dynamic_cast<TagLib::ID3v2::UnsynchronizedLyricsFrame*>(
4215                   id3Frame)) != nullptr) {
4216       setTagLibFrame(self, usltFrame, frame);
4217     } else if (TagLib::ID3v2::SynchronizedLyricsFrame* syltFrame;
4218                (syltFrame =
4219                 dynamic_cast<TagLib::ID3v2::SynchronizedLyricsFrame*>(
4220                   id3Frame)) != nullptr) {
4221       setTagLibFrame(self, syltFrame, frame);
4222     } else if (TagLib::ID3v2::EventTimingCodesFrame* etcoFrame;
4223                (etcoFrame =
4224                 dynamic_cast<TagLib::ID3v2::EventTimingCodesFrame*>(
4225                   id3Frame)) != nullptr) {
4226       setTagLibFrame(self, etcoFrame, frame);
4227     } else if (TagLib::ID3v2::PrivateFrame* privFrame;
4228                (privFrame = dynamic_cast<TagLib::ID3v2::PrivateFrame*>(
4229                   id3Frame)) != nullptr) {
4230       setTagLibFrame(self, privFrame, frame);
4231     } else if (TagLib::ID3v2::PopularimeterFrame* popmFrame;
4232                (popmFrame = dynamic_cast<TagLib::ID3v2::PopularimeterFrame*>(
4233                   id3Frame)) != nullptr) {
4234       setTagLibFrame(self, popmFrame, frame);
4235     } else if (TagLib::ID3v2::OwnershipFrame* owneFrame;
4236                (owneFrame = dynamic_cast<TagLib::ID3v2::OwnershipFrame*>(
4237                   id3Frame)) != nullptr) {
4238       setTagLibFrame(self, owneFrame, frame);
4239     } else if (TagLib::ID3v2::RelativeVolumeFrame* rva2Frame;
4240                (rva2Frame = dynamic_cast<TagLib::ID3v2::RelativeVolumeFrame*>(
4241                   id3Frame)) != nullptr) {
4242       setTagLibFrame(self, rva2Frame, frame);
4243 #if TAGLIB_VERSION >= 0x010a00
4244     } else if (TagLib::ID3v2::ChapterFrame* chapFrame;
4245                (chapFrame = dynamic_cast<TagLib::ID3v2::ChapterFrame*>(
4246                   id3Frame)) != nullptr) {
4247       setTagLibFrame(self, chapFrame, frame);
4248     } else if (TagLib::ID3v2::TableOfContentsFrame* ctocFrame;
4249                (ctocFrame = dynamic_cast<TagLib::ID3v2::TableOfContentsFrame*>(
4250                   id3Frame)) != nullptr) {
4251       setTagLibFrame(self, ctocFrame, frame);
4252 #endif
4253     } else {
4254       TagLib::ByteVector id(id3Frame->frameID());
4255       // create temporary objects for frames not known by TagLib,
4256       // an UnknownFrame copy will be created by the edit method.
4257 #if TAGLIB_VERSION < 0x010a00
4258       if (id.startsWith("SYLT")) {
4259         TagLib::ID3v2::SynchronizedLyricsFrame syltFrm(id3Frame->render());
4260         setTagLibFrame(self, &syltFrm, frame);
4261         id3Frame->setData(syltFrm.render());
4262       } else if (id.startsWith("ETCO")) {
4263         TagLib::ID3v2::EventTimingCodesFrame etcoFrm(id3Frame->render());
4264         setTagLibFrame(self, &etcoFrm, frame);
4265         id3Frame->setData(etcoFrm.render());
4266       } else
4267 #endif
4268       {
4269         setTagLibFrame(self, id3Frame, frame);
4270       }
4271     }
4272   }
4273 }
4274 
4275 /**
4276  * Get name of frame from type.
4277  *
4278  * @param type type
4279  *
4280  * @return name.
4281  */
4282 const char* getVorbisNameFromType(Frame::Type type)
4283 {
4284   static const char* const names[] = {
4285     "TITLE",           // FT_Title,
4286     "ARTIST",          // FT_Artist,
4287     "ALBUM",           // FT_Album,
4288     "COMMENT",         // FT_Comment,
4289     "DATE",            // FT_Date,
4290     "TRACKNUMBER",     // FT_Track,
4291     "GENRE",           // FT_Genre,
4292                        // FT_LastV1Frame = FT_Track,
4293     "ALBUMARTIST",     // FT_AlbumArtist,
4294     "ARRANGER",        // FT_Arranger,
4295     "AUTHOR",          // FT_Author,
4296     "BPM",             // FT_Bpm,
4297     "CATALOGNUMBER",   // FT_CatalogNumber,
4298     "COMPILATION",     // FT_Compilation,
4299     "COMPOSER",        // FT_Composer,
4300     "CONDUCTOR",       // FT_Conductor,
4301     "COPYRIGHT",       // FT_Copyright,
4302     "DISCNUMBER",      // FT_Disc,
4303     "ENCODED-BY",      // FT_EncodedBy,
4304     "ENCODERSETTINGS", // FT_EncoderSettings,
4305     "ENCODINGTIME",    // FT_EncodingTime,
4306     "GROUPING",        // FT_Grouping,
4307     "INITIALKEY",      // FT_InitialKey,
4308     "ISRC",            // FT_Isrc,
4309     "LANGUAGE",        // FT_Language,
4310     "LYRICIST",        // FT_Lyricist,
4311     "LYRICS",          // FT_Lyrics,
4312     "SOURCEMEDIA",     // FT_Media,
4313     "MOOD",            // FT_Mood,
4314     "ORIGINALALBUM",   // FT_OriginalAlbum,
4315     "ORIGINALARTIST",  // FT_OriginalArtist,
4316     "ORIGINALDATE",    // FT_OriginalDate,
4317     "DESCRIPTION",     // FT_Description,
4318     "PERFORMER",       // FT_Performer,
4319     "METADATA_BLOCK_PICTURE", // FT_Picture,
4320     "PUBLISHER",       // FT_Publisher,
4321     "RELEASECOUNTRY",  // FT_ReleaseCountry,
4322     "REMIXER",         // FT_Remixer,
4323     "ALBUMSORT",       // FT_SortAlbum,
4324     "ALBUMARTISTSORT", // FT_SortAlbumArtist,
4325     "ARTISTSORT",      // FT_SortArtist,
4326     "COMPOSERSORT",    // FT_SortComposer,
4327     "TITLESORT",       // FT_SortName,
4328     "SUBTITLE",        // FT_Subtitle,
4329     "WEBSITE",         // FT_Website,
4330     "WWWAUDIOFILE",    // FT_WWWAudioFile,
4331     "WWWAUDIOSOURCE",  // FT_WWWAudioSource,
4332     "RELEASEDATE",     // FT_ReleaseDate,
4333     "RATING",          // FT_Rating,
4334     "WORK"             // FT_Work,
4335                        // FT_Custom1
4336   };
4337   Q_STATIC_ASSERT(std::size(names) == Frame::FT_Custom1);
4338   if (type == Frame::FT_Picture &&
4339       TagConfig::instance().pictureNameIndex() == TagConfig::VP_COVERART) {
4340     return "COVERART";
4341   }
4342   if (Frame::isCustomFrameType(type)) {
4343     return Frame::getNameForCustomFrame(type);
4344   }
4345   return type <= Frame::FT_LastFrame ? names[type] : "UNKNOWN";
4346 }
4347 
4348 /**
4349  * Get the frame type for a Vorbis name.
4350  *
4351  * @param name Vorbis tag name
4352  *
4353  * @return frame type.
4354  */
4355 Frame::Type getTypeFromVorbisName(QString name)
4356 {
4357   static QMap<QString, int> strNumMap;
4358   if (strNumMap.empty()) {
4359     // first time initialization
4360     for (int i = 0; i < Frame::FT_Custom1; ++i) {
4361       auto type = static_cast<Frame::Type>(i);
4362       strNumMap.insert(QString::fromLatin1(getVorbisNameFromType(type)), type);
4363     }
4364     strNumMap.insert(QLatin1String("COVERART"), Frame::FT_Picture);
4365     strNumMap.insert(QLatin1String("METADATA_BLOCK_PICTURE"), Frame::FT_Picture);
4366   }
4367   if (auto it = strNumMap.constFind(name.remove(QLatin1Char('=')).toUpper());
4368       it != strNumMap.constEnd()) {
4369     return static_cast<Frame::Type>(*it);
4370   }
4371   return Frame::getTypeFromCustomFrameName(name.toLatin1());
4372 }
4373 
4374 /**
4375  * Get the frame type for an APE name.
4376  *
4377  * @param name APE tag name
4378  *
4379  * @return frame type.
4380  */
4381 Frame::Type getTypeFromApeName(const QString& name)
4382 {
4383   Frame::Type type = getTypeFromVorbisName(name);
4384   if (type == Frame::FT_Other) {
4385     if (name == QLatin1String("YEAR")) {
4386       type = Frame::FT_Date;
4387     } else if (name == QLatin1String("TRACK")) {
4388       type = Frame::FT_Track;
4389     } else if (name == QLatin1String("ENCODED BY")) {
4390       type = Frame::FT_EncodedBy;
4391     } else if (name.startsWith(QLatin1String("COVER ART"))) {
4392       type = Frame::FT_Picture;
4393     }
4394   }
4395   return type;
4396 }
4397 
4398 }
4399 
4400 /**
4401  * Get internal name of a Vorbis frame.
4402  *
4403  * @param frame frame
4404  *
4405  * @return Vorbis key.
4406  */
4407 QString TagLibFile::getVorbisName(const Frame& frame) const
4408 {
4409   if (Frame::Type type = frame.getType(); type == Frame::FT_Comment) {
4410     return getCommentFieldName();
4411   } else if (type <= Frame::FT_LastFrame) {
4412     return QString::fromLatin1(getVorbisNameFromType(type));
4413   }
4414   return fixUpTagKey(frame.getName(), TT_Vorbis).toUpper();
4415 }
4416 
4417 namespace {
4418 
4419 /**
4420  * Get internal name of an APE picture frame.
4421  *
4422  * @param pictureType picture type
4423  *
4424  * @return APE key.
4425  */
4426 TagLib::String getApePictureName(PictureFrame::PictureType pictureType)
4427 {
4428   TagLib::String name("COVER ART (");
4429   name += TagLib::String(PictureFrame::getPictureTypeString(pictureType))
4430       .upper();
4431   name += ')';
4432   return name;
4433 }
4434 
4435 /**
4436  * Get internal name of an APE frame.
4437  *
4438  * @param frame frame
4439  *
4440  * @return APE key.
4441  */
4442 QString getApeName(const Frame& frame)
4443 {
4444   if (Frame::Type type = frame.getType(); type == Frame::FT_Date) {
4445     return QLatin1String("YEAR");
4446   } else {
4447     if (type == Frame::FT_Track) {
4448       return QLatin1String("TRACK");
4449     }
4450     if (type == Frame::FT_Picture) {
4451       PictureFrame::PictureType pictureType;
4452       if (!PictureFrame::getPictureType(frame, pictureType)) {
4453         pictureType = Frame::PT_CoverFront;
4454       }
4455       return toQString(getApePictureName(pictureType));
4456     }
4457     if (type <= Frame::FT_LastFrame) {
4458       return QString::fromLatin1(getVorbisNameFromType(type));
4459     }
4460     return TaggedFile::fixUpTagKey(frame.getName(),
4461                                    TaggedFile::TT_Ape).toUpper();
4462   }
4463 }
4464 
4465 /** Type of data in MP4 frame. */
4466 enum Mp4ValueType {
4467   MVT_ByteArray,
4468   MVT_CoverArt,
4469   MVT_String,
4470   MVT_Bool,
4471   MVT_Int,
4472   MVT_IntPair,
4473   MVT_Byte,
4474   MVT_UInt,
4475   MVT_LongLong
4476 };
4477 
4478 /** MP4 name, frame type and value type. */
4479 struct Mp4NameTypeValue {
4480   const char* name;
4481   Frame::Type type;
4482   Mp4ValueType value;
4483 };
4484 
4485 /** Mapping between frame types and field names. */
4486 const Mp4NameTypeValue mp4NameTypeValues[] = {
4487   { "\251nam", Frame::FT_Title, MVT_String },
4488   { "\251ART", Frame::FT_Artist, MVT_String },
4489   { "\251wrt", Frame::FT_Composer, MVT_String },
4490   { "\251alb", Frame::FT_Album, MVT_String },
4491   { "\251day", Frame::FT_Date, MVT_String },
4492   { "\251enc", Frame::FT_EncodedBy, MVT_String },
4493   { "\251cmt", Frame::FT_Comment, MVT_String },
4494   { "gnre", Frame::FT_Genre, MVT_String },
4495   // (c)gen is after gnre so that it is used in the maps because TagLib uses it
4496   { "\251gen", Frame::FT_Genre, MVT_String },
4497   { "trkn", Frame::FT_Track, MVT_IntPair },
4498   { "disk", Frame::FT_Disc, MVT_IntPair },
4499   { "cpil", Frame::FT_Compilation, MVT_Bool },
4500   { "tmpo", Frame::FT_Bpm, MVT_Int },
4501   { "\251grp", Frame::FT_Grouping, MVT_String },
4502   { "aART", Frame::FT_AlbumArtist, MVT_String },
4503   { "pgap", Frame::FT_Other, MVT_Bool },
4504   { "cprt", Frame::FT_Copyright, MVT_String },
4505   { "\251lyr", Frame::FT_Lyrics, MVT_String },
4506   { "tvsh", Frame::FT_Other, MVT_String },
4507   { "tvnn", Frame::FT_Other, MVT_String },
4508   { "tven", Frame::FT_Other, MVT_String },
4509   { "tvsn", Frame::FT_Other, MVT_UInt },
4510   { "tves", Frame::FT_Other, MVT_UInt },
4511   { "desc", Frame::FT_Description, MVT_String },
4512   { "ldes", Frame::FT_Other, MVT_String },
4513   { "sonm", Frame::FT_SortName, MVT_String },
4514   { "soar", Frame::FT_SortArtist, MVT_String },
4515   { "soaa", Frame::FT_SortAlbumArtist, MVT_String },
4516   { "soal", Frame::FT_SortAlbum, MVT_String },
4517   { "soco", Frame::FT_SortComposer, MVT_String },
4518   { "sosn", Frame::FT_Other, MVT_String },
4519   { "\251too", Frame::FT_EncoderSettings, MVT_String },
4520   { "purd", Frame::FT_Other, MVT_String },
4521   { "pcst", Frame::FT_Other, MVT_Bool },
4522   { "keyw", Frame::FT_Other, MVT_String },
4523   { "catg", Frame::FT_Other, MVT_String },
4524 #if TAGLIB_VERSION >= 0x020000
4525   { "hdvd", Frame::FT_Other, MVT_UInt },
4526 #else
4527   { "hdvd", Frame::FT_Other, MVT_Bool },
4528 #endif
4529   { "stik", Frame::FT_Other, MVT_Byte },
4530   { "rtng", Frame::FT_Other, MVT_Byte },
4531   { "apID", Frame::FT_Other, MVT_String },
4532   { "akID", Frame::FT_Other, MVT_Byte },
4533   { "sfID", Frame::FT_Other, MVT_UInt },
4534   { "cnID", Frame::FT_Other, MVT_UInt },
4535   { "atID", Frame::FT_Other, MVT_UInt },
4536   { "plID", Frame::FT_Other, MVT_LongLong },
4537   { "geID", Frame::FT_Other, MVT_UInt },
4538   { "ownr", Frame::FT_Other, MVT_String },
4539 #if TAGLIB_VERSION >= 0x010c00
4540   { "purl", Frame::FT_Other, MVT_String },
4541   { "egid", Frame::FT_Other, MVT_String },
4542   { "cmID", Frame::FT_Other, MVT_UInt },
4543 #endif
4544   { "xid ", Frame::FT_Other, MVT_String },
4545   { "covr", Frame::FT_Picture, MVT_CoverArt },
4546 #if TAGLIB_VERSION >= 0x010c00
4547   { "\251wrk", Frame::FT_Work, MVT_String },
4548   { "\251mvn", Frame::FT_Other, MVT_String },
4549   { "\251mvi", Frame::FT_Other, MVT_Int },
4550   { "\251mvc", Frame::FT_Other, MVT_Int },
4551   { "shwm", Frame::FT_Other, MVT_Bool },
4552 #endif
4553   { "ARRANGER", Frame::FT_Arranger, MVT_String },
4554   { "AUTHOR", Frame::FT_Author, MVT_String },
4555   { "CATALOGNUMBER", Frame::FT_CatalogNumber, MVT_String },
4556   { "CONDUCTOR", Frame::FT_Conductor, MVT_String },
4557   { "ENCODINGTIME", Frame::FT_EncodingTime, MVT_String },
4558   { "INITIALKEY", Frame::FT_InitialKey, MVT_String },
4559   { "ISRC", Frame::FT_Isrc, MVT_String },
4560   { "LANGUAGE", Frame::FT_Language, MVT_String },
4561   { "LYRICIST", Frame::FT_Lyricist, MVT_String },
4562   { "MOOD", Frame::FT_Mood, MVT_String },
4563   { "SOURCEMEDIA", Frame::FT_Media, MVT_String },
4564   { "ORIGINALALBUM", Frame::FT_OriginalAlbum, MVT_String },
4565   { "ORIGINALARTIST", Frame::FT_OriginalArtist, MVT_String },
4566   { "ORIGINALDATE", Frame::FT_OriginalDate, MVT_String },
4567   { "PERFORMER", Frame::FT_Performer, MVT_String },
4568   { "PUBLISHER", Frame::FT_Publisher, MVT_String },
4569   { "RELEASECOUNTRY", Frame::FT_ReleaseCountry, MVT_String },
4570   { "REMIXER", Frame::FT_Remixer, MVT_String },
4571   { "SUBTITLE", Frame::FT_Subtitle, MVT_String },
4572   { "WEBSITE", Frame::FT_Website, MVT_String },
4573   { "WWWAUDIOFILE", Frame::FT_WWWAudioFile, MVT_String },
4574   { "WWWAUDIOSOURCE", Frame::FT_WWWAudioSource, MVT_String },
4575   { "RELEASEDATE", Frame::FT_ReleaseDate, MVT_String },
4576   { "rate", Frame::FT_Rating, MVT_String }
4577 };
4578 
4579 /**
4580  * Get MP4 name and value type for a frame type.
4581  *
4582  * @param type  frame type
4583  * @param name  the MP4 name is returned here
4584  * @param value the MP4 value type is returned here
4585  */
4586 void getMp4NameForType(Frame::Type type, TagLib::String& name,
4587                        Mp4ValueType& value)
4588 {
4589   static QMap<Frame::Type, unsigned> typeNameMap;
4590   if (typeNameMap.empty()) {
4591     // first time initialization
4592     for (unsigned i = 0; i < std::size(mp4NameTypeValues); ++i) {
4593       if (mp4NameTypeValues[i].type != Frame::FT_Other) {
4594         typeNameMap.insert(mp4NameTypeValues[i].type, i);
4595       }
4596     }
4597   }
4598   name = "";
4599   value = MVT_String;
4600   if (type != Frame::FT_Other) {
4601     if (auto it = typeNameMap.constFind(type); it != typeNameMap.constEnd()) {
4602       name = mp4NameTypeValues[*it].name;
4603       value = mp4NameTypeValues[*it].value;
4604     } else {
4605       if (auto customFrameName = Frame::getNameForCustomFrame(type);
4606           !customFrameName.isEmpty()) {
4607         name = TagLib::String(customFrameName.constData());
4608       }
4609     }
4610   }
4611 }
4612 
4613 /**
4614  * Get MP4 value type and frame type for an MP4 name.
4615  *
4616  * @param name  MP4 name
4617  * @param type  the frame type is returned here
4618  * @param value the MP4 value type is returned here
4619  *
4620  * @return true if free-form frame.
4621  */
4622 bool getMp4TypeForName(const TagLib::String& name, Frame::Type& type,
4623                        Mp4ValueType& value)
4624 {
4625   static QMap<TagLib::String, unsigned> nameTypeMap;
4626   if (nameTypeMap.empty()) {
4627     // first time initialization
4628     for (unsigned i = 0; i < std::size(mp4NameTypeValues); ++i) {
4629       nameTypeMap.insert(mp4NameTypeValues[i].name, i);
4630     }
4631   }
4632   if (auto it = nameTypeMap.constFind(name); it != nameTypeMap.constEnd()) {
4633     type = mp4NameTypeValues[*it].type;
4634     value = mp4NameTypeValues[*it].value;
4635     if (type == Frame::FT_Other) {
4636       type = Frame::getTypeFromCustomFrameName(name.toCString());
4637     }
4638     return name[0] >= 'A' && name[0] <= 'Z';
4639   }
4640   type = Frame::getTypeFromCustomFrameName(name.toCString());
4641   value = MVT_String;
4642   return true;
4643 }
4644 
4645 /**
4646  * Strip free form prefix from MP4 frame name.
4647  *
4648  * @param name MP4 frame name to be stripped
4649  */
4650 void stripMp4FreeFormName(TagLib::String& name)
4651 {
4652   if (name.startsWith("----")) {
4653     int nameStart = name.rfind(":");
4654     if (nameStart == -1) {
4655       nameStart = 5;
4656     } else {
4657       ++nameStart;
4658     }
4659     name = name.substr(nameStart);
4660 
4661     Frame::Type type;
4662     Mp4ValueType valueType;
4663     if (!getMp4TypeForName(name, type, valueType)) {
4664       // not detected as free form => mark with ':' as first character
4665       name = ':' + name;
4666     }
4667   }
4668 }
4669 
4670 /**
4671  * Prepend free form prefix to MP4 frame name.
4672  * Only names starting with a capital letter or ':' are prefixed.
4673  *
4674  * @param name MP4 frame name to be prefixed
4675  * @param mp4Tag tag to check for existing item
4676  */
4677 void prefixMp4FreeFormName(TagLib::String& name, const TagLib::MP4::Tag* mp4Tag)
4678 {
4679   if (
4680 #if TAGLIB_VERSION >= 0x010a00
4681       !mp4Tag->contains(name)
4682 #else
4683       !const_cast<TagLib::MP4::Tag*>(mp4Tag)->itemListMap().contains(name)
4684 #endif
4685       && ((!name.startsWith("----") &&
4686            !(name.length() == 4 &&
4687              (static_cast<char>(name[0]) == '\251' ||
4688               (name[0] >= 'a' && name[0] <= 'z')))) ||
4689 #if TAGLIB_VERSION >= 0x010a00
4690           mp4Tag->contains("----:com.apple.iTunes:" + name)
4691 #else
4692           const_cast<TagLib::MP4::Tag*>(mp4Tag)->itemListMap().contains(
4693             "----:com.apple.iTunes:" + name)
4694 #endif
4695           )
4696       ) {
4697     Frame::Type type;
4698     Mp4ValueType valueType;
4699     if (getMp4TypeForName(name, type, valueType)) {
4700       // free form
4701       if (name[0] == ':') name = name.substr(1);
4702       TagLib::String freeFormName = "----:com.apple.iTunes:" + name;
4703       if (unsigned int nameLen;
4704 #if TAGLIB_VERSION >= 0x010a00
4705           !mp4Tag->contains(freeFormName)
4706 #else
4707           !const_cast<TagLib::MP4::Tag*>(mp4Tag)->itemListMap().contains(
4708             freeFormName)
4709 #endif
4710           && (nameLen = name.length()) > 0) {
4711         // Not an iTunes free form name, maybe using another prefix
4712         // (such as "----:com.nullsoft.winamp:").
4713         // Search for a frame which ends with this name.
4714 #if TAGLIB_VERSION >= 0x010a00
4715         const TagLib::MP4::ItemMap& items = mp4Tag->itemMap();
4716 #else
4717         const TagLib::MP4::ItemListMap& items =
4718             const_cast<TagLib::MP4::Tag*>(mp4Tag)->itemListMap();
4719 #endif
4720         for (auto it = items.begin(); it != items.end(); ++it) {
4721           if (const TagLib::String& key = it->first;
4722               key.length() >= nameLen &&
4723               key.substr(key.length() - nameLen, nameLen) == name) {
4724             freeFormName = key;
4725             break;
4726           }
4727         }
4728       }
4729       name = freeFormName;
4730     }
4731   }
4732 }
4733 
4734 /**
4735  * Get an MP4 type for a frame.
4736  *
4737  * @param frame frame
4738  * @param name  the MP4 name is returned here
4739  * @param value the MP4 value type is returned here
4740  */
4741 void getMp4TypeForFrame(const Frame& frame, TagLib::String& name,
4742                         Mp4ValueType& value)
4743 {
4744   if (frame.getType() != Frame::FT_Other) {
4745     getMp4NameForType(frame.getType(), name, value);
4746     if (name.isEmpty()) {
4747       name = toTString(frame.getInternalName());
4748     }
4749   } else {
4750     Frame::Type type;
4751     name = toTString(TaggedFile::fixUpTagKey(frame.getInternalName(),
4752                                              TaggedFile::TT_Mp4));
4753     getMp4TypeForName(name, type, value);
4754   }
4755 }
4756 
4757 /**
4758  * Get an MP4 item for a frame.
4759  *
4760  * @param frame frame
4761  * @param name  the name for the item is returned here
4762  *
4763  * @return MP4 item, an invalid item is returned if not supported.
4764  */
4765 TagLib::MP4::Item getMp4ItemForFrame(const Frame& frame, TagLib::String& name)
4766 {
4767   Mp4ValueType valueType;
4768   getMp4TypeForFrame(frame, name, valueType);
4769   switch (valueType) {
4770     case MVT_String:
4771       return TagLib::MP4::Item(
4772         splitToTStringList(frame.getValue()));
4773     case MVT_Bool:
4774       return TagLib::MP4::Item(frame.getValue().toInt() != 0);
4775     case MVT_Int:
4776       return TagLib::MP4::Item(frame.getValue().toInt());
4777     case MVT_IntPair:
4778     {
4779       QString str1 = frame.getValue(), str2 = QLatin1String("0");
4780       if (int slashPos = str1.indexOf(QLatin1Char('/')); slashPos != -1) {
4781         str2 = str1.mid(slashPos + 1);
4782         str1.truncate(slashPos);
4783       }
4784       return TagLib::MP4::Item(str1.toInt(), str2.toInt());
4785     }
4786     case MVT_CoverArt:
4787     {
4788       QByteArray ba;
4789       TagLib::MP4::CoverArt::Format format = TagLib::MP4::CoverArt::JPEG;
4790       if (PictureFrame::getData(frame, ba)) {
4791         if (QString mimeType;
4792             PictureFrame::getMimeType(frame, mimeType) &&
4793             mimeType == QLatin1String("image/png")) {
4794           format = TagLib::MP4::CoverArt::PNG;
4795         }
4796       }
4797       TagLib::MP4::CoverArt coverArt(format,
4798                                      TagLib::ByteVector(ba.data(), ba.size()));
4799       TagLib::MP4::CoverArtList coverArtList;
4800       coverArtList.append(coverArt);
4801       return TagLib::MP4::Item(coverArtList);
4802     }
4803     case MVT_Byte:
4804       return TagLib::MP4::Item(static_cast<uchar>(frame.getValue().toInt()));
4805     case MVT_UInt:
4806       return TagLib::MP4::Item(frame.getValue().toUInt());
4807     case MVT_LongLong:
4808       return TagLib::MP4::Item(frame.getValue().toLongLong());
4809     case MVT_ByteArray:
4810     default:
4811       // binary data and album art are not handled by TagLib
4812       return TagLib::MP4::Item();
4813   }
4814 }
4815 
4816 }
4817 
4818 /**
4819  * Set a frame in an MP4 tag.
4820  * @param frame frame to set
4821  * @param mp4Tag MP4 tag
4822  */
4823 void TagLibFile::setMp4Frame(const Frame& frame, TagLib::MP4::Tag* mp4Tag)
4824 {
4825   TagLib::String name;
4826   if (TagLib::MP4::Item item = getMp4ItemForFrame(frame, name);
4827       item.isValid()) {
4828     if (int numTracks;
4829         name == "trkn" &&
4830         (numTracks = getTotalNumberOfTracksIfEnabled()) > 0) {
4831       if (auto [first, second] = item.toIntPair(); second == 0) {
4832         item = TagLib::MP4::Item(first, numTracks);
4833       }
4834     }
4835     prefixMp4FreeFormName(name, mp4Tag);
4836 #if TAGLIB_VERSION >= 0x010b01
4837     mp4Tag->setItem(name, item);
4838 #else
4839     mp4Tag->itemListMap()[name] = item;
4840 #endif
4841     markTagChanged(Frame::Tag_2, frame.getExtendedType());
4842   }
4843 }
4844 
4845 namespace {
4846 
4847 /** Indices of fixed ASF frames. */
4848 enum AsfFrameIndex {
4849   AFI_Title,
4850   AFI_Artist,
4851   AFI_Comment,
4852   AFI_Copyright,
4853   AFI_Rating,
4854   AFI_Attributes
4855 };
4856 
4857 /** ASF name, frame type and value type. */
4858 struct AsfNameTypeValue {
4859   const char* name;
4860   Frame::Type type;
4861   TagLib::ASF::Attribute::AttributeTypes value;
4862 };
4863 
4864 /** Mapping between frame types and field names. */
4865 const AsfNameTypeValue asfNameTypeValues[] = {
4866   { "Title", Frame::FT_Title, TagLib::ASF::Attribute::UnicodeType },
4867   { "Author", Frame::FT_Artist, TagLib::ASF::Attribute::UnicodeType },
4868   { "WM/AlbumTitle", Frame::FT_Album, TagLib::ASF::Attribute::UnicodeType },
4869   { "Description", Frame::FT_Comment, TagLib::ASF::Attribute::UnicodeType },
4870   { "WM/Year", Frame::FT_Date, TagLib::ASF::Attribute::UnicodeType },
4871   { "Copyright", Frame::FT_Copyright, TagLib::ASF::Attribute::UnicodeType },
4872   { "Rating Information", Frame::FT_Other, TagLib::ASF::Attribute::UnicodeType },
4873   { "WM/TrackNumber", Frame::FT_Track, TagLib::ASF::Attribute::UnicodeType },
4874   { "WM/Track", Frame::FT_Track, TagLib::ASF::Attribute::UnicodeType },
4875   { "WM/Genre", Frame::FT_Genre, TagLib::ASF::Attribute::UnicodeType },
4876   { "WM/GenreID", Frame::FT_Genre, TagLib::ASF::Attribute::UnicodeType },
4877   { "WM/AlbumArtist", Frame::FT_AlbumArtist, TagLib::ASF::Attribute::UnicodeType },
4878   { "WM/AlbumSortOrder", Frame::FT_SortAlbum, TagLib::ASF::Attribute::UnicodeType },
4879   { "WM/ArtistSortOrder", Frame::FT_SortArtist, TagLib::ASF::Attribute::UnicodeType },
4880   { "WM/TitleSortOrder", Frame::FT_SortName, TagLib::ASF::Attribute::UnicodeType },
4881   { "WM/Producer", Frame::FT_Arranger, TagLib::ASF::Attribute::UnicodeType },
4882   { "WM/BeatsPerMinute", Frame::FT_Bpm, TagLib::ASF::Attribute::UnicodeType },
4883   { "WM/Composer", Frame::FT_Composer, TagLib::ASF::Attribute::UnicodeType },
4884   { "WM/Conductor", Frame::FT_Conductor, TagLib::ASF::Attribute::UnicodeType },
4885   { "WM/PartOfSet", Frame::FT_Disc, TagLib::ASF::Attribute::UnicodeType },
4886   { "WM/EncodedBy", Frame::FT_EncodedBy, TagLib::ASF::Attribute::UnicodeType },
4887   { "WM/ContentGroupDescription", Frame::FT_Work, TagLib::ASF::Attribute::UnicodeType },
4888   { "WM/ISRC", Frame::FT_Isrc, TagLib::ASF::Attribute::UnicodeType },
4889   { "WM/Language", Frame::FT_Language, TagLib::ASF::Attribute::UnicodeType },
4890   { "WM/Writer", Frame::FT_Lyricist, TagLib::ASF::Attribute::UnicodeType },
4891   { "WM/Lyrics", Frame::FT_Lyrics, TagLib::ASF::Attribute::UnicodeType },
4892   { "WM/AudioSourceURL", Frame::FT_WWWAudioSource, TagLib::ASF::Attribute::UnicodeType },
4893   { "WM/OriginalAlbumTitle", Frame::FT_OriginalAlbum, TagLib::ASF::Attribute::UnicodeType },
4894   { "WM/OriginalArtist", Frame::FT_OriginalArtist, TagLib::ASF::Attribute::UnicodeType },
4895   { "WM/OriginalReleaseYear", Frame::FT_OriginalDate, TagLib::ASF::Attribute::UnicodeType },
4896   { "WM/SubTitleDescription", Frame::FT_Description, TagLib::ASF::Attribute::UnicodeType },
4897   { "WM/Picture", Frame::FT_Picture, TagLib::ASF::Attribute::BytesType },
4898   { "WM/Publisher", Frame::FT_Publisher, TagLib::ASF::Attribute::UnicodeType },
4899   { "WM/ModifiedBy", Frame::FT_Remixer, TagLib::ASF::Attribute::UnicodeType },
4900   { "WM/SubTitle", Frame::FT_Subtitle, TagLib::ASF::Attribute::UnicodeType },
4901   { "WM/AuthorURL", Frame::FT_Website, TagLib::ASF::Attribute::UnicodeType },
4902   { "AverageLevel", Frame::FT_Other, TagLib::ASF::Attribute::DWordType },
4903   { "PeakValue", Frame::FT_Other, TagLib::ASF::Attribute::DWordType },
4904   { "WM/AudioFileURL", Frame::FT_WWWAudioFile, TagLib::ASF::Attribute::UnicodeType },
4905   { "WM/EncodingSettings", Frame::FT_EncoderSettings, TagLib::ASF::Attribute::UnicodeType },
4906   { "WM/EncodingTime", Frame::FT_EncodingTime, TagLib::ASF::Attribute::BytesType },
4907   { "WM/InitialKey", Frame::FT_InitialKey, TagLib::ASF::Attribute::UnicodeType },
4908   // incorrect WM/Lyrics_Synchronised data make file inaccessible in Windows
4909   // { "WM/Lyrics_Synchronised", Frame::FT_Other, TagLib::ASF::Attribute::BytesType },
4910   { "WM/MCDI", Frame::FT_Other, TagLib::ASF::Attribute::BytesType },
4911   { "WM/MediaClassPrimaryID", Frame::FT_Other, TagLib::ASF::Attribute::GuidType },
4912   { "WM/MediaClassSecondaryID", Frame::FT_Other, TagLib::ASF::Attribute::GuidType },
4913   { "WM/Mood", Frame::FT_Mood, TagLib::ASF::Attribute::UnicodeType },
4914   { "WM/OriginalFilename", Frame::FT_Other, TagLib::ASF::Attribute::UnicodeType },
4915   { "WM/OriginalLyricist", Frame::FT_Other, TagLib::ASF::Attribute::UnicodeType },
4916   { "WM/PromotionURL", Frame::FT_Other, TagLib::ASF::Attribute::UnicodeType },
4917   { "WM/SharedUserRating", Frame::FT_Rating, TagLib::ASF::Attribute::UnicodeType },
4918   { "WM/WMCollectionGroupID", Frame::FT_Other, TagLib::ASF::Attribute::GuidType },
4919   { "WM/WMCollectionID", Frame::FT_Other, TagLib::ASF::Attribute::GuidType },
4920   { "WM/WMContentID", Frame::FT_Other, TagLib::ASF::Attribute::GuidType }
4921 };
4922 
4923 /**
4924  * Get ASF name and value type for a frame type.
4925  *
4926  * @param type  frame type
4927  * @param name  the ASF name is returned here
4928  * @param value the ASF value type is returned here
4929  */
4930 void getAsfNameForType(Frame::Type type, TagLib::String& name,
4931                        TagLib::ASF::Attribute::AttributeTypes& value)
4932 {
4933   static QMap<Frame::Type, unsigned> typeNameMap;
4934   if (typeNameMap.empty()) {
4935     // first time initialization
4936     for (unsigned i = 0; i < std::size(asfNameTypeValues); ++i) {
4937       if (asfNameTypeValues[i].type != Frame::FT_Other &&
4938           !typeNameMap.contains(asfNameTypeValues[i].type)) {
4939         typeNameMap.insert(asfNameTypeValues[i].type, i);
4940       }
4941     }
4942   }
4943   name = "";
4944   value = TagLib::ASF::Attribute::UnicodeType;
4945   if (type != Frame::FT_Other) {
4946     if (auto it = typeNameMap.constFind(type); it != typeNameMap.constEnd()) {
4947       name = asfNameTypeValues[*it].name;
4948       value = asfNameTypeValues[*it].value;
4949     } else {
4950       if (auto customFrameName = Frame::getNameForCustomFrame(type);
4951           !customFrameName.isEmpty()) {
4952         name = TagLib::String(customFrameName.constData());
4953       }
4954     }
4955   }
4956 }
4957 
4958 /**
4959  * Get ASF value type and frame type for an ASF name.
4960  *
4961  * @param name  ASF name
4962  * @param type  the frame type is returned here
4963  * @param value the ASF value type is returned here
4964  */
4965 void getAsfTypeForName(const TagLib::String& name, Frame::Type& type,
4966                        TagLib::ASF::Attribute::AttributeTypes& value)
4967 {
4968   static QMap<TagLib::String, unsigned> nameTypeMap;
4969   if (nameTypeMap.empty()) {
4970     // first time initialization
4971     for (unsigned i = 0; i < std::size(asfNameTypeValues); ++i) {
4972       nameTypeMap.insert(asfNameTypeValues[i].name, i);
4973     }
4974   }
4975   if (auto it = nameTypeMap.constFind(name); it != nameTypeMap.constEnd()) {
4976     type = asfNameTypeValues[*it].type;
4977     value = asfNameTypeValues[*it].value;
4978   } else {
4979     type = Frame::getTypeFromCustomFrameName(name.toCString());
4980     value = TagLib::ASF::Attribute::UnicodeType;
4981   }
4982 }
4983 
4984 /**
4985  * Get an ASF type for a frame.
4986  *
4987  * @param frame frame
4988  * @param name  the name for the attribute is returned here
4989  * @param value the ASF value type is returned here
4990  */
4991 void getAsfTypeForFrame(const Frame& frame, TagLib::String& name,
4992                         TagLib::ASF::Attribute::AttributeTypes& value)
4993 {
4994   if (frame.getType() != Frame::FT_Other) {
4995     getAsfNameForType(frame.getType(), name, value);
4996     if (name.isEmpty()) {
4997       name = toTString(frame.getInternalName());
4998     }
4999   } else {
5000     Frame::Type type;
5001     name = toTString(TaggedFile::fixUpTagKey(frame.getInternalName(),
5002                                              TaggedFile::TT_Asf));
5003     getAsfTypeForName(name, type, value);
5004   }
5005 }
5006 
5007 /**
5008  * Get a picture frame from a WM/Picture.
5009  *
5010  * @param picture ASF picture
5011  * @param frame   the picture frame is returned here
5012  *
5013  * @return true if ok.
5014  */
5015 bool parseAsfPicture(const TagLib::ASF::Picture& picture, Frame& frame)
5016 {
5017   if (!picture.isValid())
5018     return false;
5019 
5020   TagLib::ByteVector data = picture.picture();
5021   QString description(toQString(picture.description()));
5022   PictureFrame::setFields(frame, Frame::TE_ISO8859_1, QLatin1String("JPG"),
5023                           toQString(picture.mimeType()),
5024                           static_cast<PictureFrame::PictureType>(picture.type()),
5025                           description,
5026                           QByteArray(data.data(), data.size()));
5027   frame.setType(Frame::FT_Picture);
5028   return true;
5029 }
5030 
5031 /**
5032  * Render the bytes of a WM/Picture from a picture frame.
5033  *
5034  * @param frame   picture frame
5035  * @param picture the ASF picture is returned here
5036  */
5037 void renderAsfPicture(const Frame& frame, TagLib::ASF::Picture& picture)
5038 {
5039   Frame::TextEncoding enc;
5040   PictureFrame::PictureType pictureType;
5041   QByteArray data;
5042   QString imgFormat, mimeType, description;
5043   PictureFrame::getFields(frame, enc, imgFormat, mimeType, pictureType,
5044                           description, data);
5045 
5046   if (frame.isValueChanged()) {
5047     description = frame.getValue();
5048   }
5049   picture.setMimeType(toTString(mimeType));
5050   picture.setType(static_cast<TagLib::ASF::Picture::Type>(pictureType));
5051   picture.setDescription(toTString(description));
5052   picture.setPicture(TagLib::ByteVector(data.data(), data.size()));
5053 }
5054 
5055 /**
5056  * Get an ASF attribute for a frame.
5057  *
5058  * @param frame     frame
5059  * @param valueType ASF value type
5060  *
5061  * @return ASF attribute, an empty attribute is returned if not supported.
5062  */
5063 TagLib::ASF::Attribute getAsfAttributeForFrame(
5064   const Frame& frame,
5065   TagLib::ASF::Attribute::AttributeTypes valueType)
5066 {
5067   switch (valueType) {
5068     case TagLib::ASF::Attribute::UnicodeType:
5069       return TagLib::ASF::Attribute(toTString(frame.getValue()));
5070     case TagLib::ASF::Attribute::BoolType:
5071       return TagLib::ASF::Attribute(frame.getValue() == QLatin1String("1"));
5072     case TagLib::ASF::Attribute::WordType:
5073       return TagLib::ASF::Attribute(frame.getValue().toUShort());
5074     case TagLib::ASF::Attribute::DWordType:
5075       return TagLib::ASF::Attribute(frame.getValue().toUInt());
5076     case TagLib::ASF::Attribute::QWordType:
5077       return TagLib::ASF::Attribute(frame.getValue().toULongLong());
5078     case TagLib::ASF::Attribute::BytesType:
5079     case TagLib::ASF::Attribute::GuidType:
5080     default:
5081       if (frame.getType() != Frame::FT_Picture) {
5082         QByteArray ba;
5083         if (AttributeData(frame.getInternalName()).toByteArray(frame.getValue(), ba)) {
5084           return TagLib::ASF::Attribute(TagLib::ByteVector(ba.data(), ba.size()));
5085         }
5086         if (QVariant fieldValue = frame.getFieldValue(Frame::ID_Data);
5087             fieldValue.isValid()) {
5088           ba = fieldValue.toByteArray();
5089           return TagLib::ASF::Attribute(TagLib::ByteVector(ba.data(), ba.size()));
5090         }
5091       }
5092       else {
5093         TagLib::ASF::Picture picture;
5094         renderAsfPicture(frame, picture);
5095         return TagLib::ASF::Attribute(picture);
5096       }
5097   }
5098   return TagLib::ASF::Attribute();
5099 }
5100 
5101 /**
5102  * Get a picture frame from the bytes in an APE cover art frame.
5103  * The cover art frame has the following data:
5104  * zero terminated description string (UTF-8), picture data.
5105  *
5106  * @param name key of APE item
5107  * @param data bytes in APE cover art frame
5108  * @param frame the picture frame is returned here
5109  */
5110 void parseApePicture(const QString& name,
5111                      const TagLib::ByteVector& data, Frame& frame)
5112 {
5113   QByteArray picture;
5114   TagLib::String description;
5115   // Do not search for a description if the first byte could start JPG or PNG
5116   // data.
5117   if (int picPos = data.isEmpty() || data.at(0) == '\xff' || data.at(0) == '\x89'
5118                      ? -1 : data.find('\0');
5119       picPos >= 0) {
5120     description = TagLib::String(data.mid(0, picPos), TagLib::String::UTF8);
5121     picture = QByteArray(data.data() + picPos + 1, data.size() - picPos - 1);
5122   } else {
5123     picture = QByteArray(data.data(), data.size());
5124   }
5125   Frame::PictureType pictureType = Frame::PT_CoverFront;
5126   if (name.startsWith(QLatin1String("COVER ART (")) &&
5127       name.endsWith(QLatin1Char(')'))) {
5128     QString typeStr = name.mid(11);
5129     typeStr.chop(1);
5130     pictureType = PictureFrame::getPictureTypeFromString(typeStr.toLatin1());
5131   }
5132   PictureFrame::setFields(
5133         frame, Frame::TE_ISO8859_1, QLatin1String("JPG"),
5134         QLatin1String("image/jpeg"), pictureType,
5135         toQString(description), picture);
5136 }
5137 
5138 /**
5139  * Render the bytes of an APE cover art frame from a picture frame.
5140  *
5141  * @param frame picture frame
5142  * @param data  the bytes for the APE cover art are returned here
5143  */
5144 void renderApePicture(const Frame& frame, TagLib::ByteVector& data)
5145 {
5146   Frame::TextEncoding enc;
5147   PictureFrame::PictureType pictureType;
5148   QByteArray picture;
5149   QString imgFormat, mimeType, description;
5150   PictureFrame::getFields(frame, enc, imgFormat, mimeType, pictureType,
5151                           description, picture);
5152   if (frame.isValueChanged()) {
5153     description = frame.getValue();
5154   }
5155   data.append(toTString(description).data(TagLib::String::UTF8));
5156   data.append('\0');
5157   data.append(TagLib::ByteVector(picture.constData(), picture.size()));
5158 }
5159 
5160 #if TAGLIB_VERSION >= 0x010a00
5161 /**
5162  * Get name of INFO tag from type.
5163  *
5164  * @param type type
5165  *
5166  * @return name, NULL if not supported.
5167  */
5168 TagLib::ByteVector getInfoNameFromType(Frame::Type type)
5169 {
5170   static const char* const names[] = {
5171     "INAM",  // FT_Title,
5172     "IART",  // FT_Artist,
5173     "IPRD",  // FT_Album,
5174     "ICMT",  // FT_Comment,
5175     "ICRD",  // FT_Date,
5176     "IPRT",  // FT_Track
5177     "IGNR",  // FT_Genre,
5178              // FT_LastV1Frame = FT_Track,
5179     nullptr, // FT_AlbumArtist,
5180     "IENG",  // FT_Arranger,
5181     nullptr, // FT_Author,
5182     "IBPM",  // FT_Bpm,
5183     nullptr, // FT_CatalogNumber,
5184     nullptr, // FT_Compilation,
5185     "IMUS",  // FT_Composer,
5186     nullptr, // FT_Conductor,
5187     "ICOP",  // FT_Copyright,
5188     nullptr, // FT_Disc,
5189     "ITCH",  // FT_EncodedBy,
5190     "ISFT",  // FT_EncoderSettings,
5191     "IDIT",  // FT_EncodingTime,
5192     nullptr, // FT_Grouping,
5193     nullptr, // FT_InitialKey,
5194     "ISRC",  // FT_Isrc,
5195     "ILNG",  // FT_Language,
5196     "IWRI",  // FT_Lyricist,
5197     nullptr, // FT_Lyrics,
5198     "IMED",  // FT_Media,
5199     nullptr, // FT_Mood,
5200     nullptr, // FT_OriginalAlbum,
5201     nullptr, // FT_OriginalArtist,
5202     nullptr, // FT_OriginalDate,
5203     nullptr, // FT_Description,
5204     "ISTR",  // FT_Performer,
5205     nullptr, // FT_Picture,
5206     "IPUB",  // FT_Publisher,
5207     "ICNT",  // FT_ReleaseCountry,
5208     "IEDT",  // FT_Remixer,
5209     nullptr, // FT_SortAlbum,
5210     nullptr, // FT_SortAlbumArtist,
5211     nullptr, // FT_SortArtist,
5212     nullptr, // FT_SortComposer,
5213     nullptr, // FT_SortName,
5214     "PRT1",  // FT_Subtitle,
5215     "IBSU",  // FT_Website,
5216     nullptr, // FT_WWWAudioFile,
5217     nullptr, // FT_WWWAudioSource,
5218     nullptr, // FT_ReleaseDate,
5219     "IRTD",  // FT_Rating,
5220     nullptr, // FT_Work,
5221              // FT_Custom1
5222   };
5223   Q_STATIC_ASSERT(std::size(names) == Frame::FT_Custom1);
5224   if (type == Frame::FT_Track) {
5225     QByteArray ba = TagConfig::instance().riffTrackName().toLatin1();
5226     return TagLib::ByteVector(ba.constData(), ba.size());
5227   }
5228   if (Frame::isCustomFrameType(type)) {
5229     return TagLib::ByteVector(Frame::getNameForCustomFrame(type).constData());
5230   }
5231   const char* name = type <= Frame::FT_LastFrame ? names[type] : nullptr;
5232   return name ? TagLib::ByteVector(name, 4) : TagLib::ByteVector();
5233 }
5234 
5235 /**
5236  * Get the frame type for an INFO name.
5237  *
5238  * @param id INFO tag name
5239  *
5240  * @return frame type.
5241  */
5242 Frame::Type getTypeFromInfoName(const TagLib::ByteVector& id)
5243 {
5244   static QMap<TagLib::ByteVector, int> strNumMap;
5245   if (strNumMap.isEmpty()) {
5246     // first time initialization
5247     for (int i = 0; i < Frame::FT_Custom1; ++i) {
5248       auto type = static_cast<Frame::Type>(i);
5249       if (TagLib::ByteVector str = getInfoNameFromType(type); !str.isEmpty()) {
5250         strNumMap.insert(str, type);
5251       }
5252     }
5253     QStringList riffTrackNames = TagConfig::getRiffTrackNames();
5254     riffTrackNames.append(TagConfig::instance().riffTrackName());
5255     const auto constRiffTrackNames = riffTrackNames;
5256     for (const QString& str : constRiffTrackNames) {
5257       QByteArray ba = str.toLatin1();
5258       strNumMap.insert(TagLib::ByteVector(ba.constData(), ba.size()),
5259                        Frame::FT_Track);
5260     }
5261   }
5262   if (auto it = strNumMap.constFind(id); it != strNumMap.constEnd()) {
5263     return static_cast<Frame::Type>(*it);
5264   }
5265   return Frame::getTypeFromCustomFrameName(
5266         QByteArray(id.data(), id.size()));
5267 }
5268 
5269 /**
5270  * Get internal name of an INFO frame.
5271  *
5272  * @param frame frame
5273  *
5274  * @return INFO id, "IKEY" if not found.
5275  */
5276 TagLib::ByteVector getInfoName(const Frame& frame)
5277 {
5278   if (TagLib::ByteVector str = getInfoNameFromType(frame.getType());
5279       !str.isEmpty()) {
5280     return str;
5281   }
5282 
5283   if (QString name = frame.getInternalName(); name.length() >= 4) {
5284     QByteArray ba = name.left(4).toUpper().toLatin1();
5285     return TagLib::ByteVector(ba.constData(), 4);
5286   }
5287 
5288   return "IKEY";
5289 }
5290 #endif
5291 
5292 }
5293 
5294 /**
5295  * Get a specific frame from the tags.
5296  *
5297  * @param tagNr tag number
5298  * @param type  frame type
5299  * @param frame the frame is returned here
5300  *
5301  * @return true if ok.
5302  */
5303 bool TagLibFile::getFrame(Frame::TagNumber tagNr, Frame::Type type, Frame& frame) const
5304 {
5305   if (tagNr >= NUM_TAGS)
5306     return false;
5307 
5308   makeFileOpen();
5309   if (TagLib::Tag* tag = m_tag[tagNr]) {
5310     TagLib::String tstr;
5311     switch (type) {
5312     case Frame::FT_Album:
5313       tstr = tag->album();
5314       break;
5315     case Frame::FT_Artist:
5316       tstr = tag->artist();
5317       break;
5318     case Frame::FT_Comment:
5319       tstr = tag->comment();
5320       if (tagNr == Frame::Tag_Id3v1
5321 #if TAGLIB_VERSION < 0x010b01
5322           && !tstr.isNull()
5323 #endif
5324           ) {
5325         tstr = tstr.substr(0, 28);
5326       }
5327       break;
5328     case Frame::FT_Date:
5329     {
5330       uint nr = tag->year();
5331       tstr = nr != 0 ? TagLib::String::number(nr) : "";
5332       break;
5333     }
5334     case Frame::FT_Genre:
5335       tstr = tag->genre();
5336       break;
5337     case Frame::FT_Title:
5338       tstr = tag->title();
5339       break;
5340     case Frame::FT_Track:
5341     {
5342       uint nr = tag->track();
5343       tstr = nr != 0 ? TagLib::String::number(nr) : "";
5344       break;
5345     }
5346     default:
5347       // maybe handled in a subclass
5348       return false;
5349     }
5350 #if TAGLIB_VERSION >= 0x010b01
5351     QString str = tagNr != Frame::Tag_Id3v1 && type == Frame::FT_Genre
5352         ? getGenreString(tstr) : toQString(tstr);
5353 #else
5354     QString str = tagNr != Frame::Tag_Id3v1 && type == Frame::FT_Genre
5355         ? getGenreString(tstr)
5356         : tstr.isNull() ? QLatin1String("") : toQString(tstr);
5357 #endif
5358     frame.setValue(str);
5359   } else {
5360     frame.setValue(QString());
5361   }
5362   frame.setType(type);
5363   return true;
5364 }
5365 
5366 /**
5367  * Set a frame in the tags.
5368  *
5369  * @param tagNr tag number
5370  * @param frame frame to set
5371  *
5372  * @return true if ok.
5373  */
5374 bool TagLibFile::setFrame(Frame::TagNumber tagNr, const Frame& frame)
5375 {
5376   if (tagNr >= NUM_TAGS)
5377     return false;
5378 
5379   if (tagNr != Frame::Tag_Id3v1) {
5380     makeFileOpen();
5381     // If the frame has an index, change that specific frame
5382     if (int index = frame.getIndex(); index != -1 && m_tag[tagNr]) {
5383       if (TagLib::ID3v2::Tag* id3v2Tag;
5384           (id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr])) != nullptr) {
5385         if (const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList();
5386             index >= 0 && index < static_cast<int>(frameList.size())) {
5387           // This is a hack. The frameList should not be modified directly.
5388           // However when removing the old frame and adding a new frame,
5389           // the indices of all frames get invalid.
5390           setId3v2Frame(this, frameList[index], frame);
5391           markTagChanged(tagNr, frame.getExtendedType());
5392           return true;
5393         }
5394       } else if (TagLib::Ogg::XiphComment* oggTag;
5395                  (oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[tagNr])) != nullptr) {
5396         QString frameValue(frame.getValue());
5397         if (Frame::ExtendedType extendedType = frame.getExtendedType();
5398             extendedType.getType() == Frame::FT_Picture) {
5399           if (m_pictures.isRead()) {
5400             if (int idx = Frame::fromNegativeIndex(frame.getIndex());
5401                 idx >= 0 && idx < m_pictures.size()) {
5402               Frame newFrame(frame);
5403               PictureFrame::setDescription(newFrame, frameValue);
5404               if (PictureFrame::areFieldsEqual(m_pictures[idx], newFrame)) {
5405                 m_pictures[idx].setValueChanged(false);
5406               } else {
5407                 m_pictures[idx] = newFrame;
5408                 markTagChanged(tagNr, extendedType);
5409               }
5410               return true;
5411             }
5412             return false;
5413           }
5414           Frame newFrame(frame);
5415           PictureFrame::setDescription(newFrame, frameValue);
5416           PictureFrame::getFieldsToBase64(newFrame, frameValue);
5417           if (!frameValue.isEmpty() &&
5418             frame.getInternalName() == QLatin1String("COVERART")) {
5419             QString mimeType;
5420             PictureFrame::getMimeType(frame, mimeType);
5421             oggTag->addField("COVERARTMIME", toTString(mimeType), true);
5422           }
5423         }
5424         TagLib::String key = toTString(getVorbisName(frame));
5425         TagLib::String value = toTString(frameValue);
5426         if (const TagLib::Ogg::FieldListMap& fieldListMap = oggTag->fieldListMap();
5427             fieldListMap.contains(key) && fieldListMap[key].size() > 1) {
5428           int i = 0;
5429           bool found = false;
5430           for (auto it = fieldListMap.begin();
5431                it != fieldListMap.end();
5432                ++it) {
5433             TagLib::StringList stringList = it->second;
5434             for (auto slit = stringList.begin(); slit != stringList.end(); ++slit) {
5435               if (i++ == index) {
5436                 *slit = value;
5437                 found = true;
5438                 break;
5439               }
5440             }
5441             if (found) {
5442               // Replace all fields with this key to preserve the order.
5443 #if TAGLIB_VERSION >= 0x010b01
5444               oggTag->removeFields(key);
5445 #else
5446               oggTag->removeField(key);
5447 #endif
5448               for (auto slit = stringList.begin(); slit != stringList.end(); ++slit) {
5449                 oggTag->addField(key, *slit, false);
5450               }
5451               break;
5452             }
5453           }
5454         } else {
5455           oggTag->addField(key, value, true);
5456         }
5457         if (frame.getType() == Frame::FT_Track) {
5458           if (int numTracks = getTotalNumberOfTracksIfEnabled();
5459               numTracks > 0) {
5460             oggTag->addField("TRACKTOTAL", TagLib::String::number(numTracks), true);
5461           }
5462         }
5463         markTagChanged(tagNr, frame.getExtendedType());
5464         return true;
5465       } else if (TagLib::APE::Tag* apeTag;
5466                  (apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tag[tagNr])) != nullptr) {
5467         if (frame.getType() == Frame::FT_Picture) {
5468           TagLib::ByteVector data;
5469           renderApePicture(frame, data);
5470           QString oldName = frame.getInternalName();
5471           QString newName = getApeName(frame);
5472           if (newName != oldName) {
5473             // If the picture type changes, the frame with the old name has to
5474             // be replaced with a frame with the new name.
5475             apeTag->removeItem(toTString(oldName));
5476           }
5477           apeTag->setData(toTString(newName), data);
5478         } else {
5479           const auto key = toTString(getApeName(frame));
5480           const auto values = splitToTStringList(frame.getValue());
5481           apeTag->removeItem(key);
5482           apeTag->setItem(key, TagLib::APE::Item(key, values));
5483         }
5484         markTagChanged(tagNr, frame.getExtendedType());
5485         return true;
5486       } else if (TagLib::MP4::Tag* mp4Tag;
5487                  (mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[tagNr])) != nullptr) {
5488         if (Frame::ExtendedType extendedType = frame.getExtendedType();
5489             extendedType.getType() == Frame::FT_Picture) {
5490           if (m_pictures.isRead()) {
5491             if (int idx = Frame::fromNegativeIndex(frame.getIndex());
5492                 idx >= 0 && idx < m_pictures.size()) {
5493               if (Frame newFrame(frame);
5494                   PictureFrame::areFieldsEqual(m_pictures[idx], newFrame)) {
5495                 m_pictures[idx].setValueChanged(false);
5496               } else {
5497                 m_pictures[idx] = newFrame;
5498                 markTagChanged(tagNr, extendedType);
5499               }
5500               return true;
5501             }
5502             return false;
5503           }
5504         }
5505         setMp4Frame(frame, mp4Tag);
5506         return true;
5507       } else if (TagLib::ASF::Tag* asfTag;
5508                  (asfTag = dynamic_cast<TagLib::ASF::Tag*>(m_tag[tagNr])) != nullptr) {
5509         switch (index) {
5510           case AFI_Title:
5511             asfTag->setTitle(toTString(frame.getValue()));
5512             break;
5513           case AFI_Artist:
5514             asfTag->setArtist(toTString(frame.getValue()));
5515             break;
5516           case AFI_Comment:
5517             asfTag->setComment(toTString(frame.getValue()));
5518             break;
5519           case AFI_Copyright:
5520             asfTag->setCopyright(toTString(frame.getValue()));
5521             break;
5522           case AFI_Rating:
5523             asfTag->setRating(toTString(frame.getValue()));
5524             break;
5525           case AFI_Attributes:
5526           default:
5527           {
5528             TagLib::String name;
5529             TagLib::ASF::Attribute::AttributeTypes valueType;
5530             getAsfTypeForFrame(frame, name, valueType);
5531             TagLib::ASF::Attribute attribute =
5532               getAsfAttributeForFrame(frame, valueType);
5533             if (TagLib::ASF::AttributeListMap& attrListMap =
5534                   asfTag->attributeListMap();
5535                 attrListMap.contains(name) && attrListMap[name].size() > 1) {
5536               int i = AFI_Attributes;
5537               bool found = false;
5538               for (auto it = attrListMap.begin();
5539                    it != attrListMap.end();
5540                    ++it) {
5541                 TagLib::ASF::AttributeList& attrList = it->second;
5542                 for (auto ait = attrList.begin();
5543                      ait != attrList.end();
5544                      ++ait) {
5545                   if (i++ == index) {
5546                     found = true;
5547                     *ait = attribute;
5548                     break;
5549                   }
5550                 }
5551                 if (found) {
5552                   break;
5553                 }
5554               }
5555             } else {
5556               asfTag->setAttribute(name, attribute);
5557             }
5558           }
5559         }
5560         markTagChanged(tagNr, frame.getExtendedType());
5561         return true;
5562 #if TAGLIB_VERSION >= 0x010a00
5563       } else if (auto infoTag =
5564                  dynamic_cast<TagLib::RIFF::Info::Tag*>(m_tag[tagNr])) {
5565         infoTag->setFieldText(getInfoName(frame), toTString(frame.getValue()));
5566         markTagChanged(tagNr, frame.getExtendedType());
5567         return true;
5568 #endif
5569       }
5570     }
5571   }
5572 
5573   // Try the basic method
5574   if (QString str = frame.getValue();
5575       makeTagSettable(tagNr) && !str.isNull()) {
5576     TagLib::Tag* tag = m_tag[tagNr];
5577     if (!tag)
5578       return false;
5579     Frame::Type type = frame.getType();
5580 #if TAGLIB_VERSION >= 0x010b01
5581     TagLib::String tstr = toTString(str);
5582 #else
5583     TagLib::String tstr = str.isEmpty() ? TagLib::String::null : toTString(str);
5584 #endif
5585     TagLib::String oldTstr;
5586     uint oldNum;
5587     const char* frameId = nullptr;
5588     switch (type) {
5589     case Frame::FT_Album:
5590       oldTstr = tag->album();
5591       frameId = "TALB";
5592       break;
5593     case Frame::FT_Comment:
5594       oldTstr = tag->comment();
5595       frameId = "COMM";
5596       break;
5597     case Frame::FT_Artist:
5598       oldTstr = tag->artist();
5599       frameId = "TPE1";
5600       break;
5601     case Frame::FT_Title:
5602       oldTstr = tag->title();
5603       frameId = "TIT2";
5604       break;
5605     case Frame::FT_Genre:
5606       oldTstr = tag->genre();
5607       frameId = "TCON";
5608       break;
5609     case Frame::FT_Date:
5610       oldNum = tag->year();
5611       frameId = "TDRC";
5612       break;
5613     case Frame::FT_Track:
5614       oldNum = tag->track();
5615       frameId = "TRCK";
5616       break;
5617     default:
5618       return false;
5619     }
5620     if (type == Frame::FT_Date) {
5621       int num = frame.getValueAsNumber();
5622       if (tagNr == Frame::Tag_Id3v1) {
5623         if (num >= 0 && num != static_cast<int>(oldNum)) {
5624           tag->setYear(num);
5625           markTagChanged(tagNr, Frame::ExtendedType(type));
5626         }
5627       } else {
5628         if (num > 0 && num != static_cast<int>(oldNum) &&
5629             getDefaultTextEncoding() == TagLib::String::Latin1) {
5630           tag->setYear(num);
5631           markTagChanged(tagNr, Frame::ExtendedType(type));
5632         } else if (num == 0 || num != static_cast<int>(oldNum)){
5633           QString yearStr;
5634           if (num != 0) {
5635             yearStr.setNum(num);
5636           } else {
5637             yearStr = frame.getValue();
5638           }
5639 #if TAGLIB_VERSION >= 0x010b01
5640           TagLib::String yearTStr = toTString(yearStr);
5641 #else
5642           TagLib::String yearTStr =
5643               yearStr.isEmpty() ? TagLib::String::null : toTString(yearStr);
5644 #endif
5645           bool ok = false;
5646           if (dynamic_cast<TagLib::ID3v2::Tag*>(tag) != nullptr) {
5647             ok = setId3v2Unicode(tag, yearStr, yearTStr, frameId);
5648           } else if (auto mp4Tag =
5649                      dynamic_cast<TagLib::MP4::Tag*>(tag)) {
5650             TagLib::String name;
5651             Mp4ValueType valueType;
5652             getMp4NameForType(type, name, valueType);
5653             auto item = TagLib::MP4::Item(yearTStr);
5654             ok = valueType == MVT_String && item.isValid();
5655             if (ok) {
5656 #if TAGLIB_VERSION >= 0x010b01
5657               mp4Tag->setItem(name, item);
5658 #else
5659               mp4Tag->itemListMap()[name] = item;
5660 #endif
5661             }
5662           } else if (auto oggTag =
5663                      dynamic_cast<TagLib::Ogg::XiphComment*>(tag)) {
5664             oggTag->addField(getVorbisNameFromType(type), yearTStr, true);
5665             ok = true;
5666           }
5667           if (!ok) {
5668             tag->setYear(num);
5669           }
5670           markTagChanged(tagNr, Frame::ExtendedType(type));
5671         }
5672       }
5673     } else if (type == Frame::FT_Track) {
5674       if (int num = frame.getValueAsNumber();
5675           num >= 0 && num != static_cast<int>(oldNum)) {
5676         if (tagNr == Frame::Tag_Id3v1) {
5677           if (int n = checkTruncation(tagNr, num, 1ULL << type); n != -1) {
5678             num = n;
5679           }
5680           tag->setTrack(num);
5681         } else {
5682           int numTracks;
5683           num = splitNumberAndTotal(str, &numTracks);
5684           QString trackStr = trackNumberString(num, numTracks);
5685           if (num != static_cast<int>(oldNum)) {
5686             if (auto id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(tag)) {
5687 #if TAGLIB_VERSION >= 0x010b01
5688               TagLib::String trackTStr = toTString(trackStr);
5689 #else
5690               TagLib::String trackTStr =
5691                 trackStr.isEmpty() ? TagLib::String::null : toTString(trackStr);
5692 #endif
5693               if (!setId3v2Unicode(tag, trackStr, trackTStr, frameId)) {
5694                 auto trackFrame =
5695                     new TagLib::ID3v2::TextIdentificationFrame(
5696                       frameId, getDefaultTextEncoding());
5697                 trackFrame->setText(trackTStr);
5698                 id3v2Tag->removeFrames(frameId);
5699                 addTagLibFrame(id3v2Tag, trackFrame);
5700               }
5701             } else if (TagLib::MP4::Tag* mp4Tag;
5702                        (mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(tag)) != nullptr) {
5703               // Set a frame in order to store the total number too.
5704               Frame trackFrame(Frame::FT_Track, str, QLatin1String(""), -1);
5705               setMp4Frame(trackFrame, mp4Tag);
5706 #if TAGLIB_VERSION >= 0x010a00
5707             } else if (auto infoTag =
5708                        dynamic_cast<TagLib::RIFF::Info::Tag*>(tag)) {
5709               infoTag->setFieldText(getInfoNameFromType(Frame::FT_Track),
5710                                     toTString(trackStr));
5711 #endif
5712             } else {
5713               tag->setTrack(num);
5714             }
5715           }
5716         }
5717         markTagChanged(tagNr, Frame::ExtendedType(type));
5718       }
5719     } else {
5720       if (!(tstr == oldTstr)) {
5721         if (!setId3v2Unicode(tag, str, tstr, frameId)) {
5722           if (QString s = checkTruncation(tagNr, str, 1ULL << type,
5723                                           type == Frame::FT_Comment ? 28 : 30);
5724               !s.isNull()) {
5725             tstr = toTString(s);
5726           }
5727           switch (type) {
5728           case Frame::FT_Album:
5729             tag->setAlbum(tstr);
5730             break;
5731           case Frame::FT_Comment:
5732             tag->setComment(tstr);
5733             break;
5734           case Frame::FT_Artist:
5735             tag->setArtist(tstr);
5736             break;
5737           case Frame::FT_Title:
5738             tag->setTitle(tstr);
5739             break;
5740           case Frame::FT_Genre:
5741             if (tagNr == Frame::Tag_Id3v1) {
5742               const auto genres = splitToTStringList(toQString(tstr));
5743               for (const auto& genre : genres) {
5744                 if (TagLib::ID3v1::genreIndex(genre) != 0xff) {
5745                   tstr = genre;
5746                   break;
5747                 }
5748                 static const struct {
5749                   const char* newName;
5750                   const char* oldName;
5751                 } alternativeGenreNames[] = {
5752                     { "Avant-Garde", "Avantgarde" },
5753                     { "Beat Music", "Beat" },
5754                     { "Bebop", "Bebob" },
5755                     { "Britpop", "BritPop" },
5756                     { "Dancehall", "Dance Hall" },
5757                     { "Dark Wave", "Darkwave" },
5758                     { "Euro House", "Euro-House" },
5759                     { "Eurotechno", "Euro-Techno" },
5760                     { "Fast Fusion", "Fusion" },
5761                     { "Folk Rock", "Folk/Rock" },
5762                     { "Hip Hop", "Hip-Hop" },
5763                     { "Jazz-Funk", "Jazz+Funk" },
5764                     { "Pop-Funk", "Pop/Funk" },
5765                     { "Synth-Pop", "Synthpop" },
5766                     { "Worldbeat", "Negerpunk" }
5767                   };
5768                 static TagLib::Map<TagLib::String, TagLib::String> genreNameMap;
5769                 if (genreNameMap.isEmpty()) {
5770                   // first time initialization
5771                   for (const auto& [newName, oldName] : alternativeGenreNames) {
5772                     genreNameMap.insert(newName, oldName);
5773                   }
5774                 }
5775                 if (auto it = genreNameMap.find(tstr);
5776                   it != genreNameMap.end()) {
5777                   tstr = it->second;
5778                   break;
5779                 }
5780               }
5781               tag->setGenre(tstr);
5782               // if the string cannot be converted to a number, set the truncation flag
5783               checkTruncation(tagNr, !tstr.isEmpty() &&
5784                               TagLib::ID3v1::genreIndex(tstr) == 0xff
5785                               ? 1 : 0, 1ULL << type, 0);
5786             } else {
5787               TagLib::ID3v2::TextIdentificationFrame* genreFrame;
5788               if (auto id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(tag);
5789                   id3v2Tag && TagConfig::instance().genreNotNumeric() &&
5790                   (genreFrame = new TagLib::ID3v2::TextIdentificationFrame(
5791                     frameId, getDefaultTextEncoding())) != nullptr) {
5792                 genreFrame->setText(tstr);
5793                 id3v2Tag->removeFrames(frameId);
5794                 addTagLibFrame(id3v2Tag, genreFrame);
5795               } else {
5796                 tag->setGenre(tstr);
5797               }
5798             }
5799             break;
5800           default:
5801             return false;
5802           }
5803         }
5804         markTagChanged(tagNr, Frame::ExtendedType(type));
5805       }
5806     }
5807   }
5808   return true;
5809 }
5810 
5811 namespace {
5812 
5813 /**
5814  * Check if an ID3v2.4.0 frame ID is valid.
5815  *
5816  * @param frameId frame ID (4 characters)
5817  *
5818  * @return true if frame ID is valid.
5819  */
5820 bool isFrameIdValid(const QString& frameId)
5821 {
5822   Frame::Type type;
5823   const char* str;
5824   getTypeStringForFrameId(TagLib::ByteVector(frameId.toLatin1().data(), 4), type, str);
5825   return type != Frame::FT_UnknownFrame;
5826 }
5827 
5828 /**
5829  * Create a TagLib ID3 frame from a frame.
5830  * @param self this TagLibFile instance
5831  * @param frame frame
5832  * @return TagLib ID3 frame, 0 if invalid.
5833  */
5834 TagLib::ID3v2::Frame* createId3FrameFromFrame(const TagLibFile* self,
5835                                               Frame& frame)
5836 {
5837   TagLib::String::Type enc = TagLibFile::getDefaultTextEncoding();
5838   QString name = !Frame::isCustomFrameTypeOrOther(frame.getType())
5839       ? QString::fromLatin1(getStringForType(frame.getType()))
5840       : frame.getName();
5841   QString frameId = name;
5842   frameId.truncate(4);
5843   TagLib::ID3v2::Frame* id3Frame = nullptr;
5844 
5845   if (name == QLatin1String("AverageLevel") ||
5846       name == QLatin1String("PeakValue") ||
5847       name.startsWith(QLatin1String("WM/"))) {
5848     frameId = QLatin1String("PRIV");
5849   } else if (name.startsWith(QLatin1String("iTun"))) {
5850     frameId = QLatin1String("COMM");
5851   }
5852 
5853   if (frameId.startsWith(QLatin1String("T"))
5854 #if TAGLIB_VERSION >= 0x010b00
5855       || frameId == QLatin1String("WFED")
5856 #endif
5857 #if TAGLIB_VERSION >= 0x010c00
5858       || frameId == QLatin1String("MVIN") || frameId == QLatin1String("MVNM")
5859       || frameId == QLatin1String("GRP1")
5860 #endif
5861     ) {
5862     if (frameId == QLatin1String("TXXX")) {
5863       id3Frame = new TagLib::ID3v2::UserTextIdentificationFrame(enc);
5864     } else if (isFrameIdValid(frameId)) {
5865       id3Frame = new TagLib::ID3v2::TextIdentificationFrame(
5866         TagLib::ByteVector(frameId.toLatin1().data(), frameId.length()), enc);
5867       id3Frame->setText(""); // is necessary for createFrame() to work
5868     }
5869   } else if (frameId == QLatin1String("COMM")) {
5870     auto commFrame =
5871         new TagLib::ID3v2::CommentsFrame(enc);
5872     id3Frame = commFrame;
5873     commFrame->setLanguage("eng"); // for compatibility with iTunes
5874     if (frame.getType() == Frame::FT_Other) {
5875       commFrame->setDescription(toTString(frame.getName()));
5876     }
5877   } else if (frameId == QLatin1String("APIC")) {
5878     id3Frame = new TagLib::ID3v2::AttachedPictureFrame;
5879     static_cast<TagLib::ID3v2::AttachedPictureFrame*>(id3Frame)->setTextEncoding(enc);
5880     static_cast<TagLib::ID3v2::AttachedPictureFrame*>(id3Frame)->setMimeType(
5881       "image/jpeg");
5882     static_cast<TagLib::ID3v2::AttachedPictureFrame*>(id3Frame)->setType(
5883       TagLib::ID3v2::AttachedPictureFrame::FrontCover);
5884   } else if (frameId == QLatin1String("UFID")) {
5885     // the bytevector must not be empty
5886     auto ufidFrame =
5887         new TagLib::ID3v2::UniqueFileIdentifierFrame(
5888                   TagLib::String("http://www.id3.org/dummy/ufid.html"),
5889                   TagLib::ByteVector(" "));
5890     id3Frame = ufidFrame;
5891     if (AttributeData::isHexString(frame.getValue(), 'Z', QLatin1String("-"))) {
5892       QByteArray data = (frame.getValue() + QLatin1Char('\0')).toLatin1();
5893       ufidFrame->setIdentifier(TagLib::ByteVector(data.constData(),
5894                                                   data.size()));
5895     }
5896   } else if (frameId == QLatin1String("GEOB")) {
5897     id3Frame = new TagLib::ID3v2::GeneralEncapsulatedObjectFrame;
5898     static_cast<TagLib::ID3v2::GeneralEncapsulatedObjectFrame*>(id3Frame)->setTextEncoding(enc);
5899   } else if (frameId.startsWith(QLatin1String("W"))) {
5900     if (frameId == QLatin1String("WXXX")) {
5901       id3Frame = new TagLib::ID3v2::UserUrlLinkFrame(enc);
5902     } else if (isFrameIdValid(frameId)) {
5903       id3Frame = new TagLib::ID3v2::UrlLinkFrame(
5904         TagLib::ByteVector(frameId.toLatin1().data(), frameId.length()));
5905       id3Frame->setText("http://"); // is necessary for createFrame() to work
5906     }
5907   } else if (frameId == QLatin1String("USLT")) {
5908     id3Frame = new TagLib::ID3v2::UnsynchronizedLyricsFrame(enc);
5909     static_cast<TagLib::ID3v2::UnsynchronizedLyricsFrame*>(id3Frame)->setLanguage("eng");
5910   } else if (frameId == QLatin1String("SYLT")) {
5911     id3Frame = new TagLib::ID3v2::SynchronizedLyricsFrame(enc);
5912     static_cast<TagLib::ID3v2::SynchronizedLyricsFrame*>(id3Frame)->setLanguage("eng");
5913   } else if (frameId == QLatin1String("ETCO")) {
5914     id3Frame = new TagLib::ID3v2::EventTimingCodesFrame;
5915   } else if (frameId == QLatin1String("POPM")) {
5916     auto popmFrame =
5917         new TagLib::ID3v2::PopularimeterFrame;
5918     id3Frame = popmFrame;
5919     popmFrame->setEmail(toTString(TagConfig::instance().defaultPopmEmail()));
5920   } else if (frameId == QLatin1String("PRIV")) {
5921     auto privFrame =
5922         new TagLib::ID3v2::PrivateFrame;
5923     id3Frame = privFrame;
5924     if (!frame.getName().startsWith(QLatin1String("PRIV"))) {
5925       privFrame->setOwner(toTString(frame.getName()));
5926       if (QByteArray data;
5927           AttributeData(frame.getName()).toByteArray(frame.getValue(), data)) {
5928         privFrame->setData(TagLib::ByteVector(data.constData(), data.size()));
5929       }
5930     }
5931   } else if (frameId == QLatin1String("OWNE")) {
5932     id3Frame = new TagLib::ID3v2::OwnershipFrame(enc);
5933   } else if (frameId == QLatin1String("RVA2")) {
5934     id3Frame = new TagLib::ID3v2::RelativeVolumeFrame;
5935 #if TAGLIB_VERSION >= 0x010b00
5936   } else if (frameId == QLatin1String("PCST")) {
5937     id3Frame = new TagLib::ID3v2::PodcastFrame;
5938 #endif
5939 #if TAGLIB_VERSION >= 0x010a00
5940   } else if (frameId == QLatin1String("CHAP")) {
5941     // crashes with an empty elementID
5942     id3Frame = new TagLib::ID3v2::ChapterFrame("chp", 0, 0,
5943                                                0xffffffff, 0xffffffff);
5944   } else if (frameId == QLatin1String("CTOC")) {
5945     // crashes with an empty elementID
5946     id3Frame = new TagLib::ID3v2::TableOfContentsFrame("toc");
5947 #endif
5948   }
5949   if (!id3Frame) {
5950     auto txxxFrame =
5951       new TagLib::ID3v2::UserTextIdentificationFrame(enc);
5952     TagLib::String description;
5953     if (frame.getType() == Frame::FT_CatalogNumber) {
5954       description = "CATALOGNUMBER";
5955     } else if (frame.getType() == Frame::FT_ReleaseCountry) {
5956       description = "RELEASECOUNTRY";
5957     } else if (frame.getType() == Frame::FT_Grouping) {
5958       description = "GROUPING";
5959     } else if (frame.getType() == Frame::FT_Subtitle) {
5960       description = "SUBTITLE";
5961     } else {
5962       description = toTString(frame.getName());
5963       frame.setExtendedType(Frame::ExtendedType(Frame::FT_Other,
5964                     QLatin1String("TXXX - User defined text information")));
5965     }
5966     txxxFrame->setDescription(description);
5967     id3Frame = txxxFrame;
5968   } else {
5969     frame.setExtendedType(Frame::ExtendedType(frame.getType(), name));
5970   }
5971   if (id3Frame) {
5972     if (!frame.fieldList().empty()) {
5973       frame.setValueFromFieldList();
5974       setId3v2Frame(self, id3Frame, frame);
5975     }
5976   }
5977   return id3Frame;
5978 }
5979 
5980 }
5981 
5982 /**
5983  * Add a frame in the tags.
5984  *
5985  * @param tagNr tag number
5986  * @param frame frame to add, a field list may be added by this method
5987  *
5988  * @return true if ok.
5989  */
5990 bool TagLibFile::addFrame(Frame::TagNumber tagNr, Frame& frame)
5991 {
5992   if (tagNr >= NUM_TAGS)
5993     return false;
5994 
5995   if (tagNr != Frame::Tag_Id3v1) {
5996     // Add a new frame.
5997     if (makeTagSettable(tagNr)) {
5998       if (TagLib::ID3v2::Tag* id3v2Tag;
5999           (id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr])) != nullptr) {
6000         if (TagLib::ID3v2::Frame* id3Frame = createId3FrameFromFrame(this, frame)) {
6001           if (frame.fieldList().empty()) {
6002             // add field list to frame
6003             getFieldsFromId3Frame(id3Frame, frame.fieldList(), frame.getType());
6004             frame.setFieldListFromValue();
6005           }
6006           if (frame.getType() == Frame::FT_Other) {
6007             // Set the correct frame type if the frame was added using the ID.
6008             Frame::Type type;
6009             const char* str;
6010             getTypeStringForFrameId(id3Frame->frameID(), type, str);
6011             if (type != Frame::FT_UnknownFrame) {
6012               frame.setExtendedType(
6013                     Frame::ExtendedType(type, QString::fromLatin1(str)));
6014             }
6015           }
6016           frame.setIndex(id3v2Tag->frameList().size());
6017           addTagLibFrame(id3v2Tag, id3Frame);
6018           markTagChanged(tagNr, frame.getExtendedType());
6019           return true;
6020         }
6021       } else if (TagLib::Ogg::XiphComment* oggTag;
6022                  (oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[tagNr])) != nullptr) {
6023         QString name(getVorbisName(frame));
6024         QString value(frame.getValue());
6025         if (frame.getType() == Frame::FT_Picture) {
6026           if (frame.getFieldList().empty()) {
6027             PictureFrame::setFields(
6028               frame, Frame::TE_ISO8859_1, QLatin1String("JPG"), QLatin1String("image/jpeg"),
6029               PictureFrame::PT_CoverFront, QLatin1String(""), QByteArray());
6030           }
6031           if (m_pictures.isRead()) {
6032             PictureFrame::setDescription(frame, value);
6033             frame.setIndex(Frame::toNegativeIndex(m_pictures.size()));
6034             m_pictures.append(frame);
6035             markTagChanged(tagNr, frame.getExtendedType());
6036             return true;
6037           }
6038           PictureFrame::getFieldsToBase64(frame, value);
6039         }
6040         TagLib::String tname = toTString(name);
6041         TagLib::String tvalue = toTString(value);
6042         if (tvalue.isEmpty()) {
6043           tvalue = " "; // empty values are not added by TagLib
6044         }
6045         oggTag->addField(tname, tvalue, false);
6046         frame.setExtendedType(Frame::ExtendedType(frame.getType(), name));
6047 
6048         const TagLib::Ogg::FieldListMap& fieldListMap = oggTag->fieldListMap();
6049         int index = 0;
6050         bool found = false;
6051         for (auto it = fieldListMap.begin();
6052              it != fieldListMap.end();
6053              ++it) {
6054           if (it->first == tname) {
6055             index += it->second.size() - 1;
6056             found = true;
6057             break;
6058           }
6059           index += it->second.size();
6060         }
6061         frame.setIndex(found ? index : -1);
6062         markTagChanged(tagNr, frame.getExtendedType());
6063         return true;
6064       } else if (TagLib::APE::Tag* apeTag;
6065                  (apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tag[tagNr])) != nullptr) {
6066         if (frame.getType() == Frame::FT_Picture &&
6067             frame.getFieldList().isEmpty()) {
6068           // Do not replace an already existing picture.
6069           Frame::PictureType pictureType = Frame::PT_CoverFront;
6070           const TagLib::APE::ItemListMap& itemListMap = apeTag->itemListMap();
6071           for (int i = Frame::PT_CoverFront; i <= Frame::PT_PublisherLogo; ++i) {
6072             if (auto pt = static_cast<Frame::PictureType>(i);
6073                 itemListMap.find(getApePictureName(pt)) == itemListMap.end()) {
6074               pictureType = pt;
6075               break;
6076             }
6077           }
6078           PictureFrame::setFields(
6079                 frame, Frame::TE_ISO8859_1, QLatin1String("JPG"),
6080                 QLatin1String("image/jpeg"), pictureType);
6081         }
6082         QString name(getApeName(frame));
6083         TagLib::String tname = toTString(name);
6084         if (frame.getType() == Frame::FT_Picture) {
6085           TagLib::ByteVector data;
6086           renderApePicture(frame, data);
6087           apeTag->setData(tname, data);
6088         } else {
6089           TagLib::String tvalue = toTString(frame.getValue());
6090           if (tvalue.isEmpty()) {
6091             tvalue = " "; // empty values are not added by TagLib
6092           }
6093           apeTag->addValue(tname, tvalue, true);
6094         }
6095         frame.setExtendedType(Frame::ExtendedType(frame.getType(), name));
6096 
6097         const TagLib::APE::ItemListMap& itemListMap = apeTag->itemListMap();
6098         int index = 0;
6099         bool found = false;
6100         for (auto it = itemListMap.begin();
6101              it != itemListMap.end();
6102              ++it) {
6103           if (it->first == tname) {
6104             found = true;
6105             break;
6106           }
6107           ++index;
6108         }
6109         frame.setIndex(found ? index : -1);
6110         markTagChanged(tagNr, frame.getExtendedType());
6111         return true;
6112       } else if (TagLib::MP4::Tag* mp4Tag;
6113                  (mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[tagNr])) != nullptr) {
6114         if (frame.getType() == Frame::FT_Picture) {
6115           if (frame.getFieldList().empty()) {
6116             PictureFrame::setFields(frame);
6117           }
6118           if (m_pictures.isRead()) {
6119             frame.setIndex(Frame::toNegativeIndex(m_pictures.size()));
6120             m_pictures.append(frame);
6121             markTagChanged(tagNr, frame.getExtendedType());
6122             return true;
6123           }
6124         }
6125         TagLib::String name;
6126         TagLib::MP4::Item item = getMp4ItemForFrame(frame, name);
6127         if (!item.isValid()) {
6128           return false;
6129         }
6130         frame.setExtendedType(Frame::ExtendedType(frame.getType(),
6131                                                   toQString(name)));
6132         prefixMp4FreeFormName(name, mp4Tag);
6133 #if TAGLIB_VERSION >= 0x010b01
6134         mp4Tag->setItem(name, item);
6135         const TagLib::MP4::ItemMap& itemListMap = mp4Tag->itemMap();
6136 #else
6137         mp4Tag->itemListMap()[name] = item;
6138         const TagLib::MP4::ItemListMap& itemListMap = mp4Tag->itemListMap();
6139 #endif
6140         int index = 0;
6141         bool found = false;
6142         for (auto it = itemListMap.begin();
6143              it != itemListMap.end();
6144              ++it) {
6145           if (it->first == name) {
6146             found = true;
6147             break;
6148           }
6149           ++index;
6150         }
6151         frame.setIndex(found ? index : -1);
6152         markTagChanged(tagNr, frame.getExtendedType());
6153         return true;
6154       } else if (TagLib::ASF::Tag* asfTag;
6155                  (asfTag = dynamic_cast<TagLib::ASF::Tag*>(m_tag[tagNr])) != nullptr) {
6156         if (frame.getType() == Frame::FT_Picture &&
6157             frame.getFieldList().empty()) {
6158           PictureFrame::setFields(frame);
6159         }
6160         TagLib::String name;
6161         TagLib::ASF::Attribute::AttributeTypes valueType;
6162         getAsfTypeForFrame(frame, name, valueType);
6163         if (valueType == TagLib::ASF::Attribute::BytesType &&
6164             frame.getType() != Frame::FT_Picture) {
6165           Frame::Field field;
6166           field.m_id = Frame::ID_Data;
6167           field.m_value = QByteArray();
6168           frame.fieldList().push_back(field);
6169         }
6170         TagLib::ASF::Attribute attribute = getAsfAttributeForFrame(frame, valueType);
6171         asfTag->addAttribute(name, attribute);
6172         frame.setExtendedType(Frame::ExtendedType(frame.getType(),
6173                                                   toQString(name)));
6174 
6175         const TagLib::ASF::AttributeListMap& attrListMap = asfTag->attributeListMap();
6176         int index = AFI_Attributes;
6177         bool found = false;
6178         for (auto it = attrListMap.begin();
6179              it != attrListMap.end();
6180              ++it) {
6181           if (it->first == name) {
6182             index += it->second.size() - 1;
6183             found = true;
6184             break;
6185           }
6186           index += it->second.size();
6187         }
6188         frame.setIndex(found ? index : -1);
6189         markTagChanged(tagNr, frame.getExtendedType());
6190         return true;
6191 #if TAGLIB_VERSION >= 0x010a00
6192       } else if (auto infoTag =
6193                  dynamic_cast<TagLib::RIFF::Info::Tag*>(m_tag[tagNr])) {
6194         TagLib::ByteVector id = getInfoName(frame);
6195         TagLib::String tvalue = toTString(frame.getValue());
6196         if (tvalue.isEmpty()) {
6197           tvalue = " "; // empty values are not added by TagLib
6198         }
6199         infoTag->setFieldText(id, tvalue);
6200         QString name = QString::fromLatin1(id.data(), id.size());
6201         frame.setExtendedType(Frame::ExtendedType(frame.getType(), name));
6202         const TagLib::RIFF::Info::FieldListMap itemListMap = infoTag->fieldListMap();
6203         int index = 0;
6204         bool found = false;
6205         for (auto it = itemListMap.begin(); it != itemListMap.end(); ++it) {
6206           if (it->first == id) {
6207             found = true;
6208             break;
6209           }
6210           ++index;
6211         }
6212         frame.setIndex(found ? index : -1);
6213         markTagChanged(tagNr, frame.getExtendedType());
6214         return true;
6215 #endif
6216       }
6217     }
6218   }
6219 
6220   // Try the superclass method
6221   return TaggedFile::addFrame(tagNr, frame);
6222 }
6223 
6224 /**
6225  * Delete a frame from the tags.
6226  *
6227  * @param tagNr tag number
6228  * @param frame frame to delete.
6229  *
6230  * @return true if ok.
6231  */
6232 bool TagLibFile::deleteFrame(Frame::TagNumber tagNr, const Frame& frame)
6233 {
6234   if (tagNr >= NUM_TAGS)
6235     return false;
6236 
6237   if (tagNr != Frame::Tag_Id3v1) {
6238     makeFileOpen();
6239     // If the frame has an index, delete that specific frame
6240     if (int index = frame.getIndex(); index != -1 && m_tag[tagNr]) {
6241       if (TagLib::ID3v2::Tag* id3v2Tag;
6242           (id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr])) != nullptr) {
6243         if (const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList();
6244             index >= 0 && index < static_cast<int>(frameList.size())) {
6245           id3v2Tag->removeFrame(frameList[index]);
6246           markTagChanged(tagNr, frame.getExtendedType());
6247           return true;
6248         }
6249       } else if (TagLib::Ogg::XiphComment* oggTag;
6250                  (oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[tagNr])) != nullptr) {
6251         QString frameValue(frame.getValue());
6252         if (frame.getType() == Frame::FT_Picture) {
6253           if (m_pictures.isRead()) {
6254             if (int idx = Frame::fromNegativeIndex(frame.getIndex());
6255                 idx >= 0 && idx < m_pictures.size()) {
6256               m_pictures.removeAt(idx);
6257               while (idx < m_pictures.size()) {
6258                 m_pictures[idx].setIndex(Frame::toNegativeIndex(idx));
6259                 ++idx;
6260               }
6261               markTagChanged(tagNr, frame.getExtendedType());
6262               return true;
6263             }
6264           } else {
6265             PictureFrame::getFieldsToBase64(frame, frameValue);
6266           }
6267         }
6268         TagLib::String key =
6269           toTString(frame.getInternalName());
6270 #if TAGLIB_VERSION >= 0x010b01
6271         oggTag->removeFields(key, toTString(frameValue));
6272 #else
6273         oggTag->removeField(key, toTString(frameValue));
6274 #endif
6275         markTagChanged(tagNr, frame.getExtendedType());
6276         return true;
6277       } else if (TagLib::APE::Tag* apeTag;
6278                  (apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tag[tagNr])) != nullptr) {
6279         TagLib::String key = toTString(frame.getInternalName());
6280         apeTag->removeItem(key);
6281         markTagChanged(tagNr, frame.getExtendedType());
6282         return true;
6283       } else if (TagLib::MP4::Tag* mp4Tag;
6284                  (mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[tagNr])) != nullptr) {
6285         if (frame.getType() == Frame::FT_Picture) {
6286           if (m_pictures.isRead()) {
6287             if (int idx = Frame::fromNegativeIndex(frame.getIndex());
6288                 idx >= 0 && idx < m_pictures.size()) {
6289               m_pictures.removeAt(idx);
6290               while (idx < m_pictures.size()) {
6291                 m_pictures[idx].setIndex(Frame::toNegativeIndex(idx));
6292                 ++idx;
6293               }
6294               markTagChanged(tagNr, frame.getExtendedType());
6295               return true;
6296             }
6297           }
6298         }
6299         TagLib::String name = toTString(frame.getInternalName());
6300         prefixMp4FreeFormName(name, mp4Tag);
6301 #if TAGLIB_VERSION >= 0x010b01
6302         mp4Tag->removeItem(name);
6303 #else
6304         mp4Tag->itemListMap().erase(name);
6305 #endif
6306         markTagChanged(tagNr, frame.getExtendedType());
6307         return true;
6308       } else if (TagLib::ASF::Tag* asfTag;
6309                  (asfTag = dynamic_cast<TagLib::ASF::Tag*>(m_tag[tagNr])) != nullptr) {
6310         switch (index) {
6311           case AFI_Title:
6312             asfTag->setTitle("");
6313             break;
6314           case AFI_Artist:
6315             asfTag->setArtist("");
6316             break;
6317           case AFI_Comment:
6318             asfTag->setComment("");
6319             break;
6320           case AFI_Copyright:
6321             asfTag->setCopyright("");
6322             break;
6323           case AFI_Rating:
6324             asfTag->setRating("");
6325             break;
6326           case AFI_Attributes:
6327           default:
6328           {
6329             TagLib::String name = toTString(frame.getInternalName());
6330             if (TagLib::ASF::AttributeListMap& attrListMap = asfTag->attributeListMap();
6331                 attrListMap.contains(name) && attrListMap[name].size() > 1) {
6332               int i = AFI_Attributes;
6333               bool found = false;
6334               for (auto it = attrListMap.begin();
6335                    it != attrListMap.end();
6336                    ++it) {
6337                 TagLib::ASF::AttributeList& attrList = it->second;
6338                 for (auto ait = attrList.begin();
6339                      ait != attrList.end();
6340                      ++ait) {
6341                   if (i++ == index) {
6342                     found = true;
6343                     attrList.erase(ait);
6344                     break;
6345                   }
6346                 }
6347                 if (found) {
6348                   break;
6349                 }
6350               }
6351             } else {
6352               asfTag->removeItem(name);
6353             }
6354           }
6355         }
6356         markTagChanged(tagNr, frame.getExtendedType());
6357         return true;
6358 #if TAGLIB_VERSION >= 0x010a00
6359       } else if (auto infoTag =
6360                  dynamic_cast<TagLib::RIFF::Info::Tag*>(m_tag[tagNr])) {
6361         QByteArray ba = frame.getInternalName().toLatin1();
6362         TagLib::ByteVector id(ba.constData(), ba.size());
6363         infoTag->removeField(id);
6364         markTagChanged(tagNr, frame.getExtendedType());
6365         return true;
6366 #endif
6367       }
6368     }
6369   }
6370 
6371   // Try the superclass method
6372   return TaggedFile::deleteFrame(tagNr, frame);
6373 }
6374 
6375 namespace {
6376 
6377 /**
6378  * Create a frame from a TagLib ID3 frame.
6379  * @param id3Frame TagLib ID3 frame
6380  * @param index -1 if not used
6381  * @return frame.
6382  */
6383 Frame createFrameFromId3Frame(const TagLib::ID3v2::Frame* id3Frame, int index)
6384 {
6385   Frame::Type type;
6386   const char* name;
6387   getTypeStringForFrameId(id3Frame->frameID(), type, name);
6388   Frame frame(type, toQString(id3Frame->toString()), QString::fromLatin1(name), index);
6389   frame.setValue(getFieldsFromId3Frame(id3Frame, frame.fieldList(), type));
6390   if (id3Frame->frameID().mid(1, 3) == "XXX" ||
6391       type == Frame::FT_Comment) {
6392     if (QVariant fieldValue = frame.getFieldValue(Frame::ID_Description);
6393         fieldValue.isValid()) {
6394       if (QString description = fieldValue.toString(); !description.isEmpty()) {
6395         if (description == QLatin1String("CATALOGNUMBER")) {
6396           frame.setType(Frame::FT_CatalogNumber);
6397         } else if (description == QLatin1String("RELEASECOUNTRY")) {
6398           frame.setType(Frame::FT_ReleaseCountry);
6399         } else if (description == QLatin1String("GROUPING")) {
6400           frame.setType(Frame::FT_Grouping);
6401         } else if (description == QLatin1String("SUBTITLE")) {
6402           frame.setType(Frame::FT_Subtitle);
6403         } else {
6404           if (description.startsWith(QLatin1String("QuodLibet::"))) {
6405             // remove ExFalso/QuodLibet "namespace"
6406             description = description.mid(11);
6407           }
6408           frame.setExtendedType(Frame::ExtendedType(
6409             Frame::getTypeFromCustomFrameName(description.toLatin1()),
6410             frame.getInternalName() + QLatin1Char('\n') + description));
6411         }
6412       }
6413     }
6414   } else if (id3Frame->frameID().startsWith("PRIV")) {
6415     if (QVariant fieldValue = frame.getFieldValue(Frame::ID_Owner);
6416         fieldValue.isValid()) {
6417       if (QString owner = fieldValue.toString(); !owner.isEmpty()) {
6418         frame.setExtendedType(Frame::ExtendedType(Frame::FT_Other,
6419                   frame.getInternalName() + QLatin1Char('\n') + owner));
6420       }
6421     }
6422   }
6423   return frame;
6424 }
6425 
6426 }
6427 
6428 /**
6429  * Remove frames.
6430  *
6431  * @param tagNr tag number
6432  * @param flt filter specifying which frames to remove
6433  */
6434 void TagLibFile::deleteFrames(Frame::TagNumber tagNr, const FrameFilter& flt)
6435 {
6436   if (tagNr >= NUM_TAGS)
6437     return;
6438 
6439   makeFileOpen();
6440   if (tagNr == Frame::Tag_Id3v1) {
6441     if (m_tag[tagNr]) {
6442       TaggedFile::deleteFrames(tagNr, flt);
6443     }
6444   } else {
6445     if (m_tag[tagNr]) {
6446       TagLib::ID3v2::Tag* id3v2Tag;
6447       TagLib::Ogg::XiphComment* oggTag;
6448       TagLib::APE::Tag* apeTag;
6449       TagLib::MP4::Tag* mp4Tag;
6450       TagLib::ASF::Tag* asfTag;
6451       if (flt.areAllEnabled()) {
6452         if ((id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr])) != nullptr) {
6453           const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList();
6454           for (auto it = frameList.begin();
6455                it != frameList.end();) {
6456             id3v2Tag->removeFrame(*it++, true);
6457           }
6458           markTagChanged(tagNr, Frame::ExtendedType());
6459         } else if ((oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[tagNr])) !=
6460                    nullptr) {
6461           const TagLib::Ogg::FieldListMap& fieldListMap = oggTag->fieldListMap();
6462           for (auto it = fieldListMap.begin();
6463                it != fieldListMap.end();) {
6464 #if TAGLIB_VERSION >= 0x010b01
6465             oggTag->removeFields((it++)->first);
6466 #else
6467             oggTag->removeField((it++)->first);
6468 #endif
6469           }
6470           m_pictures.clear();
6471           markTagChanged(tagNr, Frame::ExtendedType());
6472         } else if ((apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tag[tagNr])) != nullptr) {
6473           const TagLib::APE::ItemListMap& itemListMap = apeTag->itemListMap();
6474           for (auto it = itemListMap.begin();
6475                it != itemListMap.end();) {
6476             apeTag->removeItem((it++)->first);
6477           }
6478           markTagChanged(tagNr, Frame::ExtendedType());
6479         } else if ((mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[tagNr])) != nullptr) {
6480 #if TAGLIB_VERSION >= 0x010b01
6481           const auto& itemMap = mp4Tag->itemMap();
6482           for (auto it = itemMap.begin(); it != itemMap.end();) {
6483             mp4Tag->removeItem((it++)->first);
6484           }
6485 #else
6486           mp4Tag->itemListMap().clear();
6487 #endif
6488           m_pictures.clear();
6489           markTagChanged(tagNr, Frame::ExtendedType());
6490         } else if ((asfTag = dynamic_cast<TagLib::ASF::Tag*>(m_tag[tagNr])) != nullptr) {
6491           asfTag->setTitle("");
6492           asfTag->setArtist("");
6493           asfTag->setComment("");
6494           asfTag->setCopyright("");
6495           asfTag->setRating("");
6496           asfTag->attributeListMap().clear();
6497           markTagChanged(tagNr, Frame::ExtendedType());
6498 #if TAGLIB_VERSION >= 0x010a00
6499         } else if (auto infoTag =
6500                    dynamic_cast<TagLib::RIFF::Info::Tag*>(m_tag[tagNr])) {
6501           const TagLib::RIFF::Info::FieldListMap itemListMap = infoTag->fieldListMap();
6502           for (auto it = itemListMap.begin(); it != itemListMap.end(); ++it) {
6503             infoTag->removeField(it->first);
6504           }
6505           markTagChanged(tagNr, Frame::ExtendedType());
6506 #endif
6507         } else {
6508           TaggedFile::deleteFrames(tagNr, flt);
6509         }
6510       } else {
6511         if ((id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr])) != nullptr) {
6512           const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList();
6513           for (auto it = frameList.begin();
6514                it != frameList.end();) {
6515             if (Frame frame(createFrameFromId3Frame(*it, -1));
6516                 flt.isEnabled(frame.getType(), frame.getName())) {
6517               id3v2Tag->removeFrame(*it++, true);
6518             } else {
6519               ++it;
6520             }
6521           }
6522           markTagChanged(tagNr, Frame::ExtendedType());
6523         } else if ((oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[tagNr])) !=
6524                    nullptr) {
6525           const TagLib::Ogg::FieldListMap& fieldListMap = oggTag->fieldListMap();
6526           for (auto it = fieldListMap.begin();
6527                it != fieldListMap.end();) {
6528             if (QString name(toQString(it->first));
6529                 flt.isEnabled(getTypeFromVorbisName(name), name)) {
6530 #if TAGLIB_VERSION >= 0x010b01
6531               oggTag->removeFields((it++)->first);
6532 #else
6533               oggTag->removeField((it++)->first);
6534 #endif
6535             } else {
6536               ++it;
6537             }
6538           }
6539           if (flt.isEnabled(Frame::FT_Picture)) {
6540             m_pictures.clear();
6541           }
6542           markTagChanged(tagNr, Frame::ExtendedType());
6543         } else if ((apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tag[tagNr])) != nullptr) {
6544           const TagLib::APE::ItemListMap& itemListMap = apeTag->itemListMap();
6545           for (auto it = itemListMap.begin();
6546                it != itemListMap.end();) {
6547             if (QString name(toQString(it->first));
6548                 flt.isEnabled(getTypeFromApeName(name), name)) {
6549               apeTag->removeItem((it++)->first);
6550             } else {
6551               ++it;
6552             }
6553           }
6554           markTagChanged(tagNr, Frame::ExtendedType());
6555         } else if ((mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[tagNr])) != nullptr) {
6556 #if TAGLIB_VERSION >= 0x010b01
6557           const auto& itemMap = mp4Tag->itemMap();
6558           for (auto it = itemMap.begin(); it != itemMap.end();) {
6559             TagLib::String name = it->first;
6560             stripMp4FreeFormName(name);
6561             Frame::Type type;
6562             Mp4ValueType valueType;
6563             getMp4TypeForName(name, type, valueType);
6564             if (flt.isEnabled(type, toQString(name))) {
6565               mp4Tag->removeItem((it++)->first);
6566             } else {
6567               ++it;
6568             }
6569           }
6570 #else
6571           TagLib::MP4::ItemListMap& itemListMap = mp4Tag->itemListMap();
6572           Frame::Type type;
6573           Mp4ValueType valueType;
6574           for (auto it = itemListMap.begin();
6575                it != itemListMap.end();) {
6576             TagLib::String name = it->first;
6577             stripMp4FreeFormName(name);
6578             getMp4TypeForName(name, type, valueType);
6579             if (flt.isEnabled(type, toQString(name))) {
6580               itemListMap.erase(it++);
6581             } else {
6582               ++it;
6583             }
6584           }
6585 #endif
6586           if (flt.isEnabled(Frame::FT_Picture)) {
6587             m_pictures.clear();
6588           }
6589           markTagChanged(tagNr, Frame::ExtendedType());
6590         } else if ((asfTag = dynamic_cast<TagLib::ASF::Tag*>(m_tag[tagNr])) != nullptr) {
6591           if (flt.isEnabled(Frame::FT_Title))
6592             asfTag->setTitle("");
6593           if (flt.isEnabled(Frame::FT_Artist))
6594             asfTag->setArtist("");
6595           if (flt.isEnabled(Frame::FT_Comment))
6596             asfTag->setComment("");
6597           if (flt.isEnabled(Frame::FT_Copyright))
6598             asfTag->setCopyright("");
6599           if (flt.isEnabled(Frame::FT_Other, QLatin1String("Rating Information")))
6600             asfTag->setRating("");
6601 
6602           TagLib::ASF::AttributeListMap& attrListMap = asfTag->attributeListMap();
6603           Frame::Type type;
6604           TagLib::ASF::Attribute::AttributeTypes valueType;
6605           for (auto it = attrListMap.begin();
6606                it != attrListMap.end();) {
6607             getAsfTypeForName(it->first, type, valueType);
6608             if (QString name(toQString(it->first));
6609                 flt.isEnabled(type, name)) {
6610               attrListMap.erase(it++);
6611             } else {
6612               ++it;
6613             }
6614           }
6615           markTagChanged(tagNr, Frame::ExtendedType());
6616 #if TAGLIB_VERSION >= 0x010a00
6617         } else if (auto infoTag =
6618                    dynamic_cast<TagLib::RIFF::Info::Tag*>(m_tag[tagNr])) {
6619           const TagLib::RIFF::Info::FieldListMap itemListMap = infoTag->fieldListMap();
6620           for (auto it = itemListMap.begin(); it != itemListMap.end(); ++it) {
6621             TagLib::ByteVector id = it->first;
6622             if (QString name = QString::fromLatin1(id.data(), id.size());
6623                 flt.isEnabled(getTypeFromInfoName(id), name)) {
6624               infoTag->removeField(id);
6625             }
6626           }
6627           markTagChanged(tagNr, Frame::ExtendedType());
6628 #endif
6629         } else {
6630           TaggedFile::deleteFrames(tagNr, flt);
6631         }
6632       }
6633     }
6634   }
6635 }
6636 
6637 /**
6638  * Get all frames in tag.
6639  *
6640  * @param tagNr tag number
6641  * @param frames frame collection to set.
6642  */
6643 void TagLibFile::getAllFrames(Frame::TagNumber tagNr, FrameCollection& frames)
6644 {
6645   if (tagNr >= NUM_TAGS)
6646     return;
6647 
6648   if (tagNr != Frame::Tag_Id3v1) {
6649     makeFileOpen();
6650     frames.clear();
6651     if (m_tag[tagNr]) {
6652       if (TagLib::ID3v2::Tag* id3v2Tag;
6653           (id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr])) != nullptr) {
6654         const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList();
6655         int i = 0;
6656         for (auto it = frameList.begin();
6657              it != frameList.end();
6658              ++it) {
6659           Frame frame(createFrameFromId3Frame(*it, i++));
6660           if (frame.getType() == Frame::FT_UnknownFrame) {
6661             if (TagLib::ByteVector frameID = (*it)->frameID().mid(0, 4);
6662                 frameID == "TDAT" || frameID == "TIME" || frameID == "TRDA" ||
6663                 frameID == "TYER") {
6664               // These frames are converted to a TDRC frame by TagLib.
6665               continue;
6666             }
6667           }
6668           frames.insert(frame);
6669         }
6670       } else if (TagLib::Ogg::XiphComment* oggTag;
6671                  (oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[tagNr])) != nullptr) {
6672         const TagLib::Ogg::FieldListMap& fieldListMap = oggTag->fieldListMap();
6673         int i = 0;
6674         for (auto it = fieldListMap.begin();
6675              it != fieldListMap.end();
6676              ++it) {
6677           QString name = toQString(it->first);
6678           Frame::Type type = getTypeFromVorbisName(name);
6679           const TagLib::StringList stringList = it->second;
6680           for (auto slit = stringList.begin(); slit != stringList.end(); ++slit) {
6681             if (type == Frame::FT_Picture) {
6682               Frame frame(type, QLatin1String(""), name, i++);
6683               PictureFrame::setFieldsFromBase64(
6684                     frame, toQString(TagLib::String(*slit)));
6685               if (name == QLatin1String("COVERART")) {
6686                 if (TagLib::StringList mt = oggTag->fieldListMap()["COVERARTMIME"];
6687                     !mt.isEmpty()) {
6688                   PictureFrame::setMimeType(frame, toQString(mt.front()));
6689                 }
6690               }
6691               frames.insert(frame);
6692             } else {
6693               frames.insert(Frame(type, toQString(TagLib::String(*slit)),
6694                                   name, i++));
6695             }
6696           }
6697         }
6698         if (m_pictures.isRead()) {
6699           for (auto it = m_pictures.constBegin(); it != m_pictures.constEnd(); ++it) {
6700             frames.insert(*it);
6701           }
6702         }
6703       } else if (TagLib::APE::Tag* apeTag;
6704                  (apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tag[tagNr])) != nullptr) {
6705         const TagLib::APE::ItemListMap& itemListMap = apeTag->itemListMap();
6706         int i = 0;
6707         for (auto it = itemListMap.begin();
6708              it != itemListMap.end();
6709              ++it) {
6710           QString name = toQString(it->first);
6711           Frame::Type type = getTypeFromApeName(name);
6712           TagLib::StringList values;
6713           if (type != Frame::FT_Picture) {
6714             values = it->second.values();
6715           }
6716           Frame frame(type, values.size() > 0
6717                       ? joinToQString(values) : QLatin1String(""),
6718                       name, i++);
6719           if (type == Frame::FT_Picture) {
6720             TagLib::ByteVector data = it->second.binaryData();
6721             parseApePicture(name, data, frame);
6722           }
6723           frames.insert(frame);
6724         }
6725       } else if (TagLib::MP4::Tag* mp4Tag;
6726                  (mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[tagNr])) != nullptr) {
6727 #if TAGLIB_VERSION >= 0x010b01
6728         const TagLib::MP4::ItemMap& itemListMap = mp4Tag->itemMap();
6729 #else
6730         const TagLib::MP4::ItemListMap& itemListMap = mp4Tag->itemListMap();
6731 #endif
6732         int i = 0;
6733         for (auto it = itemListMap.begin();
6734              it != itemListMap.end();
6735              ++it) {
6736           TagLib::String name = it->first;
6737           stripMp4FreeFormName(name);
6738           Frame::Type type;
6739           Mp4ValueType valueType;
6740           getMp4TypeForName(name, type, valueType);
6741           QString value;
6742           switch (valueType) {
6743             case MVT_String:
6744             {
6745               TagLib::StringList strings = it->second.toStringList();
6746               value = strings.size() > 0
6747                   ? joinToQString(strings)
6748                   : QLatin1String("");
6749               break;
6750             }
6751             case MVT_Bool:
6752               value = it->second.toBool() ? QLatin1String("1") : QLatin1String("0");
6753               break;
6754             case MVT_Int:
6755               value.setNum(it->second.toInt());
6756               break;
6757             case MVT_IntPair:
6758             {
6759               auto [first, second] = it->second.toIntPair();
6760               value.setNum(first);
6761               if (second != 0) {
6762                 value += QLatin1Char('/');
6763                 value += QString::number(second);
6764               }
6765               break;
6766             }
6767             case MVT_CoverArt:
6768               // handled by m_pictures
6769               break;
6770             case MVT_Byte:
6771               value.setNum(it->second.toByte());
6772               break;
6773             case MVT_UInt:
6774               value.setNum(it->second.toUInt());
6775               break;
6776             case MVT_LongLong:
6777               value.setNum(it->second.toLongLong());
6778               break;
6779             case MVT_ByteArray:
6780             default:
6781               // binary data and album art are not handled by TagLib
6782               value = QLatin1String("");
6783           }
6784           if (type != Frame::FT_Picture) {
6785             frames.insert(
6786               Frame(type, value, toQString(name), i++));
6787           }
6788         }
6789         if (m_pictures.isRead()) {
6790           for (auto it = m_pictures.constBegin(); it != m_pictures.constEnd(); ++it) {
6791             frames.insert(*it);
6792           }
6793         }
6794       } else if (TagLib::ASF::Tag* asfTag;
6795                  (asfTag = dynamic_cast<TagLib::ASF::Tag*>(m_tag[tagNr])) != nullptr) {
6796         TagLib::String name;
6797         TagLib::ASF::Attribute::AttributeTypes valueType;
6798         Frame::Type type = Frame::FT_Title;
6799         getAsfNameForType(type, name, valueType);
6800         QString value = toQString(asfTag->title());
6801         frames.insert(Frame(type, value, toQString(name), AFI_Title));
6802 
6803         type = Frame::FT_Artist;
6804         getAsfNameForType(type, name, valueType);
6805         value = toQString(asfTag->artist());
6806         frames.insert(Frame(type, value, toQString(name), AFI_Artist));
6807 
6808         type = Frame::FT_Comment;
6809         getAsfNameForType(type, name, valueType);
6810         value = toQString(asfTag->comment());
6811         frames.insert(Frame(type, value, toQString(name), AFI_Comment));
6812 
6813         type = Frame::FT_Copyright;
6814         getAsfNameForType(type, name, valueType);
6815         value = toQString(asfTag->copyright());
6816         frames.insert(Frame(type, value, toQString(name), AFI_Copyright));
6817 
6818         name = QT_TRANSLATE_NOOP("@default", "Rating Information");
6819         getAsfTypeForName(name, type, valueType);
6820         value = toQString(asfTag->rating());
6821         frames.insert(Frame(type, value, toQString(name), AFI_Rating));
6822 
6823         int i = AFI_Attributes;
6824         QByteArray ba;
6825         const TagLib::ASF::AttributeListMap& attrListMap = asfTag->attributeListMap();
6826         for (auto it = attrListMap.begin();
6827              it != attrListMap.end();
6828              ++it) {
6829           name = it->first;
6830           getAsfTypeForName(name, type, valueType);
6831           for (auto ait = it->second.begin();
6832                ait != it->second.end();
6833                ++ait) {
6834             switch (ait->type()) {
6835               case TagLib::ASF::Attribute::UnicodeType:
6836                 value = toQString(ait->toString());
6837                 break;
6838               case TagLib::ASF::Attribute::BoolType:
6839                 value = ait->toBool() ? QLatin1String("1") : QLatin1String("0");
6840                 break;
6841               case TagLib::ASF::Attribute::DWordType:
6842                 value.setNum(ait->toUInt());
6843                 break;
6844               case TagLib::ASF::Attribute::QWordType:
6845                 value.setNum(ait->toULongLong());
6846                 break;
6847               case TagLib::ASF::Attribute::WordType:
6848                 value.setNum(ait->toUShort());
6849                 break;
6850               case TagLib::ASF::Attribute::BytesType:
6851               case TagLib::ASF::Attribute::GuidType:
6852               default:
6853               {
6854                 TagLib::ByteVector bv = ait->toByteVector();
6855                 ba = QByteArray(bv.data(), bv.size());
6856                 value = QLatin1String("");
6857                 AttributeData(toQString(name)).toString(ba, value);
6858               }
6859             }
6860             Frame frame(type, value, toQString(name), i);
6861             if (ait->type() == TagLib::ASF::Attribute::BytesType &&
6862                 valueType == TagLib::ASF::Attribute::BytesType) {
6863               Frame::Field field;
6864               field.m_id = Frame::ID_Data;
6865               field.m_value = ba;
6866               frame.fieldList().push_back(field);
6867             }
6868             ++i;
6869             if (type == Frame::FT_Picture) {
6870               parseAsfPicture(ait->toPicture(), frame);
6871             }
6872             frames.insert(frame);
6873           }
6874         }
6875 #if TAGLIB_VERSION >= 0x010a00
6876       } else if (auto infoTag =
6877                  dynamic_cast<TagLib::RIFF::Info::Tag*>(m_tag[tagNr])) {
6878         const TagLib::RIFF::Info::FieldListMap itemListMap = infoTag->fieldListMap();
6879         int i = 0;
6880         for (auto it = itemListMap.begin(); it != itemListMap.end(); ++it) {
6881           TagLib::ByteVector id = it->first;
6882           TagLib::String s = it->second;
6883           QString name = QString::fromLatin1(id.data(), id.size());
6884           QString value = toQString(s);
6885           Frame::Type type = getTypeFromInfoName(id);
6886           Frame frame(type, value, name, i++);
6887           frames.insert(frame);
6888         }
6889 #endif
6890       } else {
6891         TaggedFile::getAllFrames(tagNr, frames);
6892       }
6893     }
6894     updateMarkedState(tagNr, frames);
6895     if (tagNr <= Frame::Tag_2) {
6896       frames.addMissingStandardFrames();
6897     }
6898     return;
6899   }
6900 
6901   TaggedFile::getAllFrames(tagNr, frames);
6902 }
6903 
6904 /**
6905  * Close file handle which is held open by the TagLib object.
6906  */
6907 void TagLibFile::closeFileHandle()
6908 {
6909   closeFile(false);
6910 }
6911 
6912 /**
6913  * Add a suitable field list for the frame if missing.
6914  * If a frame is created, its field list is empty. This method will create
6915  * a field list appropriate for the frame type and tagged file type if no
6916  * field list exists.
6917  * @param tagNr tag number
6918  * @param frame frame where field list is added
6919  */
6920 void TagLibFile::addFieldList(Frame::TagNumber tagNr, Frame& frame) const
6921 {
6922   if (dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr]) != nullptr &&
6923       frame.fieldList().isEmpty()) {
6924     TagLib::ID3v2::Frame* id3Frame = createId3FrameFromFrame(this, frame);
6925     getFieldsFromId3Frame(id3Frame, frame.fieldList(), frame.getType());
6926     frame.setFieldListFromValue();
6927     delete id3Frame;
6928   }
6929 }
6930 
6931 /**
6932  * Get a list of frame IDs which can be added.
6933  * @param tagNr tag number
6934  * @return list with frame IDs.
6935  */
6936 QStringList TagLibFile::getFrameIds(Frame::TagNumber tagNr) const
6937 {
6938   QStringList lst;
6939   if (m_tagType[tagNr] == TT_Id3v2 ||
6940       (m_tagType[tagNr] == TT_Unknown &&
6941        dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr]))) {
6942     for (int k = Frame::FT_FirstFrame; k <= Frame::FT_LastFrame; ++k) {
6943       if (auto name = Frame::ExtendedType(
6944             static_cast<Frame::Type>(k), QLatin1String("")).getName(); // clazy:exclude=reserve-candidates
6945           !name.isEmpty()) {
6946         lst.append(name);
6947       }
6948     }
6949     for (const auto& [str, type, supported] : typeStrOfId) {
6950       if (type == Frame::FT_Other && supported && str) {
6951         lst.append(QString::fromLatin1(str));
6952       }
6953     }
6954   } else if (m_tagType[tagNr] == TT_Mp4) {
6955     Mp4ValueType valueType;
6956     for (int k = Frame::FT_FirstFrame; k <= Frame::FT_LastFrame; ++k) {
6957       TagLib::String name = "";
6958       auto type = static_cast<Frame::Type>(k);
6959       getMp4NameForType(type, name, valueType);
6960       if (!name.isEmpty() && valueType != MVT_ByteArray &&
6961           !(name[0] >= 'A' && name[0] <= 'Z')) {
6962         lst.append(Frame::ExtendedType(type, QLatin1String("")).getName()); // clazy:exclude=reserve-candidates
6963       }
6964     }
6965     for (const auto& [name, type, value] : mp4NameTypeValues) {
6966       if (type == Frame::FT_Other &&
6967           value != MVT_ByteArray &&
6968           !(name[0] >= 'A' &&
6969             name[0] <= 'Z')) {
6970         lst.append(QString::fromLatin1(name));
6971       }
6972     }
6973   } else if (m_tagType[tagNr] == TT_Asf) {
6974     TagLib::ASF::Attribute::AttributeTypes valueType;
6975     for (int k = Frame::FT_FirstFrame; k <= Frame::FT_LastFrame; ++k) {
6976       TagLib::String name = "";
6977       auto type = static_cast<Frame::Type>(k);
6978       getAsfNameForType(type, name, valueType);
6979       if (!name.isEmpty()) {
6980         lst.append(Frame::ExtendedType(type, QLatin1String("")).getName()); // clazy:exclude=reserve-candidates
6981       }
6982     }
6983     for (const auto& [name, type, value] : asfNameTypeValues) {
6984       if (type == Frame::FT_Other) {
6985         lst.append(QString::fromLatin1(name));
6986       }
6987     }
6988 #if TAGLIB_VERSION >= 0x010a00
6989   } else if (m_tagType[tagNr] == TT_Info) {
6990     static const char* const fieldNames[] = {
6991       "IARL", // Archival Location
6992       "ICMS", // Commissioned
6993       "ICRP", // Cropped
6994       "IDIM", // Dimensions
6995       "IDPI", // Dots Per Inch
6996       "IKEY", // Keywords
6997       "ILGT", // Lightness
6998       "IPLT", // Palette Setting
6999       "ISBJ", // Subject
7000       "ISHP", // Sharpness
7001       "ISRF", // Source Form
7002     };
7003     for (int k = Frame::FT_FirstFrame; k <= Frame::FT_LastFrame; ++k) {
7004       if (auto type = static_cast<Frame::Type>(k);
7005           !getInfoNameFromType(type).isEmpty()) {
7006         lst.append(Frame::ExtendedType(type, QLatin1String("")).getName()); // clazy:exclude=reserve-candidates
7007       }
7008     }
7009     for (auto fieldName : fieldNames) {
7010       lst.append(QString::fromLatin1(fieldName)); // clazy:exclude=reserve-candidates
7011     }
7012 #endif
7013   } else {
7014     static const char* const fieldNames[] = {
7015       "CONTACT",
7016       "DISCTOTAL",
7017       "EAN/UPN",
7018       "ENCODING",
7019       "ENGINEER",
7020       "ENSEMBLE",
7021       "GUESTARTIST",
7022       "LABEL",
7023       "LABELNO",
7024       "LICENSE",
7025       "LOCATION",
7026       "OPUS",
7027       "ORGANIZATION",
7028       "PARTNUMBER",
7029       "PRODUCER",
7030       "PRODUCTNUMBER",
7031       "RECORDINGDATE",
7032       "TRACKTOTAL",
7033       "VERSION",
7034       "VOLUME"
7035     };
7036     const bool picturesSupported = m_pictures.isRead() ||
7037         m_tagType[tagNr] == TT_Vorbis || m_tagType[tagNr] == TT_Ape;
7038     for (int k = Frame::FT_FirstFrame; k <= Frame::FT_LastFrame; ++k) {
7039       if (k != Frame::FT_Picture || picturesSupported) {
7040         if (auto name = Frame::ExtendedType(static_cast<Frame::Type>(k),
7041                                             QLatin1String("")).getName();
7042             !name.isEmpty()) {
7043           lst.append(name);
7044         }
7045       }
7046     }
7047     for (auto fieldName : fieldNames) {
7048       lst.append(QString::fromLatin1(fieldName)); // clazy:exclude=reserve-candidates
7049     }
7050   }
7051   return lst;
7052 }
7053 
7054 /**
7055  * Set the encoding to be used for tag 1.
7056  *
7057  * @param name of encoding, default is ISO 8859-1
7058  */
7059 void TagLibFile::setTextEncodingV1(const QString& name)
7060 {
7061 #if QT_VERSION >= 0x060000
7062   TextCodecStringHandler::setStringDecoder(name);
7063 #else
7064   TextCodecStringHandler::setTextCodec(name != QLatin1String("ISO-8859-1")
7065       ? QTextCodec::codecForName(name.toLatin1().data()) : nullptr);
7066 #endif
7067 }
7068 
7069 /**
7070  * Set the default text encoding.
7071  *
7072  * @param textEnc default text encoding
7073  */
7074 void TagLibFile::setDefaultTextEncoding(TagConfig::TextEncoding textEnc)
7075 {
7076   // Do not use TagLib::ID3v2::FrameFactory::setDefaultTextEncoding(),
7077   // it will change the encoding of existing frames read in, not only
7078   // of newly created frames, which is really not what we want!
7079   switch (textEnc) {
7080     case TagConfig::TE_ISO8859_1:
7081       s_defaultTextEncoding = TagLib::String::Latin1;
7082       break;
7083     case TagConfig::TE_UTF16:
7084       s_defaultTextEncoding = TagLib::String::UTF16;
7085       break;
7086     case TagConfig::TE_UTF8:
7087     default:
7088       s_defaultTextEncoding = TagLib::String::UTF8;
7089   }
7090 }
7091 
7092 /**
7093  * Notify about configuration change.
7094  * This method shall be called when the configuration changes.
7095  */
7096 void TagLibFile::notifyConfigurationChange()
7097 {
7098   setDefaultTextEncoding(
7099     static_cast<TagConfig::TextEncoding>(TagConfig::instance().textEncoding()));
7100   setTextEncodingV1(TagConfig::instance().textEncodingV1());
7101 }
7102 
7103 namespace {
7104 
7105 /**
7106  * Used to register file types at static initialization time.
7107  */
7108 class TagLibInitializer {
7109 public:
7110   /** Constructor. */
7111   TagLibInitializer();
7112 
7113   /** Destructor. */
7114   ~TagLibInitializer();
7115 
7116   /**
7117    * Initialization.
7118    * Is deferred because it will crash on Mac OS X if done in the constructor.
7119    */
7120   void init();
7121 
7122 private:
7123   Q_DISABLE_COPY(TagLibInitializer)
7124 
7125 #if TAGLIB_VERSION < 0x020000
7126   QScopedPointer<AACFileTypeResolver> m_aacFileTypeResolver;
7127   QScopedPointer<MP2FileTypeResolver> m_mp2FileTypeResolver;
7128 #endif
7129   QScopedPointer<TextCodecStringHandler> m_textCodecStringHandler;
7130 };
7131 
7132 
7133 TagLibInitializer::TagLibInitializer() :
7134 #if TAGLIB_VERSION < 0x020000
7135     m_aacFileTypeResolver(new AACFileTypeResolver),
7136     m_mp2FileTypeResolver(new MP2FileTypeResolver),
7137 #endif
7138     m_textCodecStringHandler(new TextCodecStringHandler)
7139 {
7140 }
7141 
7142 void TagLibInitializer::init()
7143 {
7144 #if TAGLIB_VERSION < 0x020000
7145   TagLib::FileRef::addFileTypeResolver(m_aacFileTypeResolver.data());
7146   TagLib::FileRef::addFileTypeResolver(m_mp2FileTypeResolver.data());
7147 #endif
7148   TagLib::ID3v1::Tag::setStringHandler(m_textCodecStringHandler.data());
7149 }
7150 
7151 TagLibInitializer::~TagLibInitializer() {
7152   // Must not be inline because of forwared declared QScopedPointer.
7153 }
7154 
7155 TagLibInitializer tagLibInitializer;
7156 
7157 }
7158 
7159 /**
7160  * Static initialization.
7161  * Registers file types.
7162  */
7163 void TagLibFile::staticInit()
7164 {
7165   tagLibInitializer.init();
7166 }