Merge pull request #94 from Taloth/mediainfo-support

Mediainfo support
pull/96/head
Mark McDowall 10 years ago
commit 8902dde343

@ -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"

@ -36,6 +36,7 @@ namespace NzbDrone.Core.Test.Datastore
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(1)
.All()
.With(v => v.SeriesId = series[0].Id)
.With(v => v.Quality = new QualityModel())
.BuildListOfNew();
Db.InsertMany(episodeFiles);

@ -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<History.History>.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<History.History>.CreateNew().BuildNew();
var history = Builder<History.History>.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<History.History>
.CreateListOfSize(5)
.All()
.With(c => c.Quality = new QualityModel())
.With(c => c.EventType = HistoryEventType.Unknown)
.Random(3)
.With(c => c.EventType = HistoryEventType.Grabbed)

@ -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<Blacklist>.CreateNew()
.With(h => h.EpisodeIds = new List<Int32>())
.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<Blacklist>.CreateNew()
.With(b => b.SeriesId = series.Id)
.BuildNew();
.With(h => h.EpisodeIds = new List<Int32>())
.With(h => h.Quality = new QualityModel())
.With(b => b.SeriesId = series.Id)
.BuildNew();
Db.Insert(blacklist);

@ -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<EpisodeFile>.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<EpisodeFile>.CreateListOfSize(2)
.All()
.With(h => h.Quality = new QualityModel())
.BuildListOfNew();
Db.InsertMany(episodeFiles);

@ -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<History.History>.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<History.History>.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<History.History>.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<History.History>.CreateListOfSize(2)
.All()
.With(h => h.Quality = new QualityModel())
.With(h => h.SeriesId = _series.Id)
.TheFirst(1)
.With(h => h.EpisodeId = _episode.Id)

@ -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<EpisodeFile>.CreateNew()
.With(h => h.Quality = new QualityModel())
.BuildNew();
Db.Insert(series);

@ -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<PendingRelease>.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<PendingRelease>.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);

@ -95,7 +95,7 @@ namespace NzbDrone.Core.Test.MediaFiles
Subject.Execute(new DownloadedEpisodesScanCommand());
Mocker.GetMock<IMakeImportDecision>()
.Verify(c => c.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Series>(), It.IsAny<bool>(), It.IsAny<Core.Qualities.QualityModel>()),
.Verify(c => c.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Series>(), It.IsAny<bool>(), It.IsAny<QualityModel>()),
Times.Never());
VerifyNoImport();

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

@ -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]

@ -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<UpdateMediaInfoService>
{
private void GivenFileExists()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(It.IsAny<String>()))
.Returns(true);
}
private void GivenSuccessfulScan()
{
Mocker.GetMock<IVideoFileInfoReader>()
.Setup(v => v.GetMediaInfo(It.IsAny<String>()))
.Returns(new MediaInfoModel());
}
private void GivenFailedScan(String path)
{
Mocker.GetMock<IVideoFileInfoReader>()
.Setup(v => v.GetMediaInfo(path))
.Returns((MediaInfoModel)null);
}
[Test]
public void should_get_for_existing_episodefile_on_after_series_scan()
{
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(3)
.All()
.With(v => v.Path = @"C:\series\media.mkv".AsOsAgnostic())
.TheFirst(1)
.With(v => v.MediaInfo = new MediaInfoModel())
.BuildList();
Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesBySeries(1))
.Returns(episodeFiles);
GivenFileExists();
GivenSuccessfulScan();
Subject.Handle(new SeriesScannedEvent(new Tv.Series { Id = 1 }));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(@"C:\series\media.mkv".AsOsAgnostic()), Times.Exactly(2));
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Exactly(2));
}
[Test]
public void should_ignore_missing_files()
{
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(2)
.All()
.With(v => v.Path = @"C:\series\media.mkv".AsOsAgnostic())
.BuildList();
Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesBySeries(1))
.Returns(episodeFiles);
GivenSuccessfulScan();
Subject.Handle(new SeriesScannedEvent(new Tv.Series { Id = 1 }));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(@"C:\series\media.mkv".AsOsAgnostic()), Times.Never());
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Never());
}
[Test]
public void should_continue_after_failure()
{
var episodeFiles = Builder<EpisodeFile>.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<IMediaFileService>()
.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<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(@"C:\series\media.mkv".AsOsAgnostic()), Times.Exactly(1));
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<EpisodeFile>()), Times.Exactly(1));
}
}
}

@ -190,6 +190,7 @@
<Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" />
<Compile Include="MediaFiles\MediaFileServiceTest.cs" />
<Compile Include="MediaFiles\MediaFileTableCleanupServiceFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\UpdateMediaInfoServiceFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\VideoFileInfoReaderFixture.cs" />
<Compile Include="MediaFiles\RenameEpisodeFileServiceFixture.cs" />
<Compile Include="MediaFiles\UpgradeMediaFileServiceFixture.cs" />

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

