summaryrefslogtreecommitdiff
path: root/libs/taglib/taglib/flac/flacfile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/taglib/taglib/flac/flacfile.cpp')
-rw-r--r--libs/taglib/taglib/flac/flacfile.cpp431
1 files changed, 431 insertions, 0 deletions
diff --git a/libs/taglib/taglib/flac/flacfile.cpp b/libs/taglib/taglib/flac/flacfile.cpp
new file mode 100644
index 0000000000..2cf35ad246
--- /dev/null
+++ b/libs/taglib/taglib/flac/flacfile.cpp
@@ -0,0 +1,431 @@
+/***************************************************************************
+ copyright : (C) 2003-2004 by Allan Sandfeld Jensen
+ email : kde@carewolf.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * This library is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU Lesser General Public License version *
+ * 2.1 as published by the Free Software Foundation. *
+ * *
+ * This library is distributed in the hope that it will be useful, but *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+ * Lesser General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Lesser General Public *
+ * License along with this library; if not, write to the Free Software *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
+ * USA *
+ * *
+ * Alternatively, this file is available under the Mozilla Public *
+ * License Version 1.1. You may obtain a copy of the License at *
+ * http://www.mozilla.org/MPL/ *
+ ***************************************************************************/
+
+#include <tbytevector.h>
+#include <tstring.h>
+#include <tlist.h>
+#include <tdebug.h>
+#include <tagunion.h>
+
+#include <id3v2header.h>
+#include <id3v2tag.h>
+#include <id3v1tag.h>
+#include <xiphcomment.h>
+
+#include "flacfile.h"
+
+using namespace TagLib;
+
+namespace
+{
+ enum { XiphIndex = 0, ID3v2Index = 1, ID3v1Index = 2 };
+ enum { StreamInfo = 0, Padding, Application, SeekTable, VorbisComment, CueSheet };
+}
+
+class FLAC::File::FilePrivate
+{
+public:
+ FilePrivate() :
+ ID3v2FrameFactory(ID3v2::FrameFactory::instance()),
+ ID3v2Location(-1),
+ ID3v2OriginalSize(0),
+ ID3v1Location(-1),
+ properties(0),
+ flacStart(0),
+ streamStart(0),
+ streamLength(0),
+ scanned(false),
+ hasXiphComment(false),
+ hasID3v2(false),
+ hasID3v1(false) {}
+
+ ~FilePrivate()
+ {
+ delete properties;
+ }
+
+ const ID3v2::FrameFactory *ID3v2FrameFactory;
+ long ID3v2Location;
+ uint ID3v2OriginalSize;
+
+ long ID3v1Location;
+
+ TagUnion tag;
+
+ Properties *properties;
+ ByteVector streamInfoData;
+ ByteVector xiphCommentData;
+
+ long flacStart;
+ long streamStart;
+ long streamLength;
+ bool scanned;
+
+ bool hasXiphComment;
+ bool hasID3v2;
+ bool hasID3v1;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// public members
+////////////////////////////////////////////////////////////////////////////////
+
+FLAC::File::File(FileName file, bool readProperties,
+ Properties::ReadStyle propertiesStyle) :
+ TagLib::File(file)
+{
+ d = new FilePrivate;
+ read(readProperties, propertiesStyle);
+}
+
+FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
+ bool readProperties, Properties::ReadStyle propertiesStyle) :
+ TagLib::File(file)
+{
+ d = new FilePrivate;
+ d->ID3v2FrameFactory = frameFactory;
+ read(readProperties, propertiesStyle);
+}
+
+FLAC::File::~File()
+{
+ delete d;
+}
+
+TagLib::Tag *FLAC::File::tag() const
+{
+ return &d->tag;
+}
+
+FLAC::Properties *FLAC::File::audioProperties() const
+{
+ return d->properties;
+}
+
+
+bool FLAC::File::save()
+{
+ if(readOnly()) {
+ debug("FLAC::File::save() - Cannot save to a read only file.");
+ return false;
+ }
+
+ // Create new vorbis comments
+
+ Tag::duplicate(&d->tag, xiphComment(true), true);
+
+ d->xiphCommentData = xiphComment()->render(false);
+
+ // A Xiph comment portion of the data stream starts with a 4-byte descriptor.
+ // The first byte indicates the frame type. The last three bytes are used
+ // to give the length of the data segment. Here we start
+
+ ByteVector data = ByteVector::fromUInt(d->xiphCommentData.size());
+
+ data[0] = char(VorbisComment);
+ data.append(d->xiphCommentData);
+
+
+ // If file already have comment => find and update it
+ // if not => insert one
+
+ // TODO: Search for padding and use that
+
+ if(d->hasXiphComment) {
+
+ long nextBlockOffset = d->flacStart;
+ bool isLastBlock = false;
+
+ while(!isLastBlock) {
+ seek(nextBlockOffset);
+
+ ByteVector header = readBlock(4);
+ char blockType = header[0] & 0x7f;
+ isLastBlock = (header[0] & 0x80) != 0;
+ uint blockLength = header.mid(1, 3).toUInt();
+
+ if(blockType == VorbisComment) {
+ data[0] = header[0];
+ insert(data, nextBlockOffset, blockLength + 4);
+ break;
+ }
+
+ nextBlockOffset += blockLength + 4;
+ }
+ }
+ else {
+
+ const long firstBlockOffset = d->flacStart;
+ seek(firstBlockOffset);
+
+ ByteVector header = readBlock(4);
+ bool isLastBlock = (header[0] & 0x80) != 0;
+ uint blockLength = header.mid(1, 3).toUInt();
+
+ if(isLastBlock) {
+
+ // If the first block was previously also the last block, then we want to
+ // mark it as no longer being the first block (the writeBlock() call) and
+ // then set the data for the block that we're about to write to mark our
+ // new block as the last block.
+
+ seek(firstBlockOffset);
+ writeBlock(static_cast<char>(header[0] & 0x7F));
+ data[0] |= 0x80;
+ }
+
+ insert(data, firstBlockOffset + blockLength + 4, 0);
+ d->hasXiphComment = true;
+ }
+
+ // Update ID3 tags
+
+ if(ID3v2Tag()) {
+ if(d->hasID3v2) {
+ if(d->ID3v2Location < d->flacStart)
+ debug("FLAC::File::save() -- This can't be right -- an ID3v2 tag after the "
+ "start of the FLAC bytestream? Not writing the ID3v2 tag.");
+ else
+ insert(ID3v2Tag()->render(), d->ID3v2Location, d->ID3v2OriginalSize);
+ }
+ else
+ insert(ID3v2Tag()->render(), 0, 0);
+ }
+
+ if(ID3v1Tag()) {
+ seek(-128, End);
+ writeBlock(ID3v1Tag()->render());
+ }
+
+ return true;
+}
+
+ID3v2::Tag *FLAC::File::ID3v2Tag(bool create)
+{
+ if(!create || d->tag[ID3v2Index])
+ return static_cast<ID3v2::Tag *>(d->tag[ID3v2Index]);
+
+ d->tag.set(ID3v2Index, new ID3v2::Tag);
+ return static_cast<ID3v2::Tag *>(d->tag[ID3v2Index]);
+}
+
+ID3v1::Tag *FLAC::File::ID3v1Tag(bool create)
+{
+ return d->tag.access<ID3v1::Tag>(ID3v1Index, create);
+}
+
+Ogg::XiphComment *FLAC::File::xiphComment(bool create)
+{
+ return d->tag.access<Ogg::XiphComment>(XiphIndex, create);
+}
+
+void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory)
+{
+ d->ID3v2FrameFactory = factory;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// private members
+////////////////////////////////////////////////////////////////////////////////
+
+void FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
+{
+ // Look for an ID3v2 tag
+
+ d->ID3v2Location = findID3v2();
+
+ if(d->ID3v2Location >= 0) {
+
+ d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
+
+ d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
+
+ if(ID3v2Tag()->header()->tagSize() <= 0)
+ d->tag.set(ID3v2Index, 0);
+ else
+ d->hasID3v2 = true;
+ }
+
+ // Look for an ID3v1 tag
+
+ d->ID3v1Location = findID3v1();
+
+ if(d->ID3v1Location >= 0) {
+ d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
+ d->hasID3v1 = true;
+ }
+
+ // Look for FLAC metadata, including vorbis comments
+
+ scan();
+
+ if(!isValid())
+ return;
+
+ if(d->hasXiphComment)
+ d->tag.set(XiphIndex, new Ogg::XiphComment(xiphCommentData()));
+ else
+ d->tag.set(XiphIndex, new Ogg::XiphComment);
+
+ if(readProperties)
+ d->properties = new Properties(streamInfoData(), streamLength(), propertiesStyle);
+}
+
+ByteVector FLAC::File::streamInfoData()
+{
+ return isValid() ? d->streamInfoData : ByteVector();
+}
+
+ByteVector FLAC::File::xiphCommentData() const
+{
+ return (isValid() && d->hasXiphComment) ? d->xiphCommentData : ByteVector();
+}
+
+long FLAC::File::streamLength()
+{
+ return d->streamLength;
+}
+
+void FLAC::File::scan()
+{
+ // Scan the metadata pages
+
+ if(d->scanned)
+ return;
+
+ if(!isValid())
+ return;
+
+ long nextBlockOffset;
+
+ if(d->hasID3v2)
+ nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize);
+ else
+ nextBlockOffset = find("fLaC");
+
+ if(nextBlockOffset < 0) {
+ debug("FLAC::File::scan() -- FLAC stream not found");
+ setValid(false);
+ return;
+ }
+
+ nextBlockOffset += 4;
+ d->flacStart = nextBlockOffset;
+
+ seek(nextBlockOffset);
+
+ ByteVector header = readBlock(4);
+
+ // Header format (from spec):
+ // <1> Last-metadata-block flag
+ // <7> BLOCK_TYPE
+ // 0 : STREAMINFO
+ // 1 : PADDING
+ // ..
+ // 4 : VORBIS_COMMENT
+ // ..
+ // <24> Length of metadata to follow
+
+ char blockType = header[0] & 0x7f;
+ bool isLastBlock = (header[0] & 0x80) != 0;
+ uint length = header.mid(1, 3).toUInt();
+
+ // First block should be the stream_info metadata
+
+ if(blockType != StreamInfo) {
+ debug("FLAC::File::scan() -- invalid FLAC stream");
+ setValid(false);
+ return;
+ }
+
+ d->streamInfoData = readBlock(length);
+ nextBlockOffset += length + 4;
+
+ // Search through the remaining metadata
+
+ while(!isLastBlock) {
+
+ header = readBlock(4);
+ blockType = header[0] & 0x7f;
+ isLastBlock = (header[0] & 0x80) != 0;
+ length = header.mid(1, 3).toUInt();
+
+ if(blockType == Padding) {
+ // debug("FLAC::File::scan() -- Padding found");
+ }
+ // Found the vorbis-comment
+ else if(blockType == VorbisComment) {
+ d->xiphCommentData = readBlock(length);
+ d->hasXiphComment = true;
+ }
+
+ nextBlockOffset += length + 4;
+
+ if(nextBlockOffset >= File::length()) {
+ debug("FLAC::File::scan() -- FLAC stream corrupted");
+ setValid(false);
+ return;
+ }
+ seek(nextBlockOffset);
+ }
+
+ // End of metadata, now comes the datastream
+
+ d->streamStart = nextBlockOffset;
+ d->streamLength = File::length() - d->streamStart;
+
+ if(d->hasID3v1)
+ d->streamLength -= 128;
+
+ d->scanned = true;
+}
+
+long FLAC::File::findID3v1()
+{
+ if(!isValid())
+ return -1;
+
+ seek(-128, End);
+ long p = tell();
+
+ if(readBlock(3) == ID3v1::Tag::fileIdentifier())
+ return p;
+
+ return -1;
+}
+
+long FLAC::File::findID3v2()
+{
+ if(!isValid())
+ return -1;
+
+ seek(0);
+
+ if(readBlock(3) == ID3v2::Header::fileIdentifier())
+ return 0;
+
+ return -1;
+}