MythTV  0.25-pre
cddecoder-darwin.cpp
Go to the documentation of this file.
00001 #include <stdio.h>
00002 #include <stdlib.h>
00003 #include <iostream>
00004 #include <string>
00005 using namespace std;
00006 
00007 #include <QObject>
00008 #include <QIODevice>
00009 #include <QDir>
00010 #include <QFile>
00011 #include <QDomDocument>
00012 
00013 #include <mythconfig.h>   // For CONFIG_DARWIN
00014 #include "cddecoder.h"
00015 #include "constants.h"
00016 #include <audiooutput.h>
00017 #include "metadata.h"
00018 
00019 #include <mythcontext.h>
00020 #include <mythmediamonitor.h>
00021 #include <mythversion.h>
00022 #include <httpcomms.h>
00023 #include <util.h>
00024 
00025 CdDecoder::CdDecoder(const QString &file, DecoderFactory *d, QIODevice *i,
00026                      AudioOutput *o) :
00027     Decoder(d, i, o),
00028     inited(false),   user_stop(false),
00029     devicename(""),
00030     m_diskID(0),     m_firstTrack(0),
00031     m_lastTrack(0),  m_leadout(0),
00032     m_lengthInSecs(0.0),
00033     stat(0),         output_buf(NULL),
00034     output_at(0),    bks(0),
00035     bksFrames(0),    decodeBytes(0),
00036     finish(false),
00037     freq(0),         bitrate(0),
00038     chan(0),
00039     totalTime(0.0),  seekTime(-1.0),
00040     settracknum(-1), tracknum(0),
00041     start(0),        end(0),
00042     curpos(0)
00043 {
00044     setObjectName("CdDecoder");
00045     setFilename(file);
00046 }
00047 
00048 CdDecoder::~CdDecoder(void)
00049 {
00050     if (inited)
00051         deinit();
00052 }
00053 
00054 /*
00055  * Helper function for CD checksum generation
00056  */
00057 static inline int addDecimalDigits(int i)
00058 {
00059     int total = 0;
00060     while (i > 0)
00061         total += i % 10, i /= 10;
00062     return total;
00063 }
00064 
00065 /*
00066  * Work out file containing a given track
00067  */
00068 QString fileForTrack(QString path, uint track)
00069 {
00070     QDir    disc(path);
00071     QString filename;
00072 
00073     disc.setNameFilters(QStringList(QString("%1*.aiff").arg(track)));
00074     filename = disc.entryList()[0];  // Fortunately, this seems to sort nicely
00075 
00076     if (filename.isEmpty())
00077         filename = QString("%1.aiff").arg(track);
00078 
00079     return filename;
00080 }
00081 
00085 bool CdDecoder::initialize()
00086 {
00087     QFile        TOCfile(devicename + "/.TOC.plist");
00088     QDomDocument TOC;
00089     uint         trk;
00090 
00091     m_tracks.clear();
00092     m_firstTrack = m_lastTrack = m_leadout = 0;
00093 
00094     if (!TOCfile.open(QIODevice::ReadOnly))
00095     {
00096         LOG(VB_GENERAL, LOG_ERR,
00097             "Unable to open Audio CD TOC file: " + TOCfile.fileName());
00098         return false;
00099     }
00100 
00101     if (!TOC.setContent(&TOCfile))
00102     {
00103         LOG(VB_GENERAL, LOG_ERR,
00104             "Unable to parse Audio CD TOC file: " + TOCfile.fileName());
00105         TOCfile.close();
00106         return false;
00107     }
00108 
00109 
00110     // HACK. This is a really bad example of XML parsing. No type checking,
00111     // it doesn't deal with comments. It only works because the TOC.plist
00112     // file is generated (i.e. a fixed format)
00113 
00114     QDomElement root = TOC.documentElement();
00115     QDomNode    node = root.firstChild()        // <dict>
00116                            .namedItem("array")  //   <key>Sessions</key><array>
00117                            .firstChild()        //     <dict>
00118                            .firstChild();
00119     while (!node.isNull())
00120     {
00121         if (node.nodeName() == "key")
00122         {
00123             QDomText t = node.firstChild().toText();  // <key>  t  </key>
00124             node       = node.nextSibling();          // <integer>i</integer>
00125             int      i = node.firstChild().toText()
00126                              .data().toInt();
00127 
00128             if (t.data() == "First Track")
00129                 m_firstTrack = i;
00130             if (t.data() == "Last Track")
00131                 m_lastTrack = i;
00132             if (t.data() == "Leadout Block")
00133                 m_leadout = i;
00134         }
00135                                          // <key>Track Array</key>
00136         if (node.nodeName() == "array")  // <array>
00137         {
00138             node = node.firstChild();    // First track's <dict>
00139 
00140             for (trk = m_firstTrack; trk <= m_lastTrack; ++trk)
00141             {
00142                 m_tracks.push_back(node.lastChild().firstChild()
00143                                        .toText().data().toInt());
00144 
00145                 node = node.nextSibling();  // Look at next <dict> in <array>
00146             }
00147         }
00148 
00149         node = node.nextSibling();
00150     }
00151     TOCfile.close();
00152 
00153 
00154     // Calculate some stuff for later CDDB/FreeDB lookup
00155 
00156     m_lengthInSecs = (m_leadout - m_tracks[0]) / 75.0;
00157 
00158     int checkSum = 0;
00159     for (trk = 0; trk <= m_lastTrack - m_firstTrack; ++trk)
00160         checkSum += addDecimalDigits(m_tracks[trk] / 75);
00161 
00162     uint totalTracks = 1 + m_lastTrack - m_firstTrack;
00163     m_diskID = ((checkSum % 255) << 24) | (int)m_lengthInSecs << 8
00164                                         | totalTracks;
00165 
00166     QString hexID;
00167     hexID.setNum(m_diskID, 16);
00168     LOG(VB_MEDIA, LOG_INFO, QString("CD %1, ID=%2").arg(devicename).arg(hexID));
00169 
00170 
00171     // First erase any existing metadata:
00172     for (trk = 0; trk < m_mData.size(); ++trk)
00173         delete m_mData[trk];
00174     m_mData.clear();
00175 
00176 
00177     // Generate empty MetaData records.
00178     // We fill in the other details later (from CDDB if possible)
00179 
00180     m_tracks.push_back(m_leadout);  // This simplifies the loop
00181 
00182     for (trk = 1; trk <= totalTracks; ++trk)
00183     {
00184         QString file = fileForTrack(devicename, trk);
00185         uint    len  = 1000 * (m_tracks[trk] - m_tracks[trk-1]) / 75;
00186 
00187         m_mData.push_back(new Metadata(file, NULL, NULL, NULL,
00188                                        NULL, NULL, 0, trk, len));
00189     }
00190 
00191 
00192     // Try to fill in this MetaData from CDDB lookup:
00193     lookupCDDB(hexID, totalTracks);
00194 
00195 
00196     inited = true;
00197     return true;
00198 }
00199 
00203 void CdDecoder::lookupCDDB(const QString &hexID, uint totalTracks)
00204 {
00205     QString helloID = getenv("USER");
00206     QString queryID = "cddb+query+";
00207     uint    trk;
00208 
00209     if (helloID.isEmpty())
00210         helloID = "anon";
00211     helloID += QString("+%1+MythTV+%2+")
00212                .arg(gCoreContext->GetHostName()).arg(MYTH_BINARY_VERSION);
00213 
00214     queryID += QString("%1+%2+").arg(hexID).arg(totalTracks);
00215     for (trk = 0; trk < totalTracks; ++trk)
00216         queryID += QString::number(m_tracks[trk]) + '+';
00217     queryID += QString::number(m_lengthInSecs);
00218 
00219 
00220     // First, try HTTP:
00221     QString URL  = "http://freedb.freedb.org/~cddb/cddb.cgi?cmd=";
00222     QString URL2 = URL + queryID + "&hello=" + helloID + "&proto=5";
00223     QString cddb = HttpComms::getHttp(URL2);
00224 
00225 #if 0
00226     LOG(VB_MEDIA, LOG_INFO, "CDDB lookup: " + URL2);
00227     LOG(VB_MEDIA, LOG_INFO, "...returned: " + cddb);
00228 #endif
00229     //
00230     // e.g. "200 rock 960b5e0c Nichole Nordeman / Woven & Spun"
00231 
00232     // Extract/remove 3 digit status:
00233     uint stat = cddb.left(3).toUInt();
00234     cddb = cddb.mid(4);
00235 
00236     // We should check for errors here, and possibly do a CDDB lookup
00237     // (telnet 8880) if it failed, but Nigel is feeling lazy.
00238 
00239     if (stat == 210 || stat == 211)  // Multiple matches
00240     {
00241         // e.g. 210 Found exact matches, list follows (until terminating `.')
00242         //      folk 9c09590b Michael W Smith / Stand
00243         //      misc 9c09590b Michael W. Smith / Stand
00244         //      rock 9c09590b Michael W. Smith / Stand
00245         //      classical 9c09590b Michael W. Smith / Stand
00246         //      .
00247         // TODO
00248         // Parse disks, put up dialog box, select disk, prune cddb to selected
00249         LOG(VB_MEDIA, LOG_INFO,
00250             "Multiple CDDB matches. Please implement this code");
00251 
00252         // For now, we just choose the first match.
00253         //int EOL = cddb.indexOf('\n');
00254         cddb.remove(0, cddb.indexOf('\n'));
00255         LOG(VB_MEDIA, LOG_INFO, "String now: " + cddb);
00256         stat = 200;
00257     }
00258 
00259     if (stat == 200)  // One unique match
00260     {
00261         QString album;
00262         QString artist;
00263         bool    compn = false;
00264         QString genre = cddb.section(' ', 0, 0);
00265         int     year  = 0;
00266 
00267         // Now we can lookup all its details:
00268         URL2 = URL + "cddb+read+" + genre + "+"
00269                + hexID + "&hello=" + helloID + "&proto=5";
00270         cddb = HttpComms::getHttp(URL2);
00271 #if 0
00272         LOG(VB_MEDIA, LOG_INFO, "CDDB detail: " + URL2);
00273         LOG(VB_MEDIA, LOG_INFO, "...returned: " + cddb);
00274 #endif
00275 
00276         // Successful lookup.
00277         // Clear current titles (filenames), because we append to them
00278         for (trk = 0; trk < totalTracks; ++trk)
00279             m_mData[trk]->setTitle("");
00280 
00281         // Parse returned data:
00282 
00283         cddb.replace(QRegExp(".*#"), "");  // Remove comment block
00284         while (cddb.length())
00285         {
00286             // Lines should be of the form "FIELD=value\r\n"
00287 
00288             QString art;
00289             QString line  = cddb.section(QRegExp("(\r|\n)+"), 0, 0);
00290             QString value = line.section('=', 1, 1);
00291 
00292             if (value.contains(" / "))
00293             {
00294                 art   = value.section(" / ", 0, 0);  // Artist in *TITLE
00295                 value = value.section(" / ", 1, 1);
00296             }
00297 
00298             if (line.startsWith("DGENRE="))
00299                 genre = value;
00300             else if (line.startsWith("DYEAR="))
00301                 year = value.toInt();
00302             else if (line.startsWith("DTITLE="))
00303             {
00304                 // Albums (and maybe artists?) can wrap over multiple lines:
00305                 artist += art;
00306                 album  += value;
00307             }
00308             else if (line.startsWith("TTITLE"))
00309             {
00310                 trk = line.remove("TTITLE").remove(QRegExp("=.*")).toUInt();
00311 
00312                 if (trk < totalTracks)
00313                 {
00314                     Metadata *m = m_mData[trk];
00315 
00316                     // Titles can wrap over multiple lines, so we load+store:
00317                     m->setTitle(m->Title() + value);
00318 
00319                     if (art.length())
00320                     {
00321                         compn = true;  // Probably a compilation
00322 
00323                         m->setArtist(M_QSTRING_UNICODE(art.toUtf8().constData()));
00324                     }
00325                 }
00326                 else
00327                     LOG(VB_GENERAL, LOG_INFO,
00328                         QString("CDDB returned %1 on a %2 track disk!")
00329                             .arg(trk+1).arg(totalTracks));
00330             }
00331 
00332             // Get next THINGY=value line:
00333             cddb = cddb.section('\n', 1, 0xffffffff);
00334         }
00335 
00336         for (trk = 0; trk < totalTracks; ++trk)
00337         {
00338             Metadata *m = m_mData[trk];
00339 
00340             if (compn)
00341                 m->setCompilation(true);
00342 
00343             m->setGenre(M_QSTRING_UNICODE(genre.toUtf8().constData()));
00344 
00345             if (year)
00346                 m->setYear(year);
00347 
00348             if (album.length())
00349                 m->setAlbum(M_QSTRING_UNICODE(album.toUtf8().constData()));
00350 
00351             if (artist.length())
00352                 if (compn)
00353                     m->setCompilationArtist(M_QSTRING_UNICODE(artist.toUtf8().constData()));
00354                 else
00355                     m->setArtist(M_QSTRING_UNICODE(artist.toUtf8().constData()));
00356         }
00357     }
00358 }
00359 
00360 double CdDecoder::lengthInSeconds()
00361 {
00362     return m_lengthInSecs;
00363 }
00364 
00365 void CdDecoder::seek(double pos)
00366 {
00367     (void)pos;
00368 }
00369 
00370 void CdDecoder::stop()
00371 {
00372 }
00373 
00374 void CdDecoder::run()
00375 {
00376 }
00377 
00378 void CdDecoder::deinit()
00379 {
00380     // Free any stored Metadata
00381     for (unsigned int i = 0; i < m_mData.size(); ++i)
00382         delete m_mData[i];
00383     m_mData.clear();
00384 
00385     m_tracks.clear();
00386 
00387     inited = false;
00388 }
00389 
00390 int CdDecoder::getNumTracks(void)
00391 {
00392     if (!inited && !initialize())
00393         return 0;
00394 
00395     return m_lastTrack;
00396 }
00397 
00398 int CdDecoder::getNumCDAudioTracks(void)
00399 {
00400     if (!inited && !initialize())
00401         return 0;
00402 
00403     return m_lastTrack - m_firstTrack + 1;
00404 }
00405 
00406 Metadata* CdDecoder::getMetadata(int track)
00407 {
00408     if (!inited && !initialize())
00409         return NULL;
00410 
00411     if (track < 1 || (uint)track > m_mData.size())
00412     {
00413         LOG(VB_GENERAL, LOG_ERR,
00414             QString("CdDecoder::getMetadata(%1) - track out of range")
00415                 .arg(track));
00416         return NULL;
00417     }
00418 
00419     return new Metadata(*(m_mData[track - 1]));
00420 }
00421 
00422 Metadata *CdDecoder::getLastMetadata()
00423 {
00424     if (!inited && !initialize())
00425         return NULL;
00426 
00427     return new Metadata(*(m_mData[m_mData.size() - 1]));
00428 }
00429 
00430 Metadata *CdDecoder::getMetadata()
00431 {
00432     return NULL;
00433 }
00434 
00435 void CdDecoder::commitMetadata(Metadata *mdata)
00436 {
00437     (void)mdata;
00438 }
00439 
00440 bool CdDecoderFactory::supports(const QString &source) const
00441 {
00442     return (source.right(extension().length()).toLower() == extension());
00443 }
00444 
00445 const QString &CdDecoderFactory::extension() const
00446 {
00447     static QString ext(".aiff");
00448     return ext;
00449 }
00450 
00451 
00452 const QString &CdDecoderFactory::description() const
00453 {
00454     static QString desc(QObject::tr("OSX Audio CD mount parser"));
00455     return desc;
00456 }
00457 
00458 Decoder *CdDecoderFactory::create(const QString &file, QIODevice *input,
00459                                   AudioOutput *output, bool deletable)
00460 {
00461     if (deletable)
00462         return new CdDecoder(file, this, input, output);
00463 
00464     static CdDecoder *decoder = 0;
00465     if (! decoder) {
00466         decoder = new CdDecoder(file, this, input, output);
00467     } else {
00468         decoder->setInput(input);
00469         decoder->setFilename(file);
00470         decoder->setOutput(output);
00471     }
00472 
00473     return decoder;
00474 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends