| import { v4 as uuidv4 } from "uuid"; |
| import type { Caption } from "@/types"; |
|
|
| const BASE_WORD_MS = 180; |
| const CHAR_MS = 40; |
| const GAP_MS = 60; |
|
|
| |
| |
| export const WORDS_PER_CAPTION = 6; |
|
|
| function wordDuration(text: string): number { |
| return BASE_WORD_MS + text.length * CHAR_MS; |
| } |
|
|
| function groupWords(text: string, perGroup = WORDS_PER_CAPTION): string[] { |
| const words = text.trim().split(/\s+/).filter(Boolean); |
| const groups: string[] = []; |
|
|
| for (let i = 0; i < words.length; i += perGroup) { |
| groups.push(words.slice(i, i + perGroup).join(" ")); |
| } |
|
|
| |
| if (groups.length > 1 && groups[groups.length - 1].split(" ").length <= 2) { |
| const orphan = groups.pop() as string; |
| groups[groups.length - 1] = `${groups[groups.length - 1]} ${orphan}`; |
| } |
|
|
| return groups; |
| } |
|
|
| export function autoSyncCaptions(text: string, speed = 1): Caption[] { |
| const phrases = groupWords(text); |
|
|
| let cursor = 0; |
| return phrases.map((phrase) => { |
| const duration = |
| phrase.split(" ").reduce((sum, w) => sum + wordDuration(w), 0) / speed; |
| const caption: Caption = { |
| id: uuidv4(), |
| text: phrase, |
| startMs: cursor, |
| endMs: cursor + duration, |
| }; |
| cursor += duration + GAP_MS / speed; |
| return caption; |
| }); |
| } |
|
|
| export function syncCaptionsToDuration(text: string, totalDurationMs: number): Caption[] { |
| const phrases = groupWords(text); |
| if (phrases.length === 0) return []; |
|
|
| |
| const weights = phrases.map((p) => Math.max(p.length, 1)); |
| const totalWeight = weights.reduce((a, b) => a + b, 0); |
| let cursor = 0; |
|
|
| return phrases.map((phrase, index) => { |
| const duration = (weights[index] / totalWeight) * totalDurationMs; |
| const caption: Caption = { |
| id: uuidv4(), |
| text: phrase.replace(/[,;:]+$/, ""), |
| startMs: Math.round(cursor), |
| endMs: Math.round(cursor + duration), |
| }; |
| cursor += duration; |
| return caption; |
| }); |
| } |
|
|
| export function captionsToText(captions: Caption[]): string { |
| return captions.map((c) => c.text).join(" "); |
| } |
|
|