Fixed: Parsing of releases with episode titles that contain languages

Closes #861
This commit is contained in:
Mark McDowall 2022-12-08 18:59:36 -08:00 committed by GitHub
parent 16e2d130e6
commit 6216a71f8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 436 additions and 197 deletions

View File

@ -1,15 +1,13 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.CustomFormats
{
[TestFixture]
public class CustomFormatsFixture : CoreTest
public class CustomFormatsTestHelpers : CoreTest
{
private static List<CustomFormat> _customFormats { get; set; }

View File

@ -46,14 +46,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) },
};
CustomFormatsFixture.GivenCustomFormats(_format1, _format2);
CustomFormatsTestHelpers.GivenCustomFormats(_format1, _format2);
}
[Test]
public void should_allow_if_format_score_greater_than_min()
{
_remoteEpisode.CustomFormats = new List<CustomFormat> { _format1 };
_remoteEpisode.Series.QualityProfile.Value.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name);
_remoteEpisode.Series.QualityProfile.Value.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_format1.Name);
_remoteEpisode.CustomFormatScore = _remoteEpisode.Series.QualityProfile.Value.CalculateCustomFormatScore(_remoteEpisode.CustomFormats);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_deny_if_format_score_not_greater_than_min()
{
_remoteEpisode.CustomFormats = new List<CustomFormat> { _format2 };
_remoteEpisode.Series.QualityProfile.Value.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name);
_remoteEpisode.Series.QualityProfile.Value.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_format1.Name);
_remoteEpisode.CustomFormatScore = _remoteEpisode.Series.QualityProfile.Value.CalculateCustomFormatScore(_remoteEpisode.CustomFormats);
Console.WriteLine(_remoteEpisode.CustomFormatScore);
@ -76,7 +76,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_deny_if_format_score_not_greater_than_min_2()
{
_remoteEpisode.CustomFormats = new List<CustomFormat> { _format2, _format1 };
_remoteEpisode.Series.QualityProfile.Value.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name);
_remoteEpisode.Series.QualityProfile.Value.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_format1.Name);
_remoteEpisode.CustomFormatScore = _remoteEpisode.Series.QualityProfile.Value.CalculateCustomFormatScore(_remoteEpisode.CustomFormats);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
@ -86,7 +86,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_allow_if_all_format_is_defined_in_profile()
{
_remoteEpisode.CustomFormats = new List<CustomFormat> { _format2, _format1 };
_remoteEpisode.Series.QualityProfile.Value.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name);
_remoteEpisode.Series.QualityProfile.Value.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_format1.Name, _format2.Name);
_remoteEpisode.CustomFormatScore = _remoteEpisode.Series.QualityProfile.Value.CalculateCustomFormatScore(_remoteEpisode.CustomFormats);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_deny_if_no_format_was_parsed_and_min_score_positive()
{
_remoteEpisode.CustomFormats = new List<CustomFormat> { };
_remoteEpisode.Series.QualityProfile.Value.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name);
_remoteEpisode.Series.QualityProfile.Value.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_format1.Name, _format2.Name);
_remoteEpisode.CustomFormatScore = _remoteEpisode.Series.QualityProfile.Value.CalculateCustomFormatScore(_remoteEpisode.CustomFormats);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
@ -106,7 +106,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_allow_if_no_format_was_parsed_min_score_is_zero()
{
_remoteEpisode.CustomFormats = new List<CustomFormat> { };
_remoteEpisode.Series.QualityProfile.Value.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name);
_remoteEpisode.Series.QualityProfile.Value.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_format1.Name, _format2.Name);
_remoteEpisode.Series.QualityProfile.Value.MinFormatScore = 0;
_remoteEpisode.CustomFormatScore = _remoteEpisode.Series.QualityProfile.Value.CalculateCustomFormatScore(_remoteEpisode.CustomFormats);

View File

@ -45,8 +45,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void GivenProfile(QualityProfile profile)
{
CustomFormatsFixture.GivenCustomFormats();
profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems();
CustomFormatsTestHelpers.GivenCustomFormats();
profile.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems();
profile.MinFormatScore = 0;
_remoteMovie.Series.QualityProfile = profile;
@ -79,7 +79,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
_customFormat = new CustomFormat("My Format", new ResolutionSpecification { Value = (int)Resolution.R1080p }) { Id = 1 };
CustomFormatsFixture.GivenCustomFormats(_customFormat);
CustomFormatsTestHelpers.GivenCustomFormats(_customFormat);
}
[Test]
@ -221,7 +221,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Cutoff = Quality.HDTV720p.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
MinFormatScore = 0,
FormatItems = CustomFormatsFixture.GetSampleFormatItems("My Format"),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems("My Format"),
UpgradeAllowed = true
});

View File

@ -36,14 +36,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
Mocker.Resolve<UpgradableSpecification>();
CustomFormatsFixture.GivenCustomFormats();
CustomFormatsTestHelpers.GivenCustomFormats();
_series = Builder<Series>.CreateNew()
.With(e => e.QualityProfile = new QualityProfile
{
UpgradeAllowed = true,
Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatItems = CustomFormatsFixture.GetSampleFormatItems(),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(),
MinFormatScore = 0
})
.Build();
@ -74,7 +74,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.Build();
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Series>()))
.Setup(x => x.ParseCustomFormat(It.IsAny<RemoteEpisode>()))
.Returns(new List<CustomFormat>());
}
@ -88,7 +88,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void GivenQueueFormats(List<CustomFormat> formats)
{
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Series>()))
.Setup(x => x.ParseCustomFormat(It.IsAny<RemoteEpisode>()))
.Returns(formats);
}
@ -215,9 +215,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
var lowFormat = new List<CustomFormat> { new CustomFormat("Bad Format", new ResolutionSpecification { Value = (int)Resolution.R1080p }) { Id = 2 } };
CustomFormatsFixture.GivenCustomFormats(_remoteEpisode.CustomFormats.First(), lowFormat.First());
CustomFormatsTestHelpers.GivenCustomFormats(_remoteEpisode.CustomFormats.First(), lowFormat.First());
_series.QualityProfile.Value.FormatItems = CustomFormatsFixture.GetSampleFormatItems("My Format");
_series.QualityProfile.Value.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems("My Format");
GivenQueueFormats(lowFormat);

