fix(ai-avatar): 预览缩放/标题叠加/渲染422/智能封面超时
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Build Staging API Image (push) Successful in 25s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 26s
CI/CD Pipeline / Integration Tests (push) Successful in 1m11s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m21s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m43s
CI/CD Pipeline / Validate - Style (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 145h39m50s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 145h39m52s
CI/CD Pipeline / Frontend Lint (push) Failing after 145h41m31s
CI/CD Pipeline / PR Build API Image (push) Failing after 145h41m34s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 145h41m33s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 145h41m34s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 146h14m38s
CI/CD Pipeline / PR Build Web Image (push) Failing after 146h16m20s

1. 对口型预览缩小: .aa-lipsync-preview 加 max-width:240px + 居中,9:16 等比不变,约原大小 1/2
2. 标题实时叠加预览: 视频容器 relative + 标题 absolute 定位(top/bottom/center 按配置),字号按 0.55 倍等比缩
3. 渲染 422 修复: submitRender 删掉 resolution 字段(后端 CreateAiAvatarRenderRequest schema 无此字段,Pydantic 校验失败)
4. 智能封面超时: generateSmartCover 接口超时从默认 10s 加到 60s(MediaKit 抽帧+评分+OSS转存耗时 >10s)

不改其他功能,无冗余代码。
This commit is contained in:
灵应
2026-09-10 13:42:16 +08:00
parent 7dc2954d8f
commit 211166f203
4 changed files with 43 additions and 7 deletions
+5 -1
View File
@@ -484,16 +484,19 @@
margin-bottom: 12px;
}
.aa-lipsync-preview {
width: 100%;
max-width: 240px;
aspect-ratio: 9/16;
background: #0f0f1a;
border-radius: 10px;
overflow: hidden;
margin-bottom: 12px;
margin: 0 auto 12px auto;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.aa-lipsync-preview video {
@@ -1246,3 +1249,4 @@
width: auto;
min-width: 300px;
}
@@ -202,7 +202,6 @@ const AiAvatarPage: React.FC = () => {
title_config: buildTitleConfigPayload(state.titleConfig),
// cover_config:智能封面 cover_url + 截帧 timestamp
cover_config: buildCoverConfigPayload(state.coverConfig, state.coverConfig.smart_cover_url),
resolution: state.resolution,
})
message.success("渲染任务已提交,可在视频管理中查看进度")
} catch (err) {
@@ -218,7 +217,7 @@ const AiAvatarPage: React.FC = () => {
state.bRollSegments,
state.titleConfig,
state.coverConfig,
state.resolution,
])
/* ── 智能封面:调后端 MediaKit 选帧接口(#1822) ── */
@@ -359,6 +358,7 @@ const AiAvatarPage: React.FC = () => {
bRollSegments={state.bRollSegments}
onOpenBRollModal={() => state.setShowBRollModal(true)}
onRemoveBRoll={state.removeBRollSegment}
titleConfig={state.titleConfig}
/>
<div className="aa-step-btn-row">
<button type="button" className="aa-btn" onClick={handlePrevStep}>
+1 -1
View File
@@ -66,6 +66,7 @@ export const generateSmartCover = async (
const response = await apiClient.post<{ cover_url: string; status: string; message: string }>(
"/ai-avatar/render/smart-cover",
{ video_url, max_frames },
{ timeout: 60000 },
)
return response.data
}
@@ -78,7 +79,6 @@ export const submitRender = async (data: {
title_config?: Record<string, unknown>
cover_config?: Record<string, unknown>
project_id?: string
resolution?: string
}): Promise<RenderJob> => {
const response = await apiClient.post<RenderJob>("/ai-avatar/render", data)
return response.data
@@ -1,8 +1,9 @@
/**
* AI数字人 — 对口型预览面板(步骤2用)
* B-roll 画面插入 + 对口型视频预览 + 生成/重新生成按钮
* v3.1: 预览容器按 1/2 缩放、标题实时叠加预览
*/
import type { LipsyncJob, BRollSegment } from "../types"
import type { LipsyncJob, BRollSegment, AiAvatarTitleConfig } from "../types"
interface PanelLipsyncPreviewProps {
lipsyncJob: LipsyncJob | null
@@ -10,6 +11,8 @@ interface PanelLipsyncPreviewProps {
bRollSegments: BRollSegment[]
onOpenBRollModal: () => void
onRemoveBRoll: (id: string) => void
/** 标题配置(实时叠加预览用) */
titleConfig?: AiAvatarTitleConfig
}
const BROLL_MODE_LABEL: Record<BRollSegment["mode"], string> = {
@@ -29,6 +32,7 @@ export function PanelLipsyncPreview({
bRollSegments,
onOpenBRollModal,
onRemoveBRoll,
titleConfig,
}: PanelLipsyncPreviewProps) {
const isGenerating =
lipsyncJob?.status === "pending" || lipsyncJob?.status === "processing"
@@ -42,6 +46,31 @@ export function PanelLipsyncPreview({
? "排队中…"
: "对口型生成中…"
/** 标题叠加样式 */
const titleOverlayStyle: React.CSSProperties | null = titleConfig?.title
? {
position: "absolute",
left: "50%",
transform: "translateX(-50%)",
color: titleConfig.color || "#ffffff",
fontFamily: titleConfig.font || "思源黑体",
fontSize: `${(titleConfig.size || 36) * 0.55}px`, // 预览等比缩
fontWeight: titleConfig.bold ? 700 : 400,
fontStyle: titleConfig.italic ? "italic" : "normal",
whiteSpace: "pre-wrap",
textAlign: "center",
width: "90%",
padding: "4px 8px",
textShadow: titleConfig.shadow ? "0 2px 4px rgba(0,0,0,0.8)" : undefined,
WebkitTextStroke: titleConfig.stroke ? "1.5px #000" : undefined,
...(titleConfig.position === "top"
? { top: 8 }
: titleConfig.position === "bottom"
? { bottom: 8 }
: { top: "50%", transform: "translateX(-50%) translateY(-50%)" }),
}
: null
return (
<div className="aa-script-lipsync">
{/* ── B-roll 画面 ── */}
@@ -111,13 +140,16 @@ export function PanelLipsyncPreview({
)}
</div>
{/* ── 对口型预览 ─ */}
{/* ── 对口型预览(v3.1: 缩放1/2 + 标题叠加) ─ */}
<div className="aa-lipsync-section">
<div className="aa-lipsync-section__title">对口型预览</div>
<div className="aa-lipsync-preview">
{isDone && lipsyncJob?.output_video_url ? (
<video src={lipsyncJob.output_video_url} controls />
<div style={{ position: "relative", width: "100%", height: "100%" }}>
<video src={lipsyncJob.output_video_url} controls />
{titleOverlayStyle && <div style={titleOverlayStyle}>{titleConfig!.title}</div>}
</div>
) : isGenerating ? (
<div style={{ width: "80%", textAlign: "center", color: "#fff" }}>
<div style={{ fontSize: 13, marginBottom: 8 }}>