Hello everyone,
I’m new to Zig (only been learning for about a week) and have very little experience with systems programming or low-level programming. I have some basic programming knowledge but my understanding of IO is quite poor, so I might have fundamental misunderstandings about how things work here.
I’m trying to run an FFmpeg command from Zig and read its progress output in real-time. FFmpeg writes progress information to stdout when using the -progress pipe:1 flag, and errors to stderr. I found std.Io.File.MultiReader in the standard library which seemed perfect for reading both streams at the same time.
Here’s my code:
const std = @import("std");
const Io = std.Io;
pub fn main(init: std.process.Init) !void {
const io = init.io;
const allocator = init.arena.allocator();
// ffmpeg -v error -skip_frame nokey -progress pipe:1 -i input.mp4 -vf "select='gt(scene,0.1)'" -vsync vfr -q:v 2 output-%04d.jpg
const argv = &[_][]const u8{
"ffmpeg",
"-v",
"error",
"-skip_frame",
"nokey",
"-progress",
"pipe:1",
"-i",
"input.mp4",
"-vf",
"\"select='gt(scene,0.1)'\"",
"-fps_mode",
"vfr",
"-q:v",
"2",
"-y",
"output-%04d.jpg",
};
var child = try std.process.spawn(io, .{ .argv = argv, .stdout = .pipe, .stderr = .pipe, .create_no_window = true });
errdefer child.kill(io);
var multi_reader_buffer: std.Io.File.MultiReader.Buffer(2) = undefined;
var multi_reader: std.Io.File.MultiReader = undefined;
multi_reader.init(allocator, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
defer multi_reader.deinit();
const stdout_reader = multi_reader.reader(0);
const stderr_reader = multi_reader.reader(1);
while (true) {
multi_reader.checkAnyError() catch |err| {
std.debug.print("Error: {}", .{err});
while (try stderr_reader.takeDelimiter('\n')) |err_line| {
if (err_line.len > 0) {
std.debug.print("{s}\n", .{err_line});
}
}
return err;
};
const maybe_line = try stdout_reader.takeDelimiter('\n');
if (maybe_line) |line| {
if (line.len != 0) {
std.debug.print("{s}", .{line});
}
} else {
std.debug.print("Print out all progress info", .{});
break;
}
}
_ = try child.wait(io);
}
My questions:
-
Am I using MultiReader correctly? I’m not sure if I initialized it properly or if I’m using the readers the right way.
-
Why does
zig build runonly output “Print out all progress info”? I never see any of the FFmpeg progress lines that should be coming from stdout. The FFmpeg command works fine when I run it directly in the terminal and produces the expected output files. -
Should I be using multiple threads for reading stdout and stderr simultaneously? I thought MultiReader would handle this for me, but maybe I’m misunderstanding its purpose.
-
Is MultiReader the right tool for this job? If not, what’s the recommended way to read both stdout and stderr from a child process in Zig?
-
How can I read all content from stderr at once? I want to be able to print all error messages if something goes wrong, even if they weren’t line-buffered properly.
I would really appreciate any help or guidance. Since I’m new to Zig and systems programming, simple explanations would be especially helpful. Thank you!

