feat: add optional Laya engine mode for grouping and language detection (#12)

Add a languageDetectionMode setting (algo | laya | laya+algo) exposed
in Settings with three engine choices. When Laya is enabled, a laya
service (Dockerfile.laya, optional compose profile, models stored
outside the repo under ./data/models/laya) can be started, with model
download and deletion from the UI via /laya routes to manage disk
space. Add a channelLanguageFusion service combining Laya results
with the existing algorithm, and wire detection mode through
grouping, channel list and player. Cover engine selection, fusion
and laya service behavior with tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
blomios
2026-10-02 11:13:34 +02:00
parent 9e80655fd9
commit ce11ecc757
19 changed files with 780 additions and 13 deletions

View File

@ -2,10 +2,13 @@
const detector = typeof module === 'object' && module.exports
? require('./channelLanguageDetection.js')
: root.ChannelLanguageDetection;
const service = factory(detector);
const fusion = typeof module === 'object' && module.exports
? require('./channelLanguageFusion.js')
: root.ChannelLanguageFusion;
const service = factory(detector, fusion);
if (typeof module === 'object' && module.exports) module.exports = service;
if (root) root.ChannelVariantGrouping = service;
})(typeof globalThis !== 'undefined' ? globalThis : this, function (languageDetection) {
})(typeof globalThis !== 'undefined' ? globalThis : this, function (languageDetection, languageFusion) {
const QUALITY_TOKENS = new Set([
'4k', 'uhd', 'fhd', 'fullhd', 'hd', 'sd', 'hevc', 'h265', 'h264',
'1080p', '1080i', '720p', '576p', '480p'
@ -96,10 +99,14 @@
});
return Array.from(buckets, ([fingerprint, rawVariants]) => {
const variants = rawVariants.map(variant => ({
...variant,
...languageDetection.detectLanguage(variant, { sourceName: variant.sourceName })
}));
const variants = rawVariants.map(variant => {
const algo = languageDetection.detectLanguage(variant, { sourceName: variant.sourceName });
const result = options.languageResults?.get(getVariantKey(variant));
return {
...variant,
...languageFusion.applyLanguageMode(algo, result, options.languageMode || 'algo')
};
});
const visibleVariants = variants.filter(variant => !isHidden(variant));
const selectableVariants = showHidden ? variants : visibleVariants;
const languageCounts = selectableVariants.reduce((counts, variant) => {