Make sure episodes in series folder don't group as multiversion

Test for episodes in series folder


Turn on multiple versions for episodes


Support for multiversion episodes in mixed folders

Update 2 failing test cases. These were for passing for unofficially supported filenames. Dashes with no spaces, which is not how JF docs say multiversion files are supposed to be named.
Fix possible null


fix null take 2


Don't ParseName when calling ResolveVideos<Episode> 

ParseName drops everything after year in a filename. This results in episode information being dropped if year is present.

Update tests to set ParseName=false

Additional test with Mixed folder with Year in filename

Added case when calculating displayname for versions for mixed folders.
Add StringComparison.Ordinal to LastIndexOf

Was generating an error in recent build attempts.
Clean the episode filename to set the grouper value

This allows files like 
Name (2020) - S01E01 [BluRay-480p x264][AC3 2.0] - [ORIGINAL].mkv
Name (2020) - S01E01 [BluRay-1080p x264][AC3 5.1]- [Remaster].mkv

to be grouped on 'Name (2020) - S01E01'
Fix false positive merging

Only do cleanstring or " -" index cleaning, not both.
Compatiblity fix when stacking episodes and multiple versions are present


Fix linting problems
This commit is contained in:
SenorSmartyPants 2023-03-13 16:31:55 -05:00
parent 8677a17c3e
commit 9016fec892
6 changed files with 460 additions and 64 deletions

View File

