sushilideaclan01 commited on
Commit
53706f2
·
1 Parent(s): e35e038
backend/api/gpt_image_frames.py CHANGED
@@ -5,6 +5,7 @@ Feeds Veo a keyframe that matches 3–4 product reference photos.
5
 
6
  from __future__ import annotations
7
 
 
8
  import io
9
  import os
10
  from typing import Any, List
@@ -201,29 +202,19 @@ async def generate_segment_first_frame(body: SegmentFirstFrameRequest):
201
  size = _aspect_to_size(body.aspect_ratio)
202
 
203
  file_tuples: list[tuple[str, io.BytesIO, str]] = []
204
- urls_in_order = body.reference_image_urls[:4]
205
- async with httpx.AsyncClient() as dl:
206
- downloaded: list[tuple[bytes, str]] = []
207
- for idx, url in enumerate(urls_in_order):
208
- try:
209
- downloaded.append(await _download_image(dl, url))
210
- except httpx.HTTPError as e:
211
- raise HTTPException(
212
- status_code=502,
213
- detail=(
214
- f"Could not fetch reference image {idx + 1} of {len(urls_in_order)} ({url[:160]}…): HTTP error: {e}"
215
- ),
216
- ) from None
217
- except ValueError as e:
218
- raise HTTPException(
219
- status_code=400,
220
- detail=(
221
- f"Invalid reference image {idx + 1} of {len(urls_in_order)} ({url[:160]}…): {e}"
222
- ),
223
- ) from None
224
- for i, (raw, ctype) in enumerate(downloaded):
225
- ext = "png" if "png" in ctype else "jpeg"
226
- file_tuples.append((f"ref_{i}.{ext}", io.BytesIO(raw), ctype))
227
 
228
  client = AsyncOpenAI(api_key=api_key)
229
  result = None
 
5
 
6
  from __future__ import annotations
7
 
8
+ import asyncio
9
  import io
10
  import os
11
  from typing import Any, List
 
202
  size = _aspect_to_size(body.aspect_ratio)
203
 
204
  file_tuples: list[tuple[str, io.BytesIO, str]] = []
205
+ try:
206
+ urls_in_order = body.reference_image_urls[:4]
207
+ async with httpx.AsyncClient() as dl:
208
+ downloaded = await asyncio.gather(
209
+ *(_download_image(dl, url) for url in urls_in_order)
210
+ )
211
+ for i, (raw, ctype) in enumerate(downloaded):
212
+ ext = "png" if "png" in ctype else "jpeg"
213
+ file_tuples.append((f"ref_{i}.{ext}", io.BytesIO(raw), ctype))
214
+ except httpx.HTTPError as e:
215
+ raise HTTPException(status_code=502, detail=f"Could not download reference image: {e}")
216
+ except ValueError as e:
217
+ raise HTTPException(status_code=400, detail=str(e))
 
 
 
 
 
 
 
 
 
 
218
 
219
  client = AsyncOpenAI(api_key=api_key)
220
  result = None
backend/api/seedance_generation.py CHANGED
@@ -129,8 +129,10 @@ def _replicate_input_from_seedance_urls(
129
  }
130
  if n == 1:
131
  inp["image"] = mapped[0]
 
 
 
132
  else:
133
- # Multiple URLs = product reference angles, not first+last-frame (see _seedance_input_from_urls).
134
  inp["reference_images"] = mapped[:9]
135
  return inp
136
 
@@ -253,11 +255,9 @@ def _seedance_input_from_urls(
253
  """
254
  Seedance modes are mutually exclusive (OpenAPI):
255
  - 1 URL → image-to-video (first frame only): first_frame_url
256
- - 2+ URLs → multimodal reference-to-video: reference_image_urls (max 9)
257
-
258
- We intentionally do not map 2 URLs to first_frame + last_frame: this app passes
259
- multiple *product reference* angles (hero + gallery). That old 2-URL branch made
260
- KIE treat the second image as an end frame and fail fetches (content[1].image_url).
261
  """
262
  n = len(urls)
263
  base = {
@@ -270,6 +270,9 @@ def _seedance_input_from_urls(
270
  }
271
  if n == 1:
272
  base["first_frame_url"] = urls[0]
 
 
 
273
  else:
274
  base["reference_image_urls"] = urls[:9]
275
  return base
@@ -357,7 +360,7 @@ def _extract_seedance_callback_state(payload: dict) -> dict:
357
 
358
  @router.post("/seedance/create", response_model=SeedanceCreateResponse)
359
  async def seedance_create(body: SeedanceCreateBody):
360
- """Start a Seedance 2 video task. One URL = first-frame I2V; multiple = reference-to-video."""
361
  dur = _clamp_seedance_duration(body.duration)
362
 
363
  allowed_ratio = {"16:9", "4:3", "1:1", "3:4", "9:16", "21:9", "adaptive"}
 
129
  }
130
  if n == 1:
131
  inp["image"] = mapped[0]
132
+ elif n == 2:
133
+ inp["image"] = mapped[0]
134
+ inp["last_frame_image"] = mapped[1]
135
  else:
 
136
  inp["reference_images"] = mapped[:9]
137
  return inp
138
 
 
255
  """
256
  Seedance modes are mutually exclusive (OpenAPI):
257
  - 1 URL → image-to-video (first frame only): first_frame_url
258
+ - 2 URLs → first & last frames: first_frame_url + last_frame_url
259
+ - 3+ URLs → multimodal reference-to-video: reference_image_urls only (max 9)
260
+ Do not mix first/last/reference in one request.
 
 
261
  """
262
  n = len(urls)
263
  base = {
 
270
  }
271
  if n == 1:
272
  base["first_frame_url"] = urls[0]
273
+ elif n == 2:
274
+ base["first_frame_url"] = urls[0]
275
+ base["last_frame_url"] = urls[1]
276
  else:
277
  base["reference_image_urls"] = urls[:9]
278
  return base
 
360
 
361
  @router.post("/seedance/create", response_model=SeedanceCreateResponse)
362
  async def seedance_create(body: SeedanceCreateBody):
363
+ """Start a Seedance 2 video task. Maps 1–2 image URLs to first/last frame I2V; 3+ to reference_image_urls."""
364
  dur = _clamp_seedance_duration(body.duration)
365
 
366
  allowed_ratio = {"16:9", "4:3", "1:1", "3:4", "9:16", "21:9", "adaptive"}