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

Build ffmpeg filtergraphs and commands from Elixir.

The usual workflow is:

  * pass input sources to `command/3`
  * build streams with generated `FFix.Filter` helpers
  * map streams to outputs with `output/2`
  * serialize with `to_argv/1` or execute with `run/2`

`use FFix` imports the top-level helpers plus generated filter functions.
Callbacks receive and return ordinary Elixir values. Examples in this module
assume `use FFix`; without it, call `FFix.command/3`, `FFix.output/2`, and
`FFix.Filter` functions directly.

## Quick Start

`command/3` is the usual entry point. Pass an input source, build filtered
streams in the first callback, and map streams to an output target in the
second callback:

    command(
      "input.mp4",
      fn src ->
        src[:video] |> crop(w: 720, h: 720)
      end,
      fn cropped, src ->
        output("square.mp4", video: cropped, audio: src[:audio])
      end
    )

This reads `input.mp4`, crops the video stream, maps the original audio
stream, and writes `square.mp4`.

## Choosing an API Layer

  * `FFix.command/3` is the main API for building ffmpeg commands.
  * `FFix.Command` is for constructing or transforming command structs.
  * `FFix.Graph` is for parsing, serializing, or reusing filtergraphs.
  * `FFix.Runner` is the execution boundary for running commands and
    streaming events.

## Command Model

A command has three parts:

  * **input** - where media comes from, such as `"input.mp4"` or `:stdin`
  * **graph** - stream selection and filters; filters become `-filter_complex`
  * **output** - stream mappings, output options, and the output target

In `FFix`, a file path is not itself a stream. The graph callback receives an
input value, and you select streams from it:

    src[:video]
    src[:audio]
    src[audio: 1]

Filters consume streams and produce new streams:

    cropped = src[:video] |> crop(w: 720, h: 720)

`output/2` declares an ffmpeg output. The first argument is the output target
or file, and `video:`, `audio:`, or `sources:` decide which streams are mapped
into it.

    output("square.mp4", video: cropped, audio: src[:audio])

The flow is:

    input source        graph callback                output callback
    ------------        --------------                ---------------
    "input.mp4"  --->   src[:video] |> crop(...) ---> video: cropped
                        src[:audio] ---------------> audio: src[:audio]

    output target: "square.mp4"

`FFix` builds an Elixir command model and translates it to ffmpeg argv. ffmpeg
still does the demuxing, decoding, filtering, encoding, and muxing.

The quick-start example is shaped like this ffmpeg command:

    ffmpeg -i input.mp4 \
      -filter_complex '[0:v]crop=w=720:h=720[out0]' \
      -map '[out0]' \
      -map 0:a \
      square.mp4

Labels such as `[out0]` are generated by `FFix`; they are implementation
details, not names you normally need to manage.

When no filtering is needed, return input streams directly:

    command(
      "input.mp4",
      fn src ->
        [src[:video], src[:audio]]
      end,
      fn [video, audio] ->
        output("copy.mp4", video: video, audio: audio, c: :copy)
      end
    )

## Command Callbacks

The graph callback receives inputs in the shape you passed, with sources
normalized to `%FFix.Command.Input{}` values. The output callback receives graph
exports in the same shape returned by the graph callback.

For multiple graph results, return a list. The output callback can return one
output or a list of outputs, so one ffmpeg command can write multiple targets:

    command(
      "input.mp4",
      fn src ->
        [
          src[:video] |> scale(w: 1280, h: -1),
          src[:video] |> fps(fps: 1)
        ]
      end,
      fn [main, preview], src ->
        [
          output("main.mp4", video: main, audio: src[:audio]),
          output("thumb-%03d.jpg", video: preview, f: :image2)
        ]
      end
    )

Use keyword lists or maps when names make a larger graph easier to read:

    command(
      [src: input("input.mp4")],
      fn inputs ->
        [
          main: inputs[:src][:video] |> scale(w: 1280, h: -1)
        ]
      end,
      fn [main: main], inputs ->
        output("out.mp4",
          video: main,
          audio: inputs[:src][:audio],
          "c:v": :libx264,
          "c:a": :aac
        )
      end
    )

Input and graph shapes are preserved at the callback boundary:

  * `term -> term`
  * `[term] -> [term]`
  * `keyword -> keyword`
  * `map -> map`

A graph callback may also return a `%FFix.Graph{}` when you need graph settings
or terminals. An outputs callback may accept one argument for graph exports or
two arguments for graph exports and inputs.

## Options

Put each kind of option where ffmpeg expects it:

    command(
      input("input.mp4", ss: "00:00:05"),
      fn src ->
        src[:video] |> scale(w: 1280, h: -1)
      end,
      fn video, src ->
        output("out.mp4",
          video: video,
          audio: src[:audio],
          "c:v": :libx264,
          "c:a": :aac,
          movflags: [:faststart]
        )
      end,
      global: [y: true, loglevel: :error]
    )

