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 for (const auto& lkup : std::as_const(list))
315 {
316 QString title = lkup->GetTitle();
317 LOG(VB_GENERAL, LOG_INFO,
318 QString("Comparing metadata title '%1' [%2] to recording title '%3' [%4]")
319 .arg(title, lkup->GetReleaseDate().toString(), titlewoyear,
320 (year == 0) ? "N/A" : QString::number(year)));
321
322 // Consider exact title matches with or without trailing '(year)' (ignoring case),
323 // which have some artwork available.
324 if ((QString::compare(title, originaltitle, Qt::CaseInsensitive) == 0) ||
325 (QString::compare(title, titlewoyear, Qt::CaseInsensitive) == 0))
326 {
327 bool hasArtwork = ((!(lkup->GetArtwork(kArtworkFanart)).empty()) ||
328 (!(lkup->GetArtwork(kArtworkCoverart)).empty()) ||
329 (!(lkup->GetArtwork(kArtworkBanner)).empty()));
330
331 if ((lkup->GetYear() != 0) && (year == lkup->GetYear()))
332 {
333 exactTitleDate = lkup->GetReleaseDate();
334 exactTitlePopularity = lkup->GetPopularity();
335 foundMatchWithYear = true;
336 ret = lkup;
337 }
338
339 LOG(VB_GENERAL, LOG_INFO, QString("'%1', popularity = %2, ReleaseDate = %3, Year = %4")
340 .arg(title)
341 .arg(lkup->GetPopularity())
342 .arg(lkup->GetReleaseDate().toString())
343 .arg(lkup->GetYear()));
344
345 // After the first exact match, prefer any more popular one.
346 // Most of the Movie database entries have Popularity fields.
347 // The TV series database generally has no Popularity values specified,
348 // so if none are found so far in the search, pick the most recently
349 // released entry with artwork. Also, if the first exact match had
350 // no artwork, prefer any later exact match with artwork.
351 // Stop searching if we have already found a match with correct year.
352 if ((ret == nullptr) ||
353 (hasArtwork && !foundMatchWithYear &&
354 ((!foundMatchWithArt) ||
355 ((lkup->GetPopularity() > exactTitlePopularity)) ||
356 ((exactTitlePopularity == 0.0F) && (lkup->GetReleaseDate() > exactTitleDate)))))
357 {
358 exactTitleDate = lkup->GetReleaseDate();
359 exactTitlePopularity = lkup->GetPopularity();
360 ret = lkup;
361 }
362
363 exactMatches++;
364 if (hasArtwork)
365 {
366 foundMatchWithArt = true;
367 exactMatchesWithArt++;
368 }
369 }
370
371 titles.append(title);
372 }
373
374 LOG(VB_GENERAL, LOG_DEBUG, QString("exactMatches = %1, exactMatchesWithArt = %2")
375 .arg(exactMatches)
376 .arg(exactMatchesWithArt));
377
378 // If there was one or more exact matches then we can skip a more intensive
379 // and time consuming search
380 if (exactMatches > 0)
381 {
382 if (exactMatches == 1)
383 {
384 LOG(VB_GENERAL, LOG_INFO, QString("Single exact title match for '%1'")
385 .arg(originaltitle));
386 }
387 else
388 {
389 LOG(VB_GENERAL, LOG_INFO,
390 QString("Multiple exact title matches found for '%1'. "
391 "Selecting by exact year [%2] or most popular or most recent [%3]")
392 .arg(originaltitle,
393 (year == 0) ? "N/A" : QString::number(year),
394 exactTitleDate.toString()));
395 }
396 return ret;
397 }
398
399 // Apply Levenshtein distance algorithm to determine closest match
400 QString bestTitle = nearestName(originaltitle, titles);
401
402 // If no "best" was chosen, give up.
403 if (bestTitle.isEmpty())
404 {
405 LOG(VB_GENERAL, LOG_ERR,
406 QString("No adequate match or multiple "
407 "matches found for %1. Update manually.")
408 .arg(originaltitle));
409 return nullptr;
410 }
411
412 LOG(VB_GENERAL, LOG_INFO, QString("Best Title Match For %1: %2")
413 .arg(originaltitle, bestTitle));
414
415 // Grab the one item that matches the besttitle (IMPERFECT)
416 for (const auto& item : std::as_const(list))
417 {
418 if (item->GetTitle() == bestTitle)
419 {
420 ret = item;
421 break;
422 }
423 }
424
425 return ret;
426}
427
428MetadataLookupList MetadataDownload::runGrabber(const QString& cmd, const QStringList& args,
429 MetadataLookup *lookup,
430 bool passseas)
431{
432 MythSystemLegacy grabber(cmd, args, kMSStdOut);
434
435 LOG(VB_GENERAL, LOG_INFO, QString("Running Grabber: %1 %2")
436 .arg(cmd, args.join(" ")));
437
438 grabber.Run();
439 grabber.Wait();
440 QByteArray result = grabber.ReadAll();
441 if (!result.isEmpty())
442 {
443 QDomDocument doc;
444#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
445 doc.setContent(result, true);
446#else
447 doc.setContent(result, QDomDocument::ParseOption::UseNamespaceProcessing);
448#endif
449 QDomElement root = doc.documentElement();
450 QDomElement item = root.firstChildElement("item");
451
452 while (!item.isNull())
453 {
454 MetadataLookup *tmp = ParseMetadataItem(item, lookup, passseas);
455 list.append(tmp);
456 // MetadataLookup is to be owned by list
457 tmp->DecrRef();
458 item = item.nextSiblingElement("item");
459 }
460 }
461 return list;
462}
463
465{
467}
468
470{
472}
473
475{
477}
478
479bool MetadataDownload::runGrabberTest(const QString &grabberpath)
480{
481 return MetaGrabberScript(grabberpath).Test();
482}
483
485{
487 {
488 LOG(VB_GENERAL, LOG_INFO,
489 QString("Movie grabber not functional. Aborting this run."));
490 return false;
491 }
492
493 return true;
494}
495
497{
499 {
500 LOG(VB_GENERAL, LOG_INFO,
501 QString("Television grabber not functional. Aborting this run."));
502 return false;
503 }
504
505 return true;
506}
507
509 MetadataLookup *lookup,
510 bool passseas)
511{
513
514 LOG(VB_GENERAL, LOG_INFO,
515 QString("Matching MXML file found. Parsing %1 for metadata...")
516 .arg(MXMLpath));
517
518 if (lookup->GetType() == kMetadataVideo)
519 {
520 QByteArray mxmlraw;
521 QDomElement item;
522 auto *rf = new RemoteFile(MXMLpath);
523
524 if (rf->isOpen())
525 {
526 bool loaded = rf->SaveAs(mxmlraw);
527 if (loaded)
528 {
529 QDomDocument doc;
530#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
531 bool success = doc.setContent(mxmlraw, true);
532#else
533 auto parseResult = doc.setContent(mxmlraw, QDomDocument::ParseOption::UseNamespaceProcessing);
534 bool success { parseResult };
535#endif
536 if (!success)
537 {
538 lookup->SetStep(kLookupData);
539 QDomElement root = doc.documentElement();
540 item = root.firstChildElement("item");
541 }
542 else
543 {
544 LOG(VB_GENERAL, LOG_ERR,
545 QString("Corrupt or invalid MXML file."));
546 }
547 }
548 }
549
550 delete rf;
551 rf = nullptr;
552
553 MetadataLookup *tmp = ParseMetadataItem(item, lookup, passseas);
554 list.append(tmp);
555 // MetadataLookup is owned by the MetadataLookupList returned
556 tmp->DecrRef();
557 }
558
559 return list;
560}
561
563 MetadataLookup *lookup)
564{
566
567 LOG(VB_GENERAL, LOG_INFO,
568 QString("Matching NFO file found. Parsing %1 for metadata...")
569 .arg(NFOpath));
570
571 bool error = false;
572
573 if (lookup->GetType() == kMetadataVideo)
574 {
575 QByteArray nforaw;
576 QDomElement item;
577 auto *rf = new RemoteFile(NFOpath);
578
579 if (rf->isOpen())
580 {
581 bool loaded = rf->SaveAs(nforaw);
582
583 if (loaded)
584 {
585 QDomDocument doc;
586
587#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
588 bool success = doc.setContent(nforaw, true);
589#else
590 auto parseResult = doc.setContent(nforaw, QDomDocument::ParseOption::UseNamespaceProcessing);
591 bool success { parseResult };
592#endif
593 if (success)
594 {
595 lookup->SetStep(kLookupData);
596 item = doc.documentElement();
597 }
598 else
599 {
600 LOG(VB_GENERAL, LOG_ERR,
601 QString("Invalid NFO file found."));
602 error = true;
603 }
604 }
605 }
606
607 delete rf;
608 rf = nullptr;
609
610 if (!error)
611 {
613
614 list.append(tmp);
615 // MetadataLookup is owned by the MetadataLookupList returned
616 tmp->DecrRef();
617 }
618 }
619
620 return list;
621}
622
624{
626 MetaGrabberScript grabber =
628 if (!grabber.IsValid())
629 return {};
630
631 // If the inetref is populated, even in kLookupSearch mode,
632 // become a kLookupData grab and use that.
633 if (lookup->GetStep() == kLookupSearch &&
634 (!lookup->GetInetref().isEmpty() &&
635 lookup->GetInetref() != "00000000"))
636 {
637 lookup->SetStep(kLookupData);
638 }
639
640 if (lookup->GetStep() == kLookupSearch)
641 {
642 if (lookup->GetTitle().isEmpty())
643 {
644 // no point searching on nothing...
645 return list;
646 }
647 // we're searching
648 list = grabber.Search(lookup->GetTitle(), lookup);
649 }
650 else if (lookup->GetStep() == kLookupData)
651 {
652 // we're just grabbing data
653 list = grabber.LookupData(lookup->GetInetref(), lookup);
654 }
655
656 return list;
657}
658
668{
670
671 MetaGrabberScript grabber =
673 if (!grabber.IsValid())
674 return {};
675
676 // initial search mode
677 if (!lookup->GetInetref().isEmpty() && lookup->GetInetref() != "00000000" &&
678 (lookup->GetStep() == kLookupSearch || lookup->GetStep() == kLookupData))
679 {
680 // with inetref
681 lookup->SetStep(kLookupData);
682 // we're just grabbing data
683 list = grabber.LookupData(lookup->GetInetref(), lookup);
684 }
685 else if (lookup->GetStep() == kLookupSearch)
686 {
687 if (lookup->GetBaseTitle().isEmpty())
688 {
689 // no point searching on nothing...
690 return list;
691 }
692 list = grabber.Search(lookup->GetBaseTitle(), lookup);
693 }
694
695 return list;
696}
697
710{
712
713 MetaGrabberScript grabber =
715 if (!grabber.IsValid())
716 return {};
717 bool searchcollection = false;
718
719 // initial search mode
720 if (!lookup->GetInetref().isEmpty() && lookup->GetInetref() != "00000000" &&
721 (lookup->GetStep() == kLookupSearch || lookup->GetStep() == kLookupData))
722 {
723 // with inetref
724 lookup->SetStep(kLookupData);
725 if (lookup->GetSeason() || lookup->GetEpisode())
726 {
727 list = grabber.LookupData(lookup->GetInetref(), lookup->GetSeason(),
728 lookup->GetEpisode(), lookup);
729 }
730
731 if (list.isEmpty() && (!lookup->GetSubtitle().isEmpty()))
732 {
733 list = grabber.SearchSubtitle(lookup->GetInetref(),
734 lookup->GetBaseTitle() /* unused */,
735 lookup->GetSubtitle(), lookup, false);
736 }
737
738 if (list.isEmpty() && !lookup->GetCollectionref().isEmpty())
739 {
740 list = grabber.LookupCollection(lookup->GetCollectionref(), lookup);
741 searchcollection = true;
742 }
743 else if (list.isEmpty())
744 {
745 // We do not store CollectionRef in our database
746 // so try with the inetref, for all purposes with TVDB, they are
747 // always identical
748 list = grabber.LookupCollection(lookup->GetInetref(), lookup);
749 searchcollection = true;
750 }
751 }
752 else if (lookup->GetStep() == kLookupSearch)
753 {
754 if (lookup->GetBaseTitle().isEmpty())
755 {
756 // no point searching on nothing...
757 return list;
758 }
759 if (!lookup->GetSubtitle().isEmpty())
760 {
761 list = grabber.SearchSubtitle(lookup->GetBaseTitle(),
762 lookup->GetSubtitle(), lookup, false);
763 }
764 if (list.isEmpty())
765 {
766 list = grabber.Search(lookup->GetBaseTitle(), lookup);
767 }
768 }
769 else if (lookup->GetStep() == kLookupCollection)
770 {
771 list = grabber.LookupCollection(lookup->GetCollectionref(), lookup);
772 }
773
774 // Collection Fallback
775 // If the lookup allows generic metadata, and the specific
776 // season and episode are not available, try for series metadata.
777 if (!searchcollection && list.isEmpty() &&
778 !lookup->GetCollectionref().isEmpty() &&
779 lookup->GetAllowGeneric() && lookup->GetStep() == kLookupData)
780 {
781 lookup->SetStep(kLookupCollection);
782 list = grabber.LookupCollection(lookup->GetCollectionref(), lookup);
783 }
784
785 if (!list.isEmpty())
786 {
787 // mark all results so that search collection is properly handled later
788 lookup->SetIsCollection(searchcollection);
789 // NOLINTNEXTLINE(modernize-loop-convert)
790 for (auto it = list.begin(); it != list.end(); ++it)
791 {
792 (*it)->SetIsCollection(searchcollection);
793 }
794 }
795
796 return list;
797}
798
800{
802
803 if (lookup->GetSubtype() != kProbableMovie &&
804 !lookup->GetSubtitle().isEmpty())
805 {
806 list.append(handleTelevision(lookup));
807 }
808
809 if (lookup->GetSubtype() != kProbableTelevision)
810 {
811 list.append(handleMovie(lookup));
812 }
813
814 if (list.count() == 1)
815 {
816 list[0]->SetStep(kLookupData);
817 }
818
819 return list;
820}
821
823{
824 // We only enter this mode if we are pretty darn sure this is a TV show,
825 // but we're for some reason looking up a generic, or the title didn't
826 // exactly match in one of the earlier lookups. This is a total
827 // hail mary to try to get at least *series* level info and art/inetref.
828
830
831 if (lookup->GetBaseTitle().isEmpty())
832 {
833 // no point searching on nothing...
834 return list;
835 }
836
837 // no inetref known, just pull the default grabber
839
840 // cache some initial values so we can change them in the lookup later
841 LookupType origtype = lookup->GetSubtype();
842 int origseason = lookup->GetSeason();
843 int origepisode = lookup->GetEpisode();
844
845 if (origseason == 0 && origepisode == 0)
846 {
847 lookup->SetSeason(1);
848 lookup->SetEpisode(1);
849 }
850
851 list = grabber.Search(lookup->GetBaseTitle(), lookup);
852
853 if (list.count() == 1)
854 {
855 // search was successful, rerun as normal television mode
856 lookup->SetInetref(list[0]->GetInetref());
857 lookup->SetCollectionref(list[0]->GetCollectionref());
858 list = handleTelevision(lookup);
859 }
860
861 lookup->SetSeason(origseason);
862 lookup->SetEpisode(origepisode);
863 lookup->SetSubtype(origtype);
864
865 return list;
866}
867
868static QString getNameWithExtension(const QString &filename, const QString &type)
869{
870 QString ret;
871 QString newname;
872 QUrl qurl(filename);
873 QString ext = QFileInfo(qurl.path()).suffix();
874
875 if (ext.isEmpty())
876 {
877 // no extension, assume it is a directory
878 newname = filename + "/" + QFileInfo(qurl.path()).fileName() + "." + type;
879 }
880 else
881 {
882 newname = filename.left(filename.size() - ext.size()) + type;
883 }
884
885 if (RemoteFile::Exists(newname))
886 ret = newname;
887
888 return ret;
889}
890
892{
893 return getNameWithExtension(filename, "mxml");
894}
895
897{
898 return getNameWithExtension(filename, "nfo");
899}
bool isRunning(void) const
Definition: mthread.cpp:263
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:196
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:283
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:209
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
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:461
unsigned int uint
Definition: freesurround.h:24
static guint32 * tmp
Definition: goom_core.cpp:26
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