< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.MediaInfo.FFProbeVideoInfo
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
Line coverage
23%
Covered lines: 64
Uncovered lines: 212
Coverable lines: 276
Total lines: 644
Line coverage: 23.1%
Branch coverage
22%
Covered branches: 44
Total branches: 198
Branch coverage: 22.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/1/2026 - 12:13:05 AM Line coverage: 12.5% (35/279) Branch coverage: 4% (8/198) Total lines: 6455/5/2026 - 12:15:44 AM Line coverage: 12.7% (35/275) Branch coverage: 4.1% (8/192) Total lines: 6427/26/2026 - 12:17:51 AM Line coverage: 23.1% (64/276) Branch coverage: 22.2% (44/198) Total lines: 644 5/1/2026 - 12:13:05 AM Line coverage: 12.5% (35/279) Branch coverage: 4% (8/198) Total lines: 6455/5/2026 - 12:15:44 AM Line coverage: 12.7% (35/275) Branch coverage: 4.1% (8/192) Total lines: 6427/26/2026 - 12:17:51 AM Line coverage: 23.1% (64/276) Branch coverage: 22.2% (44/198) Total lines: 644

Coverage delta

Coverage delta 19 -19

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ProbeVideo()0%342180%
GetMediaInfo(...)0%620%
Fetch()0%2162460%
NormalizeChapterNames(...)0%4260%
FetchBdInfo(...)0%420200%
GetBDInfo(...)100%210%
FetchEmbeddedInfo(...)48.64%2377469.04%
FetchPeople(...)0%272160%
AddExternalSubtitlesAsync()0%7280%
AddExternalAudioAsync()100%210%
CreateDummyChapters(...)100%88100%

File(s)

/srv/git/jellyfin/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

