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

Thin execution layer for `%FFix.Command{}` values or raw argv lists.

The core library models commands as data. `FFix.Runner` is the boundary that
starts an OS process and turns stdout, stderr, logs, progress, and exit status
into Elixir data.

## Collected Execution

    {:ok, result} =
      FFix.run(command,
        stdout: :discard,
        stderr: :collect
      )

    result.exit_status
    result.stderr

`run/2` returns `{:ok, result}` for exit status `0` and `{:error, error}` for
non-zero exits or spawn failures. `run!/2` returns the result or raises
`FFix.Runner.Error`.

## Streaming Events

    FFix.stream(command, progress: true, stderr: :collect)
    |> Enum.each(fn
      {:log, log} -> IO.puts("[#{log.level}] #{log.message}")
      {:progress, progress} -> IO.inspect(progress.status)
      {:exit, result} -> IO.inspect(result.exit_status)
      _event -> :ok
    end)

`stream/2` is lazy and emits:

  * `{:start, info}`
  * `{:stdout, chunk}`
  * `{:stderr, chunk}`
  * `{:log, %FFix.Runner.Log{}}`
  * `{:progress, %FFix.Runner.Progress{}}`
  * `{:exit, %FFix.Runner.Result{}}`

When running `ffmpeg`, the runner prepends a few quiet-by-default execution flags:

  * `-hide_banner`
  * `-nostats`
  * `-loglevel level+warning`

Later command options still win, so callers can override log level or stats in
the command itself.

# `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 a collected result tuple.

Options:

  * `:stdin` - enumerable input for process stdin
  * `:stdout` - `:discard` or `:collect`
  * `:stderr` - `:discard`, `:collect`, or `{:tail, bytes}`
  * `:progress` - when `true`, adds `-progress pipe:2` for ffmpeg commands
  * `:on_event` - callback invoked with each emitted event

By default stdout is discarded and only the trailing stderr is kept.

    FFix.run(command, stderr: :collect)

# `run!`

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

Runs a command and raises `FFix.Runner.Error` on spawn failure or non-zero exit.

# `stream`

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

Runs a command as a lazy event stream.

This is useful for progress reporting, log streaming, or large stdout streams
that should not be collected into memory.

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

# `stream!`

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

Runs a command as a lazy event stream and raises on non-zero exit.

# `event`

```elixir
@type event() ::
  {:start,
   %{command: FFix.Command.t() | nil, argv: [String.t()], shell: String.t()}}
  | {:stdout, binary()}
  | {:stderr, binary()}
  | {:log, FFix.Runner.Log.t()}
  | {:progress, FFix.Runner.Progress.t()}
  | {:exit, FFix.Runner.Result.t()}
```

# `option`

```elixir
@type option() ::
  {: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
```

---

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