From 3c817a2ffe862242b461023ea8b786b2005a8ae4 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Fri, 11 Sep 2026 18:29:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(ai-avatar):=20=E7=B4=A0=E6=9D=90=E9=A2=84?= =?UTF-8?q?=E8=A7=88=E4=B8=8D=E5=8F=A0=E5=8A=A0=E6=A0=87=E9=A2=98=20+=20?= =?UTF-8?q?=E5=B0=81=E9=9D=A2=E6=8E=A5=E5=8F=A3=E4=BC=A0title=5Fconfig=20+?= =?UTF-8?q?=20=E6=B8=B2=E6=9F=93=E5=90=8E=E5=B0=81=E9=9D=A2=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E5=8F=8C=E6=A0=87=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. PanelVideoSelector: 移除素材视频上的标题叠加预览,标题只在对口型预览和最终成片上展示 2. generateSmartCover API: 传入title_config参数,让后端在对口型视频帧上用drawtext叠加标题 3. AiAvatarPage: 调用generateSmartCover时传入state.titleConfig;移除传给PanelVideoSelector的titleConfig prop 4. ai_avatar_render_service: 渲染完成后自动抽封面时不传title_config,避免最终视频已有标题再叠加导致双重标题 --- .../app/services/ai_avatar_render_service.py | 3 +- apps/web/src/pages/ai-avatar/AiAvatarPage.tsx | 3 +- apps/web/src/pages/ai-avatar/api/aiAvatar.ts | 9 +++-- .../components/PanelVideoSelector.tsx | 39 ++----------------- 4 files changed, 12 insertions(+), 42 deletions(-) diff --git a/apps/api/app/services/ai_avatar_render_service.py b/apps/api/app/services/ai_avatar_render_service.py index 92952663c..1d2294dfc 100644 --- a/apps/api/app/services/ai_avatar_render_service.py +++ b/apps/api/app/services/ai_avatar_render_service.py @@ -335,7 +335,8 @@ class AiAvatarRenderService: output_video_url, job_id=job_id, max_frames=5, - title_config=job.title_config, + # 注意:不传 title_config —— 最终输出视频已经通过 drawtext 叠加了标题, + # 再传会导致封面标题双重叠加 ) except Exception: logger.warning("智能封面(MediaKit)失败,回退 FFmpeg 封面 job_id=%s", job_id, exc_info=True) diff --git a/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx b/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx index 8ca9a999d..96637178a 100644 --- a/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx +++ b/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx @@ -279,7 +279,7 @@ const AiAvatarPage: React.FC = () => { } setSmartCoverLoading(true) try { - const res = await generateSmartCover(videoUrl, 5) + const res = await generateSmartCover(videoUrl, state.titleConfig, 5) if (res.cover_url) { state.setCoverConfig((prev) => ({ ...prev, @@ -342,7 +342,6 @@ const AiAvatarPage: React.FC = () => { selectedVideo={state.selectedVideo} onSelectVideo={() => state.setShowAssetPicker(true)} onRemoveVideo={state.removeVideo} - titleConfig={state.titleConfig} /> diff --git a/apps/web/src/pages/ai-avatar/api/aiAvatar.ts b/apps/web/src/pages/ai-avatar/api/aiAvatar.ts index ee02cde82..276c87372 100644 --- a/apps/web/src/pages/ai-avatar/api/aiAvatar.ts +++ b/apps/web/src/pages/ai-avatar/api/aiAvatar.ts @@ -2,7 +2,7 @@ * AI数字人 — API 封装(#1822 契约对齐) */ import apiClient from "@/api/client" -import type { Script, LipsyncJob, RenderJob, BRollSegment } from "../types" +import type { Script, LipsyncJob, RenderJob, BRollSegment, AiAvatarTitleConfig } from "../types" /* ── 文案库 ── */ export const getScripts = async (): Promise => { @@ -58,14 +58,15 @@ export const getLipsyncJob = async (id: string): Promise => { return response.data } -/* ── 智能封面(MediaKit 抽帧 + 质量评分选最佳帧,独立于渲染任务) ── */ +/* ── 智能封面(MediaKit 抽帧 + 质量评分选最佳帧 + 可选 drawtext 标题叠加) ── */ export const generateSmartCover = async ( video_url: string, + title_config?: AiAvatarTitleConfig | null, max_frames = 5, ): Promise<{ cover_url: string; status: string; message: string }> => { const response = await apiClient.post<{ cover_url: string; status: string; message: string }>( "/ai-avatar/render/smart-cover", - { video_url, max_frames }, + { video_url, max_frames, title_config: title_config ?? null }, // smart-cover 链路:下载视频+抽帧+drawtext 加标题+上传 OSS,需要较长时间,120s 超时 { timeout: 120000 }, ) @@ -86,7 +87,7 @@ export const submitRender = async (data: { } export const getRenderJob = async (jobId: string): Promise => { - const response = await apiClient.get(`/ai-avatar/render/${jobId}`) + const response = await apiClient.get(`/ai-avatar/render/${jobId}`, { timeout: 60000 }) return response.data } diff --git a/apps/web/src/pages/ai-avatar/components/PanelVideoSelector.tsx b/apps/web/src/pages/ai-avatar/components/PanelVideoSelector.tsx index b09baf3f0..a2379a4dd 100644 --- a/apps/web/src/pages/ai-avatar/components/PanelVideoSelector.tsx +++ b/apps/web/src/pages/ai-avatar/components/PanelVideoSelector.tsx @@ -2,17 +2,16 @@ * AI数字人 — 出镜视频选择面板 * - 未选视频:虚线上传区,点击打开素材库弹窗 * - 已选视频:竖屏 9:16 预览播放器 + 视频信息卡片 + 移除按钮 + * + * 注意:本面板只展示原始素材视频,不叠加标题(标题在对口型预览和最终成片上展示) */ import type { AssetItem } from "@/api/assets" -import type { AiAvatarTitleConfig } from "../types" -import { getFontFamily } from "@/pages/generate/constants" export interface PanelVideoSelectorProps { selectedVideo: AssetItem | null /** 触发打开素材库弹窗 */ onSelectVideo: () => void onRemoveVideo: () => void - titleConfig?: AiAvatarTitleConfig } /** 格式化时长(秒 → mm:ss) */ @@ -27,7 +26,6 @@ export function PanelVideoSelector({ selectedVideo, onSelectVideo, onRemoveVideo, - titleConfig, }: PanelVideoSelectorProps) { /* 未选视频:虚线上传区,点击打开素材库弹窗 */ if (!selectedVideo) { @@ -57,42 +55,13 @@ export function PanelVideoSelector({ return (
- {/* 竖屏 9:16 视频预览播放器 + 标题实时预览 */} -
+ {/* 竖屏 9:16 视频预览播放器(纯素材预览,不叠加标题) */} +
{fileUrl ? (
{/* 视频信息卡片:文件名 / 时长 / 分辨率 */}