|
|
|
@ -4,6 +4,7 @@ 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;
|
|
|
|
@ -15,63 +16,65 @@ using System.Linq;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
namespace MediaBrowser.Controller.MediaInfo
|
|
|
|
|
namespace MediaBrowser.Server.Implementations.MediaEncoder
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Class FFMpegManager
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class FFMpegManager
|
|
|
|
|
public class EncodingManager : IEncodingManager
|
|
|
|
|
{
|
|
|
|
|
private readonly IServerConfigurationManager _config;
|
|
|
|
|
private readonly IMediaEncoder _encoder;
|
|
|
|
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
|
|
|
|
private readonly IFileSystem _fileSystem;
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
private readonly IItemRepository _itemRepo;
|
|
|
|
|
private readonly IMediaEncoder _encoder;
|
|
|
|
|
|
|
|
|
|
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="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(IMediaEncoder encoder, ILogger logger, IItemRepository itemRepo, IFileSystem fileSystem, IServerConfigurationManager config)
|
|
|
|
|
public EncodingManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IMediaEncoder encoder)
|
|
|
|
|
{
|
|
|
|
|
_encoder = encoder;
|
|
|
|
|
_config = config;
|
|
|
|
|
_fileSystem = fileSystem;
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_itemRepo = itemRepo;
|
|
|
|
|
_fileSystem = fileSystem;
|
|
|
|
|
_config = config;
|
|
|
|
|
|
|
|
|
|
// TODO: Remove this static instance
|
|
|
|
|
Instance = this;
|
|
|
|
|
_encoder = encoder;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the chapter images data path.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value>The chapter images data path.</value>
|
|
|
|
|
public string ChapterImagesPath
|
|
|
|
|
private string SubtitleCachePath
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return Path.Combine(_config.ApplicationPaths.DataPath, "chapter-images");
|
|
|
|
|
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 subtitle cache path.
|
|
|
|
|
/// Gets the chapter images data path.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value>The subtitle cache path.</value>
|
|
|
|
|
private string SubtitleCachePath
|
|
|
|
|
/// <value>The chapter images data path.</value>
|
|
|
|
|
private string GetChapterImagesPath(Guid itemId)
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return Path.Combine(_config.ApplicationPaths.CachePath, "subtitles");
|
|
|
|
|
}
|
|
|
|
|
return Path.Combine(_config.ApplicationPaths.GetInternalMetadataPath(itemId), "chapters");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -95,7 +98,7 @@ namespace MediaBrowser.Controller.MediaInfo
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (!_config.Configuration.EnableOtherVideoChapterImageExtraction)
|
|
|
|
|
{
|
|
|
|
@ -112,18 +115,13 @@ namespace MediaBrowser.Controller.MediaInfo
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static readonly long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Extracts the chapter images.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="video">The video.</param>
|
|
|
|
|
/// <param name="chapters">The chapters.</param>
|
|
|
|
|
/// <param name="extractImages">if set to <c>true</c> [extract images].</param>
|
|
|
|
|
/// <param name="saveChapters">if set to <c>true</c> [save chapters].</param>
|
|
|
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
|
|
|
/// <returns>Task.</returns>
|
|
|
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
|
|
|
|
public async Task<bool> PopulateChapterImages(Video video, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
|
|
|
|
|
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;
|
|
|
|
@ -173,9 +171,7 @@ namespace MediaBrowser.Controller.MediaInfo
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var parentPath = Path.GetDirectoryName(path);
|
|
|
|
|
|
|
|
|
|
Directory.CreateDirectory(parentPath);
|
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
|
|
|
|
|
|
|
|
|
using (var stream = await _encoder.ExtractImage(inputPath, type, false, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false))
|
|
|
|
|
{
|
|
|
|
@ -217,6 +213,28 @@ namespace MediaBrowser.Controller.MediaInfo
|
|
|
|
|
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
|
|
|
|
@ -238,60 +256,5 @@ namespace MediaBrowser.Controller.MediaInfo
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the subtitle cache path.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="mediaPath">The media path.</param>
|
|
|
|
|
/// <param name="subtitleStream">The subtitle stream.</param>
|
|
|
|
|
/// <param name="outputExtension">The output extension.</param>
|
|
|
|
|
/// <returns>System.String.</returns>
|
|
|
|
|
public string GetSubtitleCachePath(string mediaPath, MediaStream subtitleStream, string outputExtension)
|
|
|
|
|
{
|
|
|
|
|
var ticksParam = string.Empty;
|
|
|
|
|
|
|
|
|
|
if (subtitleStream.IsExternal)
|
|
|
|
|
{
|
|
|
|
|
ticksParam += _fileSystem.GetLastWriteTimeUtc(subtitleStream.Path).Ticks;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
|
|
|
|
|
|
|
|
|
|
var filename = (mediaPath + "_" + subtitleStream.Index.ToString(_usCulture) + "_" + date.Ticks.ToString(_usCulture) + ticksParam).GetMD5() + outputExtension;
|
|
|
|
|
|
|
|
|
|
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 Directory.EnumerateFiles(path)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
catch (DirectoryNotFoundException)
|
|
|
|
|
{
|
|
|
|
|
return new List<string>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|