进阶 docs.voicebox.sh 2026-10-11 09:58:06 · 26 阅读

第28章 Voicebox 生成历史

第28章:生成历史

本章节介绍 Voicebox 中生成历史追踪机制的工作原理。

概述

历史模块追踪所有生成的音频,提供过去生成记录的可搜索日志。每条记录都会存储文本、设置参数以及对音频文件的引用。

数据模型

生成数据表

```python class Generation(Base): __tablename__ = "generations"

id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) profile_id = Column(String, ForeignKey("profiles.id"), nullable=False) text = Column(Text, nullable=False) language = Column(String, default="en") audio_path = Column(String, nullable=True) duration = Column(Float, nullable=True) seed = Column(Integer) instruct = Column(Text) engine = Column(String, default="qwen") model_size = Column(String, nullable=True) status = Column(String, default="completed") # pending | completed | failed error = Column(Text, nullable=True) is_favorited = Column(Boolean, default=False) created_at = Column(DateTime, default=lambda: datetime.now(UTC)) ```

每次生成还可以拥有多个版本,即应用了不同效果链的处理变体。原始(清洁)版本及任意数量的处理版本都存储在单独的 `generation_versions` 表中。详情请参阅“效果流水线”。

文件存储

生成的音频文件存储于指定目录中:

核心功能

创建生成记录

TTS 生成音频后,系统会创建一条历史记录:

```python async def create_generation( profile_id: str, text: str, language: str, audio_path: str, duration: float, seed: Optional[int], db: Session, instruct: Optional[str] = None, ) -> GenerationResponse: db_generation = DBGeneration( id=str(uuid.uuid4()), profile_id=profile_id, text=text, language=language, audio_path=audio_path, duration=duration, seed=seed, instruct=instruct, created_at=datetime.now(UTC), ) db.add(db_generation) db.commit() return GenerationResponse.model_validate(db_generation) ```

列出生成记录

支持过滤和分页:

```python async def list_generations( query: HistoryQuery, db: Session, ) -> HistoryListResponse: # 构建包含配置文件名称关联的查询 q = db.query( DBGeneration, DBVoiceProfile.name.label('profile_name') ).join( DBVoiceProfile, DBGeneration.profile_id == DBVoiceProfile.id ) # 应用过滤器 if query.profile_id: q = q.filter(DBGeneration.profile_id == query.profile_id) if query.search: q = q.filter(DBGeneration.text.like(f"%{query.search}%")) # 排序和分页 total = q.count() q = q.order_by(DBGeneration.created_at.desc()) q = q.offset(query.offset).limit(query.limit) return HistoryListResponse(items=results, total=total) ```

获取统计数据

为仪表盘提供聚合统计数据:

```python async def get_generation_stats(db: Session) -> dict: total = db.query(func.count(DBGeneration.id)).scalar() total_duration = db.query(func.sum(DBGeneration.duration)).scalar() by_profile = db.query( DBGeneration.profile_id, func.count(DBGeneration.id).label('count') ).group_by(DBGeneration.profile_id).all() return { "total_generations": total, "total_duration_seconds": total_duration, "generations_by_profile": { profile_id: count for profile_id, count in by_profile }, } ```

删除记录

删除某条生成记录会同时移除数据库记录及对应的音频文件:

```python async def delete_generation(generation_id: str, db: Session) -> bool: generation = db.query(DBGeneration).filter_by(id=generation_id).first() if not generation: return False # 删除音频文件 audio_path = Path(generation.audio_path) if audio_path.exists(): audio_path.unlink() # 删除数据库记录 db.delete(generation) db.commit() return True ```

级联删除

删除某个配置文件时,其名下所有生成记录也会被一并删除:

```python async def delete_generations_by_profile(profile_id: str, db: Session) -> int: generations = db.query(DBGeneration).filter_by(profile_id=profile_id).all() for generation in generations: Path(generation.audio_path).unlink(missing_ok=True) db.delete(generation) db.commit() return len(generations) ```

导出/导入

导出生成记录

生成记录可导出为 ZIP 压缩包,内含: - `generation.json` - `audio.wav`

导入生成记录

导入流程如下:

1. 解压 ZIP 文件 2. 验证元数据和音频 3. 生成新的 ID 4. 将音频复制到 generations 目录 5. 创建数据库记录

API 接口

| 方法 | 端点 | 说明 | | :--- | :--- | :--- | | GET | /history | 获取带过滤条件的生成列表 | | GET | /history/stats | 获取聚合统计数据 | | GET | /history/{id} | 根据 ID 获取生成记录 | | DELETE | /history/{id} | 删除生成记录 | | GET | /history/{id}/export | 导出为 ZIP | | GET | /history/{id}/export-audio | 仅导出音频(?format=wav\|mp3;mp3 需 ffmpeg,否则返回 503) | | POST | /history/import | 从 ZIP 导入 |

查询参数

`GET /history?profile_id=uuid&search=hello&limit=50&offset=0`

| 参数 | 类型 | 默认值 | 说明 | | :--- | :--- | :--- | :--- | | profile_id | string | null | 按配置文件过滤 | | search | string | null | 在文本中搜索 | | limit | int | 50 | 每页结果数 | | offset | int | 0 | 分页偏移量 |

响应结构

```json { "items": [ { "id": "uuid", "profile_id": "uuid", "profile_name": "My Voice", "text": "Hello world", "language": "en", "audio_path": "/path/to/audio.wav", "duration": 1.5, "seed": 42, "instruct": null, "engine": "qwen", "model_size": "1.7B", "status": "completed", "error": null, "is_favorited": false, "created_at": "2026-04-18T10:30:00Z" } ], "total": 150 } ```

在故事中的使用

生成记录可被添加到故事中,以支持多声部叙事。故事系统通过 ID 引用生成记录:

```python class StoryItem(Base): generation_id = Column(String, ForeignKey("generations.id")) ```

这允许同一生成记录在多个故事中复用,而无需复制音频文件。

存储考量

磁盘占用

每次生成都会创建一个 WAV 文件。对于 24kHz 的 10 秒片段:

- 单声道 16-bit 模式下,每个文件约占 480KB

清理策略

建议实现以下功能:

- 自动清理旧记录 - 配置每个存储配额 - 归档压缩

相关链接: - 效果流水线:音频后期处理效果与生成版本化 - 故事与时间轴:Voicebox 中多声部时间轴编辑器的工作方式

评论 (0)