fotor-sdk 0.1.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 @@
1
+ FOTOR_OPENAPI_KEY="your-api-key"
@@ -0,0 +1,29 @@
1
+ .env
2
+
3
+ __pycache__/
4
+ *.py[cod]
5
+ *.pyo
6
+
7
+ *.egg-info/
8
+ *.egg
9
+ dist/
10
+ build/
11
+ .eggs/
12
+
13
+ *.so
14
+
15
+ .venv/
16
+ venv/
17
+ env/
18
+
19
+ .idea/
20
+ .vscode/
21
+ *.swp
22
+ *.swo
23
+ *~
24
+
25
+ .mypy_cache/
26
+ .ruff_cache/
27
+ .pytest_cache/
28
+
29
+ *.log
@@ -0,0 +1,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: fotor-sdk
3
+ Version: 0.1.0
4
+ Summary: Lightweight async Python SDK for the Fotor OpenAPI
5
+ Project-URL: Homepage, https://github.com/zeng121/fotor-sdk
6
+ Project-URL: Documentation, https://github.com/zeng121/fotor-sdk#readme
7
+ Project-URL: Issues, https://github.com/zeng121/fotor-sdk/issues
8
+ License-Expression: MIT
9
+ Keywords: ai,async,fotor,image-generation,video-generation
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Multimedia :: Graphics
20
+ Classifier: Topic :: Multimedia :: Video
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: aiohttp>=3.9
23
+ Requires-Dist: python-dotenv>=1.0
24
+ Description-Content-Type: text/markdown
25
+
26
+ # fotor-sdk
27
+
28
+ Lightweight, async-first Python SDK for the [Fotor OpenAPI](https://api.fotor.com).
29
+ Generate images and videos with a single API key -- no MCP server, no S3, no
30
+ internal services required.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install -e .
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ### Single Task
41
+
42
+ ```python
43
+ import asyncio
44
+ import os
45
+ from fotor_sdk import FotorClient, text2image
46
+
47
+ async def main():
48
+ client = FotorClient(api_key=os.environ["FOTOR_OPENAPI_KEY"])
49
+ result = await text2image(
50
+ client,
51
+ prompt="A diamond kitten on velvet, studio lighting",
52
+ model_id="seedream-4-5-251128",
53
+ resolution="2k",
54
+ aspect_ratio="1:1",
55
+ )
56
+ print(result.result_url)
57
+
58
+ asyncio.run(main())
59
+ ```
60
+
61
+ ### Parallel Batch with Progress
62
+
63
+ ```python
64
+ import asyncio
65
+ import os
66
+ from fotor_sdk import FotorClient, TaskRunner, TaskSpec
67
+
68
+ async def main():
69
+ client = FotorClient(api_key=os.environ["FOTOR_OPENAPI_KEY"])
70
+ runner = TaskRunner(client, max_concurrent=5)
71
+
72
+ specs = [
73
+ TaskSpec("text2image", {"prompt": "A cat", "model_id": "seedream-4-5-251128"}, tag="cat"),
74
+ TaskSpec("text2image", {"prompt": "A dog", "model_id": "seedream-4-5-251128"}, tag="dog"),
75
+ TaskSpec("text2video", {"prompt": "Sunset", "model_id": "kling-v3", "duration": 5}, tag="sunset"),
76
+ ]
77
+
78
+ def on_progress(total, completed, failed, in_progress, latest):
79
+ print(f" {completed + failed}/{total} done, latest: {latest.metadata.get('tag')}")
80
+
81
+ results = await runner.run(specs, on_progress=on_progress)
82
+ for r in results:
83
+ print(f"{r.metadata.get('tag')}: {r.status.name} -> {r.result_url}")
84
+
85
+ asyncio.run(main())
86
+ ```
87
+
88
+ ## Configuration
89
+
90
+ | Environment Variable | Required | Default | Description |
91
+ |---|---|---|---|
92
+ | `FOTOR_OPENAPI_KEY` | Yes | -- | Your Fotor OpenAPI key |
93
+ | `FOTOR_OPENAPI_ENDPOINT` | No | `https://api.fotor.com` | API base URL |
94
+
95
+ ## Available Task Functions
96
+
97
+ | Function | Description |
98
+ |---|---|
99
+ | `text2image()` | Generate image from text |
100
+ | `image2image()` | Edit / multi-reference generation |
101
+ | `image_upscale()` | 2x or 4x upscale |
102
+ | `background_remove()` | Remove background |
103
+ | `text2video()` | Generate video from text |
104
+ | `single_image2video()` | Animate a single image |
105
+ | `start_end_frame2video()` | Interpolate between two frames |
106
+ | `multiple_image2video()` | Video from multiple images |
107
+
108
+ ## Core Classes
109
+
110
+ ### FotorClient
111
+
112
+ ```python
113
+ FotorClient(
114
+ api_key: str,
115
+ endpoint: str = "https://api.fotor.com",
116
+ poll_interval: float = 2.0,
117
+ max_poll_seconds: float = 1200,
118
+ )
119
+ ```
120
+
121
+ **Methods:**
122
+ - `await create_task(path, payload) -> task_id`
123
+ - `await get_task_status(task_id) -> TaskResult`
124
+ - `await wait_for_task(task_id) -> TaskResult`
125
+ - `await submit_and_wait(path, payload) -> TaskResult`
126
+ - `submit_and_wait_sync(path, payload) -> TaskResult`
127
+
128
+ ### TaskRunner
129
+
130
+ ```python
131
+ TaskRunner(client: FotorClient, max_concurrent: int = 5)
132
+ ```
133
+
134
+ - `await run(specs, on_progress=None) -> list[TaskResult]`
135
+ - `run_sync(specs, on_progress=None) -> list[TaskResult]`
136
+
137
+ ### TaskResult
138
+
139
+ ```python
140
+ TaskResult(task_id, status, result_url, error, elapsed_seconds, metadata)
141
+ result.success # True when COMPLETED with a result_url
142
+ ```
143
+
144
+ ### TaskSpec
145
+
146
+ ```python
147
+ TaskSpec(task_type: str, params: dict, tag: str = "")
148
+ ```
149
+
150
+ ## Error Handling
151
+
152
+ ```python
153
+ from fotor_sdk import FotorAPIError
154
+
155
+ try:
156
+ result = await text2image(client, prompt="...", model_id="bad-model")
157
+ except FotorAPIError as e:
158
+ print(f"API error: {e} code={e.code}")
159
+ ```
160
+
161
+ For batch runs, failed tasks appear in results with `status=FAILED` and the
162
+ `error` field populated. The runner never raises on individual task failures.
163
+
164
+ ## License
165
+
166
+ MIT
@@ -0,0 +1,141 @@
1
+ # fotor-sdk
2
+
3
+ Lightweight, async-first Python SDK for the [Fotor OpenAPI](https://api.fotor.com).
4
+ Generate images and videos with a single API key -- no MCP server, no S3, no
5
+ internal services required.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install -e .
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ### Single Task
16
+
17
+ ```python
18
+ import asyncio
19
+ import os
20
+ from fotor_sdk import FotorClient, text2image
21
+
22
+ async def main():
23
+ client = FotorClient(api_key=os.environ["FOTOR_OPENAPI_KEY"])
24
+ result = await text2image(
25
+ client,
26
+ prompt="A diamond kitten on velvet, studio lighting",
27
+ model_id="seedream-4-5-251128",
28
+ resolution="2k",
29
+ aspect_ratio="1:1",
30
+ )
31
+ print(result.result_url)
32
+
33
+ asyncio.run(main())
34
+ ```
35
+
36
+ ### Parallel Batch with Progress
37
+
38
+ ```python
39
+ import asyncio
40
+ import os
41
+ from fotor_sdk import FotorClient, TaskRunner, TaskSpec
42
+
43
+ async def main():
44
+ client = FotorClient(api_key=os.environ["FOTOR_OPENAPI_KEY"])
45
+ runner = TaskRunner(client, max_concurrent=5)
46
+
47
+ specs = [
48
+ TaskSpec("text2image", {"prompt": "A cat", "model_id": "seedream-4-5-251128"}, tag="cat"),
49
+ TaskSpec("text2image", {"prompt": "A dog", "model_id": "seedream-4-5-251128"}, tag="dog"),
50
+ TaskSpec("text2video", {"prompt": "Sunset", "model_id": "kling-v3", "duration": 5}, tag="sunset"),
51
+ ]
52
+
53
+ def on_progress(total, completed, failed, in_progress, latest):
54
+ print(f" {completed + failed}/{total} done, latest: {latest.metadata.get('tag')}")
55
+
56
+ results = await runner.run(specs, on_progress=on_progress)
57
+ for r in results:
58
+ print(f"{r.metadata.get('tag')}: {r.status.name} -> {r.result_url}")
59
+
60
+ asyncio.run(main())
61
+ ```
62
+
63
+ ## Configuration
64
+
65
+ | Environment Variable | Required | Default | Description |
66
+ |---|---|---|---|
67
+ | `FOTOR_OPENAPI_KEY` | Yes | -- | Your Fotor OpenAPI key |
68
+ | `FOTOR_OPENAPI_ENDPOINT` | No | `https://api.fotor.com` | API base URL |
69
+
70
+ ## Available Task Functions
71
+
72
+ | Function | Description |
73
+ |---|---|
74
+ | `text2image()` | Generate image from text |
75
+ | `image2image()` | Edit / multi-reference generation |
76
+ | `image_upscale()` | 2x or 4x upscale |
77
+ | `background_remove()` | Remove background |
78
+ | `text2video()` | Generate video from text |
79
+ | `single_image2video()` | Animate a single image |
80
+ | `start_end_frame2video()` | Interpolate between two frames |
81
+ | `multiple_image2video()` | Video from multiple images |
82
+
83
+ ## Core Classes
84
+
85
+ ### FotorClient
86
+
87
+ ```python
88
+ FotorClient(
89
+ api_key: str,
90
+ endpoint: str = "https://api.fotor.com",
91
+ poll_interval: float = 2.0,
92
+ max_poll_seconds: float = 1200,
93
+ )
94
+ ```
95
+
96
+ **Methods:**
97
+ - `await create_task(path, payload) -> task_id`
98
+ - `await get_task_status(task_id) -> TaskResult`
99
+ - `await wait_for_task(task_id) -> TaskResult`
100
+ - `await submit_and_wait(path, payload) -> TaskResult`
101
+ - `submit_and_wait_sync(path, payload) -> TaskResult`
102
+
103
+ ### TaskRunner
104
+
105
+ ```python
106
+ TaskRunner(client: FotorClient, max_concurrent: int = 5)
107
+ ```
108
+
109
+ - `await run(specs, on_progress=None) -> list[TaskResult]`
110
+ - `run_sync(specs, on_progress=None) -> list[TaskResult]`
111
+
112
+ ### TaskResult
113
+
114
+ ```python
115
+ TaskResult(task_id, status, result_url, error, elapsed_seconds, metadata)
116
+ result.success # True when COMPLETED with a result_url
117
+ ```
118
+
119
+ ### TaskSpec
120
+
121
+ ```python
122
+ TaskSpec(task_type: str, params: dict, tag: str = "")
123
+ ```
124
+
125
+ ## Error Handling
126
+
127
+ ```python
128
+ from fotor_sdk import FotorAPIError
129
+
130
+ try:
131
+ result = await text2image(client, prompt="...", model_id="bad-model")
132
+ except FotorAPIError as e:
133
+ print(f"API error: {e} code={e.code}")
134
+ ```
135
+
136
+ For batch runs, failed tasks appear in results with `status=FAILED` and the
137
+ `error` field populated. The runner never raises on individual task failures.
138
+
139
+ ## License
140
+
141
+ MIT
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env python3
2
+ """Example: run multiple Fotor image/video tasks in parallel with live progress.
3
+
4
+ Usage:
5
+ export FOTOR_OPENAPI_KEY="your-api-key"
6
+ python fotor_sdk/scripts/parallel_generate.py
7
+
8
+ Optionally set FOTOR_OPENAPI_ENDPOINT to point at a different API host.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+ import logging
15
+ import os
16
+ import time
17
+
18
+ from fotor_sdk import (
19
+ FotorClient,
20
+ TaskRunner,
21
+ TaskSpec,
22
+ TaskResult,
23
+ TaskStatus,
24
+ text2image,
25
+ )
26
+
27
+ logging.basicConfig(
28
+ level=logging.INFO,
29
+ format="%(asctime)s %(levelname)-7s %(message)s",
30
+ datefmt="%H:%M:%S",
31
+ )
32
+ log = logging.getLogger("example")
33
+
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # 1. Single task -- simplest usage
37
+ # ---------------------------------------------------------------------------
38
+
39
+ async def single_task_demo(client: FotorClient) -> None:
40
+ log.info("=== Single task demo ===")
41
+ result = await text2image(
42
+ client,
43
+ prompt="A diamond kitten on a velvet cushion, studio lighting, 4K",
44
+ model_id="seedream-4-5-251128",
45
+ resolution="1k",
46
+ aspect_ratio="1:1",
47
+ on_poll=lambda r: log.info(" polling %s status=%s %.1fs",
48
+ r.task_id, r.status.name, r.elapsed_seconds),
49
+ )
50
+ log.info("Result: %s", result)
51
+
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # 2. Parallel batch -- the main use-case
55
+ # ---------------------------------------------------------------------------
56
+
57
+ def progress_callback(
58
+ total: int,
59
+ completed: int,
60
+ failed: int,
61
+ in_progress: int,
62
+ latest: TaskResult,
63
+ ) -> None:
64
+ bar_len = 30
65
+ done_ratio = (completed + failed) / total if total else 0
66
+ filled = int(bar_len * done_ratio)
67
+ bar = "#" * filled + "-" * (bar_len - filled)
68
+ tag = latest.metadata.get("tag", latest.task_id)
69
+ status = "OK" if latest.success else latest.status.name
70
+ print(
71
+ f"\r[{bar}] {completed + failed}/{total} "
72
+ f"(ok={completed} fail={failed} run={in_progress}) "
73
+ f"latest: {tag} -> {status} ",
74
+ end="",
75
+ flush=True,
76
+ )
77
+
78
+
79
+ async def batch_demo(client: FotorClient) -> None:
80
+ log.info("=== Parallel batch demo ===")
81
+
82
+ specs = [
83
+ TaskSpec(
84
+ task_type="text2image",
85
+ params={
86
+ "prompt": "A cyberpunk city skyline at sunset, neon lights, 8K",
87
+ "model_id": "seedream-4-5-251128",
88
+ "resolution": "1k",
89
+ "aspect_ratio": "16:9",
90
+ },
91
+ tag="cyberpunk-city",
92
+ ),
93
+ TaskSpec(
94
+ task_type="text2image",
95
+ params={
96
+ "prompt": "A serene Japanese garden in autumn, koi pond, golden leaves",
97
+ "model_id": "seedream-4-5-251128",
98
+ "resolution": "1k",
99
+ "aspect_ratio": "1:1",
100
+ },
101
+ tag="zen-garden",
102
+ ),
103
+ TaskSpec(
104
+ task_type="text2video",
105
+ params={
106
+ "prompt": "Ocean waves crashing on a rocky shore, cinematic drone shot",
107
+ "model_id": "kling-v3",
108
+ "duration": 5,
109
+ "resolution": "1080p",
110
+ "aspect_ratio": "16:9",
111
+ },
112
+ tag="ocean-waves",
113
+ ),
114
+ ]
115
+
116
+ runner = TaskRunner(client, max_concurrent=5)
117
+ start = time.monotonic()
118
+ results = await runner.run(specs, on_progress=progress_callback)
119
+ print() # newline after progress bar
120
+
121
+ elapsed = time.monotonic() - start
122
+ log.info("Batch finished in %.1fs", elapsed)
123
+ for r in results:
124
+ tag = r.metadata.get("tag", r.task_id)
125
+ if r.success:
126
+ log.info(" [OK] %-20s -> %s (%.1fs)", tag, r.result_url, r.elapsed_seconds)
127
+ else:
128
+ log.info(" [FAIL] %-20s -> %s (%.1fs)", tag, r.error, r.elapsed_seconds)
129
+
130
+
131
+ # ---------------------------------------------------------------------------
132
+ # Main
133
+ # ---------------------------------------------------------------------------
134
+
135
+ async def main() -> None:
136
+ api_key = os.environ.get("FOTOR_OPENAPI_KEY", "")
137
+ endpoint = os.environ.get("FOTOR_OPENAPI_ENDPOINT", "https://api.fotor.com")
138
+
139
+ if not api_key:
140
+ print("ERROR: set FOTOR_OPENAPI_KEY environment variable first", file=sys.stderr)
141
+ sys.exit(1)
142
+
143
+ client = FotorClient(api_key=api_key, endpoint=endpoint)
144
+
145
+ await single_task_demo(client)
146
+ await batch_demo(client)
147
+
148
+
149
+ if __name__ == "__main__":
150
+ asyncio.run(main())