fraime-sdk 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Santiago Melo Medina
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: fraime-sdk
3
+ Version: 1.0.0
4
+ Summary: Python SDK for the Fraime video generation API
5
+ Author: Santiago Melo Medina
6
+ License-Expression: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.11
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: httpx
14
+ Requires-Dist: pydantic
15
+ Provides-Extra: dev
16
+ Requires-Dist: build; extra == "dev"
17
+ Requires-Dist: twine; extra == "dev"
18
+ Dynamic: license-file
19
+
20
+ # Fraime SDK
21
+
22
+ Python client for the [Fraime API](../api/README.md) — build a request with
23
+ typed models/enums instead of hand-writing JSON, point it at a running
24
+ Fraime instance, get a generated video back.
25
+
26
+ ## Prerequisites
27
+
28
+ - Python 3.11+
29
+ - A running Fraime API instance to talk to (see [`api/README.md`](../api/README.md)
30
+ for how to run one) — you'll need its base URL, and its API key if one is
31
+ configured (`AUTH_API_KEY` on the API side).
32
+
33
+ ## Install
34
+
35
+ ### Option 1 — pip
36
+
37
+ ```bash
38
+ pip install fraime-sdk
39
+ ```
40
+
41
+ ### Option 2 — from a local clone
42
+
43
+ Step by step, from scratch:
44
+
45
+ ```bash
46
+ # 1. Clone the repo (skip if you already have it)
47
+ git clone <this-repo-url>
48
+ cd fraime
49
+
50
+ # 2. (Recommended) create a virtualenv for your own project
51
+ python3 -m venv .venv
52
+ source .venv/bin/activate
53
+
54
+ # 3. Install the SDK from the sdk/ folder
55
+ pip install ./sdk
56
+ # or, for local development on the SDK itself (editable install):
57
+ pip install -e ./sdk
58
+ ```
59
+
60
+ That's it — `import fraime` is now available in that environment.
61
+
62
+ ### Option 3 — straight from git, no local clone needed
63
+
64
+ ```bash
65
+ pip install "git+ssh://git@santiago/santiagoMeloMedina/fraime.git#subdirectory=sdk"
66
+ ```
67
+
68
+ (Adjust the URL to whatever remote you actually have push/pull access to —
69
+ this is this repo's own `origin` in SSH form.)
70
+
71
+ ## Usage
72
+
73
+ ```python
74
+ from fraime import FraimeClient, VideoType, GenerationParams, CinematicPromptFields
75
+
76
+ client = FraimeClient(
77
+ base_url="http://127.0.0.1:8000", # or set FRAIME_BASE_URL instead
78
+ api_key="your-api-key", # or set FRAIME_API_KEY instead; omit both if the API has none configured
79
+ )
80
+
81
+ response = client.generate(
82
+ video_type=VideoType.PIXAR,
83
+ fields=CinematicPromptFields(
84
+ subject="a small orange fox with oversized ears",
85
+ action="hops between rocks, pauses, and looks up curiously",
86
+ scene="a sunlit forest clearing at golden hour",
87
+ camera="medium shot, slow dolly-in",
88
+ lighting="warm rim lighting from the low sun",
89
+ style="3D animated feature style, stylized proportions, warm rim lighting",
90
+ ),
91
+ params=GenerationParams(duration_s=3, fps=16, resolution="768x512"),
92
+ # model=... # optional: pin an exact model instead of auto-selecting
93
+ # references=[...] # optional: Reference(url=...) list, for image-to-video
94
+ )
95
+
96
+ print(response.video_path, response.model)
97
+ ```
98
+
99
+ `model` is optional on `client.generate()` — omit it and the API auto-selects
100
+ by hardware, same as calling it directly.
101
+
102
+ ### Picking the right fields class per video type
103
+
104
+ Every `video_type` has its own field set — some add fields the base six
105
+ (`subject`, `action`, `scene`, `camera`, `lighting`, `style`,
106
+ `negative_prompt`) don't cover:
107
+
108
+ | `VideoType` | Fields class | Extra fields |
109
+ |---|---|---|
110
+ | `PIXAR`, `ACTION`, `ANIMATION`, `ANIME`, `DOCUMENTARY`, `FASHION` | `CinematicPromptFields` | — |
111
+ | `UGC_PRODUCT_REVIEW`, `COMMERCIAL_PRODUCT_AD`, `EXPLAINER_TESTIMONIAL` | `UGCPromptFields` | `dialogue`, `reference_image` |
112
+ | `PRESENTER_AVATAR` | `PresenterPromptFields` | + `voice_tone` |
113
+ | `SOCIAL_SHORT_FORM_AD` | `SocialAdPromptFields` | + `text_overlay`, `aspect_ratio` |
114
+ | `MUSIC_VIDEO` | `MusicVideoPromptFields` | `audio_reference`, `tempo_bpm` |
115
+ | `MOTION_GRAPHICS` | `MotionGraphicsPromptFields` | `text_content`, `transitions` |
116
+
117
+ Not sure which class a given `VideoType` needs? Look it up instead of
118
+ guessing:
119
+
120
+ ```python
121
+ from fraime import PROMPT_FIELDS_BY_VIDEO_TYPE, VideoType
122
+
123
+ fields_class = PROMPT_FIELDS_BY_VIDEO_TYPE[VideoType.SOCIAL_SHORT_FORM_AD]
124
+ # -> SocialAdPromptFields
125
+ ```
126
+
127
+ ### Reference images (image-to-video)
128
+
129
+ ```python
130
+ from fraime import Reference
131
+
132
+ response = client.generate(
133
+ video_type=VideoType.UGC_PRODUCT_REVIEW,
134
+ fields=ugc_fields,
135
+ params=params,
136
+ references=[Reference(url="https://example.com/product-photo.jpg")],
137
+ )
138
+ ```
139
+
140
+ ### Error handling
141
+
142
+ ```python
143
+ from fraime import FraimeAuthError, FraimeAPIError, FraimeConnectionError
144
+
145
+ try:
146
+ response = client.generate(video_type=VideoType.PIXAR, fields=fields, params=params)
147
+ except FraimeAuthError:
148
+ ... # missing/invalid API key
149
+ except FraimeAPIError as e:
150
+ ... # e.status_code, e.detail — the API reached but returned an error
151
+ except FraimeConnectionError:
152
+ ... # couldn't reach the API at all
153
+ ```
154
+
155
+ ## Configuration reference
156
+
157
+ | `FraimeClient(...)` argument | Env var fallback | Default |
158
+ |---|---|---|
159
+ | `base_url` | `FRAIME_BASE_URL` | `http://127.0.0.1:8000` |
160
+ | `api_key` | `FRAIME_API_KEY` | none (open API) |
161
+ | `timeout` | — | `600.0` seconds |
162
+
163
+ `timeout` defaults high on purpose — real generation runs can take several
164
+ minutes; see [`api/README.md`](../api/README.md) for why.
@@ -0,0 +1,145 @@
1
+ # Fraime SDK
2
+
3
+ Python client for the [Fraime API](../api/README.md) — build a request with
4
+ typed models/enums instead of hand-writing JSON, point it at a running
5
+ Fraime instance, get a generated video back.
6
+
7
+ ## Prerequisites
8
+
9
+ - Python 3.11+
10
+ - A running Fraime API instance to talk to (see [`api/README.md`](../api/README.md)
11
+ for how to run one) — you'll need its base URL, and its API key if one is
12
+ configured (`AUTH_API_KEY` on the API side).
13
+
14
+ ## Install
15
+
16
+ ### Option 1 — pip
17
+
18
+ ```bash
19
+ pip install fraime-sdk
20
+ ```
21
+
22
+ ### Option 2 — from a local clone
23
+
24
+ Step by step, from scratch:
25
+
26
+ ```bash
27
+ # 1. Clone the repo (skip if you already have it)
28
+ git clone <this-repo-url>
29
+ cd fraime
30
+
31
+ # 2. (Recommended) create a virtualenv for your own project
32
+ python3 -m venv .venv
33
+ source .venv/bin/activate
34
+
35
+ # 3. Install the SDK from the sdk/ folder
36
+ pip install ./sdk
37
+ # or, for local development on the SDK itself (editable install):
38
+ pip install -e ./sdk
39
+ ```
40
+
41
+ That's it — `import fraime` is now available in that environment.
42
+
43
+ ### Option 3 — straight from git, no local clone needed
44
+
45
+ ```bash
46
+ pip install "git+ssh://git@santiago/santiagoMeloMedina/fraime.git#subdirectory=sdk"
47
+ ```
48
+
49
+ (Adjust the URL to whatever remote you actually have push/pull access to —
50
+ this is this repo's own `origin` in SSH form.)
51
+
52
+ ## Usage
53
+
54
+ ```python
55
+ from fraime import FraimeClient, VideoType, GenerationParams, CinematicPromptFields
56
+
57
+ client = FraimeClient(
58
+ base_url="http://127.0.0.1:8000", # or set FRAIME_BASE_URL instead
59
+ api_key="your-api-key", # or set FRAIME_API_KEY instead; omit both if the API has none configured
60
+ )
61
+
62
+ response = client.generate(
63
+ video_type=VideoType.PIXAR,
64
+ fields=CinematicPromptFields(
65
+ subject="a small orange fox with oversized ears",
66
+ action="hops between rocks, pauses, and looks up curiously",
67
+ scene="a sunlit forest clearing at golden hour",
68
+ camera="medium shot, slow dolly-in",
69
+ lighting="warm rim lighting from the low sun",
70
+ style="3D animated feature style, stylized proportions, warm rim lighting",
71
+ ),
72
+ params=GenerationParams(duration_s=3, fps=16, resolution="768x512"),
73
+ # model=... # optional: pin an exact model instead of auto-selecting
74
+ # references=[...] # optional: Reference(url=...) list, for image-to-video
75
+ )
76
+
77
+ print(response.video_path, response.model)
78
+ ```
79
+
80
+ `model` is optional on `client.generate()` — omit it and the API auto-selects
81
+ by hardware, same as calling it directly.
82
+
83
+ ### Picking the right fields class per video type
84
+
85
+ Every `video_type` has its own field set — some add fields the base six
86
+ (`subject`, `action`, `scene`, `camera`, `lighting`, `style`,
87
+ `negative_prompt`) don't cover:
88
+
89
+ | `VideoType` | Fields class | Extra fields |
90
+ |---|---|---|
91
+ | `PIXAR`, `ACTION`, `ANIMATION`, `ANIME`, `DOCUMENTARY`, `FASHION` | `CinematicPromptFields` | — |
92
+ | `UGC_PRODUCT_REVIEW`, `COMMERCIAL_PRODUCT_AD`, `EXPLAINER_TESTIMONIAL` | `UGCPromptFields` | `dialogue`, `reference_image` |
93
+ | `PRESENTER_AVATAR` | `PresenterPromptFields` | + `voice_tone` |
94
+ | `SOCIAL_SHORT_FORM_AD` | `SocialAdPromptFields` | + `text_overlay`, `aspect_ratio` |
95
+ | `MUSIC_VIDEO` | `MusicVideoPromptFields` | `audio_reference`, `tempo_bpm` |
96
+ | `MOTION_GRAPHICS` | `MotionGraphicsPromptFields` | `text_content`, `transitions` |
97
+
98
+ Not sure which class a given `VideoType` needs? Look it up instead of
99
+ guessing:
100
+
101
+ ```python
102
+ from fraime import PROMPT_FIELDS_BY_VIDEO_TYPE, VideoType
103
+
104
+ fields_class = PROMPT_FIELDS_BY_VIDEO_TYPE[VideoType.SOCIAL_SHORT_FORM_AD]
105
+ # -> SocialAdPromptFields
106
+ ```
107
+
108
+ ### Reference images (image-to-video)
109
+
110
+ ```python
111
+ from fraime import Reference
112
+
113
+ response = client.generate(
114
+ video_type=VideoType.UGC_PRODUCT_REVIEW,
115
+ fields=ugc_fields,
116
+ params=params,
117
+ references=[Reference(url="https://example.com/product-photo.jpg")],
118
+ )
119
+ ```
120
+
121
+ ### Error handling
122
+
123
+ ```python
124
+ from fraime import FraimeAuthError, FraimeAPIError, FraimeConnectionError
125
+
126
+ try:
127
+ response = client.generate(video_type=VideoType.PIXAR, fields=fields, params=params)
128
+ except FraimeAuthError:
129
+ ... # missing/invalid API key
130
+ except FraimeAPIError as e:
131
+ ... # e.status_code, e.detail — the API reached but returned an error
132
+ except FraimeConnectionError:
133
+ ... # couldn't reach the API at all
134
+ ```
135
+
136
+ ## Configuration reference
137
+
138
+ | `FraimeClient(...)` argument | Env var fallback | Default |
139
+ |---|---|---|
140
+ | `base_url` | `FRAIME_BASE_URL` | `http://127.0.0.1:8000` |
141
+ | `api_key` | `FRAIME_API_KEY` | none (open API) |
142
+ | `timeout` | — | `600.0` seconds |
143
+
144
+ `timeout` defaults high on purpose — real generation runs can take several
145
+ minutes; see [`api/README.md`](../api/README.md) for why.
@@ -0,0 +1,45 @@
1
+ from fraime.exceptions import (
2
+ FraimeAPIError,
3
+ FraimeAuthError,
4
+ FraimeConnectionError,
5
+ FraimeError,
6
+ )
7
+ from fraime.main import FraimeClient
8
+ from fraime.model import (
9
+ PROMPT_FIELDS_BY_VIDEO_TYPE,
10
+ AspectRatio,
11
+ CinematicPromptFields,
12
+ GenerateVideoRequest,
13
+ GenerateVideoResponse,
14
+ GenerationParams,
15
+ MotionGraphicsPromptFields,
16
+ MusicVideoPromptFields,
17
+ PresenterPromptFields,
18
+ PromptFields,
19
+ Reference,
20
+ SocialAdPromptFields,
21
+ UGCPromptFields,
22
+ VideoType,
23
+ )
24
+
25
+ __all__ = [
26
+ "FraimeClient",
27
+ "VideoType",
28
+ "PROMPT_FIELDS_BY_VIDEO_TYPE",
29
+ "AspectRatio",
30
+ "GenerationParams",
31
+ "Reference",
32
+ "PromptFields",
33
+ "CinematicPromptFields",
34
+ "UGCPromptFields",
35
+ "PresenterPromptFields",
36
+ "SocialAdPromptFields",
37
+ "MusicVideoPromptFields",
38
+ "MotionGraphicsPromptFields",
39
+ "GenerateVideoRequest",
40
+ "GenerateVideoResponse",
41
+ "FraimeError",
42
+ "FraimeConnectionError",
43
+ "FraimeAuthError",
44
+ "FraimeAPIError",
45
+ ]
@@ -0,0 +1,19 @@
1
+ class FraimeError(Exception):
2
+ """Base class for all Fraime SDK errors."""
3
+
4
+
5
+ class FraimeConnectionError(FraimeError):
6
+ """The API couldn't be reached at all (network/DNS/timeout)."""
7
+
8
+
9
+ class FraimeAuthError(FraimeError):
10
+ """The API rejected the request due to a missing or invalid API key."""
11
+
12
+
13
+ class FraimeAPIError(FraimeError):
14
+ """The API reached and responded, but with an error status."""
15
+
16
+ def __init__(self, status_code: int, detail: str):
17
+ self.status_code = status_code
18
+ self.detail = detail
19
+ super().__init__(f"Fraime API returned {status_code}: {detail}")
@@ -0,0 +1,51 @@
1
+ import os
2
+
3
+ from fraime.model import (
4
+ GenerateVideoRequest,
5
+ GenerateVideoResponse,
6
+ GenerationParams,
7
+ PromptFields,
8
+ Reference,
9
+ VideoType,
10
+ )
11
+ from fraime.repository import GenerationRepository
12
+ from fraime.service import GenerationService
13
+
14
+ DEFAULT_BASE_URL = "http://127.0.0.1:8000"
15
+
16
+
17
+ class FraimeClient:
18
+ def __init__(
19
+ self,
20
+ base_url: str | None = None,
21
+ api_key: str | None = None,
22
+ timeout: float = 600.0,
23
+ ):
24
+ base_url = base_url or os.environ.get("FRAIME_BASE_URL", DEFAULT_BASE_URL)
25
+ api_key = api_key or os.environ.get("FRAIME_API_KEY")
26
+
27
+ repository = GenerationRepository(base_url=base_url, api_key=api_key, timeout=timeout)
28
+ self._service = GenerationService(repository)
29
+
30
+ def generate(
31
+ self,
32
+ video_type: VideoType,
33
+ fields: PromptFields,
34
+ params: GenerationParams,
35
+ model: str | None = None,
36
+ references: list[Reference] | None = None,
37
+ vram_safety_margin: bool = True,
38
+ low_memory_decode: bool = True,
39
+ cpu_offload: bool = True,
40
+ ) -> GenerateVideoResponse:
41
+ request = GenerateVideoRequest(
42
+ video_type=video_type,
43
+ fields=fields,
44
+ params=params,
45
+ model=model,
46
+ references=references,
47
+ vram_safety_margin=vram_safety_margin,
48
+ low_memory_decode=low_memory_decode,
49
+ cpu_offload=cpu_offload,
50
+ )
51
+ return self._service.generate_video(request)
@@ -0,0 +1,142 @@
1
+ from abc import ABC
2
+ from enum import Enum
3
+
4
+ from pydantic import BaseModel, Field, HttpUrl
5
+
6
+
7
+ class VideoType(str, Enum):
8
+ PIXAR = "pixar"
9
+ ACTION = "action"
10
+ ANIMATION = "animation"
11
+ ANIME = "anime"
12
+ DOCUMENTARY = "documentary"
13
+ FASHION = "fashion"
14
+ UGC_PRODUCT_REVIEW = "ugc_product_review"
15
+ COMMERCIAL_PRODUCT_AD = "commercial_product_ad"
16
+ EXPLAINER_TESTIMONIAL = "explainer_testimonial"
17
+ PRESENTER_AVATAR = "presenter_avatar"
18
+ SOCIAL_SHORT_FORM_AD = "social_short_form_ad"
19
+ MUSIC_VIDEO = "music_video"
20
+ MOTION_GRAPHICS = "motion_graphics"
21
+
22
+
23
+ class AspectRatio(str, Enum):
24
+ VERTICAL_9_16 = "9:16"
25
+ VERTICAL_4_5 = "4:5"
26
+
27
+
28
+ class Reference(BaseModel):
29
+ url: HttpUrl = Field(description="Publicly accessible URL the file is downloaded from")
30
+
31
+
32
+ class GenerationParams(BaseModel):
33
+ duration_s: float = Field(gt=0, description="Requested clip duration in seconds")
34
+ fps: int = Field(gt=0, description="Frames per second")
35
+ resolution: str = Field(description="Target resolution, e.g. '1024x576'")
36
+ seed: int | None = Field(default=None, description="Seed for reproducible generation")
37
+ num_inference_steps: int | None = Field(
38
+ default=None,
39
+ gt=0,
40
+ description="Denoising steps; lower is faster/lower quality. Defaults to the model's own default (usually 50) when unset.",
41
+ )
42
+
43
+
44
+ class PromptFields(BaseModel, ABC):
45
+ """Fields shared by every video type."""
46
+
47
+ subject: str = Field(description="Main focus of the video: who/what")
48
+ action: str = Field(description="What happens over time; the motion")
49
+ scene: str = Field(description="Environment, background, time of day")
50
+ camera: str = Field(description="Shot type and camera movement")
51
+ lighting: str = Field(description="Lighting style and mood")
52
+ style: str = Field(description="Visual/cinematic style reference")
53
+ negative_prompt: str | None = Field(default=None, description="What to avoid in the generation")
54
+
55
+
56
+ class CinematicPromptFields(PromptFields):
57
+ """pixar, action, animation, anime, documentary, fashion — no extra fields."""
58
+
59
+
60
+ class UGCPromptFields(PromptFields):
61
+ """ugc_product_review, commercial_product_ad, explainer_testimonial."""
62
+
63
+ dialogue: str | None = Field(
64
+ default=None, description="Spoken script/dialogue delivered by the subject"
65
+ )
66
+ reference_image: str | None = Field(
67
+ default=None, description="Reference image URL for product/subject fidelity"
68
+ )
69
+
70
+
71
+ class PresenterPromptFields(UGCPromptFields):
72
+ """presenter_avatar."""
73
+
74
+ voice_tone: str | None = Field(
75
+ default=None, description="Directive for how the voice should sound, e.g. 'warm, confident, corporate'"
76
+ )
77
+
78
+
79
+ class SocialAdPromptFields(UGCPromptFields):
80
+ """social_short_form_ad."""
81
+
82
+ text_overlay: str | None = Field(default=None, description="On-screen text/captions overlaid on the video")
83
+ aspect_ratio: AspectRatio = Field(default=AspectRatio.VERTICAL_9_16, description="Target aspect ratio")
84
+
85
+
86
+ class MusicVideoPromptFields(PromptFields):
87
+ """music_video. No catalog model currently supports this — expect no viable model today."""
88
+
89
+ audio_reference: str = Field(description="Reference audio track/URL the visuals should sync to")
90
+ tempo_bpm: int | None = Field(default=None, description="Beats per minute to sync visual cuts to")
91
+
92
+
93
+ class MotionGraphicsPromptFields(PromptFields):
94
+ """motion_graphics."""
95
+
96
+ text_content: str = Field(description="On-screen text/copy driving the animation")
97
+ transitions: str | None = Field(
98
+ default=None, description="Transition style between graphic elements, e.g. 'fade, slide, zoom'"
99
+ )
100
+
101
+
102
+ PROMPT_FIELDS_BY_VIDEO_TYPE: dict[VideoType, type[PromptFields]] = {
103
+ VideoType.PIXAR: CinematicPromptFields,
104
+ VideoType.ACTION: CinematicPromptFields,
105
+ VideoType.ANIMATION: CinematicPromptFields,
106
+ VideoType.ANIME: CinematicPromptFields,
107
+ VideoType.DOCUMENTARY: CinematicPromptFields,
108
+ VideoType.FASHION: CinematicPromptFields,
109
+ VideoType.UGC_PRODUCT_REVIEW: UGCPromptFields,
110
+ VideoType.COMMERCIAL_PRODUCT_AD: UGCPromptFields,
111
+ VideoType.EXPLAINER_TESTIMONIAL: UGCPromptFields,
112
+ VideoType.PRESENTER_AVATAR: PresenterPromptFields,
113
+ VideoType.SOCIAL_SHORT_FORM_AD: SocialAdPromptFields,
114
+ VideoType.MUSIC_VIDEO: MusicVideoPromptFields,
115
+ VideoType.MOTION_GRAPHICS: MotionGraphicsPromptFields,
116
+ }
117
+
118
+
119
+ class GenerateVideoRequest(BaseModel):
120
+ video_type: VideoType
121
+ fields: PromptFields
122
+ params: GenerationParams
123
+ model: str | None = Field(
124
+ default=None, description="Explicit Hugging Face model id; omit to auto-select by hardware"
125
+ )
126
+ references: list[Reference] | None = Field(
127
+ default=None, description="Reference images; presence requires image-to-video capability"
128
+ )
129
+ vram_safety_margin: bool = Field(
130
+ default=True, description="Match against 85% of detected VRAM instead of 100%, for headroom"
131
+ )
132
+ low_memory_decode: bool = Field(
133
+ default=True, description="Decode the VAE output in slices/tiles instead of all at once"
134
+ )
135
+ cpu_offload: bool = Field(
136
+ default=True, description="Move pipeline components between CPU and accelerator instead of holding all at once"
137
+ )
138
+
139
+
140
+ class GenerateVideoResponse(BaseModel):
141
+ video_path: str = Field(description="Path to the generated .mp4 on the API server")
142
+ model: str = Field(description="Hugging Face model id that actually ran")
@@ -0,0 +1,30 @@
1
+ import httpx
2
+
3
+ from fraime.exceptions import FraimeAPIError, FraimeAuthError, FraimeConnectionError
4
+
5
+
6
+ class GenerationRepository:
7
+ """Raw HTTP transport to the Fraime API — no model knowledge beyond JSON in/out."""
8
+
9
+ def __init__(self, base_url: str, api_key: str | None = None, timeout: float = 600.0):
10
+ self._base_url = base_url.rstrip("/")
11
+ self._api_key = api_key
12
+ self._timeout = timeout
13
+
14
+ def post_generate(self, payload: dict) -> dict:
15
+ url = f"{self._base_url}/generate"
16
+ headers = {"Content-Type": "application/json"}
17
+ if self._api_key:
18
+ headers["Authorization"] = f"Bearer {self._api_key}"
19
+
20
+ try:
21
+ response = httpx.post(url, json=payload, headers=headers, timeout=self._timeout)
22
+ except httpx.RequestError as e:
23
+ raise FraimeConnectionError(f"Failed to reach Fraime API at {url}: {e}") from e
24
+
25
+ if response.status_code == 401:
26
+ raise FraimeAuthError(response.text or "Invalid or missing API key")
27
+ if response.status_code >= 400:
28
+ raise FraimeAPIError(response.status_code, response.text)
29
+
30
+ return response.json()
@@ -0,0 +1,14 @@
1
+ from fraime.model import GenerateVideoRequest, GenerateVideoResponse
2
+ from fraime.repository import GenerationRepository
3
+
4
+
5
+ class GenerationService:
6
+ def __init__(self, repository: GenerationRepository):
7
+ self._repository = repository
8
+
9
+ def generate_video(self, request: GenerateVideoRequest) -> GenerateVideoResponse:
10
+ payload = request.model_dump(mode="json", exclude_none=True, exclude={"fields"})
11
+ payload["fields"] = request.fields.model_dump(mode="json", exclude_none=True)
12
+
13
+ raw = self._repository.post_generate(payload)
14
+ return GenerateVideoResponse.model_validate(raw)
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: fraime-sdk
3
+ Version: 1.0.0
4
+ Summary: Python SDK for the Fraime video generation API
5
+ Author: Santiago Melo Medina
6
+ License-Expression: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.11
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: httpx
14
+ Requires-Dist: pydantic
15
+ Provides-Extra: dev
16
+ Requires-Dist: build; extra == "dev"
17
+ Requires-Dist: twine; extra == "dev"
18
+ Dynamic: license-file
19
+
20
+ # Fraime SDK
21
+
22
+ Python client for the [Fraime API](../api/README.md) — build a request with
23
+ typed models/enums instead of hand-writing JSON, point it at a running
24
+ Fraime instance, get a generated video back.
25
+
26
+ ## Prerequisites
27
+
28
+ - Python 3.11+
29
+ - A running Fraime API instance to talk to (see [`api/README.md`](../api/README.md)
30
+ for how to run one) — you'll need its base URL, and its API key if one is
31
+ configured (`AUTH_API_KEY` on the API side).
32
+
33
+ ## Install
34
+
35
+ ### Option 1 — pip
36
+
37
+ ```bash
38
+ pip install fraime-sdk
39
+ ```
40
+
41
+ ### Option 2 — from a local clone
42
+
43
+ Step by step, from scratch:
44
+
45
+ ```bash
46
+ # 1. Clone the repo (skip if you already have it)
47
+ git clone <this-repo-url>
48
+ cd fraime
49
+
50
+ # 2. (Recommended) create a virtualenv for your own project
51
+ python3 -m venv .venv
52
+ source .venv/bin/activate
53
+
54
+ # 3. Install the SDK from the sdk/ folder
55
+ pip install ./sdk
56
+ # or, for local development on the SDK itself (editable install):
57
+ pip install -e ./sdk
58
+ ```
59
+
60
+ That's it — `import fraime` is now available in that environment.
61
+
62
+ ### Option 3 — straight from git, no local clone needed
63
+
64
+ ```bash
65
+ pip install "git+ssh://git@santiago/santiagoMeloMedina/fraime.git#subdirectory=sdk"
66
+ ```
67
+
68
+ (Adjust the URL to whatever remote you actually have push/pull access to —
69
+ this is this repo's own `origin` in SSH form.)
70
+
71
+ ## Usage
72
+
73
+ ```python
74
+ from fraime import FraimeClient, VideoType, GenerationParams, CinematicPromptFields
75
+
76
+ client = FraimeClient(
77
+ base_url="http://127.0.0.1:8000", # or set FRAIME_BASE_URL instead
78
+ api_key="your-api-key", # or set FRAIME_API_KEY instead; omit both if the API has none configured
79
+ )
80
+
81
+ response = client.generate(
82
+ video_type=VideoType.PIXAR,
83
+ fields=CinematicPromptFields(
84
+ subject="a small orange fox with oversized ears",
85
+ action="hops between rocks, pauses, and looks up curiously",
86
+ scene="a sunlit forest clearing at golden hour",
87
+ camera="medium shot, slow dolly-in",
88
+ lighting="warm rim lighting from the low sun",
89
+ style="3D animated feature style, stylized proportions, warm rim lighting",
90
+ ),
91
+ params=GenerationParams(duration_s=3, fps=16, resolution="768x512"),
92
+ # model=... # optional: pin an exact model instead of auto-selecting
93
+ # references=[...] # optional: Reference(url=...) list, for image-to-video
94
+ )
95
+
96
+ print(response.video_path, response.model)
97
+ ```
98
+
99
+ `model` is optional on `client.generate()` — omit it and the API auto-selects
100
+ by hardware, same as calling it directly.
101
+
102
+ ### Picking the right fields class per video type
103
+
104
+ Every `video_type` has its own field set — some add fields the base six
105
+ (`subject`, `action`, `scene`, `camera`, `lighting`, `style`,
106
+ `negative_prompt`) don't cover:
107
+
108
+ | `VideoType` | Fields class | Extra fields |
109
+ |---|---|---|
110
+ | `PIXAR`, `ACTION`, `ANIMATION`, `ANIME`, `DOCUMENTARY`, `FASHION` | `CinematicPromptFields` | — |
111
+ | `UGC_PRODUCT_REVIEW`, `COMMERCIAL_PRODUCT_AD`, `EXPLAINER_TESTIMONIAL` | `UGCPromptFields` | `dialogue`, `reference_image` |
112
+ | `PRESENTER_AVATAR` | `PresenterPromptFields` | + `voice_tone` |
113
+ | `SOCIAL_SHORT_FORM_AD` | `SocialAdPromptFields` | + `text_overlay`, `aspect_ratio` |
114
+ | `MUSIC_VIDEO` | `MusicVideoPromptFields` | `audio_reference`, `tempo_bpm` |
115
+ | `MOTION_GRAPHICS` | `MotionGraphicsPromptFields` | `text_content`, `transitions` |
116
+
117
+ Not sure which class a given `VideoType` needs? Look it up instead of
118
+ guessing:
119
+
120
+ ```python
121
+ from fraime import PROMPT_FIELDS_BY_VIDEO_TYPE, VideoType
122
+
123
+ fields_class = PROMPT_FIELDS_BY_VIDEO_TYPE[VideoType.SOCIAL_SHORT_FORM_AD]
124
+ # -> SocialAdPromptFields
125
+ ```
126
+
127
+ ### Reference images (image-to-video)
128
+
129
+ ```python
130
+ from fraime import Reference
131
+
132
+ response = client.generate(
133
+ video_type=VideoType.UGC_PRODUCT_REVIEW,
134
+ fields=ugc_fields,
135
+ params=params,
136
+ references=[Reference(url="https://example.com/product-photo.jpg")],
137
+ )
138
+ ```
139
+
140
+ ### Error handling
141
+
142
+ ```python
143
+ from fraime import FraimeAuthError, FraimeAPIError, FraimeConnectionError
144
+
145
+ try:
146
+ response = client.generate(video_type=VideoType.PIXAR, fields=fields, params=params)
147
+ except FraimeAuthError:
148
+ ... # missing/invalid API key
149
+ except FraimeAPIError as e:
150
+ ... # e.status_code, e.detail — the API reached but returned an error
151
+ except FraimeConnectionError:
152
+ ... # couldn't reach the API at all
153
+ ```
154
+
155
+ ## Configuration reference
156
+
157
+ | `FraimeClient(...)` argument | Env var fallback | Default |
158
+ |---|---|---|
159
+ | `base_url` | `FRAIME_BASE_URL` | `http://127.0.0.1:8000` |
160
+ | `api_key` | `FRAIME_API_KEY` | none (open API) |
161
+ | `timeout` | — | `600.0` seconds |
162
+
163
+ `timeout` defaults high on purpose — real generation runs can take several
164
+ minutes; see [`api/README.md`](../api/README.md) for why.
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ fraime/__init__.py
5
+ fraime/exceptions.py
6
+ fraime/main.py
7
+ fraime/model.py
8
+ fraime/repository.py
9
+ fraime/service.py
10
+ fraime_sdk.egg-info/PKG-INFO
11
+ fraime_sdk.egg-info/SOURCES.txt
12
+ fraime_sdk.egg-info/dependency_links.txt
13
+ fraime_sdk.egg-info/requires.txt
14
+ fraime_sdk.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ httpx
2
+ pydantic
3
+
4
+ [dev]
5
+ build
6
+ twine
@@ -0,0 +1 @@
1
+ fraime
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fraime-sdk"
7
+ version = "1.0.0"
8
+ description = "Python SDK for the Fraime video generation API"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.11"
13
+ authors = [
14
+ { name = "Santiago Melo Medina" },
15
+ ]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Operating System :: OS Independent",
20
+ ]
21
+ dependencies = [
22
+ "httpx",
23
+ "pydantic",
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ dev = [
28
+ "build",
29
+ "twine",
30
+ ]
31
+
32
+ [tool.setuptools]
33
+ packages = ["fraime"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+