Refactor and Enable Renaming for Album and Artist Files (#61)

Refactor and Enable Renaming for Album and Artist Files
pull/66/head
Qstick 7 years ago committed by GitHub
parent 1c3cfad23f
commit 8569084255

@ -34,12 +34,7 @@ namespace NzbDrone.Api.Config
Get["/samples"] = x => GetExamples(this.Bind<NamingConfigResource>());
SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 5);
SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat();
SharedValidator.RuleFor(c => c.StandardTrackFormat).ValidTrackFormat();
SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat();
SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat();
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
SharedValidator.RuleFor(c => c.ArtistFolderFormat).ValidArtistFolderFormat();
SharedValidator.RuleFor(c => c.AlbumFolderFormat).ValidAlbumFolderFormat();
}
@ -57,7 +52,7 @@ namespace NzbDrone.Api.Config
var nameSpec = _namingConfigService.GetConfig();
var resource = nameSpec.ToResource();
if (resource.StandardEpisodeFormat.IsNotNullOrWhiteSpace())
if (resource.StandardTrackFormat.IsNotNullOrWhiteSpace())
{
var basicConfig = _filenameBuilder.GetBasicNamingConfig(nameSpec);
basicConfig.AddToResource(resource);
@ -76,45 +71,13 @@ namespace NzbDrone.Api.Config
var nameSpec = config.ToModel();
var sampleResource = new NamingSampleResource();
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec);
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec);
sampleResource.SingleEpisodeExample = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult) != null
? "Invalid format"
: singleEpisodeSampleResult.FileName;
var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec);
sampleResource.SingleTrackExample = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult) != null
? "Invalid format"
: singleTrackSampleResult.FileName;
sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null
? "Invalid format"
: multiEpisodeSampleResult.FileName;
sampleResource.DailyEpisodeExample = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult) != null
? "Invalid format"
: dailyEpisodeSampleResult.FileName;
sampleResource.AnimeEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult) != null
? "Invalid format"
: animeEpisodeSampleResult.FileName;
sampleResource.AnimeMultiEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeMultiEpisodeSampleResult) != null
? "Invalid format"
: animeMultiEpisodeSampleResult.FileName;
sampleResource.SeriesFolderExample = nameSpec.SeriesFolderFormat.IsNullOrWhiteSpace()
? "Invalid format"
: _filenameSampleService.GetSeriesFolderSample(nameSpec);
sampleResource.SeasonFolderExample = nameSpec.SeasonFolderFormat.IsNullOrWhiteSpace()
? "Invalid format"
: _filenameSampleService.GetSeasonFolderSample(nameSpec);
sampleResource.ArtistFolderExample = nameSpec.ArtistFolderFormat.IsNullOrWhiteSpace()
? "Invalid format"
: _filenameSampleService.GetArtistFolderSample(nameSpec);
@ -128,28 +91,15 @@ namespace NzbDrone.Api.Config
private void ValidateFormatResult(NamingConfig nameSpec)
{
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec);
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec);
var singleEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult);
var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec);
var singleTrackValidationResult = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult);
var multiEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult);
var dailyEpisodeValidationResult = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult);
var animeEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult);
var animeMultiEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeMultiEpisodeSampleResult);
var validationFailures = new List<ValidationFailure>();
validationFailures.AddIfNotNull(singleEpisodeValidationResult);
validationFailures.AddIfNotNull(singleTrackValidationResult);
validationFailures.AddIfNotNull(multiEpisodeValidationResult);
validationFailures.AddIfNotNull(dailyEpisodeValidationResult);
validationFailures.AddIfNotNull(animeEpisodeValidationResult);
validationFailures.AddIfNotNull(animeMultiEpisodeValidationResult);
if (validationFailures.Any())
{

@ -1,24 +1,18 @@
using NzbDrone.Api.REST;
using NzbDrone.Api.REST;
using NzbDrone.Core.Organizer;
namespace NzbDrone.Api.Config
{
public class NamingConfigResource : RestResource
{
public bool RenameEpisodes { get; set; }
public bool RenameTracks { get; set; }
public bool ReplaceIllegalCharacters { get; set; }
public int MultiEpisodeStyle { get; set; }
public string StandardEpisodeFormat { get; set; }
public string StandardTrackFormat { get; set; }
public string DailyEpisodeFormat { get; set; }
public string AnimeEpisodeFormat { get; set; }
public string SeriesFolderFormat { get; set; }
public string SeasonFolderFormat { get; set; }
public string ArtistFolderFormat { get; set; }
public string AlbumFolderFormat { get; set; }
public bool IncludeSeriesTitle { get; set; }
public bool IncludeEpisodeTitle { get; set; }
public bool IncludeArtistName { get; set; }
public bool IncludeAlbumTitle { get; set; }
public bool IncludeQuality { get; set; }
public bool ReplaceSpaces { get; set; }
public string Separator { get; set; }
@ -33,16 +27,9 @@ namespace NzbDrone.Api.Config
{
Id = model.Id,
RenameEpisodes = model.RenameEpisodes,
RenameTracks = model.RenameTracks,
ReplaceIllegalCharacters = model.ReplaceIllegalCharacters,
MultiEpisodeStyle = model.MultiEpisodeStyle,
StandardEpisodeFormat = model.StandardEpisodeFormat,
StandardTrackFormat = model.StandardTrackFormat,
DailyEpisodeFormat = model.DailyEpisodeFormat,
AnimeEpisodeFormat = model.AnimeEpisodeFormat,
SeriesFolderFormat = model.SeriesFolderFormat,
SeasonFolderFormat = model.SeasonFolderFormat,
ArtistFolderFormat = model.ArtistFolderFormat,
AlbumFolderFormat = model.AlbumFolderFormat
//IncludeSeriesTitle
@ -56,8 +43,8 @@ namespace NzbDrone.Api.Config
public static void AddToResource(this BasicNamingConfig basicNamingConfig, NamingConfigResource resource)
{
resource.IncludeSeriesTitle = basicNamingConfig.IncludeSeriesTitle;
resource.IncludeEpisodeTitle = basicNamingConfig.IncludeEpisodeTitle;
resource.IncludeArtistName = basicNamingConfig.IncludeArtistName;
resource.IncludeAlbumTitle = basicNamingConfig.IncludeAlbumTitle;
resource.IncludeQuality = basicNamingConfig.IncludeQuality;
resource.ReplaceSpaces = basicNamingConfig.ReplaceSpaces;
resource.Separator = basicNamingConfig.Separator;
@ -70,19 +57,12 @@ namespace NzbDrone.Api.Config
{
Id = resource.Id,
RenameEpisodes = resource.RenameEpisodes,
RenameTracks = resource.RenameTracks,
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
MultiEpisodeStyle = resource.MultiEpisodeStyle,
StandardEpisodeFormat = resource.StandardEpisodeFormat,
StandardTrackFormat = resource.StandardTrackFormat,
DailyEpisodeFormat = resource.DailyEpisodeFormat,
AnimeEpisodeFormat = resource.AnimeEpisodeFormat,
SeriesFolderFormat = resource.SeriesFolderFormat,
SeasonFolderFormat = resource.SeasonFolderFormat,
ArtistFolderFormat = resource.ArtistFolderFormat,
AlbumFolderFormat = resource.AlbumFolderFormat
};
}
}
}
}

@ -1,37 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Api.REST;
using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Api.Episodes
{
public class RenameEpisodeModule : NzbDroneRestModule<RenameEpisodeResource>
{
private readonly IRenameEpisodeFileService _renameEpisodeFileService;
public RenameEpisodeModule(IRenameEpisodeFileService renameEpisodeFileService)
: base("rename")
{
_renameEpisodeFileService = renameEpisodeFileService;
GetResourceAll = GetEpisodes;
}
private List<RenameEpisodeResource> GetEpisodes()
{
if (!Request.Query.SeriesId.HasValue)
{
throw new BadRequestException("seriesId is missing");
}
var seriesId = (int)Request.Query.SeriesId;
if (Request.Query.SeasonNumber.HasValue)
{
var seasonNumber = (int)Request.Query.SeasonNumber;
return _renameEpisodeFileService.GetRenamePreviews(seriesId, seasonNumber).ToResource();
}
return _renameEpisodeFileService.GetRenamePreviews(seriesId).ToResource();
}
}
}

@ -1,39 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Episodes
{
public class RenameEpisodeResource : RestResource
{
public int SeriesId { get; set; }
public int SeasonNumber { get; set; }
public List<int> EpisodeNumbers { get; set; }
public int EpisodeFileId { get; set; }
public string ExistingPath { get; set; }
public string NewPath { get; set; }
}
public static class RenameEpisodeResourceMapper
{
public static RenameEpisodeResource ToResource(this Core.MediaFiles.RenameEpisodeFilePreview model)
{
if (model == null) return null;
return new RenameEpisodeResource
{
SeriesId = model.SeriesId,
SeasonNumber = model.SeasonNumber,
EpisodeNumbers = model.EpisodeNumbers.ToList(),
EpisodeFileId = model.EpisodeFileId,
ExistingPath = model.ExistingPath,
NewPath = model.NewPath
};
}
public static List<RenameEpisodeResource> ToResource(this IEnumerable<Core.MediaFiles.RenameEpisodeFilePreview> models)
{
return models.Select(ToResource).ToList();
}
}
}

@ -164,8 +164,6 @@
<Compile Include="Episodes\EpisodeModule.cs" />
<Compile Include="Episodes\EpisodeModuleWithSignalR.cs" />
<Compile Include="Episodes\EpisodeResource.cs" />
<Compile Include="Episodes\RenameEpisodeModule.cs" />
<Compile Include="Episodes\RenameEpisodeResource.cs" />
<Compile Include="ErrorManagement\ApiException.cs" />
<Compile Include="ErrorManagement\ErrorHandler.cs" />
<Compile Include="ErrorManagement\ErrorModel.cs" />

@ -1,4 +1,4 @@
using System;
using System;
using System.Globalization;
using System.Linq;
using System.Text;
@ -78,6 +78,16 @@ namespace NzbDrone.Common.Extensions
return !string.IsNullOrWhiteSpace(text);
}
public static bool StartsWithIgnoreCase(this string text, string startsWith)
{
return text.StartsWith(startsWith, StringComparison.InvariantCultureIgnoreCase);
}
public static bool EqualsIgnoreCase(this string text, string equals)
{
return text.Equals(equals, StringComparison.InvariantCultureIgnoreCase);
}
public static bool ContainsIgnoreCase(this string text, string contains)
{
return text.IndexOf(contains, StringComparison.InvariantCultureIgnoreCase) > -1;

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLog;
using NLog.Fluent;
namespace NzbDrone.Common.Instrumentation.Extensions
{
public static class SentryLoggerExtensions
{
public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry");
public static LogBuilder SentryFingerprint(this LogBuilder logBuilder, params string[] fingerprint)
{
return logBuilder.Property("Sentry", fingerprint);
}
public static LogBuilder WriteSentryDebug(this LogBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint);
}
public static LogBuilder WriteSentryInfo(this LogBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint);
}
public static LogBuilder WriteSentryWarn(this LogBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint);
}
public static LogBuilder WriteSentryError(this LogBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint);
}
private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint)
{
SentryLogger.Log(level)
.CopyLogEvent(logBuilder.LogEventInfo)
.SentryFingerprint(fingerprint)
.Write();
return logBuilder.Property("Sentry", null);
}
private static LogBuilder CopyLogEvent(this LogBuilder logBuilder, LogEventInfo logEvent)
{
return logBuilder.LoggerName(logEvent.LoggerName)
.TimeStamp(logEvent.TimeStamp)
.Message(logEvent.Message, logEvent.Parameters)
.Properties((Dictionary<object, object>)logEvent.Properties)
.Exception(logEvent.Exception);
}
}
}

@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
@ -89,32 +89,38 @@ namespace NzbDrone.Common.Instrumentation
private static void RegisterSentry(bool updateClient)
{
// string dsn;
// if (updateClient)
// {
// dsn = RuntimeInfo.IsProduction
// ? "https://b85aa82c65b84b0e99e3b7c281438357:392b5bc007974147a922c5d841c47cf9@sentry.lidarr.audio/11"
// : "https://6168f0946aba4e60ac23e469ac08eac5:bd59e8454ccc454ea27a90cff1f814ca@sentry.lidarr.audio/9";
// }
// else
// {
// dsn = RuntimeInfo.IsProduction
// ? "https://3e8a38b1a4df4de8b0453a724f5a1139:5a708dd75c724b32ae5128b6a895650f@sentry.lidarr.audio/8"
// : "https://4ee3580e01d8407c96a7430fbc953512:5f2d07227a0b4fde99dea07041a3ff93@sentry.lidarr.audio/10";
// }
// var target = new SentryTarget(dsn)
// {
// Name = "sentryTarget",
// Layout = "${message}"
// };
// var loggingRule = new LoggingRule("*", updateClient ? LogLevel.Trace : LogLevel.Error, target);
// LogManager.Configuration.AddTarget("sentryTarget", target);
// LogManager.Configuration.LoggingRules.Add(loggingRule);
}
// TODO Enable above when we recieve sentry service account.
string dsn;
if (updateClient)
{
dsn = RuntimeInfo.IsProduction
? "https://b85aa82c65b84b0e99e3b7c281438357:392b5bc007974147a922c5d841c47cf9@sentry.lidarr.audio/11"
: "https://6168f0946aba4e60ac23e469ac08eac5:bd59e8454ccc454ea27a90cff1f814ca@sentry.lidarr.audio/9";
}
else
{
dsn = RuntimeInfo.IsProduction
? "https://3e8a38b1a4df4de8b0453a724f5a1139:5a708dd75c724b32ae5128b6a895650f@sentry.lidarr.audio/8"
: "https://4ee3580e01d8407c96a7430fbc953512:5f2d07227a0b4fde99dea07041a3ff93@sentry.lidarr.audio/10";
}
var target = new SentryTarget(dsn)
{
Name = "sentryTarget",
Layout = "${message}"
};
var loggingRule = new LoggingRule("*", updateClient ? LogLevel.Trace : LogLevel.Warn, target);
LogManager.Configuration.AddTarget("sentryTarget", target);
LogManager.Configuration.LoggingRules.Add(loggingRule);
// Events logged to Sentry go only to Sentry.
var loggingRuleSentry = new LoggingRule("Sentry", LogLevel.Debug, target) { Final = true };
LogManager.Configuration.LoggingRules.Insert(0, loggingRuleSentry);
}
private static void RegisterDebugger()
{

@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
@ -71,6 +72,11 @@ namespace NzbDrone.Common.Instrumentation.Sentry
private static List<string> GetFingerPrint(LogEventInfo logEvent)
{
if (logEvent.Properties.ContainsKey("Sentry"))
{
return ((string[])logEvent.Properties["Sentry"]).ToList();
}
var fingerPrint = new List<string>
{
logEvent.Level.Ordinal.ToString(),
@ -94,13 +100,33 @@ namespace NzbDrone.Common.Instrumentation.Sentry
return fingerPrint;
}
private bool IsSentryMessage(LogEventInfo logEvent)
{
if (logEvent.Properties.ContainsKey("Sentry"))
{
return logEvent.Properties["Sentry"] != null;
}
if (logEvent.Level >= LogLevel.Error && logEvent.Exception != null)
{
return true;
}
return false;
}
protected override void Write(LogEventInfo logEvent)
{
if (_unauthorized)
{
return;
}
try
{
// don't report non-critical events without exceptions
if (logEvent.Exception == null || _unauthorized)
if (!IsSentryMessage(logEvent))
{
return;
}
@ -112,8 +138,16 @@ namespace NzbDrone.Common.Instrumentation.Sentry
}
var extras = logEvent.Properties.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString());
extras.Remove("Sentry");
_client.Logger = logEvent.LoggerName;
if (logEvent.Exception != null)
{
foreach (DictionaryEntry data in logEvent.Exception.Data)
{
extras.Add(data.Key.ToString(), data.Value.ToString());
}
}
var sentryMessage = new SentryMessage(logEvent.Message, logEvent.Parameters);
@ -135,11 +169,16 @@ namespace NzbDrone.Common.Instrumentation.Sentry
sentryEvent.Fingerprint.Add(logEvent.Exception.GetType().FullName);
}
if (logEvent.Properties.ContainsKey("Sentry"))
{
sentryEvent.Fingerprint.Clear();
Array.ForEach((string[])logEvent.Properties["Sentry"], sentryEvent.Fingerprint.Add);
}
var osName = Environment.GetEnvironmentVariable("OS_NAME");
var osVersion = Environment.GetEnvironmentVariable("OS_VERSION");
var runTimeVersion = Environment.GetEnvironmentVariable("RUNTIME_VERSION");
sentryEvent.Tags.Add("os_name", osName);
sentryEvent.Tags.Add("os_version", $"{osName} {osVersion}");
sentryEvent.Tags.Add("runtime_version", $"{PlatformInfo.PlatformName} {runTimeVersion}");
@ -152,4 +191,4 @@ namespace NzbDrone.Common.Instrumentation.Sentry
}
}
}
}
}

@ -175,7 +175,8 @@
<Compile Include="Extensions\IEnumerableExtensions.cs" />
<Compile Include="Http\UserAgentBuilder.cs" />
<Compile Include="Instrumentation\CleanseLogMessage.cs" />
<Compile Include="Instrumentation\Extensions\LoggerProgressExtensions.cs" />
<Compile Include="Instrumentation\Extensions\SentryLoggerExtensions.cs" />
<Compile Include="Instrumentation\Extensions\LoggerExtensions.cs" />
<Compile Include="Instrumentation\GlobalExceptionHandlers.cs" />
<Compile Include="Instrumentation\LogEventExtensions.cs" />
<Compile Include="Instrumentation\NzbDroneFileTarget.cs" />

@ -1,124 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests
{
[TestFixture]
public class MoveEpisodeFileFixture : CoreTest<EpisodeFileMovingService>
{
private Series _series;
private EpisodeFile _episodeFile;
private LocalEpisode _localEpisode;
[SetUp]
public void Setup()
{
_series = Builder<Series>.CreateNew()
.With(s => s.Path = @"C:\Test\TV\Series".AsOsAgnostic())
.Build();
_episodeFile = Builder<EpisodeFile>.CreateNew()
.With(f => f.Path = null)
.With(f => f.RelativePath = @"Season 1\File.avi")
.Build();
_localEpisode = Builder<LocalEpisode>.CreateNew()
.With(l => l.Series = _series)
.With(l => l.Episodes = Builder<Episode>.CreateListOfSize(1).Build().ToList())
.Build();
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.BuildFileName(It.IsAny<List<Episode>>(), It.IsAny<Series>(), It.IsAny<EpisodeFile>(), null))
.Returns("File Name");
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.BuildFilePath(It.IsAny<Series>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(@"C:\Test\TV\Series\Season 01\File Name.avi".AsOsAgnostic());
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.BuildSeasonPath(It.IsAny<Series>(), It.IsAny<int>()))
.Returns(@"C:\Test\TV\Series\Season 01".AsOsAgnostic());
var rootFolder = @"C:\Test\TV\".AsOsAgnostic();
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(rootFolder))
.Returns(true);
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FileExists(It.IsAny<string>()))
.Returns(true);
}
[Test]
public void should_catch_UnauthorizedAccessException_during_folder_inheritance()
{
WindowsOnly();
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.InheritFolderPermissions(It.IsAny<string>()))
.Throws<UnauthorizedAccessException>();
Subject.MoveEpisodeFile(_episodeFile, _localEpisode);
}
[Test]
public void should_catch_InvalidOperationException_during_folder_inheritance()
{
WindowsOnly();
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.InheritFolderPermissions(It.IsAny<string>()))
.Throws<InvalidOperationException>();
Subject.MoveEpisodeFile(_episodeFile, _localEpisode);
}
[Test]
public void should_notify_on_series_folder_creation()
{
Subject.MoveEpisodeFile(_episodeFile, _localEpisode);
Mocker.GetMock<IEventAggregator>()
.Verify(s => s.PublishEvent<EpisodeFolderCreatedEvent>(It.Is<EpisodeFolderCreatedEvent>(p =>
p.SeriesFolder.IsNotNullOrWhiteSpace())), Times.Once());
}
[Test]
public void should_notify_on_season_folder_creation()
{
Subject.MoveEpisodeFile(_episodeFile, _localEpisode);
Mocker.GetMock<IEventAggregator>()
.Verify(s => s.PublishEvent<EpisodeFolderCreatedEvent>(It.Is<EpisodeFolderCreatedEvent>(p =>
p.SeasonFolder.IsNotNullOrWhiteSpace())), Times.Once());
}
[Test]
public void should_not_notify_if_series_folder_already_exists()
{
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(_series.Path))
.Returns(true);
Subject.MoveEpisodeFile(_episodeFile, _localEpisode);
Mocker.GetMock<IEventAggregator>()
.Verify(s => s.PublishEvent<EpisodeFolderCreatedEvent>(It.Is<EpisodeFolderCreatedEvent>(p =>
p.SeriesFolder.IsNotNullOrWhiteSpace())), Times.Never());
}
}
}

@ -1,120 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.MediaInfo;
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
{
[TestFixture]
public class FormattedAudioChannelsFixture
{
[Test]
public void should_subtract_one_from_AudioChannels_as_total_channels_if_LFE_in_AudioChannelPositionsText()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 6,
AudioChannelPositions = null,
AudioChannelPositionsText = "Front: L C R, Side: L R, LFE"
};
mediaInfoModel.FormattedAudioChannels.Should().Be(5.1m);
}
[Test]
public void should_use_AudioChannels_as_total_channels_if_LFE_not_in_AudioChannelPositionsText()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 2,
AudioChannelPositions = null,
AudioChannelPositionsText = "Front: L R"
};
mediaInfoModel.FormattedAudioChannels.Should().Be(2);
}
[Test]
public void should_return_0_if_schema_revision_is_less_than_3_and_other_properties_are_null()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 2,
AudioChannelPositions = null,
AudioChannelPositionsText = null,
SchemaRevision = 2
};
mediaInfoModel.FormattedAudioChannels.Should().Be(0);
}
[Test]
public void should_use_AudioChannels_if_schema_revision_is_3_and_other_properties_are_null()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 2,
AudioChannelPositions = null,
AudioChannelPositionsText = null,
SchemaRevision = 3
};
mediaInfoModel.FormattedAudioChannels.Should().Be(2);
}
[Test]
public void should_sum_AudioChannelPositions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 2,
AudioChannelPositions = "2/0/0",
AudioChannelPositionsText = null,
SchemaRevision = 3
};
mediaInfoModel.FormattedAudioChannels.Should().Be(2);
}
[Test]
public void should_sum_AudioChannelPositions_including_decimal()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 2,
AudioChannelPositions = "3/2/0.1",
AudioChannelPositionsText = null,
SchemaRevision = 3
};
mediaInfoModel.FormattedAudioChannels.Should().Be(5.1m);
}
[Test]
public void should_cleanup_extraneous_text_from_AudioChannelPositions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 2,
AudioChannelPositions = "Object Based / 3/2/2.1",
AudioChannelPositionsText = null,
SchemaRevision = 3
};
mediaInfoModel.FormattedAudioChannels.Should().Be(7.1m);
}
[Test]
public void should_sum_first_series_of_numbers_from_AudioChannelPositions()
{
var mediaInfoModel = new MediaInfoModel
{
AudioChannels = 2,
AudioChannelPositions = "3/2/2.1 / 3/2/2.1",
AudioChannelPositionsText = null,
SchemaRevision = 3
};
mediaInfoModel.FormattedAudioChannels.Should().Be(7.1m);
}
}
}

@ -0,0 +1,50 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
{
[TestFixture]
public class FormatAudioCodecFixture : TestBase
{
[TestCase("AC-3", "AC3")]
[TestCase("E-AC-3", "EAC3")]
[TestCase("MPEG Audio", "MPEG Audio")]
[TestCase("DTS", "DTS")]
public void should_format_audio_format(string audioFormat, string expectedFormat)
{
var mediaInfoModel = new MediaInfoModel
{
AudioFormat = audioFormat
};
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be(expectedFormat);
}
[Test]
public void should_return_MP3_for_MPEG_Audio_with_Layer_3_for_the_profile()
{
var mediaInfoModel = new MediaInfoModel
{
AudioFormat = "MPEG Audio",
AudioProfile = "Layer 3"
};
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be("MP3");
}
[Test]
public void should_return_AudioFormat_by_default()
{
var mediaInfoModel = new MediaInfoModel
{
AudioFormat = "Other Audio Format",
AudioCodecID = "Other Audio Codec"
};
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be(mediaInfoModel.AudioFormat);
ExceptionVerification.ExpectedWarns(1);
}
}
}

