You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

10 KiB

Changelog: 剧本解析自动创建分镜对白记录

日期: 2026-02-09
类型: Feature Enhancement
影响范围: 剧本解析功能
关联: 2026-02-09-screenplay-ai-parse-format-fix.md


功能描述

在剧本 AI 解析过程中,自动将 storyboards[].dialogue 数据写入 storyboard_dialogues 表,实现分镜对白的自动化管理。


背景

现状

  • AI 解析剧本时会返回 storyboards 数组,每个分镜包含 dialogue 字段
  • 之前的实现仅将 dialogue 存储在 storyboards.meta_data
  • 前端需要从 meta_data 中提取对白数据,不够规范

问题

  1. 数据冗余: 对白数据存储在 meta_data 中,不利于查询和管理
  2. 缺少结构化: 无法利用 storyboard_dialogues 表的完整功能(角色关联、时间管理、情绪标记等)
  3. 功能受限: 无法支持多条对白、对白类型、TTS 配音等高级功能

解决方案

实现逻辑

文件: server/app/services/screenplay_service.py

方法: _create_storyboards_from_ai()

新增代码:

# 7. 创建分镜对白记录(如果有 dialogue 数据)
dialogue_content = sb_data.get('dialogue', '').strip()
if dialogue_content:
    from app.models.storyboard_resource import StoryboardDialogue, DialogueType
    from app.utils.id_generator import generate_uuid
    
    # 解析对白类型(默认为旁白)
    dialogue_type = DialogueType.NARRATION  # 默认为旁白
    
    # 尝试从 meta_data 中获取对白类型
    dialogue_type_str = sb_data.get('meta_data', {}).get('dialogue_type')
    if dialogue_type_str:
        type_mapping = {
            'normal': DialogueType.NORMAL,
            'inner_monologue': DialogueType.INNER_MONOLOGUE,
            'narration': DialogueType.NARRATION
        }
        dialogue_type = type_mapping.get(dialogue_type_str.lower(), DialogueType.NARRATION)
    
    # 创建对白记录
    dialogue = StoryboardDialogue(
        dialogue_id=generate_uuid(),
        storyboard_id=created.storyboard_id,
        character_id=None,  # AI 解析暂不关联具体角色
        character_name=None,
        content=dialogue_content,
        dialogue_type=dialogue_type,
        sequence_order=0,  # 默认为第一条对白
        start_time=start_time,
        duration=estimated_duration,
        emotion=sb_data.get('meta_data', {}).get('emotion'),
        notes=None,
        created_at=datetime.now(timezone.utc),
        updated_at=datetime.now(timezone.utc)
    )
    
    self.db.add(dialogue)
    await self.db.flush()
    
    logger.info(
        "创建分镜对白: storyboard_id=%s, dialogue_id=%s, type=%s, content_length=%d",
        created.storyboard_id,
        dialogue.dialogue_id,
        dialogue.dialogue_type_str,
        len(dialogue_content)
    )

数据映射

AI 返回格式 → 数据库字段

AI 返回字段 数据库字段 类型 说明
dialogue content TEXT 对白内容(必填)
meta_data.dialogue_type dialogue_type SMALLINT 对白类型:1=普通对白, 2=内心OS, 3=旁白
start_time start_time NUMERIC(10,3) 开始时间(秒)
estimated_duration duration NUMERIC(10,3) 时长(秒)
meta_data.emotion emotion VARCHAR 情绪标记(可选)
- sequence_order INTEGER 顺序号(默认 0)
- character_id UUID 角色 ID(暂为 NULL)
- character_name VARCHAR 角色名称(暂为 NULL)

默认值策略

字段 默认值 说明
dialogue_type NARRATION (3) 默认为旁白,因为 AI 解析时通常无法准确识别对白类型
sequence_order 0 默认为第一条对白,后续可手动调整
character_id NULL AI 解析暂不关联具体角色,需要用户手动关联
character_name NULL 同上
emotion NULL 仅当 AI 返回时才填充
notes NULL 保留给用户手动添加备注

使用示例

AI 返回数据示例

{
  "storyboards": [
    {
      "shot_number": 1,
      "title": "开场镜头",
      "description": "海边日出,女孩独自站在沙滩上",
      "dialogue": "这是一个关于勇气和梦想的故事...",
      "shot_size": "full_shot",
      "camera_movement": "static",
      "estimated_duration": 10,
      "characters": ["女孩"],
      "locations": ["海边"],
      "meta_data": {
        "dialogue_type": "narration",
        "emotion": "平静"
      }
    }
  ]
}

数据库记录

storyboards 表:

INSERT INTO storyboards (
    storyboard_id, project_id, title, description,
    shot_size, camera_movement, estimated_duration,
    start_time, end_time, order_index, meta_data
) VALUES (
    '019c4212-08db-7b32-8b00-b24e66817410',
    '019c4212-08db-7b32-8b00-b24e66817411',
    '开场镜头',
    '海边日出,女孩独自站在沙滩上',
    3, -- full_shot
    1, -- static
    10.000,
    0.000,
    10.000,
    0,
    '{"dialogue": "这是一个关于勇气和梦想的故事...", "emotion": "平静"}'
);

storyboard_dialogues 表:

