mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-23 22:25:14 -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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user