short-video-maker
Version:
Creates short videos for TikTok, Instagram Reels, and YouTube Shorts using the Model Context Protocol (MCP) and a REST API.
176 lines (175 loc) • 6.81 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.APIRouter = void 0;
const express_1 = __importDefault(require("express"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const validator_1 = require("../validator");
const logger_1 = require("../../logger");
// todo abstract class
class APIRouter {
router;
shortCreator;
config;
constructor(config, shortCreator) {
this.config = config;
this.router = express_1.default.Router();
this.shortCreator = shortCreator;
this.router.use(express_1.default.json());
this.setupRoutes();
}
setupRoutes() {
this.router.post("/short-video", async (req, res) => {
try {
const input = (0, validator_1.validateCreateShortInput)(req.body);
logger_1.logger.info({ input }, "Creating short video");
const videoId = this.shortCreator.addToQueue(input.scenes, input.config);
res.status(201).json({
videoId,
});
}
catch (error) {
logger_1.logger.error(error, "Error validating input");
// Handle validation errors specifically
if (error instanceof Error && error.message.startsWith("{")) {
try {
const errorData = JSON.parse(error.message);
res.status(400).json({
error: "Validation failed",
message: errorData.message,
missingFields: errorData.missingFields,
});
return;
}
catch (parseError) {
logger_1.logger.error(parseError, "Error parsing validation error");
}
}
// Fallback for other errors
res.status(400).json({
error: "Invalid input",
message: error instanceof Error ? error.message : "Unknown error",
});
}
});
this.router.get("/short-video/:videoId/status", async (req, res) => {
const { videoId } = req.params;
if (!videoId) {
res.status(400).json({
error: "videoId is required",
});
return;
}
const status = this.shortCreator.status(videoId);
res.status(200).json({
status,
});
});
this.router.get("/music-tags", (req, res) => {
res.status(200).json(this.shortCreator.ListAvailableMusicTags());
});
this.router.get("/voices", (req, res) => {
res.status(200).json(this.shortCreator.ListAvailableVoices());
});
this.router.get("/short-videos", (req, res) => {
const videos = this.shortCreator.listAllVideos();
res.status(200).json({
videos,
});
});
this.router.delete("/short-video/:videoId", (req, res) => {
const { videoId } = req.params;
if (!videoId) {
res.status(400).json({
error: "videoId is required",
});
return;
}
this.shortCreator.deleteVideo(videoId);
res.status(200).json({
success: true,
});
});
this.router.get("/tmp/:tmpFile", (req, res) => {
const { tmpFile } = req.params;
if (!tmpFile) {
res.status(400).json({
error: "tmpFile is required",
});
return;
}
const tmpFilePath = path_1.default.join(this.config.tempDirPath, tmpFile);
if (!fs_extra_1.default.existsSync(tmpFilePath)) {
res.status(404).json({
error: "tmpFile not found",
});
return;
}
if (tmpFile.endsWith(".mp3")) {
res.setHeader("Content-Type", "audio/mpeg");
}
if (tmpFile.endsWith(".wav")) {
res.setHeader("Content-Type", "audio/wav");
}
const tmpFileStream = fs_extra_1.default.createReadStream(tmpFilePath);
tmpFileStream.on("error", (error) => {
logger_1.logger.error(error, "Error reading tmp file");
res.status(500).json({
error: "Error reading tmp file",
tmpFile,
});
});
tmpFileStream.pipe(res);
});
this.router.get("/music/:fileName", (req, res) => {
const { fileName } = req.params;
if (!fileName) {
res.status(400).json({
error: "fileName is required",
});
return;
}
const musicFilePath = path_1.default.join(this.config.musicDirPath, fileName);
if (!fs_extra_1.default.existsSync(musicFilePath)) {
res.status(404).json({
error: "music file not found",
});
return;
}
const musicFileStream = fs_extra_1.default.createReadStream(musicFilePath);
musicFileStream.on("error", (error) => {
logger_1.logger.error(error, "Error reading music file");
res.status(500).json({
error: "Error reading music file",
fileName,
});
});
musicFileStream.pipe(res);
});
this.router.get("/short-video/:videoId", (req, res) => {
try {
const { videoId } = req.params;
if (!videoId) {
res.status(400).json({
error: "videoId is required",
});
return;
}
const video = this.shortCreator.getVideo(videoId);
res.setHeader("Content-Type", "video/mp4");
res.setHeader("Content-Disposition", `inline; filename=${videoId}.mp4`);
res.send(video);
}
catch (error) {
logger_1.logger.error(error, "Error getting video");
res.status(404).json({
error: "Video not found",
});
}
});
}
}
exports.APIRouter = APIRouter;