1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-26 22:46:53 -04:00

Added: Importing extra files from downloaded movies and generate metadata such as .nfo (#2506)

Fixes #121, Fixes #167, Fixes #2262 and Fixes #1104
This commit is contained in:
Qstick
2018-02-15 13:39:01 +01:00
committed by Leonardo Galli
parent b40423f3a3
commit e7e9e2b154
78 changed files with 1381 additions and 1759 deletions
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
@@ -10,7 +10,7 @@ using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Extras
{
public class ExistingExtraFileService : IHandle<SeriesScannedEvent>
public class ExistingExtraFileService : IHandle<MovieScannedEvent>
{
private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService;
@@ -28,29 +28,29 @@ namespace NzbDrone.Core.Extras
_logger = logger;
}
public void Handle(SeriesScannedEvent message)
public void Handle(MovieScannedEvent message)
{
var series = message.Series;
var movie = message.Movie;
var extraFiles = new List<ExtraFile>();
if (!_diskProvider.FolderExists(series.Path))
if (!_diskProvider.FolderExists(movie.Path))
{
return;
}
_logger.Debug("Looking for existing extra files in {0}", series.Path);
_logger.Debug("Looking for existing extra files in {0}", movie.Path);
var filesOnDisk = _diskScanService.GetNonVideoFiles(series.Path);
var possibleExtraFiles = _diskScanService.FilterFiles(series, filesOnDisk);
var filesOnDisk = _diskScanService.GetNonVideoFiles(movie.Path);
var possibleExtraFiles = _diskScanService.FilterFiles(movie, filesOnDisk);
var filteredFiles = possibleExtraFiles;
var importedFiles = new List<string>();
foreach (var existingExtraFileImporter in _existingExtraFileImporters)
{
var imported = existingExtraFileImporter.ProcessFiles(series, filteredFiles, importedFiles);
var imported = existingExtraFileImporter.ProcessFiles(movie, filteredFiles, importedFiles);
importedFiles.AddRange(imported.Select(f => Path.Combine(series.Path, f.RelativePath)));
importedFiles.AddRange(imported.Select(f => Path.Combine(movie.Path, f.RelativePath)));
}
_logger.Info("Found {0} extra files", extraFiles.Count);
+30 -52
View File
@@ -1,7 +1,8 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Marr.Data;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
@@ -18,50 +19,50 @@ namespace NzbDrone.Core.Extras
{
public interface IExtraService
{
void ImportExtraFiles(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly);
void ImportExtraFiles(LocalMovie localMovie, MovieFile movieFile, bool isReadOnly);
}
public class ExtraService : IExtraService,
IHandle<MediaCoversUpdatedEvent>,
IHandle<EpisodeFolderCreatedEvent>,
IHandle<SeriesRenamedEvent>
IHandle<MovieRenamedEvent>
{
private readonly IMediaFileService _mediaFileService;
private readonly IEpisodeService _episodeService;
private readonly IMovieService _movieService;
private readonly IDiskProvider _diskProvider;
private readonly IConfigService _configService;
private readonly List<IManageExtraFiles> _extraFileManagers;
private readonly Logger _logger;
public ExtraService(IMediaFileService mediaFileService,
IEpisodeService episodeService,
IMovieService movieService,
IDiskProvider diskProvider,
IConfigService configService,
List<IManageExtraFiles> extraFileManagers,
Logger logger)
{
_mediaFileService = mediaFileService;
_episodeService = episodeService;
_movieService = movieService;
_diskProvider = diskProvider;
_configService = configService;
_extraFileManagers = extraFileManagers.OrderBy(e => e.Order).ToList();
_logger = logger;
}
public void ImportExtraFiles(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly)
public void ImportExtraFiles(LocalMovie localMovie, MovieFile movieFile, bool isReadOnly)
{
var series = localEpisode.Series;
var movie = localMovie.Movie;
foreach (var extraFileManager in _extraFileManagers)
{
extraFileManager.CreateAfterEpisodeImport(series, episodeFile);
extraFileManager.CreateAfterMovieImport(movie, movieFile);
}
// TODO: Remove
// Not importing files yet, testing that parsing is working properly first
return;
if (!_configService.ImportExtraFiles)
{
return;
}
var sourcePath = localEpisode.Path;
var sourcePath = localMovie.Path;
var sourceFolder = _diskProvider.GetParentFolder(sourcePath);
var sourceFileName = Path.GetFileNameWithoutExtension(sourcePath);
var files = _diskProvider.GetFiles(sourceFolder, SearchOption.TopDirectoryOnly);
@@ -70,7 +71,7 @@ namespace NzbDrone.Core.Extras
.Select(e => e.Trim(' ', '.'))
.ToList();
var matchingFilenames = files.Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(sourceFileName));
var matchingFilenames = files.Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(sourceFileName, StringComparison.InvariantCultureIgnoreCase));
foreach (var matchingFilename in matchingFilenames)
{
@@ -85,7 +86,8 @@ namespace NzbDrone.Core.Extras
{
foreach (var extraFileManager in _extraFileManagers)
{
var extraFile = extraFileManager.Import(series, episodeFile, matchingFilename, matchingExtension, isReadOnly);
var extension = Path.GetExtension(matchingFilename);
var extraFile = extraFileManager.Import(movie, movieFile, matchingFilename, extension, isReadOnly);
if (extraFile != null)
{
@@ -102,60 +104,36 @@ namespace NzbDrone.Core.Extras
public void Handle(MediaCoversUpdatedEvent message)
{
//var series = message.Series;
//var episodeFiles = GetEpisodeFiles(series.Id);
//foreach (var extraFileManager in _extraFileManagers)
//{
// extraFileManager.CreateAfterSeriesScan(series, episodeFiles);
//}
}
//TODO: Implementing this will fix a lot of our warning exceptions
//public void Handle(MediaCoversUpdatedEvent message)
//{
// var movie = message.Movie;
// var movieFiles = GetMovieFiles(movie.Id);
// foreach (var extraFileManager in _extraFileManagers)
// {
// extraFileManager.CreateAfterMovieScan(movie, movieFiles);
// }
//}
public void Handle(EpisodeFolderCreatedEvent message)
{
var series = message.Series;
var movie = message.Movie;
var movieFiles = GetMovieFiles(movie.Id);
foreach (var extraFileManager in _extraFileManagers)
{
extraFileManager.CreateAfterEpisodeImport(series, message.SeriesFolder, message.SeasonFolder);
extraFileManager.CreateAfterMovieScan(movie, movieFiles);
}
}
public void Handle(SeriesRenamedEvent message)
public void Handle(MovieRenamedEvent message)
{
var series = message.Series;
var episodeFiles = GetEpisodeFiles(series.Id);
var movie = message.Movie;
var movieFiles = GetMovieFiles(movie.Id);
foreach (var extraFileManager in _extraFileManagers)
{
extraFileManager.MoveFilesAfterRename(series, episodeFiles);
extraFileManager.MoveFilesAfterRename(movie, movieFiles);
}
}
private List<EpisodeFile> GetEpisodeFiles(int seriesId)
private List<MovieFile> GetMovieFiles(int movieId)
{
var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
var episodes = _episodeService.GetEpisodeBySeries(seriesId);
var movieFiles = _mediaFileService.GetFilesByMovie(movieId);
foreach (var episodeFile in episodeFiles)
foreach (var movieFile in movieFiles)
{
var localEpisodeFile = episodeFile;
episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
movieFile.Movie = new LazyLoaded<Movie>(_movieService.GetMovie(movieId));
}
return episodeFiles;
return movieFiles;
}
}
}
+10 -4
View File
@@ -1,16 +1,22 @@
using System;
using System;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Extras.Files
{
public abstract class ExtraFile : ModelBase
{
public int SeriesId { get; set; }
public int? EpisodeFileId { get; set; }
public int? SeasonNumber { get; set; }
public int MovieId { get; set; }
public int? MovieFileId { get; set; }
public string RelativePath { get; set; }
public DateTime Added { get; set; }
public DateTime LastUpdated { get; set; }
public string Extension { get; set; }
}
public enum ExtraFileType
{
Subtitle = 0,
Metadata = 1,
Other = 2
}
}
@@ -1,5 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
@@ -11,11 +14,10 @@ namespace NzbDrone.Core.Extras.Files
public interface IManageExtraFiles
{
int Order { get; }
IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles);
IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile);
IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder);
IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles);
ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly);
IEnumerable<ExtraFile> CreateAfterMovieScan(Movie movie, List<MovieFile> movieFiles);
IEnumerable<ExtraFile> CreateAfterMovieImport(Movie movie, MovieFile movieFile);
IEnumerable<ExtraFile> MoveFilesAfterRename(Movie movie, List<MovieFile> movieFiles);
ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly);
}
public abstract class ExtraFileManager<TExtraFile> : IManageExtraFiles
@@ -23,29 +25,40 @@ namespace NzbDrone.Core.Extras.Files
{
private readonly IConfigService _configService;
private readonly IDiskProvider _diskProvider;
private readonly IDiskTransferService _diskTransferService;
private readonly IExtraFileService<TExtraFile> _extraFileService;
private readonly Logger _logger;
public ExtraFileManager(IConfigService configService,
IDiskProvider diskProvider,
IDiskTransferService diskTransferService,
IExtraFileService<TExtraFile> extraFileService)
Logger logger)
{
_configService = configService;
_diskProvider = diskProvider;
_diskTransferService = diskTransferService;
_extraFileService = extraFileService;
_logger = logger;
}
public abstract int Order { get; }
public abstract IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles);
public abstract IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile);
public abstract IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder);
public abstract IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles);
public abstract ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly);
public abstract IEnumerable<ExtraFile> CreateAfterMovieScan(Movie movie, List<MovieFile> movieFiles);
public abstract IEnumerable<ExtraFile> CreateAfterMovieImport(Movie movie, MovieFile movieFile);
public abstract IEnumerable<ExtraFile> MoveFilesAfterRename(Movie movie, List<MovieFile> movieFiles);
public abstract ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly);
protected TExtraFile ImportFile(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly)
protected TExtraFile ImportFile(Movie movie, MovieFile movieFile, string path, bool readOnly, string extension, string fileNameSuffix = null)
{
var newFileName = Path.Combine(series.Path, Path.ChangeExtension(episodeFile.RelativePath, extension));
var newFolder = Path.GetDirectoryName(Path.Combine(movie.Path, movieFile.RelativePath));
var filenameBuilder = new StringBuilder(Path.GetFileNameWithoutExtension(movieFile.RelativePath));
if (fileNameSuffix.IsNotNullOrWhiteSpace())
{
filenameBuilder.Append(fileNameSuffix);
}
filenameBuilder.Append(extension);
var newFileName = Path.Combine(newFolder, filenameBuilder.ToString());
var transferMode = TransferMode.Move;
if (readOnly)
@@ -57,12 +70,45 @@ namespace NzbDrone.Core.Extras.Files
return new TExtraFile
{
SeriesId = series.Id,
SeasonNumber = episodeFile.SeasonNumber,
EpisodeFileId = episodeFile.Id,
RelativePath = series.Path.GetRelativePath(newFileName),
Extension = Path.GetExtension(path)
MovieId = movie.Id,
MovieFileId = movieFile.Id,
RelativePath = movie.Path.GetRelativePath(newFileName),
Extension = extension
};
}
protected TExtraFile MoveFile(Movie movie, MovieFile movieFile, TExtraFile extraFile, string fileNameSuffix = null)
{
var newFolder = Path.GetDirectoryName(Path.Combine(movie.Path, movieFile.RelativePath));
var filenameBuilder = new StringBuilder(Path.GetFileNameWithoutExtension(movieFile.RelativePath));
if (fileNameSuffix.IsNotNullOrWhiteSpace())
{
filenameBuilder.Append(fileNameSuffix);
}
filenameBuilder.Append(extraFile.Extension);
var existingFileName = Path.Combine(movie.Path, extraFile.RelativePath);
var newFileName = Path.Combine(newFolder, filenameBuilder.ToString());
if (newFileName.PathNotEquals(existingFileName))
{
try
{
_diskProvider.MoveFile(existingFileName, newFileName);
extraFile.RelativePath = movie.Path.GetRelativePath(newFileName);
return extraFile;
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to move file after rename: {0}", existingFileName);
}
}
return null;
}
}
}
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
@@ -7,12 +7,10 @@ namespace NzbDrone.Core.Extras.Files
{
public interface IExtraFileRepository<TExtraFile> : IBasicRepository<TExtraFile> where TExtraFile : ExtraFile, new()
{
void DeleteForSeries(int seriesId);
void DeleteForSeason(int seriesId, int seasonNumber);
void DeleteForEpisodeFile(int episodeFileId);
List<TExtraFile> GetFilesBySeries(int seriesId);
List<TExtraFile> GetFilesBySeason(int seriesId, int seasonNumber);
List<TExtraFile> GetFilesByEpisodeFile(int episodeFileId);
void DeleteForMovie(int movieId);
void DeleteForMovieFile(int movieFileId);
List<TExtraFile> GetFilesByMovie(int movieId);
List<TExtraFile> GetFilesByMovieFile(int movieFileId);
TExtraFile FindByPath(string path);
}
@@ -24,34 +22,24 @@ namespace NzbDrone.Core.Extras.Files
{
}
public void DeleteForSeries(int seriesId)
public void DeleteForMovie(int movieId)
{
Delete(c => c.SeriesId == seriesId);
Delete(c => c.MovieId == movieId);
}
public void DeleteForSeason(int seriesId, int seasonNumber)
public void DeleteForMovieFile(int movieFileId)
{
Delete(c => c.SeriesId == seriesId && c.SeasonNumber == seasonNumber);
Delete(c => c.MovieFileId == movieFileId);
}
public void DeleteForEpisodeFile(int episodeFileId)
public List<TExtraFile> GetFilesByMovie(int movieId)
{
Delete(c => c.EpisodeFileId == episodeFileId);
return Query.Where(c => c.MovieId == movieId);
}
public List<TExtraFile> GetFilesBySeries(int seriesId)
public List<TExtraFile> GetFilesByMovieFile(int movieFileId)
{
return Query.Where(c => c.SeriesId == seriesId);
}
public List<TExtraFile> GetFilesBySeason(int seriesId, int seasonNumber)
{
return Query.Where(c => c.SeriesId == seriesId && c.SeasonNumber == seasonNumber);
}
public List<TExtraFile> GetFilesByEpisodeFile(int episodeFileId)
{
return Query.Where(c => c.EpisodeFileId == episodeFileId);
return Query.Where(c => c.MovieFileId == movieFileId);
}
public TExtraFile FindByPath(string path)
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -15,8 +15,8 @@ namespace NzbDrone.Core.Extras.Files
public interface IExtraFileService<TExtraFile>
where TExtraFile : ExtraFile, new()
{
List<TExtraFile> GetFilesBySeries(int seriesId);
List<TExtraFile> GetFilesByEpisodeFile(int episodeFileId);
List<TExtraFile> GetFilesByMovie(int movieId);
List<TExtraFile> GetFilesByMovieFile(int movieFileId);
TExtraFile FindByPath(string path);
void Upsert(TExtraFile extraFile);
void Upsert(List<TExtraFile> extraFiles);
@@ -25,24 +25,24 @@ namespace NzbDrone.Core.Extras.Files
}
public abstract class ExtraFileService<TExtraFile> : IExtraFileService<TExtraFile>,
IHandleAsync<SeriesDeletedEvent>,
IHandleAsync<EpisodeFileDeletedEvent>
IHandleAsync<MovieDeletedEvent>,
IHandleAsync<MovieFileDeletedEvent>
where TExtraFile : ExtraFile, new()
{
private readonly IExtraFileRepository<TExtraFile> _repository;
private readonly ISeriesService _seriesService;
private readonly IMovieService _movieService;
private readonly IDiskProvider _diskProvider;
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly Logger _logger;
public ExtraFileService(IExtraFileRepository<TExtraFile> repository,
ISeriesService seriesService,
IMovieService movieService,
IDiskProvider diskProvider,
IRecycleBinProvider recycleBinProvider,
Logger logger)
{
_repository = repository;
_seriesService = seriesService;
_movieService = movieService;
_diskProvider = diskProvider;
_recycleBinProvider = recycleBinProvider;
_logger = logger;
@@ -50,14 +50,14 @@ namespace NzbDrone.Core.Extras.Files
public virtual bool PermanentlyDelete => false;
public List<TExtraFile> GetFilesBySeries(int seriesId)
public List<TExtraFile> GetFilesByMovie(int movieId)
{
return _repository.GetFilesBySeries(seriesId);
return _repository.GetFilesByMovie(movieId);
}
public List<TExtraFile> GetFilesByEpisodeFile(int episodeFileId)
public List<TExtraFile> GetFilesByMovieFile(int movieFileId)
{
return _repository.GetFilesByEpisodeFile(episodeFileId);
return _repository.GetFilesByMovieFile(movieFileId);
}
public TExtraFile FindByPath(string path)
@@ -96,28 +96,28 @@ namespace NzbDrone.Core.Extras.Files
_repository.DeleteMany(ids);
}
public void HandleAsync(SeriesDeletedEvent message)
public void HandleAsync(MovieDeletedEvent message)
{
_logger.Debug("Deleting Extra from database for series: {0}", message.Series);
_repository.DeleteForSeries(message.Series.Id);
_logger.Debug("Deleting Extra from database for movie: {0}", message.Movie);
_repository.DeleteForMovie(message.Movie.Id);
}
public void HandleAsync(EpisodeFileDeletedEvent message)
public void HandleAsync(MovieFileDeletedEvent message)
{
var episodeFile = message.EpisodeFile;
var movieFile = message.MovieFile;
if (message.Reason == DeleteMediaFileReason.NoLinkedEpisodes)
{
_logger.Debug("Removing episode file from DB as part of cleanup routine, not deleting extra files from disk.");
_logger.Debug("Removing movie file from DB as part of cleanup routine, not deleting extra files from disk.");
}
else
{
var series = _seriesService.GetSeries(message.EpisodeFile.SeriesId);
var movie = _movieService.GetMovie(message.MovieFile.MovieId);
foreach (var extra in _repository.GetFilesByEpisodeFile(episodeFile.Id))
foreach (var extra in _repository.GetFilesByMovieFile(movieFile.Id))
{
var path = Path.Combine(series.Path, extra.RelativePath);
var path = Path.Combine(movie.Path, extra.RelativePath);
if (_diskProvider.FileExists(path))
{
@@ -135,8 +135,8 @@ namespace NzbDrone.Core.Extras.Files
}
}
_logger.Debug("Deleting Extra from database for episode file: {0}", episodeFile);
_repository.DeleteForEpisodeFile(episodeFile.Id);
_logger.Debug("Deleting Extra from database for movie file: {0}", movieFile);
_repository.DeleteForMovieFile(movieFile.Id);
}
}
}
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.Tv;
@@ -7,6 +7,6 @@ namespace NzbDrone.Core.Extras
public interface IImportExistingExtraFiles
{
int Order { get; }
IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles);
IEnumerable<ExtraFile> ProcessFiles(Movie movie, List<string> filesOnDisk, List<string> importedFiles);
}
}
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NzbDrone.Common;
@@ -19,21 +19,21 @@ namespace NzbDrone.Core.Extras
}
public abstract int Order { get; }
public abstract IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles);
public abstract IEnumerable<ExtraFile> ProcessFiles(Movie movie, List<string> filesOnDisk, List<string> importedFiles);
public virtual ImportExistingExtraFileFilterResult<TExtraFile> FilterAndClean(Series series, List<string> filesOnDisk, List<string> importedFiles)
public virtual ImportExistingExtraFileFilterResult<TExtraFile> FilterAndClean(Movie movie, List<string> filesOnDisk, List<string> importedFiles)
{
var seriesFiles = _extraFileService.GetFilesBySeries(series.Id);
var movieFiles = _extraFileService.GetFilesByMovie(movie.Id);
Clean(series, filesOnDisk, importedFiles, seriesFiles);
Clean(movie, filesOnDisk, importedFiles, movieFiles);
return Filter(series, filesOnDisk, importedFiles, seriesFiles);
return Filter(movie, filesOnDisk, importedFiles, movieFiles);
}
private ImportExistingExtraFileFilterResult<TExtraFile> Filter(Series series, List<string> filesOnDisk, List<string> importedFiles, List<TExtraFile> seriesFiles)
private ImportExistingExtraFileFilterResult<TExtraFile> Filter(Movie movie, List<string> filesOnDisk, List<string> importedFiles, List<TExtraFile> movieFiles)
{
var previouslyImported = seriesFiles.IntersectBy(s => Path.Combine(series.Path, s.RelativePath), filesOnDisk, f => f, PathEqualityComparer.Instance).ToList();
var filteredFiles = filesOnDisk.Except(previouslyImported.Select(f => Path.Combine(series.Path, f.RelativePath)).ToList(), PathEqualityComparer.Instance)
var previouslyImported = movieFiles.IntersectBy(s => Path.Combine(movie.Path, s.RelativePath), filesOnDisk, f => f, PathEqualityComparer.Instance).ToList();
var filteredFiles = filesOnDisk.Except(previouslyImported.Select(f => Path.Combine(movie.Path, f.RelativePath)).ToList(), PathEqualityComparer.Instance)
.Except(importedFiles, PathEqualityComparer.Instance)
.ToList();
@@ -42,12 +42,12 @@ namespace NzbDrone.Core.Extras
return new ImportExistingExtraFileFilterResult<TExtraFile>(previouslyImported, filteredFiles);
}
private void Clean(Series series, List<string> filesOnDisk, List<string> importedFiles, List<TExtraFile> seriesFiles)
private void Clean(Movie movie, List<string> filesOnDisk, List<string> importedFiles, List<TExtraFile> movieFiles)
{
var alreadyImportedFileIds = seriesFiles.IntersectBy(f => Path.Combine(series.Path, f.RelativePath), importedFiles, i => i, PathEqualityComparer.Instance)
var alreadyImportedFileIds = movieFiles.IntersectBy(f => Path.Combine(movie.Path, f.RelativePath), importedFiles, i => i, PathEqualityComparer.Instance)
.Select(f => f.Id);
var deletedFiles = seriesFiles.ExceptBy(f => Path.Combine(series.Path, f.RelativePath), filesOnDisk, i => i, PathEqualityComparer.Instance)
var deletedFiles = movieFiles.ExceptBy(f => Path.Combine(movie.Path, f.RelativePath), filesOnDisk, i => i, PathEqualityComparer.Instance)
.Select(f => f.Id);
_extraFileService.DeleteMany(alreadyImportedFileIds);
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.MediaBrowser
public override string Name => "Emby (Legacy)";
public override MetadataFile FindMetadataFile(Series series, string path)
public override MetadataFile FindMetadataFile(Movie movie, string path)
{
var filename = Path.GetFileName(path);
@@ -33,28 +33,28 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.MediaBrowser
var metadata = new MetadataFile
{
SeriesId = series.Id,
MovieId = movie.Id,
Consumer = GetType().Name,
RelativePath = series.Path.GetRelativePath(path)
RelativePath = movie.Path.GetRelativePath(path)
};
if (filename.Equals("series.xml", StringComparison.InvariantCultureIgnoreCase))
if (filename.Equals("movie.xml", StringComparison.InvariantCultureIgnoreCase))
{
metadata.Type = MetadataType.SeriesMetadata;
metadata.Type = MetadataType.MovieMetadata;
return metadata;
}
return null;
}
public override MetadataFileResult SeriesMetadata(Series series)
public override MetadataFileResult MovieMetadata(Movie movie, MovieFile movieFile)
{
if (!Settings.SeriesMetadata)
if (!Settings.MovieMetadata)
{
return null;
}
_logger.Debug("Generating series.xml for: {0}", series.Title);
_logger.Debug("Generating movie.xml for: {0}", movie.Title);
var sb = new StringBuilder();
var xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
@@ -62,97 +62,39 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.MediaBrowser
using (var xw = XmlWriter.Create(sb, xws))
{
var tvShow = new XElement("Series");
var movieElement = new XElement("Movie");
tvShow.Add(new XElement("id", series.TvdbId));
tvShow.Add(new XElement("Status", series.Status));
tvShow.Add(new XElement("Network", series.Network));
tvShow.Add(new XElement("Airs_Time", series.AirTime));
movieElement.Add(new XElement("id", movie.ImdbId));
movieElement.Add(new XElement("Status", movie.Status));
if (series.FirstAired.HasValue)
{
tvShow.Add(new XElement("FirstAired", series.FirstAired.Value.ToString("yyyy-MM-dd")));
}
movieElement.Add(new XElement("Added", movie.Added.ToString("MM/dd/yyyy HH:mm:ss tt")));
movieElement.Add(new XElement("LockData", "false"));
movieElement.Add(new XElement("Overview", movie.Overview));
movieElement.Add(new XElement("LocalTitle", movie.Title));
tvShow.Add(new XElement("ContentRating", series.Certification));
tvShow.Add(new XElement("Added", series.Added.ToString("MM/dd/yyyy HH:mm:ss tt")));
tvShow.Add(new XElement("LockData", "false"));
tvShow.Add(new XElement("Overview", series.Overview));
tvShow.Add(new XElement("LocalTitle", series.Title));
movieElement.Add(new XElement("Rating", movie.Ratings.Value));
movieElement.Add(new XElement("ProductionYear", movie.Year));
movieElement.Add(new XElement("RunningTime", movie.Runtime));
movieElement.Add(new XElement("IMDB", movie.ImdbId));
movieElement.Add(new XElement("Genres", movie.Genres.Select(genre => new XElement("Genre", genre))));
if (series.FirstAired.HasValue)
{
tvShow.Add(new XElement("PremiereDate", series.FirstAired.Value.ToString("yyyy-MM-dd")));
}
tvShow.Add(new XElement("Rating", series.Ratings.Value));
tvShow.Add(new XElement("ProductionYear", series.Year));
tvShow.Add(new XElement("RunningTime", series.Runtime));
tvShow.Add(new XElement("IMDB", series.ImdbId));
tvShow.Add(new XElement("TVRageId", series.TvRageId));
tvShow.Add(new XElement("Genres", series.Genres.Select(genre => new XElement("Genre", genre))));
var persons = new XElement("Persons");
foreach (var person in series.Actors)
{
persons.Add(new XElement("Person",
new XElement("Name", person.Name),
new XElement("Type", "Actor"),
new XElement("Role", person.Character)
));
}
tvShow.Add(persons);
var doc = new XDocument(tvShow);
var doc = new XDocument(movieElement);
doc.Save(xw);
_logger.Debug("Saving series.xml for {0}", series.Title);
_logger.Debug("Saving movie.xml for {0}", movie.Title);
return new MetadataFileResult("series.xml", doc.ToString());
return new MetadataFileResult("movie.xml", doc.ToString());
}
}
public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile)
{
return null;
}
public override List<ImageFileResult> SeriesImages(Series series)
public override List<ImageFileResult> MovieImages(Movie movie, MovieFile movieFile)
{
return new List<ImageFileResult>();
}
public override List<ImageFileResult> SeasonImages(Series series, Season season)
private IEnumerable<ImageFileResult> ProcessMovieImages(Movie movie)
{
return new List<ImageFileResult>();
}
public override List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile)
{
return new List<ImageFileResult>();
}
private IEnumerable<ImageFileResult> ProcessSeriesImages(Series series)
{
return new List<ImageFileResult>();
}
private IEnumerable<ImageFileResult> ProcessSeasonImages(Series series, Season season)
{
return new List<ImageFileResult>();
}
private string GetEpisodeNfoFilename(string episodeFilePath)
{
return null;
}
private string GetEpisodeImageFilename(string episodeFilePath)
{
return null;
}
}
}
}
@@ -1,4 +1,4 @@
using FluentValidation;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
@@ -18,11 +18,11 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.MediaBrowser
public MediaBrowserMetadataSettings()
{
SeriesMetadata = true;
MovieMetadata = true;
}
[FieldDefinition(0, Label = "Series Metadata", Type = FieldType.Checkbox)]
public bool SeriesMetadata { get; set; }
[FieldDefinition(0, Label = "Movie Metadata", Type = FieldType.Checkbox)]
public bool MovieMetadata { get; set; }
public bool IsValid => true;
@@ -31,30 +31,30 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
_logger = logger;
}
private static List<string> ValidCertification = new List<string> { "G", "NC-17", "PG", "PG-13", "R", "UR", "UNRATED", "NR", "TV-Y", "TV-Y7", "TV-Y7-FV", "TV-G", "TV-PG", "TV-14", "TV-MA" };
private static readonly Regex SeasonImagesRegex = new Regex(@"^(season (?<season>\d+))|(?<specials>specials)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
//Re-enable when/if we store and use mpaa certification
//private static List<string> ValidCertification = new List<string> { "G", "NC-17", "PG", "PG-13", "R", "UR", "UNRATED", "NR", "TV-Y", "TV-Y7", "TV-Y7-FV", "TV-G", "TV-PG", "TV-14", "TV-MA" };
public override string Name => "Roksbox";
public override string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile)
public override string GetFilenameAfterMove(Movie movie, MovieFile movieFile, MetadataFile metadataFile)
{
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath);
if (metadataFile.Type == MetadataType.EpisodeImage)
if (metadataFile.Type == MetadataType.MovieImage)
{
return GetEpisodeImageFilename(episodeFilePath);
return GetMovieFileImageFilename(movieFilePath);
}
if (metadataFile.Type == MetadataType.EpisodeMetadata)
if (metadataFile.Type == MetadataType.MovieMetadata)
{
return GetEpisodeMetadataFilename(episodeFilePath);
return GetMovieFileMetadataFilename(movieFilePath);
}
_logger.Debug("Unknown episode file metadata: {0}", metadataFile.RelativePath);
return Path.Combine(series.Path, metadataFile.RelativePath);
_logger.Debug("Unknown movie file metadata: {0}", metadataFile.RelativePath);
return Path.Combine(movie.Path, metadataFile.RelativePath);
}
public override MetadataFile FindMetadataFile(Series series, string path)
public override MetadataFile FindMetadataFile(Movie movie, string path)
{
var filename = Path.GetFileName(path);
@@ -63,81 +63,47 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
var metadata = new MetadataFile
{
SeriesId = series.Id,
MovieId = movie.Id,
Consumer = GetType().Name,
RelativePath = series.Path.GetRelativePath(path)
RelativePath = movie.Path.GetRelativePath(path)
};
//Series and season images are both named folder.jpg, only season ones sit in season folders
if (Path.GetFileNameWithoutExtension(filename).Equals(parentdir.Name, StringComparison.InvariantCultureIgnoreCase))
{
var seasonMatch = SeasonImagesRegex.Match(parentdir.Name);
var parseResult = Parser.Parser.ParseMovieTitle(filename, false);
if (seasonMatch.Success)
{
metadata.Type = MetadataType.SeasonImage;
if (seasonMatch.Groups["specials"].Success)
{
metadata.SeasonNumber = 0;
}
else
{
metadata.SeasonNumber = Convert.ToInt32(seasonMatch.Groups["season"].Value);
}
return metadata;
}
metadata.Type = MetadataType.SeriesImage;
return metadata;
}
var parseResult = Parser.Parser.ParseTitle(filename);
if (parseResult != null &&
!parseResult.FullSeason)
if (parseResult != null)
{
var extension = Path.GetExtension(filename).ToLowerInvariant();
if (extension == ".xml")
{
metadata.Type = MetadataType.EpisodeMetadata;
metadata.Type = MetadataType.MovieMetadata;
return metadata;
}
if (extension == ".jpg")
{
if (!Path.GetFileNameWithoutExtension(filename).EndsWith("-thumb"))
if (Path.GetFileNameWithoutExtension(filename).Equals(parentdir.Name, StringComparison.InvariantCultureIgnoreCase))
{
metadata.Type = MetadataType.EpisodeImage;
metadata.Type = MetadataType.MovieImage;
return metadata;
}
}
}
}
return null;
}
public override MetadataFileResult SeriesMetadata(Series series)
public override MetadataFileResult MovieMetadata(Movie movie, MovieFile movieFile)
{
//Series metadata is not supported
return null;
}
public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile)
{
if (!Settings.EpisodeMetadata)
if (!Settings.MovieMetadata)
{
return null;
}
_logger.Debug("Generating Episode Metadata for: {0}", episodeFile.RelativePath);
_logger.Debug("Generating Movie File Metadata for: {0}", movieFile.RelativePath);
var xmlResult = string.Empty;
foreach (var episode in episodeFile.Episodes.Value)
{
var sb = new StringBuilder();
var xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
@@ -148,24 +114,11 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
var doc = new XDocument();
var details = new XElement("video");
details.Add(new XElement("title", string.Format("{0} - {1}x{2} - {3}", series.Title, episode.SeasonNumber, episode.EpisodeNumber, episode.Title)));
details.Add(new XElement("year", episode.AirDate));
details.Add(new XElement("genre", string.Join(" / ", series.Genres)));
var actors = string.Join(" , ", series.Actors.ConvertAll(c => c.Name + " - " + c.Character).GetRange(0, Math.Min(3, series.Actors.Count)));
details.Add(new XElement("actors", actors));
details.Add(new XElement("description", episode.Overview));
details.Add(new XElement("length", series.Runtime));
details.Add(new XElement("title", movie.Title));
if (series.Certification.IsNotNullOrWhiteSpace() &&
ValidCertification.Contains(series.Certification.ToUpperInvariant()))
{
details.Add(new XElement("mpaa", series.Certification.ToUpperInvariant()));
}
else
{
details.Add(new XElement("mpaa", "UNRATED"));
}
details.Add(new XElement("genre", string.Join(" / ", movie.Genres)));
details.Add(new XElement("description", movie.Overview));
details.Add(new XElement("length", movie.Runtime));
doc.Add(details);
doc.Save(xw);
@@ -173,111 +126,39 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
xmlResult += doc.ToString();
xmlResult += Environment.NewLine;
}
}
return new MetadataFileResult(GetEpisodeMetadataFilename(episodeFile.RelativePath), xmlResult.Trim(Environment.NewLine.ToCharArray()));
return new MetadataFileResult(GetMovieFileMetadataFilename(movieFile.RelativePath), xmlResult.Trim(Environment.NewLine.ToCharArray()));
}
public override List<ImageFileResult> SeriesImages(Series series)
public override List<ImageFileResult> MovieImages(Movie movie, MovieFile movieFile)
{
var image = series.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? series.Images.FirstOrDefault();
if (!Settings.MovieImages)
{
return new List<ImageFileResult>();
}
var image = movie.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? movie.Images.FirstOrDefault();
if (image == null)
{
_logger.Trace("Failed to find suitable Series image for series {0}.", series.Title);
_logger.Trace("Failed to find suitable Movie image for movie {0}.", movie.Title);
return null;
}
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
var destination = Path.GetFileName(series.Path) + Path.GetExtension(source);
var source = _mediaCoverService.GetCoverPath(movie.Id, image.CoverType);
var destination = Path.GetFileName(movie.Path) + Path.GetExtension(source);
return new List<ImageFileResult>{ new ImageFileResult(destination, source) };
return new List<ImageFileResult> { new ImageFileResult(destination, source) };
}
public override List<ImageFileResult> SeasonImages(Series series, Season season)
private string GetMovieFileMetadataFilename(string movieFilePath)
{
var seasonFolders = GetSeasonFolders(series);
string seasonFolder;
if (!seasonFolders.TryGetValue(season.SeasonNumber, out seasonFolder))
{
_logger.Trace("Failed to find season folder for series {0}, season {1}.", series.Title, season.SeasonNumber);
return new List<ImageFileResult>();
}
//Roksbox only supports one season image, so first of all try for poster otherwise just use whatever is first in the collection
var image = season.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? season.Images.FirstOrDefault();
if (image == null)
{
_logger.Trace("Failed to find suitable season image for series {0}, season {1}.", series.Title, season.SeasonNumber);
return new List<ImageFileResult>();
}
var filename = Path.GetFileName(seasonFolder) + ".jpg";
var path = series.Path.GetRelativePath(Path.Combine(series.Path, seasonFolder, filename));
return new List<ImageFileResult> { new ImageFileResult(path, image.Url) };
return Path.ChangeExtension(movieFilePath, "xml");
}
public override List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile)
private string GetMovieFileImageFilename(string movieFilePath)
{
var screenshot = episodeFile.Episodes.Value.First().Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
if (screenshot == null)
{
_logger.Trace("Episode screenshot not available");
return new List<ImageFileResult>();
}
return new List<ImageFileResult> {new ImageFileResult(GetEpisodeImageFilename(episodeFile.RelativePath), screenshot.Url)};
}
private string GetEpisodeMetadataFilename(string episodeFilePath)
{
return Path.ChangeExtension(episodeFilePath, "xml");
}
private string GetEpisodeImageFilename(string episodeFilePath)
{
return Path.ChangeExtension(episodeFilePath, "jpg");
}
private Dictionary<int, string> GetSeasonFolders(Series series)
{
var seasonFolderMap = new Dictionary<int, string>();
foreach (var folder in _diskProvider.GetDirectories(series.Path))
{
var directoryinfo = new DirectoryInfo(folder);
var seasonMatch = SeasonImagesRegex.Match(directoryinfo.Name);
if (seasonMatch.Success)
{
var seasonNumber = seasonMatch.Groups["season"].Value;
if (seasonNumber.Contains("specials"))
{
seasonFolderMap[0] = folder;
}
else
{
int matchedSeason;
if (int.TryParse(seasonNumber, out matchedSeason))
{
seasonFolderMap[matchedSeason] = folder;
}
else
{
_logger.Debug("Failed to parse season number from {0} for series {1}.", folder, series.Title);
}
}
}
else
{
_logger.Debug("Rejecting folder {0} for series {1}.", Path.GetDirectoryName(folder), series.Title);
}
}
return seasonFolderMap;
return Path.ChangeExtension(movieFilePath, "jpg");
}
}
}
@@ -1,4 +1,4 @@
using FluentValidation;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
@@ -18,23 +18,15 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
public RoksboxMetadataSettings()
{
EpisodeMetadata = true;
SeriesImages = true;
SeasonImages = true;
EpisodeImages = true;
MovieMetadata = true;
MovieImages = true;
}
[FieldDefinition(0, Label = "Episode Metadata", Type = FieldType.Checkbox)]
public bool EpisodeMetadata { get; set; }
[FieldDefinition(0, Label = "Movie Metadata", Type = FieldType.Checkbox)]
public bool MovieMetadata { get; set; }
[FieldDefinition(1, Label = "Series Images", Type = FieldType.Checkbox)]
public bool SeriesImages { get; set; }
[FieldDefinition(2, Label = "Season Images", Type = FieldType.Checkbox)]
public bool SeasonImages { get; set; }
[FieldDefinition(3, Label = "Episode Images", Type = FieldType.Checkbox)]
public bool EpisodeImages { get; set; }
[FieldDefinition(1, Label = "Movie Images", Type = FieldType.Checkbox)]
public bool MovieImages { get; set; }
public bool IsValid => true;
@@ -31,30 +31,28 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
_logger = logger;
}
private static readonly Regex SeasonImagesRegex = new Regex(@"^(season (?<season>\d+))|(?<specials>specials)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public override string Name => "WDTV";
public override string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile)
public override string GetFilenameAfterMove(Movie movie, MovieFile movieFile, MetadataFile metadataFile)
{
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath);
if (metadataFile.Type == MetadataType.EpisodeImage)
if (metadataFile.Type == MetadataType.MovieImage)
{
return GetEpisodeImageFilename(episodeFilePath);
return GetMovieFileImageFilename(movieFilePath);
}
if (metadataFile.Type == MetadataType.EpisodeMetadata)
if (metadataFile.Type == MetadataType.MovieMetadata)
{
return GetEpisodeMetadataFilename(episodeFilePath);
return GetMovieFileMetadataFilename(movieFilePath);
}
_logger.Debug("Unknown episode file metadata: {0}", metadataFile.RelativePath);
return Path.Combine(series.Path, metadataFile.RelativePath);
_logger.Debug("Unknown movie file metadata: {0}", metadataFile.RelativePath);
return Path.Combine(movie.Path, metadataFile.RelativePath);
}
public override MetadataFile FindMetadataFile(Series series, string path)
public override MetadataFile FindMetadataFile(Movie movie, string path)
{
var filename = Path.GetFileName(path);
@@ -62,75 +60,47 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
var metadata = new MetadataFile
{
SeriesId = series.Id,
MovieId = movie.Id,
Consumer = GetType().Name,
RelativePath = series.Path.GetRelativePath(path)
RelativePath = movie.Path.GetRelativePath(path)
};
//Series and season images are both named folder.jpg, only season ones sit in season folders
if (Path.GetFileName(filename).Equals("folder.jpg", StringComparison.InvariantCultureIgnoreCase))
{
var parentdir = Directory.GetParent(path);
var seasonMatch = SeasonImagesRegex.Match(parentdir.Name);
if (seasonMatch.Success)
{
metadata.Type = MetadataType.SeasonImage;
if (seasonMatch.Groups["specials"].Success)
{
metadata.SeasonNumber = 0;
}
else
{
metadata.SeasonNumber = Convert.ToInt32(seasonMatch.Groups["season"].Value);
}
return metadata;
}
metadata.Type = MetadataType.SeriesImage;
metadata.Type = MetadataType.MovieImage;
return metadata;
}
var parseResult = Parser.Parser.ParseTitle(filename);
var parseResult = Parser.Parser.ParseMovieTitle(filename, false);
if (parseResult != null &&
!parseResult.FullSeason)
if (parseResult != null)
{
switch (Path.GetExtension(filename).ToLowerInvariant())
{
case ".xml":
metadata.Type = MetadataType.EpisodeMetadata;
metadata.Type = MetadataType.MovieMetadata;
return metadata;
case ".metathumb":
metadata.Type = MetadataType.EpisodeImage;
metadata.Type = MetadataType.MovieImage;
return metadata;
}
}
return null;
}
public override MetadataFileResult SeriesMetadata(Series series)
public override MetadataFileResult MovieMetadata(Movie movie, MovieFile movieFile)
{
//Series metadata is not supported
return null;
}
public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile)
{
if (!Settings.EpisodeMetadata)
if (!Settings.MovieMetadata)
{
return null;
}
_logger.Debug("Generating Episode Metadata for: {0}", Path.Combine(series.Path, episodeFile.RelativePath));
_logger.Debug("Generating Movie File Metadata for: {0}", Path.Combine(movie.Path, movieFile.RelativePath));
var xmlResult = string.Empty;
foreach (var episode in episodeFile.Episodes.Value)
{
var sb = new StringBuilder();
var xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
@@ -141,21 +111,10 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
var doc = new XDocument();
var details = new XElement("details");
details.Add(new XElement("id", series.Id));
details.Add(new XElement("title", string.Format("{0} - {1}x{2:00} - {3}", series.Title, episode.SeasonNumber, episode.EpisodeNumber, episode.Title)));
details.Add(new XElement("series_name", series.Title));
details.Add(new XElement("episode_name", episode.Title));
details.Add(new XElement("season_number", episode.SeasonNumber.ToString("00")));
details.Add(new XElement("episode_number", episode.EpisodeNumber.ToString("00")));
details.Add(new XElement("firstaired", episode.AirDate));
details.Add(new XElement("genre", string.Join(" / ", series.Genres)));
details.Add(new XElement("actor", string.Join(" / ", series.Actors.ConvertAll(c => c.Name + " - " + c.Character))));
details.Add(new XElement("overview", episode.Overview));
//Todo: get guest stars, writer and director
//details.Add(new XElement("credits", tvdbEpisode.Writer.FirstOrDefault()));
//details.Add(new XElement("director", tvdbEpisode.Directors.FirstOrDefault()));
details.Add(new XElement("id", movie.Id));
details.Add(new XElement("title", movie.Title));
details.Add(new XElement("genre", string.Join(" / ", movie.Genres)));
details.Add(new XElement("overview", movie.Overview));
doc.Add(details);
doc.Save(xw);
@@ -163,29 +122,29 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
xmlResult += doc.ToString();
xmlResult += Environment.NewLine;
}
}
var filename = GetEpisodeMetadataFilename(episodeFile.RelativePath);
var filename = GetMovieFileMetadataFilename(movieFile.RelativePath);
return new MetadataFileResult(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
}
public override List<ImageFileResult> SeriesImages(Series series)
public override List<ImageFileResult> MovieImages(Movie movie, MovieFile moviefile)
{
if (!Settings.SeriesImages)
if (!Settings.MovieImages)
{
return new List<ImageFileResult>();
}
//Because we only support one image, attempt to get the Poster type, then if that fails grab the first
var image = series.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? series.Images.FirstOrDefault();
var image = movie.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? movie.Images.FirstOrDefault();
if (image == null)
{
_logger.Trace("Failed to find suitable Series image for series {0}.", series.Title);
_logger.Trace("Failed to find suitable Movie image for movie {0}.", movie.Title);
return new List<ImageFileResult>();
}
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
var source = _mediaCoverService.GetCoverPath(movie.Id, image.CoverType);
var destination = "folder" + Path.GetExtension(source);
return new List<ImageFileResult>
@@ -194,102 +153,14 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
};
}
public override List<ImageFileResult> SeasonImages(Series series, Season season)
private string GetMovieFileMetadataFilename(string movieFilePath)
{
if (!Settings.SeasonImages)
{
return new List<ImageFileResult>();
}
var seasonFolders = GetSeasonFolders(series);
//Work out the path to this season - if we don't have a matching path then skip this season.
string seasonFolder;
if (!seasonFolders.TryGetValue(season.SeasonNumber, out seasonFolder))
{
_logger.Trace("Failed to find season folder for series {0}, season {1}.", series.Title, season.SeasonNumber);
return new List<ImageFileResult>();
}
//WDTV only supports one season image, so first of all try for poster otherwise just use whatever is first in the collection
var image = season.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? season.Images.FirstOrDefault();
if (image == null)
{
_logger.Trace("Failed to find suitable season image for series {0}, season {1}.", series.Title, season.SeasonNumber);
return new List<ImageFileResult>();
}
var path = Path.Combine(seasonFolder, "folder.jpg");
return new List<ImageFileResult>{ new ImageFileResult(path, image.Url) };
return Path.ChangeExtension(movieFilePath, "xml");
}
public override List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile)
private string GetMovieFileImageFilename(string movieFilePath)
{
if (!Settings.EpisodeImages)
{
return new List<ImageFileResult>();
}
var screenshot = episodeFile.Episodes.Value.First().Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
if (screenshot == null)
{
_logger.Trace("Episode screenshot not available");
return new List<ImageFileResult>();
}
return new List<ImageFileResult>{ new ImageFileResult(GetEpisodeImageFilename(episodeFile.RelativePath), screenshot.Url) };
}
private string GetEpisodeMetadataFilename(string episodeFilePath)
{
return Path.ChangeExtension(episodeFilePath, "xml");
}
private string GetEpisodeImageFilename(string episodeFilePath)
{
return Path.ChangeExtension(episodeFilePath, "metathumb");
}
private Dictionary<int, string> GetSeasonFolders(Series series)
{
var seasonFolderMap = new Dictionary<int, string>();
foreach (var folder in _diskProvider.GetDirectories(series.Path))
{
var directoryinfo = new DirectoryInfo(folder);
var seasonMatch = SeasonImagesRegex.Match(directoryinfo.Name);
if (seasonMatch.Success)
{
var seasonNumber = seasonMatch.Groups["season"].Value;
if (seasonNumber.Contains("specials"))
{
seasonFolderMap[0] = folder;
}
else
{
int matchedSeason;
if (int.TryParse(seasonNumber, out matchedSeason))
{
seasonFolderMap[matchedSeason] = folder;
}
else
{
_logger.Debug("Failed to parse season number from {0} for series {1}.", folder, series.Title);
}
}
}
else
{
_logger.Debug("Rejecting folder {0} for series {1}.", Path.GetDirectoryName(folder), series.Title);
}
}
return seasonFolderMap;
return Path.ChangeExtension(movieFilePath, "metathumb");
}
}
}
@@ -1,4 +1,4 @@
using FluentValidation;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
@@ -18,23 +18,15 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
public WdtvMetadataSettings()
{
EpisodeMetadata = true;
SeriesImages = true;
SeasonImages = true;
EpisodeImages = true;
MovieMetadata = true;
MovieImages = true;
}
[FieldDefinition(0, Label = "Episode Metadata", Type = FieldType.Checkbox)]
public bool EpisodeMetadata { get; set; }
[FieldDefinition(0, Label = "Movie Metadata", Type = FieldType.Checkbox)]
public bool MovieMetadata { get; set; }
[FieldDefinition(1, Label = "Series Images", Type = FieldType.Checkbox)]
public bool SeriesImages { get; set; }
[FieldDefinition(2, Label = "Season Images", Type = FieldType.Checkbox)]
public bool SeasonImages { get; set; }
[FieldDefinition(3, Label = "Episode Images", Type = FieldType.Checkbox)]
public bool EpisodeImages { get; set; }
[FieldDefinition(1, Label = "Movie Images", Type = FieldType.Checkbox)]
public bool MovieImages { get; set; }
public bool IsValid => true;
@@ -27,357 +27,227 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
_logger = logger;
}
private static readonly Regex SeriesImagesRegex = new Regex(@"^(?<type>poster|banner|fanart)\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex SeasonImagesRegex = new Regex(@"^season(?<season>\d{2,}|-all|-specials)-(?<type>poster|banner|fanart)\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex EpisodeImageRegex = new Regex(@"-thumb\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex MovieImagesRegex = new Regex(@"^(?<type>poster|banner|fanart|clearart|disc|landscape|logo)\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex MovieFileImageRegex = new Regex(@"(?<type>-thumb|-poster|-banner|-fanart)\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public override string Name => "Kodi (XBMC) / Emby";
public override string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile)
public override string GetFilenameAfterMove(Movie movie, MovieFile movieFile, MetadataFile metadataFile)
{
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath);
var metadataPath = Path.Combine(movie.Path, metadataFile.RelativePath);
if (metadataFile.Type == MetadataType.EpisodeImage)
if (metadataFile.Type == MetadataType.MovieMetadata)
{
return GetEpisodeImageFilename(episodeFilePath);
return GetMovieMetadataFilename(movieFilePath);
}
if (metadataFile.Type == MetadataType.EpisodeMetadata)
if (metadataFile.Type == MetadataType.MovieImage)
{
return GetEpisodeMetadataFilename(episodeFilePath);
return GetMovieImageFilename(movieFilePath, metadataPath);
}
_logger.Debug("Unknown episode file metadata: {0}", metadataFile.RelativePath);
return Path.Combine(series.Path, metadataFile.RelativePath);
_logger.Debug("Unknown movie file metadata: {0}", metadataFile.RelativePath);
return Path.Combine(movie.Path, metadataFile.RelativePath);
}
public override MetadataFile FindMetadataFile(Series series, string path)
public override MetadataFile FindMetadataFile(Movie movie, string path)
{
var filename = Path.GetFileName(path);
if (filename == null) return null;
var metadata = new MetadataFile
{
SeriesId = series.Id,
Consumer = GetType().Name,
RelativePath = series.Path.GetRelativePath(path)
};
if (SeriesImagesRegex.IsMatch(filename))
{
metadata.Type = MetadataType.SeriesImage;
MovieId = movie.Id,
Consumer = GetType().Name,
RelativePath = movie.Path.GetRelativePath(path)
};
if (MovieImagesRegex.IsMatch(filename))
{
metadata.Type = MetadataType.MovieImage;
return metadata;
}
var seasonMatch = SeasonImagesRegex.Match(filename);
if (seasonMatch.Success)
if (MovieFileImageRegex.IsMatch(filename))
{
metadata.Type = MetadataType.SeasonImage;
var seasonNumberMatch = seasonMatch.Groups["season"].Value;
int seasonNumber;
if (seasonNumberMatch.Contains("specials"))
{
metadata.SeasonNumber = 0;
}
else if (int.TryParse(seasonNumberMatch, out seasonNumber))
{
metadata.SeasonNumber = seasonNumber;
}
else
{
return null;
}
metadata.Type = MetadataType.MovieImage;
return metadata;
}
if (EpisodeImageRegex.IsMatch(filename))
if (filename.Equals("movie.nfo", StringComparison.OrdinalIgnoreCase))
{
metadata.Type = MetadataType.EpisodeImage;
metadata.Type = MetadataType.MovieMetadata;
return metadata;
}
if (filename.Equals("tvshow.nfo", StringComparison.InvariantCultureIgnoreCase))
{
metadata.Type = MetadataType.SeriesMetadata;
return metadata;
}
var parseResult = Parser.Parser.ParseTitle(filename);
var parseResult = Parser.Parser.ParseMovieTitle(filename, false);
if (parseResult != null &&
!parseResult.FullSeason &&
Path.GetExtension(filename) == ".nfo")
Path.GetExtension(filename).Equals(".nfo", StringComparison.OrdinalIgnoreCase))
{
metadata.Type = MetadataType.EpisodeMetadata;
metadata.Type = MetadataType.MovieMetadata;
return metadata;
}
return null;
}
public override MetadataFileResult SeriesMetadata(Series series)
public override MetadataFileResult MovieMetadata(Movie movie, MovieFile movieFile)
{
if (!Settings.SeriesMetadata)
if (!Settings.MovieMetadata)
{
return null;
}
_logger.Debug("Generating tvshow.nfo for: {0}", series.Title);
_logger.Debug("Generating Movie Metadata for: {0}", Path.Combine(movie.Path, movieFile.RelativePath));
var xmlResult = string.Empty;
var sb = new StringBuilder();
var xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Indent = false;
var episodeGuideUrl = string.Format("http://www.thetvdb.com/api/1D62F2F90030C444/series/{0}/all/en.zip", series.TvdbId);
using (var xw = XmlWriter.Create(sb, xws))
{
var tvShow = new XElement("tvshow");
var doc = new XDocument();
var image = movie.Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
tvShow.Add(new XElement("title", series.Title));
var details = new XElement("movie");
if (series.Ratings != null && series.Ratings.Votes > 0)
details.Add(new XElement("title", movie.Title));
if (movie.Ratings != null && movie.Ratings.Votes > 0)
{
tvShow.Add(new XElement("rating", series.Ratings.Value));
details.Add(new XElement("rating", movie.Ratings.Value));
}
tvShow.Add(new XElement("plot", series.Overview));
tvShow.Add(new XElement("episodeguide", new XElement("url", episodeGuideUrl)));
tvShow.Add(new XElement("episodeguideurl", episodeGuideUrl));
tvShow.Add(new XElement("mpaa", series.Certification));
tvShow.Add(new XElement("id", series.TvdbId));
details.Add(new XElement("plot", movie.Overview));
details.Add(new XElement("id", movie.ImdbId));
details.Add(new XElement("year", movie.Year));
foreach (var genre in series.Genres)
if (movie.InCinemas.HasValue)
{
tvShow.Add(new XElement("genre", genre));
details.Add(new XElement("premiered", movie.InCinemas.Value.ToString()));
}
if (series.FirstAired.HasValue)
foreach (var genre in movie.Genres)
{
tvShow.Add(new XElement("premiered", series.FirstAired.Value.ToString("yyyy-MM-dd")));
details.Add(new XElement("genre", genre));
}
tvShow.Add(new XElement("studio", series.Network));
details.Add(new XElement("studio", movie.Studio));
foreach (var actor in series.Actors)
if (image == null)
{
var xmlActor = new XElement("actor",
new XElement("name", actor.Name),
new XElement("role", actor.Character));
details.Add(new XElement("thumb"));
}
if (actor.Images.Any())
else
{
details.Add(new XElement("thumb", image.Url));
}
details.Add(new XElement("watched", "false"));
if (movieFile.MediaInfo != null)
{
var fileInfo = new XElement("fileinfo");
var streamDetails = new XElement("streamdetails");
var video = new XElement("video");
video.Add(new XElement("aspect", (float)movieFile.MediaInfo.Width / (float)movieFile.MediaInfo.Height));
video.Add(new XElement("bitrate", movieFile.MediaInfo.VideoBitrate));
video.Add(new XElement("codec", movieFile.MediaInfo.VideoCodec));
video.Add(new XElement("framerate", movieFile.MediaInfo.VideoFps));
video.Add(new XElement("height", movieFile.MediaInfo.Height));
video.Add(new XElement("scantype", movieFile.MediaInfo.ScanType));
video.Add(new XElement("width", movieFile.MediaInfo.Width));
if (movieFile.MediaInfo.RunTime != null)
{
xmlActor.Add(new XElement("thumb", actor.Images.First().Url));
video.Add(new XElement("duration", movieFile.MediaInfo.RunTime.TotalMinutes));
video.Add(new XElement("durationinseconds", movieFile.MediaInfo.RunTime.TotalSeconds));
}
tvShow.Add(xmlActor);
streamDetails.Add(video);
var audio = new XElement("audio");
audio.Add(new XElement("bitrate", movieFile.MediaInfo.AudioBitrate));
audio.Add(new XElement("channels", movieFile.MediaInfo.AudioChannels));
audio.Add(new XElement("codec", GetAudioCodec(movieFile.MediaInfo.AudioFormat)));
audio.Add(new XElement("language", movieFile.MediaInfo.AudioLanguages));
streamDetails.Add(audio);
if (movieFile.MediaInfo.Subtitles != null && movieFile.MediaInfo.Subtitles.Length > 0)
{
var subtitle = new XElement("subtitle");
subtitle.Add(new XElement("language", movieFile.MediaInfo.Subtitles));
streamDetails.Add(subtitle);
}
fileInfo.Add(streamDetails);
details.Add(fileInfo);
}
var doc = new XDocument(tvShow);
doc.Add(details);
doc.Save(xw);
_logger.Debug("Saving tvshow.nfo for {0}", series.Title);
xmlResult += doc.ToString();
xmlResult += Environment.NewLine;
return new MetadataFileResult("tvshow.nfo", doc.ToString());
}
var metadataFileName = GetMovieMetadataFilename(movieFile.RelativePath);
if (Settings.UseMovieNfo)
{
metadataFileName = "movie.nfo";
}
return new MetadataFileResult(metadataFileName, xmlResult.Trim(Environment.NewLine.ToCharArray()));
}
public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile)
public override List<ImageFileResult> MovieImages(Movie movie, MovieFile movieFile)
{
if (!Settings.EpisodeMetadata)
{
return null;
}
_logger.Debug("Generating Episode Metadata for: {0}", Path.Combine(series.Path, episodeFile.RelativePath));
var xmlResult = string.Empty;
foreach (var episode in episodeFile.Episodes.Value)
{
var sb = new StringBuilder();
var xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Indent = false;
using (var xw = XmlWriter.Create(sb, xws))
{
var doc = new XDocument();
var image = episode.Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
var details = new XElement("episodedetails");
details.Add(new XElement("title", episode.Title));
details.Add(new XElement("season", episode.SeasonNumber));
details.Add(new XElement("episode", episode.EpisodeNumber));
details.Add(new XElement("aired", episode.AirDate));
details.Add(new XElement("plot", episode.Overview));
//If trakt ever gets airs before information for specials we should add set it
details.Add(new XElement("displayseason"));
details.Add(new XElement("displayepisode"));
if (image == null)
{
details.Add(new XElement("thumb"));
}
else
{
details.Add(new XElement("thumb", image.Url));
}
details.Add(new XElement("watched", "false"));
if (episode.Ratings != null && episode.Ratings.Votes > 0)
{
details.Add(new XElement("rating", episode.Ratings.Value));
}
if (episodeFile.MediaInfo != null)
{
var fileInfo = new XElement("fileinfo");
var streamDetails = new XElement("streamdetails");
var video = new XElement("video");
video.Add(new XElement("aspect", (float) episodeFile.MediaInfo.Width / (float) episodeFile.MediaInfo.Height));
video.Add(new XElement("bitrate", episodeFile.MediaInfo.VideoBitrate));
video.Add(new XElement("codec", episodeFile.MediaInfo.VideoCodec));
video.Add(new XElement("framerate", episodeFile.MediaInfo.VideoFps));
video.Add(new XElement("height", episodeFile.MediaInfo.Height));
video.Add(new XElement("scantype", episodeFile.MediaInfo.ScanType));
video.Add(new XElement("width", episodeFile.MediaInfo.Height));
if (episodeFile.MediaInfo.RunTime != null)
{
video.Add(new XElement("duration", episodeFile.MediaInfo.RunTime.TotalMinutes));
video.Add(new XElement("durationinseconds", episodeFile.MediaInfo.RunTime.TotalSeconds));
}
streamDetails.Add(video);
var audio = new XElement("audio");
audio.Add(new XElement("bitrate", episodeFile.MediaInfo.AudioBitrate));
audio.Add(new XElement("channels", episodeFile.MediaInfo.AudioChannels));
audio.Add(new XElement("codec", GetAudioCodec(episodeFile.MediaInfo.AudioFormat)));
audio.Add(new XElement("language", episodeFile.MediaInfo.AudioLanguages));
streamDetails.Add(audio);
if (episodeFile.MediaInfo.Subtitles != null && episodeFile.MediaInfo.Subtitles.Length > 0)
{
var subtitle = new XElement("subtitle");
subtitle.Add(new XElement("language", episodeFile.MediaInfo.Subtitles));
streamDetails.Add(subtitle);
}
fileInfo.Add(streamDetails);
details.Add(fileInfo);
}
//Todo: get guest stars, writer and director
//details.Add(new XElement("credits", tvdbEpisode.Writer.FirstOrDefault()));
//details.Add(new XElement("director", tvdbEpisode.Directors.FirstOrDefault()));
doc.Add(details);
doc.Save(xw);
xmlResult += doc.ToString();
xmlResult += Environment.NewLine;
}
}
return new MetadataFileResult(GetEpisodeMetadataFilename(episodeFile.RelativePath), xmlResult.Trim(Environment.NewLine.ToCharArray()));
}
public override List<ImageFileResult> SeriesImages(Series series)
{
if (!Settings.SeriesImages)
if (!Settings.MovieImages)
{
return new List<ImageFileResult>();
}
return ProcessSeriesImages(series).ToList();
return ProcessMovieImages(movie).ToList();
}
public override List<ImageFileResult> SeasonImages(Series series, Season season)
private IEnumerable<ImageFileResult> ProcessMovieImages(Movie movie)
{
if (!Settings.SeasonImages)
foreach (var image in movie.Images)
{
return new List<ImageFileResult>();
}
return ProcessSeasonImages(series, season).ToList();
}
public override List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile)
{
if (!Settings.EpisodeImages)
{
return new List<ImageFileResult>();
}
try
{
var screenshot = episodeFile.Episodes.Value.First().Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
if (screenshot == null)
{
_logger.Debug("Episode screenshot not available");
return new List<ImageFileResult>();
}
return new List<ImageFileResult>
{
new ImageFileResult(GetEpisodeImageFilename(episodeFile.RelativePath), screenshot.Url)
};
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to process episode image for file: " + Path.Combine(series.Path, episodeFile.RelativePath));
return new List<ImageFileResult>();
}
}
private IEnumerable<ImageFileResult> ProcessSeriesImages(Series series)
{
foreach (var image in series.Images)
{
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
var destination = image.CoverType.ToString().ToLowerInvariant() + Path.GetExtension(source);
var source = _mediaCoverService.GetCoverPath(movie.Id, image.CoverType);
var destination = Path.ChangeExtension(movie.MovieFile.RelativePath,"").TrimEnd(".") + "-" + image.CoverType.ToString().ToLowerInvariant() + Path.GetExtension(source);
yield return new ImageFileResult(destination, source);
}
}
private IEnumerable<ImageFileResult> ProcessSeasonImages(Series series, Season season)
private string GetMovieMetadataFilename(string movieFilePath)
{
foreach (var image in season.Images)
return Path.ChangeExtension(movieFilePath, "nfo");
}
private string GetMovieImageFilename(string movieFilePath, string existingImageName)
{
var fileExtention = Path.GetExtension(existingImageName);
var match = MovieFileImageRegex.Matches(existingImageName);
if (match.Count > 0)
{
var filename = string.Format("season{0:00}-{1}.jpg", season.SeasonNumber, image.CoverType.ToString().ToLower());
if (season.SeasonNumber == 0)
{
filename = string.Format("season-specials-{0}.jpg", image.CoverType.ToString().ToLower());
}
yield return new ImageFileResult(filename, image.Url);
var imageType = match[0].Groups["type"].Value;
return Parser.Parser.RemoveFileExtension(movieFilePath) + imageType + fileExtention;
}
}
private string GetEpisodeMetadataFilename(string episodeFilePath)
{
return Path.ChangeExtension(episodeFilePath, "nfo");
}
private string GetEpisodeImageFilename(string episodeFilePath)
{
return Path.ChangeExtension(episodeFilePath, "").Trim('.') + "-thumb.jpg";
return existingImageName;
}
private string GetAudioCodec(string audioCodec)
@@ -1,4 +1,4 @@
using FluentValidation;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
@@ -18,28 +18,20 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
public XbmcMetadataSettings()
{
SeriesMetadata = true;
EpisodeMetadata = true;
SeriesImages = true;
SeasonImages = true;
EpisodeImages = true;
MovieMetadata = true;
MovieImages = true;
UseMovieNfo = false;
}
[FieldDefinition(0, Label = "Series Metadata", Type = FieldType.Checkbox)]
public bool SeriesMetadata { get; set; }
[FieldDefinition(0, Label = "Movie Metadata", Type = FieldType.Checkbox)]
public bool MovieMetadata { get; set; }
[FieldDefinition(1, Label = "Episode Metadata", Type = FieldType.Checkbox)]
public bool EpisodeMetadata { get; set; }
[FieldDefinition(1, Label = "Movie Images", Type = FieldType.Checkbox)]
public bool MovieImages { get; set; }
[FieldDefinition(2, Label = "Series Images", Type = FieldType.Checkbox)]
public bool SeriesImages { get; set; }
[FieldDefinition(2, Label = "Use Movie.nfo", Type = FieldType.Checkbox, HelpText = "Radarr will write metadata to movie.nfo instead of the default <movie-filename>.nfo")]
public bool UseMovieNfo { get; set; }
[FieldDefinition(3, Label = "Season Images", Type = FieldType.Checkbox)]
public bool SeasonImages { get; set; }
[FieldDefinition(4, Label = "Episode Images", Type = FieldType.Checkbox)]
public bool EpisodeImages { get; set; }
public bool IsValid => true;
public NzbDroneValidationResult Validate()
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
@@ -32,12 +32,12 @@ namespace NzbDrone.Core.Extras.Metadata
public override int Order => 0;
public override IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles)
public override IEnumerable<ExtraFile> ProcessFiles(Movie movie, List<string> filesOnDisk, List<string> importedFiles)
{
_logger.Debug("Looking for existing metadata in {0}", series.Path);
_logger.Debug("Looking for existing metadata in {0}", movie.Path);
var metadataFiles = new List<MetadataFile>();
var filterResult = FilterAndClean(series, filesOnDisk, importedFiles);
var filterResult = FilterAndClean(movie, filesOnDisk, importedFiles);
foreach (var possibleMetadataFile in filterResult.FilesOnDisk)
{
@@ -50,38 +50,31 @@ namespace NzbDrone.Core.Extras.Metadata
foreach (var consumer in _consumers)
{
var metadata = consumer.FindMetadataFile(series, possibleMetadataFile);
var metadata = consumer.FindMetadataFile(movie, possibleMetadataFile);
if (metadata == null)
{
continue;
}
if (metadata.Type == MetadataType.EpisodeImage ||
metadata.Type == MetadataType.EpisodeMetadata)
if (metadata.Type == MetadataType.MovieImage ||
metadata.Type == MetadataType.MovieMetadata)
{
var localEpisode = _parsingService.GetLocalEpisode(possibleMetadataFile, series);
var localMovie = _parsingService.GetLocalMovie(possibleMetadataFile, movie);
if (localEpisode == null)
if (localMovie == null)
{
_logger.Debug("Unable to parse extra file: {0}", possibleMetadataFile);
continue;
}
if (localEpisode.Episodes.Empty())
if (localMovie.Movie == null)
{
_logger.Debug("Cannot find related episodes for: {0}", possibleMetadataFile);
_logger.Debug("Cannot find related movie for: {0}", possibleMetadataFile);
continue;
}
if (localEpisode.Episodes.DistinctBy(e => e.EpisodeFileId).Count() > 1)
{
_logger.Debug("Extra file: {0} does not match existing files.", possibleMetadataFile);
continue;
}
metadata.SeasonNumber = localEpisode.SeasonNumber;
metadata.EpisodeFileId = localEpisode.Episodes.First().EpisodeFileId;
metadata.MovieFileId = localMovie.Movie.MovieFileId;
}
metadata.Extension = Path.GetExtension(possibleMetadataFile);
@@ -1,4 +1,4 @@
using System.IO;
using System.IO;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Tv;
@@ -7,7 +7,7 @@ namespace NzbDrone.Core.Extras.Metadata.Files
{
public interface ICleanMetadataService
{
void Clean(Series series);
void Clean(Movie movie);
}
public class CleanExtraFileService : ICleanMetadataService
@@ -25,15 +25,15 @@ namespace NzbDrone.Core.Extras.Metadata.Files
_logger = logger;
}
public void Clean(Series series)
public void Clean(Movie movie)
{
_logger.Debug("Cleaning missing metadata files for series: {0}", series.Title);
_logger.Debug("Cleaning missing metadata files for movie: {0}", movie.Title);
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
var metadataFiles = _metadataFileService.GetFilesByMovie(movie.Id);
foreach (var metadataFile in metadataFiles)
{
if (!_diskProvider.FileExists(Path.Combine(series.Path, metadataFile.RelativePath)))
if (!_diskProvider.FileExists(Path.Combine(movie.Path, metadataFile.RelativePath)))
{
_logger.Debug("Deleting metadata file from database: {0}", metadataFile.RelativePath);
_metadataFileService.Delete(metadataFile.Id);
@@ -1,4 +1,4 @@
using NLog;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.MediaFiles;
@@ -12,8 +12,8 @@ namespace NzbDrone.Core.Extras.Metadata.Files
public class MetadataFileService : ExtraFileService<MetadataFile>, IMetadataFileService
{
public MetadataFileService(IExtraFileRepository<MetadataFile> repository, ISeriesService seriesService, IDiskProvider diskProvider, IRecycleBinProvider recycleBinProvider, Logger logger)
: base(repository, seriesService, diskProvider, recycleBinProvider, logger)
public MetadataFileService(IExtraFileRepository<MetadataFile> repository, IMovieService movieService, IDiskProvider diskProvider, IRecycleBinProvider recycleBinProvider, Logger logger)
: base(repository, movieService, diskProvider, recycleBinProvider, logger)
{
}
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.ThingiProvider;
@@ -8,12 +8,9 @@ namespace NzbDrone.Core.Extras.Metadata
{
public interface IMetadata : IProvider
{
string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile);
MetadataFile FindMetadataFile(Series series, string path);
MetadataFileResult SeriesMetadata(Series series);
MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile);
List<ImageFileResult> SeriesImages(Series series);
List<ImageFileResult> SeasonImages(Series series, Season season);
List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile);
string GetFilenameAfterMove(Movie movie, MovieFile movieFile, MetadataFile metadataFile);
MetadataFile FindMetadataFile(Movie movie, string path);
MetadataFileResult MovieMetadata(Movie movie, MovieFile movieFile);
List<ImageFileResult> MovieImages(Movie movie, MovieFile movieFile);
}
}
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using FluentValidation.Results;
@@ -29,22 +29,19 @@ namespace NzbDrone.Core.Extras.Metadata
return new ValidationResult();
}
public virtual string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile)
public virtual string GetFilenameAfterMove(Movie movie, MovieFile movieFile, MetadataFile metadataFile)
{
var existingFilename = Path.Combine(series.Path, metadataFile.RelativePath);
var existingFilename = Path.Combine(movie.Path, metadataFile.RelativePath);
var extension = Path.GetExtension(existingFilename).TrimStart('.');
var newFileName = Path.ChangeExtension(Path.Combine(series.Path, episodeFile.RelativePath), extension);
var newFileName = Path.ChangeExtension(Path.Combine(movie.Path, movieFile.RelativePath), extension);
return newFileName;
}
public abstract MetadataFile FindMetadataFile(Series series, string path);
public abstract MetadataFileResult SeriesMetadata(Series series);
public abstract MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile);
public abstract List<ImageFileResult> SeriesImages(Series series);
public abstract List<ImageFileResult> SeasonImages(Series series, Season season);
public abstract List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile);
public abstract MetadataFile FindMetadataFile(Movie movie, string path);
public abstract MetadataFileResult MovieMetadata(Movie movie, MovieFile movieFile);
public abstract List<ImageFileResult> MovieImages(Movie movie, MovieFile movieFile);
public virtual object RequestAction(string action, IDictionary<string, string> query) { return null; }
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -19,23 +19,23 @@ namespace NzbDrone.Core.Extras.Metadata
{
private readonly IMetadataFactory _metadataFactory;
private readonly ICleanMetadataService _cleanMetadataService;
private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider;
private readonly IDiskTransferService _diskTransferService;
private readonly IHttpClient _httpClient;
private readonly IMediaFileAttributeService _mediaFileAttributeService;
private readonly IMetadataFileService _metadataFileService;
private readonly Logger _logger;
public MetadataService(IConfigService configService,
IDiskProvider diskProvider,
IDiskTransferService diskTransferService,
IMetadataFactory metadataFactory,
ICleanMetadataService cleanMetadataService,
IDiskProvider diskProvider,
IHttpClient httpClient,
IMediaFileAttributeService mediaFileAttributeService,
IMetadataFileService metadataFileService,
Logger logger)
: base(configService, diskTransferService, metadataFileService)
: base(configService, diskProvider, diskTransferService, logger)
{
_metadataFactory = metadataFactory;
_cleanMetadataService = cleanMetadataService;
@@ -49,14 +49,14 @@ namespace NzbDrone.Core.Extras.Metadata
public override int Order => 0;
public override IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles)
public override IEnumerable<ExtraFile> CreateAfterMovieScan(Movie movie, List<MovieFile> movieFiles)
{
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
_cleanMetadataService.Clean(series);
var metadataFiles = _metadataFileService.GetFilesByMovie(movie.Id);
_cleanMetadataService.Clean(movie);
if (!_diskProvider.FolderExists(series.Path))
if (!_diskProvider.FolderExists(movie.Path))
{
_logger.Info("Series folder does not exist, skipping metadata creation");
_logger.Info("Movie folder does not exist, skipping metadata creation");
return Enumerable.Empty<MetadataFile>();
}
@@ -66,14 +66,10 @@ namespace NzbDrone.Core.Extras.Metadata
{
var consumerFiles = GetMetadataFilesForConsumer(consumer, metadataFiles);
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles));
files.AddRange(ProcessSeriesImages(consumer, series, consumerFiles));
files.AddRange(ProcessSeasonImages(consumer, series, consumerFiles));
foreach (var episodeFile in episodeFiles)
foreach (var episodeFile in movieFiles)
{
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, series, episodeFile, consumerFiles));
files.AddRange(ProcessEpisodeImages(consumer, series, episodeFile, consumerFiles));
files.AddIfNotNull(ProcessMovieMetadata(consumer, movie, episodeFile, consumerFiles));
files.AddRange(ProcessMovieImages(consumer, movie, episodeFile, consumerFiles));
}
}
@@ -82,15 +78,15 @@ namespace NzbDrone.Core.Extras.Metadata
return files;
}
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
public override IEnumerable<ExtraFile> CreateAfterMovieImport(Movie movie, MovieFile movieFile)
{
var files = new List<MetadataFile>();
foreach (var consumer in _metadataFactory.Enabled())
{
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, series, episodeFile, new List<MetadataFile>()));
files.AddRange(ProcessEpisodeImages(consumer, series, episodeFile, new List<MetadataFile>()));
files.AddIfNotNull(ProcessMovieMetadata(consumer, movie, movieFile, new List<MetadataFile>()));
files.AddRange(ProcessMovieImages(consumer, movie, movieFile, new List<MetadataFile>()));
}
_metadataFileService.Upsert(files);
@@ -98,41 +94,9 @@ namespace NzbDrone.Core.Extras.Metadata
return files;
}
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder)
public override IEnumerable<ExtraFile> MoveFilesAfterRename(Movie movie, List<MovieFile> movieFiles)
{
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
if (seriesFolder.IsNullOrWhiteSpace() && seasonFolder.IsNullOrWhiteSpace())
{
return new List<MetadataFile>();
}
var files = new List<MetadataFile>();
foreach (var consumer in _metadataFactory.Enabled())
{
var consumerFiles = GetMetadataFilesForConsumer(consumer, metadataFiles);
if (seriesFolder.IsNotNullOrWhiteSpace())
{
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles));
files.AddRange(ProcessSeriesImages(consumer, series, consumerFiles));
}
if (seasonFolder.IsNotNullOrWhiteSpace())
{
files.AddRange(ProcessSeasonImages(consumer, series, consumerFiles));
}
}
_metadataFileService.Upsert(files);
return files;
}
public override IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles)
{
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
var metadataFiles = _metadataFileService.GetFilesByMovie(movie.Id);
var movedFiles = new List<MetadataFile>();
// TODO: Move EpisodeImage and EpisodeMetadata metadata files, instead of relying on consumers to do it
@@ -140,26 +104,26 @@ namespace NzbDrone.Core.Extras.Metadata
foreach (var consumer in _metadataFactory.GetAvailableProviders())
{
foreach (var episodeFile in episodeFiles)
foreach (var movieFile in movieFiles)
{
var metadataFilesForConsumer = GetMetadataFilesForConsumer(consumer, metadataFiles).Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
var metadataFilesForConsumer = GetMetadataFilesForConsumer(consumer, metadataFiles).Where(m => m.MovieFileId == movieFile.Id).ToList();
foreach (var metadataFile in metadataFilesForConsumer)
{
var newFileName = consumer.GetFilenameAfterMove(series, episodeFile, metadataFile);
var existingFileName = Path.Combine(series.Path, metadataFile.RelativePath);
var newFileName = consumer.GetFilenameAfterMove(movie, movieFile, metadataFile);
var existingFileName = Path.Combine(movie.Path, metadataFile.RelativePath);
if (newFileName.PathNotEquals(existingFileName))
{
try
{
_diskProvider.MoveFile(existingFileName, newFileName);
metadataFile.RelativePath = series.Path.GetRelativePath(newFileName);
metadataFile.RelativePath = movie.Path.GetRelativePath(newFileName);
movedFiles.Add(metadataFile);
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to move metadata file: {0}", existingFileName);
_logger.Warn(ex, "Unable to move metadata file after rename: {0}", existingFileName);
}
}
}
@@ -171,94 +135,50 @@ namespace NzbDrone.Core.Extras.Metadata
return movedFiles;
}
public override ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly)
public override ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly)
{
return null;
}
private List<MetadataFile> GetMetadataFilesForConsumer(IMetadata consumer, List<MetadataFile> seriesMetadata)
private List<MetadataFile> GetMetadataFilesForConsumer(IMetadata consumer, List<MetadataFile> movieMetadata)
{
return seriesMetadata.Where(c => c.Consumer == consumer.GetType().Name).ToList();
return movieMetadata.Where(c => c.Consumer == consumer.GetType().Name).ToList();
}
private MetadataFile ProcessSeriesMetadata(IMetadata consumer, Series series, List<MetadataFile> existingMetadataFiles)
private MetadataFile ProcessMovieMetadata(IMetadata consumer, Movie movie, MovieFile movieFile, List<MetadataFile> existingMetadataFiles)
{
var seriesMetadata = consumer.SeriesMetadata(series);
var movieFileMetadata = consumer.MovieMetadata(movie, movieFile);
if (seriesMetadata == null)
if (movieFileMetadata == null)
{
return null;
}
var hash = seriesMetadata.Contents.SHA256Hash();
var fullPath = Path.Combine(movie.Path, movieFileMetadata.RelativePath);
var metadata = GetMetadataFile(series, existingMetadataFiles, e => e.Type == MetadataType.SeriesMetadata) ??
new MetadataFile
{
SeriesId = series.Id,
Consumer = consumer.GetType().Name,
Type = MetadataType.SeriesMetadata
};
if (hash == metadata.Hash)
{
if (seriesMetadata.RelativePath != metadata.RelativePath)
{
metadata.RelativePath = seriesMetadata.RelativePath;
return metadata;
}
return null;
}
var fullPath = Path.Combine(series.Path, seriesMetadata.RelativePath);
_logger.Debug("Writing Series Metadata to: {0}", fullPath);
SaveMetadataFile(fullPath, seriesMetadata.Contents);
metadata.Hash = hash;
metadata.RelativePath = seriesMetadata.RelativePath;
metadata.Extension = Path.GetExtension(fullPath);
return metadata;
}
private MetadataFile ProcessEpisodeMetadata(IMetadata consumer, Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
{
var episodeMetadata = consumer.EpisodeMetadata(series, episodeFile);
if (episodeMetadata == null)
{
return null;
}
var fullPath = Path.Combine(series.Path, episodeMetadata.RelativePath);
var existingMetadata = GetMetadataFile(series, existingMetadataFiles, c => c.Type == MetadataType.EpisodeMetadata &&
c.EpisodeFileId == episodeFile.Id);
var existingMetadata = GetMetadataFile(movie, existingMetadataFiles, c => c.Type == MetadataType.MovieMetadata &&
c.MovieFileId == movieFile.Id);
if (existingMetadata != null)
{
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
var existingFullPath = Path.Combine(movie.Path, existingMetadata.RelativePath);
if (fullPath.PathNotEquals(existingFullPath))
{
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
existingMetadata.RelativePath = episodeMetadata.RelativePath;
existingMetadata.RelativePath = movieFileMetadata.RelativePath;
}
}
var hash = episodeMetadata.Contents.SHA256Hash();
var hash = movieFileMetadata.Contents.SHA256Hash();
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
SeasonNumber = episodeFile.SeasonNumber,
EpisodeFileId = episodeFile.Id,
MovieId = movie.Id,
MovieFileId = movieFile.Id,
Consumer = consumer.GetType().Name,
Type = MetadataType.EpisodeMetadata,
RelativePath = episodeMetadata.RelativePath,
Type = MetadataType.MovieMetadata,
RelativePath = movieFileMetadata.RelativePath,
Extension = Path.GetExtension(fullPath)
};
@@ -267,105 +187,34 @@ namespace NzbDrone.Core.Extras.Metadata
return null;
}
_logger.Debug("Writing Episode Metadata to: {0}", fullPath);
SaveMetadataFile(fullPath, episodeMetadata.Contents);
_logger.Debug("Writing Movie File Metadata to: {0}", fullPath);
SaveMetadataFile(fullPath, movieFileMetadata.Contents);
metadata.Hash = hash;
return metadata;
}
private List<MetadataFile> ProcessSeriesImages(IMetadata consumer, Series series, List<MetadataFile> existingMetadataFiles)
private List<MetadataFile> ProcessMovieImages(IMetadata consumer, Movie movie, MovieFile movieFile, List<MetadataFile> existingMetadataFiles)
{
var result = new List<MetadataFile>();
foreach (var image in consumer.SeriesImages(series))
foreach (var image in consumer.MovieImages(movie, movieFile))
{
var fullPath = Path.Combine(series.Path, image.RelativePath);
var fullPath = Path.Combine(movie.Path, image.RelativePath);
if (_diskProvider.FileExists(fullPath))
{
_logger.Debug("Series image already exists: {0}", fullPath);
_logger.Debug("Movie image already exists: {0}", fullPath);
continue;
}
var metadata = GetMetadataFile(series, existingMetadataFiles, c => c.Type == MetadataType.SeriesImage &&
c.RelativePath == image.RelativePath) ??
new MetadataFile
{
SeriesId = series.Id,
Consumer = consumer.GetType().Name,
Type = MetadataType.SeriesImage,
RelativePath = image.RelativePath,
Extension = Path.GetExtension(fullPath)
};
DownloadImage(series, image);
result.Add(metadata);
}
return result;
}
private List<MetadataFile> ProcessSeasonImages(IMetadata consumer, Series series, List<MetadataFile> existingMetadataFiles)
{
var result = new List<MetadataFile>();
foreach (var season in series.Seasons)
{
foreach (var image in consumer.SeasonImages(series, season))
{
var fullPath = Path.Combine(series.Path, image.RelativePath);
if (_diskProvider.FileExists(fullPath))
{
_logger.Debug("Season image already exists: {0}", fullPath);
continue;
}
var metadata = GetMetadataFile(series, existingMetadataFiles, c => c.Type == MetadataType.SeasonImage &&
c.SeasonNumber == season.SeasonNumber &&
c.RelativePath == image.RelativePath) ??
new MetadataFile
{
SeriesId = series.Id,
SeasonNumber = season.SeasonNumber,
Consumer = consumer.GetType().Name,
Type = MetadataType.SeasonImage,
RelativePath = image.RelativePath,
Extension = Path.GetExtension(fullPath)
};
DownloadImage(series, image);
result.Add(metadata);
}
}
return result;
}
private List<MetadataFile> ProcessEpisodeImages(IMetadata consumer, Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
{
var result = new List<MetadataFile>();
foreach (var image in consumer.EpisodeImages(series, episodeFile))
{
var fullPath = Path.Combine(series.Path, image.RelativePath);
if (_diskProvider.FileExists(fullPath))
{
_logger.Debug("Episode image already exists: {0}", fullPath);
continue;
}
var existingMetadata = GetMetadataFile(series, existingMetadataFiles, c => c.Type == MetadataType.EpisodeImage &&
c.EpisodeFileId == episodeFile.Id);
var existingMetadata = GetMetadataFile(movie, existingMetadataFiles, c => c.Type == MetadataType.MovieImage &&
c.RelativePath == image.RelativePath);
if (existingMetadata != null)
{
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
var existingFullPath = Path.Combine(movie.Path, existingMetadata.RelativePath);
if (fullPath.PathNotEquals(existingFullPath))
{
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
@@ -378,16 +227,15 @@ namespace NzbDrone.Core.Extras.Metadata
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
SeasonNumber = episodeFile.SeasonNumber,
EpisodeFileId = episodeFile.Id,
MovieId = movie.Id,
MovieFileId = movieFile.Id,
Consumer = consumer.GetType().Name,
Type = MetadataType.EpisodeImage,
Type = MetadataType.MovieImage,
RelativePath = image.RelativePath,
Extension = Path.GetExtension(fullPath)
};
DownloadImage(series, image);
DownloadImage(movie, image);
result.Add(metadata);
}
@@ -395,9 +243,9 @@ namespace NzbDrone.Core.Extras.Metadata
return result;
}
private void DownloadImage(Series series, ImageFileResult image)
private void DownloadImage(Movie movie, ImageFileResult image)
{
var fullPath = Path.Combine(series.Path, image.RelativePath);
var fullPath = Path.Combine(movie.Path, image.RelativePath);
try
{
@@ -413,11 +261,11 @@ namespace NzbDrone.Core.Extras.Metadata
}
catch (WebException ex)
{
_logger.Warn(ex, "Couldn't download image {0} for {1}. {2}", image.Url, series, ex.Message);
_logger.Warn(ex, "Couldn't download image {0} for {1}. {2}", image.Url, movie, ex.Message);
}
catch (Exception ex)
{
_logger.Error(ex, "Couldn't download image {0} for {1}. {2}", image.Url, series, ex.Message);
_logger.Error(ex, "Couldn't download image {0} for {1}. {2}", image.Url, movie, ex.Message);
}
}
@@ -427,7 +275,7 @@ namespace NzbDrone.Core.Extras.Metadata
_mediaFileAttributeService.SetFilePermissions(path);
}
private MetadataFile GetMetadataFile(Series series, List<MetadataFile> existingMetadataFiles, Func<MetadataFile, bool> predicate)
private MetadataFile GetMetadataFile(Movie movie, List<MetadataFile> existingMetadataFiles, Func<MetadataFile, bool> predicate)
{
var matchingMetadataFiles = existingMetadataFiles.Where(predicate).ToList();
@@ -439,7 +287,7 @@ namespace NzbDrone.Core.Extras.Metadata
//Remove duplicate metadata files from DB and disk
foreach (var file in matchingMetadataFiles.Skip(1))
{
var path = Path.Combine(series.Path, file.RelativePath);
var path = Path.Combine(movie.Path, file.RelativePath);
_logger.Debug("Removing duplicate Metadata file: {0}", path);
@@ -1,12 +1,9 @@
namespace NzbDrone.Core.Extras.Metadata
namespace NzbDrone.Core.Extras.Metadata
{
public enum MetadataType
{
Unknown = 0,
SeriesMetadata = 1,
EpisodeMetadata = 2,
SeriesImage = 3,
SeasonImage = 4,
EpisodeImage = 5
MovieMetadata = 1,
MovieImage = 2
}
}
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
@@ -27,42 +27,43 @@ namespace NzbDrone.Core.Extras.Others
public override int Order => 2;
public override IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles)
public override IEnumerable<ExtraFile> ProcessFiles(Movie movie, List<string> filesOnDisk, List<string> importedFiles)
{
_logger.Debug("Looking for existing extra files in {0}", series.Path);
_logger.Debug("Looking for existing extra files in {0}", movie.Path);
var extraFiles = new List<OtherExtraFile>();
var filterResult = FilterAndClean(series, filesOnDisk, importedFiles);
var filterResult = FilterAndClean(movie, filesOnDisk, importedFiles);
foreach (var possibleExtraFile in filterResult.FilesOnDisk)
{
var localEpisode = _parsingService.GetLocalEpisode(possibleExtraFile, series);
var extension = Path.GetExtension(possibleExtraFile);
if (localEpisode == null)
if (extension.IsNullOrWhiteSpace())
{
_logger.Debug("No extension for file: {0}", possibleExtraFile);
continue;
}
var localMovie = _parsingService.GetLocalMovie(possibleExtraFile, movie);
if (localMovie == null)
{
_logger.Debug("Unable to parse extra file: {0}", possibleExtraFile);
continue;
}
if (localEpisode.Episodes.Empty())
if (localMovie.Movie == null)
{
_logger.Debug("Cannot find related episodes for: {0}", possibleExtraFile);
continue;
}
if (localEpisode.Episodes.DistinctBy(e => e.EpisodeFileId).Count() > 1)
{
_logger.Debug("Extra file: {0} does not match existing files.", possibleExtraFile);
_logger.Debug("Cannot find related movie for: {0}", possibleExtraFile);
continue;
}
var extraFile = new OtherExtraFile
{
SeriesId = series.Id,
SeasonNumber = localEpisode.SeasonNumber,
EpisodeFileId = localEpisode.Episodes.First().EpisodeFileId,
RelativePath = series.Path.GetRelativePath(possibleExtraFile),
Extension = Path.GetExtension(possibleExtraFile)
MovieId = movie.Id,
MovieFileId = localMovie.Movie.MovieFileId,
RelativePath = movie.Path.GetRelativePath(possibleExtraFile),
Extension = extension
};
extraFiles.Add(extraFile);
@@ -1,4 +1,4 @@
using NLog;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.MediaFiles;
@@ -12,8 +12,8 @@ namespace NzbDrone.Core.Extras.Others
public class OtherExtraFileService : ExtraFileService<OtherExtraFile>, IOtherExtraFileService
{
public OtherExtraFileService(IExtraFileRepository<OtherExtraFile> repository, ISeriesService seriesService, IDiskProvider diskProvider, IRecycleBinProvider recycleBinProvider, Logger logger)
: base(repository, seriesService, diskProvider, recycleBinProvider, logger)
public OtherExtraFileService(IExtraFileRepository<OtherExtraFile> repository, IMovieService movieService, IDiskProvider diskProvider, IRecycleBinProvider recycleBinProvider, Logger logger)
: base(repository, movieService, diskProvider, recycleBinProvider, logger)
{
}
}
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -15,71 +15,41 @@ namespace NzbDrone.Core.Extras.Others
public class OtherExtraService : ExtraFileManager<OtherExtraFile>
{
private readonly IOtherExtraFileService _otherExtraFileService;
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
public OtherExtraService(IConfigService configService,
IDiskProvider diskProvider,
IDiskTransferService diskTransferService,
IOtherExtraFileService otherExtraFileService,
IDiskProvider diskProvider,
Logger logger)
: base(configService, diskTransferService, otherExtraFileService)
: base(configService, diskProvider, diskTransferService, logger)
{
_otherExtraFileService = otherExtraFileService;
_diskProvider = diskProvider;
_logger = logger;
}
public override int Order => 2;
public override IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles)
public override IEnumerable<ExtraFile> CreateAfterMovieScan(Movie movie, List<MovieFile> movieFiles)
{
return Enumerable.Empty<ExtraFile>();
}
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
public override IEnumerable<ExtraFile> CreateAfterMovieImport(Movie movie, MovieFile movieFile)
{
return Enumerable.Empty<ExtraFile>();
}
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder)
{
return Enumerable.Empty<ExtraFile>();
}
public override IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles)
{
// TODO: Remove
// We don't want to move files after rename yet.
return Enumerable.Empty<ExtraFile>();
var extraFiles = _otherExtraFileService.GetFilesBySeries(series.Id);
public override IEnumerable<ExtraFile> MoveFilesAfterRename(Movie movie, List<MovieFile> movieFiles)
{
var extraFiles = _otherExtraFileService.GetFilesByMovie(movie.Id);
var movedFiles = new List<OtherExtraFile>();
foreach (var episodeFile in episodeFiles)
foreach (var movieFile in movieFiles)
{
var extraFilesForEpisodeFile = extraFiles.Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
var extraFilesForMovieFile = extraFiles.Where(m => m.MovieFileId == movieFile.Id).ToList();
foreach (var extraFile in extraFilesForEpisodeFile)
foreach (var extraFile in extraFilesForMovieFile)
{
var existingFileName = Path.Combine(series.Path, extraFile.RelativePath);
var extension = Path.GetExtension(existingFileName).TrimStart('.');
var newFileName = Path.ChangeExtension(Path.Combine(series.Path, episodeFile.RelativePath), extension);
if (newFileName.PathNotEquals(existingFileName))
{
try
{
_diskProvider.MoveFile(existingFileName, newFileName);
extraFile.RelativePath = series.Path.GetRelativePath(newFileName);
movedFiles.Add(extraFile);
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to move extra file: {0}", existingFileName);
}
}
movedFiles.AddIfNotNull(MoveFile(movie, movieFile, extraFile));
}
}
@@ -88,15 +58,15 @@ namespace NzbDrone.Core.Extras.Others
return movedFiles;
}
public override ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly)
public override ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly)
{
// If the extension is .nfo we need to change it to .nfo-orig
if (Path.GetExtension(path).Equals(".nfo"))
if (Path.GetExtension(path).Equals(".nfo", StringComparison.OrdinalIgnoreCase))
{
extension += "-orig";
}
var extraFile = ImportFile(series, episodeFile, path, extension, readOnly);
var extraFile = ImportFile(movie, movieFile, path, readOnly, extension, null);
_otherExtraFileService.Upsert(extraFile);
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
@@ -27,12 +27,12 @@ namespace NzbDrone.Core.Extras.Subtitles
public override int Order => 1;
public override IEnumerable<ExtraFile> ProcessFiles(Series series, List<string> filesOnDisk, List<string> importedFiles)
public override IEnumerable<ExtraFile> ProcessFiles(Movie movie, List<string> filesOnDisk, List<string> importedFiles)
{
_logger.Debug("Looking for existing subtitle files in {0}", series.Path);
_logger.Debug("Looking for existing subtitle files in {0}", movie.Path);
var subtitleFiles = new List<SubtitleFile>();
var filterResult = FilterAndClean(series, filesOnDisk, importedFiles);
var filterResult = FilterAndClean(movie, filesOnDisk, importedFiles);
foreach (var possibleSubtitleFile in filterResult.FilesOnDisk)
{
@@ -40,32 +40,25 @@ namespace NzbDrone.Core.Extras.Subtitles
if (SubtitleFileExtensions.Extensions.Contains(extension))
{
var localEpisode = _parsingService.GetLocalEpisode(possibleSubtitleFile, series);
var localMovie = _parsingService.GetLocalMovie(possibleSubtitleFile, movie);
if (localEpisode == null)
if (localMovie == null)
{
_logger.Debug("Unable to parse subtitle file: {0}", possibleSubtitleFile);
continue;
}
if (localEpisode.Episodes.Empty())
if (localMovie.Movie == null)
{
_logger.Debug("Cannot find related episodes for: {0}", possibleSubtitleFile);
continue;
}
if (localEpisode.Episodes.DistinctBy(e => e.EpisodeFileId).Count() > 1)
{
_logger.Debug("Subtitle file: {0} does not match existing files.", possibleSubtitleFile);
_logger.Debug("Cannot find related movie for: {0}", possibleSubtitleFile);
continue;
}
var subtitleFile = new SubtitleFile
{
SeriesId = series.Id,
SeasonNumber = localEpisode.SeasonNumber,
EpisodeFileId = localEpisode.Episodes.First().EpisodeFileId,
RelativePath = series.Path.GetRelativePath(possibleSubtitleFile),
MovieId = movie.Id,
MovieFileId = localMovie.Movie.MovieFileId,
RelativePath = movie.Path.GetRelativePath(possibleSubtitleFile),
Language = LanguageParser.ParseSubtitleLanguage(possibleSubtitleFile),
Extension = extension
};
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace NzbDrone.Core.Extras.Subtitles
{
@@ -8,7 +9,7 @@ namespace NzbDrone.Core.Extras.Subtitles
static SubtitleFileExtensions()
{
_fileExtensions = new HashSet<string>
_fileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".aqt",
".ass",
@@ -1,4 +1,4 @@
using NLog;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.MediaFiles;
@@ -12,8 +12,8 @@ namespace NzbDrone.Core.Extras.Subtitles
public class SubtitleFileService : ExtraFileService<SubtitleFile>, ISubtitleFileService
{
public SubtitleFileService(IExtraFileRepository<SubtitleFile> repository, ISeriesService seriesService, IDiskProvider diskProvider, IRecycleBinProvider recycleBinProvider, Logger logger)
: base(repository, seriesService, diskProvider, recycleBinProvider, logger)
public SubtitleFileService(IExtraFileRepository<SubtitleFile> repository, IMovieService movieService, IDiskProvider diskProvider, IRecycleBinProvider recycleBinProvider, Logger logger)
: base(repository, movieService, diskProvider, recycleBinProvider, logger)
{
}
}
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -17,83 +17,56 @@ namespace NzbDrone.Core.Extras.Subtitles
public class SubtitleService : ExtraFileManager<SubtitleFile>
{
private readonly ISubtitleFileService _subtitleFileService;
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
public SubtitleService(IConfigService configService,
IDiskProvider diskProvider,
IDiskTransferService diskTransferService,
ISubtitleFileService subtitleFileService,
IDiskProvider diskProvider,
Logger logger)
: base(configService, diskTransferService, subtitleFileService)
: base(configService, diskProvider, diskTransferService, logger)
{
_subtitleFileService = subtitleFileService;
_diskProvider = diskProvider;
_logger = logger;
}
public override int Order => 1;
public override IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles)
public override IEnumerable<ExtraFile> CreateAfterMovieScan(Movie movie, List<MovieFile> movieFiles)
{
return Enumerable.Empty<SubtitleFile>();
}
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
public override IEnumerable<ExtraFile> CreateAfterMovieImport(Movie movie, MovieFile movieFile)
{
return Enumerable.Empty<SubtitleFile>();
}
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder)
public override IEnumerable<ExtraFile> MoveFilesAfterRename(Movie movie, List<MovieFile> movieFiles)
{
return Enumerable.Empty<SubtitleFile>();
}
public override IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles)
{
// TODO: Remove
// We don't want to move files after rename yet.
return Enumerable.Empty<ExtraFile>();
var subtitleFiles = _subtitleFileService.GetFilesBySeries(series.Id);
var subtitleFiles = _subtitleFileService.GetFilesByMovie(movie.Id);
var movedFiles = new List<SubtitleFile>();
foreach (var episodeFile in episodeFiles)
foreach (var movieFile in movieFiles)
{
var groupedExtraFilesForEpisodeFile = subtitleFiles.Where(m => m.EpisodeFileId == episodeFile.Id)
var groupedExtraFilesForMovieFile = subtitleFiles.Where(m => m.MovieFileId == movieFile.Id)
.GroupBy(s => s.Language + s.Extension).ToList();
foreach (var group in groupedExtraFilesForEpisodeFile)
foreach (var group in groupedExtraFilesForMovieFile)
{
var groupCount = group.Count();
var copy = 1;
if (groupCount > 1)
{
_logger.Warn("Multiple subtitle files found with the same language and extension for {0}", Path.Combine(series.Path, episodeFile.RelativePath));
_logger.Warn("Multiple subtitle files found with the same language and extension for {0}", Path.Combine(movie.Path, movieFile.RelativePath));
}
foreach (var extraFile in group)
foreach (var subtitleFile in group)
{
var existingFileName = Path.Combine(series.Path, extraFile.RelativePath);
var extension = GetExtension(extraFile, existingFileName, copy, groupCount > 1);
var newFileName = Path.ChangeExtension(Path.Combine(series.Path, episodeFile.RelativePath), extension);
if (newFileName.PathNotEquals(existingFileName))
{
try
{
_diskProvider.MoveFile(existingFileName, newFileName);
extraFile.RelativePath = series.Path.GetRelativePath(newFileName);
movedFiles.Add(extraFile);
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to move subtitle file: {0}", existingFileName);
}
}
var suffix = GetSuffix(subtitleFile.Language, copy, groupCount > 1);
movedFiles.AddIfNotNull(MoveFile(movie, movieFile, subtitleFile, suffix));
copy++;
}
@@ -105,12 +78,14 @@ namespace NzbDrone.Core.Extras.Subtitles
return movedFiles;
}
public override ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly)
public override ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly)
{
if (SubtitleFileExtensions.Extensions.Contains(Path.GetExtension(path)))
{
var subtitleFile = ImportFile(series, episodeFile, path, extension, readOnly);
subtitleFile.Language = LanguageParser.ParseSubtitleLanguage(path);
var language = LanguageParser.ParseSubtitleLanguage(path);
var suffix = GetSuffix(language, 1, false);
var subtitleFile = ImportFile(movie, movieFile, path, readOnly, extension, suffix);
subtitleFile.Language = language;
_subtitleFileService.Upsert(subtitleFile);
@@ -120,26 +95,23 @@ namespace NzbDrone.Core.Extras.Subtitles
return null;
}
private string GetExtension(SubtitleFile extraFile, string existingFileName, int copy, bool multipleCopies = false)
private string GetSuffix(Language language, int copy, bool multipleCopies = false)
{
var fileExtension = Path.GetExtension(existingFileName);
var extensionBuilder = new StringBuilder();
var suffixBuilder = new StringBuilder();
if (multipleCopies)
{
extensionBuilder.Append(copy);
extensionBuilder.Append(".");
suffixBuilder.Append(".");
suffixBuilder.Append(copy);
}
if (extraFile.Language != Language.Unknown)
if (language != Language.Unknown)
{
extensionBuilder.Append(IsoLanguages.Get(extraFile.Language).TwoLetterCode);
extensionBuilder.Append(".");
suffixBuilder.Append(".");
suffixBuilder.Append(IsoLanguages.Get(language).TwoLetterCode);
}
extensionBuilder.Append(fileExtension.TrimStart('.'));
return extensionBuilder.ToString();
return suffixBuilder.ToString();
}
}
}