Rewrote most of the renamer token handling code to give it a bit more generic architecture. Also added MediaInfo as possible token.

This commit is contained in:
Taloth Saldono 2014-07-28 23:14:34 +02:00
parent 7b420fc033
commit 8a5edfeaf5
20 changed files with 452 additions and 265 deletions

View File

@ -85,19 +85,19 @@ namespace NzbDrone.Api.Config
sampleResource.SingleEpisodeExample = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult) != null
? "Invalid format"
: singleEpisodeSampleResult.Filename;
: singleEpisodeSampleResult.FileName;
sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null
? "Invalid format"
: multiEpisodeSampleResult.Filename;
: multiEpisodeSampleResult.FileName;
sampleResource.DailyEpisodeExample = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult) != null
? "Invalid format"
: dailyEpisodeSampleResult.Filename;
: dailyEpisodeSampleResult.FileName;
sampleResource.AnimeEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult) != null
? "Invalid format"
: animeEpisodeSampleResult.Filename;
: animeEpisodeSampleResult.FileName;
sampleResource.SeriesFolderExample = nameSpec.SeriesFolderFormat.IsNullOrWhiteSpace()
? "Invalid format"

View File

@ -39,7 +39,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests
.Build();
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.BuildFilename(It.IsAny<List<Episode>>(), It.IsAny<Series>(), It.IsAny<EpisodeFile>()))
.Setup(s => s.BuildFileName(It.IsAny<List<Episode>>(), It.IsAny<Series>(), It.IsAny<EpisodeFile>(), null))
.Returns("File Name");
Mocker.GetMock<IBuildFileNames>()

View File

@ -19,7 +19,7 @@ namespace NzbDrone.Core.Test.MediaFiles
"Law & Order- Criminal Intent - S10E07 - Icarus [HDTV-720p]")]
public void CleanFileName(string name, string expectedName)
{
FileNameBuilder.CleanFilename(name).Should().Be(expectedName);
FileNameBuilder.CleanFileName(name).Should().Be(expectedName);
}
[Test]

View File

