diff --git a/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs index ad660c089..fbb6aae49 100644 --- a/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs @@ -454,10 +454,11 @@ namespace NzbDrone.Core.Test.ParserTests }) }; - var (languageTags, title, language) = LanguageParser.ParseSubtitleLanguageInformation(postTitle, episode); - languageTags.Should().BeEquivalentTo(expectedTags); - title.Should().BeEquivalentTo(expectedTitle); - language.Should().BeEquivalentTo((Language)expectedLanguage); + var subtitleTitleInfo = LanguageParser.ParseSubtitleLanguageInformation(postTitle, episode); + + subtitleTitleInfo.LanguageTags.Should().BeEquivalentTo(expectedTags); + subtitleTitleInfo.Title.Should().BeEquivalentTo(expectedTitle); + subtitleTitleInfo.Language.Should().BeEquivalentTo((Language)expectedLanguage); } [TestCase("Name (2020) - S01E20 - [AAC 2.0].default.forced.ass", "Name (2020)/Season 1/Name (2020) - S01E20 - [AAC 2.0].mkv")] @@ -474,7 +475,10 @@ namespace NzbDrone.Core.Test.ParserTests RelativePath = episodeFilePath }) }; - LanguageParser.ParseSubtitleLanguageInformation(postTitle, episode).Should().BeNull(); + var subtitleTitleInfo = LanguageParser.ParseSubtitleLanguageInformation(postTitle, episode); + subtitleTitleInfo.Language.Should().BeNull(); + subtitleTitleInfo.LanguageTags.Should().BeEmpty(); + subtitleTitleInfo.RawTitle.Should().BeNull(); } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/198_parse_titles_from_existing_subtitle_files.cs b/src/NzbDrone.Core/Datastore/Migration/198_parse_titles_from_existing_subtitle_files.cs index 8982c4e76..1134c2239 100644 --- a/src/NzbDrone.Core/Datastore/Migration/198_parse_titles_from_existing_subtitle_files.cs +++ b/src/NzbDrone.Core/Datastore/Migration/198_parse_titles_from_existing_subtitle_files.cs @@ -41,15 +41,15 @@ namespace NzbDrone.Core.Datastore.Migration EpisodeFile = new LazyLoaded(episodeFile) }; - var subtitleTitleCopyInfo = LanguageParser.CopyFromTitle(LanguageParser.ParseSubtitleLanguageInformation(relativePath, episode)?.Title); + var subtitleTitleInfo = LanguageParser.ParseSubtitleLanguageInformation(relativePath, episode); - if (subtitleTitleCopyInfo.Copy != 0) + if (subtitleTitleInfo.Copy != 0) { updatedTitles.Add(new { Id = id, - Title = subtitleTitleCopyInfo.Title, - Copy = subtitleTitleCopyInfo.Copy + Title = subtitleTitleInfo.Title, + Copy = subtitleTitleInfo.Copy }); } } diff --git a/src/NzbDrone.Core/Extras/Subtitles/ExistingSubtitleImporter.cs b/src/NzbDrone.Core/Extras/Subtitles/ExistingSubtitleImporter.cs index 0b06e1874..6c5a5481e 100644 --- a/src/NzbDrone.Core/Extras/Subtitles/ExistingSubtitleImporter.cs +++ b/src/NzbDrone.Core/Extras/Subtitles/ExistingSubtitleImporter.cs @@ -5,7 +5,6 @@ using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Core.Extras.Files; using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation; -using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; @@ -73,25 +72,17 @@ namespace NzbDrone.Core.Extras.Subtitles var firstEpisode = localEpisode.Episodes.First(); - var subtitleTitleInfo = LanguageParser.ParseSubtitleLanguageInformation(possibleSubtitleFile, firstEpisode); - - var languageTags = subtitleTitleInfo?.LanguageTags ?? LanguageParser.ParseLanguageTags(possibleSubtitleFile); - var title = subtitleTitleInfo?.Title; - var language = subtitleTitleInfo?.Language ?? LanguageParser.ParseSubtitleLanguage(possibleSubtitleFile); - - var subtitleTitleCopyInfo = LanguageParser.CopyFromTitle(title); - var subtitleFile = new SubtitleFile { SeriesId = series.Id, SeasonNumber = localEpisode.SeasonNumber, EpisodeFileId = firstEpisode.EpisodeFileId, RelativePath = series.Path.GetRelativePath(possibleSubtitleFile), - Language = language, - LanguageTags = languageTags, - Title = subtitleTitleCopyInfo.Title, + Language = localEpisode.SubtitleInfo.Language, + LanguageTags = localEpisode.SubtitleInfo.LanguageTags, + Title = localEpisode.SubtitleInfo.Title, Extension = extension, - Copy = subtitleTitleCopyInfo.Copy + Copy = localEpisode.SubtitleInfo.Copy }; subtitleFiles.Add(subtitleFile); diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/AggregationService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/AggregationService.cs index 69dd300cc..49cdda995 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/AggregationService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/AggregationService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Core.Configuration; @@ -30,7 +31,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation IConfigService configService, Logger logger) { - _augmenters = augmenters; + _augmenters = augmenters.OrderBy(a => a.Order).ToList(); _diskProvider = diskProvider; _videoFileInfoReader = videoFileInfoReader; _configService = configService; diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateEpisodes.cs index f243537e5..3c978576e 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateEpisodes.cs @@ -10,6 +10,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators { public class AggregateEpisodes : IAggregateLocalEpisode { + public int Order => 1; + private readonly IParsingService _parsingService; public AggregateEpisodes(IParsingService parsingService) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguage.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguage.cs index 07c0dd34e..69444bc0b 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguage.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguage.cs @@ -10,6 +10,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators { public class AggregateLanguage : IAggregateLocalEpisode { + public int Order => 2; + private readonly List _augmentLanguages; private readonly Logger _logger; diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateQuality.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateQuality.cs index 434663f5e..bde6b1df0 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateQuality.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateQuality.cs @@ -10,6 +10,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators { public class AggregateQuality : IAggregateLocalEpisode { + public int Order => 3; + private readonly List _augmentQualities; private readonly Logger _logger; diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateReleaseGroup.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateReleaseGroup.cs index a8b1d1b8e..e85bfe3d6 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateReleaseGroup.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateReleaseGroup.cs @@ -6,6 +6,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators { public class AggregateReleaseGroup : IAggregateLocalEpisode { + public int Order => 5; + public LocalEpisode Aggregate(LocalEpisode localEpisode, DownloadClientItem downloadClientItem) { // Prefer ReleaseGroup from DownloadClient/Folder if they're not a season pack diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateReleaseInfo.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateReleaseInfo.cs index 111b465f6..85619eb4e 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateReleaseInfo.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateReleaseInfo.cs @@ -8,6 +8,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators { public class AggregateReleaseInfo : IAggregateLocalEpisode { + public int Order => 4; + private readonly IHistoryService _historyService; public AggregateReleaseInfo(IHistoryService historyService) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateSubtitleInfo.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateSubtitleInfo.cs new file mode 100644 index 000000000..3466a744d --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateSubtitleInfo.cs @@ -0,0 +1,42 @@ +using System.IO; +using System.Linq; +using NLog; +using NzbDrone.Core.Download; +using NzbDrone.Core.Extras.Subtitles; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators +{ + public class AggregateSubtitleInfo : IAggregateLocalEpisode + { + public int Order => 6; + + private readonly Logger _logger; + + public AggregateSubtitleInfo(Logger logger) + { + _logger = logger; + } + + public LocalEpisode Aggregate(LocalEpisode localEpisode, DownloadClientItem downloadClientItem) + { + var path = localEpisode.Path; + var isSubtitleFile = SubtitleFileExtensions.Extensions.Contains(Path.GetExtension(path)); + if (!isSubtitleFile) + { + return localEpisode; + } + + var firstEpisode = localEpisode.Episodes.First(); + var subtitleTitleInfo = LanguageParser.ParseSubtitleLanguageInformation(path, firstEpisode); + + subtitleTitleInfo.LanguageTags ??= LanguageParser.ParseLanguageTags(path); + subtitleTitleInfo.Language ??= LanguageParser.ParseSubtitleLanguage(path); + + localEpisode.SubtitleInfo = subtitleTitleInfo; + + return localEpisode; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/IAggregateLocalEpisode.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/IAggregateLocalEpisode.cs index e32cf7a02..7cc4f58be 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/IAggregateLocalEpisode.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/IAggregateLocalEpisode.cs @@ -5,6 +5,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators { public interface IAggregateLocalEpisode { + int Order { get; } LocalEpisode Aggregate(LocalEpisode localEpisode, DownloadClientItem downloadClientItem); } } diff --git a/src/NzbDrone.Core/Parser/LanguageParser.cs b/src/NzbDrone.Core/Parser/LanguageParser.cs index 12fc11d7e..e49f7eefb 100644 --- a/src/NzbDrone.Core/Parser/LanguageParser.cs +++ b/src/NzbDrone.Core/Parser/LanguageParser.cs @@ -34,8 +34,6 @@ namespace NzbDrone.Core.Parser private static readonly Regex SubtitleLanguageTitleRegex = new Regex(".+?(\\.((?full|forced|foreign|default|cc|psdh|sdh)|(?[a-z]{2,3})))*\\.(?[^.]*)(\\.((?<tags2>full|forced|foreign|default|cc|psdh|sdh)|(?<iso_code>[a-z]{2,3})))*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private static readonly Regex SubtitleTitleRegex = new Regex("((?<title>.+) - )?(?<copy>\\d+)", RegexOptions.Compiled); - public static List<Language> ParseLanguages(string title) { foreach (var regex in CleanSeriesTitleRegex) @@ -262,7 +260,7 @@ namespace NzbDrone.Core.Parser if (matchTitle.Groups["iso_code"].Captures.Count is var languageCodeNumber && languageCodeNumber != 1) { - return null; + return new SubtitleTitleInfo(); } var isoCode = matchTitle.Groups["iso_code"].Value; @@ -285,38 +283,18 @@ namespace NzbDrone.Core.Parser if (episodeFileTitle.Contains(title, StringComparison.OrdinalIgnoreCase) || originalEpisodeFileTitle.Contains(title, StringComparison.OrdinalIgnoreCase)) { - return null; + return new SubtitleTitleInfo(); } } return new SubtitleTitleInfo { LanguageTags = languageTags.ToList(), - Title = title, + RawTitle = title, Language = language }; } - public static SubtitleTitleCopyInfo CopyFromTitle(string title) - { - if (title is null) - { - return new SubtitleTitleCopyInfo(0, title); - } - - var match = SubtitleTitleRegex.Match(title); - - if (match.Success) - { - var copy = match.Groups["copy"].ToString(); - var newTitle = match.Groups["title"].Success ? match.Groups["title"].ToString() : null; - - return new SubtitleTitleCopyInfo(int.Parse(copy), newTitle); - } - - return new SubtitleTitleCopyInfo(0, title); - } - public static List<string> ParseLanguageTags(string fileName) { try diff --git a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs index 470310b7c..060a14e6e 100644 --- a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs +++ b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs @@ -44,6 +44,7 @@ namespace NzbDrone.Core.Parser.Model public bool FileRenamedAfterScriptImport { get; set; } public bool ShouldImportExtras { get; set; } public List<string> PossibleExtraFiles { get; set; } + public SubtitleTitleInfo SubtitleInfo { get; set; } public int SeasonNumber { diff --git a/src/NzbDrone.Core/Parser/Model/SubtitleTitleInfo.cs b/src/NzbDrone.Core/Parser/Model/SubtitleTitleInfo.cs index 26f28fe31..e960e305c 100644 --- a/src/NzbDrone.Core/Parser/Model/SubtitleTitleInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/SubtitleTitleInfo.cs @@ -1,24 +1,49 @@ using System.Collections.Generic; +using System.Text.RegularExpressions; using NzbDrone.Core.Languages; namespace NzbDrone.Core.Parser.Model { public class SubtitleTitleInfo { + private static readonly Regex SubtitleTitleRegex = new Regex("((?<title>.+) - )?(?<copy>\\d+)", RegexOptions.Compiled); public List<string> LanguageTags { get; set; } - public string Title { get; set; } + public string RawTitle { get; set; } + public string Title { + get { + if (RawTitle is null) + { + return null; + } + + var match = SubtitleTitleRegex.Match(RawTitle); + + if (match.Success) + { + return match.Groups["title"].Success ? match.Groups["title"].ToString() : null; + } + + return RawTitle; + } + } + public Language Language { get; set; } - } + public int Copy { + get { + if (RawTitle is null) + { + return 0; + } - public class SubtitleTitleCopyInfo - { - public int Copy { get; set; } - public string Title { get; set; } + var match = SubtitleTitleRegex.Match(RawTitle); - public SubtitleTitleCopyInfo(int copy, string title) - { - Copy = copy; - Title = title; + if (match.Success) + { + return int.Parse(match.Groups["copy"].ToString()); + } + + return 0; + } } } }