aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller/MediaInfo/FFMpegManager.cs')
-rw-r--r--MediaBrowser.Controller/MediaInfo/FFMpegManager.cs169
1 files changed, 115 insertions, 54 deletions
diff --git a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
index e53acfc02..ced53299d 100644
--- a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
+++ b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
@@ -1,12 +1,16 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
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;
@@ -19,79 +23,89 @@ namespace MediaBrowser.Controller.MediaInfo
/// </summary>
public class FFMpegManager
{
- /// <summary>
- /// Gets or sets the video image cache.
- /// </summary>
- /// <value>The video image cache.</value>
- internal FileSystemRepository VideoImageCache { get; set; }
-
- /// <summary>
- /// Gets or sets the subtitle cache.
- /// </summary>
- /// <value>The subtitle cache.</value>
- internal FileSystemRepository SubtitleCache { get; set; }
-
- private readonly IServerApplicationPaths _appPaths;
+ private readonly IServerConfigurationManager _config;
private readonly IMediaEncoder _encoder;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
private readonly IFileSystem _fileSystem;
+ public static FFMpegManager Instance { get; private set; }
+
/// <summary>
/// Initializes a new instance of the <see cref="FFMpegManager" /> class.
/// </summary>
- /// <param name="appPaths">The app paths.</param>
/// <param name="encoder">The encoder.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repo.</param>
/// <exception cref="System.ArgumentNullException">zipClient</exception>
- public FFMpegManager(IServerApplicationPaths appPaths, IMediaEncoder encoder, ILogger logger, IItemRepository itemRepo, IFileSystem fileSystem)
+ public FFMpegManager(IMediaEncoder encoder, ILogger logger, IItemRepository itemRepo, IFileSystem fileSystem, IServerConfigurationManager config)
{
- _appPaths = appPaths;
_encoder = encoder;
_logger = logger;
_itemRepo = itemRepo;
_fileSystem = fileSystem;
+ _config = config;
- VideoImageCache = new FileSystemRepository(VideoImagesDataPath);
- SubtitleCache = new FileSystemRepository(SubtitleCachePath);
+ // TODO: Remove this static instance
+ Instance = this;
}
/// <summary>
- /// Gets the video images data path.
+ /// Gets the chapter images data path.
/// </summary>
- /// <value>The video images data path.</value>
- public string VideoImagesDataPath
+ /// <value>The chapter images data path.</value>
+ public string ChapterImagesPath
{
get
{
- return Path.Combine(_appPaths.DataPath, "extracted-video-images");
+ return Path.Combine(_config.ApplicationPaths.DataPath, "chapter-images");
}
}
/// <summary>
- /// Gets the audio images data path.
+ /// Gets the subtitle cache path.
/// </summary>
- /// <value>The audio images data path.</value>
- public string AudioImagesDataPath
+ /// <value>The subtitle cache path.</value>
+ private string SubtitleCachePath
{
get
{
- return Path.Combine(_appPaths.DataPath, "extracted-audio-images");
+ return Path.Combine(_config.ApplicationPaths.CachePath, "subtitles");
}
}
/// <summary>
- /// Gets the subtitle cache path.
+ /// Determines whether [is eligible for chapter image extraction] [the specified video].
/// </summary>
- /// <value>The subtitle cache path.</value>
- public string SubtitleCachePath
+ /// <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)
{
- get
+ if (video is Movie)
+ {
+ if (!_config.Configuration.EnableMovieChapterImageExtraction)
+ {
+ return false;
+ }
+ }
+ else if (video is Episode)
{
- return Path.Combine(_appPaths.CachePath, "subtitles");
+ 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>
@@ -111,8 +125,7 @@ namespace MediaBrowser.Controller.MediaInfo
/// <exception cref="System.ArgumentNullException"></exception>
public async Task<bool> PopulateChapterImages(Video video, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
{
- // Can't extract images if there are no video streams
- if (!video.DefaultVideoStreamIndex.HasValue)
+ if (!IsEligibleForChapterImageExtraction(video))
{
return true;
}
@@ -122,6 +135,8 @@ namespace MediaBrowser.Controller.MediaInfo
var runtimeTicks = video.RunTimeTicks ?? 0;
+ var currentImages = GetSavedChapterImages(video);
+
foreach (var chapter in chapters)
{
if (chapter.StartPositionTicks >= runtimeTicks)
@@ -130,11 +145,9 @@ namespace MediaBrowser.Controller.MediaInfo
break;
}
- var filename = video.Path + "_" + video.DateModified.Ticks + "_" + chapter.StartPositionTicks;
-
- var path = VideoImageCache.GetResourcePath(filename, ".jpg");
+ var path = GetChapterImagePath(video, chapter.StartPositionTicks);
- if (!File.Exists(path))
+ if (!currentImages.Contains(path, StringComparer.OrdinalIgnoreCase))
{
if (extractImages)
{
@@ -157,7 +170,7 @@ namespace MediaBrowser.Controller.MediaInfo
InputType type;
- var inputPath = MediaEncoderHelpers.GetInputArgument(video, null, out type);
+ var inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, false, video.VideoType, video.IsoType, null, video.PlayableStreamFileNames, out type);
try
{
@@ -188,39 +201,87 @@ namespace MediaBrowser.Controller.MediaInfo
await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false);
}
+ DeleteDeadImages(currentImages, chapters);
+
return success;
}
+ 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);
+ }
+ }
+ }
+
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
/// <summary>
/// Gets the subtitle cache path.
/// </summary>
- /// <param name="input">The input.</param>
- /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
+ /// <param name="mediaPath">The media path.</param>
+ /// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="offset">The offset.</param>
/// <param name="outputExtension">The output extension.</param>
/// <returns>System.String.</returns>
- public string GetSubtitleCachePath(Video input, int subtitleStreamIndex, TimeSpan? offset, string outputExtension)
+ public string GetSubtitleCachePath(string mediaPath, MediaStream subtitleStream, TimeSpan? offset, string outputExtension)
{
var ticksParam = offset.HasValue ? "_" + offset.Value.Ticks : "";
- var stream = _itemRepo.GetMediaStreams(new MediaStreamQuery
+ if (subtitleStream.IsExternal)
{
- ItemId = input.Id,
- Index = subtitleStreamIndex
+ ticksParam += _fileSystem.GetLastWriteTimeUtc(subtitleStream.Path).Ticks;
+ }
+
+ var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
- }).FirstOrDefault();
+ var filename = (mediaPath + "_" + subtitleStream.Index.ToString(_usCulture) + "_" + date.Ticks.ToString(_usCulture) + ticksParam).GetMD5() + outputExtension;
- if (stream == null)
+ var prefix = filename.Substring(0, 1);
+
+ return Path.Combine(SubtitleCachePath, prefix, filename);
+ }
+
+ public string GetChapterImagePath(Video video, long chapterPositionTicks)
+ {
+ var filename = video.DateModified.Ticks.ToString(_usCulture) + "_" + chapterPositionTicks.ToString(_usCulture) + ".jpg";
+
+ var videoId = video.Id.ToString();
+ var prefix = videoId.Substring(0, 1);
+
+ return Path.Combine(ChapterImagesPath, prefix, videoId, filename);
+ }
+
+ public List<string> GetSavedChapterImages(Video video)
+ {
+ var videoId = video.Id.ToString();
+ var prefix = videoId.Substring(0, 1);
+
+ var path = Path.Combine(ChapterImagesPath, prefix, videoId);
+
+ try
{
- return null;
+ return Directory.EnumerateFiles(path)
+ .ToList();
}
-
- if (stream.IsExternal)
+ catch (DirectoryNotFoundException)
{
- ticksParam += _fileSystem.GetLastWriteTimeUtc(stream.Path).Ticks;
+ return new List<string>();
}
-
- return SubtitleCache.GetResourcePath(input.Id + "_" + subtitleStreamIndex + "_" + input.DateModified.Ticks + ticksParam, outputExtension);
}
}
}