jellyfin/Emby.Naming/Video/StackResolver.cs

243 lines
9.8 KiB
C#
Raw Normal View History

using System;
2018-09-12 19:26:21 +02:00
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.AudioBook;
2019-01-13 20:17:29 +01:00
using Emby.Naming.Common;
2018-09-12 19:26:21 +02:00
using MediaBrowser.Model.IO;
namespace Emby.Naming.Video
{
2020-11-10 19:23:10 +01:00
/// <summary>
/// Resolve <see cref="FileStack"/> from list of paths.
/// </summary>
2021-12-07 15:18:17 +01:00
public static class StackResolver
2018-09-12 19:26:21 +02:00
{
2020-11-10 19:23:10 +01:00
/// <summary>
/// Resolves only directories from paths.
/// </summary>
/// <param name="files">List of paths.</param>
2021-12-07 15:18:17 +01:00
/// <param name="namingOptions">The naming options.</param>
2020-11-10 19:23:10 +01:00
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
2021-12-07 15:18:17 +01:00
public static IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files, NamingOptions namingOptions)
2018-09-12 19:26:21 +02:00
{
2021-12-07 15:18:17 +01:00
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }), namingOptions);
2018-09-12 19:26:21 +02:00
}
2020-11-10 19:23:10 +01:00
/// <summary>
/// Resolves only files from paths.
/// </summary>
/// <param name="files">List of paths.</param>
2021-12-07 15:18:17 +01:00
/// <param name="namingOptions">The naming options.</param>
2020-11-10 19:23:10 +01:00
/// <returns>Enumerable <see cref="FileStack"/> of files.</returns>
2021-12-07 15:18:17 +01:00
public static IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files, NamingOptions namingOptions)
2018-09-12 19:26:21 +02:00
{
2021-12-07 15:18:17 +01:00
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }), namingOptions);
2018-09-12 19:26:21 +02:00
}
2020-11-10 19:23:10 +01:00
/// <summary>
/// Resolves audiobooks from paths.
/// </summary>
/// <param name="files">List of paths.</param>
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
2021-12-07 15:18:17 +01:00
public static IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files)
2018-09-12 19:26:21 +02:00
{
var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path));
2020-03-25 21:33:44 +01:00
foreach (var directory in groupedDirectoryFiles)
2018-09-12 19:26:21 +02:00
{
if (string.IsNullOrEmpty(directory.Key))
2018-09-12 19:26:21 +02:00
{
foreach (var file in directory)
{
var stack = new FileStack { Name = Path.GetFileNameWithoutExtension(file.Path), IsDirectoryStack = false };
stack.Files.Add(file.Path);
yield return stack;
}
2018-09-12 19:26:21 +02:00
}
else
{
var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false };
foreach (var file in directory)
{
stack.Files.Add(file.Path);
}
2019-05-10 20:37:42 +02:00
yield return stack;
}
2018-09-12 19:26:21 +02:00
}
}
2020-11-10 19:23:10 +01:00
/// <summary>
/// Resolves videos from paths.
/// </summary>
/// <param name="files">List of paths.</param>
2021-12-07 15:18:17 +01:00
/// <param name="namingOptions">The naming options.</param>
2020-11-10 19:23:10 +01:00
/// <returns>Enumerable <see cref="FileStack"/> of videos.</returns>
2021-12-07 15:18:17 +01:00
public static IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions)
2018-09-12 19:26:21 +02:00
{
var list = files
2021-12-07 15:18:17 +01:00
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, namingOptions) || VideoResolver.IsStubFile(i.FullName, namingOptions))
2018-09-12 19:26:21 +02:00
.OrderBy(i => i.FullName)
2021-12-07 15:18:17 +01:00
.Select(f => (f.IsDirectory, FileName: GetFileNameWithExtension(f), f.FullName))
2018-09-12 19:26:21 +02:00
.ToList();
2021-12-07 15:18:17 +01:00
// TODO is there a "nicer" way?
var cache = new Dictionary<(string, Regex, int), Match>();
var expressions = namingOptions.VideoFileStackingRegexes;
2018-09-12 19:26:21 +02:00
for (var i = 0; i < list.Count; i++)
{
var offset = 0;
var file1 = list[i];
var expressionIndex = 0;
while (expressionIndex < expressions.Length)
{
var exp = expressions[expressionIndex];
2021-12-07 15:18:17 +01:00
FileStack? stack = null;
2018-09-12 19:26:21 +02:00
// (Title)(Volume)(Ignore)(Extension)
2021-12-07 15:18:17 +01:00
var match1 = FindMatch(file1.FileName, exp, offset, cache);
2018-09-12 19:26:21 +02:00
if (match1.Success)
{
2021-12-07 15:18:17 +01:00
var title1 = match1.Groups[1].Value;
var volume1 = match1.Groups[2].Value;
var ignore1 = match1.Groups[3].Value;
var extension1 = match1.Groups[4].Value;
2018-09-12 19:26:21 +02:00
var j = i + 1;
while (j < list.Count)
{
var file2 = list[j];
if (file1.IsDirectory != file2.IsDirectory)
{
j++;
continue;
}
// (Title)(Volume)(Ignore)(Extension)
2021-12-07 15:18:17 +01:00
var match2 = FindMatch(file2.FileName, exp, offset, cache);
2018-09-12 19:26:21 +02:00
if (match2.Success)
{
var title2 = match2.Groups[1].Value;
var volume2 = match2.Groups[2].Value;
var ignore2 = match2.Groups[3].Value;
var extension2 = match2.Groups[4].Value;
if (string.Equals(title1, title2, StringComparison.OrdinalIgnoreCase))
{
if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase))
{
2019-05-10 20:37:42 +02:00
if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase)
&& string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
2018-09-12 19:26:21 +02:00
{
2021-12-07 15:18:17 +01:00
stack ??= new FileStack();
2018-09-12 19:26:21 +02:00
if (stack.Files.Count == 0)
{
stack.Name = title1 + ignore1;
stack.IsDirectoryStack = file1.IsDirectory;
stack.Files.Add(file1.FullName);
}
2019-05-10 20:37:42 +02:00
2018-09-12 19:26:21 +02:00
stack.Files.Add(file2.FullName);
}
2019-01-08 00:27:46 +01:00
else
2018-09-12 19:26:21 +02:00
{
// Sequel
offset = 0;
expressionIndex++;
break;
}
}
else if (!string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase))
{
// False positive, try again with offset
offset = match1.Groups[3].Index;
break;
}
else
{
// Extension mismatch
offset = 0;
expressionIndex++;
break;
}
}
else
{
// Title mismatch
offset = 0;
expressionIndex++;
break;
}
}
else
{
// No match 2, next expression
offset = 0;
expressionIndex++;
break;
}
j++;
}
if (j == list.Count)
{
expressionIndex = expressions.Length;
}
}
else
{
// No match 1
offset = 0;
expressionIndex++;
}
2021-12-07 15:18:17 +01:00
if (stack?.Files.Count > 1)
2018-09-12 19:26:21 +02:00
{
2020-01-22 22:18:56 +01:00
yield return stack;
2018-09-12 19:26:21 +02:00
i += stack.Files.Count - 1;
break;
}
}
}
}
2021-12-07 15:18:17 +01:00
private static string GetFileNameWithExtension(FileSystemMetadata file)
2018-09-12 19:26:21 +02:00
{
// For directories, dummy up an extension otherwise the expressions will fail
2021-12-07 15:18:17 +01:00
var input = file.FullName;
if (file.IsDirectory)
{
input = Path.ChangeExtension(input, "mkv");
}
2018-09-12 19:26:21 +02:00
return Path.GetFileName(input);
}
2021-12-07 15:18:17 +01:00
private static Match FindMatch(string input, Regex regex, int offset, Dictionary<(string, Regex, int), Match> cache)
2018-09-12 19:26:21 +02:00
{
2021-12-07 15:18:17 +01:00
if (offset < 0 || offset >= input.Length)
2018-09-12 19:26:21 +02:00
{
return Match.Empty;
}
2021-12-07 15:18:17 +01:00
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;
2018-09-12 19:26:21 +02:00
}
}
}