View File

@ -41,7 +41,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
Mocker.Resolve<UpgradableSpecification>();
_upgradeHistory = Mocker.Resolve<HistorySpecification>();
CustomFormatsFixture.GivenCustomFormats();
CustomFormatsTestHelpers.GivenCustomFormats();
var singleEpisodeList = new List<Episode> { new Episode { Id = FIRST_EPISODE_ID, SeasonNumber = 12, EpisodeNumber = 3 } };
var doubleEpisodeList = new List<Episode>
@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
{
UpgradeAllowed = true,
Cutoff = Quality.Bluray1080p.Id,
FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems("None"),
MinFormatScore = 0,
Items = Qualities.QualityFixture.GetDefaultQualities()
})
@ -87,7 +87,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
.Returns(true);
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<EpisodeHistory>()))
.Setup(x => x.ParseCustomFormat(It.IsAny<EpisodeHistory>(), It.IsAny<Series>()))
.Returns(new List<CustomFormat>());
}

View File

@ -35,7 +35,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Mocker.Resolve<UpgradableSpecification>();
_upgradeDisk = Mocker.Resolve<UpgradeDiskSpecification>();
CustomFormatsFixture.GivenCustomFormats();
CustomFormatsTestHelpers.GivenCustomFormats();
_firstFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now, Languages = new List<Language> { Language.English } };
_secondFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now, Languages = new List<Language> { Language.English } };
@ -49,7 +49,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
UpgradeAllowed = true,
Cutoff = Quality.Bluray1080p.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems("None"),
MinFormatScore = 0,
})
.Build();

View File

@ -0,0 +1,112 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Download.Aggregation.Aggregators;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Download.Aggregation.Aggregators
{
[TestFixture]
public class AggregateLanguagesFixture : CoreTest<AggregateLanguages>
{
private RemoteEpisode _remoteEpisode;
private Series _series;
private string _simpleReleaseTitle = "Series.Title.S01E01.xyz-RlsGroup";
[SetUp]
public void Setup()
{
var episodes = Builder<Episode>.CreateListOfSize(1)
.BuildList();
_series = Builder<Series>.CreateNew()
.With(m => m.OriginalLanguage = Language.English)
.Build();
_remoteEpisode = Builder<RemoteEpisode>.CreateNew()
.With(l => l.ParsedEpisodeInfo = null)
.With(l => l.Episodes = episodes)
.With(l => l.Series = _series)
.Build();
}
private ParsedEpisodeInfo GetParsedEpisodeInfo(List<Language> languages, string releaseTitle, string releaseTokens = "")
{
return new ParsedEpisodeInfo
{
Languages = languages,
ReleaseTitle = releaseTitle,
ReleaseTokens = releaseTokens
};
}
[Test]
public void should_return_existing_language_if_episode_title_does_not_have_language()
{
_remoteEpisode.ParsedEpisodeInfo = GetParsedEpisodeInfo(new List<Language> { Language.Original }, _simpleReleaseTitle);
Subject.Aggregate(_remoteEpisode).Languages.Should().Contain(_series.OriginalLanguage);
}
[Test]
public void should_return_parsed_language()
{
_remoteEpisode.ParsedEpisodeInfo = GetParsedEpisodeInfo(new List<Language> { Language.French }, _simpleReleaseTitle);
Subject.Aggregate(_remoteEpisode).Languages.Should().Equal(_remoteEpisode.ParsedEpisodeInfo.Languages);
}
[Test]
public void should_exclude_language_that_is_part_of_episode_title_when_release_tokens_contains_episode_title()
{
var releaseTitle = "Series.Title.S01E01.Jimmy.The.Greek.xyz-RlsGroup";
var releaseTokens = ".Jimmy.The.Greek.xyz-RlsGroup";
_remoteEpisode.Episodes.First().Title = "Jimmy The Greek";
_remoteEpisode.ParsedEpisodeInfo = GetParsedEpisodeInfo(new List<Language> { Language.Greek }, releaseTitle, releaseTokens);
Subject.Aggregate(_remoteEpisode).Languages.Should().Equal(_series.OriginalLanguage);
}
[Test]
public void should_remove_parsed_language_that_is_part_of_episode_title_when_release_tokens_contains_episode_title()
{
var releaseTitle = "Series.Title.S01E01.Jimmy.The.Greek.French.xyz-RlsGroup";
var releaseTokens = ".Jimmy.The.Greek.French.xyz-RlsGroup";
_remoteEpisode.Episodes.First().Title = "Jimmy The Greek";
_remoteEpisode.ParsedEpisodeInfo = GetParsedEpisodeInfo(new List<Language> { Language.Greek, Language.French }, releaseTitle, releaseTokens);
Subject.Aggregate(_remoteEpisode).Languages.Should().Equal(Language.French);
}
[Test]
public void should_not_exclude_language_that_is_part_of_episode_title_when_release_tokens_does_not_contain_episode_title()
{
var releaseTitle = "Series.Title.S01E01.xyz-RlsGroup";
var releaseTokens = ".xyz-RlsGroup";
_remoteEpisode.Episodes.First().Title = "Jimmy The Greek";
_remoteEpisode.ParsedEpisodeInfo = GetParsedEpisodeInfo(new List<Language> { Language.Greek }, releaseTitle, releaseTokens);
Subject.Aggregate(_remoteEpisode).Languages.Should().Equal(Language.Greek);
}
[Test]
public void should_use_reparse_language_after_determining_languages_that_are_in_episode_titles()
{
var releaseTitle = "Series.Title.S01E01.Jimmy.The.Greek.Greek.xyz-RlsGroup";
var releaseTokens = ".Jimmy.The.Greek.Greek.xyz-RlsGroup";
_remoteEpisode.Episodes.First().Title = "Jimmy The Greek";
_remoteEpisode.ParsedEpisodeInfo = GetParsedEpisodeInfo(new List<Language> { Language.Greek }, releaseTitle, releaseTokens);
Subject.Aggregate(_remoteEpisode).Languages.Should().Equal(Language.Greek);
}
}
}

