1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-24 22:36:19 -04:00

New: Custom Formats

Co-Authored-By: ta264 <ta264@users.noreply.github.com>
This commit is contained in:
Qstick
2022-01-23 23:42:41 -06:00
committed by Mark McDowall
parent 909af6c874
commit b04b4000b8
173 changed files with 6401 additions and 1347 deletions
@@ -0,0 +1,25 @@
using NzbDrone.Common.Extensions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class CustomFormatAllowedbyProfileSpecification : IDecisionEngineSpecification
{
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var minScore = subject.Series.QualityProfile.Value.MinFormatScore;
var score = subject.CustomFormatScore;
if (score < minScore)
{
return Decision.Reject("Custom Formats {0} have score {1} below Series profile minimum {2}", subject.CustomFormats.ConcatToString(), score, minScore);
}
return Decision.Accept();
}
}
}
@@ -1,5 +1,6 @@
using System.Linq;
using NLog;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model;
@@ -10,13 +11,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public class CutoffSpecification : IDecisionEngineSpecification
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IEpisodeFilePreferredWordCalculator _episodeFilePreferredWordCalculator;
private readonly ICustomFormatCalculationService _formatService;
private readonly Logger _logger;
public CutoffSpecification(UpgradableSpecification upgradableSpecification, IEpisodeFilePreferredWordCalculator episodeFilePreferredWordCalculator, Logger logger)
public CutoffSpecification(UpgradableSpecification upgradableSpecification, ICustomFormatCalculationService formatService, Logger logger)
{
_upgradableSpecification = upgradableSpecification;
_episodeFilePreferredWordCalculator = episodeFilePreferredWordCalculator;
_formatService = formatService;
_logger = logger;
}
@@ -38,13 +39,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger.Debug("Comparing file quality and language with report. Existing file is {0} - {1}", file.Quality, file.Language);
var customFormats = _formatService.ParseCustomFormat(file);
if (!_upgradableSpecification.CutoffNotMet(qualityProfile,
languageProfile,
file.Quality,
file.Language,
_episodeFilePreferredWordCalculator.Calculate(subject.Series, file),
subject.ParsedEpisodeInfo.Quality,
subject.PreferredWordScore))
_formatService.ParseCustomFormat(file),
subject.ParsedEpisodeInfo.Quality))
{
_logger.Debug("Cutoff already met, rejecting.");
@@ -1,5 +1,6 @@
using System.Linq;
using NLog;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
@@ -12,17 +13,17 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
private readonly IQueueService _queueService;
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly ICustomFormatCalculationService _formatService;
private readonly Logger _logger;
public QueueSpecification(IQueueService queueService,
UpgradableSpecification upgradableSpecification,
IPreferredWordService preferredWordServiceCalculator,
ICustomFormatCalculationService formatService,
Logger logger)
{
_queueService = queueService;
_upgradableSpecification = upgradableSpecification;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_formatService = formatService;
_logger = logger;
}
@@ -52,16 +53,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
continue;
}
var queuedItemCustomFormats = _formatService.ParseCustomFormat(remoteEpisode.ParsedEpisodeInfo);
_logger.Debug("Checking if existing release in queue meets cutoff. Queued: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language);
var queuedItemPreferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Series, queueItem.Title, subject.Release?.IndexerId ?? 0);
if (!_upgradableSpecification.CutoffNotMet(qualityProfile,
languageProfile,
remoteEpisode.ParsedEpisodeInfo.Quality,
remoteEpisode.ParsedEpisodeInfo.Language,
queuedItemPreferredWordScore,
subject.ParsedEpisodeInfo.Quality,
subject.PreferredWordScore))
queuedItemCustomFormats,
subject.ParsedEpisodeInfo.Quality))
{
return Decision.Reject("Release in queue already meets cutoff: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language);
}
@@ -72,10 +73,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
languageProfile,
remoteEpisode.ParsedEpisodeInfo.Quality,
remoteEpisode.ParsedEpisodeInfo.Language,
queuedItemPreferredWordScore,
remoteEpisode.CustomFormats,
subject.ParsedEpisodeInfo.Quality,
subject.ParsedEpisodeInfo.Language,
subject.PreferredWordScore))
subject.CustomFormats))
{
return Decision.Reject("Release in queue is of equal or higher preference: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language);
}
@@ -2,6 +2,7 @@ using System;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.History;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
@@ -13,20 +14,20 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
private readonly IHistoryService _historyService;
private readonly UpgradableSpecification _upgradableSpecification;
private readonly ICustomFormatCalculationService _formatService;
private readonly IConfigService _configService;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly Logger _logger;
public HistorySpecification(IHistoryService historyService,
UpgradableSpecification upgradableSpecification,
IConfigService configService,
IPreferredWordService preferredWordServiceCalculator,
Logger logger)
UpgradableSpecification upgradableSpecification,
ICustomFormatCalculationService formatService,
IConfigService configService,
Logger logger)
{
_historyService = historyService;
_upgradableSpecification = upgradableSpecification;
_formatService = formatService;
_configService = configService;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_logger = logger;
}
@@ -58,28 +59,27 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
continue;
}
var customFormats = _formatService.ParseCustomFormat(mostRecent);
// 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.
var preferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Series, mostRecent.SourceTitle, subject.Release?.IndexerId ?? 0);
var cutoffUnmet = _upgradableSpecification.CutoffNotMet(
subject.Series.QualityProfile,
subject.Series.LanguageProfile,
mostRecent.Quality,
mostRecent.Language,
preferredWordScore,
subject.ParsedEpisodeInfo.Quality,
subject.PreferredWordScore);
customFormats,
subject.ParsedEpisodeInfo.Quality);
var upgradeable = _upgradableSpecification.IsUpgradable(
subject.Series.QualityProfile,
subject.Series.LanguageProfile,
mostRecent.Quality,
mostRecent.Language,
preferredWordScore,
customFormats,
subject.ParsedEpisodeInfo.Quality,
subject.ParsedEpisodeInfo.Language,
subject.PreferredWordScore);
subject.CustomFormats);
if (!cutoffUnmet)
{
@@ -1,5 +1,8 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Profiles.Qualities;
@@ -9,10 +12,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
public interface IUpgradableSpecification
{
bool IsUpgradable(QualityProfile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality, Language newLanguage, int newScore);
bool IsUpgradable(QualityProfile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, List<CustomFormat> currentCustomFormats, QualityModel newQuality, Language newLanguage, List<CustomFormat> newCustomFormats);
bool QualityCutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null);
bool LanguageCutoffNotMet(LanguageProfile languageProfile, Language currentLanguage);
bool CutoffNotMet(QualityProfile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality = null, int newScore = 0);
bool CutoffNotMet(QualityProfile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, List<CustomFormat> currentCustomFormats, QualityModel newQuality = null);
bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality);
bool IsUpgradeAllowed(QualityProfile qualityProfile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality, Language newLanguage);
}
@@ -35,7 +38,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return newScore > currentScore;
}
public bool IsUpgradable(QualityProfile qualityProfile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality, Language newLanguage, int newScore)
public bool IsUpgradable(QualityProfile qualityProfile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, List<CustomFormat> currentCustomFormats, QualityModel newQuality, Language newLanguage, List<CustomFormat> newCustomFormats)
{
var qualityComparer = new QualityModelComparer(qualityProfile);
var qualityCompare = qualityComparer.Compare(newQuality?.Quality, currentQuality.Quality);
@@ -64,6 +67,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return true;
}
var currentFormatScore = qualityProfile.CalculateCustomFormatScore(currentCustomFormats);
var newFormatScore = qualityProfile.CalculateCustomFormatScore(newCustomFormats);
// Reject unless the user does not prefer propers/repacks and it's a revision downgrade.
if (downloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer &&
qualityRevisionComapre < 0)
@@ -86,13 +92,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return false;
}
if (!IsPreferredWordUpgradable(currentScore, newScore))
if (newFormatScore <= currentFormatScore)
{
_logger.Debug("Existing item has an equal or better preferred word score, skipping");
_logger.Debug("New item's custom formats [{0}] do not improve on [{1}], skipping",
newCustomFormats.ConcatToString(),
currentCustomFormats.ConcatToString());
return false;
}
_logger.Debug("New item has a better preferred word score");
_logger.Debug("New item has a better custom format score");
return true;
}
@@ -125,7 +133,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return languageCompare < 0;
}
public bool CutoffNotMet(QualityProfile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality = null, int newScore = 0)
private bool CustomFormatCutoffNotMet(QualityProfile profile, List<CustomFormat> currentFormats)
{
var score = profile.CalculateCustomFormatScore(currentFormats);
return score < profile.CutoffFormatScore;
}
public bool CutoffNotMet(QualityProfile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, List<CustomFormat> currentFormats, QualityModel newQuality = null)
{
// If we can upgrade the language (it is not the cutoff) then the quality doesn't
// matter as we can always get same quality with prefered language.
@@ -139,7 +153,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return true;
}
if (IsPreferredWordUpgradable(currentScore, newScore))
if (CustomFormatCutoffNotMet(profile, currentFormats))
{
return true;
}
@@ -1,5 +1,6 @@
using System.Linq;
using NLog;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model;
@@ -10,13 +11,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public class UpgradeDiskSpecification : IDecisionEngineSpecification
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IEpisodeFilePreferredWordCalculator _episodeFilePreferredWordCalculator;
private readonly ICustomFormatCalculationService _formatService;
private readonly Logger _logger;
public UpgradeDiskSpecification(UpgradableSpecification upgradableSpecification, IEpisodeFilePreferredWordCalculator episodeFilePreferredWordCalculator, Logger logger)
public UpgradeDiskSpecification(UpgradableSpecification upgradableSpecification,
ICustomFormatCalculationService formatService,
Logger logger)
{
_upgradableSpecification = upgradableSpecification;
_episodeFilePreferredWordCalculator = episodeFilePreferredWordCalculator;
_formatService = formatService;
_logger = logger;
}
@@ -33,16 +36,18 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
continue;
}
var customFormats = _formatService.ParseCustomFormat(file);
_logger.Debug("Comparing file quality and language with report. Existing file is {0} - {1}", file.Quality, file.Language);
if (!_upgradableSpecification.IsUpgradable(subject.Series.QualityProfile,
subject.Series.LanguageProfile,
file.Quality,
file.Language,
_episodeFilePreferredWordCalculator.Calculate(subject.Series, file),
customFormats,
subject.ParsedEpisodeInfo.Quality,
subject.ParsedEpisodeInfo.Language,
subject.PreferredWordScore))
subject.CustomFormats))
{
return Decision.Reject("Existing file on disk is of equal or higher preference: {0} - {1}", file.Quality, file.Language);
}