#LineLine coverage
 1#pragma warning disable CA1068, CS1591
 2
 3using System;
 4using System.Collections.Generic;
 5using System.Globalization;
 6using System.Linq;
 7using System.Threading;
 8using System.Threading.Tasks;
 9using Jellyfin.Data.Enums;
 10using Jellyfin.Extensions;
 11using MediaBrowser.Controller.Chapters;
 12using MediaBrowser.Controller.Configuration;
 13using MediaBrowser.Controller.Entities;
 14using MediaBrowser.Controller.Entities.Movies;
 15using MediaBrowser.Controller.Entities.TV;
 16using MediaBrowser.Controller.Library;
 17using MediaBrowser.Controller.MediaEncoding;
 18using MediaBrowser.Controller.Persistence;
 19using MediaBrowser.Controller.Providers;
 20using MediaBrowser.Controller.Subtitles;
 21using MediaBrowser.Model.Configuration;
 22using MediaBrowser.Model.Dlna;
 23using MediaBrowser.Model.Dto;
 24using MediaBrowser.Model.Entities;
 25using MediaBrowser.Model.Globalization;
 26using MediaBrowser.Model.MediaInfo;
 27using Microsoft.Extensions.Logging;
 28
 29namespace MediaBrowser.Providers.MediaInfo
 30{
 31    public class FFProbeVideoInfo
 32    {
 33        private readonly ILogger<FFProbeVideoInfo> _logger;
 34        private readonly IMediaSourceManager _mediaSourceManager;
 35        private readonly IMediaEncoder _mediaEncoder;
 36        private readonly IBlurayExaminer _blurayExaminer;
 37        private readonly ILocalizationManager _localization;
 38        private readonly IChapterManager _chapterManager;
 39        private readonly IServerConfigurationManager _config;
 40        private readonly ISubtitleManager _subtitleManager;
 41        private readonly ILibraryManager _libraryManager;
 42        private readonly AudioResolver _audioResolver;
 43        private readonly SubtitleResolver _subtitleResolver;
 44        private readonly IMediaAttachmentRepository _mediaAttachmentRepository;
 45        private readonly IMediaStreamRepository _mediaStreamRepository;
 46
 47        public FFProbeVideoInfo(
 48            ILogger<FFProbeVideoInfo> logger,
 49            IMediaSourceManager mediaSourceManager,
 50            IMediaEncoder mediaEncoder,
 51            IBlurayExaminer blurayExaminer,
 52            ILocalizationManager localization,
 53            IChapterManager chapterManager,
 54            IServerConfigurationManager config,
 55            ISubtitleManager subtitleManager,
 56            ILibraryManager libraryManager,
 57            AudioResolver audioResolver,
 58            SubtitleResolver subtitleResolver,
 59            IMediaAttachmentRepository mediaAttachmentRepository,
 60            IMediaStreamRepository mediaStreamRepository)
 61        {
 4262            _logger = logger;
 4263            _mediaSourceManager = mediaSourceManager;
 4264            _mediaEncoder = mediaEncoder;
 4265            _blurayExaminer = blurayExaminer;
 4266            _localization = localization;
 4267            _chapterManager = chapterManager;
 4268            _config = config;
 4269            _subtitleManager = subtitleManager;
 4270            _libraryManager = libraryManager;
 4271            _audioResolver = audioResolver;
 4272            _subtitleResolver = subtitleResolver;
 4273            _mediaAttachmentRepository = mediaAttachmentRepository;
 4274            _mediaStreamRepository = mediaStreamRepository;
 4275        }
 76
 77        public async Task<ItemUpdateType> ProbeVideo<T>(
 78            T item,
 79            MetadataRefreshOptions options,
 80            CancellationToken cancellationToken)
 81            where T : Video
 82        {
 083            BlurayDiscInfo? blurayDiscInfo = null;
 84
 085            Model.MediaInfo.MediaInfo? mediaInfoResult = null;
 86
 087            if (!item.IsShortcut || options.EnableRemoteContentProbe)
 88            {
 089                if (item.VideoType == VideoType.Dvd)
 90                {
 91                    // Get list of playable .vob files
 092                    var vobs = _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null);
 93
 94                    // Return if no playable .vob files are found
 095                    if (vobs.Count == 0)
 96                    {
 097                        _logger.LogError("No playable .vob files found in DVD structure, skipping FFprobe.");
 098                        return ItemUpdateType.MetadataImport;
 99                    }
 100
 101                    // Fetch metadata of first .vob file
 0102                    mediaInfoResult = await GetMediaInfo(
 0103                        new Video
 0104                        {
 0105                            Path = vobs[0]
 0106                        },
 0107                        cancellationToken).ConfigureAwait(false);
 108
 109                    // Sum up the runtime of all .vob files skipping the first .vob
 0110                    for (var i = 1; i < vobs.Count; i++)
 111                    {
 0112                        var tmpMediaInfo = await GetMediaInfo(
 0113                            new Video
 0114                            {
 0115                                Path = vobs[i]
 0116                            },
 0117                            cancellationToken).ConfigureAwait(false);
 118
 0119                        mediaInfoResult.RunTimeTicks += tmpMediaInfo.RunTimeTicks;
 120                    }
 0121                }
 0122                else if (item.VideoType == VideoType.BluRay)
 123                {
 124                    // Get BD disc information
 0125                    blurayDiscInfo = GetBDInfo(item.Path);
 126
 127                    // Return if no playable .m2ts files are found
 0128                    if (blurayDiscInfo is null || blurayDiscInfo.Files.Length == 0)
 129                    {
 0130                        _logger.LogError("No playable .m2ts files found in Blu-ray structure, skipping FFprobe.");
 0131                        return ItemUpdateType.MetadataImport;
 132                    }
 133
 134                    // Fetch metadata of first .m2ts file
 0135                    mediaInfoResult = await GetMediaInfo(
 0136                        new Video
 0137                        {
 0138                            Path = blurayDiscInfo.Files[0]
 0139                        },
 0140                        cancellationToken).ConfigureAwait(false);
 141                }
 142                else
 143                {
 0144                    mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
 145                }
 146
 0147                cancellationToken.ThrowIfCancellationRequested();
 148            }
 149
 0150            await Fetch(item, cancellationToken, mediaInfoResult, blurayDiscInfo, options).ConfigureAwait(false);
 151
 0152            return ItemUpdateType.MetadataImport;
 0153        }
 154
 155        private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(
 156            Video item,
 157            CancellationToken cancellationToken)
 158        {
 0159            cancellationToken.ThrowIfCancellationRequested();
 160
 0161            var path = item.Path;
 0162            var protocol = item.PathProtocol ?? MediaProtocol.File;
 163
 0164            if (item.IsShortcut)
 165            {
 0166                path = item.ShortcutPath;
 0167                protocol = _mediaSourceManager.GetPathProtocol(path);
 168            }
 169
 0170            return _mediaEncoder.GetMediaInfo(
 0171                new MediaInfoRequest
 0172                {
 0173                    ExtractChapters = true,
 0174                    MediaType = DlnaProfileType.Video,
 0175                    MediaSource = new MediaSourceInfo
 0176                    {
 0177                        Path = path,
 0178                        Protocol = protocol,
 0179                        VideoType = item.VideoType,
 0180                        IsoType = item.IsoType
 0181                    }
 0182                },
 0183                cancellationToken);
 184        }
 185
 186        protected async Task Fetch(
 187            Video video,
 188            CancellationToken cancellationToken,
 189            Model.MediaInfo.MediaInfo? mediaInfo,
 190            BlurayDiscInfo? blurayInfo,
 191            MetadataRefreshOptions options)
 192        {
 0193            List<MediaStream> mediaStreams = new List<MediaStream>();
 194            IReadOnlyList<MediaAttachment> mediaAttachments;
 195            ChapterInfo[] chapters;
 196
 0197            await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
 198
 0199            if (mediaInfo is not null)
 200            {
 0201                mediaStreams.AddRange(mediaInfo.MediaStreams);
 202
 0203                mediaAttachments = mediaInfo.MediaAttachments;
 0204                video.TotalBitrate = mediaInfo.Bitrate;
 0205                video.RunTimeTicks = mediaInfo.RunTimeTicks;
 0206                video.Container = mediaInfo.Container;
 0207                var videoType = video.VideoType;
 0208                if (videoType == VideoType.BluRay || videoType == VideoType.Dvd)
 209                {
 0210                    video.Size = mediaInfo.Size;
 211                }
 212
 0213                chapters = mediaInfo.Chapters ?? [];
 0214                if (blurayInfo is not null)
 215                {
 0216                    FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo);
 217                }
 218            }
 219            else
 220            {
 0221                foreach (var mediaStream in video.GetMediaStreams())
 222                {
 0223                    if (!mediaStream.IsExternal)
 224                    {
 0225                        mediaStreams.Add(mediaStream);
 226                    }
 227                }
 228
 0229                mediaAttachments = [];
 0230                chapters = [];
 231            }
 232
 233            // Download and insert external streams before the streams from the file to preserve stream IDs on remote vi
 0234            await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
 235
 0236            for (var i = 0; i < mediaStreams.Count; i++)
 237            {
 0238                mediaStreams[i].Index = i;
 239            }
 240
 0241            var libraryOptions = _libraryManager.GetLibraryOptions(video);
 242
 0243            if (mediaInfo is not null)
 244            {
 0245                FetchEmbeddedInfo(video, mediaInfo, options, libraryOptions);
 0246                FetchPeople(video, mediaInfo, options);
 0247                video.Timestamp = mediaInfo.Timestamp;
 0248                video.Video3DFormat ??= mediaInfo.Video3DFormat;
 249            }
 250
 0251            if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowText || libraryOptions.AllowEmbedd
 252            {
 0253                _logger.LogDebug("Disabling embedded image subtitles for {Path} due to DisableEmbeddedImageSubtitles set
 0254                mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && !i.IsTextSubtitleStre
 255            }
 256
 0257            if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowImage || libraryOptions.AllowEmbed
 258            {
 0259                _logger.LogDebug("Disabling embedded text subtitles for {Path} due to DisableEmbeddedTextSubtitles setti
 0260                mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && i.IsTextSubtitleStrea
 261            }
 262
 0263            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
 264
 0265            video.Height = videoStream?.Height ?? 0;
 0266            video.Width = videoStream?.Width ?? 0;
 267
 0268            video.DefaultVideoStreamIndex = videoStream?.Index;
 269
 0270            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
 271
 0272            _mediaStreamRepository.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);
 273
 0274            _mediaAttachmentRepository.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
 275
 0276            if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh
 0277                || options.MetadataRefreshMode == MetadataRefreshMode.Default)
 278            {
 0279                if (_config.Configuration.DummyChapterDuration > 0 && chapters.Length <= 1 && mediaStreams.Any(i => i.Ty
 280                {
 0281                    chapters = CreateDummyChapters(video);
 282                }
 283
 0284                NormalizeChapterNames(chapters);
 285
 0286                var extractDuringScan = false;
 0287                if (libraryOptions is not null)
 288                {
 0289                    extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan;
 290                }
 291
 0292                await _chapterManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan,
 293
 0294                _chapterManager.SaveChapters(video, chapters);
 295            }
 0296        }
 297
 298        private void NormalizeChapterNames(ChapterInfo[] chapters)
 299        {
 0300            for (int i = 0; i < chapters.Length; i++)
 301            {
 0302                string? name = chapters[i].Name;
 303                // Check if the name is empty and/or if the name is a time
 304                // Some ripping programs do that.
 0305                if (string.IsNullOrWhiteSpace(name)
 0306                    || TimeSpan.TryParse(name, out _))
 307                {
 0308                    chapters[i].Name = string.Format(
 0309                        CultureInfo.InvariantCulture,
 0310                        _localization.GetLocalizedString("ChapterNameValue"),
 0311                        (i + 1).ToString(CultureInfo.InvariantCulture));
 312                }
 313            }
 0314        }
 315
 316        private void FetchBdInfo(Video video, ref ChapterInfo[] chapters, List<MediaStream> mediaStreams, BlurayDiscInfo
 317        {
 0318            var ffmpegVideoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
 0319            var externalStreams = mediaStreams.Where(s => s.IsExternal).ToList();
 320
 321            // Fill video properties from the BDInfo result
 0322            mediaStreams.Clear();
 323
 324            // Rebuild the list with external streams first
 0325            int index = 0;
 0326            foreach (var stream in externalStreams.Concat(blurayInfo.MediaStreams))
 327            {
 0328                stream.Index = index++;
 0329                mediaStreams.Add(stream);
 330            }
 331
 0332            if (blurayInfo.RunTimeTicks.HasValue && blurayInfo.RunTimeTicks.Value > 0)
 333            {
 0334                video.RunTimeTicks = blurayInfo.RunTimeTicks;
 335            }
 336
 0337            if (blurayInfo.Chapters is not null)
 338            {
 0339                double[] brChapter = blurayInfo.Chapters;
 0340                chapters = new ChapterInfo[brChapter.Length];
 0341                for (int i = 0; i < brChapter.Length; i++)
 342                {
 0343                    chapters[i] = new ChapterInfo
 0344                    {
 0345                        StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks
 0346                    };
 347                }
 348            }
 349
 0350            var blurayVideoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
 351
 352            // Use the ffprobe values if these are empty
 0353            if (blurayVideoStream is not null && ffmpegVideoStream is not null)
 354            {
 355                // Always use ffmpeg's detected codec since that is what the rest of the codebase expects.
 0356                blurayVideoStream.Codec = ffmpegVideoStream.Codec;
 0357                blurayVideoStream.BitRate = blurayVideoStream.BitRate.GetValueOrDefault() == 0 ? ffmpegVideoStream.BitRa
 0358                blurayVideoStream.Width = blurayVideoStream.Width.GetValueOrDefault() == 0 ? ffmpegVideoStream.Width : b
 0359                blurayVideoStream.Height = blurayVideoStream.Height.GetValueOrDefault() == 0 ? ffmpegVideoStream.Height 
 0360                blurayVideoStream.ColorRange = ffmpegVideoStream.ColorRange;
 0361                blurayVideoStream.ColorSpace = ffmpegVideoStream.ColorSpace;
 0362                blurayVideoStream.ColorTransfer = ffmpegVideoStream.ColorTransfer;
 0363                blurayVideoStream.ColorPrimaries = ffmpegVideoStream.ColorPrimaries;
 0364                blurayVideoStream.BitDepth = ffmpegVideoStream.BitDepth;
 0365                blurayVideoStream.PixelFormat = ffmpegVideoStream.PixelFormat;
 366            }
 0367        }
 368
 369        /// <summary>
 370        /// Gets information about the longest playlist on a bdrom.
 371        /// </summary>
 372        /// <param name="path">The path.</param>
 373        /// <returns>VideoStream.</returns>
 374        private BlurayDiscInfo? GetBDInfo(string path)
 375        {
 0376            ArgumentException.ThrowIfNullOrEmpty(path);
 377
 378            try
 379            {
 0380                return _blurayExaminer.GetDiscInfo(path);
 381            }
 0382            catch (Exception ex)
 383            {
 0384                _logger.LogError(ex, "Error getting BDInfo");
 0385                return null;
 386            }
 0387        }
 388
 389        internal void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions refreshOptio
 390        {
 5391            var replaceData = refreshOptions.ReplaceAllMetadata;
 392
 5393            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.OfficialRating))
 394            {
 5395                if (string.IsNullOrWhiteSpace(video.OfficialRating) || replaceData)
 396                {
 5397                    video.OfficialRating = data.OfficialRating;
 398                }
 399            }
 400
 5401            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Genres))
 402            {
 5403                if (video.Genres.Length == 0 || replaceData)
 404                {
 5405                    video.Genres = [];
 406
 10407                    foreach (var genre in data.Genres.Trimmed())
 408                    {
 0409                        video.AddGenre(genre);
 410                    }
 411                }
 412            }
 413
 5414            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Studios))
 415            {
 5416                if (video.Studios.Length == 0 || replaceData)
 417                {
 5418                    video.SetStudios(data.Studios);
 419                }
 420            }
 421
 5422            if (!video.IsLocked && video is MusicVideo musicVideo)
 423            {
 0424                if (string.IsNullOrEmpty(musicVideo.Album) || replaceData)
 425                {
 0426                    musicVideo.Album = data.Album;
 427                }
 428
 0429                if (musicVideo.Artists.Count == 0 || replaceData)
 430                {
 0431                    musicVideo.Artists = data.Artists;
 432                }
 433            }
 434
 435            // Extras have no release date of their own, they inherit it from the item they belong to.
 5436            var useContainerDates = video.ExtraType is null;
 5437            if (useContainerDates && data.ProductionYear is not null)
 438            {
 2439                if (video.ProductionYear is null || replaceData)
 440                {
 2441                    video.ProductionYear = data.ProductionYear;
 442                }
 443            }
 444
 5445            if (useContainerDates && data.PremiereDate is not null)
 446            {
 2447                if (video.PremiereDate is null || replaceData)
 448                {
 2449                    video.PremiereDate = data.PremiereDate;
 450                }
 451            }
 452
 5453            if (data.IndexNumber.HasValue)
 454            {
 0455                if (!video.IndexNumber.HasValue || replaceData)
 456                {
 0457                    video.IndexNumber = data.IndexNumber;
 458                }
 459            }
 460
 5461            if (data.ParentIndexNumber.HasValue)
 462            {
 0463                if (!video.ParentIndexNumber.HasValue || replaceData)
 464                {
 0465                    video.ParentIndexNumber = data.ParentIndexNumber;
 466                }
 467            }
 468
 5469            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Name))
 470            {
 5471                if (!string.IsNullOrWhiteSpace(data.Name) && libraryOptions.EnableEmbeddedTitles)
 472                {
 473                    // Separate option to use the embedded name for extras because it will often be the same name as the
 0474                    if (!video.ExtraType.HasValue || libraryOptions.EnableEmbeddedExtrasTitles)
 475                    {
 0476                        video.Name = data.Name;
 477                    }
 478                }
 479
 5480                if (!string.IsNullOrWhiteSpace(data.ForcedSortName))
 481                {
 0482                    video.ForcedSortName = data.ForcedSortName;
 483                }
 484            }
 485
 486            // If we don't have a ProductionYear try and get it from PremiereDate
 5487            if (useContainerDates && video.PremiereDate is not null && video.ProductionYear is null)
 488            {
 0489                video.ProductionYear = video.PremiereDate.Value.ToLocalTime().Year;
 490            }
 491
 5492            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Overview))
 493            {
 5494                if (string.IsNullOrWhiteSpace(video.Overview) || replaceData)
 495                {
 5496                    video.Overview = data.Overview;
 497                }
 498            }
 5499        }
 500
 501        private void FetchPeople(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options)
 502        {
 0503            if (video.IsLocked
 0504                || video.LockedFields.Contains(MetadataField.Cast)
 0505                || data.People.Length == 0)
 506            {
 0507                return;
 508            }
 509
 0510            if (options.ReplaceAllMetadata || _libraryManager.GetPeople(video).Count == 0)
 511            {
 0512                var people = new List<PersonInfo>();
 513
 0514                foreach (var person in data.People)
 515                {
 0516                    if (!string.IsNullOrWhiteSpace(person.Name))
 517                    {
 0518                        PeopleHelper.AddPerson(people, new PersonInfo
 0519                        {
 0520                            Name = person.Name,
 0521                            Type = person.Type,
 0522                            Role = person.Role?.Trim()
 0523                        });
 524                    }
 525                }
 526
 0527                _libraryManager.UpdatePeople(video, people);
 528            }
 0529        }
 530
 531        /// <summary>
 532        /// Adds the external subtitles.
 533        /// </summary>
 534        /// <param name="video">The video.</param>
 535        /// <param name="currentStreams">The current streams.</param>
 536        /// <param name="options">The refreshOptions.</param>
 537        /// <param name="cancellationToken">The cancellation token.</param>
 538        /// <returns>Task.</returns>
 539        private async Task AddExternalSubtitlesAsync(
 540            Video video,
 541            List<MediaStream> currentStreams,
 542            MetadataRefreshOptions options,
 543            CancellationToken cancellationToken)
 544        {
 0545            var externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, 0, options.DirectorySer
 546
 0547            var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default ||
 0548                                            options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
 549
 0550            var libraryOptions = _libraryManager.GetLibraryOptions(video);
 551
 0552            if (enableSubtitleDownloading && libraryOptions.SubtitleDownloadLanguages is not null)
 553            {
 0554                var downloadedLanguages = await new SubtitleDownloader(
 0555                    _logger,
 0556                    _subtitleManager).DownloadSubtitles(
 0557                        video,
 0558                        currentStreams.Concat(externalSubtitleStreams).ToList(),
 0559                        libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent,
 0560                        libraryOptions.SkipSubtitlesIfAudioTrackMatches,
 0561                        libraryOptions.RequirePerfectSubtitleMatch,
 0562                        libraryOptions.SubtitleDownloadLanguages,
 0563                        libraryOptions.DisabledSubtitleFetchers,
 0564                        libraryOptions.SubtitleFetcherOrder,
 0565                        true,
 0566                        cancellationToken).ConfigureAwait(false);
 567
 568                // Rescan
 0569                if (downloadedLanguages.Count > 0)
 570                {
 0571                    externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, 0, options.Director
 572                }
 573            }
 574
 0575            video.SubtitleFiles = externalSubtitleStreams.Select(i => i.Path).Distinct().ToArray();
 576
 0577            currentStreams.InsertRange(0, externalSubtitleStreams);
 0578        }
 579
 580        /// <summary>
 581        /// Adds the external audio.
 582        /// </summary>
 583        /// <param name="video">The video.</param>
 584        /// <param name="currentStreams">The current streams.</param>
 585        /// <param name="options">The refreshOptions.</param>
 586        /// <param name="cancellationToken">The cancellation token.</param>
 587        private async Task AddExternalAudioAsync(
 588            Video video,
 589            List<MediaStream> currentStreams,
 590            MetadataRefreshOptions options,
 591            CancellationToken cancellationToken)
 592        {
 0593            var externalAudioStreams = await _audioResolver.GetExternalStreamsAsync(video, 0, options.DirectoryService, 
 594
 0595            video.AudioFiles = externalAudioStreams.Select(i => i.Path).Distinct().ToArray();
 596
 0597            currentStreams.AddRange(externalAudioStreams);
 0598        }
 599
 600        /// <summary>
 601        /// Creates dummy chapters.
 602        /// </summary>
 603        /// <param name="video">The video.</param>
 604        /// <returns>An array of dummy chapters.</returns>
 605        internal ChapterInfo[] CreateDummyChapters(Video video)
 606        {
 15607            var runtime = video.RunTimeTicks.GetValueOrDefault();
 608
 609            // Only process files with a runtime greater than 0 and less than 12h. The latter are likely corrupted.
 15610            if (runtime < 0 || runtime > TimeSpan.FromHours(12).Ticks)
 611            {
 3612                throw new ArgumentException(
 3613                    string.Format(
 3614                        CultureInfo.InvariantCulture,
 3615                        "{0} has an invalid runtime of {1} minutes",
 3616                        video.Name,
 3617                        TimeSpan.FromTicks(runtime).TotalMinutes));
 618            }
 619
 12620            long dummyChapterDuration = TimeSpan.FromSeconds(_config.Configuration.DummyChapterDuration).Ticks;
 621
 12622            if (runtime <= 0)
 623            {
 2624                return [];
 625            }
 626
 10627            int chapterCount = Math.Max(1, (int)(runtime / dummyChapterDuration));
 10628            var chapters = new ChapterInfo[chapterCount];
 629
 10630            long currentChapterTicks = 0;
 76631            for (int i = 0; i < chapterCount; i++)
 632            {
 28633                chapters[i] = new ChapterInfo
 28634                {
 28635                    StartPositionTicks = currentChapterTicks
 28636                };
 637
 28638                currentChapterTicks += dummyChapterDuration;
 639            }
 640
 10641            return chapters;
 642        }
 643    }
 644}