View File

@ -10,12 +10,9 @@ using NzbDrone.Core.Datastore;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.CustomFormats;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
@ -272,7 +269,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
.Returns(new List<CustomFormat>());
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Series>()))
.Setup(s => s.ParseCustomFormat(It.IsAny<RemoteEpisode>()))
.Returns(new List<CustomFormat>());
_localEpisode.Quality = new QualityModel(Quality.Bluray2160p);
@ -306,7 +303,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
.Returns(new List<CustomFormat>());
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Series>()))
.Setup(s => s.ParseCustomFormat(It.IsAny<RemoteEpisode>()))
.Returns(new List<CustomFormat>());
_localEpisode.Quality = new QualityModel(Quality.Bluray1080p);
@ -384,7 +381,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
.Returns(new List<CustomFormat>());
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Series>()))
.Setup(s => s.ParseCustomFormat(It.IsAny<RemoteEpisode>()))
.Returns(new List<CustomFormat>());
_localEpisode.Quality = new QualityModel(Quality.Bluray1080p);
@ -417,7 +414,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
.Returns(new List<CustomFormat>());
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Series>()))
.Setup(s => s.ParseCustomFormat(It.IsAny<RemoteEpisode>()))
.Returns(new List<CustomFormat>());
_localEpisode.Quality = new QualityModel(Quality.Bluray1080p);

View File

