emby-toolbox/EmbyToolbox/Services/FfprobeService.cs
Emby Toolbox 6264b487fe Initial commit: Emby Toolbox (conversion scroll fix, bulk Del for tracks).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 21:33:47 +05:00

146 lines
4.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}