File size: 4,843 Bytes
4021252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f36baef
4021252
 
 
 
 
 
 
 
f36baef
4021252
 
 
f36baef
4021252
 
f36baef
4021252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f36baef
 
4021252
 
 
 
 
 
 
 
 
 
f36baef
 
 
 
 
 
 
4021252
f36baef
 
4021252
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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}`);
        }
      }
    }
  }
}