INSERT INTO storyboard_dialogues (
    dialogue_id, storyboard_id, content, dialogue_type,
    sequence_order, start_time, duration, emotion,
    created_at, updated_at
) VALUES (
    '019c4212-08db-7b32-8b00-b24e66817412',
    '019c4212-08db-7b32-8b00-b24e66817410',
    '这是一个关于勇气和梦想的故事...',
    3, -- NARRATION
    0,
    0.000,
    10.000,
    '平静',
    NOW(),
    NOW()
);

功能特性

1. 自动化创建

  • AI 解析剧本时自动创建对白记录
  • 无需手动操作,提升效率
  • 保持数据一致性

2. 类型识别

  • 支持三种对白类型:普通对白、内心OS、旁白
  • 默认为旁白(最常见的场景)
  • 可通过 meta_data.dialogue_type 自定义

3. 时间同步

  • 对白的 start_timeduration 与分镜保持一致
  • 便于后续的时间轴管理和 TTS 配音

4. 扩展性

  • 预留 character_id 字段,支持后续关联角色
  • 预留 emotion 字段,支持情绪标记
  • 预留 notes 字段,支持用户备注

后续优化建议

1. 角色关联

问题: 当前 character_id 为 NULL,无法关联具体角色

建议:

  • 在 AI 解析时尝试识别对白的说话者
  • 根据 storyboards[].characters 自动关联角色
  • 提供 API 支持用户手动关联角色

实现示例:

# 尝试从分镜的角色列表中匹配
characters_in_shot = sb_data.get('characters', [])
if characters_in_shot and len(characters_in_shot) == 1:
    # 如果只有一个角色,自动关联
    char_name = characters_in_shot[0]
    char_id = character_id_map.get(char_name)
    if char_id:
        dialogue.character_id = char_id
        dialogue.character_name = char_name

2. 多条对白支持

问题: 当前仅支持单条对白(sequence_order=0

建议:

  • 支持 AI 返回多条对白(数组格式)
  • 自动分配 sequence_order
  • 支持对白的时间分段

AI 返回格式:

{
  "storyboards": [
    {
      "dialogues": [
        {
          "content": "你好,我是小明",
          "character": "小明",
          "dialogue_type": "normal",
          "start_time": 0,
          "duration": 2
        },
        {
          "content": "你好,我是小红",
          "character": "小红",
          "dialogue_type": "normal",
          "start_time": 2,
          "duration": 2
        }
      ]
    }
  ]
}

3. 对白类型智能识别

问题: 当前默认为旁白,可能不准确

建议:

  • 使用 NLP 技术识别对白类型
  • 根据对白内容和上下文判断
  • 提供置信度评分

识别规则:

  • 包含"他想"、"她想"等关键词 → 内心OS
  • 包含"旁白"、"解说"等关键词 → 旁白
  • 其他 → 普通对白

4. TTS 配音集成

问题: 对白创建后需要手动触发 TTS

建议:

  • 在对白创建后自动触发 TTS 任务
  • 生成 storyboard_voiceovers 记录
  • 支持批量配音

测试验证

测试场景 1: 基本对白创建

输入:

{
  "storyboards": [
    {
      "title": "测试分镜",
      "dialogue": "这是一段测试对白",
      "estimated_duration": 5
    }
  ]
}

预期结果:

  • 创建 1 条 storyboards 记录
  • 创建 1 条 storyboard_dialogues 记录
  • dialogue_type = 3 (NARRATION)
  • sequence_order = 0
  • duration = 5.000

测试场景 2: 自定义对白类型

输入:

{
  "storyboards": [
    {
      "title": "测试分镜",
      "dialogue": "我在想什么呢...",
      "meta_data": {
        "dialogue_type": "inner_monologue",
        "emotion": "困惑"
      }
    }
  ]
}

预期结果:

  • dialogue_type = 2 (INNER_MONOLOGUE)
  • emotion = "困惑"

测试场景 3: 空对白处理

输入:

{
  "storyboards": [
    {
      "title": "测试分镜",
      "dialogue": ""
    }
  ]
}

预期结果:

  • 创建 1 条 storyboards 记录
  • 不创建 storyboard_dialogues 记录(对白为空)

影响范围

受影响的功能

  • 剧本 AI 解析 (POST /api/v1/screenplays/{id}/parse)
  • 分镜创建逻辑 (ScreenplayService._create_storyboards_from_ai)
  • 分镜详情查询(需要关联查询 storyboard_dialogues

数据库变更

  • 新增数据写入:storyboard_dialogues
  • 无表结构变更(使用现有表)

API 变更

  • 无 API 接口变更
  • 响应数据可能包含对白信息(如果前端查询)

相关文档

  • 对白模型: server/app/models/storyboard_resource.py:StoryboardDialogue
  • 分镜服务: server/app/services/screenplay_service.py:_create_storyboards_from_ai
  • 数据库迁移: server/alembic/versions/20260203_1527_*_add_storyboard_resources_tables.py
  • 前端 Mock: client/src/mocks/storyboard-dialogues.ts

总结

本次增强实现了剧本解析时自动创建分镜对白记录的功能,将对白数据从 meta_data 迁移到专用的 storyboard_dialogues 表,提升了数据结构的规范性和可扩展性。

核心价值:

  1. 自动化: 无需手动创建对白记录
  2. 结构化: 利用专用表管理对白数据
  3. 可扩展: 支持角色关联、TTS 配音等高级功能
  4. 向后兼容: 保留 meta_data 中的对白数据,不影响现有功能