00001
00002 #include <QCoreApplication>
00003 #include <QEvent>
00004 #include <QDir>
00005 #include <QUrl>
00006
00007
00008 #include "mythcorecontext.h"
00009 #include "mythdirs.h"
00010 #include "mythuihelper.h"
00011 #include "mythsystem.h"
00012 #include "storagegroup.h"
00013 #include "metadatadownload.h"
00014 #include "mythmiscutil.h"
00015 #include "remotefile.h"
00016 #include "mythlogging.h"
00017
00018 QEvent::Type MetadataLookupEvent::kEventType =
00019 (QEvent::Type) QEvent::registerEventType();
00020
00021 QEvent::Type MetadataLookupFailure::kEventType =
00022 (QEvent::Type) QEvent::registerEventType();
00023
00024 MetadataDownload::MetadataDownload(QObject *parent) :
00025 MThread("MetadataDownload")
00026 {
00027 m_parent = parent;
00028 }
00029
00030 MetadataDownload::~MetadataDownload()
00031 {
00032 cancel();
00033 wait();
00034 }
00035
00036 void MetadataDownload::addLookup(MetadataLookup *lookup)
00037 {
00038
00039 m_mutex.lock();
00040 m_lookupList.append(lookup);
00041 if (!isRunning())
00042 start();
00043 m_mutex.unlock();
00044 }
00045
00046 void MetadataDownload::prependLookup(MetadataLookup *lookup)
00047 {
00048
00049 m_mutex.lock();
00050 m_lookupList.prepend(lookup);
00051 if (!isRunning())
00052 start();
00053 m_mutex.unlock();
00054 }
00055
00056 void MetadataDownload::cancel()
00057 {
00058 m_mutex.lock();
00059 qDeleteAll(m_lookupList);
00060 m_lookupList.clear();
00061 m_parent = NULL;
00062 m_mutex.unlock();
00063 }
00064
00065 void MetadataDownload::run()
00066 {
00067 RunProlog();
00068
00069 MetadataLookup* lookup;
00070 while ((lookup = moreWork()) != NULL)
00071 {
00072 MetadataLookupList list;
00073
00074 if (lookup->GetType() == kMetadataVideo)
00075 {
00076 if (lookup->GetSubtype() == kProbableTelevision)
00077 list = handleTelevision(lookup);
00078 else if (lookup->GetSubtype() == kProbableMovie)
00079 list = handleMovie(lookup);
00080 else
00081 list = handleVideoUndetermined(lookup);
00082
00083 if (!list.size() &&
00084 lookup->GetSubtype() == kUnknownVideo)
00085 {
00086 list = handleMovie(lookup);
00087 }
00088 }
00089 else if (lookup->GetType() == kMetadataRecording)
00090 {
00091 if (lookup->GetSubtype() == kProbableTelevision)
00092 {
00093 if (lookup->GetSeason() > 0 || lookup->GetEpisode() > 0)
00094 list = handleTelevision(lookup);
00095 else if (!lookup->GetSubtitle().isEmpty())
00096 list = handleVideoUndetermined(lookup);
00097
00098 if (!list.size())
00099 list = handleRecordingGeneric(lookup);
00100 }
00101 else if (lookup->GetSubtype() == kProbableMovie)
00102 {
00103 list = handleMovie(lookup);
00104 if (lookup->GetInetref().isEmpty())
00105 list.append(handleRecordingGeneric(lookup));
00106 }
00107 else
00108 {
00109 list = handleRecordingGeneric(lookup);
00110 if (lookup->GetInetref().isEmpty())
00111 list.append(handleMovie(lookup));
00112 }
00113 }
00114 else if (lookup->GetType() == kMetadataGame)
00115 list = handleGame(lookup);
00116
00117
00118 if (m_parent && list.count() >= 1)
00119 {
00120
00121
00122
00123 if (list.count() == 1 && list.at(0)->GetStep() == kLookupSearch)
00124 {
00125 MetadataLookup *newlookup = list.takeFirst();
00126 newlookup->SetStep(kLookupData);
00127 prependLookup(newlookup);
00128 continue;
00129 }
00130
00131
00132
00133 if (list.at(0)->GetAutomatic() && list.count() > 1)
00134 {
00135 if (!findBestMatch(list, lookup->GetTitle()))
00136 QCoreApplication::postEvent(m_parent,
00137 new MetadataLookupFailure(MetadataLookupList() << lookup));
00138 continue;
00139 }
00140
00141 LOG(VB_GENERAL, LOG_INFO,
00142 QString("Returning Metadata Results: %1 %2 %3")
00143 .arg(lookup->GetTitle()).arg(lookup->GetSeason())
00144 .arg(lookup->GetEpisode()));
00145 QCoreApplication::postEvent(m_parent,
00146 new MetadataLookupEvent(list));
00147 }
00148 else
00149 {
00150 list.append(lookup);
00151 QCoreApplication::postEvent(m_parent,
00152 new MetadataLookupFailure(list));
00153 }
00154 }
00155
00156 RunEpilog();
00157 }
00158
00159 MetadataLookup* MetadataDownload::moreWork()
00160 {
00161 MetadataLookup* result = NULL;
00162 m_mutex.lock();
00163 if (!m_lookupList.isEmpty())
00164 result = m_lookupList.takeFirst();
00165 m_mutex.unlock();
00166 return result;
00167 }
00168
00169 bool MetadataDownload::findBestMatch(MetadataLookupList list,
00170 QString originaltitle)
00171 {
00172 QStringList titles;
00173
00174
00175 for (MetadataLookupList::const_iterator i = list.begin();
00176 i != list.end(); ++i)
00177 {
00178 titles.append((*i)->GetTitle());
00179 }
00180
00181
00182 QString bestTitle = nearestName(originaltitle, titles);
00183
00184
00185 if (bestTitle.isEmpty())
00186 {
00187 LOG(VB_GENERAL, LOG_ERR,
00188 QString("No adequate match or multiple "
00189 "matches found for %1. Update manually.")
00190 .arg(originaltitle));
00191 return false;
00192 }
00193
00194 LOG(VB_GENERAL, LOG_INFO, QString("Best Title Match For %1: %2")
00195 .arg(originaltitle).arg(bestTitle));
00196
00197
00198 for (MetadataLookupList::const_iterator i = list.begin();
00199 i != list.end(); ++i)
00200 {
00201 if ((*i)->GetTitle() == bestTitle)
00202 {
00203 MetadataLookup *newlookup = (*i);
00204 newlookup->SetStep(kLookupData);
00205
00206 prependLookup(newlookup);
00207 return true;
00208 }
00209 }
00210
00211 return false;
00212 }
00213
00214 MetadataLookupList MetadataDownload::runGrabber(QString cmd, QStringList args,
00215 MetadataLookup* lookup,
00216 bool passseas)
00217 {
00218 MythSystem grabber(cmd, args, kMSNoRunShell | kMSStdOut | kMSBuffered);
00219 MetadataLookupList list;
00220
00221 LOG(VB_GENERAL, LOG_INFO, QString("Running Grabber: %1 %2")
00222 .arg(cmd).arg(args.join(" ")));
00223
00224 grabber.Run();
00225 grabber.Wait();
00226 QByteArray result = grabber.ReadAll();
00227 if (!result.isEmpty())
00228 {
00229 QDomDocument doc;
00230 doc.setContent(result, true);
00231 QDomElement root = doc.documentElement();
00232 QDomElement item = root.firstChildElement("item");
00233
00234 while (!item.isNull())
00235 {
00236 MetadataLookup *tmp = ParseMetadataItem(item, lookup,
00237 passseas);
00238 list.append(tmp);
00239 item = item.nextSiblingElement("item");
00240 }
00241 }
00242 return list;
00243 }
00244
00245 QString MetadataDownload::GetMovieGrabber()
00246 {
00247 QString def_cmd = "metadata/Movie/tmdb.py";
00248 QString db_cmd = gCoreContext->GetSetting("MovieGrabber", def_cmd);
00249
00250 return QDir::cleanPath(QString("%1/%2")
00251 .arg(GetShareDir())
00252 .arg(db_cmd));
00253 }
00254
00255 QString MetadataDownload::GetTelevisionGrabber()
00256 {
00257 QString def_cmd = "metadata/Television/ttvdb.py";
00258 QString db_cmd = gCoreContext->GetSetting("TelevisionGrabber", def_cmd);
00259
00260 return QDir::cleanPath(QString("%1/%2")
00261 .arg(GetShareDir())
00262 .arg(db_cmd));
00263 }
00264
00265 QString MetadataDownload::GetGameGrabber()
00266 {
00267 QString def_cmd = "metadata/Game/giantbomb.py";
00268 QString db_cmd = gCoreContext->GetSetting("mythgame.MetadataGrabber", def_cmd);
00269
00270 return QDir::cleanPath(QString("%1/%2")
00271 .arg(GetShareDir())
00272 .arg(db_cmd));
00273 }
00274
00275 bool MetadataDownload::runGrabberTest(const QString &grabberpath)
00276 {
00277 QStringList args;
00278 args.append("-t");
00279
00280 MythSystem grabber(grabberpath, args, kMSNoRunShell | kMSStdOut | kMSBuffered);
00281
00282 grabber.Run();
00283 uint exitcode = grabber.Wait();
00284
00285 if (exitcode != 0)
00286 return false;
00287
00288 return true;
00289 }
00290
00291 bool MetadataDownload::MovieGrabberWorks()
00292 {
00293 if (!runGrabberTest(GetMovieGrabber()))
00294 {
00295 LOG(VB_GENERAL, LOG_INFO,
00296 QString("Movie grabber not functional. Aborting this run."));
00297 return false;
00298 }
00299
00300 return true;
00301 }
00302
00303 bool MetadataDownload::TelevisionGrabberWorks()
00304 {
00305 if (!runGrabberTest(GetTelevisionGrabber()))
00306 {
00307 LOG(VB_GENERAL, LOG_INFO,
00308 QString("Television grabber not functional. Aborting this run."));
00309 return false;
00310 }
00311
00312 return true;
00313 }
00314
00315 MetadataLookupList MetadataDownload::readMXML(QString MXMLpath,
00316 MetadataLookup* lookup,
00317 bool passseas)
00318 {
00319 MetadataLookupList list;
00320
00321 LOG(VB_GENERAL, LOG_INFO,
00322 QString("Matching MXML file found. Parsing %1 for metadata...")
00323 .arg(MXMLpath));
00324
00325 if (lookup->GetType() == kMetadataVideo)
00326 {
00327 QByteArray mxmlraw;
00328 QDomElement item;
00329 if (MXMLpath.startsWith("myth://"))
00330 {
00331 RemoteFile *rf = new RemoteFile(MXMLpath);
00332 if (rf && rf->Open())
00333 {
00334 bool loaded = rf->SaveAs(mxmlraw);
00335 if (loaded)
00336 {
00337 QDomDocument doc;
00338 if (doc.setContent(mxmlraw, true))
00339 {
00340 lookup->SetStep(kLookupData);
00341 QDomElement root = doc.documentElement();
00342 item = root.firstChildElement("item");
00343 }
00344 else
00345 LOG(VB_GENERAL, LOG_ERR,
00346 QString("Corrupt or invalid MXML file."));
00347 }
00348 rf->Close();
00349 }
00350
00351 delete rf;
00352 rf = NULL;
00353 }
00354 else
00355 {
00356 QFile file(MXMLpath);
00357 if (file.open(QIODevice::ReadOnly))
00358 {
00359 mxmlraw = file.readAll();
00360 QDomDocument doc;
00361 if (doc.setContent(mxmlraw, true))
00362 {
00363 lookup->SetStep(kLookupData);
00364 QDomElement root = doc.documentElement();
00365 item = root.firstChildElement("item");
00366 }
00367 else
00368 LOG(VB_GENERAL, LOG_ERR,
00369 QString("Corrupt or invalid MXML file."));
00370 file.close();
00371 }
00372 }
00373
00374 MetadataLookup *tmp = ParseMetadataItem(item, lookup, passseas);
00375 list.append(tmp);
00376 }
00377
00378 return list;
00379 }
00380
00381 MetadataLookupList MetadataDownload::readNFO(QString NFOpath,
00382 MetadataLookup* lookup)
00383 {
00384 MetadataLookupList list;
00385
00386 LOG(VB_GENERAL, LOG_INFO,
00387 QString("Matching NFO file found. Parsing %1 for metadata...")
00388 .arg(NFOpath));
00389
00390 if (lookup->GetType() == kMetadataVideo)
00391 {
00392 QByteArray nforaw;
00393 QDomElement item;
00394 if (NFOpath.startsWith("myth://"))
00395 {
00396 RemoteFile *rf = new RemoteFile(NFOpath);
00397 if (rf && rf->Open())
00398 {
00399 bool loaded = rf->SaveAs(nforaw);
00400 if (loaded)
00401 {
00402 QDomDocument doc;
00403 if (doc.setContent(nforaw, true))
00404 {
00405 lookup->SetStep(kLookupData);
00406 item = doc.documentElement();
00407 }
00408 else
00409 LOG(VB_GENERAL, LOG_ERR,
00410 QString("PIRATE ERROR: Invalid NFO file found."));
00411 }
00412 rf->Close();
00413 }
00414
00415 delete rf;
00416 rf = NULL;
00417 }
00418 else
00419 {
00420 QFile file(NFOpath);
00421 if (file.open(QIODevice::ReadOnly))
00422 {
00423 nforaw = file.readAll();
00424 QDomDocument doc;
00425 if (doc.setContent(nforaw, true))
00426 {
00427 lookup->SetStep(kLookupData);
00428 item = doc.documentElement();
00429 }
00430 else
00431 LOG(VB_GENERAL, LOG_ERR,
00432 QString("PIRATE ERROR: Invalid NFO file found."));
00433 file.close();
00434 }
00435 }
00436
00437 MetadataLookup *tmp = ParseMetadataMovieNFO(item, lookup);
00438 list.append(tmp);
00439 }
00440
00441 return list;
00442 }
00443
00444 MetadataLookupList MetadataDownload::handleGame(MetadataLookup* lookup)
00445 {
00446 MetadataLookupList list;
00447
00448 QString cmd = GetGameGrabber();
00449
00450 QStringList args;
00451 args.append(QString("-l"));
00452 args.append(gCoreContext->GetLanguage());
00453
00454
00455
00456 if (lookup->GetStep() == kLookupSearch &&
00457 (!lookup->GetInetref().isEmpty() &&
00458 lookup->GetInetref() != "00000000"))
00459 lookup->SetStep(kLookupData);
00460
00461 if (lookup->GetStep() == kLookupSearch)
00462 {
00463 args.append(QString("-M"));
00464 QString title = lookup->GetTitle();
00465 args.append(title);
00466 }
00467 else if (lookup->GetStep() == kLookupData)
00468 {
00469 args.append(QString("-D"));
00470 args.append(lookup->GetInetref());
00471 }
00472 list = runGrabber(cmd, args, lookup);
00473
00474 return list;
00475 }
00476
00477 MetadataLookupList MetadataDownload::handleMovie(MetadataLookup* lookup)
00478 {
00479 MetadataLookupList list;
00480
00481 QString mxml;
00482 QString nfo;
00483
00484 if (!lookup->GetFilename().isEmpty())
00485 {
00486 mxml = getMXMLPath(lookup->GetFilename());
00487 nfo = getNFOPath(lookup->GetFilename());
00488 }
00489
00490 if (mxml.isEmpty() && nfo.isEmpty())
00491 {
00492 QString cmd = GetMovieGrabber();
00493
00494 QStringList args;
00495 args.append(QString("-l"));
00496 args.append(gCoreContext->GetLanguage());
00497
00498
00499
00500 if (lookup->GetStep() == kLookupSearch &&
00501 (!lookup->GetInetref().isEmpty() &&
00502 lookup->GetInetref() != "00000000"))
00503 lookup->SetStep(kLookupData);
00504
00505 if (lookup->GetStep() == kLookupSearch)
00506 {
00507 args.append(QString("-M"));
00508 QString title = lookup->GetTitle();
00509 args.append(title);
00510 }
00511 else if (lookup->GetStep() == kLookupData)
00512 {
00513 args.append(QString("-D"));
00514 args.append(lookup->GetInetref());
00515 }
00516 list = runGrabber(cmd, args, lookup);
00517 }
00518 else if (!mxml.isEmpty())
00519 list = readMXML(mxml, lookup);
00520 else if (!nfo.isEmpty())
00521 list = readNFO(nfo, lookup);
00522
00523 return list;
00524 }
00525
00526 MetadataLookupList MetadataDownload::handleTelevision(MetadataLookup* lookup)
00527 {
00528 MetadataLookupList list;
00529
00530 QString cmd = GetTelevisionGrabber();
00531
00532 QStringList args;
00533 args.append(QString("-l"));
00534 args.append(gCoreContext->GetLanguage());
00535
00536
00537
00538 if (lookup->GetStep() == kLookupSearch &&
00539 (!lookup->GetInetref().isEmpty() &&
00540 lookup->GetInetref() != "00000000"))
00541 lookup->SetStep(kLookupData);
00542
00543 if (lookup->GetStep() == kLookupSearch)
00544 {
00545 args.append(QString("-M"));
00546 if (lookup->GetInetref().isEmpty() ||
00547 lookup->GetInetref() == "00000000")
00548 {
00549 QString title = lookup->GetTitle();
00550 args.append(title);
00551 }
00552 else
00553 {
00554 QString inetref = lookup->GetInetref();
00555 args.append(inetref);
00556 }
00557 }
00558 else if (lookup->GetStep() == kLookupData)
00559 {
00560 args.append(QString("-D"));
00561 args.append(lookup->GetInetref());
00562 args.append(QString::number(lookup->GetSeason()));
00563 args.append(QString::number(lookup->GetEpisode()));
00564 }
00565 else if (lookup->GetStep() == kLookupCollection)
00566 {
00567 args.append(QString("-C"));
00568 args.append(lookup->GetCollectionref());
00569 }
00570 list = runGrabber(cmd, args, lookup);
00571
00572
00573
00574
00575 if (list.isEmpty() &&
00576 lookup->GetAllowGeneric() &&
00577 lookup->GetStep() == kLookupData)
00578 {
00579 lookup->SetStep(kLookupCollection);
00580 list = handleTelevision(lookup);
00581 }
00582
00583 return list;
00584 }
00585
00586 MetadataLookupList MetadataDownload::handleVideoUndetermined(
00587 MetadataLookup* lookup)
00588 {
00589 MetadataLookupList list;
00590
00591 QString cmd = GetTelevisionGrabber();
00592
00593
00594
00595 QStringList args;
00596 args.append(QString("-l"));
00597 args.append(gCoreContext->GetLanguage());
00598 args.append(QString("-N"));
00599 if (!lookup->GetInetref().isEmpty())
00600 {
00601 QString inetref = lookup->GetInetref();
00602 args.append(inetref);
00603 }
00604 else
00605 {
00606 QString title = lookup->GetTitle();
00607 args.append(title);
00608 }
00609 QString subtitle = lookup->GetSubtitle();
00610 args.append(subtitle);
00611
00612
00613 list = runGrabber(cmd, args, lookup, false);
00614
00615 if (list.count() == 1)
00616 list.at(0)->SetStep(kLookupData);
00617
00618 return list;
00619 }
00620
00621 MetadataLookupList MetadataDownload::handleRecordingGeneric(
00622 MetadataLookup* lookup)
00623 {
00624
00625
00626
00627
00628
00629 MetadataLookupList list;
00630
00631 QString cmd = GetTelevisionGrabber();
00632
00633 QStringList args;
00634
00635 args.append(QString("-l"));
00636 args.append(gCoreContext->GetLanguage());
00637 args.append("-M");
00638 QString title = lookup->GetTitle();
00639 args.append(title);
00640 LookupType origtype = lookup->GetSubtype();
00641 int origseason = lookup->GetSeason();
00642 int origepisode = lookup->GetEpisode();
00643
00644 lookup->SetSubtype(kProbableGenericTelevision);
00645
00646 if (origseason == 0 && origepisode == 0)
00647 {
00648 lookup->SetSeason(1);
00649 lookup->SetEpisode(1);
00650 }
00651
00652 list = runGrabber(cmd, args, lookup, true);
00653
00654 if (list.count() == 1)
00655 {
00656 lookup->SetInetref(list.at(0)->GetInetref());
00657 lookup->SetCollectionref(list.at(0)->GetCollectionref());
00658 list = handleTelevision(lookup);
00659 }
00660
00661 lookup->SetSeason(origseason);
00662 lookup->SetEpisode(origepisode);
00663 lookup->SetSubtype(origtype);
00664
00665 return list;
00666 }
00667
00668 QString MetadataDownload::getMXMLPath(QString filename)
00669 {
00670 QString ret;
00671 QString xmlname;
00672 QUrl qurl(filename);
00673 QString ext = QFileInfo(qurl.path()).suffix();
00674 xmlname = filename.left(filename.size() - ext.size()) + "mxml";
00675 QUrl xurl(xmlname);
00676
00677 if (xmlname.startsWith("myth://"))
00678 {
00679 if (qurl.host().toLower() != gCoreContext->GetHostName().toLower() &&
00680 (!gCoreContext->IsThisHost(qurl.host())))
00681 {
00682 if (RemoteFile::Exists(xmlname))
00683 ret = xmlname;
00684 }
00685 else
00686 {
00687 StorageGroup sg;
00688 QString fn = sg.FindFile(xurl.path());
00689 if (!fn.isEmpty() && QFile::exists(fn))
00690 ret = xmlname;
00691 }
00692 }
00693 else
00694 {
00695 if (QFile::exists(xmlname))
00696 ret = xmlname;
00697 }
00698
00699 return ret;
00700 }
00701
00702 QString MetadataDownload::getNFOPath(QString filename)
00703 {
00704 QString ret;
00705 QString nfoname;
00706 QUrl qurl(filename);
00707 QString ext = QFileInfo(qurl.path()).suffix();
00708 nfoname = filename.left(filename.size() - ext.size()) + "nfo";
00709 QUrl nurl(nfoname);
00710
00711 if (nfoname.startsWith("myth://"))
00712 {
00713 if (qurl.host().toLower() != gCoreContext->GetHostName().toLower() &&
00714 (!gCoreContext->IsThisHost(qurl.host())))
00715 {
00716 if (RemoteFile::Exists(nfoname))
00717 ret = nfoname;
00718 }
00719 else
00720 {
00721 StorageGroup sg;
00722 QString fn = sg.FindFile(nurl.path());
00723 if (!fn.isEmpty() && QFile::exists(fn))
00724 ret = nfoname;
00725 }
00726 }
00727 else
00728 {
00729 if (QFile::exists(nfoname))
00730 ret = nfoname;
00731 }
00732
00733 return ret;
00734 }