# `FFix.Runner`
[🔗](https://github.com/akash-akya/ffix/blob/v0.2.0/lib/ffix/runner.ex#L1)

Execute commands, collect results, and observe progress.

The top-level `FFix.run/2`, `FFix.run!/2`, and `FFix.stream/2` functions use
this runner. Pass a command built with `FFix.command/2` and choose how to
handle its output.

## Run and handle errors

    case FFix.run(command) do
      {:ok, result} -> IO.puts("Finished in #{result.duration_ms} ms")
      {:error, error} -> IO.puts(:stderr, error.message)
    end

Use `run!/2` in scripts when a failed command should raise. For diagnostics,
`FFix.Runner.Error` carries the execution result and captured stderr.
`FFix.Runner.Result` describes the available timings, progress, and output fields.

## Follow progress

    FFix.stream(command, progress: true)
    |> Enum.each(fn
      {:progress, progress} -> IO.inspect({progress.out_time, progress.speed})
      {:stderr, chunk} -> IO.binwrite(:stderr, chunk)
      {:exit, result} -> IO.inspect(result.exit_status)
      _event -> :ok
    end)

Execution starts when the stream is enumerated. Use `stream!/2` to raise on a
failed process exit; plain `stream/2` reports the exit in its result event.
Halting enumeration cancels the process. Enumerating again starts a new run.

## Pipe an image

Supply bytes with `stdin:` and collect an output written to `:stdout`:

    alias FFix.{Encoder, Filter, Muxer}

    command =
      FFix.input(:stdin, f: "image2pipe")
      |> FFix.video(0)
      |> Filter.scale(w: 640, h: -2)
      |> Encoder.png()
      |> Muxer.mux("image2pipe", :stdout)
      |> FFix.command()

    result = FFix.run!(command, stdin: File.stream!("photo.png", 65_536), stdout: :collect)
    File.write!("small.png", result.stdout)

> #### Collect only what you need {: .warning}
> `stdout: :collect` and `stderr: :collect` retain their full output in memory.
> For large media, consume stdout chunks with `stream/2` and keep stdout's
> default `:discard` capture policy. Live chunks are still emitted.
> Progress buffers are unbounded, even with limited stderr capture.

## Choose FFmpeg

Executable selection follows this order: the `ffmpeg:` runner option,
`FFMPEG_BIN`, then `ffmpeg` on `PATH`.

    FFix.run(command, ffmpeg: "/usr/local/bin/ffmpeg")

For an existing argument list, `run/2` also accepts
`["ffmpeg", "-version"]`. Raw argv uses its own executable and arguments
unchanged. For setup, see [FFmpeg downloads](https://ffmpeg.org/download.html)
or the development download task `Mix.Tasks.Ffix.Ffmpeg.Fetch`.

# `event`

```elixir
@type event() ::
  {:stdout, binary()}
  | {:stderr, binary()}
  | {:progress, FFix.Runner.Progress.t()}
  | {:exit, FFix.Runner.Result.t()}
  | {:error, FFix.Runner.Error.t()}
```

# `option`

```elixir
@type option() ::
  {:ffmpeg, String.t()}
  | {:stdin, stdin_source()}
  | {:stdout, stdout_mode()}
  | {:stderr, stderr_mode()}
  | {:progress, boolean()}
  | {:on_event, (event() -&gt; any())}
```

# `stderr_mode`

```elixir
@type stderr_mode() :: :discard | :collect | {:tail, pos_integer()}
```

# `stdin_source`

```elixir
@type stdin_source() :: Enumerable.t() | (Collectable.t() -&gt; any())
```

# `stdout_mode`

```elixir
@type stdout_mode() :: :discard | :collect
```

# `run`

```elixir
@spec run(FFix.Command.t() | [String.t(), ...], [option()]) ::
  {:ok, FFix.Runner.Result.t()} | {:error, FFix.Runner.Error.t()}
```

Runs a command and returns `{:ok, result}` or `{:error, error}`.

A zero exit status succeeds. Missing executables and non-zero exits return
`FFix.Runner.Error`. Process I/O follows `Exile.stream/2` semantics, without
translation to runner errors. Invalid configuration and exceptions in your
option callbacks, event handler, or stdin producer propagate to the caller.

## Options

- `:stdin` — an enumerable of bytes, or a function receiving a writable
  `Collectable` sink. For example, `fn sink -> Enum.into(chunks, sink) end`.
- `:stdout` — `:discard` (default) or `:collect`.
- `:stderr` — `:discard`, `:collect`, or `{:tail, bytes}`. The default keeps
  the last 65,536 bytes.
- `:ffmpeg` — executable path/name for command values; see the module guide.
- `:progress` — parse FFmpeg progress records; defaults to `false`. For command
  values, also adds `-progress pipe:2`. Raw argv must request progress itself.
- `:on_event` — a function called with every event described in `stream/2`.

Command execution adds `-hide_banner`, `-nostats`, and `-loglevel level+warning`.
Override these through command `global:` options. There is no execution deadline;
use a supervised task when your application needs one.

Capture policies control retained stdout and stderr, independently of live
events. Progress fields accumulate until `progress=continue` or `progress=end`,
independently of stderr capture. Incomplete updates are not emitted.

# `run!`

```elixir
@spec run!(FFix.Command.t() | [String.t(), ...], [option()]) :: FFix.Runner.Result.t()
```

Like `run/2`, returning the result directly and raising `FFix.Runner.Error` on execution failure.

# `stream`

```elixir
@spec stream(FFix.Command.t() | [String.t(), ...], [option()]) :: term()
```

Returns a lazy stream of process output, progress, and lifecycle events.

Accepts the same options as `run/2`. Events are:

- `{:stdout, chunk}` and `{:stderr, chunk}` — raw bytes.
- `{:progress, progress}` — a `FFix.Runner.Progress`, when enabled.
- `{:exit, result}` — process finished; inspect its `exit_status`.
- `{:error, error}` — the executable could not be found.

Quiet commands may emit no events until they exit.
A non-zero exit is still an `:exit` event; use `stream!/2` to raise on failure.
Early halt cancels and cleans up the child, without emitting an exit event for
that cancellation. Exile handles process and stdin cleanup, with a 1,000 ms
timeout per cleanup step. Exceptions from stream consumers propagate normally.

Consume large streams incrementally. Converting all events to a list retains
their payloads even when the runner's capture policy is `:discard`.

# `stream!`

```elixir
@spec stream!(FFix.Command.t() | [String.t(), ...], [option()]) :: term()
```

Like `stream/2`, raising `FFix.Runner.Error` for missing executables or non-zero exits.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
