108 lines
4.1 KiB
JavaScript
108 lines
4.1 KiB
JavaScript
// routes/hls.js
|
|
const express = require('express');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const db = require('../db/knex');
|
|
const config = require('../api_v1/config');
|
|
const { getUserAccess, filterReadableSources } = require('../api_v1/userAccess');
|
|
const { startJob, getJob } = require('../helpers/hlsManager');
|
|
|
|
const router = express.Router();
|
|
|
|
const HLS_BASE = path.resolve(process.env.HLS_BASE || path.join(process.cwd(), 'hls_output'));
|
|
const PUBLIC_BASE_URL = config.BASE_URL || null;
|
|
|
|
const PUBLIC_ROOT = path.resolve(__dirname, '..', 'public');
|
|
const PHOTOS_ROOT = path.join(PUBLIC_ROOT, 'photos');
|
|
|
|
function safeJoin(base, ...parts) {
|
|
const resolvedBase = path.resolve(base);
|
|
const resolved = path.resolve(base, ...parts);
|
|
const isInsideBase = resolved === resolvedBase || resolved.startsWith(`${resolvedBase}${path.sep}`);
|
|
if (!isInsideBase) throw new Error(`Path non sicuro: ${resolved}`);
|
|
return resolved;
|
|
}
|
|
|
|
// POST /hls/start
|
|
// body: { photoId } OR { inputPath } + outDir, callbackUrl, segmentsNeeded
|
|
router.post('/start', async (req, res) => {
|
|
try {
|
|
const caller = String(req.user?.name ?? '').trim();
|
|
if (!caller) return res.status(401).json({ error: 'Unauthorized' });
|
|
|
|
const { photoId, inputPath: providedPath, outDir = 'fabiocast', callbackUrl, segmentsNeeded = 3 } = req.body || {};
|
|
|
|
if (!callbackUrl) return res.status(400).json({ error: 'Missing callbackUrl' });
|
|
|
|
let inputPath = providedPath;
|
|
|
|
if (photoId && !providedPath) {
|
|
const row = await db('photos').where({ id: photoId }).first();
|
|
if (!row) return res.status(404).json({ error: 'Photo not found' });
|
|
|
|
const access = getUserAccess(caller);
|
|
if (!access) return res.status(403).json({ error: 'User not configured' });
|
|
|
|
const allowed = filterReadableSources(access.name, row.user);
|
|
if (!allowed || allowed.length === 0) return res.status(403).json({ error: 'Access denied to this photo' });
|
|
|
|
if (row.path && typeof row.path === 'string' && row.path.trim()) {
|
|
const rel = row.path.replace(/^\/+/, '');
|
|
inputPath = safeJoin(PUBLIC_ROOT, rel);
|
|
} else {
|
|
inputPath = safeJoin(PHOTOS_ROOT, row.user, 'original', row.cartella || '', row.name);
|
|
}
|
|
}
|
|
|
|
if (!inputPath) return res.status(400).json({ error: 'Missing inputPath or photoId' });
|
|
|
|
// outDir sotto HLS_BASE
|
|
const safeOut = String(outDir || 'fabiocast').replace(/[^a-zA-Z0-9_\-\/]/g, '_');
|
|
const jobParentDir = path.isAbsolute(safeOut) ? path.join(safeOut) : path.join(HLS_BASE, safeOut);
|
|
|
|
const jobId = startJob({
|
|
inputPath,
|
|
outDir: jobParentDir,
|
|
segmentsNeeded,
|
|
callbackUrl,
|
|
publicBaseUrl: PUBLIC_BASE_URL
|
|
});
|
|
|
|
return res.status(202).json({ jobId, outDir: jobParentDir, message: 'Transcode started' });
|
|
} catch (err) {
|
|
console.error('Error /hls/start:', err);
|
|
return res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /hls/status/:jobId
|
|
router.get('/status/:jobId', (req, res) => {
|
|
const job = getJob(req.params.jobId);
|
|
if (!job) return res.status(404).json({ error: 'Job not found' });
|
|
return res.json({
|
|
jobId: job.jobId,
|
|
inputPath: job.inputPath,
|
|
outDir: job.outDir,
|
|
notified: job.notified,
|
|
useRkmpp: job.useRkmpp,
|
|
startTime: job.startTime
|
|
});
|
|
});
|
|
|
|
// GET /hls/url/:jobId -> restituisce public URL completo se possibile
|
|
router.get('/url/:jobId', (req, res) => {
|
|
const job = getJob(req.params.jobId);
|
|
if (!job) return res.status(404).json({ error: 'Job not found' });
|
|
|
|
const masterPath = path.join(job.outDir, 'master.m3u8');
|
|
if (!fs.existsSync(masterPath)) return res.status(404).json({ error: 'Playlist not yet available' });
|
|
|
|
// costruisci la parte relativa rispetto a HLS_BASE (evita duplicazioni come /hls/hls_output/...)
|
|
const base = PUBLIC_BASE_URL || `${req.protocol}://${req.get('host')}`;
|
|
const rel = path.relative(HLS_BASE, masterPath).split(path.sep).join('/');
|
|
const publicUrl = `${base.replace(/\/$/, '')}/hls/${rel}`;
|
|
|
|
return res.json({ jobId: job.jobId, master_m3u8: publicUrl, status: job.notified ? 'ready' : 'processing' });
|
|
});
|
|
|
|
module.exports = router;
|