Methods/Properties

.ctor(Microsoft.Extensions.Logging.ILogger`1<MediaBrowser.Providers.MediaInfo.FFProbeVideoInfo>,MediaBrowser.Controller.Library.IMediaSourceManager,MediaBrowser.Controller.MediaEncoding.IMediaEncoder,MediaBrowser.Model.MediaInfo.IBlurayExaminer,MediaBrowser.Model.Globalization.ILocalizationManager,MediaBrowser.Controller.Chapters.IChapterManager,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Subtitles.ISubtitleManager,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Providers.MediaInfo.AudioResolver,MediaBrowser.Providers.MediaInfo.SubtitleResolver,MediaBrowser.Controller.Persistence.IMediaAttachmentRepository,MediaBrowser.Controller.Persistence.IMediaStreamRepository)
ProbeVideo()
GetMediaInfo(MediaBrowser.Controller.Entities.Video,System.Threading.CancellationToken)
Fetch()
NormalizeChapterNames(MediaBrowser.Model.Entities.ChapterInfo[])
FetchBdInfo(MediaBrowser.Controller.Entities.Video,MediaBrowser.Model.Entities.ChapterInfo[]&,System.Collections.Generic.List`1<MediaBrowser.Model.Entities.MediaStream>,MediaBrowser.Model.MediaInfo.BlurayDiscInfo)
GetBDInfo(System.String)
FetchEmbeddedInfo(MediaBrowser.Controller.Entities.Video,MediaBrowser.Model.MediaInfo.MediaInfo,MediaBrowser.Controller.Providers.MetadataRefreshOptions,MediaBrowser.Model.Configuration.LibraryOptions)
FetchPeople(MediaBrowser.Controller.Entities.Video,MediaBrowser.Model.MediaInfo.MediaInfo,MediaBrowser.Controller.Providers.MetadataRefreshOptions)
AddExternalSubtitlesAsync()
AddExternalAudioAsync()
CreateDummyChapters(MediaBrowser.Controller.Entities.Video)