{"record":{"id":"9b3e8d8c6a549528","repo":"ATH-MaaS/Pixelle-Video","slug":"seedance-url-data","errorCode":null,"errorMessage":"Seedance 任务成功但未返回视频 URL: {data}","messagePattern":"Seedance 任务成功但未返回视频 URL: (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"pixelle_video/services/api_services/video_seedance.py","lineNumber":158,"sourceCode":"            raise RuntimeError(f\"Seedance API 未返回任务 ID: {data}\")\n            \n        return task_id\n\n    def _poll_until_done(self, task_id: str, max_polls: int = 120, interval: int = 5) -> str:\n        # 同步更新查询接口路径\n        url = f\"{self.base_url}/contents/generations/tasks/{task_id}\"\n        \n        for i in range(max_polls):\n            resp = requests.get(url, headers=self._headers(), timeout=30, proxies=self._proxies())\n            resp.raise_for_status()\n            data = resp.json()\n            \n            status = data.get(\"status\")\n            if status == \"succeeded\":\n                # 根据实际返回体，URL 位于 content.video_url 或 video_url\n                video_url = data.get(\"content\", {}).get(\"video_url\") or data.get(\"video_url\")\n                if not video_url:\n                    raise RuntimeError(f\"Seedance 任务成功但未返回视频 URL: {data}\")\n                return video_url\n            elif status in (\"failed\", \"expired\"):\n                error_msg = data.get(\"error\", {}).get(\"message\") or data.get(\"status_msg\") or \"未知错误\"\n                raise RuntimeError(f\"Seedance 视频生成{status}: {error_msg}\")\n            \n            logger.debug(f\"SeedanceVideoClient: 任务进行中 {task_id}, status={status}, poll={i+1}\")\n            time.sleep(interval)\n            \n        raise TimeoutError(f\"Seedance 视频生成超时 (task_id={task_id})\")\n\n    def _download_video(self, url: str, save_path: str):\n        os.makedirs(os.path.dirname(save_path), exist_ok=True)\n        resp = requests.get(url, stream=True, timeout=120, proxies=self._proxies())\n        resp.raise_for_status()\n        with open(save_path, \"wb\") as f:\n            for chunk in resp.iter_content(chunk_size=8192):\n                if chunk:\n                    f.write(chunk)","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/ATH-MaaS/Pixelle-Video/blob/848b054e4fae40dabc62ec58e960b573e83793ac/pixelle_video/services/api_services/video_seedance.py#L140-L176","documentation":"_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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Inspect the full data payload in the error message and locate where the video URL actually lives","Add fallbacks for alternate shapes, e.g. data['content']['video_url'] list vs string, data['output']['video_url']","Confirm the model name passed to generate_video returns videos (not images) for this endpoint","Check for API changelog/version drift between your client and the deployed Seedance API"],"exampleFix":"// before\nvideo_url = data.get(\"content\", {}).get(\"video_url\") or data.get(\"video_url\")\n// after\ncontent = data.get(\"content\") or {}\nvu = content.get(\"video_url\") or data.get(\"video_url\")\nif isinstance(vu, list):\n    vu = vu[0].get(\"url\") if vu else None\nif isinstance(vu, dict):\n    vu = vu.get(\"url\")","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def extract_video_url(data: dict) -> str | None:\n    vu = (data.get(\"content\") or {}).get(\"video_url\") or data.get(\"video_url\")\n    if isinstance(vu, list):\n        vu = vu[0] if vu else None\n    if isinstance(vu, dict):\n        vu = vu.get(\"url\")\n    return vu if isinstance(vu, str) and vu else None","tryCatchPattern":"try:\n    video = client.generate_video(prompt=p, image_path=img, save_path=out)\nexcept RuntimeError as e:\n    if \"未返回视频 URL\" in str(e):\n        logging.error(f\"Seedance success payload malformed: {e}\")  # adapt client to new schema\n    else:\n        raise","preventionTips":["Inspect real success payloads and update URL-extraction fallbacks when the API evolves","Pin the model name and test it after any API version bump","Keep a schema smoke-test that polls a tiny task and asserts a URL is returned"],"tags":["api-response","seedance","schema-validation","video"],"backgroundTag":"unexpected-api-response","analyzedSha":"848b054e4fae40dabc62ec58e960b573e83793ac","analyzedAt":"2026-08-30T03:24:41.468Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}