ai-gateway / core /workflow.py
basyx's picture
Upload 60 files
eb808a5 verified
Raw
History Blame Contribute Delete
5.35 kB
"""Full media workflow orchestration outside the HTTP layer."""
from __future__ import annotations
from pathlib import Path
from config import Settings
from core.executor import InferenceCommand
from core.manager import TaskManager
from core.schemas import WorkflowRequest
from utils.files import OutputManager
class WorkflowService:
"""Run image, voice, music, video, and subtitle generation in order."""
def __init__(
self, settings: Settings, tasks: TaskManager, outputs: OutputManager
) -> None:
self.settings = settings
self.tasks = tasks
self.outputs = outputs
async def run(self, payload: WorkflowRequest, request_id: str) -> dict[str, Path]:
"""Queue the entire pipeline as one non-interleavable operation."""
width = payload.width if payload.width is not None else self.settings.image_width
height = payload.height if payload.height is not None else self.settings.image_height
voice = payload.voice or self.settings.kokoro_default_voice
targets = {
"image": self.outputs.allocate("images"),
"voice": self.outputs.allocate("audio"),
"music": self.outputs.allocate("music"),
"video": self.outputs.allocate("videos"),
"subtitle": self.outputs.allocate("subtitles"),
}
def execute() -> dict[str, Path]:
self.tasks.invoke_direct(
InferenceCommand(
model_name="flux",
method_name="generate",
arguments={
"prompt": payload.image_prompt,
"output_path": targets["image"],
"width": width,
"height": height,
"steps": self.settings.image_steps,
"seed": payload.seed,
"guidance_scale": self.settings.flux_guidance_scale,
},
request_id=request_id,
duration_seconds=self.settings.zerogpu_flux_duration,
),
)
self.tasks.invoke_direct(
InferenceCommand(
model_name="kokoro",
method_name="synthesize",
arguments={
"text": payload.script,
"voice": voice,
"speed": 1.0,
"output_path": targets["voice"],
},
request_id=request_id,
duration_seconds=self.settings.zerogpu_kokoro_duration,
),
)
words = len(payload.script.split())
music_duration = min(30.0, max(5.0, words / 2.5))
music_prompt = payload.music_prompt or (
f"cinematic instrumental background score for {payload.title}, no vocals"
)
self.tasks.invoke_direct(
InferenceCommand(
model_name="musicgen",
method_name="generate",
arguments={
"prompt": music_prompt,
"duration": music_duration,
"guidance_scale": self.settings.music_guidance_scale,
"seed": payload.seed,
"output_path": targets["music"],
},
request_id=request_id,
duration_seconds=self.settings.zerogpu_musicgen_duration,
),
)
self.tasks.invoke_direct(
InferenceCommand(
model_name="wan",
method_name="generate",
arguments={
"image_path": targets["image"],
"prompt": payload.video_prompt,
"negative_prompt": "low quality, distorted, static",
"output_path": targets["video"],
"steps": self.settings.wan_steps,
"frames": self.settings.video_frames,
"fps": self.settings.video_fps,
"seed": payload.seed,
"guidance_scale": self.settings.wan_guidance_scale,
},
request_id=request_id,
duration_seconds=self.settings.zerogpu_wan_duration,
gpu_size="xlarge",
),
)
self.tasks.invoke_direct(
InferenceCommand(
model_name="whisper",
method_name="transcribe",
arguments={
"source": targets["voice"],
"subtitle_path": targets["subtitle"],
"language": None,
"task": "transcribe",
},
request_id=request_id,
duration_seconds=self.settings.zerogpu_whisper_duration,
),
)
return targets
try:
return await self.tasks.run_exclusive(request_id, "workflow", execute)
except Exception:
for target in targets.values():
self.outputs.remove(target)
raise