summaryrefslogtreecommitdiffstats
path: root/plugins/decoder/flac/k3bflacdecoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/decoder/flac/k3bflacdecoder.cpp')
-rw-r--r--plugins/decoder/flac/k3bflacdecoder.cpp494
1 files changed, 494 insertions, 0 deletions
diff --git a/plugins/decoder/flac/k3bflacdecoder.cpp b/plugins/decoder/flac/k3bflacdecoder.cpp
new file mode 100644
index 0000000..762403f
--- /dev/null
+++ b/plugins/decoder/flac/k3bflacdecoder.cpp
@@ -0,0 +1,494 @@
+/*
+ * FLAC decoder module for K3b.
+ * Based on the Ogg Vorbis module for same.
+ * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org>
+ * Copyright (C) 2003-2004 John Steele Scott <toojays@toojays.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * See the file "COPYING" for the exact licensing terms.
+ */
+
+#include <config.h>
+
+#include "k3bflacdecoder.h"
+
+#include <k3bpluginfactory.h>
+
+#include <qbuffer.h>
+#include <qfile.h>
+#include <qstringlist.h>
+
+#include <kurl.h>
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <string.h>
+#include <math.h>
+#include <FLAC++/metadata.h>
+#include <FLAC++/decoder.h>
+
+#ifdef HAVE_TAGLIB
+#include <taglib/tag.h>
+#include <taglib/flacfile.h>
+#endif
+
+#if !defined FLACPP_API_VERSION_CURRENT || FLACPP_API_VERSION_CURRENT < 6
+#define LEGACY_FLAC
+#else
+#undef LEGACY_FLAC
+#endif
+
+K_EXPORT_COMPONENT_FACTORY( libk3bflacdecoder, K3bPluginFactory<K3bFLACDecoderFactory>( "libk3bflacdecoder" ) )
+
+
+class K3bFLACDecoder::Private
+#ifdef LEGACY_FLAC
+ : public FLAC::Decoder::SeekableStream
+#else
+ : public FLAC::Decoder::Stream
+#endif
+{
+public:
+ void open(QFile* f) {
+ file = f;
+ file->open(IO_ReadOnly);
+
+ internalBuffer->flush();
+
+ set_metadata_respond(FLAC__METADATA_TYPE_STREAMINFO);
+ set_metadata_respond(FLAC__METADATA_TYPE_VORBIS_COMMENT);
+
+ init();
+ process_until_end_of_metadata();
+ }
+
+ void cleanup() {
+ file->close();
+ finish();
+ delete comments;
+ comments = 0;
+ }
+
+ Private(QFile* f)
+#ifdef LEGACY_FLAC
+ : FLAC::Decoder::SeekableStream(),
+#else
+ : FLAC::Decoder::Stream(),
+#endif
+ comments(0) {
+ internalBuffer = new QBuffer();
+ internalBuffer->open(IO_ReadWrite);
+
+ open(f);
+ }
+
+
+ ~Private() {
+ cleanup();
+ delete internalBuffer;
+ }
+
+ bool seekToFrame(int frame);
+
+ QFile* file;
+ QBuffer* internalBuffer;
+ FLAC::Metadata::VorbisComment* comments;
+ unsigned rate;
+ unsigned channels;
+ unsigned bitsPerSample;
+ unsigned maxFramesize;
+ unsigned maxBlocksize;
+ unsigned minFramesize;
+ unsigned minBlocksize;
+ FLAC__uint64 samples;
+
+protected:
+#ifdef LEGACY_FLAC
+ virtual FLAC__SeekableStreamDecoderReadStatus read_callback(FLAC__byte buffer[], unsigned *bytes);
+ virtual FLAC__SeekableStreamDecoderSeekStatus seek_callback(FLAC__uint64 absolute_byte_offset);
+ virtual FLAC__SeekableStreamDecoderTellStatus tell_callback(FLAC__uint64 *absolute_byte_offset);
+ virtual FLAC__SeekableStreamDecoderLengthStatus length_callback(FLAC__uint64 *stream_length);
+#else
+ virtual FLAC__StreamDecoderReadStatus read_callback(FLAC__byte buffer[], size_t *bytes);
+ virtual FLAC__StreamDecoderSeekStatus seek_callback(FLAC__uint64 absolute_byte_offset);
+ virtual FLAC__StreamDecoderTellStatus tell_callback(FLAC__uint64 *absolute_byte_offset);
+ virtual FLAC__StreamDecoderLengthStatus length_callback(FLAC__uint64 *stream_length);
+#endif
+ virtual bool eof_callback();
+ virtual void error_callback(FLAC__StreamDecoderErrorStatus){};
+ virtual void metadata_callback(const ::FLAC__StreamMetadata *metadata);
+ virtual ::FLAC__StreamDecoderWriteStatus write_callback(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]);
+};
+
+bool K3bFLACDecoder::Private::seekToFrame(int frame) {
+ FLAC__uint64 sample = frame * rate / 75;
+ return seek_absolute(sample);
+}
+
+bool K3bFLACDecoder::Private::eof_callback() {
+ return file->atEnd();
+}
+
+#ifdef LEGACY_FLAC
+FLAC__SeekableStreamDecoderReadStatus K3bFLACDecoder::Private::read_callback(FLAC__byte buffer[], unsigned *bytes) {
+ long retval = file->readBlock((char *)buffer, (*bytes));
+ if(-1 == retval) {
+ return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR;
+ } else {
+ (*bytes) = retval;
+ return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK;
+ }
+}
+#else
+FLAC__StreamDecoderReadStatus K3bFLACDecoder::Private::read_callback(FLAC__byte buffer[], size_t *bytes) {
+ long retval = file->readBlock((char *)buffer, (*bytes));
+ if(-1 == retval) {
+ return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
+ } else {
+ (*bytes) = retval;
+ return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+ }
+}
+#endif
+
+#ifdef LEGACY_FLAC
+FLAC__SeekableStreamDecoderSeekStatus
+K3bFLACDecoder::Private::seek_callback(FLAC__uint64 absolute_byte_offset) {
+ if(!file->at(absolute_byte_offset))
+ return FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR;
+ else
+ return FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK;
+}
+#else
+FLAC__StreamDecoderSeekStatus
+K3bFLACDecoder::Private::seek_callback(FLAC__uint64 absolute_byte_offset) {
+ if(file->at(absolute_byte_offset) == FALSE)
+ return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
+ else
+ return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
+}
+#endif
+
+#ifdef LEGACY_FLAC
+FLAC__SeekableStreamDecoderTellStatus
+K3bFLACDecoder::Private::tell_callback(FLAC__uint64 *absolute_byte_offset) {
+ (*absolute_byte_offset) = file->at();
+ return FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK;
+}
+#else
+FLAC__StreamDecoderTellStatus
+K3bFLACDecoder::Private::tell_callback(FLAC__uint64 *absolute_byte_offset) {
+ (*absolute_byte_offset) = file->at();
+ return FLAC__STREAM_DECODER_TELL_STATUS_OK;
+}
+#endif
+
+#ifdef LEGACY_FLAC
+FLAC__SeekableStreamDecoderLengthStatus
+K3bFLACDecoder::Private::length_callback(FLAC__uint64 *stream_length) {
+ (*stream_length) = file->size();
+ return FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK;
+}
+#else
+FLAC__StreamDecoderLengthStatus
+K3bFLACDecoder::Private::length_callback(FLAC__uint64 *stream_length) {
+ (*stream_length) = file->size();
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
+}
+#endif
+
+
+void K3bFLACDecoder::Private::metadata_callback(const FLAC__StreamMetadata *metadata) {
+ switch (metadata->type) {
+ case FLAC__METADATA_TYPE_STREAMINFO:
+ channels = metadata->data.stream_info.channels;
+ rate = metadata->data.stream_info.sample_rate;
+ bitsPerSample = metadata->data.stream_info.bits_per_sample;
+ samples = metadata->data.stream_info.total_samples;
+ maxFramesize = metadata->data.stream_info.max_framesize;
+ minFramesize = metadata->data.stream_info.min_framesize;
+ maxBlocksize = metadata->data.stream_info.max_blocksize;
+ minBlocksize = metadata->data.stream_info.min_blocksize;
+ break;
+ case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+ comments = new FLAC::Metadata::VorbisComment((FLAC__StreamMetadata *)metadata, true);
+ break;
+ default:
+ break;
+ }
+}
+
+FLAC__StreamDecoderWriteStatus K3bFLACDecoder::Private::write_callback(const FLAC__Frame *frame, const FLAC__int32 * const buffer[]) {
+ unsigned i, j;
+ // Note that in canDecode we made sure that the input is 1-16 bit stereo or mono.
+ unsigned samples = frame->header.blocksize;
+
+ for(i=0; i < samples; i++) {
+ // in FLAC channel 0 is left, 1 is right
+ for(j=0; j < this->channels; j++) {
+ FLAC__int32 value = (buffer[j][i])<<(16 - frame->header.bits_per_sample);
+ internalBuffer->putch(value >> 8); // msb
+ internalBuffer->putch(value & 0xFF); // lsb
+ }
+ }
+
+ // Rewind the buffer so the decode method will take data from the beginning.
+ internalBuffer->at(0);
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+K3bFLACDecoder::K3bFLACDecoder( QObject* parent, const char* name )
+ : K3bAudioDecoder( parent, name )
+{
+ d = 0;
+}
+
+
+K3bFLACDecoder::~K3bFLACDecoder()
+{
+ delete d;
+}
+
+void K3bFLACDecoder::cleanup()
+{
+ if (d) {
+ d->cleanup();
+ d->open(new QFile(filename()));
+ }
+ else
+ d = new Private(new QFile(filename()));
+}
+
+bool K3bFLACDecoder::analyseFileInternal( K3b::Msf& frames, int& samplerate, int& ch )
+{
+ cleanup();
+
+ frames = (unsigned long)ceil((d->samples * 75.0)/d->rate);
+ samplerate = d->rate;
+ ch = d->channels;
+
+ // add meta info
+ if( d->comments != 0 ) {
+ kdDebug() << "(K3bFLACDecoder) unpacking Vorbis tags" << endl;
+ for( unsigned int i = 0; i < d->comments->get_num_comments(); ++i ) {
+ QString key = QString::fromUtf8( d->comments->get_comment(i).get_field_name(),
+ d->comments->get_comment(i).get_field_name_length() );
+ QString value = QString::fromUtf8( d->comments->get_comment(i).get_field_value(),
+ d->comments->get_comment(i).get_field_value_length() );
+
+ if( key.upper() == "TITLE" )
+ addMetaInfo( META_TITLE, value );
+ else if( key.upper() == "ARTIST" )
+ addMetaInfo( META_ARTIST, value );
+ else if( key.upper() == "DESCRIPTION" )
+ addMetaInfo( META_COMMENT, value );
+ }
+ }
+#ifdef HAVE_TAGLIB
+ if ((d->comments == 0) || (d->comments->get_num_comments() == 0)) {
+ // no Vorbis comments, check for ID3 tags
+ kdDebug() << "(K3bFLACDecoder) using taglib to read tag" << endl;
+ TagLib::FLAC::File f( QFile::encodeName(filename()) );
+ if( f.isOpen() ) {
+ addMetaInfo( META_TITLE, TStringToQString( f.tag()->title() ) );
+ addMetaInfo( META_ARTIST, TStringToQString( f.tag()->artist() ) );
+ addMetaInfo( META_COMMENT, TStringToQString( f.tag()->comment() ) );
+ }
+ }
+#endif
+
+ return true;
+}
+
+
+bool K3bFLACDecoder::initDecoderInternal()
+{
+ cleanup();
+
+ return true;
+}
+
+
+int K3bFLACDecoder::decodeInternal( char* _data, int maxLen )
+{
+ int bytesToCopy;
+ int bytesCopied;
+ int bytesAvailable;
+
+#ifdef LEGACY_FLAC
+ if(d->internalBuffer->size() == 0) {
+ // want more data
+ switch(d->get_state()) {
+ case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM:
+ d->finish();
+ break;
+ case FLAC__SEEKABLE_STREAM_DECODER_OK:
+ if(! d->process_single())
+ return -1;
+ break;
+ default:
+ return -1;
+ }
+ }
+#else
+ if(d->internalBuffer->size() == 0) {
+ // want more data
+ if(d->get_state() == FLAC__STREAM_DECODER_END_OF_STREAM)
+ d->finish();
+ else if(d->get_state() < FLAC__STREAM_DECODER_END_OF_STREAM) {
+ if(! d->process_single())
+ return -1;
+ }
+ else
+ return -1;
+ }
+#endif
+
+ bytesAvailable = d->internalBuffer->size() - d->internalBuffer->at();
+ bytesToCopy = QMIN(maxLen, bytesAvailable);
+ bytesCopied = (int)d->internalBuffer->readBlock(_data, bytesToCopy);
+
+ if(bytesCopied == bytesAvailable) {
+ // reset the buffer
+ d->internalBuffer->close();
+ d->internalBuffer->open(IO_ReadWrite|IO_Truncate);
+ }
+
+ return bytesCopied;
+}
+
+
+bool K3bFLACDecoder::seekInternal( const K3b::Msf& pos )
+{
+ return d->seekToFrame(pos.totalFrames());
+}
+
+
+QString K3bFLACDecoder::fileType() const
+{
+ return i18n("FLAC");
+}
+
+
+QStringList K3bFLACDecoder::supportedTechnicalInfos() const
+{
+ return QStringList::split( ";",
+ i18n("Channels") + ";" +
+ i18n("Sampling Rate") + ";" +
+ i18n("Sample Size") );
+}
+
+
+QString K3bFLACDecoder::technicalInfo( const QString& info ) const
+{
+ if( d->comments != 0 ) {
+ if( info == i18n("Vendor") )
+#ifdef FLAC_NEWER_THAN_1_1_1
+ return QString::fromUtf8((char*)d->comments->get_vendor_string());
+#else
+ return QString::fromUtf8(d->comments->get_vendor_string().get_field());
+#endif
+ else if( info == i18n("Channels") )
+ return QString::number(d->channels);
+ else if( info == i18n("Sampling Rate") )
+ return i18n("%1 Hz").arg(d->rate);
+ else if( info == i18n("Sample Size") )
+ return i18n("%1 bits").arg(d->bitsPerSample);
+ }
+
+ return QString::null;
+}
+
+
+
+K3bFLACDecoderFactory::K3bFLACDecoderFactory( QObject* parent, const char* name )
+ : K3bAudioDecoderFactory( parent, name )
+{
+}
+
+
+K3bFLACDecoderFactory::~K3bFLACDecoderFactory()
+{
+}
+
+
+K3bAudioDecoder* K3bFLACDecoderFactory::createDecoder( QObject* parent,
+ const char* name ) const
+{
+ return new K3bFLACDecoder( parent, name );
+}
+
+
+bool K3bFLACDecoderFactory::canDecode( const KURL& url )
+{
+ // buffer large enough to read an ID3 tag header
+ char buf[10];
+
+ // Note: since file is created on the stack it will be closed automatically
+ // by its destructor when this method (i.e. canDecode) returns.
+ QFile file(url.path());
+
+ if(!file.open(IO_ReadOnly)) {
+ kdDebug() << "(K3bFLACDecoder) Could not open file " << url.path() << endl;
+ return false;
+ }
+
+ // look for a fLaC magic number or ID3 tag header
+ if(10 != file.readBlock(buf, 10)) {
+ kdDebug() << "(K3bFLACDecorder) File " << url.path()
+ << " is too small to be a FLAC file" << endl;
+ return false;
+ }
+
+ if(0 == memcmp(buf, "ID3", 3)) {
+ // Found ID3 tag, try and seek past it.
+ kdDebug() << "(K3bFLACDecorder) File " << url.path() << ": found ID3 tag" << endl;
+
+ // See www.id3.org for details of the header, note that the size field
+ // unpacks to 7-bit bytes, then the +10 is for the header itself.
+ int pos;
+ pos = ((buf[6]<<21)|(buf[7]<<14)|(buf[8]<<7)|buf[9]) + 10;
+
+ kdDebug() << "(K3bFLACDecoder) " << url.path() << ": seeking to "
+ << pos << endl;
+ if(!file.at(pos)) {
+ kdDebug() << "(K3bFLACDecoder) " << url.path() << ": couldn't seek to "
+ << pos << endl;
+ return false;
+ }else{
+ // seek was okay, try and read magic number into buf
+ if(4 != file.readBlock(buf, 4)) {
+ kdDebug() << "(K3bFLACDecorder) File " << url.path()
+ << " has ID3 tag but naught else!" << endl;
+ return false;
+ }
+ }
+ }
+
+ if(memcmp(buf, "fLaC", 4) != 0) {
+ kdDebug() << "(K3bFLACDecoder) " << url.path() << ": not a FLAC file" << endl;
+ return false;
+ }
+
+ FLAC::Metadata::StreamInfo info = FLAC::Metadata::StreamInfo();
+ FLAC::Metadata::get_streaminfo(url.path().ascii(), info);
+
+ if((info.get_channels() <= 2) &&
+ (info.get_bits_per_sample() <= 16)) {
+ return true;
+ } else {
+ kdDebug() << "(K3bFLACDecoder) " << url.path() << ": wrong format:" << endl
+ << " channels: "
+ << QString::number(info.get_channels()) << endl
+ << " samplerate: "
+ << QString::number(info.get_sample_rate()) << endl
+ << " bits/sample: "
+ << QString::number(info.get_bits_per_sample()) << endl;
+ return false;
+ }
+}
+
+#include "k3bflacdecoder.moc"