diff --git a/src/NzbDrone.Core/Extras/Subtitles/ExistingSubtitleImporter.cs b/src/NzbDrone.Core/Extras/Subtitles/ExistingSubtitleImporter.cs index ee1c63179..6eca25a13 100644 --- a/src/NzbDrone.Core/Extras/Subtitles/ExistingSubtitleImporter.cs +++ b/src/NzbDrone.Core/Extras/Subtitles/ExistingSubtitleImporter.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -71,14 +72,29 @@ namespace NzbDrone.Core.Extras.Subtitles continue; } + var firstEpisode = localEpisode.Episodes.First(); + + List languageTags = null; + string title = null; + + try + { + (languageTags, title) = LanguageParser.ParseLanguageTagsAndTitle(possibleSubtitleFile, firstEpisode); + } + catch (Exception ex) + { + _logger.Debug(ex, "Failed parsing language tags with title from subtitle file: {0}", possibleSubtitleFile); + } + var subtitleFile = new SubtitleFile { SeriesId = series.Id, SeasonNumber = localEpisode.SeasonNumber, - EpisodeFileId = localEpisode.Episodes.First().EpisodeFileId, + EpisodeFileId = firstEpisode.EpisodeFileId, RelativePath = series.Path.GetRelativePath(possibleSubtitleFile), Language = LanguageParser.ParseSubtitleLanguage(possibleSubtitleFile), - LanguageTags = LanguageParser.ParseLanguageTags(possibleSubtitleFile), + LanguageTags = languageTags ?? LanguageParser.ParseLanguageTags(possibleSubtitleFile), + Title = title, Extension = extension }; diff --git a/src/NzbDrone.Core/Extras/Subtitles/SubtitleFile.cs b/src/NzbDrone.Core/Extras/Subtitles/SubtitleFile.cs index 41a28ef4a..124175b94 100644 --- a/src/NzbDrone.Core/Extras/Subtitles/SubtitleFile.cs +++ b/src/NzbDrone.Core/Extras/Subtitles/SubtitleFile.cs @@ -13,15 +13,17 @@ namespace NzbDrone.Core.Extras.Subtitles public Language Language { get; set; } - public string AggregateString => Language + LanguageTagsAsString + Extension; + public string AggregateString => Language + Title + LanguageTagsAsString + Extension; public List LanguageTags { get; set; } + public string Title { get; set; } + private string LanguageTagsAsString => string.Join(".", LanguageTags); public override string ToString() { - return $"[{Id}] {RelativePath} ({Language}{(LanguageTags.Count > 0 ? "." : "")}{LanguageTagsAsString}{Extension})"; + return $"[{Id}] {RelativePath} ({Language}{(Title is not null ? "." : "")}{Title ?? ""}{(LanguageTags.Count > 0 ? "." : "")}{LanguageTagsAsString}{Extension})"; } } } diff --git a/src/NzbDrone.Core/Extras/Subtitles/SubtitleService.cs b/src/NzbDrone.Core/Extras/Subtitles/SubtitleService.cs index 0b3a4e557..59cca6020 100644 --- a/src/NzbDrone.Core/Extras/Subtitles/SubtitleService.cs +++ b/src/NzbDrone.Core/Extras/Subtitles/SubtitleService.cs @@ -81,7 +81,7 @@ namespace NzbDrone.Core.Extras.Subtitles foreach (var subtitleFile in group) { - var suffix = GetSuffix(subtitleFile.Language, copy, subtitleFile.LanguageTags, groupCount > 1); + var suffix = GetSuffix(subtitleFile.Language, copy, subtitleFile.LanguageTags, groupCount > 1, subtitleFile.Title); movedFiles.AddIfNotNull(MoveFile(series, episodeFile, subtitleFile, suffix)); @@ -229,11 +229,23 @@ namespace NzbDrone.Core.Extras.Subtitles return importedFiles; } - private string GetSuffix(Language language, int copy, List languageTags, bool multipleCopies = false) + private string GetSuffix(Language language, int copy, List languageTags, bool multipleCopies = false, string title = null) { var suffixBuilder = new StringBuilder(); - if (multipleCopies) + if (title is not null) + { + suffixBuilder.Append('.'); + suffixBuilder.Append(title); + + if (multipleCopies) + { + suffixBuilder.Append(" - "); + suffixBuilder.Append(copy); + } + + } + else if (multipleCopies) { suffixBuilder.Append('.'); suffixBuilder.Append(copy); diff --git a/src/NzbDrone.Core/Parser/LanguageParser.cs b/src/NzbDrone.Core/Parser/LanguageParser.cs index 149af2329..8175e79d8 100644 --- a/src/NzbDrone.Core/Parser/LanguageParser.cs +++ b/src/NzbDrone.Core/Parser/LanguageParser.cs @@ -7,6 +7,7 @@ using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Languages; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Parser { @@ -28,7 +29,9 @@ namespace NzbDrone.Core.Parser private static readonly Regex GermanDualLanguageRegex = new (@"(?[a-z]{2,3})([-_. ](?full|forced|foreign|default|cc|psdh|sdh))*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex SubtitleLanguageRegex = new Regex(".+?([-_. ]((?[a-z]{2,3})|(?full|forced|foreign|default|cc|psdh|sdh)))*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex SubtitleLanguageTitleRegex = new Regex(".+?(\\.((?[a-z]{2,3})|(?full|forced|foreign|default|cc|psdh|sdh)))*(\\.(?[^.]*))??(\\.((?<iso_code>[a-z]{2,3})|(?<tags2>full|forced|foreign|default|cc|psdh|sdh)))*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); public static List<Language> ParseLanguages(string title) { @@ -249,6 +252,31 @@ namespace NzbDrone.Core.Parser return Language.Unknown; } + public static (List<string> languageTags, string title) ParseLanguageTagsAndTitle(string fileName, Episode episode) + { + var simpleFilename = Path.GetFileNameWithoutExtension(fileName); + var matchTitle = SubtitleLanguageTitleRegex.Match(simpleFilename); + var languageTags = matchTitle.Groups["tags1"].Captures + .Union(matchTitle.Groups["tags2"].Captures) + .Cast<Capture>() + .Where(tag => !tag.Value.Empty()) + .Select(tag => tag.Value.ToLower()); + var title = matchTitle.Groups["title"].Captures.Cast<Capture>().First().ToString(); + + if (matchTitle.Groups["tags1"].Captures.Empty()) + { + var episodeFile = episode.EpisodeFile.Value; + var episodeFileTitle = Path.GetFileNameWithoutExtension(episodeFile.RelativePath); + + if (episodeFileTitle.Contains(title, StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("Subtitle file title probably parsed incorrectly, not using."); + } + } + + return (languageTags.ToList(), title); + } + public static List<string> ParseLanguageTags(string fileName) { try