feat: fix timestamps
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user