feat: fix lecteur
This commit is contained in:
+163
-689
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* lyricsUtils.js
|
||||
* Logique de parsing des paroles synchronisées (Karaoké)
|
||||
*/
|
||||
|
||||
const SECTION_TAG_REGEX = /^\s*\[([^\]]+)\]\s*$/i
|
||||
const SECTION_TAG_LEADING_REGEX = /^\s*\[([^\]]+)\]\s*/i
|
||||
|
||||
const cleanText = (txt) =>
|
||||
String(txt || '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
const isSentenceEnd = (txt) => /[.!?]$/.test((txt || '').trim())
|
||||
|
||||
/**
|
||||
* Normalise les noms de sections (Chorus -> Refrain, etc.)
|
||||
*/
|
||||
const getSectionLabel = (rawLabel, type, index) => {
|
||||
const lower = rawLabel.toLowerCase()
|
||||
if (lower.includes('refrain') || lower.includes('chorus'))
|
||||
return `Refrain ${index > 1 ? index : ''}`
|
||||
if (lower.includes('couplet') || lower.includes('verse'))
|
||||
return `Couplet ${index > 1 ? index : ''}`
|
||||
if (lower.includes('intro')) return 'Intro'
|
||||
if (lower.includes('pont') || lower.includes('bridge')) return 'Pont'
|
||||
return rawLabel || `Section ${index}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Détecte le type de section pour le filtrage
|
||||
*/
|
||||
const getSectionType = (label) => {
|
||||
const l = label.toLowerCase()
|
||||
if (l.includes('refrain') || l.includes('chorus')) return 'refrain'
|
||||
if (l.includes('couplet') || l.includes('verse')) return 'couplet'
|
||||
return 'other'
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforme une liste de mots synchronisés en structure Sections > Lignes > Mots
|
||||
*/
|
||||
export const processKaraokeSections = (alignedWords = []) => {
|
||||
if (!Array.isArray(alignedWords) || alignedWords.length === 0) return []
|
||||
|
||||
const sections = []
|
||||
let currentSection = null
|
||||
let currentLineWords = []
|
||||
let sectionCounts = {}
|
||||
let globalLineIdx = 0
|
||||
|
||||
const flushSection = () => {
|
||||
if (currentSection) {
|
||||
// On finit la dernière ligne si nécessaire
|
||||
if (currentLineWords.length > 0) {
|
||||
currentSection.lines.push({
|
||||
words: [...currentLineWords],
|
||||
startS: currentLineWords[0].startS,
|
||||
endS: currentLineWords[currentLineWords.length - 1].endS,
|
||||
globalIdx: globalLineIdx++,
|
||||
})
|
||||
currentLineWords = []
|
||||
}
|
||||
if (currentSection.lines.length > 0) {
|
||||
sections.push(currentSection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const startNewSection = (rawTag) => {
|
||||
flushSection()
|
||||
const type = getSectionType(rawTag)
|
||||
sectionCounts[type] = (sectionCounts[type] || 0) + 1
|
||||
|
||||
currentSection = {
|
||||
key: `section-${sections.length}`,
|
||||
label: getSectionLabel(rawTag, type, sectionCounts[type]),
|
||||
type: type,
|
||||
lines: [],
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < alignedWords.length; i++) {
|
||||
const w = alignedWords[i]
|
||||
let wordText = String(w.word || '')
|
||||
|
||||
// 1. Détection de tag de section [Couplet]
|
||||
const tagMatch = wordText.match(SECTION_TAG_LEADING_REGEX)
|
||||
if (tagMatch) {
|
||||
startNewSection(tagMatch[1])
|
||||
wordText = wordText.replace(SECTION_TAG_LEADING_REGEX, '')
|
||||
}
|
||||
|
||||
if (!currentSection) startNewSection('Musique')
|
||||
|
||||
const cleaned = cleanText(wordText)
|
||||
if (!cleaned) continue
|
||||
|
||||
const wordObj = {
|
||||
word: cleaned,
|
||||
startS: Number(w.startS || 0),
|
||||
endS: Number(w.endS || 0),
|
||||
}
|
||||
|
||||
currentLineWords.push(wordObj)
|
||||
|
||||
// 2. Détection de fin de ligne (Ponctuation, saut de ligne ou pause > 0.8s)
|
||||
const nextWord = alignedWords[i + 1]
|
||||
const hasPause = nextWord && nextWord.startS - w.endS > 0.8
|
||||
const hasNewline = String(w.word).includes('\n')
|
||||
const hasPunctuation = isSentenceEnd(cleaned)
|
||||
|
||||
if (hasPause || hasNewline || hasPunctuation || i === alignedWords.length - 1) {
|
||||
currentSection.lines.push({
|
||||
words: [...currentLineWords],
|
||||
startS: currentLineWords[0].startS,
|
||||
endS: currentLineWords[currentLineWords.length - 1].endS,
|
||||
globalIdx: globalLineIdx++,
|
||||
})
|
||||
currentLineWords = []
|
||||
}
|
||||
}
|
||||
|
||||
flushSection()
|
||||
|
||||
// On ne garde que les couplets et refrains pour l'affichage propre
|
||||
return sections.filter((s) => s.type !== 'other' || s.label === 'Musique')
|
||||
}
|
||||
Reference in New Issue
Block a user