Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

MythMusic: Use the portable libcdio for reading CDs

This patch enables MythMusic to play and rip CDs using libcdio on all
platforms and also adds code to replace libcdaudio CDDB lookups.

NOTE: This removes libcdaudio and the CDDA Paranoia libraries as dependencies
and adds libcdio as an optional dependency (if it's missing you wont be able
to play or rip CDs). Closes #9362, #7857.

NOTE: It seems the freedb.freedb.org server is currently down so I had to do
some of the testing using the musicbrainz mirror. It was working fine yesterday
so I've left it hardcoded to the freedb server for now.
  • Loading branch information...
commit c121c3acd74a3cabfb37e22bc88ef24552e1e2e7 1 parent 72a4bad
Paul Harrison authored
View
51 mythplugins/configure
@@ -115,6 +115,7 @@ fftw
exif
newexif
dcraw
+cdio
"
enable $PLUGIN_LIST $CONFIG_LIST
@@ -468,28 +469,11 @@ if test "$music" != "no" ; then
echo "MythMusic requires FLAC."
fi
- cdaudio="no"
- if has_header cdaudio.h && has_library libcdaudio ; then
- cdaudio="yes"
- fi
-
- if test "$cdaudio" = "no" -a "$targetos" != "Darwin" -a "`echo ${targetos} | cut -c1-5`" != "MINGW"; then
- echo "MythMusic requires libcdaudio."
- fi
-
- paranoia="no"
- if has_library libcdda_paranoia && has_library libcdda_interface ; then
- if has_header cdda_paranoia.h ; then
- paranoia="yes"
+ if test "$cdio" != "no" ; then
+ cdio="no"
+ if has_header cdio/cdio.h && has_library libcdio && has_library libcdio_cdda && has_library libcdio_paranoia ; then
+ cdio="yes"
fi
- if has_header cdda/cdda_paranoia.h ; then
- paranoia="yes"
- fi
- fi
-
- if test "$paranoia" = "no" -a "$targetos" != "Darwin" -a \
- "`echo ${targetos} | cut -c1-5`" != "MINGW"; then
- echo "MythMusic requires CDDA Paranoia."
fi
mp3lame="no"
@@ -514,13 +498,7 @@ if test "$music" != "no" ; then
echo "MythMusic requires taglib 1.6 or later."
fi
- if test "$targetos" != "Darwin" -a "`echo ${targetos} | cut -c1-5`" != "MINGW" ; then
- if test "$cdaudio" = "no" -o "$paranoia" = "no" ; then
- cdlibs="no"
- fi
- fi
-
- if test "$vorbis" = "no" -o "$flac" = "no" -o "$mp3lame" = "no" -o "$taglib" = "no" -o "$cdlibs" = "no" ; then
+ if test "$vorbis" = "no" -o "$flac" = "no" -o "$mp3lame" = "no" -o "$taglib" = "no" ; then
echo "Disabling MythMusic due to missing dependencies."
music="no"
fi
@@ -753,18 +731,13 @@ if test "$music" = "yes" ; then
echo "#undef HAVE_STDINT_H" >> ./mythmusic/mythmusic/config.h
fi
- if test "$cdaudio" = "yes" ; then
- echo "#define HAVE_CDAUDIO 1" >> ./mythmusic/mythmusic/config.h
- echo "CONFIG += cdaudio" >> ./mythmusic/mythmusic/config.pro
- else
- echo "#undef HAVE_CDAUDIO" >> ./mythmusic/mythmusic/config.h
- fi
-
- if test "$paranoia" = "yes" ; then
- echo "#define HAVE_PARANOIA 1" >> ./mythmusic/mythmusic/config.h
- echo "CONFIG += paranoia" >> ./mythmusic/mythmusic/config.pro
+ if test "$cdio" = "yes" ; then
+ echo "#define HAVE_CDIO 1" >> ./mythmusic/mythmusic/config.h
+ echo "CONFIG += cdio" >> ./mythmusic/mythmusic/config.pro
+ echo " libcdio support will be included in MythMusic"
else
- echo "#undef HAVE_PARANOIA" >> ./mythmusic/mythmusic/config.h
+ echo "#undef HAVE_CDIO" >> ./mythmusic/mythmusic/config.h
+ echo " libcdio support will not be included in MythMusic"
fi
if enabled fftw_lib3; then
View
662 mythplugins/mythmusic/mythmusic/cddb.cpp
@@ -0,0 +1,662 @@
+#include "cddb.h"
+
+#include <cstddef>
+#include <cstdlib>
+
+#include <QFile>
+#include <QFileInfo>
+#include <QDir>
+#include <QVector>
+#include <QMap>
+
+#include <mythversion.h>
+#include <mythlogging.h>
+#include <mythcontext.h>
+#ifdef USING_HTTPCOMMS
+#error httpcomms is no longer supported
+#include <httpcomms.h>
+#else
+#include "mythdownloadmanager.h"
+#endif
+
+/*
+ * CDDB protocol docs:
+ * http://ftp.freedb.org/pub/freedb/latest/CDDBPROTO
+ * http://ftp.freedb.org/pub/freedb/misc/freedb_howto1.07.zip
+ * http://ftp.freedb.org/pub/freedb/misc/freedb_CDDB_protcoldoc.zip
+ */
+
+const int CDROM_LEADOUT_TRACK = 0xaa;
+const int CD_FRAMES_PER_SEC = 75;
+const int SECS_PER_MIN = 60;
+
+static const char URL[] = "http://freedb.freedb.org/~cddb/cddb.cgi?cmd=";
+//static const char URL[] = "http://freedb.musicbrainz.org/~cddb/cddb.cgi?cmd=";
+static const QString& helloID();
+
+namespace {
+/*
+ * Local cddb database
+ */
+struct Dbase
+{
+ static bool Search(Cddb::Matches&, Cddb::discid_t);
+ static bool Search(Cddb::Album&, const QString& genre, Cddb::discid_t);
+ static bool Write(const Cddb::Album&);
+
+ static void New(Cddb::discid_t, const Cddb::Toc&);
+ static void MakeAlias(const Cddb::Album&, const Cddb::discid_t);
+
+private:
+ static bool CacheGet(Cddb::Matches&, Cddb::discid_t);
+ static bool CacheGet(Cddb::Album&, const QString& genre, Cddb::discid_t);
+ static void CachePut(const Cddb::Album&);
+
+ // DiscID to album info cache
+ typedef QMap< Cddb::discid_t, Cddb::Album > cache_t;
+ static cache_t s_cache;
+
+ static const QString& GetDB();
+};
+QMap< Cddb::discid_t, Cddb::Album > Dbase::s_cache;
+}
+
+
+/*
+ * Inline helpers
+ */
+// min/sec/frame to/from lsn
+static inline unsigned long msf2lsn(const Cddb::Msf& msf)
+{
+ return ((unsigned long)msf.min * SECS_PER_MIN + msf.sec) *
+ CD_FRAMES_PER_SEC + msf.frame;
+}
+static inline Cddb::Msf lsn2msf(unsigned long lsn)
+{
+ Cddb::Msf msf;
+
+ std::div_t d = std::div(lsn, CD_FRAMES_PER_SEC);
+ msf.frame = d.rem;
+ d = std::div(d.quot, SECS_PER_MIN);
+ msf.sec = d.rem;
+ msf.min = d.quot;
+ return msf;
+}
+
+// min/sec/frame to/from seconds
+static inline int msf2sec(const Cddb::Msf& msf)
+{
+ return msf.min * SECS_PER_MIN + msf.sec;
+}
+static inline Cddb::Msf sec2msf(unsigned sec)
+{
+ Cddb::Msf msf;
+
+ std::div_t d = std::div(sec, SECS_PER_MIN);
+ msf.sec = d.rem;
+ msf.min = d.quot;
+ msf.frame = 0;
+ return msf;
+}
+
+
+/**
+ * CDDB query
+ */
+// static
+bool Cddb::Query(Matches& res, const Toc& toc)
+{
+ if (toc.size() < 2)
+ return false;
+ const unsigned totalTracks = toc.size() - 1;
+
+ unsigned secs = 0;
+ const discid_t discID = Discid(secs, toc.data(), totalTracks);
+
+ // Is it cached?
+ if (Dbase::Search(res, discID))
+ return res.matches.size() > 0;
+
+ // Construct query
+ // cddb query discid ntrks off1 off2 ... nsecs
+ QString URL2 = URL +
+ QString("cddb+query+%1+%2+").arg(discID,0,16).arg(totalTracks);
+ for (unsigned t = 0; t < totalTracks; ++t)
+ URL2 += QString("%1+").arg(msf2lsn(toc[t]));
+ URL2 += QString::number(secs);
+
+ // Send the request
+ URL2 += "&hello=" + helloID() + "&proto=5";
+ LOG(VB_MEDIA, LOG_INFO, "CDDB lookup: " + URL2);
+#ifdef USING_HTTPCOMMS
+ QString cddb = HttpComms::getHttp(URL2);
+#else
+ QString cddb;
+ { QByteArray data;
+ if (!GetMythDownloadManager()->download(URL2, &data))
+ return false;
+ cddb = data; }
+#endif
+
+ // Check returned status
+ const uint stat = cddb.left(3).toUInt(); // Extract 3 digit status:
+ cddb = cddb.mid(4);
+ switch (stat)
+ {
+ case 200: // Unique match
+ LOG(VB_MEDIA, LOG_INFO, "CDDB match: " + cddb.trimmed());
+ // e.g. "200 rock 960b5e0c Nichole Nordeman / Woven & Spun"
+ res.discID = discID;
+ res.isExact = true;
+ res.matches.push_back(Match(
+ cddb.section(' ', 0, 0), // genre
+ cddb.section(' ', 1, 1).toUInt(0,16), // discID
+ cddb.section(' ', 2).section(" / ", 0, 0), // artist
+ cddb.section(' ', 2).section(" / ", 1) // title
+ ));
+ break;
+
+ case 202: // No match for disc ID...
+ LOG(VB_MEDIA, LOG_INFO, "CDDB no match");
+ Dbase::New(discID, toc); // Stop future lookups
+ return false;
+
+ case 210: // Multiple exact matches
+ case 211: // Multiple inexact matches
+ // 210 Found exact matches, list follows (until terminating `.')
+ // 211 Found inexact matches, list follows (until terminating `.')
+ res.discID = discID;
+ res.isExact = 210 == stat;
+
+ // Remove status line
+ cddb = cddb.section('\n', 1);
+
+ // Append all matches
+ while (!cddb.isEmpty() && !cddb.startsWith("."))
+ {
+ LOG(VB_MEDIA, LOG_INFO, QString("CDDB %1 match: %2").
+ arg(210 == stat ? "exact" : "inexact").
+ arg(cddb.section('\n',0,0)));
+ res.matches.push_back(Match(
+ cddb.section(' ', 0, 0), // genre
+ cddb.section(' ', 1, 1).toUInt(0,16), // discID
+ cddb.section(' ', 2).section(" / ", 0, 0), // artist
+ cddb.section(' ', 2).section(" / ", 1) // title
+ ));
+ cddb = cddb.section('\n', 1);
+ }
+ if (res.matches.size() <= 0)
+ Dbase::New(discID, toc); // Stop future lookups
+ break;
+
+ default:
+ // TODO try a (telnet 8880) CDDB lookup
+ LOG(VB_GENERAL, LOG_INFO, QString("CDDB query error: %1").arg(stat) +
+ cddb.trimmed());
+ return false;
+ }
+ return true;
+}
+
+/**
+ * CDDB read
+ */
+// static
+bool Cddb::Read(Album& album, const QString& genre, discid_t discID)
+{
+ // Is it cached?
+ if (Dbase::Search(album, genre.toLower(), discID))
+ return true;
+
+ // Lookup the details...
+ QString URL2 = URL + QString("cddb+read+") + genre.toLower() +
+ QString("+%1").arg(discID,0,16) + "&hello=" + helloID() + "&proto=5";
+ LOG(VB_MEDIA, LOG_INFO, "CDDB read: " + URL2);
+#ifdef USING_HTTPCOMMS
+ QString cddb = HttpComms::getHttp(URL2);
+#else
+ QString cddb;
+ { QByteArray data;
+ if (!GetMythDownloadManager()->download(URL2, &data))
+ return false;
+ cddb = data; }
+#endif
+
+ // Check returned status
+ const uint stat = cddb.left(3).toUInt(); // Get 3 digit status:
+ cddb = cddb.mid(4);
+ switch (stat)
+ {
+ case 210: // OK, CDDB database entry follows (until terminating marker)
+ LOG(VB_MEDIA, LOG_INFO, "CDDB read returned: " + cddb.section(' ',0,3));
+ LOG(VB_MEDIA, LOG_DEBUG, cddb.section('\n',1).trimmed());
+ break;
+ default:
+ LOG(VB_GENERAL, LOG_INFO, QString("CDDB read error: %1").arg(stat) +
+ cddb.trimmed());
+ return false;
+ }
+
+ album = cddb;
+ album.genre = cddb.section(' ', 0, 0);
+ album.discID = discID;
+
+ // Success - add to cache
+ Dbase::Write(album);
+
+ return true;
+}
+
+/**
+ * CDDB write
+ */
+// static
+bool Cddb::Write(const Album& album, bool /*bLocalOnly =true*/)
+{
+// TODO send to cddb if !bLocalOnly
+ Dbase::Write(album);
+ return true;
+}
+
+static inline int cddb_sum(int i)
+{
+ int total = 0;
+ while (i > 0)
+ {
+ const std::div_t d = std::div(i,10);
+ total += d.rem;
+ i = d.quot;
+ }
+ return total;
+}
+
+/**
+ * discID calculation. See appendix A of freedb_howto1.07.zip
+ */
+// static
+Cddb::discid_t Cddb::Discid(unsigned& secs, const Msf v[], unsigned tracks)
+{
+ int checkSum = 0;
+ for (unsigned t = 0; t < tracks; ++t)
+ checkSum += cddb_sum(v[t].min * SECS_PER_MIN + v[t].sec);
+
+ secs = v[tracks].min * SECS_PER_MIN + v[tracks].sec -
+ (v[0].min * SECS_PER_MIN + v[0].sec);
+
+ const discid_t discID = ((discid_t)(checkSum % 255) << 24) |
+ ((discid_t)secs << 8) | tracks;
+ return discID;
+}
+
+/**
+ * Create a local alias for a matched discID
+ */
+// static
+void Cddb::Alias(const Album& album, discid_t discID)
+{
+ Dbase::MakeAlias(album, discID);
+}
+
+/**
+ * Parse CDDB text
+ */
+Cddb::Album& Cddb::Album::operator =(const QString& rhs)
+{
+ genre.clear();
+ discID = 0;
+ artist.clear();
+ title.clear();
+ year = 0;
+ submitter = "MythTV " MYTH_BINARY_VERSION;
+ rev = 1;
+ isCompilation = false;
+ tracks.empty();
+ toc.empty();
+ extd.clear();
+ ext.empty();
+
+ enum { kNorm, kToc } eState = kNorm;
+
+ QString cddb = rhs;
+ while (!cddb.isEmpty())
+ {
+ // Lines should be of the form "FIELD=value\r\n"
+ QString line = cddb.section(QRegExp("[\r\n]"), 0, 0);
+
+ if (line.startsWith("# Track frame offsets:"))
+ {
+ eState = kToc;
+ }
+ else if (line.startsWith("# Disc length:"))
+ {
+ QString s = line.section(QRegExp("[ \t]"), 3, 3);
+ unsigned secs = s.toULong();
+ if (toc.size())
+ secs -= msf2sec(toc[0]);
+ toc.push_back(sec2msf(secs));
+ eState = kNorm;
+ }
+ else if (line.startsWith("# Revision:"))
+ {
+ QString s = line.section(QRegExp("[ \t]"), 2, 2);
+ bool bValid = false;
+ int v = s.toInt(&bValid);
+ if (bValid)
+ rev = v;
+ }
+ else if (line.startsWith("# Submitted via:"))
+ {
+ submitter = line.section(QRegExp("[ \t]"), 3, 3);
+ }
+ else if (line.startsWith("#"))
+ {
+ if (kToc == eState)
+ {
+ bool bValid = false;
+ QString s = line.section(QRegExp("[ \t]"), 1).trimmed();
+ unsigned long lsn = s.toUInt(&bValid);
+ if (bValid)
+ toc.push_back(lsn2msf(lsn));
+ else
+ eState = kNorm;
+ }
+ }
+ else
+ {
+ QString value = line.section('=', 1, 1);
+ QString art;
+
+ if (value.contains(" / "))
+ {
+ art = value.section(" / ", 0, 0); // Artist in *TITLE
+ value = value.section(" / ", 1, 1);
+ }
+
+ if (line.startsWith("DISCID="))
+ {
+ bool isValid = false;
+ ulong discID = value.toULong(&isValid,16);
+ if (isValid)
+ discID = discID;
+ }
+ else if (line.startsWith("DTITLE="))
+ {
+ // Albums (and maybe artists?) can wrap over multiple lines:
+ artist += art;
+ title += value;
+ }
+ else if (line.startsWith("DYEAR="))
+ {
+ bool isValid = false;
+ int year = value.toInt(&isValid);
+ if (isValid)
+ year = year;
+ }
+ else if (line.startsWith("DGENRE="))
+ {
+ if (!value.isEmpty())
+ genre = value;
+ }
+ else if (line.startsWith("TTITLE"))
+ {
+ int trk = line.remove("TTITLE").section('=', 0, 0).toInt();
+ if (trk >= 0 && trk < CDROM_LEADOUT_TRACK)
+ {
+ if (trk >= tracks.size())
+ tracks.resize(trk + 1);
+
+ Cddb::Track& track = tracks[trk];
+
+ // Titles can wrap over multiple lines, so we load+store:
+ track.title += value;
+ track.artist += art;
+
+ if (art.length())
+ isCompilation = true;
+ }
+ }
+ else if (line.startsWith("EXTD="))
+ {
+ if (!value.isEmpty())
+ extd = value;
+ }
+ else if (line.startsWith("EXTT"))
+ {
+ int trk = line.remove("EXTT").section('=', 0, 0).toInt();
+ if (trk >= 0 && trk < CDROM_LEADOUT_TRACK)
+ {
+ if (trk >= ext.size())
+ ext.resize(trk + 1);
+
+ ext[trk] = value;
+ }
+ }
+ }
+
+ // Next response line:
+ cddb = cddb.section('\n', 1);
+ }
+ return *this;
+}
+
+/**
+ * Convert album to CDDB text form
+ */
+Cddb::Album::operator QString() const
+{
+ QString ret = "# xmcd\n"
+ "#\n"
+ "# Track frame offsets:\n";
+ for (int i = 1; i < toc.size(); ++i)
+ ret += "# " + QString::number(msf2lsn(toc[i - 1])) + '\n';
+ ret += "#\n";
+ ret += "# Disc length: " +
+ QString::number( msf2sec(toc.last()) + msf2sec(toc[0]) ) +
+ " seconds\n";
+ ret += "#\n";
+ ret += "# Revision: " + QString::number(rev) + '\n';
+ ret += "#\n";
+ ret += "# Submitted via: " + (!submitter.isEmpty() ? submitter :
+ "MythTV " MYTH_BINARY_VERSION) + '\n';
+ ret += "#\n";
+ ret += "DISCID=" + QString::number(discID,16) + '\n';
+ ret += "DTITLE=" + artist.toUtf8() + " / " + title + '\n';
+ ret += "DYEAR=" + (year ? QString::number(year) : "")+ '\n';
+ ret += "DGENRE=" + genre.toLower().toUtf8() + '\n';
+ for (int t = 0; t < tracks.size(); ++t)
+ ret += "TTITLE" + QString::number(t) + "=" +
+ tracks[t].title.toUtf8() + '\n';
+ ret += "EXTD=" + extd.toUtf8() + '\n';
+ for (int t = 0; t < tracks.size(); ++t)
+ ret += "EXTT" + QString::number(t) + "=" + ext[t].toUtf8() + '\n';
+ ret += "PLAYORDER=\n";
+
+ return ret;
+}
+
+
+/**********************************************************
+ * Local cddb database ops
+ **********************************************************/
+
+// search local database for discID
+bool Dbase::Search(Cddb::Matches& res, const Cddb::discid_t discID)
+{
+ res.matches.empty();
+
+ if (CacheGet(res, discID))
+ return true;
+
+ QFileInfoList list = QDir(GetDB()).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (QFileInfoList::const_iterator it = list.begin(); it != list.end(); ++it)
+ {
+ QString genre = it->baseName();
+
+ QFileInfoList ids = QDir(it->canonicalFilePath()).entryInfoList(QDir::Files);
+ for (QFileInfoList::const_iterator it2 = ids.begin(); it2 != ids.end(); ++it2)
+ {
+ if (it2->baseName().toUInt(0,16) == discID)
+ {
+ QFile file(it2->canonicalFilePath());
+ if (file.open(QIODevice::ReadOnly | QIODevice::Text))
+ {
+ Cddb::Album a = QTextStream(&file).readAll();
+ a.genre = genre;
+ a.discID = discID;
+ LOG(VB_MEDIA, LOG_INFO, QString("LocalCDDB found %1 in ").
+ arg(discID,0,16) + genre + " : " +
+ a.artist + " / " + a.title);
+
+ CachePut(a);
+ res.matches.push_back(Cddb::Match(genre,discID,a.artist,a.title));
+ }
+
+ }
+ }
+ }
+ return res.matches.size() > 0;
+}
+
+// search local database for genre/discID
+bool Dbase::Search(Cddb::Album& a, const QString& genre, const Cddb::discid_t discID)
+{
+ if (CacheGet(a, genre, discID))
+ return true;
+
+ QFile file(GetDB() + '/' + genre.toLower() + '/' + QString::number(discID,16));
+ if (file.open(QIODevice::ReadOnly | QIODevice::Text))
+ {
+ a = QTextStream(&file).readAll();
+ a.genre = genre.toLower();
+ a.discID = discID;
+ LOG(VB_MEDIA, LOG_INFO, QString("LocalCDDB matched %1 ").arg(discID,0,16) +
+ genre + " to " + a.artist + " / " + a.title);
+
+ CachePut(a);
+
+ return true;
+ }
+ return false;
+}
+
+// Create CDDB file
+bool Dbase::Write(const Cddb::Album& album)
+{
+ CachePut(album);
+
+ const QString genre = !album.genre.isEmpty() ?
+ album.genre.toLower().toUtf8() : "misc";
+
+ LOG(VB_MEDIA, LOG_INFO, "WriteDB " + genre +
+ QString(" %1 ").arg(album.discID,0,16) +
+ album.artist + " / " + album.title);
+
+ if (QDir(GetDB()).mkpath(genre))
+ {
+ QFile file(GetDB() + '/' + genre + '/' +
+ QString::number(album.discID,16));
+ if (file.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ QTextStream(&file) << album;
+ return true;
+ }
+ else
+ LOG(VB_GENERAL, LOG_ERR, "Cddb can't write " + file.fileName());
+ }
+ else
+ LOG(VB_GENERAL, LOG_ERR, "Cddb can't mkpath " + GetDB() + '/' + genre);
+ return false;
+}
+
+// Create a local alias for a matched discID
+// static
+void Dbase::MakeAlias(const Cddb::Album& album, const Cddb::discid_t discID)
+{
+ s_cache[ discID] = album;
+}
+
+// Create a new entry for a discID
+// static
+void Dbase::New(const Cddb::discid_t discID, const Cddb::Toc& toc)
+{
+ (s_cache[ discID] = Cddb::Album(discID)).toc = toc;
+}
+
+// static
+void Dbase::CachePut(const Cddb::Album& album)
+{
+ s_cache[ album.discID] = album;
+}
+
+// static
+bool Dbase::CacheGet(Cddb::Matches& res, const Cddb::discid_t discID)
+{
+ bool ret = false;
+ for (cache_t::const_iterator it = s_cache.find(discID); it != s_cache.end(); ++it)
+ {
+ // NB it->discID may not equal discID if it's an alias
+ if (it->discID)
+ {
+ ret = true;
+ res.discID = discID;
+ LOG(VB_MEDIA, LOG_DEBUG, QString("Cddb CacheGet found %1 ").
+ arg(discID,0,16) + it->genre + " " + it->artist + " / " + it->title);
+
+ // If it's marker for 'no match' then genre is empty
+ if (!it->genre.isEmpty())
+ res.matches.push_back(Cddb::Match(*it));
+ }
+ }
+ return ret;
+}
+
+// static
+bool Dbase::CacheGet(Cddb::Album& album, const QString& genre, const Cddb::discid_t discID)
+{
+ const Cddb::Album& a = s_cache[ discID];
+ if (a.discID && a.genre == genre)
+ {
+ album = a;
+ return true;
+ }
+ return false;
+}
+
+// Local database path
+// static
+const QString& Dbase::GetDB()
+{
+ static QString s_path;
+ if (s_path.isEmpty())
+ {
+ s_path = getenv("HOME");
+#ifdef WIN32
+ if (s_path.isEmpty())
+ {
+ s_path = getenv("HOMEDRIVE");
+ s_path += getenv("HOMEPATH");
+ }
+#endif
+ if (s_path.isEmpty())
+ s_path = ".";
+ if (!s_path.endsWith('/'))
+ s_path += '/';
+ s_path += ".cddb/";
+ }
+ return s_path;
+}
+
+// CDDB hello string
+static const QString& helloID()
+{
+ static QString helloID;
+ if (helloID.isEmpty())
+ {
+ helloID = getenv("USER");
+ if (helloID.isEmpty())
+ helloID = "anon";
+ helloID += QString("+%1+MythTV+%2+")
+ .arg(gCoreContext->GetHostName()).arg(MYTH_BINARY_VERSION);
+ }
+ return helloID;
+}
View
97 mythplugins/mythmusic/mythmusic/cddb.h
@@ -0,0 +1,97 @@
+#ifndef CDDB_H_
+#define CDDB_H_
+
+#include <QString>
+#include <QStringList>
+#include <QVector>
+
+/*
+ * CDDB lookup
+ */
+struct Cddb
+{
+ typedef unsigned long discid_t;
+ struct Album;
+
+ // A CDDB query match
+ struct Match
+ {
+ QString genre;
+ discid_t discID;
+ QString artist;
+ QString title;
+
+ Match() : discID(0) {}
+ Match(const char *g, discid_t d, const char *a, const char *t) :
+ genre(g), discID(d), artist(a), title(t)
+ {}
+ Match(const QString &g, discid_t d, const QString &a, const QString &t) :
+ genre(g), discID(d), artist(a), title(t)
+ {}
+ Match(const Album& a) : genre(a.genre), discID(a.discID),
+ artist(a.artist), title(a.title)
+ {}
+ };
+
+ // CDDB query results
+ struct Matches
+ {
+ discid_t discID; // discID of query
+ bool isExact;
+ typedef QVector< Match > match_t;
+ match_t matches;
+
+ Matches() : discID(0), isExact(false) {}
+ };
+
+ struct Msf
+ {
+ int min, sec, frame;
+ Msf(int m = 0, int s = 0, int f = 0) : min(m), sec(s), frame(f) {}
+ };
+ typedef QVector< Msf > Toc;
+
+ struct Track
+ {
+ QString artist;
+ QString title;
+ };
+
+ // CDDB detail result
+ struct Album
+ {
+ QString genre;
+ discid_t discID;
+ QString artist;
+ QString title;
+ int year;
+ QString submitter;
+ int rev;
+ bool isCompilation;
+ typedef QVector< Track > track_t;
+ track_t tracks;
+ QString extd;
+ typedef QVector< QString > ext_t;
+ ext_t ext;
+ Toc toc;
+
+ Album(discid_t d = 0, const char* g = 0) :
+ genre(g), discID(d), year(0), rev(1), isCompilation(false) {}
+
+ Album(const QString& s) { *this = s; }
+
+ Album& operator = (const QString&);
+ operator QString () const;
+ };
+
+ // Primary cddb access
+ static bool Query(Matches&, const Toc&);
+ static bool Read(Album&, const QString& genre, discid_t);
+ static bool Write(const Album&, bool bLocalOnly = true);
+
+ // Support
+ static discid_t Discid(unsigned& secs, const Msf [], unsigned tracks);
+ static void Alias(const Album&, discid_t);
+};
+
+#endif //ndef CDDB_H_
View
474 mythplugins/mythmusic/mythmusic/cddecoder-darwin.cpp
@@ -1,474 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <iostream>
-#include <string>
-using namespace std;
-
-#include <QObject>
-#include <QIODevice>
-#include <QDir>
-#include <QFile>
-#include <QDomDocument>
-
-#include <mythconfig.h> // For CONFIG_DARWIN
-#include "cddecoder.h"
-#include "constants.h"
-#include <audiooutput.h>
-#include "metadata.h"
-
-#include <mythcontext.h>
-#include <mythmediamonitor.h>
-#include <mythversion.h>
-#include <httpcomms.h>
-#include <util.h>
-
-CdDecoder::CdDecoder(const QString &file, DecoderFactory *d, QIODevice *i,
- AudioOutput *o) :
- Decoder(d, i, o),
- inited(false), user_stop(false),
- devicename(""),
- m_diskID(0), m_firstTrack(0),
- m_lastTrack(0), m_leadout(0),
- m_lengthInSecs(0.0),
- stat(0), output_buf(NULL),
- output_at(0), bks(0),
- bksFrames(0), decodeBytes(0),
- finish(false),
- freq(0), bitrate(0),
- chan(0),
- totalTime(0.0), seekTime(-1.0),
- settracknum(-1), tracknum(0),
- start(0), end(0),
- curpos(0)
-{
- setObjectName("CdDecoder");
- setFilename(file);
-}
-
-CdDecoder::~CdDecoder(void)
-{
- if (inited)
- deinit();
-}
-
-/*
- * Helper function for CD checksum generation
- */
-static inline int addDecimalDigits(int i)
-{
- int total = 0;
- while (i > 0)
- total += i % 10, i /= 10;
- return total;
-}
-
-/*
- * Work out file containing a given track
- */
-QString fileForTrack(QString path, uint track)
-{
- QDir disc(path);
- QString filename;
-
- disc.setNameFilters(QStringList(QString("%1*.aiff").arg(track)));
- filename = disc.entryList()[0]; // Fortunately, this seems to sort nicely
-
- if (filename.isEmpty())
- filename = QString("%1.aiff").arg(track);
-
- return filename;
-}
-
-/**
- * Load XML file that OS X generates for us for Audio CDs, calculate checksum
- */
-bool CdDecoder::initialize()
-{
- QFile TOCfile(devicename + "/.TOC.plist");
- QDomDocument TOC;
- uint trk;
-
- m_tracks.clear();
- m_firstTrack = m_lastTrack = m_leadout = 0;
-
- if (!TOCfile.open(QIODevice::ReadOnly))
- {
- LOG(VB_GENERAL, LOG_ERR,
- "Unable to open Audio CD TOC file: " + TOCfile.fileName());
- return false;
- }
-
- if (!TOC.setContent(&TOCfile))
- {
- LOG(VB_GENERAL, LOG_ERR,
- "Unable to parse Audio CD TOC file: " + TOCfile.fileName());
- TOCfile.close();
- return false;
- }
-
-
- // HACK. This is a really bad example of XML parsing. No type checking,
- // it doesn't deal with comments. It only works because the TOC.plist
- // file is generated (i.e. a fixed format)
-
- QDomElement root = TOC.documentElement();
- QDomNode node = root.firstChild() // <dict>
- .namedItem("array") // <key>Sessions</key><array>
- .firstChild() // <dict>
- .firstChild();
- while (!node.isNull())
- {
- if (node.nodeName() == "key")
- {
- QDomText t = node.firstChild().toText(); // <key> t </key>
- node = node.nextSibling(); // <integer>i</integer>
- int i = node.firstChild().toText()
- .data().toInt();
-
- if (t.data() == "First Track")
- m_firstTrack = i;
- if (t.data() == "Last Track")
- m_lastTrack = i;
- if (t.data() == "Leadout Block")
- m_leadout = i;
- }
- // <key>Track Array</key>
- if (node.nodeName() == "array") // <array>
- {
- node = node.firstChild(); // First track's <dict>
-
- for (trk = m_firstTrack; trk <= m_lastTrack; ++trk)
- {
- m_tracks.push_back(node.lastChild().firstChild()
- .toText().data().toInt());
-
- node = node.nextSibling(); // Look at next <dict> in <array>
- }
- }
-
- node = node.nextSibling();
- }
- TOCfile.close();
-
-
- // Calculate some stuff for later CDDB/FreeDB lookup
-
- m_lengthInSecs = (m_leadout - m_tracks[0]) / 75.0;
-
- int checkSum = 0;
- for (trk = 0; trk <= m_lastTrack - m_firstTrack; ++trk)
- checkSum += addDecimalDigits(m_tracks[trk] / 75);
-
- uint totalTracks = 1 + m_lastTrack - m_firstTrack;
- m_diskID = ((checkSum % 255) << 24) | (int)m_lengthInSecs << 8
- | totalTracks;
-
- QString hexID;
- hexID.setNum(m_diskID, 16);
- LOG(VB_MEDIA, LOG_INFO, QString("CD %1, ID=%2").arg(devicename).arg(hexID));
-
-
- // First erase any existing metadata:
- for (trk = 0; trk < m_mData.size(); ++trk)
- delete m_mData[trk];
- m_mData.clear();
-
-
- // Generate empty MetaData records.
- // We fill in the other details later (from CDDB if possible)
-
- m_tracks.push_back(m_leadout); // This simplifies the loop
-
- for (trk = 1; trk <= totalTracks; ++trk)
- {
- QString file = fileForTrack(devicename, trk);
- uint len = 1000 * (m_tracks[trk] - m_tracks[trk-1]) / 75;
-
- m_mData.push_back(new Metadata(file, NULL, NULL, NULL,
- NULL, NULL, 0, trk, len));
- }
-
-
- // Try to fill in this MetaData from CDDB lookup:
- lookupCDDB(hexID, totalTracks);
-
-
- inited = true;
- return true;
-}
-
-/**
- * Lookup FreeDB/CDDB, populate m_mData with results
- */
-void CdDecoder::lookupCDDB(const QString &hexID, uint totalTracks)
-{
- QString helloID = getenv("USER");
- QString queryID = "cddb+query+";
- uint trk;
-
- if (helloID.isEmpty())
- helloID = "anon";
- helloID += QString("+%1+MythTV+%2+")
- .arg(gCoreContext->GetHostName()).arg(MYTH_BINARY_VERSION);
-
- queryID += QString("%1+%2+").arg(hexID).arg(totalTracks);
- for (trk = 0; trk < totalTracks; ++trk)
- queryID += QString::number(m_tracks[trk]) + '+';
- queryID += QString::number(m_lengthInSecs);
-
-
- // First, try HTTP:
- QString URL = "http://freedb.freedb.org/~cddb/cddb.cgi?cmd=";
- QString URL2 = URL + queryID + "&hello=" + helloID + "&proto=5";
- QString cddb = HttpComms::getHttp(URL2);
-
-#if 0
- LOG(VB_MEDIA, LOG_INFO, "CDDB lookup: " + URL2);
- LOG(VB_MEDIA, LOG_INFO, "...returned: " + cddb);
-#endif
- //
- // e.g. "200 rock 960b5e0c Nichole Nordeman / Woven & Spun"
-
- // Extract/remove 3 digit status:
- uint stat = cddb.left(3).toUInt();
- cddb = cddb.mid(4);
-
- // We should check for errors here, and possibly do a CDDB lookup
- // (telnet 8880) if it failed, but Nigel is feeling lazy.
-
- if (stat == 210 || stat == 211) // Multiple matches
- {
- // e.g. 210 Found exact matches, list follows (until terminating `.')
- // folk 9c09590b Michael W Smith / Stand
- // misc 9c09590b Michael W. Smith / Stand
- // rock 9c09590b Michael W. Smith / Stand
- // classical 9c09590b Michael W. Smith / Stand
- // .
- // TODO
- // Parse disks, put up dialog box, select disk, prune cddb to selected
- LOG(VB_MEDIA, LOG_INFO,
- "Multiple CDDB matches. Please implement this code");
-
- // For now, we just choose the first match.
- //int EOL = cddb.indexOf('\n');
- cddb.remove(0, cddb.indexOf('\n'));
- LOG(VB_MEDIA, LOG_INFO, "String now: " + cddb);
- stat = 200;
- }
-
- if (stat == 200) // One unique match
- {
- QString album;
- QString artist;
- bool compn = false;
- QString genre = cddb.section(' ', 0, 0);
- int year = 0;
-
- // Now we can lookup all its details:
- URL2 = URL + "cddb+read+" + genre + "+"
- + hexID + "&hello=" + helloID + "&proto=5";
- cddb = HttpComms::getHttp(URL2);
-#if 0
- LOG(VB_MEDIA, LOG_INFO, "CDDB detail: " + URL2);
- LOG(VB_MEDIA, LOG_INFO, "...returned: " + cddb);
-#endif
-
- // Successful lookup.
- // Clear current titles (filenames), because we append to them
- for (trk = 0; trk < totalTracks; ++trk)
- m_mData[trk]->setTitle("");
-
- // Parse returned data:
-
- cddb.replace(QRegExp(".*#"), ""); // Remove comment block
- while (cddb.length())
- {
- // Lines should be of the form "FIELD=value\r\n"
-
- QString art;
- QString line = cddb.section(QRegExp("(\r|\n)+"), 0, 0);
- QString value = line.section('=', 1, 1);
-
- if (value.contains(" / "))
- {
- art = value.section(" / ", 0, 0); // Artist in *TITLE
- value = value.section(" / ", 1, 1);
- }
-
- if (line.startsWith("DGENRE="))
- genre = value;
- else if (line.startsWith("DYEAR="))
- year = value.toInt();
- else if (line.startsWith("DTITLE="))
- {
- // Albums (and maybe artists?) can wrap over multiple lines:
- artist += art;
- album += value;
- }
- else if (line.startsWith("TTITLE"))
- {
- trk = line.remove("TTITLE").remove(QRegExp("=.*")).toUInt();
-
- if (trk < totalTracks)
- {
- Metadata *m = m_mData[trk];
-
- // Titles can wrap over multiple lines, so we load+store:
- m->setTitle(m->Title() + value);
-
- if (art.length())
- {
- compn = true; // Probably a compilation
-
- m->setArtist(M_QSTRING_UNICODE(art.toUtf8().constData()));
- }
- }
- else
- LOG(VB_GENERAL, LOG_INFO,
- QString("CDDB returned %1 on a %2 track disk!")
- .arg(trk+1).arg(totalTracks));
- }
-
- // Get next THINGY=value line:
- cddb = cddb.section('\n', 1, 0xffffffff);
- }
-
- for (trk = 0; trk < totalTracks; ++trk)
- {
- Metadata *m = m_mData[trk];
-
- if (compn)
- m->setCompilation(true);
-
- m->setGenre(M_QSTRING_UNICODE(genre.toUtf8().constData()));
-
- if (year)
- m->setYear(year);
-
- if (album.length())
- m->setAlbum(M_QSTRING_UNICODE(album.toUtf8().constData()));
-
- if (artist.length())
- if (compn)
- m->setCompilationArtist(M_QSTRING_UNICODE(artist.toUtf8().constData()));
- else
- m->setArtist(M_QSTRING_UNICODE(artist.toUtf8().constData()));
- }
- }
-}
-
-double CdDecoder::lengthInSeconds()
-{
- return m_lengthInSecs;
-}
-
-void CdDecoder::seek(double pos)
-{
- (void)pos;
-}
-
-void CdDecoder::stop()
-{
-}
-
-void CdDecoder::run()
-{
-}
-
-void CdDecoder::deinit()
-{
- // Free any stored Metadata
- for (unsigned int i = 0; i < m_mData.size(); ++i)
- delete m_mData[i];
- m_mData.clear();
-
- m_tracks.clear();
-
- inited = false;
-}
-
-int CdDecoder::getNumTracks(void)
-{
- if (!inited && !initialize())
- return 0;
-
- return m_lastTrack;
-}
-
-int CdDecoder::getNumCDAudioTracks(void)
-{
- if (!inited && !initialize())
- return 0;
-
- return m_lastTrack - m_firstTrack + 1;
-}
-
-Metadata* CdDecoder::getMetadata(int track)
-{
- if (!inited && !initialize())
- return NULL;
-
- if (track < 1 || (uint)track > m_mData.size())
- {
- LOG(VB_GENERAL, LOG_ERR,
- QString("CdDecoder::getMetadata(%1) - track out of range")
- .arg(track));
- return NULL;
- }
-
- return new Metadata(*(m_mData[track - 1]));
-}
-
-Metadata *CdDecoder::getLastMetadata()
-{
- if (!inited && !initialize())
- return NULL;
-
- return new Metadata(*(m_mData[m_mData.size() - 1]));
-}
-
-Metadata *CdDecoder::getMetadata()
-{
- return NULL;
-}
-
-void CdDecoder::commitMetadata(Metadata *mdata)
-{
- (void)mdata;
-}
-
-bool CdDecoderFactory::supports(const QString &source) const
-{
- return (source.right(extension().length()).toLower() == extension());
-}
-
-const QString &CdDecoderFactory::extension() const
-{
- static QString ext(".aiff");
- return ext;
-}
-
-
-const QString &CdDecoderFactory::description() const
-{
- static QString desc(QObject::tr("OSX Audio CD mount parser"));
- return desc;
-}
-
-Decoder *CdDecoderFactory::create(const QString &file, QIODevice *input,
- AudioOutput *output, bool deletable)
-{
- if (deletable)
- return new CdDecoder(file, this, input, output);
-
- static CdDecoder *decoder = 0;
- if (! decoder) {
- decoder = new CdDecoder(file, this, input, output);
- } else {
- decoder->setInput(input);
- decoder->setFilename(file);
- decoder->setOutput(output);
- }
-
- return decoder;
-}
View
141 mythplugins/mythmusic/mythmusic/cddecoder-windows.cpp
@@ -1,141 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <iostream>
-#include <string>
-using namespace std;
-
-#include <QIODevice>
-#include <QObject>
-#include <QFile>
-#include <QDir>
-
-#include <mythconfig.h>
-#include "cddecoder.h"
-#include "constants.h"
-#include "metadata.h"
-
-#include <audiooutput.h>
-#include <mythcontext.h>
-#include <mythmediamonitor.h>
-#include <httpcomms.h>
-
-CdDecoder::CdDecoder(const QString &file, DecoderFactory *d, QIODevice *i,
- AudioOutput *o) :
- Decoder(d, i, o),
- inited(false), user_stop(false),
- devicename(""),
- stat(0), output_buf(NULL),
- output_at(0), bks(0),
- bksFrames(0), decodeBytes(0),
- finish(false),
- freq(0), bitrate(0),
- chan(0),
- totalTime(0.0), seekTime(-1.0),
- settracknum(-1), tracknum(0),
- start(0), end(0),
- curpos(0)
-{
- setObjectName("CdDecoder");
- setFilename(file);
-}
-
-CdDecoder::~CdDecoder(void)
-{
- if (inited)
- deinit();
-}
-
-bool CdDecoder::initialize()
-{
- inited = true;
- return true;
-}
-
-void CdDecoder::seek(double pos)
-{
- (void)pos;
-}
-
-void CdDecoder::stop()
-{
-}
-
-void CdDecoder::run()
-{
-}
-
-void CdDecoder::writeBlock()
-{
-}
-
-void CdDecoder::deinit()
-{
- inited = false;
-}
-
-int CdDecoder::getNumTracks(void)
-{
- return 0;
-}
-
-int CdDecoder::getNumCDAudioTracks(void)
-{
- return 0;
-}
-
-Metadata* CdDecoder::getMetadata(int track)
-{
- return new Metadata();
-}
-
-Metadata *CdDecoder::getLastMetadata()
-{
- return new Metadata();
-}
-
-Metadata *CdDecoder::getMetadata()
-{
- return new Metadata();
-}
-
-void CdDecoder::commitMetadata(Metadata *mdata)
-{
- (void)mdata;
-}
-
-bool CdDecoderFactory::supports(const QString &source) const
-{
- return (source.right(extension().length()).toLower() == extension());
-}
-
-const QString &CdDecoderFactory::extension() const
-{
- static QString ext(".cda");
- return ext;
-}
-
-
-const QString &CdDecoderFactory::description() const
-{
- static QString desc(QObject::tr("Windows CD parser"));
- return desc;
-}
-
-Decoder *CdDecoderFactory::create(const QString &file, QIODevice *input,
- AudioOutput *output, bool deletable)
-{
- if (deletable)
- return new CdDecoder(file, this, input, output);
-
- static CdDecoder *decoder = 0;
- if (! decoder) {
- decoder = new CdDecoder(file, this, input, output);
- } else {
- decoder->setInput(input);
- decoder->setFilename(file);
- decoder->setOutput(output);
- }
-
- return decoder;
-}
View
926 mythplugins/mythmusic/mythmusic/cddecoder.cpp
@@ -1,237 +1,390 @@
+#define DO_NOT_WANT_PARANOIA_COMPATIBILITY
+#include "cddecoder.h"
+
// C
-#include <cstdio>
#include <cstdlib>
+#include <cstring>
-// C++
-#include <iostream>
-#include <string>
-using namespace std;
+#include <unistd.h>
// Qt
#include <QIODevice>
-#include <QObject>
#include <QFile>
+#include <QObject>
+#include <QString>
+
+// libcdio
+#include <cdio/cdda.h>
+#include <cdio/logging.h>
// MythTV
#include <audiooutput.h>
#include <mythcontext.h>
-#include <mythmediamonitor.h>
-#include <util.h>
extern "C" {
#include <libavcodec/avcodec.h>
}
-
// MythMusic
-#include "cddecoder.h"
#include "constants.h"
#include "metadata.h"
-#include "mythlogging.h"
+#include "cddb.h"
+
+#define CDEXT ".cda"
+const unsigned kSamplesPerSec = 44100;
+
+// Handle cdio log output
+static void logger(cdio_log_level_t level, const char message[])
+{
+ switch (level)
+ {
+ case CDIO_LOG_DEBUG:
+ break;
+ case CDIO_LOG_INFO:
+ LOG(VB_MEDIA, LOG_DEBUG, QString("INFO cdio: %1").arg(message));
+ break;
+ case CDIO_LOG_WARN:
+ LOG(VB_MEDIA, LOG_DEBUG, QString("WARN cdio: %1").arg(message));
+ break;
+ case CDIO_LOG_ERROR:
+ case CDIO_LOG_ASSERT:
+ LOG(VB_GENERAL, LOG_ERR, QString("ERROR cdio: %1").arg(message));
+ break;
+ }
+}
+
+// Open a cdio device
+static CdIo_t * openCdio(const QString& name)
+{
+ // Setup log handler
+ static int s_logging;
+ if (!s_logging)
+ {
+ s_logging = 1;
+ cdio_log_set_handler(&logger);
+ }
+
+ CdIo_t *cdio = cdio_open(name.toAscii(), DRIVER_DEVICE);
+ if (!cdio)
+ {
+ LOG(VB_MEDIA, LOG_INFO, QString("CdDecoder: cdio_open(%1) failed").
+ arg(name));
+ }
+ return cdio;
+}
+
+// Stack-based cdio device open
+class StCdioDevice
+{
+ CdIo_t* m_cdio;
+
+ void* operator new(std::size_t); // Stack only
+ // No copying
+ StCdioDevice(const StCdioDevice&);
+ StCdioDevice& operator =(const StCdioDevice&);
+
+public:
+ StCdioDevice(const QString& dev) : m_cdio(openCdio(dev)) { }
+ ~StCdioDevice() { if (m_cdio) cdio_destroy(m_cdio); }
+
+ operator CdIo_t*() const { return m_cdio; }
+};
+
CdDecoder::CdDecoder(const QString &file, DecoderFactory *d, QIODevice *i,
AudioOutput *o) :
Decoder(d, i, o),
- inited(false), user_stop(false),
- devicename(""),
- stat(0), output_buf(NULL),
- output_at(0), bks(0),
- bksFrames(0), decodeBytes(0),
- finish(false),
- freq(0), bitrate(0),
- chan(0),
- totalTime(0.0), seekTime(-1.0),
- settracknum(-1), tracknum(0),
- device(NULL), paranoia(NULL),
- start(0), end(0),
- curpos(0)
+ m_inited(false), m_user_stop(false),
+ m_devicename(""),
+ m_stat(DecoderEvent::Error),
+ m_output_buf(NULL),
+ m_output_at(0), m_bks(0),
+ m_bksFrames(0), m_decodeBytes(0),
+ m_finish(false),
+ m_freq(0), m_bitrate(0),
+ m_chan(0),
+ m_seekTime(-1.),
+ m_settracknum(-1), m_tracknum(0),
+ m_cdio(0), m_device(0), m_paranoia( 0),
+ m_start(CDIO_INVALID_LSN),
+ m_end(CDIO_INVALID_LSN),
+ m_curpos(CDIO_INVALID_LSN)
{
- setObjectName("CdDecoder");
setFilename(file);
}
-CdDecoder::~CdDecoder(void)
+// virtual
+CdDecoder::~CdDecoder()
{
- if (inited)
+ if (m_inited)
deinit();
}
+void CdDecoder::setDevice(const QString &dev)
+{
+ m_devicename = dev;
+#ifdef WIN32
+ // libcdio needs the drive letter with no path
+ if (devicename.endsWith('\\'))
+ devicename.chop(1);
+#endif
+}
+
+// pure virtual
void CdDecoder::stop()
{
- user_stop = true;
+ m_user_stop = true;
}
+// private
void CdDecoder::writeBlock()
{
- while (seekTime <= 0)
+ while (m_seekTime <= +0.)
{
- if(output()->AddFrames(output_buf, bksFrames, -1))
+ if(output()->AddFrames(m_output_buf, m_bksFrames, -1))
{
- output_at -= bks;
- memmove(output_buf, output_buf + bks, output_at);
+ if (m_output_at >= m_bks)
+ {
+ m_output_at -= m_bks;
+ std::memmove(m_output_buf, m_output_buf + m_bks,
+ m_output_at);
+ }
break;
}
else
- usleep(output()->GetAudioBufferedTime()<<9);
+ ::usleep(output()->GetAudioBufferedTime()<<9);
}
}
+//static
+QMutex& CdDecoder::getCdioMutex()
+{
+ static QMutex mtx(QMutex::Recursive);
+ return mtx;
+}
+// pure virtual
bool CdDecoder::initialize()
{
- inited = user_stop = finish = false;
- freq = bitrate = 0;
- stat = chan = 0;
- seekTime = -1.0;
+ if (m_inited)
+ return true;
+
+ m_inited = m_user_stop = m_finish = false;
+ m_freq = m_bitrate = 0L;
+ m_stat = DecoderEvent::Error;
+ m_chan = 0;
+ m_seekTime = -1.;
if (output())
output()->PauseUntilBuffered();
- totalTime = 0.0;
+ QFile* file = dynamic_cast< QFile* >(input()); // From QIODevice*
+ if (file)
+ {
+ setFilename(file->fileName());
+ m_tracknum = getFilename().section('.', 0, 0).toUInt();
+ }
- filename = ((QFile *)input())->fileName();
- tracknum = filename.section('.', 0, 0).toUInt();
+ QMutexLocker lock(&getCdioMutex());
- QByteArray devname = devicename.toAscii();
- device = cdda_identify(devname.constData(), 0, NULL);
- if (!device)
- return FALSE;
+ m_cdio = openCdio(m_devicename);
+ if (!m_cdio)
+ return false;
- if (cdda_open(device))
+ m_start = cdio_get_track_lsn(m_cdio, m_tracknum);
+ m_end = cdio_get_track_last_lsn(m_cdio, m_tracknum);
+ if (CDIO_INVALID_LSN == m_start ||
+ CDIO_INVALID_LSN == m_end)
{
- cdda_close(device);
- return FALSE;
+ LOG(VB_MEDIA, LOG_INFO, "CdDecoder: No tracks on " + m_devicename);
+ cdio_destroy(m_cdio), m_cdio = 0;
+ return false;
}
- cdda_verbose_set(device, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
- start = cdda_track_firstsector(device, tracknum);
- end = cdda_track_lastsector(device, tracknum);
+ LOG(VB_MEDIA, LOG_DEBUG, QString("CdDecoder track=%1 lsn start=%2 end=%3")
+ .arg(m_tracknum).arg(m_start).arg(m_end));
+ m_curpos = m_start;
- if (start > end || end == start)
+ m_device = cdio_cddap_identify_cdio(m_cdio, 0, NULL);
+ if (NULL == m_device)
{
- cdda_close(device);
- return FALSE;
+ LOG(VB_GENERAL, LOG_ERR,
+ QString("Error: CdDecoder: cdio_cddap_identify(%1) failed")
+ .arg(m_devicename));
+ cdio_destroy(m_cdio), m_cdio = 0;
+ return false;
}
- paranoia = paranoia_init(device);
- paranoia_modeset(paranoia, PARANOIA_MODE_DISABLE);
- paranoia_seek(paranoia, start, SEEK_SET);
+ cdio_cddap_verbose_set(m_device,
+ VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_ANY) ? CDDA_MESSAGE_PRINTIT :
+ CDDA_MESSAGE_FORGETIT,
+ VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG) ? CDDA_MESSAGE_PRINTIT :
+ CDDA_MESSAGE_FORGETIT);
- curpos = start;
+ if (DRIVER_OP_SUCCESS == cdio_cddap_open(m_device))
+ {
+ // cdio_get_track_last_lsn is unreliable on discs with data at end
+ lsn_t end2 = cdio_cddap_track_lastsector(m_device, m_tracknum);
+ if (end2 < m_end)
+ {
+ LOG(VB_MEDIA, LOG_INFO, QString("CdDecoder: trim last lsn from %1 to %2")
+ .arg(m_end).arg(end2));
+ m_end = end2;
+ }
- totalTime = ((end - start + 1) * CD_FRAMESAMPLES) / 44100.0;
+ m_paranoia = cdio_paranoia_init(m_device);
+ if (NULL != m_paranoia)
+ {
+ cdio_paranoia_modeset(m_paranoia, PARANOIA_MODE_DISABLE);
+ (void)cdio_paranoia_seek(m_paranoia, m_start, SEEK_SET);
+ }
+ else
+ {
+ LOG(VB_GENERAL, LOG_ERR, "Warn: CD reading with paranoia is disabled");
+ }
+ }
+ else
+ {
+ LOG(VB_GENERAL, LOG_ERR,
+ QString("Warn: drive '%1' is not cdda capable").
+ arg(m_devicename));
+ }
- chan = 2;
- freq = 44100;
+ int chnls = cdio_get_track_channels(m_cdio, m_tracknum);
+ m_chan = chnls > 0 ? chnls : 2;
+ m_freq = kSamplesPerSec;
if (output())
{
- const AudioSettings settings(FORMAT_S16, chan, CODEC_ID_PCM_S16LE, freq,
- false /* AC3/DTS passthru */);
+ const AudioSettings settings(FORMAT_S16, m_chan,
+ CODEC_ID_PCM_S16LE, m_freq, false /* AC3/DTS passthru */);
output()->Reconfigure(settings);
- output()->SetSourceBitrate(44100 * 2 * 16);
+ output()->SetSourceBitrate(m_freq * m_chan * 16);
}
// 20ms worth
- bks = (freq * chan * 2) / 50;
- bksFrames = freq / 50;
+ m_bks = (m_freq * m_chan * 2) / 50;
+ m_bksFrames = m_freq / 50;
// decode 8 bks worth of samples each time we need more
- decodeBytes = bks << 3;
+ m_decodeBytes = m_bks << 3;
- output_buf = (char *)av_malloc(decodeBytes + CD_FRAMESIZE_RAW * 2);
- output_at = 0;
+ m_output_buf = reinterpret_cast< char* >(
+ ::av_malloc(m_decodeBytes + CDIO_CD_FRAMESIZE_RAW * 2));
+ m_output_at = 0;
setCDSpeed(2);
- inited = true;
- return TRUE;
+
+ return m_inited = true;
}
+// pure virtual
void CdDecoder::seek(double pos)
{
- seekTime = pos;
+ m_seekTime = pos;
if (output())
output()->PauseUntilBuffered();
}
+// private
void CdDecoder::deinit()
{
setCDSpeed(-1);
- if (paranoia)
- paranoia_free(paranoia);
- if (device)
- cdda_close(device);
- if (output_buf)
- av_free(output_buf);
- output_buf = NULL;
+ QMutexLocker lock(&getCdioMutex());
+
+ if (m_paranoia)
+ cdio_paranoia_free(m_paranoia), m_paranoia = 0;
+ if (m_device)
+ cdio_cddap_close(m_device), m_device = 0, m_cdio = 0;
+ if (m_cdio)
+ cdio_destroy(m_cdio), m_cdio = 0;
- device = NULL;
- paranoia = NULL;
+ if (m_output_buf)
+ ::av_free(m_output_buf), m_output_buf = NULL;
- inited = user_stop = finish = false;
- freq = bitrate = 0;
- stat = chan = 0;
+ m_inited = m_user_stop = m_finish = false;
+ m_freq = m_bitrate = 0L;
+ m_stat = DecoderEvent::Finished;
+ m_chan = 0;
setInput(0);
setOutput(0);
}
-static void paranoia_cb(long /*inpos*/, int /*function*/)
-{
-}
-
+// private virtual
void CdDecoder::run()
{
RunProlog();
- if (!inited)
+
+ if (!m_inited)
{
RunEpilog();
return;
}
- stat = DecoderEvent::Decoding;
+ m_stat = DecoderEvent::Decoding;
+ // NB block scope required to prevent re-entrancy
{
- DecoderEvent e((DecoderEvent::Type) stat);
+ DecoderEvent e(m_stat);
dispatch(e);
}
- int16_t *cdbuffer;
- uint fill, total;
// account for possible frame expansion in aobase (upmix, float conv)
- uint thresh = bks * 6;
+ const std::size_t thresh = m_bks * 6;
- while (!finish && !user_stop)
+ while (!m_finish && !m_user_stop)
{
- if (seekTime >= 0.0)
+ if (m_seekTime >= +0.)
{
- curpos = (int)(((seekTime * 44100) / CD_FRAMESAMPLES) + start);
- paranoia_seek(paranoia, curpos, SEEK_SET);
- output_at = 0;
- seekTime = -1.0;
+ m_curpos = m_start + static_cast< lsn_t >(
+ (m_seekTime * kSamplesPerSec) / CD_FRAMESAMPLES);
+ if (m_paranoia)
+ {
+ QMutexLocker lock(&getCdioMutex());
+ cdio_paranoia_seek(m_paranoia, m_curpos, SEEK_SET);
+ }
+
+ m_output_at = 0;
+ m_seekTime = -1.;
}
- if (output_at < bks)
+ if (m_output_at < m_bks)
{
- while (output_at < decodeBytes &&
- !finish && !user_stop && seekTime <= 0.0)
+ while (m_output_at < m_decodeBytes &&
+ !m_finish && !m_user_stop && m_seekTime <= +0.)
{
- curpos++;
-
- if (curpos <= end)
+ if (m_curpos < m_end)
{
- cdbuffer = paranoia_read(paranoia, paranoia_cb);
-
- if (cdbuffer)
+ QMutexLocker lock(&getCdioMutex());
+ if (m_paranoia)
{
- memcpy((char *)(output_buf + output_at), (char *)cdbuffer,
- CD_FRAMESIZE_RAW);
-
- output_at += CD_FRAMESIZE_RAW;
+ int16_t *cdbuffer = cdio_paranoia_read_limited(
+ m_paranoia, 0, 10);
+ if (cdbuffer)
+ memcpy(&m_output_buf[m_output_at],
+ cdbuffer, CDIO_CD_FRAMESIZE_RAW);
}
else
- finish = true;
+ {
+ driver_return_code_t c = cdio_read_audio_sector(
+ m_cdio, &m_output_buf[m_output_at],
+ m_curpos);
+ if (DRIVER_OP_SUCCESS != c)
+ {
+ LOG(VB_MEDIA, LOG_DEBUG,
+ QString("cdio_read_audio_sector(%1) error %2").
+ arg(m_curpos).arg(c));
+ memset( &m_output_buf[m_output_at],
+ 0, CDIO_CD_FRAMESIZE_RAW);
+ }
+ }
+
+ m_output_at += CDIO_CD_FRAMESIZE_RAW;
+ ++(m_curpos);
}
else
- finish = true;
+ {
+ m_finish = true;
+ }
}
}
@@ -239,367 +392,463 @@ void CdDecoder::run()
continue;
// Wait until we need to decode or supply more samples
- while (!finish && !user_stop && seekTime <= 0.0)
+ uint fill = 0, total = 0;
+ while (!m_finish && !m_user_stop && m_seekTime <= +0.)
{
output()->GetBufferStatus(fill, total);
// Make sure we have decoded samples ready and that the
// audiobuffer is reasonably populated
- if (fill < thresh << 6)
+ if (fill < (thresh << 6))
break;
else
+ {
// Wait for half of the buffer to drain
- usleep(output()->GetAudioBufferedTime()<<9);
+ ::usleep(output()->GetAudioBufferedTime()<<9);
+ }
}
// write a block if there's sufficient space for it
- if (!user_stop && output_at >= bks && fill <= total - thresh)
+ if (!m_user_stop &&
+ m_output_at >= m_bks &&
+ fill <= total - thresh)
+ {
writeBlock();
-
+ }
}
- if (user_stop)
- inited = false;
-
+ if (m_user_stop)
+ m_inited = false;
else if (output())
{
// Drain our buffer
- while (output_at >= bks)
+ while (m_output_at >= m_bks)
writeBlock();
// Drain ao buffer
output()->Drain();
}
- if (finish)
- stat = DecoderEvent::Finished;
- else if (user_stop)
- stat = DecoderEvent::Stopped;
+ if (m_finish)
+ m_stat = DecoderEvent::Finished;
+ else if (m_user_stop)
+ m_stat = DecoderEvent::Stopped;
+ else
+ m_stat = DecoderEvent::Error;
+
+ // NB block scope required to step onto next track
{
- DecoderEvent e((DecoderEvent::Type) stat);
+ DecoderEvent e(m_stat);
dispatch(e);
}
deinit();
+
RunEpilog();
}
+//public
void CdDecoder::setCDSpeed(int speed)
{
- QMutexLocker lock(getMutex());
- MediaMonitor::SetCDSpeed(devicename.toLocal8Bit().constData(), speed);
-}
-
-int CdDecoder::getNumTracks(void)
-{
- QByteArray devname = devicename.toAscii();
- int cd = cd_init_device(const_cast<char*>(devname.constData()));
+ QMutexLocker lock(&getCdioMutex());
- struct disc_info discinfo;
- if (cd_stat(cd, &discinfo) != 0)
+ StCdioDevice cdio(m_devicename);
+ if (cdio)
{
- error("Couldn't stat CD, Error.");
- cd_finish(cd);
- return 0;
+ driver_return_code_t c = cdio_set_speed(cdio, speed >= 0 ? speed : 1);
+ if (DRIVER_OP_SUCCESS != c)
+ {
+ LOG(VB_MEDIA, LOG_INFO,
+ QString("Error: cdio_set_speed('%1',%2) failed").
+ arg(m_devicename).arg(speed));
+ }
}
+}
- if (!discinfo.disc_present)
- {
- error("No disc present");
- cd_finish(cd);
- return 0;
- }
+//public
+int CdDecoder::getNumTracks()
+{
+ QMutexLocker lock(&getCdioMutex());
- int retval = discinfo.disc_total_tracks;
+ StCdioDevice cdio(m_devicename);
+ if (!cdio)
+ return 0;
- cd_finish(cd);
+ track_t tracks = cdio_get_num_tracks(cdio);
+ if (CDIO_INVALID_TRACK != tracks)
+ LOG(VB_MEDIA, LOG_DEBUG, QString("getNumTracks = %1").arg(tracks));
+ else
+ tracks = -1;
- return retval;
+ return tracks;
}
-int CdDecoder::getNumCDAudioTracks(void)
+//public
+int CdDecoder::getNumCDAudioTracks()
{
- QByteArray devname = devicename.toAscii();
- int cd = cd_init_device(const_cast<char*>(devname.constData()));
-
- struct disc_info discinfo;
- if (cd_stat(cd, &discinfo) != 0)
- {
- error("Couldn't stat CD, Error.");
- cd_finish(cd);
- return 0;
- }
+ QMutexLocker lock(&getCdioMutex());
- if (!discinfo.disc_present)
- {
- error("No disc present");
- cd_finish(cd);
+ StCdioDevice cdio(m_devicename);
+ if (!cdio)
return 0;
- }
- int retval = 0;
- for( int i = 0 ; i < discinfo.disc_total_tracks; ++i)
+ int nAudio = 0;
+ const track_t last = cdio_get_last_track_num(cdio);
+ if (CDIO_INVALID_TRACK != last)
{
- if(discinfo.disc_track[i].track_type == CDAUDIO_TRACK_AUDIO)
+ for (track_t t = cdio_get_first_track_num(cdio) ; t <= last; ++t)
{
- ++retval;
+ if (TRACK_FORMAT_AUDIO == cdio_get_track_format(cdio, t))
+ ++nAudio;
}
+ LOG(VB_MEDIA, LOG_DEBUG, QString("getNumCDAudioTracks = %1").arg(nAudio));
}
- cd_finish(cd);
-
- return retval;
+ return nAudio;
}
+//public
Metadata* CdDecoder::getMetadata(int track)
{
- settracknum = track;
+ m_settracknum = track;
return getMetadata();
}
+//public
Metadata *CdDecoder::getLastMetadata()
{
- Metadata *return_me;
for(int i = getNumTracks(); i > 0; --i)
{
- settracknum = i;
- return_me = getMetadata();
- if(return_me)
- {
- return return_me;
- }
+ Metadata *m = getMetadata(i);
+ if(m)
+ return m;
}
return NULL;
}
-Metadata *CdDecoder::getMetadata()
+// Create a TOC
+static lsn_t s_lastAudioLsn;
+static Cddb::Toc& GetToc(CdIo_t *cdio, Cddb::Toc& toc)
{
+ // Get lead-in
+ const track_t firstTrack = cdio_get_first_track_num(cdio);
+ lsn_t lsn0 = 0;
+ msf_t msf;
+ if (cdio_get_track_msf(cdio, firstTrack, &msf))
+ lsn0 = (msf.m * 60 + msf.s) * CDIO_CD_FRAMES_PER_SEC + msf.f;
+
+ const track_t lastTrack = cdio_get_last_track_num(cdio);
+ for (track_t t = firstTrack; t <= lastTrack + 1; ++t)
+ {
+#if 0 // This would be better but the msf's returned are way off in libcdio 0.81
+ if (!cdio_get_track_msf(cdio, t, &msf))
+ break;
+#else
+ lsn_t lsn = cdio_get_track_lsn(cdio, t);
+ if (s_lastAudioLsn && lsn > s_lastAudioLsn)
+ lsn = s_lastAudioLsn;
+ lsn += lsn0; // lead-in
+
+ std::div_t d = std::div(lsn, CDIO_CD_FRAMES_PER_SEC);
+ msf.f = d.rem;
+ d = std::div(d.quot, 60);
+ msf.s = d.rem;
+ msf.m = d.quot;
+#endif
+ //LOG(VB_MEDIA, LOG_INFO, QString("Track %1 msf: %2:%3:%4").
+ // arg(t,2).arg(msf.m,2).arg(msf.s,2).arg(msf.f,2) );
+ toc.push_back(Cddb::Msf(msf.m, msf.s, msf.f));
+
+ if (TRACK_FORMAT_AUDIO != cdio_get_track_format(cdio, t))
+ break;
+ }
+ return toc;
+}
+//virtual
+Metadata *CdDecoder::getMetadata()
+{
QString artist, album, compilation_artist, title, genre;
- int year = 0, tracknum = 0, length = 0;
+ int year = 0;
+ unsigned long length = 0;
+ track_t tracknum = 0;
- QByteArray devname = devicename.toAscii();
- int cd = cd_init_device(const_cast<char*>(devname.constData()));
-
- struct disc_info discinfo;
- if (cd_stat(cd, &discinfo) != 0)
+ if (-1 == m_settracknum)
+ tracknum = getFilename().toUInt();
+ else
{
- error("Couldn't stat CD, Error.");
- cd_finish(cd);
- return NULL;
+ tracknum = m_settracknum;
+ setFilename(QString("%1" CDEXT).arg(tracknum));
}
- if (!discinfo.disc_present)
- {
- error("No disc present");
- cd_finish(cd);
+ QMutexLocker lock(&getCdioMutex());
+
+ StCdioDevice cdio(m_devicename);
+ if (!cdio)
+ return NULL;
+
+ const track_t lastTrack = cdio_get_last_track_num(cdio);
+ if (CDIO_INVALID_TRACK == lastTrack)
return NULL;
+
+ if (TRACK_FORMAT_AUDIO != cdio_get_track_format(cdio, tracknum))
+ return NULL;
+
+ // Assume disc changed if max LSN different
+ bool isDiscChanged = false;
+ static lsn_t s_totalSectors;
+ lsn_t totalSectors = cdio_get_track_lsn(cdio, CDIO_CDROM_LEADOUT_TRACK);
+ if (s_totalSectors != totalSectors)
+ {
+ s_totalSectors = totalSectors;
+ isDiscChanged = true;
}
- if (settracknum == -1)
- tracknum = filename.toUInt();
- else
+ // NB cdio_get_track_last_lsn is unreliable for the last audio track
+ // of discs with data tracks beyond
+ lsn_t end = cdio_get_track_last_lsn(cdio, tracknum);
+ if (isDiscChanged)
{
- tracknum = settracknum;
- filename = QString("%1.cda").arg(tracknum);
+ const track_t audioTracks = getNumCDAudioTracks();
+ s_lastAudioLsn = cdio_get_track_last_lsn(cdio, audioTracks);
+
+ if (audioTracks < lastTrack)
+ {
+ cdrom_drive_t *dev = cdio_cddap_identify_cdio(cdio, 0, NULL);
+ if (NULL != dev)
+ {
+ if (DRIVER_OP_SUCCESS == cdio_cddap_open(dev))
+ {
+ // NB this can be S L O W but is reliable
+ lsn_t end2 = cdio_cddap_track_lastsector(dev,
+ getNumCDAudioTracks());
+ if (CDIO_INVALID_LSN != end2)
+ s_lastAudioLsn = end2;
+ }
+ cdio_cddap_close_no_free_cdio(dev);
+ }
+ }
}
- settracknum = -1;
+ if (s_lastAudioLsn && s_lastAudioLsn < end)
+ end = s_lastAudioLsn;
- if (tracknum > discinfo.disc_total_tracks)
+ const lsn_t start = cdio_get_track_lsn(cdio, tracknum);
+ if (CDIO_INVALID_LSN != start && CDIO_INVALID_LSN != end)
{
- error("No such track on CD");
- cd_finish(cd);
- return NULL;
+ length = ((end - start + 1) * 1000 + CDIO_CD_FRAMES_PER_SEC/2) /
+ CDIO_CD_FRAMES_PER_SEC;
}
- if(discinfo.disc_track[tracknum - 1].track_type != CDAUDIO_TRACK_AUDIO )
+ bool isCompilation = false;
+
+#define CDTEXT 0 // Disabled - cd-text access on discs without it is S L O W
+#if CDTEXT
+ static int s_iCdtext;
+ if (isDiscChanged)
+ s_iCdtext = -1;
+
+ if (s_iCdtext)
{
- error("Exclude non audio tracks");
- cd_finish(cd);
- return NULL;
- }
+ // cdio_get_cdtext can't take >5 seconds on some CD's without cdtext
+ if (s_iCdtext < 0)
+ LOG(VB_MEDIA, LOG_INFO,
+ QString("Getting cdtext for track %1...").arg(tracknum));
+ cdtext_t * cdtext = cdio_get_cdtext(m_cdio, tracknum);
+ if (NULL != cdtext)
+ {
+ genre = cdtext_get_const(CDTEXT_GENRE, cdtext);
+ artist = cdtext_get_const(CDTEXT_PERFORMER, cdtext);
+ title = cdtext_get_const(CDTEXT_TITLE, cdtext);
+ const char* isrc = cdtext_get_const(CDTEXT_ISRC, cdtext);
+ /* ISRC codes are 12 characters long, in the form CCXXXYYNNNNN
+ * CC = country code
+ * XXX = registrant e.g. BMG
+ * CC = year withou century
+ * NNNNN = unique ID
+ */
+ if (isrc && strlen(isrc) >= 7)
+ {
+ year = (isrc[5] - '0') * 10 + (isrc[6] - '0');
+ year += (year <= 30) ? 2000 : 1900;
+ }
+ cdtext_destroy(cdtext);
- struct disc_data discdata;
- memset(&discdata, 0, sizeof(discdata));
+ if (!title.isNull())
+ {
+ if (s_iCdtext < 0)
+ LOG(VB_MEDIA, LOG_INFO, "Found cdtext track title");
+ s_iCdtext = 1;
- int ret = cddb_read_disc_data(cd, &discdata);
+ // Get disc info
+ cdtext = cdio_get_cdtext(cdio, 0);
+ if (NULL != cdtext)
+ {
+ compilation_artist = cdtext_get_const(
+ CDTEXT_PERFORMER, cdtext);
+ if (!compilation_artist.isEmpty() &&
+ artist != compilation_artist)
+ isCompilation = true;
- if (ret < 0)
- {
- cd_finish(cd);
- LOG(VB_GENERAL, LOG_ERR,
- QString("Error during CD lookup: %1").arg(ret));
- LOG(VB_MEDIA, LOG_ERR, QString("cddb_read_disc_data() said: %1")
- .arg(cddb_message));
- return NULL;
+ album = cdtext_get_const(CDTEXT_TITLE, cdtext);
+
+ if (genre.isNull())
+ genre = cdtext_get_const(CDTEXT_GENRE, cdtext);
+
+ cdtext_destroy(cdtext);
+ }
+ }
+ else
+ {
+ if (s_iCdtext < 0)
+ LOG(VB_MEDIA, LOG_INFO, "No cdtext title for track");
+ s_iCdtext = 0;
+ }
+ }
+ else
+ {
+ if (s_iCdtext < 0)
+ LOG(VB_MEDIA, LOG_INFO, "No cdtext");
+ s_iCdtext = 0;
+ }
}
- compilation_artist = QString(M_QSTRING_UNICODE(discdata.data_artist))
- .trimmed();
+ if (title.isEmpty() || artist.isEmpty() || album.isEmpty())
+#endif // CDTEXT
+ {
+ // CDDB lookup
+ Cddb::Toc toc;
+ Cddb::Matches r;
+ if (Cddb::Query(r, GetToc(cdio, toc)))
+ {
+ Cddb::Matches::match_t::const_iterator select = r.matches.begin();
- if (compilation_artist.toLower().left(7) == "various")
- compilation_artist = QObject::tr("Various Artists");
+ if (r.matches.size() > 1)
+ {
+ // TODO prompt user to select one
+ // In the meantime, select the first non-generic genre
+ for (Cddb::Matches::match_t::const_iterator it = select;
+ it != r.matches.end(); ++it)
+ {
+ QString g = it->genre.toLower();
+ if (g != "misc" && g != "data")
+ {
+ select = it;
+ break;
+ }
+ }
+ }
- album = QString(M_QSTRING_UNICODE(discdata.data_title)).trimmed();
- genre = cddb_genre(discdata.data_genre);
+ Cddb::Album info;
+ if (Cddb::Read(info, select->genre, select->discID))
+ {
+ isCompilation = info.isCompilation;
+ if (info.genre.toLower() != "misc")
+ genre = info.genre;
+ album = info.title;
+ compilation_artist = info.artist;
+ year = info.