mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-05 02:22:31 +01:00
Merge branch 'metadata' into develop
This commit is contained in:
commit
f6077238e6
@ -12,5 +12,15 @@ public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TS
|
||||
|
||||
return source.Where(element => knownKeys.Add(keySelector(element)));
|
||||
}
|
||||
|
||||
public static void AddIfNotNull<TSource>(this List<TSource> source, TSource item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
source.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
using FluentMigrator;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(49)]
|
||||
public class add_hash_to_metadata_files : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("MetadataFiles").AddColumn("Hash").AsString().Nullable();
|
||||
}
|
||||
}
|
||||
}
|
@ -22,117 +22,23 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox
|
||||
{
|
||||
public class RoksboxMetadata : MetadataBase<RoksboxMetadataSettings>
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IMapCoversToLocal _mediaCoverService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IMetadataFileService _metadataFileService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IHttpProvider _httpProvider;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public RoksboxMetadata(IEventAggregator eventAggregator,
|
||||
IMapCoversToLocal mediaCoverService,
|
||||
IMediaFileService mediaFileService,
|
||||
IMetadataFileService metadataFileService,
|
||||
public RoksboxMetadata(IMapCoversToLocal mediaCoverService,
|
||||
IDiskProvider diskProvider,
|
||||
IHttpProvider httpProvider,
|
||||
IEpisodeService episodeService,
|
||||
Logger logger)
|
||||
: base(diskProvider, httpProvider, logger)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_mediaCoverService = mediaCoverService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_metadataFileService = metadataFileService;
|
||||
_diskProvider = diskProvider;
|
||||
_httpProvider = httpProvider;
|
||||
_episodeService = episodeService;
|
||||
_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+))|(?<season>specials)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
|
||||
{
|
||||
var metadataFiles = new List<MetadataFile>();
|
||||
|
||||
if (!_diskProvider.FolderExists(series.Path))
|
||||
{
|
||||
_logger.Info("Series folder ({0}) does not exist, skipping metadata creation", series.Path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Settings.SeriesImages)
|
||||
{
|
||||
var metadata = WriteSeriesImages(series, existingMetadataFiles);
|
||||
if (metadata != null)
|
||||
{
|
||||
metadataFiles.Add(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings.SeasonImages)
|
||||
{
|
||||
var metadata = WriteSeasonImages(series, existingMetadataFiles);
|
||||
if (metadata != null)
|
||||
{
|
||||
metadataFiles.AddRange(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
{
|
||||
if (Settings.EpisodeMetadata)
|
||||
{
|
||||
var metadata = WriteEpisodeMetadata(series, episodeFile, existingMetadataFiles);
|
||||
if (metadata != null)
|
||||
{
|
||||
metadataFiles.Add(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
{
|
||||
if (Settings.EpisodeImages)
|
||||
{
|
||||
var metadataFile = WriteEpisodeImages(series, episodeFile, existingMetadataFiles);
|
||||
|
||||
if (metadataFile != null)
|
||||
{
|
||||
metadataFiles.Add(metadataFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
metadataFiles.RemoveAll(c => c == null);
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
|
||||
}
|
||||
|
||||
public override void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload)
|
||||
{
|
||||
var metadataFiles = new List<MetadataFile>();
|
||||
|
||||
if (Settings.EpisodeMetadata)
|
||||
{
|
||||
metadataFiles.Add(WriteEpisodeMetadata(series, episodeFile, new List<MetadataFile>()));
|
||||
}
|
||||
|
||||
if (Settings.EpisodeImages)
|
||||
{
|
||||
var metadataFile = WriteEpisodeImages(series, episodeFile, new List<MetadataFile>());
|
||||
|
||||
if (metadataFile != null)
|
||||
{
|
||||
metadataFiles.Add(metadataFile);
|
||||
}
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
|
||||
}
|
||||
|
||||
public override void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
|
||||
public override List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
|
||||
{
|
||||
var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList();
|
||||
var updatedMetadataFiles = new List<MetadataFile>();
|
||||
@ -173,7 +79,7 @@ public override void AfterRename(Series series, List<MetadataFile> existingMetad
|
||||
}
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(updatedMetadataFiles));
|
||||
return updatedMetadataFiles;
|
||||
}
|
||||
|
||||
public override MetadataFile FindMetadataFile(Series series, string path)
|
||||
@ -191,7 +97,7 @@ public override MetadataFile FindMetadataFile(Series series, string path)
|
||||
};
|
||||
|
||||
//Series and season images are both named folder.jpg, only season ones sit in season folders
|
||||
if (String.Compare(filename, parentdir.Name, true) == 0)
|
||||
if (String.Compare(filename, parentdir.Name, StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||
{
|
||||
var seasonMatch = SeasonImagesRegex.Match(parentdir.Name);
|
||||
if (seasonMatch.Success)
|
||||
@ -223,24 +129,76 @@ public override MetadataFile FindMetadataFile(Series series, string path)
|
||||
if (parseResult != null &&
|
||||
!parseResult.FullSeason)
|
||||
{
|
||||
switch (Path.GetExtension(filename).ToLowerInvariant())
|
||||
var extension = Path.GetExtension(filename).ToLowerInvariant();
|
||||
|
||||
if (extension == ".xml")
|
||||
{
|
||||
case ".xml":
|
||||
metadata.Type = MetadataType.EpisodeMetadata;
|
||||
return metadata;
|
||||
case ".jpg":
|
||||
metadata.Type = MetadataType.EpisodeMetadata;
|
||||
return metadata;
|
||||
}
|
||||
|
||||
if (extension == ".jpg")
|
||||
{
|
||||
if (!Path.GetFileNameWithoutExtension(filename).EndsWith("-thumb"))
|
||||
{
|
||||
metadata.Type = MetadataType.EpisodeImage;
|
||||
return metadata;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private MetadataFile WriteSeriesImages(Series series, List<MetadataFile> existingMetadataFiles)
|
||||
public override MetadataFileResult SeriesMetadata(Series series)
|
||||
{
|
||||
//Series metadata is not supported
|
||||
return null;
|
||||
}
|
||||
|
||||
public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile)
|
||||
{
|
||||
if (!Settings.EpisodeMetadata)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_logger.Debug("Generating Episode Metadata for: {0}", episodeFile.Path);
|
||||
|
||||
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 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("mpaa", ValidCertification.Contains(series.Certification.ToUpperInvariant()) ? series.Certification.ToUpperInvariant() : "UNRATED"));
|
||||
doc.Add(details);
|
||||
doc.Save(xw);
|
||||
|
||||
xmlResult += doc.ToString();
|
||||
xmlResult += Environment.NewLine;
|
||||
}
|
||||
}
|
||||
|
||||
return new MetadataFileResult(GetEpisodeMetadataFilename(episodeFile.Path), xmlResult.Trim(Environment.NewLine.ToCharArray()));
|
||||
}
|
||||
|
||||
public override List<ImageFileResult> SeriesImages(Series series)
|
||||
{
|
||||
//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();
|
||||
if (image == null)
|
||||
{
|
||||
@ -251,39 +209,66 @@ private MetadataFile WriteSeriesImages(Series series, List<MetadataFile> existin
|
||||
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
|
||||
var destination = Path.Combine(series.Path, Path.GetFileName(series.Path) + Path.GetExtension(source));
|
||||
|
||||
//TODO: Do we want to overwrite the file if it exists?
|
||||
if (_diskProvider.FileExists(destination))
|
||||
{
|
||||
_logger.Debug("Series image: {0} already exists.", image.CoverType);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
_diskProvider.CopyFile(source, destination, false);
|
||||
|
||||
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesImage) ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.SeriesImage,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, destination)
|
||||
};
|
||||
|
||||
return metadata;
|
||||
}
|
||||
return new List<ImageFileResult>{ new ImageFileResult(destination, source) };
|
||||
}
|
||||
|
||||
private IEnumerable<MetadataFile> WriteSeasonImages(Series series, List<MetadataFile> existingMetadataFiles)
|
||||
public override List<ImageFileResult> SeasonImages(Series series, Season season)
|
||||
{
|
||||
_logger.Debug("Writing season images for {0}.", series.Title);
|
||||
//Create a dictionary between season number and output folder
|
||||
var seasonFolderMap = new Dictionary<int, string>();
|
||||
foreach (var folder in Directory.EnumerateDirectories(series.Path))
|
||||
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 = Path.Combine(series.Path, seasonFolder, filename);
|
||||
|
||||
return new List<ImageFileResult> { new ImageFileResult(path, image.Url) };
|
||||
}
|
||||
|
||||
public override List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile)
|
||||
{
|
||||
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.Path), screenshot.Url)};
|
||||
}
|
||||
|
||||
private string GetEpisodeMetadataFilename(string episodeFilePath)
|
||||
{
|
||||
return Path.ChangeExtension(episodeFilePath, "xml");
|
||||
}
|
||||
|
||||
private string GetEpisodeImageFilename(string episodeFilePath)
|
||||
{
|
||||
return Path.ChangeExtension(episodeFilePath, "jpg");
|
||||
}
|
||||
|
||||
private Dictionary<Int32, String> GetSeasonFolders(Series series)
|
||||
{
|
||||
var seasonFolderMap = new Dictionary<Int32, 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;
|
||||
@ -310,160 +295,8 @@ private IEnumerable<MetadataFile> WriteSeasonImages(Series series, List<Metadata
|
||||
_logger.Debug("Rejecting folder {0} for series {1}.", Path.GetDirectoryName(folder), series.Title);
|
||||
}
|
||||
}
|
||||
foreach (var season in series.Seasons)
|
||||
{
|
||||
//Work out the path to this season - if we don't have a matching path then skip this season.
|
||||
string seasonFolder;
|
||||
if (!seasonFolderMap.TryGetValue(season.SeasonNumber, out seasonFolder))
|
||||
{
|
||||
_logger.Trace("Failed to find season folder for series {0}, season {1}.", series.Title, season.SeasonNumber);
|
||||
continue;
|
||||
}
|
||||
|
||||
//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);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var filename = Path.GetFileName(seasonFolder) + ".jpg";
|
||||
|
||||
var path = Path.Combine(series.Path, seasonFolder, filename);
|
||||
_logger.Debug("Writing season image for series {0}, season {1} to {2}.", series.Title, season.SeasonNumber, path);
|
||||
DownloadImage(series, image.Url, path);
|
||||
|
||||
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeasonImage &&
|
||||
c.SeasonNumber == season.SeasonNumber) ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
SeasonNumber = season.SeasonNumber,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.SeasonImage,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, path)
|
||||
};
|
||||
|
||||
yield return metadata;
|
||||
}
|
||||
}
|
||||
|
||||
private MetadataFile WriteEpisodeMetadata(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
|
||||
{
|
||||
var filename = GetEpisodeMetadataFilename(episodeFile.Path);
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
|
||||
|
||||
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeMetadata &&
|
||||
c.EpisodeFileId == episodeFile.Id);
|
||||
|
||||
if (existingMetadata != null)
|
||||
{
|
||||
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||
if (!filename.PathEquals(fullPath))
|
||||
{
|
||||
_diskProvider.MoveFile(fullPath, filename);
|
||||
existingMetadata.RelativePath = relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Generating {0} for: {1}", filename, episodeFile.Path);
|
||||
|
||||
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 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("mpaa", ValidCertification.Contains( series.Certification.ToUpperInvariant() ) ? series.Certification.ToUpperInvariant() : "UNRATED" ) );
|
||||
doc.Add(details);
|
||||
doc.Save(xw);
|
||||
|
||||
xmlResult += doc.ToString();
|
||||
xmlResult += Environment.NewLine;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Saving episodedetails to: {0}", filename);
|
||||
_diskProvider.WriteAllText(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
|
||||
|
||||
var metadata = existingMetadata ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
EpisodeFileId = episodeFile.Id,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.EpisodeMetadata,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
|
||||
};
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private MetadataFile WriteEpisodeImages(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
|
||||
{
|
||||
var screenshot = episodeFile.Episodes.Value.First().Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
|
||||
|
||||
if (screenshot == null)
|
||||
{
|
||||
_logger.Trace("Episode screenshot not available");
|
||||
return null;
|
||||
}
|
||||
|
||||
var filename = GetEpisodeImageFilename(episodeFile.Path);
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
|
||||
|
||||
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeImage &&
|
||||
c.EpisodeFileId == episodeFile.Id);
|
||||
|
||||
if (existingMetadata != null)
|
||||
{
|
||||
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||
if (!filename.PathEquals(fullPath))
|
||||
{
|
||||
_diskProvider.MoveFile(fullPath, filename);
|
||||
existingMetadata.RelativePath = relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
DownloadImage(series, screenshot.Url, filename);
|
||||
|
||||
var metadata = existingMetadata ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
EpisodeFileId = episodeFile.Id,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.EpisodeImage,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
|
||||
};
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private string GetEpisodeMetadataFilename(string episodeFilePath)
|
||||
{
|
||||
return Path.ChangeExtension(episodeFilePath, "xml");
|
||||
}
|
||||
|
||||
private string GetEpisodeImageFilename(string episodeFilePath)
|
||||
{
|
||||
return Path.ChangeExtension(episodeFilePath, "jpg");
|
||||
return seasonFolderMap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
@ -22,116 +23,22 @@ namespace NzbDrone.Core.Metadata.Consumers.Wdtv
|
||||
{
|
||||
public class WdtvMetadata : MetadataBase<WdtvMetadataSettings>
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IMapCoversToLocal _mediaCoverService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IMetadataFileService _metadataFileService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IHttpProvider _httpProvider;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public WdtvMetadata(IEventAggregator eventAggregator,
|
||||
IMapCoversToLocal mediaCoverService,
|
||||
IMediaFileService mediaFileService,
|
||||
IMetadataFileService metadataFileService,
|
||||
public WdtvMetadata(IMapCoversToLocal mediaCoverService,
|
||||
IDiskProvider diskProvider,
|
||||
IHttpProvider httpProvider,
|
||||
IEpisodeService episodeService,
|
||||
Logger logger)
|
||||
: base(diskProvider, httpProvider, logger)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_mediaCoverService = mediaCoverService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_metadataFileService = metadataFileService;
|
||||
_diskProvider = diskProvider;
|
||||
_httpProvider = httpProvider;
|
||||
_episodeService = episodeService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private static readonly Regex SeasonImagesRegex = new Regex(@"^(season (?<season>\d+))|(?<season>specials)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
|
||||
{
|
||||
var metadataFiles = new List<MetadataFile>();
|
||||
|
||||
if (!_diskProvider.FolderExists(series.Path))
|
||||
{
|
||||
_logger.Info("Series folder ({0}) does not exist, skipping metadata creation", series.Path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Settings.SeriesImages)
|
||||
{
|
||||
var metadata = WriteSeriesImages(series, existingMetadataFiles);
|
||||
if (metadata != null)
|
||||
{
|
||||
metadataFiles.Add(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings.SeasonImages)
|
||||
{
|
||||
var metadata = WriteSeasonImages(series, existingMetadataFiles);
|
||||
if (metadata != null)
|
||||
{
|
||||
metadataFiles.AddRange(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
{
|
||||
if (Settings.EpisodeMetadata)
|
||||
{
|
||||
var metadata = WriteEpisodeMetadata(series, episodeFile, existingMetadataFiles);
|
||||
if (metadata != null)
|
||||
{
|
||||
metadataFiles.Add(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
{
|
||||
if (Settings.EpisodeImages)
|
||||
{
|
||||
var metadataFile = WriteEpisodeImages(series, episodeFile, existingMetadataFiles);
|
||||
|
||||
if (metadataFile != null)
|
||||
{
|
||||
metadataFiles.Add(metadataFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
metadataFiles.RemoveAll(c => c == null);
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
|
||||
}
|
||||
|
||||
public override void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload)
|
||||
{
|
||||
var metadataFiles = new List<MetadataFile>();
|
||||
|
||||
if (Settings.EpisodeMetadata)
|
||||
{
|
||||
metadataFiles.Add(WriteEpisodeMetadata(series, episodeFile, new List<MetadataFile>()));
|
||||
}
|
||||
|
||||
if (Settings.EpisodeImages)
|
||||
{
|
||||
var metadataFile = WriteEpisodeImages(series, episodeFile, new List<MetadataFile>());
|
||||
|
||||
if (metadataFile != null)
|
||||
{
|
||||
metadataFiles.Add(metadataFile);
|
||||
}
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
|
||||
}
|
||||
|
||||
public override void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
|
||||
public override List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
|
||||
{
|
||||
var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList();
|
||||
var updatedMetadataFiles = new List<MetadataFile>();
|
||||
@ -171,8 +78,7 @@ public override void AfterRename(Series series, List<MetadataFile> existingMetad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(updatedMetadataFiles));
|
||||
return updatedMetadataFiles;
|
||||
}
|
||||
|
||||
public override MetadataFile FindMetadataFile(Series series, string path)
|
||||
@ -237,137 +143,20 @@ public override MetadataFile FindMetadataFile(Series series, string path)
|
||||
return null;
|
||||
}
|
||||
|
||||
private MetadataFile WriteSeriesImages(Series series, List<MetadataFile> existingMetadataFiles)
|
||||
public override MetadataFileResult SeriesMetadata(Series series)
|
||||
{
|
||||
//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();
|
||||
if (image == null)
|
||||
//Series metadata is not supported
|
||||
return null;
|
||||
}
|
||||
|
||||
public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile)
|
||||
{
|
||||
if (!Settings.EpisodeMetadata)
|
||||
{
|
||||
_logger.Trace("Failed to find suitable Series image for series {0}.", series.Title);
|
||||
return null;
|
||||
}
|
||||
|
||||
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
|
||||
var destination = Path.Combine(series.Path, "folder" + Path.GetExtension(source));
|
||||
|
||||
//TODO: Do we want to overwrite the file if it exists?
|
||||
if (_diskProvider.FileExists(destination))
|
||||
{
|
||||
_logger.Debug("Series image: {0} already exists.", image.CoverType);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
_diskProvider.CopyFile(source, destination, false);
|
||||
|
||||
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesImage) ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.SeriesImage,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, destination)
|
||||
};
|
||||
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<MetadataFile> WriteSeasonImages(Series series, List<MetadataFile> existingMetadataFiles)
|
||||
{
|
||||
_logger.Debug("Writing season images for {0}.", series.Title);
|
||||
//Create a dictionary between season number and output folder
|
||||
var seasonFolderMap = new Dictionary<int, string>();
|
||||
foreach (var folder in Directory.EnumerateDirectories(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 (Int32.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);
|
||||
}
|
||||
}
|
||||
foreach (var season in series.Seasons)
|
||||
{
|
||||
//Work out the path to this season - if we don't have a matching path then skip this season.
|
||||
string seasonFolder;
|
||||
if (!seasonFolderMap.TryGetValue(season.SeasonNumber, out seasonFolder))
|
||||
{
|
||||
_logger.Trace("Failed to find season folder for series {0}, season {1}.", series.Title, season.SeasonNumber);
|
||||
continue;
|
||||
}
|
||||
|
||||
//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);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var filename = "folder.jpg";
|
||||
|
||||
var path = Path.Combine(series.Path, seasonFolder, filename);
|
||||
_logger.Debug("Writing season image for series {0}, season {1} to {2}.", series.Title, season.SeasonNumber, path);
|
||||
DownloadImage(series, image.Url, path);
|
||||
|
||||
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeasonImage &&
|
||||
c.SeasonNumber == season.SeasonNumber) ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
SeasonNumber = season.SeasonNumber,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.SeasonImage,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, path)
|
||||
};
|
||||
|
||||
yield return metadata;
|
||||
}
|
||||
}
|
||||
|
||||
private MetadataFile WriteEpisodeMetadata(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
|
||||
{
|
||||
var filename = GetEpisodeMetadataFilename(episodeFile.Path);
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
|
||||
|
||||
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeMetadata &&
|
||||
c.EpisodeFileId == episodeFile.Id);
|
||||
|
||||
if (existingMetadata != null)
|
||||
{
|
||||
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||
if (!filename.PathEquals(fullPath))
|
||||
{
|
||||
_diskProvider.MoveFile(fullPath, filename);
|
||||
existingMetadata.RelativePath = relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Generating {0} for: {1}", filename, episodeFile.Path);
|
||||
_logger.Debug("Generating Episode Metadata for: {0}", episodeFile.Path);
|
||||
|
||||
var xmlResult = String.Empty;
|
||||
foreach (var episode in episodeFile.Episodes.Value)
|
||||
@ -405,62 +194,82 @@ private MetadataFile WriteEpisodeMetadata(Series series, EpisodeFile episodeFile
|
||||
xmlResult += Environment.NewLine;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Saving episodedetails to: {0}", filename);
|
||||
_diskProvider.WriteAllText(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
|
||||
|
||||
var metadata = existingMetadata ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
EpisodeFileId = episodeFile.Id,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.EpisodeMetadata,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
|
||||
};
|
||||
var filename = GetEpisodeMetadataFilename(episodeFile.Path);
|
||||
|
||||
return metadata;
|
||||
return new MetadataFileResult(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
|
||||
}
|
||||
|
||||
private MetadataFile WriteEpisodeImages(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
|
||||
public override List<ImageFileResult> SeriesImages(Series series)
|
||||
{
|
||||
if (!Settings.SeriesImages)
|
||||
{
|
||||
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();
|
||||
if (image == null)
|
||||
{
|
||||
_logger.Trace("Failed to find suitable Series image for series {0}.", series.Title);
|
||||
return new List<ImageFileResult>();
|
||||
}
|
||||
|
||||
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
|
||||
var destination = Path.Combine(series.Path, "folder" + Path.GetExtension(source));
|
||||
|
||||
return new List<ImageFileResult>
|
||||
{
|
||||
new ImageFileResult(destination, source)
|
||||
};
|
||||
}
|
||||
|
||||
public override List<ImageFileResult> SeasonImages(Series series, Season season)
|
||||
{
|
||||
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(series.Path, seasonFolder, "folder.jpg");
|
||||
|
||||
return new List<ImageFileResult>{ new ImageFileResult(path, image.Url) };
|
||||
}
|
||||
|
||||
public override List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile)
|
||||
{
|
||||
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 null;
|
||||
return new List<ImageFileResult>();
|
||||
}
|
||||
|
||||
var filename = GetEpisodeImageFilename(episodeFile.Path);
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
|
||||
|
||||
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeImage &&
|
||||
c.EpisodeFileId == episodeFile.Id);
|
||||
|
||||
if (existingMetadata != null)
|
||||
{
|
||||
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||
if (!filename.PathEquals(fullPath))
|
||||
{
|
||||
_diskProvider.MoveFile(fullPath, filename);
|
||||
existingMetadata.RelativePath = relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
DownloadImage(series, screenshot.Url, filename);
|
||||
|
||||
var metadata = existingMetadata ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
EpisodeFileId = episodeFile.Id,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.EpisodeImage,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
|
||||
};
|
||||
|
||||
return metadata;
|
||||
return new List<ImageFileResult>{ new ImageFileResult(GetEpisodeImageFilename(episodeFile.Path), screenshot.Url) };
|
||||
}
|
||||
|
||||
private string GetEpisodeMetadataFilename(string episodeFilePath)
|
||||
@ -472,5 +281,45 @@ private string GetEpisodeImageFilename(string episodeFilePath)
|
||||
{
|
||||
return Path.ChangeExtension(episodeFilePath, "metathumb");
|
||||
}
|
||||
|
||||
private Dictionary<Int32, String> GetSeasonFolders(Series series)
|
||||
{
|
||||
var seasonFolderMap = new Dictionary<Int32, 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 (Int32.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,32 +20,16 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||
{
|
||||
public class XbmcMetadata : MetadataBase<XbmcMetadataSettings>
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IMapCoversToLocal _mediaCoverService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IMetadataFileService _metadataFileService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IHttpProvider _httpProvider;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public XbmcMetadata(IEventAggregator eventAggregator,
|
||||
IMapCoversToLocal mediaCoverService,
|
||||
IMediaFileService mediaFileService,
|
||||
IMetadataFileService metadataFileService,
|
||||
public XbmcMetadata(IMapCoversToLocal mediaCoverService,
|
||||
IDiskProvider diskProvider,
|
||||
IHttpProvider httpProvider,
|
||||
IEpisodeService episodeService,
|
||||
Logger logger)
|
||||
: base(diskProvider, httpProvider, logger)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_mediaCoverService = mediaCoverService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_metadataFileService = metadataFileService;
|
||||
_diskProvider = diskProvider;
|
||||
_httpProvider = httpProvider;
|
||||
_episodeService = episodeService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -53,79 +37,7 @@ public XbmcMetadata(IEventAggregator eventAggregator,
|
||||
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);
|
||||
|
||||
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
|
||||
{
|
||||
var metadataFiles = new List<MetadataFile>();
|
||||
|
||||
if (!_diskProvider.FolderExists(series.Path))
|
||||
{
|
||||
_logger.Info("Series folder does not exist, skipping metadata creation");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Settings.SeriesMetadata)
|
||||
{
|
||||
metadataFiles.Add(WriteTvShowNfo(series, existingMetadataFiles));
|
||||
}
|
||||
|
||||
if (Settings.SeriesImages)
|
||||
{
|
||||
metadataFiles.AddRange(WriteSeriesImages(series, existingMetadataFiles));
|
||||
}
|
||||
|
||||
if (Settings.SeasonImages)
|
||||
{
|
||||
metadataFiles.AddRange(WriteSeasonImages(series, existingMetadataFiles));
|
||||
}
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
{
|
||||
if (Settings.EpisodeMetadata)
|
||||
{
|
||||
metadataFiles.Add(WriteEpisodeNfo(series, episodeFile, existingMetadataFiles));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
{
|
||||
if (Settings.EpisodeImages)
|
||||
{
|
||||
var metadataFile = WriteEpisodeImages(series, episodeFile, existingMetadataFiles);
|
||||
|
||||
if (metadataFile != null)
|
||||
{
|
||||
metadataFiles.Add(metadataFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
|
||||
}
|
||||
|
||||
public override void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload)
|
||||
{
|
||||
var metadataFiles = new List<MetadataFile>();
|
||||
|
||||
if (Settings.EpisodeMetadata)
|
||||
{
|
||||
metadataFiles.Add(WriteEpisodeNfo(series, episodeFile, new List<MetadataFile>()));
|
||||
}
|
||||
|
||||
if (Settings.EpisodeImages)
|
||||
{
|
||||
var metadataFile = WriteEpisodeImages(series, episodeFile, new List<MetadataFile>());
|
||||
|
||||
if (metadataFile != null)
|
||||
{
|
||||
metadataFiles.Add(metadataFile);
|
||||
}
|
||||
WriteEpisodeImages(series, episodeFile, new List<MetadataFile>());
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
|
||||
}
|
||||
|
||||
public override void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
|
||||
public override List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
|
||||
{
|
||||
var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList();
|
||||
var updatedMetadataFiles = new List<MetadataFile>();
|
||||
@ -166,7 +78,7 @@ public override void AfterRename(Series series, List<MetadataFile> existingMetad
|
||||
}
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(updatedMetadataFiles));
|
||||
return updatedMetadataFiles;
|
||||
}
|
||||
|
||||
public override MetadataFile FindMetadataFile(Series series, string path)
|
||||
@ -240,8 +152,13 @@ public override MetadataFile FindMetadataFile(Series series, string path)
|
||||
return null;
|
||||
}
|
||||
|
||||
private MetadataFile WriteTvShowNfo(Series series, List<MetadataFile> existingMetadataFiles)
|
||||
public override MetadataFileResult SeriesMetadata(Series series)
|
||||
{
|
||||
if (!Settings.SeriesMetadata)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_logger.Debug("Generating tvshow.nfo for: {0}", series.Title);
|
||||
var sb = new StringBuilder();
|
||||
var xws = new XmlWriterSettings();
|
||||
@ -255,7 +172,7 @@ private MetadataFile WriteTvShowNfo(Series series, List<MetadataFile> existingMe
|
||||
var tvShow = new XElement("tvshow");
|
||||
|
||||
tvShow.Add(new XElement("title", series.Title));
|
||||
tvShow.Add(new XElement("rating", (decimal)series.Ratings.Percentage/10));
|
||||
tvShow.Add(new XElement("rating", (decimal) series.Ratings.Percentage/10));
|
||||
tvShow.Add(new XElement("plot", series.Overview));
|
||||
tvShow.Add(new XElement("episodeguide", new XElement("url", episodeGuideUrl)));
|
||||
tvShow.Add(new XElement("episodeguideurl", episodeGuideUrl));
|
||||
@ -277,10 +194,10 @@ private MetadataFile WriteTvShowNfo(Series series, List<MetadataFile> existingMe
|
||||
foreach (var actor in series.Actors)
|
||||
{
|
||||
tvShow.Add(new XElement("actor",
|
||||
new XElement("name", actor.Name),
|
||||
new XElement("role", actor.Character),
|
||||
new XElement("thumb", actor.Images.First().Url)
|
||||
));
|
||||
new XElement("name", actor.Name),
|
||||
new XElement("role", actor.Character),
|
||||
new XElement("thumb", actor.Images.First().Url)
|
||||
));
|
||||
}
|
||||
|
||||
var doc = new XDocument(tvShow);
|
||||
@ -288,108 +205,13 @@ private MetadataFile WriteTvShowNfo(Series series, List<MetadataFile> existingMe
|
||||
|
||||
_logger.Debug("Saving tvshow.nfo for {0}", series.Title);
|
||||
|
||||
var path = Path.Combine(series.Path, "tvshow.nfo");
|
||||
|
||||
_diskProvider.WriteAllText(path, doc.ToString());
|
||||
|
||||
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesMetadata) ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.SeriesMetadata,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, path)
|
||||
};
|
||||
|
||||
return metadata;
|
||||
return new MetadataFileResult(Path.Combine(series.Path, "tvshow.nfo"), doc.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<MetadataFile> WriteSeriesImages(Series series, List<MetadataFile> existingMetadataFiles)
|
||||
{
|
||||
foreach (var image in series.Images)
|
||||
{
|
||||
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
|
||||
var destination = Path.Combine(series.Path, image.CoverType.ToString().ToLowerInvariant() + Path.GetExtension(source));
|
||||
|
||||
//TODO: Do we want to overwrite the file if it exists?
|
||||
if (_diskProvider.FileExists(destination))
|
||||
{
|
||||
_logger.Debug("Series image: {0} already exists.", image.CoverType);
|
||||
continue;
|
||||
}
|
||||
|
||||
_diskProvider.CopyFile(source, destination, false);
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, destination);
|
||||
|
||||
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesImage &&
|
||||
c.RelativePath == relativePath) ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.SeriesImage,
|
||||
RelativePath = relativePath
|
||||
};
|
||||
|
||||
yield return metadata;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<MetadataFile> WriteSeasonImages(Series series, List<MetadataFile> existingMetadataFiles)
|
||||
{
|
||||
foreach (var season in series.Seasons)
|
||||
{
|
||||
foreach (var image in season.Images)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
var path = Path.Combine(series.Path, filename);
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, path);
|
||||
|
||||
DownloadImage(series, image.Url, path);
|
||||
|
||||
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeasonImage &&
|
||||
c.SeasonNumber == season.SeasonNumber &&
|
||||
c.RelativePath == relativePath) ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
SeasonNumber = season.SeasonNumber,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.SeasonImage,
|
||||
RelativePath = relativePath
|
||||
};
|
||||
|
||||
yield return metadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MetadataFile WriteEpisodeNfo(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
|
||||
{
|
||||
var filename = GetEpisodeNfoFilename(episodeFile.Path);
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
|
||||
|
||||
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeMetadata &&
|
||||
c.EpisodeFileId == episodeFile.Id);
|
||||
|
||||
if (existingMetadata != null)
|
||||
{
|
||||
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||
if (!filename.PathEquals(fullPath))
|
||||
{
|
||||
_diskProvider.MoveFile(fullPath, filename);
|
||||
existingMetadata.RelativePath = relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Generating {0} for: {1}", filename, episodeFile.Path);
|
||||
public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile)
|
||||
{
|
||||
_logger.Debug("Generating Episode Metadata for: {0}", episodeFile.Path);
|
||||
|
||||
var xmlResult = String.Empty;
|
||||
foreach (var episode in episodeFile.Episodes.Value)
|
||||
@ -424,9 +246,9 @@ private MetadataFile WriteEpisodeNfo(Series series, EpisodeFile episodeFile, Lis
|
||||
{
|
||||
details.Add(new XElement("thumb", image.Url));
|
||||
}
|
||||
|
||||
|
||||
details.Add(new XElement("watched", "false"));
|
||||
details.Add(new XElement("rating", (decimal)episode.Ratings.Percentage/10));
|
||||
details.Add(new XElement("rating", (decimal)episode.Ratings.Percentage / 10));
|
||||
|
||||
//Todo: get guest stars, writer and director
|
||||
//details.Add(new XElement("credits", tvdbEpisode.Writer.FirstOrDefault()));
|
||||
@ -439,25 +261,37 @@ private MetadataFile WriteEpisodeNfo(Series series, EpisodeFile episodeFile, Lis
|
||||
xmlResult += Environment.NewLine;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Saving episodedetails to: {0}", filename);
|
||||
_diskProvider.WriteAllText(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
|
||||
|
||||
var metadata = existingMetadata ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
EpisodeFileId = episodeFile.Id,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.EpisodeMetadata,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
|
||||
};
|
||||
|
||||
return metadata;
|
||||
return new MetadataFileResult(GetEpisodeNfoFilename(episodeFile.Path), xmlResult.Trim(Environment.NewLine.ToCharArray()));
|
||||
}
|
||||
|
||||
private MetadataFile WriteEpisodeImages(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
|
||||
public override List<ImageFileResult> SeriesImages(Series series)
|
||||
{
|
||||
if (!Settings.SeriesImages)
|
||||
{
|
||||
return new List<ImageFileResult>();
|
||||
}
|
||||
|
||||
return ProcessSeriesImages(series).ToList();
|
||||
}
|
||||
|
||||
public override List<ImageFileResult> SeasonImages(Series series, Season season)
|
||||
{
|
||||
if (!Settings.SeasonImages)
|
||||
{
|
||||
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>();
|
||||
}
|
||||
|
||||
var screenshot = episodeFile.Episodes.Value.First().Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
|
||||
|
||||
if (screenshot == null)
|
||||
@ -466,35 +300,38 @@ private MetadataFile WriteEpisodeImages(Series series, EpisodeFile episodeFile,
|
||||
return null;
|
||||
}
|
||||
|
||||
var filename = GetEpisodeImageFilename(episodeFile.Path);
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
|
||||
return new List<ImageFileResult>
|
||||
{
|
||||
new ImageFileResult(GetEpisodeImageFilename(episodeFile.Path), screenshot.Url)
|
||||
};
|
||||
}
|
||||
|
||||
var existingMetadata = existingMetadataFiles.FirstOrDefault(c => c.Type == MetadataType.EpisodeImage &&
|
||||
c.EpisodeFileId == episodeFile.Id);
|
||||
|
||||
if (existingMetadata != null)
|
||||
private IEnumerable<ImageFileResult> ProcessSeriesImages(Series series)
|
||||
{
|
||||
foreach (var image in series.Images)
|
||||
{
|
||||
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||
if (!filename.PathEquals(fullPath))
|
||||
{
|
||||
_diskProvider.MoveFile(fullPath, filename);
|
||||
existingMetadata.RelativePath = relativePath;
|
||||
}
|
||||
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
|
||||
var destination = Path.Combine(series.Path, image.CoverType.ToString().ToLowerInvariant() + Path.GetExtension(source));
|
||||
|
||||
yield return new ImageFileResult(destination, source);
|
||||
}
|
||||
}
|
||||
|
||||
DownloadImage(series, screenshot.Url, filename);
|
||||
private IEnumerable<ImageFileResult> ProcessSeasonImages(Series series, Season season)
|
||||
{
|
||||
foreach (var image in season.Images)
|
||||
{
|
||||
var filename = String.Format("season{0:00}-{1}.jpg", season.SeasonNumber, image.CoverType.ToString().ToLower());
|
||||
|
||||
var metadata = existingMetadata ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
EpisodeFileId = episodeFile.Id,
|
||||
Consumer = GetType().Name,
|
||||
Type = MetadataType.EpisodeImage,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
|
||||
};
|
||||
if (season.SeasonNumber == 0)
|
||||
{
|
||||
filename = String.Format("season-specials-{0}.jpg", image.CoverType.ToString().ToLower());
|
||||
}
|
||||
|
||||
return metadata;
|
||||
var path = Path.Combine(series.Path, filename);
|
||||
|
||||
yield return new ImageFileResult(Path.Combine(series.Path, filename), image.Url);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetEpisodeNfoFilename(string episodeFilePath)
|
||||
|
16
src/NzbDrone.Core/MetaData/Files/ImageFileResult.cs
Normal file
16
src/NzbDrone.Core/MetaData/Files/ImageFileResult.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Files
|
||||
{
|
||||
public class ImageFileResult
|
||||
{
|
||||
public String Path { get; set; }
|
||||
public String Url { get; set; }
|
||||
|
||||
public ImageFileResult(string path, string url)
|
||||
{
|
||||
Path = path;
|
||||
Url = url;
|
||||
}
|
||||
}
|
||||
}
|
16
src/NzbDrone.Core/MetaData/Files/MetadataFileResult.cs
Normal file
16
src/NzbDrone.Core/MetaData/Files/MetadataFileResult.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Metadata.Files
|
||||
{
|
||||
public class MetadataFileResult
|
||||
{
|
||||
public String Path { get; set; }
|
||||
public String Contents { get; set; }
|
||||
|
||||
public MetadataFileResult(string path, string contents)
|
||||
{
|
||||
Path = path;
|
||||
Contents = contents;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,9 +8,14 @@ namespace NzbDrone.Core.Metadata
|
||||
{
|
||||
public interface IMetadata : IProvider
|
||||
{
|
||||
void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
|
||||
void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload);
|
||||
void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
|
||||
List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
@ -21,6 +27,9 @@ public class MetadataService
|
||||
private readonly ICleanMetadataService _cleanMetadataService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IHttpProvider _httpProvider;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public MetadataService(IMetadataFactory metadataFactory,
|
||||
@ -28,6 +37,9 @@ public MetadataService(IMetadataFactory metadataFactory,
|
||||
ICleanMetadataService cleanMetadataService,
|
||||
IMediaFileService mediaFileService,
|
||||
IEpisodeService episodeService,
|
||||
IDiskProvider diskProvider,
|
||||
IHttpProvider httpProvider,
|
||||
IEventAggregator eventAggregator,
|
||||
Logger logger)
|
||||
{
|
||||
_metadataFactory = metadataFactory;
|
||||
@ -35,17 +47,41 @@ public MetadataService(IMetadataFactory metadataFactory,
|
||||
_cleanMetadataService = cleanMetadataService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_episodeService = episodeService;
|
||||
_diskProvider = diskProvider;
|
||||
_httpProvider = httpProvider;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Handle(MediaCoversUpdatedEvent message)
|
||||
{
|
||||
_cleanMetadataService.Clean(message.Series);
|
||||
var seriesMetadata = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
||||
|
||||
if (!_diskProvider.FolderExists(message.Series.Path))
|
||||
{
|
||||
_logger.Info("Series folder does not exist, skipping metadata creation");
|
||||
return;
|
||||
}
|
||||
|
||||
var seriesMetadataFiles = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
||||
var episodeFiles = GetEpisodeFiles(message.Series.Id);
|
||||
|
||||
foreach (var consumer in _metadataFactory.Enabled())
|
||||
{
|
||||
consumer.OnSeriesUpdated(message.Series, GetMetadataFilesForConsumer(consumer, seriesMetadata), GetEpisodeFiles(message.Series.Id));
|
||||
var consumerFiles = GetMetadataFilesForConsumer(consumer, seriesMetadataFiles);
|
||||
var files = new List<MetadataFile>();
|
||||
|
||||
files.AddIfNotNull(ProcessSeriesMetadata(consumer, message.Series, consumerFiles));
|
||||
files.AddRange(ProcessSeriesImages(consumer, message.Series, consumerFiles));
|
||||
files.AddRange(ProcessSeasonImages(consumer, message.Series, consumerFiles));
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
{
|
||||
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, message.Series, episodeFile, consumerFiles));
|
||||
files.AddRange(ProcessEpisodeImages(consumer, message.Series, episodeFile, consumerFiles));
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(files));
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,17 +89,27 @@ public void Handle(EpisodeImportedEvent message)
|
||||
{
|
||||
foreach (var consumer in _metadataFactory.Enabled())
|
||||
{
|
||||
consumer.OnEpisodeImport(message.EpisodeInfo.Series, message.ImportedEpisode, message.NewDownload);
|
||||
var files = new List<MetadataFile>();
|
||||
|
||||
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, message.EpisodeInfo.Series, message.ImportedEpisode, new List<MetadataFile>()));
|
||||
files.AddRange(ProcessEpisodeImages(consumer, message.EpisodeInfo.Series, message.ImportedEpisode, new List<MetadataFile>()));
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(files));
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(SeriesRenamedEvent message)
|
||||
{
|
||||
var seriesMetadata = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
||||
var episodeFiles = GetEpisodeFiles(message.Series.Id);
|
||||
|
||||
foreach (var consumer in _metadataFactory.Enabled())
|
||||
{
|
||||
consumer.AfterRename(message.Series, GetMetadataFilesForConsumer(consumer, seriesMetadata), GetEpisodeFiles(message.Series.Id));
|
||||
var updatedMetadataFiles = consumer.AfterRename(message.Series,
|
||||
GetMetadataFilesForConsumer(consumer, seriesMetadata),
|
||||
episodeFiles);
|
||||
|
||||
_eventAggregator.PublishEvent(new MetadataFilesUpdated(updatedMetadataFiles));
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,5 +131,219 @@ private List<MetadataFile> GetMetadataFilesForConsumer(IMetadata consumer, List<
|
||||
{
|
||||
return seriesMetadata.Where(c => c.Consumer == consumer.GetType().Name).ToList();
|
||||
}
|
||||
|
||||
private MetadataFile ProcessSeriesMetadata(IMetadata consumer, Series series, List<MetadataFile> existingMetadataFiles)
|
||||
{
|
||||
var seriesMetadata = consumer.SeriesMetadata(series);
|
||||
|
||||
if (seriesMetadata == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var hash = seriesMetadata.Contents.SHA256Hash();
|
||||
|
||||
var metadata = existingMetadataFiles.SingleOrDefault(e => e.Type == MetadataType.SeriesMetadata) ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
Consumer = consumer.GetType().Name,
|
||||
Type = MetadataType.SeriesMetadata,
|
||||
};
|
||||
|
||||
if (hash == metadata.Hash)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_logger.Debug("Writing Series Metadata to: {0}", seriesMetadata.Path);
|
||||
_diskProvider.WriteAllText(seriesMetadata.Path, seriesMetadata.Contents);
|
||||
|
||||
metadata.Hash = hash;
|
||||
metadata.RelativePath = DiskProviderBase.GetRelativePath(series.Path, seriesMetadata.Path);
|
||||
|
||||
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 relativePath = DiskProviderBase.GetRelativePath(series.Path, episodeMetadata.Path);
|
||||
|
||||
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeMetadata &&
|
||||
c.EpisodeFileId == episodeFile.Id);
|
||||
|
||||
if (existingMetadata != null)
|
||||
{
|
||||
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||
if (!episodeMetadata.Path.PathEquals(fullPath))
|
||||
{
|
||||
_diskProvider.MoveFile(fullPath, episodeMetadata.Path);
|
||||
existingMetadata.RelativePath = relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
var hash = episodeMetadata.Contents.SHA256Hash();
|
||||
|
||||
var metadata = existingMetadata ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
EpisodeFileId = episodeFile.Id,
|
||||
Consumer = consumer.GetType().Name,
|
||||
Type = MetadataType.EpisodeMetadata,
|
||||
RelativePath = relativePath
|
||||
};
|
||||
|
||||
if (hash == metadata.Hash)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_logger.Debug("Writing Episode Metadata to: {0}", episodeMetadata.Path);
|
||||
_diskProvider.WriteAllText(episodeMetadata.Path, episodeMetadata.Contents);
|
||||
|
||||
metadata.Hash = hash;
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private List<MetadataFile> ProcessSeriesImages(IMetadata consumer, Series series, List<MetadataFile> existingMetadataFiles)
|
||||
{
|
||||
var result = new List<MetadataFile>();
|
||||
|
||||
foreach (var image in consumer.SeriesImages(series))
|
||||
{
|
||||
if (_diskProvider.FileExists(image.Path))
|
||||
{
|
||||
_logger.Debug("Series image already exists: {0}", image.Path);
|
||||
continue;
|
||||
}
|
||||
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, image.Path);
|
||||
|
||||
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesImage &&
|
||||
c.RelativePath == relativePath) ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
Consumer = consumer.GetType().Name,
|
||||
Type = MetadataType.SeriesImage,
|
||||
RelativePath = relativePath
|
||||
};
|
||||
|
||||
_diskProvider.CopyFile(image.Url, image.Path);
|
||||
|
||||
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))
|
||||
{
|
||||
if (_diskProvider.FileExists(image.Path))
|
||||
{
|
||||
_logger.Debug("Season image already exists: {0}", image.Path);
|
||||
continue;
|
||||
}
|
||||
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, image.Path);
|
||||
|
||||
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeasonImage &&
|
||||
c.SeasonNumber == season.SeasonNumber &&
|
||||
c.RelativePath == relativePath) ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
SeasonNumber = season.SeasonNumber,
|
||||
Consumer = consumer.GetType().Name,
|
||||
Type = MetadataType.SeasonImage,
|
||||
RelativePath = relativePath
|
||||
};
|
||||
|
||||
DownloadImage(series, image.Url, image.Path);
|
||||
|
||||
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))
|
||||
{
|
||||
if (_diskProvider.FileExists(image.Path))
|
||||
{
|
||||
_logger.Debug("Episode image already exists: {0}", image.Path);
|
||||
continue;
|
||||
}
|
||||
|
||||
var relativePath = DiskProviderBase.GetRelativePath(series.Path, image.Path);
|
||||
|
||||
var existingMetadata = existingMetadataFiles.FirstOrDefault(c => c.Type == MetadataType.EpisodeImage &&
|
||||
c.EpisodeFileId == episodeFile.Id);
|
||||
|
||||
if (existingMetadata != null)
|
||||
{
|
||||
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
||||
if (!image.Path.PathEquals(fullPath))
|
||||
{
|
||||
_diskProvider.MoveFile(fullPath, image.Path);
|
||||
existingMetadata.RelativePath = relativePath;
|
||||
|
||||
return new List<MetadataFile>{ existingMetadata };
|
||||
}
|
||||
}
|
||||
|
||||
var metadata = existingMetadata ??
|
||||
new MetadataFile
|
||||
{
|
||||
SeriesId = series.Id,
|
||||
EpisodeFileId = episodeFile.Id,
|
||||
Consumer = consumer.GetType().Name,
|
||||
Type = MetadataType.EpisodeImage,
|
||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, image.Path)
|
||||
};
|
||||
|
||||
DownloadImage(series, image.Url, image.Path);
|
||||
|
||||
result.Add(metadata);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void DownloadImage(Series series, string url, string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
_httpProvider.DownloadFile(url, path);
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
_logger.Warn(string.Format("Couldn't download image {0} for {1}. {2}", url, series, e.Message));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.ErrorException("Couldn't download image " + url + " for " + series, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,14 @@
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Metadata.Files;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Tv.Events;
|
||||
|
||||
namespace NzbDrone.Core.Metadata
|
||||
{
|
||||
public class ExistingMetadataService : IHandle<SeriesUpdatedEvent>
|
||||
public class ExistingMetadataService : IHandle<SeriesScannedEvent>
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IMetadataFileService _metadataFileService;
|
||||
@ -33,7 +33,7 @@ public ExistingMetadataService(IDiskProvider diskProvider,
|
||||
_consumers = consumers.ToList();
|
||||
}
|
||||
|
||||
public void Handle(SeriesUpdatedEvent message)
|
||||
public void Handle(SeriesScannedEvent message)
|
||||
{
|
||||
if (!_diskProvider.FolderExists(message.Series.Path)) return;
|
||||
|
||||
|
@ -12,5 +12,6 @@ public class MetadataFile : ModelBase
|
||||
public DateTime LastUpdated { get; set; }
|
||||
public Int32? EpisodeFileId { get; set; }
|
||||
public Int32? SeasonNumber { get; set; }
|
||||
public String Hash { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -14,17 +14,6 @@ namespace NzbDrone.Core.Metadata
|
||||
{
|
||||
public abstract class MetadataBase<TSettings> : IMetadata where TSettings : IProviderConfig, new()
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IHttpProvider _httpProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
protected MetadataBase(IDiskProvider diskProvider, IHttpProvider httpProvider, Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_httpProvider = httpProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Type ConfigContract
|
||||
{
|
||||
get
|
||||
@ -43,11 +32,15 @@ public IEnumerable<ProviderDefinition> DefaultDefinitions
|
||||
|
||||
public ProviderDefinition Definition { get; set; }
|
||||
|
||||
public abstract void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
|
||||
public abstract void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload);
|
||||
public abstract void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
|
||||
public abstract List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
|
||||
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);
|
||||
|
||||
protected TSettings Settings
|
||||
{
|
||||
get
|
||||
@ -56,28 +49,6 @@ protected TSettings Settings
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void DownloadImage(Series series, string url, string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_diskProvider.FileExists(path))
|
||||
{
|
||||
_logger.Debug("Image already exists: {0}, will not download again.", path);
|
||||
return;
|
||||
}
|
||||
|
||||
_httpProvider.DownloadFile(url, path);
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
_logger.Warn(string.Format("Couldn't download image {0} for {1}. {2}", url, series, e.Message));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.ErrorException("Couldn't download image " + url + " for " + series, e);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return GetType().Name;
|
||||
|
@ -195,6 +195,7 @@
|
||||
<Compile Include="Datastore\Migration\047_add_published_date_blacklist_column.cs" />
|
||||
<Compile Include="Datastore\Migration\048_add_title_to_scenemappings.cs" />
|
||||
<Compile Include="Datastore\Migration\049_fix_dognzb_url.cs" />
|
||||
<Compile Include="Datastore\Migration\049_add_hash_to_metadata_files.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
|
||||
@ -362,6 +363,8 @@
|
||||
<Compile Include="Metadata\Consumers\Xbmc\XbmcMetadata.cs" />
|
||||
<Compile Include="Metadata\Consumers\Xbmc\XbmcMetadataSettings.cs" />
|
||||
<Compile Include="Metadata\ExistingMetadataService.cs" />
|
||||
<Compile Include="Metadata\Files\ImageFileResult.cs" />
|
||||
<Compile Include="Metadata\Files\MetadataFileResult.cs" />
|
||||
<Compile Include="Metadata\Files\MetadataFilesUpdated.cs" />
|
||||
<Compile Include="Metadata\Files\MetadataFile.cs" />
|
||||
<Compile Include="Metadata\Files\MetadataFileRepository.cs" />
|
||||
@ -540,6 +543,7 @@
|
||||
<Compile Include="Qualities\QualityProfileItem.cs" />
|
||||
<Compile Include="Rest\JsonNetSerializer.cs" />
|
||||
<Compile Include="RootFolders\RootFolderRepository.cs" />
|
||||
<Compile Include="Security.cs" />
|
||||
<Compile Include="ThingiProvider\ConfigContractNotFoundException.cs" />
|
||||
<Compile Include="ThingiProvider\Events\ProviderUpdatedEvent.cs" />
|
||||
<Compile Include="ThingiProvider\IProvider.cs" />
|
||||
|
26
src/NzbDrone.Core/Security.cs
Normal file
26
src/NzbDrone.Core/Security.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core
|
||||
{
|
||||
public static class Security
|
||||
{
|
||||
public static string SHA256Hash(this string input)
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
|
||||
using (var hash = SHA256Managed.Create())
|
||||
{
|
||||
var enc = Encoding.UTF8;
|
||||
var result = hash.ComputeHash(enc.GetBytes(input));
|
||||
|
||||
foreach (var b in result)
|
||||
{
|
||||
stringBuilder.Append(b.ToString("x2"));
|
||||
}
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user