Spaces:
Sleeping
Sleeping
on demand url
Browse files- routers/gemini.py +54 -11
- services/gemini_job_worker.py +6 -23
routers/gemini.py
CHANGED
|
@@ -325,6 +325,7 @@ async def get_job_status(
|
|
| 325 |
"""
|
| 326 |
Get the status of a job.
|
| 327 |
Poll this endpoint until status is 'completed' or 'failed'.
|
|
|
|
| 328 |
"""
|
| 329 |
query = select(GeminiJob).where(
|
| 330 |
GeminiJob.job_id == job_id,
|
|
@@ -339,6 +340,14 @@ async def get_job_status(
|
|
| 339 |
detail="Job not found"
|
| 340 |
)
|
| 341 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
response = {
|
| 343 |
"success": True,
|
| 344 |
"job_id": job.job_id,
|
|
@@ -348,6 +357,10 @@ async def get_job_status(
|
|
| 348 |
"credits_remaining": user.credits
|
| 349 |
}
|
| 350 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
if job.status == "queued":
|
| 352 |
response["position"] = await get_queue_position(db, job.job_id)
|
| 353 |
|
|
@@ -359,7 +372,7 @@ async def get_job_status(
|
|
| 359 |
response["output"] = job.output_data
|
| 360 |
|
| 361 |
# For video jobs, add download URL
|
| 362 |
-
if job.job_type == "video" and job.output_data and job.output_data.get("filename"):
|
| 363 |
response["download_url"] = f"/gemini/download/{job.job_id}"
|
| 364 |
|
| 365 |
if job.status == "failed":
|
|
@@ -377,6 +390,7 @@ async def download_video(
|
|
| 377 |
):
|
| 378 |
"""
|
| 379 |
Download a generated video.
|
|
|
|
| 380 |
"""
|
| 381 |
query = select(GeminiJob).where(
|
| 382 |
GeminiJob.job_id == job_id,
|
|
@@ -392,24 +406,53 @@ async def download_video(
|
|
| 392 |
detail="Job not found"
|
| 393 |
)
|
| 394 |
|
| 395 |
-
if job.status != "completed" or not job.output_data
|
| 396 |
raise HTTPException(
|
| 397 |
status_code=status.HTTP_400_BAD_REQUEST,
|
| 398 |
detail="Video not ready for download"
|
| 399 |
)
|
| 400 |
|
| 401 |
-
|
| 402 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
raise HTTPException(
|
| 404 |
-
status_code=status.
|
| 405 |
-
detail="
|
| 406 |
)
|
| 407 |
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
|
| 414 |
|
| 415 |
@router.post("/job/{job_id}/cancel")
|
|
|
|
| 325 |
"""
|
| 326 |
Get the status of a job.
|
| 327 |
Poll this endpoint until status is 'completed' or 'failed'.
|
| 328 |
+
For processing video jobs, this will check the Gemini API status and update the job.
|
| 329 |
"""
|
| 330 |
query = select(GeminiJob).where(
|
| 331 |
GeminiJob.job_id == job_id,
|
|
|
|
| 340 |
detail="Job not found"
|
| 341 |
)
|
| 342 |
|
| 343 |
+
# If job is processing and is a video job, check Gemini API status and update
|
| 344 |
+
if job.status == "processing" and job.job_type == "video" and job.third_party_id:
|
| 345 |
+
from services.gemini_job_worker import GeminiJobProcessor
|
| 346 |
+
processor = GeminiJobProcessor()
|
| 347 |
+
job = await processor.check_status(job, db)
|
| 348 |
+
await db.commit()
|
| 349 |
+
await db.refresh(job)
|
| 350 |
+
|
| 351 |
response = {
|
| 352 |
"success": True,
|
| 353 |
"job_id": job.job_id,
|
|
|
|
| 357 |
"credits_remaining": user.credits
|
| 358 |
}
|
| 359 |
|
| 360 |
+
# Include prompt for video jobs
|
| 361 |
+
if job.job_type == "video" and job.input_data:
|
| 362 |
+
response["prompt"] = job.input_data.get("prompt")
|
| 363 |
+
|
| 364 |
if job.status == "queued":
|
| 365 |
response["position"] = await get_queue_position(db, job.job_id)
|
| 366 |
|
|
|
|
| 372 |
response["output"] = job.output_data
|
| 373 |
|
| 374 |
# For video jobs, add download URL
|
| 375 |
+
if job.job_type == "video" and job.output_data and (job.output_data.get("filename") or job.output_data.get("video_url")):
|
| 376 |
response["download_url"] = f"/gemini/download/{job.job_id}"
|
| 377 |
|
| 378 |
if job.status == "failed":
|
|
|
|
| 390 |
):
|
| 391 |
"""
|
| 392 |
Download a generated video.
|
| 393 |
+
If the video hasn't been downloaded yet, downloads it from the stored URL.
|
| 394 |
"""
|
| 395 |
query = select(GeminiJob).where(
|
| 396 |
GeminiJob.job_id == job_id,
|
|
|
|
| 406 |
detail="Job not found"
|
| 407 |
)
|
| 408 |
|
| 409 |
+
if job.status != "completed" or not job.output_data:
|
| 410 |
raise HTTPException(
|
| 411 |
status_code=status.HTTP_400_BAD_REQUEST,
|
| 412 |
detail="Video not ready for download"
|
| 413 |
)
|
| 414 |
|
| 415 |
+
# Check if video is already downloaded
|
| 416 |
+
filename = job.output_data.get("filename")
|
| 417 |
+
if filename:
|
| 418 |
+
filepath = os.path.join(DOWNLOADS_DIR, filename)
|
| 419 |
+
if os.path.exists(filepath):
|
| 420 |
+
return FileResponse(
|
| 421 |
+
path=filepath,
|
| 422 |
+
media_type="video/mp4",
|
| 423 |
+
filename=f"video_{job_id}.mp4"
|
| 424 |
+
)
|
| 425 |
+
|
| 426 |
+
# Video not downloaded yet, download from stored URL
|
| 427 |
+
video_url = job.output_data.get("video_url")
|
| 428 |
+
if not video_url:
|
| 429 |
raise HTTPException(
|
| 430 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 431 |
+
detail="No video URL available"
|
| 432 |
)
|
| 433 |
|
| 434 |
+
# Download the video on-demand
|
| 435 |
+
from services.gemini_service import GeminiService
|
| 436 |
+
service = GeminiService() # Uses default API key
|
| 437 |
+
|
| 438 |
+
try:
|
| 439 |
+
filename = await service.download_video(video_url, job.job_id)
|
| 440 |
+
|
| 441 |
+
# Update job with downloaded filename
|
| 442 |
+
job.output_data = {**job.output_data, "filename": filename}
|
| 443 |
+
await db.commit()
|
| 444 |
+
|
| 445 |
+
filepath = os.path.join(DOWNLOADS_DIR, filename)
|
| 446 |
+
return FileResponse(
|
| 447 |
+
path=filepath,
|
| 448 |
+
media_type="video/mp4",
|
| 449 |
+
filename=f"video_{job_id}.mp4"
|
| 450 |
+
)
|
| 451 |
+
except Exception as e:
|
| 452 |
+
raise HTTPException(
|
| 453 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 454 |
+
detail=f"Failed to download video: {str(e)}"
|
| 455 |
+
)
|
| 456 |
|
| 457 |
|
| 458 |
@router.post("/job/{job_id}/cancel")
|
services/gemini_job_worker.py
CHANGED
|
@@ -112,29 +112,12 @@ class GeminiJobProcessor(JobProcessor[GeminiJob]):
|
|
| 112 |
if status_result.get("status") == "completed":
|
| 113 |
video_url = status_result.get("video_url")
|
| 114 |
if video_url:
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
except Exception as download_error:
|
| 122 |
-
# Download failed - track separately
|
| 123 |
-
error_msg = f"Download failed: {download_error}"
|
| 124 |
-
logger.error(f"Error downloading video for {job.job_id}: {download_error}")
|
| 125 |
-
|
| 126 |
-
# Fail job after 5 download attempts (don't retry forever)
|
| 127 |
-
if job.retry_count >= 5:
|
| 128 |
-
job.status = "failed"
|
| 129 |
-
job.error_message = error_msg
|
| 130 |
-
job.completed_at = datetime.utcnow()
|
| 131 |
-
else:
|
| 132 |
-
# Retry
|
| 133 |
-
job.retry_count += 1
|
| 134 |
-
job.error_message = f"Download attempt {job.retry_count} failed: {download_error}"
|
| 135 |
-
config = WorkerConfig.from_env()
|
| 136 |
-
interval = get_interval_for_priority(job.priority, config)
|
| 137 |
-
job.next_process_at = datetime.utcnow() + timedelta(seconds=interval)
|
| 138 |
else:
|
| 139 |
job.status = "failed"
|
| 140 |
job.error_message = "No video URL returned"
|
|
|
|
| 112 |
if status_result.get("status") == "completed":
|
| 113 |
video_url = status_result.get("video_url")
|
| 114 |
if video_url:
|
| 115 |
+
# Store video URL - download will happen on-demand when client requests
|
| 116 |
+
job.status = "completed"
|
| 117 |
+
job.output_data = {"video_url": video_url}
|
| 118 |
+
job.error_message = None # Clear any previous error
|
| 119 |
+
job.completed_at = datetime.utcnow()
|
| 120 |
+
success = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
else:
|
| 122 |
job.status = "failed"
|
| 123 |
job.error_message = "No video URL returned"
|