fix: support multilingual audio transcription
This commit is contained in:
8
RULES.md
8
RULES.md
@@ -11,7 +11,7 @@
|
||||
- 详见 `CLAUDE.md` 立项决策段 + `.memory/plan.md` 七步管线拆解
|
||||
- 风格:`04-Dark-Gallery-Ambient`(路径:`~/Projects/research/20260305-网页风格库/04-Dark-Gallery-Ambient.md`)
|
||||
- 第一冲刺:步骤 1-4(下载 / 拆轨 / 关键帧 / ASR+翻译)
|
||||
- 当前产品方向(2026-05-20 再确认):信息流广告快速复刻默认进入“三字段候选生成”工作流。主界面为“左侧素材输入列 + 右侧信息流复刻工作表”;工作台已取消 1800x1000 固定画布和整页缩放,改为正常流式桌面容器,宽度跟随浏览器展开,只保留 1280px 最低操作宽度防止核心表格被压烂,不再通过应用层 `zoom` 把整页缩小导致文字发虚。用户粘贴 TK 链接或上传视频后点击“开始分析”,系统自动下载源视频;下载完成后并行启动两条路:音频文案路提取原音频文案/字幕,并分析讲话人、语速节奏、背景音乐/环境声/音效;视频视觉路自动抽取参考帧。源视频工作区主体链路改为“上方参考帧池 + 转换层、下方主体元素结果栏”:参考帧池竖向排列;转换层是轻量对话式生图确认区,参考图可通过左侧缩略图 `+`、参考帧拖拽、胶片拖拽或本地图片拖入进入转换层,用户选择 GPT/Gemini 套件后先分析参考图;识别结果里的特征 chip 只作为“保留元素”本地选择,点亮=保留、再点取消,点击不立即请求模型,随下一条发送消息提交;用户再在下方发送区发送复刻/创新/卡通和画面要求,界面只保留生成要求输入框、张数控件和提示词就绪状态,不展示当前要求摘要、保留元素副本、收起记录计数或重复模型确认话术,生成数量通过发送区旁边的张数控件控制;后端返回英文出图 prompt 后不再自动弹窗,发送区主按钮直接切换为“确认生成 N 张”,用户点击才生成对应数量的统一多角度套图。主体元素结果栏在转换层下方横向展示套图输出、文件夹分组、单张重生、删除和 hover 预览,空态只保留紧凑提示,不再挤占右侧整列。旧下方“相似主体 / 主体模板库”不再作为主路径。波形下方的画面胶片只是临时预览,点击只跳转原视频时间点,双击或拖进参考帧池才正式加入关键帧,已加入的胶片直接显示“已添加”。产品图上传后独立形成产品资产包,自动识别视角/结构/比例并补缺角度。分镜工作台按逐句时间轴默认只露“文案 / 场景一句话 / 人物+产品+动作”,产品素材池、批量控制、三字段、视频候选和高级区都必须可折叠;视频候选无内容时默认不占大面积,有候选时默认只显示迷你缩略条,展开后才显示 4-grid。单条默认生成 4 个视频候选,顶部支持整片批量生成候选;首尾帧、视觉规划、产品出现方式和旧 6 字段保留在“高级”抽屉与后端 quick-plan 自动展开中,不能再作为客户默认闸门。
|
||||
- 当前产品方向(2026-05-20 再确认):信息流广告快速复刻默认进入“三字段候选生成”工作流。主界面为“左侧素材输入列 + 右侧信息流复刻工作表”;工作台已取消 1800x1000 固定画布和整页缩放,改为正常流式桌面容器,宽度跟随浏览器展开,只保留 1280px 最低操作宽度防止核心表格被压烂,不再通过应用层 `zoom` 把整页缩小导致文字发虚。用户粘贴 TK 链接或上传视频后点击“开始分析”,系统自动下载源视频;下载完成后并行启动两条路:音频文案路自动识别中文、英文和其他多语言原音频文案/字幕,统一补齐中文镜像,并分析讲话人、语速节奏、背景音乐/环境声/音效;视频视觉路自动抽取参考帧。源视频工作区主体链路改为“上方参考帧池 + 转换层、下方主体元素结果栏”:参考帧池竖向排列;转换层是轻量对话式生图确认区,参考图可通过左侧缩略图 `+`、参考帧拖拽、胶片拖拽或本地图片拖入进入转换层,用户选择 GPT/Gemini 套件后先分析参考图;识别结果里的特征 chip 只作为“保留元素”本地选择,点亮=保留、再点取消,点击不立即请求模型,随下一条发送消息提交;用户再在下方发送区发送复刻/创新/卡通和画面要求,界面只保留生成要求输入框、张数控件和提示词就绪状态,不展示当前要求摘要、保留元素副本、收起记录计数或重复模型确认话术,生成数量通过发送区旁边的张数控件控制;后端返回英文出图 prompt 后不再自动弹窗,发送区主按钮直接切换为“确认生成 N 张”,用户点击才生成对应数量的统一多角度套图。主体元素结果栏在转换层下方横向展示套图输出、文件夹分组、单张重生、删除和 hover 预览,空态只保留紧凑提示,不再挤占右侧整列。旧下方“相似主体 / 主体模板库”不再作为主路径。波形下方的画面胶片只是临时预览,点击只跳转原视频时间点,双击或拖进参考帧池才正式加入关键帧,已加入的胶片直接显示“已添加”。产品图上传后独立形成产品资产包,自动识别视角/结构/比例并补缺角度。分镜工作台按逐句时间轴默认只露“文案 / 场景一句话 / 人物+产品+动作”,产品素材池、批量控制、三字段、视频候选和高级区都必须可折叠;视频候选无内容时默认不占大面积,有候选时默认只显示迷你缩略条,展开后才显示 4-grid。单条默认生成 4 个视频候选,顶部支持整片批量生成候选;首尾帧、视觉规划、产品出现方式和旧 6 字段保留在“高级”抽屉与后端 quick-plan 自动展开中,不能再作为客户默认闸门。
|
||||
|
||||
## 部署事实
|
||||
- 平台:VPS `76.13.31.179`(Ubuntu 24.04 / Docker Compose / Coolify Traefik)
|
||||
@@ -62,7 +62,7 @@
|
||||
- 独立预览容器重建命令:服务器 `/opt/skg-marketing-studio` 下执行 `docker compose -f docker-compose.standalone.yml --env-file deploy/.env.production up -d --build`;Web 暴露 `0.0.0.0:4290->80`,后端仅在 compose 内部网络暴露,`/api/` 由 Web 容器 Nginx 反代并复用应用内登录校验。
|
||||
- 生产架构:`web` 容器用 Nginx 承载 Next 静态导出;`/login/`、`/_next/`、`/assets/`、`/skg-logo-black.svg`、`/oasis-source/` 等登录页必需静态资源公开访问;未登录访问工作台跳转 `/login/`,`/api/` 通过 Nginx `auth_request` 校验 FastAPI 会话 Cookie 后反代到 `skg-marketing-api:4291`;Traefik 通过 `coolify` 外部网络接入 80/443
|
||||
- Web 验收必须以生产 Docker 形态为准:前端是 `next export` 静态产物 + Nginx,不是 `next dev` / `next start`。任何 Web 改动部署后必须运行 `./scripts/verify-prod-docker.sh`,确认 `/login/`、`/_next/`、`/api/health`、本地 API 地址泄漏和 API 镜像 `.env` 污染检查通过;不能只用本地 `npm run build` 作为上线依据。
|
||||
- 当前音频解析:`https://ai.skg.com/azure/v1` 的 `gpt-4o-transcribe` 当前返回 `DeploymentNotFound`,且官方 Azure OpenAI transcription 路径探测也未返回可用部署;生产临时复制本地成功策略,直接使用容器内 `faster-whisper tiny.en` 真实转写,关闭 Gemini 多模态音频兜底。拿到真实 Azure ASR deployment 名后再恢复 `ASR_REMOTE_ENABLED=true`。
|
||||
- 当前音频解析:`https://ai.skg.com/azure/v1` 的 `gpt-4o-transcribe` 当前返回 `DeploymentNotFound`,且官方 Azure OpenAI transcription 路径探测也未返回可用部署;生产临时复制本地成功策略,直接使用容器内多语言 `faster-whisper` 真实转写,默认语种为 `auto`,支持中文、英文和其他多语言原文识别,关闭 Gemini 多模态音频兜底。拿到真实 Azure ASR deployment 名后再恢复 `ASR_REMOTE_ENABLED=true`,并保持 `ASR_LANGUAGE` 为空或 `auto`,除非明确只想强制单一语种。
|
||||
- 持久化目录:服务器 `./data/jobs` 挂载到后端 `/data/jobs`;全局资源中心持久化在 `./data/asset_library`、`./data/prompt_library` 和 `./data/_trash`
|
||||
- TikTok 下载登录态:公开视频默认不带 cookies 直接下载,生产环境变量必须显式保持 `YTDLP_COOKIES_FILE=`、`YTDLP_COOKIES_FROM_BROWSER=` 为空,防止容器读取不存在的浏览器 cookies。只有 TikTok 明确要求登录态时,才使用服务器私有 cookies 文件 `./secrets/tiktok_cookies.txt` 挂载到 API 容器 `/run/secrets/tiktok_cookies.txt` 并配置 `YTDLP_COOKIES_FILE=/run/secrets/tiktok_cookies.txt`;`yt-dlp` 会在任务结束时回写 cookies,因此不要把该挂载设为只读;不要使用云端浏览器读取方案,也不要把 cookies 入库。生产容器严禁使用 `YTDLP_COOKIES_FROM_BROWSER=chrome`。
|
||||
- 登录凭证:用户名写下方快捷登录;密码明文备份只放服务器 `/root/skg-marketing-studio-login.txt`,生产环境变量 `WEB_AUTH_PASSWORD` / `WEB_AUTH_SESSION_SECRET` 只放服务器 `deploy/.env.production`
|
||||
@@ -94,11 +94,11 @@
|
||||
- `LLM_BASE_URL` / `LLM_API_KEY`:OpenAI 兼容网关,用于翻译、文案改写、音频分析等文本/多模态理解模型调用
|
||||
- `ASR_BASE_URL` / `ASR_API_KEY`:OpenAI Audio Transcriptions 兼容网关,用于上传 `audio.wav` 做真实转写;未配置 `ASR_API_KEY` 时复用 `LLM_API_KEY`,生产默认指向 `https://ai.skg.com/azure/v1`
|
||||
- `ASR_MODEL`:OpenAI Audio Transcriptions 音频转写模型;微软通道使用 Azure OpenAI 部署名 `gpt-4o-transcribe`,如果 Azure 侧实际部署名不同必须同步改这里
|
||||
- `ASR_LANGUAGE`:远端 ASR 的输入语言提示,默认 `en`;微软官方说明指定 ISO-639-1 语言可改善准确率和延迟。
|
||||
- `ASR_LANGUAGE`:远端和本地 ASR 的可选输入语言提示,默认空值/`auto`,由模型自动识别中文、英文和其他多语言;只有明确知道素材固定语种时才填写 ISO-639-1 代码强制识别。
|
||||
- `ASR_REMOTE_ENABLED`:是否启用远端 OpenAI Audio Transcriptions;微软 ASR 验收时必须为 `true`。当前生产因 `https://ai.skg.com/azure/v1` 下 `gpt-4o-transcribe` 返回 `DeploymentNotFound`,临时设为 `false`,直接走容器内 `faster-whisper`,等真实 Azure deployment 名补齐后再恢复。
|
||||
- `ASR_LOCAL_FALLBACK_ENABLED`:是否允许远端 ASR 失败后落到本机 / 容器内 ASR;当前生产为 `true`,复制本地成功路径的“本机真实转写”策略,云端用 CPU 版 `faster-whisper` 替代本机 Mac 的 `mlx_whisper`。
|
||||
- `ASR_AUDIO_FALLBACK_ENABLED`:是否允许远端和本机 ASR 失败后落到多模态音频兜底;生产微软 ASR 验收设为 `false`,避免静默使用 Gemini 音频
|
||||
- `FASTER_WHISPER_MODEL` / `FASTER_WHISPER_DEVICE` / `FASTER_WHISPER_COMPUTE_TYPE`:容器内本地 ASR 兜底,仅在 `ASR_LOCAL_FALLBACK_ENABLED=true` 时启用
|
||||
- `FASTER_WHISPER_MODEL` / `FASTER_WHISPER_DEVICE` / `FASTER_WHISPER_COMPUTE_TYPE`:容器内本地 ASR 兜底,仅在 `ASR_LOCAL_FALLBACK_ENABLED=true` 时启用;默认用多语言 `base`,不要改回 `*.en` 英文专用模型,否则中文和多语言识别会退化。
|
||||
- `ASR_FALLBACK_MODEL`:多模态音频兜底模型,仅在 `ASR_AUDIO_FALLBACK_ENABLED=true` 时用于兜底或音频画像,默认 `gemini-2.5-flash`;如果模型不能真实听到音频或返回疑似逐秒假字幕,后端必须拒绝写入时间轴
|
||||
- `ASR_TIMEOUT_SECONDS`:远端 ASR / 翻译 / 音频分析单次请求超时;当前生产本地转写模式设为 45 秒,微软 ASR 重新启用时可按素材长度提高。
|
||||
- `LOCAL_ASR_BIN` / `LOCAL_ASR_MODEL` / `LOCAL_ASR_TIMEOUT_SECONDS`:本机 ASR 兜底,默认使用 `/opt/homebrew/bin/mlx_whisper` + `mlx-community/whisper-tiny`,用于当前 SKG 网关 `/audio/transcriptions` 不可用时生成真实逐句时间轴
|
||||
|
||||
@@ -35,6 +35,6 @@ uvicorn main:app --host 127.0.0.1 --port 4291
|
||||
|
||||
- `ffmpeg` 系统二进制(拆轨 / 抽帧)
|
||||
- `yt-dlp` 系统二进制(也可走 Python 包)
|
||||
- OpenAI 兼容 LLM 网关(ASR / 翻译 / 文案改写 / 视觉 brief);远端 `whisper-1` 失败后先走本机 `mlx_whisper`,再用 `ASR_FALLBACK_MODEL` 走 Gemini 多模态音频识别,后端会拒绝疑似假字幕或覆盖率过低的时间轴
|
||||
- OpenAI 兼容 LLM 网关(ASR / 翻译 / 文案改写 / 视觉 brief);ASR 默认自动识别中文、英文和其他多语言,远端失败后先走容器内多语言 `faster-whisper` / 本机 `mlx_whisper`,再按开关用 `ASR_FALLBACK_MODEL` 走多模态音频识别,后端会拒绝疑似假字幕或覆盖率过低的时间轴
|
||||
- GPT 图片网关(当前所有生图 / 修图 / 产品视角识别 / 主体资产 / 首尾帧都强制使用 `gpt-image-2`,不做其他图片模型 fallback)
|
||||
- Azure OpenAI TTS(后续新配音阶段使用 `AZURE_OPENAI_API_KEY`;默认模型 `gpt-4o-mini-tts`,按 `AZURE_TTS_PATHS` 依次尝试语音路径)
|
||||
|
||||
64
api/main.py
64
api/main.py
@@ -63,13 +63,13 @@ LLM_API_KEY = os.getenv("LLM_API_KEY", "").strip()
|
||||
ASR_BASE_URL = os.getenv("ASR_BASE_URL", LLM_BASE_URL).strip()
|
||||
ASR_API_KEY = (os.getenv("ASR_API_KEY") or LLM_API_KEY).strip()
|
||||
ASR_MODEL = os.getenv("ASR_MODEL", "whisper-1")
|
||||
ASR_LANGUAGE = os.getenv("ASR_LANGUAGE", "en").strip()
|
||||
ASR_LANGUAGE = os.getenv("ASR_LANGUAGE", "").strip()
|
||||
ASR_REMOTE_ENABLED = os.getenv("ASR_REMOTE_ENABLED", "true").strip().lower() not in {"0", "false", "no", "off"}
|
||||
ASR_LOCAL_FALLBACK_ENABLED = os.getenv("ASR_LOCAL_FALLBACK_ENABLED", "true").strip().lower() not in {"0", "false", "no", "off"}
|
||||
ASR_AUDIO_FALLBACK_ENABLED = os.getenv("ASR_AUDIO_FALLBACK_ENABLED", "true").strip().lower() not in {"0", "false", "no", "off"}
|
||||
ASR_FALLBACK_MODEL = os.getenv("ASR_FALLBACK_MODEL", "gemini-2.5-flash").strip() or "gemini-2.5-flash"
|
||||
ASR_TIMEOUT_SECONDS = max(15, int(os.getenv("ASR_TIMEOUT_SECONDS", "45")))
|
||||
FASTER_WHISPER_MODEL = os.getenv("FASTER_WHISPER_MODEL", "tiny.en").strip() or "tiny.en"
|
||||
FASTER_WHISPER_MODEL = os.getenv("FASTER_WHISPER_MODEL", "base").strip() or "base"
|
||||
FASTER_WHISPER_DEVICE = os.getenv("FASTER_WHISPER_DEVICE", "cpu").strip() or "cpu"
|
||||
FASTER_WHISPER_COMPUTE_TYPE = os.getenv("FASTER_WHISPER_COMPUTE_TYPE", "int8").strip() or "int8"
|
||||
LOCAL_ASR_BIN = os.getenv("LOCAL_ASR_BIN", "").strip()
|
||||
@@ -79,6 +79,20 @@ TRANSLATE_MODEL = os.getenv("TRANSLATE_MODEL", "gemini-2.5-flash")
|
||||
DEFAULT_GPT_TEXT_MODEL = os.getenv("GPT_TEXT_MODEL", "gpt-4o").strip() or "gpt-4o"
|
||||
|
||||
|
||||
ASR_AUTO_LANGUAGE_VALUES = {"", "auto", "detect", "multilingual", "multi"}
|
||||
|
||||
|
||||
def _asr_language_hint() -> str:
|
||||
language = ASR_LANGUAGE.strip()
|
||||
if language.lower() in ASR_AUTO_LANGUAGE_VALUES:
|
||||
return ""
|
||||
return language
|
||||
|
||||
|
||||
def _asr_language_label() -> str:
|
||||
return _asr_language_hint() or "auto"
|
||||
|
||||
|
||||
def gpt_model_env(name: str, default: str | None = None) -> str:
|
||||
value = os.getenv(name, default or DEFAULT_GPT_TEXT_MODEL).strip()
|
||||
if not value or value.lower().startswith("gemini-"):
|
||||
@@ -2811,7 +2825,7 @@ def _clean_asr_segments(segments: list[dict], duration: float) -> list[dict]:
|
||||
|
||||
|
||||
def _segment_text_key(text: str) -> str:
|
||||
return re.sub(r"[^a-z0-9]+", " ", text.lower()).strip()
|
||||
return re.sub(r"[^\w]+", " ", text.casefold(), flags=re.UNICODE).strip()
|
||||
|
||||
|
||||
def _validate_asr_segments(segments: list[dict], duration: float, source: str) -> list[dict]:
|
||||
@@ -2909,19 +2923,22 @@ def _transcribe_faster_whisper_sync(wav: Path) -> list[dict]:
|
||||
device=FASTER_WHISPER_DEVICE,
|
||||
compute_type=FASTER_WHISPER_COMPUTE_TYPE,
|
||||
)
|
||||
raw_segments, _info = model.transcribe(
|
||||
str(wav.resolve()),
|
||||
language="en",
|
||||
beam_size=1,
|
||||
vad_filter=True,
|
||||
condition_on_previous_text=False,
|
||||
)
|
||||
language_hint = _asr_language_hint()
|
||||
transcribe_options = {
|
||||
"beam_size": 1,
|
||||
"vad_filter": True,
|
||||
"condition_on_previous_text": False,
|
||||
}
|
||||
if language_hint:
|
||||
transcribe_options["language"] = language_hint
|
||||
raw_segments, _info = model.transcribe(str(wav.resolve()), **transcribe_options)
|
||||
detected_language = str(getattr(_info, "language", "") or language_hint or "auto")
|
||||
segments = [
|
||||
{"start": float(seg.start), "end": float(seg.end), "text": str(seg.text or "").strip()}
|
||||
for seg in raw_segments
|
||||
if str(seg.text or "").strip()
|
||||
]
|
||||
return _validate_asr_segments(segments, duration, f"faster-whisper:{FASTER_WHISPER_MODEL}")
|
||||
return _validate_asr_segments(segments, duration, f"faster-whisper:{FASTER_WHISPER_MODEL}:{detected_language}")
|
||||
|
||||
|
||||
def _transcribe_gemini_sync(wav: Path) -> list[dict]:
|
||||
@@ -2931,8 +2948,9 @@ def _transcribe_gemini_sync(wav: Path) -> list[dict]:
|
||||
"Transcribe the attached audio. Return strict JSON only, no markdown. "
|
||||
"If you cannot truly hear the audio, return {\"can_hear\": false}. Do not guess. "
|
||||
"If you can hear it, return {\"can_hear\": true, \"segments\": "
|
||||
"[{\"start\": 0.0, \"end\": 1.2, \"text\": \"English transcript\"}]}. "
|
||||
"Use English for the transcript. Only include timestamps you can infer from the audio."
|
||||
"[{\"start\": 0.0, \"end\": 1.2, \"text\": \"original-language transcript\"}]}. "
|
||||
"Keep the transcript in the spoken source language; do not translate it here. "
|
||||
"Only include timestamps you can infer from the audio."
|
||||
)
|
||||
last_error: Exception | None = None
|
||||
for attempt in range(3):
|
||||
@@ -2961,19 +2979,21 @@ def _transcribe_sync(wav: Path) -> list[dict]:
|
||||
if ASR_REMOTE_ENABLED:
|
||||
try:
|
||||
with wav.open("rb") as f:
|
||||
language_hint = _asr_language_hint()
|
||||
resp = asr_llm().with_options(timeout=ASR_TIMEOUT_SECONDS).audio.transcriptions.create(
|
||||
file=(wav.name, f, "audio/wav"),
|
||||
model=ASR_MODEL,
|
||||
response_format="verbose_json",
|
||||
timestamp_granularities=["segment"],
|
||||
**({"language": ASR_LANGUAGE} if ASR_LANGUAGE else {}),
|
||||
**({"language": language_hint} if language_hint else {}),
|
||||
)
|
||||
raw = resp.model_dump() if hasattr(resp, "model_dump") else resp
|
||||
segments = raw.get("segments") or []
|
||||
# 兜底:网关如果不返回 segments,把全文当一段
|
||||
if not segments and raw.get("text"):
|
||||
segments = [{"start": 0.0, "end": float(raw.get("duration", 0) or 0), "text": raw["text"]}]
|
||||
return _validate_asr_segments(segments, duration, ASR_MODEL)
|
||||
detected_language = str(raw.get("language") or language_hint or "auto")
|
||||
return _validate_asr_segments(segments, duration, f"{ASR_MODEL}:{detected_language}")
|
||||
except Exception as e:
|
||||
errors.append(f"{ASR_MODEL}: {e}")
|
||||
else:
|
||||
@@ -3001,11 +3021,13 @@ def _transcribe_sync(wav: Path) -> list[dict]:
|
||||
|
||||
def _translate_sync(segments: list[dict]) -> list[str]:
|
||||
"""批量翻译为中文,按段返回"""
|
||||
payload = [{"i": i, "en": s.get("text", "").strip()} for i, s in enumerate(segments)]
|
||||
payload = [{"i": i, "text": s.get("text", "").strip()} for i, s in enumerate(segments)]
|
||||
prompt = (
|
||||
"你是字幕翻译。把下列英文字幕段翻译为简体中文,保持原意、口语化、自然流畅。"
|
||||
"严格返回 JSON 数组,不要任何 markdown 或多余文字,schema: "
|
||||
'[{"i": 0, "zh": "..."}, ...]\n\n输入:\n'
|
||||
"你是多语言字幕翻译。把下列原语言字幕段翻译为简体中文;"
|
||||
"如果原文已经是中文,只做简体中文规范化和口语化整理,不要改写意思。"
|
||||
"保持原意、口语化、自然流畅。"
|
||||
"严格返回 JSON object,不要任何 markdown 或多余文字,schema: "
|
||||
'{"translations":[{"i": 0, "zh": "..."}]}\n\n输入:\n'
|
||||
+ json.dumps(payload, ensure_ascii=False)
|
||||
)
|
||||
try:
|
||||
@@ -3432,7 +3454,7 @@ def pipeline_transcribe(job_id: str, manage_job_status: bool = True) -> None:
|
||||
return
|
||||
|
||||
# 1) whisper ASR
|
||||
progress(f"{ASR_MODEL} 转录中…", 78)
|
||||
progress(f"{ASR_MODEL} {_asr_language_label()} 语种转录中…", 78)
|
||||
segments = _transcribe_sync(wav)
|
||||
if not segments:
|
||||
raise TranscriptionUnavailable("ASR 未返回可用字幕段")
|
||||
@@ -4494,7 +4516,7 @@ def health() -> dict:
|
||||
"voice_base_url": AZURE_OPENAI_BASE_URL,
|
||||
"models": {
|
||||
"asr": ASR_MODEL,
|
||||
"asr_language": ASR_LANGUAGE,
|
||||
"asr_language": _asr_language_label(),
|
||||
"asr_base_url": ASR_BASE_URL or LLM_BASE_URL or "openai-default",
|
||||
"asr_remote_enabled": ASR_REMOTE_ENABLED,
|
||||
"asr_local_fallback_enabled": ASR_LOCAL_FALLBACK_ENABLED,
|
||||
|
||||
@@ -569,7 +569,7 @@
|
||||
|
||||
<section id="pipeline" data-search>
|
||||
<h2>业务管线</h2>
|
||||
<p>当前产品方向已收窄为“信息流广告快速复刻”:主界面左侧是拉满工作台可用高度的 65px 胶囊工具条,鼠标移入或键盘聚焦会从侧边滑出素材输入面板,点击素材任务按钮可固定展开,右侧主画布是信息流复刻工作表;工作台已取消 1800x1000 固定画布和整页 <code>zoom</code> 缩放,改为正常流式桌面容器,宽度跟随浏览器展开,只保留 1280px 最低操作宽度防止核心表格被压烂,避免小数缩放造成文字发虚和比例失衡。后台仍按 01-09 流程顺序计算素材任务、源视频、音频文案、抽帧、主体资产、产品资产、分镜文案、三字段规划和视频候选这些状态,但这些判断不再默认显现在工作区顶部,避免状态提示挤占首屏操作空间。用户粘贴 TK 链接或上传视频后点击“开始分析”,系统自动下载源视频;下载完成后并行启动音频文案路和视频视觉路。音频文案路提取原音频文案/字幕,分析讲话人、语速节奏、背景音乐/环境声/音效,并为后续新口播和分镜文案提供时间轴;视频视觉路同步抽取参考帧。源视频工作区主体链路是“上方参考帧池 + 转换层、下方主体元素结果栏”:参考帧池只作为竖向原始参考;转换层改为轻量对话式生图确认区,参考图可通过左侧缩略图 <code>+</code>、参考帧拖拽、胶片拖拽或本地图片拖入进入转换层,用户选择 GPT/Gemini 套件后先分析参考图,再在下方消息输入区发送复刻、创新、卡通、数量和画面要求;系统返回英文出图 prompt 后不再自动弹窗,发送区主按钮直接切换为“确认生成 N 张”,用户点击后才调用主体生成并把结果送到下方主体元素结果栏。主体元素结果栏保留已有套图输出、文件夹分组、单张重生、删除和 hover 预览逻辑,空态只保留紧凑提示,不再占据右侧整列。旧下方主体模板库不再作为主路径。波形下方的画面胶片由前端临时从源视频截取,密度可调,点击只跳转原视频时间点,双击或拖入参考帧池才调用手动抽帧接口正式写入关键帧;已写入的胶片显示“已添加”,相同素材、相同密度和时长下会复用内存缓存,避免返回页面时重复扫视频。产品图上传后独立形成产品资产包:自动识别视角、左右/上下/内外侧、结构点、比例和风险,并补缺角度。最终分镜规划按逐句时间轴把文案、主体元素和产品资产汇合;每条分镜默认是左侧“文案 / 场景一句话 / 人物+产品+动作”三字段、右侧横向视频候选轨。客户可直接改中文镜像,前端会调用改写/翻译链路自动优化对应英文主值;单条和整片都可选择生成数量,整片按行排队提交。视频候选提交后立即写入当前任务,完成后自动回填 mp4,不需要用户另点“保存”;候选卡的普通点击只用于打开预览,右上角提供显式下载按钮;候选选择不再作为默认点击语义。首尾帧、视觉规划、产品出现方式等细节保留在高级抽屉和后端自动展开逻辑里,不再作为客户默认闸门。</p>
|
||||
<p>当前产品方向已收窄为“信息流广告快速复刻”:主界面左侧是拉满工作台可用高度的 65px 胶囊工具条,鼠标移入或键盘聚焦会从侧边滑出素材输入面板,点击素材任务按钮可固定展开,右侧主画布是信息流复刻工作表;工作台已取消 1800x1000 固定画布和整页 <code>zoom</code> 缩放,改为正常流式桌面容器,宽度跟随浏览器展开,只保留 1280px 最低操作宽度防止核心表格被压烂,避免小数缩放造成文字发虚和比例失衡。后台仍按 01-09 流程顺序计算素材任务、源视频、音频文案、抽帧、主体资产、产品资产、分镜文案、三字段规划和视频候选这些状态,但这些判断不再默认显现在工作区顶部,避免状态提示挤占首屏操作空间。用户粘贴 TK 链接或上传视频后点击“开始分析”,系统自动下载源视频;下载完成后并行启动音频文案路和视频视觉路。音频文案路自动识别中文、英文和其他多语言原音频文案/字幕,统一补齐中文镜像,分析讲话人、语速节奏、背景音乐/环境声/音效,并为后续新口播和分镜文案提供时间轴;视频视觉路同步抽取参考帧。源视频工作区主体链路是“上方参考帧池 + 转换层、下方主体元素结果栏”:参考帧池只作为竖向原始参考;转换层改为轻量对话式生图确认区,参考图可通过左侧缩略图 <code>+</code>、参考帧拖拽、胶片拖拽或本地图片拖入进入转换层,用户选择 GPT/Gemini 套件后先分析参考图,再在下方消息输入区发送复刻、创新、卡通、数量和画面要求;系统返回英文出图 prompt 后不再自动弹窗,发送区主按钮直接切换为“确认生成 N 张”,用户点击后才调用主体生成并把结果送到下方主体元素结果栏。主体元素结果栏保留已有套图输出、文件夹分组、单张重生、删除和 hover 预览逻辑,空态只保留紧凑提示,不再占据右侧整列。旧下方主体模板库不再作为主路径。波形下方的画面胶片由前端临时从源视频截取,密度可调,点击只跳转原视频时间点,双击或拖入参考帧池才调用手动抽帧接口正式写入关键帧;已写入的胶片显示“已添加”,相同素材、相同密度和时长下会复用内存缓存,避免返回页面时重复扫视频。产品图上传后独立形成产品资产包:自动识别视角、左右/上下/内外侧、结构点、比例和风险,并补缺角度。最终分镜规划按逐句时间轴把文案、主体元素和产品资产汇合;每条分镜默认是左侧“文案 / 场景一句话 / 人物+产品+动作”三字段、右侧横向视频候选轨。客户可直接改中文镜像,前端会调用改写/翻译链路自动优化对应英文主值;单条和整片都可选择生成数量,整片按行排队提交。视频候选提交后立即写入当前任务,完成后自动回填 mp4,不需要用户另点“保存”;候选卡的普通点击只用于打开预览,右上角提供显式下载按钮;候选选择不再作为默认点击语义。首尾帧、视觉规划、产品出现方式等细节保留在高级抽屉和后端自动展开逻辑里,不再作为客户默认闸门。</p>
|
||||
<div class="pipeline">
|
||||
<div class="step"><div class="num">01</div><h3>素材输入</h3><p>有当前素材任务即通过;输入框只负责创建或切换任务。</p></div>
|
||||
<div class="step"><div class="num">02</div><h3>源视频下载</h3><p><code>job.video_url</code> 存在即通过;<code>created/downloading</code> 视为运行中。公开视频默认不带 cookies 下载;只有 TikTok 明确要求登录态时才配置 <code>YTDLP_COOKIES_FILE</code>,生产容器禁止使用 <code>YTDLP_COOKIES_FROM_BROWSER=chrome</code>。</p></div>
|
||||
@@ -797,7 +797,7 @@ api/main.py
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>AudioScript</h3>
|
||||
<p>第一步音频解析的结构化产物。<code>pipeline_transcribe</code> 提取 <code>audio.wav</code> 后先保存原始英文转写、中文翻译、讲话人画像、口播节奏和背景音乐/环境声/音效分析。<code>rewritten_text</code> 是英文新口播,<code>rewritten_text_zh</code> 只作为团队审稿镜像;<code>voice_url</code> 等字段仍保留给后续新配音阶段。</p>
|
||||
<p>第一步音频解析的结构化产物。<code>pipeline_transcribe</code> 提取 <code>audio.wav</code> 后先保存原语言转写(支持中文、英文和其他多语言)、中文镜像、讲话人画像、口播节奏和背景音乐/环境声/音效分析。<code>rewritten_text</code> 是英文新口播,<code>rewritten_text_zh</code> 只作为团队审稿镜像;<code>voice_url</code> 等字段仍保留给后续新配音阶段。</p>
|
||||
<pre>AudioScript {
|
||||
status: idle | rewriting | completed | failed,
|
||||
source_text,
|
||||
@@ -1006,7 +1006,7 @@ ProductRefStateItem {
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>网页登录</td><td><code>POST /auth/login</code>、<code>GET /auth/check</code>、<code>POST /auth/logout</code></td><td><code>web/app/login/page.tsx</code>、Nginx <code>auth_request</code></td><td>登录页提交账号密码到 <code>/api/auth/login</code>,后端设置 HttpOnly 会话 Cookie;生产 Nginx 对工作台和 <code>/api/</code> 调 <code>/auth/check</code> 做统一校验,未登录页面跳 <code>/login/</code>,API 返回 JSON 401。</td></tr>
|
||||
<tr><td>运行配置 / 模型标注</td><td><code>GET /health</code></td><td><code>getRuntimeHealth</code>、<code>ModelTrace</code></td><td>返回 <code>models</code>:ASR、<code>asr_language</code>、<code>asr_base_url</code>、<code>asr_remote_enabled</code>、<code>asr_local_fallback_enabled</code>、<code>asr_audio_fallback_enabled</code>、<code>faster_whisper</code>、本机 ASR、ASR fallback、翻译、GPT 改写、GPT 画面理解、产品视角识别 <code>product_view</code>、主图像模型 <code>gpt-image-2</code>、图片故障兜底 <code>image_fallbacks</code>、短时熔断状态 <code>image_circuit</code>、主体 6 视图模型链路、Azure OpenAI TTS、视频别名和 Seedance 服务商。当前 <code>REWRITE_MODEL</code>、<code>AUDIO_REWRITE_MODEL</code> 和 <code>VISION_MODEL</code> 默认使用 <code>gpt-4o</code>;如果旧环境变量仍写 <code>gemini-*</code>,后端会归一化回 <code>GPT_TEXT_MODEL</code> / <code>REWRITE_MODEL</code>。语音只走 Azure OpenAI TTS,<code>models.voice_tts_paths</code> 会回传当前尝试的语音路径,方便区分路径错误和语音服务不可用。前端所有当前主路径里会调用模型的按钮旁显示模型名,点击弹出小窗口查看模型链路和输入输出逻辑;不返回 API Key 或敏感凭证。</td></tr>
|
||||
<tr><td>运行配置 / 模型标注</td><td><code>GET /health</code></td><td><code>getRuntimeHealth</code>、<code>ModelTrace</code></td><td>返回 <code>models</code>:ASR、<code>asr_language</code>(默认 <code>auto</code>,表示中文/英文/多语言自动识别)、<code>asr_base_url</code>、<code>asr_remote_enabled</code>、<code>asr_local_fallback_enabled</code>、<code>asr_audio_fallback_enabled</code>、<code>faster_whisper</code>、本机 ASR、ASR fallback、翻译、GPT 改写、GPT 画面理解、产品视角识别 <code>product_view</code>、主图像模型 <code>gpt-image-2</code>、图片故障兜底 <code>image_fallbacks</code>、短时熔断状态 <code>image_circuit</code>、主体 6 视图模型链路、Azure OpenAI TTS、视频别名和 Seedance 服务商。当前 <code>REWRITE_MODEL</code>、<code>AUDIO_REWRITE_MODEL</code> 和 <code>VISION_MODEL</code> 默认使用 <code>gpt-4o</code>;如果旧环境变量仍写 <code>gemini-*</code>,后端会归一化回 <code>GPT_TEXT_MODEL</code> / <code>REWRITE_MODEL</code>。语音只走 Azure OpenAI TTS,<code>models.voice_tts_paths</code> 会回传当前尝试的语音路径,方便区分路径错误和语音服务不可用。前端所有当前主路径里会调用模型的按钮旁显示模型名,点击弹出小窗口查看模型链路和输入输出逻辑;不返回 API Key 或敏感凭证。</td></tr>
|
||||
<tr><td>历史列表</td><td><code>GET /jobs</code></td><td><code>listJobs</code></td><td>所有 job 精简列表(id/url/status/thumbnail/mtime…),按 state.json mtime 倒序。前端 URL 无 <code>?job=</code> 时拉它回填全部历史;带 <code>limit</code> 可截断。</td></tr>
|
||||
<tr><td>创建任务</td><td><code>POST /jobs</code></td><td><code>createJob</code></td><td>提交 TK 链接,后台开始下载;前端“开始”队列会在 downloaded 后自动触发音频解析。下载阶段默认不带 cookies;生产环境必须显式保持 <code>YTDLP_COOKIES_FILE=</code> 和 <code>YTDLP_COOKIES_FROM_BROWSER=</code> 为空,避免容器内误读被打进镜像的开发 <code>api/.env</code>。只有 TikTok 明确要求登录态时,才把宿主机 <code>./secrets/tiktok_cookies.txt</code> 挂载进容器并设置 <code>YTDLP_COOKIES_FILE=/run/secrets/tiktok_cookies.txt</code>。生产容器没有 Chrome cookies 数据库,不能配置 <code>YTDLP_COOKIES_FROM_BROWSER=chrome</code>。</td></tr>
|
||||
<tr><td>一键出片终端</td><td><code>POST /agent-runs</code><br><code>GET /agent-runs/{id}</code><br><code>GET /agent-runs/{id}/final.mp4</code><br><code>GET /agent-runs/{id}/contact.jpg</code></td><td><code>web/app/agent/page.tsx</code></td><td>快速出片页的唯一主接口。前端提交 TikTok 链接和最多 6 张产品图;后端创建 <code>Job</code> 与 <code>AgentRun</code>,后台执行下载、产品图归一化、透明骨架主体参考复制、12 段镜头计划、视频生成、失败镜头自动重跑一次、审片接触表和 ffmpeg 最终合成。前端只轮询日志和结果,不直接拥有模型执行权。</td></tr>
|
||||
@@ -1014,7 +1014,7 @@ ProductRefStateItem {
|
||||
<tr><td>上传视频</td><td><code>POST /jobs/upload</code></td><td><code>uploadJob</code></td><td>保存 source.mp4,然后同样进入下载完成状态;当前上传后也加入第一步队列,下载完成后自动解析音频。</td></tr>
|
||||
<tr><td>删除输入视频</td><td><code>DELETE /jobs/{id}</code></td><td><code>deleteJob</code></td><td>从任务队列、URL 和磁盘 <code>jobs/<id></code> 目录移除整个 job,包括源视频、关键帧、元素提取图和生成视频。</td></tr>
|
||||
<tr><td>解析视频</td><td><code>POST /jobs/{id}/analyze?frames=&target=&mode=&quality=</code></td><td><code>analyzeJob</code></td><td>抽参考帧能力。当前开始流程会在视频下载完成后自动调用一次,默认 <code>frames=12</code>、<code>target=motion</code>、<code>quality=accurate</code>、<code>mode=replace</code>,形成全局动作/节奏参考帧池;原版视频旁的“抽参考 12 帧”也会用同一参数显式重跑。<code>target</code> 仍支持透明骨架人、综合、清晰主体、转场变化、表情瞬间、动作峰值。</td></tr>
|
||||
<tr><td>音频文案轨</td><td><code>POST /jobs/{id}/transcribe</code></td><td><code>triggerTranscribe</code></td><td>若尚未拆轨,先从 <code>source.mp4</code> 提取 <code>audio.wav</code> 并回填 <code>source_audio_url</code>;远端启用时把 <code>audio.wav</code> 上传到 <code>ASR_BASE_URL</code> 的 OpenAI Audio Transcriptions 兼容接口,用 <code>ASR_MODEL</code> 提取原始文案,并传 <code>ASR_LANGUAGE=en</code> 降低英文素材延迟。微软官方路径包括 <code>/openai/deployments/{deployment}/audio/transcriptions?api-version=...</code> 和 <code>/openai/v1/audio/transcriptions?api-version=preview</code>;当前 SKG 网关探测这些路径均未返回可用 ASR,<code>gpt-4o-transcribe</code> 返回 <code>DeploymentNotFound</code>。当前生产因此复制本地成功策略:<code>ASR_REMOTE_ENABLED=false</code>、<code>ASR_LOCAL_FALLBACK_ENABLED=true</code>,直接走容器内 CPU 版 <code>faster-whisper</code> 生成真实逐句时间轴;<code>ASR_AUDIO_FALLBACK_ENABLED=false</code>,避免 Gemini 多模态假字幕。后端会拒绝重复文本、逐秒假字幕或覆盖率过低的结果。中文翻译由 <code>TRANSLATE_MODEL</code> 按 ASR 段落补齐,失败时保留原文时间轴且中文可为空。再用 <code>ASR_FALLBACK_MODEL</code> 读取 <code>audio.wav</code> 和已有转写时间轴,多模态音频分析讲话人、语速节奏、停顿、背景音乐/环境声/音效,写入 <code>speaker_profile</code>、<code>rhythm_profile</code>、<code>background_audio_profile</code>;若模型分析失败,则用转写段落、时长和语速做本地估算兜底。当前第一步不默认生成 SKG 新口播和 Azure OpenAI 配音。失败后只要后台 worker 不在运行,就允许重新触发;前端也不再把失败状态下残留的半成品 transcript 当成音频完成。</td></tr>
|
||||
<tr><td>音频文案轨</td><td><code>POST /jobs/{id}/transcribe</code></td><td><code>triggerTranscribe</code></td><td>若尚未拆轨,先从 <code>source.mp4</code> 提取 <code>audio.wav</code> 并回填 <code>source_audio_url</code>;远端启用时把 <code>audio.wav</code> 上传到 <code>ASR_BASE_URL</code> 的 OpenAI Audio Transcriptions 兼容接口,用 <code>ASR_MODEL</code> 提取原始文案。<code>ASR_LANGUAGE</code> 默认空值/auto,不传固定语种,让远端和本地 ASR 自动识别中文、英文和其他多语言;只有确认素材固定语种时才填写 ISO-639-1 代码。微软官方路径包括 <code>/openai/deployments/{deployment}/audio/transcriptions?api-version=...</code> 和 <code>/openai/v1/audio/transcriptions?api-version=preview</code>;当前 SKG 网关探测这些路径均未返回可用 ASR,<code>gpt-4o-transcribe</code> 返回 <code>DeploymentNotFound</code>。当前生产因此复制本地成功策略:<code>ASR_REMOTE_ENABLED=false</code>、<code>ASR_LOCAL_FALLBACK_ENABLED=true</code>,直接走容器内 CPU 版多语言 <code>faster-whisper</code> 生成真实逐句时间轴;<code>ASR_AUDIO_FALLBACK_ENABLED=false</code>,避免 Gemini 多模态假字幕。后端会拒绝重复文本、逐秒假字幕或覆盖率过低的结果,质量校验支持中文等非空格分词文本,不再按英文字符集误判。中文镜像由 <code>TRANSLATE_MODEL</code> 按 ASR 原语言段落补齐,原文已经是中文时保留为简体中文镜像;失败时保留原文时间轴且中文可为空。再用 <code>ASR_FALLBACK_MODEL</code> 读取 <code>audio.wav</code> 和已有转写时间轴,多模态音频分析讲话人、语速节奏、停顿、背景音乐/环境声/音效,写入 <code>speaker_profile</code>、<code>rhythm_profile</code>、<code>background_audio_profile</code>;若模型分析失败,则用转写段落、时长和语速做本地估算兜底。当前第一步不默认生成 SKG 新口播和 Azure OpenAI 配音。失败后只要后台 worker 不在运行,就允许重新触发;前端也不再把失败状态下残留的半成品 transcript 当成音频完成。</td></tr>
|
||||
<tr><td>分镜脚本改写</td><td><code>POST /jobs/{id}/script/rewrite</code></td><td><code>rewriteStoryboardScript</code></td><td>根据原英文参考文案、当前英文新口播、英文 role enum、时间段和作者想法改写英文口播;作者想法若含中文,后端会先经 <code>_ensure_english</code> 兜底翻译。<code>mode=segment</code> 只改一段;<code>mode=all</code> 一次改完整片,要求整片前后连贯。后端按 <code>AUDIO_REWRITE_MODEL</code>、<code>ASR_FALLBACK_MODEL</code>、<code>TRANSLATE_MODEL</code> 依次尝试,全部失败时用英文本地模板保留可编辑文案。接口返回 <code>items[index,text,text_zh]</code>,其中 <code>text</code> 是写入模型链路的英文主值,<code>text_zh</code> 只供团队审稿镜像显示;点击保存规划后写入 <code>StoryboardScene.action</code>。</td></tr>
|
||||
<tr><td>原始音频文件</td><td><code>GET /jobs/{id}/audio.wav</code></td><td><code>sourceAudioUrl</code></td><td>返回拆轨得到的 wav;当前主界面不再渲染底部吸附音频条,右侧复刻工作表会读取该文件生成参考图式横向响度波形,并和原视频、逐句时间轴联动;波形标题栏显示当前播放秒数、总时长和鼠标指针停点秒数。</td></tr>
|
||||
<tr><td>改写配音文件</td><td><code>GET /jobs/{id}/audio-script.mp3</code></td><td><code>apiAssetUrl(job.audio_script.voice_url)</code></td><td>后续新配音阶段保留的 TTS 产物;服务端固定走 <code>VOICE_PROVIDER=azure_openai</code>,通过 <code>AZURE_OPENAI_BASE_URL</code> 的 OpenAI 协议生成 mp3,并按 <code>AZURE_TTS_PATHS</code> 依次尝试 <code>/audio/speech</code>、<code>/v1/audio/speech</code> 等路径。当前第一步不默认生成该文件。</td></tr>
|
||||
@@ -1155,6 +1155,18 @@ ProductRefStateItem {
|
||||
<h2>变更记录</h2>
|
||||
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
|
||||
<div class="changelog">
|
||||
<article class="change">
|
||||
<header>
|
||||
<h3>2026-05-22 · 音频解析改为中文和多语言自动识别</h3>
|
||||
<span class="tag blue">ASR</span>
|
||||
<span class="tag green">Audio</span>
|
||||
</header>
|
||||
<div class="body">
|
||||
<p><strong>问题:</strong>音频解析默认把 <code>ASR_LANGUAGE</code> 固定为 <code>en</code>,本地容器兜底也使用 <code>faster-whisper tiny.en</code> 并强制 <code>language="en"</code>;中文或其他语言视频容易被压成英文识别路径,且质量校验按英文字符集计算重复率,会误伤中文字幕。</p>
|
||||
<p><strong>改动:</strong><code>api/main.py</code> 将 <code>ASR_LANGUAGE</code> 默认改为空值/auto,远端和 <code>faster-whisper</code> 都只在显式配置语种时才传语言提示;本地 <code>faster-whisper</code> 默认模型改为多语言 <code>base</code>,Gemini 音频兜底也要求保留原语种而不是翻译成英文。ASR 质量校验改为 Unicode 文本 key,翻译 prompt 改为“原语言字幕 → 简体中文”,中文原文会作为中文镜像保留。</p>
|
||||
<p><strong>影响:</strong>后续音频解析默认支持中文、英文和其他多语言原文识别,分镜时间轴的 <code>en</code> 字段实际承载“原语言文案”,<code>zh</code> 字段承载中文镜像。若某个素材明确固定语种,可通过 <code>ASR_LANGUAGE=zh</code>、<code>en</code> 等 ISO-639-1 代码强制识别。</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="change">
|
||||
<header>
|
||||
<h3>2026-05-21 · 分镜视频候选点击改为预览下载</h3>
|
||||
@@ -1717,7 +1729,7 @@ ProductRefStateItem {
|
||||
</header>
|
||||
<div class="body">
|
||||
<p><strong>问题:</strong>本地音频解析成功时实际链路是远端失败后落到 <code>mlx_whisper</code>,而生产强制 <code>ASR_BASE_URL=https://ai.skg.com/azure/v1</code> + <code>ASR_MODEL=gpt-4o-transcribe</code> 且关闭本地兜底。生产探测官方 Azure OpenAI 音频路径 <code>/openai/v1/audio/transcriptions?api-version=preview</code> 和 <code>/openai/deployments/{deployment}/audio/transcriptions?api-version=...</code> 仍不可用,当前部署名返回 <code>DeploymentNotFound</code>。</p>
|
||||
<p><strong>改动:</strong>远端 ASR 请求新增 <code>ASR_LANGUAGE</code>,默认 <code>en</code>,用于按官方建议降低英文素材延迟;翻译请求也套用 <code>ASR_TIMEOUT_SECONDS</code>。生产配置临时改成 <code>ASR_REMOTE_ENABLED=false</code>、<code>ASR_LOCAL_FALLBACK_ENABLED=true</code>、<code>ASR_AUDIO_FALLBACK_ENABLED=false</code>,云端用容器内 <code>faster-whisper tiny.en</code> 复制本地“真实本机转写”路径。</p>
|
||||
<p><strong>改动:</strong>当时远端 ASR 请求新增 <code>ASR_LANGUAGE</code> 并固定英文素材优先;翻译请求也套用 <code>ASR_TIMEOUT_SECONDS</code>。生产配置临时改成 <code>ASR_REMOTE_ENABLED=false</code>、<code>ASR_LOCAL_FALLBACK_ENABLED=true</code>、<code>ASR_AUDIO_FALLBACK_ENABLED=false</code>,云端用容器内 <code>faster-whisper</code> 复制本地“真实本机转写”路径。2026-05-22 后该路径已改为默认 <code>auto</code> 语种和多语言模型。</p>
|
||||
<p><strong>影响:</strong>音频解析不再卡在不存在的 Azure deployment;当前云端 CPU 实测同一失败 job 的 <code>audio.wav</code> 可在约 13.6 秒转出 17 段。等 SKG 网关提供真实 Azure ASR deployment 后,再把 <code>ASR_REMOTE_ENABLED=true</code> 并恢复对应部署名。</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -1335,8 +1335,8 @@ function audioModelTrace(models?: RuntimeModels): ModelTraceSpec {
|
||||
title: "音频解析",
|
||||
model: modelList([models?.asr, models?.translate, models?.asr_fallback]),
|
||||
chain: [
|
||||
`ASR 转写:远端 ${remoteState},模型 ${modelValue(models?.asr)}${models?.asr_language ? `,语言 ${models.asr_language}` : ""};本机转写 ${localState},使用 ${localModel};多模态兜底${models?.asr_audio_fallback_enabled === false ? "关闭" : `为 ${modelValue(models?.asr_fallback)}`},并拒绝假字幕/重复时间轴`,
|
||||
`字幕翻译:${modelValue(models?.translate)} 按 ASR 段落输出中文;失败时保留原文时间轴,中文可为空`,
|
||||
`ASR 转写:远端 ${remoteState},模型 ${modelValue(models?.asr)},语言 ${models?.asr_language || "auto"};本机转写 ${localState},使用 ${localModel} 自动识别中文/多语言;多模态兜底${models?.asr_audio_fallback_enabled === false ? "关闭" : `为 ${modelValue(models?.asr_fallback)}`},并拒绝假字幕/重复时间轴`,
|
||||
`字幕翻译:${modelValue(models?.translate)} 按原语言 ASR 段落输出中文;原文已是中文时保留为中文镜像,失败时保留原文时间轴`,
|
||||
`讲话人 / 节奏 / 背景音:${modelValue(models?.asr_fallback)} 读取 audio.wav + 转写时间轴做多模态分析;失败时用本地时长/段落估算兜底`,
|
||||
],
|
||||
note: "点击“解析音频”后触发;开始任务下载完成后也会自动走这条链路。",
|
||||
|
||||
@@ -319,7 +319,7 @@ export function AudioStrip({ job, open, onClose }: { job: Job | null; open: bool
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center rounded-lg border border-dashed border-white/12 text-[12px] text-white/45">
|
||||
点击音频卡片后开始解析;完成后这里会按时间显示英文、中文翻译和对应波形。
|
||||
点击音频卡片后开始解析;完成后这里会按时间显示原语言文案、中文镜像和对应波形。
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2017,7 +2017,7 @@ export function ASRNode({ data, selected }: any) {
|
||||
onTogglePin={() => d.onToggleNodePin?.("asr")}
|
||||
>
|
||||
<div className="text-[11.5px] text-[var(--text-soft)]">
|
||||
OpenAI-compatible ASR · 英文带时间戳分段
|
||||
OpenAI-compatible ASR · 原语言带时间戳分段
|
||||
</div>
|
||||
{d.job && d.job.transcript.length > 0 && (
|
||||
<div className="mt-2 max-h-24 overflow-y-auto text-[11px] space-y-1 text-[var(--text-strong)]">
|
||||
|
||||
Reference in New Issue
Block a user