feat(canvas): 新增视频节点字幕体系与多轨时间线编辑(关联 #187) (#191)
* fix(ci): 后端测试 - 修复 responses 流式测试数据与 Grok 生图测试 SSRF 放行 * feat(canvas): 新增视频节点字幕体系与多轨时间线编辑(关联 #187) --------- Co-authored-by: zhaoyue <zhaoyue@rongshantech.com>
This commit is contained in:
1 parent
c75953f3c9
commit
43e65570a5
33 files changed
+3724
-30
No files matched your search
@@ -0,0 +1,91 @@
|
||||
import type { SrtEntry, SubtitleHighlight } from "@/types/timeline";
|
||||
|
||||
export function isValidSubtitleHighlight(entry: SrtEntry | undefined, highlight: SubtitleHighlight): boolean {
|
||||
if (!entry || entry.index !== highlight.entryIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (highlight.start < 0 || highlight.end <= highlight.start) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (highlight.end > highlight.sourceText.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return highlight.highlightText === highlight.sourceText.slice(highlight.start, highlight.end);
|
||||
}
|
||||
|
||||
export function isExpiredSubtitleHighlight(entry: SrtEntry | undefined, highlight: SubtitleHighlight): boolean {
|
||||
if (!entry || entry.index !== highlight.entryIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return entry.text !== highlight.sourceText;
|
||||
}
|
||||
|
||||
export function filterValidSubtitleHighlights(entries: SrtEntry[], highlights: SubtitleHighlight[]): SubtitleHighlight[] {
|
||||
const entryMap = new Map(entries.map((entry) => [entry.index, entry]));
|
||||
|
||||
return highlights.filter((highlight) => {
|
||||
const entry = entryMap.get(highlight.entryIndex);
|
||||
return isValidSubtitleHighlight(entry, highlight) && !isExpiredSubtitleHighlight(entry, highlight);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 在重分段后,把旧高亮映射到新条目上。
|
||||
* 规则:highlightText 必须作为子串出现在某个新条目的 text 中;
|
||||
* 找到第一个匹配的条目,更新 entryIndex 和 start/end;
|
||||
* 找不到则放入 dropped(通常是跨切分点的关键词)。
|
||||
*/
|
||||
export function remapHighlightsAfterResegment(oldHighlights: SubtitleHighlight[], newEntries: SrtEntry[]): { remapped: SubtitleHighlight[]; dropped: SubtitleHighlight[] } {
|
||||
const remapped: SubtitleHighlight[] = [];
|
||||
const dropped: SubtitleHighlight[] = [];
|
||||
|
||||
for (const highlight of oldHighlights) {
|
||||
const target = newEntries.find((entry) => entry.text.includes(highlight.highlightText));
|
||||
if (!target) {
|
||||
dropped.push(highlight);
|
||||
continue;
|
||||
}
|
||||
const start = target.text.indexOf(highlight.highlightText);
|
||||
remapped.push({
|
||||
entryIndex: target.index,
|
||||
start,
|
||||
end: start + highlight.highlightText.length,
|
||||
highlightText: highlight.highlightText,
|
||||
sourceText: target.text,
|
||||
});
|
||||
}
|
||||
|
||||
return { remapped, dropped };
|
||||
}
|
||||
|
||||
// 无可用大模型配置时的本地回退:按终止标点取首段作为高亮,保证功能不中断。
|
||||
const TERMINAL_PUNCTUATION = "。!?;";
|
||||
|
||||
export function buildFallbackHighlights(entries: SrtEntry[]): SubtitleHighlight[] {
|
||||
const highlights: SubtitleHighlight[] = [];
|
||||
for (const entry of entries) {
|
||||
let terminalIndex = -1;
|
||||
for (let i = 0; i < entry.text.length; i += 1) {
|
||||
if (TERMINAL_PUNCTUATION.includes(entry.text[i])) {
|
||||
terminalIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (terminalIndex <= 0) {
|
||||
continue;
|
||||
}
|
||||
const end = terminalIndex + 1;
|
||||
highlights.push({
|
||||
entryIndex: entry.index,
|
||||
start: 0,
|
||||
end,
|
||||
highlightText: entry.text.slice(0, end),
|
||||
sourceText: entry.text,
|
||||
});
|
||||
}
|
||||
return highlights;
|
||||
}
|
||||
Reference in new issue
Block a user