@ -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);
}
}
}

@ -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<EpisodeFile>.CreateNew().BuildNew();
var episodeFile = Builder<EpisodeFile>.CreateNew()
.With(h => h.Quality = new QualityModel())
.BuildNew();
Db.Insert(episodeFile);

@ -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<EpisodeFile>.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<EpisodeFile>.CreateNew()
.With(f => f.Path = "another path")
.With(c => c.Quality = new QualityModel())
.BuildNew();
Db.Insert(episodeFile);

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

@ -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);
}

@ -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();
}
}
}

@ -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");

@ -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
{

@ -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<Episode> Episodes { get; set; }
public override string ToString()

@ -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);

@ -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;

@ -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<IRejectWithReason> 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);
}

@ -9,6 +9,7 @@ namespace NzbDrone.Core.MediaFiles
{
List<EpisodeFile> GetFilesBySeries(int seriesId);
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
List<EpisodeFile> GetFilesWithoutMediaInfo();
}
@ -30,5 +31,10 @@ namespace NzbDrone.Core.MediaFiles
.AndWhere(c => c.SeasonNumber == seasonNumber)
.ToList();
}
public List<EpisodeFile> GetFilesWithoutMediaInfo()
{
return Query.Where(c => c.MediaInfo == null).ToList();
}
}
}

@ -15,6 +15,7 @@ namespace NzbDrone.Core.MediaFiles
void Delete(EpisodeFile episodeFile, bool forUpgrade = false);
List<EpisodeFile> GetFilesBySeries(int seriesId);
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
List<EpisodeFile> GetFilesWithoutMediaInfo();
List<string> FilterExistingFiles(List<string> files, int seriesId);
EpisodeFile Get(int id);
List<EpisodeFile> Get(IEnumerable<int> ids);
@ -62,6 +63,11 @@ namespace NzbDrone.Core.MediaFiles
return _mediaFileRepository.GetFilesBySeason(seriesId, seasonNumber);
}
public List<EpisodeFile> GetFilesWithoutMediaInfo()
{
return _mediaFileRepository.GetFilesWithoutMediaInfo();
}
public List<string> FilterExistingFiles(List<string> files, int seriesId)
{
var seriesFiles = GetFilesBySeries(seriesId).Select(f => f.Path).ToList();

@ -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; }

@ -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<SeriesScannedEvent>
{
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<EpisodeFile> 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);
}
}
}

@ -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))

@ -212,6 +212,7 @@
<Compile Include="Datastore\Migration\053_add_series_sorttitle.cs" />
<Compile Include="Datastore\Migration\054_rename_profiles.cs" />
<Compile Include="Datastore\Migration\055_drop_old_profile_columns.cs" />
<Compile Include="Datastore\Migration\056_add_mediainfo_to_episodefile.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
@ -482,6 +483,7 @@
</Compile>
<Compile Include="MediaFiles\MediaFileTableCleanupService.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoModel.cs" />
<Compile Include="MediaFiles\MediaInfo\UpdateMediaInfoService.cs" />
<Compile Include="MediaFiles\MediaInfo\VideoFileInfoReader.cs" />
<Compile Include="MediaFiles\RecycleBinProvider.cs" />
<Compile Include="MediaFiles\RenameEpisodeFilePreview.cs" />
@ -632,10 +634,10 @@
<Compile Include="Organizer\EpisodeSortingType.cs" />
<Compile Include="Organizer\Exception.cs" />
<Compile Include="Organizer\FileNameBuilder.cs" />
<Compile Include="Organizer\FilenameBuilderTokenEqualityComparer.cs" />
<Compile Include="Organizer\FilenameSampleService.cs" />
<Compile Include="Organizer\FileNameBuilderTokenEqualityComparer.cs" />
<Compile Include="Organizer\FileNameSampleService.cs" />
<Compile Include="Organizer\FileNameValidation.cs" />
<Compile Include="Organizer\FilenameValidationService.cs" />
<Compile Include="Organizer\FileNameValidationService.cs" />
<Compile Include="Organizer\NamingConfig.cs" />
<Compile Include="Organizer\NamingConfigService.cs" />
<Compile Include="Organizer\SampleResult.cs" />

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

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

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

@ -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)
{

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

@ -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<Episode> Episodes { get; set; }
public QualityModel Quality { get; set; }
public MediaInfoModel MediaInfo { get; set; }
public Boolean ExistingFile { get; set; }
public int SeasonNumber

@ -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;
}

@ -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);
}

@ -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}}

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

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

Loading…
Cancel
Save