This commit is contained in:
SenorSmartyPants 2024-04-25 09:15:44 -04:00 committed by GitHub
commit ba753d9962
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 479 additions and 66 deletions

View File

@ -146,8 +146,8 @@ namespace Emby.Naming.Common
CleanDateTimes = new[]
{
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*",
@"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*"
@"(.+?[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*",
@"(.+?[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*"
};
CleanStrings = new[]
@ -739,6 +739,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}))+[^\\\/]*$",
@ -864,6 +870,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>
@ -884,6 +895,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>
@ -891,6 +907,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

@ -228,7 +228,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (collectionType == CollectionType.tvshows)
{
return ResolveVideos<Episode>(parent, files, false, collectionType, true);
return ResolveVideos<Episode>(parent, files, true, collectionType, false);
}
return null;
@ -274,7 +274,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

@ -1185,10 +1185,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,186 @@ 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);
}
[Fact]
public void TestMultiVersionEpisodeABCD()
{
var files = new[]
{
@"/TV/SeriesName/SeriesName- S01E01 - EpisodeTitle - [VersionA].mkv",
@"/TV/SeriesName/SeriesName- S01E01 - EpisodeTitle - [VersionB].mkv",
@"/TV/SeriesName/SeriesName- S01E01 - EpisodeTitle - VersionC.mkv",
@"/TV/SeriesName/SeriesName- S01E01 - EpisodeTitle - VersionD.mkv"
};
var result = VideoListResolver.Resolve(
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions, false)).OfType<VideoFileInfo>().ToList(),
_namingOptions,
CollectionType.TvShows);
Assert.Single(result);
Assert.Equal(3, result[0].AlternateVersions.Count);
}
}
}

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,87 @@ namespace Jellyfin.Naming.Tests.Video
Assert.Equal(ExtraType.Trailer, result[10].ExtraType);
}
[Fact]
public void TestTVStackAndVersions()
{
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.Single(s01e04!.AlternateVersions);
}
[Fact]
public void TestTVStackAndVersionsNoFirstDash()
{
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.Single(s01e04!.AlternateVersions);
}
[Fact]
public void TestTVStack()
{
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 +158,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 +175,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 +194,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 +214,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 +235,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 +255,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 +278,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 +295,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 +313,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 +332,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 +354,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 +377,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 +393,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 +409,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 +426,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 +445,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 +462,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 +481,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 +500,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);