~ubuntu-branches/ubuntu/wily/kid3/wily

« back to all changes in this revision

Viewing changes to src/plugins/id3libmetadata/mp3file.cpp

  • Committer: Package Import Robot
  • Author(s): Mark Purcell, Patrick Matthäi, Mark Purcell
  • Date: 2013-11-30 15:44:59 UTC
  • mfrom: (1.1.16) (2.1.18 sid)
  • Revision ID: package-import@ubuntu.com-20131130154459-s6lpalx8yy2zq7gx
Tags: 3.0.2-1
* New upstream release 

[ Patrick Matthäi ]
* New upstream release.
  - Add new libreadline-dev build dependency.
* Don't explicitly request xz compression - dpkg 1.17 does this by default.
* Bump Standards-Version to 3.9.5 (no changes needed).
* Fix Vcs-Browser control field.

[ Mark Purcell ]
* Switch to dh - reduce debian/rules bloat
* kid3 Replaces kid3-qt - low popcon, reduce archive bloat
* Fix vcs-field-not-canonical
* debian/compat -> 9
* Update Description:

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 * \file mp3file.cpp
 
3
 * Handling of tagged MP3 files.
 
4
 *
 
5
 * \b Project: Kid3
 
6
 * \author Urs Fleisch
 
7
 * \date 9 Jan 2003
 
8
 *
 
9
 * Copyright (C) 2003-2013  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 "mp3file.h"
 
28
 
 
29
#include <QDir>
 
30
#include <QString>
 
31
#include <QTextCodec>
 
32
#include <QByteArray>
 
33
#include <QCoreApplication>
 
34
 
 
35
#include <cstring>
 
36
#include <sys/stat.h>
 
37
#include <id3/tag.h>
 
38
#ifdef Q_OS_WIN32
 
39
#include <id3.h>
 
40
#include <sys/utime.h>
 
41
#else
 
42
#include <utime.h>
 
43
#endif
 
44
 
 
45
#include "id3libconfig.h"
 
46
#include "genres.h"
 
47
#include "attributedata.h"
 
48
 
 
49
#ifdef Q_OS_WIN32
 
50
/**
 
51
 * This will be set for id3lib versions with Unicode bugs.
 
52
 * ID3LIB_ symbols cannot be found on Windows ?!
 
53
 */
 
54
#define UNICODE_SUPPORT_BUGGY 1
 
55
#else
 
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)
 
58
#endif
 
59
 
 
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
 
63
#endif
 
64
 
 
65
namespace {
 
66
 
 
67
/** Text codec for ID3v1 tags, 0 to use default (ISO 8859-1) */
 
68
const QTextCodec* s_textCodecV1 = 0;
 
69
 
 
70
/** Default text encoding */
 
71
ID3_TextEnc s_defaultTextEncoding = ID3TE_ISO8859_1;
 
72
 
 
73
/**
 
74
 * Get the default text encoding.
 
75
 * @return default text encoding.
 
76
 */
 
77
ID3_TextEnc getDefaultTextEncoding() { return s_defaultTextEncoding; }
 
78
 
 
79
}
 
80
 
 
81
/**
 
82
 * Constructor.
 
83
 *
 
84
 * @param dn directory name
 
85
 * @param fn filename
 
86
 * @param idx model index
 
87
 */
 
88
Mp3File::Mp3File(const QString& dn, const QString& fn,
 
89
                 const QPersistentModelIndex& idx) :
 
90
  TaggedFile(dn, fn, idx), m_tagV1(0), m_tagV2(0)
 
91
{
 
92
}
 
93
 
 
94
/**
 
95
 * Destructor.
 
96
 */
 
97
Mp3File::~Mp3File()
 
98
{
 
99
  if (m_tagV1) {
 
100
    delete m_tagV1;
 
101
  }
 
102
  if (m_tagV2) {
 
103
    delete m_tagV2;
 
104
  }
 
105
}
 
106
 
 
107
/**
 
108
 * Get key of tagged file format.
 
109
 * @return "Id3libMetadata".
 
110
 */
 
111
QString Mp3File::taggedFileKey() const
 
112
{
 
113
  return QLatin1String("Id3libMetadata");
 
114
}
 
115
 
 
116
/**
 
117
 * Get features supported.
 
118
 * @return bit mask with Feature flags set.
 
119
 */
 
120
int Mp3File::taggedFileFeatures() const
 
121
{
 
122
  return TF_ID3v11 | TF_ID3v23;
 
123
}
 
124
 
 
125
/**
 
126
 * Read tags from file.
 
127
 *
 
128
 * @param force true to force reading even if tags were already read.
 
129
 */
 
130
void Mp3File::readTags(bool force)
 
131
{
 
132
  QByteArray fn = QFile::encodeName(getDirname() + QDir::separator() + currentFilename());
 
133
 
 
134
  if (force && m_tagV1) {
 
135
    m_tagV1->Clear();
 
136
    m_tagV1->Link(fn, ID3TT_ID3V1);
 
137
    markTag1Unchanged();
 
138
  }
 
139
  if (!m_tagV1) {
 
140
    m_tagV1 = new ID3_Tag;
 
141
    m_tagV1->Link(fn, ID3TT_ID3V1);
 
142
    markTag1Unchanged();
 
143
  }
 
144
 
 
145
  if (force && m_tagV2) {
 
146
    m_tagV2->Clear();
 
147
    m_tagV2->Link(fn, ID3TT_ID3V2);
 
148
    markTag2Unchanged();
 
149
  }
 
150
  if (!m_tagV2) {
 
151
    m_tagV2 = new ID3_Tag;
 
152
    m_tagV2->Link(fn, ID3TT_ID3V2);
 
153
    markTag2Unchanged();
 
154
  }
 
155
 
 
156
  if (force) {
 
157
    setFilename(currentFilename());
 
158
  }
 
159
}
 
160
 
 
161
/**
 
162
 * Write tags to file and rename it if necessary.
 
163
 *
 
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
 
167
 *                is left unchanged
 
168
 * @param preserve true to preserve file time stamps
 
169
 *
 
170
 * @return true if ok, false if the file could not be written or renamed.
 
171
 */
 
172
bool Mp3File::writeTags(bool force, bool* renamed, bool preserve)
 
173
{
 
174
  QString fnStr(getDirname() + QDir::separator() + currentFilename());
 
175
  if (isChanged() && !QFileInfo(fnStr).isWritable()) {
 
176
    return false;
 
177
  }
 
178
 
 
179
  // store time stamp if it has to be preserved
 
180
  QByteArray fn;
 
181
  bool setUtime = false;
 
182
  struct utimbuf times;
 
183
  if (preserve) {
 
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;
 
189
      setUtime = true;
 
190
    }
 
191
  }
 
192
 
 
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);
 
198
    markTag1Unchanged();
 
199
  }
 
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);
 
204
    markTag2Unchanged();
 
205
  }
 
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);
 
211
    markTag1Unchanged();
 
212
  }
 
213
  if (m_tagV2 && (force || isTag2Changed()) && (m_tagV2->NumFrames() > 0)) {
 
214
    m_tagV2->Update(ID3TT_ID3V2);
 
215
    markTag2Unchanged();
 
216
  }
 
217
 
 
218
  // restore time stamp
 
219
  if (setUtime) {
 
220
    ::utime(fn, &times);
 
221
  }
 
222
 
 
223
  if (getFilename() != currentFilename()) {
 
224
    if (!renameFile(currentFilename(), getFilename())) {
 
225
      return false;
 
226
    }
 
227
    updateCurrentFilename();
 
228
    // link tags to new file name
 
229
    readTags(true);
 
230
    *renamed = true;
 
231
  }
 
232
  return true;
 
233
}
 
234
 
 
235
/**
 
236
 * Remove ID3v1 frames.
 
237
 *
 
238
 * @param flt filter specifying which frames to remove
 
239
 */
 
240
void Mp3File::deleteFramesV1(const FrameFilter& flt)
 
