コード例 #1
0
        protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount, BlurayDiscInfo blurayInfo, IDirectoryService directoryService)
        {
            if (data.format != null)
            {
                // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
                var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;

                if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration))
                {
                    video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
                }
            }

            var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams;

            var chapters = data.Chapters ?? new List <ChapterInfo>();

            if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
            {
                FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
            }

            AddExternalSubtitles(video, mediaStreams, directoryService);

            FetchWtvInfo(video, data);

            video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);

            if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
            {
                AddDummyChapters(video, chapters);
            }

            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);

            video.VideoBitRate            = videoStream == null ? null : videoStream.BitRate;
            video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;

            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);

            await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions
            {
                Chapters      = chapters,
                Video         = video,
                ExtractImages = false,
                SaveChapters  = false
            }, cancellationToken).ConfigureAwait(false);

            await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);

            await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false);
        }
コード例 #2
0
        private async void NewItemTimerCallback(object state)
        {
            List <Video> newItems;

            // Lock the list and release all resources
            lock (_newlyAddedItems)
            {
                newItems = _newlyAddedItems.DistinctBy(i => i.Id).ToList();
                _newlyAddedItems.Clear();

                NewItemTimer.Dispose();
                NewItemTimer = null;
            }

            // Limit to video files to reduce changes of ffmpeg crash dialog
            foreach (var item in newItems
                     .Where(i => i.LocationType == LocationType.FileSystem && i.VideoType == VideoType.VideoFile && string.IsNullOrEmpty(i.PrimaryImagePath) && i.DefaultVideoStreamIndex.HasValue)
                     .Take(1))
            {
                try
                {
                    var chapters = _itemRepo.GetChapters(item.Id).ToList();

                    await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions
                    {
                        SaveChapters  = true,
                        ExtractImages = true,
                        Video         = item,
                        Chapters      = chapters
                    }, CancellationToken.None);
                }
                catch (Exception ex)
                {
                    _logger.ErrorException("Error creating image for {0}", ex, item.Name);
                }
            }
        }
コード例 #3
0
ファイル: FFProbeVideoInfo.cs プロジェクト: zxz2020/jellyfin
        protected async Task Fetch(Video video,
                                   CancellationToken cancellationToken,
                                   Model.MediaInfo.MediaInfo mediaInfo,
                                   BlurayDiscInfo blurayInfo,
                                   MetadataRefreshOptions options)
        {
            List <MediaStream> mediaStreams;
            List <ChapterInfo> chapters;

            if (mediaInfo != null)
            {
                mediaStreams = mediaInfo.MediaStreams;

                video.TotalBitrate = mediaInfo.Bitrate;
                //video.FormatName = (mediaInfo.Container ?? string.Empty)
                //    .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);

                // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
                var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;

                if (needToSetRuntime)
                {
                    video.RunTimeTicks = mediaInfo.RunTimeTicks;
                }
                video.Size = mediaInfo.Size;

                if (video.VideoType == VideoType.VideoFile)
                {
                    var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');

                    video.Container = extension;
                }
                else
                {
                    video.Container = null;
                }
                video.Container = mediaInfo.Container;

                chapters = mediaInfo.Chapters == null ? new List <ChapterInfo>() : mediaInfo.Chapters.ToList();
                if (blurayInfo != null)
                {
                    FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
                }
            }
            else
            {
                mediaStreams = new List <MediaStream>();
                chapters     = new List <ChapterInfo>();
            }

            await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);

            var libraryOptions = _libraryManager.GetLibraryOptions(video);

            if (mediaInfo != null)
            {
                FetchEmbeddedInfo(video, mediaInfo, options, libraryOptions);
                FetchPeople(video, mediaInfo, options);
                video.Timestamp     = mediaInfo.Timestamp;
                video.Video3DFormat = video.Video3DFormat ?? mediaInfo.Video3DFormat;
            }

            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);

            video.Height = videoStream == null ? 0 : videoStream.Height ?? 0;
            video.Width  = videoStream == null ? 0 : videoStream.Width ?? 0;

            video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;

            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);

            _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);

            if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
                options.MetadataRefreshMode == MetadataRefreshMode.Default)
            {
                if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
                {
                    AddDummyChapters(video, chapters);
                }

                NormalizeChapterNames(chapters);

                var extractDuringScan = false;
                if (libraryOptions != null)
                {
                    extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan;
                }

                await _encodingManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan, false, cancellationToken).ConfigureAwait(false);

                _chapterManager.SaveChapters(video.Id.ToString(), chapters);
            }
        }
