This commit is contained in:
MarcoCoreDuo 2026-05-12 18:23:10 +02:00 committed by GitHub
commit 474ca7b688
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 75 additions and 38 deletions

View file

@ -1598,6 +1598,16 @@ namespace MediaBrowser.Controller.Entities
return lang;
}
/// <summary>
/// Gets the preferred image languages as an array (e.g., ["en", "de", "fr", "nolang"]).
/// </summary>
/// <returns>Array of preferred image languages.</returns>
public string[] GetPreferredImageLanguages()
{
var libraryOptions = LibraryManager.GetLibraryOptions(this);
return libraryOptions.PreferredImageLanguages;
}
public virtual bool IsSaveLocalMetadataEnabled()
{
if (SourceType == SourceType.Channel)

View file

@ -12,6 +12,7 @@ namespace MediaBrowser.Model.Configuration
public LibraryOptions()
{
PreferredImageLanguages = Array.Empty<string>();
TypeOptions = Array.Empty<TypeOptions>();
DisabledSubtitleFetchers = Array.Empty<string>();
DisabledMediaSegmentProviders = Array.Empty<string>();
@ -79,6 +80,13 @@ namespace MediaBrowser.Model.Configuration
/// <value>The preferred metadata language.</value>
public string? PreferredMetadataLanguage { get; set; }
/// <summary>
/// Gets or sets the preferred image languages as an array (e.g., ["en", "de", "fr", "nolang"]).
/// If empty, PreferredMetadataLanguage will be used as fallback.
/// </summary>
/// <value>The preferred image languages.</value>
public string[] PreferredImageLanguages { get; set; }
/// <summary>
/// Gets or sets the metadata country code.
/// </summary>

View file

@ -11,46 +11,47 @@ namespace MediaBrowser.Model.Extensions
public static class EnumerableExtensions
{
/// <summary>
/// Orders <see cref="RemoteImageInfo"/> by requested language in descending order, then "en", then no language, over other non-matches.
/// Orders <see cref="RemoteImageInfo"/> by preferred languages in descending order.
/// </summary>
/// <param name="remoteImageInfos">The remote image infos.</param>
/// <param name="requestedLanguage">The requested language for the images.</param>
/// <param name="requestedMetadataLanguage">The requested metadata language for fallback if no preferred languages are specified.</param>
/// <param name="preferredImageLanguages">Array of preferred image languages (e.g., ["en", "de", "fr", "nolang"]). If null or empty, uses requestedLanguage.</param>
/// <returns>The ordered remote image infos.</returns>
public static IEnumerable<RemoteImageInfo> OrderByLanguageDescending(this IEnumerable<RemoteImageInfo> remoteImageInfos, string requestedLanguage)
public static IEnumerable<RemoteImageInfo> OrderByLanguageDescending(
this IEnumerable<RemoteImageInfo> remoteImageInfos,
string requestedMetadataLanguage,
string[]? preferredImageLanguages)
{
if (string.IsNullOrWhiteSpace(requestedLanguage))
if (string.IsNullOrWhiteSpace(requestedMetadataLanguage))
{
// Default to English if no requested language is specified.
requestedLanguage = "en";
requestedMetadataLanguage = "en";
}
string[] languages = preferredImageLanguages?.Length > 0
? preferredImageLanguages
: [requestedMetadataLanguage, "en"];
return remoteImageInfos.OrderByDescending(i =>
{
// Image priority ordering:
// - Images that match the requested language
// - TODO: Images that match the original language
// - Images in English
// - Images with no language
// - Images that match preferred image languages (in order of preference)
// - Images with no language if nolang is not in preferred image languages
// - Images that don't match the requested language
if (string.Equals(requestedLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
for (int index = 0; index < languages.Length; index++)
{
return 4;
if (string.Equals(languages[index], i.Language, StringComparison.OrdinalIgnoreCase) ||
(string.Equals(languages[index], "nolang", StringComparison.OrdinalIgnoreCase) && string.IsNullOrEmpty(i.Language)))
{
// Return a high priority value, with earlier languages getting higher values
return 1 + (languages.Length - index);
}
}
if (string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase))
{
return 3;
}
if (string.IsNullOrEmpty(i.Language))
{
return 2;
}
return 0;
return string.IsNullOrEmpty(i.Language) ? 1 : 0;
})
.ThenByDescending(i => Math.Round(i.CommunityRating ?? 0, 1) )
.ThenByDescending(i => Math.Round(i.CommunityRating ?? 0, 1))
.ThenByDescending(i => i.VoteCount ?? 0);
}
}

View file

@ -155,6 +155,7 @@ namespace MediaBrowser.Providers.Manager
var typeName = item.GetType().Name;
var typeOptions = libraryOptions.GetTypeOptions(typeName) ?? new TypeOptions { Type = typeName };
var preferredImageLanguages = libraryOptions.PreferredImageLanguages.ToList();
// track library limits, adding buffer to allow lazy replacing of current images
var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop) + oldBackdropImages.Length;
@ -164,7 +165,7 @@ namespace MediaBrowser.Providers.Manager
{
if (provider is IRemoteImageProvider remoteProvider)
{
await RefreshFromProvider(item, remoteProvider, refreshOptions, typeOptions, backdropLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false);
await RefreshFromProvider(item, remoteProvider, refreshOptions, typeOptions, preferredImageLanguages, backdropLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false);
continue;
}
@ -266,6 +267,7 @@ namespace MediaBrowser.Providers.Manager
/// <param name="provider">The provider.</param>
/// <param name="refreshOptions">The refresh options.</param>
/// <param name="savedOptions">The saved options.</param>
/// <param name="preferredImageLanguages">The preferred image languages.</param>
/// <param name="backdropLimit">The backdrop limit.</param>
/// <param name="downloadedImages">The downloaded images.</param>
/// <param name="result">The result.</param>
@ -276,6 +278,7 @@ namespace MediaBrowser.Providers.Manager
IRemoteImageProvider provider,
ImageRefreshOptions refreshOptions,
TypeOptions savedOptions,
List<string> preferredImageLanguages,
int backdropLimit,
List<ImageType> downloadedImages,
RefreshResult result,
@ -297,11 +300,13 @@ namespace MediaBrowser.Providers.Manager
_logger.LogDebug("Running {Provider} for {Item}", provider.GetType().Name, item.Path ?? item.Name);
var includeAllLanguages = preferredImageLanguages.Count == 0;
var images = await _providerManager.GetAvailableRemoteImages(
item,
new RemoteImageQuery(provider.Name)
{
IncludeAllLanguages = true,
IncludeAllLanguages = includeAllLanguages,
IncludeDisabledProviders = false,
},
cancellationToken).ConfigureAwait(false);

View file

@ -303,9 +303,10 @@ namespace MediaBrowser.Providers.Manager
providers = providers.Where(i => i.GetSupportedImages(item).Contains(query.ImageType.Value));
}
var preferredLanguage = item.GetPreferredMetadataLanguage();
var preferredMetadataLanguage = item.GetPreferredMetadataLanguage();
var preferredImageLanguages = item.GetPreferredImageLanguages();
var tasks = providers.Select(i => GetImages(item, i, preferredLanguage, query.IncludeAllLanguages, cancellationToken, query.ImageType));
var tasks = providers.Select(i => GetImages(item, i, preferredMetadataLanguage, preferredImageLanguages, query.IncludeAllLanguages, cancellationToken, query.ImageType));
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
@ -317,7 +318,8 @@ namespace MediaBrowser.Providers.Manager
/// </summary>
/// <param name="item">The item.</param>
/// <param name="provider">The provider.</param>
/// <param name="preferredLanguage">The preferred language.</param>
/// <param name="preferredMetadataLanguage">The preferred language.</param>
/// <param name="preferredImageLanguages">The preferred image languages.</param>
/// <param name="includeAllLanguages">Whether to include all languages in results.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="type">The type.</param>
@ -325,12 +327,14 @@ namespace MediaBrowser.Providers.Manager
private async Task<IEnumerable<RemoteImageInfo>> GetImages(
BaseItem item,
IRemoteImageProvider provider,
string preferredLanguage,
string preferredMetadataLanguage,
string[] preferredImageLanguages,
bool includeAllLanguages,
CancellationToken cancellationToken,
ImageType? type = null)
{
bool hasPreferredLanguage = !string.IsNullOrWhiteSpace(preferredLanguage);
bool hasPreferredMetadataLanguage = !string.IsNullOrWhiteSpace(preferredMetadataLanguage);
bool hasPreferredImageLanguages = preferredImageLanguages?.Length > 0;
try
{
@ -341,17 +345,26 @@ namespace MediaBrowser.Providers.Manager
result = result.Where(i => i.Type == type.Value);
}
if (!includeAllLanguages && hasPreferredLanguage)
if (!includeAllLanguages)
{
// Filter out languages that do not match the preferred languages.
//
// TODO: should exception case of "en" (English) eventually be removed?
result = result.Where(i => string.IsNullOrWhiteSpace(i.Language) ||
string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase) ||
string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase));
if (hasPreferredImageLanguages)
{
// Filter out languages that do not match the preferred image languages.
result = result.Where(i => string.IsNullOrWhiteSpace(i.Language) ||
preferredImageLanguages!.Contains(i.Language, StringComparer.OrdinalIgnoreCase));
}
else if (hasPreferredMetadataLanguage)
{
// Fallback to metadata language if no image languages configured
//
// TODO: should exception case of "en" (English) eventually be removed?
result = result.Where(i => string.IsNullOrWhiteSpace(i.Language) ||
string.Equals(preferredMetadataLanguage, i.Language, StringComparison.OrdinalIgnoreCase) ||
string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase));
}
}
return result.OrderByLanguageDescending(preferredLanguage);
return result.OrderByLanguageDescending(preferredMetadataLanguage, preferredImageLanguages);
}
catch (OperationCanceledException)
{