146 lines
4.6 KiB
C#
146 lines
4.6 KiB
C#
using System.Diagnostics;
|
||
using System.IO;
|
||
using System.Text;
|
||
|
||
namespace EmbyToolbox.Services;
|
||
|
||
public sealed class FfprobeService
|
||
{
|
||
public async Task<FfprobeResult> AnalyzeAsync(string filePath, CancellationToken cancellationToken = default)
|
||
=> await AnalyzeInternalAsync(filePath, "-v error -show_format -show_streams -show_chapters -print_format json", cancellationToken);
|
||
|
||
public async Task<FfprobeResult> AnalyzeSubtitlePacketsAsync(string filePath, CancellationToken cancellationToken = default)
|
||
=> await AnalyzeInternalAsync(
|
||
filePath,
|
||
"-v error -select_streams s -show_packets -show_entries packet=stream_index,duration_time -print_format json",
|
||
cancellationToken);
|
||
|
||
private async Task<FfprobeResult> AnalyzeInternalAsync(string filePath, string argumentPrefix, CancellationToken cancellationToken)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath))
|
||
{
|
||
return FfprobeResult.Fail("Файл не выбран или не существует.");
|
||
}
|
||
|
||
var ffprobePath = ResolveFfprobePath();
|
||
if (!File.Exists(ffprobePath))
|
||
{
|
||
return FfprobeResult.Fail($"ffprobe не найден: {ffprobePath}");
|
||
}
|
||
|
||
var arguments = $"{argumentPrefix} \"{filePath}\"";
|
||
var startInfo = new ProcessStartInfo
|
||
{
|
||
FileName = ffprobePath,
|
||
Arguments = arguments,
|
||
RedirectStandardOutput = true,
|
||
RedirectStandardError = true,
|
||
UseShellExecute = false,
|
||
CreateNoWindow = true,
|
||
StandardOutputEncoding = Encoding.UTF8,
|
||
StandardErrorEncoding = Encoding.UTF8
|
||
};
|
||
|
||
using var process = new Process { StartInfo = startInfo };
|
||
process.Start();
|
||
using var killRegistration = cancellationToken.Register(
|
||
static state =>
|
||
{
|
||
try
|
||
{
|
||
if (state is not Process p || p.HasExited)
|
||
{
|
||
return;
|
||
}
|
||
|
||
p.Kill(entireProcessTree: true);
|
||
}
|
||
catch
|
||
{
|
||
// ignore
|
||
}
|
||
},
|
||
process,
|
||
useSynchronizationContext: false);
|
||
|
||
var outputTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
|
||
var errorTask = process.StandardError.ReadToEndAsync(cancellationToken);
|
||
try
|
||
{
|
||
await process.WaitForExitAsync(cancellationToken);
|
||
}
|
||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||
{
|
||
try
|
||
{
|
||
await Task.WhenAll(outputTask, errorTask);
|
||
}
|
||
catch
|
||
{
|
||
// ignore
|
||
}
|
||
|
||
throw;
|
||
}
|
||
|
||
var output = await outputTask;
|
||
var error = await errorTask;
|
||
|
||
if (process.ExitCode != 0)
|
||
{
|
||
var message = string.IsNullOrWhiteSpace(error)
|
||
? $"ffprobe завершился с кодом {process.ExitCode}."
|
||
: error.Trim();
|
||
return FfprobeResult.Fail(message, $"{ffprobePath} {arguments}", output, error);
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(output))
|
||
{
|
||
return FfprobeResult.Fail("ffprobe вернул пустой JSON-результат.", $"{ffprobePath} {arguments}", output, error);
|
||
}
|
||
|
||
return FfprobeResult.Ok(output, $"{ffprobePath} {arguments}", output, error);
|
||
}
|
||
|
||
private static string ResolveFfprobePath()
|
||
{
|
||
return Path.Combine(AppContext.BaseDirectory, "Tools", "ffprobe.exe");
|
||
}
|
||
}
|
||
|
||
public sealed class FfprobeResult
|
||
{
|
||
private FfprobeResult(bool isSuccess, string json, string error, string command, string stdOut, string stdErr)
|
||
{
|
||
IsSuccess = isSuccess;
|
||
Json = json;
|
||
Error = error;
|
||
Command = command;
|
||
StdOut = stdOut;
|
||
StdErr = stdErr;
|
||
}
|
||
|
||
public bool IsSuccess { get; }
|
||
|
||
public string Json { get; }
|
||
|
||
public string Error { get; }
|
||
|
||
public string Command { get; }
|
||
|
||
public string StdOut { get; }
|
||
|
||
public string StdErr { get; }
|
||
|
||
public static FfprobeResult Ok(string json, string command, string stdOut, string stdErr)
|
||
{
|
||
return new FfprobeResult(true, json, string.Empty, command, stdOut, stdErr);
|
||
}
|
||
|
||
public static FfprobeResult Fail(string error, string command = "", string stdOut = "", string stdErr = "")
|
||
{
|
||
return new FfprobeResult(false, string.Empty, error, command, stdOut, stdErr);
|
||
}
|
||
}
|
||
|