This commit is contained in:
Yashizzle 2025-11-29 14:19:05 +01:00 committed by GitHub
commit a9704b4c73
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 44 additions and 22 deletions

View file

@ -200,6 +200,7 @@ const seriesIdTokens = [
const seasonTokens = [
{ token: '{season:0}', example: '1' },
{ token: '{season:00}', example: '01' },
{ token: '{Season Year}', example: '2010' },
];
const episodeTokens = [

View file

@ -48,7 +48,7 @@ public void Setup()
.Returns(@"C:\Test\TV\Series\Season 01\File Name.avi".AsOsAgnostic());
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.BuildSeasonPath(It.IsAny<Series>(), It.IsAny<int>()))
.Setup(s => s.BuildSeasonPath(It.IsAny<Series>(), It.IsAny<int>(), It.IsAny<int>()))
.Returns(@"C:\Test\TV\Series\Season 01".AsOsAgnostic());
var rootFolder = @"C:\Test\TV\".AsOsAgnostic();
@ -112,6 +112,18 @@ public void should_notify_on_season_folder_creation()
Times.Once());
}
[Test]
public void should_use_first_episode_season_and_year()
{
Subject.MoveEpisodeFile(_episodeFile, _localEpisode);
Mocker.GetMock<IBuildFileNames>()
.Verify(s => s.BuildSeasonPath(
_series,
_localEpisode.Episodes.First().SeasonNumber,
_localEpisode.Episodes.First().AirDateUtc.Value.Year));
}
[Test]
public void should_not_notify_if_series_folder_already_exists()
{

View file

@ -70,7 +70,7 @@ public void should_replace_reserved_device_name_in_season_folder(string title, s
_series.Title = title;
_namingConfig.SeasonFolderFormat = "{Series.Title} - Season {Season:00}";
Subject.GetSeasonFolder(_series, 1).Should().Be($"{expected} - Season 01");
Subject.GetSeasonFolder(_series, 1, 2025).Should().Be($"{expected} - Season 01");
}
[TestCase("Con Game", "Con_Game")]

View file

@ -20,16 +20,18 @@ public void Setup()
.Setup(c => c.GetConfig()).Returns(_namingConfig);
}
[TestCase("Venture Bros.", 1, "{Series.Title}.{season:00}", "Venture.Bros.01")]
[TestCase("Venture Bros.", 1, "{Series Title} Season {season:00}", "Venture Bros. Season 01")]
[TestCase("Series Title?", 1, "{Series Title} Season {season:00}", "Series Title! Season 01")]
public void should_use_seriesFolderFormat_to_build_folder_name(string seriesTitle, int seasonNumber, string format, string expected)
[TestCase("Venture Bros.", 1, null, "{Series.Title}.{season:00}", "Venture.Bros.01")]
[TestCase("Venture Bros.", 1, null, "{Series Title} Season {season:00}", "Venture Bros. Season 01")]
[TestCase("Series Title?", 1, null, "{Series Title} Season {season:00}", "Series Title! Season 01")]
[TestCase("Series Title?", 1, null, "{Series Title} Season {season:00} {(Season Year)}", "Series Title! Season 01")]
[TestCase("Series Title?", 1, 2025, "{Series Title} Season {season:00} {(Season Year)}", "Series Title! Season 01 (2025)")]
public void should_use_seriesFolderFormat_to_build_folder_name(string seriesTitle, int seasonNumber, int? seasonYear, string format, string expected)
{
_namingConfig.SeasonFolderFormat = format;
var series = new Series { Title = seriesTitle };
Subject.GetSeasonFolder(series, seasonNumber, _namingConfig).Should().Be(expected);
Subject.GetSeasonFolder(series, seasonNumber, seasonYear, _namingConfig).Should().Be(expected);
}
}
}

View file