241
{
 
242
  if (m_tagV1) {
 
243
    if (flt.areAllEnabled()) {
 
244
      ID3_Tag::Iterator* iter = m_tagV1->CreateIterator();
 
245
      ID3_Frame* frame;
 
246
      while ((frame = iter->GetNext()) != NULL) {
 
247
        m_tagV1->RemoveFrame(frame);
 
248
      }
 
249
#ifdef Q_OS_WIN32
 
250
      /* allocated in Windows DLL => must be freed in the same DLL */
 
251
      ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
 
252
#else
 
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 */
 
257
#endif
 
258
      delete iter;
 
259
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
 
260
#pragma GCC diagnostic pop
 
261
#endif
 
262
#endif
 
263
      markTag1Changed(Frame::FT_UnknownFrame);
 
264
      clearTrunctionFlags();
 
265
    } else {
 
266
      TaggedFile::deleteFramesV1(flt);
 
267
    }
 
268
  }
 
269
}
 
270
 
 
271
/**
 
272
 * Fix up a unicode string from id3lib.
 
273
 *
 
274
 * @param str      unicode string
 
275
 * @param numChars number of characters in str
 
276
 *
 
277
 * @return string as QString.
 
278
 */
 
279
static QString fixUpUnicode(const unicode_t* str, size_t numChars)
 
280
{
 
281
  QString text;
 
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++) {
 
290
      qcarray[i] =
 
291
        UNICODE_SUPPORT_BUGGY ?
 
292
        (ushort)(((str[i] & 0x00ff) << 8) |
 
293
                 ((str[i] & 0xff00) >> 8)) :
 
294
        (ushort)str[i];
 
295
      if (qcarray[i].isNull()) { ++numZeroes; }
 
296
    }
 
297
    // remove a single trailing zero character
 
298
    if (numZeroes == 1 && qcarray[numChars - 1].isNull()) {
 
299
      --numChars;
 
300
    }
 
301
    text = QString(qcarray, numChars);
 
302
    delete [] qcarray;
 
303
  }
 
304
  return text;
 
305
}
 
306
 
 
307
/**
 
308
 * Get string from text field.
 
309
 *
 
310
 * @param field field
 
311
 * @param codec text codec to use, 0 for default
 
312
 *
 
313
 * @return string,
 
314
 *         "" if the field does not exist.
 
315
 */
 
316
static QString getString(ID3_Field* field, const QTextCodec* codec = 0)
 
317
{
 
318
  QString text(QLatin1String(""));
 
319
  if (field != NULL) {
 
320
    ID3_TextEnc enc = field->GetEncoding();
 
321
    if (enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
 
322
      size_t numItems = field->GetNumTextItems();
 
323
      if (numItems <= 1) {
 
324
        text = fixUpUnicode(field->GetRawUnicodeText(),
 
325
                            field->Size() / sizeof(unicode_t));
 
326
      } else {
 
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());
 
334
      }
 
335
    } else {
 
336
      // (ID3TE_IS_SINGLE_BYTE_ENC(enc))
 
337
      // (enc == ID3TE_ISO8859_1 || enc == ID3TE_UTF8)
 
338
      size_t numItems = field->GetNumTextItems();
 
339
      if (numItems <= 1) {
 
340
        text = codec ?
 
341
          codec->toUnicode(field->GetRawText(), field->Size()) :
 
342
          QString::fromLatin1(field->GetRawText());
 
343
      } else {
 
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) {
 
347
          if (itemNr == 0) {
 
348
            text = QString::fromLatin1(field->GetRawTextItem(0));
 
349
          } else {
 
350
            text += Frame::stringListSeparator();
 
351
            text += QString::fromLatin1(field->GetRawTextItem(itemNr));
 
352
          }
 
353
        }
 
354
      }
 
355
    }
 
356
  }
 
357
  return text;
 
358
}
 
359
 
 
360
/**
 
361
 * Get text field.
 
362
 *
 
363
 * @param tag ID3 tag
 
364
 * @param id  frame ID
 
365
 * @param codec text codec to use, 0 for default
 
366
 * @return string,
 
367
 *         "" if the field does not exist,
 
368
 *         QString::null if the tags do not exist.
 
369
 */
 
370
static QString getTextField(const ID3_Tag* tag, ID3_FrameID id,
 
371
                            const QTextCodec* codec = 0)
 
372
{
 
373
  if (!tag) {
 
374
    return QString();
 
375
  }
 
376
  QString str(QLatin1String(""));
 
377
  ID3_Field* fld;
 
378
  ID3_Frame* frame = tag->Find(id);
 
379
  if (frame && ((fld = frame->GetField(ID3FN_TEXT)) != NULL)) {
 
380
    str = getString(fld, codec);
 
381
  }
 
382
  return str;
 
383
}
 
384
 
 
385
/**
 
386
 * Get year.
 
387
 *
 
388
 * @param tag ID3 tag
 
389
 * @return number,
 
390
 *         0 if the field does not exist,
 
391
 *         -1 if the tags do not exist.
 
392
 */
 
393
static int getYear(const ID3_Tag* tag)
 
394
{
 
395
  QString str = getTextField(tag, ID3FID_YEAR);
 
396
  if (str.isNull()) return -1;
 
397
  if (str.isEmpty()) return 0;
 
398
  return str.toInt();
 
399
}
 
400
 
 
401
/**
 
402
 * Get track.
 
403
 *
 
404
 * @param tag ID3 tag
 
405
 * @return number,
 
406
 *         0 if the field does not exist,
 
407
 *         -1 if the tags do not exist.
 
408
 */
 
409
static int getTrackNum(const ID3_Tag* tag)
 
410
{
 
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);
 
418
  }
 
419
  return str.toInt();
 
420
}
 
421
 
 
422
/**
 
423
 * Get genre.
 
424
 *
 
425
 * @param tag ID3 tag
 
426
 * @return number,
 
427
 *         0xff if the field does not exist,
 
428
 *         -1 if the tags do not exist.
 
429
 */
 
430
static int getGenreNum(const ID3_Tag* tag)
 
431
{
 
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)) {
 
437
    bool ok;
 
438
    n = str.mid(1, cpPos - 1).toInt(&ok);
 
439
    if (!ok || n > 0xff) {
 
440
      n = 0xff;
 
441
    }
 
442
  } else {
 
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);
 
447
  }
 
448
  return n;
 
449
}
 
450
 
 
451
/**
 
452
 * Allocate a fixed up a unicode string for id3lib.
 
453
 *
 
454
 * @param text string
 
455
 *
 
456
 * @return new allocated unicode string, has to be freed with delete [].
 
457
 */
 
458
static unicode_t* newFixedUpUnicode(const QString& text)
 
459
{
 
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
 
468
  // LSB >= 0x80 bug.
 
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));
 
477
    }
 
478
  }
 
479
  unicode[unicode_size] = 0;
 
480
  return unicode;
 
481
}
 
482
 
 
483
/**
 
484
 * Set string list in text field.
 
485
 *
 
486
 * @param field field
 
487
 * @param lst   string list to set
 
488
 */
 
489
static void setStringList(ID3_Field* field, const QStringList& lst)
 
490
{
 
491
  ID3_TextEnc enc = field->GetEncoding();
 
492
  bool first = true;
 
493
  for (QStringList::const_iterator it = lst.begin(); it != lst.end(); ++it) {
 
494
    if (first) {
 
495
      if (enc == ID3TE_UTF16 || enc == ID3TE_UTF16BE) {
 
496
        unicode_t* unicode = newFixedUpUnicode(*it);
 
497
        if (unicode) {
 
498
          field->Set(unicode);
 
499
          delete [] unicode;
 
500
        }
 
501
      } else if (enc == ID3TE_UTF8) {
 
502
        field->Set((*it).toUtf8().data());
 
503
      } else {
 
504
        // enc == ID3TE_ISO8859_1
 
505
        field->Set((*it).toLatin1().data());
 
506
      }
 
507
      first = false;
 
508
    } else {
 
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);
 
516
        if (unicode) {
 
517
          field->Add(unicode);
 
518
          delete [] unicode;
 
519
        }
 
520
      } else if (enc == ID3TE_UTF8) {
 
521
        field->Add((*it).toUtf8().data());
 
522
      } else {
 
523
        // enc == ID3TE_ISO8859_1
 
524
        field->Add((*it).toLatin1().data());
 
525
      }
 
526
    }
 
