Refactor extras parsing
This commit is contained in:
parent
9cafa2cab4
commit
fde84a1e00
|
@ -14,6 +14,7 @@ namespace Emby.Naming.AudioBook
|
|||
public class AudioBookListResolver
|
||||
{
|
||||
private readonly NamingOptions _options;
|
||||
private readonly AudioBookResolver _audioBookResolver;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AudioBookListResolver"/> class.
|
||||
|
@ -22,6 +23,7 @@ namespace Emby.Naming.AudioBook
|
|||
public AudioBookListResolver(NamingOptions options)
|
||||
{
|
||||
_options = options;
|
||||
_audioBookResolver = new AudioBookResolver(_options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -31,21 +33,19 @@ namespace Emby.Naming.AudioBook
|
|||
/// <returns>Returns IEnumerable of <see cref="AudioBookInfo"/>.</returns>
|
||||
public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
|
||||
{
|
||||
var audioBookResolver = new AudioBookResolver(_options);
|
||||
|
||||
// File with empty fullname will be sorted out here.
|
||||
var audiobookFileInfos = files
|
||||
.Select(i => audioBookResolver.Resolve(i.FullName))
|
||||
.Select(i => _audioBookResolver.Resolve(i.FullName))
|
||||
.OfType<AudioBookFileInfo>()
|
||||
.ToList();
|
||||
|
||||
var stackResult = new StackResolver(_options)
|
||||
.ResolveAudioBooks(audiobookFileInfos);
|
||||
var stackResult = StackResolver.ResolveAudioBooks(audiobookFileInfos);
|
||||
|
||||
foreach (var stack in stackResult)
|
||||
{
|
||||
var stackFiles = stack.Files
|
||||
.Select(i => audioBookResolver.Resolve(i))
|
||||
.Select(i => _audioBookResolver.Resolve(i))
|
||||
.OfType<AudioBookFileInfo>()
|
||||
.ToList();
|
||||
|
||||
|
|
|
@ -126,9 +126,9 @@ namespace Emby.Naming.Common
|
|||
|
||||
VideoFileStackingExpressions = new[]
|
||||
{
|
||||
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$",
|
||||
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$",
|
||||
"(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$"
|
||||
"^(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|part|pt|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$",
|
||||
"^(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|part|pt|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$",
|
||||
"^(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$"
|
||||
};
|
||||
|
||||
CleanDateTimes = new[]
|
||||
|
@ -403,6 +403,12 @@ namespace Emby.Naming.Common
|
|||
|
||||
VideoExtraRules = new[]
|
||||
{
|
||||
new ExtraRule(
|
||||
ExtraType.Trailer,
|
||||
ExtraRuleType.DirectoryName,
|
||||
"trailers",
|
||||
MediaType.Video),
|
||||
|
||||
new ExtraRule(
|
||||
ExtraType.Trailer,
|
||||
ExtraRuleType.Filename,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Emby.Naming.Audio;
|
||||
using Emby.Naming.Common;
|
||||
|
@ -9,45 +11,27 @@ namespace Emby.Naming.Video
|
|||
/// <summary>
|
||||
/// Resolve if file is extra for video.
|
||||
/// </summary>
|
||||
public class ExtraResolver
|
||||
public static class ExtraResolver
|
||||
{
|
||||
private static readonly char[] _digits = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
||||
private readonly NamingOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExtraResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options"><see cref="NamingOptions"/> object containing VideoExtraRules and passed to <see cref="AudioFileParser"/> and <see cref="VideoResolver"/>.</param>
|
||||
public ExtraResolver(NamingOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
private static readonly char[] _digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to resolve if file is extra.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to file.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
/// <returns>Returns <see cref="ExtraResult"/> object.</returns>
|
||||
public ExtraResult GetExtraInfo(string path)
|
||||
public static ExtraResult GetExtraInfo(string path, NamingOptions namingOptions)
|
||||
{
|
||||
var result = new ExtraResult();
|
||||
|
||||
for (var i = 0; i < _options.VideoExtraRules.Length; i++)
|
||||
for (var i = 0; i < namingOptions.VideoExtraRules.Length; i++)
|
||||
{
|
||||
var rule = _options.VideoExtraRules[i];
|
||||
if (rule.MediaType == MediaType.Audio)
|
||||
var rule = namingOptions.VideoExtraRules[i];
|
||||
if ((rule.MediaType == MediaType.Audio && !AudioFileParser.IsAudioFile(path, namingOptions))
|
||||
|| (rule.MediaType == MediaType.Video && !VideoResolver.IsVideoFile(path, namingOptions)))
|
||||
{
|
||||
if (!AudioFileParser.IsAudioFile(path, _options))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (rule.MediaType == MediaType.Video)
|
||||
{
|
||||
if (!VideoResolver.IsVideoFile(path, _options))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var pathSpan = path.AsSpan();
|
||||
|
@ -76,7 +60,7 @@ namespace Emby.Naming.Video
|
|||
{
|
||||
var filename = Path.GetFileName(path);
|
||||
|
||||
var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
|
||||
var regex = new Regex(rule.Token, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
if (regex.IsMatch(filename))
|
||||
{
|
||||
|
@ -102,5 +86,68 @@ namespace Emby.Naming.Video
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds extras matching the video info.
|
||||
/// </summary>
|
||||
/// <param name="files">The list of file video infos.</param>
|
||||
/// <param name="videoInfo">The video to compare against.</param>
|
||||
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
|
||||
/// <returns>A list of video extras for [videoInfo].</returns>
|
||||
public static IReadOnlyList<VideoFileInfo> GetExtras(IReadOnlyList<VideoInfo> files, VideoFileInfo videoInfo, ReadOnlySpan<char> videoFlagDelimiters)
|
||||
{
|
||||
var parentDir = videoInfo.IsDirectory ? videoInfo.Path : Path.GetDirectoryName(videoInfo.Path.AsSpan());
|
||||
|
||||
var trimmedFileName = TrimFilenameDelimiters(videoInfo.Name, videoFlagDelimiters);
|
||||
var trimmedFileNameWithoutExtension = TrimFilenameDelimiters(videoInfo.FileNameWithoutExtension, videoFlagDelimiters);
|
||||
var trimmedVideoInfoName = TrimFilenameDelimiters(videoInfo.Name, videoFlagDelimiters);
|
||||
|
||||
var result = new List<VideoFileInfo>();
|
||||
for (var pos = files.Count - 1; pos >= 0; pos--)
|
||||
{
|
||||
var current = files[pos];
|
||||
// ignore non-extras and multi-file (can this happen?)
|
||||
if (current.ExtraType == null || current.Files.Count > 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var currentFile = files[pos].Files[0];
|
||||
var trimmedCurrentFileName = TrimFilenameDelimiters(currentFile.Name, videoFlagDelimiters);
|
||||
|
||||
// first check filenames
|
||||
bool isValid = StartsWith(trimmedCurrentFileName, trimmedFileNameWithoutExtension)
|
||||
|| (StartsWith(trimmedCurrentFileName, trimmedFileName) && currentFile.Year == videoInfo.Year)
|
||||
|| (StartsWith(trimmedCurrentFileName, trimmedVideoInfoName) && currentFile.Year == videoInfo.Year);
|
||||
|
||||
// then by directory
|
||||
if (!isValid)
|
||||
{
|
||||
// When the extra rule type is DirectoryName we must go one level higher to get the "real" dir name
|
||||
var currentParentDir = currentFile.ExtraRule?.RuleType == ExtraRuleType.DirectoryName
|
||||
? Path.GetDirectoryName(Path.GetDirectoryName(currentFile.Path.AsSpan()))
|
||||
: Path.GetDirectoryName(currentFile.Path.AsSpan());
|
||||
|
||||
isValid = !currentParentDir.IsEmpty && !parentDir.IsEmpty && currentParentDir.Equals(parentDir, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
result.Add(currentFile);
|
||||
}
|
||||
}
|
||||
|
||||
return result.OrderBy(r => r.Path).ToArray();
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters)
|
||||
{
|
||||
return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd();
|
||||
}
|
||||
|
||||
private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName)
|
||||
{
|
||||
return !baseName.IsEmpty && fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,11 @@ namespace Emby.Naming.Video
|
|||
/// <returns>True if file is in the stack.</returns>
|
||||
public bool ContainsFile(string file, bool isDirectory)
|
||||
{
|
||||
if (string.IsNullOrEmpty(file))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsDirectoryStack == isDirectory)
|
||||
{
|
||||
return Files.Contains(file, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
|
@ -12,37 +12,28 @@ namespace Emby.Naming.Video
|
|||
/// <summary>
|
||||
/// Resolve <see cref="FileStack"/> from list of paths.
|
||||
/// </summary>
|
||||
public class StackResolver
|
||||
public static class StackResolver
|
||||
{
|
||||
private readonly NamingOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StackResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options"><see cref="NamingOptions"/> object containing VideoFileStackingRegexes and passes options to <see cref="VideoResolver"/>.</param>
|
||||
public StackResolver(NamingOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves only directories from paths.
|
||||
/// </summary>
|
||||
/// <param name="files">List of paths.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
|
||||
public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files)
|
||||
public static IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files, NamingOptions namingOptions)
|
||||
{
|
||||
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }));
|
||||
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }), namingOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves only files from paths.
|
||||
/// </summary>
|
||||
/// <param name="files">List of paths.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
/// <returns>Enumerable <see cref="FileStack"/> of files.</returns>
|
||||
public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files)
|
||||
public static IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files, NamingOptions namingOptions)
|
||||
{
|
||||
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }));
|
||||
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }), namingOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -50,7 +41,7 @@ namespace Emby.Naming.Video
|
|||
/// </summary>
|
||||
/// <param name="files">List of paths.</param>
|
||||
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
|
||||
public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files)
|
||||
public static IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files)
|
||||
{
|
||||
var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path));
|
||||
|
||||
|
@ -82,15 +73,20 @@ namespace Emby.Naming.Video
|
|||
/// Resolves videos from paths.
|
||||
/// </summary>
|
||||
/// <param name="files">List of paths.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
/// <returns>Enumerable <see cref="FileStack"/> of videos.</returns>
|
||||
public IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files)
|
||||
public static IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions)
|
||||
{
|
||||
var list = files
|
||||
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, _options) || VideoResolver.IsStubFile(i.FullName, _options))
|
||||
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, namingOptions) || VideoResolver.IsStubFile(i.FullName, namingOptions))
|
||||
.OrderBy(i => i.FullName)
|
||||
.Select(f => (f.IsDirectory, FileName: GetFileNameWithExtension(f), f.FullName))
|
||||
.ToList();
|
||||
|
||||
var expressions = _options.VideoFileStackingRegexes;
|
||||
// TODO is there a "nicer" way?
|
||||
var cache = new Dictionary<(string, Regex, int), Match>();
|
||||
|
||||
var expressions = namingOptions.VideoFileStackingRegexes;
|
||||
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
|
@ -102,17 +98,17 @@ namespace Emby.Naming.Video
|
|||
while (expressionIndex < expressions.Length)
|
||||
{
|
||||
var exp = expressions[expressionIndex];
|
||||
var stack = new FileStack();
|
||||
FileStack? stack = null;
|
||||
|
||||
// (Title)(Volume)(Ignore)(Extension)
|
||||
var match1 = FindMatch(file1, exp, offset);
|
||||
var match1 = FindMatch(file1.FileName, exp, offset, cache);
|
||||
|
||||
if (match1.Success)
|
||||
{
|
||||
var title1 = match1.Groups["title"].Value;
|
||||
var volume1 = match1.Groups["volume"].Value;
|
||||
var ignore1 = match1.Groups["ignore"].Value;
|
||||
var extension1 = match1.Groups["extension"].Value;
|
||||
var title1 = match1.Groups[1].Value;
|
||||
var volume1 = match1.Groups[2].Value;
|
||||
var ignore1 = match1.Groups[3].Value;
|
||||
var extension1 = match1.Groups[4].Value;
|
||||
|
||||
var j = i + 1;
|
||||
while (j < list.Count)
|
||||
|
@ -126,7 +122,7 @@ namespace Emby.Naming.Video
|
|||
}
|
||||
|
||||
// (Title)(Volume)(Ignore)(Extension)
|
||||
var match2 = FindMatch(file2, exp, offset);
|
||||
var match2 = FindMatch(file2.FileName, exp, offset, cache);
|
||||
|
||||
if (match2.Success)
|
||||
{
|
||||
|
@ -142,6 +138,7 @@ namespace Emby.Naming.Video
|
|||
if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack ??= new FileStack();
|
||||
if (stack.Files.Count == 0)
|
||||
{
|
||||
stack.Name = title1 + ignore1;
|
||||
|
@ -204,7 +201,7 @@ namespace Emby.Naming.Video
|
|||
expressionIndex++;
|
||||
}
|
||||
|
||||
if (stack.Files.Count > 1)
|
||||
if (stack?.Files.Count > 1)
|
||||
{
|
||||
yield return stack;
|
||||
i += stack.Files.Count - 1;
|
||||
|
@ -214,26 +211,32 @@ namespace Emby.Naming.Video
|
|||
}
|
||||
}
|
||||
|
||||
private static string GetRegexInput(FileSystemMetadata file)
|
||||
private static string GetFileNameWithExtension(FileSystemMetadata file)
|
||||
{
|
||||
// For directories, dummy up an extension otherwise the expressions will fail
|
||||
var input = !file.IsDirectory
|
||||
? file.FullName
|
||||
: file.FullName + ".mkv";
|
||||
var input = file.FullName;
|
||||
if (file.IsDirectory)
|
||||
{
|
||||
input = Path.ChangeExtension(input, "mkv");
|
||||
}
|
||||
|
||||
return Path.GetFileName(input);
|
||||
}
|
||||
|
||||
private static Match FindMatch(FileSystemMetadata input, Regex regex, int offset)
|
||||
private static Match FindMatch(string input, Regex regex, int offset, Dictionary<(string, Regex, int), Match> cache)
|
||||
{
|
||||
var regexInput = GetRegexInput(input);
|
||||
|
||||
if (offset < 0 || offset >= regexInput.Length)
|
||||
if (offset < 0 || offset >= input.Length)
|
||||
{
|
||||
return Match.Empty;
|
||||
}
|
||||
|
||||
return regex.Match(regexInput, offset);
|
||||
if (!cache.TryGetValue((input, regex, offset), out var result))
|
||||
{
|
||||
result = regex.Match(input, offset, input.Length - offset);
|
||||
cache.Add((input, regex, offset), result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
|
@ -17,7 +18,6 @@ namespace Emby.Naming.Video
|
|||
Name = name;
|
||||
|
||||
Files = Array.Empty<VideoFileInfo>();
|
||||
Extras = Array.Empty<VideoFileInfo>();
|
||||
AlternateVersions = Array.Empty<VideoFileInfo>();
|
||||
}
|
||||
|
||||
|
@ -39,16 +39,15 @@ namespace Emby.Naming.Video
|
|||
/// <value>The files.</value>
|
||||
public IReadOnlyList<VideoFileInfo> Files { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the extras.
|
||||
/// </summary>
|
||||
/// <value>The extras.</value>
|
||||
public IReadOnlyList<VideoFileInfo> Extras { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alternate versions.
|
||||
/// </summary>
|
||||
/// <value>The alternate versions.</value>
|
||||
public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the extra type.
|
||||
/// </summary>
|
||||
public ExtraType? ExtraType { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Emby.Naming.Common;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
|
@ -20,11 +19,12 @@ namespace Emby.Naming.Video
|
|||
/// <param name="files">List of related video files.</param>
|
||||
/// <param name="namingOptions">The naming options.</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 IEnumerable<VideoInfo> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true)
|
||||
public static IReadOnlyList<VideoInfo> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true)
|
||||
{
|
||||
var videoInfos = files
|
||||
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions))
|
||||
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions, parseName))
|
||||
.OfType<VideoFileInfo>()
|
||||
.ToList();
|
||||
|
||||
|
@ -34,12 +34,25 @@ namespace Emby.Naming.Video
|
|||
.Where(i => i.ExtraType == null)
|
||||
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
|
||||
|
||||
var stackResult = new StackResolver(namingOptions)
|
||||
.Resolve(nonExtras).ToList();
|
||||
var stackResult = StackResolver.Resolve(nonExtras, namingOptions).ToList();
|
||||
|
||||
var remainingFiles = videoInfos
|
||||
.Where(i => !stackResult.Any(s => i.Path != null && s.ContainsFile(i.Path, i.IsDirectory)))
|
||||
.ToList();
|
||||
var remainingFiles = new List<VideoFileInfo>();
|
||||
var standaloneMedia = new List<VideoFileInfo>();
|
||||
|
||||
for (var i = 0; i < videoInfos.Count; i++)
|
||||
{
|
||||
var current = videoInfos[i];
|
||||
if (stackResult.Any(s => s.ContainsFile(current.Path, current.IsDirectory)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
remainingFiles.Add(current);
|
||||
if (current.ExtraType == null)
|
||||
{
|
||||
standaloneMedia.Add(current);
|
||||
}
|
||||
}
|
||||
|
||||
var list = new List<VideoInfo>();
|
||||
|
||||
|
@ -47,27 +60,15 @@ namespace Emby.Naming.Video
|
|||
{
|
||||
var info = new VideoInfo(stack.Name)
|
||||
{
|
||||
Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions))
|
||||
Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions, parseName))
|
||||
.OfType<VideoFileInfo>()
|
||||
.ToList()
|
||||
};
|
||||
|
||||
info.Year = info.Files[0].Year;
|
||||
|
||||
var extras = ExtractExtras(remainingFiles, stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0].AsSpan()), namingOptions.VideoFlagDelimiters);
|
||||
|
||||
if (extras.Count > 0)
|
||||
{
|
||||
info.Extras = extras;
|
||||
}
|
||||
|
||||
list.Add(info);
|
||||
}
|
||||
|
||||
var standaloneMedia = remainingFiles
|
||||
.Where(i => i.ExtraType == null)
|
||||
.ToList();
|
||||
|
||||
foreach (var media in standaloneMedia)
|
||||
{
|
||||
var info = new VideoInfo(media.Name) { Files = new[] { media } };
|
||||
|
@ -75,10 +76,6 @@ namespace Emby.Naming.Video
|
|||
info.Year = info.Files[0].Year;
|
||||
|
||||
remainingFiles.Remove(media);
|
||||
var extras = ExtractExtras(remainingFiles, media.FileNameWithoutExtension, namingOptions.VideoFlagDelimiters);
|
||||
|
||||
info.Extras = extras;
|
||||
|
||||
list.Add(info);
|
||||
}
|
||||
|
||||
|
@ -87,58 +84,12 @@ namespace Emby.Naming.Video
|
|||
list = GetVideosGroupedByVersion(list, namingOptions);
|
||||
}
|
||||
|
||||
// If there's only one resolved video, use the folder name as well to find extras
|
||||
if (list.Count == 1)
|
||||
{
|
||||
var info = list[0];
|
||||
var videoPath = list[0].Files[0].Path;
|
||||
var parentPath = Path.GetDirectoryName(videoPath.AsSpan());
|
||||
|
||||
if (!parentPath.IsEmpty)
|
||||
{
|
||||
var folderName = Path.GetFileName(parentPath);
|
||||
if (!folderName.IsEmpty)
|
||||
{
|
||||
var extras = ExtractExtras(remainingFiles, folderName, namingOptions.VideoFlagDelimiters);
|
||||
extras.AddRange(info.Extras);
|
||||
info.Extras = extras;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the extras that are just based on file name as well
|
||||
var extrasByFileName = remainingFiles
|
||||
.Where(i => i.ExtraRule != null && i.ExtraRule.RuleType == ExtraRuleType.Filename)
|
||||
.ToList();
|
||||
|
||||
remainingFiles = remainingFiles
|
||||
.Except(extrasByFileName)
|
||||
.ToList();
|
||||
|
||||
extrasByFileName.AddRange(info.Extras);
|
||||
info.Extras = extrasByFileName;
|
||||
}
|
||||
|
||||
// If there's only one video, accept all trailers
|
||||
// Be lenient because people use all kinds of mishmash conventions with trailers.
|
||||
if (list.Count == 1)
|
||||
{
|
||||
var trailers = remainingFiles
|
||||
.Where(i => i.ExtraType == ExtraType.Trailer)
|
||||
.ToList();
|
||||
|
||||
trailers.AddRange(list[0].Extras);
|
||||
list[0].Extras = trailers;
|
||||
|
||||
remainingFiles = remainingFiles
|
||||
.Except(trailers)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Whatever files are left, just add them
|
||||
list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
|
||||
{
|
||||
Files = new[] { i },
|
||||
Year = i.Year
|
||||
Year = i.Year,
|
||||
ExtraType = i.ExtraType
|
||||
}));
|
||||
|
||||
return list;
|
||||
|
@ -162,6 +113,11 @@ namespace Emby.Naming.Video
|
|||
for (var i = 0; i < videos.Count; i++)
|
||||
{
|
||||
var video = videos[i];
|
||||
if (video.ExtraType != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions))
|
||||
{
|
||||
return videos;
|
||||
|
@ -178,17 +134,14 @@ namespace Emby.Naming.Video
|
|||
|
||||
var alternateVersionsLen = videos.Count - 1;
|
||||
var alternateVersions = new VideoFileInfo[alternateVersionsLen];
|
||||
var extras = new List<VideoFileInfo>(list[0].Extras);
|
||||
for (int i = 0; i < alternateVersionsLen; i++)
|
||||
{
|
||||
var video = videos[i + 1];
|
||||
alternateVersions[i] = video.Files[0];
|
||||
extras.AddRange(video.Extras);
|
||||
}
|
||||
|
||||
list[0].AlternateVersions = alternateVersions;
|
||||
list[0].Name = folderName.ToString();
|
||||
list[0].Extras = extras;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
@ -230,7 +183,7 @@ namespace Emby.Naming.Video
|
|||
var tmpTestFilename = testFilename.ToString();
|
||||
if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName))
|
||||
{
|
||||
tmpTestFilename = cleanName.Trim().ToString();
|
||||
tmpTestFilename = cleanName.Trim();
|
||||
}
|
||||
|
||||
// The CleanStringParser should have removed common keywords etc.
|
||||
|
@ -238,67 +191,5 @@ namespace Emby.Naming.Video
|
|||
|| testFilename[0] == '-'
|
||||
|| Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled);
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters)
|
||||
{
|
||||
return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd();
|
||||
}
|
||||
|
||||
private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName, ReadOnlySpan<char> trimmedBaseName)
|
||||
{
|
||||
if (baseName.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase)
|
||||
|| (!trimmedBaseName.IsEmpty && fileName.StartsWith(trimmedBaseName, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds similar filenames to that of [baseName] and removes any matches from [remainingFiles].
|
||||
/// </summary>
|
||||
/// <param name="remainingFiles">The list of remaining filenames.</param>
|
||||
/// <param name="baseName">The base name to use for the comparison.</param>
|
||||
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
|
||||
/// <returns>A list of video extras for [baseName].</returns>
|
||||
private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> baseName, ReadOnlySpan<char> videoFlagDelimiters)
|
||||
{
|
||||
return ExtractExtras(remainingFiles, baseName, ReadOnlySpan<char>.Empty, videoFlagDelimiters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds similar filenames to that of [firstBaseName] and [secondBaseName] and removes any matches from [remainingFiles].
|
||||
/// </summary>
|
||||
/// <param name="remainingFiles">The list of remaining filenames.</param>
|
||||
/// <param name="firstBaseName">The first base name to use for the comparison.</param>
|
||||
/// <param name="secondBaseName">The second base name to use for the comparison.</param>
|
||||
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
|
||||
/// <returns>A list of video extras for [firstBaseName] and [secondBaseName].</returns>
|
||||
private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> firstBaseName, ReadOnlySpan<char> secondBaseName, ReadOnlySpan<char> videoFlagDelimiters)
|
||||
{
|
||||
var trimmedFirstBaseName = TrimFilenameDelimiters(firstBaseName, videoFlagDelimiters);
|
||||
var trimmedSecondBaseName = TrimFilenameDelimiters(secondBaseName, videoFlagDelimiters);
|
||||
|
||||
var result = new List<VideoFileInfo>();
|
||||
for (var pos = remainingFiles.Count - 1; pos >= 0; pos--)
|
||||
{
|
||||
var file = remainingFiles[pos];
|
||||
if (file.ExtraType == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var filename = file.FileNameWithoutExtension;
|
||||
if (StartsWith(filename, firstBaseName, trimmedFirstBaseName)
|
||||
|| StartsWith(filename, secondBaseName, trimmedSecondBaseName))
|
||||
{
|
||||
result.Add(file);
|
||||
remainingFiles.RemoveAt(pos);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,11 @@ namespace Emby.Naming.Video
|
|||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
/// <param name="parseName">Whether to parse the name or use the filename.</param>
|
||||
/// <returns>VideoFileInfo.</returns>
|
||||
public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions)
|
||||
public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions, bool parseName = true)
|
||||
{
|
||||
return Resolve(path, true, namingOptions);
|
||||
return Resolve(path, true, namingOptions, parseName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -74,7 +75,7 @@ namespace Emby.Naming.Video
|
|||
|
||||
var format3DResult = Format3DParser.Parse(path, namingOptions);
|
||||
|
||||
var extraResult = new ExtraResolver(namingOptions).GetExtraInfo(path);
|
||||
var extraResult = ExtraResolver.GetExtraInfo(path, namingOptions);
|
||||
|
||||
var name = Path.GetFileNameWithoutExtension(path);
|
||||
|
||||
|
|
|
@ -11,11 +11,9 @@ using System.Net;
|
|||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Naming.Audio;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.TV;
|
||||
using Emby.Naming.Video;
|
||||
using Emby.Server.Implementations.Library.Resolvers;
|
||||
using Emby.Server.Implementations.Library.Validators;
|
||||
using Emby.Server.Implementations.Playlists;
|
||||
using Emby.Server.Implementations.ScheduledTasks;
|
||||
|
@ -677,7 +675,7 @@ namespace Emby.Server.Implementations.Library
|
|||
{
|
||||
var result = resolver.ResolveMultiple(parent, fileList, collectionType, directoryService);
|
||||
|
||||
if (result != null && result.Items.Count > 0)
|
||||
if (result?.Items.Count > 0)
|
||||
{
|
||||
var items = new List<BaseItem>();
|
||||
items.AddRange(result.Items);
|
||||
|
@ -2685,89 +2683,58 @@ namespace Emby.Server.Implementations.Library
|
|||
};
|
||||
}
|
||||
|
||||
public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren)
|
||||
{
|
||||
var namingOptions = _namingOptions;
|
||||
|
||||
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
|
||||
.Where(i => string.Equals(i.Name, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, namingOptions.VideoFileExtensions, false, false))
|
||||
.ToList();
|
||||
|
||||
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
|
||||
|
||||
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (currentVideo != null)
|
||||
var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions);
|
||||
if (ownerVideoInfo == null)
|
||||
{
|
||||
files.AddRange(currentVideo.Extras.Where(i => i.ExtraType == ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
|
||||
yield break;
|
||||
}
|
||||
|
||||
var resolvers = new IItemResolver[]
|
||||
var count = fileSystemChildren.Count;
|
||||
var files = new List<FileSystemMetadata>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
new GenericVideoResolver<Trailer>(_namingOptions)
|
||||
};
|
||||
|
||||
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers)
|
||||
.OfType<Trailer>()
|
||||
.Select(video =>
|
||||
var current = fileSystemChildren[i];
|
||||
if (current.IsDirectory && BaseItem.AllExtrasTypesFolderNames.ContainsKey(current.Name))
|
||||
{
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
if (GetItemById(video.Id) is Trailer dbItem)
|
||||
{
|
||||
video = dbItem;
|
||||
}
|
||||
|
||||
video.ParentId = Guid.Empty;
|
||||
video.OwnerId = owner.Id;
|
||||
video.ExtraType = ExtraType.Trailer;
|
||||
video.TrailerTypes = new[] { TrailerType.LocalTrailer };
|
||||
|
||||
return video;
|
||||
|
||||
// Sort them so that the list can be easily compared for changes
|
||||
}).OrderBy(i => i.Path);
|
||||
}
|
||||
|
||||
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||
{
|
||||
var namingOptions = _namingOptions;
|
||||
|
||||
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
|
||||
.Where(i => BaseItem.AllExtrasTypesFolderNames.ContainsKey(i.Name ?? string.Empty))
|
||||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, namingOptions.VideoFileExtensions, false, false))
|
||||
.ToList();
|
||||
|
||||
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
|
||||
|
||||
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (currentVideo != null)
|
||||
{
|
||||
files.AddRange(currentVideo.Extras.Where(i => i.ExtraType != ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
|
||||
files.AddRange(_fileSystem.GetFiles(current.FullName, _namingOptions.VideoFileExtensions, false, false));
|
||||
}
|
||||
else if (!current.IsDirectory)
|
||||
{
|
||||
files.Add(current);
|
||||
}
|
||||
}
|
||||
|
||||
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null)
|
||||
.OfType<Video>()
|
||||
.Select(video =>
|
||||
if (files.Count == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var videos = VideoListResolver.Resolve(files, _namingOptions);
|
||||
// owner video info cannot be null as that implies it has no path
|
||||
var extras = ExtraResolver.GetExtras(videos, ownerVideoInfo, _namingOptions.VideoFlagDelimiters);
|
||||
|
||||
for (var i = 0; i < extras.Count; i++)
|
||||
{
|
||||
var currentExtra = extras[i];
|
||||
var resolved = ResolvePath(_fileSystem.GetFileInfo(currentExtra.Path));
|
||||
if (resolved is not Video video)
|
||||
{
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
var dbItem = GetItemById(video.Id) as Video;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
video = dbItem;
|
||||
}
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
if (GetItemById(resolved.Id) is Video dbItem)
|
||||
{
|
||||
video = dbItem;
|
||||
}
|
||||
|
||||
video.ParentId = Guid.Empty;
|
||||
video.OwnerId = owner.Id;
|
||||
|
||||
SetExtraTypeFromFilename(video);
|
||||
|
||||
return video;
|
||||
|
||||
// Sort them so that the list can be easily compared for changes
|
||||
}).OrderBy(i => i.Path);
|
||||
video.ExtraType = currentExtra.ExtraType;
|
||||
video.ParentId = Guid.Empty;
|
||||
video.OwnerId = owner.Id;
|
||||
yield return video;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
|
||||
|
@ -2817,15 +2784,6 @@ namespace Emby.Server.Implementations.Library
|
|||
return path;
|
||||
}
|
||||
|
||||
private void SetExtraTypeFromFilename(Video item)
|
||||
{
|
||||
var resolver = new ExtraResolver(_namingOptions);
|
||||
|
||||
var result = resolver.GetExtraInfo(item.Path);
|
||||
|
||||
item.ExtraType = result.ExtraType;
|
||||
}
|
||||
|
||||
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
|
||||
{
|
||||
return _itemRepository.GetPeople(query);
|
||||
|
|
|
@ -49,120 +49,71 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||
protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
|
||||
where TVideoType : Video, new()
|
||||
{
|
||||
var namingOptions = NamingOptions;
|
||||
VideoFileInfo videoInfo = null;
|
||||
VideoType? videoType = null;
|
||||
|
||||
// If the path is a file check for a matching extensions
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
TVideoType video = null;
|
||||
VideoFileInfo videoInfo = null;
|
||||
|
||||
// Loop through each child file/folder and see if we find a video
|
||||
foreach (var child in args.FileSystemChildren)
|
||||
{
|
||||
var filename = child.Name;
|
||||
|
||||
if (child.IsDirectory)
|
||||
{
|
||||
if (IsDvdDirectory(child.FullName, filename, args.DirectoryService))
|
||||
{
|
||||
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
|
||||
|
||||
if (videoInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
video = new TVideoType
|
||||
{
|
||||
Path = args.Path,
|
||||
VideoType = VideoType.Dvd,
|
||||
ProductionYear = videoInfo.Year
|
||||
};
|
||||
break;
|
||||
videoType = VideoType.Dvd;
|
||||
}
|
||||
|
||||
if (IsBluRayDirectory(filename))
|
||||
else if (IsBluRayDirectory(filename))
|
||||
{
|
||||
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
|
||||
|
||||
if (videoInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
video = new TVideoType
|
||||
{
|
||||
Path = args.Path,
|
||||
VideoType = VideoType.BluRay,
|
||||
ProductionYear = videoInfo.Year
|
||||
};
|
||||
break;
|
||||
videoType = VideoType.BluRay;
|
||||
}
|
||||
}
|
||||
else if (IsDvdFile(filename))
|
||||
{
|
||||
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
|
||||
|
||||
if (videoInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
video = new TVideoType
|
||||
{
|
||||
Path = args.Path,
|
||||
VideoType = VideoType.Dvd,
|
||||
ProductionYear = videoInfo.Year
|
||||
};
|
||||
break;
|
||||
videoType = VideoType.Dvd;
|
||||
}
|
||||
|
||||
if (videoType == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
videoInfo = VideoResolver.ResolveDirectory(args.Path, NamingOptions, parseName);
|
||||
break;
|
||||
}
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
video.Name = parseName ?
|
||||
videoInfo.Name :
|
||||
Path.GetFileName(args.Path);
|
||||
|
||||
Set3DFormat(video, videoInfo);
|
||||
}
|
||||
|
||||
return video;
|
||||
}
|
||||
else
|
||||
{
|
||||
var videoInfo = VideoResolver.Resolve(args.Path, false, namingOptions, false);
|
||||
|
||||
if (videoInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (VideoResolver.IsVideoFile(args.Path, NamingOptions) || videoInfo.IsStub)
|
||||
{
|
||||
var path = args.Path;
|
||||
|
||||
var video = new TVideoType
|
||||
{
|
||||
Path = path,
|
||||
IsInMixedFolder = true,
|
||||
ProductionYear = videoInfo.Year
|
||||
};
|
||||
|
||||
SetVideoType(video, videoInfo);
|
||||
|
||||
video.Name = parseName ?
|
||||
videoInfo.Name :
|
||||
Path.GetFileNameWithoutExtension(args.Path);
|
||||
|
||||
Set3DFormat(video, videoInfo);
|
||||
|
||||
return video;
|
||||
}
|
||||
videoInfo = VideoResolver.Resolve(args.Path, false, NamingOptions, parseName);
|
||||
}
|
||||
|
||||
return null;
|
||||
if (videoInfo == null || (!videoInfo.IsStub && !VideoResolver.IsVideoFile(args.Path, NamingOptions)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var video = new TVideoType
|
||||
{
|
||||
Name = videoInfo.Name,
|
||||
Path = args.Path,
|
||||
ProductionYear = videoInfo.Year,
|
||||
ExtraType = videoInfo.ExtraType
|
||||
};
|
||||
|
||||
if (videoType.HasValue)
|
||||
{
|
||||
video.VideoType = videoType.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetVideoType(video, videoInfo);
|
||||
}
|
||||
|
||||
Set3DFormat(video, videoInfo);
|
||||
|
||||
return video;
|
||||
}
|
||||
|
||||
protected void SetVideoType(Video video, VideoFileInfo videoInfo)
|
||||
|
@ -207,8 +158,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||
{
|
||||
// use disc-utils, both DVDs and BDs use UDF filesystem
|
||||
using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
|
||||
using (UdfReader udfReader = new UdfReader(videoFileStream))
|
||||
{
|
||||
UdfReader udfReader = new UdfReader(videoFileStream);
|
||||
if (udfReader.DirectoryExists("VIDEO_TS"))
|
||||
{
|
||||
video.IsoType = IsoType.Dvd;
|
||||
|
|
|
@ -26,7 +26,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
|
||||
{
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly StackResolver _stackResolver;
|
||||
|
||||
private string[] _validCollectionTypes = new[]
|
||||
{
|
||||
|
@ -46,7 +45,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
: base(namingOptions)
|
||||
{
|
||||
_imageProcessor = imageProcessor;
|
||||
_stackResolver = new StackResolver(NamingOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -62,7 +60,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
string collectionType,
|
||||
IDirectoryService directoryService)
|
||||
{
|
||||
var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
|
||||
var result = ResolveMultipleInternal(parent, files, collectionType);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
|
@ -92,16 +90,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
return null;
|
||||
}
|
||||
|
||||
Video movie = null;
|
||||
var files = args.GetActualFileSystemChildren().ToList();
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
|
||||
movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
|
||||
}
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
|
||||
movie = FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(collectionType))
|
||||
|
@ -118,17 +117,16 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
return null;
|
||||
}
|
||||
|
||||
{
|
||||
return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
|
||||
}
|
||||
movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
|
||||
}
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
|
||||
movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
|
||||
}
|
||||
|
||||
return null;
|
||||
// ignore extras
|
||||
return movie?.ExtraType == null ? movie : null;
|
||||
}
|
||||
|
||||
// Handle owned items
|
||||
|
@ -169,6 +167,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
item = ResolveVideo<Video>(args, false);
|
||||
}
|
||||
|
||||
// Ignore extras
|
||||
if (item?.ExtraType != null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
item.IsInMixedFolder = true;
|
||||
|
@ -180,8 +184,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
private MultiItemResolverResult ResolveMultipleInternal(
|
||||
Folder parent,
|
||||
List<FileSystemMetadata> files,
|
||||
string collectionType,
|
||||
IDirectoryService directoryService)
|
||||
string collectionType)
|
||||
{
|
||||
if (IsInvalid(parent, collectionType))
|
||||
{
|
||||
|
@ -190,13 +193,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
|
||||
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ResolveVideos<MusicVideo>(parent, files, directoryService, true, collectionType, false);
|
||||
return ResolveVideos<MusicVideo>(parent, files, true, collectionType, false);
|
||||
}
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ResolveVideos<Video>(parent, files, directoryService, false, collectionType, false);
|
||||
return ResolveVideos<Video>(parent, files, false, collectionType, false);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(collectionType))
|
||||
|
@ -204,7 +207,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
// Owned items should just use the plain video type
|
||||
if (parent == null)
|
||||
{
|
||||
return ResolveVideos<Video>(parent, files, directoryService, false, collectionType, false);
|
||||
return ResolveVideos<Video>(parent, files, false, collectionType, false);
|
||||
}
|
||||
|
||||
if (parent is Series || parent.GetParents().OfType<Series>().Any())
|
||||
|
@ -212,12 +215,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
return null;
|
||||
}
|
||||
|
||||
return ResolveVideos<Movie>(parent, files, directoryService, false, collectionType, true);
|
||||
return ResolveVideos<Movie>(parent, files, false, collectionType, true);
|
||||
}
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ResolveVideos<Movie>(parent, files, directoryService, true, collectionType, true);
|
||||
return ResolveVideos<Movie>(parent, files, true, collectionType, true);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -226,21 +229,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
private MultiItemResolverResult ResolveVideos<T>(
|
||||
Folder parent,
|
||||
IEnumerable<FileSystemMetadata> fileSystemEntries,
|
||||
IDirectoryService directoryService,
|
||||
bool suppportMultiEditions,
|
||||
bool supportMultiEditions,
|
||||
string collectionType,
|
||||
bool parseName)
|
||||
where T : Video, new()
|
||||
{
|
||||
var files = new List<FileSystemMetadata>();
|
||||
var videos = new List<BaseItem>();
|
||||
var leftOver = new List<FileSystemMetadata>();
|
||||
var hasCollectionType = !string.IsNullOrEmpty(collectionType);
|
||||
|
||||
// Loop through each child file/folder and see if we find a video
|
||||
foreach (var child in fileSystemEntries)
|
||||
{
|
||||
// This is a hack but currently no better way to resolve a sometimes ambiguous situation
|
||||
if (string.IsNullOrEmpty(collectionType))
|
||||
if (!hasCollectionType)
|
||||
{
|
||||
if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
|
||||
|
@ -259,29 +261,35 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
}
|
||||
}
|
||||
|
||||
var resolverResult = VideoListResolver.Resolve(files, NamingOptions, suppportMultiEditions).ToList();
|
||||
var resolverResult = VideoListResolver.Resolve(files, NamingOptions, supportMultiEditions, parseName);
|
||||
|
||||
var result = new MultiItemResolverResult
|
||||
{
|
||||
ExtraFiles = leftOver,
|
||||
Items = videos
|
||||
ExtraFiles = leftOver
|
||||
};
|
||||
|
||||
var isInMixedFolder = resolverResult.Count > 1 || (parent != null && parent.IsTopParent);
|
||||
var isInMixedFolder = resolverResult.Count > 1 || parent?.IsTopParent == true;
|
||||
|
||||
foreach (var video in resolverResult)
|
||||
{
|
||||
var firstVideo = video.Files[0];
|
||||
var path = firstVideo.Path;
|
||||
if (video.ExtraType != null)
|
||||
{
|
||||
// TODO
|
||||
result.ExtraFiles.Add(files.First(f => string.Equals(f.FullName, path, StringComparison.OrdinalIgnoreCase)));
|
||||
continue;
|
||||
}
|
||||
|
||||
var additionalParts = video.Files.Count > 1 ? video.Files.Skip(1).Select(i => i.Path).ToArray() : Array.Empty<string>();
|
||||
|
||||
var videoItem = new T
|
||||
{
|
||||
Path = video.Files[0].Path,
|
||||
Path = path,
|
||||
IsInMixedFolder = isInMixedFolder,
|
||||
ProductionYear = video.Year,
|
||||
Name = parseName ?
|
||||
video.Name :
|
||||
Path.GetFileNameWithoutExtension(video.Files[0].Path),
|
||||
AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToArray(),
|
||||
Name = parseName ? video.Name : firstVideo.Name,
|
||||
AdditionalParts = additionalParts,
|
||||
LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToArray()
|
||||
};
|
||||
|
||||
|
@ -299,21 +307,34 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
private static bool IsIgnored(string filename)
|
||||
{
|
||||
// Ignore samples
|
||||
Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
|
||||
Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
return m.Success;
|
||||
}
|
||||
|
||||
private bool ContainsFile(List<VideoInfo> result, FileSystemMetadata file)
|
||||
private static bool ContainsFile(IReadOnlyList<VideoInfo> result, FileSystemMetadata file)
|
||||
{
|
||||
return result.Any(i => ContainsFile(i, file));
|
||||
}
|
||||
for (var i = 0; i < result.Count; i++)
|
||||
{
|
||||
var current = result[i];
|
||||
for (var j = 0; j < current.Files.Count; j++)
|
||||
{
|
||||
if (ContainsFile(current.Files[j], file))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ContainsFile(VideoInfo result, FileSystemMetadata file)
|
||||
{
|
||||
return result.Files.Any(i => ContainsFile(i, file)) ||
|
||||
result.AlternateVersions.Any(i => ContainsFile(i, file)) ||
|
||||
result.Extras.Any(i => ContainsFile(i, file));
|
||||
for (var j = 0; j < current.AlternateVersions.Count; j++)
|
||||
{
|
||||
if (ContainsFile(current.AlternateVersions[j], file))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ContainsFile(VideoFileInfo result, FileSystemMetadata file)
|
||||
|
@ -431,7 +452,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
// TODO: Allow GetMultiDiscMovie in here
|
||||
const bool SupportsMultiVersion = true;
|
||||
|
||||
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, SupportsMultiVersion, collectionType, parseName) ??
|
||||
var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ??
|
||||
new MultiItemResolverResult();
|
||||
|
||||
if (result.Items.Count == 1)
|
||||
|
@ -510,7 +531,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
return null;
|
||||
}
|
||||
|
||||
var result = _stackResolver.ResolveDirectories(folderPaths).ToList();
|
||||
var result = StackResolver.ResolveDirectories(folderPaths, NamingOptions).ToList();
|
||||
|
||||
if (result.Count != 1)
|
||||
{
|
||||
|
|
|
@ -45,34 +45,36 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||
|
||||
// If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something
|
||||
// Also handle flat tv folders
|
||||
if ((season != null ||
|
||||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
|
||||
args.HasParent<Series>())
|
||||
&& (parent is Series || !BaseItem.AllExtrasTypesFolderNames.ContainsKey(parent.Name)))
|
||||
if (season != null ||
|
||||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
|
||||
args.HasParent<Series>())
|
||||
{
|
||||
var episode = ResolveVideo<Episode>(args, false);
|
||||
|
||||
if (episode != null)
|
||||
// Ignore extras
|
||||
if (episode == null || episode.ExtraType != null)
|
||||
{
|
||||
var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (series != null)
|
||||
{
|
||||
episode.SeriesId = series.Id;
|
||||
episode.SeriesName = series.Name;
|
||||
}
|
||||
var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault();
|
||||
|
||||
if (season != null)
|
||||
{
|
||||
episode.SeasonId = season.Id;
|
||||
episode.SeasonName = season.Name;
|
||||
}
|
||||
if (series != null)
|
||||
{
|
||||
episode.SeriesId = series.Id;
|
||||
episode.SeriesName = series.Name;
|
||||
}
|
||||
|
||||
// Assume season 1 if there's no season folder and a season number could not be determined
|
||||
if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue))
|
||||
{
|
||||
episode.ParentIndexNumber = 1;
|
||||
}
|
||||
if (season != null)
|
||||
{
|
||||
episode.SeasonId = season.Id;
|
||||
episode.SeasonName = season.Name;
|
||||
}
|
||||
|
||||
// Assume season 1 if there's no season folder and a season number could not be determined
|
||||
if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue))
|
||||
{
|
||||
episode.ParentIndexNumber = 1;
|
||||
}
|
||||
|
||||
return episode;
|
||||
|
|
|
@ -213,7 +213,7 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
if (item is IHasTrailers hasTrailers)
|
||||
{
|
||||
var trailers = hasTrailers.GetTrailers();
|
||||
var trailers = hasTrailers.LocalTrailers;
|
||||
var dtosTrailers = _dtoService.GetBaseItemDtos(trailers, dtoOptions, user, item);
|
||||
var allTrailers = new BaseItemDto[dtosExtras.Length + dtosTrailers.Count];
|
||||
dtosExtras.CopyTo(allTrailers, 0);
|
||||
|
|
|
@ -40,9 +40,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// </summary>
|
||||
public abstract class BaseItem : IHasProviderIds, IHasLookupInfo<ItemLookupInfo>, IEquatable<BaseItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// The trailer folder name.
|
||||
/// </summary>
|
||||
public const string TrailerFileName = "trailer";
|
||||
public const string TrailersFolderName = "trailers";
|
||||
public const string ThemeSongsFolderName = "theme-music";
|
||||
public const string ThemeSongFileName = "theme";
|
||||
|
@ -99,8 +97,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
};
|
||||
|
||||
private string _sortName;
|
||||
private Guid[] _themeSongIds;
|
||||
private Guid[] _themeVideoIds;
|
||||
|
||||
private string _forcedSortName;
|
||||
|
||||
|
@ -121,40 +117,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
ExtraIds = Array.Empty<Guid>();
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public Guid[] ThemeSongIds
|
||||
{
|
||||
get
|
||||
{
|
||||
return _themeSongIds ??= GetExtras()
|
||||
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong)
|
||||
.Select(song => song.Id)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
_themeSongIds = value;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public Guid[] ThemeVideoIds
|
||||
{
|
||||
get
|
||||
{
|
||||
return _themeVideoIds ??= GetExtras()
|
||||
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo)
|
||||
.Select(song => song.Id)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
_themeVideoIds = value;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string PreferredMetadataCountryCode { get; set; }
|
||||
|
||||
|
@ -1379,28 +1341,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
}).OrderBy(i => i.Path).ToArray();
|
||||
}
|
||||
|
||||
protected virtual BaseItem[] LoadExtras(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||
{
|
||||
return fileSystemChildren
|
||||
.Where(child => child.IsDirectory && AllExtrasTypesFolderNames.ContainsKey(child.Name))
|
||||
.SelectMany(folder => LibraryManager
|
||||
.ResolvePaths(FileSystem.GetFiles(folder.FullName), directoryService, null, new LibraryOptions())
|
||||
.OfType<Video>()
|
||||
.Select(video =>
|
||||
{
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
if (LibraryManager.GetItemById(video.Id) is Video dbItem)
|
||||
{
|
||||
video = dbItem;
|
||||
}
|
||||
|
||||
video.ExtraType = AllExtrasTypesFolderNames[folder.Name];
|
||||
return video;
|
||||
})
|
||||
.OrderBy(video => video.Path)) // Sort them so that the list can be easily compared for changes
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public Task RefreshMetadata(CancellationToken cancellationToken)
|
||||
{
|
||||
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken);
|
||||
|
@ -1434,13 +1374,8 @@ namespace MediaBrowser.Controller.Entities
|
|||
GetFileSystemChildren(options.DirectoryService).ToList() :
|
||||
new List<FileSystemMetadata>();
|
||||
|
||||
var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false);
|
||||
requiresSave = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false);
|
||||
await LibraryManager.UpdateImagesAsync(this).ConfigureAwait(false); // ensure all image properties in DB are fresh
|
||||
|
||||
if (ownedItemsChanged)
|
||||
{
|
||||
requiresSave = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -1516,35 +1451,12 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <returns><c>true</c> if any items have changed, else <c>false</c>.</returns>
|
||||
protected virtual async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var themeSongsChanged = false;
|
||||
|
||||
var themeVideosChanged = false;
|
||||
|
||||
var extrasChanged = false;
|
||||
|
||||
var localTrailersChanged = false;
|
||||
|
||||
if (IsFileProtocol && SupportsOwnedItems)
|
||||
if (!IsFileProtocol || !SupportsOwnedItems || IsInMixedFolder || this is ICollectionFolder)
|
||||
{
|
||||
if (SupportsThemeMedia)
|
||||
{
|
||||
if (!IsInMixedFolder)
|
||||
{
|
||||
themeSongsChanged = await RefreshThemeSongs(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
themeVideosChanged = await RefreshThemeVideos(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
extrasChanged = await RefreshExtras(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (this is IHasTrailers hasTrailers)
|
||||
{
|
||||
localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return themeSongsChanged || themeVideosChanged || extrasChanged || localTrailersChanged;
|
||||
return await RefreshExtras(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected virtual FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
|
||||
|
@ -1554,98 +1466,24 @@ namespace MediaBrowser.Controller.Entities
|
|||
return directoryService.GetFileSystemEntries(path);
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshLocalTrailers(IHasTrailers item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newItems = LibraryManager.FindTrailers(this, fileSystemChildren, options.DirectoryService);
|
||||
|
||||
var newItemIds = newItems.Select(i => i.Id);
|
||||
|
||||
var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds);
|
||||
var ownerId = item.Id;
|
||||
|
||||
var tasks = newItems.Select(i =>
|
||||
{
|
||||
var subOptions = new MetadataRefreshOptions(options);
|
||||
|
||||
if (i.ExtraType != Model.Entities.ExtraType.Trailer ||
|
||||
i.OwnerId != ownerId ||
|
||||
!i.ParentId.Equals(Guid.Empty))
|
||||
{
|
||||
i.ExtraType = Model.Entities.ExtraType.Trailer;
|
||||
i.OwnerId = ownerId;
|
||||
i.ParentId = Guid.Empty;
|
||||
subOptions.ForceSave = true;
|
||||
}
|
||||
|
||||
return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken);
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
item.LocalTrailerIds = newItemIds.ToArray();
|
||||
|
||||
return itemsChanged;
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var extras = LoadExtras(fileSystemChildren, options.DirectoryService);
|
||||
var themeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService);
|
||||
var themeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService);
|
||||
var newExtras = new BaseItem[extras.Length + themeVideos.Length + themeSongs.Length];
|
||||
extras.CopyTo(newExtras, 0);
|
||||
themeVideos.CopyTo(newExtras, extras.Length);
|
||||
themeSongs.CopyTo(newExtras, extras.Length + themeVideos.Length);
|
||||
|
||||
var newExtraIds = newExtras.Select(i => i.Id).ToArray();
|
||||
|
||||
var extras = LibraryManager.FindExtras(item, fileSystemChildren).ToArray();
|
||||
var newExtraIds = extras.Select(i => i.Id).ToArray();
|
||||
var extrasChanged = !item.ExtraIds.SequenceEqual(newExtraIds);
|
||||
|
||||
if (extrasChanged)
|
||||
if (!extrasChanged)
|
||||
{
|
||||
var ownerId = item.Id;
|
||||
|
||||
var tasks = newExtras.Select(i =>
|
||||
{
|
||||
var subOptions = new MetadataRefreshOptions(options);
|
||||
if (i.OwnerId != ownerId || i.ParentId != Guid.Empty)
|
||||
{
|
||||
i.OwnerId = ownerId;
|
||||
i.ParentId = Guid.Empty;
|
||||
subOptions.ForceSave = true;
|
||||
}
|
||||
|
||||
return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken);
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
item.ExtraIds = newExtraIds;
|
||||
return false;
|
||||
}
|
||||
|
||||
return extrasChanged;
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshThemeVideos(BaseItem item, MetadataRefreshOptions options, IEnumerable<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newThemeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService);
|
||||
|
||||
var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToArray();
|
||||
|
||||
var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds);
|
||||
|
||||
var ownerId = item.Id;
|
||||
|
||||
var tasks = newThemeVideos.Select(i =>
|
||||
var tasks = extras.Select(i =>
|
||||
{
|
||||
var subOptions = new MetadataRefreshOptions(options);
|
||||
|
||||
if (!i.ExtraType.HasValue ||
|
||||
i.ExtraType.Value != Model.Entities.ExtraType.ThemeVideo ||
|
||||
i.OwnerId != ownerId ||
|
||||
!i.ParentId.Equals(Guid.Empty))
|
||||
if (i.OwnerId != ownerId || i.ParentId != Guid.Empty)
|
||||
{
|
||||
i.ExtraType = Model.Entities.ExtraType.ThemeVideo;
|
||||
i.OwnerId = ownerId;
|
||||
i.ParentId = Guid.Empty;
|
||||
subOptions.ForceSave = true;
|
||||
|
@ -1656,48 +1494,9 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
// They are expected to be sorted by SortName
|
||||
item.ThemeVideoIds = newThemeVideos.OrderBy(i => i.SortName).Select(i => i.Id).ToArray();
|
||||
item.ExtraIds = newExtraIds;
|
||||
|
||||
return themeVideosChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the theme songs.
|
||||
/// </summary>
|
||||
private async Task<bool> RefreshThemeSongs(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newThemeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService);
|
||||
var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToArray();
|
||||
|
||||
var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds);
|
||||
|
||||
var ownerId = item.Id;
|
||||
|
||||
var tasks = newThemeSongs.Select(i =>
|
||||
{
|
||||
var subOptions = new MetadataRefreshOptions(options);
|
||||
|
||||
if (!i.ExtraType.HasValue ||
|
||||
i.ExtraType.Value != Model.Entities.ExtraType.ThemeSong ||
|
||||
i.OwnerId != ownerId ||
|
||||
!i.ParentId.Equals(Guid.Empty))
|
||||
{
|
||||
i.ExtraType = Model.Entities.ExtraType.ThemeSong;
|
||||
i.OwnerId = ownerId;
|
||||
i.ParentId = Guid.Empty;
|
||||
subOptions.ForceSave = true;
|
||||
}
|
||||
|
||||
return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken);
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
// They are expected to be sorted by SortName
|
||||
item.ThemeSongIds = newThemeSongs.OrderBy(i => i.SortName).Select(i => i.Id).ToArray();
|
||||
|
||||
return themeSongsChanged;
|
||||
return true;
|
||||
}
|
||||
|
||||
public string GetPresentationUniqueKey()
|
||||
|
@ -2891,14 +2690,14 @@ namespace MediaBrowser.Controller.Entities
|
|||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public IEnumerable<BaseItem> GetThemeSongs()
|
||||
public IReadOnlyList<BaseItem> GetThemeSongs()
|
||||
{
|
||||
return ThemeSongIds.Select(LibraryManager.GetItemById);
|
||||
return GetExtras().Where(e => e.ExtraType == Model.Entities.ExtraType.ThemeSong).ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<BaseItem> GetThemeVideos()
|
||||
public IReadOnlyList<BaseItem> GetThemeVideos()
|
||||
{
|
||||
return ThemeVideoIds.Select(LibraryManager.GetItemById);
|
||||
return GetExtras().Where(e => e.ExtraType == Model.Entities.ExtraType.ThemeVideo).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
|
@ -17,18 +16,10 @@ namespace MediaBrowser.Controller.Entities
|
|||
IReadOnlyList<MediaUrl> RemoteTrailers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local trailer ids.
|
||||
/// Gets the local trailers.
|
||||
/// </summary>
|
||||
/// <value>The local trailer ids.</value>
|
||||
IReadOnlyList<Guid> LocalTrailerIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the remote trailer ids.
|
||||
/// </summary>
|
||||
/// <value>The remote trailer ids.</value>
|
||||
IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
|
||||
|
||||
Guid Id { get; set; }
|
||||
/// <value>The local trailers.</value>
|
||||
IReadOnlyList<BaseItem> LocalTrailers { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -42,57 +33,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <param name="item">Media item.</param>
|
||||
/// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
|
||||
public static int GetTrailerCount(this IHasTrailers item)
|
||||
=> item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the trailer ids.
|
||||
/// </summary>
|
||||
/// <param name="item">Media item.</param>
|
||||
/// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
|
||||
public static IReadOnlyList<Guid> GetTrailerIds(this IHasTrailers item)
|
||||
{
|
||||
var localIds = item.LocalTrailerIds;
|
||||
var remoteIds = item.RemoteTrailerIds;
|
||||
|
||||
var all = new Guid[localIds.Count + remoteIds.Count];
|
||||
var index = 0;
|
||||
foreach (var id in localIds)
|
||||
{
|
||||
all[index++] = id;
|
||||
}
|
||||
|
||||
foreach (var id in remoteIds)
|
||||
{
|
||||
all[index++] = id;
|
||||
}
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the trailers.
|
||||
/// </summary>
|
||||
/// <param name="item">Media item.</param>
|
||||
/// <returns><see cref="IReadOnlyList{BaseItem}" />.</returns>
|
||||
public static IReadOnlyList<BaseItem> GetTrailers(this IHasTrailers item)
|
||||
{
|
||||
var localIds = item.LocalTrailerIds;
|
||||
var remoteIds = item.RemoteTrailerIds;
|
||||
var libraryManager = BaseItem.LibraryManager;
|
||||
|
||||
var all = new BaseItem[localIds.Count + remoteIds.Count];
|
||||
var index = 0;
|
||||
foreach (var id in localIds)
|
||||
{
|
||||
all[index++] = libraryManager.GetItemById(id);
|
||||
}
|
||||
|
||||
foreach (var id in remoteIds)
|
||||
{
|
||||
all[index++] = libraryManager.GetItemById(id);
|
||||
}
|
||||
|
||||
return all;
|
||||
}
|
||||
=> item.LocalTrailers.Count + item.RemoteTrailers.Count;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ using System.Text.Json.Serialization;
|
|||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities.Movies
|
||||
|
@ -21,10 +20,6 @@ namespace MediaBrowser.Controller.Entities.Movies
|
|||
{
|
||||
public BoxSet()
|
||||
{
|
||||
RemoteTrailers = Array.Empty<MediaUrl>();
|
||||
LocalTrailerIds = Array.Empty<Guid>();
|
||||
RemoteTrailerIds = Array.Empty<Guid>();
|
||||
|
||||
DisplayOrder = ItemSortBy.PremiereDate;
|
||||
}
|
||||
|
||||
|
@ -38,10 +33,9 @@ namespace MediaBrowser.Controller.Entities.Movies
|
|||
public override bool SupportsPeople => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Guid> LocalTrailerIds { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
|
||||
public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
|
||||
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
|
||||
.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display order.
|
||||
|
|
|
@ -7,12 +7,9 @@ using System.Collections.Generic;
|
|||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities.Movies
|
||||
|
@ -22,22 +19,29 @@ namespace MediaBrowser.Controller.Entities.Movies
|
|||
/// </summary>
|
||||
public class Movie : Video, IHasSpecialFeatures, IHasTrailers, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping
|
||||
{
|
||||
public Movie()
|
||||
private IReadOnlyList<Guid> _specialFeatureIds;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Guid> SpecialFeatureIds
|
||||
{
|
||||
SpecialFeatureIds = Array.Empty<Guid>();
|
||||
RemoteTrailers = Array.Empty<MediaUrl>();
|
||||
LocalTrailerIds = Array.Empty<Guid>();
|
||||
RemoteTrailerIds = Array.Empty<Guid>();
|
||||
get
|
||||
{
|
||||
return _specialFeatureIds ??= GetExtras()
|
||||
.Where(extra => extra.ExtraType != Model.Entities.ExtraType.Trailer)
|
||||
.Select(song => song.Id)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_specialFeatureIds = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Guid> SpecialFeatureIds { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Guid> LocalTrailerIds { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
|
||||
public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
|
||||
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
|
||||
.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the TMDB collection.
|
||||
|
@ -66,54 +70,6 @@ namespace MediaBrowser.Controller.Entities.Movies
|
|||
return 2.0 / 3;
|
||||
}
|
||||
|
||||
protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Must have a parent to have special features
|
||||
// In other words, it must be part of the Parent/Child tree
|
||||
if (IsFileProtocol && SupportsOwnedItems && !IsInMixedFolder)
|
||||
{
|
||||
var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (specialFeaturesChanged)
|
||||
{
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newItems = LibraryManager.FindExtras(this, fileSystemChildren, options.DirectoryService).ToList();
|
||||
var newItemIds = newItems.Select(i => i.Id).ToArray();
|
||||
|
||||
var itemsChanged = !SpecialFeatureIds.SequenceEqual(newItemIds);
|
||||
|
||||
var ownerId = Id;
|
||||
|
||||
var tasks = newItems.Select(i =>
|
||||
{
|
||||
var subOptions = new MetadataRefreshOptions(options);
|
||||
|
||||
if (i.OwnerId != ownerId)
|
||||
{
|
||||
i.OwnerId = ownerId;
|
||||
subOptions.ForceSave = true;
|
||||
}
|
||||
|
||||
return RefreshMetadataForOwnedItem(i, false, subOptions, cancellationToken);
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
SpecialFeatureIds = newItemIds;
|
||||
|
||||
return itemsChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override UnratedItem GetBlockUnratedType()
|
||||
{
|
||||
|
|
|
@ -20,18 +20,10 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
/// </summary>
|
||||
public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
|
||||
{
|
||||
public Episode()
|
||||
{
|
||||
RemoteTrailers = Array.Empty<MediaUrl>();
|
||||
LocalTrailerIds = Array.Empty<Guid>();
|
||||
RemoteTrailerIds = Array.Empty<Guid>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Guid> LocalTrailerIds { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
|
||||
public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
|
||||
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
|
||||
.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the season in which it aired.
|
||||
|
|
|
@ -27,9 +27,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
{
|
||||
public Series()
|
||||
{
|
||||
RemoteTrailers = Array.Empty<MediaUrl>();
|
||||
LocalTrailerIds = Array.Empty<Guid>();
|
||||
RemoteTrailerIds = Array.Empty<Guid>();
|
||||
AirDays = Array.Empty<DayOfWeek>();
|
||||
}
|
||||
|
||||
|
@ -53,10 +50,9 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
public override bool SupportsPeople => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Guid> LocalTrailerIds { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
|
||||
public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
|
||||
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
|
||||
.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display order.
|
||||
|
|
|
@ -745,10 +745,9 @@ namespace MediaBrowser.Controller.Entities
|
|||
var val = query.HasTrailer.Value;
|
||||
var trailerCount = 0;
|
||||
|
||||
var hasTrailers = item as IHasTrailers;
|
||||
if (hasTrailers != null)
|
||||
if (item is IHasTrailers hasTrailers)
|
||||
{
|
||||
trailerCount = hasTrailers.GetTrailerIds().Count;
|
||||
trailerCount = hasTrailers.GetTrailerCount();
|
||||
}
|
||||
|
||||
var ok = val ? trailerCount > 0 : trailerCount == 0;
|
||||
|
@ -763,7 +762,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
{
|
||||
var filterValue = query.HasThemeSong.Value;
|
||||
|
||||
var themeCount = item.ThemeSongIds.Length;
|
||||
var themeCount = item.GetThemeSongs().Count;
|
||||
var ok = filterValue ? themeCount > 0 : themeCount == 0;
|
||||
|
||||
if (!ok)
|
||||
|
@ -776,7 +775,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
{
|
||||
var filterValue = query.HasThemeVideo.Value;
|
||||
|
||||
var themeCount = item.ThemeVideoIds.Length;
|
||||
var themeCount = item.GetThemeVideos().Count;
|
||||
var ok = filterValue ? themeCount > 0 : themeCount == 0;
|
||||
|
||||
if (!ok)
|
||||
|
|
|
@ -6,7 +6,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Naming.Common;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
|
@ -426,29 +425,15 @@ namespace MediaBrowser.Controller.Library
|
|||
/// <returns>Guid.</returns>
|
||||
Guid GetNewItemId(string key, Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the trailers.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner.</param>
|
||||
/// <param name="fileSystemChildren">The file system children.</param>
|
||||
/// <param name="directoryService">The directory service.</param>
|
||||
/// <returns>IEnumerable<Trailer>.</returns>
|
||||
IEnumerable<Video> FindTrailers(
|
||||
BaseItem owner,
|
||||
List<FileSystemMetadata> fileSystemChildren,
|
||||
IDirectoryService directoryService);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the extras.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner.</param>
|
||||
/// <param name="fileSystemChildren">The file system children.</param>
|
||||
/// <param name="directoryService">The directory service.</param>
|
||||
/// <returns>IEnumerable<Video>.</returns>
|
||||
IEnumerable<Video> FindExtras(
|
||||
BaseItem owner,
|
||||
List<FileSystemMetadata> fileSystemChildren,
|
||||
IDirectoryService directoryService);
|
||||
List<FileSystemMetadata> fileSystemChildren);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection folders.
|
||||
|
|
|
@ -106,7 +106,7 @@ namespace MediaBrowser.LocalMetadata.Images
|
|||
{
|
||||
if (!item.IsFileProtocol)
|
||||
{
|
||||
return Enumerable.Empty<FileSystemMetadata>();
|
||||
yield break;
|
||||
}
|
||||
|
||||
var path = item.ContainingFolderPath;
|
||||
|
@ -114,20 +114,21 @@ namespace MediaBrowser.LocalMetadata.Images
|
|||
// Exit if the cache dir does not exist, alternative solution is to create it, but that's a lot of empty dirs...
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
return Enumerable.Empty<FileSystemMetadata>();
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (includeDirectories)
|
||||
var files = directoryService.GetFileSystemEntries(path).OrderBy(i => Array.IndexOf(BaseItem.SupportedImageExtensions, i.Extension ?? string.Empty));
|
||||
var count = BaseItem.SupportedImageExtensions.Length;
|
||||
foreach (var file in files)
|
||||
{
|
||||
return directoryService.GetFileSystemEntries(path)
|
||||
.Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase) || i.IsDirectory)
|
||||
|
||||
.OrderBy(i => Array.IndexOf(BaseItem.SupportedImageExtensions, i.Extension ?? string.Empty));
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if ((includeDirectories && file.IsDirectory) || string.Equals(BaseItem.SupportedImageExtensions[i], file.Extension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
yield return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return directoryService.GetFiles(path)
|
||||
.Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase))
|
||||
.OrderBy(i => Array.IndexOf(BaseItem.SupportedImageExtensions, i.Extension ?? string.Empty));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -81,9 +81,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
|
||||
private void Test(string input, ExtraType? expectedType)
|
||||
{
|
||||
var parser = GetExtraTypeParser(_videoOptions);
|
||||
|
||||
var extraType = parser.GetExtraInfo(input).ExtraType;
|
||||
var extraType = ExtraResolver.GetExtraInfo(input, _videoOptions).ExtraType;
|
||||
|
||||
Assert.Equal(expectedType, extraType);
|
||||
}
|
||||
|
@ -93,14 +91,9 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
{
|
||||
var rule = new ExtraRule(ExtraType.Unknown, ExtraRuleType.Regex, @"([eE]x(tra)?\.\w+)", MediaType.Video);
|
||||
var options = new NamingOptions { VideoExtraRules = new[] { rule } };
|
||||
var res = GetExtraTypeParser(options).GetExtraInfo("extra.mp4");
|
||||
var res = ExtraResolver.GetExtraInfo("extra.mp4", options);
|
||||
|
||||
Assert.Equal(rule, res.Rule);
|
||||
}
|
||||
|
||||
private ExtraResolver GetExtraTypeParser(NamingOptions videoOptions)
|
||||
{
|
||||
return new ExtraResolver(videoOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Single(result[0].Extras);
|
||||
Assert.Single(result.Where(v => v.ExtraType == null));
|
||||
Assert.Single(result.Where(v => v.ExtraType != null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -53,8 +53,8 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Single(result[0].Extras);
|
||||
Assert.Single(result.Where(v => v.ExtraType == null));
|
||||
Assert.Single(result.Where(v => v.ExtraType != null));
|
||||
Assert.Equal(2, result[0].AlternateVersions.Count);
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,6 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
_namingOptions).ToList();
|
||||
|
||||
Assert.Equal(7, result.Count);
|
||||
Assert.Empty(result[0].Extras);
|
||||
Assert.Empty(result[0].AlternateVersions);
|
||||
}
|
||||
|
||||
|
@ -130,7 +129,6 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Empty(result[0].Extras);
|
||||
Assert.Equal(7, result[0].AlternateVersions.Count);
|
||||
}
|
||||
|
||||
|
@ -159,7 +157,6 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
_namingOptions).ToList();
|
||||
|
||||
Assert.Equal(9, result.Count);
|
||||
Assert.Empty(result[0].Extras);
|
||||
Assert.Empty(result[0].AlternateVersions);
|
||||
}
|
||||
|
||||
|
@ -184,7 +181,6 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
_namingOptions).ToList();
|
||||
|
||||
Assert.Equal(5, result.Count);
|
||||
Assert.Empty(result[0].Extras);
|
||||
Assert.Empty(result[0].AlternateVersions);
|
||||
}
|
||||
|
||||
|
@ -211,7 +207,6 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
_namingOptions).ToList();
|
||||
|
||||
Assert.Equal(5, result.Count);
|
||||
Assert.Empty(result[0].Extras);
|
||||
Assert.Empty(result[0].AlternateVersions);
|
||||
}
|
||||
|
||||
|
@ -239,7 +234,6 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Empty(result[0].Extras);
|
||||
Assert.Equal(7, result[0].AlternateVersions.Count);
|
||||
Assert.False(result[0].AlternateVersions[2].Is3D);
|
||||
Assert.True(result[0].AlternateVersions[3].Is3D);
|
||||
|
@ -270,7 +264,6 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Empty(result[0].Extras);
|
||||
Assert.Equal(7, result[0].AlternateVersions.Count);
|
||||
Assert.False(result[0].AlternateVersions[3].Is3D);
|
||||
Assert.True(result[0].AlternateVersions[4].Is3D);
|
||||
|
@ -320,7 +313,6 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
_namingOptions).ToList();
|
||||
|
||||
Assert.Equal(7, result.Count);
|
||||
Assert.Empty(result[0].Extras);
|
||||
Assert.Empty(result[0].AlternateVersions);
|
||||
}
|
||||
|
||||
|
@ -347,7 +339,6 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
_namingOptions).ToList();
|
||||
|
||||
Assert.Equal(5, result.Count);
|
||||
Assert.Empty(result[0].Extras);
|
||||
Assert.Empty(result[0].AlternateVersions);
|
||||
}
|
||||
|
||||
|
@ -369,7 +360,6 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Empty(result[0].Extras);
|
||||
Assert.Single(result[0].AlternateVersions);
|
||||
}
|
||||
|
||||
|
@ -391,7 +381,6 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Empty(result[0].Extras);
|
||||
Assert.Single(result[0].AlternateVersions);
|
||||
}
|
||||
|
||||
|
@ -413,7 +402,6 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Empty(result[0].Extras);
|
||||
Assert.Single(result[0].AlternateVersions);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,9 +22,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"Bad Boys (2006)-trailer.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
TestStackInfo(result[0], "Bad Boys (2006)", 4);
|
||||
|
@ -39,9 +37,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"Bad Boys (2007).mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
@ -55,9 +51,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"Bad Boys 2007.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
@ -71,9 +65,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"300 (2007).mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
@ -87,9 +79,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"300 2007.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
@ -103,9 +93,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"Star Trek 2- The wrath of khan.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
|
@ -119,9 +107,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"Red Riding in the Year of Our Lord 1974 (2009).mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
@ -135,9 +121,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"d:/movies/300 2006 part2.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
TestStackInfo(result[0], "300 2006", 2);
|
||||
|
@ -155,9 +139,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"Bad Boys (2006)-trailer.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
TestStackInfo(result[0], "Bad Boys (2006).stv.unrated.multi.1080p.bluray.x264-rough", 4);
|
||||
|
@ -175,9 +157,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"Bad Boys (2006)-trailer.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
@ -194,9 +174,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"300 (2006)-trailer.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
TestStackInfo(result[0], "300 (2006)", 4);
|
||||
|
@ -214,9 +192,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"Bad Boys (2006)-trailer.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
TestStackInfo(result[0], "Bad Boys (2006)", 3);
|
||||
|
@ -238,9 +214,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"300 (2006)-trailer.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
TestStackInfo(result[1], "Bad Boys (2006)", 4);
|
||||
|
@ -256,9 +230,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"blah blah - cd 2"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveDirectories(files).ToList();
|
||||
var result = StackResolver.ResolveDirectories(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
TestStackInfo(result[0], "blah blah", 2);
|
||||
|
@ -275,9 +247,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"300-trailer.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
|
||||
|
@ -297,9 +267,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"Avengers part3.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
|
||||
|
@ -328,9 +296,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"300-trailer.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Equal(3, result.Count);
|
||||
|
||||
|
@ -354,9 +320,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"300 (2006)-trailer.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
|
||||
|
@ -375,9 +339,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
new FileSystemMetadata { FullName = "300 (2006) part1", IsDirectory = true }
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.Resolve(files).ToList();
|
||||
var result = StackResolver.Resolve(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
TestStackInfo(result[0], "300 (2006)", 3);
|
||||
|
@ -397,9 +359,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"Harry Potter and the Deathly Hallows 4.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
@ -414,9 +374,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
"Neverland (2011)[720p][PG][Voted 6.5][Family-Fantasy]part2.mkv"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveFiles(files).ToList();
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(2, result[0].Files.Count);
|
||||
|
@ -432,9 +390,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
@"M:/Movies (DVD)/Movies (Musical)/The Sound of Music/The Sound of Music (1965) (Disc 02)"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.ResolveDirectories(files).ToList();
|
||||
var result = StackResolver.ResolveDirectories(files, _namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(2, result[0].Files.Count);
|
||||
|
@ -445,10 +401,5 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
Assert.Equal(fileCount, stack.Files.Count);
|
||||
Assert.Equal(name, stack.Name);
|
||||
}
|
||||
|
||||
private StackResolver GetResolver()
|
||||
{
|
||||
return new StackResolver(_namingOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Linq;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.Video;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Xunit;
|
||||
|
||||
|
@ -48,16 +49,25 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.Equal(5, result.Count);
|
||||
Assert.Equal(11, result.Count);
|
||||
var batman = result.FirstOrDefault(x => string.Equals(x.Name, "Batman", StringComparison.Ordinal));
|
||||
Assert.NotNull(batman);
|
||||
Assert.Equal(3, batman!.Files.Count);
|
||||
Assert.Equal(3, batman!.Extras.Count);
|
||||
|
||||
var harry = result.FirstOrDefault(x => string.Equals(x.Name, "Harry Potter and the Deathly Hallows", StringComparison.Ordinal));
|
||||
Assert.NotNull(harry);
|
||||
Assert.Equal(4, harry!.Files.Count);
|
||||
Assert.Equal(2, harry!.Extras.Count);
|
||||
|
||||
Assert.False(result[2].ExtraType.HasValue);
|
||||
|
||||
Assert.Equal(ExtraType.Trailer, result[3].ExtraType);
|
||||
Assert.Equal(ExtraType.Trailer, result[4].ExtraType);
|
||||
Assert.Equal(ExtraType.DeletedScene, result[5].ExtraType);
|
||||
Assert.Equal(ExtraType.Sample, result[6].ExtraType);
|
||||
Assert.Equal(ExtraType.Trailer, result[7].ExtraType);
|
||||
Assert.Equal(ExtraType.Trailer, result[8].ExtraType);
|
||||
Assert.Equal(ExtraType.Trailer, result[9].ExtraType);
|
||||
Assert.Equal(ExtraType.Trailer, result[10].ExtraType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -97,7 +107,8 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.False(result[0].ExtraType.HasValue);
|
||||
Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -117,7 +128,8 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.False(result[0].ExtraType.HasValue);
|
||||
Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -138,15 +150,18 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.False(result[0].ExtraType.HasValue);
|
||||
Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
|
||||
Assert.Equal(ExtraType.Trailer, result[2].ExtraType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDifferentNames()
|
||||
public void Resolve_SameNameAndYear_ReturnsSingleItem()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
"Looper (2012)-trailer.mkv",
|
||||
"Looper 2012-trailer.mkv",
|
||||
"Looper.2012.bluray.720p.x264.mkv"
|
||||
};
|
||||
|
||||
|
@ -158,7 +173,30 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.False(result[0].ExtraType.HasValue);
|
||||
Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
|
||||
Assert.Equal(ExtraType.Trailer, result[2].ExtraType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolve_TrailerMatchesFolderName_ReturnsSingleItem()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
"/movies/Looper (2012)/Looper (2012)-trailer.mkv",
|
||||
"/movies/Looper (2012)/Looper.bluray.720p.x264.mkv"
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
files.Select(i => new FileSystemMetadata
|
||||
{
|
||||
IsDirectory = false,
|
||||
FullName = i
|
||||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.False(result[0].ExtraType.HasValue);
|
||||
Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -233,27 +271,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
{
|
||||
@"No (2012) part1.mp4",
|
||||
@"No (2012) part2.mp4",
|
||||
@"No (2012) part1-trailer.mp4"
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
files.Select(i => new FileSystemMetadata
|
||||
{
|
||||
IsDirectory = false,
|
||||
FullName = i
|
||||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestStackedWithTrailer2()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
@"No (2012) part1.mp4",
|
||||
@"No (2012) part2.mp4",
|
||||
@"No (2012) part1-trailer.mp4",
|
||||
@"No (2012)-trailer.mp4"
|
||||
};
|
||||
|
||||
|
@ -265,7 +283,10 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(3, result.Count);
|
||||
Assert.False(result[0].ExtraType.HasValue);
|
||||
Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
|
||||
Assert.Equal(ExtraType.Trailer, result[2].ExtraType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -276,7 +297,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
@"/Movies/Top Gun (1984)/movie.mp4",
|
||||
@"/Movies/Top Gun (1984)/Top Gun (1984)-trailer.mp4",
|
||||
@"/Movies/Top Gun (1984)/Top Gun (1984)-trailer2.mp4",
|
||||
@"trailer.mp4"
|
||||
@"/Movies/trailer.mp4"
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
|
@ -287,7 +308,10 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.False(result[0].ExtraType.HasValue);
|
||||
Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
|
||||
Assert.Equal(ExtraType.Trailer, result[2].ExtraType);
|
||||
Assert.Equal(ExtraType.Trailer, result[3].ExtraType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -396,7 +420,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
var files = new[]
|
||||
{
|
||||
@"/Server/Despicable Me/Despicable Me (2010).mkv",
|
||||
@"/Server/Despicable Me/movie-trailer.mkv"
|
||||
@"/Server/Despicable Me/trailer.mkv"
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
|
@ -407,18 +431,17 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.False(result[0].ExtraType.HasValue);
|
||||
Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestTrailerFalsePositives()
|
||||
public void Resolve_TrailerInTrailersFolder_ReturnsCorrectExtraType()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
@"/Server/Despicable Me/Skyscraper (2018) - Big Game Spot.mkv",
|
||||
@"/Server/Despicable Me/Skyscraper (2018) - Trailer.mkv",
|
||||
@"/Server/Despicable Me/Baywatch (2017) - Big Game Spot.mkv",
|
||||
@"/Server/Despicable Me/Baywatch (2017) - Trailer.mkv"
|
||||
@"/Server/Despicable Me/Despicable Me (2010).mkv",
|
||||
@"/Server/Despicable Me/trailers/some title.mkv"
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
|
@ -429,7 +452,8 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.Equal(4, result.Count);
|
||||
Assert.False(result[0].ExtraType.HasValue);
|
||||
Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -449,7 +473,8 @@ namespace Jellyfin.Naming.Tests.Video
|
|||
}).ToList(),
|
||||
_namingOptions).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.False(result[0].ExtraType.HasValue);
|
||||
Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Emby.Server.Implementations.Library.Resolvers.TV;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Server.Implementations.Library.Resolvers.TV;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
|
@ -13,12 +14,14 @@ namespace Jellyfin.Server.Implementations.Tests.Library
|
|||
{
|
||||
public class EpisodeResolverTest
|
||||
{
|
||||
private static readonly NamingOptions _namingOptions = new ();
|
||||
|
||||
[Fact]
|
||||
public void Resolve_GivenVideoInExtrasFolder_DoesNotResolveToEpisode()
|
||||
{
|
||||
var parent = new Folder { Name = "extras" };
|
||||
|
||||
var episodeResolver = new EpisodeResolver(null);
|
||||
var episodeResolver = new EpisodeResolver(_namingOptions);
|
||||
var itemResolveArgs = new ItemResolveArgs(
|
||||
Mock.Of<IServerApplicationPaths>(),
|
||||
Mock.Of<IDirectoryService>())
|
||||
|
@ -41,14 +44,14 @@ namespace Jellyfin.Server.Implementations.Tests.Library
|
|||
|
||||
// Have to create a mock because of moq proxies not being castable to a concrete implementation
|
||||
// https://github.com/jellyfin/jellyfin/blob/ab0cff8556403e123642dc9717ba778329554634/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs#L48
|
||||
var episodeResolver = new EpisodeResolverMock();
|
||||
var episodeResolver = new EpisodeResolverMock(_namingOptions);
|
||||
var itemResolveArgs = new ItemResolveArgs(
|
||||
Mock.Of<IServerApplicationPaths>(),
|
||||
Mock.Of<IDirectoryService>())
|
||||
{
|
||||
Parent = series,
|
||||
CollectionType = CollectionType.TvShows,
|
||||
FileInfo = new FileSystemMetadata()
|
||||
FileInfo = new FileSystemMetadata
|
||||
{
|
||||
FullName = "Extras/Extras S01E01.mkv"
|
||||
}
|
||||
|
@ -58,7 +61,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library
|
|||
|
||||
private class EpisodeResolverMock : EpisodeResolver
|
||||
{
|
||||
public EpisodeResolverMock() : base(null)
|
||||
public EpisodeResolverMock(NamingOptions namingOptions) : base(namingOptions)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using AutoFixture;
|
||||
using AutoFixture.AutoMoq;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Server.Implementations.Library.Resolvers;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Controller.Sorting;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Tests.Library.LibraryManager;
|
||||
|
||||
public class FindExtrasTests
|
||||
{
|
||||
private readonly Emby.Server.Implementations.Library.LibraryManager _libraryManager;
|
||||
|
||||
public FindExtrasTests()
|
||||
{
|
||||
var fixture = new Fixture().Customize(new AutoMoqCustomization());
|
||||
fixture.Register(() => new NamingOptions());
|
||||
var configMock = fixture.Freeze<Mock<IServerConfigurationManager>>();
|
||||
configMock.Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data");
|
||||
var fileSystemMock = fixture.Freeze<Mock<IFileSystem>>();
|
||||
fileSystemMock.Setup(f => f.GetFileInfo(It.IsAny<string>())).Returns<string>(path => new FileSystemMetadata { FullName = path });
|
||||
_libraryManager = fixture.Build<Emby.Server.Implementations.Library.LibraryManager>().Do(s => s.AddParts(
|
||||
fixture.Create<IEnumerable<IResolverIgnoreRule>>(),
|
||||
new List<IItemResolver> { new GenericVideoResolver<Video>(fixture.Create<NamingOptions>()) },
|
||||
fixture.Create<IEnumerable<IIntroProvider>>(),
|
||||
fixture.Create<IEnumerable<IBaseItemComparer>>(),
|
||||
fixture.Create<IEnumerable<ILibraryPostScanTask>>()))
|
||||
.Create();
|
||||
|
||||
// This is pretty terrible but unavoidable
|
||||
BaseItem.FileSystem ??= fixture.Create<IFileSystem>();
|
||||
BaseItem.MediaSourceManager ??= fixture.Create<IMediaSourceManager>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindExtras_SeparateMovieFolder_FindsCorrectExtras()
|
||||
{
|
||||
var owner = new Movie { Name = "Up", Path = "/movies/Up/Up.mkv" };
|
||||
var paths = new List<string>
|
||||
{
|
||||
"/movies/Up/Up.mkv",
|
||||
"/movies/Up/Up - trailer.mkv",
|
||||
"/movies/Up/Up - sample.mkv",
|
||||
"/movies/Up/Up something else.mkv"
|
||||
};
|
||||
|
||||
var files = paths.Select(p => new FileSystemMetadata
|
||||
{
|
||||
FullName = p,
|
||||
IsDirectory = false
|
||||
}).ToList();
|
||||
|
||||
var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList();
|
||||
|
||||
Assert.Equal(2, extras.Count);
|
||||
Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
|
||||
Assert.Equal(ExtraType.Sample, extras[1].ExtraType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindExtras_SeparateMovieFolderWithMixedExtras_FindsCorrectExtras()
|
||||
{
|
||||
var owner = new Movie { Name = "Up", Path = "/movies/Up/Up.mkv" };
|
||||
var paths = new List<string>
|
||||
{
|
||||
"/movies/Up/Up.mkv",
|
||||
"/movies/Up/Up - trailer.mkv",
|
||||
"/movies/Up/trailers/some trailer.mkv",
|
||||
"/movies/Up/behind the scenes/the making of Up.mkv",
|
||||
"/movies/Up/behind the scenes.mkv",
|
||||
"/movies/Up/Up - sample.mkv",
|
||||
"/movies/Up/Up something else.mkv"
|
||||
};
|
||||
|
||||
var files = paths.Select(p => new FileSystemMetadata
|
||||
{
|
||||
FullName = p,
|
||||
IsDirectory = false
|
||||
}).ToList();
|
||||
|
||||
var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList();
|
||||
|
||||
Assert.Equal(4, extras.Count);
|
||||
Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
|
||||
Assert.Equal(ExtraType.Trailer, extras[1].ExtraType);
|
||||
Assert.Equal(ExtraType.BehindTheScenes, extras[2].ExtraType);
|
||||
Assert.Equal(ExtraType.Sample, extras[3].ExtraType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindExtras_SeparateMovieFolderWithMixedExtras_FindsOnlyExtrasInMovieFolder()
|
||||
{
|
||||
var owner = new Movie { Name = "Up", Path = "/movies/Up/Up.mkv" };
|
||||
var paths = new List<string>
|
||||
{
|
||||
"/movies/Up/Up.mkv",
|
||||
"/movies/Up/trailer.mkv",
|
||||
"/movies/Another Movie/trailer.mkv"
|
||||
};
|
||||
|
||||
var files = paths.Select(p => new FileSystemMetadata
|
||||
{
|
||||
FullName = p,
|
||||
IsDirectory = false
|
||||
}).ToList();
|
||||
|
||||
var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList();
|
||||
|
||||
Assert.Single(extras);
|
||||
Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
|
||||
Assert.Equal("trailer", extras[0].FileNameWithoutExtension);
|
||||
Assert.Equal("/movies/Up/trailer.mkv", extras[0].Path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindExtras_SeparateMovieFolderWithParts_FindsCorrectExtras()
|
||||
{
|
||||
var owner = new Movie { Name = "Up", Path = "/movies/Up/Up - part1.mkv" };
|
||||
var paths = new List<string>
|
||||
{
|
||||
"/movies/Up/Up - part1.mkv",
|
||||
"/movies/Up/Up - part2.mkv",
|
||||
"/movies/Up/trailer.mkv",
|
||||
"/movies/Another Movie/trailer.mkv"
|
||||
};
|
||||
|
||||
var files = paths.Select(p => new FileSystemMetadata
|
||||
{
|
||||
FullName = p,
|
||||
IsDirectory = false
|
||||
}).ToList();
|
||||
|
||||
var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList();
|
||||
|
||||
Assert.Single(extras);
|
||||
Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
|
||||
Assert.Equal("trailer", extras[0].FileNameWithoutExtension);
|
||||
Assert.Equal("/movies/Up/trailer.mkv", extras[0].Path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindExtras_SeriesWithTrailers_FindsCorrectExtras()
|
||||
{
|
||||
var owner = new Series { Name = "Dexter", Path = "/series/Dexter" };
|
||||
var paths = new List<string>
|
||||
{
|
||||
"/series/Dexter/Season 1/S01E01.mkv",
|
||||
"/series/Dexter/trailer.mkv",
|
||||
"/series/Dexter/trailers/trailer2.mkv",
|
||||
};
|
||||
|
||||
var files = paths.Select(p => new FileSystemMetadata
|
||||
{
|
||||
FullName = p,
|
||||
IsDirectory = string.IsNullOrEmpty(Path.GetExtension(p))
|
||||
}).ToList();
|
||||
|
||||
var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList();
|
||||
|
||||
Assert.Equal(2, extras.Count);
|
||||
Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
|
||||
Assert.Equal("trailer", extras[0].FileNameWithoutExtension);
|
||||
Assert.Equal("/series/Dexter/trailer.mkv", extras[0].Path);
|
||||
Assert.Equal("/series/Dexter/trailers/trailer2.mkv", extras[1].Path);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user