200 lines
6.2 KiB
Python
200 lines
6.2 KiB
Python
#!/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()
|