コード例 #4
0
        /// <summary>
        /// Returns the task to be executed
        /// </summary>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="progress">The progress.</param>
        /// <returns>Task.</returns>
        public async Task Execute(CancellationToken cancellationToken, IProgress <double> progress)
        {
            var videos = _libraryManager.GetItemList(new InternalItemsQuery
            {
                MediaTypes = new[] { MediaType.Video },
                IsFolder   = false,
                Recursive  = true,
                DtoOptions = new DtoOptions(false)
                {
                    EnableImages = false
                },
                SourceTypes      = new SourceType[] { SourceType.Library },
                HasChapterImages = false,
                IsVirtualItem    = false
            })
                         .OfType <Video>()
                         .ToList();

            var numComplete = 0;

            var failHistoryPath = Path.Combine(_appPaths.CachePath, "chapter-failures.txt");

            List <string> previouslyFailedImages;

            if (File.Exists(failHistoryPath))
            {
                try
                {
                    previouslyFailedImages = File.ReadAllText(failHistoryPath)
                                             .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
                                             .ToList();
                }
                catch (IOException)
                {
                    previouslyFailedImages = new List <string>();
                }
            }
            else
            {
                previouslyFailedImages = new List <string>();
            }

            var directoryService = new DirectoryService(_logger, _fileSystem);

            foreach (var video in videos)
            {
                cancellationToken.ThrowIfCancellationRequested();

                var key = video.Path + video.DateModified.Ticks;

                var extract = !previouslyFailedImages.Contains(key, StringComparer.OrdinalIgnoreCase);

                try
                {
                    var chapters = _itemRepo.GetChapters(video);

                    var success = await _encodingManager.RefreshChapterImages(video, directoryService, chapters, extract, true, cancellationToken).ConfigureAwait(false);

                    if (!success)
                    {
                        previouslyFailedImages.Add(key);

                        var parentPath = Path.GetDirectoryName(failHistoryPath);

                        Directory.CreateDirectory(parentPath);

                        string text = string.Join("|", previouslyFailedImages);
                        File.WriteAllText(failHistoryPath, text);
                    }

                    numComplete++;
                    double percent = numComplete;
                    percent /= videos.Count;

                    progress.Report(100 * percent);
                }
                catch (ObjectDisposedException)
                {
                    //TODO Investigate and properly fix.
                    break;
                }
            }
        }
コード例 #5
0
        /// <summary>
        /// Returns the task to be executed
        /// </summary>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="progress">The progress.</param>
        /// <returns>Task.</returns>
        public async Task Execute(CancellationToken cancellationToken, IProgress <double> progress)
        {
            var videos = _libraryManager.GetItemList(new InternalItemsQuery
            {
                MediaTypes = new[] { MediaType.Video },
                IsFolder   = false,
                Recursive  = true,
                DtoOptions = new DtoOptions(false)
            })
                         .OfType <Video>()
                         .ToList();

            var numComplete = 0;

            var failHistoryPath = Path.Combine(_appPaths.CachePath, "chapter-failures.txt");

            List <string> previouslyFailedImages;

            try
            {
                previouslyFailedImages = _fileSystem.ReadAllText(failHistoryPath)
                                         .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
                                         .ToList();
            }
            catch (FileNotFoundException)
            {
                previouslyFailedImages = new List <string>();
            }
            catch (IOException)
            {
                previouslyFailedImages = new List <string>();
            }

            foreach (var video in videos)
            {
                cancellationToken.ThrowIfCancellationRequested();

                var key = video.Path + video.DateModified.Ticks;

                var extract = !previouslyFailedImages.Contains(key, StringComparer.OrdinalIgnoreCase);

                try
                {
                    var chapters = _itemRepo.GetChapters(video.Id);

                    var success = await _encodingManager.RefreshChapterImages(video, chapters, extract, true, CancellationToken.None);

                    if (!success)
                    {
                        previouslyFailedImages.Add(key);

                        var parentPath = _fileSystem.GetDirectoryName(failHistoryPath);

                        _fileSystem.CreateDirectory(parentPath);

                        _fileSystem.WriteAllText(failHistoryPath, string.Join("|", previouslyFailedImages.ToArray(previouslyFailedImages.Count)));
                    }

                    numComplete++;
                    double percent = numComplete;
                    percent /= videos.Count;

                    progress.Report(100 * percent);
                }
                catch (ObjectDisposedException)
                {
                    break;
                }
            }
        }
