"""Volcano Engine ARK video generation API client.""" import requests from django.conf import settings # API error code → user-friendly Chinese message ERROR_MESSAGES = { 'InputImageSensitiveContentDetected.PrivacyInformation': '参考图片中检测到真实人脸,系统不允许处理包含真人面部的图片', 'InputImageSensitiveContentDetected': '参考图片包含敏感内容,请更换图片后重试', 'InputVideoSensitiveContentDetected': '参考视频包含敏感内容,请更换视频后重试', 'InvalidParameter': '请求参数无效,请检查输入', 'RateLimitExceeded': 'API 调用频率超限,请稍后重试', 'InsufficientBalance': '账户余额不足,请联系管理员充值', } class AirDramaAPIError(Exception): """Raised when video generation API returns an error response.""" def __init__(self, code, message, status_code=400): self.code = code self.api_message = message self.status_code = status_code # Use friendly message if available, otherwise use API message self.user_message = ERROR_MESSAGES.get(code, message) super().__init__(self.user_message) MODEL_MAP = { 'seedance_2.0': 'doubao-seedance-2-0-260128', 'seedance_2.0_fast': 'doubao-seedance-2-0-fast-260128', } def _headers(): return { 'Content-Type': 'application/json', 'Authorization': f'Bearer {settings.ARK_API_KEY}', } def create_task(prompt, model, content_items, aspect_ratio, duration, generate_audio=True): """Create a video generation task. Args: prompt: Text prompt for video generation. model: Model key ('airdrama' or 'airdrama_fast'). content_items: List of media content dicts (image_url, video_url, audio_url). aspect_ratio: Video aspect ratio ('16:9', '9:16', etc.). duration: Video duration in seconds. generate_audio: Whether to generate audio with the video. Returns: dict: API response with task id and status. """ url = f'{settings.ARK_BASE_URL}/contents/generations/tasks' content = [] if prompt: content.append({'type': 'text', 'text': prompt}) content.extend(content_items) payload = { 'model': MODEL_MAP.get(model, model), 'content': content, 'generate_audio': generate_audio, 'ratio': aspect_ratio, 'duration': duration, 'watermark': False, } resp = requests.post(url, json=payload, headers=_headers(), timeout=60) if resp.status_code != 200: # Extract human-readable error from API response try: err = resp.json().get('error', {}) code = err.get('code', '') message = err.get('message', resp.text) except Exception: code, message = '', resp.text raise AirDramaAPIError(code, message, resp.status_code) return resp.json() def query_task(task_id): """Query a video generation task by its ARK task ID. Returns: dict: Task status, content (video URL on success), usage info. """ url = f'{settings.ARK_BASE_URL}/contents/generations/tasks/{task_id}' resp = requests.get(url, headers=_headers(), timeout=30) resp.raise_for_status() return resp.json() def extract_video_url(task_response): """Extract the video URL from a completed task response.""" content = task_response.get('content') if not content: return None # content could be a list of items or a dict if isinstance(content, list): for item in content: if item.get('type') == 'video_url': return item.get('video_url', {}).get('url') elif isinstance(content, dict): if 'video_url' in content: url = content['video_url'] return url.get('url') if isinstance(url, dict) else url return None def map_status(ark_status): """Map ARK task status to our DB status.""" mapping = { 'running': 'processing', 'submitted': 'queued', 'queued': 'queued', 'succeeded': 'completed', 'failed': 'failed', } return mapping.get(ark_status, 'processing')