aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Providers
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Providers')
-rw-r--r--MediaBrowser.Providers/Lyric/LrcLyricProvider.cs220
-rw-r--r--MediaBrowser.Providers/Lyric/LyricManager.cs58
-rw-r--r--MediaBrowser.Providers/Lyric/TxtLyricProvider.cs61
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs2
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj3
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioFileProber.cs215
-rw-r--r--MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs5
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs172
-rw-r--r--MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs5
-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
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs65
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs33
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs33
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs878
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs33
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs325
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs33
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs33
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs33
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs71
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs10
-rw-r--r--MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs21
-rw-r--r--MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs31
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs16
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs15
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs16
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs19
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs14
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs14
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs27
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs16
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs40
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs14
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs17
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs17
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs23
44 files changed, 1472 insertions, 1359 deletions
diff --git a/MediaBrowser.Providers/Lyric/LrcLyricProvider.cs b/MediaBrowser.Providers/Lyric/LrcLyricProvider.cs
new file mode 100644
index 000000000..7b108921b
--- /dev/null
+++ b/MediaBrowser.Providers/Lyric/LrcLyricProvider.cs
@@ -0,0 +1,220 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using LrcParser.Model;
+using LrcParser.Parser;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Lyrics;
+using MediaBrowser.Controller.Resolvers;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.Providers.Lyric;
+
+/// <summary>
+/// LRC Lyric Provider.
+/// </summary>
+public class LrcLyricProvider : ILyricProvider
+{
+ private readonly ILogger<LrcLyricProvider> _logger;
+
+ private readonly LyricParser _lrcLyricParser;
+
+ private static readonly string[] _acceptedTimeFormats = { "HH:mm:ss", "H:mm:ss", "mm:ss", "m:ss" };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LrcLyricProvider"/> class.
+ /// </summary>
+ /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+ public LrcLyricProvider(ILogger<LrcLyricProvider> logger)
+ {
+ _logger = logger;
+ _lrcLyricParser = new LrcParser.Parser.Lrc.LrcParser();
+ }
+
+ /// <inheritdoc />
+ public string Name => "LrcLyricProvider";
+
+ /// <summary>
+ /// Gets the priority.
+ /// </summary>
+ /// <value>The priority.</value>
+ public ResolverPriority Priority => ResolverPriority.First;
+
+ /// <inheritdoc />
+ public IReadOnlyCollection<string> SupportedMediaTypes { get; } = new[] { "lrc", "elrc" };
+
+ /// <summary>
+ /// Opens lyric file for the requested item, and processes it for API return.
+ /// </summary>
+ /// <param name="item">The item to to process.</param>
+ /// <returns>If provider can determine lyrics, returns a <see cref="LyricResponse"/> with or without metadata; otherwise, null.</returns>
+ public async Task<LyricResponse?> GetLyrics(BaseItem item)
+ {
+ string? lyricFilePath = this.GetLyricFilePath(item.Path);
+
+ if (string.IsNullOrEmpty(lyricFilePath))
+ {
+ return null;
+ }
+
+ var fileMetaData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ string lrcFileContent = await File.ReadAllTextAsync(lyricFilePath).ConfigureAwait(false);
+
+ Song lyricData;
+
+ try
+ {
+ lyricData = _lrcLyricParser.Decode(lrcFileContent);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error parsing lyric file {LyricFilePath} from {Provider}", lyricFilePath, Name);
+ return null;
+ }
+
+ List<LrcParser.Model.Lyric> sortedLyricData = lyricData.Lyrics.Where(x => x.TimeTags.Count > 0).OrderBy(x => x.TimeTags.First().Value).ToList();
+
+ // Parse metadata rows
+ var metaDataRows = lyricData.Lyrics
+ .Where(x => x.TimeTags.Count == 0)
+ .Where(x => x.Text.StartsWith('[') && x.Text.EndsWith(']'))
+ .Select(x => x.Text)
+ .ToList();
+
+ foreach (string metaDataRow in metaDataRows)
+ {
+ var index = metaDataRow.IndexOf(':', StringComparison.OrdinalIgnoreCase);
+ if (index == -1)
+ {
+ continue;
+ }
+
+ // Remove square bracket before field name, and after field value
+ // Example 1: [au: 1hitsong]
+ // Example 2: [ar: Calabrese]
+ var metaDataFieldName = GetMetadataFieldName(metaDataRow, index);
+ var metaDataFieldValue = GetMetadataValue(metaDataRow, index);
+
+ if (string.IsNullOrEmpty(metaDataFieldName) || string.IsNullOrEmpty(metaDataFieldValue))
+ {
+ continue;
+ }
+
+ fileMetaData[metaDataFieldName] = metaDataFieldValue;
+ }
+
+ if (sortedLyricData.Count == 0)
+ {
+ return null;
+ }
+
+ List<LyricLine> lyricList = new();
+
+ for (int i = 0; i < sortedLyricData.Count; i++)
+ {
+ var timeData = sortedLyricData[i].TimeTags.First().Value;
+ if (timeData is null)
+ {
+ continue;
+ }
+
+ long ticks = TimeSpan.FromMilliseconds(timeData.Value).Ticks;
+ lyricList.Add(new LyricLine(sortedLyricData[i].Text, ticks));
+ }
+
+ if (fileMetaData.Count != 0)
+ {
+ // Map metaData values from LRC file to LyricMetadata properties
+ LyricMetadata lyricMetadata = MapMetadataValues(fileMetaData);
+
+ return new LyricResponse
+ {
+ Metadata = lyricMetadata,
+ Lyrics = lyricList
+ };
+ }
+
+ return new LyricResponse
+ {
+ Lyrics = lyricList
+ };
+ }
+
+ /// <summary>
+ /// Converts metadata from an LRC file to LyricMetadata properties.
+ /// </summary>
+ /// <param name="metaData">The metadata from the LRC file.</param>
+ /// <returns>A lyricMetadata object with mapped property data.</returns>
+ private static LyricMetadata MapMetadataValues(IDictionary<string, string> metaData)
+ {
+ LyricMetadata lyricMetadata = new();
+
+ if (metaData.TryGetValue("ar", out var artist) && !string.IsNullOrEmpty(artist))
+ {
+ lyricMetadata.Artist = artist;
+ }
+
+ if (metaData.TryGetValue("al", out var album) && !string.IsNullOrEmpty(album))
+ {
+ lyricMetadata.Album = album;
+ }
+
+ if (metaData.TryGetValue("ti", out var title) && !string.IsNullOrEmpty(title))
+ {
+ lyricMetadata.Title = title;
+ }
+
+ if (metaData.TryGetValue("au", out var author) && !string.IsNullOrEmpty(author))
+ {
+ lyricMetadata.Author = author;
+ }
+
+ if (metaData.TryGetValue("length", out var length) && !string.IsNullOrEmpty(length))
+ {
+ if (DateTime.TryParseExact(length, _acceptedTimeFormats, null, DateTimeStyles.None, out var value))
+ {
+ lyricMetadata.Length = value.TimeOfDay.Ticks;
+ }
+ }
+
+ if (metaData.TryGetValue("by", out var by) && !string.IsNullOrEmpty(by))
+ {
+ lyricMetadata.By = by;
+ }
+
+ if (metaData.TryGetValue("offset", out var offset) && !string.IsNullOrEmpty(offset))
+ {
+ if (int.TryParse(offset, out var value))
+ {
+ lyricMetadata.Offset = TimeSpan.FromMilliseconds(value).Ticks;
+ }
+ }
+
+ if (metaData.TryGetValue("re", out var creator) && !string.IsNullOrEmpty(creator))
+ {
+ lyricMetadata.Creator = creator;
+ }
+
+ if (metaData.TryGetValue("ve", out var version) && !string.IsNullOrEmpty(version))
+ {
+ lyricMetadata.Version = version;
+ }
+
+ return lyricMetadata;
+ }
+
+ private static string GetMetadataFieldName(string metaDataRow, int index)
+ {
+ var metadataFieldName = metaDataRow.AsSpan(1, index - 1).Trim();
+ return metadataFieldName.IsEmpty ? string.Empty : metadataFieldName.ToString();
+ }
+
+ private static string GetMetadataValue(string metaDataRow, int index)
+ {
+ var metadataValue = metaDataRow.AsSpan(index + 1, metaDataRow.Length - index - 2).Trim();
+ return metadataValue.IsEmpty ? string.Empty : metadataValue.ToString();
+ }
+}
diff --git a/MediaBrowser.Providers/Lyric/LyricManager.cs b/MediaBrowser.Providers/Lyric/LyricManager.cs
new file mode 100644
index 000000000..f9547e0f0
--- /dev/null
+++ b/MediaBrowser.Providers/Lyric/LyricManager.cs
@@ -0,0 +1,58 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Lyrics;
+
+namespace MediaBrowser.Providers.Lyric;
+
+/// <summary>
+/// Lyric Manager.
+/// </summary>
+public class LyricManager : ILyricManager
+{
+ private readonly ILyricProvider[] _lyricProviders;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LyricManager"/> class.
+ /// </summary>
+ /// <param name="lyricProviders">All found lyricProviders.</param>
+ public LyricManager(IEnumerable<ILyricProvider> lyricProviders)
+ {
+ _lyricProviders = lyricProviders.OrderBy(i => i.Priority).ToArray();
+ }
+
+ /// <inheritdoc />
+ public async Task<LyricResponse?> GetLyrics(BaseItem item)
+ {
+ foreach (ILyricProvider provider in _lyricProviders)
+ {
+ var results = await provider.GetLyrics(item).ConfigureAwait(false);
+ if (results is not null)
+ {
+ return results;
+ }
+ }
+
+ return null;
+ }
+
+ /// <inheritdoc />
+ public bool HasLyricFile(BaseItem item)
+ {
+ foreach (ILyricProvider provider in _lyricProviders)
+ {
+ if (item is null)
+ {
+ continue;
+ }
+
+ if (provider.GetLyricFilePath(item.Path) is not null)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/MediaBrowser.Providers/Lyric/TxtLyricProvider.cs b/MediaBrowser.Providers/Lyric/TxtLyricProvider.cs
new file mode 100644
index 000000000..96a9e9dcf
--- /dev/null
+++ b/MediaBrowser.Providers/Lyric/TxtLyricProvider.cs
@@ -0,0 +1,61 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Lyrics;
+using MediaBrowser.Controller.Resolvers;
+
+namespace MediaBrowser.Providers.Lyric;
+
+/// <summary>
+/// TXT Lyric Provider.
+/// </summary>
+public class TxtLyricProvider : ILyricProvider
+{
+ /// <inheritdoc />
+ public string Name => "TxtLyricProvider";
+
+ /// <summary>
+ /// Gets the priority.
+ /// </summary>
+ /// <value>The priority.</value>
+ public ResolverPriority Priority => ResolverPriority.Second;
+
+ /// <inheritdoc />
+ public IReadOnlyCollection<string> SupportedMediaTypes { get; } = new[] { "lrc", "elrc", "txt" };
+
+ /// <summary>
+ /// Opens lyric file for the requested item, and processes it for API return.
+ /// </summary>
+ /// <param name="item">The item to to process.</param>
+ /// <returns>If provider can determine lyrics, returns a <see cref="LyricResponse"/>; otherwise, null.</returns>
+ public async Task<LyricResponse?> GetLyrics(BaseItem item)
+ {
+ string? lyricFilePath = this.GetLyricFilePath(item.Path);
+
+ if (string.IsNullOrEmpty(lyricFilePath))
+ {
+ return null;
+ }
+
+ string[] lyricTextLines = await File.ReadAllLinesAsync(lyricFilePath).ConfigureAwait(false);
+
+ if (lyricTextLines.Length == 0)
+ {
+ return null;
+ }
+
+ LyricLine[] lyricList = new LyricLine[lyricTextLines.Length];
+
+ for (int lyricLineIndex = 0; lyricLineIndex < lyricTextLines.Length; lyricLineIndex++)
+ {
+ lyricList[lyricLineIndex] = new LyricLine(lyricTextLines[lyricLineIndex]);
+ }
+
+ return new LyricResponse
+ {
+ Lyrics = lyricList
+ };
+ }
+}
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 195c6afae..ac4dc1bc3 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -171,7 +171,7 @@ namespace MediaBrowser.Providers.Manager
}
}
- // thetvdb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons...
+ // TVDb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons...
if (contentType.Equals(MediaTypeNames.Text.Html, StringComparison.OrdinalIgnoreCase))
{
throw new HttpRequestException("Invalid image received.", null, HttpStatusCode.NotFound);
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 9864db9ac..b00c036e5 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -16,12 +16,15 @@
</ItemGroup>
<ItemGroup>
+ <PackageReference Include="LrcParser" Version="2022.529.1" />
+ <PackageReference Include="MetaBrainz.MusicBrainz" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<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/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
index 96d7d139a..d60d829de 100644
--- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
@@ -31,13 +31,14 @@ namespace MediaBrowser.Providers.MediaInfo
"poster",
"folder",
"cover",
- "default"
+ "default",
+ "movie",
+ "show"
};
private static readonly string[] _backdropImageFileNames =
{
"backdrop",
- "fanart",
"background",
"art"
};
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/MediaInfoResolver.cs b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs
index d55cc4491..bb2d584c1 100644
--- a/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs
@@ -120,6 +120,7 @@ namespace MediaBrowser.Providers.MediaInfo
mediaStream.Index = startIndex++;
mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
+ mediaStream.IsHearingImpaired = pathInfo.IsHearingImpaired || mediaStream.IsHearingImpaired;
mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
}
@@ -174,12 +175,12 @@ namespace MediaBrowser.Providers.MediaInfo
return Array.Empty<ExternalPathParserResult>();
}
- var files = directoryService.GetFilePaths(folder, clearCache).ToList();
+ var files = directoryService.GetFilePaths(folder, clearCache, true).ToList();
files.Remove(video.Path);
var internalMetadataPath = video.GetInternalMetadataPath();
if (_fileSystem.DirectoryExists(internalMetadataPath))
{
- files.AddRange(directoryService.GetFilePaths(internalMetadataPath, clearCache));
+ files.AddRange(directoryService.GetFilePaths(internalMetadataPath, clearCache, true));
}
if (!files.Any())
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);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
index 9c27bd7d3..22229e377 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
@@ -1,37 +1,52 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Model.Plugins;
+using MetaBrainz.MusicBrainz;
+
+namespace MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
-namespace MediaBrowser.Providers.Plugins.MusicBrainz
+/// <summary>
+/// MusicBrainz plugin configuration.
+/// </summary>
+public class PluginConfiguration : BasePluginConfiguration
{
- public class PluginConfiguration : BasePluginConfiguration
- {
- private string _server = Plugin.DefaultServer;
+ private const string DefaultServer = "musicbrainz.org";
- private long _rateLimit = Plugin.DefaultRateLimit;
+ private const double DefaultRateLimit = 1.0;
- public string Server
- {
- get => _server;
- set => _server = value.TrimEnd('/');
- }
+ private string _server = DefaultServer;
+
+ private double _rateLimit = DefaultRateLimit;
+
+ /// <summary>
+ /// Gets or sets the server url.
+ /// </summary>
+ public string Server
+ {
+ get => _server;
- public long RateLimit
+ set => _server = value.TrimEnd('/');
+ }
+
+ /// <summary>
+ /// Gets or sets the rate limit.
+ /// </summary>
+ public double RateLimit
+ {
+ get => _rateLimit;
+ set
{
- get => _rateLimit;
- set
+ if (value < DefaultRateLimit && _server == DefaultServer)
{
- if (value < Plugin.DefaultRateLimit && _server == Plugin.DefaultServer)
- {
- _rateLimit = Plugin.DefaultRateLimit;
- }
- else
- {
- _rateLimit = value;
- }
+ _rateLimit = DefaultRateLimit;
+ }
+ else
+ {
+ _rateLimit = value;
}
}
-
- public bool ReplaceArtistName { get; set; }
}
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to replace the artist name.
+ /// </summary>
+ public bool ReplaceArtistName { get; set; }
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs
index c54cdda3d..f7850781e 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs
@@ -1,28 +1,27 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+/// <summary>
+/// MusicBrainz album artist external id.
+/// </summary>
+public class MusicBrainzAlbumArtistExternalId : IExternalId
{
- public class MusicBrainzAlbumArtistExternalId : IExternalId
- {
- /// <inheritdoc />
- public string ProviderName => "MusicBrainz";
+ /// <inheritdoc />
+ public string ProviderName => "MusicBrainz";
- /// <inheritdoc />
- public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString();
+ /// <inheritdoc />
+ public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString();
- /// <inheritdoc />
- public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
+ /// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
- /// <inheritdoc />
- public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+ /// <inheritdoc />
+ public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
- /// <inheritdoc />
- public bool Supports(IHasProviderIds item) => item is Audio;
- }
+ /// <inheritdoc />
+ public bool Supports(IHasProviderIds item) => item is Audio;
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs
index 8f7fadd06..a9d4472e7 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs
@@ -1,28 +1,27 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+/// <summary>
+/// MusicBrainz album external id.
+/// </summary>
+public class MusicBrainzAlbumExternalId : IExternalId
{
- public class MusicBrainzAlbumExternalId : IExternalId
- {
- /// <inheritdoc />
- public string ProviderName => "MusicBrainz";
+ /// <inheritdoc />
+ public string ProviderName => "MusicBrainz";
- /// <inheritdoc />
- public string Key => MetadataProvider.MusicBrainzAlbum.ToString();
+ /// <inheritdoc />
+ public string Key => MetadataProvider.MusicBrainzAlbum.ToString();
- /// <inheritdoc />
- public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
+ /// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
- /// <inheritdoc />
- public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
+ /// <inheritdoc />
+ public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/release/{0}";
- /// <inheritdoc />
- public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
- }
+ /// <inheritdoc />
+ public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
index 915fb97fd..4d9feca6d 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
@@ -1,805 +1,265 @@
-#nullable disable
-
-#pragma warning disable CS1591, SA1401
-
using System;
using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using System.IO;
using System.Linq;
-using System.Net;
using System.Net.Http;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml;
-using MediaBrowser.Common.Net;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Providers.Music
-{
- public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, AlbumInfo>, IHasOrder, IDisposable
- {
- /// <summary>
- /// For each single MB lookup/search, this is the maximum number of
- /// attempts that shall be made whilst receiving a 503 Server
- /// Unavailable (indicating throttled) response.
- /// </summary>
- private const uint MusicBrainzQueryAttempts = 5u;
-
- /// <summary>
- /// The Jellyfin user-agent is unrestricted but source IP must not exceed
- /// one request per second, therefore we rate limit to avoid throttling.
- /// Be prudent, use a value slightly above the minimum required.
- /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting.
- /// </summary>
- private readonly long _musicBrainzQueryIntervalMs;
-
- private readonly IHttpClientFactory _httpClientFactory;
- private readonly ILogger<MusicBrainzAlbumProvider> _logger;
-
- private readonly string _musicBrainzBaseUrl;
-
- private SemaphoreSlim _apiRequestLock = new SemaphoreSlim(1, 1);
- private Stopwatch _stopWatchMusicBrainz = new Stopwatch();
-
- public MusicBrainzAlbumProvider(
- IHttpClientFactory httpClientFactory,
- ILogger<MusicBrainzAlbumProvider> logger)
- {
- _httpClientFactory = httpClientFactory;
- _logger = logger;
-
- _musicBrainzBaseUrl = Plugin.Instance.Configuration.Server;
- _musicBrainzQueryIntervalMs = Plugin.Instance.Configuration.RateLimit;
-
- // Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit
- _stopWatchMusicBrainz.Start();
-
- Current = this;
- }
-
- internal static MusicBrainzAlbumProvider Current { get; private set; }
-
- /// <inheritdoc />
- public string Name => "MusicBrainz";
+using MediaBrowser.Providers.Music;
+using MetaBrainz.MusicBrainz;
+using MetaBrainz.MusicBrainz.Interfaces.Entities;
+using MetaBrainz.MusicBrainz.Interfaces.Searches;
- /// <inheritdoc />
- public int Order => 0;
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
- /// <inheritdoc />
- public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken)
- {
- var releaseId = searchInfo.GetReleaseId();
- var releaseGroupId = searchInfo.GetReleaseGroupId();
-
- string url;
-
- if (!string.IsNullOrEmpty(releaseId))
- {
- url = "/ws/2/release/?query=reid:" + releaseId.ToString(CultureInfo.InvariantCulture);
- }
- else if (!string.IsNullOrEmpty(releaseGroupId))
- {
- url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture);
- }
- else
- {
- var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId();
-
- if (!string.IsNullOrWhiteSpace(artistMusicBrainzId))
- {
- url = string.Format(
- CultureInfo.InvariantCulture,
- "/ws/2/release/?query=\"{0}\" AND arid:{1}",
- WebUtility.UrlEncode(searchInfo.Name),
- artistMusicBrainzId);
- }
- else
- {
- // I'm sure there is a better way but for now it resolves search for 12" Mixes
- var queryName = searchInfo.Name.Replace("\"", string.Empty, StringComparison.Ordinal);
-
- url = string.Format(
- CultureInfo.InvariantCulture,
- "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
- WebUtility.UrlEncode(queryName),
- WebUtility.UrlEncode(searchInfo.GetAlbumArtist()));
- }
- }
-
- if (!string.IsNullOrWhiteSpace(url))
- {
- using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return GetResultsFromResponse(stream);
- }
-
- return Enumerable.Empty<RemoteSearchResult>();
- }
+/// <summary>
+/// Music album metadata provider for MusicBrainz.
+/// </summary>
+public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, AlbumInfo>, IHasOrder, IDisposable
+{
+ private readonly Query _musicBrainzQuery;
- private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream)
- {
- using var oReader = new StreamReader(stream, Encoding.UTF8);
- var settings = new XmlReaderSettings()
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MusicBrainzAlbumProvider"/> class.
+ /// </summary>
+ public MusicBrainzAlbumProvider()
+ {
+ MusicBrainz.Plugin.Instance!.ConfigurationChanged += (_, _) =>
{
- ValidationType = ValidationType.None,
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true
+ Query.DefaultServer = MusicBrainz.Plugin.Instance.Configuration.Server;
+ Query.DelayBetweenRequests = MusicBrainz.Plugin.Instance.Configuration.RateLimit;
};
- using var reader = XmlReader.Create(oReader, settings);
- var results = ReleaseResult.Parse(reader);
-
- return results.Select(i =>
- {
- var result = new RemoteSearchResult
- {
- Name = i.Title,
- ProductionYear = i.Year
- };
+ _musicBrainzQuery = new Query();
+ }
- if (i.Artists.Count > 0)
- {
- result.AlbumArtist = new RemoteSearchResult
- {
- SearchProviderName = Name,
- Name = i.Artists[0].Item1
- };
+ /// <inheritdoc />
+ public string Name => "MusicBrainz";
- result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2);
- }
+ /// <inheritdoc />
+ public int Order => 0;
- if (!string.IsNullOrWhiteSpace(i.ReleaseId))
- {
- result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId);
- }
-
- if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId))
- {
- result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId);
- }
+ /// <inheritdoc />
+ public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken)
+ {
+ var releaseId = searchInfo.GetReleaseId();
+ var releaseGroupId = searchInfo.GetReleaseGroupId();
- return result;
- });
+ if (!string.IsNullOrEmpty(releaseId))
+ {
+ var releaseResult = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.ReleaseGroups, cancellationToken).ConfigureAwait(false);
+ return GetReleaseResult(releaseResult).SingleItemAsEnumerable();
}
- /// <inheritdoc />
- public async Task<MetadataResult<MusicAlbum>> GetMetadata(AlbumInfo info, CancellationToken cancellationToken)
+ if (!string.IsNullOrEmpty(releaseGroupId))
{
- var releaseId = info.GetReleaseId();
- var releaseGroupId = info.GetReleaseGroupId();
-
- var result = new MetadataResult<MusicAlbum>
- {
- Item = new MusicAlbum()
- };
-
- // If we have a release group Id but not a release Id...
- if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId))
- {
- releaseId = await GetReleaseIdFromReleaseGroupId(releaseGroupId, cancellationToken).ConfigureAwait(false);
- result.HasMetadata = true;
- }
-
- if (string.IsNullOrWhiteSpace(releaseId))
- {
- var artistMusicBrainzId = info.GetMusicBrainzArtistId();
-
- var releaseResult = await GetReleaseResult(artistMusicBrainzId, info.GetAlbumArtist(), info.Name, cancellationToken).ConfigureAwait(false);
-
- if (releaseResult != null)
- {
- if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseId))
- {
- releaseId = releaseResult.ReleaseId;
- result.HasMetadata = true;
- }
-
- if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseGroupId))
- {
- releaseGroupId = releaseResult.ReleaseGroupId;
- result.HasMetadata = true;
- }
-
- result.Item.ProductionYear = releaseResult.Year;
- result.Item.Overview = releaseResult.Overview;
- }
- }
+ var releaseGroupResult = await _musicBrainzQuery.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.None, null, cancellationToken).ConfigureAwait(false);
+ return GetReleaseGroupResult(releaseGroupResult.Releases);
+ }
- // If we have a release Id but not a release group Id...
- if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId))
- {
- releaseGroupId = await GetReleaseGroupFromReleaseId(releaseId, cancellationToken).ConfigureAwait(false);
- result.HasMetadata = true;
- }
+ var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId();
- if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId))
- {
- result.HasMetadata = true;
- }
+ if (!string.IsNullOrWhiteSpace(artistMusicBrainzId))
+ {
+ var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{searchInfo.Name}\" AND arid:{artistMusicBrainzId}", null, null, false, cancellationToken)
+ .ConfigureAwait(false);
- if (result.HasMetadata)
+ if (releaseSearchResults.Results.Count > 0)
{
- if (!string.IsNullOrEmpty(releaseId))
- {
- result.Item.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseId);
- }
-
- if (!string.IsNullOrEmpty(releaseGroupId))
- {
- result.Item.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseGroupId);
- }
+ return GetReleaseSearchResult(releaseSearchResults.Results);
}
-
- return result;
}
-
- private Task<ReleaseResult> GetReleaseResult(string artistMusicBrainId, string artistName, string albumName, CancellationToken cancellationToken)
+ else
{
- if (!string.IsNullOrEmpty(artistMusicBrainId))
- {
- return GetReleaseResult(albumName, artistMusicBrainId, cancellationToken);
- }
+ // I'm sure there is a better way but for now it resolves search for 12" Mixes
+ var queryName = searchInfo.Name.Replace("\"", string.Empty, StringComparison.Ordinal);
- if (string.IsNullOrWhiteSpace(artistName))
+ var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{queryName}\" AND artist:\"{searchInfo.GetAlbumArtist()}\"c", null, null, false, cancellationToken)
+ .ConfigureAwait(false);
+
+ if (releaseSearchResults.Results.Count > 0)
{
- return Task.FromResult(new ReleaseResult());
+ return GetReleaseSearchResult(releaseSearchResults.Results);
}
-
- return GetReleaseResultByArtistName(albumName, artistName, cancellationToken);
}
- private async Task<ReleaseResult> GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken)
- {
- var url = string.Format(
- CultureInfo.InvariantCulture,
- "/ws/2/release/?query=\"{0}\" AND arid:{1}",
- WebUtility.UrlEncode(albumName),
- artistId);
-
- using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- using var oReader = new StreamReader(stream, Encoding.UTF8);
- var settings = new XmlReaderSettings
- {
- ValidationType = ValidationType.None,
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true
- };
+ return Enumerable.Empty<RemoteSearchResult>();
+ }
- using var reader = XmlReader.Create(oReader, settings);
- return ReleaseResult.Parse(reader).FirstOrDefault();
+ private IEnumerable<RemoteSearchResult> GetReleaseSearchResult(IEnumerable<ISearchResult<IRelease>>? releaseSearchResults)
+ {
+ if (releaseSearchResults is null)
+ {
+ yield break;
}
- private async Task<ReleaseResult> GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken)
+ foreach (var result in releaseSearchResults)
{
- var url = string.Format(
- CultureInfo.InvariantCulture,
- "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
- WebUtility.UrlEncode(albumName),
- WebUtility.UrlEncode(artistName));
-
- using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- using var oReader = new StreamReader(stream, Encoding.UTF8);
- var settings = new XmlReaderSettings()
- {
- ValidationType = ValidationType.None,
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true
- };
-
- using var reader = XmlReader.Create(oReader, settings);
- return ReleaseResult.Parse(reader).FirstOrDefault();
+ yield return GetReleaseResult(result.Item);
}
+ }
- private static (string Name, string ArtistId) ParseArtistCredit(XmlReader reader)
+ private IEnumerable<RemoteSearchResult> GetReleaseGroupResult(IEnumerable<IRelease>? releaseSearchResults)
+ {
+ if (releaseSearchResults is null)
{
- reader.MoveToContent();
- reader.Read();
-
- // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "name-credit":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- break;
- }
-
- using var subReader = reader.ReadSubtree();
- return ParseArtistNameCredit(subReader);
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
-
- return default;
+ yield break;
}
- private static (string Name, string ArtistId) ParseArtistNameCredit(XmlReader reader)
+ foreach (var result in releaseSearchResults)
{
- reader.MoveToContent();
- reader.Read();
-
- // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "artist":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- break;
- }
-
- var id = reader.GetAttribute("id");
- using var subReader = reader.ReadSubtree();
- return ParseArtistArtistCredit(subReader, id);
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
-
- return (null, null);
+ yield return GetReleaseResult(result);
}
+ }
- private static (string Name, string ArtistId) ParseArtistArtistCredit(XmlReader reader, string artistId)
+ private RemoteSearchResult GetReleaseResult(IRelease releaseSearchResult)
+ {
+ var searchResult = new RemoteSearchResult
{
- reader.MoveToContent();
- reader.Read();
-
- string name = null;
+ Name = releaseSearchResult.Title,
+ ProductionYear = releaseSearchResult.Date?.Year,
+ PremiereDate = releaseSearchResult.Date?.NearestDate
+ };
- // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
+ if (releaseSearchResult.ArtistCredit?.Count > 0)
+ {
+ searchResult.AlbumArtist = new RemoteSearchResult
+ {
+ SearchProviderName = Name,
+ Name = releaseSearchResult.ArtistCredit[0].Name
+ };
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ if (releaseSearchResult.ArtistCredit[0].Artist?.Id is not null)
{
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "name":
- {
- name = reader.ReadElementContentAsString();
- break;
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
+ searchResult.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, releaseSearchResult.ArtistCredit[0].Artist!.Id.ToString());
}
-
- return (name, artistId);
}
- private async Task<string> GetReleaseIdFromReleaseGroupId(string releaseGroupId, CancellationToken cancellationToken)
- {
- var url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture);
-
- using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- using var oReader = new StreamReader(stream, Encoding.UTF8);
- var settings = new XmlReaderSettings
- {
- ValidationType = ValidationType.None,
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true
- };
-
- using var reader = XmlReader.Create(oReader, settings);
- var result = ReleaseResult.Parse(reader).FirstOrDefault();
+ searchResult.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseSearchResult.Id.ToString());
- return result?.ReleaseId;
+ if (releaseSearchResult.ReleaseGroup?.Id is not null)
+ {
+ searchResult.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseSearchResult.ReleaseGroup.Id.ToString());
}
- /// <summary>
- /// Gets the release group id internal.
- /// </summary>
- /// <param name="releaseEntryId">The release entry id.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{System.String}.</returns>
- private async Task<string> GetReleaseGroupFromReleaseId(string releaseEntryId, CancellationToken cancellationToken)
- {
- var url = "/ws/2/release-group/?query=reid:" + releaseEntryId.ToString(CultureInfo.InvariantCulture);
+ return searchResult;
+ }
- using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- using var oReader = new StreamReader(stream, Encoding.UTF8);
- var settings = new XmlReaderSettings
- {
- ValidationType = ValidationType.None,
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true,
- Async = true
- };
+ /// <inheritdoc />
+ public async Task<MetadataResult<MusicAlbum>> GetMetadata(AlbumInfo info, CancellationToken cancellationToken)
+ {
+ // TODO: This sets essentially nothing. As-is, it's mostly useless. Make it actually pull metadata and use it.
+ var releaseId = info.GetReleaseId();
+ var releaseGroupId = info.GetReleaseGroupId();
- using var reader = XmlReader.Create(oReader, settings);
- await reader.MoveToContentAsync().ConfigureAwait(false);
- await reader.ReadAsync().ConfigureAwait(false);
+ var result = new MetadataResult<MusicAlbum>
+ {
+ Item = new MusicAlbum()
+ };
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ // If there is a release group, but no release ID, try to match the release
+ if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId))
+ {
+ // TODO: Actually try to match the release. Simply taking the first result is stupid.
+ var releaseGroup = await _musicBrainzQuery.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.None, null, cancellationToken).ConfigureAwait(false);
+ var release = releaseGroup.Releases?.Count > 0 ? releaseGroup.Releases[0] : null;
+ if (release != null)
{
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "release-group-list":
- {
- if (reader.IsEmptyElement)
- {
- await reader.ReadAsync().ConfigureAwait(false);
- continue;
- }
-
- using var subReader = reader.ReadSubtree();
- return GetFirstReleaseGroupId(subReader);
- }
-
- default:
- {
- await reader.SkipAsync().ConfigureAwait(false);
- break;
- }
- }
- }
- else
- {
- await reader.ReadAsync().ConfigureAwait(false);
- }
+ releaseId = release.Id.ToString();
+ result.HasMetadata = true;
}
-
- return null;
}
- private string GetFirstReleaseGroupId(XmlReader reader)
+ // If there is no release ID, lookup a release with the info we have
+ if (string.IsNullOrWhiteSpace(releaseId))
{
- reader.MoveToContent();
- reader.Read();
+ var artistMusicBrainzId = info.GetMusicBrainzArtistId();
+ IRelease? releaseResult = null;
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ if (!string.IsNullOrEmpty(artistMusicBrainzId))
{
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "release-group":
- {
- return reader.GetAttribute("id");
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
+ var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{info.Name}\" AND arid:{artistMusicBrainzId}", null, null, false, cancellationToken)
+ .ConfigureAwait(false);
+ releaseResult = releaseSearchResults.Results.Count > 0 ? releaseSearchResults.Results[0].Item : null;
}
-
- return null;
- }
-
- /// <summary>
- /// Makes request to MusicBrainz server and awaits a response.
- /// A 503 Service Unavailable response indicates throttling to maintain a rate limit.
- /// A number of retries shall be made in order to try and satisfy the request before
- /// giving up and returning null.
- /// </summary>
- /// <param name="url">Address of MusicBrainz server.</param>
- /// <param name="cancellationToken">CancellationToken to use for method.</param>
- /// <returns>Returns response from MusicBrainz service.</returns>
- internal async Task<HttpResponseMessage> GetMusicBrainzResponse(string url, CancellationToken cancellationToken)
- {
- await _apiRequestLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
+ else if (!string.IsNullOrEmpty(info.GetAlbumArtist()))
{
- HttpResponseMessage response;
- var attempts = 0u;
- var requestUrl = _musicBrainzBaseUrl.TrimEnd('/') + url;
+ var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{info.Name}\" AND artist:{info.GetAlbumArtist()}", null, null, false, cancellationToken)
+ .ConfigureAwait(false);
+ releaseResult = releaseSearchResults.Results.Count > 0 ? releaseSearchResults.Results[0].Item : null;
+ }
- do
- {
- attempts++;
-
- if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs)
- {
- // MusicBrainz is extremely adamant about limiting to one request per second.
- var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds;
- await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false);
- }
-
- // Write time since last request to debug log as evidence we're meeting rate limit
- // requirement, before resetting stopwatch back to zero.
- _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds);
- _stopWatchMusicBrainz.Restart();
-
- using var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
- response = await _httpClientFactory
- .CreateClient(NamedClient.MusicBrainz)
- .SendAsync(request, cancellationToken)
- .ConfigureAwait(false);
-
- // We retry a finite number of times, and only whilst MB is indicating 503 (throttling).
- }
- while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable);
+ if (releaseResult != null)
+ {
+ releaseId = releaseResult.Id.ToString();
- // Log error if unable to query MB database due to throttling.
- if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable)
+ if (releaseResult.ReleaseGroup?.Id is not null)
{
- _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, requestUrl);
+ releaseGroupId = releaseResult.ReleaseGroup.Id.ToString();
}
- return response;
- }
- finally
- {
- _apiRequestLock.Release();
+ result.HasMetadata = true;
+ result.Item.ProductionYear = releaseResult.Date?.Year;
+ result.Item.Overview = releaseResult.Annotation;
}
}
- /// <inheritdoc />
- public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
+ // If we have a release ID but not a release group ID, lookup the release group
+ if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId))
{
- throw new NotImplementedException();
+ var release = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.Releases, cancellationToken).ConfigureAwait(false);
+ releaseGroupId = release.ReleaseGroup?.Id.ToString();
+ result.HasMetadata = true;
}
- protected virtual void Dispose(bool disposing)
+ // If we have a release ID and a release group ID
+ if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId))
{
- if (disposing)
- {
- _apiRequestLock?.Dispose();
- }
+ result.HasMetadata = true;
}
- /// <inheritdoc />
- public void Dispose()
+ if (result.HasMetadata)
{
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- private class ReleaseResult
- {
- public string ReleaseId;
- public string ReleaseGroupId;
- public string Title;
- public string Overview;
- public int? Year;
-
- public List<(string, string)> Artists = new();
-
- public static IEnumerable<ReleaseResult> Parse(XmlReader reader)
+ if (!string.IsNullOrEmpty(releaseId))
{
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "release-list":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
-
- using var subReader = reader.ReadSubtree();
- return ParseReleaseList(subReader).ToList();
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
-
- return Enumerable.Empty<ReleaseResult>();
+ result.Item.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseId);
}
- private static IEnumerable<ReleaseResult> ParseReleaseList(XmlReader reader)
+ if (!string.IsNullOrEmpty(releaseGroupId))
{
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "release":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
-
- var releaseId = reader.GetAttribute("id");
-
- using var subReader = reader.ReadSubtree();
- var release = ParseRelease(subReader, releaseId);
- if (release != null)
- {
- yield return release;
- }
-
- break;
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
+ result.Item.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseGroupId);
}
+ }
- private static ReleaseResult ParseRelease(XmlReader reader, string releaseId)
- {
- var result = new ReleaseResult
- {
- ReleaseId = releaseId
- };
-
- reader.MoveToContent();
- reader.Read();
+ return result;
+ }
- // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
+ /// <inheritdoc />
+ public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "title":
- {
- result.Title = reader.ReadElementContentAsString();
- break;
- }
-
- case "date":
- {
- var val = reader.ReadElementContentAsString();
- if (DateTime.TryParse(val, out var date))
- {
- result.Year = date.Year;
- }
-
- break;
- }
-
- case "annotation":
- {
- result.Overview = reader.ReadElementContentAsString();
- break;
- }
-
- case "release-group":
- {
- result.ReleaseGroupId = reader.GetAttribute("id");
- reader.Skip();
- break;
- }
-
- case "artist-credit":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- break;
- }
-
- using var subReader = reader.ReadSubtree();
- var artist = ParseArtistCredit(subReader);
-
- if (!string.IsNullOrEmpty(artist.Name))
- {
- result.Artists.Add(artist);
- }
-
- break;
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
- return result;
- }
+ /// <summary>
+ /// Dispose all resources.
+ /// </summary>
+ /// <param name="disposing">Whether to dispose.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _musicBrainzQuery.Dispose();
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
index 941ffea72..b89e67270 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
@@ -1,28 +1,27 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+/// <summary>
+/// MusicBrainz artist external id.
+/// </summary>
+public class MusicBrainzArtistExternalId : IExternalId
{
- public class MusicBrainzArtistExternalId : IExternalId
- {
- /// <inheritdoc />
- public string ProviderName => "MusicBrainz";
+ /// <inheritdoc />
+ public string ProviderName => "MusicBrainz";
- /// <inheritdoc />
- public string Key => MetadataProvider.MusicBrainzArtist.ToString();
+ /// <inheritdoc />
+ public string Key => MetadataProvider.MusicBrainzArtist.ToString();
- /// <inheritdoc />
- public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
+ /// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
- /// <inheritdoc />
- public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+ /// <inheritdoc />
+ public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
- /// <inheritdoc />
- public bool Supports(IHasProviderIds item) => item is MusicArtist;
- }
+ /// <inheritdoc />
+ public bool Supports(IHasProviderIds item) => item is MusicArtist;
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
index 906a42f36..2cc3a13be 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
@@ -1,15 +1,7 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
using System.Linq;
-using System.Net;
using System.Net.Http;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
@@ -18,257 +10,152 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-
-namespace MediaBrowser.Providers.Music
-{
- public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>
- {
- public string Name => "MusicBrainz";
+using MediaBrowser.Providers.Music;
+using MetaBrainz.MusicBrainz;
+using MetaBrainz.MusicBrainz.Interfaces.Entities;
+using MetaBrainz.MusicBrainz.Interfaces.Searches;
- /// <inheritdoc />
- public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
- {
- var musicBrainzId = searchInfo.GetMusicBrainzArtistId();
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
- if (!string.IsNullOrWhiteSpace(musicBrainzId))
- {
- var url = "/ws/2/artist/?query=arid:{0}" + musicBrainzId.ToString(CultureInfo.InvariantCulture);
+/// <summary>
+/// MusicBrainz artist provider.
+/// </summary>
+public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable
+{
+ private readonly Query _musicBrainzQuery;
- using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return GetResultsFromResponse(stream);
- }
- else
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MusicBrainzArtistProvider"/> class.
+ /// </summary>
+ public MusicBrainzArtistProvider()
+ {
+ MusicBrainz.Plugin.Instance!.ConfigurationChanged += (_, _) =>
{
- // They seem to throw bad request failures on any term with a slash
- var nameToSearch = searchInfo.Name.Replace('/', ' ');
-
- var url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch));
-
- using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
- await using (var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false))
- {
- var results = GetResultsFromResponse(stream).ToList();
+ Query.DefaultServer = MusicBrainz.Plugin.Instance.Configuration.Server;
+ Query.DelayBetweenRequests = MusicBrainz.Plugin.Instance.Configuration.RateLimit;
+ };
- if (results.Count > 0)
- {
- return results;
- }
- }
+ _musicBrainzQuery = new Query();
+ }
- if (searchInfo.Name.HasDiacritics())
- {
- // Try again using the search with accent characters url
- url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
+ /// <inheritdoc />
+ public string Name => "MusicBrainz";
- using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return GetResultsFromResponse(stream);
- }
- }
+ /// <inheritdoc />
+ public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
+ {
+ var artistId = searchInfo.GetMusicBrainzArtistId();
- return Enumerable.Empty<RemoteSearchResult>();
+ if (!string.IsNullOrWhiteSpace(artistId))
+ {
+ var artistResult = await _musicBrainzQuery.LookupArtistAsync(new Guid(artistId), Include.Aliases, null, null, cancellationToken).ConfigureAwait(false);
+ return GetResultFromResponse(artistResult).SingleItemAsEnumerable();
}
- private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream)
+ var artistSearchResults = await _musicBrainzQuery.FindArtistsAsync($"\"{searchInfo.Name}\"", null, null, false, cancellationToken)
+ .ConfigureAwait(false);
+ if (artistSearchResults.Results.Count > 0)
{
- using var oReader = new StreamReader(stream, Encoding.UTF8);
- var settings = new XmlReaderSettings()
- {
- ValidationType = ValidationType.None,
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true
- };
-
- using var reader = XmlReader.Create(oReader, settings);
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "artist-list":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
-
- using var subReader = reader.ReadSubtree();
- return ParseArtistList(subReader).ToList();
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
-
- return Enumerable.Empty<RemoteSearchResult>();
+ return GetResultsFromResponse(artistSearchResults.Results);
}
- private IEnumerable<RemoteSearchResult> ParseArtistList(XmlReader reader)
+ if (searchInfo.Name.HasDiacritics())
{
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ // Try again using the search with an accented characters query
+ var artistAccentsSearchResults = await _musicBrainzQuery.FindArtistsAsync($"artistaccent:\"{searchInfo.Name}\"", null, null, false, cancellationToken)
+ .ConfigureAwait(false);
+ if (artistAccentsSearchResults.Results.Count > 0)
{
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "artist":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
-
- var mbzId = reader.GetAttribute("id");
-
- using var subReader = reader.ReadSubtree();
- var artist = ParseArtist(subReader, mbzId);
- if (artist != null)
- {
- yield return artist;
- }
-
- break;
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
+ return GetResultsFromResponse(artistAccentsSearchResults.Results);
}
}
- private RemoteSearchResult ParseArtist(XmlReader reader, string artistId)
- {
- var result = new RemoteSearchResult();
-
- reader.MoveToContent();
- reader.Read();
+ return Enumerable.Empty<RemoteSearchResult>();
+ }
- // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
+ private IEnumerable<RemoteSearchResult> GetResultsFromResponse(IEnumerable<ISearchResult<IArtist>>? releaseSearchResults)
+ {
+ if (releaseSearchResults is null)
+ {
+ yield break;
+ }
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "name":
- {
- result.Name = reader.ReadElementContentAsString();
- break;
- }
+ foreach (var result in releaseSearchResults)
+ {
+ yield return GetResultFromResponse(result.Item);
+ }
+ }
- case "annotation":
- {
- result.Overview = reader.ReadElementContentAsString();
- break;
- }
+ private RemoteSearchResult GetResultFromResponse(IArtist artist)
+ {
+ var searchResult = new RemoteSearchResult
+ {
+ Name = artist.Name,
+ ProductionYear = artist.LifeSpan?.Begin?.Year,
+ PremiereDate = artist.LifeSpan?.Begin?.NearestDate
+ };
- default:
- {
- // there is sort-name if ever needed
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
+ searchResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Id.ToString());
- result.SetProviderId(MetadataProvider.MusicBrainzArtist, artistId);
+ return searchResult;
+ }
- if (string.IsNullOrWhiteSpace(artistId) || string.IsNullOrWhiteSpace(result.Name))
- {
- return null;
- }
+ /// <inheritdoc />
+ public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo info, CancellationToken cancellationToken)
+ {
+ var result = new MetadataResult<MusicArtist> { Item = new MusicArtist() };
- return result;
- }
+ var musicBrainzId = info.GetMusicBrainzArtistId();
- /// <inheritdoc />
- public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo info, CancellationToken cancellationToken)
+ if (string.IsNullOrWhiteSpace(musicBrainzId))
{
- var result = new MetadataResult<MusicArtist>
- {
- Item = new MusicArtist()
- };
+ var searchResults = await GetSearchResults(info, cancellationToken).ConfigureAwait(false);
- var musicBrainzId = info.GetMusicBrainzArtistId();
+ var singleResult = searchResults.FirstOrDefault();
- if (string.IsNullOrWhiteSpace(musicBrainzId))
+ if (singleResult != null)
{
- var searchResults = await GetSearchResults(info, cancellationToken).ConfigureAwait(false);
+ musicBrainzId = singleResult.GetProviderId(MetadataProvider.MusicBrainzArtist);
+ result.Item.Overview = singleResult.Overview;
- var singleResult = searchResults.FirstOrDefault();
-
- if (singleResult != null)
+ if (Plugin.Instance!.Configuration.ReplaceArtistName)
{
- musicBrainzId = singleResult.GetProviderId(MetadataProvider.MusicBrainzArtist);
- result.Item.Overview = singleResult.Overview;
-
- if (Plugin.Instance.Configuration.ReplaceArtistName)
- {
- result.Item.Name = singleResult.Name;
- }
+ result.Item.Name = singleResult.Name;
}
}
-
- if (!string.IsNullOrWhiteSpace(musicBrainzId))
- {
- result.HasMetadata = true;
- result.Item.SetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzId);
- }
-
- return result;
}
- /// <summary>
- /// Encodes an URL.
- /// </summary>
- /// <param name="name">The name.</param>
- /// <returns>System.String.</returns>
- private static string UrlEncode(string name)
+ if (!string.IsNullOrWhiteSpace(musicBrainzId))
{
- return WebUtility.UrlEncode(name);
+ result.HasMetadata = true;
+ result.Item.SetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzId);
}
- public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
+ return result;
+ }
+
+ /// <inheritdoc />
+ public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Dispose all resources.
+ /// </summary>
+ /// <param name="disposing">Whether to dispose.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
{
- throw new NotImplementedException();
+ _musicBrainzQuery.Dispose();
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs
index 05db2d98f..fdaa5574f 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs
@@ -1,28 +1,27 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+/// <summary>
+/// MusicBrainz other artist external id.
+/// </summary>
+public class MusicBrainzOtherArtistExternalId : IExternalId
{
- public class MusicBrainzOtherArtistExternalId : IExternalId
- {
- /// <inheritdoc />
- public string ProviderName => "MusicBrainz";
+ /// <inheritdoc />
+ public string ProviderName => "MusicBrainz";
- /// <inheritdoc />
- public string Key => MetadataProvider.MusicBrainzArtist.ToString();
+ /// <inheritdoc />
+ public string Key => MetadataProvider.MusicBrainzArtist.ToString();
- /// <inheritdoc />
- public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
+ /// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
- /// <inheritdoc />
- public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+ /// <inheritdoc />
+ public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
- /// <inheritdoc />
- public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
- }
+ /// <inheritdoc />
+ public bool Supports(IHasProviderIds item) => item is Audio or MusicAlbum;
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs
index acb652fe0..0baab9955 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs
@@ -1,28 +1,27 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+/// <summary>
+/// MusicBrainz release group external id.
+/// </summary>
+public class MusicBrainzReleaseGroupExternalId : IExternalId
{
- public class MusicBrainzReleaseGroupExternalId : IExternalId
- {
- /// <inheritdoc />
- public string ProviderName => "MusicBrainz";
+ /// <inheritdoc />
+ public string ProviderName => "MusicBrainz";
- /// <inheritdoc />
- public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString();
+ /// <inheritdoc />
+ public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString();
- /// <inheritdoc />
- public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
+ /// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
- /// <inheritdoc />
- public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
+ /// <inheritdoc />
+ public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/release-group/{0}";
- /// <inheritdoc />
- public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
- }
+ /// <inheritdoc />
+ public bool Supports(IHasProviderIds item) => item is Audio or MusicAlbum;
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs
index 14805b9b7..5c974c411 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs
@@ -1,28 +1,27 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+/// <summary>
+/// MusicBrainz track id.
+/// </summary>
+public class MusicBrainzTrackId : IExternalId
{
- public class MusicBrainzTrackId : IExternalId
- {
- /// <inheritdoc />
- public string ProviderName => "MusicBrainz";
+ /// <inheritdoc />
+ public string ProviderName => "MusicBrainz";
- /// <inheritdoc />
- public string Key => MetadataProvider.MusicBrainzTrack.ToString();
+ /// <inheritdoc />
+ public string Key => MetadataProvider.MusicBrainzTrack.ToString();
- /// <inheritdoc />
- public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
+ /// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
- /// <inheritdoc />
- public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
+ /// <inheritdoc />
+ public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/track/{0}";
- /// <inheritdoc />
- public bool Supports(IHasProviderIds item) => item is Audio;
- }
+ /// <inheritdoc />
+ public bool Supports(IHasProviderIds item) => item is Audio;
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
index cfa10dd64..39cfd727f 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
@@ -1,45 +1,64 @@
-#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
+using System.Net.Http.Headers;
+using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
+using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
+using MetaBrainz.MusicBrainz;
+
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Plugins.MusicBrainz
+/// <summary>
+/// Plugin instance.
+/// </summary>
+public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
{
- public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Plugin"/> class.
+ /// </summary>
+ /// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
+ /// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
+ /// <param name="applicationHost">Instance of the <see cref="IApplicationHost"/> interface.</param>
+ public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IApplicationHost applicationHost)
+ : base(applicationPaths, xmlSerializer)
{
- public const string DefaultServer = "https://musicbrainz.org";
-
- public const long DefaultRateLimit = 2000u;
+ Instance = this;
- public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
- : base(applicationPaths, xmlSerializer)
- {
- Instance = this;
- }
+ // TODO: Change this to "JellyfinMusicBrainzPlugin" once we take it out of the server repo.
+ Query.DefaultUserAgent.Add(new ProductInfoHeaderValue(applicationHost.Name.Replace(' ', '-'), applicationHost.ApplicationVersionString));
+ Query.DefaultUserAgent.Add(new ProductInfoHeaderValue($"({applicationHost.ApplicationUserAgentAddress})"));
+ Query.DelayBetweenRequests = Instance.Configuration.RateLimit;
+ Query.DefaultServer = Instance.Configuration.Server;
+ }
- public static Plugin Instance { get; private set; }
+ /// <summary>
+ /// Gets the current plugin instance.
+ /// </summary>
+ public static Plugin? Instance { get; private set; }
- public override Guid Id => new Guid("8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a");
+ /// <inheritdoc />
+ public override Guid Id => new Guid("8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a");
- public override string Name => "MusicBrainz";
+ /// <inheritdoc />
+ public override string Name => "MusicBrainz";
- public override string Description => "Get artist and album metadata from any MusicBrainz server.";
+ /// <inheritdoc />
+ public override string Description => "Get artist and album metadata from any MusicBrainz server.";
- // TODO remove when plugin removed from server.
- public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml";
+ /// <inheritdoc />
+ // TODO remove when plugin removed from server.
+ public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml";
- public IEnumerable<PluginPageInfo> GetPages()
+ /// <inheritdoc />
+ public IEnumerable<PluginPageInfo> GetPages()
+ {
+ yield return new PluginPageInfo
{
- yield return new PluginPageInfo
- {
- Name = Name,
- EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
- };
- }
+ Name = Name,
+ EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
+ };
}
}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index 12ea2d55b..10077e5c8 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -408,10 +408,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
}
}
- if (isEnglishRequested)
- {
- item.Overview = result.Plot;
- }
+ item.Overview = result.Plot;
if (!Plugin.Instance.Configuration.CastAndCrew)
{
diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs
index cb422ef3d..0bfab9824 100644
--- a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs
@@ -1,13 +1,17 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Plugins;
+using MediaBrowser.Model.Plugins;
namespace MediaBrowser.Providers.Plugins.StudioImages.Configuration
{
+ /// <summary>
+ /// Plugin configuration class for the studio image provider.
+ /// </summary>
public class PluginConfiguration : BasePluginConfiguration
{
private string _repository = Plugin.DefaultServer;
+ /// <summary>
+ /// Gets or sets the studio image repository URL.
+ /// </summary>
public string RepositoryUrl
{
get
diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs
index 5e653d039..78150153a 100644
--- a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs
@@ -1,5 +1,4 @@
#nullable disable
-#pragma warning disable CS1591
using System;
using System.Collections.Generic;
@@ -11,27 +10,47 @@ using MediaBrowser.Providers.Plugins.StudioImages.Configuration;
namespace MediaBrowser.Providers.Plugins.StudioImages
{
+ /// <summary>
+ /// Artwork Plugin class.
+ /// </summary>
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
{
+ /// <summary>
+ /// Artwork repository URL.
+ /// </summary>
public const string DefaultServer = "https://raw.github.com/jellyfin/emby-artwork/master/studios";
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Plugin"/> class.
+ /// </summary>
+ /// <param name="applicationPaths">application paths.</param>
+ /// <param name="xmlSerializer">xml serializer.</param>
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer)
{
Instance = this;
}
+ /// <summary>
+ /// Gets the instance of Artwork plugin.
+ /// </summary>
public static Plugin Instance { get; private set; }
+ /// <inheritdoc/>
public override Guid Id => new Guid("872a7849-1171-458d-a6fb-3de3d442ad30");
+ /// <inheritdoc/>
public override string Name => "Studio Images";
+ /// <inheritdoc/>
public override string Description => "Get artwork for studios from any Jellyfin-compatible repository.";
// TODO remove when plugin removed from server.
+
+ /// <inheritdoc/>
public override string ConfigurationFileName => "Jellyfin.Plugin.StudioImages.xml";
+ /// <inheritdoc/>
public IEnumerable<PluginPageInfo> GetPages()
{
yield return new PluginPageInfo
diff --git a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs
index ef822a22a..ffbb338e8 100644
--- a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs
@@ -1,7 +1,5 @@
#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -21,12 +19,21 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.StudioImages
{
+ /// <summary>
+ /// Studio image provider.
+ /// </summary>
public class StudiosImageProvider : IRemoteImageProvider
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IFileSystem _fileSystem;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StudiosImageProvider"/> class.
+ /// </summary>
+ /// <param name="config">The <see cref="IServerConfigurationManager"/>.</param>
+ /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+ /// <param name="fileSystem">The <see cref="IFileSystem"/>.</param>
public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
{
_config = config;
@@ -34,13 +41,16 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
_fileSystem = fileSystem;
}
+ /// <inheritdoc />
public string Name => "Artwork Repository";
+ /// <inheritdoc />
public bool Supports(BaseItem item)
{
return item is Studio;
}
+ /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
return new List<ImageType>
@@ -49,6 +59,7 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
};
}
+ /// <inheritdoc />
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
var thumbsPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotestudiothumbs.txt");
@@ -103,6 +114,7 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
return EnsureList(url, file, _fileSystem, cancellationToken);
}
+ /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
@@ -110,13 +122,13 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
}
/// <summary>
- /// Ensures the list.
+ /// Ensures the existence of a file listing.
/// </summary>
/// <param name="url">The URL.</param>
/// <param name="file">The file.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
+ /// <returns>A Task to ensure existence of a file listing.</returns>
public async Task<string> EnsureList(string url, string file, IFileSystem fileSystem, CancellationToken cancellationToken)
{
var fileInfo = fileSystem.GetFileInfo(file);
@@ -134,6 +146,12 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
return file;
}
+ /// <summary>
+ /// Get matching image for an item.
+ /// </summary>
+ /// <param name="item">The <see cref="BaseItem"/>.</param>
+ /// <param name="images">The enumerable of image strings.</param>
+ /// <returns>The matching image string.</returns>
public string FindMatch(BaseItem item, IEnumerable<string> images)
{
var name = GetComparableName(item.Name);
@@ -151,6 +169,11 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
.Replace("/", string.Empty, StringComparison.Ordinal);
}
+ /// <summary>
+ /// Get available image strings for a file.
+ /// </summary>
+ /// <param name="file">The file.</param>
+ /// <returns>All images strings of a file.</returns>
public IEnumerable<string> GetAvailableImages(string file)
{
using var fileStream = File.OpenRead(file);
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs
index 0bab7c3ca..ac3df1d5d 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs
@@ -8,7 +8,7 @@ using TMDbLib.Objects.General;
namespace MediaBrowser.Providers.Plugins.Tmdb.Api
{
/// <summary>
- /// The TMDb api controller.
+ /// The TMDb API controller.
/// </summary>
[ApiController]
[Authorize(Policy = "DefaultAuthorization")]
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs
index 3217ac2f1..0e768bb83 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
{
/// <summary>
- /// External ID for a TMDB box set.
+ /// External id for a TMDb box set.
/// </summary>
public class TmdbBoxSetExternalId : IExternalId
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
index 29a557c31..ef878e670 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
@@ -1,7 +1,5 @@
#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -18,26 +16,38 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
{
+ /// <summary>
+ /// BoxSet image provider powered by TMDb.
+ /// </summary>
public class TmdbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TmdbBoxSetImageProvider"/> class.
+ /// </summary>
+ /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+ /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
{
_httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
}
+ /// <inheritdoc />
public string Name => TmdbUtils.ProviderName;
+ /// <inheritdoc />
public int Order => 0;
+ /// <inheritdoc />
public bool Supports(BaseItem item)
{
return item is BoxSet;
}
+ /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
return new List<ImageType>
@@ -47,6 +57,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
};
}
+ /// <inheritdoc />
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
var tmdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
@@ -76,6 +87,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
return remoteImages;
}
+ /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
index 62bc9c65f..90f2aa88f 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
@@ -1,7 +1,5 @@
#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -18,12 +16,21 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
{
+ /// <summary>
+ /// BoxSet provider powered by TMDb.
+ /// </summary>
public class TmdbBoxSetProvider : IRemoteMetadataProvider<BoxSet, BoxSetInfo>
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
private readonly ILibraryManager _libraryManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TmdbBoxSetProvider"/> class.
+ /// </summary>
+ /// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param>
+ /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+ /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager, ILibraryManager libraryManager)
{
_httpClientFactory = httpClientFactory;
@@ -31,8 +38,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
_libraryManager = libraryManager;
}
+ /// <inheritdoc />
public string Name => TmdbUtils.ProviderName;
+ /// <inheritdoc />
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken)
{
var tmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
@@ -81,6 +90,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
return collections;
}
+ /// <inheritdoc />
public async Task<MetadataResult<BoxSet>> GetMetadata(BoxSetInfo info, CancellationToken cancellationToken)
{
var tmdbId = Convert.ToInt32(info.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
@@ -124,6 +134,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
return result;
}
+ /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs
index 31310a8d4..38d2c5c69 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
/// <summary>
- /// External ID for a TMBD movie.
+ /// External id for a TMDb movie.
/// </summary>
public class TmdbMovieExternalId : IExternalId
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs
index 16f0089f8..1646a93d2 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs
@@ -1,7 +1,5 @@
#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -19,26 +17,38 @@ using TMDbLib.Objects.Find;
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
+ /// <summary>
+ /// Movie image provider powered by TMDb.
+ /// </summary>
public class TmdbMovieImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TmdbMovieImageProvider"/> class.
+ /// </summary>
+ /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+ /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
public TmdbMovieImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
{
_httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
}
+ /// <inheritdoc />
public int Order => 0;
+ /// <inheritdoc />
public string Name => TmdbUtils.ProviderName;
+ /// <inheritdoc />
public bool Supports(BaseItem item)
{
return item is Movie || item is Trailer;
}
+ /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
return new List<ImageType>
@@ -49,6 +59,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
};
}
+ /// <inheritdoc />
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
var language = item.GetPreferredMetadataLanguage();
@@ -96,6 +107,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
return remoteImages;
}
+ /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index f14f31858..dd2d5d97d 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -1,7 +1,5 @@
#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -23,7 +21,7 @@ using TMDbLib.Objects.Search;
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
/// <summary>
- /// Class MovieDbProvider.
+ /// Movie provider powered by TMDb.
/// </summary>
public class TmdbMovieProvider : IRemoteMetadataProvider<Movie, MovieInfo>, IHasOrder
{
@@ -31,6 +29,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
private readonly ILibraryManager _libraryManager;
private readonly TmdbClientManager _tmdbClientManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TmdbMovieProvider"/> class.
+ /// </summary>
+ /// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param>
+ /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+ /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
public TmdbMovieProvider(
ILibraryManager libraryManager,
TmdbClientManager tmdbClientManager,
@@ -41,11 +45,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
_httpClientFactory = httpClientFactory;
}
- public string Name => TmdbUtils.ProviderName;
-
/// <inheritdoc />
public int Order => 1;
+ /// <inheritdoc />
+ public string Name => TmdbUtils.ProviderName;
+
+ /// <inheritdoc />
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken)
{
if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var id))
@@ -133,6 +139,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
return remoteSearchResults;
}
+ /// <inheritdoc />
public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
{
var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
@@ -144,7 +151,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
// Caller provides the filename with extension stripped and NOT the parsed filename
var parsedName = _libraryManager.ParseName(info.Name);
var cleanedName = TmdbUtils.CleanName(parsedName.Name);
- var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
if (searchResults.Count > 0)
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs
index 9804d60bd..027399aec 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.People
{
/// <summary>
- /// External ID for a TMDB person.
+ /// External id for a TMDb person.
/// </summary>
public class TmdbPersonExternalId : IExternalId
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
index 7ce4cfe67..d7f5c99dd 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -14,11 +12,19 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.People
{
+ /// <summary>
+ /// Person image provider powered by TMDb.
+ /// </summary>
public class TmdbPersonImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TmdbPersonImageProvider"/> class.
+ /// </summary>
+ /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+ /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
public TmdbPersonImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
{
_httpClientFactory = httpClientFactory;
@@ -31,11 +37,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
/// <inheritdoc />
public int Order => 0;
+ /// <inheritdoc />
public bool Supports(BaseItem item)
{
return item is Person;
}
+ /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
return new List<ImageType>
@@ -44,6 +52,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
};
}
+ /// <inheritdoc />
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
var person = (Person)item;
@@ -68,6 +77,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
return remoteImages;
}
+ /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
index 8790e3759..d760ad142 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
@@ -1,7 +1,5 @@
#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -16,19 +14,29 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.People
{
+ /// <summary>
+ /// Person image provider powered by TMDb.
+ /// </summary>
public class TmdbPersonProvider : IRemoteMetadataProvider<Person, PersonLookupInfo>
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TmdbPersonProvider"/> class.
+ /// </summary>
+ /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+ /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
public TmdbPersonProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
{
_httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
}
+ /// <inheritdoc />
public string Name => TmdbUtils.ProviderName;
+ /// <inheritdoc />
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken)
{
if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId))
@@ -79,6 +87,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
return remoteSearchResults;
}
+ /// <inheritdoc />
public async Task<MetadataResult<Person>> GetMetadata(PersonLookupInfo info, CancellationToken cancellationToken)
{
var personTmdbId = Convert.ToInt32(info.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
@@ -131,6 +140,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
return result;
}
+ /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
index 5eec776b5..943a3a75b 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
@@ -1,7 +1,5 @@
#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -17,22 +15,38 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
+ /// <summary>
+ /// TV episode image provider powered by TheMovieDb.
+ /// </summary>
public class TmdbEpisodeImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TmdbEpisodeImageProvider"/> class.
+ /// </summary>
+ /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+ /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
public TmdbEpisodeImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
{
_httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
}
- // After TheTvDb
+ /// <inheritdoc />
public int Order => 1;
+ /// <inheritdoc />
public string Name => TmdbUtils.ProviderName;
+ /// <inheritdoc />
+ public bool Supports(BaseItem item)
+ {
+ return item is Controller.Entities.TV.Episode;
+ }
+
+ /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
return new List<ImageType>
@@ -41,6 +55,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
};
}
+ /// <inheritdoc />
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
var episode = (Controller.Entities.TV.Episode)item;
@@ -81,14 +96,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return remoteImages;
}
+ /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
-
- public bool Supports(BaseItem item)
- {
- return item is Controller.Entities.TV.Episode;
- }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
index f50f15877..e20284e6f 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
@@ -1,7 +1,5 @@
#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -19,22 +17,32 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
+ /// <summary>
+ /// TV episode provider powered by TheMovieDb.
+ /// </summary>
public class TmdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasOrder
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TmdbEpisodeProvider"/> class.
+ /// </summary>
+ /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+ /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
public TmdbEpisodeProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
{
_httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
}
- // After TheTvDb
+ /// <inheritdoc />
public int Order => 1;
+ /// <inheritdoc />
public string Name => TmdbUtils.ProviderName;
+ /// <inheritdoc />
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
{
// The search query must either provide an episode number or date
@@ -68,6 +76,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
};
}
+ /// <inheritdoc />
public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken)
{
var metadataResult = new MetadataResult<Episode>();
@@ -209,6 +218,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return metadataResult;
}
+ /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
index 4446fa966..da32ea408 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -16,26 +14,47 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
+ /// <summary>
+ /// TV season image provider powered by TheMovieDb.
+ /// </summary>
public class TmdbSeasonImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TmdbSeasonImageProvider"/> class.
+ /// </summary>
+ /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+ /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
public TmdbSeasonImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
{
_httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
}
+ /// <inheritdoc/>
public int Order => 1;
+ /// <inheritdoc/>
public string Name => TmdbUtils.ProviderName;
- public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public bool Supports(BaseItem item)
{
- return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
+ return item is Season;
+ }
+
+ /// <inheritdoc />
+ public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
+ {
+ return new List<ImageType>
+ {
+ ImageType.Primary
+ };
}
+ /// <inheritdoc />
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
var season = (Season)item;
@@ -68,17 +87,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return remoteImages;
}
- public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
- {
- return new List<ImageType>
- {
- ImageType.Primary
- };
- }
-
- public bool Supports(BaseItem item)
+ /// <inheritdoc />
+ public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
- return item is Season;
+ return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
index 64ed3f408..2cf0f399e 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -17,19 +15,29 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
+ /// <summary>
+ /// TV season provider powered by TheMovieDb.
+ /// </summary>
public class TmdbSeasonProvider : IRemoteMetadataProvider<Season, SeasonInfo>
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TmdbSeasonProvider"/> class.
+ /// </summary>
+ /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+ /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
public TmdbSeasonProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
{
_httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
}
+ /// <inheritdoc />
public string Name => TmdbUtils.ProviderName;
+ /// <inheritdoc />
public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken)
{
var result = new MetadataResult<Season>();
@@ -114,11 +122,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return result;
}
+ /// <inheritdoc />
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken)
{
return Task.FromResult(Enumerable.Empty<RemoteSearchResult>());
}
+ /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs
index 8a2be80cd..df04cb2e7 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
/// <summary>
- /// External ID for a TMDB series.
+ /// External id for a TMDb series.
/// </summary>
public class TmdbSeriesExternalId : IExternalId
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
index 130d6ce44..e96b680b4 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -16,27 +14,38 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
+ /// <summary>
+ /// TV series image provider powered by TheMovieDb.
+ /// </summary>
public class TmdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TmdbSeriesImageProvider"/> class.
+ /// </summary>
+ /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+ /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
public TmdbSeriesImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
{
_httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
}
+ /// <inheritdoc />
public string Name => TmdbUtils.ProviderName;
- // After tvdb and fanart
+ /// <inheritdoc />
public int Order => 2;
+ /// <inheritdoc />
public bool Supports(BaseItem item)
{
return item is Series;
}
+ /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
return new List<ImageType>
@@ -47,6 +56,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
};
}
+ /// <inheritdoc />
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
var tmdbId = item.GetProviderId(MetadataProvider.Tmdb);
@@ -80,6 +90,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return remoteImages;
}
+ /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
index 4d26052fa..4e8fdf0ee 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
@@ -1,7 +1,5 @@
#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -23,12 +21,21 @@ using TMDbLib.Objects.TvShows;
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
+ /// <summary>
+ /// TV series provider powered by TheMovieDb.
+ /// </summary>
public class TmdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
private readonly TmdbClientManager _tmdbClientManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TmdbSeriesProvider"/> class.
+ /// </summary>
+ /// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param>
+ /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+ /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
public TmdbSeriesProvider(
ILibraryManager libraryManager,
IHttpClientFactory httpClientFactory,
@@ -39,11 +46,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
_tmdbClientManager = tmdbClientManager;
}
+ /// <inheritdoc />
public string Name => TmdbUtils.ProviderName;
- // After TheTVDB
+ /// <inheritdoc />
public int Order => 1;
+ /// <inheritdoc />
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
{
if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var tmdbId))
@@ -159,6 +168,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return remoteResult;
}
+ /// <inheritdoc />
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
{
var result = new MetadataResult<Series>
@@ -383,6 +393,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
}
}
+ /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
index 685eb222f..44c2c81f4 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
@@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
private static readonly Regex _nonWords = new(@"[\W_]+", RegexOptions.Compiled);
/// <summary>
- /// URL of the TMDB instance to use.
+ /// URL of the TMDb instance to use.
/// </summary>
public const string BaseTmdbUrl = "https://www.themoviedb.org/";
@@ -50,7 +50,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
}
/// <summary>
- /// Maps the TMDB provided roles for crew members to Jellyfin roles.
+ /// Maps the TMDb provided roles for crew members to Jellyfin roles.
/// </summary>
/// <param name="crew">Crew member to map against the Jellyfin person types.</param>
/// <returns>The Jellyfin person type.</returns>
@@ -103,9 +103,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
languages.Add(preferredLanguage);
- if (preferredLanguage.Length == 5) // like en-US
+ if (preferredLanguage.Length == 5) // Like en-US
{
- // Currently, TMDB supports 2-letter language codes only
+ // Currently, TMDb supports 2-letter language codes only.
// They are planning to change this in the future, thus we're
// supplying both codes if we're having a 5-letter code.
languages.Add(preferredLanguage.Substring(0, 2));
@@ -114,6 +114,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
languages.Add("null");
+ // Always add English as fallback language
if (!string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase))
{
languages.Add("en");
@@ -134,14 +135,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
return language;
}
- // They require this to be uppercase
- // Everything after the hyphen must be written in uppercase due to a way TMDB wrote their api.
+ // TMDb requires this to be uppercase
+ // Everything after the hyphen must be written in uppercase due to a way TMDb wrote their API.
// See here: https://www.themoviedb.org/talk/5119221d760ee36c642af4ad?page=3#56e372a0c3a3685a9e0019ab
var parts = language.Split('-');
if (parts.Length == 2)
{
- // TMDB doesn't support Switzerland (de-CH, it-CH or fr-CH) so use the language (de, it or fr) without country code
+ // TMDb doesn't support Switzerland (de-CH, it-CH or fr-CH) so use the language (de, it or fr) without country code
if (string.Equals(parts[1], "CH", StringComparison.OrdinalIgnoreCase))
{
return parts[0];
@@ -174,14 +175,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
}
/// <summary>
- /// Combines the metadata country code and the parental rating from the Api into the value we store in our database.
+ /// Combines the metadata country code and the parental rating from the API into the value we store in our database.
/// </summary>
- /// <param name="countryCode">The Iso 3166-1 country code of the rating country.</param>
- /// <param name="ratingValue">The rating value returned by the Tmdb Api.</param>
+ /// <param name="countryCode">The ISO 3166-1 country code of the rating country.</param>
+ /// <param name="ratingValue">The rating value returned by the TMDb API.</param>
/// <returns>The combined parental rating of country code+rating value.</returns>
public static string BuildParentalRating(string countryCode, string ratingValue)
{
- // exclude US because we store us values as TV-14 without the country code.
+ // Exclude US because we store US values as TV-14 without the country code.
var ratingPrefix = string.Equals(countryCode, "US", StringComparison.OrdinalIgnoreCase) ? string.Empty : countryCode + "-";
var newRating = ratingPrefix + ratingValue;