@ -7,7 +7,6 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
using NzbDrone.Test.Common;
using NzbDrone.Core.Configuration;
@ -23,10 +22,10 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
public void Setup()
{
_artist = new Artist
{
Id = 1,
Path = @"C:\artist".AsOsAgnostic()
};
{
Id = 1,
Path = @"C:\artist".AsOsAgnostic()
};
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableMediaInfo)
@ -59,9 +58,9 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
{
var trackFiles = Builder<TrackFile>.CreateListOfSize(3)
.All()
.With(v => v.RelativePath = "media.mkv")
.With(v => v.RelativePath = "media.flac")
.TheFirst(1)
.With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = 3 })
.With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = UpdateMediaInfoService.CURRENT_MEDIA_INFO_SCHEMA_REVISION })
.BuildList();
Mocker.GetMock<IMediaFileService>()
@ -74,7 +73,33 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
Subject.Handle(new ArtistScannedEvent(_artist));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.mkv")), Times.Exactly(2));
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(2));
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Exactly(2));
}
[Test]
public void should_skip_not_yet_date_media_info()
{
var trackFiles = Builder<TrackFile>.CreateListOfSize(3)
.All()
.With(v => v.RelativePath = "media.flac")
.TheFirst(1)
.With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = UpdateMediaInfoService.MINIMUM_MEDIA_INFO_SCHEMA_REVISION })
.BuildList();
Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesByArtist(1))
.Returns(trackFiles);
GivenFileExists();
GivenSuccessfulScan();
Subject.Handle(new ArtistScannedEvent(_artist));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(2));
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Exactly(2));
@ -85,7 +110,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
{
var trackFiles = Builder<TrackFile>.CreateListOfSize(3)
.All()
.With(v => v.RelativePath = "media.mkv")
.With(v => v.RelativePath = "media.flac")
.TheFirst(1)
.With(v => v.MediaInfo = new MediaInfoModel())
.BuildList();
@ -100,7 +125,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
Subject.Handle(new ArtistScannedEvent(_artist));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.mkv")), Times.Exactly(3));
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(3));
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Exactly(3));
@ -111,7 +136,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
{
var trackFiles = Builder<TrackFile>.CreateListOfSize(2)
.All()
.With(v => v.RelativePath = "media.mkv")
.With(v => v.RelativePath = "media.flac")
.BuildList();
Mocker.GetMock<IMediaFileService>()
@ -123,7 +148,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
Subject.Handle(new ArtistScannedEvent(_artist));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo("media.mkv"), Times.Never());
.Verify(v => v.GetMediaInfo("media.flac"), Times.Never());
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Never());
@ -132,28 +157,28 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
[Test]
public void should_continue_after_failure()
{
var trackFiles = Builder<TrackFile>.CreateListOfSize(2)
var episodeFiles = Builder<TrackFile>.CreateListOfSize(2)
.All()
.With(v => v.RelativePath = "media.mkv")
.With(v => v.RelativePath = "media.flac")
.TheFirst(1)
.With(v => v.RelativePath = "media2.mkv")
.With(v => v.RelativePath = "media2.flac")
.BuildList();
Mocker.GetMock<IMediaFileService>()
.Setup(v => v.GetFilesByArtist(1))
.Returns(trackFiles);
.Returns(episodeFiles);
GivenFileExists();
GivenSuccessfulScan();
GivenFailedScan(Path.Combine(_artist.Path, "media2.mkv"));
GivenFailedScan(Path.Combine(_artist.Path, "media2.flac"));
Subject.Handle(new ArtistScannedEvent(_artist));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.mkv")), Times.Exactly(1));
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(1));
Mocker.GetMock<IMediaFileService>()
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Exactly(1));
}
}
}
}

@ -8,12 +8,11 @@ using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.Test.MediaFiles
{
public class RenameEpisodeFileServiceFixture : CoreTest<RenameEpisodeFileService>
public class RenameTrackFileServiceFixture : CoreTest<RenameTrackFileService>
{
private Artist _artist;
private List<TrackFile> _trackFiles;
@ -35,14 +34,14 @@ namespace NzbDrone.Core.Test.MediaFiles
.Returns(_artist);
}
private void GivenNoEpisodeFiles()
private void GivenNoTrackFiles()
{
Mocker.GetMock<IMediaFileService>()
.Setup(s => s.Get(It.IsAny<IEnumerable<int>>()))
.Returns(new List<TrackFile>());
}
private void GivenEpisodeFiles()
private void GivenTrackFiles()
{
Mocker.GetMock<IMediaFileService>()
.Setup(s => s.Get(It.IsAny<IEnumerable<int>>()))
@ -58,18 +57,18 @@ namespace NzbDrone.Core.Test.MediaFiles
[Test]
public void should_not_publish_event_if_no_files_to_rename()
{
GivenNoEpisodeFiles();
GivenNoTrackFiles();
Subject.Execute(new RenameFilesCommand(_artist.Id, new List<int>{1}));
Mocker.GetMock<IEventAggregator>()
.Verify(v => v.PublishEvent(It.IsAny<SeriesRenamedEvent>()), Times.Never());
.Verify(v => v.PublishEvent(It.IsAny<ArtistRenamedEvent>()), Times.Never());
}
[Test]
public void should_not_publish_event_if_no_files_are_renamed()
{
GivenEpisodeFiles();
GivenTrackFiles();
Mocker.GetMock<IMoveTrackFiles>()
.Setup(s => s.MoveTrackFile(It.IsAny<TrackFile>(), It.IsAny<Artist>()))
@ -78,25 +77,25 @@ namespace NzbDrone.Core.Test.MediaFiles
Subject.Execute(new RenameFilesCommand(_artist.Id, new List<int> { 1 }));
Mocker.GetMock<IEventAggregator>()
.Verify(v => v.PublishEvent(It.IsAny<SeriesRenamedEvent>()), Times.Never());
.Verify(v => v.PublishEvent(It.IsAny<ArtistRenamedEvent>()), Times.Never());
}
[Test]
public void should_publish_event_if_files_are_renamed()
{
GivenEpisodeFiles();
GivenTrackFiles();
GivenMovedFiles();
Subject.Execute(new RenameFilesCommand(_artist.Id, new List<int> { 1 }));
Mocker.GetMock<IEventAggregator>()
.Verify(v => v.PublishEvent(It.IsAny<SeriesRenamedEvent>()), Times.Once());
.Verify(v => v.PublishEvent(It.IsAny<ArtistRenamedEvent>()), Times.Once());
}
[Test]
public void should_update_moved_files()
{
GivenEpisodeFiles();
GivenTrackFiles();
GivenMovedFiles();
Subject.Execute(new RenameFilesCommand(_artist.Id, new List<int> { 1 }));
@ -106,9 +105,9 @@ namespace NzbDrone.Core.Test.MediaFiles
}
[Test]
public void should_get_episodefiles_by_ids_only()
public void should_get_trackfiles_by_ids_only()
{
GivenEpisodeFiles();
GivenTrackFiles();
GivenMovedFiles();
var files = new List<int> { 1 };

@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Music;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.TrackFileMovingServiceTests
{
[TestFixture]
public class MoveTrackFileFixture : CoreTest<TrackFileMovingService>
{
private Artist _artist;
private TrackFile _trackFile;
private LocalTrack _localtrack;
[SetUp]
public void Setup()
{
_artist = Builder<Artist>.CreateNew()
.With(s => s.Path = @"C:\Test\Music\Artist".AsOsAgnostic())
.Build();
_trackFile = Builder<TrackFile>.CreateNew()
.With(f => f.Path = null)
.With(f => f.RelativePath = @"Album\File.mp3")
.Build();
_localtrack = Builder<LocalTrack>.CreateNew()
.With(l => l.Artist = _artist)
.With(l => l.Tracks = Builder<Track>.CreateListOfSize(1).Build().ToList())
.Build();
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.BuildTrackFileName(It.IsAny<List<Track>>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<TrackFile>(), null))
.Returns("File Name");
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.BuildTrackFilePath(It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(@"C:\Test\Music\Artist\Album\File Name.mp3".AsOsAgnostic());
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.BuildAlbumPath(It.IsAny<Artist>(), It.IsAny<Album>()))
.Returns(@"C:\Test\Music\Artist\Album".AsOsAgnostic());
var rootFolder = @"C:\Test\Music\".AsOsAgnostic();
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(rootFolder))
.Returns(true);
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FileExists(It.IsAny<string>()))
.Returns(true);
}
[Test]
public void should_catch_UnauthorizedAccessException_during_folder_inheritance()
{
WindowsOnly();
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.InheritFolderPermissions(It.IsAny<string>()))
.Throws<UnauthorizedAccessException>();
Subject.MoveTrackFile(_trackFile, _localtrack);
}
[Test]
public void should_catch_InvalidOperationException_during_folder_inheritance()
{
WindowsOnly();
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.InheritFolderPermissions(It.IsAny<string>()))
.Throws<InvalidOperationException>();
Subject.MoveTrackFile(_trackFile, _localtrack);
}
[Test]
public void should_notify_on_artist_folder_creation()
{
Subject.MoveTrackFile(_trackFile, _localtrack);
Mocker.GetMock<IEventAggregator>()
.Verify(s => s.PublishEvent<TrackFolderCreatedEvent>(It.Is<TrackFolderCreatedEvent>(p =>
p.ArtistFolder.IsNotNullOrWhiteSpace())), Times.Once());
}
[Test]
public void should_notify_on_album_folder_creation()
{
Subject.MoveTrackFile(_trackFile, _localtrack);
Mocker.GetMock<IEventAggregator>()
.Verify(s => s.PublishEvent<TrackFolderCreatedEvent>(It.Is<TrackFolderCreatedEvent>(p =>
p.AlbumFolder.IsNotNullOrWhiteSpace())), Times.Once());
}
[Test]
public void should_not_notify_if_artist_folder_already_exists()
{
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(_artist.Path))
.Returns(true);
Subject.MoveTrackFile(_trackFile, _localtrack);
Mocker.GetMock<IEventAggregator>()
.Verify(s => s.PublishEvent<TrackFolderCreatedEvent>(It.Is<TrackFolderCreatedEvent>(p =>
p.ArtistFolder.IsNotNullOrWhiteSpace())), Times.Never());
}
}
}

@ -5,35 +5,35 @@ using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Commands;
using NzbDrone.Core.Music;
using NzbDrone.Core.Music.Commands;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.TvTests
namespace NzbDrone.Core.Test.MusicTests
{
[TestFixture]
public class MoveSeriesServiceFixture : CoreTest<MoveSeriesService>
public class MoveArtistServiceFixture : CoreTest<MoveArtistService>
{
private Series _series;
private MoveSeriesCommand _command;
private Artist _artist;
private MoveArtistCommand _command;
[SetUp]
public void Setup()
{
_series = Builder<Series>
_artist = Builder<Artist>
.CreateNew()
.Build();
_command = new MoveSeriesCommand
{
SeriesId = 1,
SourcePath = @"C:\Test\TV\Series".AsOsAgnostic(),
DestinationPath = @"C:\Test\TV2\Series".AsOsAgnostic()
_command = new MoveArtistCommand
{
ArtistId = 1,
SourcePath = @"C:\Test\Music\Artist".AsOsAgnostic(),
DestinationPath = @"C:\Test\Music2\Artist".AsOsAgnostic()
};
Mocker.GetMock<ISeriesService>()
.Setup(s => s.GetSeries(It.IsAny<int>()))
.Returns(_series);
Mocker.GetMock<IArtistService>()
.Setup(s => s.GetArtist(It.IsAny<int>()))
.Returns(_artist);
}
private void GivenFailedMove()
@ -54,7 +54,7 @@ namespace NzbDrone.Core.Test.TvTests
}
[Test]
public void should_no_update_series_path_on_error()
public void should_no_update_artist_path_on_error()
{
GivenFailedMove();
@ -62,26 +62,26 @@ namespace NzbDrone.Core.Test.TvTests
ExceptionVerification.ExpectedErrors(1);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.IsAny<Series>()), Times.Never());
Mocker.GetMock<IArtistService>()
.Verify(v => v.UpdateArtist(It.IsAny<Artist>()), Times.Never());
}
[Test]
public void should_build_new_path_when_root_folder_is_provided()
{
_command.DestinationPath = null;
_command.DestinationRootFolder = @"C:\Test\TV3".AsOsAgnostic();
_command.DestinationRootFolder = @"C:\Test\Music3".AsOsAgnostic();
var expectedPath = @"C:\Test\TV3\Series".AsOsAgnostic();
var expectedPath = @"C:\Test\Music3\Artist".AsOsAgnostic();
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.GetSeriesFolder(It.IsAny<Series>(), null))
.Returns("Series");
.Setup(s => s.GetArtistFolder(It.IsAny<Artist>(), null))
.Returns("Artist");
Subject.Execute(_command);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.Is<Series>(s => s.Path == expectedPath)), Times.Once());
Mocker.GetMock<IArtistService>()
.Verify(v => v.UpdateArtist(It.Is<Artist>(s => s.Path == expectedPath)), Times.Once());
}
[Test]
@ -89,11 +89,11 @@ namespace NzbDrone.Core.Test.TvTests
{
Subject.Execute(_command);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.Is<Series>(s => s.Path == _command.DestinationPath)), Times.Once());
Mocker.GetMock<IArtistService>()
.Verify(v => v.UpdateArtist(It.Is<Artist>(s => s.Path == _command.DestinationPath)), Times.Once());
Mocker.GetMock<IBuildFileNames>()
.Verify(v => v.GetSeriesFolder(It.IsAny<Series>(), null), Times.Never());
.Verify(v => v.GetArtistFolder(It.IsAny<Artist>(), null), Times.Never());
}
}
}

@ -283,7 +283,8 @@
<Compile Include="MediaFiles\DiskScanServiceTests\ScanFixture.cs" />
<Compile Include="MediaFiles\DownloadedEpisodesCommandServiceFixture.cs" />
<Compile Include="MediaFiles\DownloadedEpisodesImportServiceFixture.cs" />
<Compile Include="MediaFiles\EpisodeFileMovingServiceTests\MoveEpisodeFileFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatAudioCodecFixture.cs" />
<Compile Include="MediaFiles\TrackFileMovingServiceTests\MoveTrackFileFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportDecisionMakerFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\SampleServiceFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecificationFixture.cs" />
@ -291,7 +292,6 @@
<Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecificationFixture.cs" />
<Compile Include="MediaFiles\ImportApprovedEpisodesFixture.cs" />
<Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" />
<Compile Include="MediaFiles\MediaInfo\FormattedAudioChannelsFixture.cs" />
<Compile Include="Messaging\Commands\CommandQueueManagerFixture.cs" />
<Compile Include="MetadataSource\SkyHook\SkyHookProxySearchFixture.cs" />
<Compile Include="MetadataSource\SearchSeriesComparerFixture.cs" />
@ -300,8 +300,6 @@
<Compile Include="NotificationTests\NotificationBaseFixture.cs" />
<Compile Include="NotificationTests\SynologyIndexerFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" />
<Compile Include="ParserTests\MiniSeriesEpisodeParserFixture.cs" />
<Compile Include="ParserTests\MusicParserFixture.cs" />
<Compile Include="Qualities\RevisionComparableFixture.cs" />
@ -312,7 +310,7 @@
<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\RenameTrackFileServiceFixture.cs" />
<Compile Include="MediaFiles\UpgradeMediaFileServiceFixture.cs" />
<Compile Include="Messaging\Commands\CommandEqualityComparerFixture.cs" />
<Compile Include="Messaging\Commands\CommandExecutorFixture.cs" />
@ -329,9 +327,9 @@
<Compile Include="NotificationTests\Xbmc\Json\UpdateFixture.cs" />
<Compile Include="NotificationTests\Xbmc\OnDownloadFixture.cs" />
<Compile Include="OrganizerTests\BuildFilePathFixture.cs" />
<Compile Include="OrganizerTests\GetSeasonFolderFixture.cs" />
<Compile Include="OrganizerTests\GetAlbumFolderFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\FileNameBuilderFixture.cs" />
<Compile Include="OrganizerTests\GetSeriesFolderFixture.cs" />
<Compile Include="OrganizerTests\GetArtistFolderFixture.cs" />
<Compile Include="ParserTests\AbsoluteEpisodeNumberParserFixture.cs" />
<Compile Include="ParserTests\AnimeMetadataParserFixture.cs" />
<Compile Include="ParserTests\ExtendedQualityParserRegex.cs" />
@ -378,7 +376,7 @@
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesWithFilesFixture.cs" />
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesWithoutFilesFixture.cs" />
<Compile Include="TvTests\EpisodeRepositoryTests\FindEpisodeFixture.cs" />
<Compile Include="TvTests\MoveSeriesServiceFixture.cs" />
<Compile Include="MusicTests\MoveArtistServiceFixture.cs" />
<Compile Include="TvTests\RefreshEpisodeServiceFixture.cs" />
<Compile Include="TvTests\RefreshSeriesServiceFixture.cs" />
<Compile Include="TvTests\EpisodeMonitoredServiceTests\SetEpisodeMontitoredFixture.cs" />

@ -1,8 +1,8 @@
using FizzWare.NBuilder;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
@ -24,41 +24,25 @@ namespace NzbDrone.Core.Test.OrganizerTests
}
[Test]
[TestCase("30 Rock - S01E05 - Episode Title", 1, true, "Season {season:00}", @"C:\Test\30 Rock\Season 01\30 Rock - S01E05 - Episode Title.mkv")]
[TestCase("30 Rock - S01E05 - Episode Title", 1, true, "Season {season}", @"C:\Test\30 Rock\Season 1\30 Rock - S01E05 - Episode Title.mkv")]
[TestCase("30 Rock - S01E05 - Episode Title", 1, false, "Season {season:00}", @"C:\Test\30 Rock\30 Rock - S01E05 - Episode Title.mkv")]
[TestCase("30 Rock - S01E05 - Episode Title", 1, false, "Season {season}", @"C:\Test\30 Rock\30 Rock - S01E05 - Episode Title.mkv")]
[TestCase("30 Rock - S01E05 - Episode Title", 1, true, "ReallyUglySeasonFolder {season}", @"C:\Test\30 Rock\ReallyUglySeasonFolder 1\30 Rock - S01E05 - Episode Title.mkv")]
[TestCase("30 Rock - S00E05 - Episode Title", 0, true, "Season {season}", @"C:\Test\30 Rock\Specials\30 Rock - S00E05 - Episode Title.mkv")]
public void CalculateFilePath_SeasonFolder_SingleNumber(string filename, int seasonNumber, bool useSeasonFolder, string seasonFolderFormat, string expectedPath)
public void should_clean_album_folder_when_it_contains_illegal_characters_in_album_or_artist_title()
{
var fakeSeries = Builder<Series>.CreateNew()
.With(s => s.Title = "30 Rock")
.With(s => s.Path = @"C:\Test\30 Rock".AsOsAgnostic())
.With(s => s.SeasonFolder = useSeasonFolder)
.Build();
namingConfig.SeasonFolderFormat = seasonFolderFormat;
Subject.BuildFilePath(fakeSeries, seasonNumber, filename, ".mkv").Should().Be(expectedPath.AsOsAgnostic());
}
var filename = @"02 - Track Title";
var expectedPath = @"C:\Test\Fake- The Artist\Fake- The Artist Fake- Album\02 - Track Title.flac";
[Test]
public void should_clean_season_folder_when_it_contains_illegal_characters_in_series_title()
{
var filename = @"S01E05 - Episode Title";
var seasonNumber = 1;
var expectedPath = @"C:\Test\NCIS- Los Angeles\NCIS- Los Angeles Season 1\S01E05 - Episode Title.mkv";
var fakeArtist = Builder<Artist>.CreateNew()
.With(s => s.Name = "Fake: The Artist")
.With(s => s.Path = @"C:\Test\Fake- The Artist".AsOsAgnostic())
.With(s => s.AlbumFolder = true)
.Build();
var fakeSeries = Builder<Series>.CreateNew()
.With(s => s.Title = "NCIS: Los Angeles")
.With(s => s.Path = @"C:\Test\NCIS- Los Angeles".AsOsAgnostic())
.With(s => s.SeasonFolder = true)
var fakeAlbum = Builder<Album>.CreateNew()
.With(s => s.Title = "Fake: Album")
.With(s => s.Path = @"C:\Test\Fake- The Artist\Fake- Album".AsOsAgnostic())
.Build();
namingConfig.SeasonFolderFormat = "{Series Title} Season {season:0}";
namingConfig.AlbumFolderFormat = "{Artist Name} {Album Title}";
Subject.BuildFilePath(fakeSeries, seasonNumber, filename, ".mkv").Should().Be(expectedPath.AsOsAgnostic());
Subject.BuildTrackFilePath(fakeArtist, fakeAlbum, filename, ".flac").Should().Be(expectedPath.AsOsAgnostic());
}
}
}

@ -7,37 +7,41 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
[TestFixture]
public class CleanTitleFixture : CoreTest<FileNameBuilder>
{
private Series _series;
private Episode _episode;
private EpisodeFile _episodeFile;
private Artist _artist;
private Album _album;
private Track _track;
private TrackFile _trackFile;
private NamingConfig _namingConfig;
[SetUp]
public void Setup()
{
_series = Builder<Series>
_artist = Builder<Artist>
.CreateNew()
.With(s => s.Title = "South Park")
.With(s => s.Name = "Avenged Sevenfold")
.Build();
_episode = Builder<Episode>.CreateNew()
.With(e => e.Title = "City Sushi")
.With(e => e.SeasonNumber = 15)
.With(e => e.EpisodeNumber = 6)
.With(e => e.AbsoluteEpisodeNumber = 100)
_album = Builder<Album>
.CreateNew()
.With(s => s.Title = "Hail to the King")
.Build();
_track = Builder<Track>.CreateNew()
.With(e => e.Title = "Doing Time")
.With(e => e.TrackNumber = 3)
.Build();
_episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" };
_trackFile = new TrackFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" };
_namingConfig = NamingConfig.Default;
_namingConfig.RenameEpisodes = true;
_namingConfig.RenameTracks = true;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(_namingConfig);
@ -65,20 +69,19 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
[TestCase("[a] title", "a title")]
[TestCase("backslash \\ backlash", "backslash backlash")]
[TestCase("I'm the Boss", "Im the Boss")]
//[TestCase("", "")]
public void should_get_expected_title_back(string title, string expected)
public void should_get_expected_title_back(string name, string expected)
{
_series.Title = title;
_namingConfig.StandardEpisodeFormat = "{Series CleanTitle}";
_artist.Name = name;
_namingConfig.StandardTrackFormat = "{Artist CleanName}";
Subject.BuildFileName(new List<Episode> { _episode }, _series, _episodeFile)
Subject.BuildTrackFileName(new List<Track> { _track }, _artist, _album, _trackFile)
.Should().Be(expected);
}
[Test]
public void should_use_and_as_separator_for_multiple_episodes()
{
var episodes = Builder<Episode>.CreateListOfSize(2)
var tracks = Builder<Track>.CreateListOfSize(2)
.TheFirst(1)
.With(e => e.Title = "Surrender Benson")
.TheNext(1)
@ -86,10 +89,10 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Build()
.ToList();
_namingConfig.StandardEpisodeFormat = "{Episode CleanTitle}";
_namingConfig.StandardTrackFormat = "{Track CleanTitle}";
Subject.BuildFileName(episodes, _series, _episodeFile)
.Should().Be(episodes.First().Title + " and " + episodes.Last().Title);
Subject.BuildTrackFileName(tracks, _artist, _album, _trackFile)
.Should().Be(tracks.First().Title + " and " + tracks.Last().Title);
}
}
}

