< 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: 35
Uncovered lines: 117
Coverable lines: 152
Total lines: 664
Line coverage: 23%
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 MediaBrowser.Common.Configuration;
 10using MediaBrowser.Controller.Chapters;
 11using MediaBrowser.Controller.Configuration;
 12using MediaBrowser.Controller.Entities;
 13using MediaBrowser.Controller.Entities.Movies;
 14using MediaBrowser.Controller.Entities.TV;
 15using MediaBrowser.Controller.Library;
 16using MediaBrowser.Controller.MediaEncoding;
 17using MediaBrowser.Controller.Persistence;
 18using MediaBrowser.Controller.Providers;
 19using MediaBrowser.Controller.Subtitles;
 20using MediaBrowser.Model.Configuration;
 21using MediaBrowser.Model.Dlna;
 22using MediaBrowser.Model.Dto;
 23using MediaBrowser.Model.Entities;
 24using MediaBrowser.Model.Globalization;
 25using MediaBrowser.Model.MediaInfo;
 26using MediaBrowser.Model.Providers;
 27using Microsoft.Extensions.Logging;
 28
 29namespace MediaBrowser.Providers.MediaInfo
 30{
 31    public class FFProbeVideoInfo
 32    {
 33        private readonly ILogger<FFProbeVideoInfo> _logger;
 34        private readonly IMediaEncoder _mediaEncoder;
 35        private readonly IItemRepository _itemRepo;
 36        private readonly IBlurayExaminer _blurayExaminer;
 37        private readonly ILocalizationManager _localization;
 38        private readonly IEncodingManager _encodingManager;
 39        private readonly IServerConfigurationManager _config;
 40        private readonly ISubtitleManager _subtitleManager;
 41        private readonly IChapterManager _chapterManager;
 42        private readonly ILibraryManager _libraryManager;
 43        private readonly AudioResolver _audioResolver;
 44        private readonly SubtitleResolver _subtitleResolver;
 45        private readonly IMediaSourceManager _mediaSourceManager;
 46
 47        public FFProbeVideoInfo(
 48            ILogger<FFProbeVideoInfo> logger,
 49            IMediaSourceManager mediaSourceManager,
 50            IMediaEncoder mediaEncoder,
 51            IItemRepository itemRepo,
 52            IBlurayExaminer blurayExaminer,
 53            ILocalizationManager localization,
 54            IEncodingManager encodingManager,
 55            IServerConfigurationManager config,
 56            ISubtitleManager subtitleManager,
 57            IChapterManager chapterManager,
 58            ILibraryManager libraryManager,
 59            AudioResolver audioResolver,
 60            SubtitleResolver subtitleResolver)
 61        {
 3162            _logger = logger;
 3163            _mediaSourceManager = mediaSourceManager;
 3164            _mediaEncoder = mediaEncoder;
 3165            _itemRepo = itemRepo;
 3166            _blurayExaminer = blurayExaminer;
 3167            _localization = localization;
 3168            _encodingManager = encodingManager;
 3169            _config = config;
 3170            _subtitleManager = subtitleManager;
 3171            _chapterManager = chapterManager;
 3172            _libraryManager = libraryManager;
 3173            _audioResolver = audioResolver;
 3174            _subtitleResolver = subtitleResolver;
 3175        }
 76
 77        public async Task<ItemUpdateType> ProbeVideo<T>(
 78            T item,
 79            MetadataRefreshOptions options,
 80            CancellationToken cancellationToken)
 81            where T : Video
 82        {
 83            BlurayDiscInfo? blurayDiscInfo = null;
 84
 85            Model.MediaInfo.MediaInfo? mediaInfoResult = null;
 86
 87            if (!item.IsShortcut || options.EnableRemoteContentProbe)
 88            {
 89                if (item.VideoType == VideoType.Dvd)
 90                {
 91                    // Get list of playable .vob files
 92                    var vobs = _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null);
 93
 94                    // Return if no playable .vob files are found
 95                    if (vobs.Count == 0)
 96                    {
 97                        _logger.LogError("No playable .vob files found in DVD structure, skipping FFprobe.");
 98                        return ItemUpdateType.MetadataImport;
 99                    }
 100
 101                    // Fetch metadata of first .vob file
 102                    mediaInfoResult = await GetMediaInfo(
 103                        new Video
 104                        {
 105                            Path = vobs[0]
 106                        },
 107                        cancellationToken).ConfigureAwait(false);
 108
 109                    // Sum up the runtime of all .vob files skipping the first .vob
 110                    for (var i = 1; i < vobs.Count; i++)
 111                    {
 112                        var tmpMediaInfo = await GetMediaInfo(
 113                            new Video
 114                            {
 115                                Path = vobs[i]
 116                            },
 117                            cancellationToken).ConfigureAwait(false);
 118
 119                        mediaInfoResult.RunTimeTicks += tmpMediaInfo.RunTimeTicks;
 120                    }
 121                }
 122                else if (item.VideoType == VideoType.BluRay)
 123                {
 124                    // Get BD disc information
 125                    blurayDiscInfo = GetBDInfo(item.Path);
 126
 127                    // Return if no playable .m2ts files are found
 128                    if (blurayDiscInfo is null || blurayDiscInfo.Files.Length == 0)
 129                    {
 130                        _logger.LogError("No playable .m2ts files found in Blu-ray structure, skipping FFprobe.");
 131                        return ItemUpdateType.MetadataImport;
 132                    }
 133
 134                    // Fetch metadata of first .m2ts file
 135                    mediaInfoResult = await GetMediaInfo(
 136                        new Video
 137                        {
 138                            Path = blurayDiscInfo.Files[0]
 139                        },
 140                        cancellationToken).ConfigureAwait(false);
 141                }
 142                else
 143                {
 144                    mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
 145                }
 146
 147                cancellationToken.ThrowIfCancellationRequested();
 148            }
 149
 150            await Fetch(item, cancellationToken, mediaInfoResult, blurayDiscInfo, options).ConfigureAwait(false);
 151
 152            return ItemUpdateType.MetadataImport;
 153        }
 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        {
 193            List<MediaStream> mediaStreams = new List<MediaStream>();
 194            IReadOnlyList<MediaAttachment> mediaAttachments;
 195            ChapterInfo[] chapters;
 196
 197            // Add external streams before adding the streams from the file to preserve stream IDs on remote videos
 198            await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
 199
 200            await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
 201
 202            var startIndex = mediaStreams.Count == 0 ? 0 : (mediaStreams.Max(i => i.Index) + 1);
 203
 204            if (mediaInfo is not null)
 205            {
 206                foreach (var mediaStream in mediaInfo.MediaStreams)
 207                {
 208                    mediaStream.Index = startIndex++;
 209                    mediaStreams.Add(mediaStream);
 210                }
 211
 212                mediaAttachments = mediaInfo.MediaAttachments;
 213                video.TotalBitrate = mediaInfo.Bitrate;
 214                video.RunTimeTicks = mediaInfo.RunTimeTicks;
 215                video.Size = mediaInfo.Size;
 216                video.Container = mediaInfo.Container;
 217
 218                chapters = mediaInfo.Chapters ?? Array.Empty<ChapterInfo>();
 219                if (blurayInfo is not null)
 220                {
 221                    FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo);
 222                }
 223            }
 224            else
 225            {
 226                foreach (var mediaStream in video.GetMediaStreams())
 227                {
 228                    if (!mediaStream.IsExternal)
 229                    {
 230                        mediaStream.Index = startIndex++;
 231                        mediaStreams.Add(mediaStream);
 232                    }
 233                }
 234
 235                mediaAttachments = Array.Empty<MediaAttachment>();
 236                chapters = Array.Empty<ChapterInfo>();
 237            }
 238
 239            var libraryOptions = _libraryManager.GetLibraryOptions(video);
 240
 241            if (mediaInfo is not null)
 242            {
 243                FetchEmbeddedInfo(video, mediaInfo, options, libraryOptions);
 244                FetchPeople(video, mediaInfo, options);
 245                video.Timestamp = mediaInfo.Timestamp;
 246                video.Video3DFormat ??= mediaInfo.Video3DFormat;
 247            }
 248
 249            if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowText || libraryOptions.AllowEmbedd
 250            {
 251                _logger.LogDebug("Disabling embedded image subtitles for {Path} due to DisableEmbeddedImageSubtitles set
 252                mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && !i.IsTextSubtitleStre
 253            }
 254
 255            if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowImage || libraryOptions.AllowEmbed
 256            {
 257                _logger.LogDebug("Disabling embedded text subtitles for {Path} due to DisableEmbeddedTextSubtitles setti
 258                mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && i.IsTextSubtitleStrea
 259            }
 260
 261            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
 262
 263            video.Height = videoStream?.Height ?? 0;
 264            video.Width = videoStream?.Width ?? 0;
 265
 266            video.DefaultVideoStreamIndex = videoStream?.Index;
 267
 268            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
 269
 270            _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);
 271
 272            if (mediaAttachments.Any())
 273            {
 274                _itemRepo.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
 275            }
 276
 277            if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh
 278                || options.MetadataRefreshMode == MetadataRefreshMode.Default)
 279            {
 280                if (_config.Configuration.DummyChapterDuration > 0 && chapters.Length == 0 && mediaStreams.Any(i => i.Ty
 281                {
 282                    chapters = CreateDummyChapters(video);
 283                }
 284
 285                NormalizeChapterNames(chapters);
 286
 287                var extractDuringScan = false;
 288                if (libraryOptions is not null)
 289                {
 290                    extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan;
 291                }
 292
 293                await _encodingManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan
 294
 295                _chapterManager.SaveChapters(video.Id, chapters);
 296            }
 297        }
 298
 299        private void NormalizeChapterNames(ChapterInfo[] chapters)
 300        {
 0301            for (int i = 0; i < chapters.Length; i++)
 302            {
 0303                string? name = chapters[i].Name;
 304                // Check if the name is empty and/or if the name is a time
 305                // Some ripping programs do that.
 0306                if (string.IsNullOrWhiteSpace(name)
 0307                    || TimeSpan.TryParse(name, out _))
 308                {
 0309                    chapters[i].Name = string.Format(
 0310                        CultureInfo.InvariantCulture,
 0311                        _localization.GetLocalizedString("ChapterNameValue"),
 0312                        (i + 1).ToString(CultureInfo.InvariantCulture));
 313                }
 314            }
 0315        }
 316
 317        private void FetchBdInfo(Video video, ref ChapterInfo[] chapters, List<MediaStream> mediaStreams, BlurayDiscInfo
 318        {
 0319            if (blurayInfo.Files.Length <= 1)
 320            {
 0321                return;
 322            }
 323
 0324            var ffmpegVideoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
 325
 326            // Fill video properties from the BDInfo result
 0327            mediaStreams.Clear();
 0328            mediaStreams.AddRange(blurayInfo.MediaStreams);
 329
 0330            if (blurayInfo.RunTimeTicks.HasValue && blurayInfo.RunTimeTicks.Value > 0)
 331            {
 0332                video.RunTimeTicks = blurayInfo.RunTimeTicks;
 333            }
 334
 0335            if (blurayInfo.Chapters is not null)
 336            {
 0337                double[] brChapter = blurayInfo.Chapters;
 0338                chapters = new ChapterInfo[brChapter.Length];
 0339                for (int i = 0; i < brChapter.Length; i++)
 340                {
 0341                    chapters[i] = new ChapterInfo
 0342                    {
 0343                        StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks
 0344                    };
 345                }
 346            }
 347
 0348            var blurayVideoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
 349
 350            // Use the ffprobe values if these are empty
 0351            if (blurayVideoStream is not null && ffmpegVideoStream is not null)
 352            {
 353                // Always use ffmpeg's detected codec since that is what the rest of the codebase expects.
 0354                blurayVideoStream.Codec = ffmpegVideoStream.Codec;
 0355                blurayVideoStream.BitRate = blurayVideoStream.BitRate.GetValueOrDefault() == 0 ? ffmpegVideoStream.BitRa
 0356                blurayVideoStream.Width = blurayVideoStream.Width.GetValueOrDefault() == 0 ? ffmpegVideoStream.Width : b
 0357                blurayVideoStream.Height = blurayVideoStream.Height.GetValueOrDefault() == 0 ? ffmpegVideoStream.Width :
 0358                blurayVideoStream.ColorRange = ffmpegVideoStream.ColorRange;
 0359                blurayVideoStream.ColorSpace = ffmpegVideoStream.ColorSpace;
 0360                blurayVideoStream.ColorTransfer = ffmpegVideoStream.ColorTransfer;
 0361                blurayVideoStream.ColorPrimaries = ffmpegVideoStream.ColorPrimaries;
 362            }
 0363        }
 364
 365        /// <summary>
 366        /// Gets information about the longest playlist on a bdrom.
 367        /// </summary>
 368        /// <param name="path">The path.</param>
 369        /// <returns>VideoStream.</returns>
 370        private BlurayDiscInfo? GetBDInfo(string path)
 371        {
 0372            ArgumentException.ThrowIfNullOrEmpty(path);
 373
 374            try
 375            {
 0376                return _blurayExaminer.GetDiscInfo(path);
 377            }
 0378            catch (Exception ex)
 379            {
 0380                _logger.LogError(ex, "Error getting BDInfo");
 0381                return null;
 382            }
 0383        }
 384
 385        private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions refreshOption
 386        {
 0387            var replaceData = refreshOptions.ReplaceAllMetadata;
 388
 0389            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.OfficialRating))
 390            {
 0391                if (string.IsNullOrWhiteSpace(video.OfficialRating) || replaceData)
 392                {
 0393                    video.OfficialRating = data.OfficialRating;
 394                }
 395            }
 396
 0397            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Genres))
 398            {
 0399                if (video.Genres.Length == 0 || replaceData)
 400                {
 0401                    video.Genres = Array.Empty<string>();
 402
 0403                    foreach (var genre in data.Genres)
 404                    {
 0405                        video.AddGenre(genre);
 406                    }
 407                }
 408            }
 409
 0410            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Studios))
 411            {
 0412                if (video.Studios.Length == 0 || replaceData)
 413                {
 0414                    video.SetStudios(data.Studios);
 415                }
 416            }
 417
 0418            if (!video.IsLocked && video is MusicVideo musicVideo)
 419            {
 0420                if (string.IsNullOrEmpty(musicVideo.Album) || replaceData)
 421                {
 0422                    musicVideo.Album = data.Album;
 423                }
 424
 0425                if (musicVideo.Artists.Count == 0 || replaceData)
 426                {
 0427                    musicVideo.Artists = data.Artists;
 428                }
 429            }
 430
 0431            if (data.ProductionYear.HasValue)
 432            {
 0433                if (!video.ProductionYear.HasValue || replaceData)
 434                {
 0435                    video.ProductionYear = data.ProductionYear;
 436                }
 437            }
 438
 0439            if (data.PremiereDate.HasValue)
 440            {
 0441                if (!video.PremiereDate.HasValue || replaceData)
 442                {
 0443                    video.PremiereDate = data.PremiereDate;
 444                }
 445            }
 446
 0447            if (data.IndexNumber.HasValue)
 448            {
 0449                if (!video.IndexNumber.HasValue || replaceData)
 450                {
 0451                    video.IndexNumber = data.IndexNumber;
 452                }
 453            }
 454
 0455            if (data.ParentIndexNumber.HasValue)
 456            {
 0457                if (!video.ParentIndexNumber.HasValue || replaceData)
 458                {
 0459                    video.ParentIndexNumber = data.ParentIndexNumber;
 460                }
 461            }
 462
 0463            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Name))
 464            {
 0465                if (!string.IsNullOrWhiteSpace(data.Name) && libraryOptions.EnableEmbeddedTitles)
 466                {
 467                    // Separate option to use the embedded name for extras because it will often be the same name as the
 0468                    if (!video.ExtraType.HasValue || libraryOptions.EnableEmbeddedExtrasTitles)
 469                    {
 0470                        video.Name = data.Name;
 471                    }
 472                }
 473
 0474                if (!string.IsNullOrWhiteSpace(data.ForcedSortName))
 475                {
 0476                    video.ForcedSortName = data.ForcedSortName;
 477                }
 478            }
 479
 480            // If we don't have a ProductionYear try and get it from PremiereDate
 0481            if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue)
 482            {
 0483                video.ProductionYear = video.PremiereDate.Value.ToLocalTime().Year;
 484            }
 485
 0486            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Overview))
 487            {
 0488                if (string.IsNullOrWhiteSpace(video.Overview) || replaceData)
 489                {
 0490                    video.Overview = data.Overview;
 491                }
 492            }
 0493        }
 494
 495        private void FetchPeople(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options)
 496        {
 0497            if (video.IsLocked
 0498                || video.LockedFields.Contains(MetadataField.Cast)
 0499                || data.People.Length == 0)
 500            {
 0501                return;
 502            }
 503
 0504            if (options.ReplaceAllMetadata || _libraryManager.GetPeople(video).Count == 0)
 505            {
 0506                var people = new List<PersonInfo>();
 507
 0508                foreach (var person in data.People)
 509                {
 0510                    PeopleHelper.AddPerson(people, new PersonInfo
 0511                    {
 0512                        Name = person.Name,
 0513                        Type = person.Type,
 0514                        Role = person.Role
 0515                    });
 516                }
 517
 0518                _libraryManager.UpdatePeople(video, people);
 519            }
 0520        }
 521
 522        /// <summary>
 523        /// Adds the external subtitles.
 524        /// </summary>
 525        /// <param name="video">The video.</param>
 526        /// <param name="currentStreams">The current streams.</param>
 527        /// <param name="options">The refreshOptions.</param>
 528        /// <param name="cancellationToken">The cancellation token.</param>
 529        /// <returns>Task.</returns>
 530        private async Task AddExternalSubtitlesAsync(
 531            Video video,
 532            List<MediaStream> currentStreams,
 533            MetadataRefreshOptions options,
 534            CancellationToken cancellationToken)
 535        {
 536            var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1);
 537            var externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options.Dir
 538
 539            var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default ||
 540                                            options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
 541
 542            var subtitleOptions = _config.GetConfiguration<SubtitleOptions>("subtitles");
 543
 544            var libraryOptions = _libraryManager.GetLibraryOptions(video);
 545
 546            string[] subtitleDownloadLanguages;
 547            bool skipIfEmbeddedSubtitlesPresent;
 548            bool skipIfAudioTrackMatches;
 549            bool requirePerfectMatch;
 550            bool enabled;
 551
 552            if (libraryOptions.SubtitleDownloadLanguages is null)
 553            {
 554                subtitleDownloadLanguages = subtitleOptions.DownloadLanguages;
 555                skipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent;
 556                skipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches;
 557                requirePerfectMatch = subtitleOptions.RequirePerfectMatch;
 558                enabled = (subtitleOptions.DownloadEpisodeSubtitles &&
 559                video is Episode) ||
 560                (subtitleOptions.DownloadMovieSubtitles &&
 561                video is Movie);
 562            }
 563            else
 564            {
 565                subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
 566                skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
 567                skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
 568                requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
 569                enabled = true;
 570            }
 571
 572            if (enableSubtitleDownloading && enabled)
 573            {
 574                var downloadedLanguages = await new SubtitleDownloader(
 575                    _logger,
 576                    _subtitleManager).DownloadSubtitles(
 577                        video,
 578                        currentStreams.Concat(externalSubtitleStreams).ToList(),
 579                        skipIfEmbeddedSubtitlesPresent,
 580                        skipIfAudioTrackMatches,
 581                        requirePerfectMatch,
 582                        subtitleDownloadLanguages,
 583                        libraryOptions.DisabledSubtitleFetchers,
 584                        libraryOptions.SubtitleFetcherOrder,
 585                        true,
 586                        cancellationToken).ConfigureAwait(false);
 587
 588                // Rescan
 589                if (downloadedLanguages.Count > 0)
 590                {
 591                    externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options
 592                }
 593            }
 594
 595            video.SubtitleFiles = externalSubtitleStreams.Select(i => i.Path).Distinct().ToArray();
 596
 597            currentStreams.AddRange(externalSubtitleStreams);
 598        }
 599
 600        /// <summary>
 601        /// Adds the external audio.
 602        /// </summary>
 603        /// <param name="video">The video.</param>
 604        /// <param name="currentStreams">The current streams.</param>
 605        /// <param name="options">The refreshOptions.</param>
 606        /// <param name="cancellationToken">The cancellation token.</param>
 607        private async Task AddExternalAudioAsync(
 608            Video video,
 609            List<MediaStream> currentStreams,
 610            MetadataRefreshOptions options,
 611            CancellationToken cancellationToken)
 612        {
 613            var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1;
 614            var externalAudioStreams = await _audioResolver.GetExternalStreamsAsync(video, startIndex, options.Directory
 615
 616            video.AudioFiles = externalAudioStreams.Select(i => i.Path).Distinct().ToArray();
 617
 618            currentStreams.AddRange(externalAudioStreams);
 619        }
 620
 621        /// <summary>
 622        /// Creates dummy chapters.
 623        /// </summary>
 624        /// <param name="video">The video.</param>
 625        /// <returns>An array of dummy chapters.</returns>
 626        internal ChapterInfo[] CreateDummyChapters(Video video)
 627        {
 9628            var runtime = video.RunTimeTicks.GetValueOrDefault();
 629
 630            // Only process files with a runtime higher than 0 and lower than 12h. The latter are likely corrupted.
 9631            if (runtime < 0 || runtime > TimeSpan.FromHours(12).Ticks)
 632            {
 3633                throw new ArgumentException(
 3634                    string.Format(
 3635                        CultureInfo.InvariantCulture,
 3636                        "{0} has an invalid runtime of {1} minutes",
 3637                        video.Name,
 3638                        TimeSpan.FromTicks(runtime).TotalMinutes));
 639            }
 640
 6641            long dummyChapterDuration = TimeSpan.FromSeconds(_config.Configuration.DummyChapterDuration).Ticks;
 6642            if (runtime <= dummyChapterDuration)
 643            {
 4644                return Array.Empty<ChapterInfo>();
 645            }
 646
 2647            int chapterCount = (int)(runtime / dummyChapterDuration);
 2648            var chapters = new ChapterInfo[chapterCount];
 649
 2650            long currentChapterTicks = 0;
 26651            for (int i = 0; i < chapterCount; i++)
 652            {
 11653                chapters[i] = new ChapterInfo
 11654                {
 11655                    StartPositionTicks = currentChapterTicks
 11656                };
 657
 11658                currentChapterTicks += dummyChapterDuration;
 659            }
 660
 2661            return chapters;
 662        }
 663    }
 664}

Methods/Properties

.ctor(Microsoft.Extensions.Logging.ILogger`1<MediaBrowser.Providers.MediaInfo.FFProbeVideoInfo>,MediaBrowser.Controller.Library.IMediaSourceManager,MediaBrowser.Controller.MediaEncoding.IMediaEncoder,MediaBrowser.Controller.Persistence.IItemRepository,MediaBrowser.Model.MediaInfo.IBlurayExaminer,MediaBrowser.Model.Globalization.ILocalizationManager,MediaBrowser.Controller.MediaEncoding.IEncodingManager,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Subtitles.ISubtitleManager,MediaBrowser.Controller.Chapters.IChapterManager,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Providers.MediaInfo.AudioResolver,MediaBrowser.Providers.MediaInfo.SubtitleResolver)
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)