ffmpeg / renderer /studio /tasks.py
Ava2lon's picture
Upload 38 files
fe7106b verified
Raw
History Blame Contribute Delete
11.6 kB
from __future__ import annotations
from pathlib import Path
from typing import Any
from renderer.core.config import Settings
from renderer.core.models import TaskResult
from renderer.core.utils import now, safe_filename, write_json
from renderer.studio.capabilities import (
AI_ASSISTANTS,
AI_EDITING_FEATURES,
IMAGE_GENERATION_PROVIDERS,
MUSIC_PROVIDERS,
VIDEO_GENERATION_PROVIDERS,
VOICE_PROVIDERS,
)
class StudioTaskProcessor:
"""Manifest-producing async handlers for optional AI providers and studio automation."""
def __init__(self, settings: Settings | None = None, log=None) -> None:
self.settings = settings or Settings()
self.settings.ensure_dirs()
self._logs: list[str] = []
self._log = log
def caption_generate(self, payload: dict[str, Any], job_id: str) -> TaskResult:
text = str(payload.get("text") or payload.get("transcript") or "")
captions = payload.get("events") if isinstance(payload.get("events"), list) else _captions_from_text(text)
manifest = {
"type": "caption_generation",
"status": "ready",
"engine": payload.get("engine", "whisper"),
"media": payload.get("media") or payload.get("audio"),
"template": payload.get("template", "capcut"),
"language": payload.get("language"),
"features": {
"word_timestamps": bool(payload.get("word_timestamps", True)),
"sentence_timestamps": True,
"emoji_insertion": bool(payload.get("emoji_insertion", False)),
"speaker_detection": bool(payload.get("speaker_detection", False)),
"karaoke": bool(payload.get("karaoke", True)),
"animated": bool(payload.get("animated", True)),
},
"captions": captions,
}
output = self._json_artifact(job_id, "captions", manifest)
return self._result(output, {"task": "caption_generate", "caption_count": len(captions)})
def music_generate(self, payload: dict[str, Any], job_id: str) -> TaskResult:
provider = _provider(payload.get("provider"), MUSIC_PROVIDERS, "musicgen")
prompt = str(payload.get("prompt") or payload.get("style") or "background music")
duration = float(payload.get("duration", 30))
manifest = {
"type": "music_generation",
"status": "provider_required",
"provider": provider,
"prompt": prompt,
"style": payload.get("style", "background_music"),
"duration": duration,
"bpm": payload.get("bpm"),
"license": payload.get("license", "user_configured"),
"next_step": "Configure provider credentials or connect this manifest to a local MusicGen runner.",
}
output = self._json_artifact(job_id, "music_request", manifest)
return self._result(output, {"task": "music_generate", "provider": provider, "duration": duration})
def voice_generate(self, payload: dict[str, Any], job_id: str) -> TaskResult:
provider = _provider(payload.get("provider"), VOICE_PROVIDERS, "kokoro")
text = str(payload.get("text") or "")
manifest = {
"type": "voice_generation",
"status": "provider_required",
"provider": provider,
"text": text,
"voice": payload.get("voice", "default"),
"emotion": payload.get("emotion"),
"speed": float(payload.get("speed", 1.0)),
"pitch": float(payload.get("pitch", 1.0)),
"clone_reference": payload.get("clone_reference"),
"multi_speaker": payload.get("speakers", []),
"next_step": "Configure the selected TTS backend to render audio for this manifest.",
}
output = self._json_artifact(job_id, "voice_request", manifest)
return self._result(output, {"task": "voice_generate", "provider": provider, "characters": len(text)})
def image_generate(self, payload: dict[str, Any], job_id: str) -> TaskResult:
provider = _provider(payload.get("provider"), IMAGE_GENERATION_PROVIDERS, "flux")
manifest = {
"type": "image_generation",
"status": "provider_required",
"provider": provider,
"prompt": payload.get("prompt", ""),
"negative_prompt": payload.get("negative_prompt", ""),
"mode": payload.get("mode", "text_to_image"),
"control_image": payload.get("control_image"),
"source_image": payload.get("source_image"),
"size": payload.get("size", "1024x1024"),
"features": {
"background_replacement": bool(payload.get("background_replacement", False)),
"object_removal": bool(payload.get("object_removal", False)),
"upscaling": bool(payload.get("upscaling", False)),
},
}
output = self._json_artifact(job_id, "image_request", manifest)
return self._result(output, {"task": "image_generate", "provider": provider})
def video_generate(self, payload: dict[str, Any], job_id: str) -> TaskResult:
provider = _provider(payload.get("provider"), VIDEO_GENERATION_PROVIDERS, "ltx_video")
manifest = {
"type": "video_generation",
"status": "provider_required",
"provider": provider,
"prompt": payload.get("prompt", ""),
"mode": payload.get("mode", "text_to_video"),
"image": payload.get("image"),
"duration": float(payload.get("duration", 5)),
"fps": int(payload.get("fps", 24)),
"size": payload.get("size", "1280x720"),
"next_step": "Connect Wan, LTX Video, Hunyuan Video, or Veo credentials/runtime to execute this request.",
}
output = self._json_artifact(job_id, "video_request", manifest)
return self._result(output, {"task": "video_generate", "provider": provider})
def ai_tool(self, tool: str, payload: dict[str, Any], job_id: str) -> TaskResult:
tool = _canonical(tool)
if tool not in AI_EDITING_FEATURES:
raise ValueError(f"Unsupported AI editing tool: {tool}")
manifest = {
"type": "ai_editing",
"tool": tool,
"status": "ready",
"media": payload.get("media"),
"project_id": payload.get("project_id"),
"platform": payload.get("platform", "tiktok"),
"result": _ai_result(tool, payload),
"created_at": now(),
}
output = self._json_artifact(job_id, tool, manifest)
return self._result(output, {"task": tool})
def assistant_tool(self, tool: str, payload: dict[str, Any], job_id: str) -> TaskResult:
tool = _canonical(tool)
if tool not in AI_ASSISTANTS:
raise ValueError(f"Unsupported assistant: {tool}")
text = str(payload.get("topic") or payload.get("transcript") or payload.get("prompt") or "")
manifest = {
"type": "assistant",
"tool": tool,
"status": "ready",
"input": text,
"platform": payload.get("platform", "general"),
"result": _assistant_result(tool, text, payload),
"created_at": now(),
}
output = self._json_artifact(job_id, tool, manifest)
return self._result(output, {"task": tool, "characters": len(text)})
def _json_artifact(self, job_id: str, name: str, payload: dict[str, Any]) -> Path:
output = self.settings.exports_dir / f"{job_id}_{safe_filename(name)}.json"
write_json(output, payload)
self._message(f"Wrote {name} manifest")
return output
def _result(self, output: Path, metrics: dict[str, Any]) -> TaskResult:
return TaskResult(output_path=output, commands=[], metrics=metrics, logs=list(self._logs))
def _message(self, message: str) -> None:
self._logs.append(message)
if self._log:
self._log(message)
def _provider(value: Any, supported: list[str], default: str) -> str:
provider = _canonical(str(value or default))
return provider if provider in supported else default
def _canonical(value: str) -> str:
return value.strip().lower().replace("-", "_").replace(" ", "_")
def _captions_from_text(text: str) -> list[dict[str, Any]]:
if not text:
return []
words = text.split()
chunks: list[list[str]] = []
while words:
chunks.append(words[:8])
words = words[8:]
captions = []
cursor = 0.0
for chunk in chunks:
duration = max(1.2, len(chunk) * 0.34)
captions.append({"start": round(cursor, 2), "end": round(cursor + duration, 2), "text": " ".join(chunk)})
cursor += duration
return captions
def _ai_result(tool: str, payload: dict[str, Any]) -> dict[str, Any]:
platform = str(payload.get("platform") or "tiktok")
if tool == "auto_highlight_detection":
return {"highlights": [{"start": 0, "end": 8, "reason": "opening hook"}]}
if tool == "auto_scene_detection":
return {"scenes": [{"start": 0, "end": 5, "label": "intro"}, {"start": 5, "end": 12, "label": "body"}]}
if tool in {"auto_reframe", "auto_crop", "auto_platform_optimization"}:
return {"platform": platform, "safe_zone": "vertical_center", "aspect_ratio": "9:16"}
if tool == "auto_viral_score":
return {"score": 74, "signals": ["short duration", "caption-ready", platform]}
if tool == "auto_hook_detection":
return {"hook": str(payload.get("transcript") or payload.get("text") or "")[:120], "score": 68}
if tool == "auto_thumbnail_selection":
return {"frames": [{"timestamp": 2.0, "score": 82}, {"timestamp": 6.5, "score": 75}]}
return {"plan": f"{tool} plan generated", "confidence": "heuristic", "platform": platform}
def _assistant_result(tool: str, text: str, payload: dict[str, Any]) -> dict[str, Any]:
subject = text.strip() or "your video"
short = " ".join(subject.split()[:12])
if tool == "script_writer":
return {"script": f"Hook: {short}\nValue: show the clearest proof.\nCTA: invite viewers to take the next step."}
if tool == "hook_generator":
return {"hooks": [f"Stop scrolling if you care about {short}", f"Nobody tells you this about {short}"]}
if tool == "title_generator":
return {"titles": [short.title(), f"How {short.title()} Changes Everything"]}
if tool == "description_generator":
return {"description": f"{subject}\n\nBuilt with Ava2lon Studio AI."}
if tool == "hashtag_generator":
tags = [word.strip(".,!?").lower() for word in subject.split() if len(word.strip(".,!?")) > 3]
return {"hashtags": ["#" + tag for tag in tags[:8]] or ["#video", "#creator"]}
if tool == "storyboard_generator":
return {"beats": [{"scene": 1, "goal": "hook"}, {"scene": 2, "goal": "proof"}, {"scene": 3, "goal": "CTA"}]}
if tool == "b_roll_planner":
return {"shots": [{"type": "close_up", "description": short}, {"type": "screen_recording", "description": "show the result"}]}
if tool == "thumbnail_prompt_generator":
return {"prompt": f"High contrast thumbnail for {short}, expressive face, bold text, clean background"}
if tool == "seo_optimizer":
return {"keywords": [word.strip(".,!?").lower() for word in subject.split()[:10]], "score": 72}
return {"result": subject, "options": payload}