@ -1,113 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
[TestFixture]
public class EpisodeTitleCollapseFixture : CoreTest<FileNameBuilder>
{
private Series _series;
private Episode _episode1;
private Episode _episode2;
private Episode _episode3;
private EpisodeFile _episodeFile;
private NamingConfig _namingConfig;
[SetUp]
public void Setup()
{
_series = Builder<Series>
.CreateNew()
.With(s => s.Title = "South Park")
.Build();
_namingConfig = NamingConfig.Default;
_namingConfig.RenameEpisodes = true;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(_namingConfig);
_episode1 = Builder<Episode>.CreateNew()
.With(e => e.Title = "City Sushi")
.With(e => e.SeasonNumber = 15)
.With(e => e.EpisodeNumber = 6)
.With(e => e.AbsoluteEpisodeNumber = 100)
.Build();
_episode2 = Builder<Episode>.CreateNew()
.With(e => e.Title = "City Sushi")
.With(e => e.SeasonNumber = 15)
.With(e => e.EpisodeNumber = 7)
.With(e => e.AbsoluteEpisodeNumber = 101)
.Build();
_episode3 = Builder<Episode>.CreateNew()
.With(e => e.Title = "City Sushi")
.With(e => e.SeasonNumber = 15)
.With(e => e.EpisodeNumber = 8)
.With(e => e.AbsoluteEpisodeNumber = 102)
.Build();
_episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" };
Mocker.GetMock<IQualityDefinitionService>()
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
}
[TestCase("Hey, Baby, What's Wrong (1)", "Hey, Baby, What's Wrong (2)", "Hey, Baby, What's Wrong")]
[TestCase("Meet the Guys and Girls of Cycle 20 Part 1", "Meet the Guys and Girls of Cycle 20 Part 2", "Meet the Guys and Girls of Cycle 20")]
[TestCase("Meet the Guys and Girls of Cycle 20 Part1", "Meet the Guys and Girls of Cycle 20 Part2", "Meet the Guys and Girls of Cycle 20")]
[TestCase("Meet the Guys and Girls of Cycle 20 Part01", "Meet the Guys and Girls of Cycle 20 Part02", "Meet the Guys and Girls of Cycle 20")]
[TestCase("Meet the Guys and Girls of Cycle 20 Part 01", "Meet the Guys and Girls of Cycle 20 Part 02", "Meet the Guys and Girls of Cycle 20")]
[TestCase("Meet the Guys and Girls of Cycle 20 part 1", "Meet the Guys and Girls of Cycle 20 part 2", "Meet the Guys and Girls of Cycle 20")]
[TestCase("Meet the Guys and Girls of Cycle 20 pt 1", "Meet the Guys and Girls of Cycle 20 pt 2", "Meet the Guys and Girls of Cycle 20")]
[TestCase("Meet the Guys and Girls of Cycle 20 pt. 1", "Meet the Guys and Girls of Cycle 20 pt. 2", "Meet the Guys and Girls of Cycle 20")]
public void should_collapse_episode_titles_when_episode_titles_are_the_same(string title1, string title2, string expected)
{
_namingConfig.StandardEpisodeFormat = "{Episode Title}";
_episode1.Title = title1;
_episode2.Title = title2;
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be(expected);
}
[Test]
public void should_not_collapse_episode_titles_when_episode_titles_are_not_the_same()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 3;
_episode1.Title = "Hello";
_episode2.Title = "World";
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - S15E06-E07 - Hello + World");
}
[Test]
public void should_not_collaspe_when_result_is_empty()
{
_namingConfig.StandardEpisodeFormat = "{Episode Title}";
_episode1.Title = "Part 1";
_episode2.Title = "Part 2";
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("Part 1 + Part 2");
}
}
}

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
@ -8,7 +8,7 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
@ -16,35 +16,39 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
public class FileNameBuilderFixture : CoreTest<FileNameBuilder>
{
private Series _series;
private Episode _episode1;
private EpisodeFile _episodeFile;
private Artist _artist;
private Album _album;
private Track _track1;
private TrackFile _trackFile;
private NamingConfig _namingConfig;
[SetUp]
public void Setup()
{
_series = Builder<Series>
_artist = Builder<Artist>
.CreateNew()
.With(s => s.Title = "South Park")
.With(s => s.Name = "Linkin Park")
.Build();
_album = Builder<Album>
.CreateNew()
.With(s => s.Title = "Hybrid Theory")
.Build();
_namingConfig = NamingConfig.Default;
_namingConfig.RenameEpisodes = true;
_namingConfig.RenameTracks = true;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(_namingConfig);
_episode1 = Builder<Episode>.CreateNew()
_track1 = Builder<Track>.CreateNew()
.With(e => e.Title = "City Sushi")
.With(e => e.SeasonNumber = 15)
.With(e => e.EpisodeNumber = 6)
.With(e => e.AbsoluteEpisodeNumber = 100)
.With(e => e.TrackNumber = 6)
.Build();
_episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" };
_trackFile = new TrackFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" };
Mocker.GetMock<IQualityDefinitionService>()
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
@ -53,592 +57,413 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
private void GivenProper()
{
_episodeFile.Quality.Revision.Version = 2;
_trackFile.Quality.Revision.Version = 2;
}
private void GivenReal()
{
_episodeFile.Quality.Revision.Real = 1;
_trackFile.Quality.Revision.Real = 1;
}
[Test]
public void should_replace_Series_space_Title()
public void should_replace_Artist_space_Name()
{
_namingConfig.StandardEpisodeFormat = "{Series Title}";
_namingConfig.StandardTrackFormat = "{Artist Name}";
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("South Park");
Subject.BuildTrackFileName(new List<Track> {_track1}, _artist, _album, _trackFile)
.Should().Be("Linkin Park");
}
[Test]
public void should_replace_Series_underscore_Title()
public void should_replace_Artist_underscore_Name()
{
_namingConfig.StandardEpisodeFormat = "{Series_Title}";
_namingConfig.StandardTrackFormat = "{Artist_Name}";
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("South_Park");
Subject.BuildTrackFileName(new List<Track> {_track1}, _artist, _album, _trackFile)
.Should().Be("Linkin_Park");
}
[Test]
public void should_replace_Series_dot_Title()
public void should_replace_Artist_dot_Name()
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}";
_namingConfig.StandardTrackFormat = "{Artist.Name}";
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("South.Park");
Subject.BuildTrackFileName(new List<Track> {_track1}, _artist, _album, _trackFile)
.Should().Be("Linkin.Park");
}
[Test]
public void should_replace_Series_dash_Title()
public void should_replace_Artist_dash_Name()
{
_namingConfig.StandardEpisodeFormat = "{Series-Title}";
_namingConfig.StandardTrackFormat = "{Artist-Name}";
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("South-Park");
Subject.BuildTrackFileName(new List<Track> {_track1}, _artist, _album, _trackFile)
.Should().Be("Linkin-Park");
}
[Test]
public void should_replace_SERIES_TITLE_with_all_caps()
public void should_replace_ARTIST_NAME_with_all_caps()
{
_namingConfig.StandardEpisodeFormat = "{SERIES TITLE}";
_namingConfig.StandardTrackFormat = "{ARTIST NAME}";
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("SOUTH PARK");
Subject.BuildTrackFileName(new List<Track> {_track1}, _artist, _album, _trackFile)
.Should().Be("LINKIN PARK");
}
[Test]
public void should_replace_SERIES_TITLE_with_random_casing_should_keep_original_casing()
public void should_replace_ARTIST_NAME_with_random_casing_should_keep_original_casing()
{
_namingConfig.StandardEpisodeFormat = "{sErIES-tItLE}";
_namingConfig.StandardTrackFormat = "{aRtIST-nAmE}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(_series.Title.Replace(' ', '-'));
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be(_artist.Name.Replace(' ', '-'));
}
[Test]
public void should_replace_series_title_with_all_lower_case()
public void should_replace_artist_name_with_all_lower_case()
{
_namingConfig.StandardEpisodeFormat = "{series title}";
_namingConfig.StandardTrackFormat = "{artist name}";
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("south park");
Subject.BuildTrackFileName(new List<Track> {_track1}, _artist, _album, _trackFile)
.Should().Be("linkin park");
}
[Test]
public void should_cleanup_Series_Title()
public void should_cleanup_Artist_Name()
{
_namingConfig.StandardEpisodeFormat = "{Series.CleanTitle}";
_series.Title = "South Park (1997)";
_namingConfig.StandardTrackFormat = "{Artist.CleanName}";
_artist.Name = "Linkin Park (1997)";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.1997");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Linkin.Park.1997");
}
[Test]
public void should_replace_episode_title()
public void should_replace_Album_space_Title()
{
_namingConfig.StandardEpisodeFormat = "{Episode Title}";
_namingConfig.StandardTrackFormat = "{Album Title}";
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("City Sushi");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Hybrid Theory");
}
[Test]
public void should_replace_episode_title_if_pattern_has_random_casing()
public void should_replace_Album_underscore_Title()
{
_namingConfig.StandardEpisodeFormat = "{ePisOde-TitLe}";
_namingConfig.StandardTrackFormat = "{Album_Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("City-Sushi");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Hybrid_Theory");
}
[Test]
public void should_replace_season_number_with_single_digit()
public void should_replace_Album_dot_Title()
{
_episode1.SeasonNumber = 1;
_namingConfig.StandardEpisodeFormat = "{season}x{episode}";
_namingConfig.StandardTrackFormat = "{Album.Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("1x6");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Hybrid.Theory");
}
[Test]
public void should_replace_season00_number_with_two_digits()
public void should_replace_Album_dash_Title()
{
_episode1.SeasonNumber = 1;
_namingConfig.StandardEpisodeFormat = "{season:00}x{episode}";
_namingConfig.StandardTrackFormat = "{Album-Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("01x6");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Hybrid-Theory");
}
[Test]
public void should_replace_episode_number_with_single_digit()
public void should_replace_ALBUM_TITLE_with_all_caps()
{
_episode1.SeasonNumber = 1;
_namingConfig.StandardEpisodeFormat = "{season}x{episode}";
_namingConfig.StandardTrackFormat = "{ALBUM TITLE}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("1x6");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("HYBRID THEORY");
}
[Test]
public void should_replace_episode00_number_with_two_digits()
public void should_replace_ALBUM_TITLE_with_random_casing_should_keep_original_casing()
{
_episode1.SeasonNumber = 1;
_namingConfig.StandardEpisodeFormat = "{season}x{episode:00}";
_namingConfig.StandardTrackFormat = "{aLbUM-tItLE}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("1x06");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be(_album.Title.Replace(' ', '-'));
}
[Test]
public void should_replace_quality_title()
public void should_replace_album_title_with_all_lower_case()
{
_namingConfig.StandardEpisodeFormat = "{Quality Title}";
_namingConfig.StandardTrackFormat = "{album title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("HDTV-720p");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("hybrid theory");
}
[Test]
public void should_replace_quality_proper_with_proper()
public void should_cleanup_Album_Title()
{
_namingConfig.StandardEpisodeFormat = "{Quality Proper}";
GivenProper();
_namingConfig.StandardTrackFormat = "{Artist.CleanName}";
_artist.Name = "Hybrid Theory (2000)";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("Proper");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Hybrid.Theory.2000");
}
[Test]
public void should_replace_quality_real_with_real()
public void should_replace_track_title()
{
_namingConfig.StandardEpisodeFormat = "{Quality Real}";
GivenReal();
_namingConfig.StandardTrackFormat = "{Track Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("REAL");
Subject.BuildTrackFileName(new List<Track> {_track1}, _artist, _album, _trackFile)
.Should().Be("City Sushi");
}
[Test]
public void should_replace_all_contents_in_pattern()
public void should_replace_track_title_if_pattern_has_random_casing()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} [{Quality Title}]";
_namingConfig.StandardTrackFormat = "{tRaCK-TitLe}";
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("South Park - S15E06 - City Sushi [HDTV-720p]");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("City-Sushi");
}
[Test]
public void use_file_name_when_sceneName_is_null()
public void should_replace_track_number_with_single_digit()
{
_namingConfig.RenameEpisodes = false;
_episodeFile.RelativePath = "30 Rock - S01E01 - Test";
_track1.TrackNumber = 1;
_namingConfig.StandardTrackFormat = "{track}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(Path.GetFileNameWithoutExtension(_episodeFile.RelativePath));
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("1");
}
[Test]
public void use_path_when_sceneName_and_relative_path_are_null()
public void should_replace_track00_number_with_two_digits()
{
_namingConfig.RenameEpisodes = false;
_episodeFile.RelativePath = null;
_episodeFile.Path = @"C:\Test\Unsorted\Series - S01E01 - Test";
_track1.TrackNumber = 1;
_namingConfig.StandardTrackFormat = "{track:00}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(Path.GetFileNameWithoutExtension(_episodeFile.Path));
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("01");
}
[Test]
public void use_file_name_when_sceneName_is_not_null()
public void should_replace_quality_title()
{
_namingConfig.RenameEpisodes = false;
_episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_episodeFile.RelativePath = "30 Rock - S01E01 - Test";
_namingConfig.StandardTrackFormat = "{Quality Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("30.Rock.S01E01.xvid-LOL");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("MP3-256");
}
[Test]
public void should_use_airDate_if_series_isDaily()
public void should_replace_all_contents_in_pattern()
{
_namingConfig.DailyEpisodeFormat = "{Series Title} - {air-date} - {Episode Title}";
_namingConfig.StandardTrackFormat = "{Artist Name} - {Album Title} - {track:00} - {Track Title} [{Quality Title}]";
_series.Title = "The Daily Show with Jon Stewart";
_series.SeriesType = SeriesTypes.Daily;
_episode1.AirDate = "2012-12-13";
_episode1.Title = "Kristen Stewart";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("The Daily Show with Jon Stewart - 2012-12-13 - Kristen Stewart");
Subject.BuildTrackFileName(new List<Track> {_track1}, _artist, _album, _trackFile)
.Should().Be("Linkin Park - Hybrid Theory - 06 - City Sushi [MP3-256]");
}
[Test]
public void should_set_airdate_to_unknown_if_not_available()
public void use_file_name_when_sceneName_is_null()
{
_namingConfig.DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title}";
_series.Title = "The Daily Show with Jon Stewart";
_series.SeriesType = SeriesTypes.Daily;
_namingConfig.RenameTracks = false;
_trackFile.RelativePath = "Linkin Park - 06 - Test";
_episode1.AirDate = null;
_episode1.Title = "Kristen Stewart";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("The Daily Show with Jon Stewart - Unknown - Kristen Stewart");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be(Path.GetFileNameWithoutExtension(_trackFile.RelativePath));
}
[Test]
public void should_not_clean_episode_title_if_there_is_only_one()
public void use_path_when_sceneName_and_relative_path_are_null()
{
var title = "City Sushi (1)";
_episode1.Title = title;
_namingConfig.RenameTracks = false;
_trackFile.RelativePath = null;
_trackFile.Path = @"C:\Test\Unsorted\Artist - 01 - Test";
_namingConfig.StandardEpisodeFormat = "{Episode Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(title);
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be(Path.GetFileNameWithoutExtension(_trackFile.Path));
}
[Test]
public void should_should_replace_release_group()
{
_namingConfig.StandardEpisodeFormat = "{Release Group}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(_episodeFile.ReleaseGroup);
}
[Test]
public void should_be_able_to_use_original_title()
public void should_not_clean_track_title_if_there_is_only_one()
{
_series.Title = "30 Rock";
_namingConfig.StandardEpisodeFormat = "{Series Title} - {Original Title}";
var title = "City Sushi (1)";
_track1.Title = title;
_episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_episodeFile.RelativePath = "30 Rock - S01E01 - Test";
_namingConfig.StandardTrackFormat = "{Track Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("30 Rock - 30.Rock.S01E01.xvid-LOL");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be(title);
}
[Test]
public void should_trim_periods_from_end_of_episode_title()
public void should_should_replace_release_group()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 3;
var episode = Builder<Episode>.CreateNew()
.With(e => e.Title = "Part 1.")
.With(e => e.SeasonNumber = 6)
.With(e => e.EpisodeNumber = 6)
.Build();
_namingConfig.StandardTrackFormat = "{Release Group}";
Subject.BuildFileName(new List<Episode> { episode }, new Series { Title = "30 Rock" }, _episodeFile)
.Should().Be("30 Rock - S06E06 - Part 1");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be(_trackFile.ReleaseGroup);
}
[Test]
public void should_trim_question_marks_from_end_of_episode_title()
public void should_be_able_to_use_original_title()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 3;
var episode = Builder<Episode>.CreateNew()
.With(e => e.Title = "Part 1?")
.With(e => e.SeasonNumber = 6)
.With(e => e.EpisodeNumber = 6)
.Build();
_artist.Name = "Linkin Park";
_namingConfig.StandardTrackFormat = "{Artist Name} - {Original Title} - {Track Title}";
_trackFile.SceneName = "Linkin.Park.Meteora.320-LOL";
_trackFile.RelativePath = "30 Rock - 01 - Test";
Subject.BuildFileName(new List<Episode> { episode }, new Series { Title = "30 Rock" }, _episodeFile)
.Should().Be("30 Rock - S06E06 - Part 1");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Linkin Park - Linkin.Park.Meteora.320-LOL - City Sushi");
}
[Test]
public void should_replace_double_period_with_single_period()
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}";
_namingConfig.StandardTrackFormat = "{Artist.Name}.{track:00}.{Track.Title}";
var episode = Builder<Episode>.CreateNew()
var track = Builder<Track>.CreateNew()
.With(e => e.Title = "Part 1")
.With(e => e.SeasonNumber = 6)
.With(e => e.EpisodeNumber = 6)
.With(e => e.TrackNumber = 6)
.Build();
Subject.BuildFileName(new List<Episode> { episode }, new Series { Title = "Chicago P.D." }, _episodeFile)
.Should().Be("Chicago.P.D.S06E06.Part.1");
Subject.BuildTrackFileName(new List<Track> { track }, new Artist { Name = "In The Woods." }, new Album { Title = "30 Rock" }, _trackFile)
.Should().Be("In.The.Woods.06.Part.1");
}
[Test]
public void should_replace_triple_period_with_single_period()
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}";
_namingConfig.StandardTrackFormat = "{Artist.Name}.{track:00}.{Track.Title}";
var episode = Builder<Episode>.CreateNew()
var track = Builder<Track>.CreateNew()
.With(e => e.Title = "Part 1")
.With(e => e.SeasonNumber = 6)
.With(e => e.EpisodeNumber = 6)
.With(e => e.TrackNumber = 6)
.Build();
Subject.BuildFileName(new List<Episode> { episode }, new Series { Title = "Chicago P.D.." }, _episodeFile)
.Should().Be("Chicago.P.D.S06E06.Part.1");
}
[Test]
public void should_not_replace_absolute_numbering_when_series_is_not_anime()
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{absolute:00}.{Episode.Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06.City.Sushi");
}
[Test]
public void should_replace_standard_and_absolute_numbering_when_series_is_anime()
{
_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)
.Should().Be("South.Park.S15E06.100.City.Sushi");
}
[Test]
public void should_replace_standard_numbering_when_series_is_anime()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{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_replace_absolute_numbering_when_series_is_anime()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series.Title}.{absolute:00}.{Episode.Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.100.City.Sushi");
}
[Test]
public void should_replace_duplicate_numbering_individually()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series.Title}.{season}x{episode:00}.{absolute:000}\\{Series.Title}.S{season:00}E{episode:00}.{absolute:00}.{Episode.Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.15x06.100\\South.Park.S15E06.100.City.Sushi");
}
[Test]
public void should_replace_individual_season_episode_tokens()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series Title} Season {season:0000} Episode {episode:0000}\\{Series.Title}.S{season:00}E{episode:00}.{absolute:00}.{Episode.Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park Season 0015 Episode 0006\\South.Park.S15E06.100.City.Sushi");
}
[Test]
public void should_use_standard_naming_when_anime_episode_has_no_absolute_number()
{
_series.SeriesType = SeriesTypes.Anime;
_episode1.AbsoluteEpisodeNumber = null;
_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)
.Should().Be("South Park - 15x06 - City Sushi");
Subject.BuildTrackFileName(new List<Track> { track }, new Artist { Name = "In The Woods..." }, new Album { Title = "30 Rock" }, _trackFile)
.Should().Be("In.The.Woods.06.Part.1");
}
[Test]
public void should_include_affixes_if_value_not_empty()
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}{_Episode.Title_}{Quality.Title}";
_namingConfig.StandardTrackFormat = "{Artist.Name}.{track:00}{_Track.Title_}{Quality.Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06_City.Sushi_HDTV-720p");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Linkin.Park.06_City.Sushi_MP3-256");
}
[Test]
public void should_not_include_affixes_if_value_empty()
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}{_Episode.Title_}";
_namingConfig.StandardTrackFormat = "{Artist.Name}.{track:00}{_Track.Title_}";
_episode1.Title = "";
_track1.Title = "";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Linkin.Park.06");
}
[Test]
public void should_format_mediainfo_properly()
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MEDIAINFO.FULL}";
_namingConfig.StandardTrackFormat = "{Artist.Name}.{track:00}.{Track.Title}.{MEDIAINFO.FULL}";
_episodeFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
_trackFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
{
VideoCodec = "AVC",
AudioFormat = "DTS",
AudioFormat = "FLAC",
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]");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Linkin.Park.06.City.Sushi.FLAC[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}";
_namingConfig.StandardTrackFormat = "{Artist.Name}.{track:00}.{Track.Title}.{MEDIAINFO.FULL}";
_episodeFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel()
_trackFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel
{
VideoCodec = "AVC",
AudioFormat = "DTS",
AudioFormat = "FLAC",
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]");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Linkin.Park.06.City.Sushi.FLAC.[EN+ES+IT]");
}
[Test]
public void should_remove_duplicate_non_word_characters()
{
_series.Title = "Venture Bros.";
_namingConfig.StandardEpisodeFormat = "{Series.Title}.{season}x{episode:00}";
_artist.Name = "Venture Bros.";
_namingConfig.StandardTrackFormat = "{Artist.Name}.{Album.Title}-{track:00}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("Venture.Bros.15x06");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Venture.Bros.Hybrid.Theory-06");
}
[Test]
public void should_use_existing_filename_when_scene_name_is_not_available()
{
_namingConfig.RenameEpisodes = true;
_namingConfig.StandardEpisodeFormat = "{Original Title}";
_namingConfig.RenameTracks = true;
_namingConfig.StandardTrackFormat = "{Original Title}";
_episodeFile.SceneName = null;
_episodeFile.RelativePath = "existing.file.mkv";
_trackFile.SceneName = null;
_trackFile.RelativePath = "existing.file.mkv";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(Path.GetFileNameWithoutExtension(_episodeFile.RelativePath));
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be(Path.GetFileNameWithoutExtension(_trackFile.RelativePath));
}
[Test]
public void should_be_able_to_use_only_original_title()
{
_series.Title = "30 Rock";
_namingConfig.StandardEpisodeFormat = "{Original Title}";
_artist.Name = "30 Rock";
_namingConfig.StandardTrackFormat = "{Original Title}";
_episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_episodeFile.RelativePath = "30 Rock - S01E01 - Test";
_trackFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_trackFile.RelativePath = "30 Rock - S01E01 - Test";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("30.Rock.S01E01.xvid-LOL");
}
[Test]
public void should_allow_period_between_season_and_episode()
{
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}.E{episode:00}.{Episode.Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15.E06.City.Sushi");
}
[Test]
public void should_allow_space_between_season_and_episode()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00} E{episode:00} - {Episode Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - S15 E06 - City Sushi");
}
[Test]
public void should_replace_quality_proper_with_v2_for_anime_v2()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Quality Proper}";
GivenProper();
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("v2");
}
[Test]
public void should_not_include_quality_proper_when_release_is_not_a_proper()
{
_namingConfig.StandardEpisodeFormat = "{Quality Title} {Quality Proper}";
_namingConfig.StandardTrackFormat = "{Quality Title} {Quality Proper}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("HDTV-720p");
}
[Test]
public void should_wrap_proper_in_square_brackets()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Title}] {[Quality Proper]}";
GivenProper();
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 [HDTV-720p] [Proper]");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("MP3-256");
}
[Test]
public void should_not_wrap_proper_in_square_brackets_when_not_a_proper()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Title}] {[Quality Proper]}";
_namingConfig.StandardTrackFormat = "{Artist Name} - {track:00} [{Quality Title}] {[Quality Proper]}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 [HDTV-720p]");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Linkin Park - 06 [MP3-256]");
}
[Test]
public void should_replace_quality_full_with_quality_title_only_when_not_a_proper()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Full}]";
_namingConfig.StandardTrackFormat = "{Artist Name} - {track:00} [{Quality Full}]";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 [HDTV-720p]");
}
[Test]
public void should_replace_quality_full_with_quality_title_and_proper_only_when_a_proper()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Full}]";
GivenProper();
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 [HDTV-720p Proper]");
}
[Test]
public void should_replace_quality_full_with_quality_title_and_real_when_a_real()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Full}]";
GivenReal();
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 [HDTV-720p REAL]");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Linkin Park - 06 [MP3-256]");
}
[TestCase(' ')]
@ -647,10 +472,10 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
[TestCase('_')]
public void should_trim_extra_separators_from_end_when_quality_proper_is_not_included(char separator)
{
_namingConfig.StandardEpisodeFormat = string.Format("{{Quality{0}Title}}{0}{{Quality{0}Proper}}", separator);
_namingConfig.StandardTrackFormat = string.Format("{{Quality{0}Title}}{0}{{Quality{0}Proper}}", separator);
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("HDTV-720p");
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("MP3-256");
}
[TestCase(' ')]
@ -659,67 +484,59 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
[TestCase('_')]
public void should_trim_extra_separators_from_middle_when_quality_proper_is_not_included(char separator)
{
_namingConfig.StandardEpisodeFormat = string.Format("{{Quality{0}Title}}{0}{{Quality{0}Proper}}{0}{{Episode{0}Title}}", separator);
_namingConfig.StandardTrackFormat = string.Format("{{Quality{0}Title}}{0}{{Quality{0}Proper}}{0}{{Track{0}Title}}", separator);
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(string.Format("HDTV-720p{0}City{0}Sushi", separator));
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be(string.Format("MP3-256{0}City{0}Sushi", separator));
}
[Test]
public void should_not_require_a_separator_between_tokens()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "[{Release Group}]{Series.CleanTitle}.{absolute:000}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("[LidarrTest]South.Park.100");
}
[Test]
public void should_be_able_to_use_original_filename()
{
_series.Title = "30 Rock";
_namingConfig.StandardEpisodeFormat = "{Series Title} - {Original Filename}";
_artist.Name = "30 Rock";
_namingConfig.StandardTrackFormat = "{Artist Name} - {Original Filename}";
_episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_episodeFile.RelativePath = "30 Rock - S01E01 - Test";
_trackFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_trackFile.RelativePath = "30 Rock - S01E01 - Test";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("30 Rock - 30 Rock - S01E01 - Test");
}
[Test]
public void should_be_able_to_use_original_filename_only()
{
_series.Title = "30 Rock";
_namingConfig.StandardEpisodeFormat = "{Original Filename}";
_artist.Name = "30 Rock";
_namingConfig.StandardTrackFormat = "{Original Filename}";
_episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_episodeFile.RelativePath = "30 Rock - S01E01 - Test";
_trackFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_trackFile.RelativePath = "30 Rock - S01E01 - Test";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("30 Rock - S01E01 - Test");
}
[Test]
public void should_use_Lidarr_as_release_group_when_not_available()
{
_episodeFile.ReleaseGroup = null;
_namingConfig.StandardEpisodeFormat = "{Release Group}";
_trackFile.ReleaseGroup = null;
_namingConfig.StandardTrackFormat = "{Release Group}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be("Lidarr");
}
[TestCase("{Episode Title}{-Release Group}", "City Sushi")]
[TestCase("{Episode Title}{ Release Group}", "City Sushi")]
[TestCase("{Episode Title}{ [Release Group]}", "City Sushi")]
[TestCase("{Track Title}{-Release Group}", "City Sushi")]
[TestCase("{Track Title}{ Release Group}", "City Sushi")]
[TestCase("{Track Title}{ [Release Group]}", "City Sushi")]
public void should_not_use_Lidarr_as_release_group_if_pattern_has_separator(string pattern, string expectedFileName)
{
_episodeFile.ReleaseGroup = null;
_namingConfig.StandardEpisodeFormat = pattern;
_trackFile.ReleaseGroup = null;
_namingConfig.StandardTrackFormat = pattern;
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be(expectedFileName);
}
@ -728,10 +545,10 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
[TestCase("IMMERSE")]
public void should_use_existing_casing_for_release_group(string releaseGroup)
{
_episodeFile.ReleaseGroup = releaseGroup;
_namingConfig.StandardEpisodeFormat = "{Release Group}";
_trackFile.ReleaseGroup = releaseGroup;
_namingConfig.StandardTrackFormat = "{Release Group}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
Subject.BuildTrackFileName(new List<Track> { _track1 }, _artist, _album, _trackFile)
.Should().Be(releaseGroup);
}
}

