Metadata-Version: 2.4
Name: fraime-sdk
Version: 1.1.0
Summary: Python SDK for the Fraime video, image, and voice generation API
Author: Santiago Melo Medina
License-Expression: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx
Requires-Dist: pydantic
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# Fraime SDK

Python client for the [Fraime API](../api/README.md): typed models/enums for
building a video, image, or voice generation request, instead of
hand-writing JSON.

## Prerequisites

- Python 3.11+
- A running Fraime API instance (see [`api/README.md`](../api/README.md)) —
  its base URL, and its API key if `AUTH_API_KEY` is set.

## Install

### Option 1 — pip

```bash
pip install fraime-sdk
```

### Option 2 — from a local clone

```bash
git clone <this-repo-url>
cd fraime
pip install ./sdk
#   or, for local development on the SDK itself (editable install):
pip install -e ./sdk
```

### Option 3 — straight from git, no local clone needed

```bash
pip install "git+ssh://git@santiago/santiagoMeloMedina/fraime.git#subdirectory=sdk"
```

## Usage

```python
from fraime import FraimeClient, VideoType, GenerationParams, CinematicPromptFields

client = FraimeClient(
    base_url="http://127.0.0.1:8000",  # or set FRAIME_BASE_URL instead
    api_key="your-api-key",             # or set FRAIME_API_KEY instead; omit both if the API has none configured
)

response = client.generate_video(
    video_type=VideoType.PIXAR,
    fields=CinematicPromptFields(
        subject="a small orange fox with oversized ears",
        action="hops between rocks, pauses, and looks up curiously",
        scene="a sunlit forest clearing at golden hour",
        camera="medium shot, slow dolly-in",
        lighting="warm rim lighting from the low sun",
        style="3D animated feature style, stylized proportions, warm rim lighting",
    ),
    params=GenerationParams(duration_s=3, fps=16, resolution="768x512"),
)

print(response.video_path, response.model)
```

`model` is optional — omit it and the API auto-selects by hardware.

