Skip to content

Commit

Permalink
Harmonize Title-Parsing of Movies and TV-Series
Browse files Browse the repository at this point in the history
This change respects parentheses, i.e.: curved marks "()"
in the parsed filenames of a movie, thus parsing of the
release year in parentheses will work.
It enables MythTV's movie grabber to extract the release year
from the parsed filename and do a search for title with year.
The correct syntax for a filename is from now on
"A Movie Title (YYYY).mpg" instead of "A Movie Title YYYY.mpg"
as stated in the wiki.
It resolves ambiguous parsing of different movie titles like
`Wonder Woman 1984` and `Wonder Woman`.

Note: For tv-series, it is already allowed to add the year
in the title, like `The Forsyte Saga (1967)`
e.g.: `The Forsyte Saga (1967) 01x01 A Family Festival.mkv`
is correctly parsed.

Fixes MythTV#974
  • Loading branch information
rcrdnalor committed Jan 10, 2025
1 parent cda772e commit a4e12dd
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 16 deletions.
7 changes: 4 additions & 3 deletions mythtv/bindings/python/tmdb3/tmdb3/lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#-----------------------
__title__ = "TheMovieDB.org V3"
__author__ = "Raymond Wagner, Roland Ernst"
__version__ = "0.3.11"
__version__ = "0.3.12"
# 0.1.0 Initial version
# 0.2.0 Add language support, move cache to home directory
# 0.3.0 Enable version detection to allow use in MythTV
Expand All @@ -32,6 +32,7 @@
# 0.3.9 Support TV lookup
# 0.3.10 Use new API for release dates for movies
# 0.3.11 Allow queries for specials in series in TV lookup
# 0.3.12 `buildMovieList` searches now for movies with year

# ~ from optparse import OptionParser
import sys
Expand Down Expand Up @@ -189,10 +190,10 @@ def buildMovieList(query, opts):
# as negative to all text that comes afterwards
query = query.replace('-',' ')