@ -1,271 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
[TestFixture]
public class MultiEpisodeFixture : CoreTest<FileNameBuilder>
{
private Series _series;
private Episode _episode1;
private Episode _episode2;
private Episode _episode3;
private EpisodeFile _episodeFile;
private NamingConfig _namingConfig;
[SetUp]
public void Setup()
{
_series = Builder<Series>
.CreateNew()
.With(s => s.Title = "South Park")
.Build();
_namingConfig = NamingConfig.Default;
_namingConfig.RenameEpisodes = true;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(_namingConfig);
_episode1 = Builder<Episode>.CreateNew()
.With(e => e.Title = "City Sushi")
.With(e => e.SeasonNumber = 15)
.With(e => e.EpisodeNumber = 6)
.With(e => e.AbsoluteEpisodeNumber = 100)
.Build();
_episode2 = Builder<Episode>.CreateNew()
.With(e => e.Title = "City Sushi")
.With(e => e.SeasonNumber = 15)
.With(e => e.EpisodeNumber = 7)
.With(e => e.AbsoluteEpisodeNumber = 101)
.Build();
_episode3 = Builder<Episode>.CreateNew()
.With(e => e.Title = "City Sushi")
.With(e => e.SeasonNumber = 15)
.With(e => e.EpisodeNumber = 8)
.With(e => e.AbsoluteEpisodeNumber = 102)
.Build();
_episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" };
Mocker.GetMock<IQualityDefinitionService>()
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
}
private void GivenProper()
{
_episodeFile.Quality.Revision.Version = 2;
}
[Test]
public void should_replace_Series_space_Title()
{
_namingConfig.StandardEpisodeFormat = "{Series Title}";
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("South Park");
}
[Test]
public void should_format_extend_multi_episode_properly()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 0;
Subject.BuildFileName(new List<Episode> {_episode1, _episode2}, _series, _episodeFile)
.Should().Be("South Park - S15E06-07 - City Sushi");
}
[Test]
public void should_format_duplicate_multi_episode_properly()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 1;
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 - S15E07 - City Sushi");
}
[Test]
public void should_format_repeat_multi_episode_properly()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 2;
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - S15E06E07 - City Sushi");
}
[Test]
public void should_format_scene_multi_episode_properly()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 3;
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - S15E06-E07 - City Sushi");
}
[Test]
public void should_use_dash_as_separator_when_multi_episode_style_is_extend_for_anime()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}";
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - 100-101 - City Sushi");
}
[Test]
public void should_duplicate_absolute_pattern_when_multi_episode_style_is_duplicate()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.MultiEpisodeStyle = (int)MultiEpisodeStyle.Duplicate;
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}";
Subject.BuildFileName(new List<Episode> { _episode1, _episode2, _episode3 }, _series, _episodeFile)
.Should().Be("South Park - 100 - 101 - 102 - City Sushi");
}
[Test]
public void should_get_proper_filename_when_multi_episode_is_duplicated_and_bracket_follows_pattern()
{
_namingConfig.StandardEpisodeFormat =
"{Series Title} - S{season:00}E{episode:00} - ({Quality Title}, {MediaInfo Full}, {Release Group}) - {Episode Title}";
_namingConfig.MultiEpisodeStyle = (int) MultiEpisodeStyle.Duplicate;
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 - S15E07 - (HDTV-720p, , LidarrTest) - City Sushi");
}
[Test]
public void should_format_range_multi_episode_properly()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 4;
Subject.BuildFileName(new List<Episode> { _episode1, _episode2, _episode3 }, _series, _episodeFile)
.Should().Be("South Park - S15E06-08 - City Sushi");
}
[Test]
public void should_format_range_multi_episode_anime_properly()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.MultiEpisodeStyle = 4;
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}";
Subject.BuildFileName(new List<Episode> { _episode1, _episode2, _episode3 }, _series, _episodeFile)
.Should().Be("South Park - 100-102 - City Sushi");
}
[Test]
public void should_format_repeat_multi_episode_anime_properly()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.MultiEpisodeStyle = 2;
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}";
Subject.BuildFileName(new List<Episode> { _episode1, _episode2, _episode3 }, _series, _episodeFile)
.Should().Be("South Park - 100-101-102 - City Sushi");
}
[Test]
public void should_format_single_episode_with_range_multi_episode_properly()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 4;
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 - City Sushi");
}
[Test]
public void should_format_single_anime_episode_with_range_multi_episode_properly()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.MultiEpisodeStyle = 4;
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - 100 - City Sushi");
}
[Test]
public void should_default_to_dash_when_serparator_is_not_set_for_absolute_number()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.MultiEpisodeStyle = (int)MultiEpisodeStyle.Duplicate;
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {season}x{episode:00} - [{absolute:000}] - {Episode Title} - {Quality Title}";
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park - 15x06 - 15x07 - [100-101] - City Sushi - HDTV-720p");
}
[Test]
public void should_format_prefixed_range_multi_episode_properly()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 5;
Subject.BuildFileName(new List<Episode> { _episode1, _episode2, _episode3 }, _series, _episodeFile)
.Should().Be("South Park - S15E06-E08 - City Sushi");
}
[Test]
public void should_format_prefixed_range_multi_episode_anime_properly()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.MultiEpisodeStyle = 5;
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}";
Subject.BuildFileName(new List<Episode> { _episode1, _episode2, _episode3 }, _series, _episodeFile)
.Should().Be("South Park - 100-102 - City Sushi");
}
[Test]
public void should_format_single_episode_with_prefixed_range_multi_episode_properly()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 5;
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 - City Sushi");
}
[Test]
public void should_format_single_anime_episode_with_prefixed_range_multi_episode_properly()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.MultiEpisodeStyle = 5;
_namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - 100 - City Sushi");
}
[Test]
public void should_format_prefixed_range_multi_episode_using_episode_separator()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - {season:0}x{episode:00} - {Episode Title}";
_namingConfig.MultiEpisodeStyle = 5;
Subject.BuildFileName(new List<Episode> { _episode1, _episode2, _episode3 }, _series, _episodeFile)
.Should().Be("South Park - 15x06-x08 - City Sushi");
}
}
}

@ -0,0 +1,35 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.Test.OrganizerTests
{
[TestFixture]
public class GetAlbumFolderFixture : CoreTest<FileNameBuilder>
{
private NamingConfig namingConfig;
[SetUp]
public void Setup()
{
namingConfig = NamingConfig.Default;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(namingConfig);
}
[TestCase("Venture Bros.", "Today", "{Artist.Name}.{Album.Title}", "Venture.Bros.Today")]
[TestCase("Venture Bros.", "Today", "{Artist Name} {Album Title}", "Venture Bros. Today")]
public void should_use_albumFolderFormat_to_build_folder_name(string artistName, string albumTitle, string format, string expected)
{
namingConfig.AlbumFolderFormat = format;
var artist = new Artist { Name = artistName };
var album = new Album { Title = albumTitle };
Subject.GetAlbumFolder(artist, album, namingConfig).Should().Be(expected);
}
}
}

@ -0,0 +1,39 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.Test.OrganizerTests
{
[TestFixture]
public class GetArtistFolderFixture : CoreTest<FileNameBuilder>
{
private NamingConfig namingConfig;
[SetUp]
public void Setup()
{
namingConfig = NamingConfig.Default;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(namingConfig);
}
[TestCase("Avenged Sevenfold", "{Artist Name}", "Avenged Sevenfold")]
[TestCase("Avenged Sevenfold", "{Artist.Name}", "Avenged.Sevenfold")]
[TestCase("AC/DC", "{Artist Name}", "AC+DC")]
[TestCase("In the Woods...", "{Artist.Name}", "In.the.Woods")]
[TestCase("3OH!3", "{Artist.Name}", "3OH!3")]
[TestCase("Avenged Sevenfold", ".{Artist.Name}.", "Avenged.Sevenfold")]
public void should_use_artistFolderFormat_to_build_folder_name(string artistName, string format, string expected)
{
namingConfig.ArtistFolderFormat = format;
var artist = new Artist { Name = artistName };
Subject.GetArtistFolder(artist).Should().Be(expected);
}
}
}

@ -1,34 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.OrganizerTests
{
[TestFixture]
public class GetSeasonFolderFixture : CoreTest<FileNameBuilder>
{
private NamingConfig namingConfig;
[SetUp]
public void Setup()
{
namingConfig = NamingConfig.Default;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(namingConfig);
}
[TestCase("Venture Bros.", 1, "{Series.Title}.{season:00}", "Venture.Bros.01")]
[TestCase("Venture Bros.", 1, "{Series Title} Season {season:00}", "Venture Bros. Season 01")]
public void should_use_seriesFolderFormat_to_build_folder_name(string seriesTitle, int seasonNumber, string format, string expected)
{
namingConfig.SeasonFolderFormat = format;
var series = new Series { Title = seriesTitle };
Subject.GetSeasonFolder(series, seasonNumber, namingConfig).Should().Be(expected);
}
}
}

@ -1,39 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.OrganizerTests
{
[TestFixture]
public class GetSeriesFolderFixture : CoreTest<FileNameBuilder>
{
private NamingConfig namingConfig;
[SetUp]
public void Setup()
{
namingConfig = NamingConfig.Default;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(namingConfig);
}
[TestCase("30 Rock", "{Series Title}", "30 Rock")]
[TestCase("30 Rock", "{Series.Title}", "30.Rock")]
[TestCase("24/7 Road to the NHL Winter Classic", "{Series Title}", "24+7 Road to the NHL Winter Classic")]
[TestCase("Venture Bros.", "{Series.Title}", "Venture.Bros")]
[TestCase(".hack", "{Series.Title}", "hack")]
[TestCase("30 Rock", ".{Series.Title}.", "30.Rock")]
public void should_use_seriesFolderFormat_to_build_folder_name(string seriesTitle, string format, string expected)
{
namingConfig.SeriesFolderFormat = format;
var series = new Series { Title = seriesTitle };
Subject.GetSeriesFolder(series).Should().Be(expected);
}
}
}

@ -0,0 +1,27 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(114)]
public class remove_tv_naming : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Delete.Column("RenameEpisodes").FromTable("NamingConfig");
Delete.Column("StandardEpisodeFormat").FromTable("NamingConfig");
Delete.Column("DailyEpisodeFormat").FromTable("NamingConfig");
Delete.Column("AnimeEpisodeFormat").FromTable("NamingConfig");
Delete.Column("SeasonFolderFormat").FromTable("NamingConfig");
Delete.Column("SeriesFolderFormat").FromTable("NamingConfig");
Delete.Column("MultiEpisodeStyle").FromTable("NamingConfig");
Execute.Sql("DELETE FROM Config WHERE [Key] = 'filedate'");
}
}
}

@ -1,11 +1,11 @@
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.MediaFiles.Commands
{
public class RenameFilesCommand : Command
{
public int SeriesId { get; set; }
public int ArtistId { get; set; }
public List<int> Files { get; set; }
public override bool SendUpdatesToClient => true;
@ -14,9 +14,9 @@ namespace NzbDrone.Core.MediaFiles.Commands
{
}
public RenameFilesCommand(int seriesId, List<int> files)
public RenameFilesCommand(int artistId, List<int> files)
{
SeriesId = seriesId;
ArtistId = artistId;
Files = files;
}
}

@ -1,15 +0,0 @@
using System.Collections.Generic;
namespace NzbDrone.Core.MediaFiles
{
public class EpisodeFileMoveResult
{
public EpisodeFileMoveResult()
{
OldFiles = new List<EpisodeFile>();
}
public EpisodeFile EpisodeFile { get; set; }
public List<EpisodeFile> OldFiles { get; set; }
}
}

@ -1,215 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles
{
public interface IMoveEpisodeFiles
{
EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, Series series);
EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode);
EpisodeFile CopyEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode);
}
public class EpisodeFileMovingService : IMoveEpisodeFiles
{
private readonly IEpisodeService _episodeService;
private readonly IUpdateEpisodeFileService _updateEpisodeFileService;
private readonly IBuildFileNames _buildFileNames;
private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider;
private readonly IMediaFileAttributeService _mediaFileAttributeService;
private readonly IEventAggregator _eventAggregator;
private readonly IConfigService _configService;
private readonly Logger _logger;
public EpisodeFileMovingService(IEpisodeService episodeService,
IUpdateEpisodeFileService updateEpisodeFileService,
IBuildFileNames buildFileNames,
IDiskTransferService diskTransferService,
IDiskProvider diskProvider,
IMediaFileAttributeService mediaFileAttributeService,
IEventAggregator eventAggregator,
IConfigService configService,
Logger logger)
{
_episodeService = episodeService;
_updateEpisodeFileService = updateEpisodeFileService;
_buildFileNames = buildFileNames;
_diskTransferService = diskTransferService;
_diskProvider = diskProvider;
_mediaFileAttributeService = mediaFileAttributeService;
_eventAggregator = eventAggregator;
_configService = configService;
_logger = logger;
}
public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, Series series)
{
var episodes = _episodeService.GetEpisodesByFileId(episodeFile.Id);
var newFileName = _buildFileNames.BuildFileName(episodes, series, episodeFile);
var filePath = _buildFileNames.BuildFilePath(series, episodes.First().SeasonNumber, newFileName, Path.GetExtension(episodeFile.RelativePath));
EnsureEpisodeFolder(episodeFile, series, episodes.Select(v => v.SeasonNumber).First(), filePath);
_logger.Debug("Renaming episode file: {0} to {1}", episodeFile, filePath);
return TransferFile(episodeFile, series, episodes, filePath, TransferMode.Move);
}
public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode)
{
var newFileName = _buildFileNames.BuildFileName(localEpisode.Episodes, localEpisode.Series, episodeFile);
var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(localEpisode.Path));
EnsureEpisodeFolder(episodeFile, localEpisode, filePath);
_logger.Debug("Moving episode file: {0} to {1}", episodeFile.Path, filePath);
return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Move);
}
public EpisodeFile CopyEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode)
{
var newFileName = _buildFileNames.BuildFileName(localEpisode.Episodes, localEpisode.Series, episodeFile);
var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(localEpisode.Path));
EnsureEpisodeFolder(episodeFile, localEpisode, filePath);
if (_configService.CopyUsingHardlinks)
{
_logger.Debug("Hardlinking episode file: {0} to {1}", episodeFile.Path, filePath);
return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.HardLinkOrCopy);
}
_logger.Debug("Copying episode file: {0} to {1}", episodeFile.Path, filePath);
return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Copy);
}
private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List<Episode> episodes, string destinationFilePath, TransferMode mode)
{
Ensure.That(episodeFile, () => episodeFile).IsNotNull();
Ensure.That(series, () => series).IsNotNull();
Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath();
var episodeFilePath = episodeFile.Path ?? Path.Combine(series.Path, episodeFile.RelativePath);
if (!_diskProvider.FileExists(episodeFilePath))
{
throw new FileNotFoundException("Episode file path does not exist", episodeFilePath);
}
if (episodeFilePath == destinationFilePath)
{
throw new SameFilenameException("File not moved, source and destination are the same", episodeFilePath);
}
_diskTransferService.TransferFile(episodeFilePath, destinationFilePath, mode);
episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilePath);
_updateEpisodeFileService.ChangeFileDateForFile(episodeFile, series, episodes);
try
{
_mediaFileAttributeService.SetFolderLastWriteTime(series.Path, episodeFile.DateAdded);
if (series.SeasonFolder)
{
var seasonFolder = Path.GetDirectoryName(destinationFilePath);
_mediaFileAttributeService.SetFolderLastWriteTime(seasonFolder, episodeFile.DateAdded);
}
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to set last write time");
}
_mediaFileAttributeService.SetFilePermissions(destinationFilePath);
return episodeFile;
}
private void EnsureEpisodeFolder(EpisodeFile episodeFile, LocalEpisode localEpisode, string filePath)
{
EnsureEpisodeFolder(episodeFile, localEpisode.Series, localEpisode.SeasonNumber, filePath);
}
private void EnsureEpisodeFolder(EpisodeFile episodeFile, Series series, int seasonNumber, string filePath)
{
var episodeFolder = Path.GetDirectoryName(filePath);
var seasonFolder = _buildFileNames.BuildSeasonPath(series, seasonNumber);
var seriesFolder = series.Path;
var rootFolder = new OsPath(seriesFolder).Directory.FullPath;
if (!_diskProvider.FolderExists(rootFolder))
{
throw new DirectoryNotFoundException(string.Format("Root folder '{0}' was not found.", rootFolder));
}
var changed = false;
var newEvent = new EpisodeFolderCreatedEvent(series, episodeFile);
if (!_diskProvider.FolderExists(seriesFolder))
{
CreateFolder(seriesFolder);
newEvent.SeriesFolder = seriesFolder;
changed = true;
}
if (seriesFolder != seasonFolder && !_diskProvider.FolderExists(seasonFolder))
{
CreateFolder(seasonFolder);
newEvent.SeasonFolder = seasonFolder;
changed = true;
}
if (seasonFolder != episodeFolder && !_diskProvider.FolderExists(episodeFolder))
{
CreateFolder(episodeFolder);
newEvent.EpisodeFolder = episodeFolder;
changed = true;
}
if (changed)
{
_eventAggregator.PublishEvent(newEvent);
}
}
private void CreateFolder(string directoryName)
{
Ensure.That(directoryName, () => directoryName).IsNotNullOrWhiteSpace();
var parentFolder = new OsPath(directoryName).Directory.FullPath;
if (!_diskProvider.FolderExists(parentFolder))
{
CreateFolder(parentFolder);
}
try
{
_diskProvider.CreateFolder(directoryName);
}
catch (IOException ex)
{
_logger.Error(ex, "Unable to create directory: {0}", directoryName);
}
_mediaFileAttributeService.SetFolderPermissions(directoryName);
}
}
}

@ -0,0 +1,20 @@
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.MediaFiles.Events
{
public class TrackFolderCreatedEvent : IEvent
{
public Artist Artist { get; private set; }
public TrackFile TrackFile { get; private set; }
public string ArtistFolder { get; set; }
public string AlbumFolder { get; set; }
public string TrackFolder { get; set; }
public TrackFolderCreatedEvent(Artist artist, TrackFile trackFile)
{
Artist = artist;
TrackFile = trackFile;
}
}
}

@ -1,9 +1,8 @@
namespace NzbDrone.Core.MediaFiles
namespace NzbDrone.Core.MediaFiles
{
public enum FileDateType
{
None = 0,
LocalAirDate = 1,
UtcAirDate = 2
AlbumReleaseDate = 1
}
}

@ -9,6 +9,7 @@ namespace NzbDrone.Core.MediaFiles
public interface IMediaFileRepository : IBasicRepository<TrackFile>
{
List<TrackFile> GetFilesByArtist(int artistId);
List<TrackFile> GetFilesByAlbum(int albumId);
List<TrackFile> GetFilesWithoutMediaInfo();
}
@ -29,5 +30,10 @@ namespace NzbDrone.Core.MediaFiles
{
return Query.Where(c => c.ArtistId == artistId).ToList();
}
public List<TrackFile> GetFilesByAlbum(int albumId)
{
return Query.Where(c => c.AlbumId == albumId).ToList();
}
}
}

@ -4,8 +4,6 @@ using System.Linq;
using NLog;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Common;
using NzbDrone.Core.Music;
using System;
@ -100,7 +98,7 @@ namespace NzbDrone.Core.MediaFiles
public List<TrackFile> GetFilesByAlbum(int artistId, int albumId)
{
return _mediaFileRepository.GetFilesByArtist(artistId);
return _mediaFileRepository.GetFilesByAlbum(albumId);
}
}
}

