~njh-aelius/maxosx/musicbrainz-tags

« back to all changes in this revision

Viewing changes to Frameworks/taglib/taglib/taglib/flac/flacfile.cpp

  • Committer: stephen_booth
  • Date: 2008-04-30 02:09:12 UTC
  • Revision ID: svn-v4:6b6cea13-1402-0410-9567-a7afb52bf336:trunk:1372
Update to latest taglib SVN

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/***************************************************************************
 
2
    copyright            : (C) 2003-2004 by Allan Sandfeld Jensen
 
3
    email                : kde@carewolf.org
 
4
 ***************************************************************************/
 
5
 
 
6
/***************************************************************************
 
7
 *   This library is free software; you can redistribute it and/or modify  *
 
8
 *   it under the terms of the GNU Lesser General Public License version   *
 
9
 *   2.1 as published by the Free Software Foundation.                     *
 
10
 *                                                                         *
 
11
 *   This library is distributed in the hope that it will be useful, but   *
 
12
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 
14
 *   Lesser General Public License for more details.                       *
 
15
 *                                                                         *
 
16
 *   You should have received a copy of the GNU Lesser General Public      *
 
17
 *   License along with this library; if not, write to the Free Software   *
 
18
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
 
19
 *   USA                                                                   *
 
20
 *                                                                         *
 
21
 *   Alternatively, this file is available under the Mozilla Public        *
 
22
 *   License Version 1.1.  You may obtain a copy of the License at         *
 
23
 *   http://www.mozilla.org/MPL/                                           *
 
24
 ***************************************************************************/
 
25
 
 
26
#include <tbytevector.h>
 
27
#include <tstring.h>
 
28
#include <tlist.h>
 
29
#include <tdebug.h>
 
30
#include <tagunion.h>
 
31
 
 
32
#include <id3v2header.h>
 
33
#include <id3v2tag.h>
 
34
#include <id3v1tag.h>
 
35
#include <xiphcomment.h>
 
36
 
 
37
#include "flacfile.h"
 
38
 
 
39
using namespace TagLib;
 
40
 
 
41
namespace
 
42
{
 
43
  enum { XiphIndex = 0, ID3v2Index = 1, ID3v1Index = 2 };
 
44
  enum { StreamInfo = 0, Padding, Application, SeekTable, VorbisComment, CueSheet };
 
45
}
 
46
 
 
47
class FLAC::File::FilePrivate
 
48
{
 
49
public:
 
50
  FilePrivate() :
 
51
    ID3v2FrameFactory(ID3v2::FrameFactory::instance()),
 
52
    ID3v2Location(-1),
 
53
    ID3v2OriginalSize(0),
 
54
    ID3v1Location(-1),
 
55
    properties(0),
 
56
    flacStart(0),
 
57
    streamStart(0),
 
58
    streamLength(0),
 
59
    scanned(false),
 
60
    hasXiphComment(false),
 
61
    hasID3v2(false),
 
62
    hasID3v1(false) {}
 
63
 
 
64
  ~FilePrivate()
 
65
  {
 
66
    delete properties;
 
67
  }
 
68
 
 
69
  const ID3v2::FrameFactory *ID3v2FrameFactory;
 
70
  long ID3v2Location;
 
71
  uint ID3v2OriginalSize;
 
72
 
 
73
  long ID3v1Location;
 
74
 
 
75
  TagUnion tag;
 
76
 
 
77
  Properties *properties;
 
78
  ByteVector streamInfoData;
 
79
  ByteVector xiphCommentData;
 
80
 
 
81
  long flacStart;
 
82
  long streamStart;
 
83
  long streamLength;
 
84
  bool scanned;
 
85
 
 
86
  bool hasXiphComment;
 
87
  bool hasID3v2;
 
88
  bool hasID3v1;
 
89
};
 
90
 
 
91
////////////////////////////////////////////////////////////////////////////////
 
92
// public members
 
93
////////////////////////////////////////////////////////////////////////////////
 