@ -73,7 +73,8 @@ private EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, Series series, List
{
var filePath = _buildFileNames.BuildFilePath(episodes, series, episodeFile, Path.GetExtension(episodeFile.RelativePath));
EnsureEpisodeFolder(episodeFile, series, episodes.Select(v => v.SeasonNumber).First(), filePath);
var firstEpisode = episodes.First();
EnsureEpisodeFolder(episodeFile, series, firstEpisode.SeasonNumber, firstEpisode.AirDateUtc?.Year, filePath);
_logger.Debug("Renaming episode file: {0} to {1}", episodeFile, filePath);
@ -176,13 +177,13 @@ private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List<Ep
private void EnsureEpisodeFolder(EpisodeFile episodeFile, LocalEpisode localEpisode, string filePath)
{
EnsureEpisodeFolder(episodeFile, localEpisode.Series, localEpisode.SeasonNumber, filePath);
EnsureEpisodeFolder(episodeFile, localEpisode.Series, localEpisode.SeasonNumber, localEpisode.Episodes.First().AirDateUtc?.Year, filePath);
}
private void EnsureEpisodeFolder(EpisodeFile episodeFile, Series series, int seasonNumber, string filePath)
private void EnsureEpisodeFolder(EpisodeFile episodeFile, Series series, int seasonNumber, int? seasonYear, string filePath)
{
var episodeFolder = Path.GetDirectoryName(filePath);
var seasonFolder = _buildFileNames.BuildSeasonPath(series, seasonNumber);
var seasonFolder = _buildFileNames.BuildSeasonPath(series, seasonNumber, seasonYear);
var seriesFolder = series.Path;
var rootFolder = _rootFolderService.GetBestRootFolderPath(seriesFolder);

View file

@ -23,9 +23,9 @@ public interface IBuildFileNames
{
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, string extension = "", NamingConfig namingConfig = null, List<CustomFormat> customFormats = null);
string BuildFilePath(List<Episode> episodes, Series series, EpisodeFile episodeFile, string extension, NamingConfig namingConfig = null, List<CustomFormat> customFormats = null);
string BuildSeasonPath(Series series, int seasonNumber);
string BuildSeasonPath(Series series, int seasonNumber, int? seasonYear);
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null);
string GetSeasonFolder(Series series, int seasonNumber, int? seasonYear, NamingConfig namingConfig = null);
bool RequiresEpisodeTitle(Series series, List<Episode> episodes);
bool RequiresAbsoluteEpisodeNumber();
}
@ -233,20 +233,21 @@ public string BuildFilePath(List<Episode> episodes, Series series, EpisodeFile e
{
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
var seasonPath = BuildSeasonPath(series, episodes.First().SeasonNumber);
var firstEpisode = episodes.First();
var seasonPath = BuildSeasonPath(series, firstEpisode.SeasonNumber, firstEpisode.AirDateUtc?.Year);
var remainingPathLength = LongPathSupport.MaxFilePathLength - seasonPath.GetByteCount() - 1;
var fileName = BuildFileName(episodes, series, episodeFile, extension, remainingPathLength, namingConfig, customFormats);
return Path.Combine(seasonPath, fileName);
}
public string BuildSeasonPath(Series series, int seasonNumber)
public string BuildSeasonPath(Series series, int seasonNumber, int? seasonYear)
{
var path = series.Path;
if (series.SeasonFolder)
{
var seasonFolder = GetSeasonFolder(series, seasonNumber);
var seasonFolder = GetSeasonFolder(series, seasonNumber, seasonYear);
seasonFolder = CleanFileName(seasonFolder);
@ -277,7 +278,7 @@ public string GetSeriesFolder(Series series, NamingConfig namingConfig = null)
return folderName;
}
public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null)
public string GetSeasonFolder(Series series, int seasonNumber, int? seasonYear, NamingConfig namingConfig = null)
{
if (namingConfig == null)
{
@ -288,7 +289,7 @@ public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig nami
AddSeriesTokens(tokenHandlers, series);
AddIdTokens(tokenHandlers, series);
AddSeasonTokens(tokenHandlers, seasonNumber);
AddSeasonTokens(tokenHandlers, seasonNumber, seasonYear);
var format = seasonNumber == 0 ? namingConfig.SpecialsFolderFormat : namingConfig.SeasonFolderFormat;
var folderName = ReplaceTokens(format, tokenHandlers, namingConfig);
@ -516,7 +517,9 @@ private string AddSeasonEpisodeNumberingTokens(string pattern, Dictionary<string
tokenHandlers[token] = m => seasonEpisodePattern;
}
AddSeasonTokens(tokenHandlers, episodes.First().SeasonNumber);
var firstEpisode = episodes.First();
AddSeasonTokens(tokenHandlers, firstEpisode.SeasonNumber, firstEpisode.AirDateUtc?.Year);
if (episodes.Count > 1)
{
@ -593,9 +596,10 @@ private string AddAbsoluteNumberingTokens(string pattern, Dictionary<string, Fun
return pattern;
}
private void AddSeasonTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, int seasonNumber)
private void AddSeasonTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, int seasonNumber, int? seasonYear)
{
tokenHandlers["{Season}"] = m => seasonNumber.ToString(m.CustomFormat);
tokenHandlers["{Season Year}"] = m => seasonYear?.ToString(m.CustomFormat) ?? string.Empty;
}
private void AddEpisodeTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, List<Episode> episodes)

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.MediaFiles;
@ -80,6 +81,7 @@ public FileNameSampleService(IBuildFileNames buildFileNames)
EpisodeNumber = 1,
Title = "Episode Title (1)",
AirDate = "2013-10-30",
AirDateUtc = new(2013, 10, 30, 0, 0, 0, DateTimeKind.Utc),
AbsoluteEpisodeNumber = 1,
};
@ -260,12 +262,12 @@ public string GetSeriesFolderSample(NamingConfig nameSpec)
public string GetSeasonFolderSample(NamingConfig nameSpec)
{
return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, nameSpec);
return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, _episode1.AirDateUtc?.Year, nameSpec);
}
public string GetSpecialsFolderSample(NamingConfig nameSpec)
{
return _buildFileNames.GetSeasonFolder(_standardSeries, 0, nameSpec);
return _buildFileNames.GetSeasonFolder(_standardSeries, 0, null, nameSpec);
}
private string BuildSample(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig nameSpec, List<CustomFormat> customFormats)