@ -69,7 +69,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
_namingConfig.StandardEpisodeFormat = "{Series Title}";
Subject.BuildFilename(new List<Episode> {_episode1}, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("South Park");
}
@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
_namingConfig.StandardEpisodeFormat = "{Series_Title}";
Subject.BuildFilename(new List<Episode> {_episode1}, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("South_Park");
}
@ -87,7 +87,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}";
Subject.BuildFilename(new List<Episode> {_episode1}, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("South.Park");
}
@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
_namingConfig.StandardEpisodeFormat = "{Series-Title}";
Subject.BuildFilename(new List<Episode> {_episode1}, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("South-Park");
}
@ -105,7 +105,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
_namingConfig.StandardEpisodeFormat = "{SERIES TITLE}";
Subject.BuildFilename(new List<Episode> {_episode1}, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("SOUTH PARK");
}
@ -114,7 +114,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
_namingConfig.StandardEpisodeFormat = "{sErIES-tItLE}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(_series.Title.Replace(' ', '-'));
}
@ -123,16 +123,26 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
_namingConfig.StandardEpisodeFormat = "{series title}";
Subject.BuildFilename(new List<Episode> {_episode1}, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("south park");
}
[Test]
public void should_cleanup_Series_Title()
{
_namingConfig.StandardEpisodeFormat = "{Series.CleanTitle}";
_series.Title = "South Park (1997)";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.1997");
}
[Test]
public void should_replace_episode_title()
{
_namingConfig.StandardEpisodeFormat = "{Episode Title}";
Subject.BuildFilename(new List<Episode> {_episode1}, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("City Sushi");
}
@ -141,7 +151,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
_namingConfig.StandardEpisodeFormat = "{ePisOde-TitLe}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("City-Sushi");
}
@ -151,7 +161,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_episode1.SeasonNumber = 1;
_namingConfig.StandardEpisodeFormat = "{season}x{episode}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("1x6");
}
@ -161,7 +171,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_episode1.SeasonNumber = 1;
_namingConfig.StandardEpisodeFormat = "{season:00}x{episode}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("01x6");
}
@ -171,7 +181,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_episode1.SeasonNumber = 1;
_namingConfig.StandardEpisodeFormat = "{season}x{episode}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("1x6");
}
@ -181,7 +191,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_episode1.SeasonNumber = 1;
_namingConfig.StandardEpisodeFormat = "{season}x{episode:00}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("1x06");
}
@ -190,7 +200,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
_namingConfig.StandardEpisodeFormat = "{Quality Title}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("HDTV-720p");
}
@ -200,7 +210,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_namingConfig.StandardEpisodeFormat = "{Quality Title}";
_episodeFile.Quality.Proper = true;
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("HDTV-720p Proper");
}
@ -209,7 +219,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} [{Quality Title}]";
Subject.BuildFilename(new List<Episode> {_episode1}, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("South Park - S15E06 - City Sushi [HDTV-720p]");
}
@ -219,7 +229,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_namingConfig.RenameEpisodes = false;
_episodeFile.Path = @"C:\Test\TV\30 Rock - S01E01 - Test";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(Path.GetFileNameWithoutExtension(_episodeFile.Path));
}
@ -230,7 +240,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_episodeFile.Path = @"C:\Test\TV\30 Rock - S01E01 - Test";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("30.Rock.S01E01.xvid-LOL");
}
@ -253,7 +263,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
.Build();
Subject.BuildFilename(new List<Episode> {episode2, episode}, new Series {Title = "30 Rock"}, _episodeFile)
Subject.BuildFileName(new List<Episode> {episode2, episode}, new Series {Title = "30 Rock"}, _episodeFile)
.Should().Be("30 Rock - S06E06-E07 - Hey, Baby, What's Wrong!");
}
@ -266,7 +276,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_episode1.Title = "Hello";
_episode2.Title = "World";
Subject.BuildFilename(new List<Episode> {_episode1, _episode2}, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> {_episode1, _episode2}, _series, _episodeFile)
.Should().Be("South Park - S15E06-E07 - Hello + World");
}
@ -281,7 +291,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_episode1.AirDate = "2012-12-13";
_episode1.Title = "Kristen Stewart";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("The Daily Show with Jon Stewart - 2012-12-13 - Kristen Stewart");
}
@ -296,7 +306,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_episode1.AirDate = null;
_episode1.Title = "Kristen Stewart";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("The Daily Show with Jon Stewart - Unknown - Kristen Stewart");
}
@ -306,7 +316,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 0;
Subject.BuildFilename(new List<Episode> {_episode1, _episode2}, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> {_episode1, _episode2}, _series, _episodeFile)
.Should().Be("South Park - S15E06-07 - City Sushi");
}
@ -316,7 +326,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 1;
Subject.BuildFilename(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 - S15E07 - City Sushi");
}
@ -326,7 +336,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 2;
Subject.BuildFilename(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - S15E06E07 - City Sushi");
}
@ -336,7 +346,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 3;
Subject.BuildFilename(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - S15E06-E07 - City Sushi");
}
@ -348,7 +358,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_namingConfig.StandardEpisodeFormat = "{Episode Title}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(title);
}
@ -357,7 +367,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
_namingConfig.StandardEpisodeFormat = "{Release Group}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(_episodeFile.ReleaseGroup);
}
@ -370,7 +380,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_episodeFile.Path = @"C:\Test\TV\30 Rock - S01E01 - Test";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("30 Rock - 30.Rock.S01E01.xvid-LOL");
}
@ -387,7 +397,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
.Build();
Subject.BuildFilename(new List<Episode> { episode }, new Series { Title = "30 Rock" }, _episodeFile)
Subject.BuildFileName(new List<Episode> { episode }, new Series { Title = "30 Rock" }, _episodeFile)
.Should().Be("30 Rock - S06E06 - Part 1");
}
@ -404,7 +414,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
.Build();
Subject.BuildFilename(new List<Episode> { episode }, new Series { Title = "30 Rock" }, _episodeFile)
Subject.BuildFileName(new List<Episode> { episode }, new Series { Title = "30 Rock" }, _episodeFile)
.Should().Be("30 Rock - S06E06 - Part 1");
}
@ -419,7 +429,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
.With(e => e.EpisodeNumber = 6)
.Build();
Subject.BuildFilename(new List<Episode> { episode }, new Series { Title = "Chicago P.D." }, _episodeFile)
Subject.BuildFileName(new List<Episode> { episode }, new Series { Title = "Chicago P.D." }, _episodeFile)
.Should().Be("Chicago.P.D.S06E06.Part.1");
}
@ -434,7 +444,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
.With(e => e.EpisodeNumber = 6)
.Build();
Subject.BuildFilename(new List<Episode> { episode }, new Series { Title = "Chicago P.D.." }, _episodeFile)
Subject.BuildFileName(new List<Episode> { episode }, new Series { Title = "Chicago P.D.." }, _episodeFile)
.Should().Be("Chicago.P.D.S06E06.Part.1");
}
@ -443,7 +453,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{absolute:00}.{Episode.Title}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06.City.Sushi");
}
@ -453,7 +463,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{absolute:00}.{Episode.Title}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06.100.City.Sushi");
}
@ -463,7 +473,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06.City.Sushi");
}
@ -473,7 +483,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series.Title}.{absolute:00}.{Episode.Title}";
Subject.BuildFilename(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.100.City.Sushi");
}
@ -483,7 +493,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}";
Subject.BuildFilename(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - 100-101 - City Sushi");
}
@ -496,7 +506,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
_namingConfig.StandardEpisodeFormat = "{Series Title} - {season:0}x{episode:00} - {Episode Title}";
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}";
Subject.BuildFilename(new List<Episode> { _episode1, }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1, }, _series, _episodeFile)
.Should().Be("South Park - 15x06 - City Sushi");
}
@ -507,8 +517,62 @@ namespace NzbDrone.Core.Test.OrganizerTests
_namingConfig.MultiEpisodeStyle = (int)MultiEpisodeStyle.Duplicate;
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}";
Subject.BuildFilename(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - 100 - 101 - City Sushi");
}
[Test]
public void should_include_affixes_if_value_not_empty()
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}{_Episode.Title_}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06_City.Sushi_");
}
[Test]
public void should_not_include_affixes_if_value_empty()
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}{_Episode.Title_}";
_episode1.Title = "";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06");
}
[Test]
public void should_format_mediainfo_properly()
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MEDIAINFO.FULL}";
_episodeFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
{
VideoCodec = "AVC",
AudioFormat = "DTS",
AudioLanguages = "English/Spanish",
Subtitles = "English/Spanish/Italian"
};
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06.City.Sushi.X264.DTS[EN+ES].[EN+ES+IT]");
}
[Test]
public void should_exclude_english_in_mediainfo_audio_language()
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MEDIAINFO.FULL}";
_episodeFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
{
VideoCodec = "AVC",
AudioFormat = "DTS",
AudioLanguages = "English",
Subtitles = "English/Spanish/Italian"
};
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06.City.Sushi.X264.DTS.[EN+ES+IT]");
}
}
}

View File

@ -27,7 +27,9 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
namingConfig.SeriesFolderFormat = format;
Subject.GetSeriesFolder(seriesTitle).Should().Be(expected);
var series = new NzbDrone.Core.Tv.Series { Title = seriesTitle };
Subject.GetSeriesFolder(series).Should().Be(expected);
}
}
}

View File

@ -26,7 +26,7 @@ namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests
fakeSeries.RootFolderPath = @"C:\Test\TV";
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.GetSeriesFolder(fakeSeries.Title))
.Setup(s => s.GetSeriesFolder(fakeSeries, null))
.Returns(fakeSeries.Title);
var series = Subject.AddSeries(fakeSeries);

View File

@ -46,7 +46,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
throw new NotSupportedException("Full season releases are not supported with Pneumatic.");
}
title = FileNameBuilder.CleanFilename(title);
title = FileNameBuilder.CleanFileName(title);
//Save to the Pneumatic directory (The user will need to ensure its accessible by XBMC)
var filename = Path.Combine(Settings.NzbFolder, title + ".nzb");

View File

