Fixed: Parsing anime dual language titles

closes #3756
This commit is contained in:
Taloth Saldono 2020-05-17 23:01:41 +02:00
parent 5251db7224
commit a75e10c4c9
6 changed files with 94 additions and 2 deletions

View File

@ -3,6 +3,7 @@ using NzbDrone.Api.Series;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using Sonarr.Http; using Sonarr.Http;
using Sonarr.Http.REST;
namespace NzbDrone.Api.Parse namespace NzbDrone.Api.Parse
{ {
@ -21,6 +22,12 @@ namespace NzbDrone.Api.Parse
{ {
var title = Request.Query.Title.Value as string; var title = Request.Query.Title.Value as string;
var path = Request.Query.Path.Value as string; var path = Request.Query.Path.Value as string;
if (path.IsNullOrWhiteSpace() && title.IsNullOrWhiteSpace())
{
throw new BadRequestException("title or path is missing");
}
var parsedEpisodeInfo = path.IsNotNullOrWhiteSpace() ? Parser.ParsePath(path) : Parser.ParseTitle(title); var parsedEpisodeInfo = path.IsNotNullOrWhiteSpace() ? Parser.ParsePath(path) : Parser.ParseTitle(title);
if (parsedEpisodeInfo == null) if (parsedEpisodeInfo == null)

View File

@ -1,5 +1,7 @@
using Moq; using FluentAssertions;
using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
@ -43,5 +45,18 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
.Verify(s => s.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear, .Verify(s => s.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear,
parsedEpisodeInfo.SeriesTitleInfo.Year), Times.Once()); parsedEpisodeInfo.SeriesTitleInfo.Year), Times.Once());
} }
[Test]
public void should_parse_concatenated_title()
{
var series = new Series { TvdbId = 100 };
Mocker.GetMock<ISeriesService>().Setup(v => v.FindByTitle("Welcome")).Returns(series);
Mocker.GetMock<ISceneMappingService>().Setup(v => v.FindTvdbId("Mairimashita", It.IsAny<string>())).Returns(100);
var result = Subject.GetSeries("Welcome (Mairimashita).S01E01.720p.WEB-DL-Viva");
result.Should().NotBeNull();
result.TvdbId.Should().Be(100);
}
} }
} }

View File

@ -5,5 +5,6 @@
public string Title { get; set; } public string Title { get; set; }
public string TitleWithoutYear { get; set; } public string TitleWithoutYear { get; set; }
public int Year { get; set; } public int Year { get; set; }
public string[] AllTitles { get; set; }
} }
} }

View File

