fix(ai-avatar): B-roll文案显示时长+标题字号调大 (#1870)
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 2s
CI/CD Pipeline / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 25s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 27s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 53s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m21s
CI/CD Pipeline / Integration Tests (push) Successful in 2m19s
CI/CD Pipeline / Validate - Style (push) Successful in 3m0s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m14s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m48s
CI/CD Pipeline / Validate - Security (push) Successful in 4m33s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m17s
CI/CD Pipeline / Unit Tests (push) Successful in 8m8s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 23m3s
CI/CD Pipeline / Deploy Production (push) Failing after 101h12m58s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 101h21m8s
CI/CD Pipeline / PR Build Web Image (push) Failing after 101h20m50s
CI/CD Pipeline / PR Build API Image (push) Failing after 101h20m50s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 101h12m40s
CI/CD Pipeline / Build Production Web Image (push) Failing after 101h12m40s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 101h20m19s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 101h20m19s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 101h20m19s
CI/CD Pipeline / Canary Release to Production (push) Failing after 100h56m10s
CI/CD Pipeline / CI Gate (push) Failing after 101h12m40s
CI/CD Pipeline / Frontend Lint (push) Failing after 101h20m50s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 101h20m54s
CI/CD Pipeline / Build Production API Image (push) Failing after 101h47m53s

Co-authored-by: frontend-dev <frontend-dev@xiaoxiajianji.com>
Co-committed-by: frontend-dev <frontend-dev@xiaoxiajianji.com>
This commit was merged in pull request #1870.
This commit is contained in:
2026-09-12 10:02:34 +08:00
committed by auto-approve-bot
parent 938ef0b8cc
commit ed7af0642d
7 changed files with 50 additions and 27 deletions
@@ -65,10 +65,10 @@ const ModalBRollEditor: React.FC<ModalBRollEditorProps> = ({
const [pipPosition, setPipPosition] = useState<PipPosition>("top-right")
const [pipScale, setPipScale] = useState(0.3)
/** 文案分句(使用后端精确时间戳) */
/** 文案分句(优先使用后端精确时间戳,降级为字数比例估算) */
const sentences = useMemo(
() => splitScriptIntoSentences(scriptText, sentenceTimings),
[scriptText, sentenceTimings],
() => splitScriptIntoSentences(scriptText, sentenceTimings, _outputDuration),
[scriptText, sentenceTimings, _outputDuration],
)
/** 已被现有 segments 占用的素材 id 集合(标灰、禁止重复选择) */
@@ -145,7 +145,7 @@ const ModalBRollEditor: React.FC<ModalBRollEditorProps> = ({
setSelectedAsset(asset)
}
/** 确认添加一段 B-roll(⑥ 时间取所选句子的估算起止) */
/** 确认添加一段 B-roll(⑥ 时间取所选句子的精确起止,后端静音检测 / 前端字数比例降级) */
const handleConfirm = () => {
if (!selectedAsset || !selectedSentence) return
const startTime = selectedSentence.startTime
@@ -267,11 +267,9 @@ const ModalBRollEditor: React.FC<ModalBRollEditorProps> = ({
>
<span className="aa-sentence-item__idx">{sent.index + 1}</span>
<span className="aa-sentence-item__text">{sent.text}</span>
{sent.endTime > 0 && (
<span className="aa-sentence-item__time">
{sent.startTime.toFixed(1)}-{sent.endTime.toFixed(1)}s
</span>
)}
<span className="aa-sentence-item__time">
{sent.startTime.toFixed(1)}-{sent.endTime.toFixed(1)}s
</span>
</button>
)
})}
@@ -120,7 +120,7 @@ const PanelCoverAndGenerate: React.FC<PanelCoverAndGenerateProps> = ({
wordBreak: "break-word",
whiteSpace: "pre-wrap",
color: titleConfig.color || "#ffffff",
fontSize: `${(titleConfig.size || 28) * 0.55}px`, // 预览容器缩放,与 PanelLipsyncPreview 对齐
fontSize: `${(titleConfig.size || 48) * 0.35}px`, // 预览容器缩放,与 PanelLipsyncPreview 对齐
fontFamily: getFontFamily(titleConfig.font),
fontWeight: titleConfig.bold ? "bold" : "normal",
fontStyle: titleConfig.italic ? "italic" : "normal",
@@ -58,7 +58,7 @@ export function PanelLipsyncPreview({
position: "absolute",
color: titleConfig.color || "#ffffff",
fontFamily: titleConfig.font || "思源黑体",
fontSize: `${(titleConfig.size || 28) * 0.55}px`,
fontSize: `${(titleConfig.size || 48) * 0.35}px`,
fontWeight: titleConfig.bold ? 700 : 400,
fontStyle: titleConfig.italic ? "italic" : "normal",
textAlign: "center",
+1 -1
View File
@@ -122,7 +122,7 @@ export const DEFAULT_TITLE_CONFIG: AiAvatarTitleConfig = {
title: "",
position: "bottom",
font: "思源黑体",
size: 28,
size: 48,
bold: true,
italic: false,
stroke: false,
@@ -39,7 +39,7 @@ export function buildTitleConfigPayload(cfg: AiAvatarTitleConfig): Record<string
text,
enabled: true,
font: cfg.font || "思源黑体",
font_size: Math.round(cfg.size) || 28,
font_size: Math.round(cfg.size) || 48,
font_color: cfg.color || "#ffffff",
position,
bold: !!cfg.bold,
+38 -13
View File
@@ -28,6 +28,7 @@ export interface ScriptSentence {
export function splitScriptIntoSentences(
scriptText: string,
sentenceTimings?: SentenceTiming[] | null,
outputDuration: number = 0,
): ScriptSentence[] {
const text = (scriptText || "").trim()
if (!text) return []
@@ -39,21 +40,45 @@ export function splitScriptIntoSentences(
const sentences: ScriptSentence[] = []
let accChar = 0
const totalChars = rawParts.reduce((sum, p) => sum + p.replace(/\s/g, "").length, 0)
// 如果后端返回了完整的时间戳(至少有一个有效结束时间),使用精确时间
const hasBackendTimings =
sentenceTimings &&
sentenceTimings.length > 0 &&
sentenceTimings.some((t) => (t.end_time ?? 0) > 0)
// 若有后端时间戳,取音频总时长;否则用外部传入的 outputDuration 做字数比例降级
const audioDuration = hasBackendTimings
? (sentenceTimings![sentenceTimings!.length - 1]?.end_time ?? 0)
: outputDuration
rawParts.forEach((part, i) => {
const charCount = part.replace(/\s/g, "").length
// 从后端精确时间戳获取;无数据时返回 0
const timing = sentenceTimings?.[i]
const startTime = timing?.start_time ?? 0
const endTime = timing?.end_time ?? 0
sentences.push({
index: i,
text: part,
charCount,
startChar: accChar,
startTime,
endTime,
})
if (hasBackendTimings) {
// 使用后端精确时间戳
const timing = sentenceTimings![i]
sentences.push({
index: i,
text: part,
charCount,
startChar: accChar,
startTime: timing?.start_time ?? 0,
endTime: timing?.end_time ?? 0,
})
} else {
// 降级方案:按字数比例估算(必须保证有时间,否则B-roll无法定位)
const startTime = totalChars > 0 ? (accChar / totalChars) * (audioDuration || 0) : 0
const endTime =
totalChars > 0 ? ((accChar + charCount) / totalChars) * (audioDuration || 0) : 0
sentences.push({
index: i,
text: part,
charCount,
startChar: accChar,
startTime: Math.round(startTime * 100) / 100,
endTime: Math.round(endTime * 100) / 100,
})
}
accChar += charCount
})
+1 -1
View File
@@ -481,7 +481,7 @@ def build_title_drawtext_filter(
# ── 样式参数 ──
font_name = title_config.get("font") or title_config.get("font_preset") or "思源黑体"
font_size = int(title_config.get("font_size") or title_config.get("size") or 28)
font_size = int(title_config.get("font_size") or title_config.get("size") or 48)
font_color = title_config.get("font_color") or title_config.get("color") or "#ffffff"
# 去掉 # 前缀(drawtext 用纯 hex 或颜色名)
if font_color.startswith("#"):