feat: fix lecteur
This commit is contained in:
+171
-697
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