mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-18 21:34:28 -04:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d6f6cc9f5f | |||
| 73979c416a | |||
| 348e8f9c27 | |||
| 38bdb5a75d | |||
| 5e4c51e2f7 |
+1
-1
@@ -9,7 +9,7 @@ variables:
|
|||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '0.3.32'
|
majorVersion: '0.4.0'
|
||||||
minorVersion: $[counter('minorVersion', 1)]
|
minorVersion: $[counter('minorVersion', 1)]
|
||||||
readarrVersion: '$(majorVersion).$(minorVersion)'
|
readarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
||||||
<PackageVersion Include="Sentry" Version="3.31.0" />
|
<PackageVersion Include="Sentry" Version="3.31.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.4" />
|
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||||
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.6.2" />
|
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.6.2" />
|
||||||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
|
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -219,7 +219,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
|||||||
|
|
||||||
protected virtual IList<TableDefinition> ReadTables()
|
protected virtual IList<TableDefinition> ReadTables()
|
||||||
{
|
{
|
||||||
const string sqlCommand = @"SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name;";
|
const string sqlCommand = @"SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_litestream_%' ORDER BY name;";
|
||||||
var dtTable = Read(sqlCommand).Tables[0];
|
var dtTable = Read(sqlCommand).Tables[0];
|
||||||
|
|
||||||
var tableDefinitionList = new List<TableDefinition>();
|
var tableDefinitionList = new List<TableDefinition>();
|
||||||
|
|||||||
@@ -279,6 +279,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "metaDL": // torrent magnet is being downloaded
|
case "metaDL": // torrent magnet is being downloaded
|
||||||
|
case "forcedMetaDL": // torrent metadata is being forcibly downloaded
|
||||||
if (config.DhtEnabled)
|
if (config.DhtEnabled)
|
||||||
{
|
{
|
||||||
item.Status = DownloadItemStatus.Queued;
|
item.Status = DownloadItemStatus.Queued;
|
||||||
@@ -293,7 +294,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "forcedDL": // torrent is being downloaded, and was forced started
|
case "forcedDL": // torrent is being downloaded, and was forced started
|
||||||
case "forcedMetaDL": // torrent metadata is being forcibly downloaded
|
|
||||||
case "moving": // torrent is being moved from a folder
|
case "moving": // torrent is being moved from a folder
|
||||||
case "downloading": // torrent is being downloaded and data is being transferred
|
case "downloading": // torrent is being downloaded and data is being transferred
|
||||||
item.Status = DownloadItemStatus.Downloading;
|
item.Status = DownloadItemStatus.Downloading;
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user