aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Providers
diff options
context:
space:
mode:
authorMarc Brooks <IDisposable@gmail.com>2025-03-12 10:33:27 -0500
committerGitHub <noreply@github.com>2025-03-12 10:33:27 -0500
commita5f3d942f691b914b67b098f7a64406bea569aad (patch)
treeb4c73319032ef4b62d6f00e4bc9ff8971b504793 /MediaBrowser.Providers
parent114591c1aacbdf4d07e95c536ea2e42af1c5ab0d (diff)
parent237e7bd44b3c9a6f76892be1c6a925bcde64bdbf (diff)
Merge branch 'master' into sort-nfo-data
Diffstat (limited to 'MediaBrowser.Providers')
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs17
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs10
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs23
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioFileProber.cs56
-rw-r--r--MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs27
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs6
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs10
9 files changed, 107 insertions, 51 deletions
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index 64954818a..ee22b4bc6 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
@@ -551,10 +552,16 @@ namespace MediaBrowser.Providers.Manager
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await using (stream.ConfigureAwait(false))
{
+ var mimetype = response.Content.Headers.ContentType?.MediaType;
+ if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.OrdinalIgnoreCase))
+ {
+ mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.Path));
+ }
+
await _providerManager.SaveImage(
item,
stream,
- response.Content.Headers.ContentType?.MediaType,
+ mimetype,
type,
null,
cancellationToken).ConfigureAwait(false);
@@ -677,10 +684,16 @@ namespace MediaBrowser.Providers.Manager
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await using (stream.ConfigureAwait(false))
{
+ var mimetype = response.Content.Headers.ContentType?.MediaType;
+ if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.OrdinalIgnoreCase))
+ {
+ mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.Path));
+ }
+
await _providerManager.SaveImage(
item,
stream,
- response.Content.Headers.ContentType?.MediaType,
+ mimetype,
imageType,
null,
cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index 778fbc712..1d3ddc4e2 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -1162,6 +1162,16 @@ namespace MediaBrowser.Providers.Manager
{
person.ImageUrl = personInSource.ImageUrl;
}
+
+ if (!string.IsNullOrWhiteSpace(personInSource.Role) && string.IsNullOrWhiteSpace(person.Role))
+ {
+ person.Role = personInSource.Role;
+ }
+
+ if (personInSource.SortOrder.HasValue && !person.SortOrder.HasValue)
+ {
+ person.SortOrder = personInSource.SortOrder;
+ }
}
}
}
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 6813cfa91..8c45abe25 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -205,27 +205,10 @@ namespace MediaBrowser.Providers.Manager
{
contentType = MediaTypeNames.Image.Png;
}
- else
- {
- // Deduce content type from file extension
- contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path));
- }
-
- // Throw if we still can't determine the content type
- if (string.IsNullOrEmpty(contentType))
- {
- throw new HttpRequestException("Invalid image received: contentType not set.", null, response.StatusCode);
- }
- }
-
- // 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);
}
- // some iptv/epg providers don't correctly report media type, extract from url if no extension found
- if (string.IsNullOrWhiteSpace(MimeTypes.ToExtension(contentType)))
+ // some providers don't correctly report media type, extract from url if no extension found
+ if (contentType is null || contentType.Equals(MediaTypeNames.Application.Octet, StringComparison.OrdinalIgnoreCase))
{
// Strip query parameters from url to get actual path.
contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path));
@@ -233,7 +216,7 @@ namespace MediaBrowser.Providers.Manager
if (!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
{
- throw new HttpRequestException($"Request returned {contentType} instead of an image type", null, HttpStatusCode.NotFound);
+ throw new HttpRequestException($"Request returned '{contentType}' instead of an image type", null, HttpStatusCode.NotFound);
}
var responseBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
index b504da48f..916e2625b 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
@@ -20,6 +20,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
+using static Jellyfin.Extensions.StringExtensions;
namespace MediaBrowser.Providers.MediaInfo
{
@@ -175,11 +176,15 @@ namespace MediaBrowser.Providers.MediaInfo
_logger.LogWarning("File {File} only has ID3v1 tags, some fields may be truncated", audio.Path);
}
- track.Title = string.IsNullOrEmpty(track.Title) ? mediaInfo.Name : track.Title;
- track.Album = string.IsNullOrEmpty(track.Album) ? mediaInfo.Album : track.Album;
- track.Year ??= mediaInfo.ProductionYear;
- track.TrackNumber ??= mediaInfo.IndexNumber;
- track.DiscNumber ??= mediaInfo.ParentIndexNumber;
+ // We should never use the property setter of the ATL.Track class.
+ // That setter is meant for its own tag parser and external editor usage and will have unwanted side effects
+ // For example, setting the Year property will also set the Date property, which is not what we want here.
+ // To properly handle fallback values, we make a clone of those fields when valid.
+ var trackTitle = (string.IsNullOrEmpty(track.Title) ? mediaInfo.Name : track.Title).Trim();
+ var trackAlbum = (string.IsNullOrEmpty(track.Album) ? mediaInfo.Album : track.Album).Trim();
+ var trackYear = track.Year is null or 0 ? mediaInfo.ProductionYear : track.Year;
+ var trackTrackNumber = track.TrackNumber is null or 0 ? mediaInfo.IndexNumber : track.TrackNumber;
+ var trackDiscNumber = track.DiscNumber is null or 0 ? mediaInfo.ParentIndexNumber : track.DiscNumber;
if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
{
@@ -276,22 +281,22 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
- if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(track.Title))
+ if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(trackTitle))
{
- audio.Name = track.Title;
+ audio.Name = trackTitle;
}
if (options.ReplaceAllMetadata)
{
- audio.Album = track.Album.Trim();
- audio.IndexNumber = track.TrackNumber;
- audio.ParentIndexNumber = track.DiscNumber;
+ audio.Album = trackAlbum;
+ audio.IndexNumber = trackTrackNumber;
+ audio.ParentIndexNumber = trackDiscNumber;
}
else
{
- audio.Album ??= track.Album.Trim();
- audio.IndexNumber ??= track.TrackNumber;
- audio.ParentIndexNumber ??= track.DiscNumber;
+ audio.Album ??= trackAlbum;
+ audio.IndexNumber ??= trackTrackNumber;
+ audio.ParentIndexNumber ??= trackDiscNumber;
}
if (track.Date.HasValue)
@@ -299,11 +304,12 @@ namespace MediaBrowser.Providers.MediaInfo
audio.PremiereDate = track.Date;
}
- if (track.Year.HasValue)
+ if (trackYear.HasValue)
{
- var year = track.Year.Value;
+ var year = trackYear.Value;
audio.ProductionYear = year;
+ // ATL library handles such fallback this with its own internal logic, but we also need to handle it here for the ffprobe fallbacks.
if (!audio.PremiereDate.HasValue)
{
try
@@ -312,7 +318,7 @@ namespace MediaBrowser.Providers.MediaInfo
}
catch (ArgumentOutOfRangeException ex)
{
- _logger.LogError(ex, "Error parsing YEAR tag in {File}. '{TagValue}' is an invalid year", audio.Path, track.Year);
+ _logger.LogError(ex, "Error parsing YEAR tag in {File}. '{TagValue}' is an invalid year", audio.Path, trackYear);
}
}
}
@@ -403,6 +409,24 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
+ if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzRecording, out _))
+ {
+ if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_TRACKID", out var recordingMbId)
+ || track.AdditionalFields.TryGetValue("MusicBrainz Track Id", out recordingMbId))
+ && !string.IsNullOrEmpty(recordingMbId))
+ {
+ audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, recordingMbId);
+ }
+ else if (track.AdditionalFields.TryGetValue("UFID", out var ufIdValue) && !string.IsNullOrEmpty(ufIdValue))
+ {
+ // If tagged with MB Picard, the format is 'http://musicbrainz.org\0<recording MBID>'
+ if (ufIdValue.Contains("musicbrainz.org", StringComparison.OrdinalIgnoreCase))
+ {
+ audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, ufIdValue.AsSpan().RightPart('\0').ToString());
+ }
+ }
+ }
+
// Save extracted lyrics if they exist,
// and if the audio doesn't yet have lyrics.
var lyrics = track.Lyrics.SynchronizedLyrics.Count > 0 ? track.Lyrics.FormatSynchToLRC() : track.Lyrics.UnsynchronizedLyrics;
diff --git a/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs
index f12390bc2..0716cdfa0 100644
--- a/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs
@@ -119,9 +119,9 @@ namespace MediaBrowser.Providers.MediaInfo
|| (mediaStream.Type == MediaStreamType.Subtitle && _type == DlnaProfileType.Subtitle))
{
mediaStream.Index = startIndex++;
- mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
+ mediaStream.IsDefault = pathInfo.IsDefault;
mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
- mediaStream.IsHearingImpaired = pathInfo.IsHearingImpaired || mediaStream.IsHearingImpaired.GetValueOrDefault();
+ mediaStream.IsHearingImpaired = pathInfo.IsHearingImpaired || mediaStream.IsHearingImpaired;
mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs
new file mode 100644
index 000000000..d2af62806
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs
@@ -0,0 +1,27 @@
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+/// <summary>
+/// MusicBrainz recording id.
+/// </summary>
+public class MusicBrainzRecordingId : IExternalId
+{
+ /// <inheritdoc />
+ public string ProviderName => "MusicBrainz";
+
+ /// <inheritdoc />
+ public string Key => MetadataProvider.MusicBrainzRecording.ToString();
+
+ /// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Recording;
+
+ /// <inheritdoc />
+ public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/recording/{0}";
+
+ /// <inheritdoc />
+ public bool Supports(IHasProviderIds item) => item is Audio;
+}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
index d8b33a799..ccff31eba 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
@@ -55,13 +55,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb
if (info.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string? seriesImdbId)
&& !string.IsNullOrEmpty(seriesImdbId)
- && info.IndexNumber.HasValue
- && info.ParentIndexNumber.HasValue)
+ && info.IndexNumber.HasValue)
{
result.HasMetadata = await _omdbProvider.FetchEpisodeData(
result,
info.IndexNumber.Value,
- info.ParentIndexNumber.Value,
+ info.ParentIndexNumber ?? 1,
info.GetProviderId(MetadataProvider.Imdb),
seriesImdbId,
info.MetadataLanguage,
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
index d1fec7cb1..7de0e430f 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
@@ -63,10 +63,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return Enumerable.Empty<RemoteImageInfo>();
}
- var seasonNumber = episode.ParentIndexNumber;
+ var seasonNumber = episode.ParentIndexNumber ?? 1;
var episodeNumber = episode.IndexNumber;
- if (!seasonNumber.HasValue || !episodeNumber.HasValue)
+ if (!episodeNumber.HasValue)
{
return Enumerable.Empty<RemoteImageInfo>();
}
@@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
// TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here
var episodeResult = await _tmdbClientManager
- .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken)
+ .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken)
.ConfigureAwait(false);
var stills = episodeResult?.Images?.Stills;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
index 4ee164553..73c3b4f16 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
{
// The search query must either provide an episode number or date
- if (!searchInfo.IndexNumber.HasValue || !searchInfo.ParentIndexNumber.HasValue)
+ if (!searchInfo.IndexNumber.HasValue)
{
return Enumerable.Empty<RemoteSearchResult>();
}
@@ -96,10 +96,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return metadataResult;
}
- var seasonNumber = info.ParentIndexNumber;
+ var seasonNumber = info.ParentIndexNumber ?? 1;
var episodeNumber = info.IndexNumber;
- if (!seasonNumber.HasValue || !episodeNumber.HasValue)
+ if (!episodeNumber.HasValue)
{
return metadataResult;
}
@@ -112,7 +112,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
List<TvEpisode>? result = null;
for (int? episode = startindex; episode <= endindex; episode++)
{
- var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episode.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken).ConfigureAwait(false);
+ var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber, episode.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken).ConfigureAwait(false);
if (episodeInfo is not null)
{
(result ??= new List<TvEpisode>()).Add(episodeInfo);
@@ -156,7 +156,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
else
{
episodeResult = await _tmdbClientManager
- .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
+ .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
.ConfigureAwait(false);
}