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