diff --git a/src/NzbDrone.Api/Config/NamingConfigModule.cs b/src/NzbDrone.Api/Config/NamingConfigModule.cs index 03d494aac..b7b2a035f 100644 --- a/src/NzbDrone.Api/Config/NamingConfigModule.cs +++ b/src/NzbDrone.Api/Config/NamingConfigModule.cs @@ -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" diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs index 00b1302ec..75fe64ca6 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs @@ -39,7 +39,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests .Build(); Mocker.GetMock() - .Setup(s => s.BuildFilename(It.IsAny>(), It.IsAny(), It.IsAny())) + .Setup(s => s.BuildFileName(It.IsAny>(), It.IsAny(), It.IsAny(), null)) .Returns("File Name"); Mocker.GetMock() diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTest.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTest.cs index ea567a509..d1bd43441 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTest.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTest.cs @@ -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] diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderFixture.cs index e0a06619d..278407505 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderFixture.cs @@ -69,7 +69,7 @@ namespace NzbDrone.Core.Test.OrganizerTests { _namingConfig.StandardEpisodeFormat = "{Series Title}"; - Subject.BuildFilename(new List {_episode1}, _series, _episodeFile) + Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) .Should().Be("South Park"); } @@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.OrganizerTests { _namingConfig.StandardEpisodeFormat = "{Series_Title}"; - Subject.BuildFilename(new List {_episode1}, _series, _episodeFile) + Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) .Should().Be("South_Park"); } @@ -87,7 +87,7 @@ namespace NzbDrone.Core.Test.OrganizerTests { _namingConfig.StandardEpisodeFormat = "{Series.Title}"; - Subject.BuildFilename(new List {_episode1}, _series, _episodeFile) + Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) .Should().Be("South.Park"); } @@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.OrganizerTests { _namingConfig.StandardEpisodeFormat = "{Series-Title}"; - Subject.BuildFilename(new List {_episode1}, _series, _episodeFile) + Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) .Should().Be("South-Park"); } @@ -105,7 +105,7 @@ namespace NzbDrone.Core.Test.OrganizerTests { _namingConfig.StandardEpisodeFormat = "{SERIES TITLE}"; - Subject.BuildFilename(new List {_episode1}, _series, _episodeFile) + Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) .Should().Be("SOUTH PARK"); } @@ -114,7 +114,7 @@ namespace NzbDrone.Core.Test.OrganizerTests { _namingConfig.StandardEpisodeFormat = "{sErIES-tItLE}"; - Subject.BuildFilename(new List { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 {_episode1}, _series, _episodeFile) + Subject.BuildFileName(new List {_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 { _episode1 }, _series, _episodeFile) + .Should().Be("South.Park.1997"); + } + [Test] public void should_replace_episode_title() { _namingConfig.StandardEpisodeFormat = "{Episode Title}"; - Subject.BuildFilename(new List {_episode1}, _series, _episodeFile) + Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) .Should().Be("City Sushi"); } @@ -141,7 +151,7 @@ namespace NzbDrone.Core.Test.OrganizerTests { _namingConfig.StandardEpisodeFormat = "{ePisOde-TitLe}"; - Subject.BuildFilename(new List { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) .Should().Be("1x06"); } @@ -190,7 +200,7 @@ namespace NzbDrone.Core.Test.OrganizerTests { _namingConfig.StandardEpisodeFormat = "{Quality Title}"; - Subject.BuildFilename(new List { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 {_episode1}, _series, _episodeFile) + Subject.BuildFileName(new List {_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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) .Should().Be("30.Rock.S01E01.xvid-LOL"); } @@ -253,7 +263,7 @@ namespace NzbDrone.Core.Test.OrganizerTests .Build(); - Subject.BuildFilename(new List {episode2, episode}, new Series {Title = "30 Rock"}, _episodeFile) + Subject.BuildFileName(new List {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 {_episode1, _episode2}, _series, _episodeFile) + Subject.BuildFileName(new List {_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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 {_episode1, _episode2}, _series, _episodeFile) + Subject.BuildFileName(new List {_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 { _episode1, _episode2 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1, _episode2 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1, _episode2 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) .Should().Be(title); } @@ -357,7 +367,7 @@ namespace NzbDrone.Core.Test.OrganizerTests { _namingConfig.StandardEpisodeFormat = "{Release Group}"; - Subject.BuildFilename(new List { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 }, new Series { Title = "30 Rock" }, _episodeFile) + Subject.BuildFileName(new List { 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 }, new Series { Title = "30 Rock" }, _episodeFile) + Subject.BuildFileName(new List { 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 }, new Series { Title = "Chicago P.D." }, _episodeFile) + Subject.BuildFileName(new List { 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 }, new Series { Title = "Chicago P.D.." }, _episodeFile) + Subject.BuildFileName(new List { 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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1, _episode2 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1, }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _episode1, _episode2 }, _series, _episodeFile) + Subject.BuildFileName(new List { _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 { _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 { _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 { _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 { _episode1 }, _series, _episodeFile) + .Should().Be("South.Park.S15E06.City.Sushi.X264.DTS.[EN+ES+IT]"); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetSeriesFolderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetSeriesFolderFixture.cs index abe7bf78b..8757da319 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/GetSeriesFolderFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/GetSeriesFolderFixture.cs @@ -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); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/AddSeriesFixture.cs b/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/AddSeriesFixture.cs index c62504f12..cdc1041e7 100644 --- a/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/AddSeriesFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/AddSeriesFixture.cs @@ -26,7 +26,7 @@ namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests fakeSeries.RootFolderPath = @"C:\Test\TV"; Mocker.GetMock() - .Setup(s => s.GetSeriesFolder(fakeSeries.Title)) + .Setup(s => s.GetSeriesFolder(fakeSeries, null)) .Returns(fakeSeries.Title); var series = Subject.AddSeries(fakeSeries); diff --git a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs index c0f202af3..6ad451295 100644 --- a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs +++ b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs @@ -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"); diff --git a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs b/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs index bbc077dcd..3dfe01648 100644 --- a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs @@ -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 { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs index 44bec3fbe..98d801144 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs @@ -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); diff --git a/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs b/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs index 6a81ee4c8..a960cca5f 100644 --- a/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs @@ -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)) diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 3873058a0..88d5299eb 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -634,10 +634,10 @@ - - + + - + diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 7b9920f1a..e6b27419a 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -14,13 +14,11 @@ namespace NzbDrone.Core.Organizer { public interface IBuildFileNames { - string BuildFilename(IList episodes, Series series, EpisodeFile episodeFile); - string BuildFilename(IList episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig); - string BuildFilePath(Series series, int seasonNumber, string fileName, string extension); + string BuildFileName(List 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 _patternCache; private readonly Logger _logger; - private static readonly Regex TitleRegex = new Regex(@"(?\{(?:\w+)(?\s|\.|-|_)\w+\})", + private static readonly Regex TitleRegex = new Regex(@"\{(?[- ._]*)(?(?:[a-z0-9]+)(?:(?[- ._]+)(?:[a-z0-9]+))?)(?::(?[a-z0-9]+))?(?[- ._]*)\}", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex EpisodeRegex = new Regex(@"(?\{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(@"(?\{(?:Series)(?\s|\.|-|_)Title\})", + public static readonly Regex SeriesTitleRegex = new Regex(@"(?\{(?:Series)(?\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 episodes, Series series, EpisodeFile episodeFile) + public string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null) { - var nameSpec = _namingConfigService.GetConfig(); - - return BuildFilename(episodes, series, episodeFile, nameSpec); - } + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } - public string BuildFilename(IList 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 episodeTitles = new List - { - sortedEpisodes.First().Title.TrimEnd(EpisodeTitleTrimCharaters) - }; - var tokenValues = new Dictionary(FilenameBuilderTokenEqualityComparer.Instance); + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + episodes = episodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber).ToList(); + + AddSeriesTokens(tokenHandlers, series); + + AddEpisodeTokens(tokenHandlers, episodes); - tokenValues.Add("{Series Title}", series.Title); - tokenValues.Add("{Original Title}", episodeFile.SceneName); - tokenValues.Add("{Release Group}", episodeFile.ReleaseGroup); + AddEpisodeFileTokens(tokenHandlers, episodeFile); + AddMediaInfoTokens(tokenHandlers, episodeFile); + 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)); - - AddMediaInfoTokens(episodeFile, tokenValues); - - var filename = ReplaceTokens(pattern, tokenValues).Trim(); - filename = FilenameCleanupRegex.Replace(filename, match => match.Captures[0].Value[0].ToString() ); + var filename = ReplaceTokens(pattern, tokenHandlers).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(); + } + + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddSeriesTokens(tokenHandlers, series); - return GetSeriesFolder(seriesTitle, namingConfig); + 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 tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - var tokenValues = new Dictionary(FilenameBuilderTokenEqualityComparer.Instance); - tokenValues.Add("{Series Title}", seriesTitle); + AddSeriesTokens(tokenHandlers, series); - return ReplaceTokens(namingConfig.SeriesFolderFormat, tokenValues); + 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(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 tokenValues) + private void AddSeriesTokens(Dictionary> 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> tokenHandlers, Int32 seasonNumber) { - List tokens = new List(); - 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 void AddEpisodeTokens(Dictionary> tokenHandlers, List episodes) + { + if (!episodes.First().AirDate.IsNullOrWhiteSpace()) + { + tokenHandlers["{Air Date}"] = m => episodes.First().AirDate.Replace('-', ' '); + } + else + { + tokenHandlers["{Air Date}"] = m => "Unknown"; + } + + tokenHandlers["{Episode Title}"] = m => GetEpisodeTitle(episodes); } - private string ReplaceTokens(string pattern, Dictionary tokenValues) + private void AddEpisodeFileTokens(Dictionary> tokenHandlers, EpisodeFile episodeFile) { - return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenValues)); + tokenHandlers["{Original Title}"]= m => episodeFile.SceneName; + tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup; + tokenHandlers["{Quality Title}"] = m => GetQualityTitle(episodeFile.Quality); } - private string ReplaceToken(Match match, Dictionary tokenValues) + private void AddMediaInfoTokens(Dictionary> tokenHandlers, EpisodeFile episodeFile) { - 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 (episodeFile.MediaInfo == null) return; - if (patternTokenArray.All(t => !Char.IsLetter(t) || Char.IsLower(t))) + String mediaInfoVideo; + switch (episodeFile.MediaInfo.VideoCodec) { - replacementText = replacementText.ToLowerInvariant(); + 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; } - else if (patternTokenArray.All(t => !Char.IsLetter(t) || Char.IsUpper(t))) + 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 tokens = new List(); + 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> tokenHandlers) + { + return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenHandlers)); + } + + private string ReplaceToken(Match match, Dictionary> 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 episodes) { var episodeIndex = 0; @@ -531,17 +609,22 @@ namespace NzbDrone.Core.Organizer return null; } - private string GetEpisodeTitle(List episodeTitles) + private String GetEpisodeTitle(List 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"; diff --git a/src/NzbDrone.Core/Organizer/FilenameBuilderTokenEqualityComparer.cs b/src/NzbDrone.Core/Organizer/FilenameBuilderTokenEqualityComparer.cs index 16b225131..055083d81 100644 --- a/src/NzbDrone.Core/Organizer/FilenameBuilderTokenEqualityComparer.cs +++ b/src/NzbDrone.Core/Organizer/FilenameBuilderTokenEqualityComparer.cs @@ -4,13 +4,13 @@ using System.Text.RegularExpressions; namespace NzbDrone.Core.Organizer { - public class FilenameBuilderTokenEqualityComparer : IEqualityComparer + public class FileNameBuilderTokenEqualityComparer : IEqualityComparer { - 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() { } diff --git a/src/NzbDrone.Core/Organizer/FilenameSampleService.cs b/src/NzbDrone.Core/Organizer/FilenameSampleService.cs index c4231e533..80ca9083d 100644 --- a/src/NzbDrone.Core/Organizer/FilenameSampleService.cs +++ b/src/NzbDrone.Core/Organizer/FilenameSampleService.cs @@ -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 { _episode1 }; _multiEpisodes = new List { _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 episodes, Series series, EpisodeFile episodeFile, NamingConfig nameSpec) { try { - return _buildFileNames.BuildFilename(episodes, series, episodeFile, nameSpec); + return _buildFileNames.BuildFileName(episodes, series, episodeFile, nameSpec); } catch (NamingFormatException) { diff --git a/src/NzbDrone.Core/Organizer/FilenameValidationService.cs b/src/NzbDrone.Core/Organizer/FilenameValidationService.cs index 5c2703cc1..13d91aca6 100644 --- a/src/NzbDrone.Core/Organizer/FilenameValidationService.cs +++ b/src/NzbDrone.Core/Organizer/FilenameValidationService.cs @@ -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) { diff --git a/src/NzbDrone.Core/Organizer/SampleResult.cs b/src/NzbDrone.Core/Organizer/SampleResult.cs index 928438e8c..8966646e0 100644 --- a/src/NzbDrone.Core/Organizer/SampleResult.cs +++ b/src/NzbDrone.Core/Organizer/SampleResult.cs @@ -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 Episodes { get; set; } public EpisodeFile EpisodeFile { get; set; } diff --git a/src/NzbDrone.Core/Tv/SeriesService.cs b/src/NzbDrone.Core/Tv/SeriesService.cs index f48e900b7..5d765dfd4 100644 --- a/src/NzbDrone.Core/Tv/SeriesService.cs +++ b/src/NzbDrone.Core/Tv/SeriesService.cs @@ -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); } diff --git a/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html b/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html index 33c76d757..10280503d 100644 --- a/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html +++ b/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html @@ -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}} diff --git a/src/UI/Settings/MediaManagement/Naming/Partials/MediaInfoNamingPartial.html b/src/UI/Settings/MediaManagement/Naming/Partials/MediaInfoNamingPartial.html new file mode 100644 index 000000000..49203cafc --- /dev/null +++ b/src/UI/Settings/MediaManagement/Naming/Partials/MediaInfoNamingPartial.html @@ -0,0 +1,11 @@ + diff --git a/src/UI/Settings/MediaManagement/Naming/Partials/SeriesTitleNamingPartial.html b/src/UI/Settings/MediaManagement/Naming/Partials/SeriesTitleNamingPartial.html index f2cd5fd81..cc76c95b5 100644 --- a/src/UI/Settings/MediaManagement/Naming/Partials/SeriesTitleNamingPartial.html +++ b/src/UI/Settings/MediaManagement/Naming/Partials/SeriesTitleNamingPartial.html @@ -4,5 +4,8 @@
  • Series Title
  • Series.Title
  • Series_Title
  • +
  • Series CleanTitle
  • +
  • Series.CleanTitle
  • +
  • Series_CleanTitle