MythTV master
metadatadownload.cpp
Go to the documentation of this file.
1// C/C++
2#include <cstdlib>
3
4// qt
5#include <QCoreApplication>
6#include <QEvent>
7#include <QDir>
8#include <QUrl>
9#include <QRegularExpression>
10
11// myth
19
20#include "metadatadownload.h"
21#include "metadatafactory.h"
22
23const QEvent::Type MetadataLookupEvent::kEventType =
24 (QEvent::Type) QEvent::registerEventType();
25
26const QEvent::Type MetadataLookupFailure::kEventType =
27 (QEvent::Type) QEvent::registerEventType();
28
30{
31 cancel();
32 wait();
33}
34
40{
41 // Add a lookup to the queue
42 QMutexLocker lock(&m_mutex);
43
44 m_lookupList.append(lookup);
45 lookup->DecrRef();
46 if (!isRunning())
47 start();
48}
49
55{
56 // Add a lookup to the queue
57 QMutexLocker lock(&m_mutex);
58
59 m_lookupList.prepend(lookup);
60 lookup->DecrRef();
61 if (!isRunning())
62 start();
63}
64
66{
67 QMutexLocker lock(&m_mutex);
68
69 m_lookupList.clear();
70 m_parent = nullptr;
71}
72
74{
75 RunProlog();
76
77 while (true)
78 {
79 m_mutex.lock();
80 if (m_lookupList.isEmpty())
81 {
82 // no more to process, we're done
83 m_mutex.unlock();
84 break;
85 }
86 // Ref owns the MetadataLookup object for the duration of the loop
87 // and it will be deleted automatically when the loop completes
89 m_mutex.unlock();
90 MetadataLookup *lookup = ref;
92
93 // Go go gadget Metadata Lookup
94 if (lookup->GetType() == kMetadataVideo ||
95 lookup->GetType() == kMetadataRecording)
96 {
97 // First, look for mxml and nfo files in video storage groups
98 if (lookup->GetType() == kMetadataVideo &&
99 !lookup->GetFilename().isEmpty())
100 {
101 QString mxml = getMXMLPath(lookup->GetFilename());
102 QString nfo = getNFOPath(lookup->GetFilename());
103
104 if (!mxml.isEmpty())
105 list = readMXML(mxml, lookup);
106 else if (!nfo.isEmpty())
107 list = readNFO(nfo, lookup);
108 }
109
110 // If nothing found, create lookups based on filename
111 if (list.isEmpty())
112 {
113 if (lookup->GetSubtype() == kProbableTelevision)
114 {
115 list = handleTelevision(lookup);
116 if ((findExactMatchCount(list, lookup->GetBaseTitle(), true) == 0) ||
117 (list.size() > 1 && !lookup->GetAutomatic()))
118 {
119 // There are no exact match prospects with artwork from TV search,
120 // so add in movies, where we might find a better match.
121 // In case of manual mode and ambiguous result, add it as well.
122 list.append(handleMovie(lookup));
123 }
124 }
125 else if (lookup->GetSubtype() == kProbableMovie)
126 {
127 list = handleMovie(lookup);
128 if ((findExactMatchCount(list, lookup->GetBaseTitle(), true) == 0) ||
129 (list.size() > 1 && !lookup->GetAutomatic()))
130 {
131 // There are no exact match prospects with artwork from Movie search
132 // so add in television, where we might find a better match.
133 // In case of manual mode and ambiguous result, add it as well.
134 list.append(handleTelevision(lookup));
135 }
136 }
137 else
138 {
139 // will try both movie and TV
140 list = handleVideoUndetermined(lookup);
141 }
142 }
143 }
144 else if (lookup->GetType() == kMetadataGame)
145 {
146 list = handleGame(lookup);
147 }
148
149 // inform parent we have lookup ready for it
150 if (m_parent && !list.isEmpty())
151 {
152 // If there's only one result, don't bother asking
153 // our parent about it, just add it to the back of
154 // the queue in kLookupData mode.
155 if (list.count() == 1 && list[0]->GetStep() == kLookupSearch)
156 {
157 MetadataLookup *newlookup = list.takeFirst();
158
159 newlookup->SetStep(kLookupData);
160 prependLookup(newlookup);
161 // Type may have changed
162 LookupType ret = GuessLookupType(newlookup);
163 if (ret != kUnknownVideo)
164 {
165 newlookup->SetSubtype(ret);
166 }
167 continue;
168 }
169
170 // If we're in automatic mode, we need to make
171 // these decisions on our own. Pass to title match.
172 if (list[0]->GetAutomatic() && list.count() > 1
173 && list[0]->GetStep() == kLookupSearch)
174 {
175 MetadataLookup *bestLookup = findBestMatch(list, lookup->GetBaseTitle());
176 if (bestLookup)
177 {
178 MetadataLookup *newlookup = bestLookup;
179
180 // pass through automatic type
181 newlookup->SetAutomatic(true);
182 // bestlookup is owned by list, we need an extra reference
183 newlookup->IncrRef();
184 newlookup->SetStep(kLookupData);
185 // Type may have changed
186 LookupType ret = GuessLookupType(newlookup);
187 if (ret != kUnknownVideo)
188 {
189 newlookup->SetSubtype(ret);
190 }
191 prependLookup(newlookup);
192 continue;
193 }
194
195 // Experimental:
196 // If nothing matches, always return the first found item
197 if (qEnvironmentVariableIsSet("EXPERIMENTAL_METADATA_GRAB"))
198 {
199 MetadataLookup *newlookup = list.takeFirst();
200
201 // pass through automatic type
202 newlookup->SetAutomatic(true);
203 newlookup->SetStep(kLookupData);
204 // Type may have changed
205 LookupType ret = GuessLookupType(newlookup);
206 if (ret != kUnknownVideo)
207 {
208 newlookup->SetSubtype(ret);
209 }
210 prependLookup(newlookup);
211 continue;
212 }
213
214 // nothing more we can do in automatic mode
215 QCoreApplication::postEvent(m_parent,
217 continue;
218 }
219
220 LOG(VB_GENERAL, LOG_INFO,
221 QString("Returning Metadata Results: %1 %2 %3")
222 .arg(lookup->GetBaseTitle()).arg(lookup->GetSeason())
223 .arg(lookup->GetEpisode()));
224 QCoreApplication::postEvent(m_parent,
225 new MetadataLookupEvent(list));
226 }
227 else
228 {
229 if (list.isEmpty())
230 {
231 LOG(VB_GENERAL, LOG_INFO,
232 QString("Metadata Lookup Failed: No Results %1 %2 %3")
233 .arg(lookup->GetBaseTitle()).arg(lookup->GetSeason())
234 .arg(lookup->GetEpisode()));
235 }
236 if (m_parent)
237 {
238 // list is always empty here
239 list.append(lookup);
240 QCoreApplication::postEvent(m_parent,
241 new MetadataLookupFailure(list));
242 }
243 }
244 }
245
246 RunEpilog();
247}
248
250 const QString &originaltitle,
251 bool withArt)
252{
253 unsigned int exactMatches = 0;
254 unsigned int exactMatchesWithArt = 0;
255 static const QRegularExpression year { R"( \‍(\d{4}\)$)" };
256
257 for (const auto& lkup : std::as_const(list))
258 {
259 // Consider exact title matches with or without trailing '(year)' (ignoring case)
260 QString titlewoyear = originaltitle;
261 auto match = year.match(titlewoyear);
262 if (match.hasMatch())
263 {
264 titlewoyear.remove(match.capturedStart(), match.capturedLength());
265 }
266
267 if ((QString::compare(lkup->GetTitle(), originaltitle, Qt::CaseInsensitive) == 0) ||
268 (QString::compare(lkup->GetTitle(), titlewoyear, Qt::CaseInsensitive) == 0))
269 {
270 // In lookup by name, the television database tends to only include Banner artwork.
271 // In lookup by name, the movie database tends to include only Fan and Cover artwork.
272 if ((!(lkup->GetArtwork(kArtworkFanart)).empty()) ||
273 (!(lkup->GetArtwork(kArtworkCoverart)).empty()) ||
274 (!(lkup->GetArtwork(kArtworkBanner)).empty()))
275 {
276 exactMatchesWithArt++;
277 }
278 exactMatches++;
279 }
280 }
281
282 if (withArt)
283 return exactMatchesWithArt;
284 return exactMatches;
285}
286
288 const QString &originaltitle)
289{
290 QStringList titles;
291 MetadataLookup *ret = nullptr;
292 QDate exactTitleDate;
293 float exactTitlePopularity = 0.0F;
294 int exactMatches = 0;
295 int exactMatchesWithArt = 0;
296 bool foundMatchWithArt = false;
297 bool foundMatchWithYear = false;
298 uint year = 0;
299
300 QString titlewoyear = originaltitle;
301
302 static const QRegularExpression regexyear { R"( \‍(\d{4}\)$)" };
303
304 auto match = regexyear.match(titlewoyear);
305 if (match.hasMatch())
306 {
307 titlewoyear.remove(match.capturedStart(), match.capturedLength());
308 year = match.captured(0).replace(" (","").replace(")","").toUInt();
309 LOG(VB_GENERAL, LOG_DEBUG, QString("Looking for: '%1' with release year: '%2'")
310 .arg(titlewoyear, QString::number(year)));
311 }
312
313 // Build a list of all the titles
314 titles.reserve(list.size());
315 for (const auto& lkup : std::as_const(list))
316 {
317 QString title = lkup->GetTitle();
318 LOG(VB_GENERAL, LOG_INFO,
319 QString("Comparing metadata title '%1' [%2] to recording title '%3' [%4]")
320 .arg(title, lkup->GetReleaseDate().toString(), titlewoyear,
321 (year == 0) ? "N/A" : QString::number(year)));
322
323 // Consider exact title matches with or without trailing '(year)' (ignoring case),
324 // which have some artwork available.
325 if ((QString::compare(title, originaltitle, Qt::CaseInsensitive) == 0) ||
326 (QString::compare(title, titlewoyear, Qt::CaseInsensitive) == 0))
327 {
328 bool hasArtwork = ((!(lkup->GetArtwork(kArtworkFanart)).empty()) ||
329 (!(lkup->GetArtwork(kArtworkCoverart)).empty()) ||
330 (!(lkup->GetArtwork(kArtworkBanner)).empty()));
331
332 if ((lkup->GetYear() != 0) && (year == lkup->GetYear()))
333 {
334 exactTitleDate = lkup->GetReleaseDate();
335 exactTitlePopularity = lkup->GetPopularity();
336 foundMatchWithYear = true;
337 ret = lkup;
338 }
339
340 LOG(VB_GENERAL, LOG_INFO, QString("'%1', popularity = %2, ReleaseDate = %3, Year = %4")
341 .arg(title)
342 .arg(lkup->GetPopularity())
343 .arg(lkup->GetReleaseDate().toString())
344 .arg(lkup->GetYear()));
345
346 // After the first exact match, prefer any more popular one.
347 // Most of the Movie database entries have Popularity fields.
348 // The TV series database generally has no Popularity values specified,
349 // so if none are found so far in the search, pick the most recently
350 // released entry with artwork. Also, if the first exact match had
351 // no artwork, prefer any later exact match with artwork.
352 // Stop searching if we have already found a match with correct year.
353 if ((ret == nullptr) ||
354 (hasArtwork && !foundMatchWithYear &&
355 ((!foundMatchWithArt) ||
356 (lkup->GetPopularity() > exactTitlePopularity) ||
357 ((exactTitlePopularity == 0.0F) && (lkup->GetReleaseDate() > exactTitleDate)))))
358 {
359 exactTitleDate = lkup->GetReleaseDate();
360 exactTitlePopularity = lkup->GetPopularity();
361 ret = lkup;
362 }
363
364 exactMatches++;
365 if (hasArtwork)
366 {
367 foundMatchWithArt = true;
368 exactMatchesWithArt++;
369 }
370 }
371
372 titles.append(title);
373 }
374
375 LOG(VB_GENERAL, LOG_DEBUG, QString("exactMatches = %1, exactMatchesWithArt = %2")
376 .arg(exactMatches)
377 .arg(exactMatchesWithArt));
378
379 // If there was one or more exact matches then we can skip a more intensive
380 // and time consuming search
381 if (exactMatches > 0)
382 {
383 if (exactMatches == 1)
384 {
385 LOG(VB_GENERAL, LOG_INFO, QString("Single exact title match for '%1'")
386 .arg(originaltitle));
387 }
388 else
389 {
390 LOG(VB_GENERAL, LOG_INFO,
391 QString("Multiple exact title matches found for '%1'. "
392 "Selecting by exact year [%2] or most popular or most recent [%3]")
393 .arg(originaltitle,
394 (year == 0) ? "N/A" : QString::number(year),
395 exactTitleDate.toString()));
396 }
397 return ret;
398 }
399
400 // Apply Levenshtein distance algorithm to determine closest match
401 QString bestTitle = nearestName(originaltitle, titles);
402
403 // If no "best" was chosen, give up.
404 if (bestTitle.isEmpty())
405 {
406 LOG(VB_GENERAL, LOG_ERR,
407 QString("No adequate match or multiple "
408 "matches found for %1. Update manually.")
409 .arg(originaltitle));
410 return nullptr;
411 }
412
413 LOG(VB_GENERAL, LOG_INFO, QString("Best Title Match For %1: %2")
414 .arg(originaltitle, bestTitle));
415
416 // Grab the one item that matches the besttitle (IMPERFECT)
417 for (const auto& item : std::as_const(list))
418 {
419 if (item->GetTitle() == bestTitle)
420 {
421 ret = item;
422 break;
423 }
424 }
425
426 return ret;
427}
428
429MetadataLookupList MetadataDownload::runGrabber(const QString& cmd, const QStringList& args,
430 MetadataLookup *lookup,
431 bool passseas)
432{
433 MythSystemLegacy grabber(cmd, args, kMSStdOut);
435
436 LOG(VB_GENERAL, LOG_INFO, QString("Running Grabber: %1 %2")
437 .arg(cmd, args.join(" ")));
438
439 grabber.Run();
440 grabber.Wait();
441 QByteArray result = grabber.ReadAll();
442 if (!result.isEmpty())
443 {
444 QDomDocument doc;
445#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
446 doc.setContent(result, true);
447#else
448 doc.setContent(result, QDomDocument::ParseOption::UseNamespaceProcessing);
449#endif
450 QDomElement root = doc.documentElement();
451 QDomElement item = root.firstChildElement("item");
452
453 while (!item.isNull())
454 {
455 MetadataLookup *tmp = ParseMetadataItem(item, lookup, passseas);
456 list.append(tmp);
457 // MetadataLookup is to be owned by list
458 tmp->DecrRef();
459 item = item.nextSiblingElement("item");
460 }
461 }
462 return list;
463}
464
466{
468}
469
471{
473}
474
476{
478}
479
480bool MetadataDownload::runGrabberTest(const QString &grabberpath)
481{
482 return MetaGrabberScript(grabberpath).Test();
483}
484
486{
488 {
489 LOG(VB_GENERAL, LOG_INFO,
490 QString("Movie grabber not functional. Aborting this run."));
491 return false;
492 }
493
494 return true;
495}
496
498{
500 {
501 LOG(VB_GENERAL, LOG_INFO,
502 QString("Television grabber not functional. Aborting this run."));
503 return false;
504 }
505
506 return true;
507}
508
510 MetadataLookup *lookup,
511 bool passseas)
512{
514
515 LOG(VB_GENERAL, LOG_INFO,
516 QString("Matching MXML file found. Parsing %1 for metadata...")
517 .arg(MXMLpath));
518
519 if (lookup->GetType() == kMetadataVideo)
520 {
521 QByteArray mxmlraw;
522 QDomElement item;
523 auto *rf = new RemoteFile(MXMLpath);
524
525 if (rf->isOpen())
526 {
527 bool loaded = rf->SaveAs(mxmlraw);
528 if (loaded)
529 {
530 QDomDocument doc;
531#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
532 bool success = doc.setContent(mxmlraw, true);
533#else
534 auto parseResult = doc.setContent(mxmlraw, QDomDocument::ParseOption::UseNamespaceProcessing);
535 bool success { parseResult };
536#endif
537 if (!success)
538 {
539 lookup->SetStep(kLookupData);
540 QDomElement root = doc.documentElement();
541 item = root.firstChildElement("item");
542 }
543 else
544 {
545 LOG(VB_GENERAL, LOG_ERR,
546 QString("Corrupt or invalid MXML file."));
547 }
548 }
549 }
550
551 delete rf;
552 rf = nullptr;
553
554 MetadataLookup *tmp = ParseMetadataItem(item, lookup, passseas);
555 list.append(tmp);
556 // MetadataLookup is owned by the MetadataLookupList returned
557 tmp->DecrRef();
558 }
559
560 return list;
561}
562
564 MetadataLookup *lookup)
565{
567
568 LOG(VB_GENERAL, LOG_INFO,
569 QString("Matching NFO file found. Parsing %1 for metadata...")
570 .arg(NFOpath));
571
572 bool error = false;
573
574 if (lookup->GetType() == kMetadataVideo)
575 {
576 QByteArray nforaw;
577 QDomElement item;
578 auto *rf = new RemoteFile(NFOpath);
579
580 if (rf->isOpen())
581 {
582 bool loaded = rf->SaveAs(nforaw);
583
584 if (loaded)
585 {
586 QDomDocument doc;
587
588#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
589 bool success = doc.setContent(nforaw, true);
590#else
591 auto parseResult = doc.setContent(nforaw, QDomDocument::ParseOption::UseNamespaceProcessing);
592 bool success { parseResult };
593#endif
594 if (success)
595 {
596 lookup->SetStep(kLookupData);
597 item = doc.documentElement();
598 }
599 else
600 {
601 LOG(VB_GENERAL, LOG_ERR,
602 QString("Invalid NFO file found."));
603 error = true;
604 }
605 }
606 }
607
608 delete rf;
609 rf = nullptr;
610
611 if (!error)
612 {
613 MetadataLookup *tmp = ParseMetadataMovieNFO(item, lookup);
614
615 list.append(tmp);
616 // MetadataLookup is owned by the MetadataLookupList returned
617 tmp->DecrRef();
618 }
619 }
620
621 return list;
622}
623
625{
627 MetaGrabberScript grabber =
629 if (!grabber.IsValid())
630 return {};
631
632 // If the inetref is populated, even in kLookupSearch mode,
633 // become a kLookupData grab and use that.
634 if (lookup->GetStep() == kLookupSearch &&
635 (!lookup->GetInetref().isEmpty() &&
636 lookup->GetInetref() != "00000000"))
637 {
638 lookup->SetStep(kLookupData);
639 }
640
641 if (lookup->GetStep() == kLookupSearch)
642 {
643 if (lookup->GetTitle().isEmpty())
644 {
645 // no point searching on nothing...
646 return list;
647 }
648 // we're searching
649 list = grabber.Search(lookup->GetTitle(), lookup);
650 }
651 else if (lookup->GetStep() == kLookupData)
652 {
653 // we're just grabbing data
654 list = grabber.LookupData(lookup->GetInetref(), lookup);
655 }
656
657 return list;
658}
659
669{
671
672 MetaGrabberScript grabber =
674 if (!grabber.IsValid())
675 return {};
676
677 // initial search mode
678 if (!lookup->GetInetref().isEmpty() && lookup->GetInetref() != "00000000" &&
679 (lookup->GetStep() == kLookupSearch || lookup->GetStep() == kLookupData))
680 {
681 // with inetref
682 lookup->SetStep(kLookupData);
683 // we're just grabbing data
684 list = grabber.LookupData(lookup->GetInetref(), lookup);
685 }
686 else if (lookup->GetStep() == kLookupSearch)
687 {
688 if (lookup->GetBaseTitle().isEmpty())
689 {
690 // no point searching on nothing...
691 return list;
692 }
693 list = grabber.Search(lookup->GetBaseTitle(), lookup);
694 }
695
696 return list;
697}
698
711{
713
714 MetaGrabberScript grabber =
716 if (!grabber.IsValid())
717 return {};
718 bool searchcollection = false;
719
720 // initial search mode
721 if (!lookup->GetInetref().isEmpty() && lookup->GetInetref() != "00000000" &&
722 (lookup->GetStep() == kLookupSearch || lookup->GetStep() == kLookupData))
723 {
724 // with inetref
725 lookup->SetStep(kLookupData);
726 if (lookup->GetSeason() || lookup->GetEpisode())
727 {
728 list = grabber.LookupData(lookup->GetInetref(), lookup->GetSeason(),
729 lookup->GetEpisode(), lookup);
730 }
731
732 if (list.isEmpty() && (!lookup->GetSubtitle().isEmpty()))
733 {
734 list = grabber.SearchSubtitle(lookup->GetInetref(),
735 lookup->GetBaseTitle() /* unused */,
736 lookup->GetSubtitle(), lookup, false);
737 }
738
739 if (list.isEmpty() && !lookup->GetCollectionref().isEmpty())
740 {
741 list = grabber.LookupCollection(lookup->GetCollectionref(), lookup);
742 searchcollection = true;
743 }
744 else if (list.isEmpty())
745 {
746 // We do not store CollectionRef in our database
747 // so try with the inetref, for all purposes with TVDB, they are
748 // always identical
749 list = grabber.LookupCollection(lookup->GetInetref(), lookup);
750 searchcollection = true;
751 }
752 }
753 else if (lookup->GetStep() == kLookupSearch)
754 {
755 if (lookup->GetBaseTitle().isEmpty())
756 {
757 // no point searching on nothing...
758 return list;
759 }
760 if (!lookup->GetSubtitle().isEmpty())
761 {
762 list = grabber.SearchSubtitle(lookup->GetBaseTitle(),
763 lookup->GetSubtitle(), lookup, false);
764 }
765 if (list.isEmpty())
766 {
767 list = grabber.Search(lookup->GetBaseTitle(), lookup);
768 }
769 }
770 else if (lookup->GetStep() == kLookupCollection)
771 {
772 list = grabber.LookupCollection(lookup->GetCollectionref(), lookup);
773 }
774
775 // Collection Fallback
776 // If the lookup allows generic metadata, and the specific
777 // season and episode are not available, try for series metadata.
778 if (!searchcollection && list.isEmpty() &&
779 !lookup->GetCollectionref().isEmpty() &&
780 lookup->GetAllowGeneric() && lookup->GetStep() == kLookupData)
781 {
782 lookup->SetStep(kLookupCollection);
783 list = grabber.LookupCollection(lookup->GetCollectionref(), lookup);
784 }
785
786 if (!list.isEmpty())
787 {
788 // mark all results so that search collection is properly handled later
789 lookup->SetIsCollection(searchcollection);
790 // NOLINTNEXTLINE(modernize-loop-convert)
791 for (auto it = list.begin(); it != list.end(); ++it)
792 {
793 (*it)->SetIsCollection(searchcollection);
794 }
795 }
796
797 return list;
798}
799
801{
803
804 if (lookup->GetSubtype() != kProbableMovie &&
805 !lookup->GetSubtitle().isEmpty())
806 {
807 list.append(handleTelevision(lookup));
808 }
809
810 if (lookup->GetSubtype() != kProbableTelevision)
811 {
812 list.append(handleMovie(lookup));
813 }
814
815 if (list.count() == 1)
816 {
817 list[0]->SetStep(kLookupData);
818 }
819
820 return list;
821}
822
824{
825 // We only enter this mode if we are pretty darn sure this is a TV show,
826 // but we're for some reason looking up a generic, or the title didn't
827 // exactly match in one of the earlier lookups. This is a total
828 // hail mary to try to get at least *series* level info and art/inetref.
829
831
832 if (lookup->GetBaseTitle().isEmpty())
833 {
834 // no point searching on nothing...
835 return list;
836 }
837
838 // no inetref known, just pull the default grabber
840
841 // cache some initial values so we can change them in the lookup later
842 LookupType origtype = lookup->GetSubtype();
843 int origseason = lookup->GetSeason();
844 int origepisode = lookup->GetEpisode();
845
846 if (origseason == 0 && origepisode == 0)
847 {
848 lookup->SetSeason(1);
849 lookup->SetEpisode(1);
850 }
851
852 list = grabber.Search(lookup->GetBaseTitle(), lookup);
853
854 if (list.count() == 1)
855 {
856 // search was successful, rerun as normal television mode
857 lookup->SetInetref(list[0]->GetInetref());
858 lookup->SetCollectionref(list[0]->GetCollectionref());
859 list = handleTelevision(lookup);
860 }
861
862 lookup->SetSeason(origseason);
863 lookup->SetEpisode(origepisode);
864 lookup->SetSubtype(origtype);
865
866 return list;
867}
868
869static QString getNameWithExtension(const QString &filename, const QString &type)
870{
871 QString ret;
872 QString newname;
873 QUrl qurl(filename);
874 QString ext = QFileInfo(qurl.path()).suffix();
875
876 if (ext.isEmpty())
877 {
878 // no extension, assume it is a directory
879 newname = filename + "/" + QFileInfo(qurl.path()).fileName() + "." + type;
880 }
881 else
882 {
883 newname = filename.left(filename.size() - ext.size()) + type;
884 }
885
886 if (RemoteFile::Exists(newname))
887 ret = newname;
888
889 return ret;
890}
891
893{
894 return getNameWithExtension(filename, "mxml");
895}
896
898{
899 return getNameWithExtension(filename, "nfo");
900}
bool isRunning(void) const
Definition: mthread.cpp:247
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:180
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:267
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:193
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:284
MetadataLookupList LookupData(const QString &inetref, MetadataLookup *lookup, bool passseas=true)
static MetaGrabberScript GetGrabber(GrabberType defaultType, const MetadataLookup *lookup=nullptr)
bool IsValid(void) const
GrabberType GetType(void) const
MetadataLookupList SearchSubtitle(const QString &title, const QString &subtitle, MetadataLookup *lookup, bool passseas=true)
MetadataLookupList LookupCollection(const QString &collectionref, MetadataLookup *lookup, bool passseas=true)
MetadataLookupList Search(const QString &title, MetadataLookup *lookup, bool passseas=true)
static MetadataLookupList handleTelevision(MetadataLookup *lookup)
handleTelevision attempt to find television data via the following (in order) 1- Local MXML: already ...
MetadataLookupList m_lookupList
static MetadataLookupList handleGame(MetadataLookup *lookup)
static MetadataLookupList readNFO(const QString &NFOpath, MetadataLookup *lookup)
static QString getMXMLPath(const QString &filename)
void addLookup(MetadataLookup *lookup)
addLookup: Add lookup to bottom of the queue MetadataDownload::m_lookupList takes ownership of the gi...
static MetadataLookup * findBestMatch(MetadataLookupList list, const QString &originaltitle)
static MetadataLookupList handleRecordingGeneric(MetadataLookup *lookup)
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
static QString getNFOPath(const QString &filename)
static unsigned int findExactMatchCount(MetadataLookupList list, const QString &originaltitle, bool withArt)
static MetadataLookupList handleVideoUndetermined(MetadataLookup *lookup)
static QString GetGameGrabber()
static QString GetTelevisionGrabber()
static bool MovieGrabberWorks()
static MetadataLookupList handleMovie(MetadataLookup *lookup)
handleMovie: attempt to find movie data via the following (in order) 1- Local MXML: already done befo...
static MetadataLookupList runGrabber(const QString &cmd, const QStringList &args, MetadataLookup *lookup, bool passseas=true)
static bool runGrabberTest(const QString &grabberpath)
static MetadataLookupList readMXML(const QString &MXMLpath, MetadataLookup *lookup, bool passseas=true)
~MetadataDownload() override
void prependLookup(MetadataLookup *lookup)
prependLookup: Add lookup to top of the queue MetadataDownload::m_lookupList takes ownership of the g...
static QString GetMovieGrabber()
static bool TelevisionGrabberWorks()
static const Type kEventType
static const Type kEventType
uint GetSeason() const
void SetSubtype(LookupType subtype)
MetadataType GetType() const
QString GetCollectionref() const
void SetStep(LookupStep step)
void SetInetref(const QString &inetref)
QString GetBaseTitle() const
void SetEpisode(uint episode)
QString GetSubtitle() const
bool GetAutomatic() const
QString GetFilename() const
QString GetTitle() const
LookupStep GetStep() const
void SetIsCollection(bool collection)
LookupType GetSubtype() const
void SetAutomatic(bool autom)
QString GetInetref() const
void SetSeason(uint season)
void SetCollectionref(const QString &collectionref)
bool GetAllowGeneric() const
uint GetEpisode() const
uint Wait(std::chrono::seconds timeout=0s)
void Run(std::chrono::seconds timeout=0s)
Runs a command inside the /bin/sh shell. Returns immediately.
QByteArray & ReadAll()
RefCountHandler< T > takeFirstAndDecr(void)
Removes the first item in the list and returns it.
T * takeFirst(void)
Removes the first item in the list and returns it.
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
virtual int IncrRef(void)
Increments reference count.
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:464
unsigned int uint
Definition: compat.h:60
MetadataLookup * ParseMetadataItem(const QDomElement &item, MetadataLookup *lookup, bool passseas)
MetadataLookup * ParseMetadataMovieNFO(const QDomElement &item, MetadataLookup *lookup)
QString nearestName(const QString &actual, const QStringList &candidates)
@ kLookupCollection
@ kLookupData
@ kLookupSearch
LookupType
@ kProbableTelevision
@ kUnknownVideo
@ kProbableMovie
RefCountedList< MetadataLookup > MetadataLookupList
@ kMetadataGame
@ kMetadataRecording
@ kMetadataVideo
static QString getNameWithExtension(const QString &filename, const QString &type)
LookupType GuessLookupType(ProgramInfo *pginfo)
@ kGrabberMovie
@ kGrabberTelevision
@ kGrabberGame
@ kArtworkFanart
@ kArtworkBanner
@ kArtworkCoverart
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
@ kMSStdOut
allow access to stdout
Definition: mythsystem.h:41
def error(message)
Definition: smolt.py:409