|
MythTV
0.25-pre
|
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 }
1.7.6.1