feat(live): add language-aware flexible channel search (#23)
Add backend language detection, flexible live channel search, iptv-org catalog caching, and protected built-in EPG source registration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
50
server/services/channelLanguage.js
Normal file
50
server/services/channelLanguage.js
Normal file
@ -0,0 +1,50 @@
|
||||
const LANGUAGES = Object.freeze({
|
||||
fr: { code: 'fr', short: 'FR', label: 'French' },
|
||||
en: { code: 'en', short: 'EN', label: 'English' },
|
||||
es: { code: 'es', short: 'ES', label: 'Spanish' },
|
||||
de: { code: 'de', short: 'DE', label: 'German' },
|
||||
it: { code: 'it', short: 'IT', label: 'Italian' },
|
||||
pt: { code: 'pt', short: 'PT', label: 'Portuguese' }
|
||||
});
|
||||
|
||||
const CODE_ALIASES = Object.freeze({
|
||||
fr: 'fr', fra: 'fr', fre: 'fr', french: 'fr', francais: 'fr', france: 'fr',
|
||||
en: 'en', eng: 'en', english: 'en', anglais: 'en', uk: 'en', us: 'en',
|
||||
es: 'es', spa: 'es', spanish: 'es', espanol: 'es', spain: 'es',
|
||||
de: 'de', deu: 'de', ger: 'de', german: 'de', deutsch: 'de',
|
||||
it: 'it', ita: 'it', italian: 'it', italiano: 'it',
|
||||
pt: 'pt', por: 'pt', portuguese: 'pt', portugues: 'pt'
|
||||
});
|
||||
|
||||
function normalize(value) {
|
||||
return String(value || '').normalize('NFKD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
||||
}
|
||||
|
||||
function detectLanguage(item) {
|
||||
const data = item.data && typeof item.data === 'object' ? item.data : {};
|
||||
const values = [item.name, item.category_name, data.groupTitle, data.tvgId, data.tvgName].filter(Boolean);
|
||||
const scores = {};
|
||||
const evidence = [];
|
||||
const prefix = String(item.name || '').trim().match(/^(?:\|\s*([a-z-]{2,8})\s*\||\[\s*([a-z-]{2,8})\s*\]|([a-z-]{2,8})\s*\|)/i);
|
||||
if (prefix) add(prefix[1] || prefix[2] || prefix[3], 70, 'name-prefix');
|
||||
values.forEach((value, index) => {
|
||||
const tokens = normalize(value).split(/[^a-z]+/).filter(Boolean);
|
||||
for (const token of tokens) add(token, index === 0 ? 45 : 25, index === 0 ? 'name-token' : 'metadata');
|
||||
});
|
||||
function add(token, weight, type) {
|
||||
const code = CODE_ALIASES[normalize(token)];
|
||||
if (!code) return;
|
||||
scores[code] = (scores[code] || 0) + weight;
|
||||
evidence.push({ code, weight, type, value: String(token) });
|
||||
}
|
||||
const ranked = Object.entries(scores).sort((a, b) => b[1] - a[1]);
|
||||
if (!ranked.length) return { code: 'und', short: 'UND', label: 'Unknown', confidence: 0, scores, evidence };
|
||||
const total = ranked.reduce((sum, [, score]) => sum + score, 0);
|
||||
const confidence = Math.round(ranked[0][1] / total * 100);
|
||||
if (confidence < 55 || (ranked[1] && ranked[0][1] - ranked[1][1] < 15)) {
|
||||
return { code: 'und', short: 'UND', label: 'Unknown', confidence: 0, scores, evidence };
|
||||
}
|
||||
return { ...LANGUAGES[ranked[0][0]], confidence, scores, evidence };
|
||||
}
|
||||
|
||||
module.exports = { CODE_ALIASES, LANGUAGES, detectLanguage, normalize };
|
||||
Reference in New Issue
Block a user