@ -46,7 +46,7 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
var url = remoteEpisode.Release.DownloadUrl;
var title = remoteEpisode.Release.Title;
title = FileNameBuilder.CleanFilename(title);
title = FileNameBuilder.CleanFileName(title);
var filename = Path.Combine(Settings.NzbFolder, title + ".nzb");
@ -61,7 +61,7 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
{
foreach (var folder in _diskProvider.GetDirectories(Settings.WatchFolder))
{
var title = FileNameBuilder.CleanFilename(Path.GetFileName(folder));
var title = FileNameBuilder.CleanFileName(Path.GetFileName(folder));
var files = _diskProvider.GetFiles(folder, SearchOption.AllDirectories);
@ -95,7 +95,7 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
foreach (var videoFile in _diskScanService.GetVideoFiles(Settings.WatchFolder, false))
{
var title = FileNameBuilder.CleanFilename(Path.GetFileName(videoFile));
var title = FileNameBuilder.CleanFileName(Path.GetFileName(videoFile));
var historyItem = new DownloadClientItem
{

View File

@ -49,7 +49,7 @@ namespace NzbDrone.Core.MediaFiles
public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, Series series)
{
var episodes = _episodeService.GetEpisodesByFileId(episodeFile.Id);
var newFileName = _buildFileNames.BuildFilename(episodes, series, episodeFile);
var newFileName = _buildFileNames.BuildFileName(episodes, series, episodeFile);
var filePath = _buildFileNames.BuildFilePath(series, episodes.First().SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path));
_logger.Debug("Renaming episode file: {0} to {1}", episodeFile, filePath);
@ -59,7 +59,7 @@ namespace NzbDrone.Core.MediaFiles
public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode)
{
var newFileName = _buildFileNames.BuildFilename(localEpisode.Episodes, localEpisode.Series, episodeFile);
var newFileName = _buildFileNames.BuildFileName(localEpisode.Episodes, localEpisode.Series, episodeFile);
var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path));
_logger.Debug("Moving episode file: {0} to {1}", episodeFile, filePath);
@ -69,7 +69,7 @@ namespace NzbDrone.Core.MediaFiles
public EpisodeFile CopyEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode)
{
var newFileName = _buildFileNames.BuildFilename(localEpisode.Episodes, localEpisode.Series, episodeFile);
var newFileName = _buildFileNames.BuildFileName(localEpisode.Episodes, localEpisode.Series, episodeFile);
var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path));
_logger.Debug("Copying episode file: {0} to {1}", episodeFile, filePath);

View File

@ -85,7 +85,7 @@ namespace NzbDrone.Core.MediaFiles
}
var seasonNumber = episodesInFile.First().SeasonNumber;
var newName = _filenameBuilder.BuildFilename(episodesInFile, series, file);
var newName = _filenameBuilder.BuildFileName(episodesInFile, series, file);
var newPath = _filenameBuilder.BuildFilePath(series, seasonNumber, newName, Path.GetExtension(file.Path));
if (!file.Path.PathEquals(newPath))

View File

@ -634,10 +634,10 @@
<Compile Include="Organizer\EpisodeSortingType.cs" />
<Compile Include="Organizer\Exception.cs" />
<Compile Include="Organizer\FileNameBuilder.cs" />
<Compile Include="Organizer\FilenameBuilderTokenEqualityComparer.cs" />
<Compile Include="Organizer\FilenameSampleService.cs" />
<Compile Include="Organizer\FileNameBuilderTokenEqualityComparer.cs" />
<Compile Include="Organizer\FileNameSampleService.cs" />
<Compile Include="Organizer\FileNameValidation.cs" />
<Compile Include="Organizer\FilenameValidationService.cs" />
<Compile Include="Organizer\FileNameValidationService.cs" />
<Compile Include="Organizer\NamingConfig.cs" />
<Compile Include="Organizer\NamingConfigService.cs" />
<Compile Include="Organizer\SampleResult.cs" />

View File

