aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Naming/Common/NamingOptions.cs23
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs25
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs28
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs2
-rw-r--r--MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs6
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj1
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioFileProber.cs215
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs172
-rw-r--r--MediaBrowser.Providers/MediaInfo/ProbeProvider.cs (renamed from MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs)62
-rw-r--r--MediaBrowser.Providers/Music/AlbumMetadataService.cs133
-rw-r--r--MediaBrowser.Providers/Music/AudioMetadataService.cs33
11 files changed, 484 insertions, 216 deletions
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 29cc6558c..a72e0ffc7 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -181,6 +181,24 @@ namespace Emby.Naming.Common
"volume"
};
+ ArtistSubfolders = new[]
+ {
+ "albums",
+ "broadcasts",
+ "bootlegs",
+ "compilations",
+ "dj-mixes",
+ "eps",
+ "live",
+ "mixtapes",
+ "others",
+ "remixes",
+ "singles",
+ "soundtracks",
+ "spokenwords",
+ "streets"
+ };
+
AudioFileExtensions = new[]
{
".669",
@@ -745,6 +763,11 @@ namespace Emby.Naming.Common
public string[] AlbumStackingPrefixes { get; set; }
/// <summary>
+ /// Gets or sets list of artist subfolders.
+ /// </summary>
+ public string[] ArtistSubfolders { get; set; }
+
+ /// <summary>
/// Gets or sets list of subtitle file extensions.
/// </summary>
public string[] SubtitleFileExtensions { get; set; }
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
index da00b9cfa..a922e3685 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -18,7 +19,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
/// <summary>
- /// Class MusicAlbumResolver.
+ /// The music album resolver.
/// </summary>
public class MusicAlbumResolver : ItemResolver<MusicAlbum>
{
@@ -82,7 +83,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// </summary>
/// <param name="path">The path to check.</param>
/// <param name="directoryService">The directory service.</param>
- /// <returns><c>true</c> if the provided path points to a music album, <c>false</c> otherwise.</returns>
+ /// <returns><c>true</c> if the provided path points to a music album; otherwise, <c>false</c>.</returns>
public bool IsMusicAlbum(string path, IDirectoryService directoryService)
{
return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService);
@@ -95,10 +96,19 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <returns><c>true</c> if [is music album] [the specified args]; otherwise, <c>false</c>.</returns>
private bool IsMusicAlbum(ItemResolveArgs args)
{
- // Args points to an album if parent is an Artist folder or it directly contains music
if (args.IsDirectory)
{
- // if (args.Parent is MusicArtist) return true; // saves us from testing children twice
+ // If args is a artist subfolder it's not a music album
+ foreach (var subfolder in _namingOptions.ArtistSubfolders)
+ {
+ if (Path.GetDirectoryName(args.Path.AsSpan()).Equals(subfolder, StringComparison.OrdinalIgnoreCase))
+ {
+ _logger.LogDebug("Found release folder: {Path}", args.Path);
+ return false;
+ }
+ }
+
+ // If args contains music it's a music album
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService))
{
return true;
@@ -111,22 +121,23 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <summary>
/// Determine if the supplied list contains what we should consider music.
/// </summary>
+ /// <returns><c>true</c> if the provided path list contains music; otherwise, <c>false</c>.</returns>
private bool ContainsMusic(
ICollection<FileSystemMetadata> list,
bool allowSubfolders,
IDirectoryService directoryService)
{
- // check for audio files before digging down into directories
+ // Check for audio files before digging down into directories
var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && AudioFileParser.IsAudioFile(fileSystemInfo.FullName, _namingOptions));
if (foundAudioFile)
{
- // at least one audio file exists
+ // At least one audio file exists
return true;
}
if (!allowSubfolders)
{
- // not music since no audio file exists and we're not looking into subfolders
+ // Not music since no audio file exists and we're not looking into subfolders
return false;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
index 210ed0953..2538c2b5b 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
@@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
/// <summary>
- /// Class MusicArtistResolver.
+ /// The music artist resolver.
/// </summary>
public class MusicArtistResolver : ItemResolver<MusicArtist>
{
@@ -23,8 +23,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <summary>
/// Initializes a new instance of the <see cref="MusicArtistResolver"/> class.
/// </summary>
- /// <param name="logger">The logger for the created <see cref="MusicAlbumResolver"/> instances.</param>
- /// <param name="namingOptions">The naming options.</param>
+ /// <param name="logger">Instance of the <see cref="MusicAlbumResolver"/> interface.</param>
+ /// <param name="namingOptions">The <see cref="NamingOptions"/>.</param>
public MusicArtistResolver(
ILogger<MusicAlbumResolver> logger,
NamingOptions namingOptions)
@@ -40,10 +40,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
public override ResolverPriority Priority => ResolverPriority.Second;
/// <summary>
- /// Resolves the specified args.
+ /// Resolves the specified resolver arguments.
/// </summary>
- /// <param name="args">The args.</param>
- /// <returns>MusicArtist.</returns>
+ /// <param name="args">The resolver arguments.</param>
+ /// <returns>A <see cref="MusicArtist"/>.</returns>
protected override MusicArtist Resolve(ItemResolveArgs args)
{
if (!args.IsDirectory)
@@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase);
- // If there's a collection type and it's not music, it can't be a series
+ // If there's a collection type and it's not music, it can't be a music artist
if (!isMusicMediaFolder)
{
return null;
@@ -82,14 +82,24 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var albumResolver = new MusicAlbumResolver(_logger, _namingOptions);
- // If we contain an album assume we are an artist folder
var directories = args.FileSystemChildren.Where(i => i.IsDirectory);
var result = Parallel.ForEach(directories, (fileSystemInfo, state) =>
{
+ // If we contain a artist subfolder assume we are an artist folder
+ foreach (var subfolder in _namingOptions.ArtistSubfolders)
+ {
+ if (fileSystemInfo.Name.Equals(subfolder, StringComparison.OrdinalIgnoreCase))
+ {
+ // Stop once we see an artist subfolder
+ state.Stop();
+ }
+ }
+
+ // If we contain a music album assume we are an artist folder
if (albumResolver.IsMusicAlbum(fileSystemInfo.FullName, directoryService))
{
- // stop once we see a music album
+ // Stop once we see a music album
state.Stop();
}
});
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index bd397bdd1..6555de855 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -54,7 +54,7 @@ namespace MediaBrowser.Controller.Entities.Audio
public string AlbumArtist => AlbumArtists.FirstOrDefault();
[JsonIgnore]
- public override bool SupportsPeople => false;
+ public override bool SupportsPeople => true;
/// <summary>
/// Gets the tracks.
diff --git a/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs b/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs
index 32a9cbef2..14428df5b 100644
--- a/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs
+++ b/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs
@@ -18,9 +18,9 @@ namespace MediaBrowser.Controller.Providers
/// Fetches the metadata asynchronously.
/// </summary>
/// <param name="item">The item.</param>
- /// <param name="options">The options.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{ItemUpdateType}.</returns>
+ /// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
+ /// <returns>A <see cref="Task"/> fetching the <see cref="ItemUpdateType"/>.</returns>
Task<ItemUpdateType> FetchAsync(TItemType item, MetadataRefreshOptions options, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 9864db9ac..dd43ba9cb 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -22,6 +22,7 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="OptimizedPriorityQueue" Version="5.1.0" />
<PackageReference Include="PlaylistsNET" Version="1.2.1" />
+ <PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="TMDbLib" Version="1.9.2" />
</ItemGroup>
diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
new file mode 100644
index 000000000..3699e8f49
--- /dev/null
+++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
@@ -0,0 +1,215 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.MediaInfo;
+using TagLib;
+
+namespace MediaBrowser.Providers.MediaInfo
+{
+ /// <summary>
+ /// Probes audio files for metadata.
+ /// </summary>
+ public class AudioFileProber
+ {
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IItemRepository _itemRepo;
+ private readonly ILibraryManager _libraryManager;
+ private readonly IMediaSourceManager _mediaSourceManager;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AudioFileProber"/> class.
+ /// </summary>
+ /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
+ /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
+ /// <param name="itemRepo">Instance of the <see cref="IItemRepository"/> interface.</param>
+ /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
+ public AudioFileProber(
+ IMediaSourceManager mediaSourceManager,
+ IMediaEncoder mediaEncoder,
+ IItemRepository itemRepo,
+ ILibraryManager libraryManager)
+ {
+ _mediaEncoder = mediaEncoder;
+ _itemRepo = itemRepo;
+ _libraryManager = libraryManager;
+ _mediaSourceManager = mediaSourceManager;
+ }
+
+ /// <summary>
+ /// Probes the specified item for metadata.
+ /// </summary>
+ /// <param name="item">The item to probe.</param>
+ /// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
+ /// <typeparam name="T">The type of item to resolve.</typeparam>
+ /// <returns>A <see cref="Task"/> probing the item for metadata.</returns>
+ public async Task<ItemUpdateType> Probe<T>(
+ T item,
+ MetadataRefreshOptions options,
+ CancellationToken cancellationToken)
+ where T : Audio
+ {
+ var path = item.Path;
+ var protocol = item.PathProtocol ?? MediaProtocol.File;
+
+ if (!item.IsShortcut || options.EnableRemoteContentProbe)
+ {
+ if (item.IsShortcut)
+ {
+ path = item.ShortcutPath;
+ protocol = _mediaSourceManager.GetPathProtocol(path);
+ }
+
+ var result = await _mediaEncoder.GetMediaInfo(
+ new MediaInfoRequest
+ {
+ MediaType = DlnaProfileType.Audio,
+ MediaSource = new MediaSourceInfo
+ {
+ Path = path,
+ Protocol = protocol
+ }
+ },
+ cancellationToken).ConfigureAwait(false);
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ Fetch(item, result, cancellationToken);
+ }
+
+ return ItemUpdateType.MetadataImport;
+ }
+
+ /// <summary>
+ /// Fetches the specified audio.
+ /// </summary>
+ /// <param name="audio">The <see cref="Audio"/>.</param>
+ /// <param name="mediaInfo">The <see cref="Model.MediaInfo.MediaInfo"/>.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
+ protected void Fetch(Audio audio, Model.MediaInfo.MediaInfo mediaInfo, CancellationToken cancellationToken)
+ {
+ audio.Container = mediaInfo.Container;
+ audio.TotalBitrate = mediaInfo.Bitrate;
+
+ audio.RunTimeTicks = mediaInfo.RunTimeTicks;
+ audio.Size = mediaInfo.Size;
+
+ FetchDataFromTags(audio);
+
+ _itemRepo.SaveMediaStreams(audio.Id, mediaInfo.MediaStreams, cancellationToken);
+ }
+
+ /// <summary>
+ /// Fetches data from the tags.
+ /// </summary>
+ /// <param name="audio">The <see cref="Audio"/>.</param>
+ private void FetchDataFromTags(Audio audio)
+ {
+ var file = TagLib.File.Create(audio.Path);
+ var tagTypes = file.TagTypesOnDisk;
+ Tag? tags = null;
+
+ if (tagTypes.HasFlag(TagTypes.Id3v2))
+ {
+ tags = file.GetTag(TagTypes.Id3v2);
+ }
+ else if (tagTypes.HasFlag(TagTypes.Ape))
+ {
+ tags = file.GetTag(TagTypes.Ape);
+ }
+ else if (tagTypes.HasFlag(TagTypes.FlacMetadata))
+ {
+ tags = file.GetTag(TagTypes.FlacMetadata);
+ }
+ else if (tagTypes.HasFlag(TagTypes.Apple))
+ {
+ tags = file.GetTag(TagTypes.Apple);
+ }
+ else if (tagTypes.HasFlag(TagTypes.Xiph))
+ {
+ tags = file.GetTag(TagTypes.Xiph);
+ }
+ else if (tagTypes.HasFlag(TagTypes.AudibleMetadata))
+ {
+ tags = file.GetTag(TagTypes.AudibleMetadata);
+ }
+ else if (tagTypes.HasFlag(TagTypes.Id3v1))
+ {
+ tags = file.GetTag(TagTypes.Id3v1);
+ }
+
+ if (tags != null)
+ {
+ if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
+ {
+ var people = new List<PersonInfo>();
+ var albumArtists = tags.AlbumArtists;
+ foreach (var albumArtist in albumArtists)
+ {
+ PeopleHelper.AddPerson(people, new PersonInfo
+ {
+ Name = albumArtist,
+ Type = "AlbumArtist"
+ });
+ }
+
+ var performers = tags.Performers;
+ foreach (var performer in performers)
+ {
+ PeopleHelper.AddPerson(people, new PersonInfo
+ {
+ Name = performer,
+ Type = "Artist"
+ });
+ }
+
+ foreach (var composer in tags.Composers)
+ {
+ PeopleHelper.AddPerson(people, new PersonInfo
+ {
+ Name = composer,
+ Type = "Composer"
+ });
+ }
+
+ _libraryManager.UpdatePeople(audio, people);
+ audio.Artists = performers;
+ audio.AlbumArtists = albumArtists;
+ }
+
+ audio.Name = tags.Title;
+ audio.Album = tags.Album;
+ audio.IndexNumber = Convert.ToInt32(tags.Track);
+ audio.ParentIndexNumber = Convert.ToInt32(tags.Disc);
+ if (tags.Year != 0)
+ {
+ var year = Convert.ToInt32(tags.Year);
+ audio.ProductionYear = year;
+ audio.PremiereDate = new DateTime(year, 01, 01);
+ }
+
+ if (!audio.LockedFields.Contains(MetadataField.Genres))
+ {
+ audio.Genres = tags.Genres.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
+ }
+
+ audio.SetProviderId(MetadataProvider.MusicBrainzArtist, tags.MusicBrainzArtistId);
+ audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, tags.MusicBrainzReleaseArtistId);
+ audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, tags.MusicBrainzReleaseId);
+ audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, tags.MusicBrainzReleaseGroupId);
+ audio.SetProviderId(MetadataProvider.MusicBrainzTrack, tags.MusicBrainzTrackId);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
deleted file mode 100644
index f22965436..000000000
--- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
+++ /dev/null
@@ -1,172 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.MediaInfo;
-
-namespace MediaBrowser.Providers.MediaInfo
-{
- public class FFProbeAudioInfo
- {
- private readonly IMediaEncoder _mediaEncoder;
- private readonly IItemRepository _itemRepo;
- private readonly ILibraryManager _libraryManager;
- private readonly IMediaSourceManager _mediaSourceManager;
-
- public FFProbeAudioInfo(
- IMediaSourceManager mediaSourceManager,
- IMediaEncoder mediaEncoder,
- IItemRepository itemRepo,
- ILibraryManager libraryManager)
- {
- _mediaEncoder = mediaEncoder;
- _itemRepo = itemRepo;
- _libraryManager = libraryManager;
- _mediaSourceManager = mediaSourceManager;
- }
-
- public async Task<ItemUpdateType> Probe<T>(
- T item,
- MetadataRefreshOptions options,
- CancellationToken cancellationToken)
- where T : Audio
- {
- var path = item.Path;
- var protocol = item.PathProtocol ?? MediaProtocol.File;
-
- if (!item.IsShortcut || options.EnableRemoteContentProbe)
- {
- if (item.IsShortcut)
- {
- path = item.ShortcutPath;
- protocol = _mediaSourceManager.GetPathProtocol(path);
- }
-
- var result = await _mediaEncoder.GetMediaInfo(
- new MediaInfoRequest
- {
- MediaType = DlnaProfileType.Audio,
- MediaSource = new MediaSourceInfo
- {
- Path = path,
- Protocol = protocol
- }
- },
- cancellationToken).ConfigureAwait(false);
-
- cancellationToken.ThrowIfCancellationRequested();
-
- Fetch(item, result, cancellationToken);
- }
-
- return ItemUpdateType.MetadataImport;
- }
-
- /// <summary>
- /// Fetches the specified audio.
- /// </summary>
- /// <param name="audio">The audio.</param>
- /// <param name="mediaInfo">The media information.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- protected void Fetch(Audio audio, Model.MediaInfo.MediaInfo mediaInfo, CancellationToken cancellationToken)
- {
- audio.Container = mediaInfo.Container;
- audio.TotalBitrate = mediaInfo.Bitrate;
-
- audio.RunTimeTicks = mediaInfo.RunTimeTicks;
- audio.Size = mediaInfo.Size;
-
- // var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.');
- // audio.Container = extension;
-
- FetchDataFromTags(audio, mediaInfo);
-
- _itemRepo.SaveMediaStreams(audio.Id, mediaInfo.MediaStreams, cancellationToken);
- }
-
- /// <summary>
- /// Fetches data from the tags dictionary.
- /// </summary>
- /// <param name="audio">The audio.</param>
- /// <param name="data">The data.</param>
- private void FetchDataFromTags(Audio audio, Model.MediaInfo.MediaInfo data)
- {
- // Only set Name if title was found in the dictionary
- if (!string.IsNullOrEmpty(data.Name))
- {
- audio.Name = data.Name;
- }
-
- if (!string.IsNullOrEmpty(data.ForcedSortName))
- {
- audio.ForcedSortName = data.ForcedSortName;
- }
-
- if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
- {
- var people = new List<PersonInfo>();
-
- foreach (var person in data.People)
- {
- PeopleHelper.AddPerson(people, new PersonInfo
- {
- Name = person.Name,
- Type = person.Type,
- Role = person.Role
- });
- }
-
- _libraryManager.UpdatePeople(audio, people);
- }
-
- audio.Album = data.Album;
- audio.Artists = data.Artists;
- audio.AlbumArtists = data.AlbumArtists;
- audio.IndexNumber = data.IndexNumber;
- audio.ParentIndexNumber = data.ParentIndexNumber;
- audio.ProductionYear = data.ProductionYear;
- audio.PremiereDate = data.PremiereDate;
-
- // If we don't have a ProductionYear try and get it from PremiereDate
- if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue)
- {
- audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year;
- }
-
- if (!audio.LockedFields.Contains(MetadataField.Genres))
- {
- audio.Genres = Array.Empty<string>();
-
- foreach (var genre in data.Genres)
- {
- audio.AddGenre(genre);
- }
- }
-
- if (!audio.LockedFields.Contains(MetadataField.Studios))
- {
- audio.SetStudios(data.Studios);
- }
-
- audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, data.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist));
- audio.SetProviderId(MetadataProvider.MusicBrainzArtist, data.GetProviderId(MetadataProvider.MusicBrainzArtist));
- audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, data.GetProviderId(MetadataProvider.MusicBrainzAlbum));
- audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, data.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup));
- audio.SetProviderId(MetadataProvider.MusicBrainzTrack, data.GetProviderId(MetadataProvider.MusicBrainzTrack));
- }
- }
-}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
index e58c0e281..659136607 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
@@ -1,7 +1,5 @@
#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.IO;
using System.Linq;
@@ -27,7 +25,10 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.MediaInfo
{
- public class FFProbeProvider : ICustomMetadataProvider<Episode>,
+ /// <summary>
+ /// The probe provider.
+ /// </summary>
+ public class ProbeProvider : ICustomMetadataProvider<Episode>,
ICustomMetadataProvider<MusicVideo>,
ICustomMetadataProvider<Movie>,
ICustomMetadataProvider<Trailer>,
@@ -39,14 +40,30 @@ namespace MediaBrowser.Providers.MediaInfo
IPreRefreshProvider,
IHasItemChangeMonitor
{
- private readonly ILogger<FFProbeProvider> _logger;
+ private readonly ILogger<ProbeProvider> _logger;
private readonly AudioResolver _audioResolver;
private readonly SubtitleResolver _subtitleResolver;
private readonly FFProbeVideoInfo _videoProber;
- private readonly FFProbeAudioInfo _audioProber;
+ private readonly AudioFileProber _audioProber;
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
- public FFProbeProvider(
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ProbeProvider"/> class.
+ /// </summary>
+ /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
+ /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
+ /// <param name="itemRepo">Instance of the <see cref="IItemRepository"/> interface.</param>
+ /// <param name="blurayExaminer">Instance of the <see cref="IBlurayExaminer"/> interface.</param>
+ /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+ /// <param name="encodingManager">Instance of the <see cref="IEncodingManager"/> interface.</param>
+ /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+ /// <param name="subtitleManager">Instance of the <see cref="ISubtitleManager"/> interface.</param>
+ /// <param name="chapterManager">Instance of the <see cref="IChapterManager"/> interface.</param>
+ /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
+ /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/>.</param>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ /// <param name="namingOptions">The <see cref="NamingOptions"/>.</param>
+ public ProbeProvider(
IMediaSourceManager mediaSourceManager,
IMediaEncoder mediaEncoder,
IItemRepository itemRepo,
@@ -61,7 +78,8 @@ namespace MediaBrowser.Providers.MediaInfo
ILoggerFactory loggerFactory,
NamingOptions namingOptions)
{
- _logger = loggerFactory.CreateLogger<FFProbeProvider>();
+ _logger = loggerFactory.CreateLogger<ProbeProvider>();
+ _audioProber = new AudioFileProber(mediaSourceManager, mediaEncoder, itemRepo, libraryManager);
_audioResolver = new AudioResolver(loggerFactory.CreateLogger<AudioResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
_subtitleResolver = new SubtitleResolver(loggerFactory.CreateLogger<SubtitleResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
_videoProber = new FFProbeVideoInfo(
@@ -78,14 +96,15 @@ namespace MediaBrowser.Providers.MediaInfo
libraryManager,
_audioResolver,
_subtitleResolver);
- _audioProber = new FFProbeAudioInfo(mediaSourceManager, mediaEncoder, itemRepo, libraryManager);
}
- public string Name => "ffprobe";
+ /// <inheritdoc />
+ public string Name => "Probe Provider";
- // Run last
+ /// <inheritdoc />
public int Order => 100;
+ /// <inheritdoc />
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
{
var video = item as Video;
@@ -127,41 +146,56 @@ namespace MediaBrowser.Providers.MediaInfo
return false;
}
+ /// <inheritdoc />
public Task<ItemUpdateType> FetchAsync(Episode item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
return FetchVideoInfo(item, options, cancellationToken);
}
+ /// <inheritdoc />
public Task<ItemUpdateType> FetchAsync(MusicVideo item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
return FetchVideoInfo(item, options, cancellationToken);
}
+ /// <inheritdoc />
public Task<ItemUpdateType> FetchAsync(Movie item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
return FetchVideoInfo(item, options, cancellationToken);
}
+ /// <inheritdoc />
public Task<ItemUpdateType> FetchAsync(Trailer item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
return FetchVideoInfo(item, options, cancellationToken);
}
+ /// <inheritdoc />
public Task<ItemUpdateType> FetchAsync(Video item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
return FetchVideoInfo(item, options, cancellationToken);
}
+ /// <inheritdoc />
public Task<ItemUpdateType> FetchAsync(Audio item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
return FetchAudioInfo(item, options, cancellationToken);
}
+ /// <inheritdoc />
public Task<ItemUpdateType> FetchAsync(AudioBook item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
return FetchAudioInfo(item, options, cancellationToken);
}
+ /// <summary>
+ /// Fetches video information for an item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
+ /// <typeparam name="T">The type of item to resolve.</typeparam>
+ /// <returns>A <see cref="Task"/> fetching the <see cref="ItemUpdateType"/> for an item.</returns>
public Task<ItemUpdateType> FetchVideoInfo<T>(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)
where T : Video
{
@@ -208,6 +242,14 @@ namespace MediaBrowser.Providers.MediaInfo
.FirstOrDefault(i => !string.IsNullOrWhiteSpace(i) && !i.StartsWith('#'));
}
+ /// <summary>
+ /// Fetches audio information for an item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
+ /// <typeparam name="T">The type of item to resolve.</typeparam>
+ /// <returns>A <see cref="Task"/> fetching the <see cref="ItemUpdateType"/> for an item.</returns>
public Task<ItemUpdateType> FetchAudioInfo<T>(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)
where T : Audio
{
diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
index 7743d3b27..ac40f0b3a 100644
--- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -15,8 +13,19 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Music
{
+ /// <summary>
+ /// The album metadata service.
+ /// </summary>
public class AlbumMetadataService : MetadataService<MusicAlbum, AlbumInfo>
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AlbumMetadataService"/> 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 AlbumMetadataService(
IServerConfigurationManager serverConfigurationManager,
ILogger<AlbumMetadataService> logger,
@@ -61,40 +70,46 @@ namespace MediaBrowser.Providers.Music
var songs = children.Cast<Audio>().ToArray();
- updateType |= SetAlbumArtistFromSongs(item, songs);
updateType |= SetArtistsFromSongs(item, songs);
+ updateType |= SetAlbumArtistFromSongs(item, songs);
+ updateType |= SetAlbumFromSongs(item, songs);
+ updateType |= SetPeople(item);
}
return updateType;
}
- private ItemUpdateType SetAlbumArtistFromSongs(MusicAlbum item, IEnumerable<Audio> songs)
+ private ItemUpdateType SetAlbumArtistFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs)
{
var updateType = ItemUpdateType.None;
- var artists = songs
+ var albumArtists = songs
.SelectMany(i => i.AlbumArtists)
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .OrderBy(i => i)
+ .GroupBy(i => i)
+ .OrderByDescending(g => g.Count())
+ .Select(g => g.Key)
.ToArray();
- if (!item.AlbumArtists.SequenceEqual(artists, StringComparer.OrdinalIgnoreCase))
+ updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzAlbumArtist);
+
+ if (!item.AlbumArtists.SequenceEqual(albumArtists, StringComparer.OrdinalIgnoreCase))
{
- item.AlbumArtists = artists;
+ item.AlbumArtists = albumArtists;
updateType |= ItemUpdateType.MetadataEdit;
}
return updateType;
}
- private ItemUpdateType SetArtistsFromSongs(MusicAlbum item, IEnumerable<Audio> songs)
+ private ItemUpdateType SetArtistsFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs)
{
var updateType = ItemUpdateType.None;
var artists = songs
.SelectMany(i => i.Artists)
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .OrderBy(i => i)
+ .GroupBy(i => i)
+ .OrderByDescending(g => g.Count())
+ .Select(g => g.Key)
.ToArray();
if (!item.Artists.SequenceEqual(artists, StringComparer.OrdinalIgnoreCase))
@@ -106,6 +121,85 @@ namespace MediaBrowser.Providers.Music
return updateType;
}
+ private ItemUpdateType SetAlbumFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs)
+ {
+ var updateType = ItemUpdateType.None;
+
+ updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzAlbum);
+ updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzReleaseGroup);
+
+ return updateType;
+ }
+
+ private ItemUpdateType SetProviderIdFromSongs(BaseItem item, IReadOnlyList<Audio> songs, MetadataProvider provider)
+ {
+ var ids = songs
+ .Select(i => i.GetProviderId(provider))
+ .GroupBy(i => i)
+ .OrderByDescending(g => g.Count())
+ .Select(g => g.Key)
+ .ToArray();
+
+ var id = item.GetProviderId(provider);
+ if (ids.Any())
+ {
+ var firstId = ids[0];
+ if (!string.IsNullOrEmpty(firstId)
+ && (string.IsNullOrEmpty(id)
+ || !id.Equals(firstId, StringComparison.OrdinalIgnoreCase)))
+ {
+ item.SetProviderId(provider, firstId);
+ return ItemUpdateType.MetadataEdit;
+ }
+ }
+ return ItemUpdateType.None;
+ }
+
+ private void SetProviderId(MusicAlbum sourceItem, MusicAlbum targetItem, MetadataProvider provider)
+ {
+ var source = sourceItem.GetProviderId(provider);
+ var target = targetItem.GetProviderId(provider);
+ if (!string.IsNullOrEmpty(source)
+ && (string.IsNullOrEmpty(target)
+ || !target.Equals(source, StringComparison.Ordinal)))
+ {
+ targetItem.SetProviderId(provider, source);
+ }
+ }
+
+ private ItemUpdateType SetPeople(MusicAlbum item)
+ {
+ var updateType = ItemUpdateType.None;
+
+ if (item.AlbumArtists.Any() || item.Artists.Any())
+ {
+ var people = new List<PersonInfo>();
+
+ foreach (var albumArtist in item.AlbumArtists)
+ {
+ PeopleHelper.AddPerson(people, new PersonInfo
+ {
+ Name = albumArtist,
+ Type = "AlbumArtist"
+ });
+ }
+
+ foreach (var artist in item.Artists)
+ {
+ PeopleHelper.AddPerson(people, new PersonInfo
+ {
+ Name = artist,
+ Type = "Artist"
+ });
+ }
+
+ LibraryManager.UpdatePeople(item, people);
+ updateType |= ItemUpdateType.MetadataEdit;
+ }
+
+ return updateType;
+ }
+
/// <inheritdoc />
protected override void MergeData(
MetadataResult<MusicAlbum> source,
@@ -123,6 +217,21 @@ namespace MediaBrowser.Providers.Music
{
targetItem.Artists = sourceItem.Artists;
}
+
+ if (replaceData || string.IsNullOrEmpty(targetItem.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist)))
+ {
+ SetProviderId(sourceItem, targetItem, MetadataProvider.MusicBrainzAlbumArtist);
+ }
+
+ if (replaceData || string.IsNullOrEmpty(targetItem.GetProviderId(MetadataProvider.MusicBrainzAlbum)))
+ {
+ SetProviderId(sourceItem, targetItem, MetadataProvider.MusicBrainzAlbum);
+ }
+
+ if (replaceData || string.IsNullOrEmpty(targetItem.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup)))
+ {
+ SetProviderId(sourceItem, targetItem, MetadataProvider.MusicBrainzReleaseGroup);
+ }
}
}
}
diff --git a/MediaBrowser.Providers/Music/AudioMetadataService.cs b/MediaBrowser.Providers/Music/AudioMetadataService.cs
index 4577f7745..a5b7cb895 100644
--- a/MediaBrowser.Providers/Music/AudioMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AudioMetadataService.cs
@@ -1,5 +1,4 @@
-#pragma warning disable CS1591
-
+using System;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@@ -11,8 +10,19 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Music
{
+ /// <summary>
+ /// The audio metadata service.
+ /// </summary>
public class AudioMetadataService : MetadataService<Audio, SongInfo>
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AudioMetadataService"/> 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 AudioMetadataService(
IServerConfigurationManager serverConfigurationManager,
ILogger<AudioMetadataService> logger,
@@ -23,6 +33,21 @@ namespace MediaBrowser.Providers.Music
{
}
+ private void SetProviderId(Audio sourceItem, Audio 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<Audio> source, MetadataResult<Audio> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
{
@@ -40,6 +65,10 @@ namespace MediaBrowser.Providers.Music
{
targetItem.Album = sourceItem.Album;
}
+
+ SetProviderId(sourceItem, targetItem, replaceData, MetadataProvider.MusicBrainzAlbumArtist);
+ SetProviderId(sourceItem, targetItem, replaceData, MetadataProvider.MusicBrainzAlbum);
+ SetProviderId(sourceItem, targetItem, replaceData, MetadataProvider.MusicBrainzReleaseGroup);
}
}
}