aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs')
-rw-r--r--MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs195
1 files changed, 169 insertions, 26 deletions
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
index a0ec3bd90..989e386a5 100644
--- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
+++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
@@ -14,6 +14,7 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.MediaEncoding.Encoder;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
@@ -50,12 +51,9 @@ namespace MediaBrowser.MediaEncoding.Attachments
}
/// <inheritdoc />
- public async Task<(MediaAttachment attachment, Stream stream)> GetAttachment(BaseItem item, string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken)
+ public async Task<(MediaAttachment Attachment, Stream Stream)> GetAttachment(BaseItem item, string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
if (string.IsNullOrWhiteSpace(mediaSourceId))
{
@@ -65,14 +63,14 @@ namespace MediaBrowser.MediaEncoding.Attachments
var mediaSources = await _mediaSourceManager.GetPlaybackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false);
var mediaSource = mediaSources
.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
- if (mediaSource == null)
+ if (mediaSource is null)
{
throw new ResourceNotFoundException($"MediaSource {mediaSourceId} not found");
}
var mediaAttachment = mediaSource.MediaAttachments
.FirstOrDefault(i => i.Index == attachmentStreamIndex);
- if (mediaAttachment == null)
+ if (mediaAttachment is null)
{
throw new ResourceNotFoundException($"MediaSource {mediaSourceId} has no attachment with stream index {attachmentStreamIndex}");
}
@@ -83,13 +81,167 @@ namespace MediaBrowser.MediaEncoding.Attachments
return (mediaAttachment, attachmentStream);
}
+ public async Task ExtractAllAttachments(
+ string inputFile,
+ MediaSourceInfo mediaSource,
+ string outputPath,
+ CancellationToken cancellationToken)
+ {
+ var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1));
+
+ await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ if (!Directory.Exists(outputPath))
+ {
+ await ExtractAllAttachmentsInternal(
+ _mediaEncoder.GetInputArgument(inputFile, mediaSource),
+ outputPath,
+ false,
+ cancellationToken).ConfigureAwait(false);
+ }
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ }
+
+ public async Task ExtractAllAttachmentsExternal(
+ string inputArgument,
+ string id,
+ string outputPath,
+ CancellationToken cancellationToken)
+ {
+ var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1));
+
+ await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ if (!File.Exists(Path.Join(outputPath, id)))
+ {
+ await ExtractAllAttachmentsInternal(
+ inputArgument,
+ outputPath,
+ true,
+ cancellationToken).ConfigureAwait(false);
+
+ if (Directory.Exists(outputPath))
+ {
+ File.Create(Path.Join(outputPath, id));
+ }
+ }
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ }
+
+ private async Task ExtractAllAttachmentsInternal(
+ string inputPath,
+ string outputPath,
+ bool isExternal,
+ CancellationToken cancellationToken)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(inputPath);
+ ArgumentException.ThrowIfNullOrEmpty(outputPath);
+
+ Directory.CreateDirectory(outputPath);
+
+ var processArgs = string.Format(
+ CultureInfo.InvariantCulture,
+ "-dump_attachment:t \"\" -y -i {0} -t 0 -f null null",
+ inputPath);
+
+ int exitCode;
+
+ using (var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ Arguments = processArgs,
+ FileName = _mediaEncoder.EncoderPath,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ WorkingDirectory = outputPath,
+ ErrorDialog = false
+ },
+ EnableRaisingEvents = true
+ })
+ {
+ _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
+
+ process.Start();
+
+ var ranToCompletion = await ProcessExtensions.WaitForExitAsync(process, cancellationToken).ConfigureAwait(false);
+
+ if (!ranToCompletion)
+ {
+ try
+ {
+ _logger.LogWarning("Killing ffmpeg attachment extraction process");
+ process.Kill();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error killing attachment extraction process");
+ }
+ }
+
+ exitCode = ranToCompletion ? process.ExitCode : -1;
+ }
+
+ var failed = false;
+
+ if (exitCode != 0)
+ {
+ if (isExternal && exitCode == 1)
+ {
+ // ffmpeg returns exitCode 1 because there is no video or audio stream
+ // this can be ignored
+ }
+ else
+ {
+ failed = true;
+
+ _logger.LogWarning("Deleting extracted attachments {Path} due to failure: {ExitCode}", outputPath, exitCode);
+ try
+ {
+ Directory.Delete(outputPath);
+ }
+ catch (IOException ex)
+ {
+ _logger.LogError(ex, "Error deleting extracted attachments {Path}", outputPath);
+ }
+ }
+ }
+ else if (!Directory.Exists(outputPath))
+ {
+ failed = true;
+ }
+
+ if (failed)
+ {
+ _logger.LogError("ffmpeg attachment extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath);
+
+ throw new InvalidOperationException(
+ string.Format(CultureInfo.InvariantCulture, "ffmpeg attachment extraction failed for {0} to {1}", inputPath, outputPath));
+ }
+
+ _logger.LogInformation("ffmpeg attachment extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath);
+ }
+
private async Task<Stream> GetAttachmentStream(
MediaSourceInfo mediaSource,
MediaAttachment mediaAttachment,
CancellationToken cancellationToken)
{
var attachmentPath = await GetReadableFile(mediaSource.Path, mediaSource.Path, mediaSource, mediaAttachment, cancellationToken).ConfigureAwait(false);
- return File.OpenRead(attachmentPath);
+ return AsyncFile.OpenRead(attachmentPath);
}
private async Task<string> GetReadableFile(
@@ -140,24 +292,18 @@ namespace MediaBrowser.MediaEncoding.Attachments
string outputPath,
CancellationToken cancellationToken)
{
- if (string.IsNullOrEmpty(inputPath))
- {
- throw new ArgumentNullException(nameof(inputPath));
- }
+ ArgumentException.ThrowIfNullOrEmpty(inputPath);
- if (string.IsNullOrEmpty(outputPath))
- {
- throw new ArgumentNullException(nameof(outputPath));
- }
+ ArgumentException.ThrowIfNullOrEmpty(outputPath);
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
var processArgs = string.Format(
CultureInfo.InvariantCulture,
- "-dump_attachment:{1} {2} -i {0} -t 0 -f null null",
+ "-dump_attachment:{1} \"{2}\" -i {0} -t 0 -f null null",
inputPath,
attachmentStreamIndex,
- outputPath);
+ EncodingUtils.NormalizePath(outputPath));
int exitCode;
@@ -223,16 +369,13 @@ namespace MediaBrowser.MediaEncoding.Attachments
if (failed)
{
- var msg = $"ffmpeg attachment extraction failed for {inputPath} to {outputPath}";
-
- _logger.LogError(msg);
+ _logger.LogError("ffmpeg attachment extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath);
- throw new InvalidOperationException(msg);
- }
- else
- {
- _logger.LogInformation("ffmpeg attachment extraction completed for {Path} to {Path}", inputPath, outputPath);
+ throw new InvalidOperationException(
+ string.Format(CultureInfo.InvariantCulture, "ffmpeg attachment extraction failed for {0} to {1}", inputPath, outputPath));
}
+
+ _logger.LogInformation("ffmpeg attachment extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath);
}
private string GetAttachmentCachePath(string mediaPath, MediaSourceInfo mediaSource, int attachmentStreamIndex)