This maps to the usual ffmpeg placement:

    global options -> before inputs
    input options  -> before -i
    filter options -> inside -filter_complex
    output options -> before the output target

Output options are intentionally ffmpeg-shaped and flat for now. Encoding and
muxer options such as `"c:v"`, `"c:a"`, `f:`, and `movflags:` belong in
`output/2`.

Copying is still ffmpeg's rule: direct input streams can usually be copied,
but filtered streams must be encoded again. When mixing them, set codecs per
stream:

    output("out.mp4",
      video: scaled,
      audio: src[:audio],
      "c:v": :libx264,
      "c:a": :copy
    )

## Output Mapping

Use `video:` and `audio:` for common mappings:

    output("out.mp4", video: main, audio: src[:audio])

Use `sources:` when `-map` ordering matters:

    output("archive.mkv",
      sources: [main, src[audio: 1], src[audio: 0]]
    )

Output options are intentionally ffmpeg-shaped. Keys are rendered as CLI
option names, so stream-specific options can use quoted atoms or strings such
as `"c:v"` and `"metadata:s:a:0"`.

## Boundaries

`validate!/1` performs structural checks only. It does not probe media files
or ask ffmpeg to validate the command.

`to_argv/1` is the canonical serialization boundary. `to_shell_string/1` is
useful for logs, but argv should be preferred when executing.

# `__using__`
*macro* 

Imports the high-level `FFix` helpers and generated filter functions.

This is useful for concise pipeline modules. If you prefer explicit names,
skip `use FFix` and call `FFix.command/3`, `FFix.output/2`, and
`FFix.Filter.scale/2` directly.

    defmodule Pipeline do
      use FFix

      def resize(video) do
        video |> scale(w: 1280, h: -1)
      end
    end

# `command`

```elixir
@spec command() :: FFix.Command.t()
```

Returns an empty low-level `%FFix.Command{}`.

Prefer `command/3` for the callback API.

# `command`

```elixir
@spec command(
  FFix.Command.Input.source()
  | FFix.Command.Input.t()
  | [FFix.Command.Input.source() | FFix.Command.Input.t()]
  | keyword()
  | map(),
  function(),
  function()
) :: FFix.Command.t()
```

Builds a command from inputs, a graph callback, and an output callback.

Simple commands usually pass one input and return one filtered stream:

    command(
      "input.mp4",
      fn src ->
        src[:video] |> crop(w: 720, h: 720)
      end,
      fn cropped, src ->
        output("square.mp4", video: cropped, audio: src[:audio])
      end
    )

The first argument can be a source string, `input(...)`, a list of sources or
inputs, a keyword list, or a map. Sources are normalized to
`%FFix.Command.Input{}` values, while the outer shape is preserved.

The graph callback returns streams in the shape you want the output callback
to receive.

Shapes are preserved:

  * `stream -> export`
  * `[stream] -> [export]`
  * `[name: stream] -> [name: export]`
  * `%{name: stream} -> %{name: export}`

Return `%FFix.Graph{}` when you need graph settings or terminals.

    command(
      "input.mp4",
      fn src ->
        [
          src[:video] |> scale(w: 1280, h: -1),
          src[:video] |> fps(fps: 1)
        ]
      end,
      fn [main, preview], src ->
        [
          output("out.mp4", video: main, audio: src[:audio]),
          output("thumb-%03d.jpg", video: preview, f: :image2)
        ]
      end
    )

For named inputs, pass a keyword list or map. The output callback receives the
same input shape:

    command(
      [src: "input.mp4", logo: input("logo.png", loop: 1)],
      fn inputs ->
        inputs[:src][:video]
        |> overlay(inputs[:logo][:video], x: 20, y: 20)
      end,
      fn watermarked, inputs ->
        output("out.mp4", video: watermarked, audio: inputs[:src][:audio])
      end
    )

# `command`

```elixir
@spec command(
  FFix.Command.Input.source()
  | FFix.Command.Input.t()
  | [FFix.Command.Input.source() | FFix.Command.Input.t()]
  | keyword()
  | map(),
  function(),
  function(),
  keyword()
) :: FFix.Command.t()
```

Builds a command with command-level options.

Options are passed as the final argument. Supported options:

  * `:global` - global ffmpeg options rendered before inputs

Do not pass `:inputs`, `:graph`, or `:outputs` here; those are the positional
arguments of `command/4`.

    command(
      "input.mp4",
      fn src ->
        src[:video] |> crop(w: 720, h: 720)
      end,
      fn cropped, src ->
        output("square.mp4", video: cropped, audio: src[:audio])
      end,
      global: [y: true, loglevel: :error]
    )

# `input`

```elixir
@spec input(FFix.Command.Input.source()) :: FFix.Command.Input.t()
```