@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.History;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
@ -14,28 +13,101 @@ namespace NzbDrone.Core.CustomFormats
{
public interface ICustomFormatCalculationService
{
List<CustomFormat> ParseCustomFormat(ParsedEpisodeInfo episodeInfo, Series series);
List<CustomFormat> ParseCustomFormat(RemoteEpisode remoteEpisode);
List<CustomFormat> ParseCustomFormat(EpisodeFile episodeFile, Series series);
List<CustomFormat> ParseCustomFormat(EpisodeFile episodeFile);
List<CustomFormat> ParseCustomFormat(Blocklist blocklist);
List<CustomFormat> ParseCustomFormat(EpisodeHistory history);
List<CustomFormat> ParseCustomFormat(Blocklist blocklist, Series series);
List<CustomFormat> ParseCustomFormat(EpisodeHistory history, Series series);
}
public class CustomFormatCalculationService : ICustomFormatCalculationService
{
private readonly ICustomFormatService _formatService;
private readonly IParsingService _parsingService;
private readonly ISeriesService _seriesService;
public CustomFormatCalculationService(ICustomFormatService formatService,
IParsingService parsingService,
ISeriesService seriesService)
public CustomFormatCalculationService(ICustomFormatService formatService)
{
_formatService = formatService;
_parsingService = parsingService;
_seriesService = seriesService;
}
public static List<CustomFormat> ParseCustomFormat(ParsedEpisodeInfo episodeInfo, List<CustomFormat> allCustomFormats)
public List<CustomFormat> ParseCustomFormat(RemoteEpisode remoteEpisode)
{
var input = new CustomFormatInput
{
EpisodeInfo = remoteEpisode.ParsedEpisodeInfo,
Series = remoteEpisode.Series,
Size = remoteEpisode.Release.Size,
Languages = remoteEpisode.Languages
};
return ParseCustomFormat(input);
}
public List<CustomFormat> ParseCustomFormat(EpisodeFile episodeFile, Series series)
{
return ParseCustomFormat(episodeFile, series, _formatService.All());
}
public List<CustomFormat> ParseCustomFormat(EpisodeFile episodeFile)
{
return ParseCustomFormat(episodeFile, episodeFile.Series.Value, _formatService.All());
}
public List<CustomFormat> ParseCustomFormat(Blocklist blocklist, Series series)
{
var parsed = Parser.Parser.ParseTitle(blocklist.SourceTitle);
var episodeInfo = new ParsedEpisodeInfo
{
SeriesTitle = series.Title,
ReleaseTitle = parsed?.ReleaseTitle ?? blocklist.SourceTitle,
Quality = blocklist.Quality,
Languages = blocklist.Languages,
ReleaseGroup = parsed?.ReleaseGroup
};
var input = new CustomFormatInput
{
EpisodeInfo = episodeInfo,
Series = series,
Size = blocklist.Size ?? 0,
Languages = blocklist.Languages
};
return ParseCustomFormat(input);
}
public List<CustomFormat> ParseCustomFormat(EpisodeHistory history, Series series)
{
var parsed = Parser.Parser.ParseTitle(history.SourceTitle);
long.TryParse(history.Data.GetValueOrDefault("size"), out var size);
var episodeInfo = new ParsedEpisodeInfo
{
SeriesTitle = series.Title,
ReleaseTitle = parsed?.ReleaseTitle ?? history.SourceTitle,
Quality = history.Quality,
Languages = history.Languages,
ReleaseGroup = parsed?.ReleaseGroup,
};
var input = new CustomFormatInput
{
EpisodeInfo = episodeInfo,
Series = series,
Size = size,
Languages = history.Languages
};
return ParseCustomFormat(input);
}
private List<CustomFormat> ParseCustomFormat(CustomFormatInput input)
{
return ParseCustomFormat(input, _formatService.All());
}
private static List<CustomFormat> ParseCustomFormat(CustomFormatInput input, List<CustomFormat> allCustomFormats)
{
var matches = new List<CustomFormat>();
@ -45,7 +117,7 @@ namespace NzbDrone.Core.CustomFormats
.GroupBy(t => t.GetType())
.Select(g => new SpecificationMatchesGroup
{
Matches = g.ToDictionary(t => t, t => t.IsSatisfiedBy(episodeInfo))
Matches = g.ToDictionary(t => t, t => t.IsSatisfiedBy(input))
})
.ToList();
@ -58,7 +130,7 @@ namespace NzbDrone.Core.CustomFormats
return matches;
}
public static List<CustomFormat> ParseCustomFormat(EpisodeFile episodeFile, List<CustomFormat> allCustomFormats)
private static List<CustomFormat> ParseCustomFormat(EpisodeFile episodeFile, Series series, List<CustomFormat> allCustomFormats)
{
var sceneName = string.Empty;
if (episodeFile.SceneName.IsNotNullOrWhiteSpace())
@ -74,81 +146,25 @@ namespace NzbDrone.Core.CustomFormats
sceneName = Path.GetFileName(episodeFile.RelativePath);
}
var info = new ParsedEpisodeInfo
var episodeInfo = new ParsedEpisodeInfo
{
SeriesTitle = episodeFile.Series.Value.Title,
ReleaseTitle = sceneName,
Quality = episodeFile.Quality,
Languages = episodeFile.Languages,
ReleaseGroup = episodeFile.ReleaseGroup,
ExtraInfo = new Dictionary<string, object>
{
{ "Size", episodeFile.Size },
{ "Filename", Path.GetFileName(episodeFile.RelativePath) },
{ "OriginalLanguage", episodeFile.Series.Value.OriginalLanguage }
}
ReleaseGroup = episodeFile.ReleaseGroup
};
return ParseCustomFormat(info, allCustomFormats);
}
public List<CustomFormat> ParseCustomFormat(ParsedEpisodeInfo episodeInfo, Series series)
var input = new CustomFormatInput
{
if (series?.OriginalLanguage != null)
{
episodeInfo.ExtraInfo["OriginalLanguage"] = series.OriginalLanguage;
}
return ParseCustomFormat(episodeInfo, _formatService.All());
}
public List<CustomFormat> ParseCustomFormat(EpisodeFile episodeFile)
{
return ParseCustomFormat(episodeFile, _formatService.All());
}
public List<CustomFormat> ParseCustomFormat(Blocklist blocklist)
{
var series = _seriesService.GetSeries(blocklist.SeriesId);
var parsed = Parser.Parser.ParseTitle(blocklist.SourceTitle);
var info = new ParsedEpisodeInfo
{
SeriesTitle = series.Title,
ReleaseTitle = parsed?.ReleaseTitle ?? blocklist.SourceTitle,
Quality = blocklist.Quality,
Languages = blocklist.Languages,
ReleaseGroup = parsed?.ReleaseGroup,
ExtraInfo = new Dictionary<string, object>
{
{ "Size", blocklist.Size }
}
EpisodeInfo = episodeInfo,
Series = series,
Size = episodeFile.Size,
Languages = episodeFile.Languages,
Filename = Path.GetFileName(episodeFile.RelativePath)
};
return ParseCustomFormat(info, series);
}
public List<CustomFormat> ParseCustomFormat(EpisodeHistory history)
{
var series = _seriesService.GetSeries(history.SeriesId);
var parsed = Parser.Parser.ParseTitle(history.SourceTitle);
long.TryParse(history.Data.GetValueOrDefault("size"), out var size);
var info = new ParsedEpisodeInfo
{
SeriesTitle = series.Title,
ReleaseTitle = parsed?.ReleaseTitle ?? history.SourceTitle,
Quality = history.Quality,
Languages = history.Languages,
ReleaseGroup = parsed?.ReleaseGroup,
ExtraInfo = new Dictionary<string, object>
{
{ "Size", size }
}
};
return ParseCustomFormat(info, series);
return ParseCustomFormat(input, allCustomFormats);
}
}
}

View File

@ -0,0 +1,44 @@
using System.Collections.Generic;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.CustomFormats
{
public class CustomFormatInput
{
public ParsedEpisodeInfo EpisodeInfo { get; set; }
public Series Series { get; set; }
public long Size { get; set; }
public List<Language> Languages { get; set; }
public string Filename { get; set; }
public CustomFormatInput()
{
Languages = new List<Language>();
}
// public CustomFormatInput(ParsedEpisodeInfo episodeInfo, Series series)
// {
// EpisodeInfo = episodeInfo;
// Series = series;
// }
//
// public CustomFormatInput(ParsedEpisodeInfo episodeInfo, Series series, long size, List<Language> languages)
// {
// EpisodeInfo = episodeInfo;
// Series = series;
// Size = size;
// Languages = languages;
// }
//
// public CustomFormatInput(ParsedEpisodeInfo episodeInfo, Series series, long size, List<Language> languages, string filename)
// {
// EpisodeInfo = episodeInfo;
// Series = series;
// Size = size;
// Languages = languages;
// Filename = filename;
// }
}
}

View File

@ -1,4 +1,3 @@
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
@ -21,9 +20,9 @@ namespace NzbDrone.Core.CustomFormats
public abstract NzbDroneValidationResult Validate();
public bool IsSatisfiedBy(ParsedEpisodeInfo episodeInfo)
public bool IsSatisfiedBy(CustomFormatInput input)
{
var match = IsSatisfiedByWithoutNegate(episodeInfo);
var match = IsSatisfiedByWithoutNegate(input);
if (Negate)
{
match = !match;
@ -32,6 +31,6 @@ namespace NzbDrone.Core.CustomFormats
return match;
}
protected abstract bool IsSatisfiedByWithoutNegate(ParsedEpisodeInfo episodeInfo);
protected abstract bool IsSatisfiedByWithoutNegate(CustomFormatInput input);
}
}