527
  }
 
528
}
 
529
 
 
530
/**
 
531
 * Set string in text field.
 
532
 *
 
533
 * @param field        field
 
534
 * @param text         text to set
 
535
 * @param codec        text codec to use, 0 for default
 
536
 */
 
537
static void setString(ID3_Field* field, const QString& text,
 
538
                      const QTextCodec* codec = 0)
 
539
{
 
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);
 
545
      if (unicode) {
 
546
        field->Set(unicode);
 
547
        delete [] unicode;
 
548
      }
 
549
    } else if (enc == ID3TE_UTF8) {
 
550
      field->Set(text.toUtf8().data());
 
551
    } else {
 
552
      // enc == ID3TE_ISO8859_1
 
553
      field->Set(codec ? codec->fromUnicode(text).data() : text.toLatin1().data());
 
554
    }
 
555
  } else {
 
556
    setStringList(field, text.split(Frame::stringListSeparator()));
 
557
  }
 
558
}
 
559
 
 
560
/**
 
561
 * Set text field.
 
562
 *
 
563
 * @param tag          ID3 tag
 
564
 * @param id           frame ID
 
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
 
570
 *
 
571
 * @return true if the field was changed.
 
572
 */
 
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)
 
576
{
 
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, "");
 
584
      } else {
 
585
        frame = tag->Find(id);
 
586
      }
 
587
      if (frame) {
 
588
        frame = tag->RemoveFrame(frame);
 
589
        delete frame;
 
590
        changed = true;
 
591
      }
 
592
    }
 
593
    if (!removeOnly && (replace || tag->Find(id) == NULL)) {
 
594
      frame = new ID3_Frame(id);
 
595
      if (frame) {
 
596
        ID3_Field* fld = frame->GetField(ID3FN_TEXT);
 
597
        if (fld) {
 
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) {
 
607
                enc = ID3TE_UTF16;
 
608
                break;
 
609
              }
 
610
            }
 
611
          }
 
612
          ID3_Field* encfld = frame->GetField(ID3FN_TEXTENC);
 
613
          if (encfld) {
 
614
            encfld->Set(enc);
 
615
          }
 
616
          fld->SetEncoding(enc);
 
617
          setString(fld, text, codec);
 
618
          tag->AttachFrame(frame);
 
619
        }
 
620
      }
 
621
      changed = true;
 
622
    }
 
623
  }
 
624
  return changed;
 
625
}
 
626
 
 
627
/**
 
628
 * Set year.
 
629
 *
 
630
 * @param tag ID3 tag
 
631
 * @param num number to set, 0 to remove field.
 
632
 *
 
633
 * @return true if the field was changed.
 
634
 */
 
635
static bool setYear(ID3_Tag* tag, int num)
 
636
{
 
637
  bool changed = false;
 
638
  if (num >= 0) {
 
639
    QString str;
 
640
    if (num != 0) {
 
641
      str.setNum(num);
 
642
    } else {
 
643
      str.clear();
 
644
    }
 
645
    changed = getTextField(tag, ID3FID_YEAR) != str &&
 
646
              setTextField(tag, ID3FID_YEAR, str);
 
647
  }
 
648
  return changed;
 
649
}
 
650
 
 
651
/**
 
652
 * Set track.
 
653
 *
 
654
 * @param tag ID3 tag
 
655
 * @param num number to set, 0 to remove field.
 
656
 * @param numTracks total number of tracks, <=0 to ignore
 
657
 *
 
658
 * @return true if the field was changed.
 
659
 */
 
660
bool Mp3File::setTrackNum(ID3_Tag* tag, int num, int numTracks) const
 
661
{
 
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);
 
667
  }
 
668
  return changed;
 
669
}
 
670
 
 
671
/**
 
672
 * Set genre.
 
673
 *
 
674
 * @param tag ID3 tag
 
675
 * @param num number to set, 0xff to remove field.
 
676
 *
 
677
 * @return true if the field was changed.
 
678
 */
 
679
static bool setGenreNum(ID3_Tag* tag, int num)
 
680
{
 
681
  bool changed = false;
 
682
  if (num >= 0) {
 
683
    QString str;
 
684
    if (num != 0xff) {
 
685
      str = QString(QLatin1String("(%1)")).arg(num);
 
686
    } else {
 
687
      str.clear();
 
688
    }
 
689
    changed = getTextField(tag, ID3FID_CONTENTTYPE) != str &&
 
690
              setTextField(tag, ID3FID_CONTENTTYPE, str);
 
691
  }
 
692
  return changed;
 
693
}
 
694
 
 
695
/**
 
696
 * Get ID3v1 title.
 
697
 *
 
698
 * @return string,
 
699
 *         "" if the field does not exist,
 
700
 *         QString::null if the tags do not exist.
 
701
 */
 
702
QString Mp3File::getTitleV1()
 
703
{
 
704
  return getTextField(m_tagV1, ID3FID_TITLE, s_textCodecV1);
 
705
}
 
706
 
 
707
/**
 
708
 * Get ID3v1 artist.
 
709
 *
 
710
 * @return string,
 
711
 *         "" if the field does not exist,
 
712
 *         QString::null if the tags do not exist.
 
713
 */
 
714
QString Mp3File::getArtistV1()
 
715
{
 
716
  return getTextField(m_tagV1, ID3FID_LEADARTIST, s_textCodecV1);
 
717
}
 
718
 
 
719
/**
 
720
 * Get ID3v1 album.
 
721
 *
 
722
 * @return string,
 
723
 *         "" if the field does not exist,
 
724
 *         QString::null if the tags do not exist.
 
725
 */
 
726
QString Mp3File::getAlbumV1()
 
727
{
 
728
  return getTextField(m_tagV1, ID3FID_ALBUM, s_textCodecV1);
 
729
}
 
730
 
 
731
/**
 
732
 * Get ID3v1 comment.
 
733
 *
 
734
 * @return string,
 
735
 *         "" if the field does not exist,
 
736
 *         QString::null if the tags do not exist.
 
737
 */
 
738
QString Mp3File::getCommentV1()
 
739
{
 
740
  return getTextField(m_tagV1, ID3FID_COMMENT, s_textCodecV1);
 
741
}
 
742
 
 
743
/**
 
744
 * Get ID3v1 year.
 
745
 *
 
746
 * @return number,
 
747
 *         0 if the field does not exist,
 
748
 *         -1 if the tags do not exist.
 
749
 */
 
750
int Mp3File::getYearV1()
 
751
{
 
752
  return getYear(m_tagV1);
 
753
}
 
754
 
 
755
/**
 
756
 * Get ID3v1 track.
 
757
 *
 
758
 * @return number,
 
759
 *         0 if the field does not exist,
 
760
 *         -1 if the tags do not exist.
 
761
 */
 
762
int Mp3File::getTrackNumV1()
 
763
{
 
764
  return getTrackNum(m_tagV1);
 
765
}
 
766
 
 
767
/**
 
768
 * Get ID3v1 genre.
 
769
 *
 
770
 * @return string,
 
771
 *         "" if the field does not exist,
 
772
 *         QString::null if the tags do not exist.
 
773
 */
 
774
QString Mp3File::getGenreV1()
 
775
{
 
776
  int num = getGenreNum(m_tagV1);
 
777
  if (num == -1) {
 
778
    return QString();
 
779
  } else if (num == 0xff) {
 
780
    return QLatin1String("");
 
781
  } else {
 
782
    return QString::fromLatin1(Genres::getName(num));
 
783
  }
 
784
}
 