@ -0,0 +1,200 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using NLog;
using NLog.Fluent;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Common.Instrumentation.Extensions;
namespace NzbDrone.Core.MediaFiles.MediaInfo
{
public static class MediaInfoFormatter
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(MediaInfoFormatter));
public static decimal FormatAudioChannels(MediaInfoModel mediaInfo)
{
var audioChannelPositions = mediaInfo.AudioChannelPositions;
var audioChannelPositionsText = mediaInfo.AudioChannelPositionsText;
var audioChannels = mediaInfo.AudioChannels;
if (audioChannelPositions.IsNullOrWhiteSpace())
{
if (audioChannelPositionsText.IsNullOrWhiteSpace())
{
if (mediaInfo.SchemaRevision >= 3)
{
return audioChannels;
}
return 0;
}
return mediaInfo.AudioChannelPositionsText.ContainsIgnoreCase("LFE") ? audioChannels - 1 + 0.1m : audioChannels;
}
return audioChannelPositions.Replace("Object Based / ", "")
.Split(new [] { " / " }, StringSplitOptions.None)
.First()
.Split('/')
.Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture));
}
public static string FormatAudioCodec(MediaInfoModel mediaInfo)
{
if (mediaInfo.AudioCodecID == null)
{
return FormatAudioCodecLegacy(mediaInfo);
}
var audioFormat = mediaInfo.AudioFormat;
var audioCodecID = mediaInfo.AudioCodecID ?? string.Empty;
var audioProfile = mediaInfo.AudioProfile ?? string.Empty;
var audioCodecLibrary = mediaInfo.AudioCodecLibrary ?? string.Empty;
if (audioFormat.IsNullOrWhiteSpace())
{
return string.Empty;
}
if (audioFormat.EqualsIgnoreCase("AC-3"))
{
return "AC3";
}
if (audioFormat.EqualsIgnoreCase("E-AC-3"))
{
return "EAC3";
}
if (audioFormat.EqualsIgnoreCase("AAC"))
{
if (audioCodecID == "A_AAC/MPEG4/LC/SBR")
{
return "HE-AAC";
}
return "AAC";
}
if (audioFormat.EqualsIgnoreCase("DTS"))
{
return "DTS";
}
if (audioFormat.EqualsIgnoreCase("FLAC"))
{
return "FLAC";
}
if (audioFormat.Trim().EqualsIgnoreCase("mp3"))
{
return "MP3";
}
if (audioFormat.EqualsIgnoreCase("MPEG Audio"))
{
if (mediaInfo.AudioCodecID == "55" || mediaInfo.AudioCodecID == "A_MPEG/L3" || mediaInfo.AudioProfile == "Layer 3")
{
return "MP3";
}
if (mediaInfo.AudioCodecID == "A_MPEG/L2" || mediaInfo.AudioProfile == "Layer 2")
{
return "MP2";
}
}
if (audioFormat.EqualsIgnoreCase("Opus"))
{
return "Opus";
}
if (audioFormat.EqualsIgnoreCase("PCM"))
{
return "PCM";
}
if (audioFormat.EqualsIgnoreCase("TrueHD"))
{
return "TrueHD";
}
if (audioFormat.EqualsIgnoreCase("Vorbis"))
{
return "Vorbis";
}
if (audioFormat == "WMA")
{
return "WMA";
}
Logger.Debug()
.Message("Unknown audio format: '{0}' in '{1}'.", string.Join(", ", audioFormat, audioCodecID, audioProfile, audioCodecLibrary))
.WriteSentryWarn("UnknownAudioFormat", mediaInfo.ContainerFormat, audioFormat, audioCodecID)
.Write();
return audioFormat;
}
public static string FormatAudioCodecLegacy(MediaInfoModel mediaInfo)
{
var audioFormat = mediaInfo.AudioFormat;
if (audioFormat.IsNullOrWhiteSpace())
{
return audioFormat;
}
if (audioFormat.EqualsIgnoreCase("AC-3"))
{
return "AC3";
}
if (audioFormat.EqualsIgnoreCase("E-AC-3"))
{
return "EAC3";
}
if (audioFormat.EqualsIgnoreCase("AAC"))
{
return "AAC";
}
if (audioFormat.EqualsIgnoreCase("MPEG Audio") && mediaInfo.AudioProfile == "Layer 3")
{
return "MP3";
}
if (audioFormat.EqualsIgnoreCase("DTS"))
{
return "DTS";
}
if (audioFormat.EqualsIgnoreCase("TrueHD"))
{
return "TrueHD";
}
if (audioFormat.EqualsIgnoreCase("FLAC"))
{
return "FLAC";
}
if (audioFormat.EqualsIgnoreCase("Vorbis"))
{
return "Vorbis";
}
if (audioFormat.EqualsIgnoreCase("Opus"))
{
return "Opus";
}
return audioFormat;
}
}
}

@ -9,12 +9,19 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
{
public class MediaInfoModel : IEmbeddedDocument
{
public string ContainerFormat { get; set; }
public string VideoCodec { get; set; }
public string VideoFormat { get; set; }
public string VideoCodecID { get; set; }
public string VideoProfile { get; set; }
public string VideoCodecLibrary { get; set; }
public int VideoBitrate { get; set; }
public int VideoBitDepth { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public string AudioFormat { get; set; }
public string AudioCodecID { get; set; }
public string AudioCodecLibrary { get; set; }
public int AudioBitrate { get; set; }
public string AudioBitrateMode { get; set; }
public TimeSpan RunTime { get; set; }
@ -29,32 +36,5 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
public string ScanType { get; set; }
public int SchemaRevision { get; set; }
[JsonIgnore]
public decimal FormattedAudioChannels
{
get
{
if (AudioChannelPositions.IsNullOrWhiteSpace())
{
if (AudioChannelPositionsText.IsNullOrWhiteSpace())
{
if (SchemaRevision >= 3)
{
return AudioChannels;
}
return 0;
}
return AudioChannelPositionsText.ContainsIgnoreCase("LFE") ? AudioChannels - 1 + 0.1m : AudioChannels;
}
return AudioChannelPositions.Replace("Object Based / ", "")
.Split(new string[] { " / " }, StringSplitOptions.None)
.First()
.Split('/')
.Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture));
}
}
}
}

@ -3,11 +3,10 @@ using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.MediaFiles.MediaInfo
{
@ -19,7 +18,8 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
private readonly IConfigService _configService;
private readonly Logger _logger;
private const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 3;
public const int MINIMUM_MEDIA_INFO_SCHEMA_REVISION = 3;
public const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 4;
public UpdateMediaInfoService(IDiskProvider diskProvider,
IMediaFileService mediaFileService,
@ -66,9 +66,9 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
}
var allMediaFiles = _mediaFileService.GetFilesByArtist(message.Artist.Id);
var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < CURRENT_MEDIA_INFO_SCHEMA_REVISION).ToList();
var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < MINIMUM_MEDIA_INFO_SCHEMA_REVISION).ToList();
UpdateMediaInfo(message.Artist, filteredMediaFiles);
}
}
}
}

@ -1,4 +1,4 @@
using System;
using System;
using System.Globalization;
using System.IO;
using NLog;
@ -105,47 +105,37 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
int.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out generalRuntime);
string audioBitRateMode = mediaInfo.Get(StreamKind.Audio, 0, "BitRate_Mode");
string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate");
int aBindex = aBitRate.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
if (aBindex > 0)
{
aBitRate = aBitRate.Remove(aBindex);
}
string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate").Split(new [] { " /" }, StringSplitOptions.None)[0].Trim();
int.TryParse(aBitRate, out audioBitRate);
int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "StreamCount"), out streamCount);
string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)");
int aCindex = audioChannelsStr.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
if (aCindex > 0)
{
audioChannelsStr = audioChannelsStr.Remove(aCindex);
}
string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)").Split(new [] { " /" }, StringSplitOptions.None)[0].Trim();
var audioChannelPositions = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions/String2");
var audioChannelPositionsText = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions");
string audioLanguages = mediaInfo.Get(StreamKind.General, 0, "Audio_Language_List");
string audioProfile = mediaInfo.Get(StreamKind.Audio, 0, "Format_Profile");
int aPindex = audioProfile.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
if (aPindex > 0)
{
audioProfile = audioProfile.Remove(aPindex);
}
string videoProfile = mediaInfo.Get(StreamKind.Video, 0, "Format_Profile").Split(new [] { " /" }, StringSplitOptions.None)[0].Trim();
string audioProfile = mediaInfo.Get(StreamKind.Audio, 0, "Format_Profile").Split(new [] { " /" }, StringSplitOptions.None)[0].Trim();
int.TryParse(audioChannelsStr, out audioChannels);
var mediaInfoModel = new MediaInfoModel
{
VideoCodec = mediaInfo.Get(StreamKind.Video, 0, "Codec/String"),
ContainerFormat = mediaInfo.Get(StreamKind.General, 0, "Format"),
VideoFormat = mediaInfo.Get(StreamKind.Video, 0, "Format"),
VideoCodecID = mediaInfo.Get(StreamKind.Video, 0, "CodecID"),
VideoProfile = videoProfile,
VideoCodecLibrary = mediaInfo.Get(StreamKind.Video, 0, "Encoded_Library"),
VideoBitrate = videoBitRate,
VideoBitDepth = videoBitDepth,
Height = height,
Width = width,
AudioFormat = mediaInfo.Get(StreamKind.Audio, 0, "Format"),
AudioCodecID = mediaInfo.Get(StreamKind.Audio, 0, "CodecID"),
AudioProfile = audioProfile,
AudioCodecLibrary = mediaInfo.Get(StreamKind.Audio, 0, "Encoded_Library"),
AudioBitrateMode = audioBitRateMode,
AudioBitrate = audioBitRate,
RunTime = GetBestRuntime(audioRuntime, videoRuntime, generalRuntime),
@ -153,7 +143,6 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
AudioChannels = audioChannels,
AudioChannelPositions = audioChannelPositions,
AudioChannelPositionsText = audioChannelPositionsText,
AudioProfile = audioProfile.Trim(),
VideoFps = videoFrameRate,
AudioLanguages = audioLanguages,
Subtitles = subtitles,

@ -1,14 +0,0 @@
using System.Collections.Generic;
namespace NzbDrone.Core.MediaFiles
{
public class RenameEpisodeFilePreview
{
public int SeriesId { get; set; }
public int SeasonNumber { get; set; }
public List<int> EpisodeNumbers { get; set; }
public int EpisodeFileId { get; set; }
public string ExistingPath { get; set; }
public string NewPath { get; set; }
}
}

@ -1,181 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles
{
public interface IRenameEpisodeFileService
{
List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId);
List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId, int seasonNumber);
}
public class RenameEpisodeFileService : IRenameEpisodeFileService,
IExecute<RenameFilesCommand>,
IExecute<RenameSeriesCommand>
{
private readonly ISeriesService _seriesService;
private readonly IMediaFileService _mediaFileService;
private readonly IMoveEpisodeFiles _episodeFileMover;
private readonly IEventAggregator _eventAggregator;
private readonly IEpisodeService _episodeService;
private readonly IBuildFileNames _filenameBuilder;
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
public RenameEpisodeFileService(ISeriesService seriesService,
IMediaFileService mediaFileService,
IMoveEpisodeFiles episodeFileMover,
IEventAggregator eventAggregator,
IEpisodeService episodeService,
IBuildFileNames filenameBuilder,
IDiskProvider diskProvider,
Logger logger)
{
_seriesService = seriesService;
_mediaFileService = mediaFileService;
_episodeFileMover = episodeFileMover;
_eventAggregator = eventAggregator;
_episodeService = episodeService;
_filenameBuilder = filenameBuilder;
_diskProvider = diskProvider;
_logger = logger;
}
public List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId)
{
// TODO
throw new NotImplementedException();
//var series = _seriesService.GetSeries(seriesId);
//var episodes = _episodeService.GetEpisodeBySeries(seriesId);
//var files = _mediaFileService.GetFilesBySeries(seriesId);
//return GetPreviews(series, episodes, files)
// .OrderByDescending(e => e.SeasonNumber)
// .ThenByDescending(e => e.EpisodeNumbers.First())
// .ToList();
}
public List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId, int seasonNumber)
{
// TODO
throw new NotImplementedException();
//var series = _seriesService.GetSeries(seriesId);
//var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber);
//var files = _mediaFileService.GetFilesBySeason(seriesId, seasonNumber);
//return GetPreviews(series, episodes, files)
// .OrderByDescending(e => e.EpisodeNumbers.First()).ToList();
}
private IEnumerable<RenameEpisodeFilePreview> GetPreviews(Series series, List<Episode> episodes, List<EpisodeFile> files)
{
foreach (var f in files)
{
var file = f;
var episodesInFile = episodes.Where(e => e.EpisodeFileId == file.Id).ToList();
var episodeFilePath = Path.Combine(series.Path, file.RelativePath);
if (!episodesInFile.Any())
{
_logger.Warn("File ({0}) is not linked to any episodes", episodeFilePath);
continue;
}
var seasonNumber = episodesInFile.First().SeasonNumber;
var newName = _filenameBuilder.BuildFileName(episodesInFile, series, file);
var newPath = _filenameBuilder.BuildFilePath(series, seasonNumber, newName, Path.GetExtension(episodeFilePath));
if (!episodeFilePath.PathEquals(newPath, StringComparison.Ordinal))
{
yield return new RenameEpisodeFilePreview
{
SeriesId = series.Id,
SeasonNumber = seasonNumber,
EpisodeNumbers = episodesInFile.Select(e => e.EpisodeNumber).ToList(),
EpisodeFileId = file.Id,
ExistingPath = file.RelativePath,
NewPath = series.Path.GetRelativePath(newPath)
};
}
}
}
private void RenameFiles(List<EpisodeFile> episodeFiles, Series series)
{
// TODO
throw new NotImplementedException();
//var renamed = new List<EpisodeFile>();
//foreach (var episodeFile in episodeFiles)
//{
// var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
// try
// {
// _logger.Debug("Renaming episode file: {0}", episodeFile);
// _episodeFileMover.MoveEpisodeFile(episodeFile, series);
// _mediaFileService.Update(episodeFile);
// renamed.Add(episodeFile);
// _logger.Debug("Renamed episode file: {0}", episodeFile);
// }
// catch (SameFilenameException ex)
// {
// _logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename);
// }
// catch (Exception ex)
// {
// _logger.Error(ex, "Failed to rename file {0}", episodeFilePath);
// }
//}
//if (renamed.Any())
//{
// _diskProvider.RemoveEmptySubfolders(series.Path);
// _eventAggregator.PublishEvent(new SeriesRenamedEvent(series));
//}
}
public void Execute(RenameFilesCommand message)
{
// TODO
throw new NotImplementedException();
//var series = _seriesService.GetSeries(message.SeriesId);
//var episodeFiles = _mediaFileService.Get(message.Files);
//_logger.ProgressInfo("Renaming {0} files for {1}", episodeFiles.Count, series.Title);
//RenameFiles(episodeFiles, series);
//_logger.ProgressInfo("Selected episode files renamed for {0}", series.Title);
}
public void Execute(RenameSeriesCommand message)
{
// TODO
throw new NotImplementedException();
//_logger.Debug("Renaming all files for selected series");
//var seriesToRename = _seriesService.GetSeries(message.SeriesIds);
//foreach (var series in seriesToRename)
//{
// var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id);
// _logger.ProgressInfo("Renaming all files in series: {0}", series.Title);
// RenameFiles(episodeFiles, series);
// _logger.ProgressInfo("All episode files renamed for {0}", series.Title);
//}
}
}
}

@ -21,9 +21,7 @@ namespace NzbDrone.Core.MediaFiles
List<RenameTrackFilePreview> GetRenamePreviews(int artistId, int albumId);
}
public class RenameTrackFileService : IRenameTrackFileService,
IExecute<RenameFilesCommand>,
IExecute<RenameArtistCommand>
public class RenameTrackFileService : IRenameTrackFileService, IExecute<RenameFilesCommand>, IExecute<RenameArtistCommand>
{
private readonly IArtistService _artistService;
private readonly IAlbumService _albumService;
@ -58,32 +56,29 @@ namespace NzbDrone.Core.MediaFiles
public List<RenameTrackFilePreview> GetRenamePreviews(int artistId)
{
// TODO
throw new NotImplementedException();
//var artist = _artistService.GetArtist(artistId);
//var tracks = _trackService.GetTracksByArtist(artistId);
//var files = _mediaFileService.GetFilesByArtist(artistId);
//return GetPreviews(artist, tracks, files)
// .OrderByDescending(e => e.SeasonNumber)
// .ThenByDescending(e => e.TrackNumbers.First())
// .ToList();
var artist = _artistService.GetArtist(artistId);
var tracks = _trackService.GetTracksByArtist(artistId);
var files = _mediaFileService.GetFilesByArtist(artistId);
return GetPreviews(artist, tracks, files)
.OrderByDescending(e => e.AlbumId)
.ThenByDescending(e => e.TrackNumbers.First())
.ToList();
}
public List<RenameTrackFilePreview> GetRenamePreviews(int artistId, int albumId)
{
// TODO
//throw new NotImplementedException();
var artist = _artistService.GetArtist(artistId);
var album = _albumService.GetAlbum(albumId);
var tracks = _trackService.GetTracksByAlbum(artistId, albumId);
var files = _mediaFileService.GetFilesByAlbum(artistId, albumId);
return GetPreviews(artist, album, tracks, files)
return GetPreviews(artist, tracks, files)
.OrderByDescending(e => e.TrackNumbers.First()).ToList();
}
private IEnumerable<RenameTrackFilePreview> GetPreviews(Artist artist, Album album, List<Track> tracks, List<TrackFile> files)
private IEnumerable<RenameTrackFilePreview> GetPreviews(Artist artist, List<Track> tracks, List<TrackFile> files)
{
foreach (var f in files)
{
@ -97,7 +92,8 @@ namespace NzbDrone.Core.MediaFiles
continue;
}
var albumId = tracksInFile.First().AlbumId;
var album = _albumService.GetAlbum(tracksInFile.First().AlbumId);
var newName = _filenameBuilder.BuildTrackFileName(tracksInFile, artist, album, file);
var newPath = _filenameBuilder.BuildTrackFilePath(artist, album, newName, Path.GetExtension(trackFilePath));
@ -106,7 +102,7 @@ namespace NzbDrone.Core.MediaFiles
yield return new RenameTrackFilePreview
{
ArtistId = artist.Id,
AlbumId = albumId,
AlbumId = album.Id,
TrackNumbers = tracksInFile.Select(e => e.TrackNumber).ToList(),
TrackFileId = file.Id,
ExistingPath = file.RelativePath,
@ -118,68 +114,64 @@ namespace NzbDrone.Core.MediaFiles
private void RenameFiles(List<TrackFile> trackFiles, Artist artist)
{
// TODO
throw new NotImplementedException();
//var renamed = new List<TrackFile>();
//foreach (var trackFile in trackFiles)
//{
// var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath);
// try
// {
// _logger.Debug("Renaming track file: {0}", trackFile);
// _trackFileMover.MoveTrackFile(trackFile, artist);
// _mediaFileService.Update(trackFile);
// renamed.Add(trackFile);
// _logger.Debug("Renamed track file: {0}", trackFile);
// }
// catch (SameFilenameException ex)
// {
// _logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename);
// }
// catch (Exception ex)
// {
// _logger.Error(ex, "Failed to rename file {0}", trackFilePath);
// }
//}
//if (renamed.Any())
//{
// _diskProvider.RemoveEmptySubfolders(artist.Path);
// _eventAggregator.PublishEvent(new ArtistRenamedEvent(artist));
//}
var renamed = new List<TrackFile>();
foreach (var trackFile in trackFiles)
{
var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath);
try
{
_logger.Debug("Renaming track file: {0}", trackFile);
_trackFileMover.MoveTrackFile(trackFile, artist);
_mediaFileService.Update(trackFile);
renamed.Add(trackFile);
_logger.Debug("Renamed track file: {0}", trackFile);
}
catch (SameFilenameException ex)
{
_logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename);
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to rename file {0}", trackFilePath);
}
}
if (renamed.Any())
{
_diskProvider.RemoveEmptySubfolders(artist.Path);
_eventAggregator.PublishEvent(new ArtistRenamedEvent(artist));
}
}
public void Execute(RenameFilesCommand message)
{
// TODO
throw new NotImplementedException();
//var artist = _artistService.GetArtist(message.ArtistId);
//var trackFiles = _mediaFileService.Get(message.Files);
//_logger.ProgressInfo("Renaming {0} files for {1}", trackFiles.Count, artist.Title);
//RenameFiles(trackFiles, artist);
//_logger.ProgressInfo("Selected track files renamed for {0}", artist.Title);
var artist = _artistService.GetArtist(message.ArtistId);
var trackFiles = _mediaFileService.Get(message.Files);
_logger.ProgressInfo("Renaming {0} files for {1}", trackFiles.Count, artist.Name);
RenameFiles(trackFiles, artist);
_logger.ProgressInfo("Selected track files renamed for {0}", artist.Name);
}
public void Execute(RenameArtistCommand message)
{
// TODO
throw new NotImplementedException();
//_logger.Debug("Renaming all files for selected artist");
//var artistToRename = _artistService.GetArtist(message.ArtistIds);
//foreach (var artist in artistToRename)
//{
// var trackFiles = _mediaFileService.GetFilesByArtist(artist.Id);
// _logger.ProgressInfo("Renaming all files in artist: {0}", artist.Title);
// RenameFiles(trackFiles, artist);
// _logger.ProgressInfo("All track files renamed for {0}", artist.Title);
//}
_logger.Debug("Renaming all files for selected artist");
var artistToRename = _artistService.GetArtists(message.ArtistIds);
foreach (var artist in artistToRename)
{
var trackFiles = _mediaFileService.GetFilesByArtist(artist.Id);
_logger.ProgressInfo("Renaming all files in artist: {0}", artist.Name);
RenameFiles(trackFiles, artist);
_logger.ProgressInfo("All track files renamed for {0}", artist.Name);
}
}
}
}

