diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-02-20 11:37:41 -0500 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-02-20 11:37:41 -0500 |
| commit | 888b8d619aec031f57cfd03410ccda52edcca958 (patch) | |
| tree | 143080937fb8811d107c610507a15376d6761955 /MediaBrowser.Server.Implementations | |
| parent | 160d14208809a13791e34530a3758b079d6b9638 (diff) | |
added encoding manager interface
Diffstat (limited to 'MediaBrowser.Server.Implementations')
5 files changed, 290 insertions, 8 deletions
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 989ed3c35..110e6377a 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -7,7 +7,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.MediaInfo; +using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Sorting; diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 5060dbed3..9417eb191 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -179,6 +179,7 @@ <Compile Include="LiveTv\RecordingImageProvider.cs" /> <Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" /> <Compile Include="Localization\LocalizationManager.cs" /> + <Compile Include="MediaEncoder\EncodingManager.cs" /> <Compile Include="MediaEncoder\MediaEncoder.cs" /> <Compile Include="News\NewsEntryPoint.cs" /> <Compile Include="News\NewsService.cs" /> diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs new file mode 100644 index 000000000..6f4b7a8a7 --- /dev/null +++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -0,0 +1,260 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.MediaEncoder +{ + public class EncodingManager : IEncodingManager + { + private readonly IServerConfigurationManager _config; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; + private readonly IMediaEncoder _encoder; + + public EncodingManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IMediaEncoder encoder) + { + _config = config; + _fileSystem = fileSystem; + _logger = logger; + _itemRepo = itemRepo; + _encoder = encoder; + } + + private string SubtitleCachePath + { + get + { + return Path.Combine(_config.ApplicationPaths.CachePath, "subtitles"); + } + } + + public string GetSubtitleCachePath(string originalSubtitlePath, string outputSubtitleExtension) + { + var ticksParam = _fileSystem.GetLastWriteTimeUtc(originalSubtitlePath).Ticks; + + var filename = (originalSubtitlePath + ticksParam).GetMD5() + outputSubtitleExtension; + + var prefix = filename.Substring(0, 1); + + return Path.Combine(SubtitleCachePath, prefix, filename); + } + + public string GetSubtitleCachePath(string mediaPath, int subtitleStreamIndex, string outputSubtitleExtension) + { + var ticksParam = string.Empty; + + var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); + + var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(_usCulture) + "_" + date.Ticks.ToString(_usCulture) + ticksParam).GetMD5() + outputSubtitleExtension; + + var prefix = filename.Substring(0, 1); + + return Path.Combine(SubtitleCachePath, prefix, filename); + } + + /// <summary> + /// Gets the chapter images data path. + /// </summary> + /// <value>The chapter images data path.</value> + private string GetChapterImagesPath(Guid itemId) + { + return Path.Combine(_config.ApplicationPaths.GetInternalMetadataPath(itemId), "chapters"); + } + + /// <summary> + /// Determines whether [is eligible for chapter image extraction] [the specified video]. + /// </summary> + /// <param name="video">The video.</param> + /// <returns><c>true</c> if [is eligible for chapter image extraction] [the specified video]; otherwise, <c>false</c>.</returns> + private bool IsEligibleForChapterImageExtraction(Video video) + { + if (video is Movie) + { + if (!_config.Configuration.EnableMovieChapterImageExtraction) + { + return false; + } + } + else if (video is Episode) + { + if (!_config.Configuration.EnableEpisodeChapterImageExtraction) + { + return false; + } + } + else + { + if (!_config.Configuration.EnableOtherVideoChapterImageExtraction) + { + return false; + } + } + + // Can't extract images if there are no video streams + return video.DefaultVideoStreamIndex.HasValue; + } + + /// <summary> + /// The first chapter ticks + /// </summary> + private static readonly long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks; + + public async Task<bool> RefreshChapterImages(ChapterImageRefreshOptions options, CancellationToken cancellationToken) + { + var extractImages = options.ExtractImages; + var video = options.Video; + var chapters = options.Chapters; + var saveChapters = options.SaveChapters; + + if (!IsEligibleForChapterImageExtraction(video)) + { + extractImages = false; + } + + var success = true; + var changesMade = false; + + var runtimeTicks = video.RunTimeTicks ?? 0; + + var currentImages = GetSavedChapterImages(video); + + foreach (var chapter in chapters) + { + if (chapter.StartPositionTicks >= runtimeTicks) + { + _logger.Info("Stopping chapter extraction for {0} because a chapter was found with a position greater than the runtime.", video.Name); + break; + } + + var path = GetChapterImagePath(video, chapter.StartPositionTicks); + + if (!currentImages.Contains(path, StringComparer.OrdinalIgnoreCase)) + { + if (extractImages) + { + if (video.VideoType == VideoType.HdDvd || video.VideoType == VideoType.Iso) + { + continue; + } + + if (video.VideoType == VideoType.BluRay) + { + // Can only extract reliably on single file blurays + if (video.PlayableStreamFileNames == null || video.PlayableStreamFileNames.Count != 1) + { + continue; + } + } + + // Add some time for the first chapter to make sure we don't end up with a black image + var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks); + + InputType type; + + var inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, false, video.VideoType, video.IsoType, null, video.PlayableStreamFileNames, out type); + + try + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + using (var stream = await _encoder.ExtractImage(inputPath, type, false, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false)) + { + using (var fileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) + { + await stream.CopyToAsync(fileStream).ConfigureAwait(false); + } + } + + chapter.ImagePath = path; + changesMade = true; + } + catch + { + success = false; + break; + } + } + else if (!string.IsNullOrEmpty(chapter.ImagePath)) + { + chapter.ImagePath = null; + changesMade = true; + } + } + else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase)) + { + chapter.ImagePath = path; + changesMade = true; + } + } + + if (saveChapters && changesMade) + { + await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false); + } + + DeleteDeadImages(currentImages, chapters); + + return success; + } + + private string GetChapterImagePath(Video video, long chapterPositionTicks) + { + var filename = video.DateModified.Ticks.ToString(_usCulture) + "_" + chapterPositionTicks.ToString(_usCulture) + ".jpg"; + + return Path.Combine(GetChapterImagesPath(video.Id), filename); + } + + private List<string> GetSavedChapterImages(Video video) + { + var path = GetChapterImagesPath(video.Id); + + try + { + return Directory.EnumerateFiles(path) + .ToList(); + } + catch (DirectoryNotFoundException) + { + return new List<string>(); + } + } + + private void DeleteDeadImages(IEnumerable<string> images, IEnumerable<ChapterInfo> chapters) + { + var deadImages = images + .Except(chapters.Select(i => i.ImagePath).Where(i => !string.IsNullOrEmpty(i)), StringComparer.OrdinalIgnoreCase) + .Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i), StringComparer.OrdinalIgnoreCase)) + .ToList(); + + foreach (var image in deadImages) + { + _logger.Debug("Deleting dead chapter image {0}", image); + + try + { + File.Delete(image); + } + catch (IOException ex) + { + _logger.ErrorException("Error deleting {0}.", ex, image); + } + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs index c1843504c..e71867b05 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs @@ -1,6 +1,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.IO; -using MediaBrowser.Controller.MediaInfo; +using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs index 2608ac172..2a115c8dd 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs @@ -1,7 +1,8 @@ -using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaInfo; +using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -41,17 +42,23 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks private readonly IItemRepository _itemRepo; + private readonly IApplicationPaths _appPaths; + + private readonly IEncodingManager _encodingManager; + /// <summary> /// Initializes a new instance of the <see cref="ChapterImagesTask" /> class. /// </summary> /// <param name="logManager">The log manager.</param> /// <param name="libraryManager">The library manager.</param> /// <param name="itemRepo">The item repo.</param> - public ChapterImagesTask(ILogManager logManager, ILibraryManager libraryManager, IItemRepository itemRepo) + public ChapterImagesTask(ILogManager logManager, ILibraryManager libraryManager, IItemRepository itemRepo, IApplicationPaths appPaths, IEncodingManager encodingManager) { _logger = logManager.GetLogger(GetType().Name); _libraryManager = libraryManager; _itemRepo = itemRepo; + _appPaths = appPaths; + _encodingManager = encodingManager; libraryManager.ItemAdded += libraryManager_ItemAdded; libraryManager.ItemUpdated += libraryManager_ItemAdded; @@ -102,7 +109,14 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks { var chapters = _itemRepo.GetChapters(item.Id).ToList(); - await FFMpegManager.Instance.PopulateChapterImages(item, chapters, true, true, CancellationToken.None); + await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions + { + SaveChapters = true, + ExtractImages = true, + Video = item, + Chapters = chapters + + }, CancellationToken.None); } catch (Exception ex) { @@ -137,7 +151,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks var numComplete = 0; - var failHistoryPath = Path.Combine(FFMpegManager.Instance.ChapterImagesPath, "failures.txt"); + var failHistoryPath = Path.Combine(_appPaths.CachePath, "chapter-failures.txt"); List<string> previouslyFailedImages; @@ -166,7 +180,14 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks var chapters = _itemRepo.GetChapters(video.Id).ToList(); - var success = await FFMpegManager.Instance.PopulateChapterImages(video, chapters, extract, true, cancellationToken); + var success = await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions + { + SaveChapters = true, + ExtractImages = extract, + Video = video, + Chapters = chapters + + }, CancellationToken.None); if (!success) { |