@ -726,6 +726,12 @@ namespace Emby.Naming.Common
@"^\s*(?<name>[^ ].*?)\s*$"
};
VideoVersionExpressions = new[]
{
// get filename before final space-dash-space
@"^(?<filename>.*?)(?:\s-\s(?!.*\s-\s)(.*))?$"
};
MultipleEpisodeExpressions = new[]
{
@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
@ -846,6 +852,11 @@ namespace Emby.Naming.Common
/// </summary>
public string[] CleanStrings { get; set; }
/// <summary>
/// Gets or sets list of raw clean strings regular expressions strings.
/// </summary>
public string[] VideoVersionExpressions { get; set; }
/// <summary>
/// Gets or sets list of multi-episode regular expressions.
/// </summary>
@ -866,6 +877,11 @@ namespace Emby.Naming.Common
/// </summary>
public Regex[] CleanStringRegexes { get; private set; } = Array.Empty<Regex>();
/// <summary>
/// Gets list of video version regular expressions.
/// </summary>
public Regex[] VideoVersionRegexes { get; private set; } = Array.Empty<Regex>();
/// <summary>
/// Compiles raw regex strings into regexes.
/// </summary>
@ -873,6 +889,7 @@ namespace Emby.Naming.Common
{
CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray();
CleanStringRegexes = CleanStrings.Select(Compile).ToArray();
VideoVersionRegexes = VideoVersionExpressions.Select(Compile).ToArray();
}
private Regex Compile(string exp)

View File

@ -4,7 +4,9 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Common;
using Emby.Naming.TV;
using Jellyfin.Extensions;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
namespace Emby.Naming.Video
@ -25,10 +27,16 @@ namespace Emby.Naming.Video
/// </summary>
/// <param name="videoInfos">List of related video files.</param>
/// <param name="namingOptions">The naming options.</param>
/// <param name="collectionType">Collection type of videos being resolved.</param>
/// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param>
/// <param name="parseName">Whether to parse the name or use the filename.</param>
/// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns>
public static IReadOnlyList<VideoInfo> Resolve(IReadOnlyList<VideoFileInfo> videoInfos, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true)
public static IReadOnlyList<VideoInfo> Resolve(
IReadOnlyList<VideoFileInfo> videoInfos,
NamingOptions namingOptions,
string collectionType,
bool supportMultiVersion = true,
bool parseName = true)
{
// Filter out all extras, otherwise they could cause stacks to not be resolved
// See the unit test TestStackedWithTrailer
@ -79,12 +87,19 @@ namespace Emby.Naming.Video
var info = new VideoInfo(media.Name) { Files = new[] { media } };
info.Year = info.Files[0].Year;
if (info.Year is null)
{
// parse name for year info. Episodes don't get parsed up to this point for year info
var info2 = VideoResolver.Resolve(media.Path, media.IsDirectory, namingOptions, parseName);
info.Year = info2?.Year;
}
list.Add(info);
}
if (supportMultiVersion)
{
list = GetVideosGroupedByVersion(list, namingOptions);
list = GetVideosGroupedByVersion(list, namingOptions, collectionType);
}
// Whatever files are left, just add them
@ -98,7 +113,7 @@ namespace Emby.Naming.Video
return list;
}
private static List<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos, NamingOptions namingOptions)
private static List<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos, NamingOptions namingOptions, string collectionType)
{
if (videos.Count == 0)
{
@ -112,6 +127,8 @@ namespace Emby.Naming.Video
return videos;
}
var mergeable = new List<VideoInfo>();
var notMergeable = new List<VideoInfo>();
// Cannot use Span inside local functions and delegates thus we cannot use LINQ here nor merge with the above [if]
VideoInfo? primary = null;
for (var i = 0; i < videos.Count; i++)
@ -122,9 +139,14 @@ namespace Emby.Naming.Video
continue;
}
if (!IsEligibleForMultiVersion(folderName, video.Files[0].FileNameWithoutExtension, namingOptions))
// don't merge stacked episodes
if (video.Files.Count() == 1 && IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions, collectionType))
{
return videos;
mergeable.Add(video);
}
else
{
notMergeable.Add(video);
}
if (folderName.Equals(video.Files[0].FileNameWithoutExtension, StringComparison.Ordinal))
@ -133,35 +155,59 @@ namespace Emby.Naming.Video
}
}
if (videos.Count > 1)
var list = new List<VideoInfo>();
if (collectionType.Equals(CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
var groups = videos.GroupBy(x => ResolutionRegex().IsMatch(x.Files[0].FileNameWithoutExtension)).ToList();
videos.Clear();
var groupedList = mergeable.GroupBy(x => EpisodeGrouper(x.Files[0].Path, namingOptions, collectionType));
foreach (var grouping in groupedList)
{
list.Add(OrganizeAlternateVersions(grouping.ToList(), grouping.Key.AsSpan(), primary));
}
}
else if (mergeable.Count() > 0)
{
list.Add(OrganizeAlternateVersions(mergeable, folderName, primary));
}
// add non mergeables back in
list.AddRange(notMergeable);
list.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.Ordinal));
return list;
}
private static VideoInfo OrganizeAlternateVersions(List<VideoInfo> grouping, ReadOnlySpan<char> name, VideoInfo? primary)
{
VideoInfo? groupPrimary = null;
if (primary is not null && grouping.Contains(primary))
{
groupPrimary = primary;
}
var alternateVersions = new List<VideoInfo>();
if (grouping.Count() > 1)
{
// groups resolution based into one, and all other names
var groups = grouping.GroupBy(x => ResolutionRegex().IsMatch(x.Files[0].FileNameWithoutExtension));
foreach (var group in groups)
{
if (group.Key)
{
videos.InsertRange(0, group.OrderByDescending(x => x.Files[0].FileNameWithoutExtension.ToString(), new AlphanumericComparator()));
alternateVersions.InsertRange(0, group.OrderByDescending(x => x.Files[0].FileNameWithoutExtension.ToString(), new AlphanumericComparator()));
}
else
{
videos.AddRange(group.OrderBy(x => x.Files[0].FileNameWithoutExtension.ToString(), new AlphanumericComparator()));
alternateVersions.AddRange(group.OrderBy(x => x.Files[0].FileNameWithoutExtension.ToString(), new AlphanumericComparator()));
}
}
}
primary ??= videos[0];
videos.Remove(primary);
groupPrimary ??= alternateVersions.FirstOrDefault() ?? grouping.First();
alternateVersions.Remove(groupPrimary);
groupPrimary.AlternateVersions = alternateVersions.Select(x => x.Files[0]).ToArray();
var list = new List<VideoInfo>
{
primary
};
list[0].AlternateVersions = videos.Select(x => x.Files[0]).ToArray();
list[0].Name = folderName.ToString();
return list;
groupPrimary.Name = name.ToString();
return groupPrimary;
}
private static bool HaveSameYear(IReadOnlyList<VideoInfo> videos)
@ -183,8 +229,16 @@ namespace Emby.Naming.Video
return true;
}
private static bool IsEligibleForMultiVersion(ReadOnlySpan<char> folderName, ReadOnlySpan<char> testFilename, NamingOptions namingOptions)
private static bool IsEligibleForMultiVersion(ReadOnlySpan<char> folderName, ReadOnlySpan<char> testFilePath, NamingOptions namingOptions, ReadOnlySpan<char> collectionType)
{
var testFilename = Path.GetFileNameWithoutExtension(testFilePath);
if (collectionType.Equals(CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
// episodes are always eligible to be grouped
return true;
}
if (!testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
{
return false;
@ -207,5 +261,41 @@ namespace Emby.Naming.Video
|| testFilename[0] == '-'
|| CheckMultiVersionRegex().IsMatch(testFilename);
}
private static string EpisodeGrouper(string testFilePath, NamingOptions namingOptions, ReadOnlySpan<char> collectionType)
{
// grouper for tv shows/episodes should be everything before space-dash-space
var resolver = new EpisodeResolver(namingOptions);
EpisodeInfo? episodeInfo = resolver.Resolve(testFilePath, false);
ReadOnlySpan<char> seriesName = episodeInfo!.SeriesName;
var filename = Path.GetFileNameWithoutExtension(testFilePath);
// start with grouping by filename
string g = filename;
for (var i = 0; i < namingOptions.VideoVersionRegexes.Length; i++)
{
var rule = namingOptions.VideoVersionRegexes[i];
var match = rule.Match(filename);
if (!match.Success)
{
continue;
}
g = match.Groups["filename"].Value;
// clean the filename
if (VideoResolver.TryCleanString(g, namingOptions, out string newName))
{
g = newName;
}
// never group episodes under series name
if (MemoryExtensions.Equals(g.AsSpan(), seriesName, StringComparison.OrdinalIgnoreCase))
{
g = filename;
}
}
return g;
}
}
}

View File

@ -229,7 +229,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
return ResolveVideos<Episode>(parent, files, false, collectionType, true);
return ResolveVideos<Episode>(parent, files, true, collectionType, false);
}
return null;
@ -275,7 +275,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
.Where(f => f is not null)
.ToList();
var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName);
var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, collectionType, supportMultiEditions, parseName);
var result = new MultiItemResolverResult
{

View File

@ -1179,10 +1179,18 @@ namespace MediaBrowser.Controller.Entities
{
if (HasLocalAlternateVersions)
{
var displayName = System.IO.Path.GetFileNameWithoutExtension(path)
var fileName = System.IO.Path.GetFileNameWithoutExtension(path);
var displayName = fileName
.Replace(System.IO.Path.GetFileName(ContainingFolderPath), string.Empty, StringComparison.OrdinalIgnoreCase)
.TrimStart(new char[] { ' ', '-' });
if (fileName == displayName)
{
// file does not start with parent folder name. This must be an episode in a mixed directory
// get string after last dash - this is the version name
displayName = fileName.Substring(fileName.LastIndexOf('-') + 1).TrimStart(new char[] { ' ', '-' });
}
if (!string.IsNullOrEmpty(displayName))
{
terms.Add(displayName);

View File

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.Video;
using MediaBrowser.Model.Entities;
using Xunit;
namespace Jellyfin.Naming.Tests.Video
@ -23,7 +25,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Single(result.Where(v => v.ExtraType is null));
Assert.Single(result.Where(v => v.ExtraType is not null));
@ -42,7 +45,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Single(result.Where(v => v.ExtraType is null));
Assert.Single(result.Where(v => v.ExtraType is not null));
@ -60,7 +64,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Single(result);
Assert.Single(result[0].AlternateVersions);
@ -82,7 +87,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(7, result.Count);
Assert.Empty(result[0].AlternateVersions);
@ -105,7 +111,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Single(result);
Assert.Equal(7, result[0].AlternateVersions.Count);
@ -129,7 +136,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(9, result.Count);
Assert.Empty(result[0].AlternateVersions);
@ -149,7 +157,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(5, result.Count);
Assert.Empty(result[0].AlternateVersions);
@ -171,7 +180,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(5, result.Count);
Assert.Empty(result[0].AlternateVersions);
@ -193,7 +203,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Single(result);
Assert.Equal("/movies/Iron Man/Iron Man.mkv", result[0].Files[0].Path);
@ -222,7 +233,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Single(result);
Assert.Equal("/movies/Iron Man/Iron Man.mkv", result[0].Files[0].Path);
@ -246,7 +258,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(2, result.Count);
}
@ -267,7 +280,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(7, result.Count);
Assert.Empty(result[0].AlternateVersions);
@ -289,7 +303,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(5, result.Count);
Assert.Empty(result[0].AlternateVersions);
@ -306,7 +321,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies);
Assert.Single(result);
Assert.Single(result[0].AlternateVersions);
@ -323,7 +339,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies);
Assert.Single(result);
Assert.Single(result[0].AlternateVersions);
@ -344,7 +361,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies);
Assert.Single(result);
Assert.Equal("/movies/X-Men Apocalypse (2016)/X-Men Apocalypse (2016).mkv", result[0].Files[0].Path);
@ -367,7 +385,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies);
Assert.Single(result);
Assert.Single(result[0].AlternateVersions);
@ -384,7 +403,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies);
Assert.Equal(2, result.Count);
}
@ -392,9 +412,166 @@ namespace Jellyfin.Naming.Tests.Video
[Fact]
public void TestEmptyList()
{
var result = VideoListResolver.Resolve(new List<VideoFileInfo>(), _namingOptions).ToList();
var result = VideoListResolver.Resolve(new List<VideoFileInfo>(), _namingOptions, string.Empty).ToList();
Assert.Empty(result);
}
[Fact]
public void TestMultiVersionEpisodeDontCollapse()
{
// Test for false positive
var files = new[]
{
@"/TV/Dexter/Dexter - S01E01 - One.mkv",
@"/TV/Dexter/Dexter - S01E02 - Two.mkv",
@"/TV/Dexter/Dexter - S01E03 - Three.mkv",
@"/TV/Dexter/Dexter - S01E04 - Four.mkv",
@"/TV/Dexter/Dexter - S01E05 - Five.mkv",
};
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType<VideoFileInfo>().ToList(),
_namingOptions,
CollectionType.TvShows);
Assert.Equal(5, result.Count);
Assert.Empty(result[0].AlternateVersions);
}
[Fact]
public void TestMultiVersionEpisodeDontCollapse2()
{
// Test for false positive
var files = new[]
{
@"/TV/Dexter/Dexter - S01E01 One.mkv",
@"/TV/Dexter/Dexter - S01E02 Two.mkv",
@"/TV/Dexter/Dexter - S01E03 Three.mkv",
@"/TV/Dexter/Dexter - S01E04 Four.mkv",
@"/TV/Dexter/Dexter - S01E05 Five.mkv",
@"/TV/Star Trek- Picard/Season 3/Star Trek - Picard 3x01 [WEBDL-720p Proper x264][EAC3 5.1] - Part One - The Next Generation.mkv",
@"/TV/Star Trek- Picard/Season 3/Star Trek - Picard 3x02 [WEBDL-720p Proper x264][EAC3 5.1] - Part Two - Disengage.mkv",
@"/TV/Star Trek- Picard/Season 3/Star Trek - Picard 3x03 [WEBDL-720p x264][EAC3 5.1] - Part Three - Seventeen Seconds.mkv",
};
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType<VideoFileInfo>().ToList(),
_namingOptions,
CollectionType.TvShows);
Assert.Equal(8, result.Count);
Assert.Empty(result[0].AlternateVersions);
}
[Fact]
public void TestMultiVersionEpisode()
{
var files = new[]
{
@"/TV/Dexter/Dexter - S01E01/Dexter - S01E01 - One.mkv",
@"/TV/Dexter/Dexter - S01E01/Dexter - S01E01 - Two.mkv",
};
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType<VideoFileInfo>().ToList(),
_namingOptions,
CollectionType.TvShows);
Assert.Single(result);
Assert.Single(result[0].AlternateVersions);
}
[Fact]
public void TestMultiVersionEpisodeMixedSeriesFolder()
{
var files = new[]
{
@"/TV/Dexter/Dexter - S01E01.mkv",
@"/TV/Dexter/Dexter - S01E01 - Unaired.mkv",
@"/TV/Dexter/Dexter - S01E02 - Two.mkv",
@"/TV/Dexter/Dexter - S01E03 - Three.mkv",
@"/TV/Dexter/Dexter S02E01 - Ia.mkv",
@"/TV/Dexter/Dexter S02E01 - I.mkv",
@"/TV/Dexter/Dexter - S02E02.mkv",
};
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType<VideoFileInfo>().ToList(),
_namingOptions,
CollectionType.TvShows);
Assert.Equal(5, result.Count);
Assert.Single(result[0].AlternateVersions);
Assert.Empty(result[1].AlternateVersions);
Assert.Empty(result[2].AlternateVersions);
Assert.Empty(result[3].AlternateVersions);
var s02e01 = result.FirstOrDefault(x => string.Equals(x.Name, "Dexter S02E01", StringComparison.Ordinal));
Assert.NotNull(s02e01);
Assert.Single(s02e01!.AlternateVersions);
}
[Fact]
public void TestMultiVersionEpisodeMixedSeasonFolder()
{
var files = new[]
{
@"/TV/Dexter/Season 2/Dexter - S02E01 - Ia.mkv",
@"/TV/Dexter/Season 2/Dexter - S02E01 - I.mkv",
@"/TV/Dexter/Season 2/Dexter - S02E02.mkv",
};
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType<VideoFileInfo>().ToList(),
_namingOptions,
CollectionType.TvShows);
Assert.Equal(2, result.Count);
Assert.Single(result[0].AlternateVersions);
Assert.Empty(result[1].AlternateVersions);
}
[Fact]
public void TestMultiVersionEpisodeMixedSeasonFolderWithYear()
{
var files = new[]
{
@"/TV/Name (2020)/Season 1/Name (2020) - S01E01 - [ORIGINAL].mkv",
@"/TV/Name (2020)/Season 1/Name (2020) - S01E01 - [VERSION].mkv",
@"/TV/Name (2020)/Season 1/Name (2020) - S01E02 - [ORIGINAL].mkv",
};
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType<VideoFileInfo>().ToList(),
_namingOptions,
CollectionType.TvShows);
Assert.Equal(2, result.Count);
Assert.Single(result[0].AlternateVersions);
Assert.Empty(result[1].AlternateVersions);
}
[Fact]
public void TestMultiVersionEpisodeMixedSeasonFolderWithYearAndDirtyNames()
{
var files = new[]
{
@"/TV/Name (2020)/Season 1/Name (2020) - S01E01 [BluRay-480p x264][AC3 2.0] - [ORIGINAL].mkv",
@"/TV/Name (2020)/Season 1/Name (2020) - S01E01 [BluRay-1080p x264][AC3 5.1] - [Remaster].mkv",
@"/TV/Name (2020)/Season 1/Name (2020) - S01E02 - [ORIGINAL].mkv",
};
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType<VideoFileInfo>().ToList(),
_namingOptions,
CollectionType.TvShows);
Assert.Equal(2, result.Count);
Assert.Single(result[0].AlternateVersions);
Assert.Empty(result[1].AlternateVersions);
}
}
}

