diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-05-03 13:31:23 +0200 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-05-03 13:31:23 +0200 |
| commit | d68d0fa96267ad96eaa5a0ba37e072f59a71442a (patch) | |
| tree | c3a33238cc56857d8e3daa56db01f290118c9215 /MediaBrowser.Providers | |
| parent | 00b08c0b32b3c8fa36330d72e4a25c7b157de4e3 (diff) | |
| parent | d9ced0d6399c82ddad9e983605bb0d828a608e63 (diff) | |
Merge remote-tracking branch 'upstream/master' into perf-rebased
Diffstat (limited to 'MediaBrowser.Providers')
7 files changed, 142 insertions, 32 deletions
diff --git a/MediaBrowser.Providers/Books/OpenPackagingFormat/OpfReader.cs b/MediaBrowser.Providers/Books/OpenPackagingFormat/OpfReader.cs index 5d202c59e1..15ea2ce5ab 100644 --- a/MediaBrowser.Providers/Books/OpenPackagingFormat/OpfReader.cs +++ b/MediaBrowser.Providers/Books/OpenPackagingFormat/OpfReader.cs @@ -260,6 +260,8 @@ namespace MediaBrowser.Providers.Books.OpenPackagingFormat return PersonKind.Lyricist; case "mus": return PersonKind.AlbumArtist; + case "nrt": + return PersonKind.Narrator; case "oth": return PersonKind.Unknown; case "trl": diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index e0354dbdfa..727f481b65 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -255,7 +255,7 @@ namespace MediaBrowser.Providers.Manager catch (Exception ex) { result.ErrorMessage = ex.Message; - _logger.LogError(ex, "Error in {Provider}", provider.Name); + _logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, item.Path ?? item.Name); } } @@ -339,7 +339,7 @@ namespace MediaBrowser.Providers.Manager catch (Exception ex) { result.ErrorMessage = ex.Message; - _logger.LogError(ex, "Error in {Provider}", provider.Name); + _logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, item.Path ?? item.Name); } } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index e9cb46eab5..abdfb1e3b7 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -820,7 +820,7 @@ namespace MediaBrowser.Providers.Manager } catch (Exception ex) { - Logger.LogError(ex, "Error in {Provider}", provider.Name); + Logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, logName); // If a local provider fails, consider that a failure refreshResult.ErrorMessage = ex.Message; @@ -886,7 +886,7 @@ namespace MediaBrowser.Providers.Manager catch (Exception ex) { refreshResult.ErrorMessage = ex.Message; - Logger.LogError(ex, "Error in {Provider}", provider.Name); + Logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, logName); } } @@ -935,7 +935,7 @@ namespace MediaBrowser.Providers.Manager { refreshResult.Failures++; refreshResult.ErrorMessage = ex.Message; - Logger.LogError(ex, "Error in {Provider}", provider.Name); + Logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, logName); } } diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index 869e3f292e..0ecbb6f068 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using ATL; using Jellyfin.Data.Enums; using Jellyfin.Extensions; +using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; @@ -38,6 +39,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly LyricResolver _lyricResolver; private readonly ILyricManager _lyricManager; private readonly IMediaStreamRepository _mediaStreamRepository; + private readonly IChapterManager _chapterManager; /// <summary> /// Initializes a new instance of the <see cref="AudioFileProber"/> class. @@ -49,6 +51,7 @@ namespace MediaBrowser.Providers.MediaInfo /// <param name="lyricResolver">Instance of the <see cref="LyricResolver"/> interface.</param> /// <param name="lyricManager">Instance of the <see cref="ILyricManager"/> interface.</param> /// <param name="mediaStreamRepository">Instance of the <see cref="IMediaStreamRepository"/>.</param> + /// <param name="chapterManager">Instance of the <see cref="IChapterManager"/> interface.</param> public AudioFileProber( ILogger<AudioFileProber> logger, IMediaSourceManager mediaSourceManager, @@ -56,7 +59,8 @@ namespace MediaBrowser.Providers.MediaInfo ILibraryManager libraryManager, LyricResolver lyricResolver, ILyricManager lyricManager, - IMediaStreamRepository mediaStreamRepository) + IMediaStreamRepository mediaStreamRepository, + IChapterManager chapterManager) { _mediaEncoder = mediaEncoder; _libraryManager = libraryManager; @@ -65,6 +69,7 @@ namespace MediaBrowser.Providers.MediaInfo _lyricResolver = lyricResolver; _lyricManager = lyricManager; _mediaStreamRepository = mediaStreamRepository; + _chapterManager = chapterManager; ATL.Settings.DisplayValueSeparator = InternalValueSeparator; ATL.Settings.UseFileNameWhenNoTitle = false; ATL.Settings.ID3v2_separatev2v3Values = false; @@ -99,6 +104,7 @@ namespace MediaBrowser.Providers.MediaInfo new MediaInfoRequest { MediaType = DlnaProfileType.Audio, + ExtractChapters = item is AudioBook, MediaSource = new MediaSourceInfo { Path = path, @@ -151,6 +157,11 @@ namespace MediaBrowser.Providers.MediaInfo audio.HasLyrics = mediaStreams.Any(s => s.Type == MediaStreamType.Lyric); _mediaStreamRepository.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken); + + if (audio is AudioBook && mediaInfo.Chapters is { Length: > 0 }) + { + _chapterManager.SaveChapters(audio, mediaInfo.Chapters); + } } /// <summary> @@ -212,18 +223,6 @@ namespace MediaBrowser.Providers.MediaInfo albumArtists = albumArtists.SelectMany(a => SplitWithCustomDelimiter(a, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray(); } - foreach (var albumArtist in albumArtists) - { - if (!string.IsNullOrWhiteSpace(albumArtist)) - { - PeopleHelper.AddPerson(people, new PersonInfo - { - Name = albumArtist, - Type = PersonKind.AlbumArtist - }); - } - } - string[]? performers = null; if (libraryOptions.PreferNonstandardArtistsTag) { @@ -244,31 +243,99 @@ namespace MediaBrowser.Providers.MediaInfo performers = performers.SelectMany(p => SplitWithCustomDelimiter(p, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray(); } - foreach (var performer in performers) + var isAudioBook = audio is AudioBook; + + if (isAudioBook) { - if (!string.IsNullOrWhiteSpace(performer)) + // For audiobooks: AlbumArtists/Performers = Author, NARRATOR tag = Narrator, + // ILLUSTRATOR tag = Illustrator, Composer = fallback Narrator, other performers = Cast. + // If album_artist is missing, fall back to artist/performers for the author role. + var authorSource = albumArtists.Length > 0 ? albumArtists : performers; + var authorNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + + foreach (var author in authorSource) { - PeopleHelper.AddPerson(people, new PersonInfo + if (!string.IsNullOrWhiteSpace(author)) { - Name = performer, - Type = PersonKind.Artist - }); + authorNames.Add(author.Trim()); + PeopleHelper.AddPerson(people, new PersonInfo + { + Name = author.Trim(), + Type = PersonKind.Author + }); + } + } + + // Composer tag = Narrator (Audiobookshelf and other tools use Composer for narrator) + if (!string.IsNullOrWhiteSpace(trackComposer)) + { + foreach (var composer in trackComposer.Split(InternalValueSeparator)) + { + if (!string.IsNullOrWhiteSpace(composer)) + { + PeopleHelper.AddPerson(people, new PersonInfo + { + Name = composer.Trim(), + Type = PersonKind.Narrator + }); + } + } } - } - if (!string.IsNullOrWhiteSpace(trackComposer)) + // Any performers not already listed as authors get added as cast + foreach (var performer in performers) + { + if (!string.IsNullOrWhiteSpace(performer) && !authorNames.Contains(performer.Trim())) + { + PeopleHelper.AddPerson(people, new PersonInfo + { + Name = performer.Trim(), + Type = PersonKind.Actor + }); + } + } + } + else { - foreach (var composer in trackComposer.Split(InternalValueSeparator)) + // Standard music track handling + foreach (var albumArtist in albumArtists) + { + if (!string.IsNullOrWhiteSpace(albumArtist)) + { + PeopleHelper.AddPerson(people, new PersonInfo + { + Name = albumArtist, + Type = PersonKind.AlbumArtist + }); + } + } + + foreach (var performer in performers) { - if (!string.IsNullOrWhiteSpace(composer)) + if (!string.IsNullOrWhiteSpace(performer)) { PeopleHelper.AddPerson(people, new PersonInfo { - Name = composer, - Type = PersonKind.Composer + Name = performer, + Type = PersonKind.Artist }); } } + + if (!string.IsNullOrWhiteSpace(trackComposer)) + { + foreach (var composer in trackComposer.Split(InternalValueSeparator)) + { + if (!string.IsNullOrWhiteSpace(composer)) + { + PeopleHelper.AddPerson(people, new PersonInfo + { + Name = composer, + Type = PersonKind.Composer + }); + } + } + } } _libraryManager.UpdatePeople(audio, people); @@ -359,6 +426,33 @@ namespace MediaBrowser.Providers.MediaInfo } } + // Audiobook-specific metadata: Overview, Publisher, Series + if (audio is AudioBook audioBook) + { + if (!audio.LockedFields.Contains(MetadataField.Overview)) + { + var trackDescription = GetSanitizedStringTag(track.Description, audio.Path); + var trackComment = GetSanitizedStringTag(track.Comment, audio.Path); + var overview = !string.IsNullOrWhiteSpace(trackDescription) ? trackDescription : trackComment; + + if (!string.IsNullOrWhiteSpace(overview)) + { + if (options.ReplaceAllMetadata || string.IsNullOrEmpty(audio.Overview)) + { + audio.Overview = overview; + } + } + } + + // Publisher → Studio + var trackPublisher = GetSanitizedStringTag(track.Publisher, audio.Path); + if (!string.IsNullOrWhiteSpace(trackPublisher) + && (options.ReplaceAllMetadata || audio.Studios is null || audio.Studios.Length == 0)) + { + audio.SetStudios(new[] { trackPublisher! }); + } + } + TryGetSanitizedAdditionalFields(track, "REPLAYGAIN_TRACK_GAIN", out var trackGainTag); if (trackGainTag is not null) diff --git a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs index c3ff26202f..789df8f061 100644 --- a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs @@ -110,7 +110,8 @@ namespace MediaBrowser.Providers.MediaInfo libraryManager, _lyricResolver, lyricManager, - mediaStreamRepository); + mediaStreamRepository, + chapterManager); } /// <inheritdoc /> diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 7188e9804e..f1582febf2 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -12,6 +12,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Tasks; @@ -26,19 +27,24 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ISubtitleManager _subtitleManager; private readonly ILogger<SubtitleScheduledTask> _logger; private readonly ILocalizationManager _localization; + private readonly ISubtitleProvider[] _subtitleProviders; public SubtitleScheduledTask( ILibraryManager libraryManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger<SubtitleScheduledTask> logger, - ILocalizationManager localization) + ILocalizationManager localization, + IEnumerable<ISubtitleProvider> subtitleProviders) { _libraryManager = libraryManager; _config = config; _subtitleManager = subtitleManager; _logger = logger; _localization = localization; + _subtitleProviders = subtitleProviders + .OrderBy(i => i is IHasOrder hasOrder ? hasOrder.Order : 0) + .ToArray(); } public string Name => _localization.GetLocalizedString("TaskDownloadMissingSubtitles"); @@ -76,6 +82,12 @@ namespace MediaBrowser.Providers.MediaInfo continue; } + if (_subtitleProviders.All(provider => libraryOptions.DisabledSubtitleFetchers.Contains(provider.Name, StringComparer.OrdinalIgnoreCase))) + { + // Skip this library if all subtitle providers are disabled + continue; + } + subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs index 3eacc4f0f0..590cf795de 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Api [Authorize] [Route("[controller]")] [Produces(MediaTypeNames.Application.Json)] + [ApiExplorerSettings(IgnoreApi = true)] public class TmdbController : ControllerBase { private readonly TmdbClientManager _tmdbClientManager; |