コード例 #6
0
        protected async Task Fetch(Video video,
                                   CancellationToken cancellationToken,
                                   InternalMediaInfoResult data,
                                   IIsoMount isoMount,
                                   BlurayDiscInfo blurayInfo,
                                   MetadataRefreshOptions options)
        {
            var mediaInfo    = MediaEncoderHelpers.GetMediaInfo(data);
            var mediaStreams = mediaInfo.MediaStreams;

            video.TotalBitrate = mediaInfo.TotalBitrate;
            video.FormatName   = (mediaInfo.Format ?? string.Empty)
                                 .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);

            if (data.format != null)
            {
                // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
                var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;

                if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration))
                {
                    video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
                }

                if (video.VideoType == VideoType.VideoFile)
                {
                    var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');

                    video.Container = extension;
                }
                else
                {
                    video.Container = null;
                }

                if (!string.IsNullOrEmpty(data.format.size))
                {
                    video.Size = long.Parse(data.format.size, _usCulture);
                }
                else
                {
                    video.Size = null;
                }
            }

            var mediaChapters = (data.Chapters ?? new MediaChapter[] { }).ToList();
            var chapters      = mediaChapters.Select(GetChapterInfo).ToList();

            if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
            {
                FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
            }

            await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);

            FetchWtvInfo(video, data);

            video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);

            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);

            video.VideoBitRate            = videoStream == null ? null : videoStream.BitRate;
            video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;

            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);

            ExtractTimestamp(video);
            UpdateFromMediaInfo(video, videoStream);

            await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);

            if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
                options.MetadataRefreshMode == MetadataRefreshMode.Default)
            {
                var chapterOptions = _chapterManager.GetConfiguration();

                try
                {
                    var remoteChapters = await DownloadChapters(video, chapters, chapterOptions, cancellationToken).ConfigureAwait(false);

                    if (remoteChapters.Count > 0)
                    {
                        chapters = remoteChapters;
                    }
                }
                catch (Exception ex)
                {
                    _logger.ErrorException("Error downloading chapters", ex);
                }

                if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
                {
                    AddDummyChapters(video, chapters);
                }

                NormalizeChapterNames(chapters);

                await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions
                {
                    Chapters      = chapters,
                    Video         = video,
                    ExtractImages = chapterOptions.ExtractDuringLibraryScan,
                    SaveChapters  = false
                }, cancellationToken).ConfigureAwait(false);

                await _chapterManager.SaveChapters(video.Id.ToString(), chapters, cancellationToken).ConfigureAwait(false);
            }
        }
コード例 #7
0
ファイル: FFProbeVideoInfo.cs プロジェクト: w3fs/Emby
        protected async Task Fetch(Video video,
                                   CancellationToken cancellationToken,
                                   Model.MediaInfo.MediaInfo mediaInfo,
                                   IIsoMount isoMount,
                                   BlurayDiscInfo blurayInfo,
                                   MetadataRefreshOptions options)
        {
            var mediaStreams = mediaInfo.MediaStreams;

            video.TotalBitrate = mediaInfo.Bitrate;
            //video.FormatName = (mediaInfo.Container ?? string.Empty)
            //    .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);

            // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
            var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;

            if (needToSetRuntime)
            {
                video.RunTimeTicks = mediaInfo.RunTimeTicks;
            }

            if (video.VideoType == VideoType.VideoFile)
            {
                var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');

                video.Container = extension;
            }
            else
            {
                video.Container = null;
            }

            var chapters = mediaInfo.Chapters ?? new List <ChapterInfo>();

            if (blurayInfo != null)
            {
                FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
            }

            await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);

            FetchEmbeddedInfo(video, mediaInfo, options);
            await FetchPeople(video, mediaInfo, options).ConfigureAwait(false);

            video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);

            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);

            video.VideoBitRate            = videoStream == null ? null : videoStream.BitRate;
            video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;

            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
            video.Timestamp    = mediaInfo.Timestamp;

            await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);

            if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
                options.MetadataRefreshMode == MetadataRefreshMode.Default)
            {
                var chapterOptions = _chapterManager.GetConfiguration();

                try
                {
                    var remoteChapters = await DownloadChapters(video, chapters, chapterOptions, cancellationToken).ConfigureAwait(false);

                    if (remoteChapters.Count > 0)
                    {
                        chapters = remoteChapters;
                    }
                }
                catch (Exception ex)
                {
                    _logger.ErrorException("Error downloading chapters", ex);
                }

                if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
                {
                    AddDummyChapters(video, chapters);
                }

                NormalizeChapterNames(chapters);

                await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions
                {
                    Chapters      = chapters,
                    Video         = video,
                    ExtractImages = chapterOptions.ExtractDuringLibraryScan,
                    SaveChapters  = false
                }, cancellationToken).ConfigureAwait(false);

                await _chapterManager.SaveChapters(video.Id.ToString(), chapters, cancellationToken).ConfigureAwait(false);
            }
        }