View File

@ -42,7 +42,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(11, result.Count);
var batman = result.FirstOrDefault(x => string.Equals(x.Name, "Batman", StringComparison.Ordinal));
@ -65,6 +66,90 @@ namespace Jellyfin.Naming.Tests.Video
Assert.Equal(ExtraType.Trailer, result[10].ExtraType);
}
[Fact]
public void TestTVStackAndVersions()
{
// No stacking here because there is no part/disc/etc
var files = new[]
{
@"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) - s01e01 CD1.avi",
@"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) - s01e01 CD2.avi",
@"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) - s01e02 - The First Cut is the Deepest.avi",
@"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) - s01e03.mp4",
@"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) - s01e04 - Aired Version.mp4",
@"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) - s01e04 - Uncensored Version.mp4"
};
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType<VideoFileInfo>().ToList(),
_namingOptions,
CollectionType.TvShows).ToList();
Assert.Equal(4, result.Count);
var s01e01 = result.FirstOrDefault(x => string.Equals(x.Name, "Grey's Anatomy (2005) - s01e01", StringComparison.Ordinal));
Assert.NotNull(s01e01);
Assert.Equal(2, s01e01!.Files.Count);
var s01e04 = result.FirstOrDefault(x => string.Equals(x.Name, "Grey's Anatomy (2005) - s01e04", StringComparison.Ordinal));
Assert.NotNull(s01e04);
Assert.Equal(1, s01e04!.AlternateVersions.Count);
}
[Fact]
public void TestTVStackAndVersionsNoFirstDash()
{
// No stacking here because there is no part/disc/etc
var files = new[]
{
@"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) s01e01 - pt1.avi",
@"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) s01e01 - pt2.avi",
@"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) s01e02 - The First Cut is the Deepest.avi",
@"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) s01e03.mp4",
@"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) s01e04 - Aired Version.mp4",
@"/TV/Grey's Anatomy (2005)/Grey's Anatomy (2005) s01e04 - Uncensored Version.mp4"
};
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType<VideoFileInfo>().ToList(),
_namingOptions,
CollectionType.TvShows).ToList();
Assert.Equal(4, result.Count);
var s01e01 = result.FirstOrDefault(x => string.Equals(x.Name, "Grey's Anatomy (2005) s01e01", StringComparison.Ordinal));
Assert.NotNull(s01e01);
Assert.Equal(2, s01e01!.Files.Count);
var s01e04 = result.FirstOrDefault(x => string.Equals(x.Name, "Grey's Anatomy (2005) s01e04", StringComparison.Ordinal));
Assert.NotNull(s01e04);
Assert.Equal(1, s01e04!.AlternateVersions.Count);
}
[Fact]
public void TestTVStack()
{
// No stacking here because there is no part/disc/etc
var files = new[]
{
@"/TV/Doctor Who/Season 21/Doctor Who 21x11 - Resurrection of the Daleks - Part 1.mkv",
@"/TV/Doctor Who/Season 21/Doctor Who 21x11 - Resurrection of the Daleks - Part 2.mkv",
@"/TV/Doctor Who/Season 21/Doctor Who 21x12 - Resurrection of the Daleks - Part 3.mkv",
@"/TV/Doctor Who/Season 21/Doctor Who 21x12 - Resurrection of the Daleks - Part 4.mkv"
};
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType<VideoFileInfo>().ToList(),
_namingOptions,
CollectionType.TvShows).ToList();
Assert.Equal(2, result.Count);
var s21e12 = result.FirstOrDefault(x => string.Equals(x.Name, "Doctor Who 21x12 - Resurrection of the Daleks", StringComparison.Ordinal));
Assert.NotNull(s21e12);
Assert.Equal(2, s21e12!.Files.Count);
}
[Fact]
public void TestWithMetadata()
{
@ -76,7 +161,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Single(result);
}
@ -92,7 +178,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(2, result.Count);
Assert.False(result[0].ExtraType.HasValue);
@ -110,7 +197,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(2, result.Count);
Assert.False(result[0].ExtraType.HasValue);
@ -129,7 +217,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(3, result.Count);
Assert.False(result[0].ExtraType.HasValue);
@ -149,7 +238,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(3, result.Count);
Assert.False(result[0].ExtraType.HasValue);
@ -168,7 +258,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(2, result.Count);
Assert.False(result[0].ExtraType.HasValue);
@ -190,7 +281,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
string.Empty).ToList();
Assert.Equal(5, result.Count);
}
@ -206,7 +298,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, true, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Single(result);
}
@ -223,7 +316,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, true, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(2, result.Count);
}
@ -241,7 +335,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(3, result.Count);
Assert.False(result[0].ExtraType.HasValue);
@ -262,7 +357,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(4, result.Count);
Assert.False(result[0].ExtraType.HasValue);
@ -284,7 +380,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
string.Empty).ToList();
Assert.Equal(2, result.Count);
}
@ -299,7 +396,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Single(result);
}
@ -314,7 +412,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Single(result);
}
@ -330,7 +429,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
// The result should contain two individual movies
// Version grouping should not work here, because the files are not in a directory with the name 'Four Sisters and a Wedding'
@ -348,7 +448,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(2, result.Count);
}
@ -364,7 +465,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(2, result.Count);
Assert.False(result[0].ExtraType.HasValue);
@ -382,7 +484,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(2, result.Count);
Assert.False(result[0].ExtraType.HasValue);
@ -400,7 +503,8 @@ namespace Jellyfin.Naming.Tests.Video
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
_namingOptions,
CollectionType.Movies).ToList();
Assert.Equal(2, result.Count);
Assert.False(result[0].ExtraType.HasValue);