1
/***************************************************************************
2
copyright : (C) 2003-2004 by Allan Sandfeld Jensen
3
email : kde@carewolf.org
4
***************************************************************************/
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. *
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. *
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 *
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
***************************************************************************/
26
#include <tbytevector.h>
32
#include <id3v2header.h>
35
#include <xiphcomment.h>
39
using namespace TagLib;
43
enum { XiphIndex = 0, ID3v2Index = 1, ID3v1Index = 2 };
44
enum { StreamInfo = 0, Padding, Application, SeekTable, VorbisComment, CueSheet };
47
class FLAC::File::FilePrivate
51
ID3v2FrameFactory(ID3v2::FrameFactory::instance()),
60
hasXiphComment(false),
69
const ID3v2::FrameFactory *ID3v2FrameFactory;
71
uint ID3v2OriginalSize;
77
Properties *properties;
78
ByteVector streamInfoData;
79
ByteVector xiphCommentData;
91
////////////////////////////////////////////////////////////////////////////////
93
////////////////////////////////////////////////////////////////////////////////
95
FLAC::File::File(FileName file, bool readProperties,
96
Properties::ReadStyle propertiesStyle) :
100
read(readProperties, propertiesStyle);
103
FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
104
bool readProperties, Properties::ReadStyle propertiesStyle) :
108
d->ID3v2FrameFactory = frameFactory;
109
read(readProperties, propertiesStyle);
117
TagLib::Tag *FLAC::File::tag() const
122
FLAC::Properties *FLAC::File::audioProperties() const
124
return d->properties;
128
bool FLAC::File::save()
131
debug("FLAC::File::save() - Cannot save to a read only file.");
135
// Create new vorbis comments
137
Tag::duplicate(&d->tag, xiphComment(true), true);
139
d->xiphCommentData = xiphComment()->render(false);
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
145
ByteVector data = ByteVector::fromUInt(d->xiphCommentData.size());
147
data[0] = char(VorbisComment);
148
data.append(d->xiphCommentData);
151
// If file already have comment => find and update it
152
// if not => insert one
154
// TODO: Search for padding and use that
156
if(d->hasXiphComment) {
158
long nextBlockOffset = d->flacStart;
159
bool isLastBlock = false;
161
while(!isLastBlock) {
162
seek(nextBlockOffset);
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();
169
if(blockType == VorbisComment) {
171
insert(data, nextBlockOffset, blockLength + 4);
175
nextBlockOffset += blockLength + 4;
180
const long firstBlockOffset = d->flacStart;
181
seek(firstBlockOffset);
183
ByteVector header = readBlock(4);
184
bool isLastBlock = (header[0] & 0x80) != 0;
185
uint blockLength = header.mid(1, 3).toUInt();
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.
194
seek(firstBlockOffset);
195
writeBlock(static_cast<char>(header[0] & 0x7F));
199
insert(data, firstBlockOffset + blockLength + 4, 0);
200
d->hasXiphComment = true;
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.");
211
insert(ID3v2Tag()->render(), d->ID3v2Location, d->ID3v2OriginalSize);
214
insert(ID3v2Tag()->render(), 0, 0);
219
writeBlock(ID3v1Tag()->render());
225
ID3v2::Tag *FLAC::File::ID3v2Tag(bool create)
227
if(!create || d->tag[ID3v2Index])
228
return static_cast<ID3v2::Tag *>(d->tag[ID3v2Index]);
230
d->tag.set(ID3v2Index, new ID3v2::Tag);
231
return static_cast<ID3v2::Tag *>(d->tag[ID3v2Index]);
234
ID3v1::Tag *FLAC::File::ID3v1Tag(bool create)
236
return d->tag.access<ID3v1::Tag>(ID3v1Index, create);
239
Ogg::XiphComment *FLAC::File::xiphComment(bool create)
241
return d->tag.access<Ogg::XiphComment>(XiphIndex, create);
244
void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory)
246
d->ID3v2FrameFactory = factory;
250
////////////////////////////////////////////////////////////////////////////////
252
////////////////////////////////////////////////////////////////////////////////
254
void FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
256
// Look for an ID3v2 tag
258
d->ID3v2Location = findID3v2();
260
if(d->ID3v2Location >= 0) {
262
d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
264
d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
266
if(ID3v2Tag()->header()->tagSize() <= 0)
267
d->tag.set(ID3v2Index, 0);
272
// Look for an ID3v1 tag
274
d->ID3v1Location = findID3v1();
276
if(d->ID3v1Location >= 0) {
277
d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
281
// Look for FLAC metadata, including vorbis comments
288
if(d->hasXiphComment)
289
d->tag.set(XiphIndex, new Ogg::XiphComment(xiphCommentData()));
291
d->tag.set(XiphIndex, new Ogg::XiphComment);
294
d->properties = new Properties(streamInfoData(), streamLength(), propertiesStyle);
297
ByteVector FLAC::File::streamInfoData()
299
return isValid() ? d->streamInfoData : ByteVector();
302
ByteVector FLAC::File::xiphCommentData() const
304
return (isValid() && d->hasXiphComment) ? d->xiphCommentData : ByteVector();
307
long FLAC::File::streamLength()
309
return d->streamLength;
312
void FLAC::File::scan()
314
// Scan the metadata pages
322
long nextBlockOffset;
325
nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize);
327
nextBlockOffset = find("fLaC");
329
if(nextBlockOffset < 0) {
330
debug("FLAC::File::scan() -- FLAC stream not found");
335
nextBlockOffset += 4;
336
d->flacStart = nextBlockOffset;
338
seek(nextBlockOffset);
340
ByteVector header = readBlock(4);
342
// Header format (from spec):
343
// <1> Last-metadata-block flag
348
// 4 : VORBIS_COMMENT
350
// <24> Length of metadata to follow
352
char blockType = header[0] & 0x7f;
353
bool isLastBlock = (header[0] & 0x80) != 0;
354
uint length = header.mid(1, 3).toUInt();
356
// First block should be the stream_info metadata
358
if(blockType != StreamInfo) {
359
debug("FLAC::File::scan() -- invalid FLAC stream");
364
d->streamInfoData = readBlock(length);
365
nextBlockOffset += length + 4;
367
// Search through the remaining metadata
369
while(!isLastBlock) {
371
header = readBlock(4);
372
blockType = header[0] & 0x7f;
373
isLastBlock = (header[0] & 0x80) != 0;
374
length = header.mid(1, 3).toUInt();
376
if(blockType == Padding) {
377
// debug("FLAC::File::scan() -- Padding found");
379
// Found the vorbis-comment
380
else if(blockType == VorbisComment) {
381
d->xiphCommentData = readBlock(length);
382
d->hasXiphComment = true;
385
nextBlockOffset += length + 4;
387
if(nextBlockOffset >= File::length()) {
388
debug("FLAC::File::scan() -- FLAC stream corrupted");
392
seek(nextBlockOffset);
395
// End of metadata, now comes the datastream
397
d->streamStart = nextBlockOffset;
398
d->streamLength = File::length() - d->streamStart;
401
d->streamLength -= 128;
406
long FLAC::File::findID3v1()
414
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
420
long FLAC::File::findID3v2()
427
if(readBlock(3) == ID3v2::Header::fileIdentifier())