vrex-flow-engine 0.3.1__tar.gz → 0.3.2__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.
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/PKG-INFO +1 -1
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/bridge/flow_rpc.py +22 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/bridge/flow_sdk.py +47 -4
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/catalog.py +3 -4
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/jobs.py +1 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/openai/videos.py +23 -39
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/pyproject.toml +1 -1
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/PKG-INFO +1 -1
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/PYPI_README.md +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/__init__.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/bridge/__init__.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/bridge/flow_client.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/bridge/ws_server.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/__init__.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/app.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/config.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/dashboard.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/health.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/processes.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/supervisor.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/config.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/ingest.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/job_store.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/main.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/media.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/media_store.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/openai/__init__.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/openai/_util.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/openai/images.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/openai/models.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/openai/uploads.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/pool.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/posthog_client.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/session.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/video_context.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/video_context_store.py +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/setup.cfg +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/SOURCES.txt +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/dependency_links.txt +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/entry_points.txt +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/requires.txt +0 -0
- {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/top_level.txt +0 -0
|
@@ -29,6 +29,7 @@ RPC_GENERATE_IMAGE = "ogiZ0b"
|
|
|
29
29
|
RPC_GENERATE_VIDEO_T2V = "YhhmEf"
|
|
30
30
|
RPC_GENERATE_VIDEO_I2V = "eb1hJf"
|
|
31
31
|
RPC_GENERATE_VIDEO_R2V = "MZZa6b"
|
|
32
|
+
RPC_GENERATE_VIDEO_INTERPOLATION = "nprQif"
|
|
32
33
|
RPC_GENERATE_VIDEO = RPC_GENERATE_VIDEO_T2V # historical alias
|
|
33
34
|
RPC_CHECK_MEDIA = "jwpduf"
|
|
34
35
|
RPC_GET_MEDIA = "as29s"
|
|
@@ -169,6 +170,27 @@ def video_request_item_i2v(
|
|
|
169
170
|
]
|
|
170
171
|
|
|
171
172
|
|
|
173
|
+
def video_request_item_interpolation(
|
|
174
|
+
prompt: str,
|
|
175
|
+
model_key: str,
|
|
176
|
+
aspect_enum: int,
|
|
177
|
+
start_media_id: str,
|
|
178
|
+
end_media_id: str,
|
|
179
|
+
seed: Optional[int] = None,
|
|
180
|
+
crop: Optional[list[Any]] = None,
|
|
181
|
+
) -> list[Any]:
|
|
182
|
+
"""Frames-to-video item (``nprQif``): the i2v item with the end frame in
|
|
183
|
+
the slot before the metadata — ``[textInput, modelKey, aspect, seed,
|
|
184
|
+
startImage, endImage, metadata]``. Passing the same media id for both
|
|
185
|
+
frames yields a clip that ends where it began (a seamless loop)."""
|
|
186
|
+
return [
|
|
187
|
+
_text_input(prompt), model_key, aspect_enum, seed,
|
|
188
|
+
[None, start_media_id, None, None, None, crop],
|
|
189
|
+
[None, end_media_id, None, None, None, crop],
|
|
190
|
+
_item_metadata(),
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
|
|
172
194
|
def video_request_item_r2v(
|
|
173
195
|
prompt: str,
|
|
174
196
|
model_key: str,
|
|
@@ -5,10 +5,10 @@ and the job debug endpoint can inspect Flow's payload when a shape drifts.
|
|
|
5
5
|
|
|
6
6
|
Wired from flow.google.com captures (2026-09): project list/create, model
|
|
7
7
|
catalog, credits/tier, preset voices, image upload (inline) and resumable
|
|
8
|
-
video upload, image generation (sync), text-/image-/reference-to-video
|
|
9
|
-
media status polling and signed URL
|
|
10
|
-
and edit still need their batchexecute payload captured —
|
|
11
|
-
answers 501 for them instead of guessing array positions.
|
|
8
|
+
video upload, image generation (sync), text-/image-/reference-to-video and
|
|
9
|
+
frames-to-video (interpolation), media status polling and signed URL
|
|
10
|
+
resolution. Extend and edit still need their batchexecute payload captured —
|
|
11
|
+
the videos route answers 501 for them instead of guessing array positions.
|
|
12
12
|
"""
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
@@ -350,6 +350,49 @@ class FlowSDK:
|
|
|
350
350
|
]
|
|
351
351
|
return await self._dispatch_video(flow_rpc.RPC_GENERATE_VIDEO_I2V, items, project_id, scene_id)
|
|
352
352
|
|
|
353
|
+
async def gen_video_interpolation(
|
|
354
|
+
self,
|
|
355
|
+
prompt: str,
|
|
356
|
+
project_id: str,
|
|
357
|
+
start_media_ids: list[str],
|
|
358
|
+
end_media_ids: Optional[list[str]] = None,
|
|
359
|
+
aspect_ratio: str = "VIDEO_ASPECT_RATIO_LANDSCAPE",
|
|
360
|
+
paygate_tier: Optional[int] = None,
|
|
361
|
+
seed: Optional[int] = None,
|
|
362
|
+
model_key: Optional[str] = None,
|
|
363
|
+
scene_id: Optional[str] = None,
|
|
364
|
+
) -> dict[str, Any]:
|
|
365
|
+
"""Frames-to-video: motion between a start and an end frame, one clip
|
|
366
|
+
per pair. ``end_media_ids`` defaults to ``start_media_ids`` — the
|
|
367
|
+
seamless-loop case; when both are given they are zipped pairwise and
|
|
368
|
+
must be the same length."""
|
|
369
|
+
if paygate_tier is None:
|
|
370
|
+
raise ValueError("paygate_tier is required — resolve the account first")
|
|
371
|
+
if not model_key:
|
|
372
|
+
return {"raw": None, "error": "interpolation_model_key_required"}
|
|
373
|
+
aspect_enum = flow_rpc.VIDEO_ASPECT_ENUM.get(aspect_ratio)
|
|
374
|
+
if aspect_enum is None:
|
|
375
|
+
return {"raw": None, "error": f"interpolation_aspect_unsupported_{aspect_ratio}"}
|
|
376
|
+
starts = [m for m in (start_media_ids or []) if isinstance(m, str) and m]
|
|
377
|
+
if not starts:
|
|
378
|
+
return {"raw": None, "error": "missing_start_media_id"}
|
|
379
|
+
ends = [m for m in (end_media_ids or []) if isinstance(m, str) and m] or list(starts)
|
|
380
|
+
if len(ends) != len(starts):
|
|
381
|
+
return {
|
|
382
|
+
"raw": None,
|
|
383
|
+
"error": (
|
|
384
|
+
f"start/end frame count mismatch: {len(starts)} start vs "
|
|
385
|
+
f"{len(ends)} end — pass equal-length lists or omit end frames"
|
|
386
|
+
),
|
|
387
|
+
}
|
|
388
|
+
items = [
|
|
389
|
+
flow_rpc.video_request_item_interpolation(prompt, model_key, aspect_enum, s, e, seed=seed)
|
|
390
|
+
for s, e in list(zip(starts, ends))[:MAX_VARIANT_COUNT]
|
|
391
|
+
]
|
|
392
|
+
return await self._dispatch_video(
|
|
393
|
+
flow_rpc.RPC_GENERATE_VIDEO_INTERPOLATION, items, project_id, scene_id
|
|
394
|
+
)
|
|
395
|
+
|
|
353
396
|
async def gen_video_r2v(
|
|
354
397
|
self,
|
|
355
398
|
prompt: str,
|
|
@@ -33,10 +33,9 @@ _CACHE_FILE = STORAGE_DIR / "catalog.json"
|
|
|
33
33
|
_CACHE_VERSION = 2
|
|
34
34
|
|
|
35
35
|
# Modes whose batchexecute payload has been captured and verified. Other modes
|
|
36
|
-
# (
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
_DISPATCHABLE_MODES: frozenset[str] = frozenset({"t2v", "i2v", "r2v"})
|
|
36
|
+
# (extension, edit, upsample) still resolve a key so the error can name it,
|
|
37
|
+
# but resolve() reports them as not dispatchable and the route answers 501.
|
|
38
|
+
_DISPATCHABLE_MODES: frozenset[str] = frozenset({"t2v", "i2v", "r2v", "interpolation"})
|
|
40
39
|
|
|
41
40
|
|
|
42
41
|
class _Catalog:
|
|
@@ -15,13 +15,9 @@ The generation MODE is inferred from the inputs you provide:
|
|
|
15
15
|
- ``start_media_ids`` / ``start_images`` → image-to-video (i2v, "Frames")
|
|
16
16
|
- neither → text-to-video (t2v, prompt only)
|
|
17
17
|
|
|
18
|
-
Text-, image- and
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
ported (see flow-engine/README.md). ``loop: true`` — a seamless loop, which
|
|
22
|
-
is interpolation from the start frame back to itself — is served as plain
|
|
23
|
-
image-to-video meanwhile: the caller still gets motion behind its still, only
|
|
24
|
-
the last-frame match is lost, and the response says so via ``degraded``.
|
|
18
|
+
Text-, image-, reference- and frames-to-video have captured flow.google.com
|
|
19
|
+
payloads. ``mode: extend|edit`` and r2v ``reference_audio`` answer HTTP 501
|
|
20
|
+
until their request is recorded and ported (see flow-engine/README.md).
|
|
25
21
|
"""
|
|
26
22
|
from __future__ import annotations
|
|
27
23
|
|
|
@@ -42,7 +38,7 @@ router = APIRouter(prefix="/v1/videos", tags=["videos"])
|
|
|
42
38
|
ALLOWED_VIDEO_MODEL = "veo_3_1_lite_low_priority"
|
|
43
39
|
|
|
44
40
|
# Modes with a verified flow.google.com payload.
|
|
45
|
-
SUPPORTED_MODES = frozenset({"t2v", "i2v", "r2v"})
|
|
41
|
+
SUPPORTED_MODES = frozenset({"t2v", "i2v", "r2v", "interpolation"})
|
|
46
42
|
|
|
47
43
|
|
|
48
44
|
class VideoRequest(BaseModel):
|
|
@@ -73,44 +69,29 @@ class VideoRequest(BaseModel):
|
|
|
73
69
|
source_seconds: Optional[int] = None # edit: source clip length (→ frames)
|
|
74
70
|
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
# weaker mode than it asked for.
|
|
78
|
-
LOOP_DEGRADED = "loop_unavailable_served_as_i2v"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def _select_mode(req: VideoRequest) -> tuple[str, Optional[str]]:
|
|
72
|
+
def _select_mode(req: VideoRequest) -> str:
|
|
82
73
|
"""Pick the generation mode from the request shape (explicit ``mode`` wins).
|
|
83
|
-
|
|
84
|
-
Returns ``(mode, degraded)`` — ``degraded`` names the downgrade applied
|
|
85
|
-
(currently only ``LOOP_DEGRADED``) or is None.
|
|
86
|
-
"""
|
|
74
|
+
``loop`` is sugar for "end frame is the start frame", i.e. interpolation."""
|
|
87
75
|
explicit = (req.mode or "").lower().strip()
|
|
88
76
|
if explicit in ("extend", "extension"):
|
|
89
|
-
return "extension"
|
|
77
|
+
return "extension"
|
|
90
78
|
if explicit == "edit":
|
|
91
|
-
return "edit"
|
|
79
|
+
return "edit"
|
|
92
80
|
has_ref = bool(req.ref_media_ids or req.ref_images)
|
|
93
81
|
has_start = bool(req.start_media_ids or req.start_images)
|
|
94
|
-
|
|
82
|
+
has_end = bool(req.end_media_ids or req.end_images) or (req.loop and has_start)
|
|
95
83
|
if has_ref:
|
|
96
|
-
return "r2v"
|
|
97
|
-
if has_start and
|
|
98
|
-
return "interpolation"
|
|
99
|
-
if has_start and req.loop:
|
|
100
|
-
if "interpolation" in SUPPORTED_MODES:
|
|
101
|
-
return "interpolation", None
|
|
102
|
-
# A seamless loop is interpolation from the start frame back to itself.
|
|
103
|
-
# That payload is not captured yet, so keep the caller moving with an
|
|
104
|
-
# ordinary clip from the same frame and say so in the response.
|
|
105
|
-
return "i2v", LOOP_DEGRADED
|
|
84
|
+
return "r2v"
|
|
85
|
+
if has_start and has_end:
|
|
86
|
+
return "interpolation"
|
|
106
87
|
if has_start:
|
|
107
|
-
return "i2v"
|
|
108
|
-
if
|
|
88
|
+
return "i2v"
|
|
89
|
+
if has_end:
|
|
109
90
|
raise HTTPException(
|
|
110
91
|
status_code=400,
|
|
111
92
|
detail="end frames require start frames — pass start_images/start_media_ids",
|
|
112
93
|
)
|
|
113
|
-
return "t2v"
|
|
94
|
+
return "t2v"
|
|
114
95
|
|
|
115
96
|
|
|
116
97
|
def _job_view(job: jobs.Job) -> dict[str, Any]:
|
|
@@ -148,7 +129,7 @@ async def create_video(
|
|
|
148
129
|
detail=f"only model={ALLOWED_VIDEO_MODEL!r} is supported",
|
|
149
130
|
)
|
|
150
131
|
|
|
151
|
-
mode
|
|
132
|
+
mode = _select_mode(req)
|
|
152
133
|
if mode not in SUPPORTED_MODES or (mode == "r2v" and req.reference_audio):
|
|
153
134
|
# Decided before any upload so a caller never pays for reference
|
|
154
135
|
# ingestion on a request that cannot be dispatched.
|
|
@@ -185,10 +166,16 @@ async def create_video(
|
|
|
185
166
|
ref_ids = list(req.ref_media_ids or [])
|
|
186
167
|
ref_ids += await ingest.resolve_image_inputs(sdk, project_id, req.ref_images)
|
|
187
168
|
kwargs["ref_media_ids"] = ref_ids
|
|
188
|
-
elif mode
|
|
169
|
+
elif mode in ("i2v", "interpolation"):
|
|
189
170
|
start_ids = list(req.start_media_ids or [])
|
|
190
171
|
start_ids += await ingest.resolve_image_inputs(sdk, project_id, req.start_images)
|
|
191
172
|
kwargs["start_media_ids"] = start_ids
|
|
173
|
+
if mode == "interpolation":
|
|
174
|
+
end_ids = list(req.end_media_ids or [])
|
|
175
|
+
end_ids += await ingest.resolve_image_inputs(sdk, project_id, req.end_images)
|
|
176
|
+
# `loop` = end frame is the start frame; gen_video_interpolation
|
|
177
|
+
# defaults to that when no end frames are given.
|
|
178
|
+
kwargs["end_media_ids"] = end_ids or None
|
|
192
179
|
|
|
193
180
|
try:
|
|
194
181
|
job = jobs.start_video_job(
|
|
@@ -213,9 +200,6 @@ async def create_video(
|
|
|
213
200
|
view["model"] = req.model
|
|
214
201
|
view["model_key"] = model_key
|
|
215
202
|
view["mode"] = mode
|
|
216
|
-
if degraded:
|
|
217
|
-
view["degraded"] = degraded
|
|
218
|
-
view["loop"] = False
|
|
219
203
|
return view
|
|
220
204
|
|
|
221
205
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "vrex-flow-engine"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.2"
|
|
4
4
|
description = "Launcher + supervisor and OpenAI-compatible image/video generation service (Vrex Flow Engine)."
|
|
5
5
|
# Neutral public description (the in-repo README.md carries the full internal docs).
|
|
6
6
|
readme = "PYPI_README.md"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|