コード例 #8
0
        protected async Task Fetch(
            Video video,
            CancellationToken cancellationToken,
            Model.MediaInfo.MediaInfo mediaInfo,
            BlurayDiscInfo blurayInfo,
            MetadataRefreshOptions options)
        {
            List <MediaStream> mediaStreams;
            IReadOnlyList <MediaAttachment> mediaAttachments;

            ChapterInfo[] chapters;

            if (mediaInfo != null)
            {
                mediaStreams     = mediaInfo.MediaStreams.ToList();
                mediaAttachments = mediaInfo.MediaAttachments;

                video.TotalBitrate = mediaInfo.Bitrate;
                // video.FormatName = (mediaInfo.Container ?? string.Empty)
                //    .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);

                // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
                var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;

                if (needToSetRuntime)
                {
                    video.RunTimeTicks = mediaInfo.RunTimeTicks;
                }

                video.Size = mediaInfo.Size;

                if (video.VideoType == VideoType.VideoFile)
                {
                    var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');

                    video.Container = extension;
                }
                else
                {
                    video.Container = null;
                }

                video.Container = mediaInfo.Container;

                chapters = mediaInfo.Chapters ?? Array.Empty <ChapterInfo>();
                if (blurayInfo != null)
                {
                    FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo);
                }
            }
            else
            {
                mediaStreams     = new List <MediaStream>();
                mediaAttachments = Array.Empty <MediaAttachment>();
                chapters         = Array.Empty <ChapterInfo>();
            }

            await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);

            await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);

            var libraryOptions = _libraryManager.GetLibraryOptions(video);

            if (mediaInfo != null)
            {
                FetchEmbeddedInfo(video, mediaInfo, options, libraryOptions);
                FetchPeople(video, mediaInfo, options);
                video.Timestamp = mediaInfo.Timestamp;
                video.Video3DFormat ??= mediaInfo.Video3DFormat;
            }

            if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowText || libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowNone)
            {
                _logger.LogDebug("Disabling embedded image subtitles for {Path} due to DisableEmbeddedImageSubtitles setting", video.Path);
                mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && !i.IsTextSubtitleStream);
            }

            if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowImage || libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowNone)
            {
                _logger.LogDebug("Disabling embedded text subtitles for {Path} due to DisableEmbeddedTextSubtitles setting", video.Path);
                mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && i.IsTextSubtitleStream);
            }

            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);

            video.Height = videoStream?.Height ?? 0;
            video.Width  = videoStream?.Width ?? 0;

            video.DefaultVideoStreamIndex = videoStream?.Index;

            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);

            _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);
            _itemRepo.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);

            if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
                options.MetadataRefreshMode == MetadataRefreshMode.Default)
            {
                if (chapters.Length == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
                {
                    chapters = CreateDummyChapters(video);
                }

                NormalizeChapterNames(chapters);

                var extractDuringScan = false;
                if (libraryOptions != null)
                {
                    extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan;
                }

                await _encodingManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan, false, cancellationToken).ConfigureAwait(false);

                _chapterManager.SaveChapters(video.Id, chapters);
            }
        }
コード例 #9
0
        /// <summary>
        /// Returns the task to be executed
        /// </summary>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="progress">The progress.</param>
        /// <returns>Task.</returns>
        public async Task Execute(CancellationToken cancellationToken, IProgress <double> progress)
        {
            var videos = _libraryManager.RootFolder.GetRecursiveChildren(i => i is Video)
                         .Cast <Video>()
                         .ToList();

            var numComplete = 0;

            var failHistoryPath = Path.Combine(_appPaths.CachePath, "chapter-failures.txt");

            List <string> previouslyFailedImages;

            try
            {
                previouslyFailedImages = File.ReadAllText(failHistoryPath)
                                         .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
                                         .ToList();
            }
            catch (FileNotFoundException)
            {
                previouslyFailedImages = new List <string>();
            }
            catch (DirectoryNotFoundException)
            {
                previouslyFailedImages = new List <string>();
            }

            foreach (var video in videos)
            {
                cancellationToken.ThrowIfCancellationRequested();

                var key = video.Path + video.DateModified.Ticks;

                var extract = !previouslyFailedImages.Contains(key, StringComparer.OrdinalIgnoreCase);

                var chapters = _itemRepo.GetChapters(video.Id).ToList();

                var success = await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions
                {
                    SaveChapters  = true,
                    ExtractImages = extract,
                    Video         = video,
                    Chapters      = chapters
                }, CancellationToken.None);

                if (!success)
                {
                    previouslyFailedImages.Add(key);

                    var parentPath = Path.GetDirectoryName(failHistoryPath);

                    Directory.CreateDirectory(parentPath);

                    File.WriteAllText(failHistoryPath, string.Join("|", previouslyFailedImages.ToArray()));
                }

                numComplete++;
                double percent = numComplete;
                percent /= videos.Count;

                progress.Report(100 * percent);
            }
        }