@ -1,4 +1,4 @@
using NLog;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
@ -27,7 +27,8 @@ namespace NzbDrone.Core.MediaFiles
public class TrackFileMovingService : IMoveTrackFiles
{
private readonly ITrackService _trackService;
//private readonly IUpdateTrackFileService _updateTrackFileService;
private readonly IAlbumService _albumService;
private readonly IUpdateTrackFileService _updateTrackFileService;
private readonly IBuildFileNames _buildFileNames;
private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider;
@ -36,8 +37,9 @@ namespace NzbDrone.Core.MediaFiles
private readonly IConfigService _configService;
private readonly Logger _logger;
public TrackFileMovingService(ITrackService episodeService,
//IUpdateEpisodeFileService updateEpisodeFileService,
public TrackFileMovingService(ITrackService trackService,
IAlbumService albumService,
IUpdateTrackFileService updateTrackFileService,
IBuildFileNames buildFileNames,
IDiskTransferService diskTransferService,
IDiskProvider diskProvider,
@ -46,8 +48,9 @@ namespace NzbDrone.Core.MediaFiles
IConfigService configService,
Logger logger)
{
_trackService = episodeService;
//_updateTrackFileService = updateEpisodeFileService;
_trackService = trackService;
_albumService = albumService;
_updateTrackFileService = updateTrackFileService;
_buildFileNames = buildFileNames;
_diskTransferService = diskTransferService;
_diskProvider = diskProvider;
@ -59,112 +62,107 @@ namespace NzbDrone.Core.MediaFiles
public TrackFile MoveTrackFile(TrackFile trackFile, Artist artist)
{
throw new System.NotImplementedException();
// TODO
//var tracks = _trackService.GetTracksByFileId(trackFile.Id);
//var newFileName = _buildFileNames.BuildFileName(tracks, artist, trackFile);
//var filePath = _buildFileNames.BuildFilePath(artist, tracks.First(), trackFile.AlbumId, newFileName, Path.GetExtension(trackFile.RelativePath));
//EnsureAlbumFolder(trackFile, artist, tracks.Select(v => v.Album).First(), filePath);
var tracks = _trackService.GetTracksByFileId(trackFile.Id);
var album = _albumService.GetAlbum(trackFile.AlbumId);
var newFileName = _buildFileNames.BuildTrackFileName(tracks, artist, album, trackFile);
var filePath = _buildFileNames.BuildTrackFilePath(artist, album, newFileName, Path.GetExtension(trackFile.RelativePath));
//_logger.Debug("Renaming track file: {0} to {1}", trackFile, filePath);
EnsureTrackFolder(trackFile, artist, album, filePath);
//return TransferFile(trackFile, artist, tracks, filePath, TransferMode.Move);
_logger.Debug("Renaming track file: {0} to {1}", trackFile, filePath);
return TransferFile(trackFile, artist, tracks, filePath, TransferMode.Move);
}
public TrackFile MoveTrackFile(TrackFile trackFile, LocalTrack localTrack)
{
// TODO
throw new System.NotImplementedException();
//var newFileName = _buildFileNames.BuildFileName(localEpisode.Episodes, localEpisode.Series, episodeFile);
//var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(localEpisode.Path));
//EnsureEpisodeFolder(episodeFile, localEpisode, filePath);
var newFileName = _buildFileNames.BuildTrackFileName(localTrack.Tracks, localTrack.Artist, localTrack.Album, trackFile);
var filePath = _buildFileNames.BuildTrackFilePath(localTrack.Artist, localTrack.Album, newFileName, Path.GetExtension(localTrack.Path));
EnsureTrackFolder(trackFile, localTrack, filePath);
//_logger.Debug("Moving episode file: {0} to {1}", episodeFile.Path, filePath);
_logger.Debug("Moving track file: {0} to {1}", trackFile.Path, filePath);
//return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Move);
return TransferFile(trackFile, localTrack.Artist, localTrack.Tracks, filePath, TransferMode.Move);
}
public TrackFile CopyTrackFile(TrackFile trackFile, LocalTrack localTrack)
{
// TODO
throw new System.NotImplementedException();
//var newFileName = _buildFileNames.BuildFileName(localEpisode.Episodes, localEpisode.Series, episodeFile);
//var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(localEpisode.Path));
var newFileName = _buildFileNames.BuildTrackFileName(localTrack.Tracks, localTrack.Artist, localTrack.Album, trackFile);
var filePath = _buildFileNames.BuildTrackFilePath(localTrack.Artist, localTrack.Album, newFileName, Path.GetExtension(localTrack.Path));
//EnsureEpisodeFolder(episodeFile, localEpisode, filePath);
EnsureTrackFolder(trackFile, localTrack, filePath);
//if (_configService.CopyUsingHardlinks)
//{
// _logger.Debug("Hardlinking episode file: {0} to {1}", episodeFile.Path, filePath);
// return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.HardLinkOrCopy);
//}
if (_configService.CopyUsingHardlinks)
{
_logger.Debug("Hardlinking track file: {0} to {1}", trackFile.Path, filePath);
return TransferFile(trackFile, localTrack.Artist, localTrack.Tracks, filePath, TransferMode.HardLinkOrCopy);
}
//_logger.Debug("Copying episode file: {0} to {1}", episodeFile.Path, filePath);
//return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Copy);
_logger.Debug("Copying track file: {0} to {1}", trackFile.Path, filePath);
return TransferFile(trackFile, localTrack.Artist, localTrack.Tracks, filePath, TransferMode.Copy);
}
private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List<Episode> episodes, string destinationFilePath, TransferMode mode)
private TrackFile TransferFile(TrackFile trackFile, Artist artist, List<Track> tracks, string destinationFilePath, TransferMode mode)
{
// TODO
throw new System.NotImplementedException();
//Ensure.That(episodeFile, () => episodeFile).IsNotNull();
//Ensure.That(series, () => series).IsNotNull();
//Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath();
Ensure.That(trackFile, () => trackFile).IsNotNull();
Ensure.That(artist, () => artist).IsNotNull();
Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath();
//var episodeFilePath = episodeFile.Path ?? Path.Combine(series.Path, episodeFile.RelativePath);
var trackFilePath = trackFile.Path ?? Path.Combine(artist.Path, trackFile.RelativePath);
//if (!_diskProvider.FileExists(episodeFilePath))
//{
// throw new FileNotFoundException("Episode file path does not exist", episodeFilePath);
//}
if (!_diskProvider.FileExists(trackFilePath))
{
throw new FileNotFoundException("Track file path does not exist", trackFilePath);
}
//if (episodeFilePath == destinationFilePath)
//{
// throw new SameFilenameException("File not moved, source and destination are the same", episodeFilePath);
//}
if (trackFilePath == destinationFilePath)
{
throw new SameFilenameException("File not moved, source and destination are the same", trackFilePath);
}
//_diskTransferService.TransferFile(episodeFilePath, destinationFilePath, mode);
_diskTransferService.TransferFile(trackFilePath, destinationFilePath, mode);
//episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilePath);
trackFile.RelativePath = artist.Path.GetRelativePath(destinationFilePath);
//_updateTrackFileService.ChangeFileDateForFile(episodeFile, series, episodes);
_updateTrackFileService.ChangeFileDateForFile(trackFile, artist, tracks);
//try
//{
// _mediaFileAttributeService.SetFolderLastWriteTime(series.Path, episodeFile.DateAdded);
try
{
_mediaFileAttributeService.SetFolderLastWriteTime(artist.Path, trackFile.DateAdded);
// if (series.SeasonFolder)
// {
// var seasonFolder = Path.GetDirectoryName(destinationFilePath);
if (artist.AlbumFolder)
{
var albumFolder = Path.GetDirectoryName(destinationFilePath);
// _mediaFileAttributeService.SetFolderLastWriteTime(seasonFolder, episodeFile.DateAdded);
// }
//}
_mediaFileAttributeService.SetFolderLastWriteTime(albumFolder, trackFile.DateAdded);
}
}
//catch (Exception ex)
//{
// _logger.Warn(ex, "Unable to set last write time");
//}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to set last write time");
}
//_mediaFileAttributeService.SetFilePermissions(destinationFilePath);
_mediaFileAttributeService.SetFilePermissions(destinationFilePath);
//return episodeFile;
return trackFile;
}
private void EnsureEpisodeFolder(EpisodeFile episodeFile, LocalEpisode localEpisode, string filePath)
private void EnsureTrackFolder(TrackFile trackFile, LocalTrack localTrack, string filePath)
{
EnsureEpisodeFolder(episodeFile, localEpisode.Series, localEpisode.SeasonNumber, filePath);
EnsureTrackFolder(trackFile, localTrack.Artist, localTrack.Album, filePath);
}
private void EnsureEpisodeFolder(EpisodeFile episodeFile, Series series, int seasonNumber, string filePath)
private void EnsureTrackFolder(TrackFile trackFile, Artist artist, Album album, string filePath)
{
var episodeFolder = Path.GetDirectoryName(filePath);
var seasonFolder = _buildFileNames.BuildSeasonPath(series, seasonNumber);
var seriesFolder = series.Path;
var rootFolder = new OsPath(seriesFolder).Directory.FullPath;
var trackFolder = Path.GetDirectoryName(filePath);
var albumFolder = _buildFileNames.BuildAlbumPath(artist, album);
var artistFolder = artist.Path;
var rootFolder = new OsPath(artistFolder).Directory.FullPath;
if (!_diskProvider.FolderExists(rootFolder))
{
@ -172,26 +170,26 @@ namespace NzbDrone.Core.MediaFiles
}
var changed = false;
var newEvent = new EpisodeFolderCreatedEvent(series, episodeFile);
var newEvent = new TrackFolderCreatedEvent(artist, trackFile);
if (!_diskProvider.FolderExists(seriesFolder))
if (!_diskProvider.FolderExists(artistFolder))
{
CreateFolder(seriesFolder);
newEvent.SeriesFolder = seriesFolder;
CreateFolder(artistFolder);
newEvent.ArtistFolder = artistFolder;
changed = true;
}
if (seriesFolder != seasonFolder && !_diskProvider.FolderExists(seasonFolder))
if (artistFolder != albumFolder && !_diskProvider.FolderExists(albumFolder))
{
CreateFolder(seasonFolder);
newEvent.SeasonFolder = seasonFolder;
CreateFolder(albumFolder);
newEvent.AlbumFolder = albumFolder;
changed = true;
}
if (seasonFolder != episodeFolder && !_diskProvider.FolderExists(episodeFolder))
if (albumFolder != trackFolder && !_diskProvider.FolderExists(trackFolder))
{
CreateFolder(episodeFolder);
newEvent.EpisodeFolder = episodeFolder;
CreateFolder(trackFolder);
newEvent.TrackFolder = trackFolder;
changed = true;
}

@ -1,173 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles
{
public interface IUpdateEpisodeFileService
{
void ChangeFileDateForFile(EpisodeFile episodeFile, Series series, List<Episode> episodes);
}
public class UpdateEpisodeFileService : IUpdateEpisodeFileService,
IHandle<SeriesScannedEvent>
{
private readonly IDiskProvider _diskProvider;
private readonly IConfigService _configService;
private readonly IEpisodeService _episodeService;
private readonly Logger _logger;
public UpdateEpisodeFileService(IDiskProvider diskProvider,
IConfigService configService,
IEpisodeService episodeService,
Logger logger)
{
_diskProvider = diskProvider;
_configService = configService;
_episodeService = episodeService;
_logger = logger;
}
public void ChangeFileDateForFile(EpisodeFile episodeFile, Series series, List<Episode> episodes)
{
ChangeFileDate(episodeFile, series, episodes);
}
private bool ChangeFileDate(EpisodeFile episodeFile, Series series, List<Episode> episodes)
{
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
switch (_configService.FileDate)
{
case FileDateType.LocalAirDate:
{
var airDate = episodes.First().AirDate;
var airTime = series.AirTime;
if (airDate.IsNullOrWhiteSpace() || airTime.IsNullOrWhiteSpace())
{
return false;
}
return ChangeFileDateToLocalAirDate(episodeFilePath, airDate, airTime);
}
case FileDateType.UtcAirDate:
{
var airDateUtc = episodes.First().AirDateUtc;
if (!airDateUtc.HasValue)
{
return false;
}
return ChangeFileDateToUtcAirDate(episodeFilePath, airDateUtc.Value);
}
}
return false;
}
public void Handle(SeriesScannedEvent message)
{
if (_configService.FileDate == FileDateType.None)
{
return;
}
var episodes = _episodeService.EpisodesWithFiles(message.Series.Id);
var episodeFiles = new List<EpisodeFile>();
var updated = new List<EpisodeFile>();
foreach (var group in episodes.GroupBy(e => e.EpisodeFileId))
{
var episodesInFile = group.Select(e => e).ToList();
var episodeFile = episodesInFile.First().EpisodeFile;
episodeFiles.Add(episodeFile);
if (ChangeFileDate(episodeFile, message.Series, episodesInFile))
{
updated.Add(episodeFile);
}
}
if (updated.Any())
{
_logger.ProgressDebug("Changed file date for {0} files of {1} in {2}", updated.Count, episodeFiles.Count, message.Series.Title);
}
else
{
_logger.ProgressDebug("No file dates changed for {0}", message.Series.Title);
}
}
private bool ChangeFileDateToLocalAirDate(string filePath, string fileDate, string fileTime)
{
DateTime airDate;
if (DateTime.TryParse(fileDate + ' ' + fileTime, out airDate))
{
// avoiding false +ve checks and set date skewing by not using UTC (Windows)
DateTime oldDateTime = _diskProvider.FileGetLastWrite(filePath);
if (!DateTime.Equals(airDate, oldDateTime))
{
try
{
_diskProvider.FileSetLastWriteTime(filePath, airDate);
_logger.Debug("Date of file [{0}] changed from '{1}' to '{2}'", filePath, oldDateTime, airDate);
return true;
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to set date of file [" + filePath + "]");
}
}
}
else
{
_logger.Debug("Could not create valid date to change file [{0}]", filePath);
}
return false;
}
private bool ChangeFileDateToUtcAirDate(string filePath, DateTime airDateUtc)
{
DateTime oldLastWrite = _diskProvider.FileGetLastWrite(filePath);
if (!DateTime.Equals(airDateUtc, oldLastWrite))
{
try
{
_diskProvider.FileSetLastWriteTime(filePath, airDateUtc);
_logger.Debug("Date of file [{0}] changed from '{1}' to '{2}'", filePath, oldLastWrite, airDateUtc);
return true;
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to set date of file [" + filePath + "]");
}
}
return false;
}
}
}

@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.MediaFiles
{
public interface IUpdateTrackFileService
{
void ChangeFileDateForFile(TrackFile trackFile, Artist artist, List<Track> tracks);
}
public class UpdateTrackFileService : IUpdateTrackFileService,
IHandle<ArtistScannedEvent>
{
private readonly IDiskProvider _diskProvider;
private readonly IAlbumService _albumService;
private readonly IConfigService _configService;
private readonly ITrackService _trackService;
private readonly Logger _logger;
public UpdateTrackFileService(IDiskProvider diskProvider,
IConfigService configService,
ITrackService trackService,
IAlbumService albumService,
Logger logger)
{
_diskProvider = diskProvider;
_configService = configService;
_trackService = trackService;
_albumService = albumService;
_logger = logger;
}
public void ChangeFileDateForFile(TrackFile trackFile, Artist artist, List<Track> tracks)
{
ChangeFileDate(trackFile, artist, tracks);
}
private bool ChangeFileDate(TrackFile trackFile, Artist artist, List<Track> tracks)
{
var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath);
switch (_configService.FileDate)
{
case FileDateType.AlbumReleaseDate:
{
var album = _albumService.GetAlbum(trackFile.AlbumId);
if (!album.ReleaseDate.HasValue)
{
_logger.Debug("Could not create valid date to change file [{0}]", trackFilePath);
return false;
}
var relDate = album.ReleaseDate.Value;
// avoiding false +ve checks and set date skewing by not using UTC (Windows)
DateTime oldDateTime = _diskProvider.FileGetLastWrite(trackFilePath);
if (!DateTime.Equals(relDate, oldDateTime))
{
try
{
_diskProvider.FileSetLastWriteTime(trackFilePath, relDate);
_logger.Debug("Date of file [{0}] changed from '{1}' to '{2}'", trackFilePath, oldDateTime, relDate);
return true;
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to set date of file [" + trackFilePath + "]");
}
}
return false;
}
}
return false;
}
public void Handle(ArtistScannedEvent message)
{
if (_configService.FileDate == FileDateType.None)
{
return;
}
var episodes = _trackService.TracksWithFiles(message.Artist.Id);
var trackFiles = new List<TrackFile>();
var updated = new List<TrackFile>();
foreach (var group in episodes.GroupBy(e => e.TrackFileId))
{
var tracksInFile = group.Select(e => e).ToList();
var trackFile = tracksInFile.First().TrackFile;
trackFiles.Add(trackFile);
if (ChangeFileDate(trackFile, message.Artist, tracksInFile))
{
updated.Add(trackFile);
}
}
if (updated.Any())
{
_logger.ProgressDebug("Changed file date for {0} files of {1} in {2}", updated.Count, trackFiles.Count, message.Artist.Name);
}
else
{
_logger.ProgressDebug("No file dates changed for {0}", message.Artist.Name);
}
}
}
}

@ -1,4 +1,4 @@
using System.IO;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
@ -9,7 +9,6 @@ namespace NzbDrone.Core.MediaFiles
{
public interface IUpgradeMediaFiles
{
//EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false);
TrackFileMoveResult UpgradeTrackFile(TrackFile trackFile, LocalTrack localTrack, bool copyOnly = false);
}
@ -17,7 +16,6 @@ namespace NzbDrone.Core.MediaFiles
{
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly IMediaFileService _mediaFileService;
private readonly IMoveEpisodeFiles _episodeFileMover;
private readonly IMoveTrackFiles _trackFileMover;
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
@ -46,13 +44,13 @@ namespace NzbDrone.Core.MediaFiles
foreach (var existingFile in existingFiles)
{
var file = existingFile.First();
var episodeFilePath = Path.Combine(localTrack.Artist.Path, file.RelativePath);
var subfolder = _diskProvider.GetParentFolder(localTrack.Artist.Path).GetRelativePath(_diskProvider.GetParentFolder(episodeFilePath));
var trackFilePath = Path.Combine(localTrack.Artist.Path, file.RelativePath);
var subfolder = _diskProvider.GetParentFolder(localTrack.Artist.Path).GetRelativePath(_diskProvider.GetParentFolder(trackFilePath));
if (_diskProvider.FileExists(episodeFilePath))
if (_diskProvider.FileExists(trackFilePath))
{
_logger.Debug("Removing existing episode file: {0}", file);
_recycleBinProvider.DeleteFile(episodeFilePath, subfolder);
_logger.Debug("Removing existing track file: {0}", file);
_recycleBinProvider.DeleteFile(trackFilePath, subfolder);
}
moveFileResult.OldFiles.Add(file);

@ -1,10 +1,10 @@
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Tv.Commands
namespace NzbDrone.Core.Music.Commands
{
public class MoveSeriesCommand : Command
public class MoveArtistCommand : Command
{
public int SeriesId { get; set; }
public int ArtistId { get; set; }
public string SourcePath { get; set; }
public string DestinationPath { get; set; }
public string DestinationRootFolder { get; set; }

@ -1,16 +1,16 @@
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.Tv.Events
namespace NzbDrone.Core.Music.Events
{
public class SeriesMovedEvent : IEvent
public class ArtistMovedEvent : IEvent
{
public Series Series { get; set; }
public Artist Artist { get; set; }
public string SourcePath { get; set; }
public string DestinationPath { get; set; }
public SeriesMovedEvent(Series series, string sourcePath, string destinationPath)
public ArtistMovedEvent(Artist artist, string sourcePath, string destinationPath)
{
Series = series;
Artist = artist;
SourcePath = sourcePath;
DestinationPath = destinationPath;
}

@ -6,45 +6,45 @@ using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Tv.Commands;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.Music.Commands;
using NzbDrone.Core.Music.Events;
namespace NzbDrone.Core.Tv
namespace NzbDrone.Core.Music
{
public class MoveSeriesService : IExecute<MoveSeriesCommand>
public class MoveArtistService : IExecute<MoveArtistCommand>
{
private readonly ISeriesService _seriesService;
private readonly IArtistService _artistService;
private readonly IBuildFileNames _filenameBuilder;
private readonly IDiskTransferService _diskTransferService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
public MoveSeriesService(ISeriesService seriesService,
public MoveArtistService(IArtistService artistService,
IBuildFileNames filenameBuilder,
IDiskTransferService diskTransferService,
IEventAggregator eventAggregator,
Logger logger)
{
_seriesService = seriesService;
_artistService = artistService;
_filenameBuilder = filenameBuilder;
_diskTransferService = diskTransferService;
_eventAggregator = eventAggregator;
_logger = logger;
}
public void Execute(MoveSeriesCommand message)
public void Execute(MoveArtistCommand message)
{
var series = _seriesService.GetSeries(message.SeriesId);
var artist = _artistService.GetArtist(message.ArtistId);
var source = message.SourcePath;
var destination = message.DestinationPath;
if (!message.DestinationRootFolder.IsNullOrWhiteSpace())
{
_logger.Debug("Buiding destination path using root folder: {0} and the series title", message.DestinationRootFolder);
destination = Path.Combine(message.DestinationRootFolder, _filenameBuilder.GetSeriesFolder(series));
_logger.Debug("Buiding destination path using root folder: {0} and the artist name", message.DestinationRootFolder);
destination = Path.Combine(message.DestinationRootFolder, _filenameBuilder.GetArtistFolder(artist));
}
_logger.ProgressInfo("Moving {0} from '{1}' to '{2}'", series.Title, source, destination);
_logger.ProgressInfo("Moving {0} from '{1}' to '{2}'", artist.Name, source, destination);
//TODO: Move to transactional disk operations
try
@ -53,17 +53,17 @@ namespace NzbDrone.Core.Tv
}
catch (IOException ex)
{
_logger.Error(ex, "Unable to move series from '{0}' to '{1}'", source, destination);
_logger.Error(ex, "Unable to move artist from '{0}' to '{1}'", source, destination);
throw;
}
_logger.ProgressInfo("{0} moved successfully to {1}", series.Title, series.Path);
_logger.ProgressInfo("{0} moved successfully to {1}", artist.Name, artist.Path);
//Update the series path to the new path
series.Path = destination;
series = _seriesService.UpdateSeries(series);
//Update the artist path to the new path
artist.Path = destination;
artist = _artistService.UpdateArtist(artist);
_eventAggregator.PublishEvent(new SeriesMovedEvent(series, source, destination));
_eventAggregator.PublishEvent(new ArtistMovedEvent(artist, source, destination));
}
}
}

@ -295,6 +295,7 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="Datastore\Migration\105_rename_torrent_downloadstation.cs" />
<Compile Include="Datastore\Migration\114_remove_tv_naming.cs" />
<Compile Include="Datastore\Migration\113_music_blacklist.cs" />
<Compile Include="Datastore\Migration\112_music_history.cs" />
<Compile Include="Datastore\Migration\111_setup_music.cs" />
@ -731,7 +732,10 @@
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameArtistCommand.cs" />
<Compile Include="MediaFiles\Events\EpisodeFolderCreatedEvent.cs" />
<Compile Include="MediaFiles\Events\TrackFolderCreatedEvent.cs" />
<Compile Include="MediaFiles\Events\TrackDownloadedEvent.cs" />
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatter.cs" />
<Compile Include="MediaFiles\RenameTrackFilePreview.cs" />
<Compile Include="MediaFiles\RenameTrackFileService.cs" />
<Compile Include="MediaFiles\TrackFileMovingService.cs" />
@ -749,8 +753,6 @@
</Compile>
<Compile Include="MediaFiles\DownloadedEpisodesCommandService.cs" />
<Compile Include="MediaFiles\EpisodeFile.cs" />
<Compile Include="MediaFiles\EpisodeFileMoveResult.cs" />
<Compile Include="MediaFiles\EpisodeFileMovingService.cs" />
<Compile Include="MediaFiles\EpisodeImport\IImportDecisionEngineSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportApprovedEpisodes.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportDecisionMaker.cs" />
@ -771,7 +773,6 @@
<Compile Include="MediaFiles\Events\EpisodeDownloadedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeFileAddedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeFileDeletedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeFolderCreatedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeImportedEvent.cs" />
<Compile Include="MediaFiles\Commands\RescanArtistCommand.cs" />
<Compile Include="MediaFiles\Events\SeriesRenamedEvent.cs" />
@ -793,15 +794,13 @@
<Compile Include="MediaFiles\MediaInfo\UpdateMediaInfoService.cs" />
<Compile Include="MediaFiles\MediaInfo\VideoFileInfoReader.cs" />
<Compile Include="MediaFiles\RecycleBinProvider.cs" />
<Compile Include="MediaFiles\RenameEpisodeFilePreview.cs" />
<Compile Include="MediaFiles\RenameEpisodeFileService.cs" />
<Compile Include="MediaFiles\SameFilenameException.cs" />
<Compile Include="MediaFiles\TrackFile.cs" />
<Compile Include="MediaFiles\TrackImport\ImportApprovedTracks.cs" />
<Compile Include="MediaFiles\TrackImport\ImportDecision.cs" />
<Compile Include="MediaFiles\TrackImport\ImportResult.cs" />
<Compile Include="MediaFiles\TrackImport\Specifications\SameTracksImportSpecification.cs" />
<Compile Include="MediaFiles\UpdateEpisodeFileService.cs" />
<Compile Include="MediaFiles\UpdateTrackFileService.cs" />
<Compile Include="MediaFiles\UpgradeMediaFileService.cs" />
<Compile Include="Messaging\Commands\BackendCommandAttribute.cs" />
<Compile Include="Messaging\Commands\CleanupCommandMessagingService.cs" />
@ -828,6 +827,7 @@
<Compile Include="Messaging\IProcessMessage.cs" />
<Compile Include="MetadataSource\IProvideArtistInfo.cs" />
<Compile Include="MetadataSource\IProvideSeriesInfo.cs" />
<Compile Include="MetadataSource\ISearchForNewArtist.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ActorResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\AlbumResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ArtistInfoResource.cs" />
@ -861,9 +861,11 @@
<Compile Include="Extras\Metadata\MetadataRepository.cs" />
<Compile Include="Extras\Metadata\MetadataService.cs" />
<Compile Include="Extras\Metadata\MetadataType.cs" />
<Compile Include="MetadataSource\ISearchForNewArtist.cs" />
<Compile Include="Music\Commands\MoveArtistCommand.cs" />
<Compile Include="Music\Events\ArtistMovedEvent.cs" />
<Compile Include="Music\MonitoringOptions.cs" />
<Compile Include="Music\AlbumMonitoredService.cs" />
<Compile Include="Music\MoveArtistService.cs" />
<Compile Include="Music\TrackMonitoredService.cs" />
<Compile Include="Music\Member.cs" />
<Compile Include="Music\AddArtistOptions.cs" />
@ -945,6 +947,8 @@
<Compile Include="Notifications\Webhook\WebhookService.cs" />
<Compile Include="Notifications\Webhook\WebhookSettings.cs" />
<Compile Include="Notifications\Webhook\Webhook.cs" />
<Compile Include="Organizer\AbsoluteTrackFormat.cs" />
<Compile Include="Organizer\TrackFormat.cs" />
<Compile Include="Organizer\NamingConfigRepository.cs" />
<Compile Include="Notifications\Twitter\Twitter.cs" />
<Compile Include="Notifications\Twitter\TwitterService.cs" />
@ -1055,9 +1059,7 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="Notifications\Xbmc\XbmcSettings.cs" />
<Compile Include="Organizer\AbsoluteEpisodeFormat.cs" />
<Compile Include="Organizer\BasicNamingConfig.cs" />
<Compile Include="Organizer\EpisodeFormat.cs" />
<Compile Include="Organizer\EpisodeSortingType.cs" />
<Compile Include="Organizer\Exception.cs" />
<Compile Include="Organizer\FileNameBuilder.cs" />
@ -1143,7 +1145,6 @@
<Compile Include="Tv\AddSeriesOptions.cs" />
<Compile Include="Tv\AddSeriesService.cs" />
<Compile Include="Tv\AddSeriesValidator.cs" />
<Compile Include="Tv\Commands\MoveSeriesCommand.cs" />
<Compile Include="Tv\Commands\RefreshSeriesCommand.cs" />
<Compile Include="Tv\Episode.cs" />
<Compile Include="Tv\EpisodeAddedService.cs" />
@ -1157,11 +1158,9 @@
<Compile Include="Tv\Events\SeriesAddedEvent.cs" />
<Compile Include="Tv\Events\SeriesDeletedEvent.cs" />
<Compile Include="Tv\Events\SeriesEditedEvent.cs" />
<Compile Include="Tv\Events\SeriesMovedEvent.cs" />
<Compile Include="Tv\Events\SeriesRefreshStartingEvent.cs" />
<Compile Include="Tv\Events\SeriesUpdatedEvent.cs" />
<Compile Include="Tv\MonitoringOptions.cs" />
<Compile Include="Tv\MoveSeriesService.cs" />
<Compile Include="Tv\Ratings.cs" />
<Compile Include="Tv\RefreshEpisodeService.cs" />
<Compile Include="Tv\RefreshSeriesService.cs" />

@ -1,10 +0,0 @@
namespace NzbDrone.Core.Organizer
{
public class EpisodeFormat
{
public string Separator { get; set; }
public string EpisodePattern { get; set; }
public string EpisodeSeparator { get; set; }
public string SeasonEpisodePattern { get; set; }
}
}

@ -0,0 +1,9 @@
namespace NzbDrone.Core.Organizer
{
public class TrackFormat
{
public string Separator { get; set; }
public string TrackPattern { get; set; }
public string TrackSeparator { get; set; }
}
}

@ -2,8 +2,8 @@
{
public class BasicNamingConfig
{
public bool IncludeSeriesTitle { get; set; }
public bool IncludeEpisodeTitle { get; set; }
public bool IncludeArtistName { get; set; }
public bool IncludeAlbumTitle { get; set; }
public bool IncludeQuality { get; set; }
public bool ReplaceSpaces { get; set; }
public string Separator { get; set; }

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@ -9,6 +9,7 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
@ -17,28 +18,20 @@ namespace NzbDrone.Core.Organizer
{
public interface IBuildFileNames
{
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null);
string BuildTrackFileName(List<Track> tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null);
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
string BuildTrackFilePath(Artist artist, Album album, string fileName, string extension);
string BuildSeasonPath(Series series, int seasonNumber);
string BuildAlbumPath(Artist artist, Album album);
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
string GetArtistFolder(Artist artist, NamingConfig namingConfig = null);
string GetAlbumFolder(Artist artist, Album album, NamingConfig namingConfig = null);
string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null);
// TODO: Implement Music functions
//string GetArtistFolder(Artist artist, NamingConfig namingConfig = null);
}
public class FileNameBuilder : IBuildFileNames
{
private readonly INamingConfigService _namingConfigService;
private readonly IQualityDefinitionService _qualityDefinitionService;
private readonly ICached<EpisodeFormat[]> _episodeFormatCache;
private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache;
private readonly ICached<TrackFormat[]> _trackFormatCache;
private readonly ICached<AbsoluteTrackFormat[]> _absoluteTrackFormatCache;
private readonly Logger _logger;
private static readonly Regex TitleRegex = new Regex(@"\{(?<prefix>[- ._\[(]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9]+))?(?<suffix>[- ._)\]]*)\}",
@ -91,69 +84,11 @@ namespace NzbDrone.Core.Organizer
{
_namingConfigService = namingConfigService;
_qualityDefinitionService = qualityDefinitionService;
_episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat");
_absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat");
_trackFormatCache = cacheManager.GetCache<TrackFormat[]>(GetType(), "trackFormat");
_absoluteTrackFormatCache = cacheManager.GetCache<AbsoluteTrackFormat[]>(GetType(), "absoluteTrackFormat");
_logger = logger;
}
public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null)
{
if (namingConfig == null)
{
namingConfig = _namingConfigService.GetConfig();
}
if (!namingConfig.RenameEpisodes)
{
return GetOriginalTitle(episodeFile);
}
if (namingConfig.StandardEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Standard)
{
throw new NamingFormatException("Standard episode format cannot be empty");
}
if (namingConfig.DailyEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Daily)
{
throw new NamingFormatException("Daily episode format cannot be empty");
}
if (namingConfig.AnimeEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Anime)
{
throw new NamingFormatException("Anime episode format cannot be empty");
}
var pattern = namingConfig.StandardEpisodeFormat;
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
episodes = episodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber).ToList();
if (series.SeriesType == SeriesTypes.Daily)
{
pattern = namingConfig.DailyEpisodeFormat;
}
if (series.SeriesType == SeriesTypes.Anime && episodes.All(e => e.AbsoluteEpisodeNumber.HasValue))
{
pattern = namingConfig.AnimeEpisodeFormat;
}
pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig);
pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig);
AddSeriesTokens(tokenHandlers, series);
AddEpisodeTokens(tokenHandlers, episodes);
AddEpisodeFileTokens(tokenHandlers, episodeFile);
AddQualityTokens(tokenHandlers, series, episodeFile);
AddMediaInfoTokens(tokenHandlers, episodeFile);
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
return fileName;
}
public string BuildTrackFileName(List<Track> tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null)
{
if (namingConfig == null)
@ -175,8 +110,6 @@ namespace NzbDrone.Core.Organizer
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
tracks = tracks.OrderBy(e => e.AlbumId).ThenBy(e => e.TrackNumber).ToList();
//pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig);
pattern = FormatTrackNumberTokens(pattern, "", tracks);
//pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig);
@ -186,7 +119,7 @@ namespace NzbDrone.Core.Organizer
AddTrackTokens(tokenHandlers, tracks);
AddTrackFileTokens(tokenHandlers, trackFile);
AddQualityTokens(tokenHandlers, artist, trackFile);
//AddMediaInfoTokens(tokenHandlers, trackFile); TODO ReWork MediaInfo for Tracks
AddMediaInfoTokens(tokenHandlers, trackFile);
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
@ -195,15 +128,6 @@ namespace NzbDrone.Core.Organizer
return fileName;
}
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
{
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
var path = BuildSeasonPath(series, seasonNumber);
return Path.Combine(path, fileName + extension);
}
public string BuildTrackFilePath(Artist artist, Album album, string fileName, string extension)
{
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
@ -213,29 +137,6 @@ namespace NzbDrone.Core.Organizer
return Path.Combine(path, fileName + extension);
}
public string BuildSeasonPath(Series series, int seasonNumber)
{
var path = series.Path;
if (series.SeasonFolder)
{
if (seasonNumber == 0)
{
path = Path.Combine(path, "Specials");
}
else
{
var seasonFolder = GetSeasonFolder(series, seasonNumber);
seasonFolder = CleanFileName(seasonFolder);
path = Path.Combine(path, seasonFolder);
}
}
return path;
}
public string BuildAlbumPath(Artist artist, Album album)
{
var path = artist.Path;
@ -256,20 +157,19 @@ namespace NzbDrone.Core.Organizer
public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec)
{
var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault();
var trackFormat = GetTrackFormat(nameSpec.StandardTrackFormat).LastOrDefault();
if (episodeFormat == null)
if (trackFormat == null)
{
return new BasicNamingConfig();
}
var basicNamingConfig = new BasicNamingConfig
{
Separator = episodeFormat.Separator,
NumberStyle = episodeFormat.SeasonEpisodePattern
Separator = trackFormat.Separator
};
var titleTokens = TitleRegex.Matches(nameSpec.StandardEpisodeFormat);
var titleTokens = TitleRegex.Matches(nameSpec.StandardTrackFormat);
foreach (Match match in titleTokens)
{
@ -281,14 +181,14 @@ namespace NzbDrone.Core.Organizer
basicNamingConfig.ReplaceSpaces = true;
}
if (token.StartsWith("{Series", StringComparison.InvariantCultureIgnoreCase))
if (token.StartsWith("{Artist", StringComparison.InvariantCultureIgnoreCase))
{
basicNamingConfig.IncludeSeriesTitle = true;
basicNamingConfig.IncludeArtistName = true;
}
if (token.StartsWith("{Episode", StringComparison.InvariantCultureIgnoreCase))
if (token.StartsWith("{Album", StringComparison.InvariantCultureIgnoreCase))
{
basicNamingConfig.IncludeEpisodeTitle = true;
basicNamingConfig.IncludeAlbumTitle = true;
}
if (token.StartsWith("{Quality", StringComparison.InvariantCultureIgnoreCase))
@ -300,20 +200,6 @@ namespace NzbDrone.Core.Organizer
return basicNamingConfig;
}
public string GetSeriesFolder(Series series, NamingConfig namingConfig = null)
{
if (namingConfig == null)
{
namingConfig = _namingConfigService.GetConfig();
}
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
AddSeriesTokens(tokenHandlers, series);
return CleanFolderName(ReplaceTokens(namingConfig.SeriesFolderFormat, tokenHandlers, namingConfig));
}
public string GetArtistFolder(Artist artist, NamingConfig namingConfig = null)
{
if (namingConfig == null)
@ -328,21 +214,6 @@ namespace NzbDrone.Core.Organizer
return CleanFolderName(ReplaceTokens(namingConfig.ArtistFolderFormat, tokenHandlers, namingConfig));
}
public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null)
{
if (namingConfig == null)
{
namingConfig = _namingConfigService.GetConfig();
}
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
AddSeriesTokens(tokenHandlers, series);
AddSeasonTokens(tokenHandlers, seasonNumber);
return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig));
}
public string GetAlbumFolder(Artist artist, Album album, NamingConfig namingConfig = null)
{
if (namingConfig == null)
@ -387,12 +258,6 @@ namespace NzbDrone.Core.Organizer
return name.Trim(' ', '.');
}
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 AddArtistTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Artist artist)
{
tokenHandlers["{Artist Name}"] = m => artist.Name;
@ -413,163 +278,12 @@ namespace NzbDrone.Core.Organizer
}
}
private string AddSeasonEpisodeNumberingTokens(string pattern, Dictionary<string, Func<TokenMatch, string>> tokenHandlers, List<Episode> episodes, NamingConfig namingConfig)
{
var episodeFormats = GetEpisodeFormat(pattern).DistinctBy(v => v.SeasonEpisodePattern).ToList();
int index = 1;
foreach (var episodeFormat in episodeFormats)
{
var seasonEpisodePattern = episodeFormat.SeasonEpisodePattern;
string formatPattern;
switch ((MultiEpisodeStyle)namingConfig.MultiEpisodeStyle)
{
case MultiEpisodeStyle.Duplicate:
formatPattern = episodeFormat.Separator + episodeFormat.SeasonEpisodePattern;
seasonEpisodePattern = FormatNumberTokens(seasonEpisodePattern, formatPattern, episodes);
break;
case MultiEpisodeStyle.Repeat:
formatPattern = episodeFormat.EpisodeSeparator + episodeFormat.EpisodePattern;
seasonEpisodePattern = FormatNumberTokens(seasonEpisodePattern, formatPattern, episodes);
break;
case MultiEpisodeStyle.Scene:
formatPattern = "-" + episodeFormat.EpisodeSeparator + episodeFormat.EpisodePattern;
seasonEpisodePattern = FormatNumberTokens(seasonEpisodePattern, formatPattern, episodes);
break;
case MultiEpisodeStyle.Range:
formatPattern = "-" + episodeFormat.EpisodePattern;
seasonEpisodePattern = FormatRangeNumberTokens(seasonEpisodePattern, formatPattern, episodes);
break;
case MultiEpisodeStyle.PrefixedRange:
formatPattern = "-" + episodeFormat.EpisodeSeparator + episodeFormat.EpisodePattern;
seasonEpisodePattern = FormatRangeNumberTokens(seasonEpisodePattern, formatPattern, episodes);
break;
//MultiEpisodeStyle.Extend
default:
formatPattern = "-" + episodeFormat.EpisodePattern;
seasonEpisodePattern = FormatNumberTokens(seasonEpisodePattern, formatPattern, episodes);
break;
}
var token = string.Format("{{Season Episode{0}}}", index++);
pattern = pattern.Replace(episodeFormat.SeasonEpisodePattern, token);
tokenHandlers[token] = m => seasonEpisodePattern;
}
AddSeasonTokens(tokenHandlers, episodes.First().SeasonNumber);
if (episodes.Count > 1)
{
tokenHandlers["{Episode}"] = m => episodes.First().EpisodeNumber.ToString(m.CustomFormat) + "-" + episodes.Last().EpisodeNumber.ToString(m.CustomFormat);
}
else
{
tokenHandlers["{Episode}"] = m => episodes.First().EpisodeNumber.ToString(m.CustomFormat);
}
return pattern;
}
private string AddAbsoluteNumberingTokens(string pattern, Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series, List<Episode> episodes, NamingConfig namingConfig)
{
var absoluteEpisodeFormats = GetAbsoluteFormat(pattern).DistinctBy(v => v.AbsoluteEpisodePattern).ToList();
int index = 1;
foreach (var absoluteEpisodeFormat in absoluteEpisodeFormats)
{
if (series.SeriesType != SeriesTypes.Anime)
{
pattern = pattern.Replace(absoluteEpisodeFormat.AbsoluteEpisodePattern, "");
continue;
}
var absoluteEpisodePattern = absoluteEpisodeFormat.AbsoluteEpisodePattern;
string formatPattern;
switch ((MultiEpisodeStyle) namingConfig.MultiEpisodeStyle)
{
case MultiEpisodeStyle.Duplicate:
formatPattern = absoluteEpisodeFormat.Separator + absoluteEpisodeFormat.AbsoluteEpisodePattern;
absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, episodes);
break;
case MultiEpisodeStyle.Repeat:
var repeatSeparator = absoluteEpisodeFormat.Separator.Trim().IsNullOrWhiteSpace() ? " " : absoluteEpisodeFormat.Separator.Trim();
formatPattern = repeatSeparator + absoluteEpisodeFormat.AbsoluteEpisodePattern;
absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, episodes);
break;
case MultiEpisodeStyle.Scene:
formatPattern = "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern;
absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, episodes);
break;
case MultiEpisodeStyle.Range:
case MultiEpisodeStyle.PrefixedRange:
formatPattern = "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern;
var eps = new List<Episode> {episodes.First()};
if (episodes.Count > 1) eps.Add(episodes.Last());
absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, eps);
break;
//MultiEpisodeStyle.Extend
default:
formatPattern = "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern;
absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, episodes);
break;
}
var token = string.Format("{{Absolute Pattern{0}}}", index++);
pattern = pattern.Replace(absoluteEpisodeFormat.AbsoluteEpisodePattern, token);
tokenHandlers[token] = m => absoluteEpisodePattern;
}
return pattern;
}
private void AddSeasonTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, int seasonNumber)
{
tokenHandlers["{Season}"] = m => seasonNumber.ToString(m.CustomFormat);
}
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, "+");
tokenHandlers["{Episode CleanTitle}"] = m => CleanTitle(GetEpisodeTitle(episodes, "and"));
}
private void AddTrackTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, List<Track> tracks)
{
tokenHandlers["{Track Title}"] = m => GetTrackTitle(tracks, "+");
tokenHandlers["{Track CleanTitle}"] = m => CleanTitle(GetTrackTitle(tracks, "and"));
}
private void AddEpisodeFileTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
{
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile);
tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(episodeFile);
tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Lidarr");
}
private void AddTrackFileTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, TrackFile trackFile)
{
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(trackFile);
@ -601,79 +315,20 @@ namespace NzbDrone.Core.Organizer
//tokenHandlers["{Quality Real}"] = m => qualityReal;
}
private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
{
if (episodeFile.MediaInfo == null) return;
string videoCodec;
switch (episodeFile.MediaInfo.VideoCodec)
{
case "AVC":
if (episodeFile.SceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(episodeFile.SceneName).Contains("h264"))
{
videoCodec = "h264";
}
else
{
videoCodec = "x264";
}
break;
case "V_MPEGH/ISO/HEVC":
if (episodeFile.SceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(episodeFile.SceneName).Contains("h265"))
{
videoCodec = "h265";
}
else
{
videoCodec = "x265";
}
break;
case "MPEG-2 Video":
videoCodec = "MPEG2";
break;
default:
videoCodec = episodeFile.MediaInfo.VideoCodec;
break;
}
string audioCodec;
switch (episodeFile.MediaInfo.AudioFormat)
{
case "AC-3":
audioCodec = "AC3";
break;
case "E-AC-3":
audioCodec = "EAC3";
break;
case "MPEG Audio":
if (episodeFile.MediaInfo.AudioProfile == "Layer 3")
{
audioCodec = "MP3";
}
else
{
audioCodec = episodeFile.MediaInfo.AudioFormat;
}
break;
case "DTS":
audioCodec = episodeFile.MediaInfo.AudioFormat;
break;
default:
audioCodec = episodeFile.MediaInfo.AudioFormat;
break;
}
var mediaInfoAudioLanguages = GetLanguagesToken(episodeFile.MediaInfo.AudioLanguages);
private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, TrackFile trackFile)
{
if (trackFile.MediaInfo == null)
{
return;
}
var audioCodec = MediaInfoFormatter.FormatAudioCodec(trackFile.MediaInfo);
var audioChannels = MediaInfoFormatter.FormatAudioChannels(trackFile.MediaInfo);
var mediaInfoAudioLanguages = GetLanguagesToken(trackFile.MediaInfo.AudioLanguages);
if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace())
{
mediaInfoAudioLanguages = string.Format("[{0}]", mediaInfoAudioLanguages);
mediaInfoAudioLanguages = $"[{mediaInfoAudioLanguages}]";
}
if (mediaInfoAudioLanguages == "[EN]")
@ -681,28 +336,24 @@ namespace NzbDrone.Core.Organizer
mediaInfoAudioLanguages = string.Empty;
}
var mediaInfoSubtitleLanguages = GetLanguagesToken(episodeFile.MediaInfo.Subtitles);
var mediaInfoSubtitleLanguages = GetLanguagesToken(trackFile.MediaInfo.Subtitles);
if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace())
{
mediaInfoSubtitleLanguages = string.Format("[{0}]", mediaInfoSubtitleLanguages);
mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]";
}
var videoBitDepth = episodeFile.MediaInfo.VideoBitDepth > 0 ? episodeFile.MediaInfo.VideoBitDepth.ToString() : string.Empty;
var audioChannels = episodeFile.MediaInfo.FormattedAudioChannels > 0 ?
episodeFile.MediaInfo.FormattedAudioChannels.ToString("F1", CultureInfo.InvariantCulture) :
var videoBitDepth = trackFile.MediaInfo.VideoBitDepth > 0 ? trackFile.MediaInfo.VideoBitDepth.ToString() : string.Empty;
var audioChannelsFormatted = audioChannels > 0 ?
audioChannels.ToString("F1", CultureInfo.InvariantCulture) :
string.Empty;
tokenHandlers["{MediaInfo Video}"] = m => videoCodec;
tokenHandlers["{MediaInfo VideoCodec}"] = m => videoCodec;
tokenHandlers["{MediaInfo VideoBitDepth}"] = m => videoBitDepth;
tokenHandlers["{MediaInfo Audio}"] = m => audioCodec;
tokenHandlers["{MediaInfo AudioCodec}"] = m => audioCodec;
tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannels;
tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannelsFormatted;
tokenHandlers["{MediaInfo Simple}"] = m => string.Format("{0} {1}", videoCodec, audioCodec);
tokenHandlers["{MediaInfo Simple}"] = m => $"{audioCodec}";
tokenHandlers["{MediaInfo Full}"] = m => string.Format("{0} {1}{2} {3}", videoCodec, audioCodec, mediaInfoAudioLanguages, mediaInfoSubtitleLanguages);
tokenHandlers["{MediaInfo Full}"] = m => $"{audioCodec}{mediaInfoAudioLanguages} {mediaInfoSubtitleLanguages}";
}
private string GetLanguagesToken(string mediaInfoLanguages)
@ -782,20 +433,6 @@ namespace NzbDrone.Core.Organizer
return replacementText;
}
private string FormatNumberTokens(string basePattern, string formatPattern, List<Episode> episodes)
{
var pattern = string.Empty;
for (int i = 0; i < episodes.Count; i++)
{
var patternToReplace = i == 0 ? basePattern : formatPattern;
pattern += EpisodeRegex.Replace(patternToReplace, match => ReplaceNumberToken(match.Groups["episode"].Value, episodes[i].EpisodeNumber));
}
return ReplaceSeasonTokens(pattern, episodes.First().SeasonNumber);
}
private string FormatTrackNumberTokens(string basePattern, string formatPattern, List<Track> tracks)
{
var pattern = string.Empty;
@ -810,34 +447,6 @@ namespace NzbDrone.Core.Organizer
return pattern;
}
private string FormatAbsoluteNumberTokens(string basePattern, string formatPattern, List<Episode> episodes)
{
var pattern = string.Empty;
for (int i = 0; i < episodes.Count; i++)
{
var patternToReplace = i == 0 ? basePattern : formatPattern;
pattern += AbsoluteEpisodeRegex.Replace(patternToReplace, match => ReplaceNumberToken(match.Groups["absolute"].Value, episodes[i].AbsoluteEpisodeNumber.Value));
}
return ReplaceSeasonTokens(pattern, episodes.First().SeasonNumber);
}
private string FormatRangeNumberTokens(string seasonEpisodePattern, string formatPattern, List<Episode> episodes)
{
var eps = new List<Episode> { episodes.First() };
if (episodes.Count > 1) eps.Add(episodes.Last());
return FormatNumberTokens(seasonEpisodePattern, formatPattern, eps);
}
private string ReplaceSeasonTokens(string pattern, int seasonNumber)
{
return SeasonRegex.Replace(pattern, match => ReplaceNumberToken(match.Groups["season"].Value, seasonNumber));
}
private string ReplaceNumberToken(string token, int value)
{
var split = token.Trim('{', '}').Split(':');
@ -846,52 +455,27 @@ namespace NzbDrone.Core.Organizer
return value.ToString(split[1]);
}
private EpisodeFormat[] GetEpisodeFormat(string pattern)
private TrackFormat[] GetTrackFormat(string pattern)
{
return _episodeFormatCache.Get(pattern, () => SeasonEpisodePatternRegex.Matches(pattern).OfType<Match>()
.Select(match => new EpisodeFormat
return _trackFormatCache.Get(pattern, () => SeasonEpisodePatternRegex.Matches(pattern).OfType<Match>()
.Select(match => new TrackFormat
{
EpisodeSeparator = match.Groups["episodeSeparator"].Value,
TrackSeparator = match.Groups["episodeSeparator"].Value,
Separator = match.Groups["separator"].Value,
EpisodePattern = match.Groups["episode"].Value,
SeasonEpisodePattern = match.Groups["seasonEpisode"].Value,
TrackPattern = match.Groups["episode"].Value,
}).ToArray());
}
private AbsoluteEpisodeFormat[] GetAbsoluteFormat(string pattern)
private AbsoluteTrackFormat[] GetAbsoluteFormat(string pattern)
{
return _absoluteEpisodeFormatCache.Get(pattern, () => AbsoluteEpisodePatternRegex.Matches(pattern).OfType<Match>()
.Select(match => new AbsoluteEpisodeFormat
return _absoluteTrackFormatCache.Get(pattern, () => AbsoluteEpisodePatternRegex.Matches(pattern).OfType<Match>()
.Select(match => new AbsoluteTrackFormat
{
Separator = match.Groups["separator"].Value.IsNotNullOrWhiteSpace() ? match.Groups["separator"].Value : "-",
AbsoluteEpisodePattern = match.Groups["absolute"].Value
AbsoluteTrackPattern = match.Groups["absolute"].Value
}).ToArray());
}
private string GetEpisodeTitle(List<Episode> episodes, string separator)
{
separator = string.Format(" {0} ", separator.Trim());
if (episodes.Count == 1)
{
return episodes.First().Title.TrimEnd(EpisodeTitleTrimCharacters);
}
var titles = episodes.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters))
.Select(CleanupEpisodeTitle)
.Distinct()
.ToList();
if (titles.All(t => t.IsNullOrWhiteSpace()))
{
titles = episodes.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters))
.Distinct()
.ToList();
}
return string.Join(separator, titles);
}
private string GetTrackTitle(List<Track> tracks, string separator)
{
separator = string.Format(" {0} ", separator.Trim());
@ -902,7 +486,7 @@ namespace NzbDrone.Core.Organizer
}
var titles = tracks.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters))
.Select(CleanupEpisodeTitle)
.Select(CleanupTrackTitle)
.Distinct()
.ToList();
@ -916,7 +500,7 @@ namespace NzbDrone.Core.Organizer
return string.Join(separator, titles);
}
private string CleanupEpisodeTitle(string title)
private string CleanupTrackTitle(string title)
{
//this will remove (1),(2) from the end of multi part episodes.
return MultiPartCleanupRegex.Replace(title, string.Empty).Trim();

@ -1,7 +1,6 @@
using System.Collections.Generic;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
using NzbDrone.Core.MediaFiles.MediaInfo;
@ -9,14 +8,8 @@ namespace NzbDrone.Core.Organizer
{
public interface IFilenameSampleService
{
SampleResult GetStandardSample(NamingConfig nameSpec);
SampleResult GetStandardTrackSample(NamingConfig nameSpec);
SampleResult GetMultiEpisodeSample(NamingConfig nameSpec);
SampleResult GetDailySample(NamingConfig nameSpec);
SampleResult GetAnimeSample(NamingConfig nameSpec);
SampleResult GetAnimeMultiEpisodeSample(NamingConfig nameSpec);
string GetSeriesFolderSample(NamingConfig nameSpec);
string GetSeasonFolderSample(NamingConfig nameSpec);
string GetArtistFolderSample(NamingConfig nameSpec);
string GetAlbumFolderSample(NamingConfig nameSpec);
}
@ -24,35 +17,17 @@ namespace NzbDrone.Core.Organizer
public class FileNameSampleService : IFilenameSampleService
{
private readonly IBuildFileNames _buildFileNames;
private static Series _standardSeries;
private static Artist _standardArtist;
private static Album _standardAlbum;
private static Track _track1;
private static Series _dailySeries;
private static Series _animeSeries;
private static Episode _episode1;
private static Episode _episode2;
private static Episode _episode3;
private static List<Episode> _singleEpisode;
private static List<Track> _singleTrack;
private static List<Episode> _multiEpisodes;
private static EpisodeFile _singleEpisodeFile;
private static TrackFile _singleTrackFile;
private static EpisodeFile _multiEpisodeFile;
private static EpisodeFile _dailyEpisodeFile;
private static EpisodeFile _animeEpisodeFile;
private static EpisodeFile _animeMultiEpisodeFile;
public FileNameSampleService(IBuildFileNames buildFileNames)
{
_buildFileNames = buildFileNames;
_standardSeries = new Series
{
SeriesType = SeriesTypes.Standard,
Title = "Series Title (2010)"
};
_standardArtist = new Artist
{
Name = "Artist Name"
@ -64,18 +39,6 @@ namespace NzbDrone.Core.Organizer
ReleaseDate = System.DateTime.Today
};
_dailySeries = new Series
{
SeriesType = SeriesTypes.Daily,
Title = "Series Title (2010)"
};
_animeSeries = new Series
{
SeriesType = SeriesTypes.Anime,
Title = "Series Title (2010)"
};
_track1 = new Track
{
TrackNumber = 3,
@ -84,66 +47,19 @@ namespace NzbDrone.Core.Organizer
};
_episode1 = new Episode
{
SeasonNumber = 1,
EpisodeNumber = 1,
Title = "Episode Title (1)",
AirDate = "2013-10-30",
AbsoluteEpisodeNumber = 1,
};
_episode2 = new Episode
{
SeasonNumber = 1,
EpisodeNumber = 2,
Title = "Episode Title (2)",
AbsoluteEpisodeNumber = 2
};
_episode3 = new Episode
{
SeasonNumber = 1,
EpisodeNumber = 3,
Title = "Episode Title (3)",
AbsoluteEpisodeNumber = 3
};
_singleEpisode = new List<Episode> { _episode1 };
_singleTrack = new List<Track> { _track1 };
_multiEpisodes = new List<Episode> { _episode1, _episode2, _episode3 };
var mediaInfo = new MediaInfoModel()
{
VideoCodec = "AVC",
VideoBitDepth = 8,
AudioFormat = "DTS",
AudioFormat = "FLAC",
AudioChannels = 6,
AudioChannelPositions = "3/2/0.1",
AudioLanguages = "English",
Subtitles = "English/German"
};
var mediaInfoAnime = new MediaInfoModel()
{
VideoCodec = "AVC",
VideoBitDepth = 8,
AudioFormat = "DTS",
AudioChannels = 6,
AudioChannelPositions = "3/2/0.1",
AudioLanguages = "Japanese",
Subtitles = "Japanese/English"
};
_singleEpisodeFile = new EpisodeFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(2)),
RelativePath = "Series.Title.S01E01.720p.HDTV.x264-EVOLVE.mkv",
SceneName = "Series.Title.S01E01.720p.HDTV.x264-EVOLVE",
ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfo
};
_singleTrackFile = new TrackFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(2)),
@ -153,54 +69,6 @@ namespace NzbDrone.Core.Organizer
MediaInfo = mediaInfo
};
_multiEpisodeFile = new EpisodeFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(2)),
RelativePath = "Series.Title.S01E01-E03.720p.HDTV.x264-EVOLVE.mkv",
SceneName = "Series.Title.S01E01-E03.720p.HDTV.x264-EVOLVE",
ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfo,
};
_dailyEpisodeFile = new EpisodeFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(2)),
RelativePath = "Series.Title.2013.10.30.HDTV.x264-EVOLVE.mkv",
SceneName = "Series.Title.2013.10.30.HDTV.x264-EVOLVE",
ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfo
};
_animeEpisodeFile = new EpisodeFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(2)),
RelativePath = "[RlsGroup] Series Title - 001 [720p].mkv",
SceneName = "[RlsGroup] Series Title - 001 [720p]",
ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfoAnime
};
_animeMultiEpisodeFile = new EpisodeFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(2)),
RelativePath = "[RlsGroup] Series Title - 001 - 103 [720p].mkv",
SceneName = "[RlsGroup] Series Title - 001 - 103 [720p]",
ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfoAnime
};
}
public SampleResult GetStandardSample(NamingConfig nameSpec)
{
var result = new SampleResult
{
FileName = BuildSample(_singleEpisode, _standardSeries, _singleEpisodeFile, nameSpec),
Series = _standardSeries,
Episodes = _singleEpisode,
EpisodeFile = _singleEpisodeFile
};
return result;
}
public SampleResult GetStandardTrackSample(NamingConfig nameSpec)
@ -217,68 +85,6 @@ namespace NzbDrone.Core.Organizer
return result;
}
public SampleResult GetMultiEpisodeSample(NamingConfig nameSpec)
{
var result = new SampleResult
{
FileName = BuildSample(_multiEpisodes, _standardSeries, _multiEpisodeFile, nameSpec),
Series = _standardSeries,
Episodes = _multiEpisodes,
EpisodeFile = _multiEpisodeFile
};
return result;
}
public SampleResult GetDailySample(NamingConfig nameSpec)
{
var result = new SampleResult
{
FileName = BuildSample(_singleEpisode, _dailySeries, _dailyEpisodeFile, nameSpec),
Series = _dailySeries,
Episodes = _singleEpisode,
EpisodeFile = _dailyEpisodeFile
};
return result;
}
public SampleResult GetAnimeSample(NamingConfig nameSpec)
{
var result = new SampleResult
{
FileName = BuildSample(_singleEpisode, _animeSeries, _animeEpisodeFile, nameSpec),
Series = _animeSeries,
Episodes = _singleEpisode,
EpisodeFile = _animeEpisodeFile
};
return result;
}
public SampleResult GetAnimeMultiEpisodeSample(NamingConfig nameSpec)
{
var result = new SampleResult
{
FileName = BuildSample(_multiEpisodes, _animeSeries, _animeMultiEpisodeFile, nameSpec),
Series = _animeSeries,
Episodes = _multiEpisodes,
EpisodeFile = _animeMultiEpisodeFile
};
return result;
}
public string GetSeriesFolderSample(NamingConfig nameSpec)
{
return _buildFileNames.GetSeriesFolder(_standardSeries, nameSpec);
}
public string GetSeasonFolderSample(NamingConfig nameSpec)
{
return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, nameSpec);
}
public string GetArtistFolderSample(NamingConfig nameSpec)
{
return _buildFileNames.GetArtistFolder(_standardArtist, nameSpec);
@ -289,18 +95,6 @@ namespace NzbDrone.Core.Organizer
return _buildFileNames.GetAlbumFolder(_standardArtist, _standardAlbum, nameSpec);
}
private string BuildSample(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig nameSpec)
{
try
{
return _buildFileNames.BuildFileName(episodes, series, episodeFile, nameSpec);
}
catch (NamingFormatException)
{
return string.Empty;
}
}
private string BuildTrackSample(List<Track> tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig nameSpec)
{
try

@ -1,4 +1,4 @@
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Organizer
{
@ -6,30 +6,16 @@ namespace NzbDrone.Core.Organizer
{
public static NamingConfig Default => new NamingConfig
{
RenameEpisodes = false,
RenameTracks = false,
ReplaceIllegalCharacters = true,
MultiEpisodeStyle = 0,
StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
StandardTrackFormat = "{Artist Name} - {track:00} - {Album Title} - {Track Title}",
DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title} {Quality Full}",
AnimeEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
SeriesFolderFormat = "{Series Title}",
SeasonFolderFormat = "Season {season}",
ArtistFolderFormat = "{Artist Name}",
AlbumFolderFormat = "{Album Title} ({Release Year})"
};
public bool RenameEpisodes { get; set; }
public bool RenameTracks { get; set; }
public bool ReplaceIllegalCharacters { get; set; }
public int MultiEpisodeStyle { get; set; }
public string StandardEpisodeFormat { get; set; }
public string StandardTrackFormat { get; set; }
public string DailyEpisodeFormat { get; set; }
public string AnimeEpisodeFormat { get; set; }
public string SeriesFolderFormat { get; set; }
public string SeasonFolderFormat { get; set; }
public string ArtistFolderFormat { get; set; }
public string AlbumFolderFormat { get; set; }
}

@ -1,8 +1,8 @@
namespace NzbDrone.Core.Organizer
{
public class AbsoluteEpisodeFormat
public class AbsoluteTrackFormat
{
public string Separator { get; set; }
public string AbsoluteEpisodePattern { get; set; }
public string AbsoluteTrackPattern { get; set; }
}
}

@ -47,8 +47,8 @@ namespace NzbDrone.Core.Tv
if (string.IsNullOrWhiteSpace(newSeries.Path))
{
var folderName = _fileNameBuilder.GetSeriesFolder(newSeries);
newSeries.Path = Path.Combine(newSeries.RootFolderPath, folderName);
//var folderName = _fileNameBuilder.GetSeriesFolder(newSeries);
//newSeries.Path = Path.Combine(newSeries.RootFolderPath, folderName);
}
newSeries.CleanTitle = newSeries.Title.CleanSeriesTitle();

@ -25,100 +25,65 @@ namespace NzbDrone.Integration.Test.ApiTests
public void should_be_able_to_update()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = false;
config.StandardEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}";
config.DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title}";
config.AnimeEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}";
config.RenameTracks = false;
config.StandardTrackFormat = "{Artist Name} - {track:00} - {Album Title}";
var result = NamingConfig.Put(config);
result.RenameEpisodes.Should().BeFalse();
result.StandardEpisodeFormat.Should().Be(config.StandardEpisodeFormat);
result.DailyEpisodeFormat.Should().Be(config.DailyEpisodeFormat);
result.AnimeEpisodeFormat.Should().Be(config.AnimeEpisodeFormat);
result.RenameTracks.Should().BeFalse();
result.StandardTrackFormat.Should().Be(config.StandardTrackFormat);
}
[Test]
public void should_get_bad_request_if_standard_format_is_empty()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = true;
config.StandardEpisodeFormat = "";
config.DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title}";
config.AnimeEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}";
config.RenameTracks = true;
config.StandardTrackFormat = "";
var errors = NamingConfig.InvalidPut(config);
errors.Should().NotBeNull();
}
[Test]
public void should_get_bad_request_if_standard_format_doesnt_contain_season_and_episode()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = true;
config.StandardEpisodeFormat = "{season}";
config.DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title}";
config.AnimeEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}";
var errors = NamingConfig.InvalidPut(config);
errors.Should().NotBeNull();
}
[Test]
public void should_get_bad_request_if_daily_format_doesnt_contain_season_and_episode_or_air_date()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = true;
config.StandardEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}";
config.DailyEpisodeFormat = "{Series Title} - {season} - {Episode Title}";
config.AnimeEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}";
var errors = NamingConfig.InvalidPut(config);
errors.Should().NotBeNull();
}
[Test]
public void should_get_bad_request_if_anime_format_doesnt_contain_season_and_episode_or_absolute()
public void should_get_bad_request_if_standard_format_doesnt_contain_track_number_and_title()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = false;
config.StandardEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}";
config.DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title}";
config.AnimeEpisodeFormat = "{Series Title} - {season} - {Episode Title}";
config.RenameTracks = true;
config.StandardTrackFormat = "{track:00}";
var errors = NamingConfig.InvalidPut(config);
errors.Should().NotBeNull();
}
[Test]
public void should_not_require_format_when_rename_episodes_is_false()
public void should_not_require_format_when_rename_tracks_is_false()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = false;
config.StandardEpisodeFormat = "";
config.DailyEpisodeFormat = "";
config.RenameTracks = false;
config.StandardTrackFormat = "";
var errors = NamingConfig.InvalidPut(config);
errors.Should().NotBeNull();
}
[Test]
public void should_require_format_when_rename_episodes_is_true()
public void should_require_format_when_rename_tracks_is_true()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = true;
config.StandardEpisodeFormat = "";
config.DailyEpisodeFormat = "";
config.RenameTracks = true;
config.StandardTrackFormat = "";
var errors = NamingConfig.InvalidPut(config);
errors.Should().NotBeNull();
}
[Test]
public void should_get_bad_request_if_series_folder_format_does_not_contain_series_title()
public void should_get_bad_request_if_artist_folder_format_does_not_contain_artist_name()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = true;
config.SeriesFolderFormat = "This and That";
config.RenameTracks = true;
config.ArtistFolderFormat = "This and That";
var errors = NamingConfig.InvalidPut(config);
errors.Should().NotBeNull();

