ATH-MaaS/Pixelle-Video · error · RuntimeError

Seedance 任务成功但未返回视频 URL

Error message

Seedance 任务成功但未返回视频 URL: {data}

What it means

_poll_until_done raises RuntimeError when the polled task reports status 'succeeded' but neither data['content']['video_url'] nor data['video_url'] contains a URL. Success without a downloadable URL is treated as a fatal inconsistency.

Solutions

  1. Inspect the full data payload in the error message and locate where the video URL actually lives
  2. Add fallbacks for alternate shapes, e.g. data['content']['video_url'] list vs string, data['output']['video_url']
  3. Confirm the model name passed to generate_video returns videos (not images) for this endpoint
  4. Check for API changelog/version drift between your client and the deployed Seedance API

Example fix

// before
video_url = data.get("content", {}).get("video_url") or data.get("video_url")
// after
content = data.get("content") or {}
vu = content.get("video_url") or data.get("video_url")
if isinstance(vu, list):
    vu = vu[0].get("url") if vu else None
if isinstance(vu, dict):
    vu = vu.get("url")
Defensive patterns

Strategy: try-catch

Type guard

def extract_video_url(data: dict) -> str | None:
    vu = (data.get("content") or {}).get("video_url") or data.get("video_url")
    if isinstance(vu, list):
        vu = vu[0] if vu else None
    if isinstance(vu, dict):
        vu = vu.get("url")
    return vu if isinstance(vu, str) and vu else None

Try / catch

try:
    video = client.generate_video(prompt=p, image_path=img, save_path=out)
except RuntimeError as e:
    if "未返回视频 URL" in str(e):
        logging.error(f"Seedance success payload malformed: {e}")  # adapt client to new schema
    else:
        raise

Prevention

When it happens

Trigger: Polling GET /contents/generations/tasks/{task_id} returns status=='succeeded' with an empty or differently-shaped result payload (no video_url under content or top level).

Common situations: API response schema changed (e.g. URL moved to output.video_url or a list of results); content generation produced an empty result; response truncated or proxied; using an incompatible model whose success payload differs.

Related errors


AI-assisted analysis of ATH-MaaS/Pixelle-Video@848b054e4f (2026-08-30). Data as JSON: /api/errors/9b3e8d8c6a549528. Report an issue: GitHub.

Appendix: source

Thrown at pixelle_video/services/api_services/video_seedance.py:158

            raise RuntimeError(f"Seedance API 未返回任务 ID: {data}")
            
        return task_id

    def _poll_until_done(self, task_id: str, max_polls: int = 120, interval: int = 5) -> str:
        # 同步更新查询接口路径
        url = f"{self.base_url}/contents/generations/tasks/{task_id}"
        
        for i in range(max_polls):
            resp = requests.get(url, headers=self._headers(), timeout=30, proxies=self._proxies())
            resp.raise_for_status()
            data = resp.json()
            
            status = data.get("status")
            if status == "succeeded":
                # 根据实际返回体,URL 位于 content.video_url 或 video_url
                video_url = data.get("content", {}).get("video_url") or data.get("video_url")
                if not video_url:
                    raise RuntimeError(f"Seedance 任务成功但未返回视频 URL: {data}")
                return video_url
            elif status in ("failed", "expired"):
                error_msg = data.get("error", {}).get("message") or data.get("status_msg") or "未知错误"
                raise RuntimeError(f"Seedance 视频生成{status}: {error_msg}")
            
            logger.debug(f"SeedanceVideoClient: 任务进行中 {task_id}, status={status}, poll={i+1}")
            time.sleep(interval)
            
        raise TimeoutError(f"Seedance 视频生成超时 (task_id={task_id})")

    def _download_video(self, url: str, save_path: str):
        os.makedirs(os.path.dirname(save_path), exist_ok=True)
        resp = requests.get(url, stream=True, timeout=120, proxies=self._proxies())
        resp.raise_for_status()
        with open(save_path, "wb") as f:
            for chunk in resp.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)

View on GitHub (pinned to 848b054e4f)