785
 
 
786
/**
 
787
 * Get ID3v2 title.
 
788
 *
 
789
 * @return string,
 
790
 *         "" if the field does not exist,
 
791
 *         QString::null if the tags do not exist.
 
792
 */
 
793
QString Mp3File::getTitleV2()
 
794
{
 
795
  return getTextField(m_tagV2, ID3FID_TITLE);
 
796
}
 
797
 
 
798
/**
 
799
 * Get ID3v2 artist.
 
800
 *
 
801
 * @return string,
 
802
 *         "" if the field does not exist,
 
803
 *         QString::null if the tags do not exist.
 
804
 */
 
805
QString Mp3File::getArtistV2()
 
806
{
 
807
  return getTextField(m_tagV2, ID3FID_LEADARTIST);
 
808
}
 
809
 
 
810
/**
 
811
 * Get ID3v2 album.
 
812
 *
 
813
 * @return string,
 
814
 *         "" if the field does not exist,
 
815
 *         QString::null if the tags do not exist.
 
816
 */
 
817
QString Mp3File::getAlbumV2()
 
818
{
 
819
  return getTextField(m_tagV2, ID3FID_ALBUM);
 
820
}
 
821
 
 
822
/**
 
823
 * Get ID3v2 comment.
 
824
 *
 
825
 * @return string,
 
826
 *         "" if the field does not exist,
 
827
 *         QString::null if the tags do not exist.
 
828
 */
 
829
QString Mp3File::getCommentV2()
 
830
{
 
831
  return getTextField(m_tagV2, ID3FID_COMMENT);
 
832
}
 
833
 
 
834
/**
 
835
 * Get ID3v2 year.
 
836
 *
 
837
 * @return number,
 
838
 *         0 if the field does not exist,
 
839
 *         -1 if the tags do not exist.
 
840
 */
 
841
int Mp3File::getYearV2()
 
842
{
 
843
  return getYear(m_tagV2);
 
844
}
 
845
 
 
846
/**
 
847
 * Get ID3v2 track.
 
848
 *
 
849
 * @return string,
 
850
 *         "" if the field does not exist,
 
851
 *         QString::null if the tags do not exist.
 
852
 */
 
853
QString Mp3File::getTrackV2()
 
854
{
 
855
  return getTextField(m_tagV2, ID3FID_TRACKNUM);
 
856
}
 
857
 
 
858
/**
 
859
 * Get ID3v2 genre as text.
 
860
 *
 
861
 * @return string,
 
862
 *         "" if the field does not exist,
 
863
 *         QString::null if the tags do not exist.
 
864
 */
 
865
QString Mp3File::getGenreV2()
 
866
{
 
867
  int num = getGenreNum(m_tagV2);
 
868
  if (num != 0xff && num != -1) {
 
869
    return QString::fromLatin1(Genres::getName(num));
 
870
  } else {
 
871
    return getTextField(m_tagV2, ID3FID_CONTENTTYPE);
 
872
  }
 
873
}
 
874
 
 
875
/**
 
876
 * Set ID3v1 title.
 
877
 *
 
878
 * @param str string to set, "" to remove field.
 
879
 */
 
880
void Mp3File::setTitleV1(const QString& str)
 
881
{
 
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);
 
887
  }
 
888
}
 
889
 
 
890
/**
 
891
 * Set ID3v1 artist.
 
892
 *
 
893
 * @param str string to set, "" to remove field.
 
894
 */
 
895
void Mp3File::setArtistV1(const QString& str)
 
896
{
 
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);
 
902
  }
 
903
}
 
904
 
 
905
/**
 
906
 * Set ID3v1 album.
 
907
 *
 
908
 * @param str string to set, "" to remove field.
 
909
 */
 
910
void Mp3File::setAlbumV1(const QString& str)
 
911
{
 
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);
 
917
  }
 
918
}
 
919
 
 
920
/**
 
921
 * Set ID3v1 comment.
 
922
 *
 
923
 * @param str string to set, "" to remove field.
 
924
 */
 
925
void Mp3File::setCommentV1(const QString& str)
 
926
{
 
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);
 
932
  }
 
933
}
 
934
 
 
935
/**
 
936
 * Set ID3v1 year.
 
937
 *
 
938
 * @param num number to set, 0 to remove field.
 
939
 */
 
940
void Mp3File::setYearV1(int num)
 
941
{
 
942
  if (setYear(m_tagV1, num)) {
 
943
    markTag1Changed(Frame::FT_Date);
 
944
  }
 
945
}
 
946
 
 
947
/**
 
948
 * Set ID3v1 track.
 
949
 *
 
950
 * @param num number to set, 0 to remove field.
 
951
 */
 
952
void Mp3File::setTrackNumV1(int num)
 
953
{
 
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);
 
958
  }
 
959
}
 
960
 
 
961
/**
 
962
 * Set ID3v1 genre as text.
 
963
 *
 
964
 * @param str string to set, "" to remove field, QString::null to ignore.
 
965
 */
 
966
void Mp3File::setGenreV1(const QString& str)
 
967
{
 
968
  if (!str.isNull()) {
 
969
    int num = Genres::getNumber(str);
 
970
    if (setGenreNum(m_tagV1, num)) {
 
971
      markTag1Changed(Frame::FT_Genre);
 
972
    }
 
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);
 
976
  }
 
977
}
 
978
 
 
979
/**
 
980
 * Set ID3v2 title.
 
981
 *
 
982
 * @param str string to set, "" to remove field.
 
983
 */
 
984
void Mp3File::setTitleV2(const QString& str)
 
985
{
 
986
  if (getTextField(m_tagV2, ID3FID_TITLE) != str &&
 
987
      setTextField(m_tagV2, ID3FID_TITLE, str, true)) {
 
988
    markTag2Changed(Frame::FT_Title);
 
989
  }
 
990
}
 
991
 
 
992
/**
 
993
 * Set ID3v2 artist.
 
994
 *
 
995
 * @param str string to set, "" to remove field.
 
996
 */
 
997
void Mp3File::setArtistV2(const QString& str)
 
998
{
 
999
  if (getTextField(m_tagV2, ID3FID_LEADARTIST) != str &&
 
1000
      setTextField(m_tagV2, ID3FID_LEADARTIST, str, true)) {
 
1001
    markTag2Changed(Frame::FT_Artist);
 
1002
  }
 
1003
}
 
1004
 
 
1005
/**
 
1006
 * Set ID3v2 album.
 
1007
 *
 
1008
 * @param str string to set, "" to remove field.
 
1009
 */
 
1010
void Mp3File::setAlbumV2(const QString& str)
 
1011
{
 
1012
  if (getTextField(m_tagV2, ID3FID_ALBUM) != str &&
 
1013
      setTextField(m_tagV2, ID3FID_ALBUM, str, true)) {
 
1014
    markTag2Changed(Frame::FT_Album);
 
1015
  }
 
1016
}
 
1017
 
 
1018
/**
 
1019
 * Set ID3v2 comment.
 
1020
 *
 
1021
 * @param str string to set, "" to remove field.
 
1022
 */
 
1023
void Mp3File::setCommentV2(const QString& str)
 
1024
{
 
1025
  if (getTextField(m_tagV2, ID3FID_COMMENT) != str &&
 
1026
      setTextField(m_tagV2, ID3FID_COMMENT, str, true)) {
 
1027
    markTag2Changed(Frame::FT_Comment);
 
1028
  }
 
1029
}
 
1030
 
 
1031
/**
 
1032
 * Set ID3v2 year.
 
1033
 *
 
1034
 * @param num number to set, 0 to remove field.
 
1035
 */
 
1036
void Mp3File::setYearV2(int num)
 
1037
{
 
1038
  if (setYear(m_tagV2, num)) {
 
1039
    markTag2Changed(Frame::FT_Date);
 
1040
  }
 
1041
}
 
1042
 
 
1043
/**
 
1044
 * Set ID3v2 track.
 
1045
 *
 
1046
 * @param track string to set, "" to remove field, QString::null to ignore.
 
1047
 */
 
1048
void Mp3File::setTrackV2(const QString& track)
 
