# 修复 ElevenLabs 音效生成 OSS 上传问题 **日期**: 2026-02-10 **类型**: Bug 修复 **影响范围**: AI 音效生成任务 ## 问题描述 ElevenLabs 音效生成后未上传到 OSS,导致: - 生成的音频文件无法访问 - 任务完成但 `output_data` 中缺少 `file_url` - 用户无法下载生成的音效 ## 根本原因 1. **ElevenLabs Provider 返回格式**: - 返回 `{'audio_data': bytes, 'audio_url': None, ...}` - 音频数据以二进制形式存储在 `audio_data` 字段 2. **任务检查逻辑错误**(`generate_sound_task`): ```python # ❌ 错误:只检查 'url' 字段 if result.get('url'): file_metadata = await _download_and_upload_file(...) ``` - ElevenLabs 不返回 `url` 字段,条件永远为 False - 跳过 OSS 上传逻辑 3. **不一致性**: - `generate_voice_task` 已正确处理 `audio_data` 字段 - `generate_sound_task` 缺少相同逻辑 ## 解决方案 ### 修改 `generate_sound_task` 参考 `generate_voice_task` 的实现,添加 `audio_data` 检查逻辑: ```python # ✅ 修复后:优先检查 audio_data(二进制数据) if result.get('audio_data'): # 直接上传二进制数据到 MinIO file_metadata = await _upload_file_from_bytes( file_data=result['audio_data'], filename=f"sound_{job_id}.mp3", content_type='audio/mpeg', category='ai-generated/sounds', user_id=user_id ) # 更新 result,移除 audio_data,添加文件元数据 result.pop('audio_data', None) result.update(file_metadata) elif result.get('url'): # 兼容旧的 URL 方式(其他 Provider) file_metadata = await _download_and_upload_file(...) result.update(file_metadata) ``` ### 处理流程 1. **优先检查 `audio_data`**(ElevenLabs 返回格式) - 使用 `_upload_file_from_bytes()` 直接上传二进制数据 - 移除 `audio_data` 字段(避免存储大量二进制数据) - 添加文件元数据(`file_url`, `file_size`, `checksum` 等) 2. **降级检查 `url`**(兼容其他 Provider) - 使用 `_download_and_upload_file()` 下载后上传 - 保持向后兼容性 ## 影响范围 ### 修改文件 - `server/app/tasks/ai_tasks.py` - `generate_sound_task` 函数 ### 受益功能 - ElevenLabs 音效生成(`/api/v1/ai/generate-sound`) - 所有使用 ElevenLabs 的音效生成任务 ### 兼容性 - ✅ 向后兼容:保留 `url` 检查逻辑 - ✅ 不影响其他 Provider(OpenAI、Mock 等) - ✅ 与 `generate_voice_task` 逻辑一致 ## 测试验证 ### 测试步骤 1. **调用音效生成 API**: ```bash docker exec jointo-server-app python -c " import asyncio from app.services.ai_providers.elevenlabs_provider import ElevenLabsProvider async def test(): provider = ElevenLabsProvider('elevenlabs-sound-v2') result = await provider.generate_sound_effect( text='Dog barking in the distance', duration_seconds=5.0 ) print(f'✅ 音效生成成功: {len(result[\"audio_data\"])} bytes') asyncio.run(test()) " ``` 2. **检查任务输出**: - 查询 `ai_jobs` 表的 `output_data` 字段 - 验证包含 `file_url`, `file_size`, `checksum` 等字段 3. **验证文件可访问**: - 访问 `file_url` 确认音频文件可下载 - 检查 MinIO 存储路径 `ai-generated/sounds/source/2026/02/10/` ### 预期结果 ```json { "file_url": "/api/v1/files/ai-generated/sounds/source/2026/02/10/abc123.mp3", "file_size": 81920, "checksum": "abc123...", "mime_type": "audio/mpeg", "storage_provider": "minio", "storage_path": "ai-generated/sounds/source/2026/02/10/abc123.mp3", "duration": 5.0, "metadata": { "text": "Dog barking in the distance", "duration_seconds": 5.0, "audio_size": 81920 } } ``` ## 相关文档 - RFC 142: ElevenLabs 集成方案 - Changelog: 2026-02-10 ElevenLabs 集成实现 - 测试结果: `server/tests/manual/elevenlabs_test_results.md` ## 后续优化 1. **统一上传逻辑**: - 所有 AI 生成任务统一使用 `audio_data` 优先策略 - 减少 URL 下载的网络开销 2. **添加单元测试**: - 测试 `generate_sound_task` 的 `audio_data` 处理逻辑 - 测试 `url` 降级逻辑 3. **监控告警**: - 监控 OSS 上传失败率 - 告警音频文件缺失情况