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.
Files changed (42) hide show
  1. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/PKG-INFO +1 -1
  2. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/bridge/flow_rpc.py +22 -0
  3. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/bridge/flow_sdk.py +47 -4
  4. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/catalog.py +3 -4
  5. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/jobs.py +1 -0
  6. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/openai/videos.py +23 -39
  7. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/pyproject.toml +1 -1
  8. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/PKG-INFO +1 -1
  9. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/PYPI_README.md +0 -0
  10. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/__init__.py +0 -0
  11. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/bridge/__init__.py +0 -0
  12. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/bridge/flow_client.py +0 -0
  13. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/bridge/ws_server.py +0 -0
  14. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/__init__.py +0 -0
  15. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/app.py +0 -0
  16. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/config.py +0 -0
  17. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/dashboard.py +0 -0
  18. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/health.py +0 -0
  19. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/processes.py +0 -0
  20. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/cli/supervisor.py +0 -0
  21. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/config.py +0 -0
  22. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/ingest.py +0 -0
  23. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/job_store.py +0 -0
  24. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/main.py +0 -0
  25. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/media.py +0 -0
  26. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/media_store.py +0 -0
  27. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/openai/__init__.py +0 -0
  28. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/openai/_util.py +0 -0
  29. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/openai/images.py +0 -0
  30. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/openai/models.py +0 -0
  31. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/openai/uploads.py +0 -0
  32. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/pool.py +0 -0
  33. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/posthog_client.py +0 -0
  34. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/session.py +0 -0
  35. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/video_context.py +0 -0
  36. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/flow_engine/video_context_store.py +0 -0
  37. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/setup.cfg +0 -0
  38. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/SOURCES.txt +0 -0
  39. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/dependency_links.txt +0 -0
  40. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/entry_points.txt +0 -0
  41. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/requires.txt +0 -0
  42. {vrex_flow_engine-0.3.1 → vrex_flow_engine-0.3.2}/vrex_flow_engine.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vrex-flow-engine
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Launcher + supervisor and OpenAI-compatible image/video generation service (Vrex Flow Engine).
5
5
  Author: Vrex
6
6
  License: Proprietary
@@ -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 resolution. Frame interpolation, extend
10
- and edit still need their batchexecute payload captured — the videos route
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
- # (interpolation, extension, edit, upsample) still resolve a key so the error
37
- # can name it, but resolve() reports them as not dispatchable and the route
38
- # answers 501.
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:
@@ -60,6 +60,7 @@ _DISPATCH = {
60
60
  "t2v": "gen_video_t2v",
61
61
  "i2v": "gen_video",
62
62
  "r2v": "gen_video_r2v",
63
+ "interpolation": "gen_video_interpolation",
63
64
  }
64
65
 
65
66
 
@@ -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 reference-to-video have captured flow.google.com payloads.
19
- Interpolation with explicit end frames, ``mode: extend|edit`` and r2v
20
- ``reference_audio`` answer HTTP 501 until their request is recorded and
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
- # Marker returned in the create response when a request was served in a
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", None
77
+ return "extension"
90
78
  if explicit == "edit":
91
- return "edit", None
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
- explicit_end = bool(req.end_media_ids or req.end_images)
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", None
97
- if has_start and explicit_end:
98
- return "interpolation", None
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", None
108
- if explicit_end:
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", None
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, degraded = _select_mode(req)
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 == "i2v":
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.1"
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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vrex-flow-engine
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Launcher + supervisor and OpenAI-compatible image/video generation service (Vrex Flow Engine).
5
5
  Author: Vrex
6
6
  License: Proprietary