< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.MediaInfo.FFProbeVideoInfo
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
Line coverage
12%
Covered lines: 35
Uncovered lines: 240
Coverable lines: 275
Total lines: 642
Line coverage: 12.7%
Branch coverage
4%
Covered branches: 8
Total branches: 192
Branch coverage: 4.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 AM Line coverage: 23% (36/156) Branch coverage: 6.6% (8/120) Total lines: 6744/12/2026 - 12:13:54 AM Line coverage: 22.7% (36/158) Branch coverage: 6.6% (8/120) Total lines: 6764/19/2026 - 12:14:27 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: 642 1/23/2026 - 12:11:06 AM Line coverage: 23% (36/156) Branch coverage: 6.6% (8/120) Total lines: 6744/12/2026 - 12:13:54 AM Line coverage: 22.7% (36/158) Branch coverage: 6.6% (8/120) Total lines: 6764/19/2026 - 12:14:27 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: 642

Coverage delta

Coverage delta 11 -11

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(...)0%4692680%
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        {
 3662            _logger = logger;
 3663            _mediaSourceManager = mediaSourceManager;
 3664            _mediaEncoder = mediaEncoder;
 3665            _blurayExaminer = blurayExaminer;
 3666            _localization = localization;
 3667            _chapterManager = chapterManager;
 3668            _config = config;
 3669            _subtitleManager = subtitleManager;
 3670            _libraryManager = libraryManager;
 3671            _audioResolver = audioResolver;
 3672            _subtitleResolver = subtitleResolver;
 3673            _mediaAttachmentRepository = mediaAttachmentRepository;
 3674            _mediaStreamRepository = mediaStreamRepository;
 3675        }
 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        private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions refreshOption
 390        {
 0391            var replaceData = refreshOptions.ReplaceAllMetadata;
 392
 0393            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.OfficialRating))
 394            {
 0395                if (string.IsNullOrWhiteSpace(video.OfficialRating) || replaceData)
 396                {
 0397                    video.OfficialRating = data.OfficialRating;
 398                }
 399            }
 400
 0401            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Genres))
 402            {
 0403                if (video.Genres.Length == 0 || replaceData)
 404                {
 0405                    video.Genres = [];
 406
 0407                    foreach (var genre in data.Genres.Trimmed())
 408                    {
 0409                        video.AddGenre(genre);
 410                    }
 411                }
 412            }
 413
 0414            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Studios))
 415            {
 0416                if (video.Studios.Length == 0 || replaceData)
 417                {
 0418                    video.SetStudios(data.Studios);
 419                }
 420            }
 421
 0422            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
 0435            if (data.ProductionYear.HasValue)
 436            {
 0437                if (!video.ProductionYear.HasValue || replaceData)
 438                {
 0439                    video.ProductionYear = data.ProductionYear;
 440                }
 441            }
 442
 0443            if (data.PremiereDate.HasValue)
 444            {
 0445                if (!video.PremiereDate.HasValue || replaceData)
 446                {
 0447                    video.PremiereDate = data.PremiereDate;
 448                }
 449            }
 450
 0451            if (data.IndexNumber.HasValue)
 452            {
 0453                if (!video.IndexNumber.HasValue || replaceData)
 454                {
 0455                    video.IndexNumber = data.IndexNumber;
 456                }
 457            }
 458
 0459            if (data.ParentIndexNumber.HasValue)
 460            {
 0461                if (!video.ParentIndexNumber.HasValue || replaceData)
 462                {
 0463                    video.ParentIndexNumber = data.ParentIndexNumber;
 464                }
 465            }
 466
 0467            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Name))
 468            {
 0469                if (!string.IsNullOrWhiteSpace(data.Name) && libraryOptions.EnableEmbeddedTitles)
 470                {
 471                    // Separate option to use the embedded name for extras because it will often be the same name as the
 0472                    if (!video.ExtraType.HasValue || libraryOptions.EnableEmbeddedExtrasTitles)
 473                    {
 0474                        video.Name = data.Name;
 475                    }
 476                }
 477
 0478                if (!string.IsNullOrWhiteSpace(data.ForcedSortName))
 479                {
 0480                    video.ForcedSortName = data.ForcedSortName;
 481                }
 482            }
 483
 484            // If we don't have a ProductionYear try and get it from PremiereDate
 0485            if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue)
 486            {
 0487                video.ProductionYear = video.PremiereDate.Value.ToLocalTime().Year;
 488            }
 489
 0490            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Overview))
 491            {
 0492                if (string.IsNullOrWhiteSpace(video.Overview) || replaceData)
 493                {
 0494                    video.Overview = data.Overview;
 495                }
 496            }
 0497        }
 498
 499        private void FetchPeople(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options)
 500        {
 0501            if (video.IsLocked
 0502                || video.LockedFields.Contains(MetadataField.Cast)
 0503                || data.People.Length == 0)
 504            {
 0505                return;
 506            }
 507
 0508            if (options.ReplaceAllMetadata || _libraryManager.GetPeople(video).Count == 0)
 509            {
 0510                var people = new List<PersonInfo>();
 511
 0512                foreach (var person in data.People)
 513                {
 0514                    if (!string.IsNullOrWhiteSpace(person.Name))
 515                    {
 0516                        PeopleHelper.AddPerson(people, new PersonInfo
 0517                        {
 0518                            Name = person.Name,
 0519                            Type = person.Type,
 0520                            Role = person.Role?.Trim()
 0521                        });
 522                    }
 523                }
 524
 0525                _libraryManager.UpdatePeople(video, people);
 526            }
 0527        }
 528
 529        /// <summary>
 530        /// Adds the external subtitles.
 531        /// </summary>
 532        /// <param name="video">The video.</param>
 533        /// <param name="currentStreams">The current streams.</param>
 534        /// <param name="options">The refreshOptions.</param>
 535        /// <param name="cancellationToken">The cancellation token.</param>
 536        /// <returns>Task.</returns>
 537        private async Task AddExternalSubtitlesAsync(
 538            Video video,
 539            List<MediaStream> currentStreams,
 540            MetadataRefreshOptions options,
 541            CancellationToken cancellationToken)
 542        {
 0543            var externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, 0, options.DirectorySer
 544
 0545            var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default ||
 0546                                            options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
 547
 0548            var libraryOptions = _libraryManager.GetLibraryOptions(video);
 549
 0550            if (enableSubtitleDownloading && libraryOptions.SubtitleDownloadLanguages is not null)
 551            {
 0552                var downloadedLanguages = await new SubtitleDownloader(
 0553                    _logger,
 0554                    _subtitleManager).DownloadSubtitles(
 0555                        video,
 0556                        currentStreams.Concat(externalSubtitleStreams).ToList(),
 0557                        libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent,
 0558                        libraryOptions.SkipSubtitlesIfAudioTrackMatches,
 0559                        libraryOptions.RequirePerfectSubtitleMatch,
 0560                        libraryOptions.SubtitleDownloadLanguages,
 0561                        libraryOptions.DisabledSubtitleFetchers,
 0562                        libraryOptions.SubtitleFetcherOrder,
 0563                        true,
 0564                        cancellationToken).ConfigureAwait(false);
 565
 566                // Rescan
 0567                if (downloadedLanguages.Count > 0)
 568                {
 0569                    externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, 0, options.Director
 570                }
 571            }
 572
 0573            video.SubtitleFiles = externalSubtitleStreams.Select(i => i.Path).Distinct().ToArray();
 574
 0575            currentStreams.InsertRange(0, externalSubtitleStreams);
 0576        }
 577
 578        /// <summary>
 579        /// Adds the external audio.
 580        /// </summary>
 581        /// <param name="video">The video.</param>
 582        /// <param name="currentStreams">The current streams.</param>
 583        /// <param name="options">The refreshOptions.</param>
 584        /// <param name="cancellationToken">The cancellation token.</param>
 585        private async Task AddExternalAudioAsync(
 586            Video video,
 587            List<MediaStream> currentStreams,
 588            MetadataRefreshOptions options,
 589            CancellationToken cancellationToken)
 590        {
 0591            var externalAudioStreams = await _audioResolver.GetExternalStreamsAsync(video, 0, options.DirectoryService, 
 592
 0593            video.AudioFiles = externalAudioStreams.Select(i => i.Path).Distinct().ToArray();
 594
 0595            currentStreams.AddRange(externalAudioStreams);
 0596        }
 597
 598        /// <summary>
 599        /// Creates dummy chapters.
 600        /// </summary>
 601        /// <param name="video">The video.</param>
 602        /// <returns>An array of dummy chapters.</returns>
 603        internal ChapterInfo[] CreateDummyChapters(Video video)
 604        {
 15605            var runtime = video.RunTimeTicks.GetValueOrDefault();
 606
 607            // Only process files with a runtime greater than 0 and less than 12h. The latter are likely corrupted.
 15608            if (runtime < 0 || runtime > TimeSpan.FromHours(12).Ticks)
 609            {
 3610                throw new ArgumentException(
 3611                    string.Format(
 3612                        CultureInfo.InvariantCulture,
 3613                        "{0} has an invalid runtime of {1} minutes",
 3614                        video.Name,
 3615                        TimeSpan.FromTicks(runtime).TotalMinutes));
 616            }
 617
 12618            long dummyChapterDuration = TimeSpan.FromSeconds(_config.Configuration.DummyChapterDuration).Ticks;
 619
 12620            if (runtime <= 0)
 621            {
 2622                return [];
 623            }
 624
 10625            int chapterCount = Math.Max(1, (int)(runtime / dummyChapterDuration));
 10626            var chapters = new ChapterInfo[chapterCount];
 627
 10628            long currentChapterTicks = 0;
 76629            for (int i = 0; i < chapterCount; i++)
 630            {
 28631                chapters[i] = new ChapterInfo
 28632                {
 28633                    StartPositionTicks = currentChapterTicks
 28634                };
 635
 28636                currentChapterTicks += dummyChapterDuration;
 637            }
 638
 10639            return chapters;
 640        }
 641    }
 642}

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)