import { readFileSync } from "fs"; import { resolve } from "path"; const envContent = readFileSync(resolve(import.meta.dirname, "../.env"), "utf-8"); for (const line of envContent.split("\n")) { const t = line.trim(); if (!t || t.startsWith("#")) continue; const e = t.indexOf("="); if (e < 0) continue; if (!process.env[t.slice(0, e)]) process.env[t.slice(0, e)] = t.slice(e + 1); } import { detectFormat } from "../src/lib/gts/formats"; import { richTextToPlain } from "../src/lib/gts/models/text"; import { runNetflixChecks } from "../src/lib/qc/netflix-checks"; import { runRuleChecks } from "../src/lib/qc/rule-checks"; import type { QCLine } from "../src/lib/qc/types"; const TEST_DIR = resolve(import.meta.dirname, "../test-pmws"); const manifest = JSON.parse(readFileSync(resolve(TEST_DIR, "manifest.json"), "utf-8")) as Array<{ lang: string; file: string; sensitiveTermCount: number; glyphViolationCount: number; expectedIssues: number; [key: string]: any; }>; console.log(`=== QC VALIDATION: ${manifest.length} PMW files ===`); console.log(""); let totalFiles = 0; let passedFiles = 0; let failedFiles = 0; const failures: string[] = []; for (const entry of manifest) { totalFiles++; const filePath = resolve(TEST_DIR, entry.file); try { // Parse PMW const buf = readFileSync(filePath); const handler = detectFormat(buf, entry.file); if (!handler) { console.log(`✗ ${entry.lang.padEnd(8)} | PARSE FAILED: format not detected`); failedFiles++; failures.push(`${entry.lang}: format not detected`); continue; } const doc = handler.read(buf, entry.file); const track = doc.tracks[0]; const lines: QCLine[] = track.subtitles.map((sub, i) => ({ id: i + 1, text: typeof sub.text === "string" ? sub.text : richTextToPlain(sub.text), })); // Run Netflix checks (glyph + sensitive terms) const netflixIssues = runNetflixChecks(lines, entry.lang); const glyphIssues = netflixIssues.filter((i) => i.category === "glyph"); const sensitiveIssues = netflixIssues.filter((i) => i.category === "sensitive"); // Also run rule checks const ruleIssues = runRuleChecks(lines, entry.lang); // Validate: did we detect at least as many as expected? // Note: some terms may match multiple times in different contexts, so actual >= expected const sensitiveOk = sensitiveIssues.length >= entry.sensitiveTermCount; const glyphOk = glyphIssues.length >= entry.glyphViolationCount; const status = sensitiveOk && glyphOk ? "✓" : "✗"; if (sensitiveOk && glyphOk) { passedFiles++; } else { failedFiles++; if (!sensitiveOk) failures.push(`${entry.lang}: sensitive ${sensitiveIssues.length}/${entry.sensitiveTermCount}`); if (!glyphOk) failures.push(`${entry.lang}: glyph ${glyphIssues.length}/${entry.glyphViolationCount}`); } console.log( `${status} ${entry.lang.padEnd(8)} | ` + `sensitive: ${String(sensitiveIssues.length).padStart(4)}/${String(entry.sensitiveTermCount).padStart(4)} ` + `${sensitiveOk ? "✓" : "✗"} | ` + `glyph: ${String(glyphIssues.length).padStart(2)}/${String(entry.glyphViolationCount).padStart(2)} ` + `${glyphOk ? "✓" : "✗"} | ` + `rule: ${String(ruleIssues.length).padStart(3)} | ` + `total: ${netflixIssues.length + ruleIssues.length}` ); } catch (err) { failedFiles++; const msg = (err as Error).message.substring(0, 60); console.log(`✗ ${entry.lang.padEnd(8)} | ERROR: ${msg}`); failures.push(`${entry.lang}: ${msg}`); } } console.log(""); console.log("=== SUMMARY ==="); console.log(`Files: ${totalFiles}`); console.log(`Passed: ${passedFiles}`); console.log(`Failed: ${failedFiles}`); console.log(""); if (failures.length > 0) { console.log("FAILURES:"); for (const f of failures) console.log(` ${f}`); } else { console.log("ALL 32 LANGUAGES PASSED ✓"); } // Aggregate stats const totalSensitive = manifest.reduce((s, e) => s + e.sensitiveTermCount, 0); const totalGlyphs = manifest.reduce((s, e) => s + e.glyphViolationCount, 0); console.log(""); console.log(`Injected: ${totalSensitive} sensitive terms + ${totalGlyphs} illegal glyphs across ${totalFiles} files`);