From b427954f5f27768ce9c7f7891021b32bf2043c19 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Mon, 28 Jul 2014 13:16:40 +0200 Subject: [PATCH 1/3] null EmbeddedDocuments are now stored as DBNull instead of json null. --- .../Datastore/MarrDataLazyLoadingFixture.cs | 1 + .../HistoryTests/HistoryRepositoryFixture.cs | 9 +++++++-- .../Housekeepers/CleanupOrphanedBlacklistFixture.cs | 11 +++++++++-- .../CleanupOrphanedEpisodeFilesFixture.cs | 4 ++++ .../CleanupOrphanedHistoryItemsFixture.cs | 5 +++++ .../CleanupOrphanedMetadataFilesFixture.cs | 2 ++ .../CleanupOrphanedPendingReleasesFixture.cs | 11 ++++++++--- .../DownloadedEpisodesImportServiceFixture.cs | 2 +- .../EpisodesRepositoryReadFixture.cs | 5 ++++- .../EpisodesWithFilesFixture.cs | 7 +++++-- .../Datastore/Converters/EmbeddedDocumentConverter.cs | 1 + 11 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs b/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs index 8cf59717b..49d67f063 100644 --- a/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs @@ -36,6 +36,7 @@ namespace NzbDrone.Core.Test.Datastore var episodeFiles = Builder.CreateListOfSize(1) .All() .With(v => v.SeriesId = series[0].Id) + .With(v => v.Quality = new QualityModel()) .BuildListOfNew(); Db.InsertMany(episodeFiles); diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs index e2da6251c..34b6936bb 100644 --- a/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs @@ -4,6 +4,7 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.History; using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Test.HistoryTests { @@ -15,7 +16,8 @@ namespace NzbDrone.Core.Test.HistoryTests { var historyItem = Builder.CreateListOfSize(30) .All() - .With(c=>c.Id = 0) + .With(c => c.Id = 0) + .With(c => c.Quality = new QualityModel()) .TheFirst(10).With(c => c.Date = DateTime.Now) .TheNext(20).With(c => c.Date = DateTime.Now.AddDays(-31)) .Build(); @@ -32,7 +34,9 @@ namespace NzbDrone.Core.Test.HistoryTests [Test] public void should_read_write_dictionary() { - var history = Builder.CreateNew().BuildNew(); + var history = Builder.CreateNew() + .With(c => c.Quality = new QualityModel()) + .BuildNew(); history.Data.Add("key1","value1"); history.Data.Add("key2","value2"); @@ -48,6 +52,7 @@ namespace NzbDrone.Core.Test.HistoryTests var history = Builder .CreateListOfSize(5) .All() + .With(c => c.Quality = new QualityModel()) .With(c => c.EventType = HistoryEventType.Unknown) .Random(3) .With(c => c.EventType = HistoryEventType.Grabbed) diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBlacklistFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBlacklistFixture.cs index 350471a1d..e5c3c077e 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBlacklistFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBlacklistFixture.cs @@ -3,8 +3,11 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Blacklisting; using NzbDrone.Core.Housekeeping.Housekeepers; +using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; +using System; +using System.Collections.Generic; namespace NzbDrone.Core.Test.Housekeeping.Housekeepers { @@ -15,6 +18,8 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers public void should_delete_orphaned_blacklist_items() { var blacklist = Builder.CreateNew() + .With(h => h.EpisodeIds = new List()) + .With(h => h.Quality = new QualityModel()) .BuildNew(); Db.Insert(blacklist); @@ -30,8 +35,10 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers Db.Insert(series); var blacklist = Builder.CreateNew() - .With(b => b.SeriesId = series.Id) - .BuildNew(); + .With(h => h.EpisodeIds = new List()) + .With(h => h.Quality = new QualityModel()) + .With(b => b.SeriesId = series.Id) + .BuildNew(); Db.Insert(blacklist); diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFilesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFilesFixture.cs index 0c97d0ae7..b09def40c 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFilesFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedEpisodeFilesFixture.cs @@ -6,6 +6,7 @@ using NzbDrone.Core.Housekeeping.Housekeepers; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; +using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Test.Housekeeping.Housekeepers { @@ -16,6 +17,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers public void should_delete_orphaned_episode_files() { var episodeFile = Builder.CreateNew() + .With(h => h.Quality = new QualityModel()) .BuildNew(); Db.Insert(episodeFile); @@ -27,6 +29,8 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers public void should_not_delete_unorphaned_episode_files() { var episodeFiles = Builder.CreateListOfSize(2) + .All() + .With(h => h.Quality = new QualityModel()) .BuildListOfNew(); Db.InsertMany(episodeFiles); diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs index 3ebf89dbd..022248abd 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs @@ -2,6 +2,7 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Housekeeping.Housekeepers; +using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; @@ -39,6 +40,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers GivenEpisode(); var history = Builder.CreateNew() + .With(h => h.Quality = new QualityModel()) .With(h => h.EpisodeId = _episode.Id) .BuildNew(); Db.Insert(history); @@ -53,6 +55,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers GivenSeries(); var history = Builder.CreateNew() + .With(h => h.Quality = new QualityModel()) .With(h => h.SeriesId = _series.Id) .BuildNew(); Db.Insert(history); @@ -69,6 +72,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers var history = Builder.CreateListOfSize(2) .All() + .With(h => h.Quality = new QualityModel()) .With(h => h.EpisodeId = _episode.Id) .TheFirst(1) .With(h => h.SeriesId = _series.Id) @@ -89,6 +93,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers var history = Builder.CreateListOfSize(2) .All() + .With(h => h.Quality = new QualityModel()) .With(h => h.SeriesId = _series.Id) .TheFirst(1) .With(h => h.EpisodeId = _episode.Id) diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs index 4ea0046c3..7e4dc060f 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs @@ -5,6 +5,7 @@ using NzbDrone.Core.Housekeeping.Housekeepers; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Metadata; using NzbDrone.Core.Metadata.Files; +using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; @@ -68,6 +69,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers .BuildNew(); var episodeFile = Builder.CreateNew() + .With(h => h.Quality = new QualityModel()) .BuildNew(); Db.Insert(series); diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedPendingReleasesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedPendingReleasesFixture.cs index 34965efae..104ba9bfc 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedPendingReleasesFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedPendingReleasesFixture.cs @@ -3,6 +3,7 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Housekeeping.Housekeepers; +using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; @@ -15,7 +16,9 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers public void should_delete_orphaned_pending_items() { var pendingRelease = Builder.CreateNew() - .BuildNew(); + .With(h => h.ParsedEpisodeInfo = new ParsedEpisodeInfo()) + .With(h => h.Release = new ReleaseInfo()) + .BuildNew(); Db.Insert(pendingRelease); Subject.Clean(); @@ -30,8 +33,10 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers Db.Insert(series); var pendingRelease = Builder.CreateNew() - .With(h => h.SeriesId = series.Id) - .BuildNew(); + .With(h => h.SeriesId = series.Id) + .With(h => h.ParsedEpisodeInfo = new ParsedEpisodeInfo()) + .With(h => h.Release = new ReleaseInfo()) + .BuildNew(); Db.Insert(pendingRelease); diff --git a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs index 3cc90f748..ad3d85bbb 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs @@ -95,7 +95,7 @@ namespace NzbDrone.Core.Test.MediaFiles Subject.Execute(new DownloadedEpisodesScanCommand()); Mocker.GetMock() - .Verify(c => c.GetImportDecisions(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), + .Verify(c => c.GetImportDecisions(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); VerifyNoImport(); diff --git a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesRepositoryReadFixture.cs b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesRepositoryReadFixture.cs index 1a7f06151..07a43b9ca 100644 --- a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesRepositoryReadFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesRepositoryReadFixture.cs @@ -2,6 +2,7 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; @@ -25,7 +26,9 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests [Test] public void should_get_episodes_by_file() { - var episodeFile = Builder.CreateNew().BuildNew(); + var episodeFile = Builder.CreateNew() + .With(h => h.Quality = new QualityModel()) + .BuildNew(); Db.Insert(episodeFile); diff --git a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithFilesFixture.cs b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithFilesFixture.cs index 5dd3b03d2..3925850ee 100644 --- a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithFilesFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithFilesFixture.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; +using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests { @@ -21,8 +22,9 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests public void Setup() { _episodeFiles = Builder.CreateListOfSize(5) - .BuildListOfNew() - .ToList(); + .All() + .With(c => c.Quality = new QualityModel()) + .BuildListOfNew(); Db.InsertMany(_episodeFiles); @@ -56,6 +58,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests { var episodeFile = Builder.CreateNew() .With(f => f.Path = "another path") + .With(c => c.Quality = new QualityModel()) .BuildNew(); Db.Insert(episodeFile); diff --git a/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs b/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs index 806bbf5f4..222c53b2a 100644 --- a/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs @@ -55,6 +55,7 @@ namespace NzbDrone.Core.Datastore.Converters public object ToDB(object clrValue) { if (clrValue == null) return null; + if (clrValue == DBNull.Value) return DBNull.Value; return JsonConvert.SerializeObject(clrValue, SerializerSetting); } From 7b420fc03309fe2a87b7639c0b71ee80ab647393 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Thu, 10 Apr 2014 19:58:50 +0200 Subject: [PATCH 2/3] Added MediaInfo to EpisodeFile. --- .../UpdateMediaInfoServiceFixture.cs | 118 ++++++++++++++++++ .../NzbDrone.Core.Test.csproj | 1 + .../056_add_mediainfo_to_episodefile.cs | 15 +++ src/NzbDrone.Core/MediaFiles/EpisodeFile.cs | 2 + .../EpisodeImport/ImportApprovedEpisodes.cs | 1 + .../EpisodeImport/ImportDecisionMaker.cs | 7 +- .../MediaFiles/MediaFileRepository.cs | 6 + .../MediaFiles/MediaFileService.cs | 6 + .../MediaFiles/MediaInfo/MediaInfoModel.cs | 3 +- .../MediaInfo/UpdateMediaInfoService.cs | 63 ++++++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 2 + .../Organizer/FileNameBuilder.cs | 87 +++++++++++++ .../Parser/Model/LocalEpisode.cs | 2 + src/NzbDrone.Core/Parser/ParsingService.cs | 3 + 14 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/056_add_mediainfo_to_episodefile.cs create mode 100644 src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs new file mode 100644 index 000000000..4b29c0b46 --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs @@ -0,0 +1,118 @@ +using FizzWare.NBuilder; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Disk; +using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.MediaFiles.MediaInfo; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Test.MediaFiles.MediaInfo +{ + [TestFixture] + public class UpdateMediaInfoServiceFixture : CoreTest + { + private void GivenFileExists() + { + Mocker.GetMock() + .Setup(v => v.FileExists(It.IsAny())) + .Returns(true); + } + + private void GivenSuccessfulScan() + { + Mocker.GetMock() + .Setup(v => v.GetMediaInfo(It.IsAny())) + .Returns(new MediaInfoModel()); + } + + private void GivenFailedScan(String path) + { + Mocker.GetMock() + .Setup(v => v.GetMediaInfo(path)) + .Returns((MediaInfoModel)null); + } + + [Test] + public void should_get_for_existing_episodefile_on_after_series_scan() + { + var episodeFiles = Builder.CreateListOfSize(3) + .All() + .With(v => v.Path = @"C:\series\media.mkv".AsOsAgnostic()) + .TheFirst(1) + .With(v => v.MediaInfo = new MediaInfoModel()) + .BuildList(); + + Mocker.GetMock() + .Setup(v => v.GetFilesBySeries(1)) + .Returns(episodeFiles); + + GivenFileExists(); + GivenSuccessfulScan(); + + Subject.Handle(new SeriesScannedEvent(new Tv.Series { Id = 1 })); + + Mocker.GetMock() + .Verify(v => v.GetMediaInfo(@"C:\series\media.mkv".AsOsAgnostic()), Times.Exactly(2)); + + Mocker.GetMock() + .Verify(v => v.Update(It.IsAny()), Times.Exactly(2)); + } + + [Test] + public void should_ignore_missing_files() + { + var episodeFiles = Builder.CreateListOfSize(2) + .All() + .With(v => v.Path = @"C:\series\media.mkv".AsOsAgnostic()) + .BuildList(); + + Mocker.GetMock() + .Setup(v => v.GetFilesBySeries(1)) + .Returns(episodeFiles); + + GivenSuccessfulScan(); + + Subject.Handle(new SeriesScannedEvent(new Tv.Series { Id = 1 })); + + Mocker.GetMock() + .Verify(v => v.GetMediaInfo(@"C:\series\media.mkv".AsOsAgnostic()), Times.Never()); + + Mocker.GetMock() + .Verify(v => v.Update(It.IsAny()), Times.Never()); + } + + [Test] + public void should_continue_after_failure() + { + var episodeFiles = Builder.CreateListOfSize(2) + .All() + .With(v => v.Path = @"C:\series\media.mkv".AsOsAgnostic()) + .TheFirst(1) + .With(v => v.Path = @"C:\series\media2.mkv".AsOsAgnostic()) + .BuildList(); + + Mocker.GetMock() + .Setup(v => v.GetFilesBySeries(1)) + .Returns(episodeFiles); + + GivenFileExists(); + GivenSuccessfulScan(); + GivenFailedScan(@"C:\series\media2.mkv".AsOsAgnostic()); + + Subject.Handle(new SeriesScannedEvent(new Tv.Series { Id = 1 })); + + Mocker.GetMock() + .Verify(v => v.GetMediaInfo(@"C:\series\media.mkv".AsOsAgnostic()), Times.Exactly(1)); + + Mocker.GetMock() + .Verify(v => v.Update(It.IsAny()), Times.Exactly(1)); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 9d137cfde..bf8c6b567 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -190,6 +190,7 @@ + diff --git a/src/NzbDrone.Core/Datastore/Migration/056_add_mediainfo_to_episodefile.cs b/src/NzbDrone.Core/Datastore/Migration/056_add_mediainfo_to_episodefile.cs new file mode 100644 index 000000000..f8763332b --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/056_add_mediainfo_to_episodefile.cs @@ -0,0 +1,15 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; +using System.Data; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(56)] + public class add_mediainfo_to_episodefile : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("EpisodeFiles").AddColumn("MediaInfo").AsString().Nullable(); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs index 12dccb7b0..242ee02aa 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs @@ -2,6 +2,7 @@ using NzbDrone.Core.Datastore; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; +using NzbDrone.Core.MediaFiles.MediaInfo; namespace NzbDrone.Core.MediaFiles { @@ -15,6 +16,7 @@ namespace NzbDrone.Core.MediaFiles public string SceneName { get; set; } public string ReleaseGroup { get; set; } public QualityModel Quality { get; set; } + public MediaInfoModel MediaInfo { get; set; } public LazyList Episodes { get; set; } public override string ToString() diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index e0e6c7263..5494717e8 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -73,6 +73,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport episodeFile.Path = localEpisode.Path.CleanFilePath(); episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path); episodeFile.Quality = localEpisode.Quality; + episodeFile.MediaInfo = localEpisode.MediaInfo; episodeFile.SeasonNumber = localEpisode.SeasonNumber; episodeFile.Episodes = localEpisode.Episodes; episodeFile.ReleaseGroup = localEpisode.ParsedEpisodeInfo.ReleaseGroup; diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs index 3836c1ef0..93382b430 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs @@ -8,6 +8,7 @@ using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; +using NzbDrone.Core.MediaFiles.MediaInfo; namespace NzbDrone.Core.MediaFiles.EpisodeImport @@ -23,19 +24,21 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport private readonly IParsingService _parsingService; private readonly IMediaFileService _mediaFileService; private readonly IDiskProvider _diskProvider; + private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly Logger _logger; public ImportDecisionMaker(IEnumerable specifications, IParsingService parsingService, IMediaFileService mediaFileService, IDiskProvider diskProvider, - + IVideoFileInfoReader videoFileInfoReader, Logger logger) { _specifications = specifications; _parsingService = parsingService; _mediaFileService = mediaFileService; _diskProvider = diskProvider; + _videoFileInfoReader = videoFileInfoReader; _logger = logger; } @@ -69,6 +72,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport parsedEpisode.Size = _diskProvider.GetFileSize(file); _logger.Debug("Size: {0}", parsedEpisode.Size); + parsedEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file); + decision = GetDecision(parsedEpisode); } diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs index 0a7fe8a70..57f9976cb 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs @@ -9,6 +9,7 @@ namespace NzbDrone.Core.MediaFiles { List GetFilesBySeries(int seriesId); List GetFilesBySeason(int seriesId, int seasonNumber); + List GetFilesWithoutMediaInfo(); } @@ -30,5 +31,10 @@ namespace NzbDrone.Core.MediaFiles .AndWhere(c => c.SeasonNumber == seasonNumber) .ToList(); } + + public List GetFilesWithoutMediaInfo() + { + return Query.Where(c => c.MediaInfo == null).ToList(); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs index 3bc146fad..f45753929 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -15,6 +15,7 @@ namespace NzbDrone.Core.MediaFiles void Delete(EpisodeFile episodeFile, bool forUpgrade = false); List GetFilesBySeries(int seriesId); List GetFilesBySeason(int seriesId, int seasonNumber); + List GetFilesWithoutMediaInfo(); List FilterExistingFiles(List files, int seriesId); EpisodeFile Get(int id); List Get(IEnumerable ids); @@ -62,6 +63,11 @@ namespace NzbDrone.Core.MediaFiles return _mediaFileRepository.GetFilesBySeason(seriesId, seasonNumber); } + public List GetFilesWithoutMediaInfo() + { + return _mediaFileRepository.GetFilesWithoutMediaInfo(); + } + public List FilterExistingFiles(List files, int seriesId) { var seriesFiles = GetFilesBySeries(seriesId).Select(f => f.Path).ToList(); diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs index c18ec0870..964c62d4d 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs @@ -1,8 +1,9 @@ using System; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.MediaFiles.MediaInfo { - public class MediaInfoModel + public class MediaInfoModel : IEmbeddedDocument { public string VideoCodec { get; set; } public int VideoBitrate { get; set; } diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs new file mode 100644 index 000000000..cf3502dea --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs @@ -0,0 +1,63 @@ +using NLog; +using NzbDrone.Common.Disk; +using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Tv; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles.MediaInfo +{ + public class UpdateMediaInfoService : IHandle + { + private readonly IDiskProvider _diskProvider; + private readonly IMediaFileService _mediaFileService; + private readonly IVideoFileInfoReader _videoFileInfoReader; + private readonly Logger _logger; + + public UpdateMediaInfoService(IDiskProvider diskProvider, + IMediaFileService mediaFileService, + IVideoFileInfoReader videoFileInfoReader, + Logger logger) + { + _diskProvider = diskProvider; + _mediaFileService = mediaFileService; + _videoFileInfoReader = videoFileInfoReader; + _logger = logger; + } + + private void UpdateMediaInfo(List mediaFiles) + { + foreach (var mediaFile in mediaFiles) + { + var path = mediaFile.Path; + + if (!_diskProvider.FileExists(path)) + { + _logger.Debug("Can't update MediaInfo because '{0}' does not exist", path); + continue; + } + + mediaFile.MediaInfo = _videoFileInfoReader.GetMediaInfo(path); + + if (mediaFile.MediaInfo != null) + { + _mediaFileService.Update(mediaFile); + _logger.Debug("Updated MediaInfo for '{0}'", path); + } + } + } + + public void Handle(SeriesScannedEvent message) + { + var mediaFiles = _mediaFileService.GetFilesBySeries(message.Series.Id) + .Where(c => c.MediaInfo == null) + .ToList(); + + UpdateMediaInfo(mediaFiles); + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 6ea6013bd..3873058a0 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -212,6 +212,7 @@ + @@ -482,6 +483,7 @@ + diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 7a65d7dfb..7b9920f1a 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -216,6 +216,8 @@ namespace NzbDrone.Core.Organizer 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() ); @@ -333,6 +335,91 @@ namespace NzbDrone.Core.Organizer return result.Trim(); } + private void AddMediaInfoTokens(EpisodeFile episodeFile, Dictionary tokenValues) + { + 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); + } + + 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 tokenValues) { return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenValues)); diff --git a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs index 447055328..eefe76e51 100644 --- a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs +++ b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Collections.Generic; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; +using NzbDrone.Core.MediaFiles.MediaInfo; namespace NzbDrone.Core.Parser.Model { @@ -14,6 +15,7 @@ namespace NzbDrone.Core.Parser.Model public Series Series { get; set; } public List Episodes { get; set; } public QualityModel Quality { get; set; } + public MediaInfoModel MediaInfo { get; set; } public Boolean ExistingFile { get; set; } public int SeasonNumber diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index 61ede3492..d39a53064 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -27,16 +27,19 @@ namespace NzbDrone.Core.Parser private readonly IEpisodeService _episodeService; private readonly ISeriesService _seriesService; private readonly ISceneMappingService _sceneMappingService; + private readonly IDiskProvider _diskProvider; private readonly Logger _logger; public ParsingService(IEpisodeService episodeService, ISeriesService seriesService, ISceneMappingService sceneMappingService, + IDiskProvider diskProvider, Logger logger) { _episodeService = episodeService; _seriesService = seriesService; _sceneMappingService = sceneMappingService; + _diskProvider = diskProvider; _logger = logger; } From 8a5edfeaf50ea59d0c7902cf333e538a2405855a Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Mon, 28 Jul 2014 23:14:34 +0200 Subject: [PATCH 3/3] Rewrote most of the renamer token handling code to give it a bit more generic architecture. Also added MediaInfo as possible token. --- src/NzbDrone.Api/Config/NamingConfigModule.cs | 8 +- .../MoveEpisodeFileFixture.cs | 2 +- .../MediaFiles/MediaFileServiceTest.cs | 2 +- .../OrganizerTests/FileNameBuilderFixture.cs | 144 ++++-- .../OrganizerTests/GetSeriesFolderFixture.cs | 4 +- .../SeriesServiceTests/AddSeriesFixture.cs | 2 +- .../Download/Clients/Pneumatic/Pneumatic.cs | 2 +- .../UsenetBlackhole/UsenetBlackhole.cs | 6 +- .../MediaFiles/EpisodeFileMovingService.cs | 6 +- .../MediaFiles/RenameEpisodeFileService.cs | 2 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 6 +- .../Organizer/FileNameBuilder.cs | 427 +++++++++++------- .../FilenameBuilderTokenEqualityComparer.cs | 6 +- .../Organizer/FilenameSampleService.cs | 59 ++- .../Organizer/FilenameValidationService.cs | 8 +- src/NzbDrone.Core/Organizer/SampleResult.cs | 2 +- src/NzbDrone.Core/Tv/SeriesService.cs | 2 +- .../Naming/NamingViewTemplate.html | 3 + .../Partials/MediaInfoNamingPartial.html | 11 + .../Partials/SeriesTitleNamingPartial.html | 3 + 20 files changed, 446 insertions(+), 259 deletions(-) create mode 100644 src/UI/Settings/MediaManagement/Naming/Partials/MediaInfoNamingPartial.html 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