2013-03-06 22:20:33 +01:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.IO;
|
|
|
|
using System.Linq;
|
|
|
|
using NLog;
|
2013-07-05 05:26:07 +02:00
|
|
|
using NzbDrone.Core.Configuration;
|
2013-03-06 22:20:33 +01:00
|
|
|
using NzbDrone.Core.Datastore;
|
|
|
|
using NzbDrone.Core.MediaFiles;
|
|
|
|
using NzbDrone.Core.Tv;
|
|
|
|
|
|
|
|
namespace NzbDrone.Core.Organizer
|
|
|
|
{
|
|
|
|
public interface IBuildFileNames
|
|
|
|
{
|
2013-03-06 22:35:39 +01:00
|
|
|
string BuildFilename(IList<Episode> episodes, Series series, EpisodeFile episodeFile);
|
2013-03-06 23:20:34 +01:00
|
|
|
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
|
2013-03-06 22:20:33 +01:00
|
|
|
}
|
|
|
|
|
2013-04-25 06:27:49 +02:00
|
|
|
public interface INamingConfigService
|
2013-03-06 22:20:33 +01:00
|
|
|
{
|
2013-04-25 06:27:49 +02:00
|
|
|
NamingConfig GetConfig();
|
|
|
|
NamingConfig Save(NamingConfig namingConfig);
|
|
|
|
}
|
2013-03-06 22:20:33 +01:00
|
|
|
|
2013-04-25 06:27:49 +02:00
|
|
|
public class NamingConfigService : INamingConfigService
|
|
|
|
{
|
|
|
|
private readonly IBasicRepository<NamingConfig> _repository;
|
|
|
|
|
|
|
|
public NamingConfigService(IBasicRepository<NamingConfig> repository)
|
2013-03-06 22:20:33 +01:00
|
|
|
{
|
2013-04-25 06:27:49 +02:00
|
|
|
_repository = repository;
|
|
|
|
}
|
|
|
|
|
|
|
|
public NamingConfig GetConfig()
|
|
|
|
{
|
|
|
|
var config = _repository.SingleOrDefault();
|
|
|
|
|
|
|
|
if (config == null)
|
|
|
|
{
|
|
|
|
_repository.Insert(NamingConfig.Default);
|
|
|
|
config = _repository.Single();
|
|
|
|
}
|
|
|
|
|
|
|
|
return config;
|
|
|
|
}
|
|
|
|
|
|
|
|
public NamingConfig Save(NamingConfig namingConfig)
|
|
|
|
{
|
|
|
|
return _repository.Upsert(namingConfig);
|
2013-03-06 22:20:33 +01:00
|
|
|
}
|
2013-04-25 06:27:49 +02:00
|
|
|
}
|
2013-03-06 22:20:33 +01:00
|
|
|
|
2013-04-25 06:27:49 +02:00
|
|
|
public class FileNameBuilder : IBuildFileNames
|
|
|
|
{
|
2013-07-05 05:26:07 +02:00
|
|
|
private readonly IConfigService _configService;
|
2013-04-25 06:27:49 +02:00
|
|
|
private readonly INamingConfigService _namingConfigService;
|
|
|
|
private readonly Logger _logger;
|
|
|
|
|
2013-07-05 05:26:07 +02:00
|
|
|
public FileNameBuilder(INamingConfigService namingConfigService, IConfigService configService, Logger logger)
|
2013-03-06 22:20:33 +01:00
|
|
|
{
|
2013-04-25 06:27:49 +02:00
|
|
|
_namingConfigService = namingConfigService;
|
2013-07-05 05:26:07 +02:00
|
|
|
_configService = configService;
|
2013-04-25 06:27:49 +02:00
|
|
|
_logger = logger;
|
2013-03-06 22:20:33 +01:00
|
|
|
}
|
|
|
|
|
2013-03-06 22:35:39 +01:00
|
|
|
public string BuildFilename(IList<Episode> episodes, Series series, EpisodeFile episodeFile)
|
2013-03-06 22:20:33 +01:00
|
|
|
{
|
2013-04-25 06:27:49 +02:00
|
|
|
var nameSpec = _namingConfigService.GetConfig();
|
2013-03-06 22:20:33 +01:00
|
|
|
|
2013-07-05 05:26:07 +02:00
|
|
|
if (!nameSpec.RenameEpisodes)
|
2013-03-06 22:20:33 +01:00
|
|
|
{
|
|
|
|
if (String.IsNullOrWhiteSpace(episodeFile.SceneName))
|
|
|
|
{
|
2013-03-06 23:20:34 +01:00
|
|
|
return Path.GetFileNameWithoutExtension(episodeFile.Path);
|
2013-03-06 22:20:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return episodeFile.SceneName;
|
|
|
|
}
|
|
|
|
|
|
|
|
var sortedEpisodes = episodes.OrderBy(e => e.EpisodeNumber);
|
|
|
|
|
2013-03-06 23:20:34 +01:00
|
|
|
var numberStyle = GetNumberStyle(nameSpec.NumberStyle);
|
2013-03-06 22:20:33 +01:00
|
|
|
|
2013-03-06 23:20:34 +01:00
|
|
|
var episodeNames = new List<string>
|
|
|
|
{
|
2013-04-15 03:41:39 +02:00
|
|
|
Parser.Parser.CleanupEpisodeTitle(sortedEpisodes.First().Title)
|
2013-03-06 23:20:34 +01:00
|
|
|
};
|
2013-03-06 22:20:33 +01:00
|
|
|
|
2013-03-06 23:20:34 +01:00
|
|
|
var result = String.Empty;
|
2013-03-06 22:20:33 +01:00
|
|
|
|
2013-04-25 06:27:49 +02:00
|
|
|
if (nameSpec.IncludeSeriesTitle)
|
2013-03-06 22:20:33 +01:00
|
|
|
{
|
2013-03-06 23:20:34 +01:00
|
|
|
result += series.Title + nameSpec.Separator;
|
2013-03-06 22:20:33 +01:00
|
|
|
}
|
|
|
|
|
2013-03-24 05:16:00 +01:00
|
|
|
if (series.SeriesType == SeriesTypes.Standard)
|
2013-03-06 22:20:33 +01:00
|
|
|
{
|
|
|
|
result += numberStyle.Pattern.Replace("%0e",
|
|
|
|
String.Format("{0:00}", sortedEpisodes.First().EpisodeNumber));
|
|
|
|
|
|
|
|
if (episodes.Count > 1)
|
|
|
|
{
|
|
|
|
var multiEpisodeStyle =
|
2013-03-06 23:20:34 +01:00
|
|
|
GetMultiEpisodeStyle(nameSpec.MultiEpisodeStyle);
|
2013-03-06 22:20:33 +01:00
|
|
|
|
|
|
|
foreach (var episode in sortedEpisodes.Skip(1))
|
|
|
|
{
|
|
|
|
if (multiEpisodeStyle.Name == "Duplicate")
|
|
|
|
{
|
2013-03-06 23:20:34 +01:00
|
|
|
result += nameSpec.Separator + numberStyle.Pattern;
|
2013-03-06 22:20:33 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result += multiEpisodeStyle.Pattern;
|
|
|
|
}
|
|
|
|
|
|
|
|
result = result.Replace("%0e", String.Format("{0:00}", episode.EpisodeNumber));
|
2013-04-15 03:41:39 +02:00
|
|
|
episodeNames.Add(Parser.Parser.CleanupEpisodeTitle(episode.Title));
|
2013-03-06 22:20:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
result = result
|
|
|
|
.Replace("%s", String.Format("{0}", episodes.First().SeasonNumber))
|
|
|
|
.Replace("%0s", String.Format("{0:00}", episodes.First().SeasonNumber))
|
|
|
|
.Replace("%x", numberStyle.EpisodeSeparator)
|
2013-03-06 23:20:34 +01:00
|
|
|
.Replace("%p", nameSpec.Separator);
|
2013-03-06 22:20:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (episodes.First().AirDate.HasValue)
|
|
|
|
result += episodes.First().AirDate.Value.ToString("yyyy-MM-dd");
|
|
|
|
|
|
|
|
else
|
|
|
|
result += "Unknown";
|
|
|
|
}
|
|
|
|
|
2013-03-06 22:35:39 +01:00
|
|
|
if (nameSpec.IncludeEpisodeTitle)
|
2013-03-06 22:20:33 +01:00
|
|
|
{
|
|
|
|
if (episodeNames.Distinct().Count() == 1)
|
2013-03-06 23:20:34 +01:00
|
|
|
result += nameSpec.Separator + episodeNames.First();
|
2013-03-06 22:20:33 +01:00
|
|
|
|
|
|
|
else
|
2013-03-06 23:20:34 +01:00
|
|
|
result += nameSpec.Separator + String.Join(" + ", episodeNames.Distinct());
|
2013-03-06 22:20:33 +01:00
|
|
|
}
|
|
|
|
|
2013-04-25 06:27:49 +02:00
|
|
|
if (nameSpec.IncludeQuality)
|
2013-03-06 22:20:33 +01:00
|
|
|
{
|
2013-04-11 07:08:55 +02:00
|
|
|
result += String.Format(" [{0}]", episodeFile.Quality.Quality);
|
2013-03-06 22:20:33 +01:00
|
|
|
|
2013-04-01 08:22:16 +02:00
|
|
|
if (episodeFile.Quality.Proper)
|
2013-03-06 22:20:33 +01:00
|
|
|
result += " [Proper]";
|
|
|
|
}
|
|
|
|
|
2013-03-06 22:35:39 +01:00
|
|
|
if (nameSpec.ReplaceSpaces)
|
2013-03-06 22:20:33 +01:00
|
|
|
result = result.Replace(' ', '.');
|
|
|
|
|
|
|
|
_logger.Trace("New File Name is: [{0}]", result.Trim());
|
|
|
|
return CleanFilename(result.Trim());
|
|
|
|
}
|
|
|
|
|
2013-03-06 23:20:34 +01:00
|
|
|
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
|
2013-03-06 22:20:33 +01:00
|
|
|
{
|
|
|
|
string path = series.Path;
|
|
|
|
if (series.SeasonFolder)
|
|
|
|
{
|
2013-07-05 05:26:07 +02:00
|
|
|
var seasonFolder = _configService.SeasonFolderFormat
|
|
|
|
.Replace("%sn", series.Title)
|
|
|
|
.Replace("%s.n", series.Title.Replace(' ', '.'))
|
|
|
|
.Replace("%s_n", series.Title.Replace(' ', '_'))
|
2013-03-06 22:20:33 +01:00
|
|
|
.Replace("%0s", seasonNumber.ToString("00"))
|
|
|
|
.Replace("%s", seasonNumber.ToString());
|
|
|
|
|
|
|
|
path = Path.Combine(path, seasonFolder);
|
|
|
|
}
|
|
|
|
|
2013-03-06 23:20:34 +01:00
|
|
|
return Path.Combine(path, fileName + extension);
|
2013-03-06 22:20:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static string CleanFilename(string name)
|
|
|
|
{
|
|
|
|
string result = name;
|
|
|
|
string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" };
|
|
|
|
string[] goodCharacters = { "+", "+", "{", "}", "!", "@", "-", "#", "`" };
|
|
|
|
|
|
|
|
for (int i = 0; i < badCharacters.Length; i++)
|
|
|
|
result = result.Replace(badCharacters[i], goodCharacters[i]);
|
|
|
|
|
|
|
|
return result.Trim();
|
|
|
|
}
|
2013-03-06 23:20:34 +01:00
|
|
|
|
|
|
|
private static readonly List<EpisodeSortingType> NumberStyles = new List<EpisodeSortingType>
|
|
|
|
{
|
|
|
|
new EpisodeSortingType
|
|
|
|
{
|
|
|
|
Id = 0,
|
|
|
|
Name = "1x05",
|
|
|
|
Pattern = "%sx%0e",
|
|
|
|
EpisodeSeparator = "x"
|
|
|
|
|
|
|
|
},
|
|
|
|
new EpisodeSortingType
|
|
|
|
{
|
|
|
|
Id = 1,
|
|
|
|
Name = "01x05",
|
|
|
|
Pattern = "%0sx%0e",
|
|
|
|
EpisodeSeparator = "x"
|
|
|
|
},
|
|
|
|
new EpisodeSortingType
|
|
|
|
{
|
|
|
|
Id = 2,
|
|
|
|
Name = "S01E05",
|
|
|
|
Pattern = "S%0sE%0e",
|
|
|
|
EpisodeSeparator = "E"
|
|
|
|
},
|
|
|
|
new EpisodeSortingType
|
|
|
|
{
|
|
|
|
Id = 3,
|
|
|
|
Name = "s01e05",
|
|
|
|
Pattern = "s%0se%0e",
|
|
|
|
EpisodeSeparator = "e"
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private static readonly List<EpisodeSortingType> MultiEpisodeStyles = new List<EpisodeSortingType>
|
|
|
|
{
|
|
|
|
new EpisodeSortingType
|
|
|
|
{
|
|
|
|
Id = 0,
|
|
|
|
Name = "Extend",
|
|
|
|
Pattern = "-%0e"
|
|
|
|
},
|
|
|
|
new EpisodeSortingType
|
|
|
|
{
|
|
|
|
Id = 1,
|
|
|
|
Name = "Duplicate",
|
|
|
|
Pattern = "%p%0s%x%0e"
|
|
|
|
},
|
|
|
|
new EpisodeSortingType
|
|
|
|
{
|
|
|
|
Id = 2,
|
|
|
|
Name = "Repeat",
|
|
|
|
Pattern = "%x%0e"
|
|
|
|
},
|
|
|
|
new EpisodeSortingType
|
|
|
|
{
|
|
|
|
Id = 3,
|
|
|
|
Name = "Scene",
|
|
|
|
Pattern = "-%x%0e"
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private static EpisodeSortingType GetNumberStyle(int id)
|
|
|
|
{
|
|
|
|
return NumberStyles.Single(s => s.Id == id);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static EpisodeSortingType GetMultiEpisodeStyle(int id)
|
|
|
|
{
|
|
|
|
return MultiEpisodeStyles.Single(s => s.Id == id);
|
|
|
|
}
|
2013-03-06 22:20:33 +01:00
|
|
|
}
|
|
|
|
}
|