@ -161,7 +161,7 @@ module.exports = Marionette.Layout.extend({
command : {
name : 'albumSearch',
artistId : this.artist.id,
albumIds : [this.model.get('id')]
albumIds : [this.model.get('id')]
}
});
@ -170,7 +170,7 @@ module.exports = Marionette.Layout.extend({
command : {
name : 'renameFiles',
artistId : this.artist.id,
albumId : this.model.get('id')
albumId : this.model.get('id')
}
});
},

@ -86,8 +86,8 @@ module.exports = Marionette.Layout.extend({
element : this.ui.rename,
command : {
name : 'renameFiles',
seriesId : this.model.id,
seasonNumber : -1
artistId : this.model.id,
albumId : -1
}
});
},

@ -14,8 +14,8 @@ module.exports = Marionette.ItemView.extend({
initialize : function(options) {
this.artist = options.artist;
this.templateHelpers = {
numberOfArtist : this.artist.length,
artist : new Backbone.Collection(this.artist).toJSON()
numberOfArtists : this.artist.length,
artist : new Backbone.Collection(this.artist).toJSON()
};
},

@ -1,7 +1,7 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Organize of Selected Series</h3>
<h3>Organize of Selected Artist(s)</h3>
</div>
<div class="modal-body update-files-artist-modal">
<div class="alert alert-info">
@ -9,12 +9,12 @@
Tip: To preview a rename... select "Cancel" then any artist title and use the <i data-original-title="" class="icon-lidarr-rename" title=""></i>
</div>
Are you sure you want to update all files in the {{numberOfSeries}} selected artist?
Are you sure you want to update all files in the {{numberOfArtists}} selected artist(s)?
{{debug}}
<ul class="selected-artist">
{{#each series}}
<li>{{title}}</li>
{{#each artist}}
<li>{{name}}</li>
{{/each}}
</ul>
</div>

@ -8,25 +8,25 @@ module.exports = Backbone.Collection.extend({
originalFetch : Backbone.Collection.prototype.fetch,
initialize : function(options) {
if (!options.seriesId) {
throw 'seriesId is required';
if (!options.artistId) {
throw 'artistId is required';
}
this.seriesId = options.seriesId;
this.seasonNumber = options.seasonNumber;
this.artistId = options.artistId;
this.albumId = options.albumId;
},
fetch : function(options) {
if (!this.seriesId) {
throw 'seriesId is required';
if (!this.artistId) {
throw 'artistId is required';
}
options = options || {};
options.data = {};
options.data.seriesId = this.seriesId;
options.data.artistId = this.artistId;
if (this.seasonNumber !== undefined) {
options.data.seasonNumber = this.seasonNumber;
if (this.albumId !== undefined) {
options.data.albumId = this.albumId;
}
return this.originalFetch.call(this, options);

@ -7,9 +7,10 @@ module.exports = Marionette.ItemView.extend({
templateHelpers : function() {
var type = this.model.get('seriesType');
return {
rename : this.naming.get('renameEpisodes'),
format : this.naming.get(type + 'EpisodeFormat')
rename : this.naming.get('renameTracks'),
format : this.naming.get('standardTrackFormat')
};
},

@ -29,12 +29,12 @@ module.exports = Marionette.Layout.extend({
},
initialize : function(options) {
this.model = options.series;
this.seasonNumber = options.seasonNumber;
this.model = options.artist;
this.albumId = options.albumId;
var viewOptions = {};
viewOptions.seriesId = this.model.id;
viewOptions.seasonNumber = this.seasonNumber;
viewOptions.artistId = this.model.id;
viewOptions.albumId = this.albumId;
this.collection = new RenamePreviewCollection(viewOptions);
this.listenTo(this.collection, 'sync', this._showPreviews);
@ -66,7 +66,7 @@ module.exports = Marionette.Layout.extend({
}
var files = _.map(this.collection.where({ rename : true }), function(model) {
return model.get('episodeFileId');
return model.get('trackFileId');
});
if (files.length === 0) {
@ -74,18 +74,18 @@ module.exports = Marionette.Layout.extend({
return;
}
if (this.seasonNumber) {
if (this.albumId) {
CommandController.Execute('renameFiles', {
name : 'renameFiles',
seriesId : this.model.id,
seasonNumber : this.seasonNumber,
artistId : this.model.id,
albumId : this.albumId,
files : files
});
} else {
CommandController.Execute('renameFiles', {
name : 'renameFiles',
seriesId : this.model.id,
seasonNumber : -1,
artistId : this.model.id,
albumId : -1,
files : files
});
}

@ -74,11 +74,10 @@
<i class="icon-lidarr-form-info" title="Change file date on import/rescan"/>
</div>
<div class="col-sm-2 col-sm-pull-1">
<div class="col-sm-3 col-sm-pull-1">
<select class="form-control" name="fileDate">
<option value="none">None</option>
<option value="localAirDate">Local Air Date</option>
<option value="utcAirDate">UTC Air Date</option>
<option value="albumReleaseDate">Album Release Date</option>
</select>
</div>
</div>

@ -10,9 +10,7 @@ var view = Marionette.ItemView.extend({
ui : {
namingOptions : '.x-naming-options',
singleEpisodeExample : '.x-single-episode-example',
multiEpisodeExample : '.x-multi-episode-example',
dailyEpisodeExample : '.x-daily-episode-example'
singleTrackExample : '.x-single-track-example'
},
initialize : function(options) {
@ -26,12 +24,12 @@ var view = Marionette.ItemView.extend({
},
_parseNamingModel : function() {
var standardFormat = this.namingModel.get('standardEpisodeFormat');
var standardFormat = this.namingModel.get('standardTrackFormat');
var includeSeriesTitle = standardFormat.match(/\{Series[-_. ]Title\}/i);
var includeEpisodeTitle = standardFormat.match(/\{Episode[-_. ]Title\}/i);
var includeArtistName = standardFormat.match(/\{Artist[-_. ]Name\}/i);
var includeAlbumTitle = standardFormat.match(/\{Album[-_. ]Title\}/i);
var includeQuality = standardFormat.match(/\{Quality[-_. ]Title\}/i);
var numberStyle = standardFormat.match(/s?\{season(?:\:0+)?\}[ex]\{episode(?:\:0+)?\}/i);
var numberStyle = standardFormat.match(/\{track(?:\:0+)?\}/i);
var replaceSpaces = standardFormat.indexOf(' ') === -1;
var separator = standardFormat.match(/\}( - |\.-\.|\.| )|( - |\.-\.|\.| )\{/i);
@ -42,14 +40,14 @@ var view = Marionette.ItemView.extend({
}
if (numberStyle === null) {
numberStyle = 'S{season:00}E{episode:00}';
numberStyle = '{track:00}';
} else {
numberStyle = numberStyle[0];
}
this.model.set({
includeSeriesTitle : includeSeriesTitle !== null,
includeEpisodeTitle : includeEpisodeTitle !== null,
includeArtistName : includeArtistName !== null,
includeAlbumTitle : includeAlbumTitle !== null,
includeQuality : includeQuality !== null,
numberStyle : numberStyle,
replaceSpaces : replaceSpaces,
@ -62,56 +60,52 @@ var view = Marionette.ItemView.extend({
return;
}
var standardEpisodeFormat = '';
var dailyEpisodeFormat = '';
var standardTrackFormat = '';
if (this.model.get('includeSeriesTitle')) {
if (this.model.get('includeArtistName')) {
if (this.model.get('replaceSpaces')) {
standardEpisodeFormat += '{Series.Title}';
dailyEpisodeFormat += '{Series.Title}';
standardTrackFormat += '{Artist.Name}';
} else {
standardEpisodeFormat += '{Series Title}';
dailyEpisodeFormat += '{Series Title}';
standardTrackFormat += '{Artist Name}';
}
standardEpisodeFormat += this.model.get('separator');
dailyEpisodeFormat += this.model.get('separator');
standardTrackFormat += this.model.get('separator');
}
standardEpisodeFormat += this.model.get('numberStyle');
dailyEpisodeFormat += '{Air-Date}';
if (this.model.get('includeEpisodeTitle')) {
standardEpisodeFormat += this.model.get('separator');
dailyEpisodeFormat += this.model.get('separator');
if (this.model.get('includeAlbumTitle')) {
if (this.model.get('replaceSpaces')) {
standardEpisodeFormat += '{Episode.Title}';
dailyEpisodeFormat += '{Episode.Title}';
standardTrackFormat += '{Album.Title}';
} else {
standardEpisodeFormat += '{Episode Title}';
dailyEpisodeFormat += '{Episode Title}';
standardTrackFormat += '{Album Title}';
}
standardTrackFormat += this.model.get('separator');
}
standardTrackFormat += this.model.get('numberStyle');
standardTrackFormat += this.model.get('separator');
if (this.model.get('replaceSpaces')) {
standardTrackFormat += '{Track.Title}';
} else {
standardTrackFormat += '{Track Title}';
}
if (this.model.get('includeQuality')) {
if (this.model.get('replaceSpaces')) {
standardEpisodeFormat += ' {Quality.Title}';
dailyEpisodeFormat += ' {Quality.Title}';
standardTrackFormat += ' {Quality.Title}';
} else {
standardEpisodeFormat += ' {Quality Title}';
dailyEpisodeFormat += ' {Quality Title}';
standardTrackFormat += ' {Quality Title}';
}
}
if (this.model.get('replaceSpaces')) {
standardEpisodeFormat = standardEpisodeFormat.replace(/\s/g, '.');
dailyEpisodeFormat = dailyEpisodeFormat.replace(/\s/g, '.');
standardTrackFormat = standardTrackFormat.replace(/\s/g, '.');
}
this.namingModel.set('standardEpisodeFormat', standardEpisodeFormat);
this.namingModel.set('dailyEpisodeFormat', dailyEpisodeFormat);
this.namingModel.set('animeEpisodeFormat', standardEpisodeFormat);
this.namingModel.set('standardTrackFormat', standardTrackFormat);
}
});

@ -1,10 +1,10 @@
<div class="form-group">
<label class="col-sm-3 control-label">Include Series Title</label>
<label class="col-sm-3 control-label">Include Artist Name</label>
<div class="col-sm-9">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="includeSeriesTitle"/>
<input type="checkbox" name="includeArtistName"/>
<p>
<span>Yes</span>
@ -17,15 +17,13 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Include Episode Title</label>
<label class="col-sm-3 control-label">Include Album Title</label>
<div class="col-sm-9">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="includeEpisodeTitle"/>
<input type="checkbox" name="includeAlbumTitle"/>
<p>
<span>Yes</span>
@ -93,10 +91,8 @@
<div class="col-sm-9">
<select class="form-control" name="numberStyle">
<option value="{season}x{episode:00}">1x05</option>
<option value="{season:00}x{episode:00}">01x05</option>
<option value="S{season:00}E{episode:00}">S01E05</option>
<option value="s{season:00}e{episode:00}">s01e05</option>
<option value="{track}">1</option>
<option value="{track:00}">01</option>
</select>
</div>
</div>

Loading…
Cancel
Save