Salvataggio modifiche correnti prima di implementare condivisione canto
This commit is contained in:
@@ -380,12 +380,95 @@ export class ProposeCantoPage implements OnInit {
|
||||
return this.parseSongSpatially(words);
|
||||
}
|
||||
|
||||
sanitizeOcrChord(text: string): string {
|
||||
if (!text) return text;
|
||||
if (text.includes('/')) {
|
||||
return text.split('/').map(part => this.sanitizeOcrChord(part.trim())).join('/');
|
||||
}
|
||||
let cleaned = text;
|
||||
// Replace D0/d0 with DO/do
|
||||
cleaned = cleaned.replace(/^D0/gi, 'DO');
|
||||
// Replace S0L/s0l with SOL/sol
|
||||
cleaned = cleaned.replace(/^S0L/gi, 'SOL');
|
||||
// Clean H/sharp mismatches:
|
||||
cleaned = cleaned.replace(/H#/gi, '#');
|
||||
cleaned = cleaned.replace(/#H/gi, '#');
|
||||
cleaned = cleaned.replace(/([CDEFGAB]|DO|RE|MI|FA|SOL|LA|SI)H/gi, '$1#');
|
||||
// Clean duplicate sharps (e.g. ## -> #)
|
||||
cleaned = cleaned.replace(/##+/g, '#');
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
convertEnglishChordToItalian(chord: string): string {
|
||||
if (!chord) return chord;
|
||||
if (chord.includes('/')) {
|
||||
return chord.split('/').map(part => this.convertEnglishChordToItalian(part.trim())).join('/');
|
||||
}
|
||||
const upper = chord.toUpperCase();
|
||||
if (upper.startsWith('DO')) {
|
||||
return chord;
|
||||
}
|
||||
if (upper.startsWith('FA')) {
|
||||
if (upper.startsWith('FAUG') || upper.startsWith('FADD') || upper.startsWith('FALT')) {
|
||||
return 'FA' + chord.slice(1);
|
||||
}
|
||||
return chord;
|
||||
}
|
||||
if (upper.startsWith('C')) {
|
||||
return 'DO' + chord.slice(1);
|
||||
}
|
||||
if (upper.startsWith('D')) {
|
||||
return 'RE' + chord.slice(1);
|
||||
}
|
||||
if (upper.startsWith('E')) {
|
||||
return 'MI' + chord.slice(1);
|
||||
}
|
||||
if (upper.startsWith('F')) {
|
||||
return 'FA' + chord.slice(1);
|
||||
}
|
||||
if (upper.startsWith('G')) {
|
||||
return 'SOL' + chord.slice(1);
|
||||
}
|
||||
if (upper.startsWith('A')) {
|
||||
return 'LA' + chord.slice(1);
|
||||
}
|
||||
if (upper.startsWith('B')) {
|
||||
return 'SI' + chord.slice(1);
|
||||
}
|
||||
return chord;
|
||||
}
|
||||
|
||||
isLabelLine(text: string): boolean {
|
||||
return /^(Intro|Strofa|Rit|Special|Coro|Bridge|RIT|CHORUS|VERSE)/i.test(text.trim());
|
||||
}
|
||||
|
||||
parseSongSpatially(words: any[]): string {
|
||||
if (!words || words.length === 0) {
|
||||
console.warn('[OCR-Capture] Nessuna parola ricevuta dall\'OCR.');
|
||||
return '';
|
||||
}
|
||||
|
||||
// Preprocess words to split run-together chords like BA
|
||||
const preprocessedWords: any[] = [];
|
||||
words.forEach(w => {
|
||||
if (!w.text) return;
|
||||
const match = w.text.match(/^([ABCDEFG])([ABCDEFG])$/i);
|
||||
if (match && !(match[1].toUpperCase() === 'F' && match[2].toUpperCase() === 'A')) {
|
||||
const charWidth = (w.bbox.x1 - w.bbox.x0) / 2;
|
||||
preprocessedWords.push({
|
||||
text: match[1],
|
||||
bbox: { ...w.bbox, x1: w.bbox.x0 + charWidth }
|
||||
});
|
||||
preprocessedWords.push({
|
||||
text: match[2],
|
||||
bbox: { ...w.bbox, x0: w.bbox.x0 + charWidth }
|
||||
});
|
||||
} else {
|
||||
preprocessedWords.push(w);
|
||||
}
|
||||
});
|
||||
words = preprocessedWords;
|
||||
|
||||
console.log(`[OCR-Capture] Parole totali ricevute dall'OCR: ${words.length}`);
|
||||
const validWords = words.filter(w => w.text && w.text.trim().length > 0);
|
||||
console.log(`[OCR-Capture] Parole valide dopo filtraggio: ${validWords.length}`);
|
||||
@@ -436,6 +519,12 @@ export class ProposeCantoPage implements OnInit {
|
||||
let clean = text.toUpperCase().replace(/\s+/g, '');
|
||||
clean = clean.replace(/\((.*?)\)/g, '/$1');
|
||||
clean = clean.replace(/[\.\,]$/g, '');
|
||||
clean = this.sanitizeOcrChord(clean);
|
||||
if (clean.includes('_')) {
|
||||
const parts = clean.split('_').filter(p => p.length > 0);
|
||||
if (parts.length === 0) return false;
|
||||
return parts.every(p => chordRegex.test(p));
|
||||
}
|
||||
return chordRegex.test(clean);
|
||||
};
|
||||
|
||||
@@ -481,7 +570,8 @@ export class ProposeCantoPage implements OnInit {
|
||||
|
||||
if (current.isChords) {
|
||||
const next = (i + 1 < classifiedLines.length) ? classifiedLines[i + 1] : null;
|
||||
if (next && !next.isChords) {
|
||||
const nextText = next ? next.words.map(w => w.text).join(' ') : '';
|
||||
if (next && !next.isChords && !this.isLabelLine(nextText)) {
|
||||
// Merge spatially!
|
||||
const merged = this.mergeChordsAndLyrics(current.words, next.words);
|
||||
const lineText = next.words.map(w => w.text).join(' ');
|
||||
@@ -502,21 +592,29 @@ export class ProposeCantoPage implements OnInit {
|
||||
const multiChordRegex = /((?:DO|RE|MI|FA|SOL|LA|SI|C|D|E|F|G|A|B)(?:#|B)?(?:MIN|MAJ|AUG|DIM|M|-)?(?:7|9|11|13)?(?:\/(?:DO|RE|MI|FA|SOL|LA|SI|C|D|E|F|G|A|B)(?:#|B)?)?)/gi;
|
||||
const expandedChords: string[] = [];
|
||||
current.words.forEach(w => {
|
||||
let cleanText = w.text.toUpperCase().replace(/\s+/g, '');
|
||||
cleanText = cleanText.replace(/\((.*?)\)/g, '/$1');
|
||||
cleanText = cleanText.replace(/[\.\,]$/g, '');
|
||||
|
||||
if (chordRegex.test(cleanText)) {
|
||||
expandedChords.push(`[${cleanText}]`);
|
||||
} else {
|
||||
const matches = [...cleanText.matchAll(multiChordRegex)];
|
||||
const fullMatchStr = matches.map(m => m[0]).join('');
|
||||
if (matches.length > 0 && fullMatchStr === cleanText) {
|
||||
expandedChords.push(...matches.map((m: string) => `[${m[0].toUpperCase()}]`));
|
||||
const parts = w.text.split(/(_)/);
|
||||
const processedParts = parts.map((part: string) => {
|
||||
if (part === '_') return ' _ ';
|
||||
if (!part.trim()) return part;
|
||||
|
||||
let cleanText = part.toUpperCase().replace(/\s+/g, '');
|
||||
cleanText = cleanText.replace(/\((.*?)\)/g, '/$1');
|
||||
cleanText = cleanText.replace(/[\.\,]$/g, '');
|
||||
cleanText = this.sanitizeOcrChord(cleanText);
|
||||
|
||||
if (chordRegex.test(cleanText)) {
|
||||
return `[${this.convertEnglishChordToItalian(cleanText)}]`;
|
||||
} else {
|
||||
expandedChords.push(w.text); // keep original text if it's not a pure chord merge
|
||||
const matches = [...cleanText.matchAll(multiChordRegex)];
|
||||
const fullMatchStr = matches.map(m => m[0]).join('');
|
||||
if (matches.length > 0 && fullMatchStr === cleanText) {
|
||||
return matches.map((m: any) => `[${this.convertEnglishChordToItalian(m[0].toUpperCase())}]`).join(' ');
|
||||
} else {
|
||||
return part;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
expandedChords.push(processedParts.join(''));
|
||||
});
|
||||
const wrapped = expandedChords.join(' ');
|
||||
if (wrapped) {
|
||||
@@ -535,7 +633,7 @@ export class ProposeCantoPage implements OnInit {
|
||||
inVerse = true;
|
||||
}
|
||||
|
||||
processedLines.push(lineText);
|
||||
processedLines.push(this.wrapChords(lineText, chordRegex));
|
||||
}
|
||||
|
||||
// If we see a large vertical gap, close open blocks
|
||||
@@ -606,8 +704,14 @@ export class ProposeCantoPage implements OnInit {
|
||||
|
||||
if (!textWords || textWords.length === 0) {
|
||||
return mergedChordWords.map(c => {
|
||||
let clean = c.text.toUpperCase().replace(/\s+/g, '').replace(/\((.*?)\)/g, '/$1').replace(/[\.\,\[\]]/g, '');
|
||||
return `[${clean}]`;
|
||||
const parts = c.text.split(/(_)/);
|
||||
return parts.map((part: string) => {
|
||||
if (part === '_') return ' _ ';
|
||||
if (!part.trim()) return part;
|
||||
let clean = part.toUpperCase().replace(/\s+/g, '').replace(/\((.*?)\)/g, '/$1').replace(/[\.\,\[\]]/g, '');
|
||||
clean = this.sanitizeOcrChord(clean);
|
||||
return `[${this.convertEnglishChordToItalian(clean)}]`;
|
||||
}).join('');
|
||||
}).join(' ');
|
||||
}
|
||||
|
||||
@@ -616,30 +720,54 @@ export class ProposeCantoPage implements OnInit {
|
||||
|
||||
const expandedChordWords: any[] = [];
|
||||
mergedChordWords.forEach(chord => {
|
||||
let originalText = chord.text.toUpperCase().replace(/\s+/g, '').replace(/\((.*?)\)/g, '/$1').replace(/[\.\,\[\]]/g, '');
|
||||
const matches = [...originalText.matchAll(multiChordRegex)];
|
||||
const parts = chord.text.split(/(_)/);
|
||||
let currentX = chord.bbox.x0;
|
||||
const totalLen = chord.text.length || 1;
|
||||
const widthPerChar = (chord.bbox.x1 - chord.bbox.x0) / totalLen;
|
||||
|
||||
if (matches.length === 0) {
|
||||
expandedChordWords.push(chord);
|
||||
} else {
|
||||
const fullMatchStr = matches.map(m => m[0]).join('');
|
||||
if (fullMatchStr === originalText) {
|
||||
const charWidth = (chord.bbox.x1 - chord.bbox.x0) / Math.max(1, originalText.length);
|
||||
matches.forEach(match => {
|
||||
const matchIndex = match.index!;
|
||||
const matchLength = match[0].length;
|
||||
const newX0 = chord.bbox.x0 + matchIndex * charWidth;
|
||||
const newX1 = chord.bbox.x0 + (matchIndex + matchLength) * charWidth;
|
||||
|
||||
expandedChordWords.push({
|
||||
text: match[0],
|
||||
bbox: { ...chord.bbox, x0: newX0, x1: newX1 }
|
||||
});
|
||||
parts.forEach((part: string) => {
|
||||
const partLen = part.length;
|
||||
const partWidth = partLen * widthPerChar;
|
||||
const partX0 = currentX;
|
||||
const partX1 = currentX + partWidth;
|
||||
currentX = partX1;
|
||||
|
||||
if (part === '_' || !part.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let originalText = part.toUpperCase().replace(/\s+/g, '').replace(/\((.*?)\)/g, '/$1').replace(/[\.\,\[\]]/g, '');
|
||||
originalText = this.sanitizeOcrChord(originalText);
|
||||
const matches = [...originalText.matchAll(multiChordRegex)];
|
||||
|
||||
if (matches.length === 0) {
|
||||
expandedChordWords.push({
|
||||
text: originalText,
|
||||
bbox: { ...chord.bbox, x0: partX0, x1: partX1 }
|
||||
});
|
||||
} else {
|
||||
expandedChordWords.push(chord);
|
||||
const fullMatchStr = matches.map(m => m[0]).join('');
|
||||
if (fullMatchStr === originalText) {
|
||||
const matchCharWidth = (partX1 - partX0) / Math.max(1, originalText.length);
|
||||
matches.forEach(match => {
|
||||
const matchIndex = match.index!;
|
||||
const matchLength = match[0].length;
|
||||
const newX0 = partX0 + matchIndex * matchCharWidth;
|
||||
const newX1 = partX0 + (matchIndex + matchLength) * matchCharWidth;
|
||||
|
||||
expandedChordWords.push({
|
||||
text: match[0],
|
||||
bbox: { ...chord.bbox, x0: newX0, x1: newX1 }
|
||||
});
|
||||
});
|
||||
} else {
|
||||
expandedChordWords.push({
|
||||
text: originalText,
|
||||
bbox: { ...chord.bbox, x0: partX0, x1: partX1 }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const chordAssignments = new Map<any, any[]>();
|
||||
@@ -691,8 +819,9 @@ export class ProposeCantoPage implements OnInit {
|
||||
if (charIndex < 0) charIndex = 0;
|
||||
if (charIndex > wordText.length) charIndex = wordText.length;
|
||||
|
||||
const cleanChord = chord.text.replace(/[\(\)\[\]]/g, '').toUpperCase();
|
||||
wordResult += wordText.substring(lastCharIndex, charIndex) + `[${cleanChord}]`;
|
||||
let cleanChord = chord.text.replace(/[\(\)\[\]]/g, '').toUpperCase();
|
||||
cleanChord = this.sanitizeOcrChord(cleanChord);
|
||||
wordResult += wordText.substring(lastCharIndex, charIndex) + `[${this.convertEnglishChordToItalian(cleanChord)}]`;
|
||||
lastCharIndex = charIndex;
|
||||
});
|
||||
|
||||
@@ -704,10 +833,15 @@ export class ProposeCantoPage implements OnInit {
|
||||
}
|
||||
});
|
||||
|
||||
const finalResult = result.replace(/\]\[/g, '] [');
|
||||
let finalResult = result.replace(/\]\[/g, '] [');
|
||||
finalResult = finalResult.replace(/\]_\[/g, '] _ [');
|
||||
finalResult = finalResult.replace(/\]_/g, '] _ ');
|
||||
finalResult = finalResult.replace(/_\[/g, ' _ [');
|
||||
// Normalize any duplicate spaces around underscores:
|
||||
finalResult = finalResult.replace(/\s*_\s*/g, ' _ ');
|
||||
|
||||
console.log(`[OCR-Debug] Linea generata: ${finalResult}`);
|
||||
|
||||
// Assicura che due accordi consecutivi abbiano sempre 3 spazi (es. [LA][MI] diventa [LA] [MI])
|
||||
return finalResult;
|
||||
}
|
||||
|
||||
@@ -750,11 +884,22 @@ export class ProposeCantoPage implements OnInit {
|
||||
}
|
||||
|
||||
private wrapChords(line: string, regex: RegExp): string {
|
||||
const chordsInLine = line.match(regex);
|
||||
if (chordsInLine && chordsInLine.length > 0) {
|
||||
return line.replace(regex, (match) => `[${match.toUpperCase()}]`);
|
||||
}
|
||||
return line;
|
||||
const globalRegex = new RegExp(regex.source.replace(/^\^/, '').replace(/\$$/, ''), 'gi');
|
||||
const parts = line.split(/(_)/);
|
||||
return parts.map((part: string) => {
|
||||
if (part === '_') return ' _ ';
|
||||
return part.replace(globalRegex, (match) => {
|
||||
const lower = match.toLowerCase();
|
||||
if (match === 'a' || match === 'e' || match === 'o' || match === 'i') {
|
||||
return match;
|
||||
}
|
||||
if (['la', 'mi', 're', 'do', 'si', 'fa', 'sol'].includes(lower) && match === lower) {
|
||||
return match;
|
||||
}
|
||||
const sanitized = this.sanitizeOcrChord(match.toUpperCase());
|
||||
return `[${this.convertEnglishChordToItalian(sanitized)}]`;
|
||||
});
|
||||
}).join('');
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user