@ -385,6 +385,9 @@ namespace NzbDrone.Core.Parser
private static readonly Regex YearInTitleRegex = new Regex(@"^(?<title>.+?)(?:\W|_)?(?<year>\d{4})", private static readonly Regex YearInTitleRegex = new Regex(@"^(?<title>.+?)(?:\W|_)?(?<year>\d{4})",
RegexOptions.IgnoreCase | RegexOptions.Compiled); RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex TitleComponentsRegex = new Regex(@"^(?<title>.+?) \((?<title>.+?)\)$",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex WordDelimiterRegex = new Regex(@"(\s|\.|,|_|-|=|\|)+", RegexOptions.Compiled); private static readonly Regex WordDelimiterRegex = new Regex(@"(\s|\.|,|_|-|=|\|)+", RegexOptions.Compiled);
private static readonly Regex PunctuationRegex = new Regex(@"[^\w\s]", RegexOptions.Compiled); private static readonly Regex PunctuationRegex = new Regex(@"[^\w\s]", RegexOptions.Compiled);
private static readonly Regex CommonWordRegex = new Regex(@"\b(a|an|the|and|or|of)\b\s?", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex CommonWordRegex = new Regex(@"\b(a|an|the|and|or|of)\b\s?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
@ -670,13 +673,19 @@ namespace NzbDrone.Core.Parser
{ {
seriesTitleInfo.TitleWithoutYear = title; seriesTitleInfo.TitleWithoutYear = title;
} }
else else
{ {
seriesTitleInfo.TitleWithoutYear = match.Groups["title"].Value; seriesTitleInfo.TitleWithoutYear = match.Groups["title"].Value;
seriesTitleInfo.Year = Convert.ToInt32(match.Groups["year"].Value); seriesTitleInfo.Year = Convert.ToInt32(match.Groups["year"].Value);
} }
var matchComponents = TitleComponentsRegex.Match(seriesTitleInfo.TitleWithoutYear);
if (matchComponents.Success)
{
seriesTitleInfo.AllTitles = matchComponents.Groups["title"].Captures.OfType<Capture>().Select(v => v.Value).ToArray();
}
return seriesTitleInfo; return seriesTitleInfo;
} }

View File

@ -57,6 +57,11 @@ namespace NzbDrone.Core.Parser
var series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle); var series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
if (series == null && parsedEpisodeInfo.SeriesTitleInfo.AllTitles != null)
{
series = GetSeriesByAllTitles(parsedEpisodeInfo);
}
if (series == null) if (series == null)
{ {
series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear, series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear,
@ -66,6 +71,49 @@ namespace NzbDrone.Core.Parser
return series; return series;
} }
private Series GetSeriesByAllTitles(ParsedEpisodeInfo parsedEpisodeInfo)
{
Series foundSeries = null;
int? foundTvdbId = null;
// Match each title individually, they must all resolve to the same tvdbid
foreach (var title in parsedEpisodeInfo.SeriesTitleInfo.AllTitles)
{
var series = _seriesService.FindByTitle(title);
var tvdbId = series?.TvdbId;
if (series == null)
{
tvdbId = _sceneMappingService.FindTvdbId(title, parsedEpisodeInfo.ReleaseTitle);
}
if (!tvdbId.HasValue)
{
_logger.Trace("Title {0} not matching any series.", title);
return null;
}
if (foundTvdbId.HasValue && tvdbId != foundTvdbId)
{
_logger.Trace("Title {0} both matches tvdbid {1} and {2}, no series selected.", parsedEpisodeInfo.SeriesTitle, foundTvdbId, tvdbId);
return null;
}
if (foundSeries == null)
{
foundSeries = series;
}
foundTvdbId = tvdbId;
}
if (foundSeries == null && foundTvdbId.HasValue)
{
foundSeries = _seriesService.FindByTvdbId(foundTvdbId.Value);
}
return foundSeries;
}
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null) public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null)
{ {
var remoteEpisode = new RemoteEpisode var remoteEpisode = new RemoteEpisode
@ -270,6 +318,11 @@ namespace NzbDrone.Core.Parser
series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle); series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
if (series == null && parsedEpisodeInfo.SeriesTitleInfo.AllTitles != null)
{
series = GetSeriesByAllTitles(parsedEpisodeInfo);
}
if (series == null && parsedEpisodeInfo.SeriesTitleInfo.Year > 0) if (series == null && parsedEpisodeInfo.SeriesTitleInfo.Year > 0)
{ {
series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear, parsedEpisodeInfo.SeriesTitleInfo.Year); series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear, parsedEpisodeInfo.SeriesTitleInfo.Year);

View File

@ -3,6 +3,7 @@ using NzbDrone.Core.Parser;
using Sonarr.Api.V3.Episodes; using Sonarr.Api.V3.Episodes;
using Sonarr.Api.V3.Series; using Sonarr.Api.V3.Series;
using Sonarr.Http; using Sonarr.Http;
using Sonarr.Http.REST;
namespace Sonarr.Api.V3.Parse namespace Sonarr.Api.V3.Parse
{ {
@ -21,6 +22,12 @@ namespace Sonarr.Api.V3.Parse
{ {
var title = Request.Query.Title.Value as string; var title = Request.Query.Title.Value as string;
var path = Request.Query.Path.Value as string; var path = Request.Query.Path.Value as string;
if (path.IsNullOrWhiteSpace() && title.IsNullOrWhiteSpace())
{
throw new BadRequestException("title or path is missing");
}
var parsedEpisodeInfo = path.IsNotNullOrWhiteSpace() ? Parser.ParsePath(path) : Parser.ParseTitle(title); var parsedEpisodeInfo = path.IsNotNullOrWhiteSpace() ? Parser.ParsePath(path) : Parser.ParseTitle(title);
if (parsedEpisodeInfo == null) if (parsedEpisodeInfo == null)