View File

@ -15,6 +15,6 @@ namespace NzbDrone.Core.CustomFormats
NzbDroneValidationResult Validate();
ICustomFormatSpecification Clone();
bool IsSatisfiedBy(ParsedEpisodeInfo episodeInfo);
bool IsSatisfiedBy(CustomFormatInput input);
}
}

View File

@ -2,7 +2,6 @@ using System.Linq;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
@ -32,13 +31,13 @@ namespace NzbDrone.Core.CustomFormats
[FieldDefinition(1, Label = "Language", Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter))]
public int Value { get; set; }
protected override bool IsSatisfiedByWithoutNegate(ParsedEpisodeInfo episodeInfo)
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{
var comparedLanguage = episodeInfo != null && Value == Language.Original.Id && episodeInfo.ExtraInfo.ContainsKey("OriginalLanguage")
? (Language)episodeInfo.ExtraInfo["OriginalLanguage"]
var comparedLanguage = input.EpisodeInfo != null && Value == Language.Original.Id && input.Series.OriginalLanguage != Language.Unknown
? input.Series.OriginalLanguage
: (Language)Value;
return episodeInfo?.Languages?.Contains(comparedLanguage) ?? false;
return input.Languages?.Contains(comparedLanguage) ?? false;
}
public override NzbDroneValidationResult Validate()

View File

@ -1,5 +1,3 @@
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.CustomFormats
{
public class ReleaseGroupSpecification : RegexSpecificationBase
@ -8,9 +6,9 @@ namespace NzbDrone.Core.CustomFormats
public override string ImplementationName => "Release Group";
public override string InfoLink => "https://wiki.servarr.com/sonarr/settings#custom-formats-2";
protected override bool IsSatisfiedByWithoutNegate(ParsedEpisodeInfo episodeInfo)
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{
return MatchString(episodeInfo?.ReleaseGroup);
return MatchString(input.EpisodeInfo?.ReleaseGroup);
}
}
}

View File

@ -1,7 +1,3 @@
using System.Collections.Generic;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.CustomFormats
{
public class ReleaseTitleSpecification : RegexSpecificationBase
@ -10,11 +6,9 @@ namespace NzbDrone.Core.CustomFormats
public override string ImplementationName => "Release Title";
public override string InfoLink => "https://wiki.servarr.com/sonarr/settings#custom-formats-2";
protected override bool IsSatisfiedByWithoutNegate(ParsedEpisodeInfo episodeInfo)
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{
var filename = (string)episodeInfo?.ExtraInfo?.GetValueOrDefault("Filename");
return MatchString(episodeInfo?.ReleaseTitle) || MatchString(filename);
return MatchString(input.EpisodeInfo?.ReleaseTitle) || MatchString(input.Filename);
}
}
}

View File

@ -24,9 +24,9 @@ namespace NzbDrone.Core.CustomFormats
[FieldDefinition(1, Label = "Resolution", Type = FieldType.Select, SelectOptions = typeof(Resolution))]
public int Value { get; set; }
protected override bool IsSatisfiedByWithoutNegate(ParsedEpisodeInfo episodeInfo)
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{
return (episodeInfo?.Quality?.Quality?.Resolution ?? (int)Resolution.Unknown) == Value;
return (input.EpisodeInfo?.Quality?.Quality?.Resolution ?? (int)Resolution.Unknown) == Value;
}
public override NzbDroneValidationResult Validate()

View File

@ -1,8 +1,5 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
@ -29,9 +26,9 @@ namespace NzbDrone.Core.CustomFormats
[FieldDefinition(1, Label = "Maximum Size", HelpText = "Release must be less than or equal to this size", Unit = "GB", Type = FieldType.Number)]
public double Max { get; set; }
protected override bool IsSatisfiedByWithoutNegate(ParsedEpisodeInfo episodeInfo)
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{
var size = (episodeInfo?.ExtraInfo?.GetValueOrDefault("Size", 0.0) as long?) ?? 0;
var size = input.Size;
return size > Min.Gigabytes() && size <= Max.Gigabytes();
}

View File

@ -24,9 +24,9 @@ namespace NzbDrone.Core.CustomFormats
[FieldDefinition(1, Label = "Source", Type = FieldType.Select, SelectOptions = typeof(QualitySource))]
public int Value { get; set; }
protected override bool IsSatisfiedByWithoutNegate(ParsedEpisodeInfo episodeInfo)
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{
return (episodeInfo?.Quality?.Quality?.Source ?? (int)QualitySource.Unknown) == (QualitySource)Value;
return (input.EpisodeInfo?.Quality?.Quality?.Source ?? (int)QualitySource.Unknown) == (QualitySource)Value;
}
public override NzbDroneValidationResult Validate()

View File

