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

Build and run FFmpeg commands in Elixir.

Use FFix to convert media, extract tracks, choose codecs and containers, or
process audio and video with filters. Start with inputs and outputs; add a
filter pipeline when you need to change the picture or sound.

You will need [FFmpeg](https://ffmpeg.org/download.html) on your `PATH` to run
these examples. See `FFix.Runner` to choose another executable.

## Your first command

Extract the first audio track from an interview into a WAV file:

    command =
      FFix.input("interview.mp4")
      |> FFix.audio(0)
      |> FFix.output("interview.wav")
      |> FFix.command()

    FFix.run!(command)

An input file can contain several streams: video, audio tracks, subtitles,
and more. `audio(input, 0)` selects the first audio stream; indexes start at
zero. FFmpeg chooses the WAV format and its default encoder from the filename.

There are two deliberate steps at the end. An **output** describes what to
write and where. A **command** brings the outputs and their inputs together.
Building either is just preparation; `run!/2` starts FFmpeg.

## Choose a codec and a container

An encoder compresses the media. A muxer packages streams into a container,
such as MP4 or Matroska. Here, AAC is the audio codec and MP4 is the container
used for the `.m4a` file:

    alias FFix.{Encoder, Muxer}

    command =
      FFix.input("interview.wav")
      |> FFix.audio(0)
      |> Encoder.aac(b: "128k")
      |> Muxer.mp4("interview.m4a")
      |> FFix.command()

    FFix.run!(command)

`b: "128k"` sets the audio bitrate to 128 kbit/s. An encoder can be used directly
on an input stream: filtering is optional. See `FFix.Encoder` for quality and
bitrate settings, and `FFix.Muxer` for format-specific options.

## Copy streams without re-encoding

When the existing codecs suit the destination, stream copy is faster and
preserves the encoded media. This example puts an MP4's first video track
and all its audio tracks into a Matroska file:

    source = FFix.input("interview.mp4")

    output =
      FFix.output(
        [
          FFix.stream_copy(FFix.video(source, 0)),
          FFix.stream_copy(FFix.audio(source, :all))
        ],
        "interview.mkv"
      )

    FFix.command(output) |> FFix.run!()

`:all` selects every stream of that media type. See `stream_copy/1` for format
compatibility and `FFix.Command.Input` for selecting optional tracks.

## Add a filter

Filters work on decoded pictures or sound. Apply them before choosing the
output encoder. Here we resize a video while copying its audio:

    alias FFix.{Encoder, Filter, Muxer}
    source = FFix.input("interview.mp4")

    picture =
      source
      |> FFix.video(0)
      |> Filter.scale(w: 1280, h: -2)
      |> Encoder.libx264(crf: 23, preset: "medium")

    output =
      Muxer.mp4(
        [picture, FFix.stream_copy(FFix.audio(source, 0))],
        "interview-small.mp4",
        movflags: [:faststart]
      )

    FFix.command(output) |> FFix.run!()

`h: -2` keeps the aspect ratio and gives the height an even number of pixels.
`crf` controls video quality; lower values mean higher quality and usually
larger files. `faststart` helps an MP4 begin playing before its download finishes.
This example assumes an audio track compatible with MP4; use `FFix.Encoder.aac/2`
instead of stream copy when you need AAC audio.

Explore `FFix.Filter` for cropping, overlays, audio processing, and combining streams.

## Write several outputs

Pass an ordered list of outputs to `command/2`. Reuse an input declaration to
read it once, even when outputs use different encoders or settings.

This video-only example scales once, then splits the filtered pictures into
a full-size rendition and a small preview:

    alias FFix.{Encoder, Filter, Muxer}
    source = FFix.input("interview.mp4")

    [main, preview] =
      source
      |> FFix.video(0)
      |> Filter.scale(w: 1280, h: -2)
      |> Filter.split(outputs: 2)

    main_output = Muxer.mp4(Encoder.libx264(main, crf: 20), "main.mp4")

    preview_output =
      preview
      |> Filter.scale(w: 320, h: -2)
      |> Encoder.libx264(crf: 28)
      |> Muxer.mp4("preview.mp4")

    FFix.command([main_output, preview_output]) |> FFix.run!()

A filtered stream needs a split to feed two branches. Direct input streams
can be reused as they are. Outputs can also use entirely different inputs.
They still run in one FFmpeg process; use separate commands when jobs need
independent cancellation or retries.

## Inspect, run, and learn more

Inspect a command before executing it:

    FFix.to_shell_string(command)
    FFix.to_argv(command)

`run/2` returns `{:ok, result}` or `{:error, error}`. `run!/2` returns the result
or raises on execution failure. Both accept runner options such as
`ffmpeg: "/usr/local/bin/ffmpeg"` and `stderr: :collect`.

- `FFix.Command.Input`: stream selection and input options.
- `FFix.Command.Output`: mapping order and named output-option callbacks.
- `FFix.Demuxer` and `FFix.Decoder`: explicit input formats and decoding settings.
- `FFix.Graph`: parse and reuse filtergraphs.
- `FFix.Runner`: progress events, piping media, and handling failures.
- `FFix.Discovery`: find the codecs, formats, and filters in your FFmpeg build.

The [FFmpeg command guide](https://ffmpeg.org/ffmpeg.html) is a useful companion
when you need the meaning of a particular FFmpeg option.

# `audio`

```elixir
@spec audio(FFix.Command.Input.t(), stream_index(), [
  FFix.Command.Input.selection_option()
]) ::
  FFix.Graph.StreamRef.t() | FFix.Selection.t()
```

Selects an audio stream by zero-based audio index, or all audio streams with `:all`.

    FFix.audio(source, 0)
    FFix.audio(source, :all, optional: true)

`optional: true` lets FFmpeg omit missing matches from an output. One audio
stream may contain several channels; stereo is usually one stream.
See `FFix.Command.Input` for selection examples.

# `command`

```elixir
@spec command(FFix.Command.Output.t() | [FFix.Command.Output.t()], keyword()) ::
  FFix.Command.t()
```

Assembles and validates one output or an ordered list of outputs as a command.

    FFix.command(output)
    FFix.command([main_output, preview_output], global: [n: :flag])

The command collects the inputs used by its outputs. Reusing an input
declaration opens it once; separate declarations open separate inputs.

Options:

- `:global` — FFmpeg options for the whole invocation.
- `:inputs` — a complete, ordered input list. Use this for positional graph
  inputs, additional metadata inputs, or a specific input order. Include every
  input used by the outputs, with the same configuration.
- `:terminals` — sink branches to execute alongside the outputs.
- `:settings` — graph settings, currently `sws_flags`.

With explicit `inputs:`, selections follow their input declarations even when
the order changes. Configure sources before selecting streams; see
`FFix.Command.Input` for examples. Output callbacks run during serialization.

> #### Overwriting files {: .warning}
> `global: [y: :flag]` permits replacing existing output files.
> Use `global: [n: :flag]` to refuse overwrites.

For direct construction and manipulation of command data, see `FFix.Command`.

# `filter`

```elixir
@spec filter(
  FFix.Graph.StreamRef.t() | [FFix.Graph.StreamRef.t()],
  String.t() | atom(),
  [output_media()],
  list()
) ::
  FFix.Graph.StreamRef.t()
  | [FFix.Graph.StreamRef.t()]
  | FFix.Graph.Terminal.t()
```

Builds a filter with an explicit name and ordered output media.

Use the named `FFix.Filter` helpers for common filters. See
`FFix.Filter.filter/4` for custom names and output shapes.

# `graph`

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

Groups filter outputs into a reusable graph.

    graph = FFix.graph(outputs: [main: video, sound: audio])
    graph[:main]

Supply `output:` for a single unnamed output or `outputs:` for an ordered list,
optionally named. `terminals:` includes sink branches and `settings:` accepts
`sws_flags`. See `FFix.Graph` for templates, binding, and parsing.

# `input`

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

Declares a media source and its input options.

    source = FFix.input("interview.mp4", ss: 30)

Input options apply before reading this source. Here, `ss` seeks to 30 seconds.
For file, URL, and pipe sources, see `FFix.Command.Input.new/2`.

# `output`

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

Declares an output from one source or an ordered list, a target, and output options.

    FFix.output([video, audio], "interview.mp4", t: 30)

Bare streams use FFmpeg's default encoding. Apply `FFix.Encoder` or
`stream_copy/1` to choose it explicitly. `FFix.Muxer` helpers provide
format-specific options. See `FFix.Command.Output` for named mappings and callbacks.

# `select`

```elixir
@spec select(FFix.Command.Input.t(), FFix.Command.Input.selector(), [
  FFix.Command.Input.selection_option()
]) :: FFix.Graph.StreamRef.t() | FFix.Selection.t()
```

Selects by absolute stream index, `:all`, or an FFmpeg stream specifier.

    FFix.select(source, 3)
    FFix.select(source, :all)
    FFix.select(source, "a:m:language:eng", optional: true)

An absolute index counts every stream in the file. Prefer `video/3`, `audio/3`,
and `subtitle/3` when selecting by media type. Strings and optional selections
are output queries; use an indexed media helper to select a filter input.
See `FFix.Command.Input` and FFmpeg's
[stream specifiers](https://ffmpeg.org/ffmpeg.html#Stream-specifiers).

# `stream_copy`

```elixir
@spec stream_copy(FFix.Command.source()) :: FFix.Command.Mapping.t()
```

Copies the selected encoded streams into an output, saving encoding time and quality loss.

    audio = source |> FFix.audio(:all) |> FFix.stream_copy()
    FFix.output(audio, "audio.mka")

> #### Check the destination format {: .warning}
> The output container must support the copied codecs. Stream copy also
> requires an unfiltered source: after filtering, choose an encoder instead.

# `subtitle`

```elixir
@spec subtitle(FFix.Command.Input.t(), stream_index(), [
  FFix.Command.Input.selection_option()
]) ::
  FFix.Graph.StreamRef.t() | FFix.Selection.t()
```

Selects a subtitle track by zero-based subtitle index, or all subtitle tracks with `:all`.

Use `optional: true` to allow a missing track. These selections add subtitle
streams to outputs. To draw subtitles onto the video itself, use
`FFix.Filter.subtitles/2`.

# `video`

```elixir
@spec video(FFix.Command.Input.t(), stream_index(), [video_option()]) ::
  FFix.Graph.StreamRef.t() | FFix.Selection.t()
```

Selects a video stream by zero-based video index, or all video streams with `:all`.

    FFix.video(source, 0)
    FFix.video(source, :all, optional: true)
    FFix.video(source, 0, attached_pictures: false)

Options:

- `:optional` — allow a missing match when mapping an output; defaults to `false`.
- `:attached_pictures` — include cover art and thumbnails in matching; defaults
  to `true`. Set it to `false` to use FFmpeg's `V` selector.

An indexed, required selection can feed a filter. `:all` and optional selections
go directly to outputs. See `FFix.Command.Input` for the selection rules.

# `to_argv`

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

Returns a command as an argument list beginning with `"ffmpeg"`.

Prefer this list when passing a command to another process library. Arguments
are escaped for FFmpeg and do not need shell quoting. The runner chooses its
executable separately through `FFix.Runner.run/2` options.

# `to_filtergraph`

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

Validates a graph and returns its FFmpeg filtergraph text. See `FFix.Graph.to_filtergraph/1`.

# `to_shell_string`

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

Returns a shell-quoted command for logs and debugging. Use `to_argv/1` for process execution.

# `validate!`

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

Checks graph or command structure and returns the original value.

Raises `ArgumentError` for invalid connections or configuration. Validation
checks the instructions; FFmpeg checks actual files and available codecs when
the command runs. Deferred output option values are checked during serialization.

# `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, returning `{:ok, result}` or `{:error, error}`.

See `FFix.Runner.run/2` for executable selection, input/output capture, and
progress options, and `FFix.Runner.Result` for result fields.

# `run!`

```elixir
@spec run!(FFix.Command.t() | [String.t(), ...], [FFix.Runner.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(), ...], [FFix.Runner.option()]) ::
  Enumerable.t()
```

Returns a lazy stream of execution events. See `FFix.Runner.stream/2` for progress and piping examples.

# `stream!`

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

Like `stream/2`, raising `FFix.Runner.Error` on execution failure.

# `output_media`

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

# `stream_index`

```elixir
@type stream_index() :: non_neg_integer() | :all
```

# `video_option`

```elixir
@type video_option() ::
  FFix.Command.Input.selection_option() | {:attached_pictures, boolean()}
```

---

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