00001 #include <stdio.h>
00002 #include <stdlib.h>
00003 #include <iostream>
00004 #include <string>
00005 #include <qobject.h>
00006 #include <qiodevice.h>
00007 #include <qdir.h>
00008 #include <qfile.h>
00009 using namespace std;
00010
00011 #include <mythtv/mythconfig.h>
00012 #include "cddecoder.h"
00013 #include "constants.h"
00014 #include <mythtv/audiooutput.h>
00015 #include "metadata.h"
00016
00017 #include <mythtv/mythcontext.h>
00018 #include <mythtv/mythmediamonitor.h>
00019 #include <mythtv/httpcomms.h>
00020
00021 CdDecoder::CdDecoder(const QString &file, DecoderFactory *d, QIODevice *i,
00022 AudioOutput *o)
00023 : Decoder(d, i, o)
00024 {
00025 filename = file;
00026 inited = FALSE;
00027 }
00028
00029 CdDecoder::~CdDecoder(void)
00030 {
00031 if (inited)
00032 deinit();
00033 }
00034
00035
00036
00037
00038 static inline int addDecimalDigits(int i)
00039 {
00040 int total = 0;
00041 while (i > 0)
00042 total += i % 10, i /= 10;
00043 return total;
00044 }
00045
00046
00047
00048
00049 QString fileForTrack(QString path, uint track)
00050 {
00051 QDir disc(path);
00052 QString filename;
00053
00054 disc.setNameFilter(QString("%1*.aiff").arg(track));
00055 filename = disc.entryList()[0];
00056
00057 if (filename.isNull())
00058 filename = QString("%1.aiff").arg(track);
00059
00060 return filename;
00061 }
00062
00066 bool CdDecoder::initialize()
00067 {
00068 QFile TOCfile(devicename + "/.TOC.plist");
00069 QDomDocument TOC;
00070 uint trk;
00071
00072 if (!TOCfile.open(IO_ReadOnly))
00073 {
00074 VERBOSE(VB_GENERAL,
00075 "Unable to open Audio CD TOC file: " + TOCfile.name());
00076 return false;
00077 }
00078
00079 if (!TOC.setContent(&TOCfile))
00080 {
00081 VERBOSE(VB_GENERAL,
00082 "Unable to parse Audio CD TOC file: " + TOCfile.name());
00083 TOCfile.close();
00084 return false;
00085 }
00086
00087 m_tracks.clear();
00088
00089
00090
00091
00092
00093 QDomElement root = TOC.documentElement();
00094 QDomNode node = root.firstChild()
00095 .namedItem("array")
00096 .firstChild()
00097 .firstChild();
00098 while (!node.isNull())
00099 {
00100 if (node.nodeName() == "key")
00101 {
00102 QDomText t = node.firstChild().toText();
00103 node = node.nextSibling();
00104 int i = node.firstChild().toText()
00105 .data().toInt();
00106
00107 if (t.data() == "First Track")
00108 m_firstTrack = i;
00109 if (t.data() == "Last Track")
00110 m_lastTrack = i;
00111 if (t.data() == "Leadout Block")
00112 m_leadout = i;
00113 }
00114
00115 if (node.nodeName() == "array")
00116 {
00117 node = node.firstChild();
00118
00119 for (trk = m_firstTrack; trk <= m_lastTrack; ++trk)
00120 {
00121 m_tracks.push_back(node.lastChild().firstChild()
00122 .toText().data().toInt());
00123
00124 node = node.nextSibling();
00125 }
00126 }
00127
00128 node = node.nextSibling();
00129 }
00130 TOCfile.close();
00131
00132
00133
00134
00135 m_lengthInSecs = (m_leadout - m_tracks[0]) / 75.0;
00136
00137 int checkSum = 0;
00138 for (trk = 0; trk <= m_lastTrack - m_firstTrack; ++trk)
00139 checkSum += addDecimalDigits(m_tracks[trk] / 75);
00140
00141 uint totalTracks = 1 + m_lastTrack - m_firstTrack;
00142 m_diskID = ((checkSum % 255) << 24) | (int)m_lengthInSecs << 8
00143 | totalTracks;
00144
00145 QString hexID;
00146 hexID.setNum(m_diskID, 16);
00147 VERBOSE(VB_MEDIA, QString("CD %1, ID=%2").arg(devicename).arg(hexID));
00148
00149
00150
00151 for (trk = 0; trk < m_mData.size(); ++trk)
00152 delete m_mData[trk];
00153 m_mData.clear();
00154
00155
00156
00157
00158
00159 m_tracks.push_back(m_leadout);
00160
00161 for (trk = 1; trk <= totalTracks; ++trk)
00162 {
00163 QString file = fileForTrack(devicename, trk);
00164 uint len = 1000 * (m_tracks[trk] - m_tracks[trk-1]) / 75;
00165
00166 m_mData.push_back(new Metadata(file, NULL, NULL, NULL,
00167 NULL, NULL, 0, trk, len));
00168 }
00169
00170
00171
00172 lookupCDDB(hexID, totalTracks);
00173
00174
00175 inited = true;
00176 return true;
00177 }
00178
00182 void CdDecoder::lookupCDDB(const QString &hexID, uint totalTracks)
00183 {
00184 QString helloID = getenv("USER");
00185 QString queryID = "cddb+query+";
00186 uint trk;
00187
00188 if (helloID.isNull())
00189 helloID = "anon";
00190 helloID += QString("+%1+MythTV+%2+")
00191 .arg(gContext->GetHostName()).arg(MYTH_BINARY_VERSION);
00192
00193 queryID += QString("%1+%2+").arg(hexID).arg(totalTracks);
00194 for (trk = 0; trk < totalTracks; ++trk)
00195 queryID += QString::number(m_tracks[trk]) + '+';
00196 queryID += QString::number(m_lengthInSecs);
00197
00198
00199
00200 QString URL = "http://freedb.freedb.org/~cddb/cddb.cgi?cmd=";
00201 QString URL2 = URL + queryID + "&hello=" + helloID + "&proto=5";
00202 QString cddb = HttpComms::getHttp(URL2);
00203
00204
00205
00206
00207
00208
00209
00210 uint stat = cddb.left(3).toUInt();
00211 cddb = cddb.mid(4);
00212
00213
00214
00215
00216 if (stat == 211)
00217 {
00218
00219 VERBOSE(VB_MEDIA, "Multiple CDDB matches. Please implement this code");
00220 }
00221
00222 if (stat == 200)
00223 {
00224 QString album;
00225 QString artist;
00226 bool compn = false;
00227 QString genre = cddb.section(' ', 0, 0);
00228 int year = 0;
00229
00230
00231 URL2 = URL + "cddb+read+" + genre + "+"
00232 + hexID + "&hello=" + helloID + "&proto=5";
00233 cddb = HttpComms::getHttp(URL2);
00234
00235
00236
00237
00238
00239 for (trk = 0; trk < totalTracks; ++trk)
00240 m_mData[trk]->setTitle("");
00241
00242
00243
00244 cddb.replace(QRegExp(".*#"), "");
00245 while (cddb.length())
00246 {
00247
00248
00249 QString art = "";
00250 QString line = cddb.section(QRegExp("(\r|\n)+"), 0, 0);
00251 QString value = line.section('=', 1, 1);
00252
00253 if (value.contains(" / "))
00254 {
00255 art = value.section(" / ", 0, 0);
00256 value = value.section(" / ", 1, 1);
00257 }
00258
00259 if (line.startsWith("DGENRE="))
00260 genre = value;
00261 else if (line.startsWith("DYEAR="))
00262 year = value.toInt();
00263 else if (line.startsWith("DTITLE="))
00264 {
00265
00266 artist += art;
00267 album += value;
00268 }
00269 else if (line.startsWith("TTITLE"))
00270 {
00271 trk = line.remove("TTITLE").remove(QRegExp("=.*")).toUInt();
00272
00273 if (trk < totalTracks)
00274 {
00275 Metadata *m = m_mData[trk];
00276
00277
00278 m->setTitle(m->Title() + value);
00279
00280 if (art.length())
00281 {
00282 compn = true;
00283
00284 m->setArtist(M_QSTRING_UNICODE(art));
00285 }
00286 }
00287 else
00288 VERBOSE(VB_GENERAL,
00289 QString("CDDB returned %1 on a %2 track disk!")
00290 .arg(trk+1).arg(totalTracks));
00291 }
00292
00293
00294 cddb = cddb.section('\n', 1, 0xffffffff);
00295 }
00296
00297 for (trk = 0; trk < totalTracks; ++trk)
00298 {
00299 Metadata *m = m_mData[trk];
00300
00301 if (compn)
00302 m->setCompilation(true);
00303
00304 m->setGenre(M_QSTRING_UNICODE(genre));
00305
00306 if (year)
00307 m->setYear(year);
00308
00309 if (album.length())
00310 m->setAlbum(M_QSTRING_UNICODE(album));
00311
00312 if (artist.length())
00313 if (compn)
00314 m->setCompilationArtist(M_QSTRING_UNICODE(artist));
00315 else
00316 m->setArtist(M_QSTRING_UNICODE(artist));
00317 }
00318 }
00319 }
00320
00321 double CdDecoder::lengthInSeconds()
00322 {
00323 return m_lengthInSecs;
00324 }
00325
00326 void CdDecoder::seek(double pos)
00327 {
00328 (void)pos;
00329 }
00330
00331 void CdDecoder::stop()
00332 {
00333 }
00334
00335 void CdDecoder::run()
00336 {
00337 }
00338
00339 void CdDecoder::flush(bool final)
00340 {
00341 (void)final;
00342 }
00343
00344 void CdDecoder::deinit()
00345 {
00346
00347 for (unsigned int i = 0; i < m_mData.size(); ++i)
00348 delete m_mData[i];
00349 m_mData.clear();
00350
00351 m_tracks.clear();
00352
00353 inited = false;
00354 }
00355
00356 int CdDecoder::getNumTracks(void)
00357 {
00358 if (!inited)
00359 initialize();
00360
00361 return m_lastTrack;
00362 }
00363
00364 int CdDecoder::getNumCDAudioTracks(void)
00365 {
00366 if (!inited)
00367 initialize();
00368
00369 return m_lastTrack - m_firstTrack + 1;
00370 }
00371
00372 Metadata* CdDecoder::getMetadata(int track)
00373 {
00374 if (!inited)
00375 initialize();
00376
00377 if (track < 1 || (uint)track > m_mData.size())
00378 {
00379 VERBOSE(VB_GENERAL,
00380 QString("CdDecoder::getMetadata(%1) - track out of range")
00381 .arg(track));
00382 return NULL;
00383 }
00384
00385 return new Metadata(*(m_mData[track - 1]));
00386 }
00387
00388 Metadata *CdDecoder::getLastMetadata()
00389 {
00390 if (!inited)
00391 initialize();
00392
00393 return new Metadata(*(m_mData[m_mData.size() - 1]));
00394 }
00395
00396 Metadata *CdDecoder::getMetadata()
00397 {
00398 return NULL;
00399 }
00400
00401 void CdDecoder::commitMetadata(Metadata *mdata)
00402 {
00403 (void)mdata;
00404 }
00405
00406 bool CdDecoderFactory::supports(const QString &source) const
00407 {
00408 return (source.right(extension().length()).lower() == extension());
00409 }
00410
00411 const QString &CdDecoderFactory::extension() const
00412 {
00413 static QString ext(".aiff");
00414 return ext;
00415 }
00416
00417
00418 const QString &CdDecoderFactory::description() const
00419 {
00420 static QString desc(QObject::tr("OSX Audio CD mount parser"));
00421 return desc;
00422 }
00423
00424 Decoder *CdDecoderFactory::create(const QString &file, QIODevice *input,
00425 AudioOutput *output, bool deletable)
00426 {
00427 if (deletable)
00428 return new CdDecoder(file, this, input, output);
00429
00430 static CdDecoder *decoder = 0;
00431 if (! decoder) {
00432 decoder = new CdDecoder(file, this, input, output);
00433 } else {
00434 decoder->setInput(input);
00435 decoder->setFilename(file);
00436 decoder->setOutput(output);
00437 }
00438
00439 return decoder;
00440 }