shiveshnavin commited on
Commit
4021252
·
1 Parent(s): 8b0803b
server-plugins/apply.js CHANGED
@@ -6,6 +6,7 @@ import { CaptionPlugin } from './generate-captions.js';
6
  import { CropPlugin } from './crop.js';
7
  import { FlattenPathsPlugin } from './flatten-paths.js';
8
  import { SpeedPlugin } from './speed.js';
 
9
 
10
  const pluginRegistry = {
11
  'split-media-render': SplitRenderPlugin,
@@ -14,6 +15,7 @@ const pluginRegistry = {
14
  'crop': CropPlugin,
15
  'flatten-paths': FlattenPathsPlugin,
16
  'speed': SpeedPlugin,
 
17
  };
18
 
19
  export async function applyPluginsPrerender(plugins, originalManuscript, originalManuscriptPath, jobId) {
 
6
  import { CropPlugin } from './crop.js';
7
  import { FlattenPathsPlugin } from './flatten-paths.js';
8
  import { SpeedPlugin } from './speed.js';
9
+ import { OverlayPlugin } from './overlay.js';
10
 
11
  const pluginRegistry = {
12
  'split-media-render': SplitRenderPlugin,
 
15
  'crop': CropPlugin,
16
  'flatten-paths': FlattenPathsPlugin,
17
  'speed': SpeedPlugin,
18
+ 'overlay': OverlayPlugin,
19
  };
20
 
21
  export async function applyPluginsPrerender(plugins, originalManuscript, originalManuscriptPath, jobId) {
server-plugins/crop.js CHANGED
@@ -8,9 +8,6 @@ export class CropPlugin extends Plugin {
8
  super(name, options);
9
  }
10
 
11
- mediaPathFlatten(mediaPath) {
12
- return path.join('public', path.basename(mediaPath));
13
- }
14
 
15
  async applyPrerender(originalManuscript, jobId) {
16
  const transcript = originalManuscript.transcript || [];
 
8
  super(name, options);
9
  }
10
 
 
 
 
11
 
12
  async applyPrerender(originalManuscript, jobId) {
13
  const transcript = originalManuscript.transcript || [];
server-plugins/overlay.js ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import { Plugin } from './plugin.js';
4
+ import { FFMpegUtils } from 'common-utils';
5
+
6
+ export class OverlayPlugin extends Plugin {
7
+ constructor(name, options) {
8
+ super(name, options);
9
+ }
10
+
11
+ async applyPrerender(originalManuscript, jobId) {
12
+ const transcript = originalManuscript.transcript || [];
13
+ const overlayOpt = this.options.overlay;
14
+
15
+ if (!overlayOpt) {
16
+ this.log('No overlay option provided, skipping overlay plugin.');
17
+ return;
18
+ }
19
+
20
+ let overlayPath = null;
21
+ // if option looks like a file path
22
+ if (/\.(mp4|webm)$/i.test(overlayOpt)) {
23
+ const flattened = this.mediaPathFlatten(overlayOpt);
24
+ if (fs.existsSync(flattened)) overlayPath = flattened;
25
+ else if (fs.existsSync(overlayOpt)) overlayPath = overlayOpt;
26
+ else {
27
+ this.log(`Overlay path does not exist: ${overlayOpt}`);
28
+ return;
29
+ }
30
+ } else {
31
+ // look inside public/assets/effects for mp4 or webm
32
+ const effectsDir = path.join('public', 'assets', 'effects');
33
+ const mp4Path = path.join(effectsDir, `${overlayOpt}.mp4`);
34
+ const webmPath = path.join(effectsDir, `${overlayOpt}.webm`);
35
+ if (fs.existsSync(mp4Path)) overlayPath = mp4Path;
36
+ else if (fs.existsSync(webmPath)) overlayPath = webmPath;
37
+ else {
38
+ this.log(`No overlay found for '${overlayOpt}' in ${effectsDir}`);
39
+ return;
40
+ }
41
+ }
42
+
43
+ const overlayExt = path.extname(overlayPath).toLowerCase();
44
+ this.log(`Using overlay: ${overlayPath}`);
45
+
46
+ for (let item of transcript) {
47
+ if (!item.mediaAbsPaths || !item.mediaAbsPaths.length) continue;
48
+ for (let mediaObj of item.mediaAbsPaths) {
49
+ try {
50
+ let mediaPath = mediaObj.path;
51
+ if (!mediaPath || !fs.existsSync(mediaPath)) {
52
+ const flattenedPath = this.mediaPathFlatten(mediaPath);
53
+ if (fs.existsSync(flattenedPath)) {
54
+ mediaObj.path = flattenedPath;
55
+ mediaPath = flattenedPath;
56
+ this.log(`Using flattened media path: ${flattenedPath}`);
57
+ } else {
58
+ this.log(`Media path does not exist: ${mediaPath}. Tried flattened: ${flattenedPath}`);
59
+ continue;
60
+ }
61
+ }
62
+
63
+ const meta = await FFMpegUtils.getMediaMetadata(mediaPath);
64
+ if (!meta || !meta.video || !meta.video.width || !meta.video.height) {
65
+ this.log(`No video stream found for ${mediaPath}`);
66
+ continue;
67
+ }
68
+
69
+ const srcW = parseInt(meta.video.width);
70
+ const srcH = parseInt(meta.video.height);
71
+
72
+ const ext = path.extname(mediaPath);
73
+ const base = path.basename(mediaPath, ext);
74
+ const outPath = path.join(path.dirname(mediaPath), `${base}.overlay${ext}`);
75
+
76
+ let cmd = null;
77
+
78
+ if (overlayExt === '.mp4') {
79
+ // mp4 uses green-screen (chroma key) -> remove green then overlay
80
+ // chromakey params: color (green), similarity, blend
81
+ cmd = `ffmpeg -i "${mediaPath}" -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 -shortest "${outPath}" -y`;
82
+ } else {
83
+ // webm assumed to have alpha (transparent video)
84
+ cmd = `ffmpeg -i "${mediaPath}" -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 -shortest "${outPath}" -y`;
85
+ }
86
+
87
+ this.log(`Applying overlay ${overlayPath} -> ${outPath} on ${mediaPath}`);
88
+ await FFMpegUtils.execute(cmd);
89
+
90
+ mediaObj._originalPath = mediaObj.path;
91
+ mediaObj.path = outPath;
92
+ } catch (err) {
93
+ this.log(`Error applying overlay to ${mediaObj?.path}: ${err}`);
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
server-plugins/plugin.js CHANGED
@@ -1,3 +1,4 @@
 
1
 
2
  export class Plugin {
3
  constructor(name, options) {
@@ -15,5 +16,9 @@ export class Plugin {
15
  log(...args) {
16
  console.log(`[Plugin: ${this.name}]`, ...args);
17
  }
 
 
 
 
18
  }
19
 
 
1
+ import path from "path";
2
 
3
  export class Plugin {
4
  constructor(name, options) {
 
16
  log(...args) {
17
  console.log(`[Plugin: ${this.name}]`, ...args);
18
  }
19
+
20
+ mediaPathFlatten(mediaPath) {
21
+ return path.join('public', path.basename(mediaPath));
22
+ }
23
  }
24