server implementation for poster generation
This commit is contained in:
152
server/movie_posters/lib/poster.js
Normal file
152
server/movie_posters/lib/poster.js
Normal file
@@ -0,0 +1,152 @@
|
||||
import express from "express";
|
||||
import { createPoster } from "./common.js";
|
||||
import Joi from 'joi';
|
||||
|
||||
// Define default parameters for the legacy /poster endpoint
|
||||
const DEFAULT_WIDTH = 480;
|
||||
const DEFAULT_HEIGHT = 320;
|
||||
const DEFAULT_COUNT = 4;
|
||||
const DEFAULT_ORIENTATION = "horizontal";
|
||||
const DEFAULT_SHUFFLE = true;
|
||||
const DEFAULT_BACKGROUND_COLOR = "#000000"; // Consistent 6-digit hex
|
||||
const DEFAULT_FORMAT = 'png'; // Default format consistent with POST /
|
||||
const DEFAULT_LANGUAGE = 'de-DE'; // Default language consistent with POST /
|
||||
const DEFAULT_OUTPUT = 'image'; // Default output consistent with POST /
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// --- Validation Schema for POST / ---
|
||||
// Defines the expected structure and constraints for the request body.
|
||||
const posterRequestSchema = Joi.object({
|
||||
// Dimensions and Count
|
||||
width: Joi.number().integer().min(1).max(4000).default(DEFAULT_WIDTH)
|
||||
.messages({'number.base': "'width' must be a number", 'number.min': "'width' must be at least 1", 'number.max': "'width' cannot exceed 4000"}),
|
||||
height: Joi.number().integer().min(1).max(4000).default(DEFAULT_HEIGHT)
|
||||
.messages({'number.base': "'height' must be a number", 'number.min': "'height' must be at least 1", 'number.max': "'height' cannot exceed 4000"}),
|
||||
count: Joi.number().integer().min(1).max(20).default(DEFAULT_COUNT)
|
||||
.messages({'number.base': "'count' must be a number", 'number.min': "'count' must be at least 1", 'number.max': "'count' cannot exceed 20"}),
|
||||
|
||||
// Layout and Content
|
||||
orientation: Joi.string().required().valid('horizontal', 'vertical').default(DEFAULT_ORIENTATION)
|
||||
.messages({'any.required': "'orientation' is required", 'any.only': "'orientation' must be either 'horizontal' or 'vertical'"}),
|
||||
shuffle: Joi.boolean().default(DEFAULT_SHUFFLE)
|
||||
.messages({'boolean.base': "'shuffle' must be a boolean"}),
|
||||
language: Joi.string().trim().default(DEFAULT_LANGUAGE) // Added trim()
|
||||
.messages({'string.base': "'language' must be a string"}),
|
||||
|
||||
// Styling and Output
|
||||
backgroundColor: Joi.string().trim() // Added trim()
|
||||
.default(DEFAULT_BACKGROUND_COLOR) // Use 6-digit hex for consistency
|
||||
.regex(/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$/) // Allow 3 or 6 hex digits
|
||||
.message("The 'backgroundColor' must be a valid hexadecimal color code (e.g., #000 or #FF0000)"),
|
||||
format: Joi.string().valid('png', 'jpeg').default(DEFAULT_FORMAT)
|
||||
.messages({'any.only': "'format' must be either 'png' or 'jpeg'"}),
|
||||
output: Joi.string().valid('image', 'lvgl', 'lvgl_binary').default(DEFAULT_OUTPUT)
|
||||
.messages({'any.only': "'output' must be either 'image', 'lvgl' or 'lvgl_binary'"}),
|
||||
});
|
||||
|
||||
// --- Validation Middleware ---
|
||||
// Validates the request body against the posterRequestSchema.
|
||||
const validatePosterRequest = (req, res, next) => {
|
||||
const { error, value } = posterRequestSchema.validate(req.body, {
|
||||
abortEarly: false, // Report all errors, not just the first one
|
||||
stripUnknown: true // Remove unknown keys from the validated value
|
||||
});
|
||||
|
||||
if (error) {
|
||||
// Log the detailed validation error for debugging
|
||||
console.warn('Validation failed:', error.details);
|
||||
// Construct a user-friendly error message
|
||||
const errorMessages = error.details.map(detail => detail.message).join('. ');
|
||||
return res.status(400).send({ error: `Invalid request body: ${errorMessages}` });
|
||||
}
|
||||
|
||||
// Replace req.body with the validated, defaulted, and potentially stripped value.
|
||||
// This is a common pattern in Express middleware.
|
||||
req.body = value;
|
||||
next(); // Proceed to the main route handler
|
||||
};
|
||||
|
||||
// --- Main POST Endpoint ---
|
||||
// Handles requests to generate the poster image or LVGL data.
|
||||
router.post("/", validatePosterRequest, async (req, res) => {
|
||||
// Destructure the validated and defaulted body for clarity
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
count,
|
||||
orientation,
|
||||
shuffle,
|
||||
backgroundColor,
|
||||
format,
|
||||
language,
|
||||
output
|
||||
} = req.body;
|
||||
|
||||
try {
|
||||
// Delegate the core poster creation logic to the common function.
|
||||
// createPoster handles fetching data, drawing, and sending the response.
|
||||
await createPoster(
|
||||
req, // Pass req for API key access within createPoster
|
||||
res,
|
||||
width,
|
||||
height,
|
||||
count,
|
||||
orientation,
|
||||
shuffle,
|
||||
backgroundColor,
|
||||
format,
|
||||
language,
|
||||
output
|
||||
);
|
||||
} catch (error) {
|
||||
// This catch block acts as a final safety net.
|
||||
// Although createPoster has internal error handling, this catches
|
||||
// potential unexpected issues during its execution.
|
||||
console.error("Unhandled error during POST / handler:", error);
|
||||
// Ensure response is sent only once
|
||||
if (!res.headersSent) {
|
||||
res.status(500).send({ error: "An unexpected error occurred while processing your request." });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated Use the POST /poster endpoint instead for more flexibility.
|
||||
* GET /poster: Generates a movie poster collage using predefined default settings.
|
||||
* This endpoint is maintained for backward compatibility.
|
||||
*/
|
||||
router.get("/", async (req, res) => {
|
||||
// Call the common poster creation function with hardcoded legacy defaults.
|
||||
// Note: This endpoint does not support customization via query parameters.
|
||||
try {
|
||||
await createPoster(
|
||||
req, // Pass req for API key access within createPoster
|
||||
res,
|
||||
DEFAULT_WIDTH,
|
||||
DEFAULT_HEIGHT,
|
||||
DEFAULT_COUNT,
|
||||
DEFAULT_ORIENTATION,
|
||||
DEFAULT_SHUFFLE,
|
||||
DEFAULT_BACKGROUND_COLOR,
|
||||
DEFAULT_FORMAT,
|
||||
DEFAULT_LANGUAGE,
|
||||
DEFAULT_OUTPUT
|
||||
);
|
||||
// createPoster handles sending the response on success.
|
||||
} catch (error) {
|
||||
// Although createPoster has internal error handling and sends responses,
|
||||
// catch potential unexpected errors during the await or within createPoster
|
||||
// if they somehow bypass its internal handling.
|
||||
console.error("Error in legacy /cinema endpoint:", error);
|
||||
// Ensure a response is sent if headers haven't been sent yet.
|
||||
if (!res.headersSent) {
|
||||
res.status(500).send({ error: "Failed to generate cinema poster due to an internal error." });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Export the router under the 'cinema' namespace
|
||||
export const poster = {
|
||||
router, // Use shorthand property name
|
||||
};
|
Reference in New Issue
Block a user