3
* Handling of tagged MP3 files.
9
* Copyright (C) 2003-2013 Urs Fleisch
11
* This file is part of Kid3.
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.
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.
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/>.
33
#include <QCoreApplication>
40
#include <sys/utime.h>
45
#include "id3libconfig.h"
47
#include "attributedata.h"
51
* This will be set for id3lib versions with Unicode bugs.
52
* ID3LIB_ symbols cannot be found on Windows ?!
54
#define UNICODE_SUPPORT_BUGGY 1
56
/** This will be set for id3lib versions with Unicode bugs. */
57
#define UNICODE_SUPPORT_BUGGY ((((ID3LIB_MAJOR_VERSION) << 16) + ((ID3LIB_MINOR_VERSION) << 8) + (ID3LIB_PATCH_VERSION)) <= 0x030803)
60
#if defined __GNUC__ && (__GNUC__ * 100 + __GNUC_MINOR__) >= 407
61
/** Defined if GCC is used and supports diagnostic pragmas */
62
#define GCC_HAS_DIAGNOSTIC_PRAGMA
67
/** Text codec for ID3v1 tags, 0 to use default (ISO 8859-1) */
68
const QTextCodec* s_textCodecV1 = 0;
70
/** Default text encoding */
71
ID3_TextEnc s_defaultTextEncoding = ID3TE_ISO8859_1;
74
* Get the default text encoding.
75
* @return default text encoding.
77
ID3_TextEnc getDefaultTextEncoding() { return s_defaultTextEncoding; }
84
* @param dn directory name
86
* @param idx model index
88
Mp3File::Mp3File(const QString& dn, const QString& fn,
89
const QPersistentModelIndex& idx) :
90
TaggedFile(dn, fn, idx), m_tagV1(0), m_tagV2(0)
108
* Get key of tagged file format.
109
* @return "Id3libMetadata".
111
QString Mp3File::taggedFileKey() const
113
return QLatin1String("Id3libMetadata");
117
* Get features supported.
118
* @return bit mask with Feature flags set.
120
int Mp3File::taggedFileFeatures() const
122
return TF_ID3v11 | TF_ID3v23;
126
* Read tags from file.
128
* @param force true to force reading even if tags were already read.
130
void Mp3File::readTags(bool force)
132
QByteArray fn = QFile::encodeName(getDirname() + QDir::separator() + currentFilename());
134
if (force && m_tagV1) {
136
m_tagV1->Link(fn, ID3TT_ID3V1);
140
m_tagV1 = new ID3_Tag;
141
m_tagV1->Link(fn, ID3TT_ID3V1);
145
if (force && m_tagV2) {
147
m_tagV2->Link(fn, ID3TT_ID3V2);
151
m_tagV2 = new ID3_Tag;
152
m_tagV2->Link(fn, ID3TT_ID3V2);
157
setFilename(currentFilename());
162
* Write tags to file and rename it if necessary.
164
* @param force true to force writing even if file was not changed.
165
* @param renamed will be set to true if the file was renamed,
166
* i.e. the file name is no longer valid, else *renamed
168
* @param preserve true to preserve file time stamps
170
* @return true if ok, false if the file could not be written or renamed.
172
bool Mp3File::writeTags(bool force, bool* renamed, bool preserve)
174
QString fnStr(getDirname() + QDir::separator() + currentFilename());
175
if (isChanged() && !QFileInfo(fnStr).isWritable()) {
179
// store time stamp if it has to be preserved
181
bool setUtime = false;
182
struct utimbuf times;
184
fn = QFile::encodeName(fnStr);
185
struct stat fileStat;
186
if (::stat(fn, &fileStat) == 0) {
187
times.actime = fileStat.st_atime;
188
times.modtime = fileStat.st_mtime;
193
// There seems to be a bug in id3lib: The V1 genre is not
194
// removed. So we check here and strip the whole header
195
// if there are no frames.
196
if (m_tagV1 && (force || isTag1Changed()) && (m_tagV1->NumFrames() == 0)) {
197
m_tagV1->Strip(ID3TT_ID3V1);
200
// Even after removing all frames, HasV2Tag() still returns true,
201
// so we strip the whole header.
202
if (m_tagV2 && (force || isTag2Changed()) && (m_tagV2->NumFrames() == 0)) {
203
m_tagV2->Strip(ID3TT_ID3V2);
206
// There seems to be a bug in id3lib: If I update an ID3v1 and then
207
// strip the ID3v2 the ID3v1 is removed too and vice versa, so I
208
// first make any stripping and then the updating.
209
if (m_tagV1 && (force || isTag1Changed()) && (m_tagV1->NumFrames() > 0)) {
210
m_tagV1->Update(ID3TT_ID3V1);
213
if (m_tagV2 && (force || isTag2Changed()) && (m_tagV2->NumFrames() > 0)) {
214
m_tagV2->Update(ID3TT_ID3V2);
218
// restore time stamp
223
if (getFilename() != currentFilename()) {
224
if (!renameFile(currentFilename(), getFilename())) {
227
updateCurrentFilename();
228
// link tags to new file name
236
* Remove ID3v1 frames.
238
* @param flt filter specifying which frames to remove
240
void Mp3File::deleteFramesV1(const FrameFilter& flt)
243
if (flt.areAllEnabled()) {
244
ID3_Tag::Iterator* iter = m_tagV1->CreateIterator();
246
while ((frame = iter->GetNext()) != NULL) {
247
m_tagV1->RemoveFrame(frame);
250
/* allocated in Windows DLL => must be freed in the same DLL */
251
ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
253
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
254
#pragma GCC diagnostic push
255
#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
256
/* Is safe because iterator implementation has default destructor */
259
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
260
#pragma GCC diagnostic pop
263
markTag1Changed(Frame::FT_UnknownFrame);
264
clearTrunctionFlags();
266
TaggedFile::deleteFramesV1(flt);
272
* Fix up a unicode string from id3lib.
274
* @param str unicode string
275
* @param numChars number of characters in str
277
* @return string as QString.
279
static QString fixUpUnicode(const unicode_t* str, size_t numChars)
282
if (numChars > 0 && str && *str) {
283
QChar* qcarray = new QChar[numChars];
284
// Unfortunately, Unicode support in id3lib is rather buggy
285
// in the current version: The codes are mirrored.
286
// In the hope that my patches will be included, I try here
287
// to work around these bugs.
288
size_t numZeroes = 0;
289
for (size_t i = 0; i < numChars; i++) {
291
UNICODE_SUPPORT_BUGGY ?
292
(ushort)(((str[i] & 0x00ff) << 8) |
293
((str[i] & 0xff00) >> 8)) :
295
if (qcarray[i].isNull()) { ++numZeroes; }
297
// remove a single trailing zero character
298
if (numZeroes == 1 && qcarray[numChars - 1].isNull()) {
301
text = QString(qcarray, numChars);
308
* Get string from text field.
311
* @param codec text codec to use, 0 for default
314
* "" if the field does not exist.
316
static QString getString(ID3_Field* field, const QTextCodec* codec = 0)
318
QString text(QLatin1String(""));
320
ID3_TextEnc enc = field->GetEncoding();
321
if (enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
322
size_t numItems = field->GetNumTextItems();
324
text = fixUpUnicode(field->GetRawUnicodeText(),
325
field->Size() / sizeof(unicode_t));
327
// if there are multiple items, put them into one string
328
// separated by a special separator.
329
// ID3_Field::GetRawUnicodeTextItem() returns a pointer to a temporary
330
// object, so I do not use it.
331
text = fixUpUnicode(field->GetRawUnicodeText(),
332
field->Size() / sizeof(unicode_t));
333
text.replace(QLatin1Char('\0'), Frame::stringListSeparator());
336
// (ID3TE_IS_SINGLE_BYTE_ENC(enc))
337
// (enc == ID3TE_ISO8859_1 || enc == ID3TE_UTF8)
338
size_t numItems = field->GetNumTextItems();
341
codec->toUnicode(field->GetRawText(), field->Size()) :
342
QString::fromLatin1(field->GetRawText());
344
// if there are multiple items, put them into one string
345
// separated by a special separator.
346
for (size_t itemNr = 0; itemNr < numItems; ++itemNr) {
348
text = QString::fromLatin1(field->GetRawTextItem(0));
350
text += Frame::stringListSeparator();
351
text += QString::fromLatin1(field->GetRawTextItem(itemNr));
365
* @param codec text codec to use, 0 for default
367
* "" if the field does not exist,
368
* QString::null if the tags do not exist.
370
static QString getTextField(const ID3_Tag* tag, ID3_FrameID id,
371
const QTextCodec* codec = 0)
376
QString str(QLatin1String(""));
378
ID3_Frame* frame = tag->Find(id);
379
if (frame && ((fld = frame->GetField(ID3FN_TEXT)) != NULL)) {
380
str = getString(fld, codec);
390
* 0 if the field does not exist,
391
* -1 if the tags do not exist.
393
static int getYear(const ID3_Tag* tag)
395
QString str = getTextField(tag, ID3FID_YEAR);
396
if (str.isNull()) return -1;
397
if (str.isEmpty()) return 0;
406
* 0 if the field does not exist,
407
* -1 if the tags do not exist.
409
static int getTrackNum(const ID3_Tag* tag)
411
QString str = getTextField(tag, ID3FID_TRACKNUM);
412
if (str.isNull()) return -1;
413
if (str.isEmpty()) return 0;
414
// handle "track/total number of tracks" format
415
int slashPos = str.indexOf(QLatin1Char('/'));
416
if (slashPos != -1) {
417
str.truncate(slashPos);
427
* 0xff if the field does not exist,
428
* -1 if the tags do not exist.
430
static int getGenreNum(const ID3_Tag* tag)
432
QString str = getTextField(tag, ID3FID_CONTENTTYPE);
433
if (str.isNull()) return -1;
434
if (str.isEmpty()) return 0xff;
435
int cpPos = 0, n = 0xff;
436
if ((str[0] == QLatin1Char('(')) && ((cpPos = str.indexOf(QLatin1Char(')'), 2)) > 1)) {
438
n = str.mid(1, cpPos - 1).toInt(&ok);
439
if (!ok || n > 0xff) {
443
// ID3v2 genres can be stored as "(9)", "(9)Metal" or "Metal".
444
// If the string does not start with '(', try to get the genre number
445
// from a string containing a genre text.
446
n = Genres::getNumber(str);
452
* Allocate a fixed up a unicode string for id3lib.
456
* @return new allocated unicode string, has to be freed with delete [].
458
static unicode_t* newFixedUpUnicode(const QString& text)
460
// Unfortunately, Unicode support in id3lib is rather buggy in the
461
// current version: The codes are mirrored, a second different
462
// BOM may be added, if the LSB >= 0x80, the MSB is set to 0xff.
463
// If iconv is used (id3lib on Linux), the character do not come
464
// back mirrored, but with a second (different)! BOM 0xfeff and
465
// they are still written in the wrong order (big endian).
466
// In the hope that my patches will be included, I try here to
467
// work around these bugs, but there is no solution for the
469
const QChar* qcarray = text.unicode();
470
uint unicode_size = text.length();
471
unicode_t* unicode = new unicode_t[unicode_size + 1];
472
for (uint i = 0; i < unicode_size; i++) {
473
unicode[i] = (ushort)qcarray[i].unicode();
474
if (UNICODE_SUPPORT_BUGGY) {
475
unicode[i] = (ushort)(((unicode[i] & 0x00ff) << 8) |
476
((unicode[i] & 0xff00) >> 8));
479
unicode[unicode_size] = 0;
484
* Set string list in text field.
487
* @param lst string list to set
489
static void setStringList(ID3_Field* field, const QStringList& lst)
491
ID3_TextEnc enc = field->GetEncoding();
493
for (QStringList::const_iterator it = lst.begin(); it != lst.end(); ++it) {
495
if (enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
496
unicode_t* unicode = newFixedUpUnicode(*it);
501
} else if (enc == ID3TE_UTF8) {
502
field->Set((*it).toUtf8().data());
504
// enc == ID3TE_ISO8859_1
505
field->Set((*it).toLatin1().data());
509
// This will not work with buggy id3lib. A BOM 0xfffe is written before
510
// the first string, but not before the subsequent strings. Prepending a
511
// BOM or changing the byte order does not help when id3lib rewrites
512
// this field when another frame is changed. So you cannot use
513
// string lists with Unicode encoding.
514
if (enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
515
unicode_t* unicode = newFixedUpUnicode(*it);
520
} else if (enc == ID3TE_UTF8) {
521
field->Add((*it).toUtf8().data());
523
// enc == ID3TE_ISO8859_1
524
field->Add((*it).toLatin1().data());
531
* Set string in text field.
534
* @param text text to set
535
* @param codec text codec to use, 0 for default
537
static void setString(ID3_Field* field, const QString& text,
538
const QTextCodec* codec = 0)
540
if (text.indexOf(Frame::stringListSeparator()) == -1) {
541
ID3_TextEnc enc = field->GetEncoding();
542
// (ID3TE_IS_DOUBLE_BYTE_ENC(enc))
543
if (enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
544
unicode_t* unicode = newFixedUpUnicode(text);
549
} else if (enc == ID3TE_UTF8) {
550
field->Set(text.toUtf8().data());
552
// enc == ID3TE_ISO8859_1
553
field->Set(codec ? codec->fromUnicode(text).data() : text.toLatin1().data());
556
setStringList(field, text.split(Frame::stringListSeparator()));
565
* @param text text to set
566
* @param allowUnicode true to allow setting of Unicode encoding if necessary
567
* @param replace true to replace an existing field
568
* @param removeEmpty true to remove a field if text is empty
569
* @param codec text codec to use, 0 for default
571
* @return true if the field was changed.
573
static bool setTextField(ID3_Tag* tag, ID3_FrameID id, const QString& text,
574
bool allowUnicode = false, bool replace = true,
575
bool removeEmpty = true, const QTextCodec* codec = 0)
577
bool changed = false;
578
if (tag && !text.isNull()) {
579
ID3_Frame* frame = NULL;
580
bool removeOnly = removeEmpty && text.isEmpty();
581
if (replace || removeOnly) {
582
if (id == ID3FID_COMMENT && tag->HasV2Tag()) {
583
frame = tag->Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, "");
585
frame = tag->Find(id);
588
frame = tag->RemoveFrame(frame);
593
if (!removeOnly && (replace || tag->Find(id) == NULL)) {
594
frame = new ID3_Frame(id);
596
ID3_Field* fld = frame->GetField(ID3FN_TEXT);
598
ID3_TextEnc enc = tag->HasV2Tag() ?
599
getDefaultTextEncoding() : ID3TE_ISO8859_1;
600
if (allowUnicode && enc == ID3TE_ISO8859_1) {
601
// check if information is lost if the string is not unicode
602
uint i, unicode_size = text.length();
603
const QChar* qcarray = text.unicode();
604
for (i = 0; i < unicode_size; i++) {
605
char ch = qcarray[i].toLatin1();
606
if (ch == 0 || (ch & 0x80) != 0) {
612
ID3_Field* encfld = frame->GetField(ID3FN_TEXTENC);
616
fld->SetEncoding(enc);
617
setString(fld, text, codec);
618
tag->AttachFrame(frame);
631
* @param num number to set, 0 to remove field.
633
* @return true if the field was changed.
635
static bool setYear(ID3_Tag* tag, int num)
637
bool changed = false;
645
changed = getTextField(tag, ID3FID_YEAR) != str &&
646
setTextField(tag, ID3FID_YEAR, str);
655
* @param num number to set, 0 to remove field.
656
* @param numTracks total number of tracks, <=0 to ignore
658
* @return true if the field was changed.
660
bool Mp3File::setTrackNum(ID3_Tag* tag, int num, int numTracks) const
662
bool changed = false;
663
if (num >= 0 && getTrackNum(tag) != num) {
664
QString str = trackNumberString(num, numTracks);
665
changed = getTextField(tag, ID3FID_TRACKNUM) != str &&
666
setTextField(tag, ID3FID_TRACKNUM, str);
675
* @param num number to set, 0xff to remove field.
677
* @return true if the field was changed.
679
static bool setGenreNum(ID3_Tag* tag, int num)
681
bool changed = false;
685
str = QString(QLatin1String("(%1)")).arg(num);
689
changed = getTextField(tag, ID3FID_CONTENTTYPE) != str &&
690
setTextField(tag, ID3FID_CONTENTTYPE, str);
699
* "" if the field does not exist,
700
* QString::null if the tags do not exist.
702
QString Mp3File::getTitleV1()
704
return getTextField(m_tagV1, ID3FID_TITLE, s_textCodecV1);
711
* "" if the field does not exist,
712
* QString::null if the tags do not exist.
714
QString Mp3File::getArtistV1()
716
return getTextField(m_tagV1, ID3FID_LEADARTIST, s_textCodecV1);
723
* "" if the field does not exist,
724
* QString::null if the tags do not exist.
726
QString Mp3File::getAlbumV1()
728
return getTextField(m_tagV1, ID3FID_ALBUM, s_textCodecV1);
735
* "" if the field does not exist,
736
* QString::null if the tags do not exist.
738
QString Mp3File::getCommentV1()
740
return getTextField(m_tagV1, ID3FID_COMMENT, s_textCodecV1);
747
* 0 if the field does not exist,
748
* -1 if the tags do not exist.
750
int Mp3File::getYearV1()
752
return getYear(m_tagV1);
759
* 0 if the field does not exist,
760
* -1 if the tags do not exist.
762
int Mp3File::getTrackNumV1()
764
return getTrackNum(m_tagV1);
771
* "" if the field does not exist,
772
* QString::null if the tags do not exist.
774
QString Mp3File::getGenreV1()
776
int num = getGenreNum(m_tagV1);
779
} else if (num == 0xff) {
780
return QLatin1String("");
782
return QString::fromLatin1(Genres::getName(num));
790
* "" if the field does not exist,
791
* QString::null if the tags do not exist.
793
QString Mp3File::getTitleV2()
795
return getTextField(m_tagV2, ID3FID_TITLE);
802
* "" if the field does not exist,
803
* QString::null if the tags do not exist.
805
QString Mp3File::getArtistV2()
807
return getTextField(m_tagV2, ID3FID_LEADARTIST);
814
* "" if the field does not exist,
815
* QString::null if the tags do not exist.
817
QString Mp3File::getAlbumV2()
819
return getTextField(m_tagV2, ID3FID_ALBUM);
826
* "" if the field does not exist,
827
* QString::null if the tags do not exist.
829
QString Mp3File::getCommentV2()
831
return getTextField(m_tagV2, ID3FID_COMMENT);
838
* 0 if the field does not exist,
839
* -1 if the tags do not exist.
841
int Mp3File::getYearV2()
843
return getYear(m_tagV2);
850
* "" if the field does not exist,
851
* QString::null if the tags do not exist.
853
QString Mp3File::getTrackV2()
855
return getTextField(m_tagV2, ID3FID_TRACKNUM);
859
* Get ID3v2 genre as text.
862
* "" if the field does not exist,
863
* QString::null if the tags do not exist.
865
QString Mp3File::getGenreV2()
867
int num = getGenreNum(m_tagV2);
868
if (num != 0xff && num != -1) {
869
return QString::fromLatin1(Genres::getName(num));
871
return getTextField(m_tagV2, ID3FID_CONTENTTYPE);
878
* @param str string to set, "" to remove field.
880
void Mp3File::setTitleV1(const QString& str)
882
if (getTextField(m_tagV1, ID3FID_TITLE, s_textCodecV1) != str &&
883
setTextField(m_tagV1, ID3FID_TITLE, str, false, true, true, s_textCodecV1)) {
884
markTag1Changed(Frame::FT_Title);
885
QString s = checkTruncation(str, 1ULL << Frame::FT_Title);
886
if (!s.isNull()) setTextField(m_tagV1, ID3FID_TITLE, s, false, true, true, s_textCodecV1);
893
* @param str string to set, "" to remove field.
895
void Mp3File::setArtistV1(const QString& str)
897
if (getTextField(m_tagV1, ID3FID_LEADARTIST, s_textCodecV1) != str &&
898
setTextField(m_tagV1, ID3FID_LEADARTIST, str, false, true, true, s_textCodecV1)) {
899
markTag1Changed(Frame::FT_Artist);
900
QString s = checkTruncation(str, 1ULL << Frame::FT_Artist);
901
if (!s.isNull()) setTextField(m_tagV1, ID3FID_LEADARTIST, s, false, true, true, s_textCodecV1);
908
* @param str string to set, "" to remove field.
910
void Mp3File::setAlbumV1(const QString& str)
912
if (getTextField(m_tagV1, ID3FID_ALBUM, s_textCodecV1) != str &&
913
setTextField(m_tagV1, ID3FID_ALBUM, str, false, true, true, s_textCodecV1)) {
914
markTag1Changed(Frame::FT_Album);
915
QString s = checkTruncation(str, 1ULL << Frame::FT_Album);
916
if (!s.isNull()) setTextField(m_tagV1, ID3FID_ALBUM, s, false, true, true, s_textCodecV1);
923
* @param str string to set, "" to remove field.
925
void Mp3File::setCommentV1(const QString& str)
927
if (getTextField(m_tagV1, ID3FID_COMMENT, s_textCodecV1) != str &&
928
setTextField(m_tagV1, ID3FID_COMMENT, str, false, true, true, s_textCodecV1)) {
929
markTag1Changed(Frame::FT_Comment);
930
QString s = checkTruncation(str, 1ULL << Frame::FT_Comment, 28);
931
if (!s.isNull()) setTextField(m_tagV1, ID3FID_COMMENT, s, false, true, true, s_textCodecV1);
938
* @param num number to set, 0 to remove field.
940
void Mp3File::setYearV1(int num)
942
if (setYear(m_tagV1, num)) {
943
markTag1Changed(Frame::FT_Date);
950
* @param num number to set, 0 to remove field.
952
void Mp3File::setTrackNumV1(int num)
954
if (setTrackNum(m_tagV1, num)) {
955
markTag1Changed(Frame::FT_Track);
956
int n = checkTruncation(num, 1ULL << Frame::FT_Track);
957
if (n != -1) setTrackNum(m_tagV1, n);
962
* Set ID3v1 genre as text.
964
* @param str string to set, "" to remove field, QString::null to ignore.
966
void Mp3File::setGenreV1(const QString& str)
969
int num = Genres::getNumber(str);
970
if (setGenreNum(m_tagV1, num)) {
971
markTag1Changed(Frame::FT_Genre);
973
// if the string cannot be converted to a number, set the truncation flag
974
checkTruncation(num == 0xff && !str.isEmpty() ? 1 : 0,
975
1ULL << Frame::FT_Genre, 0);
982
* @param str string to set, "" to remove field.
984
void Mp3File::setTitleV2(const QString& str)
986
if (getTextField(m_tagV2, ID3FID_TITLE) != str &&
987
setTextField(m_tagV2, ID3FID_TITLE, str, true)) {
988
markTag2Changed(Frame::FT_Title);
995
* @param str string to set, "" to remove field.
997
void Mp3File::setArtistV2(const QString& str)
999
if (getTextField(m_tagV2, ID3FID_LEADARTIST) != str &&
1000
setTextField(m_tagV2, ID3FID_LEADARTIST, str, true)) {
1001
markTag2Changed(Frame::FT_Artist);
1008
* @param str string to set, "" to remove field.
1010
void Mp3File::setAlbumV2(const QString& str)
1012
if (getTextField(m_tagV2, ID3FID_ALBUM) != str &&
1013
setTextField(m_tagV2, ID3FID_ALBUM, str, true)) {
1014
markTag2Changed(Frame::FT_Album);
1019
* Set ID3v2 comment.
1021
* @param str string to set, "" to remove field.
1023
void Mp3File::setCommentV2(const QString& str)
1025
if (getTextField(m_tagV2, ID3FID_COMMENT) != str &&
1026
setTextField(m_tagV2, ID3FID_COMMENT, str, true)) {
1027
markTag2Changed(Frame::FT_Comment);
1034
* @param num number to set, 0 to remove field.
1036
void Mp3File::setYearV2(int num)
1038
if (setYear(m_tagV2, num)) {
1039
markTag2Changed(Frame::FT_Date);
1046
* @param track string to set, "" to remove field, QString::null to ignore.
1048
void Mp3File::setTrackV2(const QString& track)
1051
int num = splitNumberAndTotal(track, &numTracks);
1052
if (setTrackNum(m_tagV2, num, numTracks)) {
1053
markTag2Changed(Frame::FT_Track);
1058
* Set ID3v2 genre as text.
1060
* @param str string to set, "" to remove field, QString::null to ignore.
1062
void Mp3File::setGenreV2(const QString& str)
1064
if (!str.isNull()) {
1066
if (!TagConfig::instance().genreNotNumeric()) {
1067
num = Genres::getNumber(str);
1069
if (num >= 0 && num != 0xff) {
1070
if (getGenreNum(m_tagV2) != num &&
1071
setGenreNum(m_tagV2, num)) {
1072
markTag2Changed(Frame::FT_Genre);
1075
if (getTextField(m_tagV2, ID3FID_CONTENTTYPE) != str &&
1076
setTextField(m_tagV2, ID3FID_CONTENTTYPE, str, true)) {
1077
markTag2Changed(Frame::FT_Genre);
1084
* Check if tag information has already been read.
1086
* @return true if information is available,
1087
* false if the tags have not been read yet, in which case
1088
* hasTagV1() and hasTagV2() do not return meaningful information.
1090
bool Mp3File::isTagInformationRead() const
1092
return m_tagV1 || m_tagV2;
1096
* Check if file has an ID3v1 tag.
1098
* @return true if a V1 tag is available.
1099
* @see isTagInformationRead()
1101
bool Mp3File::hasTagV1() const
1103
return m_tagV1 && m_tagV1->HasV1Tag();
1107
* Check if ID3v1 tags are supported by the format of this file.
1111
bool Mp3File::isTagV1Supported() const
1117
* Check if file has an ID3v2 tag.
1119
* @return true if a V2 tag is available.
1120
* @see isTagInformationRead()
1122
bool Mp3File::hasTagV2() const
1124
return m_tagV2 && m_tagV2->HasV2Tag();
1128
* Get technical detail information.
1130
* @param info the detail information is returned here
1132
void Mp3File::getDetailInfo(DetailInfo& info) const
1134
if (getFilename().right(4).toLower() == QLatin1String(".aac")) {
1136
info.format = QLatin1String("AAC");
1140
const Mp3_Headerinfo* headerInfo = 0;
1142
headerInfo = m_tagV2->GetMp3HeaderInfo();
1144
if (!headerInfo && m_tagV1) {
1145
headerInfo = m_tagV1->GetMp3HeaderInfo();
1149
switch (headerInfo->version) {
1151
info.format = QLatin1String("MPEG 1 ");
1154
info.format = QLatin1String("MPEG 2 ");
1156
case MPEGVERSION_2_5:
1157
info.format = QLatin1String("MPEG 2.5 ");
1162
switch (headerInfo->layer) {
1164
info.format += QLatin1String("Layer 1");
1167
info.format += QLatin1String("Layer 2");
1170
info.format += QLatin1String("Layer 3");
1175
info.bitrate = headerInfo->bitrate / 1000;
1176
#ifndef HAVE_NO_ID3LIB_VBR
1177
if (headerInfo->vbr_bitrate > 1000) {
1179
info.bitrate = headerInfo->vbr_bitrate / 1000;
1182
info.sampleRate = headerInfo->frequency;
1183
switch (headerInfo->channelmode) {
1184
case MP3CHANNELMODE_STEREO:
1185
info.channelMode = DetailInfo::CM_Stereo;
1188
case MP3CHANNELMODE_JOINT_STEREO:
1189
info.channelMode = DetailInfo::CM_JointStereo;
1192
case MP3CHANNELMODE_DUAL_CHANNEL:
1195
case MP3CHANNELMODE_SINGLE_CHANNEL:
1201
info.duration = headerInfo->time;
1208
* Get duration of file.
1210
* @return duration in seconds,
1213
unsigned Mp3File::getDuration() const
1215
unsigned duration = 0;
1216
const Mp3_Headerinfo* info = NULL;
1218
info = m_tagV2->GetMp3HeaderInfo();
1220
if (!info && m_tagV1) {
1221
info = m_tagV1->GetMp3HeaderInfo();
1223
if (info && info->time > 0) {
1224
duration = info->time;
1230
* Get file extension including the dot.
1232
* @return file extension ".mp3".
1234
QString Mp3File::getFileExtension() const
1236
QString ext(getFilename().right(4).toLower());
1237
if (ext == QLatin1String(".aac") || ext == QLatin1String(".mp2"))
1240
return QLatin1String(".mp3");
1244
* Get the format of tag 1.
1246
* @return string describing format of tag 1,
1249
QString Mp3File::getTagFormatV1() const
1251
return hasTagV1() ? QString(QLatin1String("ID3v1.1")) : QString();
1255
* Get the format of tag 2.
1257
* @return string describing format of tag 2,
1258
* e.g. "ID3v2.3", "ID3v2.4".
1260
QString Mp3File::getTagFormatV2() const
1262
if (m_tagV2 && m_tagV2->HasV2Tag()) {
1263
switch (m_tagV2->GetSpec()) {
1265
return QLatin1String("ID3v2.3.0");
1267
return QLatin1String("ID3v2.4.0");
1269
return QLatin1String("ID3v2.2.0");
1271
return QLatin1String("ID3v2.2.1");
1279
/** Types and descriptions for id3lib frame IDs */
1280
static const struct TypeStrOfId {
1284
{ Frame::FT_UnknownFrame, 0 }, /* ???? */
1285
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "AENC - Audio encryption") }, /* AENC */
1286
{ Frame::FT_Picture, QT_TRANSLATE_NOOP("@default", "APIC - Attached picture") }, /* APIC */
1287
{ Frame::FT_Other, 0 }, /* ASPI */
1288
{ Frame::FT_Comment, QT_TRANSLATE_NOOP("@default", "COMM - Comments") }, /* COMM */
1289
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "COMR - Commercial") }, /* COMR */
1290
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "ENCR - Encryption method registration") }, /* ENCR */
1291
{ Frame::FT_Other, 0 }, /* EQU2 */
1292
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "EQUA - Equalization") }, /* EQUA */
1293
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "ETCO - Event timing codes") }, /* ETCO */
1294
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "GEOB - General encapsulated object") }, /* GEOB */
1295
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "GRID - Group identification registration") }, /* GRID */
1296
{ Frame::FT_Arranger, QT_TRANSLATE_NOOP("@default", "IPLS - Involved people list") }, /* IPLS */
1297
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "LINK - Linked information") }, /* LINK */
1298
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "MCDI - Music CD identifier") }, /* MCDI */
1299
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "MLLT - MPEG location lookup table") }, /* MLLT */
1300
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "OWNE - Ownership frame") }, /* OWNE */
1301
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "PRIV - Private frame") }, /* PRIV */
1302
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "PCNT - Play counter") }, /* PCNT */
1303
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "POPM - Popularimeter") }, /* POPM */
1304
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "POSS - Position synchronisation frame") }, /* POSS */
1305
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "RBUF - Recommended buffer size") }, /* RBUF */
1306
{ Frame::FT_Other, 0 }, /* RVA2 */
1307
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "RVAD - Relative volume adjustment") }, /* RVAD */
1308
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "RVRB - Reverb") }, /* RVRB */
1309
{ Frame::FT_Other, 0 }, /* SEEK */
1310
{ Frame::FT_Other, 0 }, /* SIGN */
1311
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "SYLT - Synchronized lyric/text") }, /* SYLT */
1312
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "SYTC - Synchronized tempo codes") }, /* SYTC */
1313
{ Frame::FT_Album, QT_TRANSLATE_NOOP("@default", "TALB - Album/Movie/Show title") }, /* TALB */
1314
{ Frame::FT_Bpm, QT_TRANSLATE_NOOP("@default", "TBPM - BPM (beats per minute)") }, /* TBPM */
1315
{ Frame::FT_Composer, QT_TRANSLATE_NOOP("@default", "TCOM - Composer") }, /* TCOM */
1316
{ Frame::FT_Genre, QT_TRANSLATE_NOOP("@default", "TCON - Content type") }, /* TCON */
1317
{ Frame::FT_Copyright, QT_TRANSLATE_NOOP("@default", "TCOP - Copyright message") }, /* TCOP */
1318
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "TDAT - Date") }, /* TDAT */
1319
{ Frame::FT_Other, 0 }, /* TDEN */
1320
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "TDLY - Playlist delay") }, /* TDLY */
1321
{ Frame::FT_Other, 0 }, /* TDOR */
1322
{ Frame::FT_Other, 0 }, /* TDRC */
1323
{ Frame::FT_Other, 0 }, /* TDRL */
1324
{ Frame::FT_Other, 0 }, /* TDTG */
1325
{ Frame::FT_Other, 0 }, /* TIPL */
1326
{ Frame::FT_EncodedBy, QT_TRANSLATE_NOOP("@default", "TENC - Encoded by") }, /* TENC */
1327
{ Frame::FT_Lyricist, QT_TRANSLATE_NOOP("@default", "TEXT - Lyricist/Text writer") }, /* TEXT */
1328
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "TFLT - File type") }, /* TFLT */
1329
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "TIME - Time") }, /* TIME */
1330
{ Frame::FT_Grouping, QT_TRANSLATE_NOOP("@default", "TIT1 - Content group description") }, /* TIT1 */
1331
{ Frame::FT_Title, QT_TRANSLATE_NOOP("@default", "TIT2 - Title/songname/content description") }, /* TIT2 */
1332
{ Frame::FT_Subtitle, QT_TRANSLATE_NOOP("@default", "TIT3 - Subtitle/Description refinement") }, /* TIT3 */
1333
{ Frame::FT_InitialKey, QT_TRANSLATE_NOOP("@default", "TKEY - Initial key") }, /* TKEY */
1334
{ Frame::FT_Language, QT_TRANSLATE_NOOP("@default", "TLAN - Language(s)") }, /* TLAN */
1335
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "TLEN - Length") }, /* TLEN */
1336
{ Frame::FT_Other, 0 }, /* TMCL */
1337
{ Frame::FT_Media, QT_TRANSLATE_NOOP("@default", "TMED - Media type") }, /* TMED */
1338
{ Frame::FT_Other, 0 }, /* TMOO */
1339
{ Frame::FT_OriginalAlbum, QT_TRANSLATE_NOOP("@default", "TOAL - Original album/movie/show title") }, /* TOAL */
1340
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "TOFN - Original filename") }, /* TOFN */
1341
{ Frame::FT_Author, QT_TRANSLATE_NOOP("@default", "TOLY - Original lyricist(s)/text writer(s)") }, /* TOLY */
1342
{ Frame::FT_OriginalArtist, QT_TRANSLATE_NOOP("@default", "TOPE - Original artist(s)/performer(s)") }, /* TOPE */
1343
{ Frame::FT_OriginalDate, QT_TRANSLATE_NOOP("@default", "TORY - Original release year") }, /* TORY */
1344
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "TOWN - File owner/licensee") }, /* TOWN */
1345
{ Frame::FT_Artist, QT_TRANSLATE_NOOP("@default", "TPE1 - Lead performer(s)/Soloist(s)") }, /* TPE1 */
1346
{ Frame::FT_AlbumArtist, QT_TRANSLATE_NOOP("@default", "TPE2 - Band/orchestra/accompaniment") }, /* TPE2 */
1347
{ Frame::FT_Conductor, QT_TRANSLATE_NOOP("@default", "TPE3 - Conductor/performer refinement") }, /* TPE3 */
1348
{ Frame::FT_Remixer, QT_TRANSLATE_NOOP("@default", "TPE4 - Interpreted, remixed, or otherwise modified by") }, /* TPE4 */
1349
{ Frame::FT_Disc, QT_TRANSLATE_NOOP("@default", "TPOS - Part of a set") }, /* TPOS */
1350
{ Frame::FT_Other, 0 }, /* TPRO */
1351
{ Frame::FT_Publisher, QT_TRANSLATE_NOOP("@default", "TPUB - Publisher") }, /* TPUB */
1352
{ Frame::FT_Track, QT_TRANSLATE_NOOP("@default", "TRCK - Track number/Position in set") }, /* TRCK */
1353
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "TRDA - Recording dates") }, /* TRDA */
1354
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "TRSN - Internet radio station name") }, /* TRSN */
1355
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "TRSO - Internet radio station owner") }, /* TRSO */
1356
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "TSIZ - Size") }, /* TSIZ */
1357
{ Frame::FT_Other, 0 }, /* TSOA */
1358
{ Frame::FT_Other, 0 }, /* TSOP */
1359
{ Frame::FT_Other, 0 }, /* TSOT */
1360
{ Frame::FT_Isrc, QT_TRANSLATE_NOOP("@default", "TSRC - ISRC (international standard recording code)") }, /* TSRC */
1361
{ Frame::FT_EncoderSettings,QT_TRANSLATE_NOOP("@default", "TSSE - Software/Hardware and settings used for encoding") },/* TSSE */
1362
{ Frame::FT_Part, 0 }, /* TSST */
1363
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "TXXX - User defined text information") }, /* TXXX */
1364
{ Frame::FT_Date, QT_TRANSLATE_NOOP("@default", "TYER - Year") }, /* TYER */
1365
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "UFID - Unique file identifier") }, /* UFID */
1366
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "USER - Terms of use") }, /* USER */
1367
{ Frame::FT_Lyrics, QT_TRANSLATE_NOOP("@default", "USLT - Unsynchronized lyric/text transcription") }, /* USLT */
1368
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "WCOM - Commercial information") }, /* WCOM */
1369
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "WCOP - Copyright/Legal information") }, /* WCOP */
1370
{ Frame::FT_WWWAudioFile, QT_TRANSLATE_NOOP("@default", "WOAF - Official audio file webpage") }, /* WOAF */
1371
{ Frame::FT_Website, QT_TRANSLATE_NOOP("@default", "WOAR - Official artist/performer webpage") }, /* WOAR */
1372
{ Frame::FT_WWWAudioSource, QT_TRANSLATE_NOOP("@default", "WOAS - Official audio source webpage") }, /* WOAS */
1373
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "WORS - Official internet radio station homepage") }, /* WORS */
1374
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "WPAY - Payment") }, /* WPAY */
1375
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "WPUB - Official publisher webpage") }, /* WPUB */
1376
{ Frame::FT_Other, QT_TRANSLATE_NOOP("@default", "WXXX - User defined URL link") } /* WXXX */
1379
/** Not instantiated struct to check array size at compilation time. */
1380
struct not_used { int array_size_check[
1381
sizeof(typeStrOfId) / sizeof(typeStrOfId[0]) == ID3FID_WWWUSER + 1
1382
? 1 : -1 ]; /**< not used */ };
1385
* Get type and description of frame.
1387
* @param id ID of frame
1388
* @param type the type is returned here
1389
* @param str the description is returned here, 0 if not available
1391
static void getTypeStringForId3libFrameId(ID3_FrameID id, Frame::Type& type,
1394
const TypeStrOfId& ts = typeStrOfId[id <= ID3FID_WWWUSER ? id : 0];
1400
* Get id3lib frame ID for frame type.
1402
* @param type frame type
1404
* @return id3lib frame ID or ID3FID_NOFRAME if type not found.
1406
static ID3_FrameID getId3libFrameIdForType(Frame::Type type)
1408
// IPLS is mapped to FT_Arranger and FT_Performer
1409
if (type == Frame::FT_Performer) {
1410
return ID3FID_INVOLVEDPEOPLE;
1411
} else if (type == Frame::FT_CatalogNumber ||
1412
type == Frame::FT_ReleaseCountry) {
1413
return ID3FID_USERTEXT;
1416
static int typeIdMap[Frame::FT_LastFrame + 1] = { -1, };
1417
if (typeIdMap[0] == -1) {
1418
// first time initialization
1419
for (int i = 0; i <= ID3FID_WWWUSER; ++i) {
1420
int t = typeStrOfId[i].type;
1421
if (t <= Frame::FT_LastFrame) {
1426
return type <= Frame::FT_LastFrame ?
1427
static_cast<ID3_FrameID>(typeIdMap[type]) : ID3FID_NOFRAME;
1431
* Get id3lib frame ID for frame name.
1433
* @param name frame name
1435
* @return id3lib frame ID or ID3FID_NOFRAME if name not found.
1437
static ID3_FrameID getId3libFrameIdForName(const QString& name)
1439
if (name.length() >= 4) {
1440
QByteArray nameBytes = name.toLatin1();
1441
const char* nameStr = nameBytes.constData();
1442
for (int i = 0; i <= ID3FID_WWWUSER; ++i) {
1443
const char* s = typeStrOfId[i].str;
1444
if (s && ::strncmp(s, nameStr, 4) == 0) {
1445
return static_cast<ID3_FrameID>(i);
1449
return ID3FID_NOFRAME;
1453
* Get the fields from an ID3v2 tag.
1455
* @param id3Frame frame
1456
* @param fields the fields are appended to this list
1458
* @return text representation of fields (Text or URL).
1460
static QString getFieldsFromId3Frame(ID3_Frame* id3Frame,
1461
Frame::FieldList& fields)
1464
ID3_Frame::Iterator* iter = id3Frame->CreateIterator();
1465
ID3_FrameID id3Id = id3Frame->GetID();
1466
ID3_Field* id3Field;
1468
while ((id3Field = iter->GetNext()) != 0) {
1469
ID3_FieldID id = id3Field->GetID();
1470
ID3_FieldType type = id3Field->GetType();
1472
if (type == ID3FTY_INTEGER) {
1473
field.m_value = id3Field->Get();
1475
else if (type == ID3FTY_BINARY) {
1476
QByteArray ba((const char *)id3Field->GetRawBinary(),
1477
(unsigned int)id3Field->Size());
1480
else if (type == ID3FTY_TEXTSTRING) {
1481
if (id == ID3FN_TEXT || id == ID3FN_DESCRIPTION || id == ID3FN_URL) {
1482
text = getString(id3Field);
1483
if (id3Id == ID3FID_CONTENTTYPE) {
1484
text = Genres::getNameString(text);
1486
field.m_value = text;
1488
field.m_value = getString(id3Field);
1491
field.m_value.clear();
1493
fields.push_back(field);
1496
/* allocated in Windows DLL => must be freed in the same DLL */
1497
ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
1499
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1500
#pragma GCC diagnostic push
1501
#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
1502
/* Is safe because iterator implementation has default destructor */
1505
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1506
#pragma GCC diagnostic pop
1513
* Get ID3v2 frame by index.
1515
* @param tag ID3v2 tag
1516
* @param index index
1518
* @return frame, 0 if index invalid.
1520
static ID3_Frame* getId3v2Frame(ID3_Tag* tag, int index)
1522
ID3_Frame* result = 0;
1525
ID3_Tag::Iterator* iter = tag->CreateIterator();
1527
while ((frame = iter->GetNext()) != 0) {
1535
/* allocated in Windows DLL => must be freed in the same DLL */
1536
ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
1538
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1539
#pragma GCC diagnostic push
1540
#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
1541
/* Is safe because iterator implementation has default destructor */
1544
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1545
#pragma GCC diagnostic pop
1553
* Set the fields in an id3lib frame from the field in the frame.
1555
* @param id3Frame id3lib frame
1556
* @param frame frame with fields
1558
void Mp3File::setId3v2Frame(ID3_Frame* id3Frame, const Frame& frame) const
1560
ID3_Frame::Iterator* iter = id3Frame->CreateIterator();
1561
ID3_FrameID id3Id = id3Frame->GetID();
1562
ID3_TextEnc enc = ID3TE_NONE;
1563
for (Frame::FieldList::const_iterator fldIt = frame.getFieldList().begin();
1564
fldIt != frame.getFieldList().end();
1566
ID3_Field* id3Field = iter->GetNext();
1568
qDebug("early end of ID3 fields");
1571
const Frame::Field& fld = *fldIt;
1572
switch (fld.m_value.type()) {
1574
case QVariant::UInt:
1576
int intVal = fld.m_value.toInt();
1577
if (fld.m_id == ID3FN_TEXTENC) {
1578
if (intVal == ID3TE_UTF8) intVal = ID3TE_UTF16;
1579
enc = static_cast<ID3_TextEnc>(intVal);
1581
id3Field->Set(intVal);
1585
case QVariant::String:
1587
if (enc != ID3TE_NONE) {
1588
id3Field->SetEncoding(enc);
1590
QString value(fld.m_value.toString());
1591
if (id3Id == ID3FID_CONTENTTYPE) {
1592
if (!TagConfig::instance().genreNotNumeric()) {
1593
value = Genres::getNumberString(value, true);
1595
} else if (id3Id == ID3FID_TRACKNUM) {
1596
formatTrackNumberIfEnabled(value, true);
1598
setString(id3Field, value);
1602
case QVariant::ByteArray:
1604
const QByteArray& ba = fld.m_value.toByteArray();
1605
id3Field->Set(reinterpret_cast<const unsigned char*>(ba.data()),
1611
qDebug("Unknown type %d in field %d", fld.m_value.type(), fld.m_id);
1615
/* allocated in Windows DLL => must be freed in the same DLL */
1616
ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
1618
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1619
#pragma GCC diagnostic push
1620
#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
1621
/* Is safe because iterator implementation has default destructor */
1624
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1625
#pragma GCC diagnostic pop
1631
* Set a frame in the tags 2.
1633
* @param frame frame to set
1635
* @return true if ok.
1637
bool Mp3File::setFrameV2(const Frame& frame)
1639
// If the frame has an index, change that specific frame
1640
int index = frame.getIndex();
1641
if (index != -1 && m_tagV2) {
1642
ID3_Frame* id3Frame = getId3v2Frame(m_tagV2, index);
1644
// If value is changed or field list is empty,
1645
// set from value, else from FieldList.
1646
if (frame.isValueChanged() || frame.getFieldList().empty()) {
1647
QString value(frame.getValue());
1649
if ((fld = id3Frame->GetField(ID3FN_URL)) != 0) {
1650
if (getString(fld) != value) {
1651
fld->Set(value.toLatin1().data());
1652
markTag2Changed(frame.getType());
1655
} else if ((fld = id3Frame->GetField(ID3FN_TEXT)) != 0 ||
1656
(fld = id3Frame->GetField(ID3FN_DESCRIPTION)) != 0) {
1657
if (id3Frame->GetID() == ID3FID_CONTENTTYPE) {
1658
if (!TagConfig::instance().genreNotNumeric()) {
1659
value = Genres::getNumberString(value, true);
1661
} else if (id3Frame->GetID() == ID3FID_TRACKNUM) {
1662
formatTrackNumberIfEnabled(value, true);
1665
const ID3_TextEnc enc = fld->GetEncoding();
1666
ID3_TextEnc newEnc = static_cast<ID3_TextEnc>(
1667
frame.getFieldValue(Frame::Field::ID_TextEnc).toInt(&hasEnc));
1671
if (newEnc != ID3TE_ISO8859_1 && newEnc != ID3TE_UTF16) {
1672
// only ISO-8859-1 and UTF16 are allowed for ID3v2.3.0
1673
newEnc = ID3TE_UTF16;
1675
if (newEnc == ID3TE_ISO8859_1) {
1676
// check if information is lost if the string is not unicode
1677
uint i, unicode_size = value.length();
1678
const QChar* qcarray = value.unicode();
1679
for (i = 0; i < unicode_size; i++) {
1680
char ch = qcarray[i].toLatin1();
1681
if (ch == 0 || (ch & 0x80) != 0) {
1682
newEnc = ID3TE_UTF16;
1687
if (enc != newEnc) {
1688
ID3_Field* encfld = id3Frame->GetField(ID3FN_TEXTENC);
1690
encfld->Set(newEnc);
1692
fld->SetEncoding(newEnc);
1693
markTag2Changed(frame.getType());
1695
if (getString(fld) != value) {
1696
setString(fld, value);
1697
markTag2Changed(frame.getType());
1700
} else if (id3Frame->GetID() == ID3FID_PRIVATE &&
1701
(fld = id3Frame->GetField(ID3FN_DATA)) != 0) {
1702
ID3_Field* ownerFld = id3Frame->GetField(ID3FN_OWNER);
1704
QByteArray newData, oldData;
1705
if (ownerFld && !(owner = getString(ownerFld)).isEmpty() &&
1706
AttributeData(owner).toByteArray(value, newData)) {
1707
oldData = QByteArray((const char *)fld->GetRawBinary(),
1708
(unsigned int)fld->Size());
1709
if (newData != oldData) {
1710
fld->Set(reinterpret_cast<const unsigned char*>(newData.data()),
1712
markTag2Changed(frame.getType());
1716
} else if (id3Frame->GetID() == ID3FID_CDID &&
1717
(fld = id3Frame->GetField(ID3FN_DATA)) != 0) {
1718
QByteArray newData, oldData;
1719
if (AttributeData::isHexString(value, 'F', QLatin1String("+")) &&
1720
AttributeData(AttributeData::Utf16).toByteArray(value, newData)) {
1721
oldData = QByteArray((const char *)fld->GetRawBinary(),
1722
(unsigned int)fld->Size());
1723
if (newData != oldData) {
1724
fld->Set(reinterpret_cast<const unsigned char*>(newData.data()),
1726
markTag2Changed(frame.getType());
1730
} else if (id3Frame->GetID() == ID3FID_UNIQUEFILEID &&
1731
(fld = id3Frame->GetField(ID3FN_DATA)) != 0) {
1732
QByteArray newData, oldData;
1733
if (AttributeData::isHexString(value, 'Z')) {
1734
newData = (value + QLatin1Char('\0')).toLatin1();
1735
oldData = QByteArray((const char *)fld->GetRawBinary(),
1736
(unsigned int)fld->Size());
1737
if (newData != oldData) {
1738
fld->Set(reinterpret_cast<const unsigned char*>(newData.data()),
1740
markTag2Changed(frame.getType());
1744
} else if (id3Frame->GetID() == ID3FID_POPULARIMETER &&
1745
(fld = id3Frame->GetField(ID3FN_RATING)) != 0) {
1746
if (getString(fld) != value) {
1747
fld->Set(value.toInt());
1748
markTag2Changed(frame.getType());
1753
setId3v2Frame(id3Frame, frame);
1754
markTag2Changed(frame.getType());
1760
// Try the superclass method
1761
return TaggedFile::setFrameV2(frame);
1766
* Add a frame in the tags 2.
1768
* @param frame frame to add, a field list may be added by this method
1770
* @return true if ok.
1772
bool Mp3File::addFrameV2(Frame& frame)
1776
if (frame.getType() != Frame::FT_Other) {
1777
id = getId3libFrameIdForType(frame.getType());
1779
id = getId3libFrameIdForName(frame.getName());
1780
if (id == ID3FID_NOFRAME) {
1781
if (frame.getName() == QLatin1String("AverageLevel") ||
1782
frame.getName() == QLatin1String("PeakValue") ||
1783
frame.getName().startsWith(QLatin1String("WM/"))) {
1784
id = ID3FID_PRIVATE;
1785
} else if (frame.getName().startsWith(QLatin1String("iTun"))) {
1786
id = ID3FID_COMMENT;
1788
id = ID3FID_USERTEXT;
1792
if (id != ID3FID_NOFRAME && id != ID3FID_SETSUBTITLE && m_tagV2) {
1793
ID3_Frame* id3Frame = new ID3_Frame(id);
1794
ID3_Field* fld = id3Frame->GetField(ID3FN_TEXT);
1796
ID3_TextEnc enc = getDefaultTextEncoding();
1797
ID3_Field* encfld = id3Frame->GetField(ID3FN_TEXTENC);
1801
fld->SetEncoding(enc);
1803
if (id == ID3FID_USERTEXT && !frame.getName().startsWith(QLatin1String("TXXX"))) {
1804
fld = id3Frame->GetField(ID3FN_DESCRIPTION);
1806
QString description;
1807
if (frame.getType() == Frame::FT_CatalogNumber) {
1808
description = QLatin1String("CATALOGNUMBER");
1809
} else if (frame.getType() == Frame::FT_ReleaseCountry) {
1810
description = QLatin1String("RELEASECOUNTRY");
1812
description = frame.getName();
1814
setString(fld, description);
1816
} else if (id == ID3FID_COMMENT && frame.getType() == Frame::FT_Other) {
1817
fld = id3Frame->GetField(ID3FN_DESCRIPTION);
1819
setString(fld, frame.getName());
1821
} else if (id == ID3FID_PRIVATE && !frame.getName().startsWith(QLatin1String("PRIV"))) {
1822
fld = id3Frame->GetField(ID3FN_OWNER);
1824
setString(fld, frame.getName());
1826
if (AttributeData(frame.getName()).toByteArray(frame.getValue(), data)) {
1827
fld = id3Frame->GetField(ID3FN_DATA);
1829
fld->Set(reinterpret_cast<const unsigned char*>(data.data()),
1834
} else if (id == ID3FID_UNIQUEFILEID) {
1836
if (AttributeData::isHexString(frame.getValue(), 'Z')) {
1837
data = (frame.getValue() + QLatin1Char('\0')).toLatin1();
1838
fld = id3Frame->GetField(ID3FN_DATA);
1840
fld->Set(reinterpret_cast<const unsigned char*>(data.data()),
1844
} else if (id == ID3FID_PICTURE) {
1845
fld = id3Frame->GetField(ID3FN_MIMETYPE);
1847
setString(fld, QLatin1String("image/jpeg"));
1849
fld = id3Frame->GetField(ID3FN_PICTURETYPE);
1851
fld->Set(ID3PT_COVERFRONT);
1854
if (!frame.fieldList().empty()) {
1855
setId3v2Frame(id3Frame, frame);
1859
getTypeStringForId3libFrameId(id, type, name);
1860
m_tagV2->AttachFrame(id3Frame);
1861
frame.setExtendedType(Frame::ExtendedType(type, QString::fromLatin1(name)));
1862
frame.setIndex(m_tagV2->NumFrames() - 1);
1863
if (frame.fieldList().empty()) {
1864
// add field list to frame
1865
getFieldsFromId3Frame(id3Frame, frame.fieldList());
1866
frame.setFieldListFromValue();
1868
markTag2Changed(frame.getType());
1872
// Try the superclass method
1873
return TaggedFile::addFrameV2(frame);
1877
* Delete a frame in the tags 2.
1879
* @param frame frame to delete.
1881
* @return true if ok.
1883
bool Mp3File::deleteFrameV2(const Frame& frame)
1885
// If the frame has an index, delete that specific frame
1886
int index = frame.getIndex();
1887
if (index != -1 && m_tagV2) {
1888
ID3_Frame* id3Frame = getId3v2Frame(m_tagV2, index);
1890
m_tagV2->RemoveFrame(id3Frame);
1891
markTag2Changed(frame.getType());
1896
// Try the superclass method
1897
return TaggedFile::deleteFrameV2(frame);
1901
* Remove ID3v2 frames.
1903
* @param flt filter specifying which frames to remove
1905
void Mp3File::deleteFramesV2(const FrameFilter& flt)
1908
if (flt.areAllEnabled()) {
1909
ID3_Tag::Iterator* iter = m_tagV2->CreateIterator();
1911
while ((frame = iter->GetNext()) != NULL) {
1912
m_tagV2->RemoveFrame(frame);
1915
/* allocated in Windows DLL => must be freed in the same DLL */
1916
ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
1918
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1919
#pragma GCC diagnostic push
1920
#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
1921
/* Is safe because iterator implementation has default destructor */
1924
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1925
#pragma GCC diagnostic pop
1928
markTag2Changed(Frame::FT_UnknownFrame);
1930
ID3_Tag::Iterator* iter = m_tagV2->CreateIterator();
1932
while ((frame = iter->GetNext()) != NULL) {
1935
getTypeStringForId3libFrameId(frame->GetID(), type, name);
1936
if (flt.isEnabled(type, QString::fromLatin1(name))) {
1937
m_tagV2->RemoveFrame(frame);
1941
/* allocated in Windows DLL => must be freed in the same DLL */
1942
ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
1944
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1945
#pragma GCC diagnostic push
1946
#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
1947
/* Is safe because iterator implementation has default destructor */
1950
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
1951
#pragma GCC diagnostic pop
1954
markTag2Changed(Frame::FT_UnknownFrame);
1960
* Get all frames in tag 2.
1962
* @param frames frame collection to set.
1964
void Mp3File::getAllFramesV2(FrameCollection& frames)
1968
ID3_Tag::Iterator* iter = m_tagV2->CreateIterator();
1969
ID3_Frame* id3Frame;
1973
while ((id3Frame = iter->GetNext()) != 0) {
1974
getTypeStringForId3libFrameId(id3Frame->GetID(), type, name);
1975
Frame frame(type, QLatin1String(""), QString::fromLatin1(name), i++);
1976
frame.setValue(getFieldsFromId3Frame(id3Frame, frame.fieldList()));
1977
if (id3Frame->GetID() == ID3FID_USERTEXT ||
1978
id3Frame->GetID() == ID3FID_WWWUSER ||
1979
id3Frame->GetID() == ID3FID_COMMENT) {
1980
QVariant fieldValue = frame.getFieldValue(Frame::Field::ID_Description);
1981
if (fieldValue.isValid()) {
1982
QString description = fieldValue.toString();
1983
if (!description.isEmpty()) {
1984
if (description == QLatin1String("CATALOGNUMBER")) {
1985
frame.setType(Frame::FT_CatalogNumber);
1986
} else if (description == QLatin1String("RELEASECOUNTRY")) {
1987
frame.setType(Frame::FT_ReleaseCountry);
1989
frame.setExtendedType(Frame::ExtendedType(Frame::FT_Other,
1990
QString::fromLatin1(name) + QLatin1Char('\n') + description));
1994
} else if (id3Frame->GetID() == ID3FID_PRIVATE) {
1995
const Frame::FieldList& fields = frame.getFieldList();
1998
for (Frame::FieldList::const_iterator it = fields.begin();
2001
if ((*it).m_id == Frame::Field::ID_Owner) {
2002
owner = (*it).m_value.toString();
2003
if (!owner.isEmpty()) {
2004
frame.setExtendedType(Frame::ExtendedType(Frame::FT_Other,
2005
QString::fromLatin1(name) + QLatin1Char('\n') + owner));
2007
} else if ((*it).m_id == Frame::Field::ID_Data) {
2008
data = (*it).m_value.toByteArray();
2011
if (!owner.isEmpty() && !data.isEmpty()) {
2013
if (AttributeData(owner).toString(data, str)) {
2014
frame.setValue(str);
2017
} else if (id3Frame->GetID() == ID3FID_CDID) {
2018
QVariant fieldValue = frame.getFieldValue(Frame::Field::ID_Data);
2019
if (fieldValue.isValid()) {
2021
if (AttributeData(AttributeData::Utf16).toString(fieldValue.toByteArray(), str) &&
2022
AttributeData::isHexString(str, 'F', QLatin1String("+"))) {
2023
frame.setValue(str);
2026
} else if (id3Frame->GetID() == ID3FID_UNIQUEFILEID) {
2027
QVariant fieldValue = frame.getFieldValue(Frame::Field::ID_Data);
2028
if (fieldValue.isValid()) {
2029
QByteArray ba(fieldValue.toByteArray());
2030
QString str(QString::fromLatin1(ba));
2031
if (ba.size() - str.length() <= 1 &&
2032
AttributeData::isHexString(str, 'Z')) {
2033
frame.setValue(str);
2036
} else if (id3Frame->GetID() == ID3FID_POPULARIMETER) {
2037
QVariant fieldValue = frame.getFieldValue(Frame::Field::ID_Rating);
2038
if (fieldValue.isValid()) {
2039
QString str(fieldValue.toString());
2040
if (!str.isEmpty()) {
2041
frame.setValue(str);
2045
frames.insert(frame);
2048
/* allocated in Windows DLL => must be freed in the same DLL */
2049
ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
2051
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2052
#pragma GCC diagnostic push
2053
#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
2054
/* Is safe because iterator implementation has default destructor */
2057
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
2058
#pragma GCC diagnostic pop
2062
frames.addMissingStandardFrames();
2066
* Get a list of frame IDs which can be added.
2068
* @return list with frame IDs.
2070
QStringList Mp3File::getFrameIds() const
2073
for (int type = Frame::FT_FirstFrame; type <= Frame::FT_LastFrame; ++type) {
2074
if (type != Frame::FT_Part) {
2075
lst.append(Frame::ExtendedType(static_cast<Frame::Type>(type), QLatin1String("")).
2076
getTranslatedName());
2079
for (int i = 0; i <= ID3FID_WWWUSER; ++i) {
2080
if (typeStrOfId[i].type == Frame::FT_Other) {
2081
const char* s = typeStrOfId[i].str;
2083
lst.append(QCoreApplication::translate("@default", s));
2091
* Set the text codec to be used for tag 1.
2093
* @param codec text codec, 0 to use default (ISO 8859-1)
2095
void Mp3File::setTextCodecV1(const QTextCodec* codec)
2097
s_textCodecV1 = codec;
2101
* Set the default text encoding.
2103
* @param textEnc default text encoding
2105
void Mp3File::setDefaultTextEncoding(TagConfig::TextEncoding textEnc)
2107
// UTF8 encoding is buggy, so UTF16 is used when UTF8 is configured
2108
s_defaultTextEncoding = textEnc == TagConfig::TE_ISO8859_1 ?
2109
ID3TE_ISO8859_1 : ID3TE_UTF16;
2113
* Notify about configuration change.
2114
* This method shall be called when the configuration changes.
2116
void Mp3File::notifyConfigurationChange()
2118
const QTextCodec* id3v1TextCodec =
2119
TagConfig::instance().textEncodingV1() != QLatin1String("ISO-8859-1") ?
2120
QTextCodec::codecForName(TagConfig::instance().textEncodingV1().toLatin1().data()) : 0;
2121
setDefaultTextEncoding(
2122
static_cast<TagConfig::TextEncoding>(TagConfig::instance().textEncoding()));
2123
setTextCodecV1(id3v1TextCodec);