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 };
45 enum { MinPaddingLength = 4096 };
48 class FLAC::File::FilePrivate
52 ID3v2FrameFactory(ID3v2::FrameFactory::instance()),
61 hasXiphComment(false),
70 const ID3v2::FrameFactory *ID3v2FrameFactory;
72 uint ID3v2OriginalSize;
78 Properties *properties;
79 ByteVector streamInfoData;
80 ByteVector xiphCommentData;
92 ////////////////////////////////////////////////////////////////////////////////
94 ////////////////////////////////////////////////////////////////////////////////
96 FLAC::File::File(FileName file, bool readProperties,
97 Properties::ReadStyle propertiesStyle) :
101 read(readProperties, propertiesStyle);
104 FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
105 bool readProperties, Properties::ReadStyle propertiesStyle) :
109 d->ID3v2FrameFactory = frameFactory;
110 read(readProperties, propertiesStyle);
118 TagLib::Tag *FLAC::File::tag() const
123 FLAC::Properties *FLAC::File::audioProperties() const
125 return d->properties;
129 bool FLAC::File::save()
132 debug("FLAC::File::save() - Cannot save to a read only file.");
136 // Create new vorbis comments
138 Tag::duplicate(&d->tag, xiphComment(true), true);
140 d->xiphCommentData = xiphComment()->render(false);
142 // A Xiph comment portion of the data stream starts with a 4-byte descriptor.
143 // The first byte indicates the frame type. The last three bytes are used
144 // to give the length of the data segment. Here we start
146 ByteVector data = ByteVector::fromUInt(d->xiphCommentData.size());
148 data[0] = char(VorbisComment);
149 data.append(d->xiphCommentData);
152 // If file already have comment => find and update it
153 // if not => insert one
155 // TODO: Search for padding and use that
157 if(d->hasXiphComment) {
159 long nextBlockOffset = d->flacStart;
160 bool isLastBlock = false;
162 while(!isLastBlock) {
163 seek(nextBlockOffset);
165 ByteVector header = readBlock(4);
166 char blockType = header[0] & 0x7f;
167 isLastBlock = (header[0] & 0x80) != 0;
168 uint blockLength = header.mid(1, 3).toUInt();
170 if(blockType == VorbisComment) {
172 long paddingBreak = 0;
175 paddingBreak = findPaddingBreak(nextBlockOffset + blockLength + 4,
176 nextBlockOffset + d->xiphCommentData.size() + 8,
180 uint paddingLength = 0;
184 // There is space for comment and padding blocks without rewriting the
185 // whole file. Note: This cannot overflow.
187 paddingLength = paddingBreak - (nextBlockOffset + d->xiphCommentData.size() + 8);
191 // Not enough space, so we will have to rewrite the whole file
192 // following this block
194 paddingLength = d->xiphCommentData.size();
196 if(paddingLength < MinPaddingLength)
197 paddingLength = MinPaddingLength;
199 paddingBreak = nextBlockOffset + blockLength + 4;
202 ByteVector padding = ByteVector::fromUInt(paddingLength);
209 padding.resize(paddingLength + 4);
210 ByteVector pair(data);
211 pair.append(padding);
212 insert(pair, nextBlockOffset, paddingBreak - nextBlockOffset);
216 nextBlockOffset += blockLength + 4;
221 const long firstBlockOffset = d->flacStart;
222 seek(firstBlockOffset);
224 ByteVector header = readBlock(4);
225 bool isLastBlock = (header[0] & 0x80) != 0;
226 uint blockLength = header.mid(1, 3).toUInt();
230 // If the first block was previously also the last block, then we want to
231 // mark it as no longer being the first block (the writeBlock() call) and
232 // then set the data for the block that we're about to write to mark our
233 // new block as the last block.
235 seek(firstBlockOffset);
236 writeBlock(static_cast<char>(header[0] & 0x7F));
240 insert(data, firstBlockOffset + blockLength + 4, 0);
241 d->hasXiphComment = true;
248 if(d->ID3v2Location < d->flacStart)
249 debug("FLAC::File::save() -- This can't be right -- an ID3v2 tag after the "
250 "start of the FLAC bytestream? Not writing the ID3v2 tag.");
252 insert(ID3v2Tag()->render(), d->ID3v2Location, d->ID3v2OriginalSize);
255 insert(ID3v2Tag()->render(), 0, 0);
260 writeBlock(ID3v1Tag()->render());
266 ID3v2::Tag *FLAC::File::ID3v2Tag(bool create)
268 if(!create || d->tag[ID3v2Index])
269 return static_cast<ID3v2::Tag *>(d->tag[ID3v2Index]);
271 d->tag.set(ID3v2Index, new ID3v2::Tag);
272 return static_cast<ID3v2::Tag *>(d->tag[ID3v2Index]);
275 ID3v1::Tag *FLAC::File::ID3v1Tag(bool create)
277 return d->tag.access<ID3v1::Tag>(ID3v1Index, create);
280 Ogg::XiphComment *FLAC::File::xiphComment(bool create)
282 return d->tag.access<Ogg::XiphComment>(XiphIndex, create);
285 void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory)
287 d->ID3v2FrameFactory = factory;
291 ////////////////////////////////////////////////////////////////////////////////
293 ////////////////////////////////////////////////////////////////////////////////
295 void FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
297 // Look for an ID3v2 tag
299 d->ID3v2Location = findID3v2();
301 if(d->ID3v2Location >= 0) {
303 d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
305 d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
307 if(ID3v2Tag()->header()->tagSize() <= 0)
308 d->tag.set(ID3v2Index, 0);
313 // Look for an ID3v1 tag
315 d->ID3v1Location = findID3v1();
317 if(d->ID3v1Location >= 0) {
318 d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
322 // Look for FLAC metadata, including vorbis comments
329 if(d->hasXiphComment)
330 d->tag.set(XiphIndex, new Ogg::XiphComment(xiphCommentData()));
332 d->tag.set(XiphIndex, new Ogg::XiphComment);
335 d->properties = new Properties(streamInfoData(), streamLength(), propertiesStyle);
338 ByteVector FLAC::File::streamInfoData()
340 return isValid() ? d->streamInfoData : ByteVector();
343 ByteVector FLAC::File::xiphCommentData() const
345 return (isValid() && d->hasXiphComment) ? d->xiphCommentData : ByteVector();
348 long FLAC::File::streamLength()
350 return d->streamLength;
353 void FLAC::File::scan()
355 // Scan the metadata pages
363 long nextBlockOffset;
366 nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize);
368 nextBlockOffset = find("fLaC");
370 if(nextBlockOffset < 0) {
371 debug("FLAC::File::scan() -- FLAC stream not found");
376 nextBlockOffset += 4;
377 d->flacStart = nextBlockOffset;
379 seek(nextBlockOffset);
381 ByteVector header = readBlock(4);
383 // Header format (from spec):
384 // <1> Last-metadata-block flag
389 // 4 : VORBIS_COMMENT
391 // <24> Length of metadata to follow
393 char blockType = header[0] & 0x7f;
394 bool isLastBlock = (header[0] & 0x80) != 0;
395 uint length = header.mid(1, 3).toUInt();
397 // First block should be the stream_info metadata
399 if(blockType != StreamInfo) {
400 debug("FLAC::File::scan() -- invalid FLAC stream");
405 d->streamInfoData = readBlock(length);
406 nextBlockOffset += length + 4;
408 // Search through the remaining metadata
409 while(!isLastBlock) {
411 header = readBlock(4);
412 blockType = header[0] & 0x7f;
413 isLastBlock = (header[0] & 0x80) != 0;
414 length = header.mid(1, 3).toUInt();
416 // Found the vorbis-comment
417 if(blockType == VorbisComment) {
418 if(!d->hasXiphComment) {
419 d->xiphCommentData = readBlock(length);
420 d->hasXiphComment = true;
423 debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, using the first one");
427 nextBlockOffset += length + 4;
429 if(nextBlockOffset >= File::length()) {
430 debug("FLAC::File::scan() -- FLAC stream corrupted");
434 seek(nextBlockOffset);
437 // End of metadata, now comes the datastream
439 d->streamStart = nextBlockOffset;
440 d->streamLength = File::length() - d->streamStart;
443 d->streamLength -= 128;
448 long FLAC::File::findID3v1()
456 if(readBlock(3) == ID3v1::Tag::fileIdentifier())
462 long FLAC::File::findID3v2()
469 if(readBlock(3) == ID3v2::Header::fileIdentifier())
475 long FLAC::File::findPaddingBreak(long nextBlockOffset, long targetOffset, bool *isLast)
477 // Starting from nextBlockOffset, step over padding blocks to find the
478 // address of a block which is after targetOffset. Return zero if
479 // a non-padding block occurs before that point.
482 seek(nextBlockOffset);
484 ByteVector header = readBlock(4);
485 char blockType = header[0] & 0x7f;
486 bool isLastBlock = header[0] & 0x80;
487 uint length = header.mid(1, 3).toUInt();
489 if(blockType != Padding)
492 nextBlockOffset += 4 + length;
494 if(nextBlockOffset >= targetOffset) {
495 *isLast = isLastBlock;
496 return nextBlockOffset;