#!/usr/bin/env python3 from pathlib import Path from datetime import datetime import re import shutil import sys ROOT = Path("/home/nvme/dockerdata/prove/s152js") MANAGER = ROOT / "helpers/hlsManager.js" ROUTE = ROOT / "routes/hls.js" def replace_once(text, pattern, replacement, label, flags=0): new_text, count = re.subn( pattern, replacement, text, count=1, flags=flags ) if count != 1: raise RuntimeError( f"{label}: attesa 1 sostituzione, trovate {count}. " "File non modificati." ) print(f"[OK] {label}") return new_text def main(): if not MANAGER.is_file() or not ROUTE.is_file(): sys.exit(f"ERRORE: file non trovati sotto {ROOT}") manager = MANAGER.read_text(encoding="utf-8") route = ROUTE.read_text(encoding="utf-8") # 1. Stato esplicito del job. manager = replace_once( manager, r"notified:\s*false,", "notified: false,\n" " ready: false,\n" " status: 'processing',", "Stato iniziale del job", ) # 2. Il poller dichiara il job pronto solo alla soglia richiesta. manager = replace_once( manager, r"if\s*\(\s*tsCount\s*>=\s*job\.segmentsNeeded\s*\)" r"\s*\{\s*job\.notified\s*=\s*true;", "if (tsCount >= job.segmentsNeeded) {\n" " job.ready = true;\n" " job.status = 'ready';\n" " job.notified = true;", "Soglia dei segmenti HLS", ) # 3. Registra esplicitamente il timeout. timeout_start = manager.find("// Timeout") exit_start = manager.find("// FFmpeg exit", timeout_start) if timeout_start < 0 or exit_start < 0: raise RuntimeError("Blocco timeout non trovato. Nessun file scritto.") timeout_block = manager[timeout_start:exit_start] timeout_block = replace_once( timeout_block, r"job\.notified\s*=\s*true;", "job.notified = true;\n job.status = 'timeout';", "Stato timeout", ) manager = ( manager[:timeout_start] + timeout_block + manager[exit_start:] ) # 4. All'uscita di FFmpeg, non confondere fine conversione # con buffer sufficiente per iniziare il casting. manager = replace_once( manager, r"let status;\s*" r"if\s*\(code === 0 && masterExists\)\s*\{\s*" r"status = 'finished';\s*" r"\}\s*else\s*\{\s*" r"status = 'error';\s*" r"\}\s*" r"job\.notified\s*=\s*true;", "const hasMinimumBuffer =\n" " code === 0 &&\n" " masterExists &&\n" " tsCount >= job.segmentsNeeded;\n" "\n" " if (hasMinimumBuffer) {\n" " job.ready = true;\n" " }\n" "\n" " job.status = job.ready\n" " ? 'ready'\n" " : (code === 0 && masterExists\n" " ? 'finished_insufficient_segments'\n" " : 'error');\n" "\n" " status = job.status;\n" " job.notified = true;", "Controllo finale all'uscita di FFmpeg", ) # 5. Conserva il job per due minuti dopo l'uscita di FFmpeg. manager = replace_once( manager, r"finally\s*\{\s*jobs\.delete\(\s*jobId\s*\);\s*\}", "finally {\n" " job.finishedAt = Date.now();\n" " setTimeout(() => {\n" " jobs.delete(jobId);\n" " }, 2 * 60 * 1000).unref();\n" " }", "Conservazione temporanea del job", ) # 6. Aggiunge il conteggio locale dei segmenti .ts alla route. route = replace_once( route, r"const router = express\.Router\(\);", "const router = express.Router();\n" "\n" "function countTsSegments(outDir) {\n" " try {\n" " return fs.readdirSync(outDir).filter(\n" " name => name.toLowerCase().endsWith('.ts')\n" " ).length;\n" " } catch (err) {\n" " return 0;\n" " }\n" "}", "Conteggio segmenti nella route", ) # 7. L'endpoint URL richiede almeno segmentsNeeded segmenti. route = replace_once( route, r"if\s*\(\s*" r"!job\.notified\s*\|\|\s*" r"!fs\.existsSync\(masterPath\)\s*" r"\)\s*\{\s*" r"return res\.status\(404\)\.json\(\{\s*" r"error:\s*'Playlist not yet ready'\s*" r"\}\);\s*" r"\}", "const tsCount = countTsSegments(job.outDir);\n" "\n" " if (\n" " !job.ready ||\n" " !fs.existsSync(masterPath) ||\n" " tsCount < job.segmentsNeeded\n" " ) {\n" " if (job.exitCode !== null) {\n" " return res.status(409).json({\n" " error: 'HLS finished before minimum buffer was ready',\n" " status: job.status,\n" " ready_segments: tsCount,\n" " required_segments: job.segmentsNeeded\n" " });\n" " }\n" "\n" " return res.status(404).json({\n" " error: 'Playlist not yet ready',\n" " ready_segments: tsCount,\n" " required_segments: job.segmentsNeeded\n" " });\n" " }", "Controllo della soglia nell'endpoint URL", flags=re.S, ) # Tutte le sostituzioni sono state verificate prima di scrivere. # Crea backup e poi salva entrambi i file. stamp = datetime.now().strftime("%Y%m%d_%H%M%S") backup_manager = MANAGER.with_name( f"hlsManager.js.bak_{stamp}" ) backup_route = ROUTE.with_name( f"hls.js.bak_{stamp}" ) shutil.copy2(MANAGER, backup_manager) shutil.copy2(ROUTE, backup_route) MANAGER.write_text(manager, encoding="utf-8") ROUTE.write_text(route, encoding="utf-8") print("\nModifiche applicate.") print(f"Backup: {backup_manager}") print(f"Backup: {backup_route}") print("\nVerifica ora la sintassi JavaScript con:") print("node --check helpers/hlsManager.js") print("node --check routes/hls.js") if __name__ == "__main__": main()