Extract attachments in one ffmpeg command when dumping

This commit is contained in:
Shadowghost
2026-05-23 22:41:44 +02:00
parent 31f4ce93a6
commit e627c723e2

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AsyncKeyedLock;
@@ -102,13 +104,10 @@ namespace MediaBrowser.MediaEncoding.Attachments
&& (a.FileName.Contains('/', StringComparison.OrdinalIgnoreCase) || a.FileName.Contains('\\', StringComparison.OrdinalIgnoreCase)));
if (shouldExtractOneByOne && !inputFile.EndsWith(".mks", StringComparison.OrdinalIgnoreCase))
{
foreach (var attachment in mediaSource.MediaAttachments)
{
if (!string.Equals(attachment.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase))
{
await ExtractAttachment(inputFile, mediaSource, attachment, cancellationToken).ConfigureAwait(false);
}
}
await ExtractAllAttachmentsIndividuallyInternal(
inputFile,
mediaSource,
cancellationToken).ConfigureAwait(false);
}
else
{
@@ -119,6 +118,140 @@ namespace MediaBrowser.MediaEncoding.Attachments
}
}
private async Task ExtractAllAttachmentsIndividuallyInternal(
string inputFile,
MediaSourceInfo mediaSource,
CancellationToken cancellationToken)
{
var inputPath = _mediaEncoder.GetInputArgument(inputFile, mediaSource);
ArgumentException.ThrowIfNullOrEmpty(inputPath);
var outputFolder = _pathManager.GetAttachmentFolderPath(mediaSource.Id);
if (outputFolder is null)
{
_logger.LogDebug("Skipping attachment extraction for input {InputFile}: MediaSource Id is not a GUID.", inputFile);
return;
}
using (await _semaphoreLocks.LockAsync(outputFolder, cancellationToken).ConfigureAwait(false))
{
Directory.CreateDirectory(outputFolder);
var dumpArgs = new StringBuilder();
var missingPaths = new List<string>();
foreach (var attachment in mediaSource.MediaAttachments)
{
if (string.Equals(attachment.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var indexName = attachment.Index.ToString(CultureInfo.InvariantCulture);
var attachmentPath = _pathManager.GetAttachmentPath(mediaSource.Id, attachment.FileName ?? indexName)
?? _pathManager.GetAttachmentPath(mediaSource.Id, indexName)!;
if (File.Exists(attachmentPath))
{
continue;
}
dumpArgs.AppendFormat(
CultureInfo.InvariantCulture,
"-dump_attachment:{0} \"{1}\" ",
attachment.Index,
EncodingUtils.NormalizePath(attachmentPath));
missingPaths.Add(attachmentPath);
}
if (missingPaths.Count == 0)
{
// Skip extraction if all files already exist
return;
}
var hasVideoOrAudioStream = mediaSource.MediaStreams
.Any(s => s.Type == MediaStreamType.Video || s.Type == MediaStreamType.Audio);
var processArgs = string.Format(
CultureInfo.InvariantCulture,
"{0}{1} -i {2} {3}",
dumpArgs,
inputPath.EndsWith(".concat\"", StringComparison.OrdinalIgnoreCase) ? "-f concat -safe 0" : string.Empty,
inputPath,
hasVideoOrAudioStream ? "-t 0 -f null null" : string.Empty);
int exitCode;
using (var process = new Process
{
StartInfo = new ProcessStartInfo
{
Arguments = processArgs,
FileName = _mediaEncoder.EncoderPath,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
},
EnableRaisingEvents = true
})
{
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
process.Start();
try
{
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
exitCode = process.ExitCode;
}
catch (OperationCanceledException)
{
process.Kill(true);
exitCode = -1;
}
}
var failed = false;
if (exitCode != 0 && (hasVideoOrAudioStream || exitCode != 1))
{
failed = true;
foreach (var path in missingPaths)
{
if (!File.Exists(path))
{
continue;
}
try
{
_fileSystem.DeleteFile(path);
}
catch (IOException ex)
{
_logger.LogError(ex, "Error deleting extracted attachment {Path}", path);
}
}
}
if (!failed && missingPaths.Exists(p => !File.Exists(p)))
{
failed = true;
}
if (failed)
{
_logger.LogError("ffmpeg attachment extraction failed for {InputPath} to {OutputPath}", inputPath, outputFolder);
throw new InvalidOperationException(
string.Format(CultureInfo.InvariantCulture, "ffmpeg attachment extraction failed for {0} to {1}", inputPath, outputFolder));
}
_logger.LogInformation("ffmpeg attachment extraction completed for {InputPath} to {OutputPath}", inputPath, outputFolder);
}
}
private async Task ExtractAllAttachmentsInternal(
string inputFile,
MediaSourceInfo mediaSource,