mirror of
https://github.com/hanzoai/video.git
synced 2026-10-30 18:18:27 +00:00
11 production pipelines, 47 tools, 124 agent skills. Supports cloud APIs (fal.ai, OpenAI, ElevenLabs, Suno, HeyGen, Runway) and free local providers (diffusers, Piper TTS, WAN 2.1, Hunyuan, CogVideo). Co-authored-by: Hanzo Dev <[email protected]>
188 lines
5.8 KiB
Python
188 lines
5.8 KiB
Python
"""ElevenLabs text-to-speech provider tool."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from tools.base_tool import (
|
|
BaseTool,
|
|
Determinism,
|
|
ExecutionMode,
|
|
ResourceProfile,
|
|
RetryPolicy,
|
|
ToolResult,
|
|
ToolRuntime,
|
|
ToolStability,
|
|
ToolStatus,
|
|
ToolTier,
|
|
)
|
|
|
|
|
|
class ElevenLabsTTS(BaseTool):
|
|
name = "elevenlabs_tts"
|
|
version = "0.1.0"
|
|
tier = ToolTier.VOICE
|
|
capability = "tts"
|
|
provider = "elevenlabs"
|
|
stability = ToolStability.EXPERIMENTAL
|
|
execution_mode = ExecutionMode.SYNC
|
|
determinism = Determinism.STOCHASTIC
|
|
runtime = ToolRuntime.API
|
|
|
|
dependencies = []
|
|
install_instructions = (
|
|
"Set the ELEVENLABS_API_KEY environment variable:\n"
|
|
" export ELEVENLABS_API_KEY=your_key_here\n"
|
|
"Get a key at https://elevenlabs.io"
|
|
)
|
|
fallback = "openai_tts"
|
|
fallback_tools = ["openai_tts", "piper_tts"]
|
|
agent_skills = ["elevenlabs", "text-to-speech"]
|
|
|
|
capabilities = [
|
|
"text_to_speech",
|
|
"voice_selection",
|
|
"ssml_support",
|
|
"pronunciation_control",
|
|
]
|
|
supports = {
|
|
"voice_cloning": True,
|
|
"multilingual": True,
|
|
"offline": False,
|
|
"native_audio": True,
|
|
}
|
|
best_for = [
|
|
"high-quality narration",
|
|
"voice-sensitive spokesperson videos",
|
|
"multilingual spoken delivery",
|
|
]
|
|
not_good_for = [
|
|
"fully offline production",
|
|
"privacy-constrained local-only workflows",
|
|
]
|
|
|
|
input_schema = {
|
|
"type": "object",
|
|
"required": ["text"],
|
|
"properties": {
|
|
"text": {"type": "string", "description": "Text to convert to speech"},
|
|
"voice_id": {
|
|
"type": "string",
|
|
"description": "ElevenLabs voice ID (default: Rachel)",
|
|
},
|
|
"model_id": {
|
|
"type": "string",
|
|
"default": "eleven_multilingual_v2",
|
|
"description": "TTS model to use",
|
|
},
|
|
"stability": {
|
|
"type": "number",
|
|
"default": 0.5,
|
|
"minimum": 0,
|
|
"maximum": 1,
|
|
},
|
|
"similarity_boost": {
|
|
"type": "number",
|
|
"default": 0.75,
|
|
"minimum": 0,
|
|
"maximum": 1,
|
|
},
|
|
"style": {
|
|
"type": "number",
|
|
"default": 0.0,
|
|
"minimum": 0,
|
|
"maximum": 1,
|
|
},
|
|
"output_path": {"type": "string"},
|
|
"output_format": {
|
|
"type": "string",
|
|
"default": "mp3_44100_128",
|
|
"enum": ["mp3_44100_128", "mp3_44100_192", "pcm_16000", "pcm_24000"],
|
|
},
|
|
},
|
|
}
|
|
|
|
resource_profile = ResourceProfile(
|
|
cpu_cores=1, ram_mb=256, vram_mb=0, disk_mb=50, network_required=True
|
|
)
|
|
retry_policy = RetryPolicy(max_retries=2, retryable_errors=["rate_limit", "timeout"])
|
|
idempotency_key_fields = ["text", "voice_id", "model_id"]
|
|
side_effects = ["writes audio file to output_path", "calls ElevenLabs API"]
|
|
user_visible_verification = ["Listen to generated audio for natural speech quality"]
|
|
|
|
DEFAULT_VOICE_ID = "21m00Tcm4TlvDq8ikWAM"
|
|
|
|
def get_status(self) -> ToolStatus:
|
|
if os.environ.get("ELEVENLABS_API_KEY"):
|
|
return ToolStatus.AVAILABLE
|
|
return ToolStatus.UNAVAILABLE
|
|
|
|
def estimate_cost(self, inputs: dict[str, Any]) -> float:
|
|
return round(len(inputs.get("text", "")) * 0.0003, 4)
|
|
|
|
def execute(self, inputs: dict[str, Any]) -> ToolResult:
|
|
api_key = os.environ.get("ELEVENLABS_API_KEY")
|
|
if not api_key:
|
|
return ToolResult(success=False, error="No ElevenLabs API key. " + self.install_instructions)
|
|
|
|
start = time.time()
|
|
try:
|
|
result = self._generate(inputs, api_key)
|
|
except Exception as exc:
|
|
return ToolResult(success=False, error=f"TTS generation failed: {exc}")
|
|
|
|
result.duration_seconds = round(time.time() - start, 2)
|
|
result.cost_usd = self.estimate_cost(inputs)
|
|
return result
|
|
|
|
def _generate(self, inputs: dict[str, Any], api_key: str) -> ToolResult:
|
|
import requests
|
|
|
|
text = inputs["text"]
|
|
voice_id = inputs.get("voice_id", self.DEFAULT_VOICE_ID)
|
|
model_id = inputs.get("model_id", "eleven_multilingual_v2")
|
|
output_format = inputs.get("output_format", "mp3_44100_128")
|
|
|
|
response = requests.post(
|
|
f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
|
|
headers={
|
|
"xi-api-key": api_key,
|
|
"Content-Type": "application/json",
|
|
"Accept": "audio/mpeg",
|
|
},
|
|
json={
|
|
"text": text,
|
|
"model_id": model_id,
|
|
"voice_settings": {
|
|
"stability": inputs.get("stability", 0.5),
|
|
"similarity_boost": inputs.get("similarity_boost", 0.75),
|
|
"style": inputs.get("style", 0.0),
|
|
},
|
|
},
|
|
params={"output_format": output_format},
|
|
timeout=120,
|
|
)
|
|
response.raise_for_status()
|
|
|
|
ext = "mp3" if "mp3" in output_format else "wav"
|
|
output_path = Path(inputs.get("output_path", f"tts_output.{ext}"))
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
output_path.write_bytes(response.content)
|
|
|
|
return ToolResult(
|
|
success=True,
|
|
data={
|
|
"provider": self.provider,
|
|
"model": model_id,
|
|
"voice_id": voice_id,
|
|
"text_length": len(text),
|
|
"output": str(output_path),
|
|
"format": output_format,
|
|
},
|
|
artifacts=[str(output_path)],
|
|
model=model_id,
|
|
)
|