1 /**
2  * \file taglibfile.cpp
3  * Handling of tagged files using TagLib.
4  *
5  * \b Project: Kid3
6  * \author Urs Fleisch
7  * \date 12 Sep 2006
8  *
9  * Copyright (C) 2006-2018  Urs Fleisch
10  *
11  * This file is part of Kid3.
12  *
13  * Kid3 is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * Kid3 is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #include "taglibfile.h"
28 #include <QDir>
29 #include <QString>
30 #if QT_VERSION >= 0x060000
31 #include <QStringConverter>
32 #else
33 #include <QTextCodec>
34 #endif
35 #include <QByteArray>
36 #include <QVarLengthArray>
37 #include <QScopedPointer>
38 #include <QMimeDatabase>
39 #include "genres.h"
40 #include "attributedata.h"
41 #include "pictureframe.h"
42 
43 // Just using include <oggfile.h>, include <flacfile.h> as recommended in the
44 // TagLib documentation does not work, as there are files with these names
45 // in this directory.
46 #include <mpegfile.h>
47 #include <oggfile.h>
48 #include <vorbisfile.h>
49 #include <flacfile.h>
50 #include <mpcfile.h>
51 #include <id3v1tag.h>
52 #include <id3v2tag.h>
53 #include <id3v2header.h>
54 #include <apetag.h>
55 #include <textidentificationframe.h>
56 #include <commentsframe.h>
57 #include <attachedpictureframe.h>
58 #include <uniquefileidentifierframe.h>
59 #include <generalencapsulatedobjectframe.h>
60 #include <urllinkframe.h>
61 #include <unsynchronizedlyricsframe.h>
62 #include <speexfile.h>
63 #include <trueaudiofile.h>
64 #include <wavpackfile.h>
65 #include <oggflacfile.h>
66 #include <relativevolumeframe.h>
67 #include <mp4file.h>
68 #include <asffile.h>
69 #include <aifffile.h>
70 #include <wavfile.h>
71 #include <popularimeterframe.h>
72 #include <privateframe.h>
73 #include <apefile.h>
74 #include <ownershipframe.h>
75 #include <modfile.h>
76 #include <s3mfile.h>
77 #include <itfile.h>
78 #include <tfilestream.h>
79 #include <xmfile.h>
80 #include <opusfile.h>
81 #include "taglibext/dsf/dsffiletyperesolver.h"
82 #include "taglibext/dsf/dsffile.h"
83 
84 #if TAGLIB_VERSION >= 0x010a00
85 #include <synchronizedlyricsframe.h>
86 #include <eventtimingcodesframe.h>
87 #include <chapterframe.h>
88 #include <tableofcontentsframe.h>
89 #else
90 #include "taglibext/synchronizedlyricsframe.h"
91 #include "taglibext/eventtimingcodesframe.h"
92 #endif
93 
94 #if TAGLIB_VERSION >= 0x010b00
95 #include <podcastframe.h>
96 #endif
97 #include "taglibext/aac/aacfiletyperesolver.h"
98 #include "taglibext/mp2/mp2filetyperesolver.h"
99 
100 /** for loop through all supported tag number values. */
101 #define FOR_TAGLIB_TAGS(variable) \
102   for (Frame::TagNumber variable = Frame::Tag_1; \
103        variable < NUM_TAGS; \
104        variable = static_cast<Frame::TagNumber>(variable + 1))
105 
106 /** for loop through all supported tag number values in reverse order. */
107 #define FOR_TAGLIB_TAGS_REVERSE(variable) \
108   for (Frame::TagNumber variable = static_cast<Frame::TagNumber>(NUM_TAGS - 1); \
109        variable >= Frame::Tag_1; \
110        variable = static_cast<Frame::TagNumber>(variable - 1))
111 
112 #ifdef TAGLIB_WITH_OFFSET_TYPE
113 typedef TagLib::offset_t taglib_offset_t;
114 typedef TagLib::offset_t taglib_uoffset_t;
115 #else
116 typedef long taglib_offset_t;
117 typedef ulong taglib_uoffset_t;
118 #endif
119 
120 namespace {
121 
122 /** Convert QString @a s to a TagLib::String. */
toTString(const QString & s)123 TagLib::String toTString(const QString& s)
124 {
125   int len = s.length();
126   QVarLengthArray<wchar_t> a(len + 1);
127   wchar_t* ws = a.data();
128   len = s.toWCharArray(ws);
129   ws[len] = 0;
130   return TagLib::String(ws);
131 }
132 
133 /** Convert TagLib::String @a s to a QString. */
toQString(const TagLib::String & s)134 inline QString toQString(const TagLib::String& s)
135 {
136   return QString::fromWCharArray(s.toCWString(), s.size());
137 }
138 
139 /**
140  * Set a picture frame from a FLAC picture.
141  *
142  * @param pic FLAC picture
143  * @param frame the picture frame is returned here
144  */
flacPictureToFrame(const TagLib::FLAC::Picture * pic,Frame & frame)145 void flacPictureToFrame(const TagLib::FLAC::Picture* pic, Frame& frame)
146 {
147   TagLib::ByteVector picData(pic->data());
148   QByteArray ba(picData.data(), picData.size());
149   PictureFrame::ImageProperties imgProps(
150         pic->width(), pic->height(), pic->colorDepth(),
151         pic->numColors(), ba);
152   PictureFrame::setFields(
153     frame, Frame::TE_ISO8859_1, QLatin1String("JPG"), toQString(pic->mimeType()),
154     static_cast<PictureFrame::PictureType>(pic->type()),
155     toQString(pic->description()),
156     ba, &imgProps);
157 }
158 
159 /**
160  * Set a FLAC picture from a frame.
161  *
162  * @param frame picture frame
163  * @param pic the FLAC picture to set
164  */
frameToFlacPicture(const Frame & frame,TagLib::FLAC::Picture * pic)165 void frameToFlacPicture(const Frame& frame, TagLib::FLAC::Picture* pic)
166 {
167   Frame::TextEncoding enc;
168   QString imgFormat;
169   QString mimeType;
170   PictureFrame::PictureType pictureType;
171   QString description;
172   QByteArray data;
173   PictureFrame::ImageProperties imgProps;
174   PictureFrame::getFields(frame, enc, imgFormat, mimeType, pictureType,
175                           description, data, &imgProps);
176   pic->setType(static_cast<TagLib::FLAC::Picture::Type>(pictureType));
177   pic->setMimeType(toTString(mimeType));
178   pic->setDescription(toTString(description));
179   pic->setData(TagLib::ByteVector(data.data(), data.size()));
180   if (!imgProps.isValidForImage(data)) {
181     imgProps = PictureFrame::ImageProperties(data);
182   }
183   pic->setWidth(imgProps.width());
184   pic->setHeight(imgProps.height());
185   pic->setColorDepth(imgProps.depth());
186   pic->setNumColors(imgProps.numColors());
187 }
188 
189 
190 /**
191  * TagLib::RIFF::WAV::File subclass with additional method for id3 chunk name.
192  */
193 class WavFile : public TagLib::RIFF::WAV::File {
194 public:
195   /**
196    * Constructor.
197    * @param stream stream to open
198    */
199   explicit WavFile(TagLib::IOStream *stream);
200   virtual ~WavFile() override;
201 
202   /**
203    * Replace the "ID3 " chunk with a lowercase named "id3 " chunk.
204    * This method has to be called after successfully calling save() to use
205    * lowercase "id3 " chunk names.
206    */
207   void changeToLowercaseId3Chunk();
208 };
209 
210 /**
211  * Destructor.
212  */
~WavFile()213 WavFile::~WavFile()
214 {
215   // not inline or default to silence weak-vtables warning
216 }
217 
WavFile(TagLib::IOStream * stream)218 WavFile::WavFile(TagLib::IOStream *stream) : TagLib::RIFF::WAV::File(stream)
219 {
220 }
221 
changeToLowercaseId3Chunk()222 void WavFile::changeToLowercaseId3Chunk()
223 {
224   if (readOnly() || !isValid())
225     return;
226 
227   int i;
228   for (i = chunkCount() - 1; i >= 0; --i) {
229     if (chunkName(i) == "ID3 ") {
230       break;
231     }
232   }
233   if (i >= 0) {
234     TagLib::ByteVector data = chunkData(i);
235     removeChunk(i);
236     setChunkData("id3 ", data);
237   }
238 }
239 
240 }
241 
242 /**
243  * Wrapper around TagLib::FileStream which reduces the number of open file
244  * descriptors.
245  *
246  * Using streams, closing the file descriptor is also possible for modified
247  * files because the TagLib file does not have to be deleted just to close the
248  * file descriptor.
249  */
250 class FileIOStream : public TagLib::IOStream {
251 public:
252   /**
253    * Constructor.
254    * @param fileName path to file
255    */
256   explicit FileIOStream(const QString& fileName);
257 
258   /**
259    * Destructor.
260    */
261   virtual ~FileIOStream() override;
262 
263   FileIOStream(const FileIOStream&) = delete;
264   FileIOStream& operator=(const FileIOStream&) = delete;
265 
266   /**
267    * Close the file handle.
268    * The file will automatically be opened again if needed.
269    */
270   void closeFileHandle();
271 
272   /**
273    * Change the file name.
274    * Can be used to modify the file name when it has changed because a path
275    * component was renamed.
276    * @param fileName path to file
277    */
278   void setName(const QString& fileName);
279 
280   // Reimplemented from TagLib::IOStream, delegate to TagLib::FileStream.
281   /** File name in local file system encoding. */
282   virtual TagLib::FileName name() const override;
283   /** Read block of size @a length at current pointer. */
284   virtual TagLib::ByteVector readBlock(ulong length) override;
285   /** Write block @a data at current pointer. */
286   virtual void writeBlock(const TagLib::ByteVector &data) override;
287   /**
288    * Insert @a data at position @a start in the file overwriting @a replace
289    * bytes of the original content.
290    */
291   virtual void insert(const TagLib::ByteVector &data,
292               taglib_uoffset_t start = 0, ulong replace = 0) override;
293   /** Remove block starting at @a start for @a length bytes. */
294   virtual void removeBlock(taglib_uoffset_t start = 0, ulong length = 0) override;
295   /** True if the file is read only. */
296   virtual bool readOnly() const override;
297   /** Check if open in constructor succeeded. */
298   virtual bool isOpen() const override;
299   /** Move I/O pointer to @a offset in the file from position @a p. */
300   virtual void seek(taglib_offset_t offset, Position p = Beginning) override;
301   /** Reset the end-of-file and error flags on the file. */
302   virtual void clear() override;
303   /** Current offset within the file. */
304   virtual taglib_offset_t tell() const override;
305   /** Length of the file. */
306   virtual taglib_offset_t length() override;
307   /** Truncate the file to @a length. */
308   virtual void truncate(taglib_offset_t length) override;
309 
310   /**
311    * Create a TagLib file for a stream.
312    * TagLib::FileRef::create() adapted for IOStream.
313    * @param stream stream with name() of which the extension is used to deduce
314    * the file type
315    * @return file, 0 if not supported.
316    */
317   static TagLib::File* create(IOStream* stream);
318 
319 private:
320   /**
321    * Open file handle, is called by operations which need a file handle.
322    *
323    * @return true if file is open.
324    */
325   bool openFileHandle() const;
326 
327   /**
328    * Create a TagLib file for a stream.
329    * @param stream stream with name() of which the extension is used to deduce
330    * the file type
331    * @return file, 0 if not supported.
332    */
333   static TagLib::File* createFromExtension(IOStream* stream);
334 
335   /**
336    * Create a TagLib file for a stream.
337    * @param stream stream
338    * @param ext uppercase extension used to deduce the file type
339    * @return file, 0 if not supported.
340    */
341   static TagLib::File* createFromExtension(TagLib::IOStream* stream,
342                                            const TagLib::String& ext);
343 
344   /**
345    * Create a TagLib file for a stream.
346    * @param stream stream where the contents are used to deduce the file type
347    * @return file, 0 if not supported.
348    */
349   static TagLib::File* createFromContents(IOStream* stream);
350 
351   /**
352    * Register open files, so that the number of open files can be limited.
353    * If the number of open files exceeds a limit, files are closed.
354    *
355    * @param stream new open file to be registered
356    */
357   static void registerOpenFile(FileIOStream* stream);
358 
359   /**
360    * Deregister open file.
361    *
362    * @param stream file which is no longer open
363    */
364   static void deregisterOpenFile(FileIOStream* stream);
365 
366 #ifdef Q_OS_WIN32
367   wchar_t* m_fileName;
368 #else
369   char* m_fileName;
370 #endif
371   TagLib::FileStream* m_fileStream;
372   long m_offset;
373 
374   /** list of file streams with open file descriptor */
375   static QList<FileIOStream*> s_openFiles;
376 };
377 
378 QList<FileIOStream*> FileIOStream::s_openFiles;
379 
FileIOStream(const QString & fileName)380 FileIOStream::FileIOStream(const QString& fileName)
381   : m_fileName(nullptr), m_fileStream(nullptr), m_offset(0)
382 {
383   setName(fileName);
384 }
385 
~FileIOStream()386 FileIOStream::~FileIOStream()
387 {
388   deregisterOpenFile(this);
389   delete m_fileStream;
390   delete [] m_fileName;
391 }
392 
openFileHandle() const393 bool FileIOStream::openFileHandle() const
394 {
395   if (!m_fileStream) {
396     auto self = const_cast<FileIOStream*>(this);
397     self->m_fileStream =
398         new TagLib::FileStream(TagLib::FileName(m_fileName));
399     if (!self->m_fileStream->isOpen()) {
400       delete self->m_fileStream;
401       self->m_fileStream = nullptr;
402       return false;
403     }
404     if (m_offset > 0) {
405       m_fileStream->seek(m_offset);
406     }
407     registerOpenFile(self);
408   }
409   return true;
410 }
411 
closeFileHandle()412 void FileIOStream::closeFileHandle()
413 {
414   if (m_fileStream) {
415     m_offset = m_fileStream->tell();
416     delete m_fileStream;
417     m_fileStream = nullptr;
418     deregisterOpenFile(this);
419   }
420 }
421 
setName(const QString & fileName)422 void FileIOStream::setName(const QString& fileName)
423 {
424   delete m_fileName;
425 #ifdef Q_OS_WIN32
426   int fnLen = fileName.length();
427   m_fileName = new wchar_t[fnLen + 1];
428   m_fileName[fnLen] = 0;
429   fileName.toWCharArray(m_fileName);
430 #else
431   QByteArray fn = QFile::encodeName(fileName);
432   m_fileName = new char[fn.size() + 1];
433   qstrcpy(m_fileName, fn.data());
434 #endif
435 }
436 
name() const437 TagLib::FileName FileIOStream::name() const
438 {
439   if (m_fileStream) {
440     return m_fileStream->name();
441   }
442   return TagLib::FileName(m_fileName);
443 }
444 
readBlock(ulong length)445 TagLib::ByteVector FileIOStream::readBlock(ulong length)
446 {
447   if (openFileHandle()) {
448     return m_fileStream->readBlock(length);
449   }
450   return TagLib::ByteVector();
451 }
452 
writeBlock(const TagLib::ByteVector & data)453 void FileIOStream::writeBlock(const TagLib::ByteVector &data)
454 {
455   if (openFileHandle()) {
456     m_fileStream->writeBlock(data);
457   }
458 }
459 
insert(const TagLib::ByteVector & data,taglib_uoffset_t start,ulong replace)460 void FileIOStream::insert(const TagLib::ByteVector &data,
461                           taglib_uoffset_t start, ulong replace)
462 {
463   if (openFileHandle()) {
464     m_fileStream->insert(data, start, replace);
465   }
466 }
467 
removeBlock(taglib_uoffset_t start,ulong length)468 void FileIOStream::removeBlock(taglib_uoffset_t start, ulong length)
469 {
470   if (openFileHandle()) {
471     m_fileStream->removeBlock(start, length);
472   }
473 }
474 
readOnly() const475 bool FileIOStream::readOnly() const
476 {
477   if (openFileHandle()) {
478     return m_fileStream->readOnly();
479   }
480   return true;
481 }
482 
isOpen() const483 bool FileIOStream::isOpen() const
484 {
485   if (m_fileStream) {
486     return m_fileStream->isOpen();
487   }
488   return true;
489 }
490 
seek(taglib_offset_t offset,Position p)491 void FileIOStream::seek(taglib_offset_t offset, Position p)
492 {
493   if (openFileHandle()) {
494     m_fileStream->seek(offset, p);
495   }
496 }
497 
clear()498 void FileIOStream::clear()
499 {
500   if (openFileHandle()) {
501     m_fileStream->clear();
502   }
503 }
504 
tell() const505 taglib_offset_t FileIOStream::tell() const
506 {
507   if (openFileHandle()) {
508     return m_fileStream->tell();
509   }
510   return 0;
511 }
512 
length()513 taglib_offset_t FileIOStream::length()
514 {
515   if (openFileHandle()) {
516     return m_fileStream->length();
517   }
518   return 0;
519 }
520 
truncate(taglib_offset_t length)521 void FileIOStream::truncate(taglib_offset_t length)
522 {
523   if (openFileHandle()) {
524     m_fileStream->truncate(length);
525   }
526 }
527 
create(TagLib::IOStream * stream)528 TagLib::File* FileIOStream::create(TagLib::IOStream* stream)
529 {
530   TagLib::File* file = createFromExtension(stream);
531   if (!file || !file->isValid()) {
532     file = createFromContents(stream);
533   }
534   return file;
535 }
536 
createFromExtension(TagLib::IOStream * stream)537 TagLib::File* FileIOStream::createFromExtension(TagLib::IOStream* stream)
538 {
539 #ifdef Q_OS_WIN32
540   TagLib::String fn = stream->name().toString();
541 #else
542   TagLib::String fn = stream->name();
543 #endif
544   const int extPos = fn.rfind(".");
545   return extPos != -1
546       ? createFromExtension(stream, fn.substr(extPos + 1).upper())
547       : nullptr;
548 }
549 
createFromExtension(TagLib::IOStream * stream,const TagLib::String & ext)550 TagLib::File* FileIOStream::createFromExtension(TagLib::IOStream* stream,
551                                                 const TagLib::String& ext)
552 {
553   if (ext == "MP3" || ext == "MP2" || ext == "AAC")
554     return new TagLib::MPEG::File(stream,
555                                   TagLib::ID3v2::FrameFactory::instance());
556   if (ext == "OGG") {
557     TagLib::File* file = new TagLib::Vorbis::File(stream);
558     if (!file->isValid()) {
559       delete file;
560       file = new TagLib::Ogg::FLAC::File(stream);
561     }
562     return file;
563   }
564   if (ext == "OGA") {
565     TagLib::File* file = new TagLib::Ogg::FLAC::File(stream);
566     if (!file->isValid()) {
567       delete file;
568       file = new TagLib::Vorbis::File(stream);
569     }
570     return file;
571   }
572   if (ext == "FLAC")
573     return new TagLib::FLAC::File(stream,
574                                   TagLib::ID3v2::FrameFactory::instance());
575   if (ext == "MPC")
576     return new TagLib::MPC::File(stream);
577   if (ext == "WV")
578     return new TagLib::WavPack::File(stream);
579   if (ext == "SPX")
580     return new TagLib::Ogg::Speex::File(stream);
581   if (ext == "OPUS")
582     return new TagLib::Ogg::Opus::File(stream);
583   if (ext == "TTA")
584     return new TagLib::TrueAudio::File(stream);
585   if (ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" ||
586       ext == "M4R" || ext == "MP4" || ext == "3G2" || ext == "M4V" ||
587       ext == "MP4V")
588     return new TagLib::MP4::File(stream);
589   if (ext == "WMA" || ext == "ASF" || ext == "WMV")
590     return new TagLib::ASF::File(stream);
591   if (ext == "AIF" || ext == "AIFF")
592     return new TagLib::RIFF::AIFF::File(stream);
593   if (ext == "WAV")
594     return new WavFile(stream);
595   if (ext == "APE")
596     return new TagLib::APE::File(stream);
597   if (ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW")
598     return new TagLib::Mod::File(stream);
599   if (ext == "S3M")
600     return new TagLib::S3M::File(stream);
601   if (ext == "IT")
602     return new TagLib::IT::File(stream);
603   if (ext == "XM")
604     return new TagLib::XM::File(stream);
605   if (ext == "DSF")
606     return new DSFFile(stream, TagLib::ID3v2::FrameFactory::instance());
607   return nullptr;
608 }
609 
createFromContents(TagLib::IOStream * stream)610 TagLib::File* FileIOStream::createFromContents(TagLib::IOStream* stream)
611 {
612   static const struct ExtensionForMimeType {
613     const char* mime;
614     const char* ext;
615   } extensionForMimeType[] = {
616     { "application/ogg", "OGG" },
617     { "application/vnd.ms-asf", "WMA" },
618     { "audio/aac", "AAC" },
619     { "audio/flac", "FLAC" },
620     { "audio/mp4", "MP4" },
621     { "audio/mpeg", "MP3" },
622     { "audio/x-aiff", "AIFF" },
623     { "audio/x-ape", "APE" },
624     { "audio/x-flac+ogg", "OGG" },
625     { "audio/x-it", "IT" },
626     { "audio/x-musepack", "MPC" },
627     { "audio/x-opus+ogg", "OPUS" },
628     { "audio/x-s3m", "S3M" },
629     { "audio/x-speex+ogg", "SPX" },
630     { "audio/x-tta", "TTA" },
631     { "audio/x-vorbis+ogg", "OGG" },
632     { "audio/x-wav", "WAV" },
633     { "audio/x-wavpack", "WV" },
634     { "audio/x-xm", "XM" },
635     { "video/mp4", "MP4" }
636 };
637 
638   static QMap<QString, TagLib::String> mimeExtMap;
639   if (mimeExtMap.empty()) {
640     // first time initialization
641     for (const auto& efm : extensionForMimeType) {
642       mimeExtMap.insert(QString::fromLatin1(efm.mime), efm.ext);
643     }
644   }
645 
646   stream->seek(0);
647   TagLib::ByteVector bv = stream->readBlock(4096);
648   stream->seek(0);
649   QMimeDatabase mimeDb;
650   auto mimeType =
651       mimeDb.mimeTypeForData(QByteArray(bv.data(), static_cast<int>(bv.size())));
652   TagLib::String ext = mimeExtMap.value(mimeType.name());
653   if (!ext.isEmpty()) {
654     return createFromExtension(stream, ext);
655   }
656   return nullptr;
657 }
658 
registerOpenFile(FileIOStream * stream)659 void FileIOStream::registerOpenFile(FileIOStream* stream)
660 {
661   if (s_openFiles.contains(stream))
662     return;
663 
664   int numberOfFilesToClose = s_openFiles.size() - 15;
665   if (numberOfFilesToClose > 5) {
666     for (auto it = s_openFiles.begin(); it != s_openFiles.end(); ++it) { // clazy:exclude=detaching-member
667       (*it)->closeFileHandle();
668       if (--numberOfFilesToClose <= 0) {
669         break;
670       }
671     }
672   }
673   s_openFiles.append(stream);
674 }
675 
676 /**
677  * Deregister open file.
678  *
679  * @param stream file which is no longer open
680  */
deregisterOpenFile(FileIOStream * stream)681 void FileIOStream::deregisterOpenFile(FileIOStream* stream)
682 {
683   s_openFiles.removeAll(stream);
684 }
685 
686 namespace {
687 
688 /**
689  * Data encoding in ID3v1 tags.
690  */
691 class TextCodecStringHandler : public TagLib::ID3v1::StringHandler {
692 public:
693   /**
694    * Constructor.
695    */
696   TextCodecStringHandler() = default;
697 
698   /**
699    * Destructor.
700    */
701   virtual ~TextCodecStringHandler() = default;
702 
703   TextCodecStringHandler(const TextCodecStringHandler&) = delete;
704   TextCodecStringHandler& operator=(const TextCodecStringHandler&) = delete;
705 
706   /**
707    * Decode a string from data.
708    *
709    * @param data data to decode
710    */
711   virtual TagLib::String parse(const TagLib::ByteVector& data) const override;
712 
713   /**
714    * Encode a byte vector with the data from a string.
715    *
716    * @param s string to encode
717    */
718   virtual TagLib::ByteVector render(const TagLib::String& s) const override;
719 
720 #if QT_VERSION >= 0x060000
721   /**
722    * Set string decoder.
723    * @param encodingName encoding, empty for default behavior (ISO 8859-1)
724    */
setStringDecoder(const QString & encodingName)725   static void setStringDecoder(const QString& encodingName) {
726     if (auto encoding = QStringConverter::encodingForName(encodingName.toLatin1())) {
727       s_encoder = QStringEncoder(encoding.value());
728       s_decoder = QStringDecoder(encoding.value());
729     } else {
730       s_encoder = QStringEncoder();
731       s_decoder = QStringDecoder();
732     }
733   }
734 #else
735   /**
736    * Set text codec.
737    * @param codec text codec, 0 for default behavior (ISO 8859-1)
738    */
setTextCodec(const QTextCodec * codec)739   static void setTextCodec(const QTextCodec* codec) { s_codec = codec; }
740 #endif
741 
742 private:
743 #if QT_VERSION >= 0x060000
744   static QStringDecoder s_decoder;
745   static QStringEncoder s_encoder;
746 #else
747   static const QTextCodec* s_codec;
748 #endif
749 };
750 
751 #if QT_VERSION >= 0x060000
752 QStringDecoder TextCodecStringHandler::s_decoder;
753 QStringEncoder TextCodecStringHandler::s_encoder;
754 #else
755 const QTextCodec* TextCodecStringHandler::s_codec = nullptr;
756 #endif
757 
758 /**
759  * Decode a string from data.
760  *
761  * @param data data to decode
762  */
parse(const TagLib::ByteVector & data) const763 TagLib::String TextCodecStringHandler::parse(const TagLib::ByteVector& data) const
764 {
765 #if QT_VERSION >= 0x060000
766   return s_decoder.isValid()
767       ? toTString(s_decoder(QByteArray(data.data(), data.size()))).stripWhiteSpace()
768       : TagLib::String(data, TagLib::String::Latin1).stripWhiteSpace();
769 #else
770   return s_codec
771       ? toTString(s_codec->toUnicode(data.data(), data.size())).stripWhiteSpace()
772       : TagLib::String(data, TagLib::String::Latin1).stripWhiteSpace();
773 #endif
774 }
775 
776 /**
777  * Encode a byte vector with the data from a string.
778  *
779  * @param s string to encode
780  */
render(const TagLib::String & s) const781 TagLib::ByteVector TextCodecStringHandler::render(const TagLib::String& s) const
782 {
783 #if QT_VERSION >= 0x060000
784   if (s_encoder.isValid()) {
785     QByteArray ba = s_encoder(toQString(s));
786     return TagLib::ByteVector(ba.data(), ba.size());
787   } else {
788     return s.data(TagLib::String::Latin1);
789   }
790 #else
791   if (s_codec) {
792     QByteArray ba(s_codec->fromUnicode(toQString(s)));
793     return TagLib::ByteVector(ba.data(), ba.size());
794   } else {
795     return s.data(TagLib::String::Latin1);
796   }
797 #endif
798 }
799 
800 }
801 
802 /** Default text encoding */
803 TagLib::String::Type TagLibFile::s_defaultTextEncoding = TagLib::String::Latin1;
804 
805 
806 /**
807  * Constructor.
808  *
809  * @param idx index in file proxy model
810  */
TagLibFile(const QPersistentModelIndex & idx)811 TagLibFile::TagLibFile(const QPersistentModelIndex& idx)
812   : TaggedFile(idx),
813     m_tagInformationRead(false), m_fileRead(false),
814     m_stream(nullptr),
815     m_id3v2Version(0),
816     m_activatedFeatures(0), m_duration(0)
817 {
818   FOR_TAGLIB_TAGS(tagNr) {
819     m_hasTag[tagNr] = false;
820     m_isTagSupported[tagNr] = tagNr == Frame::Tag_2;
821     m_tag[tagNr] = nullptr;
822     m_tagType[tagNr] = TT_Unknown;
823   }
824 }
825 
826 /**
827  * Destructor.
828  */
~TagLibFile()829 TagLibFile::~TagLibFile()
830 {
831   closeFile(true);
832 }
833 
834 /**
835  * Get key of tagged file format.
836  * @return "TaglibMetadata".
837  */
taggedFileKey() const838 QString TagLibFile::taggedFileKey() const
839 {
840   return QLatin1String("TaglibMetadata");
841 }
842 
843 /**
844  * Get features supported.
845  * @return bit mask with Feature flags set.
846  */
taggedFileFeatures() const847 int TagLibFile::taggedFileFeatures() const
848 {
849   return TF_ID3v11 | TF_ID3v22 |
850       TF_OggFlac |
851       TF_OggPictures |
852       TF_ID3v23 |
853       TF_ID3v24;
854 }
855 
856 /**
857  * Get currently active tagged file features.
858  * @return active tagged file features (TF_ID3v23, TF_ID3v24, or 0).
859  * @see setActiveTaggedFileFeatures()
860  */
activeTaggedFileFeatures() const861 int TagLibFile::activeTaggedFileFeatures() const
862 {
863   return m_activatedFeatures;
864 }
865 
866 /**
867  * Activate some features provided by the tagged file.
868  * TagLibFile provides the TF_ID3v23 and TF_ID3v24 features, which determine
869  * the ID3v2 version used in writeTags() (the overload without id3v2Version).
870  * If 0 is set, the default behavior applies, i.e. for new files,
871  * TagConfig::id3v2Version() is used, else the existing version.
872  *
873  * @param features TF_ID3v23, TF_ID3v24, or 0
874  */
setActiveTaggedFileFeatures(int features)875 void TagLibFile::setActiveTaggedFileFeatures(int features)
876 {
877   m_activatedFeatures = features;
878 }
879 
880 /**
881  * Free resources allocated when calling readTags().
882  *
883  * @param force true to force clearing even if the tags are modified
884  */
clearTags(bool force)885 void TagLibFile::clearTags(bool force)
886 {
887   if (isChanged() && !force)
888     return;
889 
890   bool priorIsTagInformationRead = isTagInformationRead();
891   closeFile(true);
892   m_pictures.clear();
893   m_pictures.setRead(false);
894   m_tagInformationRead = false;
895   FOR_TAGLIB_TAGS(tagNr) {
896     m_hasTag[tagNr] = false;
897     m_tagFormat[tagNr].clear();
898     m_tagType[tagNr] = TT_Unknown;
899     markTagUnchanged(tagNr);
900   }
901   notifyModelDataChanged(priorIsTagInformationRead);
902 }
903 
904 /**
905  * Read tags from file.
906  *
907  * @param force true to force reading even if tags were already read.
908  */
readTags(bool force)909 void TagLibFile::readTags(bool force)
910 {
911   bool priorIsTagInformationRead = isTagInformationRead();
912   QString fileName = currentFilePath();
913 
914   if (force || m_fileRef.isNull()) {
915     delete m_stream;
916     m_stream = new FileIOStream(fileName);
917     m_fileRef = TagLib::FileRef(FileIOStream::create(m_stream));
918     FOR_TAGLIB_TAGS(tagNr) {
919       m_tag[tagNr] = nullptr;
920       markTagUnchanged(tagNr);
921     }
922     m_fileRead = true;
923 
924     m_pictures.clear();
925     m_pictures.setRead(false);
926   }
927 
928   TagLib::File* file;
929   if (!m_fileRef.isNull() && (file = m_fileRef.file()) != nullptr) {
930     TagLib::MPEG::File* mpegFile;
931     TagLib::FLAC::File* flacFile;
932 #if TAGLIB_VERSION >= 0x010b00
933     TagLib::MPC::File* mpcFile;
934     TagLib::WavPack::File* wvFile;
935 #endif
936     TagLib::TrueAudio::File* ttaFile;
937     TagLib::RIFF::WAV::File* wavFile;
938     DSFFile* dsfFile;
939     TagLib::APE::File* apeFile;
940     m_fileExtension = QLatin1String(".mp3");
941     m_isTagSupported[Frame::Tag_1] = false;
942     if ((mpegFile = dynamic_cast<TagLib::MPEG::File*>(file)) != nullptr) {
943       QString ext(fileName.right(4).toLower());
944       m_fileExtension =
945           (ext == QLatin1String(".aac") || ext == QLatin1String(".mp2"))
946           ? ext : QLatin1String(".mp3");
947       m_isTagSupported[Frame::Tag_1] = true;
948       m_isTagSupported[Frame::Tag_3] = true;
949       if (!m_tag[Frame::Tag_1]) {
950         m_tag[Frame::Tag_1] = mpegFile->ID3v1Tag();
951         markTagUnchanged(Frame::Tag_1);
952       }
953       if (!m_tag[Frame::Tag_2]) {
954         TagLib::ID3v2::Tag* id3v2Tag = mpegFile->ID3v2Tag();
955         setId3v2VersionFromTag(id3v2Tag);
956         m_tag[Frame::Tag_2] = id3v2Tag;
957         markTagUnchanged(Frame::Tag_2);
958       }
959       if (!m_tag[Frame::Tag_3]) {
960         m_tag[Frame::Tag_3] = mpegFile->APETag();
961         markTagUnchanged(Frame::Tag_3);
962       }
963     } else if ((flacFile = dynamic_cast<TagLib::FLAC::File*>(file)) != nullptr) {
964       m_fileExtension = QLatin1String(".flac");
965       m_isTagSupported[Frame::Tag_1] = true;
966       m_isTagSupported[Frame::Tag_3] = true;
967       if (!m_tag[Frame::Tag_1]) {
968         m_tag[Frame::Tag_1] = flacFile->ID3v1Tag();
969         markTagUnchanged(Frame::Tag_1);
970       }
971       if (!m_tag[Frame::Tag_2]) {
972         m_tag[Frame::Tag_2] = flacFile->xiphComment();
973         markTagUnchanged(Frame::Tag_2);
974       }
975       if (!m_tag[Frame::Tag_3]) {
976         m_tag[Frame::Tag_3] = flacFile->ID3v2Tag();
977         markTagUnchanged(Frame::Tag_3);
978       }
979       if (!m_pictures.isRead()) {
980         const TagLib::List<TagLib::FLAC::Picture*> pics(flacFile->pictureList());
981         int i = 0;
982         for (auto it = pics.begin(); it != pics.end(); ++it) {
983           PictureFrame frame;
984           flacPictureToFrame(*it, frame);
985           frame.setIndex(Frame::toNegativeIndex(i++));
986           m_pictures.append(frame);
987         }
988         m_pictures.setRead(true);
989       }
990 #if TAGLIB_VERSION >= 0x010b00
991     } else if ((mpcFile = dynamic_cast<TagLib::MPC::File*>(file)) != nullptr) {
992       m_fileExtension = QLatin1String(".mpc");
993       m_isTagSupported[Frame::Tag_1] = true;
994       if (!m_tag[Frame::Tag_1]) {
995         m_tag[Frame::Tag_1] = mpcFile->ID3v1Tag();
996         markTagUnchanged(Frame::Tag_1);
997       }
998       if (!m_tag[Frame::Tag_2]) {
999         m_tag[Frame::Tag_2] = mpcFile->APETag();
1000         markTagUnchanged(Frame::Tag_2);
1001       }
1002     } else if ((wvFile = dynamic_cast<TagLib::WavPack::File*>(file)) != nullptr) {
1003       m_fileExtension = QLatin1String(".wv");
1004       m_isTagSupported[Frame::Tag_1] = true;
1005       if (!m_tag[Frame::Tag_1]) {
1006         m_tag[Frame::Tag_1] = wvFile->ID3v1Tag();
1007         markTagUnchanged(Frame::Tag_1);
1008       }
1009       if (!m_tag[Frame::Tag_2]) {
1010         m_tag[Frame::Tag_2] = wvFile->APETag();
1011         markTagUnchanged(Frame::Tag_2);
1012       }
1013 #endif
1014     } else if ((ttaFile = dynamic_cast<TagLib::TrueAudio::File*>(file)) != nullptr) {
1015       m_fileExtension = QLatin1String(".tta");
1016       m_isTagSupported[Frame::Tag_1] = true;
1017       if (!m_tag[Frame::Tag_1]) {
1018         m_tag[Frame::Tag_1] = ttaFile->ID3v1Tag();
1019         markTagUnchanged(Frame::Tag_1);
1020       }
1021       if (!m_tag[Frame::Tag_2]) {
1022         m_tag[Frame::Tag_2] = ttaFile->ID3v2Tag();
1023         markTagUnchanged(Frame::Tag_2);
1024       }
1025     } else if ((apeFile = dynamic_cast<TagLib::APE::File*>(file)) != nullptr) {
1026       m_fileExtension = QLatin1String(".ape");
1027       m_isTagSupported[Frame::Tag_1] = true;
1028       if (!m_tag[Frame::Tag_1]) {
1029         m_tag[Frame::Tag_1] = apeFile->ID3v1Tag();
1030         markTagUnchanged(Frame::Tag_1);
1031       }
1032       if (!m_tag[Frame::Tag_2]) {
1033         m_tag[Frame::Tag_2] = apeFile->APETag();
1034         markTagUnchanged(Frame::Tag_2);
1035       }
1036     } else if ((wavFile = dynamic_cast<TagLib::RIFF::WAV::File*>(file)) != nullptr) {
1037       m_fileExtension = QLatin1String(".wav");
1038       m_tag[Frame::Tag_1] = nullptr;
1039       markTagUnchanged(Frame::Tag_1);
1040 #if TAGLIB_VERSION >= 0x010a00
1041       m_isTagSupported[Frame::Tag_3] = true;
1042       if (!m_tag[Frame::Tag_2]) {
1043         TagLib::ID3v2::Tag* id3v2Tag = wavFile->ID3v2Tag();
1044         setId3v2VersionFromTag(id3v2Tag);
1045         m_tag[Frame::Tag_2] = id3v2Tag;
1046         markTagUnchanged(Frame::Tag_2);
1047       }
1048       if (!m_tag[Frame::Tag_3]) {
1049         m_tag[Frame::Tag_3] = wavFile->InfoTag();
1050         markTagUnchanged(Frame::Tag_3);
1051       }
1052 #else
1053       if (!m_tag[Frame::Tag_2]) {
1054         m_tag[Frame::Tag_2] = wavFile->tag();
1055         markTagUnchanged(Frame::Tag_2);
1056       }
1057 #endif
1058     } else if ((dsfFile = dynamic_cast<DSFFile*>(file)) != nullptr) {
1059       m_fileExtension = QLatin1String(".dsf");
1060       m_tag[Frame::Tag_1] = nullptr;
1061       markTagUnchanged(Frame::Tag_1);
1062       if (!m_tag[Frame::Tag_2]) {
1063         TagLib::ID3v2::Tag* id3v2Tag = dsfFile->ID3v2Tag();
1064         setId3v2VersionFromTag(id3v2Tag);
1065         m_tag[Frame::Tag_2] = id3v2Tag;
1066         markTagUnchanged(Frame::Tag_2);
1067       }
1068     } else {
1069       if (dynamic_cast<TagLib::Vorbis::File*>(file) != nullptr) {
1070         m_fileExtension = QLatin1String(".ogg");
1071       } else if (dynamic_cast<TagLib::Ogg::Speex::File*>(file) != nullptr) {
1072         m_fileExtension = QLatin1String(".spx");
1073 #if TAGLIB_VERSION < 0x010b00
1074       } else if (dynamic_cast<TagLib::MPC::File*>(file) != 0) {
1075         m_fileExtension = QLatin1String(".mpc");
1076       } else if (dynamic_cast<TagLib::WavPack::File*>(file) != 0) {
1077         m_fileExtension = QLatin1String(".wv");
1078 #endif
1079       } else if (dynamic_cast<TagLib::MP4::File*>(file) != nullptr) {
1080         m_fileExtension = QLatin1String(".m4a");
1081       } else if (dynamic_cast<TagLib::ASF::File*>(file) != nullptr) {
1082         m_fileExtension = QLatin1String(".wma");
1083       } else if (dynamic_cast<TagLib::RIFF::AIFF::File*>(file) != nullptr) {
1084         m_fileExtension = QLatin1String(".aiff");
1085       } else if (dynamic_cast<TagLib::Mod::File*>(file) != nullptr) {
1086         m_fileExtension = QLatin1String(".mod");
1087       } else if (dynamic_cast<TagLib::S3M::File*>(file) != nullptr) {
1088         m_fileExtension = QLatin1String(".s3m");
1089       } else if (dynamic_cast<TagLib::IT::File*>(file) != nullptr) {
1090         m_fileExtension = QLatin1String(".it");
1091       } else if (dynamic_cast<TagLib::XM::File*>(file) != nullptr) {
1092         m_fileExtension = QLatin1String(".xm");
1093       } else if (dynamic_cast<TagLib::Ogg::Opus::File*>(file) != nullptr) {
1094         m_fileExtension = QLatin1String(".opus");
1095       }
1096       m_tag[Frame::Tag_1] = nullptr;
1097       markTagUnchanged(Frame::Tag_1);
1098       if (!m_tag[Frame::Tag_2]) {
1099         m_tag[Frame::Tag_2] = m_fileRef.tag();
1100         markTagUnchanged(Frame::Tag_2);
1101       }
1102       if (!m_pictures.isRead()) {
1103 #if TAGLIB_VERSION >= 0x010b00
1104         if (auto xiphComment =
1105             dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[Frame::Tag_2])) {
1106           const TagLib::List<TagLib::FLAC::Picture*> pics(xiphComment->pictureList());
1107           int i = 0;
1108           for (auto it = pics.begin(); it != pics.end(); ++it) {
1109             PictureFrame frame;
1110             flacPictureToFrame(*it, frame);
1111             frame.setIndex(Frame::toNegativeIndex(i++));
1112             m_pictures.append(frame);
1113           }
1114           m_pictures.setRead(true);
1115         } else
1116 #endif
1117         if (auto mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[Frame::Tag_2])) {
1118 #if TAGLIB_VERSION >= 0x010a00
1119           const TagLib::MP4::CoverArtList pics(mp4Tag->item("covr").toCoverArtList());
1120 #else
1121           const TagLib::MP4::CoverArtList pics(mp4Tag->itemListMap()["covr"].toCoverArtList());
1122 #endif
1123           int i = 0;
1124           for (auto it = pics.begin(); it != pics.end(); ++it) {
1125             const TagLib::MP4::CoverArt& coverArt = *it;
1126             TagLib::ByteVector bv = coverArt.data();
1127             QString mimeType, imgFormat;
1128             switch (coverArt.format()) {
1129             case TagLib::MP4::CoverArt::PNG:
1130               mimeType = QLatin1String("image/png");
1131               imgFormat = QLatin1String("PNG");
1132               break;
1133             case TagLib::MP4::CoverArt::BMP:
1134               mimeType = QLatin1String("image/bmp");
1135               imgFormat = QLatin1String("BMP");
1136               break;
1137             case TagLib::MP4::CoverArt::GIF:
1138               mimeType = QLatin1String("image/gif");
1139               imgFormat = QLatin1String("GIF");
1140               break;
1141             case TagLib::MP4::CoverArt::JPEG:
1142             case TagLib::MP4::CoverArt::Unknown:
1143             default:
1144               mimeType = QLatin1String("image/jpeg");
1145               imgFormat = QLatin1String("JPG");
1146             }
1147             PictureFrame frame(
1148                   QByteArray(bv.data(), static_cast<int>(bv.size())),
1149                   QLatin1String(""), PictureFrame::PT_CoverFront, mimeType,
1150                   Frame::TE_ISO8859_1, imgFormat);
1151             frame.setIndex(Frame::toNegativeIndex(i++));
1152             frame.setExtendedType(Frame::ExtendedType(Frame::FT_Picture,
1153                                                       QLatin1String("covr")));
1154             m_pictures.append(frame);
1155           }
1156           m_pictures.setRead(true);
1157         }
1158       }
1159     }
1160   }
1161 
1162   // Cache information, so that it is available after file is closed.
1163   m_tagInformationRead = true;
1164   FOR_TAGLIB_TAGS(tagNr) {
1165     m_hasTag[tagNr] = m_tag[tagNr] && !m_tag[tagNr]->isEmpty();
1166     m_tagFormat[tagNr] = getTagFormat(m_tag[tagNr], m_tagType[tagNr]);
1167   }
1168   readAudioProperties();
1169 
1170   if (force) {
1171     setFilename(currentFilename());
1172   }
1173 
1174   closeFile(false);
1175 
1176   notifyModelDataChanged(priorIsTagInformationRead);
1177 }
1178 
1179 /**
1180  * Close file handle.
1181  * TagLib keeps the file handle open until the FileRef is destroyed.
1182  * This causes problems when the operating system has a limited number of
1183  * open file handles. This method closes the file by assigning a new file
1184  * reference. Note that this will also invalidate the tag pointers.
1185  * The file is only closed if there are no unsaved tag changes or if the
1186  * @a force parameter is set.
1187  *
1188  * @param force true to close the file even if tags are changed
1189  */
closeFile(bool force)1190 void TagLibFile::closeFile(bool force)
1191 {
1192   if (force) {
1193     m_fileRef = TagLib::FileRef();
1194     delete m_stream;
1195     m_stream = nullptr;
1196     FOR_TAGLIB_TAGS(tagNr) {
1197       m_tag[tagNr] = nullptr;
1198     }
1199     m_fileRead = false;
1200   } else if (m_stream) {
1201     m_stream->closeFileHandle();
1202   }
1203 }
1204 
1205 /**
1206  * Make sure that file is open.
1207  * This method should be called before accessing m_fileRef, m_tag.
1208  *
1209  * @param force true to force reopening of file even if it is already open
1210  */
makeFileOpen(bool force) const1211 void TagLibFile::makeFileOpen(bool force) const
1212 {
1213   if (!m_fileRead || force) {
1214     const_cast<TagLibFile*>(this)->readTags(force);
1215   }
1216 }
1217 
1218 /**
1219  * Write tags to file and rename it if necessary.
1220  *
1221  * @param force    true to force writing even if file was not changed.
1222  * @param renamed  will be set to true if the file was renamed,
1223  *                 i.e. the file name is no longer valid, else *renamed
1224  *                 is left unchanged
1225  * @param preserve true to preserve file time stamps
1226  *
1227  * @return true if ok, false if the file could not be written or renamed.
1228  */
writeTags(bool force,bool * renamed,bool preserve)1229 bool TagLibFile::writeTags(bool force, bool* renamed, bool preserve)
1230 {
1231   int id3v2Version;
1232   if (m_activatedFeatures & TF_ID3v24)
1233     id3v2Version = 4;
1234   else if (m_activatedFeatures & TF_ID3v23)
1235     id3v2Version = 3;
1236   else
1237     id3v2Version = 0;
1238   return writeTags(force, renamed, preserve, id3v2Version);
1239 }
1240 
1241 /**
1242  * Write tags to file and rename it if necessary.
1243  *
1244  * @param force    true to force writing even if file was not changed.
1245  * @param renamed  will be set to true if the file was renamed,
1246  *                 i.e. the file name is no longer valid, else *renamed
1247  *                 is left unchanged
1248  * @param preserve true to preserve file time stamps
1249  * @param id3v2Version ID3v2 version to use, 0 to use existing or preferred,
1250  *                     3 to force ID3v2.3.0, 4 to force ID3v2.4.0. Is ignored
1251  *                     if TagLib version is less than 1.8.0.
1252  *
1253  * @return true if ok, false if the file could not be written or renamed.
1254  */
writeTags(bool force,bool * renamed,bool preserve,int id3v2Version)1255 bool TagLibFile::writeTags(bool force, bool* renamed, bool preserve,
1256                            int id3v2Version)
1257 {
1258   QString fnStr(currentFilePath());
1259   if (isChanged() && !QFileInfo(fnStr).isWritable()) {
1260     closeFile(false);
1261     revertChangedFilename();
1262     return false;
1263   }
1264 
1265   // store time stamp if it has to be preserved
1266   quint64 actime = 0, modtime = 0;
1267   if (preserve) {
1268     getFileTimeStamps(fnStr, actime, modtime);
1269   }
1270 
1271   bool fileChanged = false;
1272   TagLib::File* file;
1273   if (!m_fileRef.isNull() && (file = m_fileRef.file()) != nullptr) {
1274     if (m_stream) {
1275 #ifndef Q_OS_WIN32
1276       QString fileName = QFile::decodeName(m_stream->name());
1277 #else
1278       QString fileName = toQString(m_stream->name().toString());
1279 #endif
1280       if (fnStr != fileName) {
1281         qDebug("TagLibFile: Fix file name mismatch, should be '%s', not '%s'",
1282                qPrintable(fnStr), qPrintable(fileName));
1283         m_stream->setName(fnStr);
1284       }
1285     }
1286     auto mpegFile = dynamic_cast<TagLib::MPEG::File*>(file);
1287     if (mpegFile) {
1288       static const int tagTypes[NUM_TAGS] = {
1289         TagLib::MPEG::File::ID3v1, TagLib::MPEG::File::ID3v2,
1290         TagLib::MPEG::File::APE
1291       };
1292       int saveMask = 0;
1293       // We iterate through the tags in reverse order to work around
1294       // a TagLib bug: When stripping the APE tag after the ID3v1 tag,
1295       // the ID3v1 tag is not removed.
1296       FOR_TAGLIB_TAGS_REVERSE(tagNr) {
1297         if (m_tag[tagNr] && (force || isTagChanged(tagNr))) {
1298           if (m_tag[tagNr]->isEmpty()) {
1299             mpegFile->strip(tagTypes[tagNr]);
1300             fileChanged = true;
1301             markTagUnchanged(tagNr);
1302             m_tag[tagNr] = nullptr;
1303           } else {
1304             saveMask |= tagTypes[tagNr];
1305           }
1306         }
1307       }
1308       if (saveMask != 0) {
1309         setId3v2VersionOrDefault(id3v2Version);
1310         if (
1311 #if TAGLIB_VERSION >= 0x010c00
1312             mpegFile->save(
1313               saveMask, TagLib::File::StripNone,
1314               m_id3v2Version == 4 ? TagLib::ID3v2::v4 : TagLib::ID3v2::v3,
1315               TagLib::File::DoNotDuplicate)
1316 #else
1317             mpegFile->save(saveMask, false, m_id3v2Version, false)
1318 #endif
1319             ) {
1320           fileChanged = true;
1321           FOR_TAGLIB_TAGS(tagNr) {
1322             if (saveMask & tagTypes[tagNr]) {
1323               markTagUnchanged(tagNr);
1324             }
1325           }
1326         }
1327       }
1328     } else {
1329       bool needsSave = false;
1330       FOR_TAGLIB_TAGS(tagNr) {
1331         if (m_tag[tagNr] && (force || isTagChanged(tagNr))) {
1332           needsSave = true;
1333           break;
1334         }
1335       }
1336       if (needsSave) {
1337         if (auto ttaFile =
1338             dynamic_cast<TagLib::TrueAudio::File*>(file)) {
1339           static const int tagTypes[NUM_TAGS] = {
1340             TagLib::MPEG::File::ID3v1, TagLib::MPEG::File::ID3v2,
1341             TagLib::MPEG::File::NoTags
1342           };
1343           FOR_TAGLIB_TAGS(tagNr) {
1344             if (m_tag[tagNr] && (force || isTagChanged(tagNr)) && m_tag[tagNr]->isEmpty()) {
1345               ttaFile->strip(tagTypes[tagNr]);
1346               fileChanged = true;
1347               markTagUnchanged(tagNr);
1348               m_tag[tagNr] = nullptr;
1349             }
1350           }
1351         } else if (auto mpcFile =
1352                    dynamic_cast<TagLib::MPC::File*>(file)) {
1353 #if TAGLIB_VERSION >= 0x010b00
1354           static const int tagTypes[NUM_TAGS] = {
1355             TagLib::MPC::File::ID3v1 | TagLib::MPC::File::ID3v2,
1356             TagLib::MPC::File::APE, TagLib::MPC::File::NoTags
1357           };
1358           FOR_TAGLIB_TAGS(tagNr) {
1359             if (m_tag[tagNr] && (force || isTagChanged(tagNr)) &&
1360                 m_tag[tagNr]->isEmpty()) {
1361               mpcFile->strip(tagTypes[tagNr]);
1362               fileChanged = true;
1363               markTagUnchanged(tagNr);
1364               m_tag[tagNr] = nullptr;
1365             }
1366           }
1367 #else
1368           // it does not work if there is also an ID3 tag (bug in TagLib)
1369           mpcFile->remove(TagLib::MPC::File::ID3v1 | TagLib::MPC::File::ID3v2);
1370           fileChanged = true;
1371 #endif
1372         } else if (auto wvFile =
1373                    dynamic_cast<TagLib::WavPack::File*>(file)) {
1374 #if TAGLIB_VERSION >= 0x010b00
1375           static const int tagTypes[NUM_TAGS] = {
1376             TagLib::WavPack::File::ID3v1, TagLib::WavPack::File::APE,
1377             TagLib::WavPack::File::NoTags
1378           };
1379           FOR_TAGLIB_TAGS(tagNr) {
1380             if (m_tag[tagNr] && (force || isTagChanged(tagNr)) &&
1381                 m_tag[tagNr]->isEmpty()) {
1382               wvFile->strip(tagTypes[tagNr]);
1383               fileChanged = true;
1384               markTagUnchanged(tagNr);
1385               m_tag[tagNr] = nullptr;
1386             }
1387           }
1388 #else
1389           // it does not work if there is also an ID3 tag (bug in TagLib)
1390           wvFile->strip(TagLib::WavPack::File::ID3v1);
1391           fileChanged = true;
1392 #endif
1393         }
1394         else if (auto apeFile =
1395                  dynamic_cast<TagLib::APE::File*>(file)) {
1396           static const int tagTypes[NUM_TAGS] = {
1397             TagLib::MPEG::File::ID3v1, TagLib::APE::File::APE,
1398             TagLib::APE::File::NoTags
1399           };
1400           FOR_TAGLIB_TAGS(tagNr) {
1401             if (m_tag[tagNr] && (force || isTagChanged(tagNr)) && m_tag[tagNr]->isEmpty()) {
1402               apeFile->strip(tagTypes[tagNr]);
1403               fileChanged = true;
1404               markTagUnchanged(tagNr);
1405               m_tag[tagNr] = nullptr;
1406             }
1407           }
1408         }
1409         else if (auto flacFile =
1410             dynamic_cast<TagLib::FLAC::File*>(file)) {
1411 #if TAGLIB_VERSION >= 0x010b00
1412           static const int tagTypes[NUM_TAGS] = {
1413             TagLib::FLAC::File::ID3v1, TagLib::FLAC::File::XiphComment,
1414             TagLib::FLAC::File::ID3v2
1415           };
1416           FOR_TAGLIB_TAGS(tagNr) {
1417             if (m_tag[tagNr] && (force || isTagChanged(tagNr)) && m_tag[tagNr]->isEmpty()) {
1418               flacFile->strip(tagTypes[tagNr]);
1419               fileChanged = true;
1420               markTagUnchanged(tagNr);
1421               m_tag[tagNr] = nullptr;
1422             }
1423           }
1424 #endif
1425           flacFile->removePictures();
1426           const auto frames = m_pictures;
1427           for (const Frame& frame : frames) {
1428             auto pic = new TagLib::FLAC::Picture;
1429             frameToFlacPicture(frame, pic);
1430             flacFile->addPicture(pic);
1431           }
1432         }
1433         else if (auto wavFile = dynamic_cast<WavFile*>(file)) {
1434           static const TagLib::RIFF::WAV::File::TagTypes tagTypes[NUM_TAGS] = {
1435             TagLib::RIFF::WAV::File::NoTags, TagLib::RIFF::WAV::File::ID3v2,
1436 #if TAGLIB_VERSION >= 0x010a00
1437             TagLib::RIFF::WAV::File::Info
1438 #else
1439             TagLib::RIFF::WAV::File::NoTags
1440 #endif
1441           };
1442           int saveTags = 0;
1443           FOR_TAGLIB_TAGS(tagNr) {
1444             if (m_tag[tagNr] && (force || isTagChanged(tagNr)) &&
1445                 m_tag[tagNr]->isEmpty()) {
1446               m_tag[tagNr] = nullptr;
1447             } else {
1448               saveTags |= tagTypes[tagNr];
1449             }
1450           }
1451           setId3v2VersionOrDefault(id3v2Version);
1452           if (
1453 #if TAGLIB_VERSION >= 0x010c00
1454               wavFile->save(
1455                 static_cast<TagLib::RIFF::WAV::File::TagTypes>(saveTags),
1456                 TagLib::File::StripOthers,
1457                 m_id3v2Version == 4 ? TagLib::ID3v2::v4 : TagLib::ID3v2::v3)
1458 #else
1459               wavFile->save(static_cast<TagLib::RIFF::WAV::File::TagTypes>(
1460                               saveTags), true, m_id3v2Version)
1461 #endif
1462               ) {
1463             if (TagConfig::instance().lowercaseId3RiffChunk()) {
1464               wavFile->changeToLowercaseId3Chunk();
1465             }
1466             fileChanged = true;
1467             FOR_TAGLIB_TAGS(tagNr) {
1468               markTagUnchanged(tagNr);
1469             }
1470             needsSave = false;
1471           }
1472         }
1473         else if (auto dsfFile = dynamic_cast<DSFFile*>(file)) {
1474           setId3v2VersionOrDefault(id3v2Version);
1475           if (dsfFile->save(m_id3v2Version)) {
1476             fileChanged = true;
1477             FOR_TAGLIB_TAGS(tagNr) {
1478               markTagUnchanged(tagNr);
1479             }
1480             needsSave = false;
1481           }
1482         }
1483 #if TAGLIB_VERSION >= 0x010b00
1484         else if (auto xiphComment =
1485                  dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[Frame::Tag_2])) {
1486           xiphComment->removeAllPictures();
1487           const auto frames = m_pictures;
1488           for (const Frame& frame : frames) {
1489             auto pic = new TagLib::FLAC::Picture;
1490             frameToFlacPicture(frame, pic);
1491             xiphComment->addPicture(pic);
1492           }
1493         }
1494 #endif
1495         else if (auto mp4Tag =
1496                  dynamic_cast<TagLib::MP4::Tag*>(m_tag[Frame::Tag_2])) {
1497           if (!m_pictures.isEmpty()) {
1498             TagLib::MP4::CoverArtList coverArtList;
1499             const auto frames = m_pictures;
1500             for (const Frame& frame : frames) {
1501               QByteArray ba;
1502               TagLib::MP4::CoverArt::Format format = TagLib::MP4::CoverArt::JPEG;
1503               if (PictureFrame::getData(frame, ba)) {
1504                 QString mimeType;
1505                 if (PictureFrame::getMimeType(frame, mimeType)) {
1506                   if (mimeType == QLatin1String("image/png")) {
1507                     format = TagLib::MP4::CoverArt::PNG;
1508                   } else if (mimeType == QLatin1String("image/bmp")) {
1509                     format = TagLib::MP4::CoverArt::BMP;
1510                   } else if (mimeType == QLatin1String("image/gif")) {
1511                     format = TagLib::MP4::CoverArt::GIF;
1512                   }
1513                 }
1514               }
1515               coverArtList.append(TagLib::MP4::CoverArt(
1516                           format,
1517                           TagLib::ByteVector(
1518                             ba.data(), static_cast<unsigned int>(ba.size()))));
1519             }
1520 #if TAGLIB_VERSION >= 0x010a00
1521             mp4Tag->setItem("covr", coverArtList);
1522 #else
1523             mp4Tag->itemListMap()["covr"] = coverArtList;
1524 #endif
1525           } else {
1526 #if TAGLIB_VERSION >= 0x010a00
1527             mp4Tag->removeItem("covr");
1528 #else
1529             mp4Tag->itemListMap().erase("covr");
1530 #endif
1531           }
1532         }
1533         if (needsSave && m_fileRef.save()) {
1534           fileChanged = true;
1535           FOR_TAGLIB_TAGS(tagNr) {
1536             markTagUnchanged(tagNr);
1537           }
1538         }
1539       }
1540     }
1541   }
1542 
1543   // If the file was changed, make sure it is written to disk.
1544   // This is done when the file is closed. Later the file is opened again.
1545   // If the file is not properly closed, doubled tags can be
1546   // written if the file is finally closed!
1547   // This can be reproduced with an untagged MP3 file, then add
1548   // an ID3v2 title, save, add an ID3v2 artist, save, reload
1549   // => double ID3v2 tags.
1550   // On Windows it is necessary to close the file before renaming it,
1551   // so it is done even if the file is not changed.
1552 #ifndef Q_OS_WIN32
1553   closeFile(fileChanged);
1554 #else
1555   closeFile(true);
1556 #endif
1557 
1558   // restore time stamp
1559   if (actime || modtime) {
1560     setFileTimeStamps(fnStr, actime, modtime);
1561   }
1562 
1563   if (isFilenameChanged()) {
1564     if (!renameFile()) {
1565       return false;
1566     }
1567     markFilenameUnchanged();
1568     *renamed = true;
1569   }
1570 
1571 #ifndef Q_OS_WIN32
1572   if (fileChanged)
1573 #endif
1574     makeFileOpen(true);
1575   return true;
1576 }
1577 
1578 namespace {
1579 
1580 /**
1581  * Get a genre string from a string which can contain the genre itself,
1582  * or only the genre number or the genre number in parenthesis.
1583  *
1584  * @param str genre string
1585  *
1586  * @return genre.
1587  */
getGenreString(const TagLib::String & str)1588 QString getGenreString(const TagLib::String& str)
1589 {
1590 #if TAGLIB_VERSION < 0x010b01
1591   if (str.isNull()) {
1592     return QLatin1String("");
1593   }
1594 #endif
1595   QString qs = toQString(str);
1596   int cpPos = 0, n = 0xff;
1597   bool ok = false;
1598   if (qs[0] == QLatin1Char('(') && (cpPos = qs.indexOf(QLatin1Char(')'), 2)) > 1) {
1599 #if QT_VERSION >= 0x060000
1600     n = qs.mid(1, cpPos - 1).toInt(&ok);
1601 #else
1602     n = qs.midRef(1, cpPos - 1).toInt(&ok);
1603 #endif
1604     if (!ok || n > 0xff) {
1605       n = 0xff;
1606     }
1607     return QString::fromLatin1(Genres::getName(n));
1608   } else if ((n = qs.toInt(&ok)) >= 0 && n <= 0xff && ok) {
1609     return QString::fromLatin1(Genres::getName(n));
1610   } else {
1611     return qs;
1612   }
1613 }
1614 
1615 }
1616 
1617 /**
1618  * Create tag if it does not already exist so that it can be set.
1619  *
1620  * @return true if tag can be set.
1621  */
makeTagSettable(Frame::TagNumber tagNr)1622 bool TagLibFile::makeTagSettable(Frame::TagNumber tagNr)
1623 {
1624   if (tagNr >= NUM_TAGS)
1625     return false;
1626 
1627   makeFileOpen();
1628   if (!m_tag[tagNr]) {
1629     TagLib::File* file;
1630     if (!m_fileRef.isNull() && (file = m_fileRef.file()) != nullptr) {
1631       TagLib::MPEG::File* mpegFile;
1632       TagLib::FLAC::File* flacFile;
1633       TagLib::MPC::File* mpcFile;
1634       TagLib::WavPack::File* wvFile;
1635       TagLib::TrueAudio::File* ttaFile;
1636       TagLib::APE::File* apeFile;
1637       if (tagNr == Frame::Tag_1) {
1638         if ((mpegFile = dynamic_cast<TagLib::MPEG::File*>(file)) != nullptr) {
1639           m_tag[tagNr] = mpegFile->ID3v1Tag(true);
1640         } else if ((flacFile = dynamic_cast<TagLib::FLAC::File*>(file)) != nullptr) {
1641           m_tag[tagNr] = flacFile->ID3v1Tag(true);
1642 #if TAGLIB_VERSION >= 0x010b00
1643         } else if ((mpcFile = dynamic_cast<TagLib::MPC::File*>(file)) != nullptr) {
1644           m_tag[tagNr] = mpcFile->ID3v1Tag(true);
1645         } else if ((wvFile = dynamic_cast<TagLib::WavPack::File*>(file)) != nullptr) {
1646           m_tag[tagNr] = wvFile->ID3v1Tag(true);
1647 #endif
1648         } else if ((ttaFile = dynamic_cast<TagLib::TrueAudio::File*>(file)) != nullptr) {
1649           m_tag[tagNr] = ttaFile->ID3v1Tag(true);
1650         } else if ((apeFile = dynamic_cast<TagLib::APE::File*>(file)) != nullptr) {
1651           m_tag[tagNr] = apeFile->ID3v1Tag(true);
1652         }
1653       } else if (tagNr == Frame::Tag_2) {
1654         if ((mpegFile = dynamic_cast<TagLib::MPEG::File*>(file)) != nullptr) {
1655           m_tag[tagNr] = mpegFile->ID3v2Tag(true);
1656         } else if ((flacFile = dynamic_cast<TagLib::FLAC::File*>(file)) != nullptr) {
1657           m_tag[tagNr] = flacFile->xiphComment(true);
1658         } else if ((mpcFile = dynamic_cast<TagLib::MPC::File*>(file)) != nullptr) {
1659           m_tag[tagNr] = mpcFile->APETag(true);
1660         } else if ((wvFile = dynamic_cast<TagLib::WavPack::File*>(file)) != nullptr) {
1661           m_tag[tagNr] = wvFile->APETag(true);
1662         } else if ((ttaFile = dynamic_cast<TagLib::TrueAudio::File*>(file)) != nullptr) {
1663           m_tag[tagNr] = ttaFile->ID3v2Tag(true);
1664         } else if ((apeFile = dynamic_cast<TagLib::APE::File*>(file)) != nullptr) {
1665           m_tag[tagNr] = apeFile->APETag(true);
1666         } else if (auto wavFile =
1667                    dynamic_cast<TagLib::RIFF::WAV::File*>(file)) {
1668           m_tag[tagNr] = wavFile->ID3v2Tag();
1669         }
1670       } else if (tagNr == Frame::Tag_3) {
1671         if ((mpegFile = dynamic_cast<TagLib::MPEG::File*>(file)) != nullptr) {
1672           m_tag[tagNr] = mpegFile->APETag(true);
1673         } else if ((flacFile = dynamic_cast<TagLib::FLAC::File*>(file)) != nullptr) {
1674           m_tag[tagNr] = flacFile->ID3v2Tag(true);
1675 #if TAGLIB_VERSION >= 0x010a00
1676         } else if (auto wavFile =
1677                    dynamic_cast<TagLib::RIFF::WAV::File*>(file)) {
1678           m_tag[tagNr] = wavFile->InfoTag();
1679 #endif
1680         }
1681       }
1682     }
1683   }
1684   return m_tag[tagNr] != nullptr;
1685 }
1686 
1687 namespace {
1688 
1689 /**
1690  * Check if string needs Unicode encoding.
1691  *
1692  * @return true if Unicode needed,
1693  *         false if Latin-1 sufficient.
1694  */
needsUnicode(const QString & qstr)1695 bool needsUnicode(const QString& qstr)
1696 {
1697   bool result = false;
1698   uint unicodeSize = qstr.length();
1699   const QChar* qcarray = qstr.unicode();
1700   for (uint i = 0; i < unicodeSize; ++i) {
1701     char ch = qcarray[i].toLatin1();
1702     if (ch == 0 || (ch & 0x80) != 0) {
1703       result = true;
1704       break;
1705     }
1706   }
1707   return result;
1708 }
1709 
1710 /**
1711  * Get the configured text encoding.
1712  *
1713  * @param unicode true if unicode is required
1714  *
1715  * @return text encoding.
1716  */
getTextEncodingConfig(bool unicode)1717 TagLib::String::Type getTextEncodingConfig(bool unicode)
1718 {
1719   TagLib::String::Type enc = TagLibFile::getDefaultTextEncoding();
1720   if (unicode && enc == TagLib::String::Latin1) {
1721     enc = TagLib::String::UTF8;
1722   }
1723   return enc;
1724 }
1725 
1726 /**
1727  * Remove the first COMM frame with an empty description.
1728  *
1729  * @param id3v2Tag ID3v2 tag
1730  */
removeCommentFrame(TagLib::ID3v2::Tag * id3v2Tag)1731 void removeCommentFrame(TagLib::ID3v2::Tag* id3v2Tag)
1732 {
1733   const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList("COMM");
1734   for (auto it = frameList.begin();
1735        it != frameList.end();
1736        ++it) {
1737     auto id3Frame =
1738       dynamic_cast<TagLib::ID3v2::CommentsFrame*>(*it);
1739     if (id3Frame && id3Frame->description().isEmpty()) {
1740       id3v2Tag->removeFrame(id3Frame, true);
1741       break;
1742     }
1743   }
1744 }
1745 
1746 /**
1747  * Write a Unicode field if the tag is ID3v2 and Latin-1 is not sufficient.
1748  *
1749  * @param tag     tag
1750  * @param qstr    text as QString
1751  * @param tstr    text as TagLib::String
1752  * @param frameId ID3v2 frame ID
1753  *
1754  * @return true if an ID3v2 Unicode field was written.
1755  */
setId3v2Unicode(TagLib::Tag * tag,const QString & qstr,const TagLib::String & tstr,const char * frameId)1756 bool setId3v2Unicode(TagLib::Tag* tag, const QString& qstr,
1757                      const TagLib::String& tstr, const char* frameId)
1758 {
1759   TagLib::ID3v2::Tag* id3v2Tag;
1760   if (tag && (id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(tag)) != nullptr) {
1761     // first check if this string needs to be stored as unicode
1762     TagLib::String::Type enc = getTextEncodingConfig(needsUnicode(qstr));
1763     TagLib::ByteVector id(frameId);
1764     if (enc != TagLib::String::Latin1 || id == "COMM" || id == "TDRC") {
1765       if (id == "COMM") {
1766         removeCommentFrame(id3v2Tag);
1767       } else {
1768         id3v2Tag->removeFrames(id);
1769       }
1770       if (!tstr.isEmpty()) {
1771         TagLib::ID3v2::Frame* frame;
1772         if (frameId[0] != 'C') {
1773           frame = new TagLib::ID3v2::TextIdentificationFrame(id, enc);
1774         } else {
1775           auto commFrame =
1776               new TagLib::ID3v2::CommentsFrame(enc);
1777           frame = commFrame;
1778           commFrame->setLanguage("eng"); // for compatibility with iTunes
1779         }
1780         frame->setText(tstr);
1781 #ifdef Q_OS_WIN32
1782         // freed in Windows DLL => must be allocated in the same DLL
1783         TagLib::ID3v2::Frame* dllAllocatedFrame =
1784           TagLib::ID3v2::FrameFactory::instance()->createFrame(frame->render());
1785         if (dllAllocatedFrame) {
1786           id3v2Tag->addFrame(dllAllocatedFrame);
1787         }
1788         delete frame;
1789 #else
1790         id3v2Tag->addFrame(frame);
1791 #endif
1792       }
1793       return true;
1794     }
1795   }
1796   return false;
1797 }
1798 
1799 }
1800 
1801 /**
1802  * Check if tag information has already been read.
1803  *
1804  * @return true if information is available,
1805  *         false if the tags have not been read yet, in which case
1806  *         hasTag() does not return meaningful information.
1807  */
isTagInformationRead() const1808 bool TagLibFile::isTagInformationRead() const
1809 {
1810   return m_tagInformationRead;
1811 }
1812 
1813 /**
1814  * Check if tags are supported by the format of this file.
1815  *
1816  * @param tagNr tag number
1817  * @return true.
1818  */
isTagSupported(Frame::TagNumber tagNr) const1819 bool TagLibFile::isTagSupported(Frame::TagNumber tagNr) const
1820 {
1821   return tagNr < NUM_TAGS ? m_isTagSupported[tagNr] : false;
1822 }
1823 
1824 /**
1825  * Check if file has a tag.
1826  *
1827  * @param tagNr tag number
1828  * @return true if tag is available.
1829  * @see isTagInformationRead()
1830  */
hasTag(Frame::TagNumber tagNr) const1831 bool TagLibFile::hasTag(Frame::TagNumber tagNr) const
1832 {
1833   return tagNr < NUM_TAGS ? m_hasTag[tagNr] : false;
1834 }
1835 
1836 /**
1837  * Get technical detail information.
1838  *
1839  * @param info the detail information is returned here
1840  */
getDetailInfo(DetailInfo & info) const1841 void TagLibFile::getDetailInfo(DetailInfo& info) const
1842 {
1843   info = m_detailInfo;
1844 }
1845 
1846 /**
1847  * Cache technical detail information.
1848  */
readAudioProperties()1849 void TagLibFile::readAudioProperties()
1850 {
1851   TagLib::AudioProperties* audioProperties;
1852   if (!m_fileRef.isNull() &&
1853       (audioProperties = m_fileRef.audioProperties()) != nullptr) {
1854     TagLib::MPEG::Properties* mpegProperties;
1855     TagLib::Ogg::Speex::Properties* speexProperties;
1856     TagLib::TrueAudio::Properties* ttaProperties;
1857     TagLib::WavPack::Properties* wvProperties;
1858     TagLib::APE::Properties* apeProperties;
1859     TagLib::Mod::Properties* modProperties;
1860     TagLib::S3M::Properties* s3mProperties;
1861     TagLib::IT::Properties* itProperties;
1862     TagLib::XM::Properties* xmProperties;
1863     TagLib::Ogg::Opus::Properties* opusProperties;
1864     DSFProperties* dsfProperties;
1865     m_detailInfo.valid = true;
1866     if ((mpegProperties =
1867          dynamic_cast<TagLib::MPEG::Properties*>(audioProperties)) != nullptr) {
1868       if (getFilename().right(4).toLower() == QLatin1String(".aac")) {
1869         m_detailInfo.format = QLatin1String("AAC");
1870         return;
1871       }
1872       switch (mpegProperties->version()) {
1873         case TagLib::MPEG::Header::Version1:
1874           m_detailInfo.format = QLatin1String("MPEG 1 ");
1875           break;
1876         case TagLib::MPEG::Header::Version2:
1877           m_detailInfo.format = QLatin1String("MPEG 2 ");
1878           break;
1879         case TagLib::MPEG::Header::Version2_5:
1880           m_detailInfo.format = QLatin1String("MPEG 2.5 ");
1881           break;
1882       }
1883       int layer = mpegProperties->layer();
1884       if (layer >= 1 && layer <= 3) {
1885         m_detailInfo.format += QLatin1String("Layer ");
1886         m_detailInfo.format += QString::number(layer);
1887       }
1888       switch (mpegProperties->channelMode()) {
1889         case TagLib::MPEG::Header::Stereo:
1890           m_detailInfo.channelMode = DetailInfo::CM_Stereo;
1891           m_detailInfo.channels = 2;
1892           break;
1893         case TagLib::MPEG::Header::JointStereo:
1894           m_detailInfo.channelMode = DetailInfo::CM_JointStereo;
1895           m_detailInfo.channels = 2;
1896           break;
1897         case TagLib::MPEG::Header::DualChannel:
1898           m_detailInfo.channels = 2;
1899           break;
1900         case TagLib::MPEG::Header::SingleChannel:
1901           m_detailInfo.channels = 1;
1902           break;
1903       }
1904     } else if (dynamic_cast<TagLib::Vorbis::Properties*>(audioProperties) !=
1905                nullptr) {
1906       m_detailInfo.format = QLatin1String("Ogg Vorbis");
1907     } else if (TagLib::FLAC::Properties* flacProperties =
1908                 dynamic_cast<TagLib::FLAC::Properties*>(audioProperties)) {
1909       m_detailInfo.format = QLatin1String("FLAC");
1910 #if TAGLIB_VERSION >= 0x010a00
1911       int bits = flacProperties->bitsPerSample();
1912       if (bits > 0) {
1913         m_detailInfo.format += QLatin1Char(' ');
1914         m_detailInfo.format += QString::number(bits);
1915         m_detailInfo.format += QLatin1String(" bit");
1916       }
1917 #endif
1918     } else if (dynamic_cast<TagLib::MPC::Properties*>(audioProperties) != nullptr) {
1919       m_detailInfo.format = QLatin1String("MPC");
1920     } else if ((speexProperties =
1921                 dynamic_cast<TagLib::Ogg::Speex::Properties*>(audioProperties)) != nullptr) {
1922       m_detailInfo.format = QString(QLatin1String("Speex %1")).arg(speexProperties->speexVersion());
1923     } else if ((ttaProperties =
1924                 dynamic_cast<TagLib::TrueAudio::Properties*>(audioProperties)) != nullptr) {
1925       m_detailInfo.format = QLatin1String("True Audio ");
1926       m_detailInfo.format += QString::number(ttaProperties->ttaVersion());
1927       m_detailInfo.format += QLatin1Char(' ');
1928       m_detailInfo.format += QString::number(ttaProperties->bitsPerSample());
1929       m_detailInfo.format += QLatin1String(" bit");
1930     } else if ((wvProperties =
1931                 dynamic_cast<TagLib::WavPack::Properties*>(audioProperties)) != nullptr) {
1932       m_detailInfo.format = QLatin1String("WavPack ");
1933       m_detailInfo.format += QString::number(wvProperties->version(), 16);
1934       m_detailInfo.format += QLatin1Char(' ');
1935       m_detailInfo.format += QString::number(wvProperties->bitsPerSample());
1936       m_detailInfo.format += QLatin1String(" bit");
1937     } else if (TagLib::MP4::Properties* mp4Properties =
1938                 dynamic_cast<TagLib::MP4::Properties*>(audioProperties)) {
1939       m_detailInfo.format = QLatin1String("MP4");
1940 #if TAGLIB_VERSION >= 0x010a00
1941       switch (mp4Properties->codec()) {
1942       case TagLib::MP4::Properties::AAC:
1943         m_detailInfo.format += QLatin1String(" AAC");
1944         break;
1945       case TagLib::MP4::Properties::ALAC:
1946         m_detailInfo.format += QLatin1String(" ALAC");
1947         break;
1948       case TagLib::MP4::Properties::Unknown:
1949         ;
1950       }
1951       int bits = mp4Properties->bitsPerSample();
1952       if (bits > 0) {
1953         m_detailInfo.format += QLatin1Char(' ');
1954         m_detailInfo.format += QString::number(bits);
1955         m_detailInfo.format += QLatin1String(" bit");
1956       }
1957 #endif
1958     } else if (dynamic_cast<TagLib::ASF::Properties*>(audioProperties) != nullptr) {
1959       m_detailInfo.format = QLatin1String("ASF");
1960     } else if (TagLib::RIFF::AIFF::Properties* aiffProperties =
1961                dynamic_cast<TagLib::RIFF::AIFF::Properties*>(audioProperties)) {
1962       m_detailInfo.format = QLatin1String("AIFF");
1963 #if TAGLIB_VERSION >= 0x010a00
1964       int bits = aiffProperties->bitsPerSample();
1965       if (bits > 0) {
1966         m_detailInfo.format += QLatin1Char(' ');
1967         m_detailInfo.format += QString::number(bits);
1968         m_detailInfo.format += QLatin1String(" bit");
1969       }
1970 #endif
1971     } else if (TagLib::RIFF::WAV::Properties* wavProperties =
1972                dynamic_cast<TagLib::RIFF::WAV::Properties*>(audioProperties)) {
1973       m_detailInfo.format = QLatin1String("WAV");
1974 #if TAGLIB_VERSION >= 0x010a00
1975       int format = wavProperties->format();
1976       if (format > 0) {
1977         // https://tools.ietf.org/html/rfc2361#appendix-A
1978         static const struct {
1979           int code;
1980           const char* name;
1981         } codeToName[] = {
1982         {0x0001, "PCM"}, {0x0002, "ADPCM"}, {0x003, "IEEE Float"},
1983         {0x0004, "VSELP"}, {0x0005, "IBM CVSD"}, {0x0006, "ALAW"},
1984         {0x0007, "MULAW"}, {0x0010, "OKI ADPCM"}, {0x0011, "DVI ADPCM"},
1985         {0x0012, "MediaSpace ADPCM"}, {0x0013, "Sierra ADPCM"},
1986         {0x0014, "G.723 ADPCM"}, {0x0015, "DIGISTD"}, {0x0016, "DIGIFIX"},
1987         {0x0017, "OKI ADPCM"}, {0x0018, "MediaVision ADPCM"}, {0x0019, "CU"},
1988         {0x0020, "Yamaha ADPCM"}, {0x0021, "Sonarc"}, {0x0022, "True Speech"},
1989         {0x0023, "EchoSC1"}, {0x0024, "AF36"}, {0x0025, "APTX"},
1990         {0x0026, "AF10"}, {0x0027, "Prosody 1612"}, {0x0028, "LRC"},
1991         {0x0030, "Dolby AC2"}, {0x0031, "GSM610"}, {0x0032, "MSNAudio"},
1992         {0x0033, "Antex ADPCME"}, {0x0034, "Control Res VQLPC"}, {0x0035, "Digireal"},
1993         {0x0036, "DigiADPCM"}, {0x0037, "Control Res CR10"}, {0x0038, "NMS VBXADPCM"},
1994         {0x0039, "Roland RDAC"}, {0x003a, "EchoSC3"}, {0x003b, "Rockwell ADPCM"},
1995         {0x003c, "Rockwell DIGITALK"}, {0x003d, "Xebec"}, {0x0040, "G.721 ADPCM"},
1996         {0x0041, "G.728 CELP"}, {0x0042, "MSG723"}, {0x0050, "MPEG"},
1997         {0x0052, "RT24"}, {0x0053, "PAC"}, {0x0055, "MPEG Layer 3"},
1998         {0x0059, "Lucent G.723"}, {0x0060, "Cirrus"}, {0x0061, "ESPCM"},
1999         {0x0062, "Voxware"}, {0x0063, "Canopus Atrac"}, {0x0064, "G.726 ADPCM"},
2000         {0x0065, "G.722 ADPCM"}, {0x0066, "DSAT"}, {0x0067, "DSAT Display"},
2001         {0x0069, "Voxware Byte Aligned"}, {0x0070, "Voxware AC8"}, {0x0071, "Voxware AC10"},
2002         {0x0072, "Voxware AC16"}, {0x0073, "Voxware AC20"}, {0x0074, "Voxware MetaVoice"},
2003         {0x0075, "Voxware MetaSound"}, {0x0076, "Voxware RT29HW"}, {0x0077, "Voxware VR12"},
2004         {0x0078, "Voxware VR18"}, {0x0079, "Voxware TQ40"}, {0x0080, "Softsound"},
2005         {0x0081, "Voxware TQ60"}, {0x0082, "MSRT24"}, {0x0083, "G.729A"},
2006         {0x0084, "MVI MV12"}, {0x0085, "DF G.726"}, {0x0086, "DF GSM610"},
2007         {0x0088, "ISIAudio"}, {0x0089, "Onlive"}, {0x0091, "SBC24"},
2008         {0x0092, "Dolby AC3 SPDIF"}, {0x0097, "ZyXEL ADPCM"}, {0x0098, "Philips LPCBB"},
2009         {0x0099, "Packed"}, {0x0100, "Rhetorex ADPCM"}, {0x0101, "IRAT"},
2010         {0x0111, "Vivo G.723"}, {0x0112, "Vivo Siren"}, {0x0123, "Digital G.723"},
2011         {0x0200, "Creative ADPCM"}, {0x0202, "Creative FastSpeech8"}, {0x0203, "Creative FastSpeech10"},
2012         {0x0220, "Quarterdeck"}, {0x0300, "FM Towns Snd"}, {0x0400, "BTV Digital"},
2013         {0x0680, "VME VMPCM"}, {0x1000, "OLIGSM"}, {0x1001, "OLIADPCM"},
2014         {0x1002, "OLICELP"}, {0x1003, "OLISBC"}, {0x1004, "OLIOPR"},
2015         {0x1100, "LH Codec"}, {0x1400, "Norris"}, {0x1401, "ISIAudio"},
2016         {0x1500, "Soundspace Music Compression"}, {0x2000, "DVM"}
2017         };
2018         for (const auto& c2n : codeToName) {
2019           if (format == c2n.code) {
2020             m_detailInfo.format += QLatin1Char(' ');
2021             m_detailInfo.format += QString::fromLatin1(c2n.name);
2022             break;
2023           }
2024         }
2025       }
2026       int bits = wavProperties->bitsPerSample();
2027       if (bits > 0) {
2028         m_detailInfo.format += QLatin1Char(' ');
2029         m_detailInfo.format += QString::number(bits);
2030         m_detailInfo.format += QLatin1String(" bit");
2031       }
2032 #endif
2033     } else if ((apeProperties =
2034                 dynamic_cast<TagLib::APE::Properties*>(audioProperties)) != nullptr) {
2035       m_detailInfo.format = QString(QLatin1String("APE %1.%2 %3 bit"))
2036         .arg(apeProperties->version() / 1000)
2037         .arg(apeProperties->version() % 1000)
2038         .arg(apeProperties->bitsPerSample());
2039     } else if ((modProperties =
2040                 dynamic_cast<TagLib::Mod::Properties*>(audioProperties)) != nullptr) {
2041       m_detailInfo.format = QString(QLatin1String("Mod %1 %2 Instruments"))
2042           .arg(getTrackerName())
2043           .arg(modProperties->instrumentCount());
2044     } else if ((s3mProperties =
2045                 dynamic_cast<TagLib::S3M::Properties*>(audioProperties)) != nullptr) {
2046       m_detailInfo.format = QString(QLatin1String("S3M %1 V%2 T%3"))
2047           .arg(getTrackerName())
2048           .arg(s3mProperties->fileFormatVersion())
2049           .arg(s3mProperties->trackerVersion(), 0, 16);
2050       m_detailInfo.channelMode = s3mProperties->stereo()
2051           ? DetailInfo::CM_Stereo : DetailInfo::CM_None;
2052     } else if ((itProperties =
2053                 dynamic_cast<TagLib::IT::Properties*>(audioProperties)) != nullptr) {
2054       m_detailInfo.format = QString(QLatin1String("IT %1 V%2 %3 Instruments"))
2055           .arg(getTrackerName())
2056           .arg(itProperties->version(), 0, 16)
2057           .arg(itProperties->instrumentCount());
2058       m_detailInfo.channelMode = itProperties->stereo()
2059           ? DetailInfo::CM_Stereo : DetailInfo::CM_None;
2060     } else if ((xmProperties =
2061                 dynamic_cast<TagLib::XM::Properties*>(audioProperties)) != nullptr) {
2062       m_detailInfo.format = QString(QLatin1String("XM %1 V%2 %3 Instruments"))
2063           .arg(getTrackerName())
2064           .arg(xmProperties->version(), 0, 16)
2065           .arg(xmProperties->instrumentCount());
2066     } else if ((opusProperties =
2067           dynamic_cast<TagLib::Ogg::Opus::Properties*>(audioProperties)) != nullptr) {
2068       m_detailInfo.format = QString(QLatin1String("Opus %1"))
2069           .arg(opusProperties->opusVersion());
2070     } else if ((dsfProperties =
2071           dynamic_cast<DSFProperties*>(audioProperties)) != nullptr) {
2072       m_detailInfo.format = QString(QLatin1String("DSF %1"))
2073           .arg(dsfProperties->version());
2074     }
2075     m_detailInfo.bitrate = audioProperties->bitrate();
2076     m_detailInfo.sampleRate = audioProperties->sampleRate();
2077     if (audioProperties->channels() > 0) {
2078       m_detailInfo.channels = audioProperties->channels();
2079     }
2080     m_detailInfo.duration = audioProperties->length();
2081   } else {
2082     m_detailInfo.valid = false;
2083   }
2084 }
2085 
2086 /**
2087  * Get tracker name of a module file.
2088  *
2089  * @return tracker name, null if not found.
2090  */
getTrackerName() const2091 QString TagLibFile::getTrackerName() const
2092 {
2093   QString trackerName;
2094   if (auto modTag = dynamic_cast<TagLib::Mod::Tag*>(m_tag[Frame::Tag_2])) {
2095     trackerName = toQString(modTag->trackerName()).trimmed();
2096   }
2097   return trackerName;
2098 }
2099 
2100 
2101 /**
2102  * Set m_id3v2Version to 3 or 4 from tag if it exists, else to 0.
2103  * @param id3v2Tag ID3v2 tag
2104  */
setId3v2VersionFromTag(TagLib::ID3v2::Tag * id3v2Tag)2105 void TagLibFile::setId3v2VersionFromTag(TagLib::ID3v2::Tag* id3v2Tag)
2106 {
2107   TagLib::ID3v2::Header* header;
2108   m_id3v2Version = 0;
2109   if (id3v2Tag && (header = id3v2Tag->header()) != nullptr) {
2110     if (!id3v2Tag->isEmpty()) {
2111       m_id3v2Version = header->majorVersion();
2112     } else {
2113       header->setMajorVersion(TagConfig::instance().id3v2Version() ==
2114                               TagConfig::ID3v2_3_0 ? 3 : 4);
2115     }
2116   }
2117 }
2118 
2119 /**
2120  * Set m_id3v2Version from given value (3 or 4) or use default from
2121  * configuration if not already set to 3 or 4.
2122  * @param id3v2Version 3 or 4 to force version, 0 to use existing version
2123  * or default
2124  */
setId3v2VersionOrDefault(int id3v2Version)2125 void TagLibFile::setId3v2VersionOrDefault(int id3v2Version)
2126 {
2127   if (id3v2Version == 3 || id3v2Version == 4) {
2128     m_id3v2Version = id3v2Version;
2129   }
2130   if (m_id3v2Version != 3 && m_id3v2Version != 4) {
2131     m_id3v2Version = TagConfig::instance().id3v2Version() ==
2132         TagConfig::ID3v2_3_0 ? 3 : 4;
2133   }
2134 }
2135 
2136 /**
2137  * Get duration of file.
2138  *
2139  * @return duration in seconds,
2140  *         0 if unknown.
2141  */
getDuration() const2142 unsigned TagLibFile::getDuration() const
2143 {
2144   return m_detailInfo.valid ? m_detailInfo.duration : 0;
2145 }
2146 
2147 /**
2148  * Get file extension including the dot.
2149  *
2150  * @return file extension ".mp3".
2151  */
getFileExtension() const2152 QString TagLibFile::getFileExtension() const
2153 {
2154   return m_fileExtension;
2155 }
2156 
2157 /**
2158  * Get the format of a tag.
2159  *
2160  * @param tag tag, 0 if no tag available
2161  * @param type the tag type is returned here
2162  *
2163  * @return string describing format of tag,
2164  *         e.g. "ID3v1.1", "ID3v2.3", "Vorbis", "APE",
2165  *         QString::null if unknown.
2166  */
getTagFormat(const TagLib::Tag * tag,TagType & type)2167 QString TagLibFile::getTagFormat(const TagLib::Tag* tag, TagType& type)
2168 {
2169   if (tag && !tag->isEmpty()) {
2170     const TagLib::ID3v2::Tag* id3v2Tag;
2171     if (dynamic_cast<const TagLib::ID3v1::Tag*>(tag) != nullptr) {
2172       type = TT_Id3v1;
2173       return QLatin1String("ID3v1.1");
2174     } else if ((id3v2Tag = dynamic_cast<const TagLib::ID3v2::Tag*>(tag)) != nullptr) {
2175       type = TT_Id3v2;
2176       TagLib::ID3v2::Header* header = id3v2Tag->header();
2177       if (header) {
2178         uint majorVersion = header->majorVersion();
2179         uint revisionNumber = header->revisionNumber();
2180         return QString(QLatin1String("ID3v2.%1.%2"))
2181           .arg(majorVersion).arg(revisionNumber);
2182       } else {
2183         return QLatin1String("ID3v2");
2184       }
2185     } else if (dynamic_cast<const TagLib::Ogg::XiphComment*>(tag) != nullptr) {
2186       type = TT_Vorbis;
2187       return QLatin1String("Vorbis");
2188     } else if (dynamic_cast<const TagLib::APE::Tag*>(tag) != nullptr) {
2189       type = TT_Ape;
2190       return QLatin1String("APE");
2191     } else if (dynamic_cast<const TagLib::MP4::Tag*>(tag) != nullptr) {
2192       type = TT_Mp4;
2193       return QLatin1String("MP4");
2194     } else if (dynamic_cast<const TagLib::ASF::Tag*>(tag) != nullptr) {
2195       type = TT_Asf;
2196       return QLatin1String("ASF");
2197 #if TAGLIB_VERSION >= 0x010a00
2198     } else if (dynamic_cast<const TagLib::RIFF::Info::Tag*>(tag) != nullptr) {
2199       type = TT_Info;
2200       return QLatin1String("RIFF INFO");
2201 #endif
2202     }
2203   }
2204   type = TT_Unknown;
2205   return QString();
2206 }
2207 
2208 /**
2209  * Get the format of tag.
2210  *
2211  * @param tagNr tag number
2212  * @return string describing format of tag,
2213  *         e.g. "ID3v1.1", "ID3v2.3", "Vorbis", "APE",
2214  *         QString::null if unknown.
2215  */
getTagFormat(Frame::TagNumber tagNr) const2216 QString TagLibFile::getTagFormat(Frame::TagNumber tagNr) const
2217 {
2218   return tagNr < NUM_TAGS ? m_tagFormat[tagNr] : QString();
2219 }
2220 
2221 
2222 namespace TagLibFileInternal {
2223 
2224 /**
2225  * Fix up the format of the value if needed for an ID3v2 frame.
2226  *
2227  * @param self this TagLibFile instance
2228  * @param frameType type of frame
2229  * @param value the value to be set for frame, will be modified if needed
2230  */
fixUpTagLibFrameValue(const TagLibFile * self,Frame::Type frameType,QString & value)2231 void fixUpTagLibFrameValue(const TagLibFile* self,
2232                            Frame::Type frameType, QString& value)
2233 {
2234   if (frameType == Frame::FT_Genre) {
2235     const bool useId3v23 = self->m_id3v2Version == 3;
2236     if (!TagConfig::instance().genreNotNumeric() ||
2237         (useId3v23 && value.contains(Frame::stringListSeparator()))) {
2238       value = Genres::getNumberString(value, useId3v23);
2239     }
2240   } else if (frameType == Frame::FT_Track) {
2241     self->formatTrackNumberIfEnabled(value, true);
2242   } else if ((frameType == Frame::FT_Arranger ||
2243               frameType == Frame::FT_Performer) &&
2244              !value.isEmpty() &&
2245              !value.contains(Frame::stringListSeparator())) {
2246     // When using TIPL or TMCL and writing an ID3v2.3.0 tag, TagLib
2247     // needs in ID3v2::Tag::downgradeFrames() a string list with at
2248     // least two elements, otherwise it will not take the value over
2249     // to an IPLS frame. If there is a single value in such a case,
2250     // add a second element.
2251     value += Frame::stringListSeparator();
2252   }
2253 }
2254 
2255 }
2256 
2257 
2258 using namespace TagLibFileInternal;
2259 
2260 namespace {
2261 
2262 /** Types and descriptions for id3lib frame IDs */
2263 const struct TypeStrOfId {
2264   const char* str;
2265   Frame::Type type;
2266   bool supported;
2267 } typeStrOfId[] = {
2268   { QT_TRANSLATE_NOOP("@default", "AENC - Audio encryption"), Frame::FT_Other, false },
2269   { QT_TRANSLATE_NOOP("@default", "APIC - Attached picture"), Frame::FT_Picture, true },
2270   { QT_TRANSLATE_NOOP("@default", "ASPI - Audio seek point index"), Frame::FT_Other, false },
2271 #if TAGLIB_VERSION >= 0x010a00
2272   { QT_TRANSLATE_NOOP("@default", "CHAP - Chapter"), Frame::FT_Other, true },
2273 #endif
2274   { QT_TRANSLATE_NOOP("@default", "COMM - Comments"), Frame::FT_Comment, true },
2275   { QT_TRANSLATE_NOOP("@default", "COMR - Commercial"), Frame::FT_Other, false },
2276 #if TAGLIB_VERSION >= 0x010a00
2277   { QT_TRANSLATE_NOOP("@default", "CTOC - Table of contents"), Frame::FT_Other, true },
2278 #endif
2279   { QT_TRANSLATE_NOOP("@default", "ENCR - Encryption method registration"), Frame::FT_Other, false },
2280   { QT_TRANSLATE_NOOP("@default", "EQU2 - Equalisation (2)"), Frame::FT_Other, false },
2281   { QT_TRANSLATE_NOOP("@default", "ETCO - Event timing codes"), Frame::FT_Other, true },
2282   { QT_TRANSLATE_NOOP("@default", "GEOB - General encapsulated object"), Frame::FT_Other, true },
2283   { QT_TRANSLATE_NOOP("@default", "GRID - Group identification registration"), Frame::FT_Other, false },
2284 #if TAGLIB_VERSION >= 0x010c00
2285   { QT_TRANSLATE_NOOP("@default", "GRP1 - Grouping"), Frame::FT_Other, true },
2286 #endif
2287   { QT_TRANSLATE_NOOP("@default", "LINK - Linked information"), Frame::FT_Other, false },
2288   { QT_TRANSLATE_NOOP("@default", "MCDI - Music CD identifier"), Frame::FT_Other, false },
2289   { QT_TRANSLATE_NOOP("@default", "MLLT - MPEG location lookup table"), Frame::FT_Other, false },
2290 #if TAGLIB_VERSION >= 0x010c00
2291   { QT_TRANSLATE_NOOP("@default", "MVIN - Movement Number"), Frame::FT_Other, true },
2292   { QT_TRANSLATE_NOOP("@default", "MVNM - Movement Name"), Frame::FT_Other, true },
2293 #endif
2294   { QT_TRANSLATE_NOOP("@default", "OWNE - Ownership frame"), Frame::FT_Other, true },
2295   { QT_TRANSLATE_NOOP("@default", "PRIV - Private frame"), Frame::FT_Other, true },
2296   { QT_TRANSLATE_NOOP("@default", "PCNT - Play counter"), Frame::FT_Other, false },
2297 #if TAGLIB_VERSION >= 0x010b00
2298   { QT_TRANSLATE_NOOP("@default", "PCST - Podcast"), Frame::FT_Other, true },
2299 #endif
2300   { QT_TRANSLATE_NOOP("@default", "POPM - Popularimeter"), Frame::FT_Rating, true },
2301   { QT_TRANSLATE_NOOP("@default", "POSS - Position synchronisation frame"), Frame::FT_Other, false },
2302   { QT_TRANSLATE_NOOP("@default", "RBUF - Recommended buffer size"), Frame::FT_Other, false },
2303   { QT_TRANSLATE_NOOP("@default", "RVA2 - Relative volume adjustment (2)"), Frame::FT_Other, true },
2304   { QT_TRANSLATE_NOOP("@default", "RVRB - Reverb"), Frame::FT_Other, false },
2305   { QT_TRANSLATE_NOOP("@default", "SEEK - Seek frame"), Frame::FT_Other, false },
2306   { QT_TRANSLATE_NOOP("@default", "SIGN - Signature frame"), Frame::FT_Other, false },
2307   { QT_TRANSLATE_NOOP("@default", "SYLT - Synchronized lyric/text"), Frame::FT_Other, true },
2308   { QT_TRANSLATE_NOOP("@default", "SYTC - Synchronized tempo codes"), Frame::FT_Other, false },
2309   { QT_TRANSLATE_NOOP("@default", "TALB - Album/Movie/Show title"), Frame::FT_Album, true },
2310   { QT_TRANSLATE_NOOP("@default", "TBPM - BPM (beats per minute)"),  Frame::FT_Bpm, true },
2311 #if TAGLIB_VERSION >= 0x010b00
2312   { QT_TRANSLATE_NOOP("@default", "TCAT - Podcast category"), Frame::FT_Other, true },
2313 #endif
2314   { QT_TRANSLATE_NOOP("@default", "TCMP - iTunes compilation flag"), Frame::FT_Compilation, true },
2315   { QT_TRANSLATE_NOOP("@default", "TCOM - Composer"), Frame::FT_Composer, true },
2316   { QT_TRANSLATE_NOOP("@default", "TCON - Content type"), Frame::FT_Genre, true },
2317   { QT_TRANSLATE_NOOP("@default", "TCOP - Copyright message"), Frame::FT_Copyright, true },
2318   { QT_TRANSLATE_NOOP("@default", "TDEN - Encoding time"), Frame::FT_EncodingTime, true },
2319 #if TAGLIB_VERSION >= 0x010b00
2320   { QT_TRANSLATE_NOOP("@default", "TDES - Podcast description"), Frame::FT_Other, true },
2321 #endif
2322   { QT_TRANSLATE_NOOP("@default", "TDLY - Playlist delay"), Frame::FT_Other, true },
2323   { QT_TRANSLATE_NOOP("@default", "TDOR - Original release time"), Frame::FT_OriginalDate, true },
2324   { QT_TRANSLATE_NOOP("@default", "TDRC - Recording time"), Frame::FT_Date, true },
2325   { QT_TRANSLATE_NOOP("@default", "TDRL - Release time"), Frame::FT_ReleaseDate, true },
2326   { QT_TRANSLATE_NOOP("@default", "TDTG - Tagging time"), Frame::FT_Other, true },
2327   { QT_TRANSLATE_NOOP("@default", "TENC - Encoded by"), Frame::FT_EncodedBy, true },
2328   { QT_TRANSLATE_NOOP("@default", "TEXT - Lyricist/Text writer"), Frame::FT_Lyricist, true },
2329   { QT_TRANSLATE_NOOP("@default", "TFLT - File type"), Frame::FT_Other, true },
2330 #if TAGLIB_VERSION >= 0x010b00
2331   { QT_TRANSLATE_NOOP("@default", "TGID - Podcast identifier"), Frame::FT_Other, true },
2332 #endif
2333   { QT_TRANSLATE_NOOP("@default", "TIPL - Involved people list"), Frame::FT_Arranger, true },
2334   { QT_TRANSLATE_NOOP("@default", "TIT1 - Content group description"), Frame::FT_Work, true },
2335   { QT_TRANSLATE_NOOP("@default", "TIT2 - Title/songname/content description"), Frame::FT_Title, true },
2336   { QT_TRANSLATE_NOOP("@default", "TIT3 - Subtitle/Description refinement"), Frame::FT_Description, true },
2337   { QT_TRANSLATE_NOOP("@default", "TKEY - Initial key"), Frame::FT_InitialKey, true },
2338 #if TAGLIB_VERSION >= 0x010b00
2339   { QT_TRANSLATE_NOOP("@default", "TKWD - Podcast keywords"), Frame::FT_Other, true },
2340 #endif
2341   { QT_TRANSLATE_NOOP("@default", "TLAN - Language(s)"), Frame::FT_Language, true },
2342   { QT_TRANSLATE_NOOP("@default", "TLEN - Length"), Frame::FT_Other, true },
2343   { QT_TRANSLATE_NOOP("@default", "TMCL - Musician credits list"), Frame::FT_Performer, true },
2344   { QT_TRANSLATE_NOOP("@default", "TMED - Media type"), Frame::FT_Media, true },
2345   { QT_TRANSLATE_NOOP("@default", "TMOO - Mood"), Frame::FT_Mood, true },
2346   { QT_TRANSLATE_NOOP("@default", "TOAL - Original album/movie/show title"), Frame::FT_OriginalAlbum, true },
2347   { QT_TRANSLATE_NOOP("@default", "TOFN - Original filename"), Frame::FT_Other, true },
2348   { QT_TRANSLATE_NOOP("@default", "TOLY - Original lyricist(s)/text writer(s)"), Frame::FT_Author, true },
2349   { QT_TRANSLATE_NOOP("@default", "TOPE - Original artist(s)/performer(s)"), Frame::FT_OriginalArtist, true },
2350   { QT_TRANSLATE_NOOP("@default", "TOWN - File owner/licensee"), Frame::FT_Other, true },
2351   { QT_TRANSLATE_NOOP("@default", "TPE1 - Lead performer(s)/Soloist(s)"), Frame::FT_Artist, true },
2352   { QT_TRANSLATE_NOOP("@default", "TPE2 - Band/orchestra/accompaniment"), Frame::FT_AlbumArtist, true },
2353   { QT_TRANSLATE_NOOP("@default", "TPE3 - Conductor/performer refinement"), Frame::FT_Conductor, true },
2354   { QT_TRANSLATE_NOOP("@default", "TPE4 - Interpreted, remixed, or otherwise modified by"), Frame::FT_Remixer, true },
2355   { QT_TRANSLATE_NOOP("@default", "TPOS - Part of a set"), Frame::FT_Disc, true },
2356   { QT_TRANSLATE_NOOP("@default", "TPRO - Produced notice"), Frame::FT_Other, true },
2357   { QT_TRANSLATE_NOOP("@default", "TPUB - Publisher"), Frame::FT_Publisher, true },
2358   { QT_TRANSLATE_NOOP("@default", "TRCK - Track number/Position in set"), Frame::FT_Track, true },
2359   { QT_TRANSLATE_NOOP("@default", "TRSN - Internet radio station name"), Frame::FT_Other, true },
2360   { QT_TRANSLATE_NOOP("@default", "TRSO - Internet radio station owner"), Frame::FT_Other, true },
2361   { QT_TRANSLATE_NOOP("@default", "TSO2 - Album artist sort order"), Frame::FT_SortAlbumArtist, true },
2362   { QT_TRANSLATE_NOOP("@default", "TSOA - Album sort order"), Frame::FT_SortAlbum, true },
2363   { QT_TRANSLATE_NOOP("@default", "TSOC - Composer sort order"), Frame::FT_SortComposer, true },
2364   { QT_TRANSLATE_NOOP("@default", "TSOP - Performer sort order"), Frame::FT_SortArtist, true },
2365   { QT_TRANSLATE_NOOP("@default", "TSOT - Title sort order"), Frame::FT_SortName, true },
2366   { QT_TRANSLATE_NOOP("@default", "TSRC - ISRC (international standard recording code)"), Frame::FT_Isrc, true },
2367   { QT_TRANSLATE_NOOP("@default", "TSSE - Software/Hardware and settings used for encoding"), Frame::FT_EncoderSettings, true },
2368   { QT_TRANSLATE_NOOP("@default", "TSST - Set subtitle"), Frame::FT_Subtitle, true },
2369   { QT_TRANSLATE_NOOP("@default", "TXXX - User defined text information"), Frame::FT_Other, true },
2370   { QT_TRANSLATE_NOOP("@default", "UFID - Unique file identifier"), Frame::FT_Other, true },
2371   { QT_TRANSLATE_NOOP("@default", "USER - Terms of use"), Frame::FT_Other, false },
2372   { QT_TRANSLATE_NOOP("@default", "USLT - Unsynchronized lyric/text transcription"), Frame::FT_Lyrics, true },
2373   { QT_TRANSLATE_NOOP("@default", "WCOM - Commercial information"), Frame::FT_Other, true },
2374   { QT_TRANSLATE_NOOP("@default", "WCOP - Copyright/Legal information"), Frame::FT_Other, true },
2375 #if TAGLIB_VERSION >= 0x010b00
2376   { QT_TRANSLATE_NOOP("@default", "WFED - Podcast feed"), Frame::FT_Other, true },
2377 #endif
2378   { QT_TRANSLATE_NOOP("@default", "WOAF - Official audio file webpage"), Frame::FT_WWWAudioFile, true },
2379   { QT_TRANSLATE_NOOP("@default", "WOAR - Official artist/performer webpage"), Frame::FT_Website, true },
2380   { QT_TRANSLATE_NOOP("@default", "WOAS - Official audio source webpage"), Frame::FT_WWWAudioSource, true },
2381   { QT_TRANSLATE_NOOP("@default", "WORS - Official internet radio station homepage"), Frame::FT_Other, true },
2382   { QT_TRANSLATE_NOOP("@default", "WPAY - Payment"), Frame::FT_Other, true },
2383   { QT_TRANSLATE_NOOP("@default", "WPUB - Official publisher webpage"), Frame::FT_Other, true },
2384   { QT_TRANSLATE_NOOP("@default", "WXXX - User defined URL link"), Frame::FT_Other, true }
2385 };
2386 
2387 /**
2388  * Get type and description of frame.
2389  *
2390  * @param id   ID of frame
2391  * @param type the type is returned here
2392  * @param str  the description is returned here
2393  */
getTypeStringForFrameId(const TagLib::ByteVector & id,Frame::Type & type,const char * & str)2394 void getTypeStringForFrameId(const TagLib::ByteVector& id, Frame::Type& type,
2395                              const char*& str)
2396 {
2397   static TagLib::Map<TagLib::ByteVector, unsigned> idIndexMap;
2398   if (idIndexMap.isEmpty()) {
2399     for (unsigned i = 0;
2400          i < sizeof(typeStrOfId) / sizeof(typeStrOfId[0]);
2401          ++i) {
2402       idIndexMap.insert(TagLib::ByteVector(typeStrOfId[i].str, 4), i);
2403     }
2404   }
2405   if (idIndexMap.contains(id)) {
2406     const TypeStrOfId& ts = typeStrOfId[idIndexMap[id]];
2407     type = ts.type;
2408     str = ts.str;
2409   } else {
2410     type = Frame::FT_UnknownFrame;
2411     str = "????";
2412   }
2413 }
2414 
2415 /**
2416  * Get string description starting with 4 bytes ID.
2417  *
2418  * @param type type of frame
2419  *
2420  * @return string.
2421  */
getStringForType(Frame::Type type)2422 const char* getStringForType(Frame::Type type)
2423 {
2424   if (type != Frame::FT_Other) {
2425     for (const auto& ts : typeStrOfId) {
2426       if (ts.type == type) {
2427         return ts.str;
2428       }
2429     }
2430   }
2431   return "????";
2432 }
2433 
2434 /**
2435  * Get the fields from a text identification frame.
2436  *
2437  * @param tFrame text identification frame
2438  * @param fields the fields are appended to this list
2439  * @param type   frame type
2440  *
2441  * @return text representation of fields (Text or URL).
2442  */
getFieldsFromTextFrame(const TagLib::ID3v2::TextIdentificationFrame * tFrame,Frame::FieldList & fields,Frame::Type type)2443 QString getFieldsFromTextFrame(
2444   const TagLib::ID3v2::TextIdentificationFrame* tFrame,
2445   Frame::FieldList& fields, Frame::Type type)
2446 {
2447   QString text;
2448   Frame::Field field;
2449   field.m_id = Frame::ID_TextEnc;
2450   field.m_value = tFrame->textEncoding();
2451   fields.push_back(field);
2452 
2453   const TagLib::ID3v2::UserTextIdentificationFrame* txxxFrame;
2454   if ((txxxFrame =
2455        dynamic_cast<const TagLib::ID3v2::UserTextIdentificationFrame*>(tFrame))
2456       != nullptr) {
2457     field.m_id = Frame::ID_Description;
2458     field.m_value = toQString(txxxFrame->description());
2459     fields.push_back(field);
2460 
2461     TagLib::StringList slText = tFrame->fieldList();
2462     text = slText.size() > 1 ? toQString(slText[1]) : QLatin1String("");
2463   } else {
2464     // if there are multiple items, put them into one string
2465     // separated by a special separator.
2466     text = toQString(tFrame->fieldList().toString(Frame::stringListSeparator().toLatin1()));
2467   }
2468   field.m_id = Frame::ID_Text;
2469   if (type == Frame::FT_Genre) {
2470     text = Genres::getNameString(text);
2471   }
2472   field.m_value = text;
2473   fields.push_back(field);
2474 
2475   return text;
2476 }
2477 
2478 /**
2479  * Get the fields from an attached picture frame.
2480  *
2481  * @param apicFrame attached picture frame
2482  * @param fields the fields are appended to this list
2483  *
2484  * @return text representation of fields (Text or URL).
2485  */
getFieldsFromApicFrame(const TagLib::ID3v2::AttachedPictureFrame * apicFrame,Frame::FieldList & fields)2486 QString getFieldsFromApicFrame(
2487   const TagLib::ID3v2::AttachedPictureFrame* apicFrame,
2488   Frame::FieldList& fields)
2489 {
2490   QString text;
2491   Frame::Field field;
2492   field.m_id = Frame::ID_TextEnc;
2493   field.m_value = apicFrame->textEncoding();
2494   fields.push_back(field);
2495 
2496   // for compatibility with ID3v2.3 id3lib
2497   field.m_id = Frame::ID_ImageFormat;
2498   field.m_value = QString(QLatin1String(""));
2499   fields.push_back(field);
2500 
2501   field.m_id = Frame::ID_MimeType;
2502   field.m_value = toQString(apicFrame->mimeType());
2503   fields.push_back(field);
2504 
2505   field.m_id = Frame::ID_PictureType;
2506   field.m_value = apicFrame->type();
2507   fields.push_back(field);
2508 
2509   field.m_id = Frame::ID_Description;
2510   text = toQString(apicFrame->description());
2511   field.m_value = text;
2512   fields.push_back(field);
2513 
2514   field.m_id = Frame::ID_Data;
2515   TagLib::ByteVector pic = apicFrame->picture();
2516   QByteArray ba;
2517   ba = QByteArray(pic.data(), pic.size());
2518   field.m_value = ba;
2519   fields.push_back(field);
2520 
2521   return text;
2522 }
2523 
2524 /**
2525  * Get the fields from a comments frame.
2526  *
2527  * @param commFrame comments frame
2528  * @param fields the fields are appended to this list
2529  *
2530  * @return text representation of fields (Text or URL).
2531  */
getFieldsFromCommFrame(const TagLib::ID3v2::CommentsFrame * commFrame,Frame::FieldList & fields)2532 QString getFieldsFromCommFrame(
2533   const TagLib::ID3v2::CommentsFrame* commFrame, Frame::FieldList& fields)
2534 {
2535   QString text;
2536   Frame::Field field;
2537   field.m_id = Frame::ID_TextEnc;
2538   field.m_value = commFrame->textEncoding();
2539   fields.push_back(field);
2540 
2541   field.m_id = Frame::ID_Language;
2542   TagLib::ByteVector bvLang = commFrame->language();
2543   field.m_value = QString::fromLatin1(QByteArray(bvLang.data(), bvLang.size()));
2544   fields.push_back(field);
2545 
2546   field.m_id = Frame::ID_Description;
2547   field.m_value = toQString(commFrame->description());
2548   fields.push_back(field);
2549 
2550   field.m_id = Frame::ID_Text;
2551   text = toQString(commFrame->toString());
2552   field.m_value = text;
2553   fields.push_back(field);
2554 
2555   return text;
2556 }
2557 
2558 /**
2559  * Get the fields from a unique file identifier frame.
2560  *
2561  * @param ufidFrame unique file identifier frame
2562  * @param fields the fields are appended to this list
2563  *
2564  * @return text representation of fields (Text or URL).
2565  */
getFieldsFromUfidFrame(const TagLib::ID3v2::UniqueFileIdentifierFrame * ufidFrame,Frame::FieldList & fields)2566 QString getFieldsFromUfidFrame(
2567   const TagLib::ID3v2::UniqueFileIdentifierFrame* ufidFrame,
2568   Frame::FieldList& fields)
2569 {
2570   Frame::Field field;
2571   field.m_id = Frame::ID_Owner;
2572   field.m_value = toQString(ufidFrame->owner());
2573   fields.push_back(field);
2574 
2575   field.m_id = Frame::ID_Id;
2576   TagLib::ByteVector id = ufidFrame->identifier();
2577   QByteArray ba;
2578   ba = QByteArray(id.data(), id.size());
2579   field.m_value = ba;
2580   fields.push_back(field);
2581 
2582   if (!ba.isEmpty()) {
2583     QString text(QString::fromLatin1(ba));
2584     if (ba.size() - text.length() <= 1 &&
2585         AttributeData::isHexString(text, 'Z', QLatin1String("-"))) {
2586       return text;
2587     }
2588   }
2589   return QString();
2590 }
2591 
2592 /**
2593  * Get the fields from a general encapsulated object frame.
2594  *
2595  * @param geobFrame general encapsulated object frame
2596  * @param fields the fields are appended to this list
2597  *
2598  * @return text representation of fields (Text or URL).
2599  */
getFieldsFromGeobFrame(const TagLib::ID3v2::GeneralEncapsulatedObjectFrame * geobFrame,Frame::FieldList & fields)2600 QString getFieldsFromGeobFrame(
2601   const TagLib::ID3v2::GeneralEncapsulatedObjectFrame* geobFrame,
2602   Frame::FieldList& fields)
2603 {
2604   QString text;
2605   Frame::Field field;
2606   field.m_id = Frame::ID_TextEnc;
2607   field.m_value = geobFrame->textEncoding();
2608   fields.push_back(field);
2609 
2610   field.m_id = Frame::ID_MimeType;
2611   field.m_value = toQString(geobFrame->mimeType());
2612   fields.push_back(field);
2613 
2614   field.m_id = Frame::ID_Filename;
2615   field.m_value = toQString(geobFrame->fileName());
2616   fields.push_back(field);
2617 
2618   field.m_id = Frame::ID_Description;
2619   text = toQString(geobFrame->description());
2620   field.m_value = text;
2621   fields.push_back(field);
2622 
2623   field.m_id = Frame::ID_Data;
2624   TagLib::ByteVector obj = geobFrame->object();
2625   QByteArray ba;
2626   ba = QByteArray(obj.data(), obj.size());
2627   field.m_value = ba;
2628   fields.push_back(field);
2629 
2630   return text;
2631 }
2632 
2633 /**
2634  * Get the fields from a URL link frame.
2635  *
2636  * @param wFrame URL link frame
2637  * @param fields the fields are appended to this list
2638  *
2639  * @return text representation of fields (Text or URL).
2640  */
getFieldsFromUrlFrame(const TagLib::ID3v2::UrlLinkFrame * wFrame,Frame::FieldList & fields)2641 QString getFieldsFromUrlFrame(
2642   const TagLib::ID3v2::UrlLinkFrame* wFrame, Frame::FieldList& fields)
2643 {
2644   QString text;
2645   Frame::Field field;
2646   field.m_id = Frame::ID_Url;
2647   text = toQString(wFrame->url());
2648   field.m_value = text;
2649   fields.push_back(field);
2650 
2651   return text;
2652 }
2653 
2654 /**
2655  * Get the fields from a user URL link frame.
2656  *
2657  * @param wxxxFrame user URL link frame
2658  * @param fields the fields are appended to this list
2659  *
2660  * @return text representation of fields (Text or URL).
2661  */
getFieldsFromUserUrlFrame(const TagLib::ID3v2::UserUrlLinkFrame * wxxxFrame,Frame::FieldList & fields)2662 QString getFieldsFromUserUrlFrame(
2663   const TagLib::ID3v2::UserUrlLinkFrame* wxxxFrame, Frame::FieldList& fields)
2664 {
2665   QString text;
2666   Frame::Field field;
2667   field.m_id = Frame::ID_TextEnc;
2668   field.m_value = wxxxFrame->textEncoding();
2669   fields.push_back(field);
2670 
2671   field.m_id = Frame::ID_Description;
2672   field.m_value = toQString(wxxxFrame->description());
2673   fields.push_back(field);
2674 
2675   field.m_id = Frame::ID_Url;
2676   text = toQString(wxxxFrame->url());
2677   field.m_value = text;
2678   fields.push_back(field);
2679 
2680   return text;
2681 }
2682 
2683 /**
2684  * Get the fields from an unsynchronized lyrics frame.
2685  * This is copy-pasted from editCommFrame().
2686  *
2687  * @param usltFrame unsynchronized frame
2688  * @param fields the fields are appended to this list
2689  *
2690  * @return text representation of fields (Text or URL).
2691  */
getFieldsFromUsltFrame(const TagLib::ID3v2::UnsynchronizedLyricsFrame * usltFrame,Frame::FieldList & fields)2692 QString getFieldsFromUsltFrame(
2693   const TagLib::ID3v2::UnsynchronizedLyricsFrame* usltFrame,
2694   Frame::FieldList& fields)
2695 {
2696   QString text;
2697   Frame::Field field;
2698   field.m_id = Frame::ID_TextEnc;
2699   field.m_value = usltFrame->textEncoding();
2700   fields.push_back(field);
2701 
2702   field.m_id = Frame::ID_Language;
2703   TagLib::ByteVector bvLang = usltFrame->language();
2704   field.m_value = QString::fromLatin1(QByteArray(bvLang.data(), bvLang.size()));
2705   fields.push_back(field);
2706 
2707   field.m_id = Frame::ID_Description;
2708   field.m_value = toQString(usltFrame->description());
2709   fields.push_back(field);
2710 
2711   field.m_id = Frame::ID_Text;
2712   text = toQString(usltFrame->toString());
2713   field.m_value = text;
2714   fields.push_back(field);
2715 
2716   return text;
2717 }
2718 
2719 /**
2720  * Get the fields from a synchronized lyrics frame.
2721  *
2722  * @param syltFrame synchronized lyrics frame
2723  * @param fields the fields are appended to this list
2724  *
2725  * @return text representation of fields (Text or URL).
2726  */
getFieldsFromSyltFrame(const TagLib::ID3v2::SynchronizedLyricsFrame * syltFrame,Frame::FieldList & fields)2727 QString getFieldsFromSyltFrame(
2728   const TagLib::ID3v2::SynchronizedLyricsFrame* syltFrame,
2729   Frame::FieldList& fields)
2730 {
2731   QString text;
2732   Frame::Field field;
2733   field.m_id = Frame::ID_TextEnc;
2734   field.m_value = syltFrame->textEncoding();
2735   fields.push_back(field);
2736 
2737   field.m_id = Frame::ID_Language;
2738   TagLib::ByteVector bvLang = syltFrame->language();
2739   field.m_value = QString::fromLatin1(QByteArray(bvLang.data(), bvLang.size()));
2740   fields.push_back(field);
2741 
2742   field.m_id = Frame::ID_TimestampFormat;
2743   field.m_value = syltFrame->timestampFormat();
2744   fields.push_back(field);
2745 
2746   field.m_id = Frame::ID_ContentType;
2747   field.m_value = syltFrame->type();
2748   fields.push_back(field);
2749 
2750   field.m_id = Frame::ID_Description;
2751   text = toQString(syltFrame->description());
2752   field.m_value = text;
2753   fields.push_back(field);
2754 
2755   field.m_id = Frame::ID_Data;
2756   QVariantList synchedData;
2757   const TagLib::ID3v2::SynchronizedLyricsFrame::SynchedTextList stl =
2758       syltFrame->synchedText();
2759   for (auto it = stl.begin(); it != stl.end(); ++it) {
2760     synchedData.append(static_cast<quint32>(it->time));
2761     synchedData.append(toQString(it->text));
2762   }
2763   field.m_value = synchedData;
2764   fields.push_back(field);
2765 
2766   return text;
2767 }
2768 
2769 /**
2770  * Get the fields from an event timing codes frame.
2771  *
2772  * @param etcoFrame event timing codes frame
2773  * @param fields the fields are appended to this list
2774  *
2775  * @return text representation of fields (Text or URL).
2776  */
getFieldsFromEtcoFrame(const TagLib::ID3v2::EventTimingCodesFrame * etcoFrame,Frame::FieldList & fields)2777 QString getFieldsFromEtcoFrame(
2778   const TagLib::ID3v2::EventTimingCodesFrame* etcoFrame,
2779   Frame::FieldList& fields)
2780 {
2781   Frame::Field field;
2782   field.m_id = Frame::ID_TimestampFormat;
2783   field.m_value = etcoFrame->timestampFormat();
2784   fields.push_back(field);
2785 
2786   field.m_id = Frame::ID_Data;
2787   QVariantList synchedData;
2788   const TagLib::ID3v2::EventTimingCodesFrame::SynchedEventList sel =
2789       etcoFrame->synchedEvents();
2790   for (auto it = sel.begin(); it != sel.end(); ++it) {
2791     synchedData.append(static_cast<quint32>(it->time));
2792     synchedData.append(static_cast<int>(it->type));
2793   }
2794   field.m_value = synchedData;
2795   fields.push_back(field);
2796 
2797   return QString();
2798 }
2799 
2800 /**
2801  * Get the fields from a private frame.
2802  *
2803  * @param privFrame private frame
2804  * @param fields the fields are appended to this list
2805  *
2806  * @return text representation of fields (Text or URL).
2807  */
getFieldsFromPrivFrame(const TagLib::ID3v2::PrivateFrame * privFrame,Frame::FieldList & fields)2808 QString getFieldsFromPrivFrame(
2809   const TagLib::ID3v2::PrivateFrame* privFrame,
2810   Frame::FieldList& fields)
2811 {
2812   QString owner;
2813   Frame::Field field;
2814   field.m_id = Frame::ID_Owner;
2815   owner = toQString(privFrame->owner());
2816   field.m_value = owner;
2817   fields.push_back(field);
2818 
2819   field.m_id = Frame::ID_Data;
2820   TagLib::ByteVector data = privFrame->data();
2821   QByteArray ba;
2822   ba = QByteArray(data.data(), data.size());
2823   field.m_value = ba;
2824   fields.push_back(field);
2825 
2826   if (!owner.isEmpty() && !ba.isEmpty()) {
2827     QString str;
2828     if (AttributeData(owner).toString(ba, str)) {
2829       return str;
2830     }
2831   }
2832   return QString();
2833 }
2834 
2835 /**
2836  * Get the fields from a popularimeter frame.
2837  *
2838  * @param popmFrame popularimeter frame
2839  * @param fields the fields are appended to this list
2840  *
2841  * @return text representation of fields (Text or URL).
2842  */
getFieldsFromPopmFrame(const TagLib::ID3v2::PopularimeterFrame * popmFrame,Frame::FieldList & fields)2843 QString getFieldsFromPopmFrame(
2844   const TagLib::ID3v2::PopularimeterFrame* popmFrame,
2845   Frame::FieldList& fields)
2846 {
2847   Frame::Field field;
2848   field.m_id = Frame::ID_Email;
2849   field.m_value = toQString(popmFrame->email());
2850   fields.push_back(field);
2851 
2852   field.m_id = Frame::ID_Rating;
2853   field.m_value = popmFrame->rating();
2854   QString text(field.m_value.toString());
2855   fields.push_back(field);
2856 
2857   field.m_id = Frame::ID_Counter;
2858   field.m_value = popmFrame->counter();
2859   fields.push_back(field);
2860 
2861   return text;
2862 }
2863 
2864 /**
2865  * Get the fields from an ownership frame.
2866  *
2867  * @param owneFrame ownership frame
2868  * @param fields the fields are appended to this list
2869  *
2870  * @return text representation of fields (Text or URL).
2871  */
getFieldsFromOwneFrame(const TagLib::ID3v2::OwnershipFrame * owneFrame,Frame::FieldList & fields)2872 QString getFieldsFromOwneFrame(
2873   const TagLib::ID3v2::OwnershipFrame* owneFrame,
2874   Frame::FieldList& fields)
2875 {
2876   Frame::Field field;
2877   field.m_id = Frame::ID_TextEnc;
2878   field.m_value = owneFrame->textEncoding();
2879   fields.push_back(field);
2880 
2881   field.m_id = Frame::ID_Date;
2882   field.m_value = toQString(owneFrame->datePurchased());
2883   fields.push_back(field);
2884 
2885   field.m_id = Frame::ID_Price;
2886   field.m_value = toQString(owneFrame->pricePaid());
2887   fields.push_back(field);
2888 
2889   field.m_id = Frame::ID_Seller;
2890   QString text(toQString(owneFrame->seller()));
2891   field.m_value = text;
2892   fields.push_back(field);
2893 
2894   return text;
2895 }
2896 
2897 /**
2898  * Get a string representation of the data in an RVA2 frame.
2899  * @param rva2Frame RVA2 frame
2900  * @return string containing lines with space separated values for
2901  * type of channel, volume adjustment, bits representing peak,
2902  * peak volume. The peak volume is a hex byte array, the other values
2903  * are integers, the volume adjustment is signed. Bits representing peak
2904  * and peak volume are omitted if they have zero bits.
2905  */
rva2FrameToString(const TagLib::ID3v2::RelativeVolumeFrame * rva2Frame)2906 QString rva2FrameToString(
2907     const TagLib::ID3v2::RelativeVolumeFrame* rva2Frame)
2908 {
2909   QString text;
2910   const TagLib::List<TagLib::ID3v2::RelativeVolumeFrame::ChannelType> channels =
2911       rva2Frame->channels();
2912   for (auto it = channels.begin(); it != channels.end(); ++it) {
2913     TagLib::ID3v2::RelativeVolumeFrame::ChannelType type = *it;
2914     if (!text.isEmpty()) {
2915       text += QLatin1Char('\n');
2916     }
2917     short adj = rva2Frame->volumeAdjustmentIndex(type);
2918     TagLib::ID3v2::RelativeVolumeFrame::PeakVolume peak =
2919         rva2Frame->peakVolume(type);
2920     text += QString::number(type);
2921     text += QLatin1Char(' ');
2922     text += QString::number(adj);
2923     if (peak.bitsRepresentingPeak > 0) {
2924       text += QLatin1Char(' ');
2925       text += QString::number(peak.bitsRepresentingPeak);
2926       text += QLatin1Char(' ');
2927       text += QString::fromLatin1(
2928             QByteArray(peak.peakVolume.data(), peak.peakVolume.size()).toHex());
2929     }
2930   }
2931   return text;
2932 }
2933 
2934 /**
2935  * Set the data in an RVA2 frame from a string representation.
2936  * @param rva2Frame RVA2 frame to set
2937  * @param text string representation
2938  * @see rva2FrameToString()
2939  */
rva2FrameFromString(TagLib::ID3v2::RelativeVolumeFrame * rva2Frame,const TagLib::String & text)2940 void rva2FrameFromString(TagLib::ID3v2::RelativeVolumeFrame* rva2Frame,
2941                          const TagLib::String& text)
2942 {
2943   // Unfortunately, it is not possible to remove data for a specific channel.
2944   // Only the whole frame could be deleted and a new one created.
2945   const auto lines = toQString(text).split(QLatin1Char('\n'));
2946   for (const QString& line : lines) {
2947     QStringList strs = line.split(QLatin1Char(' '));
2948     if (strs.size() > 1) {
2949       bool ok;
2950       int typeInt = strs.at(0).toInt(&ok);
2951       if (ok && typeInt >= 0 && typeInt <= 8) {
2952         short adj = strs.at(1).toShort(&ok);
2953         if (ok) {
2954           auto type =
2955               static_cast<TagLib::ID3v2::RelativeVolumeFrame::ChannelType>(
2956                 typeInt);
2957           rva2Frame->setVolumeAdjustmentIndex(adj, type);
2958           TagLib::ID3v2::RelativeVolumeFrame::PeakVolume peak;
2959           if (strs.size() > 3) {
2960             int bitsInt = strs.at(2).toInt(&ok);
2961             QByteArray ba = QByteArray::fromHex(strs.at(3).toLatin1());
2962             if (ok && bitsInt > 0 && bitsInt <= 255 &&
2963                 bitsInt <= ba.size() * 8) {
2964               peak.bitsRepresentingPeak = bitsInt;
2965               peak.peakVolume.setData(ba.constData(), ba.size());
2966               rva2Frame->setPeakVolume(peak, type);
2967             }
2968           }
2969         }
2970       }
2971     }
2972   }
2973 }
2974 
2975 /**
2976  * Get the fields from a relative volume frame.
2977  *
2978  * @param rva2Frame relative volume frame
2979  * @param fields the fields are appended to this list
2980  *
2981  * @return text representation of fields (Text or URL).
2982  */
getFieldsFromRva2Frame(const TagLib::ID3v2::RelativeVolumeFrame * rva2Frame,Frame::FieldList & fields)2983 QString getFieldsFromRva2Frame(
2984   const TagLib::ID3v2::RelativeVolumeFrame* rva2Frame,
2985   Frame::FieldList& fields)
2986 {
2987   Frame::Field field;
2988   field.m_id = Frame::ID_Id;
2989   field.m_value = toQString(rva2Frame->identification());
2990   fields.push_back(field);
2991 
2992   QString text = rva2FrameToString(rva2Frame);
2993   field.m_id = Frame::ID_Text;
2994   field.m_value = text;
2995   fields.push_back(field);
2996   return text;
2997 }
2998 
2999 #if TAGLIB_VERSION >= 0x010a00
3000 Frame createFrameFromId3Frame(const TagLib::ID3v2::Frame* id3Frame, int index);
3001 
3002 /**
3003  * Get the fields from a chapter frame.
3004  *
3005  * @param chapFrame chapter frame
3006  * @param fields the fields are appended to this list
3007  *
3008  * @return text representation of fields (Text or URL).
3009  */
getFieldsFromChapFrame(const TagLib::ID3v2::ChapterFrame * chapFrame,Frame::FieldList & fields)3010 QString getFieldsFromChapFrame(
3011   const TagLib::ID3v2::ChapterFrame* chapFrame,
3012   Frame::FieldList& fields)
3013 {
3014   Frame::Field field;
3015   field.m_id = Frame::ID_Id;
3016   QString text = toQString(
3017         TagLib::String(chapFrame->elementID(), TagLib::String::Latin1));
3018   field.m_value = text;
3019   fields.push_back(field);
3020 
3021   field.m_id = Frame::ID_Data;
3022   QVariantList data;
3023   data.append(chapFrame->startTime());
3024   data.append(chapFrame->endTime());
3025   data.append(chapFrame->startOffset());
3026   data.append(chapFrame->endOffset());
3027   field.m_value = data;
3028   fields.push_back(field);
3029 
3030   field.m_id = Frame::ID_Subframe;
3031   const TagLib::ID3v2::FrameList& frameList = chapFrame->embeddedFrameList();
3032   for (auto it = frameList.begin();
3033        it != frameList.end();
3034        ++it) {
3035     Frame frame(createFrameFromId3Frame(*it, -1));
3036     field.m_value = frame.getExtendedType().getName();
3037     fields.push_back(field);
3038     fields.append(frame.getFieldList());
3039   }
3040 
3041   return text;
3042 }
3043 
3044 /**
3045  * Get the fields from a table of contents frame.
3046  *
3047  * @param ctocFrame table of contents frame
3048  * @param fields the fields are appended to this list
3049  *
3050  * @return text representation of fields (Text or URL).
3051  */
getFieldsFromCtocFrame(const TagLib::ID3v2::TableOfContentsFrame * ctocFrame,Frame::FieldList & fields)3052 QString getFieldsFromCtocFrame(
3053   const TagLib::ID3v2::TableOfContentsFrame* ctocFrame,
3054   Frame::FieldList& fields)
3055 {
3056   Frame::Field field;
3057   field.m_id = Frame::ID_Id;
3058   QString text = toQString(
3059         TagLib::String(ctocFrame->elementID(), TagLib::String::Latin1));
3060   field.m_value = text;
3061   fields.push_back(field);
3062 
3063   field.m_id = Frame::ID_Data;
3064   QVariantList data;
3065   data.append(ctocFrame->isTopLevel());
3066   data.append(ctocFrame->isOrdered());
3067   QStringList elements;
3068   const TagLib::ByteVectorList childElements = ctocFrame->childElements();
3069   for (auto it = childElements.begin(); it != childElements.end(); ++it) {
3070     elements.append(toQString(TagLib::String(*it, TagLib::String::Latin1)));
3071   }
3072   data.append(elements);
3073   field.m_value = data;
3074   fields.push_back(field);
3075 
3076   field.m_id = Frame::ID_Subframe;
3077   const TagLib::ID3v2::FrameList& frameList = ctocFrame->embeddedFrameList();
3078   for (auto it = frameList.begin();
3079        it != frameList.end();
3080        ++it) {
3081     Frame frame(createFrameFromId3Frame(*it, -1));
3082     field.m_value = frame.getExtendedType().getName();
3083     fields.push_back(field);
3084     fields.append(frame.getFieldList());
3085   }
3086 
3087   return text;
3088 }
3089 #endif
3090 
3091 /**
3092  * Get the fields from an unknown frame.
3093  *
3094  * @param unknownFrame unknown frame
3095  * @param fields the fields are appended to this list
3096  *
3097  * @return text representation of fields (Text or URL).
3098  */
getFieldsFromUnknownFrame(const TagLib::ID3v2::Frame * unknownFrame,Frame::FieldList & fields)3099 QString getFieldsFromUnknownFrame(
3100   const TagLib::ID3v2::Frame* unknownFrame, Frame::FieldList& fields)
3101 {
3102   Frame::Field field;
3103   field.m_id = Frame::ID_Data;
3104   TagLib::ByteVector dat = unknownFrame->render();
3105   QByteArray ba;
3106   ba = QByteArray(dat.data(), dat.size());
3107   field.m_value = ba;
3108   fields.push_back(field);
3109   return QString();
3110 }
3111 
3112 /**
3113  * Get the fields from an ID3v2 tag.
3114  *
3115  * @param frame  frame
3116  * @param fields the fields are appended to this list
3117  * @param type   frame type
3118  *
3119  * @return text representation of fields (Text or URL).
3120  */
getFieldsFromId3Frame(const TagLib::ID3v2::Frame * frame,Frame::FieldList & fields,Frame::Type type)3121 QString getFieldsFromId3Frame(const TagLib::ID3v2::Frame* frame,
3122                               Frame::FieldList& fields, Frame::Type type)
3123 {
3124   if (frame) {
3125     const TagLib::ID3v2::TextIdentificationFrame* tFrame;
3126     const TagLib::ID3v2::AttachedPictureFrame* apicFrame;
3127     const TagLib::ID3v2::CommentsFrame* commFrame;
3128     const TagLib::ID3v2::UniqueFileIdentifierFrame* ufidFrame;
3129     const TagLib::ID3v2::GeneralEncapsulatedObjectFrame* geobFrame;
3130     const TagLib::ID3v2::UserUrlLinkFrame* wxxxFrame;
3131     const TagLib::ID3v2::UrlLinkFrame* wFrame;
3132     const TagLib::ID3v2::UnsynchronizedLyricsFrame* usltFrame;
3133     const TagLib::ID3v2::SynchronizedLyricsFrame* syltFrame;
3134     const TagLib::ID3v2::EventTimingCodesFrame* etcoFrame;
3135     const TagLib::ID3v2::PrivateFrame* privFrame;
3136     const TagLib::ID3v2::PopularimeterFrame* popmFrame;
3137     const TagLib::ID3v2::OwnershipFrame* owneFrame;
3138     const TagLib::ID3v2::RelativeVolumeFrame* rva2Frame;
3139 #if TAGLIB_VERSION >= 0x010a00
3140     const TagLib::ID3v2::ChapterFrame* chapFrame;
3141     const TagLib::ID3v2::TableOfContentsFrame* ctocFrame;
3142 #endif
3143     if ((tFrame =
3144          dynamic_cast<const TagLib::ID3v2::TextIdentificationFrame*>(frame)) !=
3145         nullptr) {
3146       return getFieldsFromTextFrame(tFrame, fields, type);
3147     } else if ((apicFrame =
3148                 dynamic_cast<const TagLib::ID3v2::AttachedPictureFrame*>(frame))
3149                != nullptr) {
3150       return getFieldsFromApicFrame(apicFrame, fields);
3151     } else if ((commFrame = dynamic_cast<const TagLib::ID3v2::CommentsFrame*>(
3152                   frame)) != nullptr) {
3153       return getFieldsFromCommFrame(commFrame, fields);
3154     } else if ((ufidFrame =
3155                 dynamic_cast<const TagLib::ID3v2::UniqueFileIdentifierFrame*>(
3156                   frame)) != nullptr) {
3157       return getFieldsFromUfidFrame(ufidFrame, fields);
3158     } else if ((geobFrame =
3159                 dynamic_cast<const TagLib::ID3v2::GeneralEncapsulatedObjectFrame*>(
3160                   frame)) != nullptr) {
3161       return getFieldsFromGeobFrame(geobFrame, fields);
3162     } else if ((wxxxFrame = dynamic_cast<const TagLib::ID3v2::UserUrlLinkFrame*>(
3163                   frame)) != nullptr) {
3164       return getFieldsFromUserUrlFrame(wxxxFrame, fields);
3165     } else if ((wFrame = dynamic_cast<const TagLib::ID3v2::UrlLinkFrame*>(
3166                   frame)) != nullptr) {
3167       return getFieldsFromUrlFrame(wFrame, fields);
3168     } else if ((usltFrame = dynamic_cast<const TagLib::ID3v2::UnsynchronizedLyricsFrame*>(
3169                 frame)) != nullptr) {
3170       return getFieldsFromUsltFrame(usltFrame, fields);
3171     } else if ((syltFrame = dynamic_cast<const TagLib::ID3v2::SynchronizedLyricsFrame*>(
3172                 frame)) != nullptr) {
3173       return getFieldsFromSyltFrame(syltFrame, fields);
3174     } else if ((etcoFrame = dynamic_cast<const TagLib::ID3v2::EventTimingCodesFrame*>(
3175                 frame)) != nullptr) {
3176       return getFieldsFromEtcoFrame(etcoFrame, fields);
3177     } else if ((privFrame = dynamic_cast<const TagLib::ID3v2::PrivateFrame*>(
3178                 frame)) != nullptr) {
3179       return getFieldsFromPrivFrame(privFrame, fields);
3180     } else if ((popmFrame = dynamic_cast<const TagLib::ID3v2::PopularimeterFrame*>(
3181                 frame)) != nullptr) {
3182       return getFieldsFromPopmFrame(popmFrame, fields);
3183     } else if ((owneFrame = dynamic_cast<const TagLib::ID3v2::OwnershipFrame*>(
3184                 frame)) != nullptr) {
3185       return getFieldsFromOwneFrame(owneFrame, fields);
3186     } else if ((rva2Frame = dynamic_cast<const TagLib::ID3v2::RelativeVolumeFrame*>(
3187                 frame)) != nullptr) {
3188       return getFieldsFromRva2Frame(rva2Frame, fields);
3189 #if TAGLIB_VERSION >= 0x010a00
3190     } else if ((chapFrame = dynamic_cast<const TagLib::ID3v2::ChapterFrame*>(
3191                 frame)) != nullptr) {
3192       return getFieldsFromChapFrame(chapFrame, fields);
3193     } else if ((ctocFrame = dynamic_cast<const TagLib::ID3v2::TableOfContentsFrame*>(
3194                 frame)) != nullptr) {
3195       return getFieldsFromCtocFrame(ctocFrame, fields);
3196 #endif
3197     } else {
3198       TagLib::ByteVector id = frame->frameID();
3199 #if TAGLIB_VERSION < 0x010a00
3200       if (id.startsWith("SYLT")) {
3201         TagLib::ID3v2::SynchronizedLyricsFrame syltFrm(frame->render());
3202         return getFieldsFromSyltFrame(&syltFrm, fields);
3203       } else if (id.startsWith("ETCO")) {
3204         TagLib::ID3v2::EventTimingCodesFrame etcoFrm(frame->render());
3205         return getFieldsFromEtcoFrame(&etcoFrm, fields);
3206       } else
3207 #endif
3208         return getFieldsFromUnknownFrame(frame, fields);
3209     }
3210   }
3211   return QString();
3212 }
3213 
3214 /**
3215  * Convert a string to a language code byte vector.
3216  *
3217  * @param str string containing language code.
3218  *
3219  * @return 3 byte vector with language code.
3220  */
languageCodeByteVector(QString str)3221 TagLib::ByteVector languageCodeByteVector(QString str)
3222 {
3223   uint len = str.length();
3224   if (len > 3) {
3225     str.truncate(3);
3226   } else if (len < 3) {
3227     for (uint i = len; i < 3; ++i) {
3228       str += QLatin1Char(' ');
3229     }
3230   }
3231   return TagLib::ByteVector(str.toLatin1().data(), str.length());
3232 }
3233 
3234 /**
3235  * The following template functions are used to uniformly set fields
3236  * in the different ID3v2 frames.
3237  */
3238 //! @cond
3239 template <class T>
setTextEncoding(T *,TagLib::String::Type)3240 void setTextEncoding(T*, TagLib::String::Type) {}
3241 
3242 template <>
setTextEncoding(TagLib::ID3v2::TextIdentificationFrame * f,TagLib::String::Type enc)3243 void setTextEncoding(TagLib::ID3v2::TextIdentificationFrame* f,
3244                      TagLib::String::Type enc)
3245 {
3246   f->setTextEncoding(enc);
3247 }
3248 
3249 template <>
setTextEncoding(TagLib::ID3v2::AttachedPictureFrame * f,TagLib::String::Type enc)3250 void setTextEncoding(TagLib::ID3v2::AttachedPictureFrame* f,
3251                      TagLib::String::Type enc)
3252 {
3253   f->setTextEncoding(enc);
3254 }
3255 
3256 template <>
setTextEncoding(TagLib::ID3v2::CommentsFrame * f,TagLib::String::Type enc)3257 void setTextEncoding(TagLib::ID3v2::CommentsFrame* f,
3258                      TagLib::String::Type enc)
3259 {
3260   f->setTextEncoding(enc);
3261 }
3262 
3263 template <>
setTextEncoding(TagLib::ID3v2::GeneralEncapsulatedObjectFrame * f,TagLib::String::Type enc)3264 void setTextEncoding(TagLib::ID3v2::GeneralEncapsulatedObjectFrame* f,
3265                      TagLib::String::Type enc)
3266 {
3267   f->setTextEncoding(enc);
3268 }
3269 
3270 template <>
setTextEncoding(TagLib::ID3v2::UserUrlLinkFrame * f,TagLib::String::Type enc)3271 void setTextEncoding(TagLib::ID3v2::UserUrlLinkFrame* f,
3272                      TagLib::String::Type enc)
3273 {
3274   f->setTextEncoding(enc);
3275 }
3276 
3277 template <>
setTextEncoding(TagLib::ID3v2::UnsynchronizedLyricsFrame * f,TagLib::String::Type enc)3278 void setTextEncoding(TagLib::ID3v2::UnsynchronizedLyricsFrame* f,
3279                      TagLib::String::Type enc)
3280 {
3281   f->setTextEncoding(enc);
3282 }
3283 
3284 template <>
setTextEncoding(TagLib::ID3v2::SynchronizedLyricsFrame * f,TagLib::String::Type enc)3285 void setTextEncoding(TagLib::ID3v2::SynchronizedLyricsFrame* f,
3286                      TagLib::String::Type enc)
3287 {
3288   f->setTextEncoding(enc);
3289 }
3290 
3291 
3292 template <class T>
setDescription(T *,const Frame::Field &)3293 void setDescription(T*, const Frame::Field&) {}
3294 
3295 template <>
setDescription(TagLib::ID3v2::UserTextIdentificationFrame * f,const Frame::Field & fld)3296 void setDescription(TagLib::ID3v2::UserTextIdentificationFrame* f,
3297                     const Frame::Field& fld)
3298 {
3299   f->setDescription(toTString(fld.m_value.toString()));
3300 }
3301 
3302 template <>
setDescription(TagLib::ID3v2::AttachedPictureFrame * f,const Frame::Field & fld)3303 void setDescription(TagLib::ID3v2::AttachedPictureFrame* f,
3304                     const Frame::Field& fld)
3305 {
3306   f->setDescription(toTString(fld.m_value.toString()));
3307 }
3308 
3309 template <>
setDescription(TagLib::ID3v2::CommentsFrame * f,const Frame::Field & fld)3310 void setDescription(TagLib::ID3v2::CommentsFrame* f, const Frame::Field& fld)
3311 {
3312   f->setDescription(toTString(fld.m_value.toString()));
3313 }
3314 
3315 template <>
setDescription(TagLib::ID3v2::GeneralEncapsulatedObjectFrame * f,const Frame::Field & fld)3316 void setDescription(TagLib::ID3v2::GeneralEncapsulatedObjectFrame* f,
3317                     const Frame::Field& fld)
3318 {
3319   f->setDescription(toTString(fld.m_value.toString()));
3320 }
3321 
3322 template <>
setDescription(TagLib::ID3v2::UserUrlLinkFrame * f,const Frame::Field & fld)3323 void setDescription(TagLib::ID3v2::UserUrlLinkFrame* f, const Frame::Field& fld)
3324 {
3325   f->setDescription(toTString(fld.m_value.toString()));
3326 }
3327 
3328 template <>
setDescription(TagLib::ID3v2::UnsynchronizedLyricsFrame * f,const Frame::Field & fld)3329 void setDescription(TagLib::ID3v2::UnsynchronizedLyricsFrame* f,
3330                     const Frame::Field& fld)
3331 {
3332   f->setDescription(toTString(fld.m_value.toString()));
3333 }
3334 
3335 template <>
setDescription(TagLib::ID3v2::SynchronizedLyricsFrame * f,const Frame::Field & fld)3336 void setDescription(TagLib::ID3v2::SynchronizedLyricsFrame* f,
3337                     const Frame::Field& fld)
3338 {
3339   f->setDescription(toTString(fld.m_value.toString()));
3340 }
3341 
3342 template <class T>
setMimeType(T *,const Frame::Field &)3343 void setMimeType(T*, const Frame::Field&) {}
3344 
3345 template <>
setMimeType(TagLib::ID3v2::AttachedPictureFrame * f,const Frame::Field & fld)3346 void setMimeType(TagLib::ID3v2::AttachedPictureFrame* f,
3347                  const Frame::Field& fld)
3348 {
3349   f->setMimeType(toTString(fld.m_value.toString()));
3350 }
3351 
3352 template <>
setMimeType(TagLib::ID3v2::GeneralEncapsulatedObjectFrame * f,const Frame::Field & fld)3353 void setMimeType(TagLib::ID3v2::GeneralEncapsulatedObjectFrame* f,
3354                  const Frame::Field& fld)
3355 {
3356   f->setMimeType(toTString(fld.m_value.toString()));
3357 }
3358 
3359 template <class T>
setPictureType(T *,const Frame::Field &)3360 void setPictureType(T*, const Frame::Field&) {}
3361 
3362 template <>
setPictureType(TagLib::ID3v2::AttachedPictureFrame * f,const Frame::Field & fld)3363 void setPictureType(TagLib::ID3v2::AttachedPictureFrame* f,
3364                     const Frame::Field& fld)
3365 {
3366   f->setType(
3367     static_cast<TagLib::ID3v2::AttachedPictureFrame::Type>(
3368       fld.m_value.toInt()));
3369 }
3370 
3371 template <class T>
setData(T *,const Frame::Field &)3372 void setData(T*, const Frame::Field&) {}
3373 
3374 template <>
setData(TagLib::ID3v2::Frame * f,const Frame::Field & fld)3375 void setData(TagLib::ID3v2::Frame* f, const Frame::Field& fld)
3376 {
3377   QByteArray ba(fld.m_value.toByteArray());
3378   f->setData(TagLib::ByteVector(ba.data(), ba.size()));
3379 }
3380 
3381 template <>
setData(TagLib::ID3v2::AttachedPictureFrame * f,const Frame::Field & fld)3382 void setData(TagLib::ID3v2::AttachedPictureFrame* f, const Frame::Field& fld)
3383 {
3384   QByteArray ba(fld.m_value.toByteArray());
3385   f->setPicture(TagLib::ByteVector(ba.data(), ba.size()));
3386 }
3387 
3388 template <>
setData(TagLib::ID3v2::GeneralEncapsulatedObjectFrame * f,const Frame::Field & fld)3389 void setData(TagLib::ID3v2::GeneralEncapsulatedObjectFrame* f,
3390              const Frame::Field& fld)
3391 {
3392   QByteArray ba(fld.m_value.toByteArray());
3393   f->setObject(TagLib::ByteVector(ba.data(), ba.size()));
3394 }
3395 
3396 template <>
setData(TagLib::ID3v2::UniqueFileIdentifierFrame * f,const Frame::Field & fld)3397 void setData(TagLib::ID3v2::UniqueFileIdentifierFrame* f,
3398              const Frame::Field& fld)
3399 {
3400   QByteArray ba(fld.m_value.toByteArray());
3401   f->setIdentifier(TagLib::ByteVector(ba.data(), ba.size()));
3402 }
3403 
3404 template <>
setData(TagLib::ID3v2::SynchronizedLyricsFrame * f,const Frame::Field & fld)3405 void setData(TagLib::ID3v2::SynchronizedLyricsFrame* f,
3406              const Frame::Field& fld)
3407 {
3408   TagLib::ID3v2::SynchronizedLyricsFrame::SynchedTextList stl;
3409   QVariantList synchedData(fld.m_value.toList());
3410   QListIterator<QVariant> it(synchedData);
3411   while (it.hasNext()) {
3412     quint32 time = it.next().toUInt();
3413     if (!it.hasNext())
3414       break;
3415 
3416     TagLib::String text = toTString(it.next().toString());
3417     stl.append(TagLib::ID3v2::SynchronizedLyricsFrame::SynchedText(time, text));
3418   }
3419   f->setSynchedText(stl);
3420 }
3421 
3422 template <>
setData(TagLib::ID3v2::EventTimingCodesFrame * f,const Frame::Field & fld)3423 void setData(TagLib::ID3v2::EventTimingCodesFrame* f,
3424              const Frame::Field& fld)
3425 {
3426   TagLib::ID3v2::EventTimingCodesFrame::SynchedEventList sel;
3427   QVariantList synchedData(fld.m_value.toList());
3428   QListIterator<QVariant> it(synchedData);
3429   while (it.hasNext()) {
3430     quint32 time = it.next().toUInt();
3431     if (!it.hasNext())
3432       break;
3433 
3434     auto type =
3435         static_cast<TagLib::ID3v2::EventTimingCodesFrame::EventType>(
3436           it.next().toInt());
3437     sel.append(TagLib::ID3v2::EventTimingCodesFrame::SynchedEvent(time, type));
3438   }
3439   f->setSynchedEvents(sel);
3440 }
3441 
3442 template <class T>
setLanguage(T *,const Frame::Field &)3443 void setLanguage(T*, const Frame::Field&) {}
3444 
3445 template <>
setLanguage(TagLib::ID3v2::CommentsFrame * f,const Frame::Field & fld)3446 void setLanguage(TagLib::ID3v2::CommentsFrame* f, const Frame::Field& fld)
3447 {
3448   f->setLanguage(languageCodeByteVector(fld.m_value.toString()));
3449 }
3450 
3451 template <>
setLanguage(TagLib::ID3v2::UnsynchronizedLyricsFrame * f,const Frame::Field & fld)3452 void setLanguage(TagLib::ID3v2::UnsynchronizedLyricsFrame* f,
3453                  const Frame::Field& fld)
3454 {
3455   f->setLanguage(languageCodeByteVector(fld.m_value.toString()));
3456 }
3457 
3458 template <>
setLanguage(TagLib::ID3v2::SynchronizedLyricsFrame * f,const Frame::Field & fld)3459 void setLanguage(TagLib::ID3v2::SynchronizedLyricsFrame* f,
3460                  const Frame::Field& fld)
3461 {
3462   f->setLanguage(languageCodeByteVector(fld.m_value.toString()));
3463 }
3464 
3465 template <class T>
setOwner(T *,const Frame::Field &)3466 void setOwner(T*, const Frame::Field&) {}
3467 
3468 template <>
setOwner(TagLib::ID3v2::UniqueFileIdentifierFrame * f,const Frame::Field & fld)3469 void setOwner(TagLib::ID3v2::UniqueFileIdentifierFrame* f,
3470               const Frame::Field& fld)
3471 {
3472   f->setOwner(toTString(fld.m_value.toString()));
3473 }
3474 
3475 template <>
setOwner(TagLib::ID3v2::PrivateFrame * f,const Frame::Field & fld)3476 void setOwner(TagLib::ID3v2::PrivateFrame* f,
3477               const Frame::Field& fld)
3478 {
3479   f->setOwner(toTString(fld.m_value.toString()));
3480 }
3481 
3482 template <>
setData(TagLib::ID3v2::PrivateFrame * f,const Frame::Field & fld)3483 void setData(TagLib::ID3v2::PrivateFrame* f,
3484              const Frame::Field& fld)
3485 {
3486   QByteArray ba(fld.m_value.toByteArray());
3487   f->setData(TagLib::ByteVector(ba.data(), ba.size()));
3488 }
3489 
3490 template <class T>
setIdentifier(T *,const Frame::Field &)3491 void setIdentifier(T*, const Frame::Field&) {}
3492 
3493 template <>
setIdentifier(TagLib::ID3v2::UniqueFileIdentifierFrame * f,const Frame::Field & fld)3494 void setIdentifier(TagLib::ID3v2::UniqueFileIdentifierFrame* f,
3495                    const Frame::Field& fld)
3496 {
3497   QByteArray ba(fld.m_value.toByteArray());
3498   f->setIdentifier(TagLib::ByteVector(ba.data(), ba.size()));
3499 }
3500 
3501 template <class T>
setFilename(T *,const Frame::Field &)3502 void setFilename(T*, const Frame::Field&) {}
3503 
3504 template <>
setFilename(TagLib::ID3v2::GeneralEncapsulatedObjectFrame * f,const Frame::Field & fld)3505 void setFilename(TagLib::ID3v2::GeneralEncapsulatedObjectFrame* f,
3506                  const Frame::Field& fld)
3507 {
3508   f->setFileName(toTString(fld.m_value.toString()));
3509 }
3510 
3511 template <class T>
setUrl(T *,const Frame::Field &)3512 void setUrl(T*, const Frame::Field&) {}
3513 
3514 template <>
setUrl(TagLib::ID3v2::UrlLinkFrame * f,const Frame::Field & fld)3515 void setUrl(TagLib::ID3v2::UrlLinkFrame* f, const Frame::Field& fld)
3516 {
3517   f->setUrl(toTString(fld.m_value.toString()));
3518 }
3519 
3520 template <>
setUrl(TagLib::ID3v2::UserUrlLinkFrame * f,const Frame::Field & fld)3521 void setUrl(TagLib::ID3v2::UserUrlLinkFrame* f, const Frame::Field& fld)
3522 {
3523   f->setUrl(toTString(fld.m_value.toString()));
3524 }
3525 
3526 template <class T>
setValue(T * f,const TagLib::String & text)3527 void setValue(T* f, const TagLib::String& text)
3528 {
3529   f->setText(text);
3530 }
3531 
3532 template <>
setValue(TagLib::ID3v2::AttachedPictureFrame * f,const TagLib::String & text)3533 void setValue(TagLib::ID3v2::AttachedPictureFrame* f, const TagLib::String& text)
3534 {
3535   f->setDescription(text);
3536 }
3537 
3538 template <>
setValue(TagLib::ID3v2::GeneralEncapsulatedObjectFrame * f,const TagLib::String & text)3539 void setValue(TagLib::ID3v2::GeneralEncapsulatedObjectFrame* f,
3540               const TagLib::String& text)
3541 {
3542   f->setDescription(text);
3543 }
3544 
setStringOrList(TagLib::ID3v2::TextIdentificationFrame * f,const TagLib::String & text)3545 void setStringOrList(TagLib::ID3v2::TextIdentificationFrame* f,
3546                      const TagLib::String& text)
3547 {
3548   if (text.find(Frame::stringListSeparator().toLatin1()) == -1) {
3549     f->setText(text);
3550   } else {
3551     f->setText(TagLib::StringList::split(text, Frame::stringListSeparator().toLatin1()));
3552   }
3553 }
3554 
3555 template <>
setValue(TagLib::ID3v2::TextIdentificationFrame * f,const TagLib::String & text)3556 void setValue(TagLib::ID3v2::TextIdentificationFrame* f, const TagLib::String& text)
3557 {
3558   setStringOrList(f, text);
3559 }
3560 
3561 template <>
setValue(TagLib::ID3v2::UniqueFileIdentifierFrame * f,const TagLib::String & text)3562 void setValue(TagLib::ID3v2::UniqueFileIdentifierFrame* f, const TagLib::String& text)
3563 {
3564   if (AttributeData::isHexString(toQString(text), 'Z', QLatin1String("-"))) {
3565     TagLib::ByteVector data(text.data(TagLib::String::Latin1));
3566     data.append('\0');
3567     f->setIdentifier(data);
3568   }
3569 }
3570 
3571 template <>
setValue(TagLib::ID3v2::SynchronizedLyricsFrame * f,const TagLib::String & text)3572 void setValue(TagLib::ID3v2::SynchronizedLyricsFrame* f, const TagLib::String& text)
3573 {
3574   f->setDescription(text);
3575 }
3576 
3577 template <>
setValue(TagLib::ID3v2::PrivateFrame * f,const TagLib::String & text)3578 void setValue(TagLib::ID3v2::PrivateFrame* f, const TagLib::String& text)
3579 {
3580   QByteArray newData;
3581   TagLib::String owner = f->owner();
3582   if (!owner.isEmpty() &&
3583       AttributeData(toQString(owner))
3584       .toByteArray(toQString(text), newData)) {
3585     f->setData(TagLib::ByteVector(newData.data(), newData.size()));
3586   }
3587 }
3588 
3589 template <>
setValue(TagLib::ID3v2::PopularimeterFrame * f,const TagLib::String & text)3590 void setValue(TagLib::ID3v2::PopularimeterFrame* f, const TagLib::String& text)
3591 {
3592   f->setRating(text.toInt());
3593 }
3594 
3595 template <class T>
setText(T * f,const TagLib::String & text)3596 void setText(T* f, const TagLib::String& text)
3597 {
3598   f->setText(text);
3599 }
3600 
3601 template <>
setText(TagLib::ID3v2::TextIdentificationFrame * f,const TagLib::String & text)3602 void setText(TagLib::ID3v2::TextIdentificationFrame* f, const TagLib::String& text)
3603 {
3604   setStringOrList(f, text);
3605 }
3606 
3607 template <class T>
setEmail(T *,const Frame::Field &)3608 void setEmail(T*, const Frame::Field&) {}
3609 
3610 template <>
setEmail(TagLib::ID3v2::PopularimeterFrame * f,const Frame::Field & fld)3611 void setEmail(TagLib::ID3v2::PopularimeterFrame* f, const Frame::Field& fld)
3612 {
3613   f->setEmail(toTString(fld.m_value.toString()));
3614 }
3615 
3616 template <class T>
setRating(T *,const Frame::Field &)3617 void setRating(T*, const Frame::Field&) {}
3618 
3619 template <>
setRating(TagLib::ID3v2::PopularimeterFrame * f,const Frame::Field & fld)3620 void setRating(TagLib::ID3v2::PopularimeterFrame* f, const Frame::Field& fld)
3621 {
3622   f->setRating(fld.m_value.toInt());
3623 }
3624 
3625 template <class T>
setCounter(T *,const Frame::Field &)3626 void setCounter(T*, const Frame::Field&) {}
3627 
3628 template <>
setCounter(TagLib::ID3v2::PopularimeterFrame * f,const Frame::Field & fld)3629 void setCounter(TagLib::ID3v2::PopularimeterFrame* f, const Frame::Field& fld)
3630 {
3631   f->setCounter(fld.m_value.toUInt());
3632 }
3633 
3634 template <class T>
setDate(T *,const Frame::Field &)3635 void setDate(T*, const Frame::Field&) {}
3636 
3637 template <>
setDate(TagLib::ID3v2::OwnershipFrame * f,const Frame::Field & fld)3638 void setDate(TagLib::ID3v2::OwnershipFrame* f, const Frame::Field& fld)
3639 {
3640   // The date string must have exactly 8 characters (should be YYYYMMDD)
3641   QString date(fld.m_value.toString().leftJustified(8, QLatin1Char(' '), true));
3642   f->setDatePurchased(toTString(date));
3643 }
3644 
3645 template <class T>
setPrice(T *,const Frame::Field &)3646 void setPrice(T*, const Frame::Field&) {}
3647 
3648 template <>
setPrice(TagLib::ID3v2::OwnershipFrame * f,const Frame::Field & fld)3649 void setPrice(TagLib::ID3v2::OwnershipFrame* f, const Frame::Field& fld)
3650 {
3651   f->setPricePaid(toTString(fld.m_value.toString()));
3652 }
3653 
3654 template <class T>
setSeller(T *,const Frame::Field &)3655 void setSeller(T*, const Frame::Field&) {}
3656 
3657 template <>
setSeller(TagLib::ID3v2::OwnershipFrame * f,const Frame::Field & fld)3658 void setSeller(TagLib::ID3v2::OwnershipFrame* f, const Frame::Field& fld)
3659 {
3660   f->setSeller(toTString(fld.m_value.toString()));
3661 }
3662 
3663 template <>
setTextEncoding(TagLib::ID3v2::OwnershipFrame * f,TagLib::String::Type enc)3664 void setTextEncoding(TagLib::ID3v2::OwnershipFrame* f,
3665                      TagLib::String::Type enc)
3666 {
3667   f->setTextEncoding(enc);
3668 }
3669 
3670 template <>
setValue(TagLib::ID3v2::OwnershipFrame * f,const TagLib::String & text)3671 void setValue(TagLib::ID3v2::OwnershipFrame* f, const TagLib::String& text)
3672 {
3673   f->setSeller(text);
3674 }
3675 
3676 template <class T>
setTimestampFormat(T *,const Frame::Field &)3677 void setTimestampFormat(T*, const Frame::Field&) {}
3678 
3679 template <>
setTimestampFormat(TagLib::ID3v2::SynchronizedLyricsFrame * f,const Frame::Field & fld)3680 void setTimestampFormat(TagLib::ID3v2::SynchronizedLyricsFrame* f,
3681                         const Frame::Field& fld)
3682 {
3683   f->setTimestampFormat(
3684         static_cast<TagLib::ID3v2::SynchronizedLyricsFrame::TimestampFormat>(
3685           fld.m_value.toInt()));
3686 }
3687 
3688 template <>
setTimestampFormat(TagLib::ID3v2::EventTimingCodesFrame * f,const Frame::Field & fld)3689 void setTimestampFormat(TagLib::ID3v2::EventTimingCodesFrame* f,
3690                         const Frame::Field& fld)
3691 {
3692   f->setTimestampFormat(
3693         static_cast<TagLib::ID3v2::EventTimingCodesFrame::TimestampFormat>(
3694           fld.m_value.toInt()));
3695 }
3696 
3697 template <class T>
setContentType(T *,const Frame::Field &)3698 void setContentType(T*, const Frame::Field&) {}
3699 
3700 template <>
setContentType(TagLib::ID3v2::SynchronizedLyricsFrame * f,const Frame::Field & fld)3701 void setContentType(TagLib::ID3v2::SynchronizedLyricsFrame* f,
3702                     const Frame::Field& fld)
3703 {
3704   f->setType(static_cast<TagLib::ID3v2::SynchronizedLyricsFrame::Type>(
3705                fld.m_value.toInt()));
3706 }
3707 
3708 template <>
setIdentifier(TagLib::ID3v2::RelativeVolumeFrame * f,const Frame::Field & fld)3709 void setIdentifier(TagLib::ID3v2::RelativeVolumeFrame* f,
3710                    const Frame::Field& fld)
3711 {
3712   f->setIdentification(toTString(fld.m_value.toString()));
3713 }
3714 
3715 template <>
setText(TagLib::ID3v2::RelativeVolumeFrame * f,const TagLib::String & text)3716 void setText(TagLib::ID3v2::RelativeVolumeFrame* f, const TagLib::String& text)
3717 {
3718   rva2FrameFromString(f, text);
3719 }
3720 
3721 template <>
setValue(TagLib::ID3v2::RelativeVolumeFrame * f,const TagLib::String & text)3722 void setValue(TagLib::ID3v2::RelativeVolumeFrame* f, const TagLib::String& text)
3723 {
3724   rva2FrameFromString(f, text);
3725 }
3726 
3727 #if TAGLIB_VERSION >= 0x010a00
3728 TagLib::ID3v2::Frame* createId3FrameFromFrame(const TagLibFile* self,
3729                                               Frame& frame);
3730 
3731 template <>
setIdentifier(TagLib::ID3v2::ChapterFrame * f,const Frame::Field & fld)3732 void setIdentifier(TagLib::ID3v2::ChapterFrame* f,
3733                    const Frame::Field& fld)
3734 {
3735   QByteArray id = fld.m_value.toString().toLatin1();
3736   f->setElementID(TagLib::ByteVector(id.constData(), id.size()));
3737 }
3738 
3739 template <>
setIdentifier(TagLib::ID3v2::TableOfContentsFrame * f,const Frame::Field & fld)3740 void setIdentifier(TagLib::ID3v2::TableOfContentsFrame* f,
3741                    const Frame::Field& fld)
3742 {
3743   QByteArray id = fld.m_value.toString().toLatin1();
3744   f->setElementID(TagLib::ByteVector(id.constData(), id.size()));
3745 }
3746 
3747 template <>
setValue(TagLib::ID3v2::ChapterFrame * f,const TagLib::String & text)3748 void setValue(TagLib::ID3v2::ChapterFrame* f, const TagLib::String& text)
3749 {
3750   f->setElementID(text.data(TagLib::String::Latin1));
3751 }
3752 
3753 template <>
setValue(TagLib::ID3v2::TableOfContentsFrame * f,const TagLib::String & text)3754 void setValue(TagLib::ID3v2::TableOfContentsFrame* f, const TagLib::String& text)
3755 {
3756   f->setElementID(text.data(TagLib::String::Latin1));
3757 }
3758 
3759 template <>
setData(TagLib::ID3v2::ChapterFrame * f,const Frame::Field & fld)3760 void setData(TagLib::ID3v2::ChapterFrame* f,
3761              const Frame::Field& fld)
3762 {
3763   QVariantList data(fld.m_value.toList());
3764   if (data.size() == 4) {
3765     f->setStartTime(data.at(0).toUInt());
3766     f->setEndTime(data.at(1).toUInt());
3767     f->setStartOffset(data.at(2).toUInt());
3768     f->setEndOffset(data.at(3).toUInt());
3769   }
3770   // The embedded frames are deleted here because frames without subframes
3771   // do not have an ID_Subframe field and setSubframes() is not called.
3772   while (!f->embeddedFrameList().isEmpty()) {
3773     f->removeEmbeddedFrame(f->embeddedFrameList()[0]);
3774   }
3775   // f->removeEmbeddedFrame() calls erase() thereby invalidating an iterator
3776   // on f->embeddedFrameList(). The uncommented code below will therefore crash.
3777   // const TagLib::ID3v2::FrameList l = f->embeddedFrameList();
3778   // for (auto it = l.begin(); it != l.end(); ++it) {
3779   //   f->removeEmbeddedFrame(*it, true);
3780   // }
3781 }
3782 
3783 template <>
setData(TagLib::ID3v2::TableOfContentsFrame * f,const Frame::Field & fld)3784 void setData(TagLib::ID3v2::TableOfContentsFrame* f,
3785              const Frame::Field& fld)
3786 {
3787   QVariantList data(fld.m_value.toList());
3788   if (data.size() >= 3) {
3789     f->setIsTopLevel(data.at(0).toBool());
3790     f->setIsOrdered(data.at(1).toBool());
3791     QStringList elementStrings = data.at(2).toStringList();
3792     TagLib::ByteVectorList elements;
3793     for (auto it = elementStrings.constBegin();
3794          it != elementStrings.constEnd();
3795          ++it) {
3796       QByteArray id = it->toLatin1();
3797       elements.append(TagLib::ByteVector(id.constData(), id.size()));
3798     }
3799     f->setChildElements(elements);
3800   }
3801   // The embedded frames are deleted here because frames without subframes
3802   // do not have an ID_Subframe field and setSubframes() is not called.
3803   while (!f->embeddedFrameList().isEmpty()) {
3804     f->removeEmbeddedFrame(f->embeddedFrameList()[0]);
3805   }
3806   // f->removeEmbeddedFrame() calls erase() thereby invalidating an iterator
3807   // on f->embeddedFrameList(). The uncommented code below will therefore crash.
3808   // const TagLib::ID3v2::FrameList l = f->embeddedFrameList();
3809   // for (auto it = l.begin(); it != l.end(); ++it) {
3810   //   f->removeEmbeddedFrame(*it, true);
3811   // }
3812 }
3813 
3814 template <class T>
setSubframes(const TagLibFile *,T *,Frame::FieldList::const_iterator,Frame::FieldList::const_iterator)3815 void setSubframes(const TagLibFile*, T*, Frame::FieldList::const_iterator, // clazy:exclude=function-args-by-ref
3816                   Frame::FieldList::const_iterator) {}
3817 
3818 template <>
setSubframes(const TagLibFile * self,TagLib::ID3v2::ChapterFrame * f,Frame::FieldList::const_iterator begin,Frame::FieldList::const_iterator end)3819 void setSubframes(const TagLibFile* self, TagLib::ID3v2::ChapterFrame* f,
3820                   Frame::FieldList::const_iterator begin, // clazy:exclude=function-args-by-ref
3821                   Frame::FieldList::const_iterator end) // clazy:exclude=function-args-by-ref
3822 {
3823   FrameCollection frames = FrameCollection::fromSubframes(begin, end);
3824   for (auto it = frames.begin(); it != frames.end(); ++it) {
3825     f->addEmbeddedFrame(createId3FrameFromFrame(self, const_cast<Frame&>(*it)));
3826   }
3827 }
3828 
3829 template <>
setSubframes(const TagLibFile * self,TagLib::ID3v2::TableOfContentsFrame * f,Frame::FieldList::const_iterator begin,Frame::FieldList::const_iterator end)3830 void setSubframes(const TagLibFile* self, TagLib::ID3v2::TableOfContentsFrame* f,
3831                   Frame::FieldList::const_iterator begin, // clazy:exclude=function-args-by-ref
3832                   Frame::FieldList::const_iterator end) // clazy:exclude=function-args-by-ref
3833 {
3834   FrameCollection frames = FrameCollection::fromSubframes(begin, end);
3835   for (auto it = frames.begin(); it != frames.end(); ++it) {
3836     f->addEmbeddedFrame(createId3FrameFromFrame(self, const_cast<Frame&>(*it)));
3837   }
3838 }
3839 #endif
3840 
3841 //! @endcond
3842 
3843 /**
3844  * Set the fields in a TagLib ID3v2 frame.
3845  *
3846  * @param self   this TagLibFile instance
3847  * @param tFrame TagLib frame to set
3848  * @param frame  frame with field values
3849  */
3850 template <class T>
setTagLibFrame(const TagLibFile * self,T * tFrame,const Frame & frame)3851 void setTagLibFrame(const TagLibFile* self, T* tFrame, const Frame& frame)
3852 {
3853   const Frame::FieldList& fieldList = frame.getFieldList();
3854   // If value is changed or field list is empty,
3855   // set from value, else from FieldList.
3856   if (frame.isValueChanged() || fieldList.empty()) {
3857     QString text(frame.getValue());
3858     fixUpTagLibFrameValue(self, frame.getType(), text);
3859     setValue(tFrame, toTString(text));
3860     setTextEncoding(tFrame, getTextEncodingConfig(needsUnicode(text)));
3861   } else {
3862     for (auto fldIt = fieldList.constBegin(); fldIt != fieldList.constEnd(); ++fldIt) {
3863       const Frame::Field& fld = *fldIt;
3864       switch (fld.m_id) {
3865         case Frame::ID_Text:
3866         {
3867           QString value(fld.m_value.toString());
3868           fixUpTagLibFrameValue(self, frame.getType(), value);
3869           setText(tFrame, toTString(value));
3870           break;
3871         }
3872         case Frame::ID_TextEnc:
3873           setTextEncoding(tFrame, static_cast<TagLib::String::Type>(
3874                             fld.m_value.toInt()));
3875           break;
3876         case Frame::ID_Description:
3877           setDescription(tFrame, fld);
3878           break;
3879         case Frame::ID_MimeType:
3880           setMimeType(tFrame, fld);
3881           break;
3882         case Frame::ID_PictureType:
3883           setPictureType(tFrame, fld);
3884           break;
3885         case Frame::ID_Data:
3886           setData(tFrame, fld);
3887           break;
3888         case Frame::ID_Language:
3889           setLanguage(tFrame, fld);
3890           break;
3891         case Frame::ID_Owner:
3892           setOwner(tFrame, fld);
3893           break;
3894         case Frame::ID_Id:
3895           setIdentifier(tFrame, fld);
3896           break;
3897         case Frame::ID_Filename:
3898           setFilename(tFrame, fld);
3899           break;
3900         case Frame::ID_Url:
3901           setUrl(tFrame, fld);
3902           break;
3903         case Frame::ID_Email:
3904           setEmail(tFrame, fld);
3905           break;
3906         case Frame::ID_Rating:
3907           setRating(tFrame, fld);
3908           break;
3909         case Frame::ID_Counter:
3910           setCounter(tFrame, fld);
3911           break;
3912         case Frame::ID_Price:
3913           setPrice(tFrame, fld);
3914           break;
3915         case Frame::ID_Date:
3916           setDate(tFrame, fld);
3917           break;
3918         case Frame::ID_Seller:
3919           setSeller(tFrame, fld);
3920           break;
3921         case Frame::ID_TimestampFormat:
3922           setTimestampFormat(tFrame, fld);
3923           break;
3924         case Frame::ID_ContentType:
3925           setContentType(tFrame, fld);
3926           break;
3927 #if TAGLIB_VERSION >= 0x010a00
3928         case Frame::ID_Subframe:
3929           setSubframes(self, tFrame, fldIt, fieldList.end());
3930           return;
3931 #endif
3932       }
3933     }
3934   }
3935 }
3936 
3937 /**
3938  * Modify an ID3v2 frame.
3939  *
3940  * @param self     this TagLibFile instance
3941  * @param id3Frame original ID3v2 frame
3942  * @param frame    frame with fields to set in new frame
3943  */
setId3v2Frame(const TagLibFile * self,TagLib::ID3v2::Frame * id3Frame,const Frame & frame)3944 void setId3v2Frame(const TagLibFile* self,
3945   TagLib::ID3v2::Frame* id3Frame, const Frame& frame)
3946 {
3947   if (id3Frame) {
3948     TagLib::ID3v2::TextIdentificationFrame* tFrame;
3949     TagLib::ID3v2::AttachedPictureFrame* apicFrame;
3950     TagLib::ID3v2::CommentsFrame* commFrame;
3951     TagLib::ID3v2::UniqueFileIdentifierFrame* ufidFrame;
3952     TagLib::ID3v2::GeneralEncapsulatedObjectFrame* geobFrame;
3953     TagLib::ID3v2::UserUrlLinkFrame* wxxxFrame;
3954     TagLib::ID3v2::UrlLinkFrame* wFrame;
3955     TagLib::ID3v2::UnsynchronizedLyricsFrame* usltFrame;
3956     TagLib::ID3v2::SynchronizedLyricsFrame* syltFrame;
3957     TagLib::ID3v2::EventTimingCodesFrame* etcoFrame;
3958     TagLib::ID3v2::PrivateFrame* privFrame;
3959     TagLib::ID3v2::PopularimeterFrame* popmFrame;
3960     TagLib::ID3v2::OwnershipFrame* owneFrame;
3961     TagLib::ID3v2::RelativeVolumeFrame* rva2Frame;
3962 #if TAGLIB_VERSION >= 0x010a00
3963     TagLib::ID3v2::ChapterFrame* chapFrame;
3964     TagLib::ID3v2::TableOfContentsFrame* ctocFrame;
3965 #endif
3966     if ((tFrame =
3967          dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(id3Frame))
3968         != nullptr) {
3969       auto txxxFrame =
3970         dynamic_cast<TagLib::ID3v2::UserTextIdentificationFrame*>(id3Frame);
3971       if (txxxFrame) {
3972         setTagLibFrame(self, txxxFrame, frame);
3973       } else {
3974         setTagLibFrame(self, tFrame, frame);
3975       }
3976     } else if ((apicFrame =
3977                 dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(id3Frame))
3978                != nullptr) {
3979       setTagLibFrame(self, apicFrame, frame);
3980     } else if ((commFrame = dynamic_cast<TagLib::ID3v2::CommentsFrame*>(
3981                   id3Frame)) != nullptr) {
3982       setTagLibFrame(self, commFrame, frame);
3983     } else if ((ufidFrame =
3984                 dynamic_cast<TagLib::ID3v2::UniqueFileIdentifierFrame*>(
3985                   id3Frame)) != nullptr) {
3986       setTagLibFrame(self, ufidFrame, frame);
3987     } else if ((geobFrame =
3988                 dynamic_cast<TagLib::ID3v2::GeneralEncapsulatedObjectFrame*>(
3989                   id3Frame)) != nullptr) {
3990       setTagLibFrame(self, geobFrame, frame);
3991     } else if ((wxxxFrame = dynamic_cast<TagLib::ID3v2::UserUrlLinkFrame*>(
3992                   id3Frame)) != nullptr) {
3993       setTagLibFrame(self, wxxxFrame, frame);
3994     } else if ((wFrame = dynamic_cast<TagLib::ID3v2::UrlLinkFrame*>(
3995                   id3Frame)) != nullptr) {
3996       setTagLibFrame(self, wFrame, frame);
3997     } else if ((usltFrame =
3998                 dynamic_cast<TagLib::ID3v2::UnsynchronizedLyricsFrame*>(
3999                   id3Frame)) != nullptr) {
4000       setTagLibFrame(self, usltFrame, frame);
4001     } else if ((syltFrame =
4002                 dynamic_cast<TagLib::ID3v2::SynchronizedLyricsFrame*>(
4003                   id3Frame)) != nullptr) {
4004       setTagLibFrame(self, syltFrame, frame);
4005     } else if ((etcoFrame =
4006                 dynamic_cast<TagLib::ID3v2::EventTimingCodesFrame*>(
4007                   id3Frame)) != nullptr) {
4008       setTagLibFrame(self, etcoFrame, frame);
4009     } else if ((privFrame = dynamic_cast<TagLib::ID3v2::PrivateFrame*>(
4010                   id3Frame)) != nullptr) {
4011       setTagLibFrame(self, privFrame, frame);
4012     } else if ((popmFrame = dynamic_cast<TagLib::ID3v2::PopularimeterFrame*>(
4013                   id3Frame)) != nullptr) {
4014       setTagLibFrame(self, popmFrame, frame);
4015     } else if ((owneFrame = dynamic_cast<TagLib::ID3v2::OwnershipFrame*>(
4016                   id3Frame)) != nullptr) {
4017       setTagLibFrame(self, owneFrame, frame);
4018     } else if ((rva2Frame = dynamic_cast<TagLib::ID3v2::RelativeVolumeFrame*>(
4019                   id3Frame)) != nullptr) {
4020       setTagLibFrame(self, rva2Frame, frame);
4021 #if TAGLIB_VERSION >= 0x010a00
4022     } else if ((chapFrame = dynamic_cast<TagLib::ID3v2::ChapterFrame*>(
4023                   id3Frame)) != nullptr) {
4024       setTagLibFrame(self, chapFrame, frame);
4025     } else if ((ctocFrame = dynamic_cast<TagLib::ID3v2::TableOfContentsFrame*>(
4026                   id3Frame)) != nullptr) {
4027       setTagLibFrame(self, ctocFrame, frame);
4028 #endif
4029     } else {
4030       TagLib::ByteVector id(id3Frame->frameID());
4031       // create temporary objects for frames not known by TagLib,
4032       // an UnknownFrame copy will be created by the edit method.
4033 #if TAGLIB_VERSION < 0x010a00
4034       if (id.startsWith("SYLT")) {
4035         TagLib::ID3v2::SynchronizedLyricsFrame syltFrm(id3Frame->render());
4036         setTagLibFrame(self, &syltFrm, frame);
4037         id3Frame->setData(syltFrm.render());
4038       } else if (id.startsWith("ETCO")) {
4039         TagLib::ID3v2::EventTimingCodesFrame etcoFrm(id3Frame->render());
4040         setTagLibFrame(self, &etcoFrm, frame);
4041         id3Frame->setData(etcoFrm.render());
4042       } else
4043 #endif
4044       {
4045         setTagLibFrame(self, id3Frame, frame);
4046       }
4047     }
4048   }
4049 }
4050 
4051 /**
4052  * Get name of frame from type.
4053  *
4054  * @param type type
4055  *
4056  * @return name.
4057  */
getVorbisNameFromType(Frame::Type type)4058 const char* getVorbisNameFromType(Frame::Type type)
4059 {
4060   static const char* const names[] = {
4061     "TITLE",           // FT_Title,
4062     "ARTIST",          // FT_Artist,
4063     "ALBUM",           // FT_Album,
4064     "COMMENT",         // FT_Comment,
4065     "DATE",            // FT_Date,
4066     "TRACKNUMBER",     // FT_Track,
4067     "GENRE",           // FT_Genre,
4068                        // FT_LastV1Frame = FT_Track,
4069     "ALBUMARTIST",     // FT_AlbumArtist,
4070     "ARRANGER",        // FT_Arranger,
4071     "AUTHOR",          // FT_Author,
4072     "BPM",             // FT_Bpm,
4073     "CATALOGNUMBER",   // FT_CatalogNumber,
4074     "COMPILATION",     // FT_Compilation,
4075     "COMPOSER",        // FT_Composer,
4076     "CONDUCTOR",       // FT_Conductor,
4077     "COPYRIGHT",       // FT_Copyright,
4078     "DISCNUMBER",      // FT_Disc,
4079     "ENCODED-BY",      // FT_EncodedBy,
4080     "ENCODERSETTINGS", // FT_EncoderSettings,
4081     "ENCODINGTIME",    // FT_EncodingTime,
4082     "GROUPING",        // FT_Grouping,
4083     "INITIALKEY",      // FT_InitialKey,
4084     "ISRC",            // FT_Isrc,
4085     "LANGUAGE",        // FT_Language,
4086     "LYRICIST",        // FT_Lyricist,
4087     "LYRICS",          // FT_Lyrics,
4088     "SOURCEMEDIA",     // FT_Media,
4089     "MOOD",            // FT_Mood,
4090     "ORIGINALALBUM",   // FT_OriginalAlbum,
4091     "ORIGINALARTIST",  // FT_OriginalArtist,
4092     "ORIGINALDATE",    // FT_OriginalDate,
4093     "DESCRIPTION",     // FT_Description,
4094     "PERFORMER",       // FT_Performer,
4095     "METADATA_BLOCK_PICTURE", // FT_Picture,
4096     "PUBLISHER",       // FT_Publisher,
4097     "RELEASECOUNTRY",  // FT_ReleaseCountry,
4098     "REMIXER",         // FT_Remixer,
4099     "ALBUMSORT",       // FT_SortAlbum,
4100     "ALBUMARTISTSORT", // FT_SortAlbumArtist,
4101     "ARTISTSORT",      // FT_SortArtist,
4102     "COMPOSERSORT",    // FT_SortComposer,
4103     "TITLESORT",       // FT_SortName,
4104     "SUBTITLE",        // FT_Subtitle,
4105     "WEBSITE",         // FT_Website,
4106     "WWWAUDIOFILE",    // FT_WWWAudioFile,
4107     "WWWAUDIOSOURCE",  // FT_WWWAudioSource,
4108     "RELEASEDATE",     // FT_ReleaseDate,
4109     "RATING",          // FT_Rating,
4110     "WORK"             // FT_Work,
4111                        // FT_LastFrame = FT_Work
4112   };
4113   Q_STATIC_ASSERT(sizeof(names) / sizeof(names[0]) == Frame::FT_LastFrame + 1);
4114   if (type == Frame::FT_Picture &&
4115       TagConfig::instance().pictureNameIndex() == TagConfig::VP_COVERART) {
4116     return "COVERART";
4117   }
4118   return type <= Frame::FT_LastFrame ? names[type] : "UNKNOWN";
4119 }
4120 
4121 /**
4122  * Get the frame type for a Vorbis name.
4123  *
4124  * @param name Vorbis tag name
4125  *
4126  * @return frame type.
4127  */
getTypeFromVorbisName(QString name)4128 Frame::Type getTypeFromVorbisName(QString name)
4129 {
4130   static QMap<QString, int> strNumMap;
4131   if (strNumMap.empty()) {
4132     // first time initialization
4133     for (int i = 0; i <= Frame::FT_LastFrame; ++i) {
4134       auto type = static_cast<Frame::Type>(i);
4135       strNumMap.insert(QString::fromLatin1(getVorbisNameFromType(type)), type);
4136     }
4137     strNumMap.insert(QLatin1String("COVERART"), Frame::FT_Picture);
4138     strNumMap.insert(QLatin1String("METADATA_BLOCK_PICTURE"), Frame::FT_Picture);
4139   }
4140   auto it = strNumMap.constFind(name.remove(QLatin1Char('=')).toUpper());
4141   if (it != strNumMap.constEnd()) {
4142     return static_cast<Frame::Type>(*it);
4143   }
4144   return Frame::FT_Other;
4145 }
4146 
4147 /**
4148  * Get the frame type for an APE name.
4149  *
4150  * @param name APE tag name
4151  *
4152  * @return frame type.
4153  */
getTypeFromApeName(const QString & name)4154 Frame::Type getTypeFromApeName(const QString& name)
4155 {
4156   Frame::Type type = getTypeFromVorbisName(name);
4157   if (type == Frame::FT_Other) {
4158     if (name == QLatin1String("YEAR")) {
4159       type = Frame::FT_Date;
4160     } else if (name == QLatin1String("TRACK")) {
4161       type = Frame::FT_Track;
4162     } else if (name == QLatin1String("ENCODED BY")) {
4163       type = Frame::FT_EncodedBy;
4164     } else if (name.startsWith(QLatin1String("COVER ART"))) {
4165       type = Frame::FT_Picture;
4166     }
4167   }
4168   return type;
4169 }
4170 
4171 }
4172 
4173 /**
4174  * Get internal name of a Vorbis frame.
4175  *
4176  * @param frame frame
4177  *
4178  * @return Vorbis key.
4179  */
getVorbisName(const Frame & frame) const4180 QString TagLibFile::getVorbisName(const Frame& frame) const
4181 {
4182   Frame::Type type = frame.getType();
4183   if (type == Frame::FT_Comment) {
4184     return getCommentFieldName();
4185   } else if (type <= Frame::FT_LastFrame) {
4186     return QString::fromLatin1(getVorbisNameFromType(type));
4187   } else {
4188     return frame.getName().remove(QLatin1Char('=')).toUpper();
4189   }
4190 }
4191 
4192 namespace {
4193 
4194 /**
4195  * Get internal name of an APE picture frame.
4196  *
4197  * @param pictureType picture type
4198  *
4199  * @return APE key.
4200  */
getApePictureName(PictureFrame::PictureType pictureType)4201 TagLib::String getApePictureName(PictureFrame::PictureType pictureType)
4202 {
4203   TagLib::String name("COVER ART (");
4204   name += TagLib::String(PictureFrame::getPictureTypeString(pictureType))
4205       .upper();
4206   name += ')';
4207   return name;
4208 }
4209 
4210 /**
4211  * Get internal name of an APE frame.
4212  *
4213  * @param frame frame
4214  *
4215  * @return APE key.
4216  */
getApeName(const Frame & frame)4217 QString getApeName(const Frame& frame)
4218 {
4219   Frame::Type type = frame.getType();
4220   if (type == Frame::FT_Date) {
4221     return QLatin1String("YEAR");
4222   } else if (type == Frame::FT_Track) {
4223     return QLatin1String("TRACK");
4224   } else if (type == Frame::FT_Picture) {
4225     PictureFrame::PictureType pictureType;
4226     if (!PictureFrame::getPictureType(frame, pictureType)) {
4227       pictureType = Frame::PT_CoverFront;
4228     }
4229     return toQString(getApePictureName(pictureType));
4230   } else if (type <= Frame::FT_LastFrame) {
4231     return QString::fromLatin1(getVorbisNameFromType(type));
4232   } else {
4233     return frame.getName().toUpper();
4234   }
4235 }
4236 
4237 /** Type of data in MP4 frame. */
4238 enum Mp4ValueType {
4239   MVT_ByteArray,
4240   MVT_CoverArt,
4241   MVT_String,
4242   MVT_Bool,
4243   MVT_Int,
4244   MVT_IntPair,
4245   MVT_Byte,
4246   MVT_UInt,
4247   MVT_LongLong
4248 };
4249 
4250 /** MP4 name, frame type and value type. */
4251 struct Mp4NameTypeValue {
4252   const char* name;
4253   Frame::Type type;
4254   Mp4ValueType value;
4255 };
4256 
4257 /** Mapping between frame types and field names. */
4258 const Mp4NameTypeValue mp4NameTypeValues[] = {
4259   { "\251nam", Frame::FT_Title, MVT_String },
4260   { "\251ART", Frame::FT_Artist, MVT_String },
4261   { "\251wrt", Frame::FT_Composer, MVT_String },
4262   { "\251alb", Frame::FT_Album, MVT_String },
4263   { "\251day", Frame::FT_Date, MVT_String },
4264   { "\251enc", Frame::FT_EncodedBy, MVT_String },
4265   { "\251cmt", Frame::FT_Comment, MVT_String },
4266   { "gnre", Frame::FT_Genre, MVT_String },
4267   // (c)gen is after gnre so that it is used in the maps because TagLib uses it
4268   { "\251gen", Frame::FT_Genre, MVT_String },
4269   { "trkn", Frame::FT_Track, MVT_IntPair },
4270   { "disk", Frame::FT_Disc, MVT_IntPair },
4271   { "cpil", Frame::FT_Compilation, MVT_Bool },
4272   { "tmpo", Frame::FT_Bpm, MVT_Int },
4273   { "\251grp", Frame::FT_Grouping, MVT_String },
4274   { "aART", Frame::FT_AlbumArtist, MVT_String },
4275   { "pgap", Frame::FT_Other, MVT_Bool },
4276   { "cprt", Frame::FT_Copyright, MVT_String },
4277   { "\251lyr", Frame::FT_Lyrics, MVT_String },
4278   { "tvsh", Frame::FT_Other, MVT_String },
4279   { "tvnn", Frame::FT_Other, MVT_String },
4280   { "tven", Frame::FT_Other, MVT_String },
4281   { "tvsn", Frame::FT_Other, MVT_UInt },
4282   { "tves", Frame::FT_Other, MVT_UInt },
4283   { "desc", Frame::FT_Description, MVT_String },
4284   { "ldes", Frame::FT_Other, MVT_String },
4285   { "sonm", Frame::FT_SortName, MVT_String },
4286   { "soar", Frame::FT_SortArtist, MVT_String },
4287   { "soaa", Frame::FT_SortAlbumArtist, MVT_String },
4288   { "soal", Frame::FT_SortAlbum, MVT_String },
4289   { "soco", Frame::FT_SortComposer, MVT_String },
4290   { "sosn", Frame::FT_Other, MVT_String },
4291   { "\251too", Frame::FT_EncoderSettings, MVT_String },
4292   { "purd", Frame::FT_Other, MVT_String },
4293   { "pcst", Frame::FT_Other, MVT_Bool },
4294   { "keyw", Frame::FT_Other, MVT_String },
4295   { "catg", Frame::FT_Other, MVT_String },
4296   { "hdvd", Frame::FT_Other, MVT_Bool },
4297   { "stik", Frame::FT_Other, MVT_Byte },
4298   { "rtng", Frame::FT_Other, MVT_Byte },
4299   { "apID", Frame::FT_Other, MVT_String },
4300   { "akID", Frame::FT_Other, MVT_Byte },
4301   { "sfID", Frame::FT_Other, MVT_UInt },
4302   { "cnID", Frame::FT_Other, MVT_UInt },
4303   { "atID", Frame::FT_Other, MVT_UInt },
4304   { "plID", Frame::FT_Other, MVT_LongLong },
4305   { "geID", Frame::FT_Other, MVT_UInt },
4306   { "ownr", Frame::FT_Other, MVT_String },
4307 #if TAGLIB_VERSION >= 0x010c00
4308   { "purl", Frame::FT_Other, MVT_String },
4309   { "egid", Frame::FT_Other, MVT_String },
4310   { "cmID", Frame::FT_Other, MVT_UInt },
4311 #endif
4312   { "xid ", Frame::FT_Other, MVT_String },
4313   { "covr", Frame::FT_Picture, MVT_CoverArt },
4314 #if TAGLIB_VERSION >= 0x010c00
4315   { "\251wrk", Frame::FT_Work, MVT_String },
4316   { "\251mvn", Frame::FT_Other, MVT_String },
4317   { "\251mvi", Frame::FT_Other, MVT_Int },
4318   { "\251mvc", Frame::FT_Other, MVT_Int },
4319   { "shwm", Frame::FT_Other, MVT_Bool },
4320 #endif
4321   { "ARRANGER", Frame::FT_Arranger, MVT_String },
4322   { "AUTHOR", Frame::FT_Author, MVT_String },
4323   { "CATALOGNUMBER", Frame::FT_CatalogNumber, MVT_String },
4324   { "CONDUCTOR", Frame::FT_Conductor, MVT_String },
4325   { "ENCODINGTIME", Frame::FT_EncodingTime, MVT_String },
4326   { "INITIALKEY", Frame::FT_InitialKey, MVT_String },
4327   { "ISRC", Frame::FT_Isrc, MVT_String },
4328   { "LANGUAGE", Frame::FT_Language, MVT_String },
4329   { "LYRICIST", Frame::FT_Lyricist, MVT_String },
4330   { "MOOD", Frame::FT_Mood, MVT_String },
4331   { "SOURCEMEDIA", Frame::FT_Media, MVT_String },
4332   { "ORIGINALALBUM", Frame::FT_OriginalAlbum, MVT_String },
4333   { "ORIGINALARTIST", Frame::FT_OriginalArtist, MVT_String },
4334   { "ORIGINALDATE", Frame::FT_OriginalDate, MVT_String },
4335   { "PERFORMER", Frame::FT_Performer, MVT_String },
4336   { "PUBLISHER", Frame::FT_Publisher, MVT_String },
4337   { "RELEASECOUNTRY", Frame::FT_ReleaseCountry, MVT_String },
4338   { "REMIXER", Frame::FT_Remixer, MVT_String },
4339   { "SUBTITLE", Frame::FT_Subtitle, MVT_String },
4340   { "WEBSITE", Frame::FT_Website, MVT_String },
4341   { "WWWAUDIOFILE", Frame::FT_WWWAudioFile, MVT_String },
4342   { "WWWAUDIOSOURCE", Frame::FT_WWWAudioSource, MVT_String },
4343   { "RELEASEDATE", Frame::FT_ReleaseDate, MVT_String },
4344   { "rate", Frame::FT_Rating, MVT_String }
4345 };
4346 
4347 /**
4348  * Get MP4 name and value type for a frame type.
4349  *
4350  * @param type  frame type
4351  * @param name  the MP4 name is returned here
4352  * @param value the MP4 value type is returned here
4353  */
getMp4NameForType(Frame::Type type,TagLib::String & name,Mp4ValueType & value)4354 void getMp4NameForType(Frame::Type type, TagLib::String& name,
4355                        Mp4ValueType& value)
4356 {
4357   static QMap<Frame::Type, unsigned> typeNameMap;
4358   if (typeNameMap.empty()) {
4359     // first time initialization
4360     for (unsigned i = 0;
4361          i < sizeof(mp4NameTypeValues) / sizeof(mp4NameTypeValues[0]); ++i) {
4362       if (mp4NameTypeValues[i].type != Frame::FT_Other) {
4363         typeNameMap.insert(mp4NameTypeValues[i].type, i);
4364       }
4365     }
4366   }
4367   name = "";
4368   value = MVT_String;
4369   if (type != Frame::FT_Other) {
4370     auto it = typeNameMap.constFind(type);
4371     if (it != typeNameMap.constEnd()) {
4372       name = mp4NameTypeValues[*it].name;
4373       value = mp4NameTypeValues[*it].value;
4374     }
4375   }
4376 }
4377 
4378 /**
4379  * Get MP4 value type and frame type for an MP4 name.
4380  *
4381  * @param name  MP4 name
4382  * @param type  the frame type is returned here
4383  * @param value the MP4 value type is returned here
4384  *
4385  * @return true if free-form frame.
4386  */
getMp4TypeForName(const TagLib::String & name,Frame::Type & type,Mp4ValueType & value)4387 bool getMp4TypeForName(const TagLib::String& name, Frame::Type& type,
4388                        Mp4ValueType& value)
4389 {
4390   static QMap<TagLib::String, unsigned> nameTypeMap;
4391   if (nameTypeMap.empty()) {
4392     // first time initialization
4393     for (unsigned i = 0;
4394          i < sizeof(mp4NameTypeValues) / sizeof(mp4NameTypeValues[0]); ++i) {
4395       nameTypeMap.insert(mp4NameTypeValues[i].name, i);
4396     }
4397   }
4398   auto it = nameTypeMap.constFind(name);
4399   if (it != nameTypeMap.constEnd()) {
4400     type = mp4NameTypeValues[*it].type;
4401     value = mp4NameTypeValues[*it].value;
4402     return name[0] >= 'A' && name[0] <= 'Z';
4403   } else {
4404     type = Frame::FT_Other;
4405     value = MVT_String;
4406     return true;
4407   }
4408 }
4409 
4410 /**
4411  * Strip free form prefix from MP4 frame name.
4412  *
4413  * @param name MP4 frame name to be stripped
4414  */
stripMp4FreeFormName(TagLib::String & name)4415 void stripMp4FreeFormName(TagLib::String& name)
4416 {
4417   if (name.startsWith("----")) {
4418     int nameStart = name.rfind(":");
4419     if (nameStart == -1) {
4420       nameStart = 5;
4421     } else {
4422       ++nameStart;
4423     }
4424     name = name.substr(nameStart);
4425 
4426     Frame::Type type;
4427     Mp4ValueType valueType;
4428     if (!getMp4TypeForName(name, type, valueType)) {
4429       // not detected as free form => mark with ':' as first character
4430       name = ':' + name;
4431     }
4432   }
4433 }
4434 
4435 /**
4436  * Prepend free form prefix to MP4 frame name.
4437  * Only names starting with a capital letter or ':' are prefixed.
4438  *
4439  * @param name MP4 frame name to be prefixed
4440  * @param mp4Tag tag to check for existing item
4441  */
prefixMp4FreeFormName(TagLib::String & name,const TagLib::MP4::Tag * mp4Tag)4442 void prefixMp4FreeFormName(TagLib::String& name, const TagLib::MP4::Tag* mp4Tag)
4443 {
4444   if (
4445 #if TAGLIB_VERSION >= 0x010a00
4446       !mp4Tag->contains(name)
4447 #else
4448       !const_cast<TagLib::MP4::Tag*>(mp4Tag)->itemListMap().contains(name)
4449 #endif
4450       && !name.startsWith("----") &&
4451       !(name.length() == 4 &&
4452         (static_cast<char>(name[0]) == '\251' ||
4453          (name[0] >= 'a' && name[0] <= 'z')))) {
4454     Frame::Type type;
4455     Mp4ValueType valueType;
4456     if (getMp4TypeForName(name, type, valueType)) {
4457       // free form
4458       if (name[0] == ':') name = name.substr(1);
4459       TagLib::String freeFormName = "----:com.apple.iTunes:" + name;
4460       unsigned int nameLen;
4461       if (
4462 #if TAGLIB_VERSION >= 0x010a00
4463           !mp4Tag->contains(freeFormName)
4464 #else
4465           !const_cast<TagLib::MP4::Tag*>(mp4Tag)->itemListMap().contains(
4466             freeFormName)
4467 #endif
4468           && (nameLen = name.length()) > 0) {
4469         // Not an iTunes free form name, maybe using another prefix
4470         // (such as "----:com.nullsoft.winamp:").
4471         // Search for a frame which ends with this name.
4472 #if TAGLIB_VERSION >= 0x010a00
4473         const TagLib::MP4::ItemMap& items = mp4Tag->itemMap();
4474 #else
4475         const TagLib::MP4::ItemListMap& items =
4476             const_cast<TagLib::MP4::Tag*>(mp4Tag)->itemListMap();
4477 #endif
4478         for (auto it = items.begin(); it != items.end(); ++it) {
4479           const TagLib::String& key = it->first;
4480           if (key.length() >= nameLen &&
4481               key.substr(key.length() - nameLen, nameLen) == name) {
4482             freeFormName = key;
4483             break;
4484           }
4485         }
4486       }
4487       name = freeFormName;
4488     }
4489   }
4490 }
4491 
4492 /**
4493  * Get an MP4 type for a frame.
4494  *
4495  * @param frame frame
4496  * @param name  the MP4 name is returned here
4497  * @param value the MP4 value type is returned here
4498  */
getMp4TypeForFrame(const Frame & frame,TagLib::String & name,Mp4ValueType & value)4499 void getMp4TypeForFrame(const Frame& frame, TagLib::String& name,
4500                         Mp4ValueType& value)
4501 {
4502   if (frame.getType() != Frame::FT_Other) {
4503     getMp4NameForType(frame.getType(), name, value);
4504     if (name.isEmpty()) {
4505       name = toTString(frame.getInternalName());
4506     }
4507   } else {
4508     Frame::Type type;
4509     name = toTString(frame.getInternalName());
4510     getMp4TypeForName(name, type, value);
4511   }
4512 }
4513 
4514 /**
4515  * Get an MP4 item for a frame.
4516  *
4517  * @param frame frame
4518  * @param name  the name for the item is returned here
4519  *
4520  * @return MP4 item, an invalid item is returned if not supported.
4521  */
getMp4ItemForFrame(const Frame & frame,TagLib::String & name)4522 TagLib::MP4::Item getMp4ItemForFrame(const Frame& frame, TagLib::String& name)
4523 {
4524   Mp4ValueType valueType;
4525   getMp4TypeForFrame(frame, name, valueType);
4526   switch (valueType) {
4527     case MVT_String:
4528       return TagLib::MP4::Item(
4529             TagLib::StringList::split(toTString(frame.getValue()),
4530                                       Frame::stringListSeparator().toLatin1()));
4531     case MVT_Bool:
4532       return TagLib::MP4::Item(frame.getValue().toInt() != 0);
4533     case MVT_Int:
4534       return TagLib::MP4::Item(frame.getValue().toInt());
4535     case MVT_IntPair:
4536     {
4537       QString str1 = frame.getValue(), str2 = QLatin1String("0");
4538       int slashPos = str1.indexOf(QLatin1Char('/'));
4539       if (slashPos != -1) {
4540         str2 = str1.mid(slashPos + 1);
4541         str1.truncate(slashPos);
4542       }
4543       return TagLib::MP4::Item(str1.toInt(), str2.toInt());
4544     }
4545     case MVT_CoverArt:
4546     {
4547       QByteArray ba;
4548       TagLib::MP4::CoverArt::Format format = TagLib::MP4::CoverArt::JPEG;
4549       if (PictureFrame::getData(frame, ba)) {
4550         QString mimeType;
4551         if (PictureFrame::getMimeType(frame, mimeType) &&
4552             mimeType == QLatin1String("image/png")) {
4553           format = TagLib::MP4::CoverArt::PNG;
4554         }
4555       }
4556       TagLib::MP4::CoverArt coverArt(format,
4557                                      TagLib::ByteVector(ba.data(), ba.size()));
4558       TagLib::MP4::CoverArtList coverArtList;
4559       coverArtList.append(coverArt);
4560       return TagLib::MP4::Item(coverArtList);
4561     }
4562     case MVT_Byte:
4563       return TagLib::MP4::Item(static_cast<uchar>(frame.getValue().toInt()));
4564     case MVT_UInt:
4565       return TagLib::MP4::Item(frame.getValue().toUInt());
4566     case MVT_LongLong:
4567       return TagLib::MP4::Item(frame.getValue().toLongLong());
4568     case MVT_ByteArray:
4569     default:
4570       // binary data and album art are not handled by TagLib
4571       return TagLib::MP4::Item();
4572   }
4573 }
4574 
4575 }
4576 
4577 /**
4578  * Set a frame in an MP4 tag.
4579  * @param frame frame to set
4580  * @param mp4Tag MP4 tag
4581  */
setMp4Frame(const Frame & frame,TagLib::MP4::Tag * mp4Tag)4582 void TagLibFile::setMp4Frame(const Frame& frame, TagLib::MP4::Tag* mp4Tag)
4583 {
4584   TagLib::String name;
4585   TagLib::MP4::Item item = getMp4ItemForFrame(frame, name);
4586   if (item.isValid()) {
4587     int numTracks;
4588     if (name == "trkn" &&
4589         (numTracks = getTotalNumberOfTracksIfEnabled()) > 0) {
4590       TagLib::MP4::Item::IntPair pair = item.toIntPair();
4591       if (pair.second == 0) {
4592         item = TagLib::MP4::Item(pair.first, numTracks);
4593       }
4594     }
4595     prefixMp4FreeFormName(name, mp4Tag);
4596 #if TAGLIB_VERSION >= 0x010b01
4597     mp4Tag->setItem(name, item);
4598 #else
4599     mp4Tag->itemListMap()[name] = item;
4600 #endif
4601     markTagChanged(Frame::Tag_2, frame.getType());
4602   }
4603 }
4604 
4605 namespace {
4606 
4607 /** Indices of fixed ASF frames. */
4608 enum AsfFrameIndex {
4609   AFI_Title,
4610   AFI_Artist,
4611   AFI_Comment,
4612   AFI_Copyright,
4613   AFI_Rating,
4614   AFI_Attributes
4615 };
4616 
4617 /** ASF name, frame type and value type. */
4618 struct AsfNameTypeValue {
4619   const char* name;
4620   Frame::Type type;
4621   TagLib::ASF::Attribute::AttributeTypes value;
4622 };
4623 
4624 /** Mapping between frame types and field names. */
4625 const AsfNameTypeValue asfNameTypeValues[] = {
4626   { "Title", Frame::FT_Title, TagLib::ASF::Attribute::UnicodeType },
4627   { "Author", Frame::FT_Artist, TagLib::ASF::Attribute::UnicodeType },
4628   { "WM/AlbumTitle", Frame::FT_Album, TagLib::ASF::Attribute::UnicodeType },
4629   { "Description", Frame::FT_Comment, TagLib::ASF::Attribute::UnicodeType },
4630   { "WM/Year", Frame::FT_Date, TagLib::ASF::Attribute::UnicodeType },
4631   { "Copyright", Frame::FT_Copyright, TagLib::ASF::Attribute::UnicodeType },
4632   { "Rating Information", Frame::FT_Other, TagLib::ASF::Attribute::UnicodeType },
4633   { "WM/TrackNumber", Frame::FT_Track, TagLib::ASF::Attribute::UnicodeType },
4634   { "WM/Track", Frame::FT_Track, TagLib::ASF::Attribute::UnicodeType },
4635   { "WM/Genre", Frame::FT_Genre, TagLib::ASF::Attribute::UnicodeType },
4636   { "WM/GenreID", Frame::FT_Genre, TagLib::ASF::Attribute::UnicodeType },
4637   { "WM/AlbumArtist", Frame::FT_AlbumArtist, TagLib::ASF::Attribute::UnicodeType },
4638   { "WM/AlbumSortOrder", Frame::FT_SortAlbum, TagLib::ASF::Attribute::UnicodeType },
4639   { "WM/ArtistSortOrder", Frame::FT_SortArtist, TagLib::ASF::Attribute::UnicodeType },
4640   { "WM/TitleSortOrder", Frame::FT_SortName, TagLib::ASF::Attribute::UnicodeType },
4641   { "WM/Producer", Frame::FT_Arranger, TagLib::ASF::Attribute::UnicodeType },
4642   { "WM/BeatsPerMinute", Frame::FT_Bpm, TagLib::ASF::Attribute::UnicodeType },
4643   { "WM/Composer", Frame::FT_Composer, TagLib::ASF::Attribute::UnicodeType },
4644   { "WM/Conductor", Frame::FT_Conductor, TagLib::ASF::Attribute::UnicodeType },
4645   { "WM/PartOfSet", Frame::FT_Disc, TagLib::ASF::Attribute::UnicodeType },
4646   { "WM/EncodedBy", Frame::FT_EncodedBy, TagLib::ASF::Attribute::UnicodeType },
4647   { "WM/ContentGroupDescription", Frame::FT_Work, TagLib::ASF::Attribute::UnicodeType },
4648   { "WM/ISRC", Frame::FT_Isrc, TagLib::ASF::Attribute::UnicodeType },
4649   { "WM/Language", Frame::FT_Language, TagLib::ASF::Attribute::UnicodeType },
4650   { "WM/Writer", Frame::FT_Lyricist, TagLib::ASF::Attribute::UnicodeType },
4651   { "WM/Lyrics", Frame::FT_Lyrics, TagLib::ASF::Attribute::UnicodeType },
4652   { "WM/AudioSourceURL", Frame::FT_WWWAudioSource, TagLib::ASF::Attribute::UnicodeType },
4653   { "WM/OriginalAlbumTitle", Frame::FT_OriginalAlbum, TagLib::ASF::Attribute::UnicodeType },
4654   { "WM/OriginalArtist", Frame::FT_OriginalArtist, TagLib::ASF::Attribute::UnicodeType },
4655   { "WM/OriginalReleaseYear", Frame::FT_OriginalDate, TagLib::ASF::Attribute::UnicodeType },
4656   { "WM/SubTitleDescription", Frame::FT_Description, TagLib::ASF::Attribute::UnicodeType },
4657   { "WM/Picture", Frame::FT_Picture, TagLib::ASF::Attribute::BytesType },
4658   { "WM/Publisher", Frame::FT_Publisher, TagLib::ASF::Attribute::UnicodeType },
4659   { "WM/ModifiedBy", Frame::FT_Remixer, TagLib::ASF::Attribute::UnicodeType },
4660   { "WM/SubTitle", Frame::FT_Subtitle, TagLib::ASF::Attribute::UnicodeType },
4661   { "WM/AuthorURL", Frame::FT_Website, TagLib::ASF::Attribute::UnicodeType },
4662   { "AverageLevel", Frame::FT_Other, TagLib::ASF::Attribute::DWordType },
4663   { "PeakValue", Frame::FT_Other, TagLib::ASF::Attribute::DWordType },
4664   { "WM/AudioFileURL", Frame::FT_WWWAudioFile, TagLib::ASF::Attribute::UnicodeType },
4665   { "WM/EncodingSettings", Frame::FT_EncoderSettings, TagLib::ASF::Attribute::UnicodeType },
4666   { "WM/EncodingTime", Frame::FT_EncodingTime, TagLib::ASF::Attribute::BytesType },
4667   { "WM/InitialKey", Frame::FT_InitialKey, TagLib::ASF::Attribute::UnicodeType },
4668   // incorrect WM/Lyrics_Synchronised data make file inaccessible in Windows
4669   // { "WM/Lyrics_Synchronised", Frame::FT_Other, TagLib::ASF::Attribute::BytesType },
4670   { "WM/MCDI", Frame::FT_Other, TagLib::ASF::Attribute::BytesType },
4671   { "WM/MediaClassPrimaryID", Frame::FT_Other, TagLib::ASF::Attribute::GuidType },
4672   { "WM/MediaClassSecondaryID", Frame::FT_Other, TagLib::ASF::Attribute::GuidType },
4673   { "WM/Mood", Frame::FT_Mood, TagLib::ASF::Attribute::UnicodeType },
4674   { "WM/OriginalFilename", Frame::FT_Other, TagLib::ASF::Attribute::UnicodeType },
4675   { "WM/OriginalLyricist", Frame::FT_Other, TagLib::ASF::Attribute::UnicodeType },
4676   { "WM/PromotionURL", Frame::FT_Other, TagLib::ASF::Attribute::UnicodeType },
4677   { "WM/SharedUserRating", Frame::FT_Rating, TagLib::ASF::Attribute::UnicodeType },
4678   { "WM/WMCollectionGroupID", Frame::FT_Other, TagLib::ASF::Attribute::GuidType },
4679   { "WM/WMCollectionID", Frame::FT_Other, TagLib::ASF::Attribute::GuidType },
4680   { "WM/WMContentID", Frame::FT_Other, TagLib::ASF::Attribute::GuidType }
4681 };
4682 
4683 /**
4684  * Get ASF name and value type for a frame type.
4685  *
4686  * @param type  frame type
4687  * @param name  the ASF name is returned here
4688  * @param value the ASF value type is returned here
4689  */
getAsfNameForType(Frame::Type type,TagLib::String & name,TagLib::ASF::Attribute::AttributeTypes & value)4690 void getAsfNameForType(Frame::Type type, TagLib::String& name,
4691                        TagLib::ASF::Attribute::AttributeTypes& value)
4692 {
4693   static QMap<Frame::Type, unsigned> typeNameMap;
4694   if (typeNameMap.empty()) {
4695     // first time initialization
4696     for (unsigned i = 0;
4697          i < sizeof(asfNameTypeValues) / sizeof(asfNameTypeValues[0]); ++i) {
4698       if (asfNameTypeValues[i].type != Frame::FT_Other &&
4699           !typeNameMap.contains(asfNameTypeValues[i].type)) {
4700         typeNameMap.insert(asfNameTypeValues[i].type, i);
4701       }
4702     }
4703   }
4704   name = "";
4705   value = TagLib::ASF::Attribute::UnicodeType;
4706   if (type != Frame::FT_Other) {
4707     auto it = typeNameMap.constFind(type);
4708     if (it != typeNameMap.constEnd()) {
4709       name = asfNameTypeValues[*it].name;
4710       value = asfNameTypeValues[*it].value;
4711     }
4712   }
4713 }
4714 
4715 /**
4716  * Get ASF value type and frame type for an ASF name.
4717  *
4718  * @param name  ASF name
4719  * @param type  the frame type is returned here
4720  * @param value the ASF value type is returned here
4721  */
getAsfTypeForName(const TagLib::String & name,Frame::Type & type,TagLib::ASF::Attribute::AttributeTypes & value)4722 void getAsfTypeForName(const TagLib::String& name, Frame::Type& type,
4723                        TagLib::ASF::Attribute::AttributeTypes& value)
4724 {
4725   static QMap<TagLib::String, unsigned> nameTypeMap;
4726   if (nameTypeMap.empty()) {
4727     // first time initialization
4728     for (unsigned i = 0;
4729          i < sizeof(asfNameTypeValues) / sizeof(asfNameTypeValues[0]); ++i) {
4730       nameTypeMap.insert(asfNameTypeValues[i].name, i);
4731     }
4732   }
4733   auto it = nameTypeMap.constFind(name);
4734   if (it != nameTypeMap.constEnd()) {
4735     type = asfNameTypeValues[*it].type;
4736     value = asfNameTypeValues[*it].value;
4737   } else {
4738     type = Frame::FT_Other;
4739     value = TagLib::ASF::Attribute::UnicodeType;
4740   }
4741 }
4742 
4743 /**
4744  * Get an ASF type for a frame.
4745  *
4746  * @param frame frame
4747  * @param name  the name for the attribute is returned here
4748  * @param value the ASF value type is returned here
4749  */
getAsfTypeForFrame(const Frame & frame,TagLib::String & name,TagLib::ASF::Attribute::AttributeTypes & value)4750 void getAsfTypeForFrame(const Frame& frame, TagLib::String& name,
4751                         TagLib::ASF::Attribute::AttributeTypes& value)
4752 {
4753   if (frame.getType() != Frame::FT_Other) {
4754     getAsfNameForType(frame.getType(), name, value);
4755     if (name.isEmpty()) {
4756       name = toTString(frame.getInternalName());
4757     }
4758   } else {
4759     Frame::Type type;
4760     name = toTString(frame.getInternalName());
4761     getAsfTypeForName(name, type, value);
4762   }
4763 }
4764 
4765 /**
4766  * Get a picture frame from a WM/Picture.
4767  *
4768  * @param picture ASF picture
4769  * @param frame   the picture frame is returned here
4770  *
4771  * @return true if ok.
4772  */
parseAsfPicture(const TagLib::ASF::Picture & picture,Frame & frame)4773 bool parseAsfPicture(const TagLib::ASF::Picture& picture, Frame& frame)
4774 {
4775   if (!picture.isValid())
4776     return false;
4777 
4778   TagLib::ByteVector data = picture.picture();
4779   QString description(toQString(picture.description()));
4780   PictureFrame::setFields(frame, Frame::TE_ISO8859_1, QLatin1String("JPG"),
4781                           toQString(picture.mimeType()),
4782                           static_cast<PictureFrame::PictureType>(picture.type()),
4783                           description,
4784                           QByteArray(data.data(), data.size()));
4785   frame.setType(Frame::FT_Picture);
4786   return true;
4787 }
4788 
4789 /**
4790  * Render the bytes of a WM/Picture from a picture frame.
4791  *
4792  * @param frame   picture frame
4793  * @param picture the ASF picture is returned here
4794  */
renderAsfPicture(const Frame & frame,TagLib::ASF::Picture & picture)4795 void renderAsfPicture(const Frame& frame, TagLib::ASF::Picture& picture)
4796 {
4797   Frame::TextEncoding enc;
4798   PictureFrame::PictureType pictureType;
4799   QByteArray data;
4800   QString imgFormat, mimeType, description;
4801   PictureFrame::getFields(frame, enc, imgFormat, mimeType, pictureType,
4802                           description, data);
4803 
4804   if (frame.isValueChanged()) {
4805     description = frame.getValue();
4806   }
4807   picture.setMimeType(toTString(mimeType));
4808   picture.setType(static_cast<TagLib::ASF::Picture::Type>(pictureType));
4809   picture.setDescription(toTString(description));
4810   picture.setPicture(TagLib::ByteVector(data.data(), data.size()));
4811 }
4812 
4813 /**
4814  * Get an ASF attribute for a frame.
4815  *
4816  * @param frame     frame
4817  * @param valueType ASF value type
4818  *
4819  * @return ASF attribute, an empty attribute is returned if not supported.
4820  */
getAsfAttributeForFrame(const Frame & frame,TagLib::ASF::Attribute::AttributeTypes valueType)4821 TagLib::ASF::Attribute getAsfAttributeForFrame(
4822   const Frame& frame,
4823   TagLib::ASF::Attribute::AttributeTypes valueType)
4824 {
4825   switch (valueType) {
4826     case TagLib::ASF::Attribute::UnicodeType:
4827       return TagLib::ASF::Attribute(toTString(frame.getValue()));
4828     case TagLib::ASF::Attribute::BoolType:
4829       return TagLib::ASF::Attribute(frame.getValue() == QLatin1String("1"));
4830     case TagLib::ASF::Attribute::WordType:
4831       return TagLib::ASF::Attribute(frame.getValue().toUShort());
4832     case TagLib::ASF::Attribute::DWordType:
4833       return TagLib::ASF::Attribute(frame.getValue().toUInt());
4834     case TagLib::ASF::Attribute::QWordType:
4835       return TagLib::ASF::Attribute(frame.getValue().toULongLong());
4836     case TagLib::ASF::Attribute::BytesType:
4837     case TagLib::ASF::Attribute::GuidType:
4838     default:
4839       if (frame.getType() != Frame::FT_Picture) {
4840         QByteArray ba;
4841         if (AttributeData(frame.getInternalName()).toByteArray(frame.getValue(), ba)) {
4842           return TagLib::ASF::Attribute(TagLib::ByteVector(ba.data(), ba.size()));
4843         }
4844         QVariant fieldValue = frame.getFieldValue(Frame::ID_Data);
4845         if (fieldValue.isValid()) {
4846           ba = fieldValue.toByteArray();
4847           return TagLib::ASF::Attribute(TagLib::ByteVector(ba.data(), ba.size()));
4848         }
4849       }
4850       else {
4851         TagLib::ASF::Picture picture;
4852         renderAsfPicture(frame, picture);
4853         return TagLib::ASF::Attribute(picture);
4854       }
4855   }
4856   return TagLib::ASF::Attribute();
4857 }
4858 
4859 /**
4860  * Get a picture frame from the bytes in an APE cover art frame.
4861  * The cover art frame has the following data:
4862  * zero terminated description string (UTF-8), picture data.
4863  *
4864  * @param name key of APE item
4865  * @param data bytes in APE cover art frame
4866  * @param frame the picture frame is returned here
4867  */
parseApePicture(const QString & name,const TagLib::ByteVector & data,Frame & frame)4868 void parseApePicture(const QString& name,
4869                      const TagLib::ByteVector& data, Frame& frame)
4870 {
4871   QByteArray picture;
4872   TagLib::String description;
4873   // Do not search for a description if the first byte could start JPG or PNG
4874   // data.
4875   int picPos = data.isEmpty() || data.at(0) == '\xff' || data.at(0) == '\x89'
4876       ? -1 : data.find('\0');
4877   if (picPos >= 0) {
4878     description = TagLib::String(data.mid(0, picPos), TagLib::String::UTF8);
4879     picture = QByteArray(data.data() + picPos + 1, data.size() - picPos - 1);
4880   } else {
4881     picture = QByteArray(data.data(), data.size());
4882   }
4883   Frame::PictureType pictureType = Frame::PT_CoverFront;
4884   if (name.startsWith(QLatin1String("COVER ART (")) &&
4885       name.endsWith(QLatin1Char(')'))) {
4886     QString typeStr = name.mid(11);
4887     typeStr.chop(1);
4888     pictureType = PictureFrame::getPictureTypeFromString(typeStr.toLatin1());
4889   }
4890   PictureFrame::setFields(
4891         frame, Frame::TE_ISO8859_1, QLatin1String("JPG"),
4892         QLatin1String("image/jpeg"), pictureType,
4893         toQString(description), picture);
4894 }
4895 
4896 /**
4897  * Render the bytes of an APE cover art frame from a picture frame.
4898  *
4899  * @param frame picture frame
4900  * @param data  the bytes for the APE cover art are returned here
4901  */
renderApePicture(const Frame & frame,TagLib::ByteVector & data)4902 void renderApePicture(const Frame& frame, TagLib::ByteVector& data)
4903 {
4904   Frame::TextEncoding enc;
4905   PictureFrame::PictureType pictureType;
4906   QByteArray picture;
4907   QString imgFormat, mimeType, description;
4908   PictureFrame::getFields(frame, enc, imgFormat, mimeType, pictureType,
4909                           description, picture);
4910   if (frame.isValueChanged()) {
4911     description = frame.getValue();
4912   }
4913   data.append(toTString(description).data(TagLib::String::UTF8));
4914   data.append('\0');
4915   data.append(TagLib::ByteVector(picture.constData(), picture.size()));
4916 }
4917 
4918 #if TAGLIB_VERSION >= 0x010a00
4919 /**
4920  * Get name of INFO tag from type.
4921  *
4922  * @param type type
4923  *
4924  * @return name, NULL if not supported.
4925  */
getInfoNameFromType(Frame::Type type)4926 TagLib::ByteVector getInfoNameFromType(Frame::Type type)
4927 {
4928   static const char* const names[] = {
4929     "INAM",  // FT_Title,
4930     "IART",  // FT_Artist,
4931     "IPRD",  // FT_Album,
4932     "ICMT",  // FT_Comment,
4933     "ICRD",  // FT_Date,
4934     "IPRT",  // FT_Track
4935     "IGNR",  // FT_Genre,
4936              // FT_LastV1Frame = FT_Track,
4937     nullptr, // FT_AlbumArtist,
4938     "IENG",  // FT_Arranger,
4939     nullptr, // FT_Author,
4940     "IBPM",  // FT_Bpm,
4941     nullptr, // FT_CatalogNumber,
4942     nullptr, // FT_Compilation,
4943     "IMUS",  // FT_Composer,
4944     nullptr, // FT_Conductor,
4945     "ICOP",  // FT_Copyright,
4946     nullptr, // FT_Disc,
4947     "ITCH",  // FT_EncodedBy,
4948     "ISFT",  // FT_EncoderSettings,
4949     "IDIT",  // FT_EncodingTime,
4950     nullptr, // FT_Grouping,
4951     nullptr, // FT_InitialKey,
4952     "ISRC",  // FT_Isrc,
4953     "ILNG",  // FT_Language,
4954     "IWRI",  // FT_Lyricist,
4955     nullptr, // FT_Lyrics,
4956     "IMED",  // FT_Media,
4957     nullptr, // FT_Mood,
4958     nullptr, // FT_OriginalAlbum,
4959     nullptr, // FT_OriginalArtist,
4960     nullptr, // FT_OriginalDate,
4961     nullptr, // FT_Description,
4962     "ISTR",  // FT_Performer,
4963     nullptr, // FT_Picture,
4964     "IPUB",  // FT_Publisher,
4965     "ICNT",  // FT_ReleaseCountry,
4966     "IEDT",  // FT_Remixer,
4967     nullptr, // FT_SortAlbum,
4968     nullptr, // FT_SortAlbumArtist,
4969     nullptr, // FT_SortArtist,
4970     nullptr, // FT_SortComposer,
4971     nullptr, // FT_SortName,
4972     "PRT1",  // FT_Subtitle,
4973     "IBSU",  // FT_Website,
4974     nullptr, // FT_WWWAudioFile,
4975     nullptr, // FT_WWWAudioSource,
4976     nullptr, // FT_ReleaseDate,
4977     "IRTD",  // FT_Rating,
4978     nullptr, // FT_Work,
4979              // FT_LastFrame = FT_Work
4980   };
4981   Q_STATIC_ASSERT(sizeof(names) / sizeof(names[0]) == Frame::FT_LastFrame + 1);
4982   if (type == Frame::FT_Track) {
4983     QByteArray ba = TagConfig::instance().riffTrackName().toLatin1();
4984     return TagLib::ByteVector(ba.constData(), ba.size());
4985   }
4986   const char* name = type <= Frame::FT_LastFrame ? names[type] : nullptr;
4987   return name ? TagLib::ByteVector(name, 4) : TagLib::ByteVector();
4988 }
4989 
4990 /**
4991  * Get the frame type for an INFO name.
4992  *
4993  * @param id INFO tag name
4994  *
4995  * @return frame type.
4996  */
getTypeFromInfoName(const TagLib::ByteVector & id)4997 Frame::Type getTypeFromInfoName(const TagLib::ByteVector& id)
4998 {
4999   static QMap<TagLib::ByteVector, int> strNumMap;
5000   if (strNumMap.isEmpty()) {
5001     // first time initialization
5002     for (int i = 0; i <= Frame::FT_LastFrame; ++i) {
5003       auto type = static_cast<Frame::Type>(i);
5004       TagLib::ByteVector str = getInfoNameFromType(type);
5005       if (!str.isEmpty()) {
5006         strNumMap.insert(str, type);
5007       }
5008     }
5009     QStringList riffTrackNames = TagConfig::getRiffTrackNames();
5010     riffTrackNames.append(TagConfig::instance().riffTrackName());
5011     const auto constRiffTrackNames = riffTrackNames;
5012     for (const QString& str : constRiffTrackNames) {
5013       QByteArray ba = str.toLatin1();
5014       strNumMap.insert(TagLib::ByteVector(ba.constData(), ba.size()),
5015                        Frame::FT_Track);
5016     }
5017   }
5018   auto it = strNumMap.constFind(id);
5019   if (it != strNumMap.constEnd()) {
5020     return static_cast<Frame::Type>(*it);
5021   }
5022   return Frame::FT_Other;
5023 }
5024 
5025 /**
5026  * Get internal name of an INFO frame.
5027  *
5028  * @param frame frame
5029  *
5030  * @return INFO id, "IKEY" if not found.
5031  */
getInfoName(const Frame & frame)5032 TagLib::ByteVector getInfoName(const Frame& frame)
5033 {
5034   TagLib::ByteVector str = getInfoNameFromType(frame.getType());
5035   if (!str.isEmpty()) {
5036     return str;
5037   }
5038 
5039   QString name = frame.getInternalName();
5040   if (name.length() >= 4) {
5041     QByteArray ba = name.left(4).toUpper().toLatin1();
5042     return TagLib::ByteVector(ba.constData(), 4);
5043   }
5044 
5045   return "IKEY";
5046 }
5047 #endif
5048 
5049 }
5050 
5051 /**
5052  * Get a specific frame from the tags.
5053  *
5054  * @param tagNr tag number
5055  * @param type  frame type
5056  * @param frame the frame is returned here
5057  *
5058  * @return true if ok.
5059  */
getFrame(Frame::TagNumber tagNr,Frame::Type type,Frame & frame) const5060 bool TagLibFile::getFrame(Frame::TagNumber tagNr, Frame::Type type, Frame& frame) const
5061 {
5062   if (tagNr >= NUM_TAGS)
5063     return false;
5064 
5065   makeFileOpen();
5066   TagLib::Tag* tag = m_tag[tagNr];
5067   TagLib::String tstr;
5068   if (tag) {
5069     switch (type) {
5070     case Frame::FT_Album:
5071       tstr = tag->album();
5072       break;
5073     case Frame::FT_Artist:
5074       tstr = tag->artist();
5075       break;
5076     case Frame::FT_Comment:
5077       tstr = tag->comment();
5078       if (tagNr == Frame::Tag_Id3v1
5079 #if TAGLIB_VERSION < 0x010b01
5080           && !tstr.isNull()
5081 #endif
5082           ) {
5083         tstr = tstr.substr(0, 28);
5084       }
5085       break;
5086     case Frame::FT_Date:
5087     {
5088       uint nr = tag->year();
5089       tstr = nr != 0 ? TagLib::String::number(nr) : "";
5090       break;
5091     }
5092     case Frame::FT_Genre:
5093       tstr = tag->genre();
5094       break;
5095     case Frame::FT_Title:
5096       tstr = tag->title();
5097       break;
5098     case Frame::FT_Track:
5099     {
5100       uint nr = tag->track();
5101       tstr = nr != 0 ? TagLib::String::number(nr) : "";
5102       break;
5103     }
5104     default:
5105       // maybe handled in a subclass
5106       return false;
5107     }
5108 #if TAGLIB_VERSION >= 0x010b01
5109     QString str = tagNr != Frame::Tag_Id3v1 && type == Frame::FT_Genre
5110         ? getGenreString(tstr) : toQString(tstr);
5111 #else
5112     QString str = tagNr != Frame::Tag_Id3v1 && type == Frame::FT_Genre
5113         ? getGenreString(tstr)
5114         : tstr.isNull() ? QLatin1String("") : toQString(tstr);
5115 #endif
5116     frame.setValue(str);
5117   } else {
5118     frame.setValue(QString());
5119   }
5120   frame.setType(type);
5121   return true;
5122 }
5123 
5124 /**
5125  * Set a frame in the tags.
5126  *
5127  * @param tagNr tag number
5128  * @param frame frame to set
5129  *
5130  * @return true if ok.
5131  */
setFrame(Frame::TagNumber tagNr,const Frame & frame)5132 bool TagLibFile::setFrame(Frame::TagNumber tagNr, const Frame& frame)
5133 {
5134   if (tagNr >= NUM_TAGS)
5135     return false;
5136 
5137   if (tagNr != Frame::Tag_Id3v1) {
5138     makeFileOpen();
5139     // If the frame has an index, change that specific frame
5140     int index = frame.getIndex();
5141     if (index != -1 && m_tag[tagNr]) {
5142       TagLib::ID3v2::Tag* id3v2Tag;
5143       TagLib::Ogg::XiphComment* oggTag;
5144       TagLib::APE::Tag* apeTag;
5145       TagLib::MP4::Tag* mp4Tag;
5146       TagLib::ASF::Tag* asfTag;
5147       if ((id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr])) != nullptr) {
5148         const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList();
5149         if (index >= 0 && index < static_cast<int>(frameList.size())) {
5150           // This is a hack. The frameList should not be modified directly.
5151           // However when removing the old frame and adding a new frame,
5152           // the indices of all frames get invalid.
5153           setId3v2Frame(this, frameList[index], frame);
5154           markTagChanged(tagNr, frame.getType());
5155           return true;
5156         }
5157       } else if ((oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[tagNr])) != nullptr) {
5158         QString frameValue(frame.getValue());
5159         if (frame.getType() == Frame::FT_Picture) {
5160           if (m_pictures.isRead()) {
5161             int idx = Frame::fromNegativeIndex(frame.getIndex());
5162             if (idx >= 0 && idx < m_pictures.size()) {
5163               Frame newFrame(frame);
5164               PictureFrame::setDescription(newFrame, frameValue);
5165               if (PictureFrame::areFieldsEqual(m_pictures[idx], newFrame)) {
5166                 m_pictures[idx].setValueChanged(false);
5167               } else {
5168                 m_pictures[idx] = newFrame;
5169                 markTagChanged(tagNr, Frame::FT_Picture);
5170               }
5171               return true;
5172             } else {
5173               return false;
5174             }
5175           } else {
5176             Frame newFrame(frame);
5177             PictureFrame::setDescription(newFrame, frameValue);
5178             PictureFrame::getFieldsToBase64(newFrame, frameValue);
5179             if (!frameValue.isEmpty() &&
5180                 frame.getInternalName() == QLatin1String("COVERART")) {
5181               QString mimeType;
5182               PictureFrame::getMimeType(frame, mimeType);
5183               oggTag->addField("COVERARTMIME", toTString(mimeType), true);
5184             }
5185           }
5186         }
5187         TagLib::String key = toTString(getVorbisName(frame));
5188         TagLib::String value = toTString(frameValue);
5189         const TagLib::Ogg::FieldListMap& fieldListMap = oggTag->fieldListMap();
5190         if (fieldListMap.contains(key) && fieldListMap[key].size() > 1) {
5191           int i = 0;
5192           bool found = false;
5193           for (auto it = fieldListMap.begin();
5194                it != fieldListMap.end();
5195                ++it) {
5196             TagLib::StringList stringList = (*it).second;
5197             for (auto slit = stringList.begin(); slit != stringList.end(); ++slit) {
5198               if (i++ == index) {
5199                 *slit = value;
5200                 found = true;
5201                 break;
5202               }
5203             }
5204             if (found) {
5205               // Replace all fields with this key to preserve the order.
5206 #if TAGLIB_VERSION >= 0x010b01
5207               oggTag->removeFields(key);
5208 #else
5209               oggTag->removeField(key);
5210 #endif
5211               for (auto slit = stringList.begin(); slit != stringList.end(); ++slit) {
5212                 oggTag->addField(key, *slit, false);
5213               }
5214               break;
5215             }
5216           }
5217         } else {
5218           oggTag->addField(key, value, true);
5219         }
5220         if (frame.getType() == Frame::FT_Track) {
5221           int numTracks = getTotalNumberOfTracksIfEnabled();
5222           if (numTracks > 0) {
5223             oggTag->addField("TRACKTOTAL", TagLib::String::number(numTracks), true);
5224           }
5225         }
5226         markTagChanged(tagNr, frame.getType());
5227         return true;
5228       } else if ((apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tag[tagNr])) != nullptr) {
5229         if (frame.getType() == Frame::FT_Picture) {
5230           TagLib::ByteVector data;
5231           renderApePicture(frame, data);
5232           QString oldName = frame.getInternalName();
5233           QString newName = getApeName(frame);
5234           if (newName != oldName) {
5235             // If the picture type changes, the frame with the old name has to
5236             // be replaced with a frame with the new name.
5237             apeTag->removeItem(toTString(oldName));
5238           }
5239           apeTag->setData(toTString(newName), data);
5240         } else {
5241           apeTag->addValue(toTString(getApeName(frame)),
5242                            toTString(frame.getValue()));
5243         }
5244         markTagChanged(tagNr, frame.getType());
5245         return true;
5246       } else if ((mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[tagNr])) != nullptr) {
5247         if (frame.getType() == Frame::FT_Picture) {
5248           if (m_pictures.isRead()) {
5249             int idx = Frame::fromNegativeIndex(frame.getIndex());
5250             if (idx >= 0 && idx < m_pictures.size()) {
5251               Frame newFrame(frame);
5252               if (PictureFrame::areFieldsEqual(m_pictures[idx], newFrame)) {
5253                 m_pictures[idx].setValueChanged(false);
5254               } else {
5255                 m_pictures[idx] = newFrame;
5256                 markTagChanged(tagNr, Frame::FT_Picture);
5257               }
5258               return true;
5259             } else {
5260               return false;
5261             }
5262           }
5263         }
5264         setMp4Frame(frame, mp4Tag);
5265         return true;
5266       } else if ((asfTag = dynamic_cast<TagLib::ASF::Tag*>(m_tag[tagNr])) != nullptr) {
5267         switch (index) {
5268           case AFI_Title:
5269             asfTag->setTitle(toTString(frame.getValue()));
5270             break;
5271           case AFI_Artist:
5272             asfTag->setArtist(toTString(frame.getValue()));
5273             break;
5274           case AFI_Comment:
5275             asfTag->setComment(toTString(frame.getValue()));
5276             break;
5277           case AFI_Copyright:
5278             asfTag->setCopyright(toTString(frame.getValue()));
5279             break;
5280           case AFI_Rating:
5281             asfTag->setRating(toTString(frame.getValue()));
5282             break;
5283           case AFI_Attributes:
5284           default:
5285           {
5286             TagLib::String name;
5287             TagLib::ASF::Attribute::AttributeTypes valueType;
5288             getAsfTypeForFrame(frame, name, valueType);
5289             TagLib::ASF::Attribute attribute =
5290               getAsfAttributeForFrame(frame, valueType);
5291             TagLib::ASF::AttributeListMap& attrListMap = asfTag->attributeListMap();
5292             if (attrListMap.contains(name) && attrListMap[name].size() > 1) {
5293               int i = AFI_Attributes;
5294               bool found = false;
5295               for (auto it = attrListMap.begin();
5296                    it != attrListMap.end();
5297                    ++it) {
5298                 TagLib::ASF::AttributeList& attrList = (*it).second;
5299                 for (auto ait = attrList.begin();
5300                      ait != attrList.end();
5301                      ++ait) {
5302                   if (i++ == index) {
5303                     found = true;
5304                     *ait = attribute;
5305                     break;
5306                   }
5307                 }
5308                 if (found) {
5309                   break;
5310                 }
5311               }
5312             } else {
5313               asfTag->setAttribute(name, attribute);
5314             }
5315           }
5316         }
5317         markTagChanged(tagNr, frame.getType());
5318         return true;
5319 #if TAGLIB_VERSION >= 0x010a00
5320       } else if (auto infoTag =
5321                  dynamic_cast<TagLib::RIFF::Info::Tag*>(m_tag[tagNr])) {
5322         infoTag->setFieldText(getInfoName(frame), toTString(frame.getValue()));
5323         markTagChanged(tagNr, frame.getType());
5324         return true;
5325 #endif
5326       }
5327     }
5328   }
5329 
5330   // Try the basic method
5331   QString str = frame.getValue();
5332   if (makeTagSettable(tagNr) && !str.isNull()) {
5333     TagLib::Tag* tag = m_tag[tagNr];
5334     if (!tag)
5335       return false;
5336     Frame::Type type = frame.getType();
5337 #if TAGLIB_VERSION >= 0x010b01
5338     TagLib::String tstr = toTString(str);
5339 #else
5340     TagLib::String tstr = str.isEmpty() ? TagLib::String::null : toTString(str);
5341 #endif
5342     TagLib::String oldTstr;
5343     uint oldNum;
5344     const char* frameId = nullptr;
5345     switch (type) {
5346     case Frame::FT_Album:
5347       oldTstr = tag->album();
5348       frameId = "TALB";
5349       break;
5350     case Frame::FT_Comment:
5351       oldTstr = tag->comment();
5352       frameId = "COMM";
5353       break;
5354     case Frame::FT_Artist:
5355       oldTstr = tag->artist();
5356       frameId = "TPE1";
5357       break;
5358     case Frame::FT_Title:
5359       oldTstr = tag->title();
5360       frameId = "TIT2";
5361       break;
5362     case Frame::FT_Genre:
5363       oldTstr = tag->genre();
5364       frameId = "TCON";
5365       break;
5366     case Frame::FT_Date:
5367       oldNum = tag->year();
5368       frameId = "TDRC";
5369       break;
5370     case Frame::FT_Track:
5371       oldNum = tag->track();
5372       frameId = "TRCK";
5373       break;
5374     default:
5375       return false;
5376     }
5377     if (type == Frame::FT_Date) {
5378       int num = frame.getValueAsNumber();
5379       if (tagNr == Frame::Tag_Id3v1) {
5380         if (num >= 0 && num != static_cast<int>(oldNum)) {
5381           tag->setYear(num);
5382           markTagChanged(tagNr, type);
5383         }
5384       } else {
5385         if (num > 0 && num != static_cast<int>(oldNum) &&
5386             getDefaultTextEncoding() == TagLib::String::Latin1) {
5387           tag->setYear(num);
5388           markTagChanged(tagNr, type);
5389         } else if (num == 0 || num != static_cast<int>(oldNum)){
5390           QString yearStr;
5391           if (num != 0) {
5392             yearStr.setNum(num);
5393           } else {
5394             yearStr = frame.getValue();
5395           }
5396 #if TAGLIB_VERSION >= 0x010b01
5397           TagLib::String tstr = toTString(yearStr);
5398 #else
5399           TagLib::String tstr =
5400               yearStr.isEmpty() ? TagLib::String::null : toTString(yearStr);
5401 #endif
5402           bool ok = false;
5403           if (dynamic_cast<TagLib::ID3v2::Tag*>(tag) != nullptr) {
5404             ok = setId3v2Unicode(tag, yearStr, tstr, frameId);
5405           } else if (auto mp4Tag =
5406                      dynamic_cast<TagLib::MP4::Tag*>(tag)) {
5407             TagLib::String name;
5408             Mp4ValueType valueType;
5409             getMp4NameForType(type, name, valueType);
5410             TagLib::MP4::Item item = TagLib::MP4::Item(tstr);
5411             ok = valueType == MVT_String && item.isValid();
5412             if (ok) {
5413 #if TAGLIB_VERSION >= 0x010b01
5414               mp4Tag->setItem(name, item);
5415 #else
5416               mp4Tag->itemListMap()[name] = item;
5417 #endif
5418             }
5419           } else if (auto oggTag =
5420                      dynamic_cast<TagLib::Ogg::XiphComment*>(tag)) {
5421             oggTag->addField(getVorbisNameFromType(type), tstr, true);
5422             ok = true;
5423           }
5424           if (!ok) {
5425             tag->setYear(num);
5426           }
5427           markTagChanged(tagNr, type);
5428         }
5429       }
5430     } else if (type == Frame::FT_Track) {
5431       int num = frame.getValueAsNumber();
5432       if (num >= 0 && num != static_cast<int>(oldNum)) {
5433         if (tagNr == Frame::Tag_Id3v1) {
5434           int n = checkTruncation(tagNr, num, 1ULL << type);
5435           if (n != -1) {
5436             num = n;
5437           }
5438           tag->setTrack(num);
5439         } else {
5440           int numTracks;
5441           num = splitNumberAndTotal(str, &numTracks);
5442           QString trackStr = trackNumberString(num, numTracks);
5443           if (num != static_cast<int>(oldNum)) {
5444             auto id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(tag);
5445             TagLib::MP4::Tag* mp4Tag;
5446             if (id3v2Tag) {
5447 #if TAGLIB_VERSION >= 0x010b01
5448               TagLib::String tstr = toTString(trackStr);
5449 #else
5450               TagLib::String tstr =
5451                 trackStr.isEmpty() ? TagLib::String::null : toTString(trackStr);
5452 #endif
5453               if (!setId3v2Unicode(tag, trackStr, tstr, frameId)) {
5454                 TagLib::ID3v2::TextIdentificationFrame* frame =
5455                     new TagLib::ID3v2::TextIdentificationFrame(
5456                       frameId, getDefaultTextEncoding());
5457                 frame->setText(tstr);
5458                 id3v2Tag->removeFrames(frameId);
5459 #ifdef Q_OS_WIN32
5460                 // freed in Windows DLL => must be allocated in the same DLL
5461                 TagLib::ID3v2::Frame* dllAllocatedFrame =
5462                   TagLib::ID3v2::FrameFactory::instance()->createFrame(frame->render());
5463                 if (dllAllocatedFrame) {
5464                   id3v2Tag->addFrame(dllAllocatedFrame);
5465                 }
5466                 delete frame;
5467 #else
5468                 id3v2Tag->addFrame(frame);
5469 #endif
5470               }
5471             } else if ((mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(tag)) != nullptr) {
5472               // Set a frame in order to store the total number too.
5473               Frame frame(Frame::FT_Track, str, QLatin1String(""), -1);
5474               setMp4Frame(frame, mp4Tag);
5475 #if TAGLIB_VERSION >= 0x010a00
5476             } else if (auto infoTag =
5477                        dynamic_cast<TagLib::RIFF::Info::Tag*>(tag)) {
5478               infoTag->setFieldText(getInfoNameFromType(Frame::FT_Track),
5479                                     toTString(trackStr));
5480 #endif
5481             } else {
5482               tag->setTrack(num);
5483             }
5484           }
5485         }
5486         markTagChanged(tagNr, type);
5487       }
5488     } else {
5489       if (!(tstr == oldTstr)) {
5490         if (!setId3v2Unicode(tag, str, tstr, frameId)) {
5491           QString s = checkTruncation(tagNr, str, 1ULL << type,
5492                                       type == Frame::FT_Comment ? 28 : 30);
5493           if (!s.isNull()) {
5494             tstr = toTString(s);
5495           }
5496           switch (type) {
5497           case Frame::FT_Album:
5498             tag->setAlbum(tstr);
5499             break;
5500           case Frame::FT_Comment:
5501             tag->setComment(tstr);
5502             break;
5503           case Frame::FT_Artist:
5504             tag->setArtist(tstr);
5505             break;
5506           case Frame::FT_Title:
5507             tag->setTitle(tstr);
5508             break;
5509           case Frame::FT_Genre:
5510             if (tagNr == Frame::Tag_Id3v1) {
5511               tag->setGenre(tstr);
5512               // if the string cannot be converted to a number, set the truncation flag
5513               checkTruncation(tagNr, !str.isEmpty() && Genres::getNumber(str) == 0xff
5514                               ? 1 : 0, 1ULL << type, 0);
5515             } else {
5516               TagLib::ID3v2::TextIdentificationFrame* frame;
5517               auto id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(tag);
5518               if (id3v2Tag && TagConfig::instance().genreNotNumeric() &&
5519                   (frame = new TagLib::ID3v2::TextIdentificationFrame(
5520                     frameId, getDefaultTextEncoding())) != nullptr) {
5521                 frame->setText(tstr);
5522                 id3v2Tag->removeFrames(frameId);
5523 #ifdef Q_OS_WIN32
5524                 // freed in Windows DLL => must be allocated in the same DLL
5525                 TagLib::ID3v2::Frame* dllAllocatedFrame =
5526                   TagLib::ID3v2::FrameFactory::instance()->createFrame(frame->render());
5527                 if (dllAllocatedFrame) {
5528                   id3v2Tag->addFrame(dllAllocatedFrame);
5529                 }
5530                 delete frame;
5531 #else
5532                 id3v2Tag->addFrame(frame);
5533 #endif
5534               } else {
5535                 tag->setGenre(tstr);
5536               }
5537             }
5538             break;
5539           default:
5540             return false;
5541           }
5542         }
5543         markTagChanged(tagNr, type);
5544       }
5545     }
5546   }
5547   return true;
5548 }
5549 
5550 namespace {
5551 
5552 /**
5553  * Check if an ID3v2.4.0 frame ID is valid.
5554  *
5555  * @param frameId frame ID (4 characters)
5556  *
5557  * @return true if frame ID is valid.
5558  */
isFrameIdValid(const QString & frameId)5559 bool isFrameIdValid(const QString& frameId)
5560 {
5561   Frame::Type type;
5562   const char* str;
5563   getTypeStringForFrameId(TagLib::ByteVector(frameId.toLatin1().data(), 4), type, str);
5564   return type != Frame::FT_UnknownFrame;
5565 }
5566 
5567 /**
5568  * Create a TagLib ID3 frame from a frame.
5569  * @param self this TagLibFile instance
5570  * @param frame frame
5571  * @return TagLib ID3 frame, 0 if invalid.
5572  */
createId3FrameFromFrame(const TagLibFile * self,Frame & frame)5573 TagLib::ID3v2::Frame* createId3FrameFromFrame(const TagLibFile* self,
5574                                               Frame& frame)
5575 {
5576   TagLib::String::Type enc = TagLibFile::getDefaultTextEncoding();
5577   QString name = frame.getType() != Frame::FT_Other
5578       ? QString::fromLatin1(getStringForType(frame.getType()))
5579       : frame.getName();
5580   QString frameId = name;
5581   frameId.truncate(4);
5582   TagLib::ID3v2::Frame* id3Frame = nullptr;
5583 
5584   if (name == QLatin1String("AverageLevel") ||
5585       name == QLatin1String("PeakValue") ||
5586       name.startsWith(QLatin1String("WM/"))) {
5587     frameId = QLatin1String("PRIV");
5588   } else if (name.startsWith(QLatin1String("iTun"))) {
5589     frameId = QLatin1String("COMM");
5590   }
5591 
5592   if (frameId.startsWith(QLatin1String("T"))
5593 #if TAGLIB_VERSION >= 0x010b00
5594       || frameId == QLatin1String("WFED")
5595 #endif
5596 #if TAGLIB_VERSION >= 0x010c00
5597       || frameId == QLatin1String("MVIN") || frameId == QLatin1String("MVNM")
5598       || frameId == QLatin1String("GRP1")
5599 #endif
5600     ) {
5601     if (frameId == QLatin1String("TXXX")) {
5602       id3Frame = new TagLib::ID3v2::UserTextIdentificationFrame(enc);
5603     } else if (isFrameIdValid(frameId)) {
5604       id3Frame = new TagLib::ID3v2::TextIdentificationFrame(
5605         TagLib::ByteVector(frameId.toLatin1().data(), frameId.length()), enc);
5606       id3Frame->setText(""); // is necessary for createFrame() to work
5607     }
5608   } else if (frameId == QLatin1String("COMM")) {
5609     auto commFrame =
5610         new TagLib::ID3v2::CommentsFrame(enc);
5611     id3Frame = commFrame;
5612     commFrame->setLanguage("eng"); // for compatibility with iTunes
5613     if (frame.getType() == Frame::FT_Other) {
5614       commFrame->setDescription(toTString(frame.getName()));
5615     }
5616   } else if (frameId == QLatin1String("APIC")) {
5617     id3Frame = new TagLib::ID3v2::AttachedPictureFrame;
5618     static_cast<TagLib::ID3v2::AttachedPictureFrame*>(id3Frame)->setTextEncoding(enc);
5619     static_cast<TagLib::ID3v2::AttachedPictureFrame*>(id3Frame)->setMimeType(
5620       "image/jpeg");
5621     static_cast<TagLib::ID3v2::AttachedPictureFrame*>(id3Frame)->setType(
5622       TagLib::ID3v2::AttachedPictureFrame::FrontCover);
5623   } else if (frameId == QLatin1String("UFID")) {
5624     // the bytevector must not be empty
5625     TagLib::ID3v2::UniqueFileIdentifierFrame* ufidFrame =
5626         new TagLib::ID3v2::UniqueFileIdentifierFrame(
5627                   TagLib::String("http://www.id3.org/dummy/ufid.html"),
5628                   TagLib::ByteVector(" "));
5629     id3Frame = ufidFrame;
5630     QByteArray data;
5631     if (AttributeData::isHexString(frame.getValue(), 'Z', QLatin1String("-"))) {
5632       data = (frame.getValue() + QLatin1Char('\0')).toLatin1();
5633       ufidFrame->setIdentifier(TagLib::ByteVector(data.constData(),
5634                                                   data.size()));
5635     }
5636   } else if (frameId == QLatin1String("GEOB")) {
5637     id3Frame = new TagLib::ID3v2::GeneralEncapsulatedObjectFrame;
5638     static_cast<TagLib::ID3v2::GeneralEncapsulatedObjectFrame*>(id3Frame)->setTextEncoding(enc);
5639   } else if (frameId.startsWith(QLatin1String("W"))) {
5640     if (frameId == QLatin1String("WXXX")) {
5641       id3Frame = new TagLib::ID3v2::UserUrlLinkFrame(enc);
5642     } else if (isFrameIdValid(frameId)) {
5643       id3Frame = new TagLib::ID3v2::UrlLinkFrame(
5644         TagLib::ByteVector(frameId.toLatin1().data(), frameId.length()));
5645       id3Frame->setText("http://"); // is necessary for createFrame() to work
5646     }
5647   } else if (frameId == QLatin1String("USLT")) {
5648     id3Frame = new TagLib::ID3v2::UnsynchronizedLyricsFrame(enc);
5649     static_cast<TagLib::ID3v2::UnsynchronizedLyricsFrame*>(id3Frame)->setLanguage("eng");
5650   } else if (frameId == QLatin1String("SYLT")) {
5651     id3Frame = new TagLib::ID3v2::SynchronizedLyricsFrame(enc);
5652     static_cast<TagLib::ID3v2::SynchronizedLyricsFrame*>(id3Frame)->setLanguage("eng");
5653   } else if (frameId == QLatin1String("ETCO")) {
5654     id3Frame = new TagLib::ID3v2::EventTimingCodesFrame;
5655   } else if (frameId == QLatin1String("POPM")) {
5656     auto popmFrame =
5657         new TagLib::ID3v2::PopularimeterFrame;
5658     id3Frame = popmFrame;
5659     popmFrame->setEmail(toTString(TagConfig::instance().defaultPopmEmail()));
5660   } else if (frameId == QLatin1String("PRIV")) {
5661     auto privFrame =
5662         new TagLib::ID3v2::PrivateFrame;
5663     id3Frame = privFrame;
5664     if (!frame.getName().startsWith(QLatin1String("PRIV"))) {
5665       privFrame->setOwner(toTString(frame.getName()));
5666       QByteArray data;
5667       if (AttributeData(frame.getName()).toByteArray(frame.getValue(), data)) {
5668         privFrame->setData(TagLib::ByteVector(data.constData(), data.size()));
5669       }
5670     }
5671   } else if (frameId == QLatin1String("OWNE")) {
5672     id3Frame = new TagLib::ID3v2::OwnershipFrame(enc);
5673   } else if (frameId == QLatin1String("RVA2")) {
5674     id3Frame = new TagLib::ID3v2::RelativeVolumeFrame;
5675 #if TAGLIB_VERSION >= 0x010b00
5676   } else if (frameId == QLatin1String("PCST")) {
5677     id3Frame = new TagLib::ID3v2::PodcastFrame;
5678 #endif
5679 #if TAGLIB_VERSION >= 0x010a00
5680   } else if (frameId == QLatin1String("CHAP")) {
5681     // crashes with an empty elementID
5682     id3Frame = new TagLib::ID3v2::ChapterFrame("chp", 0, 0,
5683                                                0xffffffff, 0xffffffff);
5684   } else if (frameId == QLatin1String("CTOC")) {
5685     // crashes with an empty elementID
5686     id3Frame = new TagLib::ID3v2::TableOfContentsFrame("toc");
5687 #endif
5688   }
5689   if (!id3Frame) {
5690     auto txxxFrame =
5691       new TagLib::ID3v2::UserTextIdentificationFrame(enc);
5692     TagLib::String description;
5693     if (frame.getType() == Frame::FT_CatalogNumber) {
5694       description = "CATALOGNUMBER";
5695     } else if (frame.getType() == Frame::FT_ReleaseCountry) {
5696       description = "RELEASECOUNTRY";
5697     } else if (frame.getType() == Frame::FT_Grouping) {
5698       description = "GROUPING";
5699     } else if (frame.getType() == Frame::FT_Subtitle) {
5700       description = "SUBTITLE";
5701     } else {
5702       description = toTString(frame.getName());
5703       frame.setExtendedType(Frame::ExtendedType(Frame::FT_Other,
5704                     QLatin1String("TXXX - User defined text information")));
5705     }
5706     txxxFrame->setDescription(description);
5707     id3Frame = txxxFrame;
5708   } else {
5709     frame.setExtendedType(Frame::ExtendedType(frame.getType(), name));
5710   }
5711   if (id3Frame) {
5712     if (!frame.fieldList().empty()) {
5713       frame.setValueFromFieldList();
5714       setId3v2Frame(self, id3Frame, frame);
5715     }
5716   }
5717   return id3Frame;
5718 }
5719 
5720 }
5721 
5722 /**
5723  * Add a frame in the tags.
5724  *
5725  * @param tagNr tag number
5726  * @param frame frame to add, a field list may be added by this method
5727  *
5728  * @return true if ok.
5729  */
addFrame(Frame::TagNumber tagNr,Frame & frame)5730 bool TagLibFile::addFrame(Frame::TagNumber tagNr, Frame& frame)
5731 {
5732   if (tagNr >= NUM_TAGS)
5733     return false;
5734 
5735   if (tagNr != Frame::Tag_Id3v1) {
5736     // Add a new frame.
5737     if (makeTagSettable(tagNr)) {
5738       TagLib::ID3v2::Tag* id3v2Tag;
5739       TagLib::Ogg::XiphComment* oggTag;
5740       TagLib::APE::Tag* apeTag;
5741       TagLib::MP4::Tag* mp4Tag;
5742       TagLib::ASF::Tag* asfTag;
5743       if ((id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr])) != nullptr) {
5744         TagLib::ID3v2::Frame* id3Frame = createId3FrameFromFrame(this, frame);
5745         if (id3Frame) {
5746 #ifdef Q_OS_WIN32
5747           // freed in Windows DLL => must be allocated in the same DLL
5748           TagLib::ID3v2::Frame* dllAllocatedFrame =
5749             TagLib::ID3v2::FrameFactory::instance()->createFrame(id3Frame->render());
5750           if (dllAllocatedFrame) {
5751             id3v2Tag->addFrame(dllAllocatedFrame);
5752           }
5753 #else
5754           id3v2Tag->addFrame(id3Frame);
5755 #endif
5756           frame.setIndex(id3v2Tag->frameList().size() - 1);
5757           if (frame.fieldList().empty()) {
5758             // add field list to frame
5759             getFieldsFromId3Frame(id3Frame, frame.fieldList(), frame.getType());
5760             frame.setFieldListFromValue();
5761           }
5762           if (frame.getType() == Frame::FT_Other) {
5763             // Set the correct frame type if the frame was added using the ID.
5764             Frame::Type type;
5765             const char* str;
5766             getTypeStringForFrameId(id3Frame->frameID(), type, str);
5767             if (type != Frame::FT_UnknownFrame) {
5768               frame.setExtendedType(
5769                     Frame::ExtendedType(type, QString::fromLatin1(str)));
5770             }
5771           }
5772 #ifdef Q_OS_WIN32
5773           delete id3Frame;
5774 #endif
5775           markTagChanged(tagNr, frame.getType());
5776           return true;
5777         }
5778       } else if ((oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[tagNr])) != nullptr) {
5779         QString name(getVorbisName(frame));
5780         QString value(frame.getValue());
5781         if (frame.getType() == Frame::FT_Picture) {
5782           if (frame.getFieldList().empty()) {
5783             PictureFrame::setFields(
5784               frame, Frame::TE_ISO8859_1, QLatin1String("JPG"), QLatin1String("image/jpeg"),
5785               PictureFrame::PT_CoverFront, QLatin1String(""), QByteArray());
5786           }
5787           if (m_pictures.isRead()) {
5788             PictureFrame::setDescription(frame, value);
5789             frame.setIndex(Frame::toNegativeIndex(m_pictures.size()));
5790             m_pictures.append(frame);
5791             markTagChanged(tagNr, Frame::FT_Picture);
5792             return true;
5793           } else {
5794             PictureFrame::getFieldsToBase64(frame, value);
5795           }
5796         }
5797         TagLib::String tname = toTString(name);
5798         TagLib::String tvalue = toTString(value);
5799         if (tvalue.isEmpty()) {
5800           tvalue = " "; // empty values are not added by TagLib
5801         }
5802         oggTag->addField(tname, tvalue, false);
5803         frame.setExtendedType(Frame::ExtendedType(frame.getType(), name));
5804 
5805         const TagLib::Ogg::FieldListMap& fieldListMap = oggTag->fieldListMap();
5806         int index = 0;
5807         bool found = false;
5808         for (auto it = fieldListMap.begin();
5809              it != fieldListMap.end();
5810              ++it) {
5811           if ((*it).first == tname) {
5812             index += (*it).second.size() - 1;
5813             found = true;
5814             break;
5815           }
5816           index += (*it).second.size();
5817         }
5818         frame.setIndex(found ? index : -1);
5819         markTagChanged(tagNr, frame.getType());
5820         return true;
5821       } else if ((apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tag[tagNr])) != nullptr) {
5822         if (frame.getType() == Frame::FT_Picture &&
5823             frame.getFieldList().isEmpty()) {
5824           // Do not replace an already existing picture.
5825           Frame::PictureType pictureType = Frame::PT_CoverFront;
5826           const TagLib::APE::ItemListMap& itemListMap = apeTag->itemListMap();
5827           for (int i = Frame::PT_CoverFront; i <= Frame::PT_PublisherLogo; ++i) {
5828             auto pt = static_cast<Frame::PictureType>(i);
5829             if (itemListMap.find(getApePictureName(pt)) == itemListMap.end()) {
5830               pictureType = pt;
5831               break;
5832             }
5833           }
5834           PictureFrame::setFields(
5835                 frame, Frame::TE_ISO8859_1, QLatin1String("JPG"),
5836                 QLatin1String("image/jpeg"), pictureType);
5837         }
5838         QString name(getApeName(frame));
5839         TagLib::String tname = toTString(name);
5840         if (frame.getType() == Frame::FT_Picture) {
5841           TagLib::ByteVector data;
5842           renderApePicture(frame, data);
5843           apeTag->setData(tname, data);
5844         } else {
5845           TagLib::String tvalue = toTString(frame.getValue());
5846           if (tvalue.isEmpty()) {
5847             tvalue = " "; // empty values are not added by TagLib
5848           }
5849           apeTag->addValue(tname, tvalue, true);
5850         }
5851         frame.setExtendedType(Frame::ExtendedType(frame.getType(), name));
5852 
5853         const TagLib::APE::ItemListMap& itemListMap = apeTag->itemListMap();
5854         int index = 0;
5855         bool found = false;
5856         for (auto it = itemListMap.begin();
5857              it != itemListMap.end();
5858              ++it) {
5859           if ((*it).first == tname) {
5860             found = true;
5861             break;
5862           }
5863           ++index;
5864         }
5865         frame.setIndex(found ? index : -1);
5866         markTagChanged(tagNr, frame.getType());
5867         return true;
5868       } else if ((mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[tagNr])) != nullptr) {
5869         if (frame.getType() == Frame::FT_Picture) {
5870           if (frame.getFieldList().empty()) {
5871             PictureFrame::setFields(frame);
5872           }
5873           if (m_pictures.isRead()) {
5874             frame.setIndex(Frame::toNegativeIndex(m_pictures.size()));
5875             m_pictures.append(frame);
5876             markTagChanged(tagNr, Frame::FT_Picture);
5877             return true;
5878           }
5879         }
5880         TagLib::String name;
5881         TagLib::MP4::Item item = getMp4ItemForFrame(frame, name);
5882         if (!item.isValid()) {
5883           return false;
5884         }
5885         frame.setExtendedType(Frame::ExtendedType(frame.getType(),
5886                                                   toQString(name)));
5887         prefixMp4FreeFormName(name, mp4Tag);
5888 #if TAGLIB_VERSION >= 0x010b01
5889         mp4Tag->setItem(name, item);
5890         const TagLib::MP4::ItemMap& itemListMap = mp4Tag->itemMap();
5891 #else
5892         mp4Tag->itemListMap()[name] = item;
5893         const TagLib::MP4::ItemListMap& itemListMap = mp4Tag->itemListMap();
5894 #endif
5895         int index = 0;
5896         bool found = false;
5897         for (auto it = itemListMap.begin();
5898              it != itemListMap.end();
5899              ++it) {
5900           if ((*it).first == name) {
5901             found = true;
5902             break;
5903           }
5904           ++index;
5905         }
5906         frame.setIndex(found ? index : -1);
5907         markTagChanged(tagNr, frame.getType());
5908         return true;
5909       } else if ((asfTag = dynamic_cast<TagLib::ASF::Tag*>(m_tag[tagNr])) != nullptr) {
5910         if (frame.getType() == Frame::FT_Picture &&
5911             frame.getFieldList().empty()) {
5912           PictureFrame::setFields(frame);
5913         }
5914         TagLib::String name;
5915         TagLib::ASF::Attribute::AttributeTypes valueType;
5916         getAsfTypeForFrame(frame, name, valueType);
5917         if (valueType == TagLib::ASF::Attribute::BytesType &&
5918             frame.getType() != Frame::FT_Picture) {
5919           Frame::Field field;
5920           field.m_id = Frame::ID_Data;
5921           field.m_value = QByteArray();
5922           frame.fieldList().push_back(field);
5923         }
5924         TagLib::ASF::Attribute attribute = getAsfAttributeForFrame(frame, valueType);
5925         asfTag->addAttribute(name, attribute);
5926         frame.setExtendedType(Frame::ExtendedType(frame.getType(),
5927                                                   toQString(name)));
5928 
5929         const TagLib::ASF::AttributeListMap& attrListMap = asfTag->attributeListMap();
5930         int index = AFI_Attributes;
5931         bool found = false;
5932         for (auto it = attrListMap.begin();
5933              it != attrListMap.end();
5934              ++it) {
5935           if ((*it).first == name) {
5936             index += (*it).second.size() - 1;
5937             found = true;
5938             break;
5939           }
5940           index += (*it).second.size();
5941         }
5942         frame.setIndex(found ? index : -1);
5943         markTagChanged(tagNr, frame.getType());
5944         return true;
5945 #if TAGLIB_VERSION >= 0x010a00
5946       } else if (auto infoTag =
5947                  dynamic_cast<TagLib::RIFF::Info::Tag*>(m_tag[tagNr])) {
5948         TagLib::ByteVector id = getInfoName(frame);
5949         TagLib::String tvalue = toTString(frame.getValue());
5950         if (tvalue.isEmpty()) {
5951           tvalue = " "; // empty values are not added by TagLib
5952         }
5953         infoTag->setFieldText(id, tvalue);
5954         QString name = QString::fromLatin1(id.data(), id.size());
5955         frame.setExtendedType(Frame::ExtendedType(frame.getType(), name));
5956         const TagLib::RIFF::Info::FieldListMap itemListMap = infoTag->fieldListMap();
5957         int index = 0;
5958         bool found = false;
5959         for (auto it = itemListMap.begin(); it != itemListMap.end(); ++it) {
5960           if ((*it).first == id) {
5961             found = true;
5962             break;
5963           }
5964           ++index;
5965         }
5966         frame.setIndex(found ? index : -1);
5967         markTagChanged(tagNr, frame.getType());
5968         return true;
5969 #endif
5970       }
5971     }
5972   }
5973 
5974   // Try the superclass method
5975   return TaggedFile::addFrame(tagNr, frame);
5976 }
5977 
5978 /**
5979  * Delete a frame from the tags.
5980  *
5981  * @param tagNr tag number
5982  * @param frame frame to delete.
5983  *
5984  * @return true if ok.
5985  */
deleteFrame(Frame::TagNumber tagNr,const Frame & frame)5986 bool TagLibFile::deleteFrame(Frame::TagNumber tagNr, const Frame& frame)
5987 {
5988   if (tagNr >= NUM_TAGS)
5989     return false;
5990 
5991   if (tagNr != Frame::Tag_Id3v1) {
5992     makeFileOpen();
5993     // If the frame has an index, delete that specific frame
5994     int index = frame.getIndex();
5995     if (index != -1 && m_tag[tagNr]) {
5996       TagLib::ID3v2::Tag* id3v2Tag;
5997       TagLib::Ogg::XiphComment* oggTag;
5998       TagLib::APE::Tag* apeTag;
5999       TagLib::MP4::Tag* mp4Tag;
6000       TagLib::ASF::Tag* asfTag;
6001       if ((id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr])) != nullptr) {
6002         const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList();
6003         if (index >= 0 && index < static_cast<int>(frameList.size())) {
6004           id3v2Tag->removeFrame(frameList[index]);
6005           markTagChanged(tagNr, frame.getType());
6006           return true;
6007         }
6008       } else if ((oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[tagNr])) != nullptr) {
6009         QString frameValue(frame.getValue());
6010         if (frame.getType() == Frame::FT_Picture) {
6011           if (m_pictures.isRead()) {
6012             int idx = Frame::fromNegativeIndex(frame.getIndex());
6013             if (idx >= 0 && idx < m_pictures.size()) {
6014               m_pictures.removeAt(idx);
6015               while (idx < m_pictures.size()) {
6016                 m_pictures[idx].setIndex(Frame::toNegativeIndex(idx));
6017                 ++idx;
6018               }
6019               markTagChanged(tagNr, Frame::FT_Picture);
6020               return true;
6021             }
6022           } else {
6023             PictureFrame::getFieldsToBase64(frame, frameValue);
6024           }
6025         }
6026         TagLib::String key =
6027           toTString(frame.getInternalName());
6028 #if TAGLIB_VERSION >= 0x010b01
6029         oggTag->removeFields(key, toTString(frameValue));
6030 #else
6031         oggTag->removeField(key, toTString(frameValue));
6032 #endif
6033         markTagChanged(tagNr, frame.getType());
6034         return true;
6035       } else if ((apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tag[tagNr])) != nullptr) {
6036         TagLib::String key = toTString(frame.getInternalName());
6037         apeTag->removeItem(key);
6038         markTagChanged(tagNr, frame.getType());
6039         return true;
6040       } else if ((mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[tagNr])) != nullptr) {
6041         if (frame.getType() == Frame::FT_Picture) {
6042           if (m_pictures.isRead()) {
6043             int idx = Frame::fromNegativeIndex(frame.getIndex());
6044             if (idx >= 0 && idx < m_pictures.size()) {
6045               m_pictures.removeAt(idx);
6046               while (idx < m_pictures.size()) {
6047                 m_pictures[idx].setIndex(Frame::toNegativeIndex(idx));
6048                 ++idx;
6049               }
6050               markTagChanged(tagNr, Frame::FT_Picture);
6051               return true;
6052             }
6053           }
6054         }
6055         TagLib::String name = toTString(frame.getInternalName());
6056         prefixMp4FreeFormName(name, mp4Tag);
6057 #if TAGLIB_VERSION >= 0x010b01
6058         mp4Tag->removeItem(name);
6059 #else
6060         mp4Tag->itemListMap().erase(name);
6061 #endif
6062         markTagChanged(tagNr, frame.getType());
6063         return true;
6064       } else if ((asfTag = dynamic_cast<TagLib::ASF::Tag*>(m_tag[tagNr])) != nullptr) {
6065         switch (index) {
6066           case AFI_Title:
6067             asfTag->setTitle("");
6068             break;
6069           case AFI_Artist:
6070             asfTag->setArtist("");
6071             break;
6072           case AFI_Comment:
6073             asfTag->setComment("");
6074             break;
6075           case AFI_Copyright:
6076             asfTag->setCopyright("");
6077             break;
6078           case AFI_Rating:
6079             asfTag->setRating("");
6080             break;
6081           case AFI_Attributes:
6082           default:
6083           {
6084             TagLib::String name = toTString(frame.getInternalName());
6085             TagLib::ASF::AttributeListMap& attrListMap = asfTag->attributeListMap();
6086             if (attrListMap.contains(name) && attrListMap[name].size() > 1) {
6087               int i = AFI_Attributes;
6088               bool found = false;
6089               for (auto it = attrListMap.begin();
6090                    it != attrListMap.end();
6091                    ++it) {
6092                 TagLib::ASF::AttributeList& attrList = (*it).second;
6093                 for (auto ait = attrList.begin();
6094                      ait != attrList.end();
6095                      ++ait) {
6096                   if (i++ == index) {
6097                     found = true;
6098                     attrList.erase(ait);
6099                     break;
6100                   }
6101                 }
6102                 if (found) {
6103                   break;
6104                 }
6105               }
6106             } else {
6107               asfTag->removeItem(name);
6108             }
6109           }
6110         }
6111         markTagChanged(tagNr, frame.getType());
6112         return true;
6113 #if TAGLIB_VERSION >= 0x010a00
6114       } else if (auto infoTag =
6115                  dynamic_cast<TagLib::RIFF::Info::Tag*>(m_tag[tagNr])) {
6116         QByteArray ba = frame.getInternalName().toLatin1();
6117         TagLib::ByteVector id(ba.constData(), ba.size());
6118         infoTag->removeField(id);
6119         markTagChanged(tagNr, frame.getType());
6120         return true;
6121 #endif
6122       }
6123     }
6124   }
6125 
6126   // Try the superclass method
6127   return TaggedFile::deleteFrame(tagNr, frame);
6128 }
6129 
6130 namespace {
6131 
6132 /**
6133  * Create a frame from a TagLib ID3 frame.
6134  * @param id3Frame TagLib ID3 frame
6135  * @param index, -1 if not used
6136  * @return frame.
6137  */
createFrameFromId3Frame(const TagLib::ID3v2::Frame * id3Frame,int index)6138 Frame createFrameFromId3Frame(const TagLib::ID3v2::Frame* id3Frame, int index)
6139 {
6140   Frame::Type type;
6141   const char* name;
6142   getTypeStringForFrameId(id3Frame->frameID(), type, name);
6143   Frame frame(type, toQString(id3Frame->toString()), QString::fromLatin1(name), index);
6144   frame.setValue(getFieldsFromId3Frame(id3Frame, frame.fieldList(), type));
6145   if (id3Frame->frameID().mid(1, 3) == "XXX" ||
6146       type == Frame::FT_Comment) {
6147     QVariant fieldValue = frame.getFieldValue(Frame::ID_Description);
6148     if (fieldValue.isValid()) {
6149       QString description = fieldValue.toString();
6150       if (!description.isEmpty()) {
6151         if (description == QLatin1String("CATALOGNUMBER")) {
6152           frame.setType(Frame::FT_CatalogNumber);
6153         } else if (description == QLatin1String("RELEASECOUNTRY")) {
6154           frame.setType(Frame::FT_ReleaseCountry);
6155         } else if (description == QLatin1String("GROUPING")) {
6156           frame.setType(Frame::FT_Grouping);
6157         } else if (description == QLatin1String("SUBTITLE")) {
6158           frame.setType(Frame::FT_Subtitle);
6159         } else {
6160           if (description.startsWith(QLatin1String("QuodLibet::"))) {
6161             // remove ExFalso/QuodLibet "namespace"
6162             description = description.mid(11);
6163           }
6164           frame.setExtendedType(Frame::ExtendedType(Frame::FT_Other,
6165             frame.getInternalName() + QLatin1Char('\n') + description));
6166         }
6167       }
6168     }
6169   } else if (id3Frame->frameID().startsWith("PRIV")) {
6170     QVariant fieldValue = frame.getFieldValue(Frame::ID_Owner);
6171     if (fieldValue.isValid()) {
6172       QString owner = fieldValue.toString();
6173       if (!owner.isEmpty()) {
6174         frame.setExtendedType(Frame::ExtendedType(Frame::FT_Other,
6175                   frame.getInternalName() + QLatin1Char('\n') + owner));
6176       }
6177     }
6178   }
6179   return frame;
6180 }
6181 
6182 }
6183 
6184 /**
6185  * Remove frames.
6186  *
6187  * @param tagNr tag number
6188  * @param flt filter specifying which frames to remove
6189  */
deleteFrames(Frame::TagNumber tagNr,const FrameFilter & flt)6190 void TagLibFile::deleteFrames(Frame::TagNumber tagNr, const FrameFilter& flt)
6191 {
6192   if (tagNr >= NUM_TAGS)
6193     return;
6194 
6195   makeFileOpen();
6196   if (tagNr == Frame::Tag_Id3v1) {
6197     if (m_tag[tagNr]) {
6198       TaggedFile::deleteFrames(tagNr, flt);
6199     }
6200   } else {
6201     if (m_tag[tagNr]) {
6202       TagLib::ID3v2::Tag* id3v2Tag;
6203       TagLib::Ogg::XiphComment* oggTag;
6204       TagLib::APE::Tag* apeTag;
6205       TagLib::MP4::Tag* mp4Tag;
6206       TagLib::ASF::Tag* asfTag;
6207       if (flt.areAllEnabled()) {
6208         if ((id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr])) != nullptr) {
6209           const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList();
6210           for (auto it = frameList.begin();
6211                it != frameList.end();) {
6212             id3v2Tag->removeFrame(*it++, true);
6213           }
6214           markTagChanged(tagNr, Frame::FT_UnknownFrame);
6215         } else if ((oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[tagNr])) !=
6216                    nullptr) {
6217           const TagLib::Ogg::FieldListMap& fieldListMap = oggTag->fieldListMap();
6218           for (auto it = fieldListMap.begin();
6219                it != fieldListMap.end();) {
6220 #if TAGLIB_VERSION >= 0x010b01
6221             oggTag->removeFields((*it++).first);
6222 #else
6223             oggTag->removeField((*it++).first);
6224 #endif
6225           }
6226           m_pictures.clear();
6227           markTagChanged(tagNr, Frame::FT_UnknownFrame);
6228         } else if ((apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tag[tagNr])) != nullptr) {
6229           const TagLib::APE::ItemListMap& itemListMap = apeTag->itemListMap();
6230           for (auto it = itemListMap.begin();
6231                it != itemListMap.end();) {
6232             apeTag->removeItem((*it++).first);
6233           }
6234           markTagChanged(tagNr, Frame::FT_UnknownFrame);
6235         } else if ((mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[tagNr])) != nullptr) {
6236 #if TAGLIB_VERSION >= 0x010b01
6237           const auto& itemMap = mp4Tag->itemMap();
6238           for (auto it = itemMap.begin(); it != itemMap.end();) {
6239             mp4Tag->removeItem((*it++).first);
6240           }
6241 #else
6242           mp4Tag->itemListMap().clear();
6243 #endif
6244           m_pictures.clear();
6245           markTagChanged(tagNr, Frame::FT_UnknownFrame);
6246         } else if ((asfTag = dynamic_cast<TagLib::ASF::Tag*>(m_tag[tagNr])) != nullptr) {
6247           asfTag->setTitle("");
6248           asfTag->setArtist("");
6249           asfTag->setComment("");
6250           asfTag->setCopyright("");
6251           asfTag->setRating("");
6252           asfTag->attributeListMap().clear();
6253           markTagChanged(tagNr, Frame::FT_UnknownFrame);
6254 #if TAGLIB_VERSION >= 0x010a00
6255         } else if (auto infoTag =
6256                    dynamic_cast<TagLib::RIFF::Info::Tag*>(m_tag[tagNr])) {
6257           const TagLib::RIFF::Info::FieldListMap itemListMap = infoTag->fieldListMap();
6258           for (auto it = itemListMap.begin(); it != itemListMap.end(); ++it) {
6259             infoTag->removeField((*it).first);
6260           }
6261           markTagChanged(tagNr, Frame::FT_UnknownFrame);
6262 #endif
6263         } else {
6264           TaggedFile::deleteFrames(tagNr, flt);
6265         }
6266       } else {
6267         if ((id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr])) != nullptr) {
6268           const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList();
6269           for (auto it = frameList.begin();
6270                it != frameList.end();) {
6271             Frame frame(createFrameFromId3Frame(*it, -1));
6272             if (flt.isEnabled(frame.getType(), frame.getName())) {
6273               id3v2Tag->removeFrame(*it++, true);
6274             } else {
6275               ++it;
6276             }
6277           }
6278           markTagChanged(tagNr, Frame::FT_UnknownFrame);
6279         } else if ((oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[tagNr])) !=
6280                    nullptr) {
6281           const TagLib::Ogg::FieldListMap& fieldListMap = oggTag->fieldListMap();
6282           for (auto it = fieldListMap.begin();
6283                it != fieldListMap.end();) {
6284             QString name(toQString((*it).first));
6285             if (flt.isEnabled(getTypeFromVorbisName(name), name)) {
6286 #if TAGLIB_VERSION >= 0x010b01
6287               oggTag->removeFields((*it++).first);
6288 #else
6289               oggTag->removeField((*it++).first);
6290 #endif
6291             } else {
6292               ++it;
6293             }
6294           }
6295           if (flt.isEnabled(Frame::FT_Picture)) {
6296             m_pictures.clear();
6297           }
6298           markTagChanged(tagNr, Frame::FT_UnknownFrame);
6299         } else if ((apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tag[tagNr])) != nullptr) {
6300           const TagLib::APE::ItemListMap& itemListMap = apeTag->itemListMap();
6301           for (auto it = itemListMap.begin();
6302                it != itemListMap.end();) {
6303             QString name(toQString((*it).first));
6304             if (flt.isEnabled(getTypeFromApeName(name), name)) {
6305               apeTag->removeItem((*it++).first);
6306             } else {
6307               ++it;
6308             }
6309           }
6310           markTagChanged(tagNr, Frame::FT_UnknownFrame);
6311         } else if ((mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[tagNr])) != nullptr) {
6312 #if TAGLIB_VERSION >= 0x010b01
6313           const auto& itemMap = mp4Tag->itemMap();
6314           for (auto it = itemMap.begin(); it != itemMap.end();) {
6315             TagLib::String name = it->first;
6316             stripMp4FreeFormName(name);
6317             Frame::Type type;
6318             Mp4ValueType valueType;
6319             getMp4TypeForName(name, type, valueType);
6320             if (flt.isEnabled(type, toQString(name))) {
6321               mp4Tag->removeItem((*it++).first);
6322             } else {
6323               ++it;
6324             }
6325           }
6326 #else
6327           TagLib::MP4::ItemListMap& itemListMap = mp4Tag->itemListMap();
6328           Frame::Type type;
6329           Mp4ValueType valueType;
6330           for (auto it = itemListMap.begin();
6331                it != itemListMap.end();) {
6332             TagLib::String name = (*it).first;
6333             stripMp4FreeFormName(name);
6334             getMp4TypeForName(name, type, valueType);
6335             if (flt.isEnabled(type, toQString(name))) {
6336               itemListMap.erase(it++);
6337             } else {
6338               ++it;
6339             }
6340           }
6341 #endif
6342           if (flt.isEnabled(Frame::FT_Picture)) {
6343             m_pictures.clear();
6344           }
6345           markTagChanged(tagNr, Frame::FT_UnknownFrame);
6346         } else if ((asfTag = dynamic_cast<TagLib::ASF::Tag*>(m_tag[tagNr])) != nullptr) {
6347           if (flt.isEnabled(Frame::FT_Title))
6348             asfTag->setTitle("");
6349           if (flt.isEnabled(Frame::FT_Artist))
6350             asfTag->setArtist("");
6351           if (flt.isEnabled(Frame::FT_Comment))
6352             asfTag->setComment("");
6353           if (flt.isEnabled(Frame::FT_Copyright))
6354             asfTag->setCopyright("");
6355           if (flt.isEnabled(Frame::FT_Other, QLatin1String("Rating Information")))
6356             asfTag->setRating("");
6357 
6358           TagLib::ASF::AttributeListMap& attrListMap = asfTag->attributeListMap();
6359           Frame::Type type;
6360           TagLib::ASF::Attribute::AttributeTypes valueType;
6361           for (auto it = attrListMap.begin();
6362                it != attrListMap.end();) {
6363             getAsfTypeForName((*it).first, type, valueType);
6364             QString name(toQString((*it).first));
6365             if (flt.isEnabled(type, name)) {
6366               attrListMap.erase(it++);
6367             } else {
6368               ++it;
6369             }
6370           }
6371           markTagChanged(tagNr, Frame::FT_UnknownFrame);
6372 #if TAGLIB_VERSION >= 0x010a00
6373         } else if (auto infoTag =
6374                    dynamic_cast<TagLib::RIFF::Info::Tag*>(m_tag[tagNr])) {
6375           const TagLib::RIFF::Info::FieldListMap itemListMap = infoTag->fieldListMap();
6376           for (auto it = itemListMap.begin(); it != itemListMap.end(); ++it) {
6377             TagLib::ByteVector id = (*it).first;
6378             QString name = QString::fromLatin1(id.data(), id.size());
6379             if (flt.isEnabled(getTypeFromInfoName(id), name)) {
6380               infoTag->removeField(id);
6381             }
6382           }
6383           markTagChanged(tagNr, Frame::FT_UnknownFrame);
6384 #endif
6385         } else {
6386           TaggedFile::deleteFrames(tagNr, flt);
6387         }
6388       }
6389     }
6390   }
6391 }
6392 
6393 /**
6394  * Get all frames in tag.
6395  *
6396  * @param tagNr tag number
6397  * @param frames frame collection to set.
6398  */
getAllFrames(Frame::TagNumber tagNr,FrameCollection & frames)6399 void TagLibFile::getAllFrames(Frame::TagNumber tagNr, FrameCollection& frames)
6400 {
6401   if (tagNr >= NUM_TAGS)
6402     return;
6403 
6404   if (tagNr != Frame::Tag_Id3v1) {
6405     makeFileOpen();
6406     frames.clear();
6407     if (m_tag[tagNr]) {
6408       TagLib::ID3v2::Tag* id3v2Tag;
6409       TagLib::Ogg::XiphComment* oggTag;
6410       TagLib::APE::Tag* apeTag;
6411       TagLib::MP4::Tag* mp4Tag;
6412       TagLib::ASF::Tag* asfTag;
6413       if ((id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr])) != nullptr) {
6414         const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList();
6415         int i = 0;
6416         for (auto it = frameList.begin();
6417              it != frameList.end();
6418              ++it) {
6419           Frame frame(createFrameFromId3Frame(*it, i++));
6420           if (frame.getType() == Frame::FT_UnknownFrame) {
6421             TagLib::ByteVector frameID = (*it)->frameID().mid(0, 4);
6422             if (frameID == "TDAT" || frameID == "TIME" || frameID == "TRDA" ||
6423                 frameID == "TYER") {
6424               // These frames are converted to a TDRC frame by TagLib.
6425               continue;
6426             }
6427           }
6428           frames.insert(frame);
6429         }
6430       } else if ((oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tag[tagNr])) != nullptr) {
6431         const TagLib::Ogg::FieldListMap& fieldListMap = oggTag->fieldListMap();
6432         int i = 0;
6433         for (auto it = fieldListMap.begin();
6434              it != fieldListMap.end();
6435              ++it) {
6436           QString name = toQString((*it).first);
6437           Frame::Type type = getTypeFromVorbisName(name);
6438           const TagLib::StringList stringList = (*it).second;
6439           for (auto slit = stringList.begin(); slit != stringList.end(); ++slit) {
6440             if (type == Frame::FT_Picture) {
6441               Frame frame(type, QLatin1String(""), name, i++);
6442               PictureFrame::setFieldsFromBase64(
6443                     frame, toQString(TagLib::String(*slit)));
6444               if (name == QLatin1String("COVERART")) {
6445                 TagLib::StringList mt = oggTag->fieldListMap()["COVERARTMIME"];
6446                 if (!mt.isEmpty()) {
6447                   PictureFrame::setMimeType(frame, toQString(mt.front()));
6448                 }
6449               }
6450               frames.insert(frame);
6451             } else {
6452               frames.insert(Frame(type, toQString(TagLib::String(*slit)),
6453                                   name, i++));
6454             }
6455           }
6456         }
6457         if (m_pictures.isRead()) {
6458           for (auto it = m_pictures.constBegin(); it != m_pictures.constEnd(); ++it) {
6459             frames.insert(*it);
6460           }
6461         }
6462       } else if ((apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tag[tagNr])) != nullptr) {
6463         const TagLib::APE::ItemListMap& itemListMap = apeTag->itemListMap();
6464         int i = 0;
6465         for (auto it = itemListMap.begin();
6466              it != itemListMap.end();
6467              ++it) {
6468           QString name = toQString((*it).first);
6469           Frame::Type type = getTypeFromApeName(name);
6470           TagLib::StringList values;
6471           if (type != Frame::FT_Picture) {
6472             values = (*it).second.toStringList();
6473           }
6474           Frame frame(type, values.size() > 0
6475                       ? toQString(values.front()) : QLatin1String(""),
6476                       name, i++);
6477           if (type == Frame::FT_Picture) {
6478             TagLib::ByteVector data = (*it).second.binaryData();
6479             parseApePicture(name, data, frame);
6480           }
6481           frames.insert(frame);
6482         }
6483       } else if ((mp4Tag = dynamic_cast<TagLib::MP4::Tag*>(m_tag[tagNr])) != nullptr) {
6484 #if TAGLIB_VERSION >= 0x010b01
6485         const TagLib::MP4::ItemMap& itemListMap = mp4Tag->itemMap();
6486 #else
6487         const TagLib::MP4::ItemListMap& itemListMap = mp4Tag->itemListMap();
6488 #endif
6489         int i = 0;
6490         for (auto it = itemListMap.begin();
6491              it != itemListMap.end();
6492              ++it) {
6493           TagLib::String name = (*it).first;
6494           stripMp4FreeFormName(name);
6495           Frame::Type type;
6496           Mp4ValueType valueType;
6497           getMp4TypeForName(name, type, valueType);
6498           QString value;
6499           switch (valueType) {
6500             case MVT_String:
6501             {
6502               TagLib::StringList strings = (*it).second.toStringList();
6503               value = strings.size() > 0
6504                   ? toQString(strings.toString(
6505                                 Frame::stringListSeparator().toLatin1()))
6506                   : QLatin1String("");
6507               break;
6508             }
6509             case MVT_Bool:
6510               value = (*it).second.toBool() ? QLatin1String("1") : QLatin1String("0");
6511               break;
6512             case MVT_Int:
6513               value.setNum((*it).second.toInt());
6514               break;
6515             case MVT_IntPair:
6516             {
6517               TagLib::MP4::Item::IntPair intPair = (*it).second.toIntPair();
6518               value.setNum(intPair.first);
6519               if (intPair.second != 0) {
6520                 value += QLatin1Char('/');
6521                 value += QString::number(intPair.second);
6522               }
6523               break;
6524             }
6525             case MVT_CoverArt:
6526               // handled by m_pictures
6527               break;
6528             case MVT_Byte:
6529               value.setNum((*it).second.toByte());
6530               break;
6531             case MVT_UInt:
6532               value.setNum((*it).second.toUInt());
6533               break;
6534             case MVT_LongLong:
6535               value.setNum((*it).second.toLongLong());
6536               break;
6537             case MVT_ByteArray:
6538             default:
6539               // binary data and album art are not handled by TagLib
6540               value = QLatin1String("");
6541           }
6542           if (type != Frame::FT_Picture) {
6543             frames.insert(
6544               Frame(type, value, toQString(name), i++));
6545           }
6546         }
6547         if (m_pictures.isRead()) {
6548           for (auto it = m_pictures.constBegin(); it != m_pictures.constEnd(); ++it) {
6549             frames.insert(*it);
6550           }
6551         }
6552       } else if ((asfTag = dynamic_cast<TagLib::ASF::Tag*>(m_tag[tagNr])) != nullptr) {
6553         TagLib::String name;
6554         TagLib::ASF::Attribute::AttributeTypes valueType;
6555         Frame::Type type = Frame::FT_Title;
6556         getAsfNameForType(type, name, valueType);
6557         QString value = toQString(asfTag->title());
6558         frames.insert(Frame(type, value, toQString(name), AFI_Title));
6559 
6560         type = Frame::FT_Artist;
6561         getAsfNameForType(type, name, valueType);
6562         value = toQString(asfTag->artist());
6563         frames.insert(Frame(type, value, toQString(name), AFI_Artist));
6564 
6565         type = Frame::FT_Comment;
6566         getAsfNameForType(type, name, valueType);
6567         value = toQString(asfTag->comment());
6568         frames.insert(Frame(type, value, toQString(name), AFI_Comment));
6569 
6570         type = Frame::FT_Copyright;
6571         getAsfNameForType(type, name, valueType);
6572         value = toQString(asfTag->copyright());
6573         frames.insert(Frame(type, value, toQString(name), AFI_Copyright));
6574 
6575         name = QT_TRANSLATE_NOOP("@default", "Rating Information");
6576         getAsfTypeForName(name, type, valueType);
6577         value = toQString(asfTag->rating());
6578         frames.insert(Frame(type, value, toQString(name), AFI_Rating));
6579 
6580         int i = AFI_Attributes;
6581         QByteArray ba;
6582         const TagLib::ASF::AttributeListMap& attrListMap = asfTag->attributeListMap();
6583         for (auto it = attrListMap.begin();
6584              it != attrListMap.end();
6585              ++it) {
6586           name = (*it).first;
6587           getAsfTypeForName(name, type, valueType);
6588           for (auto ait = (*it).second.begin();
6589                ait != (*it).second.end();
6590                ++ait) {
6591             switch ((*ait).type()) {
6592               case TagLib::ASF::Attribute::UnicodeType:
6593                 value = toQString((*ait).toString());
6594                 break;
6595               case TagLib::ASF::Attribute::BoolType:
6596                 value = (*ait).toBool() ? QLatin1String("1") : QLatin1String("0");
6597                 break;
6598               case TagLib::ASF::Attribute::DWordType:
6599                 value.setNum((*ait).toUInt());
6600                 break;
6601               case TagLib::ASF::Attribute::QWordType:
6602                 value.setNum((*ait).toULongLong());
6603                 break;
6604               case TagLib::ASF::Attribute::WordType:
6605                 value.setNum((*ait).toUShort());
6606                 break;
6607               case TagLib::ASF::Attribute::BytesType:
6608               case TagLib::ASF::Attribute::GuidType:
6609               default:
6610               {
6611                 TagLib::ByteVector bv = (*ait).toByteVector();
6612                 ba = QByteArray(bv.data(), bv.size());
6613                 value = QLatin1String("");
6614                 AttributeData(toQString(name)).toString(ba, value);
6615               }
6616             }
6617             Frame frame(type, value, toQString(name), i);
6618             if ((*ait).type() == TagLib::ASF::Attribute::BytesType &&
6619                 valueType == TagLib::ASF::Attribute::BytesType) {
6620               Frame::Field field;
6621               field.m_id = Frame::ID_Data;
6622               field.m_value = ba;
6623               frame.fieldList().push_back(field);
6624             }
6625             ++i;
6626             if (type == Frame::FT_Picture) {
6627               parseAsfPicture((*ait).toPicture(), frame);
6628             }
6629             frames.insert(frame);
6630           }
6631         }
6632 #if TAGLIB_VERSION >= 0x010a00
6633       } else if (auto infoTag =
6634                  dynamic_cast<TagLib::RIFF::Info::Tag*>(m_tag[tagNr])) {
6635         const TagLib::RIFF::Info::FieldListMap itemListMap = infoTag->fieldListMap();
6636         int i = 0;
6637         for (auto it = itemListMap.begin(); it != itemListMap.end(); ++it) {
6638           TagLib::ByteVector id = (*it).first;
6639           TagLib::String s = (*it).second;
6640           QString name = QString::fromLatin1(id.data(), id.size());
6641           QString value = toQString(s);
6642           Frame::Type type = getTypeFromInfoName(id);
6643           Frame frame(type, value, name, i++);
6644           frames.insert(frame);
6645         }
6646 #endif
6647       } else {
6648         TaggedFile::getAllFrames(tagNr, frames);
6649       }
6650     }
6651     updateMarkedState(tagNr, frames);
6652     if (tagNr <= Frame::Tag_2) {
6653       frames.addMissingStandardFrames();
6654     }
6655     return;
6656   }
6657 
6658   TaggedFile::getAllFrames(tagNr, frames);
6659 }
6660 
6661 /**
6662  * Close file handle which is held open by the TagLib object.
6663  */
closeFileHandle()6664 void TagLibFile::closeFileHandle()
6665 {
6666   closeFile(false);
6667 }
6668 
6669 /**
6670  * Add a suitable field list for the frame if missing.
6671  * If a frame is created, its field list is empty. This method will create
6672  * a field list appropriate for the frame type and tagged file type if no
6673  * field list exists.
6674  * @param tagNr tag number
6675  * @param frame frame where field list is added
6676  */
addFieldList(Frame::TagNumber tagNr,Frame & frame) const6677 void TagLibFile::addFieldList(Frame::TagNumber tagNr, Frame& frame) const
6678 {
6679   if (dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr]) != nullptr &&
6680       frame.fieldList().isEmpty()) {
6681     TagLib::ID3v2::Frame* id3Frame = createId3FrameFromFrame(this, frame);
6682     getFieldsFromId3Frame(id3Frame, frame.fieldList(), frame.getType());
6683     frame.setFieldListFromValue();
6684     delete id3Frame;
6685   }
6686 }
6687 
6688 /**
6689  * Get a list of frame IDs which can be added.
6690  * @param tagNr tag number
6691  * @return list with frame IDs.
6692  */
getFrameIds(Frame::TagNumber tagNr) const6693 QStringList TagLibFile::getFrameIds(Frame::TagNumber tagNr) const
6694 {
6695   QStringList lst;
6696   if (m_tagType[tagNr] == TT_Id3v2 ||
6697       (m_tagType[tagNr] == TT_Unknown &&
6698        dynamic_cast<TagLib::ID3v2::Tag*>(m_tag[tagNr]))) {
6699     for (int k = Frame::FT_FirstFrame; k <= Frame::FT_LastFrame; ++k) {
6700       lst.append(Frame::ExtendedType(static_cast<Frame::Type>(k), QLatin1String("")). // clazy:exclude=reserve-candidates
6701                  getName());
6702     }
6703     for (const auto& ts : typeStrOfId) {
6704       if (ts.type == Frame::FT_Other && ts.supported && ts.str) {
6705         lst.append(QString::fromLatin1(ts.str));
6706       }
6707     }
6708   } else if (m_tagType[tagNr] == TT_Mp4) {
6709     TagLib::String name;
6710     Mp4ValueType valueType;
6711     Frame::Type type;
6712     for (int k = Frame::FT_FirstFrame; k <= Frame::FT_LastFrame; ++k) {
6713       name = "";
6714       type = static_cast<Frame::Type>(k);
6715       getMp4NameForType(type, name, valueType);
6716       if (!name.isEmpty() && valueType != MVT_ByteArray &&
6717           !(name[0] >= 'A' && name[0] <= 'Z')) {
6718         lst.append(Frame::ExtendedType(type, QLatin1String("")).getName()); // clazy:exclude=reserve-candidates
6719       }
6720     }
6721     for (const auto& mp4NameTypeValue : mp4NameTypeValues) {
6722       if (mp4NameTypeValue.type == Frame::FT_Other &&
6723           mp4NameTypeValue.value != MVT_ByteArray &&
6724           !(mp4NameTypeValue.name[0] >= 'A' &&
6725             mp4NameTypeValue.name[0] <= 'Z')) {
6726         lst.append(QString::fromLatin1(mp4NameTypeValue.name));
6727       }
6728     }
6729   } else if (m_tagType[tagNr] == TT_Asf) {
6730     TagLib::String name;
6731     TagLib::ASF::Attribute::AttributeTypes valueType;
6732     Frame::Type type;
6733     for (int k = Frame::FT_FirstFrame; k <= Frame::FT_LastFrame; ++k) {
6734       name = "";
6735       type = static_cast<Frame::Type>(k);
6736       getAsfNameForType(type, name, valueType);
6737       if (!name.isEmpty()) {
6738         lst.append(Frame::ExtendedType(type, QLatin1String("")).getName()); // clazy:exclude=reserve-candidates
6739       }
6740     }
6741     for (const auto& asfNameTypeValue : asfNameTypeValues) {
6742       if (asfNameTypeValue.type == Frame::FT_Other) {
6743         lst.append(QString::fromLatin1(asfNameTypeValue.name));
6744       }
6745     }
6746 #if TAGLIB_VERSION >= 0x010a00
6747   } else if (m_tagType[tagNr] == TT_Info) {
6748     static const char* const fieldNames[] = {
6749       "IARL", // Archival Location
6750       "ICMS", // Commissioned
6751       "ICRP", // Cropped
6752       "IDIM", // Dimensions
6753       "IDPI", // Dots Per Inch
6754       "IKEY", // Keywords
6755       "ILGT", // Lightness
6756       "IPLT", // Palette Setting
6757       "ISBJ", // Subject
6758       "ISHP", // Sharpness
6759       "ISRF", // Source Form
6760     };
6761     for (int k = Frame::FT_FirstFrame; k <= Frame::FT_LastFrame; ++k) {
6762       auto type = static_cast<Frame::Type>(k);
6763       if (!getInfoNameFromType(type).isEmpty()) {
6764         lst.append(Frame::ExtendedType(type, QLatin1String("")).getName()); // clazy:exclude=reserve-candidates
6765       }
6766     }
6767     for (auto fieldName : fieldNames) {
6768       lst.append(QString::fromLatin1(fieldName)); // clazy:exclude=reserve-candidates
6769     }
6770 #endif
6771   } else {
6772     static const char* const fieldNames[] = {
6773       "CONTACT",
6774       "DISCTOTAL",
6775       "EAN/UPN",
6776       "ENCODING",
6777       "ENGINEER",
6778       "ENSEMBLE",
6779       "GUESTARTIST",
6780       "LABEL",
6781       "LABELNO",
6782       "LICENSE",
6783       "LOCATION",
6784       "OPUS",
6785       "ORGANIZATION",
6786       "PARTNUMBER",
6787       "PRODUCER",
6788       "PRODUCTNUMBER",
6789       "RECORDINGDATE",
6790       "TRACKTOTAL",
6791       "VERSION",
6792       "VOLUME"
6793     };
6794     const bool picturesSupported = m_pictures.isRead() ||
6795         m_tagType[tagNr] == TT_Vorbis || m_tagType[tagNr] == TT_Ape;
6796     for (int k = Frame::FT_FirstFrame; k <= Frame::FT_LastFrame; ++k) {
6797       if (k != Frame::FT_Picture || picturesSupported) {
6798         lst.append(Frame::ExtendedType(static_cast<Frame::Type>(k),
6799                                        QLatin1String("")).getName());
6800       }
6801     }
6802     for (auto fieldName : fieldNames) {
6803       lst.append(QString::fromLatin1(fieldName)); // clazy:exclude=reserve-candidates
6804     }
6805   }
6806   return lst;
6807 }
6808 
6809 /**
6810  * Set the encoding to be used for tag 1.
6811  *
6812  * @param name of encoding, default is ISO 8859-1
6813  */
setTextEncodingV1(const QString & name)6814 void TagLibFile::setTextEncodingV1(const QString& name)
6815 {
6816 #if QT_VERSION >= 0x060000
6817   TextCodecStringHandler::setStringDecoder(name);
6818 #else
6819   TextCodecStringHandler::setTextCodec(name != QLatin1String("ISO-8859-1")
6820       ? QTextCodec::codecForName(name.toLatin1().data()) : nullptr);
6821 #endif
6822 }
6823 
6824 /**
6825  * Set the default text encoding.
6826  *
6827  * @param textEnc default text encoding
6828  */
setDefaultTextEncoding(TagConfig::TextEncoding textEnc)6829 void TagLibFile::setDefaultTextEncoding(TagConfig::TextEncoding textEnc)
6830 {
6831   // Do not use TagLib::ID3v2::FrameFactory::setDefaultTextEncoding(),
6832   // it will change the encoding of existing frames read in, not only
6833   // of newly created frames, which is really not what we want!
6834   switch (textEnc) {
6835     case TagConfig::TE_ISO8859_1:
6836       s_defaultTextEncoding = TagLib::String::Latin1;
6837       break;
6838     case TagConfig::TE_UTF16:
6839       s_defaultTextEncoding = TagLib::String::UTF16;
6840       break;
6841     case TagConfig::TE_UTF8:
6842     default:
6843       s_defaultTextEncoding = TagLib::String::UTF8;
6844   }
6845 }
6846 
6847 /**
6848  * Notify about configuration change.
6849  * This method shall be called when the configuration changes.
6850  */
notifyConfigurationChange()6851 void TagLibFile::notifyConfigurationChange()
6852 {
6853   setDefaultTextEncoding(
6854     static_cast<TagConfig::TextEncoding>(TagConfig::instance().textEncoding()));
6855   setTextEncodingV1(TagConfig::instance().textEncodingV1());
6856 }
6857 
6858 namespace {
6859 
6860 /**
6861  * Used to register file types at static initialization time.
6862  */
6863 class TagLibInitializer {
6864 public:
6865   /** Constructor. */
6866   TagLibInitializer();
6867 
6868   /** Destructor. */
6869   ~TagLibInitializer();
6870 
6871   /**
6872    * Initialization.
6873    * Is deferred because it will crash on Mac OS X if done in the constructor.
6874    */
6875   void init();
6876 
6877 private:
6878   Q_DISABLE_COPY(TagLibInitializer)
6879 
6880   QScopedPointer<AACFileTypeResolver> m_aacFileTypeResolver;
6881   QScopedPointer<MP2FileTypeResolver> m_mp2FileTypeResolver;
6882   QScopedPointer<TextCodecStringHandler> m_textCodecStringHandler;
6883 };
6884 
6885 
TagLibInitializer()6886 TagLibInitializer::TagLibInitializer()
6887   : m_aacFileTypeResolver(new AACFileTypeResolver),
6888     m_mp2FileTypeResolver(new MP2FileTypeResolver),
6889     m_textCodecStringHandler(new TextCodecStringHandler)
6890 {
6891 }
6892 
init()6893 void TagLibInitializer::init()
6894 {
6895   TagLib::FileRef::addFileTypeResolver(m_aacFileTypeResolver.data());
6896   TagLib::FileRef::addFileTypeResolver(m_mp2FileTypeResolver.data());
6897   TagLib::ID3v1::Tag::setStringHandler(m_textCodecStringHandler.data());
6898 }
6899 
~TagLibInitializer()6900 TagLibInitializer::~TagLibInitializer() {
6901   // Must not be inline because of forwared declared QScopedPointer.
6902 }
6903 
6904 TagLibInitializer tagLibInitializer;
6905 
6906 }
6907 
6908 /**
6909  * Static initialization.
6910  * Registers file types.
6911  */
staticInit()6912 void TagLibFile::staticInit()
6913 {
6914   tagLibInitializer.init();
6915 }
6916