@ -88,19 +88,11 @@ namespace NzbDrone.Core.DecisionEngine
}
}
if (parsedEpisodeInfo != null && report.Size > 0)
{
parsedEpisodeInfo.ExtraInfo.Add("Size", report.Size);
}
if (parsedEpisodeInfo != null && !parsedEpisodeInfo.SeriesTitle.IsNullOrWhiteSpace())
{
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, report.TvdbId, report.TvRageId, searchCriteria);
remoteEpisode.Release = report;
remoteEpisode.CustomFormats = _formatCalculator.ParseCustomFormat(parsedEpisodeInfo, remoteEpisode.Series);
remoteEpisode.CustomFormatScore = remoteEpisode?.Series?.QualityProfile?.Value.CalculateCustomFormatScore(remoteEpisode.CustomFormats) ?? 0;
if (remoteEpisode.Series == null)
{
var reason = "Unknown Series";
@ -120,6 +112,10 @@ namespace NzbDrone.Core.DecisionEngine
else
{
_aggregationService.Augment(remoteEpisode);
remoteEpisode.CustomFormats = _formatCalculator.ParseCustomFormat(remoteEpisode);
remoteEpisode.CustomFormatScore = remoteEpisode?.Series?.QualityProfile?.Value.CalculateCustomFormatScore(remoteEpisode.CustomFormats) ?? 0;
remoteEpisode.DownloadAllowed = remoteEpisode.Episodes.Any();
decision = GetDecisionForReport(remoteEpisode, searchCriteria);
}

View File

@ -52,7 +52,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
continue;
}
var queuedItemCustomFormats = _formatService.ParseCustomFormat(remoteEpisode.ParsedEpisodeInfo, subject.Series);
var queuedItemCustomFormats = _formatService.ParseCustomFormat(remoteEpisode);
_logger.Debug("Checking if existing release in queue meets cutoff. Queued: {0}", remoteEpisode.ParsedEpisodeInfo.Quality);

View File

@ -59,7 +59,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
continue;
}
var customFormats = _formatService.ParseCustomFormat(mostRecent);
var customFormats = _formatService.ParseCustomFormat(mostRecent, subject.Series);
// The series will be the same as the one in history since it's the same episode.
// Instead of fetching the series from the DB reuse the known series.

View File

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Aggregation.Aggregators
{
public class AggregateLanguages : IAggregateRemoteEpisode
{
private readonly Logger _logger;
public AggregateLanguages(Logger logger)
{
_logger = logger;
}
public RemoteEpisode Aggregate(RemoteEpisode remoteEpisode)
{
var parsedEpisodeInfo = remoteEpisode.ParsedEpisodeInfo;
var languages = parsedEpisodeInfo.Languages;
var series = remoteEpisode.Series;
var releaseTokens = parsedEpisodeInfo.ReleaseTokens ?? parsedEpisodeInfo.ReleaseTitle;
var normalizedReleaseTokens = Parser.Parser.NormalizeEpisodeTitle(releaseTokens);
var languagesToRemove = new List<Language>();
if (series == null)
{
_logger.Debug("Unable to aggregate languages, using parsed values: {0}", string.Join(", ", languages.ToList()));
remoteEpisode.Languages = languages;
return remoteEpisode;
}
// Exclude any languages that are part of the episode title, if the episode title is in the release tokens (falls back to release title)
foreach (var episode in remoteEpisode.Episodes)
{
var episodeTitleLanguage = LanguageParser.ParseLanguages(episode.Title);
if (!episodeTitleLanguage.Contains(Language.Unknown))
{
var normalizedEpisodeTitle = Parser.Parser.NormalizeEpisodeTitle(episode.Title);
var episodeTitleIndex = normalizedReleaseTokens.IndexOf(normalizedEpisodeTitle, StringComparison.CurrentCultureIgnoreCase);
if (episodeTitleIndex >= 0)
{
releaseTokens = releaseTokens.Remove(episodeTitleIndex, normalizedEpisodeTitle.Length);
languagesToRemove.AddRange(episodeTitleLanguage);
}
}
}
// Remove any languages still in the title that would normally be removed
languagesToRemove = languagesToRemove.Except(LanguageParser.ParseLanguages(releaseTokens)).ToList();
// Remove all languages that aren't part of the updated releaseTokens
languages = languages.Except(languagesToRemove).ToList();
// Use series language as fallback if we couldn't parse a language
if (languages.Count == 0 || (languages.Count == 1 && languages.First() == Language.Unknown))
{
languages = new List<Language> { series.OriginalLanguage };
_logger.Debug("Language couldn't be parsed from release, fallback to series original language: {0}", series.OriginalLanguage.Name);
}
if (languages.Contains(Language.Original))
{
languages.Remove(Language.Original);
if (!languages.Contains(series.OriginalLanguage))
{
languages.Add(series.OriginalLanguage);
}
else
{
languages.Add(Language.Unknown);
}
}
_logger.Debug("Selected languages: {0}", string.Join(", ", languages.ToList()));
remoteEpisode.Languages = languages;
return remoteEpisode;
}
}
}

View File

@ -57,7 +57,7 @@ namespace NzbDrone.Core.Download.Clients.Flood
result.Add(remoteEpisode.ParsedEpisodeInfo.Quality.Quality.ToString());
break;
case (int)AdditionalTags.Languages:
result.UnionWith(remoteEpisode.ParsedEpisodeInfo.Languages.ConvertAll(language => language.ToString()));
result.UnionWith(remoteEpisode.Languages.ConvertAll(language => language.ToString()));
break;
case (int)AdditionalTags.ReleaseGroup:
result.Add(remoteEpisode.ParsedEpisodeInfo.ReleaseGroup);

View File