94
 
 
95
FLAC::File::File(FileName file, bool readProperties,
 
96
                 Properties::ReadStyle propertiesStyle) :
 
97
  TagLib::File(file)
 
98
{
 
99
  d = new FilePrivate;
 
100
  read(readProperties, propertiesStyle);
 
101
}
 
102
 
 
103
FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
 
104
                 bool readProperties, Properties::ReadStyle propertiesStyle) :
 
105
  TagLib::File(file)
 
106
{
 
107
  d = new FilePrivate;
 
108
  d->ID3v2FrameFactory = frameFactory;
 
109
  read(readProperties, propertiesStyle);
 
110
}
 
111
 
 
112
FLAC::File::~File()
 
113
{
 
114
  delete d;
 
115
}
 
116
 
 
117
TagLib::Tag *FLAC::File::tag() const
 
118
{
 
119
  return &d->tag;
 
120
}
 
121
 
 
122
FLAC::Properties *FLAC::File::audioProperties() const
 
123
{
 
124
  return d->properties;
 
125
}
 
126
 
 
127
 
 
128
bool FLAC::File::save()
 
129
{
 
130
  if(readOnly()) {
 
131
    debug("FLAC::File::save() - Cannot save to a read only file.");
 
132
    return false;
 
133
  }
 
134
 
 
135
  // Create new vorbis comments
 
136
 
 
137
  Tag::duplicate(&d->tag, xiphComment(true), true);
 
138
 
 
139
  d->xiphCommentData = xiphComment()->render(false);
 
140
 
 
141
  // A Xiph comment portion of the data stream starts with a 4-byte descriptor.
 
142
  // The first byte indicates the frame type.  The last three bytes are used
 
143
  // to give the length of the data segment.  Here we start
 
144
 
 
145
  ByteVector data = ByteVector::fromUInt(d->xiphCommentData.size());
 
146
 
 
147
  data[0] = char(VorbisComment);
 
148
  data.append(d->xiphCommentData);
 
149
 
 
150
 
 
151
   // If file already have comment => find and update it
 
152
   // if not => insert one
 
153
 
 
154
   // TODO: Search for padding and use that
 
155
 
 
156
  if(d->hasXiphComment) {
 
157
 
 
158
    long nextBlockOffset = d->flacStart;
 
159
    bool isLastBlock = false;
 
160
 
 
161
    while(!isLastBlock) {
 
162
      seek(nextBlockOffset);
 
163
 
 
164
      ByteVector header = readBlock(4);
 
165
      char blockType = header[0] & 0x7f;
 
166
      isLastBlock = (header[0] & 0x80) != 0;
 
167
      uint blockLength = header.mid(1, 3).toUInt();
 
168
 
 
169
      if(blockType == VorbisComment) {
 
170
        data[0] = header[0];
 
171
        insert(data, nextBlockOffset, blockLength + 4);
 
172
        break;
 
173
      }
 
174
 
 
175
      nextBlockOffset += blockLength + 4;
 
176
    }
 
177
  }
 
178
  else {
 
179
 
 
180
    const long firstBlockOffset = d->flacStart;
 
181
    seek(firstBlockOffset);
 
182
 
 
183
    ByteVector header = readBlock(4);
 
184
    bool isLastBlock = (header[0] & 0x80) != 0;
 
185
    uint blockLength = header.mid(1, 3).toUInt();
 
186
 
 
187
    if(isLastBlock) {
 
188
 
 
189
      // If the first block was previously also the last block, then we want to
 
190
      // mark it as no longer being the first block (the writeBlock() call) and
 
191
      // then set the data for the block that we're about to write to mark our
 
192
      // new block as the last block.
 
193
 
 
194
      seek(firstBlockOffset);
 
195
      writeBlock(static_cast<char>(header[0] & 0x7F));
 
196
      data[0] |= 0x80;
 
197
    }
 
198
 
 
199
    insert(data, firstBlockOffset + blockLength + 4, 0);
 
200
    d->hasXiphComment = true;
 
201
  }
 
202
 
 
203
  // Update ID3 tags
 
204
 
 
205
  if(ID3v2Tag()) {
 
206
    if(d->hasID3v2) {
 
207
      if(d->ID3v2Location < d->flacStart)
 
208
        debug("FLAC::File::save() -- This can't be right -- an ID3v2 tag after the "
 
209
              "start of the FLAC bytestream?  Not writing the ID3v2 tag.");
 
210
      else
 
211
        insert(ID3v2Tag()->render(), d->ID3v2Location, d->ID3v2OriginalSize);
 
212
    }
 
213
    else
 
214
      insert(ID3v2Tag()->render(), 0, 0);
 
215
  }
 
216
 
 
217
  if(ID3v1Tag()) {
 
218
    seek(-128, End);
 
219
    writeBlock(ID3v1Tag()->render());
 
220
  }
 
221
 
 
222
  return true;
 
223
}
 