1049
{
 
1050
  int numTracks;
 
1051
  int num = splitNumberAndTotal(track, &numTracks);
 
1052
  if (setTrackNum(m_tagV2, num, numTracks)) {
 
1053
    markTag2Changed(Frame::FT_Track);
 
1054
  }
 
1055
}
 
1056
 
 
1057
/**
 
1058
 * Set ID3v2 genre as text.
 
1059
 *
 
1060
 * @param str string to set, "" to remove field, QString::null to ignore.
 
1061
 */
 
1062
void Mp3File::setGenreV2(const QString& str)
 
1063
{
 
1064
  if (!str.isNull()) {
 
1065
    int num = 0xff;
 
1066
    if (!TagConfig::instance().genreNotNumeric()) {
 
1067
      num = Genres::getNumber(str);
 
1068
    }
 
1069
    if (num >= 0 && num != 0xff) {
 
1070
      if (getGenreNum(m_tagV2) != num &&
 
1071
          setGenreNum(m_tagV2, num)) {
 
1072
        markTag2Changed(Frame::FT_Genre);
 
1073
      }
 
1074
    } else {
 
1075
      if (getTextField(m_tagV2, ID3FID_CONTENTTYPE) != str &&
 
1076
          setTextField(m_tagV2, ID3FID_CONTENTTYPE, str, true)) {
 
1077
        markTag2Changed(Frame::FT_Genre);
 
1078
      }
 
1079
    }
 
1080
  }
 
1081
}
 
1082
 
 
1083
/**
 
1084
 * Check if tag information has already been read.
 
1085
 *
 
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.
 
1089
 */
 
1090
bool Mp3File::isTagInformationRead() const
 
1091
{
 
1092
  return m_tagV1 || m_tagV2;
 
1093
}
 
1094
 
 
1095
/**
 
1096
 * Check if file has an ID3v1 tag.
 
1097
 *
 
1098
 * @return true if a V1 tag is available.
 
1099
 * @see isTagInformationRead()
 
1100
 */
 
1101
bool Mp3File::hasTagV1() const
 
1102
{
 
1103
  return m_tagV1 && m_tagV1->HasV1Tag();
 
1104
}
 
1105
 
 
1106
/**
 
1107
 * Check if ID3v1 tags are supported by the format of this file.
 
1108
 *
 
1109
 * @return true.
 
1110
 */
 
1111
bool Mp3File::isTagV1Supported() const
 
1112
{
 
1113
  return true;
 
1114
}
 
1115
 
 
1116
/**
 
1117
 * Check if file has an ID3v2 tag.
 
1118
 *
 
1119
 * @return true if a V2 tag is available.
 
1120
 * @see isTagInformationRead()
 
1121
 */
 
1122
bool Mp3File::hasTagV2() const
 
1123
{
 
1124
  return m_tagV2 && m_tagV2->HasV2Tag();
 
1125
}
 
1126
 
 
1127
/**
 
1128
 * Get technical detail information.
 
1129
 *
 
1130
 * @param info the detail information is returned here
 
1131
 */
 
1132
void Mp3File::getDetailInfo(DetailInfo& info) const
 
1133
{
 
1134
  if (getFilename().right(4).toLower() == QLatin1String(".aac")) {
 
1135
    info.valid = true;
 
1136
    info.format = QLatin1String("AAC");
 
1137
    return;
 
1138
  }
 
1139
 
 
1140
  const Mp3_Headerinfo* headerInfo = 0;
 
1141
  if (m_tagV2) {
 
1142
    headerInfo = m_tagV2->GetMp3HeaderInfo();
 
1143
  }
 
1144
  if (!headerInfo && m_tagV1) {
 
1145
    headerInfo = m_tagV1->GetMp3HeaderInfo();
 
1146
  }
 
1147
  if (headerInfo) {
 
1148
    info.valid = true;
 
1149
    switch (headerInfo->version) {
 
1150
      case MPEGVERSION_1:
 
1151
        info.format = QLatin1String("MPEG 1 ");
 
1152
        break;
 
1153
      case MPEGVERSION_2:
 
1154
        info.format = QLatin1String("MPEG 2 ");
 
1155
        break;
 
1156
      case MPEGVERSION_2_5:
 
1157
        info.format = QLatin1String("MPEG 2.5 ");
 
1158
        break;
 
1159
      default:
 
1160
        ; // nothing
 
1161
    }
 
1162
    switch (headerInfo->layer) {
 
1163
      case MPEGLAYER_I:
 
1164
        info.format += QLatin1String("Layer 1");
 
1165
        break;
 
1166
      case MPEGLAYER_II:
 
1167
        info.format += QLatin1String("Layer 2");
 
1168
        break;
 
1169
      case MPEGLAYER_III:
 
1170
        info.format += QLatin1String("Layer 3");
 
1171
        break;
 
1172
      default:
 
1173
        ; // nothing
 
1174
    }
 
1175
    info.bitrate = headerInfo->bitrate / 1000;
 
1176
#ifndef HAVE_NO_ID3LIB_VBR
 
1177
    if (headerInfo->vbr_bitrate > 1000) {
 
1178
      info.vbr = true;
 
1179
      info.bitrate = headerInfo->vbr_bitrate / 1000;
 
1180
    }
 
1181
#endif
 
1182
    info.sampleRate = headerInfo->frequency;
 
1183
    switch (headerInfo->channelmode) {
 
1184
      case MP3CHANNELMODE_STEREO:
 
1185
        info.channelMode = DetailInfo::CM_Stereo;
 
1186
        info.channels = 2;
 
1187
        break;
 
1188
      case MP3CHANNELMODE_JOINT_STEREO:
 
1189
        info.channelMode = DetailInfo::CM_JointStereo;
 
1190
        info.channels = 2;
 
1191
        break;
 
1192
      case MP3CHANNELMODE_DUAL_CHANNEL:
 
1193
        info.channels = 2;
 
1194
        break;
 
1195
      case MP3CHANNELMODE_SINGLE_CHANNEL:
 
1196
        info.channels = 1;
 
1197
        break;
 
1198
      default:
 
1199
        ; // nothing
 
1200
    }
 
1201
    info.duration = headerInfo->time;
 
1202
  } else {
 
1203
    info.valid = false;
 
1204
  }
 
1205
}
 
1206
 
 
1207
/**
 
1208
 * Get duration of file.
 
1209
 *
 
1210
 * @return duration in seconds,
 
1211
 *         0 if unknown.
 
1212
 */
 
1213
unsigned Mp3File::getDuration() const
 
1214
{
 
1215
  unsigned duration = 0;
 
1216
  const Mp3_Headerinfo* info = NULL;
 
1217
  if (m_tagV2) {
 
1218
    info = m_tagV2->GetMp3HeaderInfo();
 
1219
  }
 
1220
  if (!info && m_tagV1) {
 
1221
    info = m_tagV1->GetMp3HeaderInfo();
 
1222
  }
 
1223
  if (info && info->time > 0) {
 
1224
    duration = info->time;
 
1225
  }
 
1226
  return duration;
 
1227
}
 
1228
 
 
1229
/**
 
1230
 * Get file extension including the dot.
 
1231
 *
 
1232
 * @return file extension ".mp3".
 
1233
 */
 
1234
QString Mp3File::getFileExtension() const
 
1235
{
 
1236
  QString ext(getFilename().right(4).toLower());
 
1237
  if (ext == QLatin1String(".aac") || ext == QLatin1String(".mp2"))
 
1238
    return ext;
 
1239
  else
 
1240
    return QLatin1String(".mp3");
 
1241
}
 
1242
 
 
1243
/**
 
1244
 * Get the format of tag 1.
 
1245
 *
 
1246
 * @return string describing format of tag 1,
 
1247
 *         e.g. "ID3v1.1".
 
1248
 */
 
1249
QString Mp3File::getTagFormatV1() const
 
1250
{
 
1251
  return hasTagV1() ? QString(QLatin1String("ID3v1.1")) : QString();
 
1252
}
 