@ -39,7 +39,7 @@ namespace NzbDrone.Core.Download
{
SeriesId = series.Id,
EpisodeIds = episodes.Select(e => e.Id).ToList(),
Languages = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Languages,
Languages = trackedDownload.RemoteEpisode.Languages,
Quality = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Quality,
SourceTitle = trackedDownload.DownloadItem.Title,
DownloadClientInfo = trackedDownload.DownloadItem.DownloadClientInfo,

View File

@ -7,6 +7,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download.Aggregation;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.Languages;
@ -44,7 +45,8 @@ namespace NzbDrone.Core.Download.Pending
private readonly IDelayProfileService _delayProfileService;
private readonly ITaskManager _taskManager;
private readonly IConfigService _configService;
private readonly ICustomFormatCalculationService _customFormatCalculationService;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly IRemoteEpisodeAggregationService _aggregationService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
@ -55,7 +57,8 @@ namespace NzbDrone.Core.Download.Pending
IDelayProfileService delayProfileService,
ITaskManager taskManager,
IConfigService configService,
ICustomFormatCalculationService customFormatCalculationService,
ICustomFormatCalculationService formatCalculator,
IRemoteEpisodeAggregationService aggregationService,
IEventAggregator eventAggregator,
Logger logger)
{
@ -66,7 +69,8 @@ namespace NzbDrone.Core.Download.Pending
_delayProfileService = delayProfileService;
_taskManager = taskManager;
_configService = configService;
_customFormatCalculationService = customFormatCalculationService;
_formatCalculator = formatCalculator;
_aggregationService = aggregationService;
_eventAggregator = eventAggregator;
_logger = logger;
}
@ -158,6 +162,7 @@ namespace NzbDrone.Core.Download.Pending
var nextRssSync = new Lazy<DateTime>(() => _taskManager.GetNextExecution(typeof(RssSyncCommand)));
var pendingReleases = IncludeRemoteEpisodes(_repository.WithoutFallback());
foreach (var pendingRelease in pendingReleases)
{
foreach (var episode in pendingRelease.RemoteEpisode.Episodes)
@ -185,7 +190,7 @@ namespace NzbDrone.Core.Download.Pending
Id = GetQueueId(pendingRelease, episode),
Series = pendingRelease.RemoteEpisode.Series,
Episode = episode,
Languages = pendingRelease.RemoteEpisode.ParsedEpisodeInfo.Languages,
Languages = pendingRelease.RemoteEpisode.Languages,
Quality = pendingRelease.RemoteEpisode.ParsedEpisodeInfo.Quality,
Title = pendingRelease.Title,
Size = pendingRelease.RemoteEpisode.Release.Size,
@ -330,7 +335,8 @@ namespace NzbDrone.Core.Download.Pending
release.RemoteEpisode.Episodes = new List<Episode>();
}
release.RemoteEpisode.CustomFormats = _customFormatCalculationService.ParseCustomFormat(release.RemoteEpisode.ParsedEpisodeInfo, release.RemoteEpisode.Series);
_aggregationService.Augment(release.RemoteEpisode);
release.RemoteEpisode.CustomFormats = _formatCalculator.ParseCustomFormat(release.RemoteEpisode);
result.Add(release);
}

View File

@ -5,6 +5,7 @@ using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Download.Aggregation;
using NzbDrone.Core.Download.History;
using NzbDrone.Core.History;
using NzbDrone.Core.Messaging.Events;
@ -31,6 +32,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
private readonly IHistoryService _historyService;
private readonly IEventAggregator _eventAggregator;
private readonly IDownloadHistoryService _downloadHistoryService;
private readonly IRemoteEpisodeAggregationService _aggregationService;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly Logger _logger;
private readonly ICached<TrackedDownload> _cache;
@ -41,6 +43,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
ICustomFormatCalculationService formatCalculator,
IEventAggregator eventAggregator,
IDownloadHistoryService downloadHistoryService,
IRemoteEpisodeAggregationService aggregationService,
Logger logger)
{
_parsingService = parsingService;
@ -48,6 +51,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
_formatCalculator = formatCalculator;
_eventAggregator = eventAggregator;
_downloadHistoryService = downloadHistoryService;
_aggregationService = aggregationService;
_cache = cacheManager.GetCache<TrackedDownload>(GetType());
_logger = logger;
}
@ -111,6 +115,8 @@ namespace NzbDrone.Core.Download.TrackedDownloads
if (parsedEpisodeInfo != null)
{
trackedDownload.RemoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0, 0);
_aggregationService.Augment(trackedDownload.RemoteEpisode);
}
var downloadHistory = _downloadHistoryService.GetLatestDownloadHistoryItem(downloadItem.DownloadId);
@ -147,7 +153,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
// Calculate custom formats
if (trackedDownload.RemoteEpisode != null)
{
trackedDownload.RemoteEpisode.CustomFormats = _formatCalculator.ParseCustomFormat(parsedEpisodeInfo, trackedDownload.RemoteEpisode.Series);
trackedDownload.RemoteEpisode.CustomFormats = _formatCalculator.ParseCustomFormat(trackedDownload.RemoteEpisode);
}
// Track it so it can be displayed in the queue even though we can't determine which series it is for
@ -206,6 +212,8 @@ namespace NzbDrone.Core.Download.TrackedDownloads
var parsedEpisodeInfo = Parser.Parser.ParseTitle(trackedDownload.DownloadItem.Title);
trackedDownload.RemoteEpisode = parsedEpisodeInfo == null ? null : _parsingService.Map(parsedEpisodeInfo, 0, 0);
_aggregationService.Augment(trackedDownload.RemoteEpisode);
}
private static TrackedDownloadState GetStateFromHistory(DownloadHistoryEventType eventType)

View File

@ -148,7 +148,7 @@ namespace NzbDrone.Core.History
SeriesId = episode.SeriesId,
EpisodeId = episode.Id,
DownloadId = message.DownloadId,
Languages = message.Episode.ParsedEpisodeInfo.Languages,
Languages = message.Episode.Languages,
};
history.Data.Add("Indexer", message.Episode.Release.Indexer);

View File

