imagepipeline 0.3.0__py3-none-any.whl
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.
- imagepipeline/__init__.py +7 -0
- imagepipeline/_transport.py +114 -0
- imagepipeline/client.py +88 -0
- imagepipeline/exceptions.py +45 -0
- imagepipeline/models.py +111 -0
- imagepipeline/py.typed +0 -0
- imagepipeline/resources/__init__.py +23 -0
- imagepipeline/resources/generate.py +179 -0
- imagepipeline/resources/identity.py +334 -0
- imagepipeline/resources/misc.py +468 -0
- imagepipeline-0.3.0.dist-info/METADATA +280 -0
- imagepipeline-0.3.0.dist-info/RECORD +14 -0
- imagepipeline-0.3.0.dist-info/WHEEL +4 -0
- imagepipeline-0.3.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""ImagePipeline Python SDK"""
|
|
2
|
+
|
|
3
|
+
from .client import ImagePipeline
|
|
4
|
+
from .models import Job, JobStatus, SegmentItem, SegmentResult, UploadResult
|
|
5
|
+
|
|
6
|
+
__all__ = ["ImagePipeline", "Job", "JobStatus", "UploadResult", "SegmentItem", "SegmentResult"]
|
|
7
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Low-level HTTP transport shared by all resource classes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any, Dict, IO, Optional
|
|
7
|
+
|
|
8
|
+
import requests as _requests
|
|
9
|
+
|
|
10
|
+
from .exceptions import (
|
|
11
|
+
APIError,
|
|
12
|
+
AuthenticationError,
|
|
13
|
+
JobFailedError,
|
|
14
|
+
JobTimeoutError,
|
|
15
|
+
RateLimitError,
|
|
16
|
+
)
|
|
17
|
+
from .models import Job, JobStatus
|
|
18
|
+
|
|
19
|
+
_DEFAULT_BASE_URL = "https://api.imagepipeline.io"
|
|
20
|
+
_DEFAULT_POLL_INTERVAL = 3 # seconds
|
|
21
|
+
_DEFAULT_TIMEOUT = 300 # seconds
|
|
22
|
+
_SDK_VERSION = "0.3.0"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _Transport:
|
|
26
|
+
"""Wraps requests.Session with auth headers and error handling."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, api_key: str, base_url: str, timeout: int):
|
|
29
|
+
self._base_url = base_url.rstrip("/")
|
|
30
|
+
self._timeout = timeout
|
|
31
|
+
self._session = _requests.Session()
|
|
32
|
+
self._session.headers.update({
|
|
33
|
+
"X-API-Key": api_key,
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
"User-Agent": f"imagepipeline-python/{_SDK_VERSION}",
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
def post(self, path: str, body: dict) -> dict:
|
|
39
|
+
url = f"{self._base_url}{path}"
|
|
40
|
+
resp = self._session.post(url, json=body, timeout=self._timeout)
|
|
41
|
+
return self._handle(resp)
|
|
42
|
+
|
|
43
|
+
def get(self, path: str) -> dict:
|
|
44
|
+
url = f"{self._base_url}{path}"
|
|
45
|
+
resp = self._session.get(url, timeout=self._timeout)
|
|
46
|
+
return self._handle(resp)
|
|
47
|
+
|
|
48
|
+
def post_file(self, path: str, file_obj: IO[bytes], filename: str, content_type: str) -> dict:
|
|
49
|
+
"""POST multipart/form-data — used by the upload endpoint."""
|
|
50
|
+
url = f"{self._base_url}{path}"
|
|
51
|
+
# Remove Content-Type so requests sets multipart boundary automatically
|
|
52
|
+
headers = {k: v for k, v in self._session.headers.items() if k.lower() != "content-type"}
|
|
53
|
+
resp = self._session.post(
|
|
54
|
+
url,
|
|
55
|
+
files={"file": (filename, file_obj, content_type)},
|
|
56
|
+
headers=headers,
|
|
57
|
+
timeout=self._timeout,
|
|
58
|
+
)
|
|
59
|
+
return self._handle(resp)
|
|
60
|
+
|
|
61
|
+
def _handle(self, resp: _requests.Response) -> dict:
|
|
62
|
+
if resp.status_code == 401:
|
|
63
|
+
raise AuthenticationError("Invalid or missing API key.")
|
|
64
|
+
if resp.status_code == 429:
|
|
65
|
+
retry_after = int(resp.headers.get("Retry-After", 60))
|
|
66
|
+
raise RateLimitError("Rate limit exceeded.", retry_after=retry_after)
|
|
67
|
+
if resp.status_code == 204:
|
|
68
|
+
return {}
|
|
69
|
+
if not resp.ok:
|
|
70
|
+
try:
|
|
71
|
+
detail = resp.json().get("detail") or resp.text
|
|
72
|
+
except Exception:
|
|
73
|
+
detail = resp.text
|
|
74
|
+
raise APIError(resp.status_code, detail)
|
|
75
|
+
return resp.json()
|
|
76
|
+
|
|
77
|
+
def submit_and_poll(
|
|
78
|
+
self,
|
|
79
|
+
endpoint: str,
|
|
80
|
+
body: dict,
|
|
81
|
+
poll_interval: int = _DEFAULT_POLL_INTERVAL,
|
|
82
|
+
timeout: int = _DEFAULT_TIMEOUT,
|
|
83
|
+
) -> Job:
|
|
84
|
+
"""POST to endpoint, then poll /status/{job_id} until done."""
|
|
85
|
+
data = self.post(f"/{endpoint}", body)
|
|
86
|
+
job_id = data["job_id"]
|
|
87
|
+
return self.poll(endpoint, job_id, poll_interval=poll_interval, timeout=timeout)
|
|
88
|
+
|
|
89
|
+
def submit(self, endpoint: str, body: dict) -> Job:
|
|
90
|
+
"""POST to endpoint and return immediately without polling."""
|
|
91
|
+
data = self.post(f"/{endpoint}", body)
|
|
92
|
+
return Job(
|
|
93
|
+
job_id=data["job_id"],
|
|
94
|
+
status=JobStatus(data.get("status", "queued")),
|
|
95
|
+
endpoint=endpoint,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def poll(
|
|
99
|
+
self,
|
|
100
|
+
endpoint: str,
|
|
101
|
+
job_id: str,
|
|
102
|
+
poll_interval: int = _DEFAULT_POLL_INTERVAL,
|
|
103
|
+
timeout: int = _DEFAULT_TIMEOUT,
|
|
104
|
+
) -> Job:
|
|
105
|
+
deadline = time.monotonic() + timeout
|
|
106
|
+
while time.monotonic() < deadline:
|
|
107
|
+
data = self.get(f"/{endpoint}/status/{job_id}")
|
|
108
|
+
status = JobStatus(data.get("status", "queued"))
|
|
109
|
+
if status == JobStatus.COMPLETED:
|
|
110
|
+
return Job._from_status_response(data, endpoint)
|
|
111
|
+
if status == JobStatus.FAILED:
|
|
112
|
+
raise JobFailedError(job_id, data.get("error"))
|
|
113
|
+
time.sleep(poll_interval)
|
|
114
|
+
raise JobTimeoutError(job_id, timeout)
|
imagepipeline/client.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Top-level ImagePipeline client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ._transport import _Transport
|
|
6
|
+
from .resources import (
|
|
7
|
+
BackgroundResource,
|
|
8
|
+
BrandingResource,
|
|
9
|
+
EditResource,
|
|
10
|
+
GenerateResource,
|
|
11
|
+
IdentityResource,
|
|
12
|
+
SegmentResource,
|
|
13
|
+
UploadResource,
|
|
14
|
+
UpscaleResource,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_DEFAULT_BASE_URL = "https://api.imagepipeline.io"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ImagePipeline:
|
|
21
|
+
"""Client for the ImagePipeline API.
|
|
22
|
+
|
|
23
|
+
Usage::
|
|
24
|
+
|
|
25
|
+
from imagepipeline import ImagePipeline
|
|
26
|
+
|
|
27
|
+
ip = ImagePipeline("ip_live_xxxxxxxxxxxx")
|
|
28
|
+
|
|
29
|
+
# Generate an image
|
|
30
|
+
result = ip.generate.image(prompt="sunset over tokyo", width=1024, height=1024)
|
|
31
|
+
print(result.url)
|
|
32
|
+
|
|
33
|
+
# Upload a local file, then edit it
|
|
34
|
+
upload = ip.upload.image("product.png")
|
|
35
|
+
job = ip.edit.image(
|
|
36
|
+
input_image=[person_url, upload.url],
|
|
37
|
+
prompt="dress the person in the product from image 2",
|
|
38
|
+
mask_segment="upper-clothes",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Virtual try-on
|
|
42
|
+
result = ip.identity.tryon(
|
|
43
|
+
person_image="https://.../person.jpg",
|
|
44
|
+
clothing_image="https://.../shirt.jpg",
|
|
45
|
+
gender="woman",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Segment an image to find label names
|
|
49
|
+
seg = ip.segment.image("https://.../person.jpg")
|
|
50
|
+
print(seg.segments) # [SegmentItem(label='upper-clothes', display='Top / Shirt'), ...]
|
|
51
|
+
|
|
52
|
+
# Background change (subject is automatically preserved)
|
|
53
|
+
result = ip.background.change(
|
|
54
|
+
input_image="https://.../photo.jpg",
|
|
55
|
+
prompt="tropical beach at sunset",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Manage identity profiles
|
|
59
|
+
profile = ip.identity.create_profile(
|
|
60
|
+
name="Brand Model",
|
|
61
|
+
prompt_template="{{ user_prompt }}, Caucasian woman, 20s, blue eyes",
|
|
62
|
+
seed_strategy="fixed",
|
|
63
|
+
fixed_seed=42,
|
|
64
|
+
)
|
|
65
|
+
print(profile["profile_id"])
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
api_key: Your API key (starts with ``ip_live_``).
|
|
69
|
+
base_url: Override the API base URL (useful for staging environments).
|
|
70
|
+
timeout: HTTP request timeout in seconds.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
api_key: str,
|
|
76
|
+
base_url: str = _DEFAULT_BASE_URL,
|
|
77
|
+
timeout: int = 30,
|
|
78
|
+
):
|
|
79
|
+
self._transport = _Transport(api_key=api_key, base_url=base_url, timeout=timeout)
|
|
80
|
+
|
|
81
|
+
self.generate = GenerateResource(self._transport)
|
|
82
|
+
self.identity = IdentityResource(self._transport)
|
|
83
|
+
self.edit = EditResource(self._transport)
|
|
84
|
+
self.background = BackgroundResource(self._transport)
|
|
85
|
+
self.branding = BrandingResource(self._transport)
|
|
86
|
+
self.upscale = UpscaleResource(self._transport)
|
|
87
|
+
self.upload = UploadResource(self._transport)
|
|
88
|
+
self.segment = SegmentResource(self._transport)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Exceptions raised by the ImagePipeline SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ImagePipelineError(Exception):
|
|
7
|
+
"""Base exception for all SDK errors."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AuthenticationError(ImagePipelineError):
|
|
11
|
+
"""API key is missing or invalid (401)."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RateLimitError(ImagePipelineError):
|
|
15
|
+
"""Rate limit exceeded (429). Retry after the indicated delay."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, message: str, retry_after: int = 60):
|
|
18
|
+
super().__init__(message)
|
|
19
|
+
self.retry_after = retry_after
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class JobFailedError(ImagePipelineError):
|
|
23
|
+
"""The submitted job completed with status 'failed'."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, job_id: str, reason: str | None = None):
|
|
26
|
+
super().__init__(f"Job {job_id} failed: {reason or 'unknown error'}")
|
|
27
|
+
self.job_id = job_id
|
|
28
|
+
self.reason = reason
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class JobTimeoutError(ImagePipelineError):
|
|
32
|
+
"""Polling timed out before the job completed."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, job_id: str, timeout: int):
|
|
35
|
+
super().__init__(f"Job {job_id} did not complete within {timeout}s")
|
|
36
|
+
self.job_id = job_id
|
|
37
|
+
self.timeout = timeout
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class APIError(ImagePipelineError):
|
|
41
|
+
"""Unexpected HTTP error from the API."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, status_code: int, message: str):
|
|
44
|
+
super().__init__(f"HTTP {status_code}: {message}")
|
|
45
|
+
self.status_code = status_code
|
imagepipeline/models.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Core data models for the ImagePipeline SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class JobStatus(str, Enum):
|
|
11
|
+
QUEUED = "queued"
|
|
12
|
+
PENDING = "pending"
|
|
13
|
+
PROCESSING = "processing"
|
|
14
|
+
COMPLETED = "completed"
|
|
15
|
+
FAILED = "failed"
|
|
16
|
+
CANCELLED = "cancelled"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Job:
|
|
21
|
+
"""Represents a submitted or completed job."""
|
|
22
|
+
|
|
23
|
+
job_id: str
|
|
24
|
+
status: JobStatus
|
|
25
|
+
endpoint: str
|
|
26
|
+
result_url: Optional[str] = None
|
|
27
|
+
error: Optional[str] = None
|
|
28
|
+
|
|
29
|
+
# Progress
|
|
30
|
+
progress: Optional[int] = None
|
|
31
|
+
result_mime_type: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
# Timing
|
|
34
|
+
queue_wait_seconds: Optional[float] = None
|
|
35
|
+
inference_time_seconds: Optional[float] = None
|
|
36
|
+
total_elapsed_seconds: Optional[float] = None
|
|
37
|
+
estimated_time_seconds: Optional[int] = None
|
|
38
|
+
processing_time_seconds: Optional[float] = None
|
|
39
|
+
|
|
40
|
+
# Queue position (enterprise plans)
|
|
41
|
+
queue_position: Optional[int] = None
|
|
42
|
+
queue_metrics: Optional[Dict[str, Any]] = None
|
|
43
|
+
queue_metrics_hint: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
# Failure info
|
|
46
|
+
failure_reason_code: Optional[str] = None
|
|
47
|
+
retryable: Optional[bool] = None
|
|
48
|
+
warnings: Optional[List[str]] = None
|
|
49
|
+
prompt_hash: Optional[str] = None
|
|
50
|
+
|
|
51
|
+
# Billing
|
|
52
|
+
cost_usd: Optional[float] = None
|
|
53
|
+
balance_remaining_usd: Optional[float] = None
|
|
54
|
+
|
|
55
|
+
# Background removal specific
|
|
56
|
+
cutout_url: Optional[str] = None
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def url(self) -> Optional[str]:
|
|
60
|
+
"""Alias for result_url."""
|
|
61
|
+
return self.result_url
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def _from_status_response(cls, data: dict, endpoint: str) -> "Job":
|
|
65
|
+
return cls(
|
|
66
|
+
job_id=data.get("job_id", ""),
|
|
67
|
+
status=JobStatus(data.get("status", "queued")),
|
|
68
|
+
endpoint=endpoint,
|
|
69
|
+
result_url=data.get("result_url"),
|
|
70
|
+
error=data.get("error"),
|
|
71
|
+
progress=data.get("progress"),
|
|
72
|
+
result_mime_type=data.get("result_mime_type"),
|
|
73
|
+
queue_wait_seconds=data.get("queue_wait_seconds"),
|
|
74
|
+
inference_time_seconds=data.get("inference_time_seconds"),
|
|
75
|
+
total_elapsed_seconds=data.get("total_elapsed_seconds"),
|
|
76
|
+
estimated_time_seconds=data.get("estimated_time_seconds"),
|
|
77
|
+
processing_time_seconds=data.get("processing_time_seconds"),
|
|
78
|
+
queue_position=data.get("queue_position"),
|
|
79
|
+
queue_metrics=data.get("queue_metrics"),
|
|
80
|
+
queue_metrics_hint=data.get("queue_metrics_hint"),
|
|
81
|
+
failure_reason_code=data.get("failure_reason_code"),
|
|
82
|
+
retryable=data.get("retryable"),
|
|
83
|
+
warnings=data.get("warnings"),
|
|
84
|
+
prompt_hash=data.get("prompt_hash"),
|
|
85
|
+
cost_usd=data.get("cost_usd"),
|
|
86
|
+
balance_remaining_usd=data.get("balance_remaining_usd"),
|
|
87
|
+
cutout_url=data.get("cutout_url"),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class UploadResult:
|
|
93
|
+
"""Result of a file upload."""
|
|
94
|
+
url: str
|
|
95
|
+
filename: str
|
|
96
|
+
content_type: str
|
|
97
|
+
size_bytes: int
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class SegmentItem:
|
|
102
|
+
"""A single detected segment from segmentation."""
|
|
103
|
+
label: str
|
|
104
|
+
display: str
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class SegmentResult:
|
|
109
|
+
"""Result of image segmentation."""
|
|
110
|
+
preview_url: str
|
|
111
|
+
segments: List[SegmentItem]
|
imagepipeline/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Resource sub-package init."""
|
|
2
|
+
|
|
3
|
+
from .generate import GenerateResource
|
|
4
|
+
from .identity import IdentityResource
|
|
5
|
+
from .misc import (
|
|
6
|
+
BackgroundResource,
|
|
7
|
+
BrandingResource,
|
|
8
|
+
EditResource,
|
|
9
|
+
SegmentResource,
|
|
10
|
+
UploadResource,
|
|
11
|
+
UpscaleResource,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"GenerateResource",
|
|
16
|
+
"IdentityResource",
|
|
17
|
+
"EditResource",
|
|
18
|
+
"BackgroundResource",
|
|
19
|
+
"BrandingResource",
|
|
20
|
+
"UpscaleResource",
|
|
21
|
+
"UploadResource",
|
|
22
|
+
"SegmentResource",
|
|
23
|
+
]
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Generate namespace: image, video, speech, 3d."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
from .._transport import _Transport
|
|
8
|
+
from ..models import Job
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GenerateResource:
|
|
12
|
+
def __init__(self, transport: _Transport):
|
|
13
|
+
self._t = transport
|
|
14
|
+
|
|
15
|
+
def image(
|
|
16
|
+
self,
|
|
17
|
+
*,
|
|
18
|
+
prompt: str,
|
|
19
|
+
width: int = 1024,
|
|
20
|
+
height: int = 1024,
|
|
21
|
+
seed: int = -1,
|
|
22
|
+
output_format: str = "webp",
|
|
23
|
+
num_inference_steps: Optional[int] = None,
|
|
24
|
+
guidance_scale: Optional[float] = None,
|
|
25
|
+
enhance_prompt: bool = False,
|
|
26
|
+
logo_url: Optional[str] = None,
|
|
27
|
+
profile_id: Optional[str] = None,
|
|
28
|
+
callback_url: Optional[str] = None,
|
|
29
|
+
wait: bool = True,
|
|
30
|
+
) -> Job:
|
|
31
|
+
"""Generate an image from a text prompt using Z-Image Turbo.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
prompt: Text description of the image to generate.
|
|
35
|
+
width: Output width in pixels (max 1024).
|
|
36
|
+
height: Output height in pixels (max 1024).
|
|
37
|
+
seed: Reproducibility seed (-1 for random).
|
|
38
|
+
output_format: ``'webp'`` (default), ``'jpeg'``, or ``'png'``.
|
|
39
|
+
num_inference_steps: Diffusion steps (default 8). Higher = slower but sharper.
|
|
40
|
+
guidance_scale: CFG scale. Leave unset to use the model default (0.0 for Z-Image Turbo).
|
|
41
|
+
enhance_prompt: Run the prompt through a lightweight AI enhancer before generation.
|
|
42
|
+
Expands terse prompts into detailed visual descriptions. Adds ~1–2 s.
|
|
43
|
+
logo_url: Public URL of your company logo (PNG/WebP). Stamped at bottom-right at 50% opacity.
|
|
44
|
+
profile_id: Identity profile ID — applies the profile's prompt template and quality settings.
|
|
45
|
+
callback_url: Webhook URL. We POST a ``WebhookEvent`` on completion.
|
|
46
|
+
wait: If True (default), poll until complete and return a finished Job.
|
|
47
|
+
If False, return immediately with a QUEUED Job.
|
|
48
|
+
"""
|
|
49
|
+
body: dict = {
|
|
50
|
+
"prompt": prompt,
|
|
51
|
+
"width": width,
|
|
52
|
+
"height": height,
|
|
53
|
+
"seed": seed,
|
|
54
|
+
"output_format": output_format,
|
|
55
|
+
"enhance_prompt": enhance_prompt,
|
|
56
|
+
}
|
|
57
|
+
if num_inference_steps is not None:
|
|
58
|
+
body["num_inference_steps"] = num_inference_steps
|
|
59
|
+
if guidance_scale is not None:
|
|
60
|
+
body["guidance_scale"] = guidance_scale
|
|
61
|
+
if logo_url:
|
|
62
|
+
body["logo_url"] = logo_url
|
|
63
|
+
if profile_id:
|
|
64
|
+
body["profile_id"] = profile_id
|
|
65
|
+
if callback_url:
|
|
66
|
+
body["callback_url"] = callback_url
|
|
67
|
+
endpoint = "generate/image/v1"
|
|
68
|
+
return self._t.submit_and_poll(endpoint, body) if wait else self._t.submit(endpoint, body)
|
|
69
|
+
|
|
70
|
+
def video(
|
|
71
|
+
self,
|
|
72
|
+
*,
|
|
73
|
+
input_image: str,
|
|
74
|
+
prompt: str = "make this image come alive, cinematic motion, smooth animation",
|
|
75
|
+
width: int = 896,
|
|
76
|
+
height: int = 512,
|
|
77
|
+
duration_seconds: float = 2.0,
|
|
78
|
+
seed: int = 42,
|
|
79
|
+
callback_url: Optional[str] = None,
|
|
80
|
+
wait: bool = True,
|
|
81
|
+
) -> Job:
|
|
82
|
+
"""Generate a short video from an input image (image-to-video).
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
input_image: Public URL of the image to animate.
|
|
86
|
+
prompt: Animation style description.
|
|
87
|
+
width: Output width in pixels (max 1536, divisible by 32).
|
|
88
|
+
height: Output height in pixels (max 1536, divisible by 32).
|
|
89
|
+
duration_seconds: Video length in seconds (0.1–10.0).
|
|
90
|
+
seed: Reproducibility seed.
|
|
91
|
+
callback_url: Webhook URL. We POST a ``WebhookEvent`` on completion.
|
|
92
|
+
wait: Poll until complete if True.
|
|
93
|
+
"""
|
|
94
|
+
body: dict = {
|
|
95
|
+
"input_image": input_image,
|
|
96
|
+
"prompt": prompt,
|
|
97
|
+
"width": width,
|
|
98
|
+
"height": height,
|
|
99
|
+
"duration_seconds": duration_seconds,
|
|
100
|
+
"seed": seed,
|
|
101
|
+
}
|
|
102
|
+
if callback_url:
|
|
103
|
+
body["callback_url"] = callback_url
|
|
104
|
+
endpoint = "generate/video/v1"
|
|
105
|
+
return self._t.submit_and_poll(endpoint, body) if wait else self._t.submit(endpoint, body)
|
|
106
|
+
|
|
107
|
+
def speech(
|
|
108
|
+
self,
|
|
109
|
+
*,
|
|
110
|
+
text: str,
|
|
111
|
+
language_id: str = "en",
|
|
112
|
+
target_voice_path: Optional[str] = None,
|
|
113
|
+
max_new_tokens: int = 256,
|
|
114
|
+
exaggeration: float = 0.5,
|
|
115
|
+
apply_watermark: bool = True,
|
|
116
|
+
callback_url: Optional[str] = None,
|
|
117
|
+
wait: bool = True,
|
|
118
|
+
) -> Job:
|
|
119
|
+
"""Convert text to speech.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
text: Text to synthesise (max 5000 chars).
|
|
123
|
+
language_id: Language code e.g. ``'en'``, ``'es'``, ``'fr'``.
|
|
124
|
+
target_voice_path: URL of a reference voice sample for voice cloning.
|
|
125
|
+
max_new_tokens: Maximum tokens to generate (max 1024).
|
|
126
|
+
exaggeration: Voice expressiveness (0.0–1.0).
|
|
127
|
+
apply_watermark: Apply audio watermark.
|
|
128
|
+
callback_url: Webhook URL. We POST a ``WebhookEvent`` on completion.
|
|
129
|
+
wait: Poll until complete if True.
|
|
130
|
+
"""
|
|
131
|
+
body: dict = {
|
|
132
|
+
"text": text,
|
|
133
|
+
"language_id": language_id,
|
|
134
|
+
"max_new_tokens": max_new_tokens,
|
|
135
|
+
"exaggeration": exaggeration,
|
|
136
|
+
"apply_watermark": apply_watermark,
|
|
137
|
+
}
|
|
138
|
+
if target_voice_path:
|
|
139
|
+
body["target_voice_path"] = target_voice_path
|
|
140
|
+
if callback_url:
|
|
141
|
+
body["callback_url"] = callback_url
|
|
142
|
+
endpoint = "generate/speech/v1"
|
|
143
|
+
return self._t.submit_and_poll(endpoint, body) if wait else self._t.submit(endpoint, body)
|
|
144
|
+
|
|
145
|
+
def generate_3d(
|
|
146
|
+
self,
|
|
147
|
+
*,
|
|
148
|
+
image_path: str,
|
|
149
|
+
mode: str = "generate_and_paint",
|
|
150
|
+
mesh_save_name: Optional[str] = None,
|
|
151
|
+
painted_save_name: Optional[str] = None,
|
|
152
|
+
auto_unload: bool = True,
|
|
153
|
+
callback_url: Optional[str] = None,
|
|
154
|
+
wait: bool = True,
|
|
155
|
+
) -> Job:
|
|
156
|
+
"""Convert an image to a 3D mesh (Pro plan required).
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
image_path: Public URL of the image to convert.
|
|
160
|
+
mode: ``'generate'`` | ``'paint'`` | ``'generate_and_paint'``.
|
|
161
|
+
mesh_save_name: Optional filename for the output mesh.
|
|
162
|
+
painted_save_name: Optional filename for the textured mesh.
|
|
163
|
+
auto_unload: Unload model from GPU after generation.
|
|
164
|
+
callback_url: Webhook URL. We POST a ``WebhookEvent`` on completion.
|
|
165
|
+
wait: Poll until complete if True.
|
|
166
|
+
"""
|
|
167
|
+
body: dict = {
|
|
168
|
+
"image_path": image_path,
|
|
169
|
+
"mode": mode,
|
|
170
|
+
"auto_unload": auto_unload,
|
|
171
|
+
}
|
|
172
|
+
if mesh_save_name:
|
|
173
|
+
body["mesh_save_name"] = mesh_save_name
|
|
174
|
+
if painted_save_name:
|
|
175
|
+
body["painted_save_name"] = painted_save_name
|
|
176
|
+
if callback_url:
|
|
177
|
+
body["callback_url"] = callback_url
|
|
178
|
+
endpoint = "generate/3d/v1"
|
|
179
|
+
return self._t.submit_and_poll(endpoint, body) if wait else self._t.submit(endpoint, body)
|