1253
 
 
1254
/**
 
1255
 * Get the format of tag 2.
 
1256
 *
 
1257
 * @return string describing format of tag 2,
 
1258
 *         e.g. "ID3v2.3", "ID3v2.4".
 
1259
 */
 
1260
QString Mp3File::getTagFormatV2() const
 
1261
{
 
1262
  if (m_tagV2 && m_tagV2->HasV2Tag()) {
 
1263
    switch (m_tagV2->GetSpec()) {
 
1264
      case ID3V2_3_0:
 
1265
        return QLatin1String("ID3v2.3.0");
 
1266
      case ID3V2_4_0:
 
1267
        return QLatin1String("ID3v2.4.0");
 
1268
      case ID3V2_2_0:
 
1269
        return QLatin1String("ID3v2.2.0");
 
1270
      case ID3V2_2_1:
 
1271
        return QLatin1String("ID3v2.2.1");
 
1272
      default:
 
1273
        break;
 
1274
    }
 
1275
  }
 
1276
  return QString();
 
1277
}
 
1278
 
 
1279
/** Types and descriptions for id3lib frame IDs */
 
1280
static const struct TypeStrOfId {
 
1281
  Frame::Type type;
 
1282
  const char* str;
 
1283
} 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 */
 
1377
};
 
1378
 
 
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 */ };
 
1383
 
 
1384
/**
 
1385
 * Get type and description of frame.
 
1386
 *
 
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
 
1390
 */
 
1391
static void getTypeStringForId3libFrameId(ID3_FrameID id, Frame::Type& type,
 
1392
                                          const char*& str)
 
1393
{
 
1394
  const TypeStrOfId& ts = typeStrOfId[id <= ID3FID_WWWUSER ? id : 0];
 
1395
  type = ts.type;
 
1396
  str = ts.str;
 
1397
}
 
1398
 
 
1399
/**
 
1400
 * Get id3lib frame ID for frame type.
 
1401
 *
 
1402
 * @param type frame type
 
1403
 *
 
1404
 * @return id3lib frame ID or ID3FID_NOFRAME if type not found.
 
1405
 */
 
1406
static ID3_FrameID getId3libFrameIdForType(Frame::Type type)
 
1407
{
 
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;
 
1414
  }
 
1415
 
 
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) {
 
1422
        typeIdMap[t] = i;
 
1423
      }
 
1424
    }
 
1425
  }
 
1426
  return type <= Frame::FT_LastFrame ?
 
1427
    static_cast<ID3_FrameID>(typeIdMap[type]) : ID3FID_NOFRAME;
 
1428
}
 
1429
 
 
1430
/**
 
1431
 * Get id3lib frame ID for frame name.
 
1432
 *
 
1433
 * @param name frame name
 
1434
 *
 
1435
 * @return id3lib frame ID or ID3FID_NOFRAME if name not found.
 
1436
 */
 
1437
static ID3_FrameID getId3libFrameIdForName(const QString& name)
 
1438
{
 
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);
 
1446
      }
 
1447
    }
 
1448
  }
 
1449
  return ID3FID_NOFRAME;
 
1450
}
 
1451
 
 
1452
/**
 
1453
 * Get the fields from an ID3v2 tag.
 
1454
 *
 
1455
 * @param id3Frame frame
 
1456
 * @param fields   the fields are appended to this list
 
1457
 *
 
1458
 * @return text representation of fields (Text or URL).
 
1459
 */
 
1460
static QString getFieldsFromId3Frame(ID3_Frame* id3Frame,
 
1461
                                     Frame::FieldList& fields)
 
1462
{
 
1463
  QString text;
 
1464
  ID3_Frame::Iterator* iter = id3Frame->CreateIterator();
 
1465
  ID3_FrameID id3Id = id3Frame->GetID();
 
1466
  ID3_Field* id3Field;
 
1467
  Frame::Field field;
 
1468
  while ((id3Field = iter->GetNext()) != 0) {
 
1469
    ID3_FieldID id = id3Field->GetID();
 
1470
    ID3_FieldType type = id3Field->GetType();
 
1471
    field.m_id = id;
 
1472
    if (type == ID3FTY_INTEGER) {
 
1473
      field.m_value = id3Field->Get();
 
1474
    }
 
1475
    else if (type == ID3FTY_BINARY) {
 
1476
      QByteArray ba((const char *)id3Field->GetRawBinary(),
 
1477
                    (unsigned int)id3Field->Size());
 
1478
      field.m_value = ba;
 
1479
    }
 
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);
 
1485
        }
 
1486
        field.m_value = text;
 
1487
      } else {
 
1488
        field.m_value = getString(id3Field);
 
1489
      }
 
1490
    } else {
 
1491
      field.m_value.clear();
 
1492
    }
 
1493
    fields.push_back(field);
 
1494
  }
 
1495
#ifdef Q_OS_WIN32
 
1496
  /* allocated in Windows DLL => must be freed in the same DLL */
 
1497
  ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
 
1498
#else
 
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 */
 
1503
#endif
 
1504
  delete iter;
 
1505
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
 
1506
#pragma GCC diagnostic pop
 
1507
#endif
 
1508
#endif
 
1509
  return text;
 
1510
}
 
1511
 
 
1512
/**
 
1513
 * Get ID3v2 frame by index.
 
1514
 *
 
1515
 * @param tag   ID3v2 tag
 
1516
 * @param index index
 
1517
 *
 
1518
 * @return frame, 0 if index invalid.
 
1519
 */
 
1520
static ID3_Frame* getId3v2Frame(ID3_Tag* tag, int index)
 
1521
{
 
1522
  ID3_Frame* result = 0;
 
1523
  if (tag) {
 
1524
    ID3_Frame* frame;
 
1525
    ID3_Tag::Iterator* iter = tag->CreateIterator();
 
1526
    int i = 0;
 
1527
    while ((frame = iter->GetNext()) != 0) {
 
1528
      if (i == index) {
 
1529
        result = frame;
 
1530
        break;
 
1531
      }
 
1532
      ++i;
 
1533
    }
 
1534
#ifdef Q_OS_WIN32
 
1535
    /* allocated in Windows DLL => must be freed in the same DLL */
 
1536
    ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
 
1537
#else
 
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 */
 
1542
#endif
 
1543
    delete iter;
 
1544
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
 
1545
#pragma GCC diagnostic pop
 
1546
#endif
 
1547
#endif
 
1548
  }
 
1549
  return result;
 
1550
}
 
1551
 
 
1552
/**
 
1553
 * Set the fields in an id3lib frame from the field in the frame.
 
1554
 *
 
1555
 * @param id3Frame id3lib frame
 
1556
 * @param frame    frame with fields
 
1557
 */
 
1558
void Mp3File::setId3v2Frame(ID3_Frame* id3Frame, const Frame& frame) const
 
1559
{
 
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();
 
1565
       ++fldIt) {
 
1566
    ID3_Field* id3Field = iter->GetNext();
 
1567
    if (!id3Field) {
 
1568
      qDebug("early end of ID3 fields");
 
1569
      break;
 
1570
    }
 
1571
    const Frame::Field& fld = *fldIt;
 
1572
    switch (fld.m_value.type()) {
 
1573
      case QVariant::Int:
 
1574
      case QVariant::UInt:
 
1575
      {
 
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);
 
1580
        }
 
1581
        id3Field->Set(intVal);
 
1582
        break;
 
1583
      }
 
1584
 
 
1585
      case QVariant::String:
 
1586
      {
 
1587
        if (enc != ID3TE_NONE) {
 
1588
          id3Field->SetEncoding(enc);
 
1589
        }
 
1590
        QString value(fld.m_value.toString());
 
1591
        if (id3Id == ID3FID_CONTENTTYPE) {
 
1592
          if (!TagConfig::instance().genreNotNumeric()) {
 
1593
            value = Genres::getNumberString(value, true);
 
1594
          }
 
1595
        } else if (id3Id == ID3FID_TRACKNUM) {
 
1596
          formatTrackNumberIfEnabled(value, true);
 
1597
        }
 
1598
        setString(id3Field, value);
 
1599
        break;
 
1600
      }
 
