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.
- fotor_sdk-0.1.0/.env.example +1 -0
- fotor_sdk-0.1.0/.gitignore +29 -0
- fotor_sdk-0.1.0/PKG-INFO +166 -0
- fotor_sdk-0.1.0/README.md +141 -0
- fotor_sdk-0.1.0/examples/parallel_generate.py +150 -0
- fotor_sdk-0.1.0/examples/test_all_features.py +460 -0
- fotor_sdk-0.1.0/pyproject.toml +31 -0
- fotor_sdk-0.1.0/src/fotor_sdk/__init__.py +34 -0
- fotor_sdk-0.1.0/src/fotor_sdk/client.py +175 -0
- fotor_sdk-0.1.0/src/fotor_sdk/models.py +42 -0
- fotor_sdk-0.1.0/src/fotor_sdk/runner.py +159 -0
- fotor_sdk-0.1.0/src/fotor_sdk/tasks.py +248 -0
|
@@ -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
|
fotor_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -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())
|