diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/LocalEpisodeCustomFormatCalculationServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/LocalEpisodeCustomFormatCalculationServiceFixture.cs new file mode 100644 index 000000000..3a5ac8ea2 --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/LocalEpisodeCustomFormatCalculationServiceFixture.cs @@ -0,0 +1,100 @@ +using System.Collections.Generic; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.CustomFormats; +using NzbDrone.Core.Languages; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.MediaFiles.EpisodeImport; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport +{ + [TestFixture] + public class LocalEpisodeCustomFormatCalculationServiceFixture : CoreTest + { + private const int EnglishCustomFormatScore = 10; + private const int SpanishCustomFormatScore = 2; + private LocalEpisode _localEpisode; + private Series _series; + private QualityModel _quality; + private CustomFormat _englishCustomFormat; + private CustomFormat _spanishCustomFormat; + + [SetUp] + public void Setup() + { + _englishCustomFormat = new CustomFormat("HasEnglish") { Id = 1 }; + _spanishCustomFormat = new CustomFormat("HasSpanish") { Id = 2 }; + _series = Builder.CreateNew() + .With(e => e.Path = @"C:\Test\Series".AsOsAgnostic()) + .With(e => e.QualityProfile = new QualityProfile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + FormatItems = [ + new ProfileFormatItem { Format = _englishCustomFormat, Score = EnglishCustomFormatScore }, + new ProfileFormatItem { Format = _spanishCustomFormat, Score = SpanishCustomFormatScore } + ] + }) + .Build(); + + _quality = new QualityModel(Quality.DVD); + + _localEpisode = new LocalEpisode + { + Series = _series, + Quality = _quality, + Languages = new List { Language.Spanish }, + Episodes = new List { new Episode() }, + Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.Spanish.XviD-OSiTV.avi" + }; + + Mocker.GetMock() + .Setup(s => s.ParseCustomFormat(It.IsAny(), It.Is(x => x.Contains("English")))) + .Returns([_englishCustomFormat]); + + Mocker.GetMock() + .Setup(s => s.ParseCustomFormat(It.IsAny(), It.Is(x => x.Contains("Spanish")))) + .Returns([_spanishCustomFormat]); + } + + [Test] + public void should_build_a_filename_and_use_it_to_calculate_custom_score() + { + var renamedFileName = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.English.XviD-OSiTV.avi"; + + Mocker.GetMock() + .Setup(s => s.BuildFileName(It.IsAny>(), It.IsAny(), It.IsAny(), "", null, null)) + .Returns(renamedFileName); + + Subject.ParseEpisodeCustomFormats(_localEpisode).Should().BeEquivalentTo([_englishCustomFormat]); + } + + [Test] + public void should_update_custom_formats_on_local_episode() + { + var renamedFileName = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.English.XviD-OSiTV.avi"; + + Mocker.GetMock() + .Setup(s => s.BuildFileName(It.IsAny>(), It.IsAny(), It.IsAny(), "", null, null)) + .Returns(renamedFileName); + + Subject.UpdateEpisodeCustomFormats(_localEpisode); + _localEpisode.FileNameUsedForCustomFormatCalculation.Should().Be(renamedFileName); + + _localEpisode.OriginalFileNameCustomFormats.Should().BeEquivalentTo([_spanishCustomFormat]); + _localEpisode.OriginalFileNameCustomFormatScore.Should().Be(SpanishCustomFormatScore); + + _localEpisode.CustomFormats.Should().BeEquivalentTo([_englishCustomFormat]); + _localEpisode.CustomFormatScore.Should().Be(EnglishCustomFormatScore); + } + } +} diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs index fbad49de8..eaa888733 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs @@ -9,6 +9,7 @@ using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles; @@ -565,5 +566,48 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeFalse(); } + + [Test] + public void should_return_false_and_a_specific_reason_if_not_upgrade_to_custom_format_score_after_local_file_rename_but_was_before() + { + var episodeFileCustomFormats = Builder.CreateListOfSize(1).Build().ToList(); + + var episodeFile = new EpisodeFile + { + Quality = new QualityModel(Quality.Bluray1080p) + }; + + _series.QualityProfile.Value.FormatItems = episodeFileCustomFormats.Select(c => new ProfileFormatItem + { + Format = c, + Score = 50 + }) + .ToList(); + + Mocker.GetMock() + .Setup(s => s.DownloadPropersAndRepacks) + .Returns(ProperDownloadTypes.DoNotPrefer); + + Mocker.GetMock() + .Setup(s => s.ParseCustomFormat(episodeFile)) + .Returns(episodeFileCustomFormats); + + _localEpisode.Quality = new QualityModel(Quality.Bluray1080p); + _localEpisode.CustomFormats = Builder.CreateListOfSize(1).Build().ToList(); + _localEpisode.CustomFormatScore = 20; + _localEpisode.OriginalFileNameCustomFormats = Builder.CreateListOfSize(1).Build().ToList(); + _localEpisode.OriginalFileNameCustomFormatScore = 60; + + _localEpisode.Episodes = Builder.CreateListOfSize(1) + .All() + .With(e => e.EpisodeFileId = 1) + .With(e => e.EpisodeFile = new LazyLoaded(episodeFile)) + .Build() + .ToList(); + + var result = Subject.IsSatisfiedBy(_localEpisode, null); + result.Accepted.Should().BeFalse(); + result.Reason.Should().Be(ImportRejectionReason.NotCustomFormatUpgradeAfterRename); + } } } diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs b/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs index 1f0cb296b..14f206609 100644 --- a/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs +++ b/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.CustomFormats List ParseCustomFormat(EpisodeFile episodeFile); List ParseCustomFormat(Blocklist blocklist, Series series); List ParseCustomFormat(EpisodeHistory history, Series series); - List ParseCustomFormat(LocalEpisode localEpisode); + List ParseCustomFormat(LocalEpisode localEpisode, string fileName); } public class CustomFormatCalculationService : ICustomFormatCalculationService @@ -114,12 +114,12 @@ namespace NzbDrone.Core.CustomFormats return ParseCustomFormat(input); } - public List ParseCustomFormat(LocalEpisode localEpisode) + public List ParseCustomFormat(LocalEpisode localEpisode, string fileName) { var episodeInfo = new ParsedEpisodeInfo { SeriesTitle = localEpisode.Series.Title, - ReleaseTitle = localEpisode.SceneName.IsNotNullOrWhiteSpace() ? localEpisode.SceneName : Path.GetFileName(localEpisode.Path), + ReleaseTitle = localEpisode.SceneName.IsNotNullOrWhiteSpace() ? localEpisode.SceneName : fileName, Quality = localEpisode.Quality, Languages = localEpisode.Languages, ReleaseGroup = localEpisode.ReleaseGroup @@ -133,7 +133,7 @@ namespace NzbDrone.Core.CustomFormats Languages = localEpisode.Languages, IndexerFlags = localEpisode.IndexerFlags, ReleaseType = localEpisode.ReleaseType, - Filename = Path.GetFileName(localEpisode.Path) + Filename = fileName }; return ParseCustomFormat(input); diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index 90f15a348..0db1234e3 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using NLog; using NzbDrone.Common.Disk; @@ -85,24 +84,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport continue; } - var episodeFile = new EpisodeFile(); - episodeFile.DateAdded = DateTime.UtcNow; - episodeFile.SeriesId = localEpisode.Series.Id; - episodeFile.Path = localEpisode.Path.CleanFilePath(); + var episodeFile = localEpisode.ToEpisodeFile(); episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path); - episodeFile.Quality = localEpisode.Quality; - episodeFile.MediaInfo = localEpisode.MediaInfo; - episodeFile.Series = localEpisode.Series; - episodeFile.SeasonNumber = localEpisode.SeasonNumber; - episodeFile.Episodes = localEpisode.Episodes; - episodeFile.ReleaseGroup = localEpisode.ReleaseGroup; - episodeFile.ReleaseHash = localEpisode.ReleaseHash; - episodeFile.Languages = localEpisode.Languages; - - // Prefer the release type from the download client, folder and finally the file so we have the most accurate information. - episodeFile.ReleaseType = localEpisode.DownloadClientEpisodeInfo?.ReleaseType ?? - localEpisode.FolderEpisodeInfo?.ReleaseType ?? - localEpisode.FileEpisodeInfo.ReleaseType; if (downloadClientItem?.DownloadId.IsNotNullOrWhiteSpace() == true) { @@ -118,23 +101,12 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport // Prefer the release type from the grabbed history if (Enum.TryParse(grabHistory?.Data.GetValueOrDefault("releaseType"), true, out ReleaseType releaseType)) { - episodeFile.ReleaseType = releaseType; + if (releaseType != ReleaseType.Unknown) + { + episodeFile.ReleaseType = releaseType; + } } } - else - { - episodeFile.IndexerFlags = localEpisode.IndexerFlags; - episodeFile.ReleaseType = localEpisode.ReleaseType; - } - - // Fall back to parsed information if history is unavailable or missing - if (episodeFile.ReleaseType == ReleaseType.Unknown) - { - // Prefer the release type from the download client, folder and finally the file so we have the most accurate information. - episodeFile.ReleaseType = localEpisode.DownloadClientEpisodeInfo?.ReleaseType ?? - localEpisode.FolderEpisodeInfo?.ReleaseType ?? - localEpisode.FileEpisodeInfo.ReleaseType; - } bool copyOnly; switch (importMode) @@ -153,15 +125,20 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport if (newDownload) { - episodeFile.SceneName = localEpisode.SceneName; - episodeFile.OriginalFilePath = GetOriginalFilePath(downloadClientItem, localEpisode); + if (downloadClientItem is { OutputPath.IsEmpty: false }) + { + var outputDirectory = downloadClientItem.OutputPath.Directory.ToString(); + + if (outputDirectory.IsParentPath(localEpisode.Path)) + { + episodeFile.OriginalFilePath = outputDirectory.GetRelativePath(localEpisode.Path); + } + } oldFiles = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly).OldFiles; } else { - episodeFile.RelativePath = localEpisode.Series.Path.GetRelativePath(episodeFile.Path); - // Delete existing files from the DB mapped to this path var previousFiles = _mediaFileService.GetFilesWithRelativePath(localEpisode.Series.Id, episodeFile.RelativePath); @@ -228,42 +205,5 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport return importResults; } - - private string GetOriginalFilePath(DownloadClientItem downloadClientItem, LocalEpisode localEpisode) - { - var path = localEpisode.Path; - - if (downloadClientItem != null && !downloadClientItem.OutputPath.IsEmpty) - { - var outputDirectory = downloadClientItem.OutputPath.Directory.ToString(); - - if (outputDirectory.IsParentPath(path)) - { - return outputDirectory.GetRelativePath(path); - } - } - - var folderEpisodeInfo = localEpisode.FolderEpisodeInfo; - - if (folderEpisodeInfo != null) - { - var folderPath = path.GetAncestorPath(folderEpisodeInfo.ReleaseTitle); - - if (folderPath != null) - { - return folderPath.GetParentPath().GetRelativePath(path); - } - } - - var parentPath = path.GetParentPath(); - var grandparentPath = parentPath.GetParentPath(); - - if (grandparentPath != null) - { - return grandparentPath.GetRelativePath(path); - } - - return Path.GetFileName(path); - } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs index 762a5a4f3..64ee67750 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs @@ -4,7 +4,6 @@ using System.Linq; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; -using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Download; using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation; @@ -30,7 +29,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport private readonly IDiskProvider _diskProvider; private readonly IDetectSample _detectSample; private readonly ITrackedDownloadService _trackedDownloadService; - private readonly ICustomFormatCalculationService _formatCalculator; + private readonly ILocalEpisodeCustomFormatCalculationService _formatCalculator; private readonly Logger _logger; public ImportDecisionMaker(IEnumerable specifications, @@ -39,7 +38,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport IDiskProvider diskProvider, IDetectSample detectSample, ITrackedDownloadService trackedDownloadService, - ICustomFormatCalculationService formatCalculator, + ILocalEpisodeCustomFormatCalculationService formatCalculator, Logger logger) { _specifications = specifications; @@ -158,8 +157,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport } } - localEpisode.CustomFormats = _formatCalculator.ParseCustomFormat(localEpisode); - localEpisode.CustomFormatScore = localEpisode.Series.QualityProfile?.Value.CalculateCustomFormatScore(localEpisode.CustomFormats) ?? 0; + _formatCalculator.UpdateEpisodeCustomFormats(localEpisode); decision = GetDecision(localEpisode, downloadClientItem); } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportRejectionReason.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportRejectionReason.cs index 7a0ce4170..20ca27b02 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportRejectionReason.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportRejectionReason.cs @@ -36,5 +36,6 @@ public enum ImportRejectionReason UnverifiedSceneMapping, NotQualityUpgrade, NotRevisionUpgrade, - NotCustomFormatUpgrade + NotCustomFormatUpgrade, + NotCustomFormatUpgradeAfterRename } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/LocalEpisodeCustomFormatCalculationService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/LocalEpisodeCustomFormatCalculationService.cs new file mode 100644 index 000000000..5ddf0e957 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/LocalEpisodeCustomFormatCalculationService.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.IO; +using NzbDrone.Core.CustomFormats; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.MediaFiles.EpisodeImport; + +public interface ILocalEpisodeCustomFormatCalculationService +{ + public List ParseEpisodeCustomFormats(LocalEpisode localEpisode); + public void UpdateEpisodeCustomFormats(LocalEpisode localEpisode); +} + +public class LocalEpisodeCustomFormatCalculationService : ILocalEpisodeCustomFormatCalculationService +{ + private readonly IBuildFileNames _fileNameBuilder; + private readonly ICustomFormatCalculationService _formatCalculator; + + public LocalEpisodeCustomFormatCalculationService(IBuildFileNames fileNameBuilder, ICustomFormatCalculationService formatCalculator) + { + _fileNameBuilder = fileNameBuilder; + _formatCalculator = formatCalculator; + } + + public List ParseEpisodeCustomFormats(LocalEpisode localEpisode) + { + var fileNameUsedForCustomFormatCalculation = _fileNameBuilder.BuildFileName(localEpisode.Episodes, localEpisode.Series, localEpisode.ToEpisodeFile()); + return _formatCalculator.ParseCustomFormat(localEpisode, fileNameUsedForCustomFormatCalculation); + } + + public void UpdateEpisodeCustomFormats(LocalEpisode localEpisode) + { + var fileNameUsedForCustomFormatCalculation = _fileNameBuilder.BuildFileName(localEpisode.Episodes, localEpisode.Series, localEpisode.ToEpisodeFile()); + localEpisode.CustomFormats = _formatCalculator.ParseCustomFormat(localEpisode, fileNameUsedForCustomFormatCalculation); + localEpisode.FileNameUsedForCustomFormatCalculation = fileNameUsedForCustomFormatCalculation; + localEpisode.CustomFormatScore = localEpisode.Series.QualityProfile?.Value.CalculateCustomFormatScore(localEpisode.CustomFormats) ?? 0; + + localEpisode.OriginalFileNameCustomFormats = _formatCalculator.ParseCustomFormat(localEpisode, Path.GetFileName(localEpisode.Path)); + localEpisode.OriginalFileNameCustomFormatScore = localEpisode.Series.QualityProfile?.Value.CalculateCustomFormatScore(localEpisode.OriginalFileNameCustomFormats) ?? 0; + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs index 5adf30d8e..11c9d56b6 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs @@ -41,6 +41,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService; private readonly IMediaFileService _mediaFileService; private readonly ICustomFormatCalculationService _formatCalculator; + private readonly ILocalEpisodeCustomFormatCalculationService _localEpisodeFormatCalculator; private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; @@ -55,6 +56,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual ITrackedDownloadService trackedDownloadService, IDownloadedEpisodesImportService downloadedEpisodesImportService, IMediaFileService mediaFileService, + ILocalEpisodeCustomFormatCalculationService localEpisodeFormatCalculator, ICustomFormatCalculationService formatCalculator, IEventAggregator eventAggregator, Logger logger) @@ -70,6 +72,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual _trackedDownloadService = trackedDownloadService; _downloadedEpisodesImportService = downloadedEpisodesImportService; _mediaFileService = mediaFileService; + _localEpisodeFormatCalculator = localEpisodeFormatCalculator; _formatCalculator = formatCalculator; _eventAggregator = eventAggregator; _logger = logger; @@ -180,8 +183,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual localEpisode.IndexerFlags = (IndexerFlags)indexerFlags; localEpisode.ReleaseType = releaseType; - localEpisode.CustomFormats = _formatCalculator.ParseCustomFormat(localEpisode); - localEpisode.CustomFormatScore = localEpisode.Series?.QualityProfile?.Value.CalculateCustomFormatScore(localEpisode.CustomFormats) ?? 0; + _localEpisodeFormatCalculator.UpdateEpisodeCustomFormats(localEpisode); // Augment episode file so imported files have all additional information an automatic import would localEpisode = _aggregationService.Augment(localEpisode, downloadClientItem); @@ -445,8 +447,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual if (decision.LocalEpisode.Series != null) { item.Series = decision.LocalEpisode.Series; - - item.CustomFormats = _formatCalculator.ParseCustomFormat(decision.LocalEpisode); + item.CustomFormats = _localEpisodeFormatCalculator.ParseEpisodeCustomFormats(decision.LocalEpisode); item.CustomFormatScore = item.Series.QualityProfile?.Value.CalculateCustomFormatScore(item.CustomFormats) ?? 0; } @@ -537,8 +538,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual localEpisode.IndexerFlags = (IndexerFlags)file.IndexerFlags; localEpisode.ReleaseType = file.ReleaseType; - localEpisode.CustomFormats = _formatCalculator.ParseCustomFormat(localEpisode); - localEpisode.CustomFormatScore = localEpisode.Series.QualityProfile?.Value.CalculateCustomFormatScore(localEpisode.CustomFormats) ?? 0; + _localEpisodeFormatCalculator.UpdateEpisodeCustomFormats(localEpisode); // TODO: Cleanup non-tracked downloads diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs index ae824ffb9..585213ccd 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs @@ -62,6 +62,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications var currentFormatScore = qualityProfile.CalculateCustomFormatScore(currentFormats); var newFormats = localEpisode.CustomFormats; var newFormatScore = localEpisode.CustomFormatScore; + var newFormatsBeforeRename = localEpisode.OriginalFileNameCustomFormats; + var newFormatScoreBeforeRename = localEpisode.OriginalFileNameCustomFormatScore; if (qualityCompare == 0 && newFormatScore < currentFormatScore) { @@ -71,6 +73,18 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications currentFormats != null ? currentFormats.ConcatToString() : "", currentFormatScore); + if (newFormatScoreBeforeRename > currentFormatScore) + { + return ImportSpecDecision.Reject(ImportRejectionReason.NotCustomFormatUpgradeAfterRename, + "Not a Custom Format upgrade for existing episode file(s). AfterRename: [{0}] ({1}) do not improve on Existing: [{2}] ({3}) even though BeforeRename: [{4}] ({5}) did.", + newFormats != null ? newFormats.ConcatToString() : "", + newFormatScore, + currentFormats != null ? currentFormats.ConcatToString() : "", + currentFormatScore, + newFormatsBeforeRename != null ? newFormatsBeforeRename.ConcatToString() : "", + newFormatScoreBeforeRename); + } + return ImportSpecDecision.Reject(ImportRejectionReason.NotCustomFormatUpgrade, "Not a Custom Format upgrade for existing episode file(s). New: [{0}] ({1}) do not improve on Existing: [{2}] ({3})", newFormats != null ? newFormats.ConcatToString() : "", diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs index cde1db229..bf2a6bc1d 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs @@ -85,7 +85,12 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo } episodeFile.MediaInfo = updatedMediaInfo; - _mediaFileService.Update(episodeFile); + + if (episodeFile.Id != 0) + { + _mediaFileService.Update(episodeFile); + } + _logger.Debug("Updated MediaInfo for '{0}'", path); return true; diff --git a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs index 0129c5d0c..f0e2ad0a3 100644 --- a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs +++ b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; @@ -13,13 +14,6 @@ namespace NzbDrone.Core.Parser.Model { public class LocalEpisode { - public LocalEpisode() - { - Episodes = new List(); - Languages = new List(); - CustomFormats = new List(); - } - public string Path { get; set; } public long Size { get; set; } public ParsedEpisodeInfo FileEpisodeInfo { get; set; } @@ -27,10 +21,10 @@ namespace NzbDrone.Core.Parser.Model public DownloadClientItem DownloadItem { get; set; } public ParsedEpisodeInfo FolderEpisodeInfo { get; set; } public Series Series { get; set; } - public List Episodes { get; set; } + public List Episodes { get; set; } = new(); public List OldFiles { get; set; } public QualityModel Quality { get; set; } - public List Languages { get; set; } + public List Languages { get; set; } = new(); public IndexerFlags IndexerFlags { get; set; } public ReleaseType ReleaseType { get; set; } public MediaInfoModel MediaInfo { get; set; } @@ -40,11 +34,14 @@ namespace NzbDrone.Core.Parser.Model public string ReleaseHash { get; set; } public string SceneName { get; set; } public bool OtherVideoFiles { get; set; } - public List CustomFormats { get; set; } + public List CustomFormats { get; set; } = new(); public int CustomFormatScore { get; set; } + public List OriginalFileNameCustomFormats { get; set; } = new(); + public int OriginalFileNameCustomFormatScore { get; set; } public GrabbedReleaseInfo Release { get; set; } public bool ScriptImported { get; set; } public string FileNameBeforeRename { get; set; } + public string FileNameUsedForCustomFormatCalculation { get; set; } public bool ShouldImportExtras { get; set; } public List PossibleExtraFiles { get; set; } public SubtitleTitleInfo SubtitleInfo { get; set; } @@ -75,5 +72,80 @@ namespace NzbDrone.Core.Parser.Model { return Path; } + + public string GetSceneOrFileName() + { + if (SceneName.IsNotNullOrWhiteSpace()) + { + return SceneName; + } + + if (Path.IsNotNullOrWhiteSpace()) + { + return System.IO.Path.GetFileNameWithoutExtension(Path); + } + + return string.Empty; + } + + public EpisodeFile ToEpisodeFile() + { + var episodeFile = new EpisodeFile + { + DateAdded = DateTime.UtcNow, + SeriesId = Series.Id, + Path = Path.CleanFilePath(), + Quality = Quality, + MediaInfo = MediaInfo, + Series = Series, + SeasonNumber = SeasonNumber, + Episodes = Episodes, + ReleaseGroup = ReleaseGroup, + ReleaseHash = ReleaseHash, + Languages = Languages, + IndexerFlags = IndexerFlags, + ReleaseType = ReleaseType, + SceneName = SceneName, + OriginalFilePath = GetOriginalFilePath() + }; + + if (Series.Path.IsParentPath(episodeFile.Path)) + { + episodeFile.RelativePath = Series.Path.GetRelativePath(Path.CleanFilePath()); + } + + if (episodeFile.ReleaseType == ReleaseType.Unknown) + { + episodeFile.ReleaseType = DownloadClientEpisodeInfo?.ReleaseType ?? + FolderEpisodeInfo?.ReleaseType ?? + FileEpisodeInfo?.ReleaseType ?? + ReleaseType.Unknown; + } + + return episodeFile; + } + + private string GetOriginalFilePath() + { + if (FolderEpisodeInfo != null) + { + var folderPath = Path.GetAncestorPath(FolderEpisodeInfo.ReleaseTitle); + + if (folderPath != null) + { + return folderPath.GetParentPath().GetRelativePath(Path); + } + } + + var parentPath = Path.GetParentPath(); + var grandparentPath = parentPath.GetParentPath(); + + if (grandparentPath != null) + { + return grandparentPath.GetRelativePath(Path); + } + + return System.IO.Path.GetFileName(Path); + } } }