mirror of https://github.com/ErsatzTV/ErsatzTV.git
9 changed files with 254 additions and 1 deletions
@ -0,0 +1,119 @@
@@ -0,0 +1,119 @@
|
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Metadata; |
||||
using FluentAssertions; |
||||
using LanguageExt; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ErsatzTV.Core.Tests.Metadata |
||||
{ |
||||
[TestFixture] |
||||
public class TestMediaScannerTests |
||||
{ |
||||
private readonly TestMediaScanner _scanner = new(); |
||||
|
||||
[Test] |
||||
public void NewMovieFile_WithoutNfo_WithoutPoster() |
||||
{ |
||||
// new movie file without nfo and without poster should have statistics and fallback metadata
|
||||
var movieFileName = "/movies/test (2021)/test (2021).mkv"; |
||||
string[] fileNames = { movieFileName }; |
||||
|
||||
Seq<LocalMediaItemScanningPlan> result = _scanner.DetermineActions( |
||||
MediaType.Movie, |
||||
Seq<MediaItem>.Empty, |
||||
fileNames.ToSeq()); |
||||
|
||||
result.Count.Should().Be(1); |
||||
(Either<string, MediaItem> source, List<ItemScanningPlan> itemScanningPlans) = result.Head(); |
||||
source.IsLeft.Should().BeTrue(); |
||||
source.LeftToSeq().Should().BeEquivalentTo(movieFileName); |
||||
itemScanningPlans.Should().BeEquivalentTo( |
||||
new ItemScanningPlan(movieFileName, ScanningAction.Statistics), |
||||
new ItemScanningPlan(movieFileName, ScanningAction.FallbackMetadata)); |
||||
} |
||||
|
||||
[Test] |
||||
public void NewMovieFile_WithNfo_WithoutPoster( |
||||
[Values("test (2021).nfo", "movie.nfo")] |
||||
string nfoFile) |
||||
{ |
||||
// new movie file without nfo and without poster should have statistics and fallback metadata
|
||||
var movieFileName = "/movies/test (2021)/test (2021).mkv"; |
||||
var nfoFileName = $"/movies/test (2021)/{nfoFile}"; |
||||
string[] fileNames = { movieFileName, nfoFileName }; |
||||
|
||||
Seq<LocalMediaItemScanningPlan> result = _scanner.DetermineActions( |
||||
MediaType.Movie, |
||||
Seq<MediaItem>.Empty, |
||||
fileNames.ToSeq()); |
||||
|
||||
result.Count.Should().Be(1); |
||||
(Either<string, MediaItem> source, List<ItemScanningPlan> itemScanningPlans) = result.Head(); |
||||
source.IsLeft.Should().BeTrue(); |
||||
source.LeftToSeq().Should().BeEquivalentTo(movieFileName); |
||||
itemScanningPlans.Should().BeEquivalentTo( |
||||
new ItemScanningPlan(movieFileName, ScanningAction.Statistics), |
||||
new ItemScanningPlan(nfoFileName, ScanningAction.SidecarMetadata)); |
||||
} |
||||
|
||||
[Test] |
||||
public void NewMovieFile_WithoutNfo_WithPoster( |
||||
[Values("", "test (2021)-")] |
||||
string basePosterName, |
||||
[Values("jpg", "jpeg", "png", "gif", "tbn")] |
||||
string posterExtension) |
||||
{ |
||||
// new movie file without nfo and without poster should have statistics and fallback metadata
|
||||
var movieFileName = "/movies/test (2021)/test (2021).mkv"; |
||||
var posterFileName = $"/movies/test (2021)/{basePosterName}poster.{posterExtension}"; |
||||
|
||||
string[] fileNames = { movieFileName, posterFileName }; |
||||
|
||||
Seq<LocalMediaItemScanningPlan> result = _scanner.DetermineActions( |
||||
MediaType.Movie, |
||||
Seq<MediaItem>.Empty, |
||||
fileNames.ToSeq()); |
||||
|
||||
result.Count.Should().Be(1); |
||||
(Either<string, MediaItem> source, List<ItemScanningPlan> itemScanningPlans) = result.Head(); |
||||
source.IsLeft.Should().BeTrue(); |
||||
source.LeftToSeq().Should().BeEquivalentTo(movieFileName); |
||||
itemScanningPlans.Should().BeEquivalentTo( |
||||
new ItemScanningPlan(movieFileName, ScanningAction.Statistics), |
||||
new ItemScanningPlan(movieFileName, ScanningAction.FallbackMetadata), |
||||
new ItemScanningPlan(posterFileName, ScanningAction.Poster)); |
||||
} |
||||
|
||||
[Test] |
||||
public void NewMovieFile_WithNfo_WithPoster( |
||||
[Values("test (2021).nfo", "movie.nfo")] |
||||
string nfoFile, |
||||
[Values("", "test (2021)-")] |
||||
string basePosterName, |
||||
[Values("jpg", "jpeg", "png", "gif", "tbn")] |
||||
string posterExtension) |
||||
{ |
||||
// new movie file without nfo and without poster should have statistics and fallback metadata
|
||||
var movieFileName = "/movies/test (2021)/test (2021).mkv"; |
||||
var nfoFileName = $"/movies/test (2021)/{nfoFile}"; |
||||
var posterFileName = $"/movies/test (2021)/{basePosterName}poster.{posterExtension}"; |
||||
|
||||
string[] fileNames = { movieFileName, nfoFileName, posterFileName }; |
||||
|
||||
Seq<LocalMediaItemScanningPlan> result = _scanner.DetermineActions( |
||||
MediaType.Movie, |
||||
Seq<MediaItem>.Empty, |
||||
fileNames.ToSeq()); |
||||
|
||||
result.Count.Should().Be(1); |
||||
(Either<string, MediaItem> source, List<ItemScanningPlan> itemScanningPlans) = result.Head(); |
||||
source.IsLeft.Should().BeTrue(); |
||||
source.LeftToSeq().Should().BeEquivalentTo(movieFileName); |
||||
itemScanningPlans.Should().BeEquivalentTo( |
||||
new ItemScanningPlan(movieFileName, ScanningAction.Statistics), |
||||
new ItemScanningPlan(nfoFileName, ScanningAction.SidecarMetadata), |
||||
new ItemScanningPlan(posterFileName, ScanningAction.Poster)); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
namespace ErsatzTV.Core.Domain |
||||
{ |
||||
public enum MetadataSource |
||||
{ |
||||
Sidecar = 1, |
||||
Fallback = 2 |
||||
} |
||||
} |
||||
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
namespace ErsatzTV.Core.Metadata |
||||
{ |
||||
public record ItemScanningPlan(string TargetPath, ScanningAction TargetAction); |
||||
} |
||||
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Core.Domain; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Core.Metadata |
||||
{ |
||||
public record LocalMediaItemScanningPlan(Either<string, MediaItem> Source, List<ItemScanningPlan> ActionPlans); |
||||
} |
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
namespace ErsatzTV.Core.Metadata |
||||
{ |
||||
public enum ScanningAction |
||||
{ |
||||
None = 0, |
||||
Statistics = 1, |
||||
SidecarMetadata = 2, |
||||
FallbackMetadata = 3, |
||||
Collections = 4, |
||||
Poster = 5 |
||||
} |
||||
} |
||||
@ -0,0 +1,98 @@
@@ -0,0 +1,98 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using ErsatzTV.Core.Domain; |
||||
using LanguageExt; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Core.Metadata |
||||
{ |
||||
public class TestMediaScanner |
||||
{ |
||||
// @formatter:off
|
||||
private static readonly Seq<string> VideoFileExtensions = Seq( |
||||
".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".ogg", ".mp4", |
||||
".m4p", ".m4v", ".avi", ".wmv", ".mov", ".mkv", ".ts"); |
||||
// @formatter:on
|
||||
|
||||
private static readonly Seq<string> ImageFileExtensions = Seq("jpg", "jpeg", "png", "gif", "tbn"); |
||||
|
||||
public Seq<LocalMediaItemScanningPlan> DetermineActions( |
||||
MediaType mediaType, |
||||
Seq<MediaItem> mediaItems, |
||||
Seq<string> files) |
||||
{ |
||||
var results = new IntermediateResults(); |
||||
var videoFiles = files.Filter(f => VideoFileExtensions.Contains(Path.GetExtension(f))); |
||||
|
||||
(Seq<string> newFiles, Seq<MediaItem> existingMediaItems) = videoFiles.Map( |
||||
s => mediaItems.Find(i => i.Path == s).ToEither(s)) |
||||
.Partition(); |
||||
|
||||
// new files
|
||||
foreach (string file in newFiles) |
||||
{ |
||||
results.Add(file, new ItemScanningPlan(file, ScanningAction.Statistics)); |
||||
|
||||
Option<string> maybeNfoFile = LocateNfoFile(mediaType, files, file); |
||||
maybeNfoFile.BiIter( |
||||
nfoFile => results.Add(file, new ItemScanningPlan(nfoFile, ScanningAction.SidecarMetadata)), |
||||
() => results.Add(file, new ItemScanningPlan(file, ScanningAction.FallbackMetadata))); |
||||
|
||||
Option<string> maybePoster = LocatePoster(mediaType, files, file); |
||||
maybePoster.IfSome( |
||||
posterFile => results.Add(file, new ItemScanningPlan(posterFile, ScanningAction.Poster))); |
||||
} |
||||
|
||||
// existing media items
|
||||
|
||||
return results.Summarize(); |
||||
} |
||||
|
||||
private static Option<string> LocateNfoFile(MediaType mediaType, Seq<string> files, string file) |
||||
{ |
||||
switch (mediaType) |
||||
{ |
||||
case MediaType.Movie: |
||||
string movieAsNfo = Path.ChangeExtension(file, "nfo"); |
||||
string movieNfo = Path.Combine(Path.GetDirectoryName(file) ?? string.Empty, "movie.nfo"); |
||||
return Seq(movieAsNfo, movieNfo) |
||||
.Filter(s => files.Contains(s)) |
||||
.HeadOrNone(); |
||||
} |
||||
|
||||
return None; |
||||
} |
||||
|
||||
private static Option<string> LocatePoster(MediaType mediaType, Seq<string> files, string file) |
||||
{ |
||||
string folder = Path.GetDirectoryName(file) ?? string.Empty; |
||||
|
||||
switch (mediaType) |
||||
{ |
||||
case MediaType.Movie: |
||||
IEnumerable<string> possiblePosters = ImageFileExtensions.Collect( |
||||
ext => new[] { $"poster.{ext}", Path.GetFileNameWithoutExtension(file) + $"-poster.{ext}" }) |
||||
.Map(f => Path.Combine(folder, f)); |
||||
return possiblePosters.Filter(s => files.Contains(s)).HeadOrNone(); |
||||
} |
||||
|
||||
return None; |
||||
} |
||||
|
||||
private class IntermediateResults |
||||
{ |
||||
private readonly List<Tuple<Either<string, MediaItem>, ItemScanningPlan>> _rawResults = new(); |
||||
|
||||
public void Add(Either<string, MediaItem> source, ItemScanningPlan plan) => |
||||
_rawResults.Add(Tuple(source, plan)); |
||||
|
||||
public Seq<LocalMediaItemScanningPlan> Summarize() => |
||||
_rawResults |
||||
.GroupBy(t => t.Item1) |
||||
.Select(g => new LocalMediaItemScanningPlan(g.Key, g.Select(g2 => g2.Item2).ToList())) |
||||
.ToSeq(); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue