eldouma-graphics / src /lib /timeline-model.ts
Moeeldouma's picture
Deploy Eldouma Graphics — Docker Space, bring-your-own-key demo
39d98a0 verified
Raw
History Blame Contribute Delete
2.3 kB
// The transcript→EDL engine. This is the heart of "edit by deleting text":
// kept words collapse into contiguous source-time runs (clips); the EDL is their
// left-packed (ripple) projection onto the timeline. Deleting a word splits/shrinks
// the run; the timeline reflows with no gaps.
export interface Word {
id: string;
text: string; // display text (trimmed)
startMs: number; // source-relative
endMs: number;
deleted?: boolean;
}
export interface Clip {
id: string;
sourceInMs: number;
sourceOutMs: number;
wordIds: string[];
}
export interface EDLItem extends Clip {
timelineStartMs: number;
durationMs: number;
}
/** Collapse contiguous kept words into clips; a deletion breaks the run. */
export function buildClips(words: Word[]): Clip[] {
const clips: Clip[] = [];
let cur: Clip | null = null;
for (const w of words) {
if (w.deleted) { cur = null; continue; }
if (!cur) {
cur = { id: "c_" + w.id, sourceInMs: w.startMs, sourceOutMs: w.endMs, wordIds: [w.id] };
clips.push(cur);
} else {
cur.sourceOutMs = w.endMs;
cur.wordIds.push(w.id);
}
}
return clips;
}
/** Left-pack (ripple) projection: each clip starts where the previous ended. */
export function buildEDL(words: Word[]): EDLItem[] {
let t = 0;
return buildClips(words).map((c) => {
const durationMs = c.sourceOutMs - c.sourceInMs;
const item: EDLItem = { ...c, timelineStartMs: t, durationMs };
t += durationMs;
return item;
});
}
export const totalMs = (edl: EDLItem[]) => edl.reduce((s, i) => s + i.durationMs, 0);
/** timeline ms → which clip + the equivalent source ms (for audio playback / seeking). */
export function timelineToSource(edl: EDLItem[], tlMs: number) {
for (const it of edl) {
if (tlMs >= it.timelineStartMs && tlMs < it.timelineStartMs + it.durationMs) {
return { clip: it, sourceMs: it.sourceInMs + (tlMs - it.timelineStartMs) };
}
}
return null;
}
/** source ms of a word's start → its position on the (reflowed) timeline, or null if cut. */
export function wordTimelineStart(edl: EDLItem[], word: Word): number | null {
for (const it of edl) {
if (it.wordIds.includes(word.id)) {
return it.timelineStartMs + (word.startMs - it.sourceInMs);
}
}
return null;
}