< 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: 120
Coverable lines: 156
Total lines: 674
Line coverage: 23%
Branch coverage
6%
Covered branches: 8
Total branches: 118
Branch coverage: 6.7%
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%210140%
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.Common.Configuration;
 12using MediaBrowser.Controller.Chapters;
 13using MediaBrowser.Controller.Configuration;
 14using MediaBrowser.Controller.Entities;
 15using MediaBrowser.Controller.Entities.Movies;
 16using MediaBrowser.Controller.Entities.TV;
 17using MediaBrowser.Controller.Library;
 18using MediaBrowser.Controller.MediaEncoding;
 19using MediaBrowser.Controller.Persistence;
 20using MediaBrowser.Controller.Providers;
 21using MediaBrowser.Controller.Subtitles;
 22using MediaBrowser.Model.Configuration;
 23using MediaBrowser.Model.Dlna;
 24using MediaBrowser.Model.Dto;
 25using MediaBrowser.Model.Entities;
 26using MediaBrowser.Model.Globalization;
 27using MediaBrowser.Model.MediaInfo;
 28using MediaBrowser.Model.Providers;
 29using Microsoft.Extensions.Logging;
 30
 31namespace MediaBrowser.Providers.MediaInfo
 32{
 33    public class FFProbeVideoInfo
 34    {
 35        private readonly ILogger<FFProbeVideoInfo> _logger;
 36        private readonly IMediaSourceManager _mediaSourceManager;
 37        private readonly IMediaEncoder _mediaEncoder;
 38        private readonly IBlurayExaminer _blurayExaminer;
 39        private readonly ILocalizationManager _localization;
 40        private readonly IChapterManager _chapterManager;
 41        private readonly IServerConfigurationManager _config;
 42        private readonly ISubtitleManager _subtitleManager;
 43        private readonly ILibraryManager _libraryManager;
 44        private readonly AudioResolver _audioResolver;
 45        private readonly SubtitleResolver _subtitleResolver;
 46        private readonly IMediaAttachmentRepository _mediaAttachmentRepository;
 47        private readonly IMediaStreamRepository _mediaStreamRepository;
 48
 49        public FFProbeVideoInfo(
 50            ILogger<FFProbeVideoInfo> logger,
 51            IMediaSourceManager mediaSourceManager,
 52            IMediaEncoder mediaEncoder,
 53            IBlurayExaminer blurayExaminer,
 54            ILocalizationManager localization,
 55            IChapterManager chapterManager,
 56            IServerConfigurationManager config,
 57            ISubtitleManager subtitleManager,
 58            ILibraryManager libraryManager,
 59            AudioResolver audioResolver,
 60            SubtitleResolver subtitleResolver,
 61            IMediaAttachmentRepository mediaAttachmentRepository,
 62            IMediaStreamRepository mediaStreamRepository)
 63        {
 3064            _logger = logger;
 3065            _mediaSourceManager = mediaSourceManager;
 3066            _mediaEncoder = mediaEncoder;
 3067            _blurayExaminer = blurayExaminer;
 3068            _localization = localization;
 3069            _chapterManager = chapterManager;
 3070            _config = config;
 3071            _subtitleManager = subtitleManager;
 3072            _libraryManager = libraryManager;
 3073            _audioResolver = audioResolver;
 3074            _subtitleResolver = subtitleResolver;
 3075            _mediaAttachmentRepository = mediaAttachmentRepository;
 3076            _mediaStreamRepository = mediaStreamRepository;
 3077            _mediaStreamRepository = mediaStreamRepository;
 3078        }
 79
 80        public async Task<ItemUpdateType> ProbeVideo<T>(
 81            T item,
 82            MetadataRefreshOptions options,
 83            CancellationToken cancellationToken)
 84            where T : Video
 85        {
 86            BlurayDiscInfo? blurayDiscInfo = null;
 87
 88            Model.MediaInfo.MediaInfo? mediaInfoResult = null;
 89
 90            if (!item.IsShortcut || options.EnableRemoteContentProbe)
 91            {
 92                if (item.VideoType == VideoType.Dvd)
 93                {
 94                    // Get list of playable .vob files
 95                    var vobs = _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null);
 96
 97                    // Return if no playable .vob files are found
 98                    if (vobs.Count == 0)
 99                    {
 100                        _logger.LogError("No playable .vob files found in DVD structure, skipping FFprobe.");
 101                        return ItemUpdateType.MetadataImport;
 102                    }
 103
 104                    // Fetch metadata of first .vob file
 105                    mediaInfoResult = await GetMediaInfo(
 106                        new Video
 107                        {
 108                            Path = vobs[0]
 109                        },
 110                        cancellationToken).ConfigureAwait(false);
 111
 112                    // Sum up the runtime of all .vob files skipping the first .vob
 113                    for (var i = 1; i < vobs.Count; i++)
 114                    {
 115                        var tmpMediaInfo = await GetMediaInfo(
 116                            new Video
 117                            {
 118                                Path = vobs[i]
 119                            },
 120                            cancellationToken).ConfigureAwait(false);
 121
 122                        mediaInfoResult.RunTimeTicks += tmpMediaInfo.RunTimeTicks;
 123                    }
 124                }
 125                else if (item.VideoType == VideoType.BluRay)
 126                {
 127                    // Get BD disc information
 128                    blurayDiscInfo = GetBDInfo(item.Path);
 129
 130                    // Return if no playable .m2ts files are found
 131                    if (blurayDiscInfo is null || blurayDiscInfo.Files.Length == 0)
 132                    {
 133                        _logger.LogError("No playable .m2ts files found in Blu-ray structure, skipping FFprobe.");
 134                        return ItemUpdateType.MetadataImport;
 135                    }
 136
 137                    // Fetch metadata of first .m2ts file
 138                    mediaInfoResult = await GetMediaInfo(
 139                        new Video
 140                        {
 141                            Path = blurayDiscInfo.Files[0]
 142                        },
 143                        cancellationToken).ConfigureAwait(false);
 144                }
 145                else
 146                {
 147                    mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
 148                }
 149
 150                cancellationToken.ThrowIfCancellationRequested();
 151            }
 152
 153            await Fetch(item, cancellationToken, mediaInfoResult, blurayDiscInfo, options).ConfigureAwait(false);
 154
 155            return ItemUpdateType.MetadataImport;
 156        }
 157
 158        private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(
 159            Video item,
 160            CancellationToken cancellationToken)
 161        {
 0162            cancellationToken.ThrowIfCancellationRequested();
 163
 0164            var path = item.Path;
 0165            var protocol = item.PathProtocol ?? MediaProtocol.File;
 166
 0167            if (item.IsShortcut)
 168            {
 0169                path = item.ShortcutPath;
 0170                protocol = _mediaSourceManager.GetPathProtocol(path);
 171            }
 172
 0173            return _mediaEncoder.GetMediaInfo(
 0174                new MediaInfoRequest
 0175                {
 0176                    ExtractChapters = true,
 0177                    MediaType = DlnaProfileType.Video,
 0178                    MediaSource = new MediaSourceInfo
 0179                    {
 0180                        Path = path,
 0181                        Protocol = protocol,
 0182                        VideoType = item.VideoType,
 0183                        IsoType = item.IsoType
 0184                    }
 0185                },
 0186                cancellationToken);
 187        }
 188
 189        protected async Task Fetch(
 190            Video video,
 191            CancellationToken cancellationToken,
 192            Model.MediaInfo.MediaInfo? mediaInfo,
 193            BlurayDiscInfo? blurayInfo,
 194            MetadataRefreshOptions options)
 195        {
 196            List<MediaStream> mediaStreams = new List<MediaStream>();
 197            IReadOnlyList<MediaAttachment> mediaAttachments;
 198            ChapterInfo[] chapters;
 199
 200            // Add external streams before adding the streams from the file to preserve stream IDs on remote videos
 201            await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
 202
 203            await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
 204
 205            var startIndex = mediaStreams.Count == 0 ? 0 : (mediaStreams.Max(i => i.Index) + 1);
 206
 207            if (mediaInfo is not null)
 208            {
 209                foreach (var mediaStream in mediaInfo.MediaStreams)
 210                {
 211                    mediaStream.Index = startIndex++;
 212                    mediaStreams.Add(mediaStream);
 213                }
 214
 215                mediaAttachments = mediaInfo.MediaAttachments;
 216                video.TotalBitrate = mediaInfo.Bitrate;
 217                video.RunTimeTicks = mediaInfo.RunTimeTicks;
 218                video.Container = mediaInfo.Container;
 219                var videoType = video.VideoType;
 220                if (videoType == VideoType.BluRay || videoType == VideoType.Dvd)
 221                {
 222                    video.Size = mediaInfo.Size;
 223                }
 224
 225                chapters = mediaInfo.Chapters ?? [];
 226                if (blurayInfo is not null)
 227                {
 228                    FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo);
 229                }
 230            }
 231            else
 232            {
 233                foreach (var mediaStream in video.GetMediaStreams())
 234                {
 235                    if (!mediaStream.IsExternal)
 236                    {
 237                        mediaStream.Index = startIndex++;
 238                        mediaStreams.Add(mediaStream);
 239                    }
 240                }
 241
 242                mediaAttachments = [];
 243                chapters = [];
 244            }
 245
 246            var libraryOptions = _libraryManager.GetLibraryOptions(video);
 247
 248            if (mediaInfo is not null)
 249            {
 250                FetchEmbeddedInfo(video, mediaInfo, options, libraryOptions);
 251                FetchPeople(video, mediaInfo, options);
 252                video.Timestamp = mediaInfo.Timestamp;
 253                video.Video3DFormat ??= mediaInfo.Video3DFormat;
 254            }
 255
 256            if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowText || libraryOptions.AllowEmbedd
 257            {
 258                _logger.LogDebug("Disabling embedded image subtitles for {Path} due to DisableEmbeddedImageSubtitles set
 259                mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && !i.IsTextSubtitleStre
 260            }
 261
 262            if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowImage || libraryOptions.AllowEmbed
 263            {
 264                _logger.LogDebug("Disabling embedded text subtitles for {Path} due to DisableEmbeddedTextSubtitles setti
 265                mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && i.IsTextSubtitleStrea
 266            }
 267
 268            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
 269
 270            video.Height = videoStream?.Height ?? 0;
 271            video.Width = videoStream?.Width ?? 0;
 272
 273            video.DefaultVideoStreamIndex = videoStream?.Index;
 274
 275            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
 276
 277            _mediaStreamRepository.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);
 278
 279            _mediaAttachmentRepository.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
 280
 281            if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh
 282                || options.MetadataRefreshMode == MetadataRefreshMode.Default)
 283            {
 284                if (_config.Configuration.DummyChapterDuration > 0 && chapters.Length == 0 && mediaStreams.Any(i => i.Ty
 285                {
 286                    chapters = CreateDummyChapters(video);
 287                }
 288
 289                NormalizeChapterNames(chapters);
 290
 291                var extractDuringScan = false;
 292                if (libraryOptions is not null)
 293                {
 294                    extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan;
 295                }
 296
 297                await _chapterManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan,
 298
 299                _chapterManager.SaveChapters(video, chapters);
 300            }
 301        }
 302
 303        private void NormalizeChapterNames(ChapterInfo[] chapters)
 304        {
 0305            for (int i = 0; i < chapters.Length; i++)
 306            {
 0307                string? name = chapters[i].Name;
 308                // Check if the name is empty and/or if the name is a time
 309                // Some ripping programs do that.
 0310                if (string.IsNullOrWhiteSpace(name)
 0311                    || TimeSpan.TryParse(name, out _))
 312                {
 0313                    chapters[i].Name = string.Format(
 0314                        CultureInfo.InvariantCulture,
 0315                        _localization.GetLocalizedString("ChapterNameValue"),
 0316                        (i + 1).ToString(CultureInfo.InvariantCulture));
 317                }
 318            }
 0319        }
 320
 321        private void FetchBdInfo(Video video, ref ChapterInfo[] chapters, List<MediaStream> mediaStreams, BlurayDiscInfo
 322        {
 0323            var ffmpegVideoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
 0324            var externalStreams = mediaStreams.Where(s => s.IsExternal).ToList();
 325
 326            // Fill video properties from the BDInfo result
 0327            mediaStreams.Clear();
 328
 329            // Rebuild the list with external streams first
 0330            int index = 0;
 0331            foreach (var stream in externalStreams.Concat(blurayInfo.MediaStreams))
 332            {
 0333                stream.Index = index++;
 0334                mediaStreams.Add(stream);
 335            }
 336
 0337            if (blurayInfo.RunTimeTicks.HasValue && blurayInfo.RunTimeTicks.Value > 0)
 338            {
 0339                video.RunTimeTicks = blurayInfo.RunTimeTicks;
 340            }
 341
 0342            if (blurayInfo.Chapters is not null)
 343            {
 0344                double[] brChapter = blurayInfo.Chapters;
 0345                chapters = new ChapterInfo[brChapter.Length];
 0346                for (int i = 0; i < brChapter.Length; i++)
 347                {
 0348                    chapters[i] = new ChapterInfo
 0349                    {
 0350                        StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks
 0351                    };
 352                }
 353            }
 354
 0355            var blurayVideoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
 356
 357            // Use the ffprobe values if these are empty
 0358            if (blurayVideoStream is not null && ffmpegVideoStream is not null)
 359            {
 360                // Always use ffmpeg's detected codec since that is what the rest of the codebase expects.
 0361                blurayVideoStream.Codec = ffmpegVideoStream.Codec;
 0362                blurayVideoStream.BitRate = blurayVideoStream.BitRate.GetValueOrDefault() == 0 ? ffmpegVideoStream.BitRa
 0363                blurayVideoStream.Width = blurayVideoStream.Width.GetValueOrDefault() == 0 ? ffmpegVideoStream.Width : b
 0364                blurayVideoStream.Height = blurayVideoStream.Height.GetValueOrDefault() == 0 ? ffmpegVideoStream.Height 
 0365                blurayVideoStream.ColorRange = ffmpegVideoStream.ColorRange;
 0366                blurayVideoStream.ColorSpace = ffmpegVideoStream.ColorSpace;
 0367                blurayVideoStream.ColorTransfer = ffmpegVideoStream.ColorTransfer;
 0368                blurayVideoStream.ColorPrimaries = ffmpegVideoStream.ColorPrimaries;
 369            }
 0370        }
 371
 372        /// <summary>
 373        /// Gets information about the longest playlist on a bdrom.
 374        /// </summary>
 375        /// <param name="path">The path.</param>
 376        /// <returns>VideoStream.</returns>
 377        private BlurayDiscInfo? GetBDInfo(string path)
 378        {
 0379            ArgumentException.ThrowIfNullOrEmpty(path);
 380
 381            try
 382            {
 0383                return _blurayExaminer.GetDiscInfo(path);
 384            }
 0385            catch (Exception ex)
 386            {
 0387                _logger.LogError(ex, "Error getting BDInfo");
 0388                return null;
 389            }
 0390        }
 391
 392        private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions refreshOption
 393        {
 0394            var replaceData = refreshOptions.ReplaceAllMetadata;
 395
 0396            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.OfficialRating))
 397            {
 0398                if (string.IsNullOrWhiteSpace(video.OfficialRating) || replaceData)
 399                {
 0400                    video.OfficialRating = data.OfficialRating;
 401                }
 402            }
 403
 0404            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Genres))
 405            {
 0406                if (video.Genres.Length == 0 || replaceData)
 407                {
 0408                    video.Genres = [];
 409
 0410                    foreach (var genre in data.Genres.Trimmed())
 411                    {
 0412                        video.AddGenre(genre);
 413                    }
 414                }
 415            }
 416
 0417            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Studios))
 418            {
 0419                if (video.Studios.Length == 0 || replaceData)
 420                {
 0421                    video.SetStudios(data.Studios);
 422                }
 423            }
 424
 0425            if (!video.IsLocked && video is MusicVideo musicVideo)
 426            {
 0427                if (string.IsNullOrEmpty(musicVideo.Album) || replaceData)
 428                {
 0429                    musicVideo.Album = data.Album;
 430                }
 431
 0432                if (musicVideo.Artists.Count == 0 || replaceData)
 433                {
 0434                    musicVideo.Artists = data.Artists;
 435                }
 436            }
 437
 0438            if (data.ProductionYear.HasValue)
 439            {
 0440                if (!video.ProductionYear.HasValue || replaceData)
 441                {
 0442                    video.ProductionYear = data.ProductionYear;
 443                }
 444            }
 445
 0446            if (data.PremiereDate.HasValue)
 447            {
 0448                if (!video.PremiereDate.HasValue || replaceData)
 449                {
 0450                    video.PremiereDate = data.PremiereDate;
 451                }
 452            }
 453
 0454            if (data.IndexNumber.HasValue)
 455            {
 0456                if (!video.IndexNumber.HasValue || replaceData)
 457                {
 0458                    video.IndexNumber = data.IndexNumber;
 459                }
 460            }
 461
 0462            if (data.ParentIndexNumber.HasValue)
 463            {
 0464                if (!video.ParentIndexNumber.HasValue || replaceData)
 465                {
 0466                    video.ParentIndexNumber = data.ParentIndexNumber;
 467                }
 468            }
 469
 0470            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Name))
 471            {
 0472                if (!string.IsNullOrWhiteSpace(data.Name) && libraryOptions.EnableEmbeddedTitles)
 473                {
 474                    // Separate option to use the embedded name for extras because it will often be the same name as the
 0475                    if (!video.ExtraType.HasValue || libraryOptions.EnableEmbeddedExtrasTitles)
 476                    {
 0477                        video.Name = data.Name;
 478                    }
 479                }
 480
 0481                if (!string.IsNullOrWhiteSpace(data.ForcedSortName))
 482                {
 0483                    video.ForcedSortName = data.ForcedSortName;
 484                }
 485            }
 486
 487            // If we don't have a ProductionYear try and get it from PremiereDate
 0488            if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue)
 489            {
 0490                video.ProductionYear = video.PremiereDate.Value.ToLocalTime().Year;
 491            }
 492
 0493            if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Overview))
 494            {
 0495                if (string.IsNullOrWhiteSpace(video.Overview) || replaceData)
 496                {
 0497                    video.Overview = data.Overview;
 498                }
 499            }
 0500        }
 501
 502        private void FetchPeople(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options)
 503        {
 0504            if (video.IsLocked
 0505                || video.LockedFields.Contains(MetadataField.Cast)
 0506                || data.People.Length == 0)
 507            {
 0508                return;
 509            }
 510
 0511            if (options.ReplaceAllMetadata || _libraryManager.GetPeople(video).Count == 0)
 512            {
 0513                var people = new List<PersonInfo>();
 514
 0515                foreach (var person in data.People)
 516                {
 0517                    if (!string.IsNullOrWhiteSpace(person.Name))
 518                    {
 0519                        PeopleHelper.AddPerson(people, new PersonInfo
 0520                        {
 0521                            Name = person.Name,
 0522                            Type = person.Type,
 0523                            Role = person.Role.Trim()
 0524                        });
 525                    }
 526                }
 527
 0528                _libraryManager.UpdatePeople(video, people);
 529            }
 0530        }
 531
 532        /// <summary>
 533        /// Adds the external subtitles.
 534        /// </summary>
 535        /// <param name="video">The video.</param>
 536        /// <param name="currentStreams">The current streams.</param>
 537        /// <param name="options">The refreshOptions.</param>
 538        /// <param name="cancellationToken">The cancellation token.</param>
 539        /// <returns>Task.</returns>
 540        private async Task AddExternalSubtitlesAsync(
 541            Video video,
 542            List<MediaStream> currentStreams,
 543            MetadataRefreshOptions options,
 544            CancellationToken cancellationToken)
 545        {
 546            var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1);
 547            var externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options.Dir
 548
 549            var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default ||
 550                                            options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
 551
 552            var subtitleOptions = _config.GetConfiguration<SubtitleOptions>("subtitles");
 553
 554            var libraryOptions = _libraryManager.GetLibraryOptions(video);
 555
 556            string[] subtitleDownloadLanguages;
 557            bool skipIfEmbeddedSubtitlesPresent;
 558            bool skipIfAudioTrackMatches;
 559            bool requirePerfectMatch;
 560            bool enabled;
 561
 562            if (libraryOptions.SubtitleDownloadLanguages is null)
 563            {
 564                subtitleDownloadLanguages = subtitleOptions.DownloadLanguages;
 565                skipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent;
 566                skipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches;
 567                requirePerfectMatch = subtitleOptions.RequirePerfectMatch;
 568                enabled = (subtitleOptions.DownloadEpisodeSubtitles &&
 569                video is Episode) ||
 570                (subtitleOptions.DownloadMovieSubtitles &&
 571                video is Movie);
 572            }
 573            else
 574            {
 575                subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
 576                skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
 577                skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
 578                requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
 579                enabled = true;
 580            }
 581
 582            if (enableSubtitleDownloading && enabled)
 583            {
 584                var downloadedLanguages = await new SubtitleDownloader(
 585                    _logger,
 586                    _subtitleManager).DownloadSubtitles(
 587                        video,
 588                        currentStreams.Concat(externalSubtitleStreams).ToList(),
 589                        skipIfEmbeddedSubtitlesPresent,
 590                        skipIfAudioTrackMatches,
 591                        requirePerfectMatch,
 592                        subtitleDownloadLanguages,
 593                        libraryOptions.DisabledSubtitleFetchers,
 594                        libraryOptions.SubtitleFetcherOrder,
 595                        true,
 596                        cancellationToken).ConfigureAwait(false);
 597
 598                // Rescan
 599                if (downloadedLanguages.Count > 0)
 600                {
 601                    externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options
 602                }
 603            }
 604
 605            video.SubtitleFiles = externalSubtitleStreams.Select(i => i.Path).Distinct().ToArray();
 606
 607            currentStreams.AddRange(externalSubtitleStreams);
 608        }
 609
 610        /// <summary>
 611        /// Adds the external audio.
 612        /// </summary>
 613        /// <param name="video">The video.</param>
 614        /// <param name="currentStreams">The current streams.</param>
 615        /// <param name="options">The refreshOptions.</param>
 616        /// <param name="cancellationToken">The cancellation token.</param>
 617        private async Task AddExternalAudioAsync(
 618            Video video,
 619            List<MediaStream> currentStreams,
 620            MetadataRefreshOptions options,
 621            CancellationToken cancellationToken)
 622        {
 623            var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1;
 624            var externalAudioStreams = await _audioResolver.GetExternalStreamsAsync(video, startIndex, options.Directory
 625
 626            video.AudioFiles = externalAudioStreams.Select(i => i.Path).Distinct().ToArray();
 627
 628            currentStreams.AddRange(externalAudioStreams);
 629        }
 630
 631        /// <summary>
 632        /// Creates dummy chapters.
 633        /// </summary>
 634        /// <param name="video">The video.</param>
 635        /// <returns>An array of dummy chapters.</returns>
 636        internal ChapterInfo[] CreateDummyChapters(Video video)
 637        {
 9638            var runtime = video.RunTimeTicks.GetValueOrDefault();
 639
 640            // Only process files with a runtime greater than 0 and less than 12h. The latter are likely corrupted.
 9641            if (runtime < 0 || runtime > TimeSpan.FromHours(12).Ticks)
 642            {
 3643                throw new ArgumentException(
 3644                    string.Format(
 3645                        CultureInfo.InvariantCulture,
 3646                        "{0} has an invalid runtime of {1} minutes",
 3647                        video.Name,
 3648                        TimeSpan.FromTicks(runtime).TotalMinutes));
 649            }
 650
 6651            long dummyChapterDuration = TimeSpan.FromSeconds(_config.Configuration.DummyChapterDuration).Ticks;
 6652            if (runtime <= dummyChapterDuration)
 653            {
 4654                return [];
 655            }
 656
 2657            int chapterCount = (int)(runtime / dummyChapterDuration);
 2658            var chapters = new ChapterInfo[chapterCount];
 659
 2660            long currentChapterTicks = 0;
 26661            for (int i = 0; i < chapterCount; i++)
 662            {
 11663                chapters[i] = new ChapterInfo
 11664                {
 11665                    StartPositionTicks = currentChapterTicks
 11666                };
 667
 11668                currentChapterTicks += dummyChapterDuration;
 669            }
 670
 2671            return chapters;
 672        }
 673    }
 674}

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)