@ -38,7 +38,7 @@ namespace NzbDrone.Core.Organizer
private readonly INamingConfigService _namingConfigService;
private readonly IQualityDefinitionService _qualityDefinitionService;
private readonly IUpdateMediaInfo _mediaInfoUpdater;
private readonly ICustomFormatService _formatService;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly ICached<EpisodeFormat[]> _episodeFormatCache;
private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache;
private readonly ICached<bool> _requiresEpisodeTitleCache;
@ -115,13 +115,13 @@ namespace NzbDrone.Core.Organizer
IQualityDefinitionService qualityDefinitionService,
ICacheManager cacheManager,
IUpdateMediaInfo mediaInfoUpdater,
ICustomFormatService formatService,
ICustomFormatCalculationService formatCalculator,
Logger logger)
{
_namingConfigService = namingConfigService;
_qualityDefinitionService = qualityDefinitionService;
_mediaInfoUpdater = mediaInfoUpdater;
_formatService = formatService;
_formatCalculator = formatCalculator;
_episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat");
_absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat");
_requiresEpisodeTitleCache = cacheManager.GetCache<bool>(GetType(), "requiresEpisodeTitle");
@ -691,7 +691,7 @@ namespace NzbDrone.Core.Organizer
if (customFormats == null)
{
episodeFile.Series = series;
customFormats = CustomFormatCalculationService.ParseCustomFormat(episodeFile, _formatService.All());
customFormats = _formatCalculator.ParseCustomFormat(episodeFile, series);
}
tokenHandlers["{Custom Formats}"] = m => string.Join(" ", customFormats.Where(x => x.IncludeCustomFormatWhenRenaming));

View File

@ -30,9 +30,6 @@ namespace NzbDrone.Core.Parser.Model
public string ReleaseTokens { get; set; }
public int? DailyPart { get; set; }
[JsonIgnore]
public Dictionary<string, object> ExtraInfo { get; set; } = new Dictionary<string, object>();
public ParsedEpisodeInfo()
{
EpisodeNumbers = new int[0];

View File

@ -4,6 +4,7 @@ using System.Linq;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Parser.Model
@ -23,11 +24,13 @@ namespace NzbDrone.Core.Parser.Model
public List<CustomFormat> CustomFormats { get; set; }
public int CustomFormatScore { get; set; }
public SeriesMatchType SeriesMatchType { get; set; }
public List<Language> Languages { get; set; }
public RemoteEpisode()
{
Episodes = new List<Episode>();
CustomFormats = new List<CustomFormat>();
Languages = new List<Language>();
}
public bool IsRecentEpisode()

View File

@ -184,30 +184,9 @@ namespace NzbDrone.Core.Parser
{
remoteEpisode.Episodes = GetEpisodes(parsedEpisodeInfo, series, remoteEpisode.MappedSeasonNumber, sceneSource, searchCriteria);
}
parsedEpisodeInfo.ExtraInfo["OriginalLanguage"] = series.OriginalLanguage;
}
// Use series language as fallback if we could't parse a language (more accurate than just using English)
if (parsedEpisodeInfo.Languages.Count <= 1 && parsedEpisodeInfo.Languages.First() == Language.Unknown && series != null)
{
parsedEpisodeInfo.Languages = new List<Language> { series.OriginalLanguage };
_logger.Debug("Language couldn't be parsed from release, fallback to series original language: {0}", series.OriginalLanguage.Name);
}
if (parsedEpisodeInfo.Languages.Contains(Language.Original))
{
parsedEpisodeInfo.Languages.Remove(Language.Original);
if (series != null && !parsedEpisodeInfo.Languages.Contains(series.OriginalLanguage))
{
parsedEpisodeInfo.Languages.Add(series.OriginalLanguage);
}
else
{
parsedEpisodeInfo.Languages.Add(Language.Unknown);
}
}
remoteEpisode.Languages = parsedEpisodeInfo.Languages;
if (remoteEpisode.Episodes == null)
{

View File

@ -44,7 +44,7 @@ namespace Sonarr.Api.V3.Blocklist
SourceTitle = model.SourceTitle,
Languages = model.Languages,
Quality = model.Quality,
CustomFormats = formatCalculator.ParseCustomFormat(model).ToResource(),
CustomFormats = formatCalculator.ParseCustomFormat(model, model.Series).ToResource(),
Date = model.Date,
Protocol = model.Protocol,
Indexer = model.Indexer,

View File

@ -49,7 +49,7 @@ namespace Sonarr.Api.V3.History
SourceTitle = model.SourceTitle,
Languages = model.Languages,
Quality = model.Quality,
CustomFormats = formatCalculator.ParseCustomFormat(model).ToResource(),
CustomFormats = formatCalculator.ParseCustomFormat(model, model.Series).ToResource(),
// QualityCutoffNotMet
Date = model.Date,

View File

@ -104,7 +104,7 @@ namespace Sonarr.Api.V3.Indexers
Title = releaseInfo.Title,
FullSeason = parsedEpisodeInfo.FullSeason,
SeasonNumber = parsedEpisodeInfo.SeasonNumber,
Languages = parsedEpisodeInfo.Languages,
Languages = remoteEpisode.Languages,
AirDate = parsedEpisodeInfo.AirDate,
SeriesTitle = parsedEpisodeInfo.SeriesTitle,
EpisodeNumbers = parsedEpisodeInfo.EpisodeNumbers,

View File

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Download.Aggregation;
using NzbDrone.Core.Parser;
using Sonarr.Api.V3.Episodes;
using Sonarr.Api.V3.Series;
@ -11,10 +12,13 @@ namespace Sonarr.Api.V3.Parse
public class ParseController : Controller
{
private readonly IParsingService _parsingService;
private readonly IRemoteEpisodeAggregationService _aggregationService;
public ParseController(IParsingService parsingService)
public ParseController(IParsingService parsingService,
IRemoteEpisodeAggregationService aggregationService)
{
_parsingService = parsingService;
_aggregationService = aggregationService;
}
[HttpGet]
@ -38,6 +42,8 @@ namespace Sonarr.Api.V3.Parse
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0, 0);
_aggregationService.Augment(remoteEpisode);
if (remoteEpisode != null)
{
return new ParseResource