1601
 
 
1602
      case QVariant::ByteArray:
 
1603
      {
 
1604
        const QByteArray& ba = fld.m_value.toByteArray();
 
1605
        id3Field->Set(reinterpret_cast<const unsigned char*>(ba.data()),
 
1606
                      ba.size());
 
1607
        break;
 
1608
      }
 
1609
 
 
1610
      default:
 
1611
        qDebug("Unknown type %d in field %d", fld.m_value.type(), fld.m_id);
 
1612
    }
 
1613
  }
 
1614
#ifdef Q_OS_WIN32
 
1615
  /* allocated in Windows DLL => must be freed in the same DLL */
 
1616
  ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
 
1617
#else
 
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 */
 
1622
#endif
 
1623
  delete iter;
 
1624
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
 
1625
#pragma GCC diagnostic pop
 
1626
#endif
 
1627
#endif
 
1628
}
 
1629
 
 
1630
/**
 
1631
 * Set a frame in the tags 2.
 
1632
 *
 
1633
 * @param frame frame to set
 
1634
 *
 
1635
 * @return true if ok.
 
1636
 */
 
1637
bool Mp3File::setFrameV2(const Frame& frame)
 
1638
{
 
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);
 
1643
    if (id3Frame) {
 
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());
 
1648
        ID3_Field* fld;
 
1649
        if ((fld = id3Frame->GetField(ID3FN_URL)) != 0) {
 
1650
          if (getString(fld) != value) {
 
1651
            fld->Set(value.toLatin1().data());
 
1652
            markTag2Changed(frame.getType());
 
1653
          }
 
1654
          return true;
 
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);
 
1660
            }
 
1661
          } else if (id3Frame->GetID() == ID3FID_TRACKNUM) {
 
1662
            formatTrackNumberIfEnabled(value, true);
 
1663
          }
 
1664
          bool hasEnc;
 
1665
          const ID3_TextEnc enc = fld->GetEncoding();
 
1666
          ID3_TextEnc newEnc = static_cast<ID3_TextEnc>(
 
1667
                frame.getFieldValue(Frame::Field::ID_TextEnc).toInt(&hasEnc));
 
1668
          if (!hasEnc) {
 
1669
            newEnc = enc;
 
1670
          }
 
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;
 
1674
          }
 
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;
 
1683
                break;
 
1684
              }
 
1685
            }
 
1686
          }
 
1687
          if (enc != newEnc) {
 
1688
            ID3_Field* encfld = id3Frame->GetField(ID3FN_TEXTENC);
 
1689
            if (encfld) {
 
1690
              encfld->Set(newEnc);
 
1691
            }
 
1692
            fld->SetEncoding(newEnc);
 
1693
            markTag2Changed(frame.getType());
 
1694
          }
 
1695
          if (getString(fld) != value) {
 
1696
            setString(fld, value);
 
1697
            markTag2Changed(frame.getType());
 
1698
          }
 
1699
          return true;
 
1700
        } else if (id3Frame->GetID() == ID3FID_PRIVATE &&
 
1701
                   (fld = id3Frame->GetField(ID3FN_DATA)) != 0) {
 
1702
          ID3_Field* ownerFld = id3Frame->GetField(ID3FN_OWNER);
 
1703
          QString 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()),
 
1711
                       newData.size());
 
1712
              markTag2Changed(frame.getType());
 
1713
            }
 
1714
            return true;
 
1715
          }
 
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()),
 
1725
                       newData.size());
 
1726
              markTag2Changed(frame.getType());
 
1727
            }
 
1728
            return true;
 
1729
          }
 
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()),
 
1739
                       newData.size());
 
1740
              markTag2Changed(frame.getType());
 
1741
            }
 
1742
            return true;
 
1743
          }
 
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());
 
1749
          }
 
1750
          return true;
 
1751
        }
 
1752
      } else {
 
1753
        setId3v2Frame(id3Frame, frame);
 
1754
        markTag2Changed(frame.getType());
 
1755
        return true;
 
1756
      }
 
1757
    }
 
1758
  }
 
1759
 
 
1760
  // Try the superclass method
 
1761
  return TaggedFile::setFrameV2(frame);
 
1762
}
 
1763
 
 
1764
 
 
1765
/**
 
1766
 * Add a frame in the tags 2.
 
1767
 *
 
1768
 * @param frame frame to add, a field list may be added by this method
 
1769
 *
 
1770
 * @return true if ok.
 
1771
 */
 
1772
bool Mp3File::addFrameV2(Frame& frame)
 
1773
{
 
1774
  // Add a new frame.
 
1775
  ID3_FrameID id;
 
1776
  if (frame.getType() != Frame::FT_Other) {
 
1777
    id = getId3libFrameIdForType(frame.getType());
 
1778
  } else {
 
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;
 
1787
      } else {
 
1788
        id = ID3FID_USERTEXT;
 
1789
      }
 
1790
    }
 
1791
  }
 
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);
 
1795
    if (fld) {
 
1796
      ID3_TextEnc enc = getDefaultTextEncoding();
 
1797
      ID3_Field* encfld = id3Frame->GetField(ID3FN_TEXTENC);
 
1798
      if (encfld) {
 
1799
        encfld->Set(enc);
 
1800
      }
 
1801
      fld->SetEncoding(enc);
 
1802
    }
 
1803
    if (id == ID3FID_USERTEXT && !frame.getName().startsWith(QLatin1String("TXXX"))) {
 
1804
      fld = id3Frame->GetField(ID3FN_DESCRIPTION);
 
1805
      if (fld) {
 
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");
 
1811
        } else {
 
1812
          description = frame.getName();
 
1813
        }
 
1814
        setString(fld, description);
 
1815
      }
 
1816
    } else if (id == ID3FID_COMMENT && frame.getType() == Frame::FT_Other) {
 
1817
      fld = id3Frame->GetField(ID3FN_DESCRIPTION);
 
1818
      if (fld) {
 
1819
        setString(fld, frame.getName());
 
1820
      }
 
1821
    } else if (id == ID3FID_PRIVATE && !frame.getName().startsWith(QLatin1String("PRIV"))) {
 
1822
      fld = id3Frame->GetField(ID3FN_OWNER);
 
1823
      if (fld) {
 
1824
        setString(fld, frame.getName());
 
1825
        QByteArray data;
 
1826
        if (AttributeData(frame.getName()).toByteArray(frame.getValue(), data)) {
 
1827
          fld = id3Frame->GetField(ID3FN_DATA);
 
1828
          if (fld) {
 
1829
            fld->Set(reinterpret_cast<const unsigned char*>(data.data()),
 
1830
                     data.size());
 
1831
          }
 
1832
        }
 
1833
      }
 
1834
    } else if (id == ID3FID_UNIQUEFILEID) {
 
1835
      QByteArray data;
 
1836
      if (AttributeData::isHexString(frame.getValue(), 'Z')) {
 
1837
        data = (frame.getValue() + QLatin1Char('\0')).toLatin1();
 
1838
        fld = id3Frame->GetField(ID3FN_DATA);
 
1839
        if (fld) {
 
1840
          fld->Set(reinterpret_cast<const unsigned char*>(data.data()),
 
1841
                   data.size());
 
1842
        }
 
1843
      }
 
1844
    } else if (id == ID3FID_PICTURE) {
 
1845
      fld = id3Frame->GetField(ID3FN_MIMETYPE);
 
1846
      if (fld) {
 
1847
        setString(fld, QLatin1String("image/jpeg"));
 
1848
      }
 
1849
      fld = id3Frame->GetField(ID3FN_PICTURETYPE);
 
1850
      if (fld) {
 
1851
        fld->Set(ID3PT_COVERFRONT);
 
1852
      }
 
1853
    }
 
1854
    if (!frame.fieldList().empty()) {
 
1855
      setId3v2Frame(id3Frame, frame);
 
1856
    }
 
