New: Track Kometa metadata files

(cherry picked from commit 84710a31bde67b6d33086fc645b7c3679c965b06)
pull/5018/head
Stevie Robinson 3 months ago committed by servarr
parent b514de2840
commit 432524809f

@ -0,0 +1,76 @@
using System.IO;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Extras.Metadata;
using NzbDrone.Core.Extras.Metadata.Consumers.Kometa;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Extras.Metadata.Consumers.Kometa
{
[TestFixture]
public class FindMetadataFileFixture : CoreTest<KometaMetadata>
{
private Series _series;
[SetUp]
public void Setup()
{
_series = Builder<Series>.CreateNew()
.With(s => s.Path = @"C:\Test\TV\The.Series".AsOsAgnostic())
.Build();
}
[Test]
public void should_return_null_if_filename_is_not_handled()
{
var path = Path.Combine(_series.Path, "file.jpg");
Subject.FindMetadataFile(_series, path).Should().BeNull();
}
[TestCase("Season00")]
[TestCase("Season01")]
[TestCase("Season02")]
public void should_return_season_image(string folder)
{
var path = Path.Combine(_series.Path, folder + ".jpg");
Subject.FindMetadataFile(_series, path).Type.Should().Be(MetadataType.SeasonImage);
}
[TestCase(".jpg", MetadataType.EpisodeImage)]
public void should_return_metadata_for_episode_if_valid_file_for_episode(string extension, MetadataType type)
{
var path = Path.Combine(_series.Path, "s01e01" + extension);
Subject.FindMetadataFile(_series, path).Type.Should().Be(type);
}
[TestCase(".jpg")]
public void should_return_null_if_not_valid_file_for_episode(string extension)
{
var path = Path.Combine(_series.Path, "the.series.episode" + extension);
Subject.FindMetadataFile(_series, path).Should().BeNull();
}
[Test]
public void should_not_return_metadata_if_image_file_is_a_thumb()
{
var path = Path.Combine(_series.Path, "the.series.s01e01.episode-thumb.jpg");
Subject.FindMetadataFile(_series, path).Should().BeNull();
}
[Test]
public void should_return_series_image_for_folder_jpg_in_series_folder()
{
var path = Path.Combine(_series.Path, "poster.jpg");
Subject.FindMetadataFile(_series, path).Type.Should().Be(MetadataType.SeriesImage);
}
}
}

@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Extras.Metadata.Consumers.Kometa
{
public class KometaMetadata : MetadataBase<KometaMetadataSettings>
{
private readonly Logger _logger;
private readonly IMapCoversToLocal _mediaCoverService;
public KometaMetadata(IMapCoversToLocal mediaCoverService,
Logger logger)
{
_mediaCoverService = mediaCoverService;
_logger = logger;
}
private static readonly Regex SeriesImagesRegex = new Regex(@"^(?<type>poster)\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex SeasonImagesRegex = new Regex(@"^Season(?<season>\d{2,})\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex EpisodeImageRegex = new Regex(@"^S(?<season>\d{2,})E(?<episode>\d{2,})\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public override string Name => "Kometa";
public override string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile)
{
if (metadataFile.Type == MetadataType.EpisodeImage)
{
return GetEpisodeImageFilename(series, episodeFile);
}
_logger.Debug("Unknown episode file metadata: {0}", metadataFile.RelativePath);
return Path.Combine(series.Path, metadataFile.RelativePath);
}
public override MetadataFile FindMetadataFile(Series series, 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;
return metadata;
}
var seasonMatch = SeasonImagesRegex.Match(filename);
if (seasonMatch.Success)
{
metadata.Type = MetadataType.SeasonImage;
var seasonNumberMatch = seasonMatch.Groups["season"].Value;
if (int.TryParse(seasonNumberMatch, out var seasonNumber))
{
metadata.SeasonNumber = seasonNumber;
}
else
{
return null;
}
return metadata;
}
if (EpisodeImageRegex.IsMatch(filename))
{
metadata.Type = MetadataType.EpisodeImage;
return metadata;
}
return null;
}
public override MetadataFileResult SeriesMetadata(Series series)
{
return null;
}
public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile)
{
return null;
}
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>();
}
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(series, episodeFile), screenshot.RemoteUrl)
};
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to process episode image for file: {0}", Path.Combine(series.Path, episodeFile.RelativePath));
return new List<ImageFileResult>();
}
}
private IEnumerable<ImageFileResult> ProcessSeriesImages(Series series)
{
foreach (var image in series.Images)
{
if (image.CoverType == MediaCoverTypes.Poster)
{
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
var destination = image.CoverType + Path.GetExtension(source);
yield return new ImageFileResult(destination, source);
}
}
}
private IEnumerable<ImageFileResult> ProcessSeasonImages(Series series, Season season)
{
foreach (var image in season.Images)
{
if (image.CoverType == MediaCoverTypes.Poster)
{
var filename = string.Format("Season{0:00}.jpg", season.SeasonNumber);
if (season.SeasonNumber == 0)
{
filename = "Season00.jpg";
}
yield return new ImageFileResult(filename, image.RemoteUrl);
}
}
}
private string GetEpisodeImageFilename(Series series, EpisodeFile episodeFile)
{
var filename = string.Format("S{0:00}E{1:00}.jpg", episodeFile.SeasonNumber, episodeFile.Episodes.Value.FirstOrDefault()?.EpisodeNumber);
return Path.Combine(series.Path, filename);
}
}
}

@ -0,0 +1,39 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Extras.Metadata.Consumers.Kometa
{
public class KometaSettingsValidator : AbstractValidator<KometaMetadataSettings>
{
}
public class KometaMetadataSettings : IProviderConfig
{
private static readonly KometaSettingsValidator Validator = new KometaSettingsValidator();
public KometaMetadataSettings()
{
SeriesImages = true;
SeasonImages = true;
EpisodeImages = true;
}
[FieldDefinition(0, Label = "MetadataSettingsSeriesImages", Type = FieldType.Checkbox, Section = MetadataSectionType.Image, HelpText = "Poster.jpg")]
public bool SeriesImages { get; set; }
[FieldDefinition(1, Label = "MetadataSettingsSeasonImages", Type = FieldType.Checkbox, Section = MetadataSectionType.Image, HelpText = "Season##.jpg")]
public bool SeasonImages { get; set; }
[FieldDefinition(2, Label = "MetadataSettingsEpisodeImages", Type = FieldType.Checkbox, Section = MetadataSectionType.Image, HelpText = "S##E##.jpg")]
public bool EpisodeImages { get; set; }
public bool IsValid => true;
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}
Loading…
Cancel
Save