remote-rdr / server-plugins /overlay.js
shiveshnavin's picture
Fix overly
f36baef
import path from 'path';
import fs from 'fs';
import { Plugin } from './plugin.js';
import { FFMpegUtils } from 'common-utils';
export class OverlayPlugin extends Plugin {
constructor(name, options) {
super(name, options);
}
async applyPrerender(originalManuscript, jobId) {
const transcript = originalManuscript.transcript || [];
const overlayOpt = this.options.overlay;
if (!overlayOpt) {
this.log('No overlay option provided, skipping overlay plugin.');
return;
}
let overlayPath = null;
// if option looks like a file path
if (/\.(mp4|webm|png)$/i.test(overlayOpt)) {
const flattened = this.mediaPathFlatten(overlayOpt);
if (fs.existsSync(flattened)) overlayPath = flattened;
else if (fs.existsSync(overlayOpt)) overlayPath = overlayOpt;
else {
this.log(`Overlay path does not exist: ${overlayOpt}`);
return;
}
} else {
// look inside public/assets/effects for mp4, webm, or png
const effectsDir = path.join('public', 'assets', 'effects');
const mp4Path = path.join(effectsDir, `${overlayOpt}.mp4`);
const webmPath = path.join(effectsDir, `${overlayOpt}.webm`);
const pngPath = path.join(effectsDir, `${overlayOpt}.png`);
if (fs.existsSync(mp4Path)) overlayPath = mp4Path;
else if (fs.existsSync(webmPath)) overlayPath = webmPath;
else if (fs.existsSync(pngPath)) overlayPath = pngPath;
else {
this.log(`No overlay found for '${overlayOpt}' in ${effectsDir}`);
return;
}
}
const overlayExt = path.extname(overlayPath).toLowerCase();
this.log(`Using overlay: ${overlayPath}`);
for (let item of transcript) {
if (!item.mediaAbsPaths || !item.mediaAbsPaths.length) continue;
for (let mediaObj of item.mediaAbsPaths) {
try {
let mediaPath = mediaObj.path;
if (!mediaPath || !fs.existsSync(mediaPath)) {
const flattenedPath = this.mediaPathFlatten(mediaPath);
if (fs.existsSync(flattenedPath)) {
mediaObj.path = flattenedPath;
mediaPath = flattenedPath;
this.log(`Using flattened media path: ${flattenedPath}`);
} else {
this.log(`Media path does not exist: ${mediaPath}. Tried flattened: ${flattenedPath}`);
continue;
}
}
const meta = await FFMpegUtils.getMediaMetadata(mediaPath);
if (!meta || !meta.video || !meta.video.width || !meta.video.height) {
this.log(`No video stream found for ${mediaPath}`);
continue;
}
const srcW = parseInt(meta.video.width);
const srcH = parseInt(meta.video.height);
const srcDuration = parseFloat(meta.duration || meta.format?.duration || 0);
const durationArg = srcDuration > 0 ? `-t ${srcDuration}` : '';
const ext = path.extname(mediaPath);
const base = path.basename(mediaPath, ext);
const outPath = path.join(path.dirname(mediaPath), `${base}.overlay${ext}`);
let cmd = null;
if (overlayExt === '.mp4') {
// mp4 uses green-screen (chroma key) -> remove green then overlay
// chromakey params: color (green), similarity, blend
cmd = `ffmpeg -i "${mediaPath}" -stream_loop -1 -i "${overlayPath}" -filter_complex "[1:v]scale=${srcW}:${srcH},chromakey=0x00FF00:0.3:0.1[ovr];[0:v][ovr]overlay=0:0:format=auto,format=yuv420p" -c:v libx264 -preset veryfast -crf 23 -c:a copy ${durationArg} "${outPath}" -y`;
} else if (overlayExt === '.webm') {
// webm assumed to have alpha (transparent video) and should loop to match source duration
cmd = `ffmpeg -i "${mediaPath}" -stream_loop -1 -i "${overlayPath}" -filter_complex "[1:v]scale=${srcW}:${srcH}[ovr];[0:v][ovr]overlay=0:0:format=auto,format=yuv420p" -c:v libx264 -preset veryfast -crf 23 -c:a copy ${durationArg} "${outPath}" -y`;
} else if (overlayExt === '.png') {
// png is static image overlay, loop to cover full video duration
cmd = `ffmpeg -i "${mediaPath}" -loop 1 -i "${overlayPath}" -filter_complex "[1:v]scale=${srcW}:${srcH}[ovr];[0:v][ovr]overlay=0:0:format=auto,format=yuv420p" -c:v libx264 -preset veryfast -crf 23 -c:a copy ${durationArg} "${outPath}" -y`;
} else {
this.log(`Unsupported overlay extension: ${overlayExt}`);
continue;
}
this.log(`Applying overlay ${overlayPath} -> ${outPath} on ${mediaPath}`);
await FFMpegUtils.execute(cmd);
mediaObj._originalPath = mediaObj.path;
mediaObj.path = outPath;
} catch (err) {
this.log(`Error applying overlay to ${mediaObj?.path}: ${err}`);
}
}
}
}
}