1857
    Frame::Type type;
 
1858
    const char* name;
 
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();
 
1867
    }
 
1868
    markTag2Changed(frame.getType());
 
1869
    return true;
 
1870
  }
 
1871
 
 
1872
  // Try the superclass method
 
1873
  return TaggedFile::addFrameV2(frame);
 
1874
}
 
1875
 
 
1876
/**
 
1877
 * Delete a frame in the tags 2.
 
1878
 *
 
1879
 * @param frame frame to delete.
 
1880
 *
 
1881
 * @return true if ok.
 
1882
 */
 
1883
bool Mp3File::deleteFrameV2(const Frame& frame)
 
1884
{
 
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);
 
1889
    if (id3Frame) {
 
1890
      m_tagV2->RemoveFrame(id3Frame);
 
1891
      markTag2Changed(frame.getType());
 
1892
      return true;
 
1893
    }
 
1894
  }
 
1895
 
 
1896
  // Try the superclass method
 
1897
  return TaggedFile::deleteFrameV2(frame);
 
1898
}
 
1899
 
 
1900
/**
 
1901
 * Remove ID3v2 frames.
 
1902
 *
 
1903
 * @param flt filter specifying which frames to remove
 
1904
 */
 
1905
void Mp3File::deleteFramesV2(const FrameFilter& flt)
 
1906
{
 
1907
  if (m_tagV2) {
 
1908
    if (flt.areAllEnabled()) {
 
1909
      ID3_Tag::Iterator* iter = m_tagV2->CreateIterator();
 
1910
      ID3_Frame* frame;
 
1911
      while ((frame = iter->GetNext()) != NULL) {
 
1912
        m_tagV2->RemoveFrame(frame);
 
1913
      }
 
1914
#ifdef Q_OS_WIN32
 
1915
      /* allocated in Windows DLL => must be freed in the same DLL */
 
1916
      ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
 
1917
#else
 
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 */
 
1922
#endif
 
1923
      delete iter;
 
1924
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
 
1925
#pragma GCC diagnostic pop
 
1926
#endif
 
1927
#endif
 
1928
      markTag2Changed(Frame::FT_UnknownFrame);
 
1929
    } else {
 
1930
      ID3_Tag::Iterator* iter = m_tagV2->CreateIterator();
 
1931
      ID3_Frame* frame;
 
1932
      while ((frame = iter->GetNext()) != NULL) {
 
1933
        Frame::Type type;
 
1934
        const char* name;
 
1935
        getTypeStringForId3libFrameId(frame->GetID(), type, name);
 
1936
        if (flt.isEnabled(type, QString::fromLatin1(name))) {
 
1937
          m_tagV2->RemoveFrame(frame);
 
1938
        }
 
1939
      }
 
1940
#ifdef Q_OS_WIN32
 
1941
      /* allocated in Windows DLL => must be freed in the same DLL */
 
1942
      ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
 
1943
#else
 
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 */
 
1948
#endif
 
1949
      delete iter;
 
1950
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
 
1951
#pragma GCC diagnostic pop
 
1952
#endif
 
1953
#endif
 
1954
      markTag2Changed(Frame::FT_UnknownFrame);
 
1955
    }
 
1956
  }
 
1957
}
 
1958
 
 
1959
/**
 
1960
 * Get all frames in tag 2.
 
1961
 *
 
1962
 * @param frames frame collection to set.
 
1963
 */
 
1964
void Mp3File::getAllFramesV2(FrameCollection& frames)
 
1965
{
 
1966
  frames.clear();
 
1967
  if (m_tagV2) {
 
1968
    ID3_Tag::Iterator* iter = m_tagV2->CreateIterator();
 
1969
    ID3_Frame* id3Frame;
 
1970
    int i = 0;
 
1971
    Frame::Type type;
 
1972
    const char* name;
 
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);
 
1988
            } else {
 
1989
              frame.setExtendedType(Frame::ExtendedType(Frame::FT_Other,
 
1990
                  QString::fromLatin1(name) + QLatin1Char('\n') + description));
 
1991
            }
 
1992
          }
 
1993
        }
 
1994
      } else if (id3Frame->GetID() == ID3FID_PRIVATE) {
 
1995
        const Frame::FieldList& fields = frame.getFieldList();
 
1996
        QString owner;
 
1997
        QByteArray data;
 
1998
        for (Frame::FieldList::const_iterator it = fields.begin();
 
1999
             it != fields.end();
 
2000
             ++it) {
 
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));
 
2006
            }
 
2007
          } else if ((*it).m_id == Frame::Field::ID_Data) {
 
2008
            data = (*it).m_value.toByteArray();
 
2009
          }
 
2010
        }
 
2011
        if (!owner.isEmpty() && !data.isEmpty()) {
 
2012
          QString str;
 
2013
          if (AttributeData(owner).toString(data, str)) {
 
2014
            frame.setValue(str);
 
2015
          }
 
2016
        }
 
2017
      } else if (id3Frame->GetID() == ID3FID_CDID) {
 
2018
        QVariant fieldValue = frame.getFieldValue(Frame::Field::ID_Data);
 
2019
        if (fieldValue.isValid()) {
 
2020
          QString str;
 
2021
          if (AttributeData(AttributeData::Utf16).toString(fieldValue.toByteArray(), str) &&
 
2022
              AttributeData::isHexString(str, 'F', QLatin1String("+"))) {
 
2023
            frame.setValue(str);
 
2024
          }
 
2025
        }
 
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);
 
2034
          }
 
2035
        }
 
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);
 
2042
          }
 
2043
        }
 
2044
      }
 
2045
      frames.insert(frame);
 
2046
    }
 
2047
#ifdef Q_OS_WIN32
 
2048
    /* allocated in Windows DLL => must be freed in the same DLL */
 
2049
    ID3TagIterator_Delete(reinterpret_cast<ID3TagIterator*>(iter));
 
2050
#else
 
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 */
 
2055
#endif
 
2056
    delete iter;
 
2057
#ifdef GCC_HAS_DIAGNOSTIC_PRAGMA
 
2058
#pragma GCC diagnostic pop
 
2059
#endif
 
2060
#endif
 
2061
  }
 
2062
  frames.addMissingStandardFrames();
 
2063
}
 
2064
 
 
2065
/**
 
2066
 * Get a list of frame IDs which can be added.
 
2067
 *
 
2068
 * @return list with frame IDs.
 
2069
 */
 
2070
QStringList Mp3File::getFrameIds() const
 
2071
{
 
2072
  QStringList lst;
 
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());
 
2077
    }
 
2078
  }
 
2079
  for (int i = 0; i <= ID3FID_WWWUSER; ++i) {
 
2080
    if (typeStrOfId[i].type == Frame::FT_Other) {
 
2081
      const char* s = typeStrOfId[i].str;
 
2082
      if (s) {
 
2083
        lst.append(QCoreApplication::translate("@default", s));
 
2084
      }
 
2085
    }
 
2086
  }
 
2087
  return lst;
 
2088
}
 
2089
 
 
2090
/**
 
2091
 * Set the text codec to be used for tag 1.
 
2092
 *
 
2093
 * @param codec text codec, 0 to use default (ISO 8859-1)
 
2094
 */
 
2095
void Mp3File::setTextCodecV1(const QTextCodec* codec)
 
2096
{
 
2097
  s_textCodecV1 = codec;
 
2098
}
 
2099
 
 
2100
/**
 
2101
 * Set the default text encoding.
 
2102
 *
 
2103
 * @param textEnc default text encoding
 
2104
 */
 
2105
void Mp3File::setDefaultTextEncoding(TagConfig::TextEncoding textEnc)
 
2106
{
 
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;
 
2110
}
 
2111
 
 
2112
/**
 
2113
 * Notify about configuration change.
 
2114
 * This method shall be called when the configuration changes.
 
2115
 */
 
2116
void Mp3File::notifyConfigurationChange()
 
2117
{
 
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);
 
2124
}