from MythTV.tmdb3 import searchMovie
from MythTV.tmdb3 import searchMovieWithYear
from MythTV import VideoMetadata
from lxml import etree
results = iter(searchMovie(query))
results = iter(searchMovieWithYear(query))
tree = etree.XML('<metadata></metadata>')
mapping = [['runtime', 'runtime'], ['title', 'originaltitle'],
['releasedate', 'releasedate'], ['tagline', 'tagline'],
Expand Down
67 changes: 55 additions & 12 deletions mythtv/libs/libmythmetadata/metadatadownload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <QEvent>
#include <QDir>
#include <QUrl>
#include <QRegularExpression>

// myth
#include "libmythbase/mythcorecontext.h"
Expand Down Expand Up @@ -199,7 +200,7 @@ void MetadataDownload::run()
MetadataLookup *newlookup = list.takeFirst();

// pass through automatic type
newlookup->SetAutomatic(true); // ### XXX RER
newlookup->SetAutomatic(true);
newlookup->SetStep(kLookupData);
// Type may have changed
LookupType ret = GuessLookupType(newlookup);
Expand Down Expand Up @@ -252,11 +253,20 @@ unsigned int MetadataDownload::findExactMatchCount(MetadataLookupList list,
{
unsigned int exactMatches = 0;
unsigned int exactMatchesWithArt = 0;
static const QRegularExpression year { R"( \(\d{4}\)$)" };

for (const auto& lkup : std::as_const(list))
{
// Consider exact title matches (ignoring case)
if ((QString::compare(lkup->GetTitle(), originaltitle, Qt::CaseInsensitive) == 0))
// Consider exact title matches with or without trailing '(year)' (ignoring case)
QString titlewoyear = originaltitle;
auto match = year.match(titlewoyear);
if (match.hasMatch())
{
titlewoyear.remove(match.capturedStart(), match.capturedLength());
}

if ((QString::compare(lkup->GetTitle(), originaltitle, Qt::CaseInsensitive) == 0) ||
(QString::compare(lkup->GetTitle(), titlewoyear, Qt::CaseInsensitive) == 0))
{
// In lookup by name, the television database tends to only include Banner artwork.
// In lookup by name, the movie database tends to include only Fan and Cover artwork.
Expand Down Expand Up @@ -285,33 +295,63 @@ MetadataLookup* MetadataDownload::findBestMatch(MetadataLookupList list,
int exactMatches = 0;
int exactMatchesWithArt = 0;
bool foundMatchWithArt = false;
bool foundMatchWithYear = false;
uint year = 0;

QString titlewoyear = originaltitle;

static const QRegularExpression regexyear { R"( \(\d{4}\)$)" };

auto match = regexyear.match(titlewoyear);
if (match.hasMatch())
{
titlewoyear.remove(match.capturedStart(), match.capturedLength());
year = match.captured(0).replace(" (","").replace(")","").toUInt();
LOG(VB_GENERAL, LOG_DEBUG, QString("Looking for: '%1' with release year: '%2'")
.arg(titlewoyear, QString::number(year)));
}

// Build a list of all the titles
for (const auto& lkup : std::as_const(list))
{
QString title = lkup->GetTitle();
LOG(VB_GENERAL, LOG_INFO, QString("Comparing metadata title '%1' [%2] to recording title '%3'")
.arg(title, lkup->GetReleaseDate().toString(), originaltitle));
// Consider exact title matches (ignoring case), which have some artwork available.
if (QString::compare(title, originaltitle, Qt::CaseInsensitive) == 0)
LOG(VB_GENERAL, LOG_INFO,
QString("Comparing metadata title '%1' [%2] to recording title '%3' [%4]")
.arg(title, lkup->GetReleaseDate().toString(), titlewoyear,
(year == 0) ? "N/A" : QString::number(year)));

// Consider exact title matches with or without trailing '(year)' (ignoring case),
// which have some artwork available.
if ((QString::compare(title, originaltitle, Qt::CaseInsensitive) == 0) ||
(QString::compare(title, titlewoyear, Qt::CaseInsensitive) == 0))
{
bool hasArtwork = ((!(lkup->GetArtwork(kArtworkFanart)).empty()) ||
(!(lkup->GetArtwork(kArtworkCoverart)).empty()) ||
(!(lkup->GetArtwork(kArtworkBanner)).empty()));

LOG(VB_GENERAL, LOG_INFO, QString("'%1', popularity = %2, ReleaseDate = %3")
if ((lkup->GetYear() != 0) && (year == lkup->GetYear()))
{
exactTitleDate = lkup->GetReleaseDate();
exactTitlePopularity = lkup->GetPopularity();
foundMatchWithYear = true;
ret = lkup;
}

LOG(VB_GENERAL, LOG_INFO, QString("'%1', popularity = %2, ReleaseDate = %3, Year = %4")
.arg(title)
.arg(lkup->GetPopularity())
.arg(lkup->GetReleaseDate().toString()));
.arg(lkup->GetReleaseDate().toString())
.arg(lkup->GetYear()));

// After the first exact match, prefer any more popular one.
// Most of the Movie database entries have Popularity fields.
// The TV series database generally has no Popularity values specified,
// so if none are found so far in the search, pick the most recently
// released entry with artwork. Also, if the first exact match had
// no artwork, prefer any later exact match with artwork.
// Stop searching if we have already found a match with correct year.
if ((ret == nullptr) ||
(hasArtwork &&
(hasArtwork && !foundMatchWithYear &&
((!foundMatchWithArt) ||
((lkup->GetPopularity() > exactTitlePopularity)) ||
((exactTitlePopularity == 0.0F) && (lkup->GetReleaseDate() > exactTitleDate)))))
Expand All @@ -320,6 +360,7 @@ MetadataLookup* MetadataDownload::findBestMatch(MetadataLookupList list,
exactTitlePopularity = lkup->GetPopularity();
ret = lkup;
}

exactMatches++;
if (hasArtwork)
{
Expand Down Expand Up @@ -348,8 +389,10 @@ MetadataLookup* MetadataDownload::findBestMatch(MetadataLookupList list,
{
LOG(VB_GENERAL, LOG_INFO,
QString("Multiple exact title matches found for '%1'. "
"Selecting most popular or most recent [%2]")
.arg(originaltitle, exactTitleDate.toString()));
"Selecting by exact year [%2] or most popular or most recent [%3]")
.arg(originaltitle,
(year == 0) ? "N/A" : QString::number(year),
exactTitleDate.toString()));
}
return ret;
}
Expand Down
2 changes: 1 addition & 1 deletion mythtv/libs/libmythmetadata/videometadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1210,8 +1210,8 @@ QString VideoMetadata::FilenameToMeta(const QString &file_name, int position)
title = title.right(title.length() -
title.lastIndexOf('/') -1);

// Allow parentheses "()", but remove content inside other braces
title = eatBraces(title, "[", "]");
title = eatBraces(title, "(", ")");
title = eatBraces(title, "{", "}");
return title.trimmed();
}
Expand Down

0 comments on commit a4e12dd

Please sign in to comment.