Implement video region support

This commit is contained in:
2026-07-26 14:54:50 +01:00
parent 07ec17ad91
commit eb1f33d82f
30 changed files with 1243 additions and 134 deletions
+65
View File
@@ -1,3 +1,6 @@
const fs = require('fs');
const path = require('path');
module.exports = function registerAdminContentRoutes(app, deps) {
const pool = deps.pool;
const common = deps.common;
@@ -23,6 +26,8 @@ module.exports = function registerAdminContentRoutes(app, deps) {
const { buildPagination } = require('../../lib/pagination');
const LIST_PAGE_SIZE = 10;
const IMAGE_UPLOAD_MAX_BYTES = 100 * 1024 * 1024;
const VIDEO_UPLOAD_MAX_BYTES = 1024 * 1024 * 1024;
if (!pool || !common || !pages || !upload || typeof fetchScreensBySlideId !== 'function' || typeof fetchScreensByTemplateId !== 'function' || typeof collectUploadReferencesFromSlide !== 'function' || typeof collectUploadReferencesFromTemplate !== 'function' || typeof collectUploadReferencesFromPayload !== 'function' || typeof removeUnusedUploadFiles !== 'function' || typeof syncPlaylistUploadsOnChange !== 'function' || typeof getAuditUserId !== 'function' || typeof redirectAfterSave !== 'function' || typeof notifyPlayerScreens !== 'function' || typeof broadcastDashboardState !== 'function' || typeof getSlideDeleteBlockMessage !== 'function' || typeof getTemplateDeleteBlockMessage !== 'function' || typeof getCanvasSizeDeleteBlockMessage !== 'function' || typeof hasAnyPermission !== 'function') {
throw new Error('registerAdminContentRoutes requires the content route dependencies.');
@@ -43,6 +48,62 @@ module.exports = function registerAdminContentRoutes(app, deps) {
next(error);
}
function getUploadedFileMediaType(file) {
const mimeType = String(file && file.mimetype || '').trim().toLowerCase();
const extension = path.extname(String(file && file.originalname || '')).toLowerCase();
if (mimeType.indexOf('video/') === 0 || ['.mp4', '.webm', '.ogg', '.ogv', '.mov', '.avi', '.mkv'].indexOf(extension) !== -1) {
return 'video';
}
if (mimeType.indexOf('image/') === 0 || ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.avif', '.tif', '.tiff'].indexOf(extension) !== -1) {
return 'image';
}
return null;
}
function getUploadedFileLimitBytes(file) {
return getUploadedFileMediaType(file) === 'video' ? VIDEO_UPLOAD_MAX_BYTES : IMAGE_UPLOAD_MAX_BYTES;
}
function getUploadedFileLimitLabel(file) {
return getUploadedFileMediaType(file) === 'video' ? '1 GB' : '100 MB';
}
async function removeUploadedFile(file) {
if (!file || !file.filename || !deps.uploadDir) {
return;
}
const filePath = path.join(deps.uploadDir, file.filename);
try {
await fs.promises.unlink(filePath);
} catch (_error) {
// Ignore cleanup failures.
}
}
async function validateUploadedFiles(files) {
const list = Array.isArray(files) ? files.filter(Boolean) : [];
for (let i = 0; i < list.length; i += 1) {
const file = list[i];
const mediaType = getUploadedFileMediaType(file);
if (!mediaType) {
await removeUploadedFile(file);
const error = new Error('Unsupported upload type.');
error.statusCode = 400;
error.expose = true;
throw error;
}
if (Number(file.size || 0) > getUploadedFileLimitBytes(file)) {
await removeUploadedFile(file);
const error = new Error('File must be ' + getUploadedFileLimitLabel(file) + ' or smaller.');
error.statusCode = 400;
error.expose = true;
throw error;
}
}
}
function queueSlideThumbnailRefresh(slideId, previousThumbnailPath) {
if (!backgroundTaskQueue || typeof backgroundTaskQueue.enqueueTask !== 'function') {
return Promise.resolve(null);
@@ -168,6 +229,8 @@ module.exports = function registerAdminContentRoutes(app, deps) {
return res.status(400).json({ error: 'No file was uploaded.' });
}
await validateUploadedFiles([req.file]);
res.json({
path: '/media/uploads/' + req.file.filename,
filename: req.file.filename,
@@ -195,6 +258,7 @@ module.exports = function registerAdminContentRoutes(app, deps) {
app.post('/slides', requirePermission('slides.create'), upload.any(), async function (req, res, next) {
try {
await validateUploadedFiles(req.files || []);
const payload = await common.buildSlidePayload(pool, req, null);
if (await common.fetchDuplicateName(pool, 'slides', payload.title, null, 'title')) {
return res.status(400).send('A slide with that title already exists.');
@@ -225,6 +289,7 @@ module.exports = function registerAdminContentRoutes(app, deps) {
app.post('/slides/:id', requirePermission('slides.update'), upload.any(), async function (req, res, next) {
try {
await validateUploadedFiles(req.files || []);
const slide = await common.fetchSlideById(pool, Number(req.params.id));
if (!slide) {
return res.status(404).send('Slide not found');