{% extends "base.html" %} {% block title %}{{ project.name }} - {{ brand.page_title }}{% endblock %} {% block top_action %} 返回项目列表 {% endblock %} {% block content %} {# 编辑锁只跟"本项目正在生成"有关:排队等待期间剧情与话术可编辑(与服务端同一口径)。 edit_locked 由服务端 app.py 给出;进程还没重启(旧上下文没有该键)时按 status 兜底, 保证运行中的项目在任何时刻都不会渲染成"可编辑"。 #} {% set edit_locked = project_workflow.edit_locked if project_workflow.edit_locked is defined else (project_workflow.status in ['running', 'starting', 'cancelling']) %} {% set edit_locked_hint = '本任务正在生成,剧情与话术暂时只读;生成结束后即可编辑(需要立刻修改可先点「中断」)' %} {% set queued_edit_hint = '本任务已提交、正在排队等待生成:剧情与话术现在就能编辑。你的手工改动会沿用到本次生成(点「重新生成剧情脚本」才会覆盖)。' %} {# 任务启动类/破坏性操作(清空、重新生成)在任务活跃时不做:排队中也不做,避免和本次任务互相打架 #} {% set task_busy_hint = '本任务已提交或正在运行,请等本次任务结束后再执行该操作' %} {% macro story_field(label, value, path, kind='textarea', rows=2, value_type='string', wide=false) -%}
{{ label }} {% if kind == 'number' %} {% elif kind == 'short' %} {% else %} {% endif %}
{%- endmacro %} {% macro task_progress(panel_id, title, task_step) -%} {% set task_active = project_workflow.active and project_workflow.step == task_step %}
{{ title }}
{% if task_step == 'understanding' and task_active %}智能体正在运行中。{% else %}{{ project_workflow.detail if task_active else '任务将在后台运行,可以安全刷新页面。' }}{% endif %}
{%- endmacro %} {# 阶段耗时:耗时(运行中每秒自增)+ 开始时间;老项目没有 stage_times 时显示 —,绝不渲染 None。 被预运行提前做完的阶段(排队期间完成、本任务直接复用)执行耗时是 0 秒,不能显示"用时 0 秒", 改显示预运行实际耗时(数据取自该项目 prewarm.json)。 #} {% macro stage_timing(step_id) -%} {% set t = (stage_time_map or {}).get(step_id) %} {% if t and t.prewarm %} {{ t.prewarm.short_text }} {%- elif t and not t.missing %} {{ ('已用 ' if t.running else '用时 ') ~ t.seconds_label }}{% if t.first_started_clock %} · {{ t.first_started_clock }} 开始{% endif %} {%- else %} — {%- endif %} {%- endmacro %}

{{ project.name }}

项目 ID:{{ project.id }} 创建时间:{{ project.created_at }} {% if manual_story_mode %}项目类型:已有剧情创作 {% elif original_creation_mode %}项目类型:原创剧情 {% else %}源视频:{{ project.source_filename }}{% endif %} {% if exact_clone_mode %}创作方式:一键复刻{% elif original_creation_mode %}创作方式:一键创作{% elif not manual_story_mode %}创作方式:一键改编{% endif %} 视频参数:{{ project.h3_resolution|default('480p', true) }} · {{ h3_generation_mode.ui_label }} · {{ h3_generation_mode.fixed_steps }} 步 {# 生成后端:只读项目记录(project.json.comfyui_backend/comfyui_url),没有记录就显示"未记录",绝不用当前配置去猜 #} 生成后端:{{ '☁ 云端' if comfyui_backend == 'cloud' else ('🖥 本机' if comfyui_backend == 'local' else '未记录') }}{% if comfyui_url %}({{ comfyui_url }}){% endif %}
{% if message %}
{{ message }}
{% endif %} {% if error %}
{{ error }}
{% endif %} {# 保存反馈 / 运行中被拒绝编辑的说明(服务端按真实状态生成文案,排队页不会出现"已锁定") #} {% if page_notice %}
{{ page_notice }}
{% endif %} {# 生成过程(实时):一键成片的 API 阶段(视觉理解 → 剧情脚本/分镜 → 导演提示词)把模型的 思考(reasoning_content)与正文流式写到 <项目目录>/stream.jsonl,这里经 SSE 实时显示, 让用户随时判断"在跑还是卡住":正常/重试/错误都会出现,60 秒没新输出会给出提示。 任务结束后仍可回看(?tail=500);完全没有日志的项目不显示本面板(JS 自动隐藏)。 #} {% if original_creation_mode and current_step == "understanding" %} {% endif %} {% if current_step == "understanding" %}

视频理解

{{ stage_timing('understanding') }}
Step 1
{% if source_video.url %} {% else %}
未找到源视频文件
{% endif %}

源视频

{{ source_video.name or project.source_filename }}
{% if source_video.uses_compatible_preview %} {% endif %}
{{ project.source_platform or "本地上传" }} {% if project.download_tool %}{{ project.download_tool }}{% endif %}
{{ task_progress('videoUnderstandingProgress', '正在分析视频', 'understanding') }}
{% if false %}

FunASR 原音轨辅助文案

FunASR 本地模型 {% if source_transcript.speaker_enabled %}说话人分离已启用{% endif %} {% if source_transcript.events %}{{ source_transcript.events|length }} 段{% endif %}
{% if source_transcript.events %} {% if source_transcript.status in ["partial", "degraded"] %}
辅助台词已通过语音片段恢复,可能存在遗漏或时间偏差。工作流继续运行,最终台词由逐镜原音轨分析校正。
{% endif %}
{% for item in source_transcript.events %} {% endfor %}
原音轨时间 说话人 ASR辅助文案
{{ item.time_range }} {{ item.speaker or "未分离" }} {{ item.text }}
识别模型:{{ source_transcript.model }} {% if source_transcript.speaker_model %}说话人模型:{{ source_transcript.speaker_model }}{% endif %} {% if source_transcript.updated_at %}更新时间:{{ source_transcript.updated_at }}{% endif %} {% if source_transcript.speaker_enabled %}说话人编号来自本地声纹聚类,不等同于角色名。{% else %}FunASR 当前未启用说话人分离。{% endif %}最终完整人物台词由视频理解模型结合画面、口型、字幕和本底稿生成。
{% elif source_transcript.status == "error" %}
辅助台词暂不可用,工作流会继续通过原视频画面与音轨分析。此提示不代表原视频没有对白。
查看识别诊断{{ source_transcript.error }}
{% elif source_transcript.status == "no_speech" %}
FunASR 未在原视频音轨中识别到有效语音。
{% else %}
尚未生成 FunASR 完整文案。点击“{{ '重新分析原视频' if video_analysis else '一键分析原视频' }}”开始提取。
{% endif %}
{% endif %}

视频理解结果

{% set source_outline_mode = video_understanding_result and video_understanding_result.shots and not video_understanding_result.shots[0].content %} {% set deep_understanding_mode = video_understanding_result and video_understanding_result.shots and video_understanding_result.shots[0].content %}
{% if source_outline_mode %}正在生成资产与切片概要;完成后会继续逐镜深度理解。{% else %}已合并全局资产、原片时间、镜头总结、按秒画面、完整保留要求、台词与声音。{% endif %}
结构化视频理解
{% if video_understanding_result %}
全局资产 {{ video_understanding_result.assets|length }} 人物、场景与产品道具
剧情分镜 {{ video_understanding_result.shots|length }} 按生成顺序自动编号
分镜总时长 {{ video_understanding_result.shots|sum(attribute='duration')|round(1) }}秒 由各镜头时长累计
风

整体视觉风格

由视频理解模型根据全片画面统一提取

{{ video_understanding_result.visual_style|default('', true) }}

资

全局资产描述

全片唯一资产及用于资产图片提示词的固定视觉特征

{{ video_understanding_result.assets|length }} 项
{% for asset in video_understanding_result.assets %} {% set category_class = 'character' if asset.category == '人物主体' else ('scene' if asset.category == '场景' else 'product') %} {% endfor %}
资产 ID 资产类别 资产名称 固定视觉特征描述
{{ asset.id }} {{ asset.category }} {{ asset.name }}

{{ asset.visual_features }}

剧

原片分镜理解结果

连续记录原片时间,并展示每个切片的完整深度理解内容

{{ video_understanding_result.shots|length }} 镜
{% if deep_understanding_mode %}{% endif %} {% for shot in video_understanding_result.shots %} {% if deep_understanding_mode %} {% endif %} {% endfor %}
镜头 镜头总结 / 原片时间 / 时长 出现资产按秒画面 / 完整保留要求台词 / 声音设计
{{ shot.id }} {{ shot.summary }} {% if shot.source_start is defined and shot.source_end is defined %} 原片 {{ shot.source_start }}–{{ shot.source_end }} 秒 {% endif %} {{ shot.duration }} 秒
{% for asset_id in shot.asset_ids %} {% set related_asset = video_understanding_asset_by_id.get(asset_id, {}) %} {{ related_asset.get('name', asset_id) }} {% endfor %}

{{ shot.content }}

fully_preserved

{{ shot.fully_preserved }}

环境与动作音效

{{ shot.overall_soundscape }}

{% if shot.non_diegetic_music and shot.non_diegetic_music != 'N/A' %}
画外配乐

{{ shot.non_diegetic_music }}

{% endif %} {% for dialogue_item in shot.dialogue %} {% set speaker_asset = video_understanding_asset_by_id.get(dialogue_item.speaker_asset_id, {}) %}
{{ dialogue_item.start }}-{{ dialogue_item.end }}秒 {{ '旁白' if dialogue_item.speaker_asset_id == 'NARRATOR' else speaker_asset.get('name', dialogue_item.speaker_asset_id or '人物台词') }} {% if dialogue_item.emotion %}{{ dialogue_item.emotion }}{% endif %}

{{ dialogue_item.text }}

{% endfor %}
{% else %}
尚未执行视频理解。点击一键分析后,结果会显示在这里。
{% endif %}
以上结构化数据是后续流程的第一步权威输入;本步骤不执行复刻或广告改编。 {% if video_understanding_result %}下一步:{{ "剧情复刻" if exact_clone_mode else "剧情改编" }}{% endif %}
{% endif %} {% if false and current_step == "understanding" %}

剧情解析结果

Step 1
{{ video_analysis|markdown }}
确认解析内容可用后,进入剧情改写。 {% if video_analysis %} 下一步:剧情改写 {% else %} 视频理解结果生成中 {% endif %}
{% endif %} {% if current_step == "rewrite" %} {% if original_creation_mode %}

创作输入

Step 1
{{ creative_references|length if creative_references else 0 }} 产品图片
{{ project.get('target_duration_seconds', 30) if project.get else 30 }}秒 目标视频时长
{{ shot_script.shots|length if shot_script and shot_script.shots else 0 }} 已生成镜头
{% if original_story_brief %}
创

剧情要求(自动生成模式)

提交时未填写剧情脚本,由智能体结合产品图片自动生成

{{ original_story_brief }}

{% endif %} {% if creative_references %}
图

产品图片

上传的参考图,智能体已从中识别产品信息

{{ creative_references|length }} 项
{% for ref in creative_references %}
{% if ref.url %} {{ ref.filename }} {% endif %}
{{ ref.analysis.get('subject') if ref.analysis and ref.analysis.get('subject') else ref.filename }} {% if ref.analysis and ref.analysis.get('readable_text') %} {{ ref.analysis.get('readable_text')|join(' · ') }} {% endif %}
{% endfor %}
{% endif %}
以下剧情脚本由智能体按 R1-R7 爆款短剧规则自动生成,可直接编辑后继续。
{% endif %}

剧情脚本

{{ stage_timing('rewrite') }}
{% if shot_script %}
{% elif original_creation_mode %}
{% elif video_understanding_result %}
{% endif %} Step 2
{# 剧情/话术编辑权限说明:排队期间=可编辑(含预运行成果作废的如实预告),生成中=只读+解除条件。 story_edit_notes 由服务端生成;旧上下文(进程未重启)时用同口径的兜底文案。 #} {% set story_note_now = story_edit_note_now or (edit_locked_hint if edit_locked else (queued_edit_hint if project_workflow.status in ['queued', 'pending'] else '')) %}
{{ story_note_now }}
{% if shot_script %}

整体视觉风格

{{ story_field('整体视觉风格', shot_script.visual_style|default('', true), ['visual_style'], 'textarea', 3, wide=true) }}
{% set story_asset_categories = [ ("character", "人物主体", "人物资产"), ("scene", "场景", "场景资产"), ("product", "产品道具", "产品道具") ] %} {% set requested_asset_group = request.args.get('asset_category', 'character') %}

全局资产目录

{{ shot_script.assets|length }} 项资产
{% for group_key, category, group_title in story_asset_categories %} {% set is_active_group = requested_asset_group == group_key or (requested_asset_group not in ['character', 'scene', 'product'] and loop.first) %}
{% for asset in story_asset_groups.get(group_key, []) %} {% set asset_visual_field = 'visual_features' if asset.category|default('') else 'image_prompt' %} {% set asset_visual_text = asset.visual_features|default(asset.image_prompt|default('', true), true) %}
{{ asset.id }}

{{ asset.name }}

{{ asset.category|default('人物主体' if group_key == 'character' else ('场景' if group_key == 'scene' else '产品道具'), true) }}
{{ story_field('资产名称', asset.name, ['assets', asset._story_index, 'name'], 'short', wide=true) }}
{{ story_field('固定视觉特征描述', asset_visual_text, ['assets', asset._story_index, asset_visual_field], 'textarea', 5, wide=true) }}
{% endfor %}
{% endfor %}
{% set requested_shot_index = request.args.get('shot', '0')|int %} {% if requested_shot_index < 0 or requested_shot_index >= shot_script.shots|length %} {% set requested_shot_index = 0 %} {% endif %}

镜头剧本

鼠标移到镜头标签上,点击边缘的+即可在该位置插入空白镜头。

{{ shot_script.shots|length }} 个镜头
{% for shot in shot_script.shots %} {% set shot_index = loop.index0 %}

{{ shot.summary or '空白镜头' }}

基本信息

{{ story_field('镜头一句话总结', shot.summary, ['shots', shot_index, 'summary'], 'short') }} {{ story_field('镜头时长(秒)', shot.duration, ['shots', shot_index, 'duration'], 'number') }} {% if shot.reference_video is defined %} {{ story_field('剧情是否参考对应原片(true / false)', shot.reference_video|default(false, true)|string|lower, ['shots', shot_index, 'reference_video'], 'short', 2, 'boolean', wide=true) }} {{ story_field('原片视频参考说明', shot.reference_video_instruction, ['shots', shot_index, 'reference_video_instruction'], 'textarea', 2, wide=true) }} {% endif %}

出镜资产

{% for asset_id in shot.asset_ids %} {% set bound_asset = story_asset_by_id.get(asset_id, {}) %} {{ asset_id }}{{ bound_asset.get('name', '未命名资产') }} {% endfor %}
勾选本镜需要引用的资产 {{ shot.asset_ids|length }} 项
{% for selectable_asset in shot_script.assets %} {% endfor %}

画面描述

{{ shot.duration }}秒
{{ story_field('逐秒驱动画面描述', shot.content, ['shots', shot_index, 'content'], 'textarea', 8, wide=true) }}

参考完整保留要求

文本字符串
{{ story_field('fully_preserved(用 1-2 句话概括必须保留的人物身份、场景、产品、构图、动作顺序和首尾状态)', shot.fully_preserved, ['shots', shot_index, 'fully_preserved'], 'textarea', 3, wide=true) }}

环境音效与配乐

独立声音设计
{{ story_field('环境底声 / 背景现场声 / 动作与道具音效', shot.overall_soundscape, ['shots', shot_index, 'overall_soundscape'], 'textarea', 4, wide=true) }} {{ story_field('画外配乐(没有则填写 N/A)', shot.non_diegetic_music, ['shots', shot_index, 'non_diegetic_music'], 'textarea', 2, wide=true) }}

人物台词

{{ shot.dialogue|length }} 条
{% if shot.dialogue %}
{% for dialogue_item in shot.dialogue %} {% set dialogue_index = loop.index0 %}
{{ loop.index }}
人物台词 {{ dialogue_item.start }}s — {{ dialogue_item.end }}s {% if dialogue_item.speaker_asset_id %}{{ dialogue_item.speaker_asset_id }}{% endif %} {% if dialogue_item.emotion %}{{ dialogue_item.emotion }}{% endif %}
{{ dialogue_item.text or '空白台词' }}
{{ story_field('开始秒', dialogue_item.start, ['shots', shot_index, 'dialogue', dialogue_index, 'start'], 'number') }} {{ story_field('结束秒', dialogue_item.end, ['shots', shot_index, 'dialogue', dialogue_index, 'end'], 'number') }} {{ story_field('说话人资产', dialogue_item.speaker_asset_id, ['shots', shot_index, 'dialogue', dialogue_index, 'speaker_asset_id'], 'short') }} {{ story_field('说话情绪 / 语气', dialogue_item.emotion, ['shots', shot_index, 'dialogue', dialogue_index, 'emotion'], 'short') }} {{ story_field('台词内容', dialogue_item.text, ['shots', shot_index, 'dialogue', dialogue_index, 'text'], 'textarea', 3, wide=true) }}
{% endfor %}
{% else %}
本镜头没有人物台词,环境音效仍由上方独立声音设计控制。
{% endif %}
{% endfor %}
下一步:生成资产图片
{% else %} {% if video_understanding_result %}
{% if exact_clone_mode %} 第一步精简时间表已就绪。点击生成后会按时间切割原片,并发调用火山引擎逐镜理解画面、完整保留要求、环境音效、配乐和人物台词。 {% else %} 第一步精简时间表已就绪。点击生成后会先并行理解原片切片,再创作改编剧情,并为每镜标注完整参考、部分参考或不参考原片。 {% endif %}
第二步只保存 assets 与 shots;第三步再按资产描述生成图片。
{% else %} {% if original_creation_mode %}
当前没有剧情脚本(可能刚被清空)。点击右上角「重新生成剧情脚本」,智能体会按产品图片与剧情要求重新创作;旧脚本已备份到 repair_backups。
重新生成只重做「理解产品图 + 剧情脚本」两步,资产图/提示词/视频随后在各自步骤里生成。
{% else %}
尚未完成原视频分析,请先返回第一步分析原视频。
{% endif %} {% endif %} {% endif %}
{% endif %} {% if current_step == "assets" %}

资产图片

{{ stage_timing('assets') }}
Step 3
{% if asset_plan and asset_plan.assets %}
共 {{ asset_plan.assets|length }} 项;图片提示词默认使用剧情脚本中的固定视觉特征;补缺失会跳过已有图片,全部重新生成会覆盖所有图片类型资产。 {% else %} 当前项目是旧版剧情脚本,尚未包含资产目录。请返回第二步重新生成统一剧情脚本。 {% endif %}
{{ task_progress('assetGenerationProgress', '正在处理资产图片与提示词', 'assets') }}

资产图与素材绑定

{% for group_key, group_title in [("character", "人物图片"), ("scene", "场景图片"), ("product", "产品图片")] %}

{{ group_title }}

{{ asset_groups.get(group_key, [])|length }} 项
{% if asset_groups.get(group_key) %}
{% for asset in asset_groups.get(group_key, []) %} {% set asset_type_label = group_title %} {% include "_asset_card.html" %} {% endfor %}
{% else %}
暂无{{ group_title }}
{% endif %}
{% endfor %} {% if asset_groups.get("other") %}

其他素材

{{ asset_groups.get("other", [])|length }} 项
{% for asset in asset_groups.get("other", []) %} {% set asset_type_label = "其他素材" %} {% include "_asset_card.html" %} {% endfor %}
{% endif %}
{% if false %}{% endif %}
{% endif %} {% if current_step == "prompts" %}

MiniMax H3 视频提示词

{{ stage_timing('prompts') }}
{% if video_prompt_items %}
{% endif %} Step 4
{% if video_prompt_items %} {% set batch_generation_active = project_workflow.active and project_workflow.step == 'generation' and project_workflow.action != 'local_minimax_generation_single' %} {% set prompt_generation_active = project_workflow.active and project_workflow.step == 'prompts' and project_workflow.action in ['h3_prompts_generate_all', 'h3_prompt_generate_single', 'h3_prompt_optimize_single'] %}
正在调用后台 AI 模型生成导演级提示词
{{ project_workflow.detail if prompt_generation_active else '剧情镜头数与视频提示词数严格一致;每条使用 MiniMax 官方 Full-Reference 六段格式。' }}
正在通过远程地址池并发生成全部视频
{% if batch_generation_active %}{{ project_workflow.detail }}{% else %}剧情镜头与视频任务一一对应;提示词原样提交并按远程地址池并发生成,不使用末帧图片或参考视频。{% endif %}
{% if generation_failures %}
并发生成中有 {{ generation_failures|length }} 个镜头失败;其他成功镜头会保留,可单独补齐失败镜头后再合并。
{% endif %}
{% for prompt in video_prompt_items %} {% set raw_selected_asset_ids = prompt.reference_asset_ids|default([]) %} {% set asset_picture_slot_limit = video_max_reference_images %} {% set selected_asset_ids = raw_selected_asset_ids[:asset_picture_slot_limit] %} {% set uploaded_reference_media = prompt.uploaded_reference_media|default([]) %} {% set generation_failure = generation_failures.get(prompt.shot_id) %} {% set shot_h3_resolution = prompt.h3_resolution|default(project.h3_resolution|default('480p', true), true) %} {% set shot_h3_duration = prompt.h3_duration|default(prompt.duration|default(h3_video_duration_range.default, true), true) %} {% set shot_editor_duration = prompt.editor_duration|default(shot_h3_duration, true) %} {% set shot_h3_aspect_ratio = prompt.h3_aspect_ratio|default(prompt.aspect_ratio|default(project.aspect_ratio|default('9:16', true), true), true) %} {% set shot_generation_active = prompt.shot_id in active_shot_generation_ids %}

{{ prompt.shot_id }}

{{ shot_h3_duration }} 秒生成素材 · {{ shot_h3_aspect_ratio }} · 剧情镜头一一对应
{% for asset_id in selected_asset_ids %} {{ asset_id }} {% endfor %}
{% if prompt.director_warnings %}
{% for warning in prompt.director_warnings %}{{ warning.message }}{% if not loop.last %};{% endif %}{% endfor %}
{% endif %} {% if generation_failure %}
{{ prompt.shot_id }} 最近一次生成失败 · 阶段:{{ generation_failure.failed_stage or "未能确认" }} · {{ generation_failure.error or "视频生成后端未返回可用视频" }}
{% endif %}
本段参考资产(以第二步人工选择为准)
{% for slot_index in range(asset_picture_slot_limit) %} {% set selected_slot_asset_id = selected_asset_ids[slot_index] if slot_index < selected_asset_ids|length else '' %} {% set actual_picture_number = slot_index + 1 %} {% endfor %} {% set reference_videos = uploaded_reference_media|selectattr('media_type', 'equalto', 'video')|list %} {% set reference_audios = uploaded_reference_media|selectattr('media_type', 'equalto', 'audio')|list %} {% for media in reference_videos %}
Video {{ loop.index }} 已保存 视频 {{ media.original_name|default(media.name|default('参考视频')) }} {{ media.duration_seconds|default(0)|round(2) }} 秒 · 24fps{% if media.has_audio %} · 文件含声音,但不自动建立 Audio 引用{% endif %} {% if media.locked %} 原片自动关联 {% endif %}
{% endfor %} {% for media in reference_audios %}
Audio {{ loop.index }} 已保存 音频 {{ media.original_name|default(media.name|default('参考音频')) }} {{ media.duration_seconds|default(0)|round(2) }} 秒 · 32kHz
{% endfor %}
{{ prompt.shot_id }} 视频正在生成 {{ shot_h3_resolution }} · {{ h3_generation_mode.ui_label }} · {{ h3_generation_mode.fixed_steps }} 步 · {{ shot_h3_duration }} 秒 · {{ shot_h3_aspect_ratio }}
正在提交到 ComfyUI;任务在后台运行,可以刷新页面。
{% if shot_video_map.get(prompt.shot_id) %} {% set _st = (shot_times or {}).get(prompt.shot_id|int) %}
{{ prompt.shot_id }} 已有视频,可预览和合并{% if _st and _st.seconds_label %} · 用时 {{ _st.seconds_label }}{% endif %}
{% else %} {% endif %}
{% endfor %}
{% else %}
暂无 MiniMax H3 提示词任务。请先完成剧情脚本和资产绑定。
{% endif %}
{% if video_prompt_items %} {% for modal_id, modal_title, modal_action, progress_title, modal_confirm in [ ('generateMissingClipsModal', '补齐缺失镜头片段', 'local_minimax_generation_all_clips', '正在使用当前提示词补齐缺失镜头', '确定并补齐'), ('regenerateAllClipsModal', '一键生成全部视频', 'local_minimax_generation_regenerate_all', '正在通过远程地址池并发生成全部视频', '确定并生成全部视频') ] %} {% endfor %} {% endif %} {% endif %} {% if current_step == "generation" %}

完整视频

{% if exact_clone_mode %}前面的复刻片段全部生成后在这里合并;可选择是否添加字幕,添加后仍可在下方修改字幕并重新生成。{% else %}前面的分段视频全部生成完成后,在这里合并;可选择是否添加字幕。{% endif %}
{{ stage_timing('generation') }}
{% if video_prompt_items and shot_videos_ready %}
{% elif video_prompt_items %}
{% endif %} Step 5
{% if final_video_url %}
{% elif video_prompt_items and shot_videos_ready %}
所有分段视频已生成,可以点击上方“合并视频”生成完整视频。
{% elif video_prompt_items %}
请先回到第四步生成全部分段视频:{{ missing_shot_video_ids|join(", ") }}
{% else %}
暂无视频提示词。请先完成第四步。
{% endif %}
{# 模板即时生效(TEMPLATES_AUTO_RELOAD),但 Python 进程要重启才加载新 view_model: 进程还没重启时上下文里没有 has_timing_data,这里回退成旧口径,保证页面不空、不报错。 #} {% set tl_has_timing = stage_timeline.has_timing_data if stage_timeline.has_timing_data is defined else stage_timeline.has_stage_times %}

制作流程时间线

{% if tl_has_timing %} {% if stage_timeline.queue.running and not stage_timeline.has_stage_times %} {# 还在排队、什么都还没执行:只报"排队等待中",不报 0 秒的执行合计(避免误读成"跑完了 0 秒") #} 排队等待中 · 已等 {{ stage_timeline.queue.label }} {% else %} {% if stage_timeline.has_stage_times %}执行合计 {{ stage_timeline.total_label }}{% endif %} {% if stage_timeline.grand_total_label %}总耗时 {{ stage_timeline.grand_total_label }}{% endif %} {% if stage_timeline.queue.has %}其中排队等待 {{ stage_timeline.queue.label }}{% endif %} {% endif %} {% else %} 无计时数据 {% endif %}
{% if tl_has_timing %} 创建 {{ project.created_at or '—' }}{% if stage_timeline.queue.has %} · 入队等待 {{ stage_timeline.queue.started_clock or '—' }} → 开始执行 {{ stage_timeline.queue.finished_clock or '排队中' }}{% else %} · 开始执行 {{ stage_timeline.project_started_clock or '—' }}{% endif %}{% if stage_timeline.project_finished_clock %} · 结束 {{ stage_timeline.project_finished_clock }}{% else %} · 进行中{% endif %} {% if stage_timeline.queue.has and stage_timeline.has_stage_times %}
排队等待 {{ stage_timeline.queue.label }}(入队→开始执行,不计入任何阶段耗时) + 阶段执行 {{ stage_timeline.total_label }} = 总耗时 {{ stage_timeline.grand_total_label }}{% if stage_timeline.wall_clock_label %};项目创建→结束墙钟 {{ stage_timeline.wall_clock_label }}{% if stage_timeline.reconcile_diff_seconds is not none %}(差 {{ '%.1f'|format(stage_timeline.reconcile_diff_seconds) }} 秒){% endif %}{% endif %}{% elif stage_timeline.has_stage_times %} {% if stage_timeline.wall_clock_label %}项目创建→结束墙钟 {{ stage_timeline.wall_clock_label }}{% endif %}{% if stage_timeline.gap_label %} · 其中 {{ stage_timeline.gap_label }} 为阶段间空隙{% endif %}{% endif %} {% if not stage_timeline.has_stage_times %}
{% if stage_timeline.queue.running %}任务还没开始执行:下面只有排队等待,开跑后各阶段的开始时间与耗时会自动出现。{% else %}该任务由旧版本生成,未记录阶段时间;下面只有本任务的排队等待。{% endif %}{% endif %} {% else %} 该任务由旧版本生成,未记录阶段时间。新任务会自动记录每个步骤的开始时间、结束时间与耗时。 {% endif %}
{% if tl_has_timing %}
{% if stage_timeline.queue.has %} {% endif %} {% for row in stage_timeline.rows %} {% endfor %} {% if stage_timeline.has_stage_times %} {% endif %} {% if stage_timeline.grand_total_label %} {% endif %}
制作流程开始时间结束时间耗时
排队等待 {{ stage_timeline.queue.started_clock or '—' }} {% if stage_timeline.queue.running %}排队中{% else %}{{ stage_timeline.queue.finished_clock or '—' }}{% endif %} {% if stage_timeline.queue.running %}已等 {{ stage_timeline.queue.label }}{% else %}{{ stage_timeline.queue.label }}{% endif %}
{{ row.title }}{% if row.runs > 1 %} ×{{ row.runs }}{% endif %} {{ row.first_started_clock or '—' }} {% if row.running %}进行中{% else %}{{ row.finished_clock or '—' }}{% endif %} {% if row.prewarm %} {# 排队期间由预运行做完、本任务直接复用:执行耗时 0 秒会误导,显示预运行实际耗时 #} {{ row.prewarm.text }} {%- elif row.missing %}— {%- else -%} {{ ('已用 ' if row.running else '用时 ') ~ row.seconds_label }} {%- endif %}
执行合计 {{ stage_timeline.project_started_clock or '—' }} {% if stage_timeline.project_running %}进行中{% else %}{{ stage_timeline.project_finished_clock or '—' }}{% endif %} {{ stage_timeline.total_label }}
总耗时(排队 + 执行) {{ (stage_timeline.queue.started_clock if stage_timeline.queue.has else stage_timeline.project_started_clock) or '—' }} {% if stage_timeline.grand_total_running %}进行中{% else %}{{ stage_timeline.grand_total_finished_clock or '—' }}{% endif %} {{ stage_timeline.grand_total_label }} {% if stage_timeline.has_stage_times %}({{ stage_timeline.grand_total_note }}){% endif %}
{% if stage_timeline.has_stage_times %}「视频生成」已包含下面的分段(分镜视频生成 / 拼接成片 / 画质超分 / 字幕水印);分段之和 ≤ 该阶段总用时(镜头之间有卸模型、等待等开销)。{% endif %} {% if stage_timeline.prewarm.note %}
{{ stage_timeline.prewarm.note }} 标黄行的耗时取自该项目 prewarm.json(不是本任务的执行耗时)。{% endif %} {% if stage_timeline.prewarm.has is defined and not stage_timeline.prewarm.has %}本项目没有预运行记录(prewarm.json 不存在),未复用任何预运行成果。{% endif %}
{% endif %}
画质增强(超分) {% if enhanced_video_url %} 已增强到 {{ enhanced_resolution }} {% if enhance_info and enhance_info.size %}输出 {{ enhance_info.size }}|{{ enhance_info.frames }} 帧|用时 {{ enhance_info.seconds }} 秒({{ enhance_info.fps }} 帧/秒)|模型 {{ enhance_info.model or 'ffmpeg 增强' }}{% endif %} {% else %} 把成片提升到 720p / 1080p / 2K / 4K:画面内容不变、保留原音轨;480p 生成省显存,最后统一超分。 {% endif %}
{% if enhanced_video_url %} 下载增强版 {% endif %}
{% if subtitled_video_url %} 下载字幕水印版 {% endif %}
{{ task_progress('enhanceProgress', '正在超分增强成片', 'generation') }} {% if enhanced_video_url %}
{% endif %}
{% if subtitle_timeline %}

字幕时间轴

{% if subtitle_timeline_source == "edited" %} 当前使用手动修改后的字幕。 {% else %} 当前显示的是切分后的字幕结果,可直接修改错别字后重新生成。 {% endif %}
{{ subtitle_timeline|length }} 条
{% for item in subtitle_timeline %} {% endfor %}
序号 开始时间 结束时间 文案
#{{ item.number }} {{ item.display_start }} {{ item.display_end }}
保存后会写入本地字幕文件,并使用修改后的字幕文本重新生成最终视频。
{% elif final_video_url and shot_videos_ready %}
当前没有可编辑的字幕。请确认第 4 步提示词中包含台词内容。
{% endif %} {% if generated_videos %} {% endif %}
{% endif %}
{% endblock %} {% block page_scripts %} {# 这个块与 content 块各自作用域,同样的兜底要再来一份: edit_locked 由 app.py 给出,缺少该键(进程未重启)时按 status 判定 #} {% set edit_locked = project_workflow.edit_locked if project_workflow.edit_locked is defined else (project_workflow.status in ['running', 'starting', 'cancelling']) %} {% endblock %}