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