@ -14,13 +14,11 @@ namespace NzbDrone.Core.Organizer
{
public interface IBuildFileNames
{
string BuildFilename(IList<Episode> episodes, Series series, EpisodeFile episodeFile);
string BuildFilename(IList<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig);
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null);
string BuildFilePath(Series series, Int32 seasonNumber, String fileName, String extension);
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
string GetSeriesFolder(string seriesTitle);
string GetSeriesFolder(string seriesTitle, NamingConfig namingConfig);
string GetSeasonFolder(string seriesTitle, int seasonNumber, NamingConfig namingConfig);
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
string GetSeasonFolder(Series series, Int32 seasonNumber, NamingConfig namingConfig = null);
}
public class FileNameBuilder : IBuildFileNames
@ -30,7 +28,7 @@ namespace NzbDrone.Core.Organizer
private readonly ICached<EpisodeFormat> _patternCache;
private readonly Logger _logger;
private static readonly Regex TitleRegex = new Regex(@"(?<token>\{(?:\w+)(?<separator>\s|\.|-|_)\w+\})",
private static readonly Regex TitleRegex = new Regex(@"\{(?<prefix>[- ._]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9]+))?(?<suffix>[- ._]*)\}",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex EpisodeRegex = new Regex(@"(?<episode>\{episode(?:\:0+)?})",
@ -50,12 +48,12 @@ namespace NzbDrone.Core.Organizer
public static readonly Regex AirDateRegex = new Regex(@"\{Air(\s|\W|_)Date\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>\s|\.|-|_)Title\})",
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>\s|\.|-|_)(Clean)?Title\})",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex FilenameCleanupRegex = new Regex(@"\.{2,}", RegexOptions.Compiled);
private static readonly Regex FileNameCleanupRegex = new Regex(@"\.{2,}", RegexOptions.Compiled);
private static readonly char[] EpisodeTitleTrimCharaters = new[] { ' ', '.', '?' };
private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' };
public FileNameBuilder(INamingConfigService namingConfigService,
IQualityDefinitionService qualityDefinitionService,
@ -68,18 +66,16 @@ namespace NzbDrone.Core.Organizer
_logger = logger;
}
public string BuildFilename(IList<Episode> episodes, Series series, EpisodeFile episodeFile)
public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null)
{
var nameSpec = _namingConfigService.GetConfig();
if (namingConfig == null)
{
namingConfig = _namingConfigService.GetConfig();
}
return BuildFilename(episodes, series, episodeFile, nameSpec);
}
public string BuildFilename(IList<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig)
{
if (!namingConfig.RenameEpisodes)
{
if (String.IsNullOrWhiteSpace(episodeFile.SceneName))
if (episodeFile.SceneName.IsNullOrWhiteSpace())
{
return Path.GetFileNameWithoutExtension(episodeFile.Path);
}
@ -87,42 +83,33 @@ namespace NzbDrone.Core.Organizer
return episodeFile.SceneName;
}
if (String.IsNullOrWhiteSpace(namingConfig.StandardEpisodeFormat) && series.SeriesType == SeriesTypes.Standard)
if (namingConfig.StandardEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Standard)
{
throw new NamingFormatException("Standard episode format cannot be null");
}
if (String.IsNullOrWhiteSpace(namingConfig.DailyEpisodeFormat) && series.SeriesType == SeriesTypes.Daily)
if (namingConfig.DailyEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Daily)
{
throw new NamingFormatException("Daily episode format cannot be null");
}
var sortedEpisodes = episodes.OrderBy(e => e.EpisodeNumber).ToList();
var pattern = namingConfig.StandardEpisodeFormat;
var tokenHandlers = new Dictionary<String, Func<TokenMatch, String>>(FileNameBuilderTokenEqualityComparer.Instance);
episodes = episodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber).ToList();
AddSeriesTokens(tokenHandlers, series);
AddEpisodeTokens(tokenHandlers, episodes);
AddEpisodeFileTokens(tokenHandlers, episodeFile);
AddMediaInfoTokens(tokenHandlers, episodeFile);
var episodeTitles = new List<string>
{
sortedEpisodes.First().Title.TrimEnd(EpisodeTitleTrimCharaters)
};
var tokenValues = new Dictionary<string, string>(FilenameBuilderTokenEqualityComparer.Instance);
tokenValues.Add("{Series Title}", series.Title);
tokenValues.Add("{Original Title}", episodeFile.SceneName);
tokenValues.Add("{Release Group}", episodeFile.ReleaseGroup);
if (series.SeriesType == SeriesTypes.Daily)
{
pattern = namingConfig.DailyEpisodeFormat;
if (!String.IsNullOrWhiteSpace(episodes.First().AirDate))
{
tokenValues.Add("{Air Date}", episodes.First().AirDate.Replace('-', ' '));
}
else {
tokenValues.Add("{Air Date}", "Unknown");
}
}
if (series.SeriesType == SeriesTypes.Anime && episodes.All(e => e.AbsoluteEpisodeNumber > 0))
@ -137,7 +124,7 @@ namespace NzbDrone.Core.Organizer
pattern = pattern.Replace(episodeFormat.SeasonEpisodePattern, "{Season Episode}");
var seasonEpisodePattern = episodeFormat.SeasonEpisodePattern;
foreach (var episode in sortedEpisodes.Skip(1))
foreach (var episode in episodes.Skip(1))
{
switch ((MultiEpisodeStyle)namingConfig.MultiEpisodeStyle)
{
@ -158,12 +145,10 @@ namespace NzbDrone.Core.Organizer
seasonEpisodePattern += "-" + episodeFormat.EpisodePattern;
break;
}
episodeTitles.Add(episode.Title.TrimEnd(EpisodeTitleTrimCharaters));
}
seasonEpisodePattern = ReplaceNumberTokens(seasonEpisodePattern, sortedEpisodes);
tokenValues.Add("{Season Episode}", seasonEpisodePattern);
seasonEpisodePattern = ReplaceNumberTokens(seasonEpisodePattern, episodes);
tokenHandlers["{Season Episode}"] = m => seasonEpisodePattern;
}
//TODO: Extract to another method
@ -181,7 +166,7 @@ namespace NzbDrone.Core.Organizer
pattern = pattern.Replace(absoluteEpisodeFormat.AbsoluteEpisodePattern, "{Absolute Pattern}");
var absoluteEpisodePattern = absoluteEpisodeFormat.AbsoluteEpisodePattern;
foreach (var episode in sortedEpisodes.Skip(1))
foreach (var episode in episodes.Skip(1))
{
switch ((MultiEpisodeStyle)namingConfig.MultiEpisodeStyle)
{
@ -204,24 +189,17 @@ namespace NzbDrone.Core.Organizer
absoluteEpisodePattern += "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern;
break;
}
episodeTitles.Add(episode.Title.TrimEnd(EpisodeTitleTrimCharaters));
}
absoluteEpisodePattern = ReplaceAbsoluteNumberTokens(absoluteEpisodePattern, sortedEpisodes);
tokenValues.Add("{Absolute Pattern}", absoluteEpisodePattern);
absoluteEpisodePattern = ReplaceAbsoluteNumberTokens(absoluteEpisodePattern, episodes);
tokenHandlers["{Absolute Pattern}"] = m => absoluteEpisodePattern;
}
}
tokenValues.Add("{Episode Title}", GetEpisodeTitle(episodeTitles));
tokenValues.Add("{Quality Title}", GetQualityTitle(episodeFile.Quality));
var filename = ReplaceTokens(pattern, tokenHandlers).Trim();
filename = FileNameCleanupRegex.Replace(filename, match => match.Captures[0].Value[0].ToString());
AddMediaInfoTokens(episodeFile, tokenValues);
var filename = ReplaceTokens(pattern, tokenValues).Trim();
filename = FilenameCleanupRegex.Replace(filename, match => match.Captures[0].Value[0].ToString() );
return CleanFilename(filename);
return filename;
}
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
@ -240,10 +218,10 @@ namespace NzbDrone.Core.Organizer
else
{
var nameSpec = _namingConfigService.GetConfig();
seasonFolder = GetSeasonFolder(series.Title, seasonNumber, nameSpec);
seasonFolder = GetSeasonFolder(series, seasonNumber, nameSpec);
}
seasonFolder = CleanFilename(seasonFolder);
seasonFolder = CleanFileName(seasonFolder);
path = Path.Combine(path, seasonFolder);
}
@ -297,160 +275,260 @@ namespace NzbDrone.Core.Organizer
return basicNamingConfig;
}
public string GetSeriesFolder(string seriesTitle)
public string GetSeriesFolder(Series series, NamingConfig namingConfig = null)
{
var namingConfig = _namingConfigService.GetConfig();
if (namingConfig == null)
{
namingConfig = _namingConfigService.GetConfig();
}
return GetSeriesFolder(seriesTitle, namingConfig);
var tokenHandlers = new Dictionary<string, Func<TokenMatch, String>>(FileNameBuilderTokenEqualityComparer.Instance);
AddSeriesTokens(tokenHandlers, series);
return ReplaceTokens(namingConfig.SeriesFolderFormat, tokenHandlers);
}
public string GetSeriesFolder(string seriesTitle, NamingConfig namingConfig)
public string GetSeasonFolder(Series series, Int32 seasonNumber, NamingConfig namingConfig = null)
{
seriesTitle = CleanFilename(seriesTitle);
if (namingConfig == null)
{
namingConfig = _namingConfigService.GetConfig();
}
var tokenValues = new Dictionary<string, string>(FilenameBuilderTokenEqualityComparer.Instance);
tokenValues.Add("{Series Title}", seriesTitle);
var tokenHandlers = new Dictionary<string, Func<TokenMatch, String>>(FileNameBuilderTokenEqualityComparer.Instance);
return ReplaceTokens(namingConfig.SeriesFolderFormat, tokenValues);
AddSeriesTokens(tokenHandlers, series);
AddSeasonTokens(tokenHandlers, seasonNumber);
return ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers);
}
public string GetSeasonFolder(string seriesTitle, int seasonNumber, NamingConfig namingConfig)
public static string CleanTitle(string name)
{
var tokenValues = new Dictionary<string, string>(FilenameBuilderTokenEqualityComparer.Instance);
tokenValues.Add("{Series Title}", seriesTitle);
var seasonFolder = ReplaceSeasonTokens(namingConfig.SeasonFolderFormat, seasonNumber);
return ReplaceTokens(seasonFolder, tokenValues);
string[] dropCharacters = { ":", ".", "(", ")" };
string result = name;
for (int i = 0; i < dropCharacters.Length; i++)
{
result = result.Replace(dropCharacters[i], "");
}
return result;
}
public static string CleanFilename(string name)
public static string CleanFileName(string name)
{
string result = name;
string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" };
string[] goodCharacters = { "+", "+", "", "", "!", "-", "-", "", "" };
string[] goodCharacters = { "+", "+", "", "", "!", "-", "-", "", "" };
for (int i = 0; i < badCharacters.Length; i++)
{
result = result.Replace(badCharacters[i], goodCharacters[i]);
}
return result.Trim();
}
private void AddMediaInfoTokens(EpisodeFile episodeFile, Dictionary<string, string> tokenValues)
private void AddSeriesTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, Series series)
{
if (episodeFile.MediaInfo == null)
return;
var mediaInfoFull = string.Empty;
switch (episodeFile.MediaInfo.VideoCodec)
{
case "AVC":
if (Path.GetFileNameWithoutExtension(episodeFile.Path).Contains("x264"))
mediaInfoFull += "x264";
else if (Path.GetFileNameWithoutExtension(episodeFile.Path).Contains("h264"))
mediaInfoFull += "h264";
else
mediaInfoFull += "h264";
break;
default:
mediaInfoFull += episodeFile.MediaInfo.VideoCodec;
break;
}
switch (episodeFile.MediaInfo.AudioFormat)
{
case "AC-3":
mediaInfoFull += ".AC3";
break;
case "MPEG Audio":
if (episodeFile.MediaInfo.AudioProfile == "Layer 3")
mediaInfoFull += ".MP3";
else
mediaInfoFull += "." + episodeFile.MediaInfo.AudioFormat;
break;
case "DTS":
mediaInfoFull += "." + episodeFile.MediaInfo.AudioFormat;
break;
default:
mediaInfoFull += "." + episodeFile.MediaInfo.AudioFormat;
break;
}
tokenValues.Add("{MediaInfo Short}", mediaInfoFull);
var audioLanguagesToken = GetLanguagesToken(episodeFile.MediaInfo.AudioLanguages);
if (!string.IsNullOrEmpty(audioLanguagesToken) && audioLanguagesToken != "EN")
mediaInfoFull += string.Format("[{0}]", audioLanguagesToken);
var subtitleLanguagesToken = GetLanguagesToken(episodeFile.MediaInfo.Subtitles);
if (!string.IsNullOrEmpty(subtitleLanguagesToken))
mediaInfoFull += string.Format(".[{0}]", subtitleLanguagesToken);
tokenValues.Add("{MediaInfo Full}", mediaInfoFull);
tokenHandlers["{Series Title}"] = m => series.Title;
tokenHandlers["{Series CleanTitle}"] = m => CleanTitle(series.Title);
}
private string GetLanguagesToken(string mediaInfoLanguages)
private void AddSeasonTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, Int32 seasonNumber)
{
List<string> tokens = new List<string>();
foreach (var item in mediaInfoLanguages.Split('/'))
{
if (!string.IsNullOrWhiteSpace(item))
tokens.Add(item.Trim());
}
var cultures = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.NeutralCultures);
for (int i = 0; i < tokens.Count; i++)
{
try
{
var cultureInfo = cultures.FirstOrDefault(p => p.EnglishName == tokens[i]);
if (cultureInfo != null)
tokens[i] = cultureInfo.TwoLetterISOLanguageName.ToUpper();
}
catch
{
}
}
return string.Join("+", tokens.Distinct());
tokenHandlers["{Season}"] = m => seasonNumber.ToString(m.CustomFormat ?? "0");
}
private string ReplaceTokens(string pattern, Dictionary<string, string> tokenValues)
private void AddEpisodeTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, List<Episode> episodes)
{
return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenValues));
}
private string ReplaceToken(Match match, Dictionary<string, string> tokenValues)
{
var separator = match.Groups["separator"].Value;
var token = match.Groups["token"].Value;
var replacementText = "";
var patternTokenArray = token.ToCharArray();
if (!tokenValues.TryGetValue(token, out replacementText)) return null;
if (patternTokenArray.All(t => !Char.IsLetter(t) || Char.IsLower(t)))
if (!episodes.First().AirDate.IsNullOrWhiteSpace())
{
replacementText = replacementText.ToLowerInvariant();
tokenHandlers["{Air Date}"] = m => episodes.First().AirDate.Replace('-', ' ');
}
else
{
tokenHandlers["{Air Date}"] = m => "Unknown";
}
else if (patternTokenArray.All(t => !Char.IsLetter(t) || Char.IsUpper(t)))
tokenHandlers["{Episode Title}"] = m => GetEpisodeTitle(episodes);
}
private void AddEpisodeFileTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, EpisodeFile episodeFile)
{
tokenHandlers["{Original Title}"]= m => episodeFile.SceneName;
tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup;
tokenHandlers["{Quality Title}"] = m => GetQualityTitle(episodeFile.Quality);
}
private void AddMediaInfoTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, EpisodeFile episodeFile)
{
if (episodeFile.MediaInfo == null) return;
String mediaInfoVideo;
switch (episodeFile.MediaInfo.VideoCodec)
{
case "AVC":
// TODO: What to do if the original SceneName is hashed?
if (!episodeFile.SceneName.IsNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(episodeFile.SceneName).Contains("h264"))
{
mediaInfoVideo = "h264";
}
else
{
mediaInfoVideo = "x264";
}
break;
default:
mediaInfoVideo = episodeFile.MediaInfo.VideoCodec;
break;
}
String mediaInfoAudio;
switch (episodeFile.MediaInfo.AudioFormat)
{
case "AC-3":
mediaInfoAudio = "AC3";
break;
case "MPEG Audio":
if (episodeFile.MediaInfo.AudioProfile == "Layer 3")
{
mediaInfoAudio = "MP3";
}
else
{
mediaInfoAudio = episodeFile.MediaInfo.AudioFormat;
}
break;
case "DTS":
mediaInfoAudio = episodeFile.MediaInfo.AudioFormat;
break;
default:
mediaInfoAudio = episodeFile.MediaInfo.AudioFormat;
break;
}
var mediaInfoAudioLanguages = GetLanguagesToken(episodeFile.MediaInfo.AudioLanguages);
if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace())
{
mediaInfoAudioLanguages = String.Format("[{0}]", mediaInfoAudioLanguages);
}
if (mediaInfoAudioLanguages == "[EN]")
{
mediaInfoAudioLanguages = String.Empty;
}
var mediaInfoSubtitleLanguages = GetLanguagesToken(episodeFile.MediaInfo.Subtitles);
if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace())
{
mediaInfoSubtitleLanguages = String.Format("[{0}]", mediaInfoSubtitleLanguages);
}
tokenHandlers["{MediaInfo Video}"] = m => mediaInfoVideo;
tokenHandlers["{MediaInfo Audio}"] = m => mediaInfoAudio;
tokenHandlers["{MediaInfo Simple}"] = m => String.Format("{0} {1}", mediaInfoVideo, mediaInfoAudio);
tokenHandlers["{MediaInfo Full}"] = m => String.Format("{0} {1}{2} {3}", mediaInfoVideo, mediaInfoAudio, mediaInfoAudioLanguages, mediaInfoSubtitleLanguages);
}
private string GetLanguagesToken(String mediaInfoLanguages)
{
List<string> tokens = new List<string>();
foreach (var item in mediaInfoLanguages.Split('/'))
{
if (!string.IsNullOrWhiteSpace(item))
tokens.Add(item.Trim());
}
var cultures = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.NeutralCultures);
for (int i = 0; i < tokens.Count; i++)
{
try
{
var cultureInfo = cultures.FirstOrDefault(p => p.EnglishName == tokens[i]);
if (cultureInfo != null)
tokens[i] = cultureInfo.TwoLetterISOLanguageName.ToUpper();
}
catch
{
}
}
return string.Join("+", tokens.Distinct());
}
private string ReplaceTokens(String pattern, Dictionary<String, Func<TokenMatch, String>> tokenHandlers)
{
return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenHandlers));
}
private string ReplaceToken(Match match, Dictionary<String, Func<TokenMatch, String>> tokenHandlers)
{
var tokenMatch = new TokenMatch
{
RegexMatch = match,
Prefix = match.Groups["prefix"].Value,
Separator = match.Groups["separator"].Value,
Suffix = match.Groups["suffix"].Value,
Token = match.Groups["token"].Value,
CustomFormat = match.Groups["customFormat"].Value
};
if (tokenMatch.CustomFormat.IsNullOrWhiteSpace())
{
tokenMatch.CustomFormat = null;
}
var tokenHandler = tokenHandlers.GetValueOrDefault(tokenMatch.Token, m => String.Empty);
var replacementText = tokenHandler(tokenMatch).Trim();
if (tokenMatch.Token.All(t => !Char.IsLetter(t) || Char.IsLower(t)))
{
replacementText = replacementText.ToLower();
}
else if (tokenMatch.Token.All(t => !Char.IsLetter(t) || Char.IsUpper(t)))
{
replacementText = replacementText.ToUpper();
}
if (!separator.Equals(" "))
if (!tokenMatch.Separator.IsNullOrWhiteSpace())
{
replacementText = replacementText.Replace(" ", separator);
replacementText = replacementText.Replace(" ", tokenMatch.Separator);
}
replacementText = CleanFileName(replacementText);
if (!replacementText.IsNullOrWhiteSpace())
{
replacementText = tokenMatch.Prefix + replacementText + tokenMatch.Suffix;
}
return replacementText;
}
private sealed class TokenMatch
{
public Match RegexMatch { get; set; }
public String Prefix { get; set; }
public String Separator { get; set; }
public String Suffix { get; set; }
public String Token { get; set; }
public String CustomFormat { get; set; }
}
private string ReplaceNumberTokens(string pattern, List<Episode> episodes)
{
var episodeIndex = 0;
@ -531,17 +609,22 @@ namespace NzbDrone.Core.Organizer
return null;
}
private string GetEpisodeTitle(List<string> episodeTitles)
private String GetEpisodeTitle(List<Episode> episodes)
{
if (episodeTitles.Count == 1)
if (episodes.Count == 1)
{
return episodeTitles.First();
return episodes.First().Title.TrimEnd(EpisodeTitleTrimCharacters);
}
return String.Join(" + ", episodeTitles.Select(Parser.Parser.CleanupEpisodeTitle).Distinct());
var titles = episodes
.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters))
.Select(Parser.Parser.CleanupEpisodeTitle)
.Distinct();
return String.Join(" + ", titles);
}
private string GetQualityTitle(QualityModel quality)
private String GetQualityTitle(QualityModel quality)
{
if (quality.Proper)
return _qualityDefinitionService.Get(quality.Quality).Title + " Proper";

View File

@ -4,13 +4,13 @@ using System.Text.RegularExpressions;
namespace NzbDrone.Core.Organizer
{
public class FilenameBuilderTokenEqualityComparer : IEqualityComparer<String>
public class FileNameBuilderTokenEqualityComparer : IEqualityComparer<String>
{
public static readonly FilenameBuilderTokenEqualityComparer Instance = new FilenameBuilderTokenEqualityComparer();
public static readonly FileNameBuilderTokenEqualityComparer Instance = new FileNameBuilderTokenEqualityComparer();
private static readonly Regex SimpleTokenRegex = new Regex(@"\s|_|\W", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private FilenameBuilderTokenEqualityComparer()
private FileNameBuilderTokenEqualityComparer()
{
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.MediaFiles.MediaInfo;
namespace NzbDrone.Core.Organizer
{
@ -16,7 +17,7 @@ namespace NzbDrone.Core.Organizer
String GetSeasonFolderSample(NamingConfig nameSpec);
}
public class FilenameSampleService : IFilenameSampleService
public class FileNameSampleService : IFilenameSampleService
{
private readonly IBuildFileNames _buildFileNames;
private static Series _standardSeries;
@ -31,26 +32,26 @@ namespace NzbDrone.Core.Organizer
private static EpisodeFile _dailyEpisodeFile;
private static EpisodeFile _animeEpisodeFile;
public FilenameSampleService(IBuildFileNames buildFileNames)
public FileNameSampleService(IBuildFileNames buildFileNames)
{
_buildFileNames = buildFileNames;
_standardSeries = new Series
{
SeriesType = SeriesTypes.Standard,
Title = "Series Title"
};
{
SeriesType = SeriesTypes.Standard,
Title = "Series Title (2010)"
};
_dailySeries = new Series
{
SeriesType = SeriesTypes.Daily,
Title = "Series Title"
Title = "Series Title (2010)"
};
_animeSeries = new Series
{
SeriesType = SeriesTypes.Anime,
Title = "Series Title"
Title = "Series Title (2010)"
};
_episode1 = new Episode
@ -73,32 +74,52 @@ namespace NzbDrone.Core.Organizer
_singleEpisode = new List<Episode> { _episode1 };
_multiEpisodes = new List<Episode> { _episode1, _episode2 };
var mediaInfo = new MediaInfoModel()
{
VideoCodec = "AVC",
AudioFormat = "DTS",
AudioLanguages = "English",
Subtitles = "English/German"
};
var mediaInfoAnime = new MediaInfoModel()
{
VideoCodec = "AVC",
AudioFormat = "DTS",
AudioLanguages = "Japanese",
Subtitles = "Japanese/English"
};
_singleEpisodeFile = new EpisodeFile
{
Quality = new QualityModel(Quality.HDTV720p),
Path = @"C:\Test\Series.Title.S01E01.720p.HDTV.x264-EVOLVE.mkv",
ReleaseGroup = "RlsGrp"
ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfo
};
_multiEpisodeFile = new EpisodeFile
{
Quality = new QualityModel(Quality.HDTV720p),
Path = @"C:\Test\Series.Title.S01E01-E02.720p.HDTV.x264-EVOLVE.mkv",
ReleaseGroup = "RlsGrp"
ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfo
};
_dailyEpisodeFile = new EpisodeFile
{
Quality = new QualityModel(Quality.HDTV720p),
Path = @"C:\Test\Series.Title.2013.10.30.HDTV.x264-EVOLVE.mkv",
ReleaseGroup = "RlsGrp"
ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfo
};
_animeEpisodeFile = new EpisodeFile
{
Quality = new QualityModel(Quality.HDTV720p),
Path = @"C:\Test\Series.Title.001.HDTV.x264-EVOLVE.mkv",
ReleaseGroup = "RlsGrp"
ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfoAnime
};
}
@ -106,7 +127,7 @@ namespace NzbDrone.Core.Organizer
{
var result = new SampleResult
{
Filename = BuildSample(_singleEpisode, _standardSeries, _singleEpisodeFile, nameSpec),
FileName = BuildSample(_singleEpisode, _standardSeries, _singleEpisodeFile, nameSpec),
Series = _standardSeries,
Episodes = _singleEpisode,
EpisodeFile = _singleEpisodeFile
@ -119,7 +140,7 @@ namespace NzbDrone.Core.Organizer
{
var result = new SampleResult
{
Filename = BuildSample(_multiEpisodes, _standardSeries, _multiEpisodeFile, nameSpec),
FileName = BuildSample(_multiEpisodes, _standardSeries, _multiEpisodeFile, nameSpec),
Series = _standardSeries,
Episodes = _multiEpisodes,
EpisodeFile = _multiEpisodeFile
@ -132,7 +153,7 @@ namespace NzbDrone.Core.Organizer
{
var result = new SampleResult
{
Filename = BuildSample(_singleEpisode, _dailySeries, _dailyEpisodeFile, nameSpec),
FileName = BuildSample(_singleEpisode, _dailySeries, _dailyEpisodeFile, nameSpec),
Series = _dailySeries,
Episodes = _singleEpisode,
EpisodeFile = _dailyEpisodeFile
@ -145,7 +166,7 @@ namespace NzbDrone.Core.Organizer
{
var result = new SampleResult
{
Filename = BuildSample(_singleEpisode, _animeSeries, _animeEpisodeFile, nameSpec),
FileName = BuildSample(_singleEpisode, _animeSeries, _animeEpisodeFile, nameSpec),
Series = _animeSeries,
Episodes = _singleEpisode,
EpisodeFile = _animeEpisodeFile
@ -156,19 +177,19 @@ namespace NzbDrone.Core.Organizer
public string GetSeriesFolderSample(NamingConfig nameSpec)
{
return _buildFileNames.GetSeriesFolder(_standardSeries.Title, nameSpec);
return _buildFileNames.GetSeriesFolder(_standardSeries, nameSpec);
}
public string GetSeasonFolderSample(NamingConfig nameSpec)
{
return _buildFileNames.GetSeasonFolder(_standardSeries.Title, _episode1.SeasonNumber, nameSpec);
return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, nameSpec);
}
private string BuildSample(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig nameSpec)
{
try
{
return _buildFileNames.BuildFilename(episodes, series, episodeFile, nameSpec);
return _buildFileNames.BuildFileName(episodes, series, episodeFile, nameSpec);
}
catch (NamingFormatException)
{

View File

@ -14,14 +14,14 @@ namespace NzbDrone.Core.Organizer
ValidationFailure ValidateAnimeFilename(SampleResult sampleResult);
}
public class FilenameValidationService : IFilenameValidationService
public class FileNameValidationService : IFilenameValidationService
{
private const string ERROR_MESSAGE = "Produces invalid file names";
public ValidationFailure ValidateStandardFilename(SampleResult sampleResult)
{
var validationFailure = new ValidationFailure("StandardEpisodeFormat", ERROR_MESSAGE);
var parsedEpisodeInfo = Parser.Parser.ParseTitle(sampleResult.Filename);
var parsedEpisodeInfo = Parser.Parser.ParseTitle(sampleResult.FileName);
if (parsedEpisodeInfo == null)
{
@ -39,7 +39,7 @@ namespace NzbDrone.Core.Organizer
public ValidationFailure ValidateDailyFilename(SampleResult sampleResult)
{
var validationFailure = new ValidationFailure("DailyEpisodeFormat", ERROR_MESSAGE);
var parsedEpisodeInfo = Parser.Parser.ParseTitle(sampleResult.Filename);
var parsedEpisodeInfo = Parser.Parser.ParseTitle(sampleResult.FileName);
if (parsedEpisodeInfo == null)
{
@ -67,7 +67,7 @@ namespace NzbDrone.Core.Organizer
public ValidationFailure ValidateAnimeFilename(SampleResult sampleResult)
{
var validationFailure = new ValidationFailure("AnimeEpisodeFormat", ERROR_MESSAGE);
var parsedEpisodeInfo = Parser.Parser.ParseTitle(sampleResult.Filename);
var parsedEpisodeInfo = Parser.Parser.ParseTitle(sampleResult.FileName);
if (parsedEpisodeInfo == null)
{

View File

@ -9,7 +9,7 @@ namespace NzbDrone.Core.Organizer
{
public class SampleResult
{
public string Filename { get; set; }
public String FileName { get; set; }
public Series Series { get; set; }
public List<Episode> Episodes { get; set; }
public EpisodeFile EpisodeFile { get; set; }

View File

@ -71,7 +71,7 @@ namespace NzbDrone.Core.Tv
if (String.IsNullOrWhiteSpace(newSeries.Path))
{
var folderName = _fileNameBuilder.GetSeriesFolder(newSeries.Title);
var folderName = _fileNameBuilder.GetSeriesFolder(newSeries);
newSeries.Path = Path.Combine(newSeries.RootFolderPath, folderName);
}

View File

@ -48,6 +48,7 @@
{{> EpisodeNamingPartial}}
{{> EpisodeTitleNamingPartial}}
{{> QualityTitleNamingPartial}}
{{> MediaInfoNamingPartial}}
{{> ReleaseGroupNamingPartial}}
{{> OriginalTitleNamingPartial}}
{{> SeparatorNamingPartial}}
@ -79,6 +80,7 @@
{{> EpisodeNamingPartial}}
{{> EpisodeTitleNamingPartial}}
{{> QualityTitleNamingPartial}}
{{> MediaInfoNamingPartial}}
{{> ReleaseGroupNamingPartial}}
{{> OriginalTitleNamingPartial}}
{{> SeparatorNamingPartial}}
@ -110,6 +112,7 @@
{{> EpisodeNamingPartial}}
{{> EpisodeTitleNamingPartial}}
{{> QualityTitleNamingPartial}}
{{> MediaInfoNamingPartial}}
{{> ReleaseGroupNamingPartial}}
{{> OriginalTitleNamingPartial}}
{{> SeparatorNamingPartial}}

View File

@ -0,0 +1,11 @@
<li class="dropdown-submenu">
<a href="#" tabindex="-1" data-token="MediaInfo.Simple">MediaInfo</a>
<ul class="dropdown-menu">
<li><a href="#" data-token="MediaInfo Simple">MediaInfo Simple</a></li>
<li><a href="#" data-token="MediaInfo.Simple">MediaInfo.Simple</a></li>
<li><a href="#" data-token="MediaInfo_Simple">MediaInfo_Simple</a></li>
<li><a href="#" data-token="MediaInfo Full">MediaInfo Full</a></li>
<li><a href="#" data-token="MediaInfo.Full">MediaInfo.Full</a></li>
<li><a href="#" data-token="MediaInfo_Full">MediaInfo_Full</a></li>
</ul>
</li>

View File

@ -4,5 +4,8 @@
<li><a href="#" data-token="Series Title">Series Title</a></li>
<li><a href="#" data-token="Series.Title">Series.Title</a></li>
<li><a href="#" data-token="Series_Title">Series_Title</a></li>
<li><a href="#" data-token="Series CleanTitle">Series CleanTitle</a></li>
<li><a href="#" data-token="Series.CleanTitle">Series.CleanTitle</a></li>
<li><a href="#" data-token="Series_CleanTitle">Series_CleanTitle</a></li>
</ul>
</li>