feat: fix timestamps

This commit is contained in:
2026-03-04 15:07:43 +01:00
parent d4fd96ce03
commit 8ad410d8a5
6 changed files with 212 additions and 50 deletions
+125
View File
@@ -0,0 +1,125 @@
const SECTION_TAG_LEADING_REGEX = /^\s*\[([^\]]+)\]\s*/i
const TOKEN_CLEANUP_REGEX = /[.,!?;:()[\]{}"'`]+/g
const MAX_FIRST_SECTION_START_S = 3
const MIN_DUPLICATE_SECTION_START_S = 8
const MIN_DUPLICATE_GAP_S = 4
const MIN_DUPLICATE_PREFIX_TOKENS = 12
const MIN_DUPLICATE_PREFIX_RATIO = 0.9
const toSeconds = (value) => {
const n = Number(value)
return Number.isFinite(n) && n >= 0 ? n : 0
}
const normalizeTag = (value = '') =>
String(value || '')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase()
const normalizeToken = (value = '') =>
String(value || '')
.replace(/\n/g, ' ')
.replace(TOKEN_CLEANUP_REGEX, ' ')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase()
const tokenizeWord = (value = '') => {
const normalized = normalizeToken(value)
return normalized ? normalized.split(' ') : []
}
const isVerseLikeTag = (tag = '') => {
const normalized = normalizeTag(tag)
return normalized.includes('verse') || normalized.includes('couplet')
}
const buildSegments = (alignedWords = []) => {
const segments = []
let current = null
const flush = () => {
if (current && current.tokens.length > 0) {
segments.push(current)
}
current = null
}
for (let index = 0; index < alignedWords.length; index++) {
const entry = alignedWords[index] || {}
const rawWord = String(entry.word || '')
const tagMatch = rawWord.match(SECTION_TAG_LEADING_REGEX)
if (tagMatch) {
flush()
current = {
tag: normalizeTag(tagMatch[1]),
startIdx: index,
endIdx: index,
startS: toSeconds(entry.startS),
tokens: [],
}
} else if (!current) {
current = {
tag: '',
startIdx: index,
endIdx: index,
startS: toSeconds(entry.startS),
tokens: [],
}
}
const noTagWord = tagMatch ? rawWord.replace(SECTION_TAG_LEADING_REGEX, '') : rawWord
const tokens = tokenizeWord(noTagWord)
if (tokens.length > 0) {
current.tokens.push(...tokens)
current.endIdx = index
} else if (current && current.tokens.length === 0) {
current.endIdx = index
}
}
flush()
return segments
}
const countCommonPrefixTokens = (tokensA = [], tokensB = []) => {
const max = Math.min(tokensA.length, tokensB.length)
let matched = 0
while (matched < max && tokensA[matched] === tokensB[matched]) {
matched += 1
}
return matched
}
export const trimLeadingDuplicateSection = (alignedWords = []) => {
if (!Array.isArray(alignedWords) || alignedWords.length === 0) return []
const segments = buildSegments(alignedWords)
if (segments.length < 2) return alignedWords
const first = segments[0]
const second = segments[1]
if (!isVerseLikeTag(first.tag) || !isVerseLikeTag(second.tag)) return alignedWords
if (first.tag !== second.tag) return alignedWords
const firstStartS = toSeconds(first.startS)
const secondStartS = toSeconds(second.startS)
if (firstStartS > MAX_FIRST_SECTION_START_S) return alignedWords
if (secondStartS < MIN_DUPLICATE_SECTION_START_S) return alignedWords
if (secondStartS - firstStartS < MIN_DUPLICATE_GAP_S) return alignedWords
const matchedPrefixTokens = countCommonPrefixTokens(first.tokens, second.tokens)
const minTokenCount = Math.min(first.tokens.length, second.tokens.length)
const matchedRatio = minTokenCount > 0 ? matchedPrefixTokens / minTokenCount : 0
if (matchedPrefixTokens < MIN_DUPLICATE_PREFIX_TOKENS) return alignedWords
if (matchedRatio < MIN_DUPLICATE_PREFIX_RATIO) return alignedWords
return [...alignedWords.slice(0, first.startIdx), ...alignedWords.slice(first.endIdx + 1)]
}
export default trimLeadingDuplicateSection