parent
5612cb8178
commit
3ae08b4f06
@ -0,0 +1,19 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities.AudioBooks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Images
|
||||
{
|
||||
public class AudioBookImageProvider : BaseFolderImageProvider<AudioBook>
|
||||
{
|
||||
public AudioBookImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager)
|
||||
: base(fileSystem, providerManager, applicationPaths, imageProcessor, libraryManager)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable SA1642
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Emby.Naming.Common;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Entities.AudioBooks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers.AudioBooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Class AudioBookFileResolver.
|
||||
/// NOTE: Should these files be moved to MediaBrowser.Controller.Resolvers.(AudioBooks?) directory?.
|
||||
/// </summary>
|
||||
public class AudioBookFileResolver : BaseAudioBookResolver<AudioBookFile>
|
||||
{
|
||||
private readonly ILogger<AudioBookFileResolver> _logger;
|
||||
private readonly NamingOptions _namingOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the AudioBookFileResolver class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="namingOptions">Options for naming.</param>
|
||||
/// <param name="directoryService">Some other thing.</param>
|
||||
public AudioBookFileResolver(ILogger<AudioBookFileResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService)
|
||||
: base(logger, namingOptions, directoryService)
|
||||
{
|
||||
_logger = logger;
|
||||
_namingOptions = namingOptions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
public override ResolverPriority Priority => ResolverPriority.Fourth;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the specified args.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns>Episode.</returns>
|
||||
protected override AudioBookFile Resolve(ItemResolveArgs args)
|
||||
{
|
||||
if (!IsValid(args))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ResolveAudioBookFile<AudioBookFile>(args, true);
|
||||
}
|
||||
|
||||
private bool IsValid(ItemResolveArgs args)
|
||||
{
|
||||
if (args is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var parent = args.Parent;
|
||||
|
||||
if (parent is null || args.IsDirectory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parent.GetType() != typeof(AudioBook))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (args.CollectionType != Jellyfin.Data.Enums.CollectionType.books)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var path = args.Path;
|
||||
if (path.Length == 0 || Path.GetFileNameWithoutExtension(path).Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_namingOptions.AudioFileExtensions.Contains(Path.GetExtension(args.Path), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Emby.Naming.Audio;
|
||||
using Emby.Naming.Common;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Entities.AudioBooks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers.AudioBooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolve an AudioBook instance from a file system path.
|
||||
/// Resolve returns a partially initialized AudioBook instance with name (title), year, and path.
|
||||
/// Rely on audio file metadata for majority of other information.
|
||||
/// NOTE: Should these files be moved to MediaBrowser.Controller.Resolvers.(AudioBooks?) directory?.
|
||||
/// </summary>
|
||||
public class AudioBookResolver : GenericFolderResolver<AudioBook>
|
||||
{
|
||||
private readonly ILogger<AudioBookResolver> _logger;
|
||||
private readonly NamingOptions _namingOptions;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
|
||||
public AudioBookResolver(ILogger<AudioBookResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService)
|
||||
{
|
||||
_logger = logger;
|
||||
_namingOptions = namingOptions;
|
||||
_directoryService = directoryService;
|
||||
}
|
||||
|
||||
// <summary>
|
||||
// Gets the priority.
|
||||
// </summary>
|
||||
// <value>The priority.</value>
|
||||
// public override ResolverPriority Priority => ResolverPriority.Fourth;
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to resolve a single audio book from args.Path.
|
||||
/// Expected cases:
|
||||
/// Path is an AudioBook directory: Resolve path as AudioBook and children as AudioBookFiles.
|
||||
/// Path is single-file AudioBook: Resolve path as AudioBook w/ AudioBookFile of path.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns>Entities.AudioBook.</returns>
|
||||
protected override AudioBook Resolve(ItemResolveArgs args)
|
||||
{
|
||||
// We only care about audiobooks
|
||||
// TODO: Change this to new CollectionType.audiobooks?
|
||||
if (args.GetCollectionType() != CollectionType.books)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: I don't know if there's a valid case where parent is null and CollectionType is books
|
||||
if (args.Parent != null && args.Parent.GetType() != typeof(MediaBrowser.Controller.Entities.Folder))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var audioBook = new AudioBook();
|
||||
|
||||
// TODO: Get title from path...
|
||||
var bookFileName = Path.GetFileNameWithoutExtension(args.Path);
|
||||
var name = MatchFor(bookFileName, "name");
|
||||
if (name == null)
|
||||
{
|
||||
name = bookFileName;
|
||||
}
|
||||
|
||||
var year = MatchFor(bookFileName, "year");
|
||||
|
||||
// Process directory to resolve single audio book
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
// Majority children must be audio files
|
||||
int audioFiles = 0;
|
||||
int nonAudioFiles = 0;
|
||||
// TODO: What's the fancy C# way to do this?
|
||||
for (var i = 0; i < args.FileSystemChildren.Length; i++)
|
||||
{
|
||||
var child = args.FileSystemChildren[i];
|
||||
if (AudioFileParser.IsAudioFile(child.FullName, _namingOptions))
|
||||
{
|
||||
audioFiles += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
nonAudioFiles += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (audioFiles == 0 || nonAudioFiles / audioFiles > 1)
|
||||
{
|
||||
_logger.LogDebug("Less than half of the files in {0} were audio files, probably not an AudioBook directory", args.Path);
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Must not contain sub directories
|
||||
return audioBook;
|
||||
}
|
||||
|
||||
// Resolve a single-file AudioBook
|
||||
if (AudioFileParser.IsAudioFile(args.Path, _namingOptions))
|
||||
{
|
||||
// Create AudioBookFile
|
||||
var audioBookFile = new AudioBookFile
|
||||
{
|
||||
Path = args.Path
|
||||
};
|
||||
|
||||
// Set as child of audioBook
|
||||
// TODO: Is making both calls redundant?
|
||||
audioBookFile.SetParent(audioBook);
|
||||
audioBook.AddChild(audioBookFile);
|
||||
|
||||
var extension = Path.GetExtension(args.Path);
|
||||
|
||||
audioBookFile.Container = extension.TrimStart('.');
|
||||
audioBookFile.Chapter = 0;
|
||||
// audioBookFile.path = args.path; ?
|
||||
|
||||
return audioBook;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Put this somewhere better where both AudioBook classes can access
|
||||
private string MatchFor(string name, string matchGroup)
|
||||
{
|
||||
foreach (var expression in _namingOptions.AudioBookNamesExpressions)
|
||||
{
|
||||
var match = Regex.Match(name, expression, RegexOptions.IgnoreCase);
|
||||
if (match.Success)
|
||||
{
|
||||
var value = match.Groups[matchGroup];
|
||||
if (value.Success)
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Emby.Naming.Common;
|
||||
using MediaBrowser.Controller.Entities.AudioBooks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers.AudioBooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves a Path into a Video or Video subclass.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of item to resolve.</typeparam>
|
||||
public abstract class BaseAudioBookResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T>
|
||||
where T : AudioBookFile, new()
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
protected BaseAudioBookResolver(ILogger logger, NamingOptions namingOptions, IDirectoryService directoryService)
|
||||
{
|
||||
_logger = logger;
|
||||
NamingOptions = namingOptions;
|
||||
DirectoryService = directoryService;
|
||||
}
|
||||
|
||||
protected NamingOptions NamingOptions { get; }
|
||||
|
||||
protected IDirectoryService DirectoryService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the specified args.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns>`0.</returns>
|
||||
protected override T Resolve(ItemResolveArgs args)
|
||||
{
|
||||
return ResolveAudioBookFile<T>(args, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the video.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAudioBookType">The type of the T video type.</typeparam>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <param name="parseName">if set to <c>true</c> [parse name].</param>
|
||||
/// <returns>``0.</returns>
|
||||
protected virtual TAudioBookType ResolveAudioBookFile<TAudioBookType>(ItemResolveArgs args, bool parseName)
|
||||
where TAudioBookType : AudioBookFile, new()
|
||||
{
|
||||
var audioBookFile = new TAudioBookType
|
||||
{
|
||||
Path = args.Path
|
||||
};
|
||||
|
||||
// Get AudioBookFileInfo
|
||||
ResolveFileInfo(audioBookFile);
|
||||
|
||||
return audioBookFile;
|
||||
}
|
||||
|
||||
private int ParseChapter(string path)
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(path);
|
||||
foreach (var expression in NamingOptions.AudioBookPartsExpressions)
|
||||
{
|
||||
var match = Regex.Match(fileName, expression, RegexOptions.IgnoreCase);
|
||||
if (match.Success)
|
||||
{
|
||||
var value = match.Groups["chapter"];
|
||||
if (value.Success)
|
||||
{
|
||||
if (int.TryParse(value.ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
|
||||
{
|
||||
return intValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void ResolveFileInfo(AudioBookFile audioBookFile)
|
||||
{
|
||||
var path = audioBookFile.Path;
|
||||
|
||||
var extension = Path.GetExtension(path);
|
||||
|
||||
audioBookFile.Container = extension.TrimStart('.');
|
||||
|
||||
audioBookFile.Chapter = ParseChapter(path);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1724, CS1591
|
||||
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
public class AudioBook : Audio.Audio, IHasSeries, IHasLookupInfo<SongInfo>
|
||||
{
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPositionTicksResume => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPlayedStatus => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public string SeriesPresentationUniqueKey { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string SeriesName { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Guid SeriesId { get; set; }
|
||||
|
||||
public string FindSeriesSortName()
|
||||
{
|
||||
return SeriesName;
|
||||
}
|
||||
|
||||
public string FindSeriesName()
|
||||
{
|
||||
return SeriesName;
|
||||
}
|
||||
|
||||
public string FindSeriesPresentationUniqueKey()
|
||||
{
|
||||
return SeriesPresentationUniqueKey;
|
||||
}
|
||||
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public Guid FindSeriesId()
|
||||
{
|
||||
return SeriesId;
|
||||
}
|
||||
|
||||
public override bool CanDownload()
|
||||
{
|
||||
return IsFileProtocol;
|
||||
}
|
||||
|
||||
public override UnratedItem GetBlockUnratedType()
|
||||
{
|
||||
return UnratedItem.Book;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
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.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities.AudioBooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Class Season.
|
||||
/// </summary>
|
||||
public class AudioBook : Folder, IHasLookupInfo<AudioBookFolderInfo>, IMetadataContainer, IHasMediaSources
|
||||
{
|
||||
public AudioBook()
|
||||
{
|
||||
Authors = Array.Empty<string>();
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override MediaType MediaType => MediaType.Audio;
|
||||
|
||||
public IReadOnlyList<string> Authors { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsAddingToPlaylist => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool IsPreSorted => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsDateLastMediaAdded => false;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPeople => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsInheritedParentImages => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public string SeriesPresentationUniqueKey { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPositionTicksResume => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tracks.
|
||||
/// </summary>
|
||||
/// <value>The tracks.</value>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Audio.Audio> Tracks => GetRecursiveChildren(i => i is AudioBookFile).Cast<Audio.Audio>();
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<AudioBookFile> Chapters => GetRecursiveChildren(i => i is AudioBookFile).Cast<AudioBookFile>();
|
||||
|
||||
public override int GetChildCount(User user)
|
||||
{
|
||||
var result = GetChildren(user, true).Count;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
|
||||
{
|
||||
return Tracks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the name of the sort.
|
||||
/// </summary>
|
||||
/// <returns>System.String.</returns>
|
||||
protected override string CreateSortName()
|
||||
{
|
||||
return IndexNumber is not null ? IndexNumber.Value.ToString("0000", CultureInfo.InvariantCulture) : Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lookup information.
|
||||
/// </summary>
|
||||
/// <returns>SeasonInfo.</returns>
|
||||
public AudioBookFolderInfo GetLookupInfo()
|
||||
{
|
||||
var id = GetItemLookupInfo<AudioBookFolderInfo>();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override bool CanDownload()
|
||||
{
|
||||
return IsFileProtocol;
|
||||
}
|
||||
|
||||
public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var items = GetRecursiveChildren();
|
||||
|
||||
var totalItems = items.Count;
|
||||
var numComplete = 0;
|
||||
|
||||
var childUpdateType = ItemUpdateType.None;
|
||||
|
||||
// Refresh AudioBook files/chapters only
|
||||
foreach (var item in items.OfType<AudioBookFile>())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var updateType = await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
|
||||
childUpdateType = childUpdateType | updateType;
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
percent /= totalItems;
|
||||
progress.Report(percent * 95);
|
||||
}
|
||||
|
||||
// get album LUFS
|
||||
LUFS = items.OfType<AudioBookFile>().Max(item => item.LUFS);
|
||||
|
||||
var parentRefreshOptions = refreshOptions;
|
||||
if (childUpdateType > ItemUpdateType.None)
|
||||
{
|
||||
parentRefreshOptions = new MetadataRefreshOptions(refreshOptions)
|
||||
{
|
||||
MetadataRefreshMode = MetadataRefreshMode.FullRefresh
|
||||
};
|
||||
}
|
||||
|
||||
// Refresh current item
|
||||
await RefreshMetadata(parentRefreshOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// if (!refreshOptions.IsAutomated)
|
||||
// {
|
||||
// await RefreshArtists(refreshOptions, cancellationToken).ConfigureAwait(false);
|
||||
// }
|
||||
}
|
||||
|
||||
// private async Task RefreshArtists(MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
|
||||
// {
|
||||
// foreach (var i in this.GetAllArtists())
|
||||
// {
|
||||
// This should not be necessary but we're seeing some cases of it
|
||||
// if (string.IsNullOrEmpty(i))
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// var artist = LibraryManager.GetArtist(i);
|
||||
|
||||
// if (!artist.IsAccessedByName)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// await artist.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
|
||||
// }
|
||||
// }
|
||||
protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources()
|
||||
{
|
||||
// var tracks = Enumerable.Empty<(BaseItem Item, MediaSourceType MediaSourceType)>;
|
||||
var tracks = new List<(BaseItem Item, MediaSourceType MediaSourceType)>();
|
||||
foreach (var track in Tracks)
|
||||
{
|
||||
tracks.Add((track, MediaSourceType.Default));
|
||||
}
|
||||
|
||||
return tracks;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
// TODO: Use ChapterInfo?.
|
||||
namespace MediaBrowser.Controller.Entities.AudioBooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Class AudioBookFile.
|
||||
/// </summary>
|
||||
public class AudioBookFile : Audio.Audio, IHasLookupInfo<AudioBookFileInfo>
|
||||
{
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPositionTicksResume => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override MediaType MediaType => MediaType.Audio;
|
||||
|
||||
public string BookTitle { get; set; }
|
||||
|
||||
public int Chapter { get; set; }
|
||||
|
||||
public IReadOnlyList<string> Authors { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPlayedStatus => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPeople => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsAddingToPlaylist => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsInheritedParentImages => true;
|
||||
|
||||
[JsonIgnore]
|
||||
protected override bool SupportsOwnedItems => false;
|
||||
|
||||
[JsonIgnore]
|
||||
public override Folder LatestItemsIndexContainer => AudioBookEntity;
|
||||
|
||||
[JsonIgnore]
|
||||
public AudioBook AudioBookEntity => FindParent<AudioBook>();
|
||||
|
||||
[JsonIgnore]
|
||||
public string SeriesName { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string SeasonName { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsRemoteImageDownloading => true;
|
||||
|
||||
public IEnumerable<AudioBookFile> GetNextChapters()
|
||||
{
|
||||
var chapters = AudioBookEntity.Chapters;
|
||||
var nextChapters = new List<AudioBookFile>();
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
if (chapter.Chapter > Chapter)
|
||||
{
|
||||
nextChapters.Add(chapter);
|
||||
}
|
||||
}
|
||||
|
||||
return nextChapters;
|
||||
}
|
||||
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get user data keys.
|
||||
/// </summary>
|
||||
/// <returns>User data keys associated with this item.</returns>
|
||||
public override List<string> GetUserDataKeys()
|
||||
{
|
||||
var list = base.GetUserDataKeys();
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public string FindSeriesPresentationUniqueKey()
|
||||
{
|
||||
return AudioBookEntity?.PresentationUniqueKey;
|
||||
}
|
||||
|
||||
public string FindAudioBookName()
|
||||
{
|
||||
return AudioBookEntity is null ? "AudioBook Unknown" : AudioBookEntity.Name;
|
||||
}
|
||||
|
||||
public Guid FindAudioBookId()
|
||||
{
|
||||
return AudioBookEntity is null ? Guid.Empty : AudioBookEntity.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the name of the sort.
|
||||
/// </summary>
|
||||
/// <returns>System.String.</returns>
|
||||
protected override string CreateSortName()
|
||||
{
|
||||
return (ParentIndexNumber is not null ? ParentIndexNumber.Value.ToString("000 - ", CultureInfo.InvariantCulture) : string.Empty)
|
||||
+ (IndexNumber is not null ? IndexNumber.Value.ToString("0000 - ", CultureInfo.InvariantCulture) : string.Empty) + Name;
|
||||
}
|
||||
|
||||
public override IEnumerable<FileSystemMetadata> GetDeletePaths()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new FileSystemMetadata
|
||||
{
|
||||
FullName = Path,
|
||||
IsDirectory = IsFolder
|
||||
}
|
||||
}.Concat(GetLocalMetadataFilesToDelete());
|
||||
}
|
||||
|
||||
public new AudioBookFileInfo GetLookupInfo()
|
||||
{
|
||||
var id = GetItemLookupInfo<AudioBookFileInfo>();
|
||||
|
||||
id.BookTitle = BookTitle;
|
||||
id.Authors = Authors;
|
||||
id.Container = Container;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
|
||||
{
|
||||
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
public override List<ExternalUrl> GetRelatedUrls()
|
||||
{
|
||||
var list = base.GetRelatedUrls();
|
||||
|
||||
// TODO: Is there one of these for books and are those changes worth it?
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources()
|
||||
{
|
||||
var chapters = AudioBookEntity.Chapters;
|
||||
var nextChapters = new List<(BaseItem Item, MediaSourceType MediaSourceType)>();
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
if (chapter.Chapter >= this.Chapter)
|
||||
{
|
||||
nextChapters.Add((chapter, MediaSourceType.Default));
|
||||
}
|
||||
}
|
||||
|
||||
return nextChapters;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Controller.Providers
|
||||
{
|
||||
public class AudioBookFileInfo : ItemLookupInfo
|
||||
{
|
||||
public AudioBookFileInfo()
|
||||
{
|
||||
Authors = Array.Empty<string>();
|
||||
}
|
||||
|
||||
public string BookTitle { get; set; }
|
||||
|
||||
public int Chapter { get; set; }
|
||||
|
||||
public string Container { get; set; }
|
||||
|
||||
public IReadOnlyList<string> Authors { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Controller.Providers
|
||||
{
|
||||
public class AudioBookFolderInfo : ItemLookupInfo
|
||||
{
|
||||
public AudioBookFolderInfo()
|
||||
{
|
||||
Authors = Array.Empty<string>();
|
||||
}
|
||||
|
||||
public string BookTitle { get; set; }
|
||||
|
||||
public string Container { get; set; }
|
||||
|
||||
public int Chapters { get; set; }
|
||||
|
||||
public IReadOnlyList<string> Authors { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.AudioBooks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Providers.Manager;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Providers.Books
|
||||
{
|
||||
/// <summary>
|
||||
/// The audio metadata service.
|
||||
/// </summary>
|
||||
public class AudioBookFileMetadataService : MetadataService<AudioBookFile, AudioBookFileInfo>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AudioBookFileMetadataService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param>
|
||||
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
||||
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
|
||||
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||
public AudioBookFileMetadataService(
|
||||
IServerConfigurationManager serverConfigurationManager,
|
||||
ILogger<AudioBookFileMetadataService> logger,
|
||||
IProviderManager providerManager,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryManager libraryManager)
|
||||
: base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager)
|
||||
{
|
||||
}
|
||||
|
||||
private void SetProviderId(AudioBookFile sourceItem, AudioBookFile targetItem, bool replaceData, MetadataProvider provider)
|
||||
{
|
||||
var target = targetItem.GetProviderId(provider);
|
||||
if (replaceData || string.IsNullOrEmpty(target))
|
||||
{
|
||||
var source = sourceItem.GetProviderId(provider);
|
||||
if (!string.IsNullOrEmpty(source)
|
||||
&& (string.IsNullOrEmpty(target)
|
||||
|| !target.Equals(source, StringComparison.Ordinal)))
|
||||
{
|
||||
targetItem.SetProviderId(provider, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<AudioBookFile> source, MetadataResult<AudioBookFile> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
|
||||
var sourceItem = source.Item;
|
||||
var targetItem = target.Item;
|
||||
|
||||
if (replaceData || targetItem.Authors.Count == 0)
|
||||
{
|
||||
targetItem.Authors = sourceItem.Authors;
|
||||
}
|
||||
|
||||
if (replaceData || string.IsNullOrEmpty(targetItem.Album))
|
||||
{
|
||||
targetItem.Album = sourceItem.Album;
|
||||
}
|
||||
|
||||
// TODO: Create and register provider specific to book information and AudioBook information
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue