aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authortrialuser02 <trialuser02@90c681e8-e032-0410-971d-27865f9a5e38>2008-11-27 23:09:03 +0000
committertrialuser02 <trialuser02@90c681e8-e032-0410-971d-27865f9a5e38>2008-11-27 23:09:03 +0000
commita3e55825c3f38ae1bc5e67584a7f37955de3fb87 (patch)
treeb695b742e08b3cc735827c1eb389fe1eb702b39a /src
parentdc8dac141f57b50a9316131c8491e72b488a2d2c (diff)
downloadqmmp-a3e55825c3f38ae1bc5e67584a7f37955de3fb87.tar.gz
qmmp-a3e55825c3f38ae1bc5e67584a7f37955de3fb87.tar.bz2
qmmp-a3e55825c3f38ae1bc5e67584a7f37955de3fb87.zip
flac plugin: embeded cue support (using cuesheet xiph comment)
git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@643 90c681e8-e032-0410-971d-27865f9a5e38
Diffstat (limited to 'src')
-rw-r--r--src/plugins/Input/flac/CMakeLists.txt2
-rw-r--r--src/plugins/Input/flac/cueparser.cpp194
-rw-r--r--src/plugins/Input/flac/cueparser.h59
-rw-r--r--src/plugins/Input/flac/decoder_flac.cpp94
-rw-r--r--src/plugins/Input/flac/decoder_flac.h7
-rw-r--r--src/plugins/Input/flac/decoderflacfactory.cpp24
-rw-r--r--src/plugins/Input/flac/flac.pro12
7 files changed, 363 insertions, 29 deletions
diff --git a/src/plugins/Input/flac/CMakeLists.txt b/src/plugins/Input/flac/CMakeLists.txt
index 9e41e52f7..080629d99 100644
--- a/src/plugins/Input/flac/CMakeLists.txt
+++ b/src/plugins/Input/flac/CMakeLists.txt
@@ -40,12 +40,14 @@ SET(libflac_SRCS
decoder_flac.cpp
decoderflacfactory.cpp
detailsdialog.cpp
+ cueparser.cpp
)
SET(libflac_MOC_HDRS
decoderflacfactory.h
decoder_flac.h
detailsdialog.h
+ cueparser.h
)
SET(libflac_RCCS translations/translations.qrc)
diff --git a/src/plugins/Input/flac/cueparser.cpp b/src/plugins/Input/flac/cueparser.cpp
new file mode 100644
index 000000000..64870c97f
--- /dev/null
+++ b/src/plugins/Input/flac/cueparser.cpp
@@ -0,0 +1,194 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Ilya Kotov *
+ * forkotov02@hotmail.ru *
+ * *
+ * 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. *
+ * *
+ * This program 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 General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+
+
+#include <QDir>
+#include <QSettings>
+#include <QTextStream>
+#include <QTextCodec>
+
+#include <qmmp/decoder.h>
+
+#include "cueparser.h"
+
+CUEParser::CUEParser(const QByteArray &array, const QString &fileName)
+{
+ QString album, genre, date, comment;
+ QTextStream textStream (array);
+ QSettings settings(QDir::homePath()+"/.qmmp/qmmprc", QSettings::IniFormat);
+ QTextCodec *codec = QTextCodec::codecForName(settings.value("CUE/encoding","ISO-8859-1").toByteArray ());
+ textStream.setCodec(codec);
+ m_filePath = fileName;
+ QString artist;
+ while (!textStream.atEnd())
+ {
+ QString line = textStream.readLine().trimmed();
+ QStringList words = splitLine(line);
+ if (words.size() < 2)
+ continue;
+
+ if (words[0] == "FILE")
+ {
+ //m_filePath = QUrl(fileName).path ();
+ //m_filePath = QFileInfo(m_filePath).dir().filePath(words[1]);
+ }
+ else if (words[0] == "PERFORMER")
+ {
+ if (m_infoList.isEmpty())
+ {
+ artist = words[1];
+ continue;
+ }
+ else
+ m_infoList.last().setMetaData(Qmmp::ARTIST, words[1]);
+
+ }
+ else if (words[0] == "TITLE")
+ {
+ if (m_infoList.isEmpty())
+ album = words[1];
+ else
+ m_infoList.last().setMetaData(Qmmp::TITLE, words[1]);
+ }
+ else if (words[0] == "TRACK")
+ {
+ FileInfo info("flac://" + fileName + QString("#%1").arg(words[1].toInt()));
+ info.setMetaData(Qmmp::TRACK, words[1].toInt());
+ m_infoList << info;
+ m_offsets << 0;
+ }
+ else if (words[0] == "INDEX")
+ {
+ if (m_infoList.isEmpty())
+ continue;
+ m_infoList.last ().setLength(getLength(words[2]));
+ m_offsets.last() = getLength(words[2]);
+ }
+ else if (words[0] == "REM")
+ {
+ if (words.size() < 3)
+ continue;
+ if (words[1] == "GENRE")
+ genre = words[2];
+ else if (words[1] == "DATE")
+ date = words[2];
+ else if (words[1] == "COMMENT")
+ comment = words[2];
+ }
+ }
+ //calculate length
+ for (int i = 0; i < m_infoList.size() - 1; ++i)
+ m_infoList[i].setLength(m_infoList[i+1].length() - m_infoList[i].length());
+ //calculate last item length
+ QList <FileInfo *> f_list;
+ //f_list = Decoder::createPlayList(m_filePath);
+ qint64 l = f_list.isEmpty() ? 0 : f_list.at(0)->length();
+ if (l > m_infoList.last().length())
+ m_infoList.last().setLength(l - m_infoList.last().length());
+ else
+ m_infoList.last().setLength(0);
+
+ for (int i = 0; i < m_infoList.size(); ++i)
+ {
+ m_infoList[i].setMetaData(Qmmp::ALBUM, album);
+ m_infoList[i].setMetaData(Qmmp::GENRE, genre);
+ m_infoList[i].setMetaData(Qmmp::YEAR, date);
+ m_infoList[i].setMetaData(Qmmp::COMMENT, comment);
+ if(!m_infoList[i].metaData().count(Qmmp::ARTIST) && !artist.isEmpty())
+ m_infoList[i].setMetaData(Qmmp::ARTIST, artist);
+ }
+}
+
+
+CUEParser::~CUEParser()
+{
+}
+
+QList<FileInfo*> CUEParser::createPlayList()
+{
+ QList<FileInfo*> list;
+ foreach(FileInfo info, m_infoList)
+ {
+ list << new FileInfo(info);
+ }
+ return list;
+}
+
+const QString CUEParser::filePath()
+{
+ return m_filePath;
+}
+
+qint64 CUEParser::offset(int track)
+{
+ return m_offsets.at(track - 1);
+}
+
+qint64 CUEParser::length(int track)
+{
+ return m_infoList.at(track - 1).length();
+}
+
+int CUEParser::count()
+{
+ return m_infoList.count();
+}
+
+FileInfo *CUEParser::info(int track)
+{
+ return &m_infoList[track - 1];
+}
+
+QStringList CUEParser::splitLine(const QString &line)
+{
+ //qDebug("row string = %s",qPrintable(line));
+ QStringList list;
+ QString buf = line.trimmed();
+ if (buf.isEmpty())
+ return list;
+ while (!buf.isEmpty())
+ {
+ //qDebug(qPrintable(buf));
+ if (buf.startsWith('"'))
+ {
+ int end = buf.indexOf('"',1);
+ list << buf.mid (1, end - 1);
+ buf.remove (0, end+1);
+ }
+ else
+ {
+ int end = buf.indexOf(' ', 0);
+ if (end < 0)
+ end = buf.size();
+ list << buf.mid (0, end);
+ buf.remove (0, end);
+ }
+ buf = buf.trimmed();
+ }
+ return list;
+}
+
+int CUEParser::getLength(const QString &str)
+{
+ QStringList list = str.split(":");
+ if (list.size() < 2)
+ return 0;
+ return list.at(0).toInt()*60 + list.at(1).toInt();
+}
diff --git a/src/plugins/Input/flac/cueparser.h b/src/plugins/Input/flac/cueparser.h
new file mode 100644
index 000000000..365076c9a
--- /dev/null
+++ b/src/plugins/Input/flac/cueparser.h
@@ -0,0 +1,59 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Ilya Kotov *
+ * forkotov02@hotmail.ru *
+ * *
+ * 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. *
+ * *
+ * This program 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 General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+#ifndef CUEPARSER_H
+#define CUEPARSER_H
+
+#include <QList>
+#include <QMap>
+#include <QByteArray>
+#include <QString>
+#include <QStringList>
+
+#include <qmmp/fileinfo.h>
+
+
+/**
+ @author Ilya Kotov <forkotov02@hotmail.ru>
+*/
+class CUEParser
+{
+public:
+ CUEParser(const QByteArray &array, const QString &fileName);
+
+ ~CUEParser();
+
+ QList<FileInfo*> createPlayList();
+ const QString filePath();
+ qint64 offset(int track);
+ qint64 length(int track);
+ int count();
+ FileInfo *info(int track);
+
+private:
+ QList< QMap<int, int> > m_map;
+ QString m_filePath;
+ QList <FileInfo> m_infoList;
+ QList <int> m_offsets;
+ QStringList splitLine(const QString &line);
+ int getLength(const QString &str);
+
+};
+
+#endif
diff --git a/src/plugins/Input/flac/decoder_flac.cpp b/src/plugins/Input/flac/decoder_flac.cpp
index 5bb2b6219..44618c919 100644
--- a/src/plugins/Input/flac/decoder_flac.cpp
+++ b/src/plugins/Input/flac/decoder_flac.cpp
@@ -23,14 +23,23 @@
and libxmms-flac written by Josh Coalson. */
+#include <taglib/tag.h>
+#include <taglib/fileref.h>
+#include <taglib/flacfile.h>
+#include <taglib/xiphcomment.h>
+#include <taglib/tmap.h>
+
#include <qmmp/constants.h>
#include <qmmp/buffer.h>
#include <qmmp/output.h>
#include <qmmp/recycler.h>
#include <QObject>
+#include <QFile>
#include <QIODevice>
#include <FLAC/all.h>
+
+#include "cueparser.h"
#include "decoder_flac.h"
@@ -146,7 +155,7 @@ static FLAC__StreamDecoderReadStatus flac_callback_read (const FLAC__StreamDecod
DecoderFLAC *dflac = (DecoderFLAC *) client_data;
qint64 res;
- res = dflac->input()->read((char *)buffer, *bytes);
+ res = dflac->data()->input->read((char *)buffer, *bytes);
if (res > 0)
{
@@ -189,7 +198,7 @@ static FLAC__StreamDecoderTellStatus flac_callback_tell (const FLAC__StreamDecod
void *client_data)
{
DecoderFLAC *dflac = (DecoderFLAC *) client_data;
- *offset = dflac->input()->pos ();
+ *offset = dflac->data()->input->pos ();
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
}
@@ -199,7 +208,7 @@ static FLAC__StreamDecoderSeekStatus flac_callback_seek (const FLAC__StreamDecod
{
DecoderFLAC *dflac = (DecoderFLAC *) client_data;
- return dflac->input()->seek(offset)
+ return dflac->data()->input->seek(offset)
? FLAC__STREAM_DECODER_SEEK_STATUS_OK
: FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
}
@@ -209,7 +218,7 @@ static FLAC__StreamDecoderLengthStatus flac_callback_length (const FLAC__StreamD
void *client_data)
{
DecoderFLAC *dflac = (DecoderFLAC *) client_data;
- *stream_length = dflac->input()->size();
+ *stream_length = dflac->data()->input->size();
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
}
@@ -248,7 +257,7 @@ static void flac_callback_error (const FLAC__StreamDecoder *,
// Decoder class
-DecoderFLAC::DecoderFLAC(QObject *parent, DecoderFactory *d, QIODevice *i, Output *o)
+DecoderFLAC::DecoderFLAC(QObject *parent, DecoderFactory *d, QIODevice *i, Output *o, const QString &path)
: Decoder(parent, d, i, o)
{
inited = FALSE;
@@ -268,6 +277,14 @@ DecoderFLAC::DecoderFLAC(QObject *parent, DecoderFactory *d, QIODevice *i, Outpu
chan = 0;
output_size = 0;
m_data = 0;
+ m_path = path;
+
+ m_offset = 0;
+ m_length = 0;
+ m_cue = FALSE;
+ m_data = new flac_data;
+ m_data->decoder = NULL;
+ data()->input = i;
}
@@ -346,10 +363,43 @@ bool DecoderFLAC::initialize()
totalTime = 0.0;
- if (! input())
+ if (!data()->input)
{
- qWarning("DecoderFLAC: cannot initialize. No input.");
- return FALSE;
+ QString p = m_path;
+ if (m_path.startsWith("flac://")) //embeded cue track
+ {
+ p = QUrl(m_path).path();
+ if (!p.endsWith(".flac"))
+ {
+ qWarning("DecoderFLAC: invalid url.");
+ return FALSE;
+ }
+ qDebug("DecoderFLAC: using embeded cue");
+ TagLib::FLAC::File fileRef(p.toLocal8Bit ());
+ //looking for cuesheet comment
+ TagLib::Ogg::XiphComment *xiph_comment = fileRef.xiphComment();
+ QList <FileInfo*> list;
+ if (xiph_comment && xiph_comment->fieldListMap().contains("CUESHEET"))
+ {
+ qDebug("DecoderFLAC: found cuesheet xiph comment");
+ CUEParser parser(xiph_comment->fieldListMap()["CUESHEET"].toString().toCString(TRUE), m_path);
+ int track = m_path.section("#", -1).toInt();
+ m_offset = parser.offset(track);
+ m_length = parser.length(track);
+ data()->input = new QFile(p);
+ m_cue = TRUE;
+ }
+ else
+ {
+ qWarning("DecoderFLAC: unable to find cuesheet comment.");
+ return FALSE;
+ }
+ }
+ else
+ {
+ qWarning("DecoderFLAC: cannot initialize. No input.");
+ return FALSE;
+ }
}
if (! output_buf)
@@ -357,9 +407,9 @@ bool DecoderFLAC::initialize()
output_at = 0;
output_bytes = 0;
- if (!input()->isOpen())
+ if (!data()->input->isOpen())
{
- if (!input()->open(QIODevice::ReadOnly))
+ if (!data()->input->open(QIODevice::ReadOnly))
{
return FALSE;
}
@@ -371,19 +421,13 @@ bool DecoderFLAC::initialize()
output_at = 0;
output_bytes = 0;
- if (! input()->isOpen())
+ if (! data()->input->isOpen())
{
- if (! input()->open(QIODevice::ReadOnly))
+ if (! data()->input->open(QIODevice::ReadOnly))
{
return FALSE;
}
}
- if (!m_data)
- {
- m_data = new flac_data;
- m_data->decoder = NULL;
- }
-
m_data->bitrate = -1;
m_data->abort = 0;
m_data->sample_buffer_fill = 0;
@@ -424,6 +468,10 @@ bool DecoderFLAC::initialize()
totalTime = data()->length;
inited = TRUE;
+ if (m_offset)
+ seekTime = m_offset;
+ if (m_length)
+ totalTime = m_length;
qDebug("DecoderFLAC: initialize succes");
return TRUE;
}
@@ -441,7 +489,7 @@ qint64 DecoderFLAC::lengthInSeconds()
void DecoderFLAC::seek(qint64 pos)
{
if (totalTime > 0)
- seekTime = pos;
+ seekTime = pos + m_offset;
}
@@ -453,6 +501,11 @@ void DecoderFLAC::deinit()
len = freq = bitrate = 0;
stat = chan = 0;
output_size = 0;
+ if (!input() && data()->input)
+ {
+ data()->input->close();
+ data()->input = 0;
+ };
}
void DecoderFLAC::run()
@@ -526,6 +579,9 @@ void DecoderFLAC::run()
m_finish = TRUE;
}
+ if (m_length && (StateHandler::instance()->elapsed() >= m_length))
+ m_finish = TRUE;
+
mutex()->unlock();
}
diff --git a/src/plugins/Input/flac/decoder_flac.h b/src/plugins/Input/flac/decoder_flac.h
index 8431a4555..cef0cd561 100644
--- a/src/plugins/Input/flac/decoder_flac.h
+++ b/src/plugins/Input/flac/decoder_flac.h
@@ -53,12 +53,13 @@ struct flac_data
int ok; /* was this stream successfully opened? */
//struct decoder_error error;
+ QIODevice *input;
};
class DecoderFLAC : public Decoder
{
public:
- DecoderFLAC(QObject *, DecoderFactory *, QIODevice *, Output *);
+ DecoderFLAC(QObject *, DecoderFactory *, QIODevice *, Output *, const QString &path);
virtual ~DecoderFLAC();
// Standard Decoder API
@@ -98,6 +99,10 @@ private:
int chan;
unsigned long output_size;
double totalTime, seekTime;
+ QString m_path;
+ qint64 m_offset;
+ qint64 m_length;
+ bool m_cue;
};
diff --git a/src/plugins/Input/flac/decoderflacfactory.cpp b/src/plugins/Input/flac/decoderflacfactory.cpp
index 1843ccac0..2da4c69de 100644
--- a/src/plugins/Input/flac/decoderflacfactory.cpp
+++ b/src/plugins/Input/flac/decoderflacfactory.cpp
@@ -21,7 +21,11 @@
#include <QtGui>
#include <taglib/tag.h>
#include <taglib/fileref.h>
+#include <taglib/flacfile.h>
+#include <taglib/xiphcomment.h>
+#include <taglib/tmap.h>
+#include "cueparser.h"
#include "detailsdialog.h"
#include "decoder_flac.h"
#include "decoderflacfactory.h"
@@ -48,22 +52,23 @@ const DecoderProperties DecoderFLACFactory::properties() const
properties.filter = "*.flac";
properties.description = tr("FLAC Files");
//properties.contentType = ;
+ properties.protocols = "flac";
properties.hasAbout = TRUE;
properties.hasSettings = FALSE;
return properties;
}
Decoder *DecoderFLACFactory::create(QObject *parent, QIODevice *input,
- Output *output, const QString &)
+ Output *output, const QString &path)
{
- return new DecoderFLAC(parent, this, input, output);
+ return new DecoderFLAC(parent, this, input, output, path);
}
QList<FileInfo *> DecoderFLACFactory::createPlayList(const QString &fileName)
{
FileInfo *info = new FileInfo(fileName);
- TagLib::FileRef fileRef(fileName.toLocal8Bit ());
+ TagLib::FLAC::File fileRef(fileName.toLocal8Bit ());
TagLib::Tag *tag = fileRef.tag();
if (tag && !tag->isEmpty())
@@ -84,8 +89,19 @@ QList<FileInfo *> DecoderFLACFactory::createPlayList(const QString &fileName)
if (fileRef.audioProperties())
info->setLength(fileRef.audioProperties()->length());
+
+ //looking for cuesheet comment
+ TagLib::Ogg::XiphComment *xiph_comment = fileRef.xiphComment();
QList <FileInfo*> list;
- list << info;
+ if (xiph_comment && xiph_comment->fieldListMap().contains("CUESHEET"))
+ {
+ qDebug(xiph_comment->fieldListMap()["CUESHEET"].toString().toCString(TRUE));
+ CUEParser parser(xiph_comment->fieldListMap()["CUESHEET"].toString().toCString(TRUE), fileName);
+ list = parser.createPlayList();
+ delete info;
+ }
+ else
+ list << info;
return list;
}
diff --git a/src/plugins/Input/flac/flac.pro b/src/plugins/Input/flac/flac.pro
index 1a24d2fda..8b6d6382b 100644
--- a/src/plugins/Input/flac/flac.pro
+++ b/src/plugins/Input/flac/flac.pro
@@ -3,12 +3,14 @@ include(../../plugins.pri)
FORMS += detailsdialog.ui
HEADERS += decoderflacfactory.h \
decoder_flac.h \
- detailsdialog.h
+ detailsdialog.h \
+ cueparser.h
SOURCES += decoder_flac.cpp \
decoderflacfactory.cpp \
- detailsdialog.cpp
+ detailsdialog.cpp \
+ cueparser.cpp
-TARGET=$$PLUGINS_PREFIX/Input/flac
+TARGET =$$PLUGINS_PREFIX/Input/flac
QMAKE_CLEAN =$$PLUGINS_PREFIX/Input/libflac.so
INCLUDEPATH += ../../../
@@ -28,8 +30,8 @@ TRANSLATIONS = translations/flac_plugin_ru.ts \
translations/flac_plugin_de.ts
RESOURCES = translations/translations.qrc
-isEmpty (LIB_DIR){
-LIB_DIR = /lib
+isEmpty(LIB_DIR){
+ LIB_DIR = /lib
}
target.path = $$LIB_DIR/qmmp/Input
INSTALLS += target