< 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: 36
Uncovered lines: 119
Coverable lines: 155
Total lines: 673
Line coverage: 23.2%
Branch coverage
6%
Covered branches: 8
Total branches: 116
Branch coverage: 6.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetMediaInfo(...)0%620%
NormalizeChapterNames(...)0%4260%
FetchBdInfo(...)0%420200%
GetBDInfo(...)100%210%
FetchEmbeddedInfo(...)0%4692680%
FetchPeople(...)0%156120%
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.Extensions;
 10using MediaBrowser.Common.Configuration;
 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 MediaBrowser.Model.Providers;
 28using Microsoft.Extensions.Logging;
 29
 30namespace MediaBrowser.Providers.MediaInfo
 31{
 32    public class FFProbeVideoInfo
 33    {
 34        private readonly ILogger<FFProbeVideoInfo> _logger;
 35        private readonly IMediaSourceManager _mediaSourceManager;
 36        private readonly IMediaEncoder _mediaEncoder;
 37        private readonly IBlurayExaminer _blurayExaminer;
 38        private readonly ILocalizationManager _localization;
 39        private readonly IChapterManager _chapterManager;
 40        private readonly IServerConfigurationManager _config;
 41        private readonly ISubtitleManager _subtitleManager;
 42        private readonly ILibraryManager _libraryManager;
 43        private readonly AudioResolver _audioResolver;
 44        private readonly SubtitleResolver _subtitleResolver;
 45        private readonly IMediaAttachmentRepository _mediaAttachmentRepository;
 46        private readonly IMediaStreamRepository _mediaStreamRepository;
 47
 48        public FFProbeVideoInfo(
 49            ILogger<FFProbeVideoInfo> logger,
 50            IMediaSourceManager mediaSourceManager,
 51            IMediaEncoder mediaEncoder,
 52            IBlurayExaminer blurayExaminer,
 53            ILocalizationManager localization,
 54            IChapterManager chapterManager,
 55            IServerConfigurationManager config,
 56            ISubtitleManager subtitleManager,
 57            ILibraryManager libraryManager,
 58            AudioResolver audioResolver,
 59            SubtitleResolver subtitleResolver,
 60            IMediaAttachmentRepository mediaAttachmentRepository,
 61            IMediaStreamRepository mediaStreamRepository)
 62        {
 3063            _logger = logger;
 3064            _mediaSourceManager = mediaSourceManager;
 3065            _mediaEncoder = mediaEncoder;
 3066            _blurayExaminer = blurayExaminer;
 3067            _localization = localization;
 3068            _chapterManager = chapterManager;
 3069            _config = config;
 3070            _subtitleManager = subtitleManager;
 3071            _libraryManager = libraryManager;
 3072            _audioResolver = audioResolver;
 3073            _subtitleResolver = subtitleResolver;
 3074            _mediaAttachmentRepository = mediaAttachmentRepository;
 3075            _mediaStreamRepository = mediaStreamRepository;
 3076            _mediaStreamRepository = mediaStreamRepository;
 3077        }
 78
 79        public async Task<ItemUpdateType> ProbeVideo<T>(
 80            T item,
 81            MetadataRefreshOptions options,
 82            CancellationToken cancellationToken)
 83            where T : Video
 84        {
 85            BlurayDiscInfo? blurayDiscInfo = null;
 86
 87            Model.MediaInfo.MediaInfo? mediaInfoResult = null;
 88
 89            if (!item.IsShortcut || options.EnableRemoteContentProbe)
 90            {
 91                if (item.VideoType == VideoType.Dvd)
 92                {
 93                    // Get list of playable .vob files
 94                    var vobs = _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null);
 95
 96                    // Return if no playable .vob files are found
 97                    if (vobs.Count == 0)
 98                    {
 99                        _logger.LogError("No playable .vob files found in DVD structure, skipping FFprobe.");
 100                        return ItemUpdateType.MetadataImport;
 101                    }
 102
 103                    // Fetch metadata of first .vob file
 104                    mediaInfoResult = await GetMediaInfo(
 105                        new Video
 106                        {
 107                            Path = vobs[0]
 108                        },
 109                        cancellationToken).ConfigureAwait(false);
 110
 111                    // Sum up the runtime of all .vob files skipping the first .vob
 112                    for (var i = 1; i < vobs.Count; i++)
 113                    {
 114                        var tmpMediaInfo = await GetMediaInfo(
 115                            new Video
 116                            {
 117                                Path = vobs[i]
 118                            },
 119                            cancellationToken).ConfigureAwait(false);
 120
 121                        mediaInfoResult.RunTimeTicks += tmpMediaInfo.RunTimeTicks;
 122                    }
 123                }
 124                else if (item.VideoType == VideoType.BluRay)
 125                {
 126                    // Get BD disc information
 127                    blurayDiscInfo = GetBDInfo(item.Path);
 128
 129                    // Return if no playable .m2ts files are found
 130                    if (blurayDiscInfo is null || blurayDiscInfo.Files.Length == 0)
 131                    {
 132                        _logger.LogError("No playable .m2ts files found in Blu-ray structure, skipping FFprobe.");
 133                        return ItemUpdateType.MetadataImport;
 134                    }
 135
 136                    // Fetch metadata of first .m2ts file
 137                    mediaInfoResult = await GetMediaInfo(
 138                        new Video
 139                        {
 140                            Path = blurayDiscInfo.Files[0]
 141                        },
 142                        cancellationToken).ConfigureAwait(false);
 143                }
 144                else
 145                {
 146                    mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
 147                }
 148
 149                cancellationToken.ThrowIfCancellationRequested();
 150            }
 151
 152            await Fetch(item, cancellationToken, mediaInfoResult, blurayDiscInfo, options).ConfigureAwait(false);
 153
 154            return ItemUpdateType.MetadataImport;
 155        }
 156
 157        private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(
 158            Video item,
 159            CancellationToken cancellationToken)
 160        {
 0161            cancellationToken.ThrowIfCancellationRequested();
 162
 0163            var path = item.Path;
 0164            var protocol = item.PathProtocol ?? MediaProtocol.File;
 165
 0166            if (item.IsShortcut)
 167            {
 0168                path = item.ShortcutPath;
 0169                protocol = _mediaSourceManager.GetPathProtocol(path);
 170            }
 171
 0172            return _mediaEncoder.GetMediaInfo(
 0173                new MediaInfoRequest
 0174                {
 0175                    ExtractChapters = true,
 0176                    MediaType = DlnaProfileType.Video,
 0177                    MediaSource = new MediaSourceInfo
 0178                    {
 0179                        Path = path,
 0180                        Protocol = protocol,
 0181                        VideoType = item.VideoType,
 0182                        IsoType = item.IsoType
 0183                    }
 0184                },
 0185                cancellationToken);
 186        }
 187
 188        protected async Task Fetch(
 189            Video video,
 190            CancellationToken cancellationToken,
 191            Model.MediaInfo.MediaInfo? mediaInfo,
 192            BlurayDiscInfo? blurayInfo,
 193            MetadataRefreshOptions options)
 194        {
 195            List<MediaStream> mediaStreams = new List<MediaStream>();
 196            IReadOnlyList<MediaAttachment> mediaAttachments;
 197            ChapterInfo[] chapters;
 198
 199            // Add external streams before adding the streams from the file to preserve stream IDs on remote videos
 200            await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
 201
 202            await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
 203
 204            var startIndex = mediaStreams.Count == 0 ? 0 : (mediaStreams.Max(i => i.Index) + 1);
 205
 206            if (mediaInfo is not null)
 207            {
 208                foreach (var mediaStream in mediaInfo.MediaStreams)
 209                {
 210                    mediaStream.Index = startIndex++;
 211                    mediaStreams.Add(mediaStream);
 212                }
 213
 214                mediaAttachments = mediaInfo.MediaAttachments;
 215                video.TotalBitrate = mediaInfo.Bitrate;
 216                video.RunTimeTicks = mediaInfo.RunTimeTicks;
 217                video.Container = mediaInfo.Container;
 218                var videoType = video.VideoType;
 219                if (videoType == VideoType.BluRay || videoType == VideoType.Dvd)
 220                {
 221                    video.Size = mediaInfo.Size;
 222                }
 223
 224                chapters = mediaInfo.Chapters ?? [];
 225                if (blurayInfo is not null)
 226                {
 227                    FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo);
 228                }
 229            }
 230            else
 231            {
 232                foreach (var mediaStream in video.GetMediaStreams())
 233                {
 234                    if (!mediaStream.IsExternal)
 235                    {
 236                        mediaStream.Index = startIndex++;
 237                        mediaStreams.Add(mediaStream);
 238                    }
 239                }
 240
 241                mediaAttachments = [];
 242                chapters = [];
 243            }
 244
 245            var libraryOptions = _libraryManager.GetLibraryOptions(video);
 246
 247            if (mediaInfo is not null)
 248            {
 249                FetchEmbeddedInfo(video, mediaInfo, options, libraryOptions);
 250                FetchPeople(video, mediaInfo, options);
 251                video.Timestamp = mediaInfo.Timestamp;
 252                video.Video3DFormat ??= mediaInfo.Video3DFormat;
 253            }
 254
 255            if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowText || libraryOptions.AllowEmbedd
 256            {
 257                _logger.LogDebug("Disabling embedded image subtitles for {Path} due to DisableEmbeddedImageSubtitles set
 258                mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && !i.IsTextSubtitleStre
 259            }
 260
 261            if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowImage || libraryOptions.AllowEmbed
 262            {
 263                _logger.LogDebug("Disabling embedded text subtitles for {Path} due to DisableEmbeddedTextSubtitles setti
 264                mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && i.IsTextSubtitleStrea
 265            }
 266
 267            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
 268
 269            video.Height = videoStream?.Height ?? 0;
 270            video.Width = videoStream?.Width ?? 0;
 271
 272            video.DefaultVideoStreamIndex = videoStream?.Index;
 273
 274            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
 275
 276            _mediaStreamRepository.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);
 277
 278            if (mediaAttachments.Any())
 279            {
 280                _mediaAttachmentRepository.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
 281            }
 282
 283            if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh
 284                || options.MetadataRefreshMode == MetadataRefreshMode.Default)
 285            {
 286                if (_config.Configuration.DummyChapterDuration > 0 && chapters.Length == 0 && mediaStreams.Any(i => i.Ty
 287                {
 288                    chapters = CreateDummyChapters(video);
 289                }
 290
 291                NormalizeChapterNames(chapters);
 292
 293                var extractDuringScan = false;
 294                if (libraryOptions is not null)
 295                {
 296                    extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan;
 297                }
 298
 299                await _chapterManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan,
 300
 301                _chapterManager.SaveChapters(video, chapters);
 302            }
 303        }
 304
 305        private void NormalizeChapterNames(ChapterInfo[] chapters)
 306        {
 0307            for (int i = 0; i < chapters.Length; i++)
 308            {
 0309                string? name = chapters[i].Name;
 310                // Check if the name is empty and/or if the name is a time
 311                // Some ripping programs do that.
 0312                if (string.IsNullOrWhiteSpace(name)
 0313                    || TimeSpan.TryParse(name, out _))
 314                {
 0315                    chapters[i].Name = string.Format(
 0316                        CultureInfo.InvariantCulture,
 0317                        _localization.GetLocalizedString("ChapterNameValue"),
 0318                        (i + 1).ToString(CultureInfo.InvariantCulture));
 319                }
 320            }
 0321        }
 322
 323        private void FetchBdInfo(Video video, ref ChapterInfo[] chapters, List<MediaStream> mediaStreams, BlurayDiscInfo
 324        {
 0325            var ffmpegVideoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
 0326            var externalStreams = mediaStreams.Where(s => s.IsExternal).ToList();
 327
 328            // Fill video properties from the BDInfo result
 0329            mediaStreams.Clear();
 330
 331            // Rebuild the list with external streams first
 0332            int index = 0;
 0333            foreach (var stream in externalStreams.Concat(blurayInfo.MediaStreams))
 334            {
 0335                stream.Index = index++;
 0336                mediaStreams.Add(stream);
 337            }
 338
 0339            if (blurayInfo.RunTimeTicks.HasValue && blurayInfo.RunTimeTicks.Value > 0)
 340            {
 0341                video.RunTimeTicks = blurayInfo.RunTimeTicks;
 342            }
 343
 0344            if (blurayInfo.Chapters is not null)
 345            {
 0346                double[] brChapter = blurayInfo.Chapters;
 0347                chapters = new ChapterInfo[brChapter.Length];
 0348                for (int i = 0; i < brChapter.Length; i++)
 349                {
 0350                    chapters[i] = new ChapterInfo
 0351                    {
 0352                        StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks
 0353                    };
 354                }
 355            }
 356
 0357            var blurayVideoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
 358
 359            // Use the ffprobe values if these are empty
 0360            if (blurayVideoStream is not null && ffmpegVideoStream is not null)
 361            {
 362                // Always use ffmpeg's detected codec since that is what the rest of the codebase expects.
 0363                blurayVideoStream.Codec = ffmpegVideoStream.Codec;
 0364                blurayVideoStream.BitRate = blurayVideoStream.BitRate.GetValueOrDefault() == 0 ? ffmpegVideoStream.BitRa
 0365                blurayVideoStream.Width = blurayVideoStream.Width.GetValueOrDefault() == 0 ? ffmpegVideoStream.Width : b
 0366                blurayVideoStream.Height = blurayVideoStream.Height.GetValueOrDefault() == 0 ? ffmpegVideoStream.Height 
 0367                blurayVideoStream.ColorRange = ffmpegVideoStream.ColorRange;
 0368                blurayVideoStream.ColorSpace = ffmpegVideoStream.ColorSpace;
 0369                blurayVideoStream.ColorTransfer = ffmpegVideoStream.ColorTransfer;
 0370                blurayVideoStream.ColorPrimaries = ffmpegVideoStream.ColorPrimaries;
 371            }
 0372        }
 373
 374        /// <summary>
 375        /// Gets information about the longest playlist on a bdrom.
 376        /// </summary>
 377        /// <param name="path">The path.</param>
 378        /// <returns>VideoStream.</returns>
 379        private BlurayDiscInfo? GetBDInfo(string path)
 380        {
 0381            ArgumentException.ThrowIfNullOrEmpty(path);
 382
 383            try
 384            {
 0385                return _blurayExaminer.GetDiscInfo(path);
 386            }
 0387            catch (Exception ex)
 388            {
 0389                _logger.LogError(ex, "Error getting BDInfo");
 0390                return null;
 391            }
 0392        }
 393
 394        private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions refreshOption
 395        {
 0396            var replaceData = refreshOptions.ReplaceAllMetadata;
 397
 0398            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.OfficialRating))
 399            {
 0400                if (string.IsNullOrWhiteSpace(video.OfficialRating) || replaceData)
 401                {
 0402                    video.OfficialRating = data.OfficialRating;
 403                }
 404            }
 405
 0406            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Genres))
 407            {
 0408                if (video.Genres.Length == 0 || replaceData)
 409                {
 0410                    video.Genres = [];
 411
 0412                    foreach (var genre in data.Genres.Trimmed())
 413                    {
 0414                        video.AddGenre(genre);
 415                    }
 416                }
 417            }
 418
 0419            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Studios))
 420            {
 0421                if (video.Studios.Length == 0 || replaceData)
 422                {
 0423                    video.SetStudios(data.Studios);
 424                }
 425            }
 426
 0427            if (!video.IsLocked && video is MusicVideo musicVideo)
 428            {
 0429                if (string.IsNullOrEmpty(musicVideo.Album) || replaceData)
 430                {
 0431                    musicVideo.Album = data.Album;
 432                }
 433
 0434                if (musicVideo.Artists.Count == 0 || replaceData)
 435                {
 0436                    musicVideo.Artists = data.Artists;
 437                }
 438            }
 439
 0440            if (data.ProductionYear.HasValue)
 441            {
 0442                if (!video.ProductionYear.HasValue || replaceData)
 443                {
 0444                    video.ProductionYear = data.ProductionYear;
 445                }
 446            }
 447
 0448            if (data.PremiereDate.HasValue)
 449            {
 0450                if (!video.PremiereDate.HasValue || replaceData)
 451                {
 0452                    video.PremiereDate = data.PremiereDate;
 453                }
 454            }
 455
 0456            if (data.IndexNumber.HasValue)
 457            {
 0458                if (!video.IndexNumber.HasValue || replaceData)
 459                {
 0460                    video.IndexNumber = data.IndexNumber;
 461                }
 462            }
 463
 0464            if (data.ParentIndexNumber.HasValue)
 465            {
 0466                if (!video.ParentIndexNumber.HasValue || replaceData)
 467                {
 0468                    video.ParentIndexNumber = data.ParentIndexNumber;
 469                }
 470            }
 471
 0472            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Name))
 473            {
 0474                if (!string.IsNullOrWhiteSpace(data.Name) && libraryOptions.EnableEmbeddedTitles)
 475                {
 476                    // Separate option to use the embedded name for extras because it will often be the same name as the
 0477                    if (!video.ExtraType.HasValue || libraryOptions.EnableEmbeddedExtrasTitles)
 478                    {
 0479                        video.Name = data.Name;
 480                    }
 481                }
 482
 0483                if (!string.IsNullOrWhiteSpace(data.ForcedSortName))
 484                {
 0485                    video.ForcedSortName = data.ForcedSortName;
 486                }
 487            }
 488
 489            // If we don't have a ProductionYear try and get it from PremiereDate
 0490            if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue)
 491            {
 0492                video.ProductionYear = video.PremiereDate.Value.ToLocalTime().Year;
 493            }
 494
 0495            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Overview))
 496            {
 0497                if (string.IsNullOrWhiteSpace(video.Overview) || replaceData)
 498                {
 0499                    video.Overview = data.Overview;
 500                }
 501            }
 0502        }
 503
 504        private void FetchPeople(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options)
 505        {
 0506            if (video.IsLocked
 0507                || video.LockedFields.Contains(MetadataField.Cast)
 0508                || data.People.Length == 0)
 509            {
 0510                return;
 511            }
 512
 0513            if (options.ReplaceAllMetadata || _libraryManager.GetPeople(video).Count == 0)
 514            {
 0515                var people = new List<PersonInfo>();
 516
 0517                foreach (var person in data.People)
 518                {
 0519                    PeopleHelper.AddPerson(people, new PersonInfo
 0520                    {
 0521                        Name = person.Name.Trim(),
 0522                        Type = person.Type,
 0523                        Role = person.Role.Trim()
 0524                    });
 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        {
 545            var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1);
 546            var externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options.Dir
 547
 548            var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default ||
 549                                            options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
 550
 551            var subtitleOptions = _config.GetConfiguration<SubtitleOptions>("subtitles");
 552
 553            var libraryOptions = _libraryManager.GetLibraryOptions(video);
 554
 555            string[] subtitleDownloadLanguages;
 556            bool skipIfEmbeddedSubtitlesPresent;
 557            bool skipIfAudioTrackMatches;
 558            bool requirePerfectMatch;
 559            bool enabled;
 560
 561            if (libraryOptions.SubtitleDownloadLanguages is null)
 562            {
 563                subtitleDownloadLanguages = subtitleOptions.DownloadLanguages;
 564                skipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent;
 565                skipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches;
 566                requirePerfectMatch = subtitleOptions.RequirePerfectMatch;
 567                enabled = (subtitleOptions.DownloadEpisodeSubtitles &&
 568                video is Episode) ||
 569                (subtitleOptions.DownloadMovieSubtitles &&
 570                video is Movie);
 571            }
 572            else
 573            {
 574                subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
 575                skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
 576                skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
 577                requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
 578                enabled = true;
 579            }
 580
 581            if (enableSubtitleDownloading && enabled)
 582            {
 583                var downloadedLanguages = await new SubtitleDownloader(
 584                    _logger,
 585                    _subtitleManager).DownloadSubtitles(
 586                        video,
 587                        currentStreams.Concat(externalSubtitleStreams).ToList(),
 588                        skipIfEmbeddedSubtitlesPresent,
 589                        skipIfAudioTrackMatches,
 590                        requirePerfectMatch,
 591                        subtitleDownloadLanguages,
 592                        libraryOptions.DisabledSubtitleFetchers,
 593                        libraryOptions.SubtitleFetcherOrder,
 594                        true,
 595                        cancellationToken).ConfigureAwait(false);
 596
 597                // Rescan
 598                if (downloadedLanguages.Count > 0)
 599                {
 600                    externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options
 601                }
 602            }
 603
 604            video.SubtitleFiles = externalSubtitleStreams.Select(i => i.Path).Distinct().ToArray();
 605
 606            currentStreams.AddRange(externalSubtitleStreams);
 607        }
 608
 609        /// <summary>
 610        /// Adds the external audio.
 611        /// </summary>
 612        /// <param name="video">The video.</param>
 613        /// <param name="currentStreams">The current streams.</param>
 614        /// <param name="options">The refreshOptions.</param>
 615        /// <param name="cancellationToken">The cancellation token.</param>
 616        private async Task AddExternalAudioAsync(
 617            Video video,
 618            List<MediaStream> currentStreams,
 619            MetadataRefreshOptions options,
 620            CancellationToken cancellationToken)
 621        {
 622            var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1;
 623            var externalAudioStreams = await _audioResolver.GetExternalStreamsAsync(video, startIndex, options.Directory
 624
 625            video.AudioFiles = externalAudioStreams.Select(i => i.Path).Distinct().ToArray();
 626
 627            currentStreams.AddRange(externalAudioStreams);
 628        }
 629
 630        /// <summary>
 631        /// Creates dummy chapters.
 632        /// </summary>
 633        /// <param name="video">The video.</param>
 634        /// <returns>An array of dummy chapters.</returns>
 635        internal ChapterInfo[] CreateDummyChapters(Video video)
 636        {
 9637            var runtime = video.RunTimeTicks.GetValueOrDefault();
 638
 639            // Only process files with a runtime greater than 0 and less than 12h. The latter are likely corrupted.
 9640            if (runtime < 0 || runtime > TimeSpan.FromHours(12).Ticks)
 641            {
 3642                throw new ArgumentException(
 3643                    string.Format(
 3644                        CultureInfo.InvariantCulture,
 3645                        "{0} has an invalid runtime of {1} minutes",
 3646                        video.Name,
 3647                        TimeSpan.FromTicks(runtime).TotalMinutes));
 648            }
 649
 6650            long dummyChapterDuration = TimeSpan.FromSeconds(_config.Configuration.DummyChapterDuration).Ticks;
 6651            if (runtime <= dummyChapterDuration)
 652            {
 4653                return [];
 654            }
 655
 2656            int chapterCount = (int)(runtime / dummyChapterDuration);
 2657            var chapters = new ChapterInfo[chapterCount];
 658
 2659            long currentChapterTicks = 0;
 26660            for (int i = 0; i < chapterCount; i++)
 661            {
 11662                chapters[i] = new ChapterInfo
 11663                {
 11664                    StartPositionTicks = currentChapterTicks
 11665                };
 666
 11667                currentChapterTicks += dummyChapterDuration;
 668            }
 669
 2670            return chapters;
 671        }
 672    }
 673}

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)
GetMediaInfo(MediaBrowser.Controller.Entities.Video,System.Threading.CancellationToken)
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)
CreateDummyChapters(MediaBrowser.Controller.Entities.Video)