Commit 5c7aa0cb authored by shrabbit's avatar shrabbit
Browse files

修复执行问题。

parent 32ff5176
Loading
Loading
Loading
Loading
+95 −67
Original line number Diff line number Diff line
@@ -107,10 +107,10 @@ public class MainCommand : AsyncCommand<MainCommand.Settings>
            args.Add("-ss");
            args.Add((i * interval.TotalSeconds).ToString("0.###", CultureInfo.InvariantCulture));
            args.Add("-i");
            args.Add($"\"{file}\"");
            args.Add(file);
            args.Add("-frames:v");
            args.Add("1");
            args.Add($"\"{Path.Join(outputDir, $"{baseName}_{i.ToString($"D{width}")}.{settings.Format}")}\"");
            args.Add(Path.Join(outputDir, $"{baseName}_{i.ToString($"D{width}")}.{settings.Format}"));

            yield return args.ToArray();
        }
@@ -143,7 +143,6 @@ public class MainCommand : AsyncCommand<MainCommand.Settings>
        process.StartInfo.ArgumentList.Add("-i");
        process.StartInfo.ArgumentList.Add(file);

        if (verbose)
        ShowStartInfo(si, "启动探测:");

        if (!process.Start())
@@ -166,7 +165,7 @@ public class MainCommand : AsyncCommand<MainCommand.Settings>
    /// <param name="output">ffmpeg -i 的标准错误输出。</param>
    /// <param name="verbose"></param>
    /// <returns>时长与编码器名称。</returns>
    public static (TimeSpan Duration, string Codec) ParseProbeOutput(string output, bool verbose)
    public static (TimeSpan Duration, string Codec) ParseProbeOutput(string output, bool verbose = false)
    {
        if (verbose)
            AnsiConsole.MarkupLineInterpolated($"[grey]{output}[/]");
@@ -208,6 +207,27 @@ public class MainCommand : AsyncCommand<MainCommand.Settings>
        return (duration, codec);
    }

    public static void ShowStartInfo(ProcessStartInfo si, string header)
    {
        var tree = new Tree(header);
        tree.AddNode($"启动程序:[cyan]{Markup.Escape(si.FileName)}[/]");
        tree.AddNode($"参数:[green]{Markup.Escape(ShowArray(si.ArgumentList))}[/]");
        tree.AddNode($"工作路径:[gray]{Markup.Escape(si.WorkingDirectory)}[/]");

        AnsiConsole.Write(tree);
        AnsiConsole.WriteLine();
    }

    public static string ShowArray<T>(IEnumerable<T> array)
    {
        var sb = new StringBuilder();
        sb.Append("[ ");
        sb.Append(string.Join(", ", array));
        sb.Append(" ]");

        return sb.ToString();
    }

    public static IEnumerable<string> GetFiles(Settings settings, IDirectory dir)
    {
        if (settings.File is { } s) yield break;
@@ -228,88 +248,96 @@ public class MainCommand : AsyncCommand<MainCommand.Settings>

    protected override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
    {
        if (settings.NoExecute)
        {
            DirectoryBridge.Default.CreateDirectory(Path.Combine(settings.Directory, settings.Disc));
        var dirBridge = DirectoryBridge.Default;
        dirBridge.CreateDirectory(Path.Combine(settings.Directory, settings.Disc));

            foreach (var f in GetFiles(settings, DirectoryBridge.Default))
        var failed = 0;

        foreach (var f in GetFiles(settings, dirBridge))
        {
            var (duration, codec) = ProbeMedia(settings.FfMpegPath, f, !settings.NoExecute);
            var commands = GenerateFfMpegCommand(settings, f, duration, codec).ToArray();

                foreach (var arr in GenerateFfMpegCommand(settings, f, duration, codec))
            if (settings.NoExecute)
            {
                    AnsiConsole.WriteLine($"{settings.FfMpegPath} {string.Join('\u0020', arr)}");
                }
            }
            return 0;
                foreach (var arr in commands)
                    AnsiConsole.WriteLine(FormatCommand(settings.FfMpegPath, arr));
                continue;
            }

        var tasks = new List<Task>();
        var semaphore = new SemaphoreSlim((int)settings.ParallelNumber);
        foreach (var f in GetFiles(settings, DirectoryBridge.Default))
        {
            AnsiConsole.WriteLine($"找到文件:{f}");
            var (duration, codec) = ProbeMedia(settings.FfMpegPath, f, !settings.NoExecute);
            var degree = settings.ParallelNumber == 0 ? 1 : (int)settings.ParallelNumber;

            var count = 0;
            foreach (var arr in GenerateFfMpegCommand(settings, f, duration, codec))
            await Parallel.ForEachAsync(commands, new ParallelOptions
            {
                var si = new ProcessStartInfo(settings.FfMpegPath, arr);
                ShowStartInfo(si, $"创建的启动 [[{count}]]:");
                tasks.Add(ExecuteSemaphore(semaphore, si, cancellationToken));
                count++;
            }
                MaxDegreeOfParallelism = degree,
                CancellationToken = cancellationToken,
            }, async (arr, ct) =>
            {
                if (await RunFfMpegAsync(settings.FfMpegPath, arr, ct) != 0)
                    Interlocked.Increment(ref failed);
            });
        }

        await Task.WhenAll(tasks);

        return 0;
        return failed > 0 ? 1 : 0;
    }

    public static void ShowStartInfo(ProcessStartInfo si, string header)
    /// <summary>
    /// 将命令参数格式化为可复制到终端的命令行文本,含空格的参数自动加引号。
    /// </summary>
    private static string FormatCommand(string ffmpegPath, IEnumerable<string> args)
    {
        var tree = new Tree(header);
        tree.AddNode($"启动程序:[cyan]{Markup.Escape(si.FileName)}[/]");
        tree.AddNode($"参数:[green]{Markup.Escape(ShowArray(si.ArgumentList))}[/]");
        tree.AddNode($"工作路径:[gray]{Markup.Escape(si.WorkingDirectory)}[/]");

        AnsiConsole.Write(tree);
        AnsiConsole.WriteLine();
        var parts = args.Select(a => a.Contains(' ') || a.Contains('"') ? $"\"{a}\"" : a);
        return $"{ffmpegPath} {string.Join('\u0020', parts)}";
    }

    public static string ShowArray<T>(IEnumerable<T> array)
    /// <summary>
    /// 执行一条 ffmpeg 命令并返回退出码。
    /// </summary>
    private static async Task<int> RunFfMpegAsync(string ffmpegPath, string[] args, CancellationToken ct)
    {
        var sb = new StringBuilder();
        sb.Append("[ ");
        sb.Append(string.Join(", ", array));
        sb.Append(" ]");
        var psi = new ProcessStartInfo
        {
            FileName = ffmpegPath,
            UseShellExecute = false,
            CreateNoWindow = true,
            RedirectStandardError = true,
            RedirectStandardOutput = true,
        };

        return sb.ToString();
    }
        psi.ArgumentList.Add("-hide_banner");
        foreach (var a in args)
            psi.ArgumentList.Add(a);

    public async Task ExecuteSemaphore(SemaphoreSlim semaphore, ProcessStartInfo startInfo, CancellationToken cancellationToken)
    {
        await semaphore.WaitAsync(cancellationToken);
        try
        {
            var process = Process.Start(startInfo)!;
            await process.WaitForExitAsync(cancellationToken);
        using var process = new Process { StartInfo = psi };

            if (process.ExitCode != 0)
        if (!process.Start())
        {
                var tree = new Tree($"[red]出现错误 ({startInfo.FileName} {startInfo.Arguments})[/]");
                tree.AddNode($"返回值:[red]{process.ExitCode}[/]");
                tree.AddNode("错误流")
                    .AddNode(await process.StandardError.ReadToEndAsync(cancellationToken));
                tree.AddNode("输出流")
                    .AddNode(await process.StandardOutput.ReadToEndAsync(cancellationToken));
                
                AnsiConsole.Write(tree);
            AnsiConsole.MarkupLineInterpolated($"[red]无法启动:[/] {FormatCommand(ffmpegPath, args)}");
            return -1;
        }

        var stdoutTask = process.StandardOutput.ReadToEndAsync(ct);
        var stderrTask = process.StandardError.ReadToEndAsync(ct);

        await process.WaitForExitAsync(ct);

        var stderr = await stderrTask;
        await stdoutTask;

        var output = args[^1];

        if (process.ExitCode == 0)
        {
            AnsiConsole.MarkupLineInterpolated($"[green]已导出:[/] {output}");
        }
        finally
        else
        {
            semaphore.Release();
            AnsiConsole.MarkupLineInterpolated($"[red]失败 ({process.ExitCode}):[/] {output}");
            if (!string.IsNullOrWhiteSpace(stderr))
                AnsiConsole.WriteLine(stderr.Trim());
        }

        return process.ExitCode;
    }
}
+18 −18
Original line number Diff line number Diff line
@@ -79,23 +79,23 @@ public class MainCommandTest
        Assert.Collection(commands,
            c => Assert.Equal(
            [
                "-ss", "0", "-i", $"\"{file}\"", "-frames:v", "1",
                $"\"{Path.Join(Path.Combine("videos", "output"), "a.mp4_000.png")}\""
                "-ss", "0", "-i", Path.Combine("videos", "a.mp4"), "-frames:v", "1",
                Path.Join(Path.Combine("videos", "output"), "a.mp4_000.png")
            ], c),
            c => Assert.Equal(
            [
                "-ss", "1800", "-i", $"\"{file}\"", "-frames:v", "1",
                $"\"{Path.Join(Path.Combine("videos", "output"), "a.mp4_001.png")}\""
                "-ss", "1800", "-i", Path.Combine("videos", "a.mp4"), "-frames:v", "1",
                Path.Join(Path.Combine("videos", "output"), "a.mp4_001.png")
            ], c),
            c => Assert.Equal(
            [
                "-ss", "3600", "-i", $"\"{file}\"", "-frames:v", "1",
                $"\"{Path.Join(Path.Combine("videos", "output"), "a.mp4_002.png")}\""
                "-ss", "3600", "-i", Path.Combine("videos", "a.mp4"), "-frames:v", "1",
                Path.Join(Path.Combine("videos", "output"), "a.mp4_002.png")
            ], c),
            c => Assert.Equal(
            [
                "-ss", "5400", "-i", $"\"{file}\"", "-frames:v", "1",
                $"\"{Path.Join(Path.Combine("videos", "output"), "a.mp4_003.png")}\""
                "-ss", "5400", "-i", Path.Combine("videos", "a.mp4"), "-frames:v", "1",
                Path.Join(Path.Combine("videos", "output"), "a.mp4_003.png")
            ], c));
    }

