mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 23:05:49 +00:00
feat(captions): segment captions by sentence using punctuation and pauses
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { flattenCaptionWords } from "./captionLayout";
|
||||
import type { CaptionCue } from "./types";
|
||||
|
||||
describe("flattenCaptionWords", () => {
|
||||
it("forces a break at every cue boundary so each phrase shows on its own", () => {
|
||||
const cues: CaptionCue[] = [
|
||||
{
|
||||
id: "a",
|
||||
startMs: 0,
|
||||
endMs: 1_000,
|
||||
text: "hello world",
|
||||
words: [
|
||||
{ text: "hello", startMs: 0, endMs: 500 },
|
||||
{ text: "world", startMs: 500, endMs: 1_000, leadingSpace: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
// back-to-back with cue "a" (no gap) — would previously be re-packed by width
|
||||
id: "b",
|
||||
startMs: 1_000,
|
||||
endMs: 2_000,
|
||||
text: "next one",
|
||||
words: [
|
||||
{ text: "next", startMs: 1_000, endMs: 1_500 },
|
||||
{ text: "one", startMs: 1_500, endMs: 2_000, leadingSpace: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const flattened = flattenCaptionWords(cues);
|
||||
const firstWordOfSecondCue = flattened.find(
|
||||
(word) => word.cueId === "b" && word.cueWordIndex === 0,
|
||||
);
|
||||
|
||||
expect(firstWordOfSecondCue?.forcedBreakBefore).toBe(true);
|
||||
expect(firstWordOfSecondCue?.leadingSpace).toBe(false);
|
||||
// the very first word of the first cue never forces a break
|
||||
expect(flattened[0].forcedBreakBefore).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -179,8 +179,10 @@ export function flattenCaptionWords(cues: CaptionCue[]) {
|
||||
const cueDuration = Math.max(1, cue.endMs - cue.startMs);
|
||||
const fallbackWordDuration = cueDuration / sourceWords.length;
|
||||
const previousCue = cueIndex > 0 ? cues[cueIndex - 1] : null;
|
||||
const shouldForceCueBreak =
|
||||
previousCue !== null && cue.startMs - previousCue.endMs >= CAPTION_BLOCK_GAP_BREAK_MS;
|
||||
// Each cue is its own phrase, so always start a new line/page at a cue boundary.
|
||||
// Otherwise back-to-back phrases (a small gap) get re-packed together by width and
|
||||
// their boundary disappears on screen — we want one phrase shown at a time.
|
||||
const shouldForceCueBreak = previousCue !== null;
|
||||
|
||||
sourceWords.forEach((word, wordIndex) => {
|
||||
const fallbackStartMs = cue.startMs + fallbackWordDuration * wordIndex;
|
||||
|
||||
Reference in New Issue
Block a user