If the API is configured with `CLOUD_S3_OUTPUT_BUCKET` (see
[`api/README.md`](../api/README.md#s3-output)), `response.video_path` is
`None` and `response.s3_bucket`, `response.s3_key`, and `response.s3_url` (a
presigned link, valid for 1 hour) are populated instead.

### Picking the right fields class per video type

Every `video_type` has its own field set — some add fields the base six
(`subject`, `action`, `scene`, `camera`, `lighting`, `style`,
`negative_prompt`) don't cover:

| `VideoType` | Fields class | Extra fields |
|---|---|---|
| `PIXAR`, `ACTION`, `ANIMATION`, `ANIME`, `DOCUMENTARY`, `FASHION` | `CinematicPromptFields` | — |
| `UGC_PRODUCT_REVIEW`, `COMMERCIAL_PRODUCT_AD`, `EXPLAINER_TESTIMONIAL` | `UGCPromptFields` | `dialogue`, `reference_image` |
| `PRESENTER_AVATAR` | `PresenterPromptFields` | + `voice_tone` |
| `SOCIAL_SHORT_FORM_AD` | `SocialAdPromptFields` | + `text_overlay`, `aspect_ratio` |
| `MUSIC_VIDEO` | `MusicVideoPromptFields` | `audio_reference`, `tempo_bpm` |
| `MOTION_GRAPHICS` | `MotionGraphicsPromptFields` | `text_content`, `transitions` |

Look up a class from `VideoType` directly instead of hardcoding the table:
`PROMPT_FIELDS_BY_VIDEO_TYPE[VideoType.SOCIAL_SHORT_FORM_AD]` returns
`SocialAdPromptFields`.

For image-to-video, pass `references=[Reference(url="https://...")]` to
`generate_video`.

## Image generation

Unlike video, there's no per-type variation — `ImagePromptFields` is a
single fixed field set for every image request:

```python
from fraime import FraimeClient, ImagePromptFields, ImageGenerationParams

client = FraimeClient(base_url="http://127.0.0.1:8000")

response = client.generate_image(
    fields=ImagePromptFields(
        subject="a matte black ceramic coffee mug with a minimalist logo",
        scene="a clean marble kitchen counter with soft blurred greenery in the background",
        camera="close-up, straight-on angle, shallow depth of field",
        lighting="controlled studio softbox lighting, warm highlight on the ceramic surface",
        style="polished commercial product photography, crisp reflections",
        color_palette="warm neutrals with a matte black accent",
        negative_prompt="blurry, low quality, warped shape, extra objects, watermark, text overlay",
    ),
    params=ImageGenerationParams(width=1024, height=1024),
)

print(response.image_path, response.model)
```

`response` is a `GenerateImageResponse` (`image_path`, `model`, `s3_bucket`,
`s3_key`, `s3_url`) — same S3-output behavior as video: if the API has
`CLOUD_S3_OUTPUT_BUCKET` configured, `image_path` is `None` and the S3 fields
are populated instead.

Reference images (image-to-image) work the same way as video's
image-to-video: pass `references=[Reference(url=...)]` to `generate_image`.

## Voice generation

Voice has no structured `fields` the way video/image do — chatterbox's
input is literal spoken `text`, not a compiled scene description:

```python
from fraime import FraimeClient, VoiceGenerationParams

client = FraimeClient(base_url="http://127.0.0.1:8000")

response = client.generate_voice(
    text="Hello from Chatterbox. This is a test of the Fraime voice generation pipeline.",
    params=VoiceGenerationParams(exaggeration=0.5, cfg_weight=0.5, temperature=0.8),
    variant=None,      # VoiceVariant.BASE / .TURBO / .MULTILINGUAL; omit to auto-select
    language=None,     # ISO code, e.g. "es"; only honored when the resolved variant is multilingual
    voice=None,        # Reference(url=...) to clone a voice from a 5-20s clean clip
)

print(response.voice_path, response.model)
```

`response` is a `GenerateVoiceResponse` (`voice_path`, `model`, `s3_bucket`,
`s3_key`, `s3_url`) — same S3-output behavior as video/image.

Unlike video/image, `generate_voice` has no `model`/`references`/`cpu_offload`
argument: chatterbox ships three fixed classes rather than arbitrary
swappable HF repos, so `variant` is the pin mechanism instead of `model`, and
`voice` is a single `Reference` rather than a list.

- `base`/`turbo` are English-only and ignore `language`; only `multilingual`
  (23 languages) honors it.
- Every variant supports zero-shot voice cloning via `voice`; `turbo` trades
  some expressiveness for much lower VRAM/latency.

### Inspecting the API's configuration

```python
models_config = client.get_models_config()
for key, entry in models_config.models.items():
    print(key, entry.media_type, entry.id, entry.capabilities, entry.min_vram_gb)

rules_config = client.get_rules_config()
print(rules_config.shared.fields, rules_config.types["pixar"].style_guidance)
print(rules_config.image_fields, rules_config.image_evaluation_criteria)
```

`get_models_config()` returns `ModelsConfig` (`models: dict[str, ModelCatalogEntry]`,
`video_type_capabilities: dict[str, VideoTypeCapabilityRequirement]`) —
video, image, and voice models together in one catalog, distinguished by
each entry's `media_type` (`MediaType.VIDEO` / `.IMAGE` / `.VOICE`).

`get_rules_config()` returns `RulesConfig` — video and image rules together,
in one file on the API side: `shared`/`types` for video,
`image_fields`/`image_evaluation_criteria` for image, plus a shared
`criteria_schema` describing what each evaluation criterion field means.
Voice has no equivalent — there's nothing to compile, so nothing to score.

Both raise the same `FraimeAuthError` / `FraimeAPIError` / `FraimeConnectionError`
as `generate_video()`/`generate_image()`/`generate_voice()`.

### Error handling

```python
from fraime import FraimeAuthError, FraimeAPIError, FraimeConnectionError

try:
    response = client.generate_video(video_type=VideoType.PIXAR, fields=fields, params=params)
except FraimeAuthError:
    ...  # missing/invalid API key
except FraimeAPIError as e:
    ...  # e.status_code, e.detail — the API reached but returned an error
except FraimeConnectionError:
    ...  # couldn't reach the API at all
```

## Configuration reference

| `FraimeClient(...)` argument | Env var fallback | Default |
|---|---|---|
| `base_url` | `FRAIME_BASE_URL` | `http://127.0.0.1:8000` |
| `api_key` | `FRAIME_API_KEY` | none (open API) |
| `timeout` | — | `600.0` seconds |

`timeout` defaults high because generation runs can take several minutes.