@@ -110,8 +110,8 @@ public class MainCommandTest

        Assert.Equal(
        [
            "-ss", "0", "-i", $"\"{file}\"", "-frames:v", "1",
            $"\"{Path.Join(Path.Combine("videos", "output"), "a.mp4_000.png")}\""
            "-ss", "0", "-i", Path.Combine("videos", "a.mp4"), "-frames:v", "1",
            Path.Join(Path.Combine("videos", "output"), "a.mp4_000.png")
        ], command);
    }

@@ -125,8 +125,8 @@ public class MainCommandTest

        Assert.Equal(
        [
            "-c:v", "av1_cuvid", "-ss", "0", "-i", $"\"{file}\"", "-frames:v", "1",
            $"\"{Path.Join(Path.Combine("videos", "output"), "a.mkv_000.png")}\""
            "-c:v", "av1_cuvid", "-ss", "0", "-i", Path.Combine("videos", "a.mkv"), "-frames:v", "1",
            Path.Join(Path.Combine("videos", "output"), "a.mkv_000.png")
        ], command);
    }

@@ -140,8 +140,8 @@ public class MainCommandTest

        Assert.Equal(
        [
            "-c:v", "h264_cuvid", "-ss", "0", "-i", $"\"{file}\"", "-frames:v", "1",
            $"\"{Path.Join(Path.Combine("videos", "output"), "a.mp4_000.png")}\""
            "-c:v", "h264_cuvid", "-ss", "0", "-i", Path.Combine("videos", "a.mp4"), "-frames:v", "1",
            Path.Join(Path.Combine("videos", "output"), "a.mp4_000.png")
        ], command);
    }

@@ -155,8 +155,8 @@ public class MainCommandTest

        Assert.Equal(
        [
            "-hwaccel", "cuda", "-ss", "0", "-i", $"\"{file}\"", "-frames:v", "1",
            $"\"{Path.Join(Path.Combine("videos", "output"), "a.mp4_000.png")}\""
            "-hwaccel", "cuda", "-ss", "0", "-i", Path.Combine("videos", "a.mp4"), "-frames:v", "1",
            Path.Join(Path.Combine("videos", "output"), "a.mp4_000.png")
        ], command);
    }

@@ -170,8 +170,8 @@ public class MainCommandTest

        Assert.Equal(
        [
            "-hwaccel", "dxva2", "-ss", "0", "-i", $"\"{file}\"", "-frames:v", "1",
            $"\"{Path.Join(Path.Combine("videos", "output"), "a.mp4_000.png")}\""
            "-hwaccel", "dxva2", "-ss", "0", "-i", Path.Combine("videos", "a.mp4"), "-frames:v", "1",
            Path.Join(Path.Combine("videos", "output"), "a.mp4_000.png")
        ], command);
    }