mirror of
https://github.com/hanzoskill/venice-ai-media.git
synced 2026-10-30 21:00:50 +00:00
232 lines
7.0 KiB
Python
232 lines
7.0 KiB
Python
"""Shared utilities for Venice AI scripts."""
|
|
|
|
import base64
|
|
import datetime as dt
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
BOT_CONFIG_PATH = Path.home() / ".bot" / "bot.json"
|
|
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
|
|
API_BASE = "https://api.venice.ai/api/v1"
|
|
|
|
|
|
def get_api_key() -> str | None:
|
|
"""
|
|
Get API key from multiple sources in order:
|
|
1. VENICE_API_KEY environment variable
|
|
2. ~/.bot/bot.json at skills.entries.venice-ai-media.env.VENICE_API_KEY
|
|
"""
|
|
api_key = os.environ.get("VENICE_API_KEY", "").strip()
|
|
if api_key:
|
|
return api_key
|
|
|
|
if BOT_CONFIG_PATH.exists():
|
|
try:
|
|
config = json.loads(BOT_CONFIG_PATH.read_text(encoding="utf-8"))
|
|
api_key = (
|
|
config.get("skills", {})
|
|
.get("entries", {})
|
|
.get("venice-ai-media", {})
|
|
.get("env", {})
|
|
.get("VENICE_API_KEY", "")
|
|
)
|
|
if api_key:
|
|
return api_key.strip()
|
|
except (json.JSONDecodeError, OSError):
|
|
pass
|
|
|
|
return None
|
|
|
|
|
|
def require_api_key() -> str:
|
|
"""Get API key or exit with error."""
|
|
api_key = get_api_key()
|
|
if not api_key:
|
|
print("Error: VENICE_API_KEY not found", file=sys.stderr)
|
|
print("Set VENICE_API_KEY env var or configure in ~/.bot/bot.json", file=sys.stderr)
|
|
print("Get your API key at: https://venice.ai/settings/api", file=sys.stderr)
|
|
sys.exit(2)
|
|
return api_key
|
|
|
|
|
|
def make_request(
|
|
endpoint: str,
|
|
method: str = "GET",
|
|
data: bytes | None = None,
|
|
headers: dict | None = None,
|
|
timeout: int = 120,
|
|
) -> bytes:
|
|
"""Make HTTP request to Venice API with proper headers."""
|
|
url = f"{API_BASE}/{endpoint.lstrip('/')}"
|
|
|
|
req_headers = {
|
|
"User-Agent": USER_AGENT,
|
|
}
|
|
if headers:
|
|
req_headers.update(headers)
|
|
|
|
req = urllib.request.Request(url, method=method, headers=req_headers, data=data)
|
|
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
return resp.read()
|
|
except urllib.error.HTTPError as e:
|
|
error_body = e.read().decode("utf-8", errors="replace")
|
|
raise RuntimeError(f"Venice API error ({e.code}): {error_body}") from e
|
|
|
|
|
|
def api_json(
|
|
endpoint: str,
|
|
method: str = "GET",
|
|
payload: dict | None = None,
|
|
api_key: str | None = None,
|
|
timeout: int = 120,
|
|
) -> dict:
|
|
"""Make JSON request to Venice API."""
|
|
headers = {"Authorization": f"Bearer {api_key}"} if api_key else {}
|
|
|
|
if payload:
|
|
headers["Content-Type"] = "application/json"
|
|
data = json.dumps(payload).encode("utf-8")
|
|
else:
|
|
data = None
|
|
|
|
resp = make_request(endpoint, method, data, headers, timeout)
|
|
return json.loads(resp.decode("utf-8"))
|
|
|
|
|
|
def list_models(api_key: str, model_type: str = "image") -> list[dict]:
|
|
"""Fetch available models from Venice API."""
|
|
data = api_json(f"/models?type={model_type}", api_key=api_key, timeout=30)
|
|
return data.get("data", [])
|
|
|
|
|
|
def print_models(models: list[dict]) -> None:
|
|
"""Print models in a formatted table with detailed info."""
|
|
if not models:
|
|
print("No models found.")
|
|
return
|
|
|
|
print(f"\n{'Model ID':<35} {'Status':<12} {'Info'}")
|
|
print("-" * 100)
|
|
for m in models:
|
|
model_id = m.get("id", "unknown")
|
|
model_type = m.get("type", "")
|
|
spec = m.get("model_spec", {})
|
|
|
|
# Determine status from model_spec fields
|
|
if spec.get("offline", False):
|
|
status = "offline"
|
|
elif spec.get("beta", False):
|
|
status = "beta"
|
|
else:
|
|
status = "available"
|
|
|
|
# Check for deprecation warning
|
|
deprecation = spec.get("deprecation", {})
|
|
if deprecation.get("date"):
|
|
status = "deprecated"
|
|
|
|
# Build info string based on model type
|
|
info_parts = []
|
|
|
|
# Description
|
|
desc = spec.get("description", "")
|
|
if desc:
|
|
info_parts.append(desc[:40])
|
|
|
|
# Model-specific details
|
|
constraints = spec.get("constraints", {})
|
|
|
|
if model_type == "image":
|
|
# Show resolutions if available
|
|
resolutions = constraints.get("resolutions", [])
|
|
if resolutions:
|
|
info_parts.append(f"res: {'/'.join(resolutions)}")
|
|
|
|
elif model_type == "video":
|
|
# Show durations and type
|
|
durations = constraints.get("durations", [])
|
|
video_type = constraints.get("model_type", "")
|
|
if video_type:
|
|
info_parts.append(video_type)
|
|
if durations:
|
|
info_parts.append(f"dur: {'/'.join(durations)}")
|
|
|
|
# Deprecation notice
|
|
if deprecation.get("date"):
|
|
info_parts.insert(0, f"[EOL {deprecation['date'][:10]}]")
|
|
|
|
info = " | ".join(info_parts) if info_parts else ""
|
|
print(f"{model_id:<35} {status:<12} {info[:55]}")
|
|
|
|
print()
|
|
print(f"Total: {len(models)} models")
|
|
|
|
|
|
def validate_model(api_key: str, model: str, model_type: str = "image") -> tuple[bool, list[str]]:
|
|
"""Check if model exists and is available. Returns (exists, list_of_available_models)."""
|
|
try:
|
|
models = list_models(api_key, model_type)
|
|
# Model is available if it exists and is not offline
|
|
available = [
|
|
m.get("id") for m in models
|
|
if not m.get("model_spec", {}).get("offline", False)
|
|
]
|
|
return model in available, available
|
|
except RuntimeError:
|
|
return True, []
|
|
|
|
|
|
def print_media_line(filepath: Path) -> None:
|
|
"""Print MEDIA: line for Hanzo Bot auto-attach."""
|
|
print(f"\nMEDIA: {filepath.as_posix()}")
|
|
|
|
|
|
def get_mime_type(filename: str) -> str:
|
|
"""Get MIME type from filename."""
|
|
suffix = Path(filename).suffix.lower()
|
|
return {
|
|
".jpg": "image/jpeg",
|
|
".jpeg": "image/jpeg",
|
|
".png": "image/png",
|
|
".webp": "image/webp",
|
|
".gif": "image/gif",
|
|
".bmp": "image/bmp",
|
|
".tiff": "image/tiff",
|
|
".tif": "image/tiff",
|
|
}.get(suffix, "application/octet-stream")
|
|
|
|
|
|
def default_out_dir(prefix: str = "venice") -> Path:
|
|
"""Create timestamped output directory."""
|
|
now = dt.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
|
|
base = Path("./tmp")
|
|
base.mkdir(parents=True, exist_ok=True)
|
|
return base / f"{prefix}-{now}"
|
|
|
|
|
|
def file_to_data_url(filepath: Path) -> str:
|
|
"""Convert local file to data URL."""
|
|
suffix = filepath.suffix.lower()
|
|
mime_types = {
|
|
".jpg": "image/jpeg",
|
|
".jpeg": "image/jpeg",
|
|
".png": "image/png",
|
|
".webp": "image/webp",
|
|
".gif": "image/gif",
|
|
".mp4": "video/mp4",
|
|
".mov": "video/quicktime",
|
|
".webm": "video/webm",
|
|
".wav": "audio/wav",
|
|
".mp3": "audio/mpeg",
|
|
}
|
|
mime = mime_types.get(suffix, "application/octet-stream")
|
|
data = filepath.read_bytes()
|
|
b64 = base64.b64encode(data).decode("ascii")
|
|
return f"data:{mime};base64,{b64}"
|