Declares an ffmpeg input without input options.

This returns an `%FFix.Command.Input{}`. In `command/3`, pass it wherever you
need input options:

    command(
      input("input.mp4", ss: "00:00:05"),
      fn src -> src[:video] end,
      fn video, src ->
        output("copy.mp4", video: video, audio: src[:audio], c: :copy)
      end
    )

For optionless file inputs, pass the source string directly.

# `input`

```elixir
@spec input(
  FFix.Command.Input.source(),
  keyword()
) :: FFix.Command.Input.t()
```

Declares an ffmpeg input with input options.

Options are rendered before `-i`.

    input("clip.mp4", ss: "00:00:05", t: "00:00:10")
    input(:stdin, f: :wav)

Option keys are ffmpeg CLI option names without the leading dash.

# `output`

```elixir
@spec output(
  FFix.Command.Output.target(),
  keyword()
) :: FFix.Command.Output.t()
```

Declares an output target and stream mappings.

Use `video:` and `audio:` for common cases:

    output("out.mp4",
      video: main,
      audio: src[:audio],
      "c:v": :libx264,
      "c:a": :aac
    )

Use `sources:` when exact `-map` order matters:

    output("archive.mkv",
      sources: [main, src[audio: 1], src[audio: 0]],
      c: :copy
    )

Remaining options are rendered as ffmpeg output options after all `-map`
entries and before the output target.

# `expr`

```elixir
@spec expr(String.t()) :: FFix.Expr.t()
```

Wraps a raw ffmpeg expression so it is serialized as an expression value.

    drawtext(video, text: "Hello", x: expr("w-tw-20"), y: 20)

# `filter`

```elixir
@spec filter(atom() | String.t(), [FFix.Stream.t()], keyword()) ::
  FFix.Stream.t() | FFix.Terminal.t() | [FFix.Stream.t()] | tuple()
```

Applies a filter by name.

Most code should call generated helpers from `FFix.Filter`, such as
`scale/2`, `overlay/3`, or `fps/2`. Use `filter/3` when the filter name is
dynamic.

    FFix.filter(:scale, [video], w: 1280, h: -1)

# `graph`

```elixir
@spec graph(keyword()) :: FFix.Graph.t()
```

Builds a `%FFix.Graph{}` from exported streams and terminal sinks.

Common options:

  * `:output` - a single unnamed exported stream
  * `:outputs` - a list of named or unnamed exported streams
  * `:terminals` - sink-ending filter results, such as `nullsink`
  * `:settings` - graph-level settings such as `sws_flags`

In `command/3`, graph callbacks can return streams, lists, keyword lists, or
maps directly. Use `graph/1` when you need terminals or graph settings.

    FFix.graph(
      outputs: [main: video |> scale(w: 1280, h: -1)],
      settings: [sws_flags: [:accurate_rnd]]
    )

# `shape`

```elixir
@spec shape(FFix.Stream.t() | [FFix.Stream.t()] | tuple(), [output_media()]) ::
  FFix.Stream.t() | [FFix.Stream.t()]
```

Assigns a concrete output shape to a dynamic or ambiguous filter result.

Some ffmpeg filters have output counts or media types that depend on options.
`shape/2` lets you state the result explicitly before exporting or mapping it.

    [audio, video] =
      input_audio
      |> ebur128(video: true)
      |> FFix.shape([:audio, :video])

# `to_argv`

```elixir
@spec to_argv(FFix.Command.t()) :: [String.t()]
```

Serializes a command to the argv list that should be passed to an OS process.

    FFix.to_argv(command)
    #=> ["ffmpeg", "-i", "input.mp4", "-map", "0:v", "out.mp4"]

# `to_filtergraph`

```elixir
@spec to_filtergraph(FFix.Graph.t()) :: String.t()
```

Validates and serializes a graph to ffmpeg filtergraph syntax.

# `to_shell_string`

```elixir
@spec to_shell_string(FFix.Command.t()) :: String.t()
```

Serializes a command as a shell-escaped string for logs and debugging.

Prefer `to_argv/1` for execution.

# `run`

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

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

See `FFix.Runner` for stdout/stderr capture, progress events, and streaming
execution options.

# `run!`

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

Runs a command and returns the result, raising `FFix.Runner.Error` on failure.

# `stream`

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

Runs a command as a lazy stream of execution events.

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

# `stream!`

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

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

# `validate!`

```elixir
@spec validate!(FFix.Graph.t() | FFix.Command.t()) ::
  FFix.Graph.t() | FFix.Command.t()
```

Performs structural validation on a graph or command.

Validation checks the model that `FFix` controls: declared inputs, graph refs,
output sources, and filtered graph export mappings. It does not probe media
files or execute ffmpeg.

# `output_media`

```elixir
@type output_media() :: :audio | :video | :unknown
```

Media type for a shaped filter output.

---

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