224
 
 
225
ID3v2::Tag *FLAC::File::ID3v2Tag(bool create)
 
226
{
 
227
  if(!create || d->tag[ID3v2Index])
 
228
    return static_cast<ID3v2::Tag *>(d->tag[ID3v2Index]);
 
229
 
 
230
  d->tag.set(ID3v2Index, new ID3v2::Tag);
 
231
  return static_cast<ID3v2::Tag *>(d->tag[ID3v2Index]);
 
232
}
 
233
 
 
234
ID3v1::Tag *FLAC::File::ID3v1Tag(bool create)
 
235
{
 
236
  return d->tag.access<ID3v1::Tag>(ID3v1Index, create);
 
237
}
 
238
 
 
239
Ogg::XiphComment *FLAC::File::xiphComment(bool create)
 
240
{
 
241
  return d->tag.access<Ogg::XiphComment>(XiphIndex, create);
 
242
}
 
243
 
 
244
void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory)
 
245
{
 
246
  d->ID3v2FrameFactory = factory;
 
247
}
 
248
 
 
249
 
 
250
////////////////////////////////////////////////////////////////////////////////
 
251
// private members
 
252
////////////////////////////////////////////////////////////////////////////////
 
253
 
 
254
void FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
 
255
{
 
256
  // Look for an ID3v2 tag
 
257
 
 
258
  d->ID3v2Location = findID3v2();
 
259
 
 
260
  if(d->ID3v2Location >= 0) {
 
261
 
 
262
    d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
 
263
 
 
264
    d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
 
265
 
 
266
    if(ID3v2Tag()->header()->tagSize() <= 0)
 
267
      d->tag.set(ID3v2Index, 0);
 
268
    else
 
269
      d->hasID3v2 = true;
 
270
  }
 
271
 
 
272
  // Look for an ID3v1 tag
 
273
 
 
274
  d->ID3v1Location = findID3v1();
 
275
 
 
276
  if(d->ID3v1Location >= 0) {
 
277
    d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
 
278
    d->hasID3v1 = true;
 
279
  }
 
280
 
 
281
  // Look for FLAC metadata, including vorbis comments
 
282
 
 
283
  scan();
 
284
 
 
285
  if(!isValid())
 
286
    return;
 
287
 
 
288
  if(d->hasXiphComment)
 
289
    d->tag.set(XiphIndex, new Ogg::XiphComment(xiphCommentData()));
 
290
  else
 
291
    d->tag.set(XiphIndex, new Ogg::XiphComment);
 
292
 
 
293
  if(readProperties)
 
294
    d->properties = new Properties(streamInfoData(), streamLength(), propertiesStyle);
 
295
}
 
296
 
 
297
ByteVector FLAC::File::streamInfoData()
 
298
{
 
299
  return isValid() ? d->streamInfoData : ByteVector();
 
300
}
 
301
 
 
302
ByteVector FLAC::File::xiphCommentData() const
 
303
{
 
304
  return (isValid() && d->hasXiphComment) ? d->xiphCommentData : ByteVector();
 
305
}
 
306
 
 
307
long FLAC::File::streamLength()
 
308
{
 
309
  return d->streamLength;
 
310
}
 
311
 
 
312
void FLAC::File::scan()
 
