mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
Add Name and Year parsing for audiobooks
This commit is contained in:
parent
50a2ef9d8a
commit
1e71775688
@ -11,12 +11,14 @@ namespace Emby.Naming.AudioBook
|
|||||||
/// Initializes a new instance of the <see cref="AudioBookInfo" /> class.
|
/// Initializes a new instance of the <see cref="AudioBookInfo" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">Name of audiobook.</param>
|
/// <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>();
|
Files = new List<AudioBookFileInfo>();
|
||||||
Extras = new List<AudioBookFileInfo>();
|
Extras = new List<AudioBookFileInfo>();
|
||||||
AlternateVersions = new List<AudioBookFileInfo>();
|
AlternateVersions = new List<AudioBookFileInfo>();
|
||||||
Name = name;
|
Name = name;
|
||||||
|
Year = year;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -41,9 +41,9 @@ namespace Emby.Naming.AudioBook
|
|||||||
|
|
||||||
stackFiles.Sort();
|
stackFiles.Sort();
|
||||||
|
|
||||||
// stack.Name can be empty when we have file without folder, but always have some files
|
var result = new AudioBookNameParser(_options).Parse(stack.Name);
|
||||||
var name = string.IsNullOrEmpty(stack.Name) ? stack.Files[0] : stack.Name;
|
|
||||||
var info = new AudioBookInfo(name) { Files = stackFiles };
|
var info = new AudioBookInfo(result.Name, result.Year) { Files = stackFiles };
|
||||||
|
|
||||||
yield return info;
|
yield return info;
|
||||||
}
|
}
|
||||||
|
59
Emby.Naming/AudioBook/AudioBookNameParser.cs
Normal file
59
Emby.Naming/AudioBook/AudioBookNameParser.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
Emby.Naming/AudioBook/AudioBookNameParserResult.cs
Normal file
12
Emby.Naming/AudioBook/AudioBookNameParserResult.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
@ -575,6 +575,13 @@ namespace Emby.Naming.Common
|
|||||||
@"dis(?:c|k)[\s_-]?(?<chapter>[0-9]+)"
|
@"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();
|
var extensions = VideoFileExtensions.ToList();
|
||||||
|
|
||||||
extensions.AddRange(new[]
|
extensions.AddRange(new[]
|
||||||
@ -658,6 +665,8 @@ namespace Emby.Naming.Common
|
|||||||
|
|
||||||
public string[] AudioBookPartsExpressions { get; set; }
|
public string[] AudioBookPartsExpressions { get; set; }
|
||||||
|
|
||||||
|
public string[] AudioBookNamesExpressions { get; set; }
|
||||||
|
|
||||||
public StubTypeRule[] StubTypes { get; set; }
|
public StubTypeRule[] StubTypes { get; set; }
|
||||||
|
|
||||||
public char[] VideoFlagDelimiters { get; set; }
|
public char[] VideoFlagDelimiters { get; set; }
|
||||||
|
@ -36,13 +36,25 @@ namespace Emby.Naming.Video
|
|||||||
|
|
||||||
foreach (var directory in groupedDirectoryFiles)
|
foreach (var directory in groupedDirectoryFiles)
|
||||||
{
|
{
|
||||||
var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false };
|
if (string.IsNullOrEmpty(directory.Key))
|
||||||
foreach (var file in directory)
|
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Emby.Naming.AudioBook;
|
using Emby.Naming.AudioBook;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
@ -72,33 +73,69 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TestYearExtraction()
|
public void TestNameYearExtraction()
|
||||||
{
|
{
|
||||||
var files = new[]
|
var data = new[]
|
||||||
{
|
{
|
||||||
"Harry Potter and the Deathly Hallows (2007)/Chapter 1.ogg",
|
new NameYearPath
|
||||||
"Harry Potter and the Deathly Hallows (2007)/Chapter 2.mp3",
|
{
|
||||||
|
Name = "Harry Potter and the Deathly Hallows",
|
||||||
"Batman (2020).ogg",
|
Path = "Harry Potter and the Deathly Hallows (2007)/Chapter 1.ogg",
|
||||||
|
Year = 2007
|
||||||
"Batman(2021).mp3",
|
},
|
||||||
|
new NameYearPath
|
||||||
"Batman.mp3"
|
{
|
||||||
|
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 resolver = GetResolver();
|
||||||
|
|
||||||
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
|
var result = resolver.Resolve(data.Select(i => new FileSystemMetadata
|
||||||
{
|
{
|
||||||
IsDirectory = false,
|
IsDirectory = false,
|
||||||
FullName = i
|
FullName = i.Path
|
||||||
})).ToList();
|
})).ToList();
|
||||||
|
|
||||||
Assert.Equal(3, result[0].Files.Count);
|
Assert.Equal(data.Length, result.Count);
|
||||||
Assert.Equal(2007, result[0].Year);
|
|
||||||
Assert.Equal(2020, result[1].Year);
|
for (int i = 0; i < data.Length; i++)
|
||||||
Assert.Equal(2021, result[2].Year);
|
{
|
||||||
Assert.Null(result[2].Year);
|
Assert.Equal(data[i].Name, result[i].Name);
|
||||||
|
Assert.Equal(data[i].Year, result[i].Year);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -180,5 +217,12 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
|||||||
{
|
{
|
||||||
return new AudioBookListResolver(_namingOptions);
|
return new AudioBookListResolver(_namingOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal struct NameYearPath
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public string Path;
|
||||||
|
public int? Year;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,6 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(GetResolveFileTestData))]
|
[MemberData(nameof(GetResolveFileTestData))]
|
||||||
public void Resolve_ValidFileName_Success(AudioBookFileInfo expectedResult)
|
public void Resolve_ValidFileName_Success(AudioBookFileInfo expectedResult)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user