# `FreeImageToVideo`
[🔗](https://github.com/hanshs474/fitv-ex/blob/main/lib/freeimagetovideo.ex#L1)

Official Elixir SDK for [Free Image to Video](https://freeimagetovideo.ai).
Free image to video AI with no daily cap: start with no sign-up, keep going on
a free account. Every video comes with sound and no watermark.

    {:ok, url} = FreeImageToVideo.generate_video("a paper lantern drifting over a night river")
    {:ok, url} = FreeImageToVideo.generate_video("the camera slowly orbits the statue",
                   image_url: "https://example.com/statue.jpg", options: %{"duration" => 6})
    {:ok, left} = FreeImageToVideo.credits()

Pass `api_key:` or set `FREEIMAGETOVIDEO_API_KEY`; create a key at
https://freeimagetovideo.ai/settings/apikeys.

Failures are `{:error, reason, message}` where `reason` is `:auth`, `:quota`,
`:plan`, `:busy`, `:rejected`, `:generation`, `:timeout` or `:api`.

Uses only `:httpc` from OTP and the standard `JSON` module; no dependencies.

# `error`

```elixir
@type error() :: {:error, reason(), String.t()}
```

# `reason`

```elixir
@type reason() ::
  :auth | :quota | :plan | :busy | :rejected | :generation | :timeout | :api
```

# `body`

```elixir
@spec body(String.t(), keyword()) :: map()
```

The request body for `POST /api/ai/generate`.

# `classify`

```elixir
@spec classify(integer(), map()) :: reason()
```

Maps an HTTP status and envelope to the reason that says what to do next.

# `credits`

```elixir
@spec credits(keyword()) :: {:ok, integer()} | error()
```

Credits left on the account.

# `generate_video`

```elixir
@spec generate_video(String.t(), keyword()) :: {:ok, String.t()} | error()
```

Generates one clip with sound and returns the URL of the finished MP4.

Options: `:image_url` (a public https image; it becomes the first frame),
`:options` (map sent as the request's `options`: `"duration"` 4/5/6/8/10/15,
`"resolution"` 480p/768p, `"aspect_ratio"` 16:9/9:16/1:1), `:api_key`,
`:poll_ms` (10000), `:timeout_ms` (30 minutes), `:base_url`.

# `parse_envelope`

```elixir
@spec parse_envelope(integer(), binary()) :: {:ok, term()} | error()
```

Reads one answer: `{:ok, data}` on code 0, else the classified error.

Refusals usually arrive as HTTP 200 with a non-zero code, so the status alone
reads a refusal as success.

# `status`

```elixir
@spec status(String.t(), keyword()) :: {:ok, map()} | error()
```

Returns the run as `/api/ai/query` reports it: a map with `"id"`, `"status"`
(`"pending"`, `"processing"`, `"success"`, `"failed"` or `"canceled"`),
`"videos"`, `"errorMessage"`, `"failureKind"`, `"queued"` and `"queueEta"`.

# `submit`

```elixir
@spec submit(String.t(), keyword()) :: {:ok, String.t()} | error()
```

Submits a run and returns its task id without waiting. Same options as `generate_video/2`.

# `wait`

```elixir
@spec wait(String.t(), keyword()) :: {:ok, String.t()} | error()
```

Polls a submitted run until it finishes and returns its video URL.

---

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