313
{
 
314
  // Scan the metadata pages
 
315
 
 
316
  if(d->scanned)
 
317
    return;
 
318
 
 
319
  if(!isValid())
 
320
    return;
 
321
 
 
322
  long nextBlockOffset;
 
323
 
 
324
  if(d->hasID3v2)
 
325
    nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize);
 
326
  else
 
327
    nextBlockOffset = find("fLaC");
 
328
 
 
329
  if(nextBlockOffset < 0) {
 
330
    debug("FLAC::File::scan() -- FLAC stream not found");
 
331
    setValid(false);
 
332
    return;
 
333
  }
 
334
 
 
335
  nextBlockOffset += 4;
 
336
  d->flacStart = nextBlockOffset;
 
337
 
 
338
  seek(nextBlockOffset);
 
339
 
 
340
  ByteVector header = readBlock(4);
 
341
 
 
342
  // Header format (from spec):
 
343
  // <1> Last-metadata-block flag
 
344
  // <7> BLOCK_TYPE
 
345
  //    0 : STREAMINFO
 
346
  //    1 : PADDING
 
347
  //    ..
 
348
  //    4 : VORBIS_COMMENT
 
349
  //    ..
 
350
  // <24> Length of metadata to follow
 
351
 
 
352
  char blockType = header[0] & 0x7f;
 
353
  bool isLastBlock = (header[0] & 0x80) != 0;
 
354
  uint length = header.mid(1, 3).toUInt();
 
355
 
 
356
  // First block should be the stream_info metadata
 
357
 
 
358
  if(blockType != StreamInfo) {
 
359
    debug("FLAC::File::scan() -- invalid FLAC stream");
 
360
    setValid(false);
 
361
    return;
 
362
  }
 
363
 
 
364
  d->streamInfoData = readBlock(length);
 
365
  nextBlockOffset += length + 4;
 
366
 
 
367
  // Search through the remaining metadata
 
368
 
 
369
  while(!isLastBlock) {
 
370
 
 
371
    header = readBlock(4);
 
372
    blockType = header[0] & 0x7f;
 
373
    isLastBlock = (header[0] & 0x80) != 0;
 
374
    length = header.mid(1, 3).toUInt();
 
375
 
 
376
    if(blockType == Padding) {
 
377
      // debug("FLAC::File::scan() -- Padding found");
 
378
    }
 
379
    // Found the vorbis-comment
 
380
    else if(blockType == VorbisComment) {
 
381
      d->xiphCommentData = readBlock(length);
 
382
      d->hasXiphComment = true;
 
383
    }
 
384
 
 
385
    nextBlockOffset += length + 4;
 
386
 
 
387
    if(nextBlockOffset >= File::length()) {
 
388
      debug("FLAC::File::scan() -- FLAC stream corrupted");
 
389
      setValid(false);
 
390
      return;
 
391
    }
 
392
    seek(nextBlockOffset);
 
393
  }
 
394
 
 
395
  // End of metadata, now comes the datastream
 
396
 
 
397
  d->streamStart = nextBlockOffset;
 
398
  d->streamLength = File::length() - d->streamStart;
 
399
 
 
400
  if(d->hasID3v1)
 
401
    d->streamLength -= 128;
 
402
 
 
403
  d->scanned = true;
 
404
}
 
405
 
 
406
long FLAC::File::findID3v1()
 
407
{
 
408
  if(!isValid())
 
409
    return -1;
 
410
 
 
411
  seek(-128, End);
 
412
  long p = tell();
 
413
 
 
414
  if(readBlock(3) == ID3v1::Tag::fileIdentifier())
 
415
    return p;
 
416
 
 
417
  return -1;
 
418
}
 
419
 
 
420
long FLAC::File::findID3v2()
 
421
{
 
422
  if(!isValid())
 
423
    return -1;
 
424
 
 
425
  seek(0);
 
426
 
 
427
  if(readBlock(3) == ID3v2::Header::fileIdentifier())
 
428
    return 0;
 
429
 
 
430
  return -1;
 
431
}