const fs = require('fs'); const path = require('path'); function ensureDir(dirPath) { fs.mkdirSync(dirPath, { recursive: true }); } function transliterateForFilename(input) { return String(input || '') // German — must come before NFD to get ae/oe/ue instead of just a/o/u .replace(/ä/g, 'ae').replace(/Ä/g, 'Ae') .replace(/ö/g, 'oe').replace(/Ö/g, 'Oe') .replace(/ü/g, 'ue').replace(/Ü/g, 'Ue') .replace(/ß/g, 'ss') // Danish / Norwegian / Swedish .replace(/å/g, 'a').replace(/Å/g, 'A') .replace(/æ/g, 'ae').replace(/Æ/g, 'Ae') .replace(/ø/g, 'o').replace(/Ø/g, 'O') // Icelandic .replace(/ð/g, 'd').replace(/Ð/g, 'D') .replace(/þ/g, 'th').replace(/Þ/g, 'Th') // NFD decomposition + strip combining diacritical marks (handles é→e, ñ→n, ç→c, etc.) .normalize('NFD') .replace(/\p{M}+/gu, '') // Drop any remaining non-ASCII characters .replace(/[^\x00-\x7F]/g, ''); } function sanitizeFileName(input) { return transliterateForFilename(String(input || 'untitled')) .replace(/[\\/:*?"<>|]/g, '_') .replace(/\s+/g, ' ') .trim() .slice(0, 180); } function renderTemplate(template, values) { return String(template || '${title} (${year})').replace(/\$\{([^}]+)\}/g, (_, key) => { const val = values[key.trim()]; if (val === undefined || val === null || val === '') { return 'unknown'; } return String(val); }); } function findLargestMediaFile(dirPath, extensions = ['.mkv', '.mp4']) { const files = findMediaFiles(dirPath, extensions); if (files.length === 0) { return null; } return files.reduce((largest, file) => (largest === null || file.size > largest.size ? file : largest), null); } function findMediaFiles(dirPath, extensions = ['.mkv', '.mp4']) { const results = []; function walk(current) { const entries = fs.readdirSync(current, { withFileTypes: true }); for (const entry of entries) { const abs = path.join(current, entry.name); if (entry.isDirectory()) { walk(abs); } else { const ext = path.extname(entry.name).toLowerCase(); if (!extensions.includes(ext)) { continue; } const stat = fs.statSync(abs); results.push({ path: abs, size: stat.size }); } } } walk(dirPath); results.sort((a, b) => b.size - a.size || a.path.localeCompare(b.path)); return results; } module.exports = { ensureDir, transliterateForFilename, sanitizeFileName, renderTemplate, findLargestMediaFile, findMediaFiles };