using System.Diagnostics; using System.IO; using System.Text; using EmbyToolbox.Models; namespace EmbyToolbox.Services; public sealed class TrackExtractionService { private readonly FfprobeService _ffprobe; public TrackExtractionService(FfprobeService ffprobe) { _ffprobe = ffprobe; } public async Task AnalyzeMediaAsync(string filePath, LoggingService logging, CancellationToken ct) { var probe = await _ffprobe.AnalyzeAsync(filePath, ct).ConfigureAwait(false); if (!probe.IsSuccess) { logging.Error($"ffprobe: {probe.Error}", "tracks.extract"); return null; } var parsed = MediaAnalysisParser.TryParse(probe.Json); if (parsed is null) { logging.Error($"ffprobe JSON не распознан: {Path.GetFileName(filePath)}", "tracks.extract"); } return parsed; } /// Создаёт [destination]\extract\{audio|subtitles|attachments}, возвращает путь к extract. public string? PrepareExtractLayout(string destinationRootFolder) => TrackExtractOutputPaths.TryPrepareExtractDirectories(destinationRootFolder); /// Успех, stderr ffmpeg. public async Task<(bool Success, string StdErr)> RunExtractProcessAsync(IReadOnlyList ffmpegArgumentList, CancellationToken ct) { var exe = ResolveFfmpegPath(); if (!File.Exists(exe)) { return (false, $"ffmpeg не найден: {exe}"); } var start = new ProcessStartInfo { FileName = exe, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8, }; foreach (var a in ffmpegArgumentList) { start.ArgumentList.Add(a); } using var p = new Process { StartInfo = start }; p.Start(); using var reg = ct.Register(static state => { try { if (state is not Process proc || proc.HasExited) { return; } proc.Kill(entireProcessTree: true); } catch { // ignore } }, p, useSynchronizationContext: false); var stderrTask = p.StandardError.ReadToEndAsync(ct); var stdoutTask = p.StandardOutput.ReadToEndAsync(ct); try { await p.WaitForExitAsync(ct).ConfigureAwait(false); } catch (OperationCanceledException) when (ct.IsCancellationRequested) { try { await Task.WhenAll(stderrTask, stdoutTask).ConfigureAwait(false); } catch { // ignore } throw; } await Task.WhenAll(stderrTask, stdoutTask).ConfigureAwait(false); var err = stderrTask.Result; var ok = p.ExitCode == 0 && !ct.IsCancellationRequested; return (ok, err.Trim()); } private static string ResolveFfmpegPath() => Path.Combine(AppContext.BaseDirectory, "Tools", "ffmpeg.exe"); }