Files
foliate-js/panel-detection/load-scripts.js
T
john-okeefe 7971ad7996 feat: add panel detection with multi-tier fallback system
Implement intelligent panel detection for manga/comics using a three-tier
fallback system that automatically selects the best detection method:

- Tier 1: OpenCV edge detection (fast, accurate for clear panel borders)
- Tier 2: COCO-SSD ML detection (handles irregular layouts)
- Tier 3: Grid-based detection (lightweight, always works)

The system automatically falls back through tiers if higher tiers fail
or if CSP blocks 'unsafe-eval' required by ML libraries.

Changes:
- panel-detection/detector.js: Modified to use dynamic script loading
  * Calls loadMLLibraries() on first use for lazy loading
  * Checks if ML libraries loaded successfully before using them
  * Falls back to grid detection if CSP blocks eval or libraries fail
  * Uses globalThis.cv/tf/cocoSsd for UMD/global library access

- panel-detection/load-scripts.js: Created dynamic script loader
  * Dynamically injects <script> tags when panel detection enabled
  * Checks CSP compatibility with canUseEval() function
  * Loads libraries in correct order: TensorFlow → OpenCV → COCO-SSD
  * Falls back to grid if ML libraries fail to load
  * Uses Promise-based API for clean async loading

This library-native approach keeps all functionality within foliate-js
without requiring changes to reader.html or consumer applications.
2026-04-13 19:14:44 -04:00

75 lines
2.0 KiB
JavaScript

// panel-detection/load-scripts.js
// Dynamically loads ML/CV libraries as script tags
const loadedScripts = new Set();
export async function loadMLLibraries() {
// Check if already loaded
if (globalThis.cv && globalThis.tf && globalThis.cocoSsd) {
return { loaded: true, method: "cached" };
}
// Check CSP compatibility
if (!canUseEval()) {
console.warn("Panel detection requires CSP with unsafe-eval");
return { loaded: false, reason: "csp-blocked" };
}
const scripts = [
{ name: "TensorFlow", src: "./vendor/tfjs/tf.min.js", global: "tf" },
{ name: "OpenCV", src: "./vendor/opencv/opencv.js", global: "cv" },
{
name: "COCO-SSD",
src: "./vendor/coco-ssd/coco-ssd.min.js",
global: "cocoSsd",
},
];
try {
for (const { name, src, global: globalName } of scripts) {
if (globalThis[globalName]) continue; // Already loaded
await loadScript(src);
loadedScripts.add(src);
// Verify global was set
if (!globalThis[globalName]) {
throw new Error(`${name} failed to load (global not set)`);
}
}
return { loaded: true, method: "dynamic" };
} catch (e) {
console.warn("Failed to load ML libraries:", e);
return { loaded: false, reason: e.message };
}
}
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = src;
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Failed to load: ${src}`));
document.head.appendChild(script);
});
}
function canUseEval() {
// Try to detect if CSP allows eval
try {
const test = new Function("return true")();
return test === true;
} catch {
return false;
}
}
export function getLibrariesStatus() {
return {
opencv: !!globalThis.cv,
tensorflow: !!globalThis.tf,
cocoSsd: !!globalThis.cocoSsd,
};
}
export function clearScripts() {
// Note: We don't remove script tags as they can't be unloaded
// This is for future cleanup if needed
loadedScripts.clear();
}