< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.MediaInfo.ProbeProvider
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
Line coverage
25%
Covered lines: 29
Uncovered lines: 85
Coverable lines: 114
Total lines: 373
Line coverage: 25.4%
Branch coverage
0%
Covered branches: 0
Total branches: 78
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/2/2026 - 12:12:50 AM Line coverage: 28.5% (28/98) Branch coverage: 0% (0/62) Total lines: 3185/4/2026 - 12:15:16 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: 373 5/2/2026 - 12:12:50 AM Line coverage: 28.5% (28/98) Branch coverage: 0% (0/62) Total lines: 3185/4/2026 - 12:15:16 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: 373

Coverage delta

Coverage delta 4 -4

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Name()100%210%
get_Order()100%210%
HasChanged(...)0%930300%
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;
 2254        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        {
 2289            _logger = loggerFactory.CreateLogger<ProbeProvider>();
 2290            _audioResolver = new AudioResolver(loggerFactory.CreateLogger<AudioResolver>(), localization, mediaEncoder, 
 2291            _subtitleResolver = new SubtitleResolver(loggerFactory.CreateLogger<SubtitleResolver>(), localization, media
 2292            _lyricResolver = new LyricResolver(loggerFactory.CreateLogger<LyricResolver>(), localization, mediaEncoder, 
 93
 2294            _videoProber = new FFProbeVideoInfo(
 2295                loggerFactory.CreateLogger<FFProbeVideoInfo>(),
 2296                mediaSourceManager,
 2297                mediaEncoder,
 2298                blurayExaminer,
 2299                localization,
 22100                chapterManager,
 22101                config,
 22102                subtitleManager,
 22103                libraryManager,
 22104                _audioResolver,
 22105                _subtitleResolver,
 22106                mediaAttachmentRepository,
 22107                mediaStreamRepository);
 108
 22109            _audioProber = new AudioFileProber(
 22110                loggerFactory.CreateLogger<AudioFileProber>(),
 22111                mediaSourceManager,
 22112                mediaEncoder,
 22113                libraryManager,
 22114                _lyricResolver,
 22115                lyricManager,
 22116                mediaStreamRepository,
 22117                chapterManager);
 22118        }
 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        {
 0129            var video = item as Video;
 0130            if (video is null || video.VideoType == VideoType.VideoFile || video.VideoType == VideoType.Iso)
 131            {
 0132                var path = item.Path;
 133
 0134                if (!string.IsNullOrWhiteSpace(path) && item.IsFileProtocol)
 135                {
 0136                    var file = directoryService.GetFile(path);
 0137                    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
 0145            if (video is not null
 0146                && item.SupportsLocalMetadata
 0147                && !video.IsPlaceHolder)
 148            {
 0149                var externalFiles = new HashSet<string>(_subtitleResolver.GetExternalFiles(video, directoryService, fals
 0150                if (!new HashSet<string>(video.SubtitleFiles, StringComparer.Ordinal).SetEquals(externalFiles))
 151                {
 0152                    _logger.LogDebug("Refreshing {ItemPath} due to external subtitles change.", item.Path);
 0153                    return true;
 154                }
 155
 0156                externalFiles = new HashSet<string>(_audioResolver.GetExternalFiles(video, directoryService, false).Sele
 0157                if (!new HashSet<string>(video.AudioFiles, StringComparer.Ordinal).SetEquals(externalFiles))
 158                {
 0159                    _logger.LogDebug("Refreshing {ItemPath} due to external audio change.", item.Path);
 0160                    return true;
 161                }
 162            }
 163
 0164            if (item is Audio audio
 0165                && item.SupportsLocalMetadata)
 166            {
 0167                var externalFiles = new HashSet<string>(_lyricResolver.GetExternalFiles(audio, directoryService, false).
 0168                if (!new HashSet<string>(audio.LyricFiles, StringComparer.Ordinal).SetEquals(externalFiles))
 169                {
 0170                    _logger.LogDebug("Refreshing {ItemPath} due to external lyrics change.", item.Path);
 0171                    return true;
 172                }
 173            }
 174
 0175            return false;
 176        }
 177
 178        /// <inheritdoc />
 179        public Task<ItemUpdateType> FetchAsync(Episode item, MetadataRefreshOptions options, CancellationToken cancellat
 180        {
 0181            return FetchVideoInfo(item, options, cancellationToken);
 182        }
 183
 184        /// <inheritdoc />
 185        public Task<ItemUpdateType> FetchAsync(MusicVideo item, MetadataRefreshOptions options, CancellationToken cancel
 186        {
 0187            return FetchVideoInfo(item, options, cancellationToken);
 188        }
 189
 190        /// <inheritdoc />
 191        public Task<ItemUpdateType> FetchAsync(Movie item, MetadataRefreshOptions options, CancellationToken cancellatio
 192        {
 0193            return FetchVideoInfo(item, options, cancellationToken);
 194        }
 195
 196        /// <inheritdoc />
 197        public Task<ItemUpdateType> FetchAsync(Trailer item, MetadataRefreshOptions options, CancellationToken cancellat
 198        {
 0199            return FetchVideoInfo(item, options, cancellationToken);
 200        }
 201
 202        /// <inheritdoc />
 203        public Task<ItemUpdateType> FetchAsync(Video item, MetadataRefreshOptions options, CancellationToken cancellatio
 204        {
 0205            return FetchVideoInfo(item, options, cancellationToken);
 206        }
 207
 208        /// <inheritdoc />
 209        public Task<ItemUpdateType> FetchAsync(Audio item, MetadataRefreshOptions options, CancellationToken cancellatio
 210        {
 0211            return FetchAudioInfo(item, options, cancellationToken);
 212        }
 213
 214        /// <inheritdoc />
 215        public Task<ItemUpdateType> FetchAsync(AudioBook item, MetadataRefreshOptions options, CancellationToken cancell
 216        {
 0217            return FetchAudioInfo(item, options, cancellationToken);
 218        }
 219
 220        /// <inheritdoc />
 221        public Task<ItemUpdateType> FetchAsync(Book item, MetadataRefreshOptions options, CancellationToken cancellation
 222        {
 0223            if (item.IsVirtualItem || !item.IsFileProtocol)
 224            {
 0225                return _cachedTask;
 226            }
 227
 228            long pageCount;
 0229            switch (Path.GetExtension(item.Path).ToLowerInvariant())
 230            {
 231                case ".cb7":
 232                case ".cbr":
 233                case ".cbt":
 234                case ".cbz":
 0235                    using (var stream = File.OpenRead(item.Path))
 0236                    using (var archive = ArchiveFactory.OpenArchive(stream))
 237                    {
 0238                        pageCount = archive.Entries.Count(e => !e.IsDirectory);
 0239                    }
 240
 241                    break;
 242
 243#pragma warning disable CA1416
 244                case ".pdf":
 0245                    using (var stream = File.OpenRead(item.Path))
 246                    {
 0247                        pageCount = Conversion.GetPageCount(stream);
 0248                    }
 249
 250                    break;
 251#pragma warning restore CA1416
 252
 253                case ".epub":
 254                    // TODO process CFI and store as a string when multiple progress types are supported
 255                    // current progress value is percentage stored as a proportion of one second worth of ticks
 0256                    item.RunTimeTicks = TimeSpan.TicksPerSecond;
 257
 0258                    return Task.FromResult(ItemUpdateType.MetadataImport);
 259
 260                default:
 0261                    return _cachedTask;
 262            }
 263
 264            // TODO use page count without modification when multiple progress types are supported
 265            // book players report page count and the web client multiplies that value by 10000 to convert the expected 
 0266            item.RunTimeTicks = pageCount * 10000;
 267
 0268            return Task.FromResult(ItemUpdateType.MetadataImport);
 269        }
 270
 271        /// <summary>
 272        /// Fetches video information for an item.
 273        /// </summary>
 274        /// <param name="item">The item.</param>
 275        /// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
 276        /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
 277        /// <typeparam name="T">The type of item to resolve.</typeparam>
 278        /// <returns>A <see cref="Task"/> fetching the <see cref="ItemUpdateType"/> for an item.</returns>
 279        public Task<ItemUpdateType> FetchVideoInfo<T>(T item, MetadataRefreshOptions options, CancellationToken cancella
 280            where T : Video
 281        {
 0282            if (item.IsPlaceHolder)
 283            {
 0284                return _cachedTask;
 285            }
 286
 0287            if (!item.IsCompleteMedia)
 288            {
 0289                return _cachedTask;
 290            }
 291
 0292            if (item.IsVirtualItem)
 293            {
 0294                return _cachedTask;
 295            }
 296
 0297            if (!options.EnableRemoteContentProbe && !item.IsFileProtocol)
 298            {
 0299                return _cachedTask;
 300            }
 301
 0302            if (item.IsShortcut)
 303            {
 0304                FetchShortcutInfo(item);
 305            }
 306
 0307            return _videoProber.ProbeVideo(item, options, cancellationToken);
 308        }
 309
 310        private string NormalizeStrmLine(string line)
 311        {
 0312            return line.Replace("\t", string.Empty, StringComparison.Ordinal)
 0313                .Replace("\r", string.Empty, StringComparison.Ordinal)
 0314                .Replace("\n", string.Empty, StringComparison.Ordinal)
 0315                .Trim();
 316        }
 317
 318        private void FetchShortcutInfo(BaseItem item)
 319        {
 0320            var shortcutPath = File.ReadAllLines(item.Path)
 0321                .Select(NormalizeStrmLine)
 0322                .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i) && !i.StartsWith('#'));
 323
 0324            if (string.IsNullOrWhiteSpace(shortcutPath))
 325            {
 0326                return;
 327            }
 328
 329            // Only allow remote URLs in .strm files to prevent local file access
 0330            if (Uri.TryCreate(shortcutPath, UriKind.Absolute, out var uri)
 0331                && (string.Equals(uri.Scheme, "http", StringComparison.OrdinalIgnoreCase)
 0332                    || string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase)
 0333                    || string.Equals(uri.Scheme, "rtsp", StringComparison.OrdinalIgnoreCase)
 0334                    || string.Equals(uri.Scheme, "rtp", StringComparison.OrdinalIgnoreCase)))
 335            {
 0336                item.ShortcutPath = shortcutPath;
 337            }
 338            else
 339            {
 0340                _logger.LogWarning("Ignoring invalid or non-remote .strm path in {File}: {Path}", item.Path, shortcutPat
 341            }
 0342        }
 343
 344        /// <summary>
 345        /// Fetches audio information for an item.
 346        /// </summary>
 347        /// <param name="item">The item.</param>
 348        /// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
 349        /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
 350        /// <typeparam name="T">The type of item to resolve.</typeparam>
 351        /// <returns>A <see cref="Task"/> fetching the <see cref="ItemUpdateType"/> for an item.</returns>
 352        public Task<ItemUpdateType> FetchAudioInfo<T>(T item, MetadataRefreshOptions options, CancellationToken cancella
 353            where T : Audio
 354        {
 0355            if (item.IsVirtualItem)
 356            {
 0357                return _cachedTask;
 358            }
 359
 0360            if (!options.EnableRemoteContentProbe && !item.IsFileProtocol)
 361            {
 0362                return _cachedTask;
 363            }
 364
 0365            if (item.IsShortcut)
 366            {
 0367                FetchShortcutInfo(item);
 368            }
 369
 0370            return _audioProber.Probe(item, options, cancellationToken);
 371        }
 372    }
 373}

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)
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)