Add Name and Year parsing for audiobooks

This commit is contained in:
Stepan 2020-11-02 20:03:12 +01:00
parent 50a2ef9d8a
commit 1e71775688
8 changed files with 163 additions and 26 deletions

View File

@ -11,12 +11,14 @@ namespace Emby.Naming.AudioBook
/// Initializes a new instance of the <see cref="AudioBookInfo" /> class.
/// </summary>
/// <param name="name">Name of audiobook.</param>
public AudioBookInfo(string name)
/// <param name="year">Year of audiobook release.</param>
public AudioBookInfo(string name, int? year)
{
Files = new List<AudioBookFileInfo>();
Extras = new List<AudioBookFileInfo>();
AlternateVersions = new List<AudioBookFileInfo>();
Name = name;
Year = year;
}
/// <summary>

View File

@ -41,9 +41,9 @@ namespace Emby.Naming.AudioBook
stackFiles.Sort();
// stack.Name can be empty when we have file without folder, but always have some files
var name = string.IsNullOrEmpty(stack.Name) ? stack.Files[0] : stack.Name;
var info = new AudioBookInfo(name) { Files = stackFiles };
var result = new AudioBookNameParser(_options).Parse(stack.Name);
var info = new AudioBookInfo(result.Name, result.Year) { Files = stackFiles };
yield return info;
}

View File

@ -0,0 +1,59 @@
#nullable enable
#pragma warning disable CS1591
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using Emby.Naming.Common;
namespace Emby.Naming.AudioBook
{
public class AudioBookNameParser
{
private readonly NamingOptions _options;
public AudioBookNameParser(NamingOptions options)
{
_options = options;
}
public AudioBookNameParserResult Parse(string name)
{
AudioBookNameParserResult result = default;
foreach (var expression in _options.AudioBookNamesExpressions)
{
var match = new Regex(expression, RegexOptions.IgnoreCase).Match(name);
if (match.Success)
{
if (result.Name == null)
{
var value = match.Groups["name"];
if (value.Success)
{
result.Name = value.Value;
}
}
if (!result.Year.HasValue)
{
var value = match.Groups["year"];
if (value.Success)
{
if (int.TryParse(value.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
{
result.Year = intValue;
}
}
}
}
}
if (string.IsNullOrEmpty(result.Name))
{
result.Name = name;
}
return result;
}
}
}

View File

@ -0,0 +1,12 @@
#nullable enable
#pragma warning disable CS1591
namespace Emby.Naming.AudioBook
{
public struct AudioBookNameParserResult
{
public string Name { get; set; }
public int? Year { get; set; }
}
}

View File

@ -575,6 +575,13 @@ namespace Emby.Naming.Common
@"dis(?:c|k)[\s_-]?(?<chapter>[0-9]+)"
};
AudioBookNamesExpressions = new[]
{
// Detect year usually in brackets after name Batman (2020)
@"^(?<name>.+?)\s*\(\s*(?<year>\d{4})\s*\)\s*$",
@"^\s*(?<name>.+?)\s*$"
};
var extensions = VideoFileExtensions.ToList();
extensions.AddRange(new[]
@ -658,6 +665,8 @@ namespace Emby.Naming.Common
public string[] AudioBookPartsExpressions { get; set; }
public string[] AudioBookNamesExpressions { get; set; }
public StubTypeRule[] StubTypes { get; set; }
public char[] VideoFlagDelimiters { get; set; }

View File

@ -36,13 +36,25 @@ namespace Emby.Naming.Video
foreach (var directory in groupedDirectoryFiles)
{
var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false };
foreach (var file in directory)
if (string.IsNullOrEmpty(directory.Key))
{
stack.Files.Add(file.Path);
foreach (var file in directory)
{
var stack = new FileStack { Name = Path.GetFileNameWithoutExtension(file.Path), IsDirectoryStack = false };
stack.Files.Add(file.Path);
yield return stack;
}
}
else
{
var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false };
foreach (var file in directory)
{
stack.Files.Add(file.Path);
}
yield return stack;
yield return stack;
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Emby.Naming.AudioBook;
using Emby.Naming.Common;
@ -72,33 +73,69 @@ namespace Jellyfin.Naming.Tests.AudioBook
}
[Fact]
public void TestYearExtraction()
public void TestNameYearExtraction()
{
var files = new[]
var data = new[]
{
"Harry Potter and the Deathly Hallows (2007)/Chapter 1.ogg",
"Harry Potter and the Deathly Hallows (2007)/Chapter 2.mp3",
"Batman (2020).ogg",
"Batman(2021).mp3",
"Batman.mp3"
new NameYearPath
{
Name = "Harry Potter and the Deathly Hallows",
Path = "Harry Potter and the Deathly Hallows (2007)/Chapter 1.ogg",
Year = 2007
},
new NameYearPath
{
Name = "Batman",
Path = "Batman (2020).ogg",
Year = 2020
},
new NameYearPath
{
Name = "Batman",
Path = "Batman( 2021 ).mp3",
Year = 2021
},
new NameYearPath
{
Name = "Batman(*2021*)",
Path = "Batman(*2021*).mp3",
Year = null
},
new NameYearPath
{
Name = "Batman",
Path = "Batman.mp3",
Year = null
},
new NameYearPath
{
Name = "+ Batman .",
Path = " + Batman . .mp3",
Year = null
},
new NameYearPath
{
Name = " ",
Path = " .mp3",
Year = null
}
};
var resolver = GetResolver();
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
var result = resolver.Resolve(data.Select(i => new FileSystemMetadata
{
IsDirectory = false,
FullName = i
FullName = i.Path
})).ToList();
Assert.Equal(3, result[0].Files.Count);
Assert.Equal(2007, result[0].Year);
Assert.Equal(2020, result[1].Year);
Assert.Equal(2021, result[2].Year);
Assert.Null(result[2].Year);
Assert.Equal(data.Length, result.Count);
for (int i = 0; i < data.Length; i++)
{
Assert.Equal(data[i].Name, result[i].Name);
Assert.Equal(data[i].Year, result[i].Year);
}
}
[Fact]
@ -180,5 +217,12 @@ namespace Jellyfin.Naming.Tests.AudioBook
{
return new AudioBookListResolver(_namingOptions);
}
internal struct NameYearPath
{
public string Name;
public string Path;
public int? Year;
}
}
}

View File

@ -35,7 +35,6 @@ namespace Jellyfin.Naming.Tests.AudioBook
};
}
[Theory]
[MemberData(nameof(GetResolveFileTestData))]
public void Resolve_ValidFileName_Success(AudioBookFileInfo expectedResult)