< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.MediaInfo.ProbeProvider
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
Line coverage
45%
Covered lines: 59
Uncovered lines: 70
Coverable lines: 129
Total lines: 398
Line coverage: 45.7%
Branch coverage
40%
Covered branches: 39
Total branches: 96
Branch coverage: 40.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/12/2026 - 12:15:57 AM Line coverage: 29.2% (29/99) Branch coverage: 0% (0/62) Total lines: 3197/22/2026 - 12:16:22 AM Line coverage: 25.4% (29/114) Branch coverage: 0% (0/78) Total lines: 37310/29/2026 - 12:17:11 AM Line coverage: 45.7% (59/129) Branch coverage: 40.6% (39/96) Total lines: 398 5/12/2026 - 12:15:57 AM Line coverage: 29.2% (29/99) Branch coverage: 0% (0/62) Total lines: 3197/22/2026 - 12:16:22 AM Line coverage: 25.4% (29/114) Branch coverage: 0% (0/78) Total lines: 37310/29/2026 - 12:17:11 AM Line coverage: 45.7% (59/129) Branch coverage: 40.6% (39/96) Total lines: 398

Coverage delta

Coverage delta 41 -41

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Name()100%210%
get_Order()100%210%
HasChanged(...)75%743265.51%
IsMissingMediaInfo(...)93.75%161691.66%
FetchAsync(...)100%210%
FetchAsync(...)100%210%
FetchAsync(...)100%210%
FetchAsync(...)100%210%
FetchAsync(...)100%210%
FetchAsync(...)100%210%
FetchAsync(...)100%210%
FetchAsync(...)0%272160%
FetchVideoInfo(...)0%156120%
NormalizeStrmLine(...)100%210%
FetchShortcutInfo(...)0%156120%
FetchAudioInfo(...)0%7280%

File(s)

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

#LineLine coverage
 1#nullable disable
 2
 3using System;
 4using System.Collections.Generic;
 5using System.IO;
 6using System.Linq;
 7using System.Threading;
 8using System.Threading.Tasks;
 9using Emby.Naming.Common;
 10using MediaBrowser.Controller.Chapters;
 11using MediaBrowser.Controller.Configuration;
 12using MediaBrowser.Controller.Entities;
 13using MediaBrowser.Controller.Entities.Audio;
 14using MediaBrowser.Controller.Entities.Movies;
 15using MediaBrowser.Controller.Entities.TV;
 16using MediaBrowser.Controller.Library;
 17using MediaBrowser.Controller.Lyrics;
 18using MediaBrowser.Controller.MediaEncoding;
 19using MediaBrowser.Controller.Persistence;
 20using MediaBrowser.Controller.Providers;
 21using MediaBrowser.Controller.Subtitles;
 22using MediaBrowser.Model.Entities;
 23using MediaBrowser.Model.Globalization;
 24using MediaBrowser.Model.IO;
 25using MediaBrowser.Model.MediaInfo;
 26using Microsoft.Extensions.Logging;
 27using PDFtoImage;
 28using SharpCompress.Archives;
 29
 30namespace MediaBrowser.Providers.MediaInfo
 31{
 32    /// <summary>
 33    /// The probe provider.
 34    /// </summary>
 35    public class ProbeProvider : ICustomMetadataProvider<Episode>,
 36        ICustomMetadataProvider<MusicVideo>,
 37        ICustomMetadataProvider<Movie>,
 38        ICustomMetadataProvider<Trailer>,
 39        ICustomMetadataProvider<Video>,
 40        ICustomMetadataProvider<Audio>,
 41        ICustomMetadataProvider<AudioBook>,
 42        ICustomMetadataProvider<Book>,
 43        IHasOrder,
 44        IForcedProvider,
 45        IPreRefreshProvider,
 46        IHasItemChangeMonitor
 47    {
 48        private readonly ILogger<ProbeProvider> _logger;
 49        private readonly AudioResolver _audioResolver;
 50        private readonly SubtitleResolver _subtitleResolver;
 51        private readonly LyricResolver _lyricResolver;
 52        private readonly FFProbeVideoInfo _videoProber;
 53        private readonly AudioFileProber _audioProber;
 3554        private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
 55
 56        /// <summary>
 57        /// Initializes a new instance of the <see cref="ProbeProvider"/> class.
 58        /// </summary>
 59        /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
 60        /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
 61        /// <param name="blurayExaminer">Instance of the <see cref="IBlurayExaminer"/> interface.</param>
 62        /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
 63        /// <param name="chapterManager">Instance of the <see cref="IChapterManager"/> interface.</param>
 64        /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
 65        /// <param name="subtitleManager">Instance of the <see cref="ISubtitleManager"/> interface.</param>
 66        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 67        /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/>.</param>
 68        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
 69        /// <param name="namingOptions">The <see cref="NamingOptions"/>.</param>
 70        /// <param name="lyricManager">Instance of the <see cref="ILyricManager"/> interface.</param>
 71        /// <param name="mediaAttachmentRepository">Instance of the <see cref="IMediaAttachmentRepository"/> interface.<
 72        /// <param name="mediaStreamRepository">Instance of the <see cref="IMediaStreamRepository"/> interface.</param>
 73        public ProbeProvider(
 74            IMediaSourceManager mediaSourceManager,
 75            IMediaEncoder mediaEncoder,
 76            IBlurayExaminer blurayExaminer,
 77            ILocalizationManager localization,
 78            IChapterManager chapterManager,
 79            IServerConfigurationManager config,
 80            ISubtitleManager subtitleManager,
 81            ILibraryManager libraryManager,
 82            IFileSystem fileSystem,
 83            ILoggerFactory loggerFactory,
 84            NamingOptions namingOptions,
 85            ILyricManager lyricManager,
 86            IMediaAttachmentRepository mediaAttachmentRepository,
 87            IMediaStreamRepository mediaStreamRepository)
 88        {
 3589            _logger = loggerFactory.CreateLogger<ProbeProvider>();
 3590            _audioResolver = new AudioResolver(loggerFactory.CreateLogger<AudioResolver>(), localization, mediaEncoder, 
 3591            _subtitleResolver = new SubtitleResolver(loggerFactory.CreateLogger<SubtitleResolver>(), localization, media
 3592            _lyricResolver = new LyricResolver(loggerFactory.CreateLogger<LyricResolver>(), localization, mediaEncoder, 
 93
 3594            _videoProber = new FFProbeVideoInfo(
 3595                loggerFactory.CreateLogger<FFProbeVideoInfo>(),
 3596                mediaSourceManager,
 3597                mediaEncoder,
 3598                blurayExaminer,
 3599                localization,
 35100                chapterManager,
 35101                config,
 35102                subtitleManager,
 35103                libraryManager,
 35104                _audioResolver,
 35105                _subtitleResolver,
 35106                mediaAttachmentRepository,
 35107                mediaStreamRepository);
 108
 35109            _audioProber = new AudioFileProber(
 35110                loggerFactory.CreateLogger<AudioFileProber>(),
 35111                mediaSourceManager,
 35112                mediaEncoder,
 35113                libraryManager,
 35114                _lyricResolver,
 35115                lyricManager,
 35116                mediaStreamRepository,
 35117                chapterManager);
 35118        }
 119
 120        /// <inheritdoc />
 0121        public string Name => "Probe Provider";
 122
 123        /// <inheritdoc />
 0124        public int Order => 100;
 125
 126        /// <inheritdoc />
 127        public bool HasChanged(BaseItem item, IDirectoryService directoryService)
 128        {
 8129            var video = item as Video;
 8130            if (video is null || video.VideoType == VideoType.VideoFile || video.VideoType == VideoType.Iso)
 131            {
 8132                var path = item.Path;
 133
 8134                if (!string.IsNullOrWhiteSpace(path) && item.IsFileProtocol)
 135                {
 8136                    var file = directoryService.GetFile(path);
 8137                    if (file is not null && item.HasChanged(file.LastWriteTimeUtc))
 138                    {
 0139                        _logger.LogDebug("Refreshing {ItemPath} due to file system modification.", path);
 0140                        return true;
 141                    }
 142                }
 143            }
 144
 8145            if (IsMissingMediaInfo(item))
 146            {
 2147                _logger.LogDebug("Refreshing {ItemPath} because it has no media information.", item.Path);
 2148                return true;
 149            }
 150
 6151            if (video is not null
 6152                && item.SupportsLocalMetadata
 6153                && !video.IsPlaceHolder)
 154            {
 5155                var externalFiles = new HashSet<string>(_subtitleResolver.GetExternalFiles(video, directoryService, fals
 5156                if (!new HashSet<string>(video.SubtitleFiles, StringComparer.Ordinal).SetEquals(externalFiles))
 157                {
 0158                    _logger.LogDebug("Refreshing {ItemPath} due to external subtitles change.", item.Path);
 0159                    return true;
 160                }
 161
 5162                externalFiles = new HashSet<string>(_audioResolver.GetExternalFiles(video, directoryService, false).Sele
 5163                if (!new HashSet<string>(video.AudioFiles, StringComparer.Ordinal).SetEquals(externalFiles))
 164                {
 0165                    _logger.LogDebug("Refreshing {ItemPath} due to external audio change.", item.Path);
 0166                    return true;
 167                }
 168            }
 169
 6170            if (item is Audio audio
 6171                && item.SupportsLocalMetadata)
 172            {
 0173                var externalFiles = new HashSet<string>(_lyricResolver.GetExternalFiles(audio, directoryService, false).
 0174                if (!new HashSet<string>(audio.LyricFiles, StringComparer.Ordinal).SetEquals(externalFiles))
 175                {
 0176                    _logger.LogDebug("Refreshing {ItemPath} due to external lyrics change.", item.Path);
 0177                    return true;
 178                }
 179            }
 180
 6181            return false;
 182        }
 183
 184        private static bool IsMissingMediaInfo(BaseItem item)
 185        {
 8186            if (item.RunTimeTicks.HasValue
 8187                || item.TotalBitrate.HasValue
 8188                || item.IsVirtualItem
 8189                || item.IsShortcut
 8190                || !item.IsFileProtocol)
 191            {
 5192                return false;
 193            }
 194
 3195            return item switch
 3196            {
 2197                Video video => !video.IsPlaceHolder && video.IsCompleteMedia,
 1198                Audio => true,
 0199                _ => false
 3200            };
 201        }
 202
 203        /// <inheritdoc />
 204        public Task<ItemUpdateType> FetchAsync(Episode item, MetadataRefreshOptions options, CancellationToken cancellat
 205        {
 0206            return FetchVideoInfo(item, options, cancellationToken);
 207        }
 208
 209        /// <inheritdoc />
 210        public Task<ItemUpdateType> FetchAsync(MusicVideo item, MetadataRefreshOptions options, CancellationToken cancel
 211        {
 0212            return FetchVideoInfo(item, options, cancellationToken);
 213        }
 214
 215        /// <inheritdoc />
 216        public Task<ItemUpdateType> FetchAsync(Movie item, MetadataRefreshOptions options, CancellationToken cancellatio
 217        {
 0218            return FetchVideoInfo(item, options, cancellationToken);
 219        }
 220
 221        /// <inheritdoc />
 222        public Task<ItemUpdateType> FetchAsync(Trailer item, MetadataRefreshOptions options, CancellationToken cancellat
 223        {
 0224            return FetchVideoInfo(item, options, cancellationToken);
 225        }
 226
 227        /// <inheritdoc />
 228        public Task<ItemUpdateType> FetchAsync(Video item, MetadataRefreshOptions options, CancellationToken cancellatio
 229        {
 0230            return FetchVideoInfo(item, options, cancellationToken);
 231        }
 232
 233        /// <inheritdoc />
 234        public Task<ItemUpdateType> FetchAsync(Audio item, MetadataRefreshOptions options, CancellationToken cancellatio
 235        {
 0236            return FetchAudioInfo(item, options, cancellationToken);
 237        }
 238
 239        /// <inheritdoc />
 240        public Task<ItemUpdateType> FetchAsync(AudioBook item, MetadataRefreshOptions options, CancellationToken cancell
 241        {
 0242            return FetchAudioInfo(item, options, cancellationToken);
 243        }
 244
 245        /// <inheritdoc />
 246        public Task<ItemUpdateType> FetchAsync(Book item, MetadataRefreshOptions options, CancellationToken cancellation
 247        {
 0248            if (item.IsVirtualItem || !item.IsFileProtocol)
 249            {
 0250                return _cachedTask;
 251            }
 252
 253            long pageCount;
 0254            switch (Path.GetExtension(item.Path).ToLowerInvariant())
 255            {
 256                case ".cb7":
 257                case ".cbr":
 258                case ".cbt":
 259                case ".cbz":
 0260                    using (var stream = File.OpenRead(item.Path))
 0261                    using (var archive = ArchiveFactory.OpenArchive(stream))
 262                    {
 0263                        pageCount = archive.Entries.Count(e => !e.IsDirectory);
 0264                    }
 265
 266                    break;
 267
 268#pragma warning disable CA1416
 269                case ".pdf":
 0270                    using (var stream = File.OpenRead(item.Path))
 271                    {
 0272                        pageCount = Conversion.GetPageCount(stream);
 0273                    }
 274
 275                    break;
 276#pragma warning restore CA1416
 277
 278                case ".epub":
 279                    // TODO process CFI and store as a string when multiple progress types are supported
 280                    // current progress value is percentage stored as a proportion of one second worth of ticks
 0281                    item.RunTimeTicks = TimeSpan.TicksPerSecond;
 282
 0283                    return Task.FromResult(ItemUpdateType.MetadataImport);
 284
 285                default:
 0286                    return _cachedTask;
 287            }
 288
 289            // TODO use page count without modification when multiple progress types are supported
 290            // book players report page count and the web client multiplies that value by 10000 to convert the expected 
 0291            item.RunTimeTicks = pageCount * 10000;
 292
 0293            return Task.FromResult(ItemUpdateType.MetadataImport);
 294        }
 295
 296        /// <summary>
 297        /// Fetches video information for an item.
 298        /// </summary>
 299        /// <param name="item">The item.</param>
 300        /// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
 301        /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
 302        /// <typeparam name="T">The type of item to resolve.</typeparam>
 303        /// <returns>A <see cref="Task"/> fetching the <see cref="ItemUpdateType"/> for an item.</returns>
 304        public Task<ItemUpdateType> FetchVideoInfo<T>(T item, MetadataRefreshOptions options, CancellationToken cancella
 305            where T : Video
 306        {
 0307            if (item.IsPlaceHolder)
 308            {
 0309                return _cachedTask;
 310            }
 311
 0312            if (!item.IsCompleteMedia)
 313            {
 0314                return _cachedTask;
 315            }
 316
 0317            if (item.IsVirtualItem)
 318            {
 0319                return _cachedTask;
 320            }
 321
 0322            if (!options.EnableRemoteContentProbe && !item.IsFileProtocol)
 323            {
 0324                return _cachedTask;
 325            }
 326
 0327            if (item.IsShortcut)
 328            {
 0329                FetchShortcutInfo(item);
 330            }
 331
 0332            return _videoProber.ProbeVideo(item, options, cancellationToken);
 333        }
 334
 335        private string NormalizeStrmLine(string line)
 336        {
 0337            return line.Replace("\t", string.Empty, StringComparison.Ordinal)
 0338                .Replace("\r", string.Empty, StringComparison.Ordinal)
 0339                .Replace("\n", string.Empty, StringComparison.Ordinal)
 0340                .Trim();
 341        }
 342
 343        private void FetchShortcutInfo(BaseItem item)
 344        {
 0345            var shortcutPath = File.ReadAllLines(item.Path)
 0346                .Select(NormalizeStrmLine)
 0347                .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i) && !i.StartsWith('#'));
 348
 0349            if (string.IsNullOrWhiteSpace(shortcutPath))
 350            {
 0351                return;
 352            }
 353
 354            // Only allow remote URLs in .strm files to prevent local file access
 0355            if (Uri.TryCreate(shortcutPath, UriKind.Absolute, out var uri)
 0356                && (string.Equals(uri.Scheme, "http", StringComparison.OrdinalIgnoreCase)
 0357                    || string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase)
 0358                    || string.Equals(uri.Scheme, "rtsp", StringComparison.OrdinalIgnoreCase)
 0359                    || string.Equals(uri.Scheme, "rtp", StringComparison.OrdinalIgnoreCase)))
 360            {
 0361                item.ShortcutPath = shortcutPath;
 362            }
 363            else
 364            {
 0365                _logger.LogWarning("Ignoring invalid or non-remote .strm path in {File}: {Path}", item.Path, shortcutPat
 366            }
 0367        }
 368
 369        /// <summary>
 370        /// Fetches audio information for an item.
 371        /// </summary>
 372        /// <param name="item">The item.</param>
 373        /// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
 374        /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
 375        /// <typeparam name="T">The type of item to resolve.</typeparam>
 376        /// <returns>A <see cref="Task"/> fetching the <see cref="ItemUpdateType"/> for an item.</returns>
 377        public Task<ItemUpdateType> FetchAudioInfo<T>(T item, MetadataRefreshOptions options, CancellationToken cancella
 378            where T : Audio
 379        {
 0380            if (item.IsVirtualItem)
 381            {
 0382                return _cachedTask;
 383            }
 384
 0385            if (!options.EnableRemoteContentProbe && !item.IsFileProtocol)
 386            {
 0387                return _cachedTask;
 388            }
 389
 0390            if (item.IsShortcut)
 391            {
 0392                FetchShortcutInfo(item);
 393            }
 394
 0395            return _audioProber.Probe(item, options, cancellationToken);
 396        }
 397    }
 398}

Methods/Properties

.ctor(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.Model.IO.IFileSystem,Microsoft.Extensions.Logging.ILoggerFactory,Emby.Naming.Common.NamingOptions,MediaBrowser.Controller.Lyrics.ILyricManager,MediaBrowser.Controller.Persistence.IMediaAttachmentRepository,MediaBrowser.Controller.Persistence.IMediaStreamRepository)
get_Name()
get_Order()
HasChanged(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.IDirectoryService)
IsMissingMediaInfo(MediaBrowser.Controller.Entities.BaseItem)
FetchAsync(MediaBrowser.Controller.Entities.TV.Episode,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
FetchAsync(MediaBrowser.Controller.Entities.MusicVideo,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
FetchAsync(MediaBrowser.Controller.Entities.Movies.Movie,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
FetchAsync(MediaBrowser.Controller.Entities.Trailer,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
FetchAsync(MediaBrowser.Controller.Entities.Video,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
FetchAsync(MediaBrowser.Controller.Entities.Audio.Audio,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
FetchAsync(MediaBrowser.Controller.Entities.AudioBook,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
FetchAsync(MediaBrowser.Controller.Entities.Book,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
FetchVideoInfo(T,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
NormalizeStrmLine(System.String)
FetchShortcutInfo(MediaBrowser.Controller.Entities.BaseItem)
FetchAudioInfo(T,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)