mirror of https://github.com/jellyfin/jellyfin.git
Compare commits
9 Commits
5890ec114d
...
ba753d9962
Author | SHA1 | Date |
---|---|---|
SenorSmartyPants | ba753d9962 | |
renovate[bot] | 5612cb8178 | |
renovate[bot] | ccd06bc547 | |
Bond-009 | d29b85a134 | |
SenorSmartyPants | 8f3a4ca3a6 | |
SenorSmartyPants | 48176f5d80 | |
SenorSmartyPants | 09e5c3ba69 | |
SenorSmartyPants | 9016fec892 | |
SenorSmartyPants | 8677a17c3e |
|
@ -20,7 +20,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
|
||||
with:
|
||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
permissions: read-all
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
|
@ -39,7 +39,7 @@ jobs:
|
|||
permissions: read-all
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
|
@ -78,12 +78,12 @@ jobs:
|
|||
- openapi-base
|
||||
steps:
|
||||
- name: Download openapi-head
|
||||
uses: actions/download-artifact@9c19ed7fe5d278cd354c7dfd5d3b88589c7e2395 # v4.1.6
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||
with:
|
||||
name: openapi-head
|
||||
path: openapi-head
|
||||
- name: Download openapi-base
|
||||
uses: actions/download-artifact@9c19ed7fe5d278cd354c7dfd5d3b88589c7e2395 # v4.1.6
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||
with:
|
||||
name: openapi-base
|
||||
path: openapi-base
|
||||
|
@ -152,7 +152,7 @@ jobs:
|
|||
run: |-
|
||||
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
|
||||
- name: Download openapi-head
|
||||
uses: actions/download-artifact@9c19ed7fe5d278cd354c7dfd5d3b88589c7e2395 # v4.1.6
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||
with:
|
||||
name: openapi-head
|
||||
path: openapi-head
|
||||
|
|
|
@ -19,7 +19,7 @@ jobs:
|
|||
|
||||
runs-on: "${{ matrix.os }}"
|
||||
steps:
|
||||
- uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
|
||||
- uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
|
||||
with:
|
||||
|
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
reactions: '+1'
|
||||
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
@ -51,7 +51,7 @@ jobs:
|
|||
reactions: eyes
|
||||
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
@ -128,7 +128,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: pull in script
|
||||
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
with:
|
||||
repository: jellyfin/jellyfin-triage-script
|
||||
- name: install python
|
||||
|
|
|
@ -10,7 +10,7 @@ jobs:
|
|||
issues: write
|
||||
steps:
|
||||
- name: pull in script
|
||||
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
with:
|
||||
repository: jellyfin/jellyfin-triage-script
|
||||
- name: install python
|
||||
|
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
yq-version: v4.9.8
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
with:
|
||||
ref: ${{ env.TAG_BRANCH }}
|
||||
|
||||
|
@ -66,7 +66,7 @@ jobs:
|
|||
NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
with:
|
||||
ref: ${{ env.TAG_BRANCH }}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<PackageVersion Include="BlurHashSharp" Version="1.3.2" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
|
||||
<PackageVersion Include="Diacritics" Version="3.3.27" />
|
||||
<PackageVersion Include="Diacritics" Version="3.3.29" />
|
||||
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
||||
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
||||
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="4.4.3" />
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@ using MediaBrowser.Model.Library;
|
|||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TMDbLib.Objects.Authentication;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
|
||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
||||
|
@ -1612,14 +1611,18 @@ namespace Emby.Server.Implementations.Library
|
|||
/// <returns>IEnumerable{System.String}.</returns>
|
||||
public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user)
|
||||
{
|
||||
if (IntroProviders.Length == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var tasks = IntroProviders
|
||||
.Take(1)
|
||||
.Select(i => GetIntros(i, item, user));
|
||||
|
||||
var items = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
return items
|
||||
.SelectMany(i => i.ToArray())
|
||||
.SelectMany(i => i)
|
||||
.Select(ResolveIntro)
|
||||
.Where(i => i is not null)!; // null values got filtered out
|
||||
}
|
||||
|
|
|
@ -274,7 +274,7 @@ namespace Emby.Server.Implementations.Library
|
|||
var tasks = _providers.Select(i => GetDynamicMediaSources(item, i, cancellationToken));
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
return results.SelectMany(i => i.ToList());
|
||||
return results.SelectMany(i => i);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, IMediaSourceProvider provider, CancellationToken cancellationToken)
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -286,7 +286,7 @@ namespace MediaBrowser.Providers.Manager
|
|||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
return results.SelectMany(i => i.ToList());
|
||||
return results.SelectMany(i => i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue