File size: 3,481 Bytes
b0c8eb8
 
 
 
 
 
 
 
 
 
b93e09e
 
 
 
b0c8eb8
 
f8a99c0
 
b0c8eb8
 
 
 
 
 
b93e09e
b0c8eb8
b93e09e
 
 
 
 
 
 
 
 
b0c8eb8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import path from 'path';
import fs from 'fs';
import { Plugin } from './plugin.js';
import { FFMpegUtils } from 'common-utils';

export class CropPlugin extends Plugin {
  constructor(name, options) {
    super(name, options);
  }

  mediaPathFlatten(mediaPath) {
    return path.join('public', path.basename(mediaPath));
  }

  async applyPrerender(originalManuscript, jobId) {
    const transcript = originalManuscript.transcript || [];
    const targetWidth = +this.options.width || +this.options.targetWidth || 1080;
    const targetHeight = +this.options.height || +this.options.targetHeight || 1920;
    const targetAspect = targetWidth / targetHeight;

    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}. Trying at flattened path: ${flattenedPath} failed.`);
              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 srcAspect = srcW / srcH;

          let cropW = srcW, cropH = srcH, cropX = 0, cropY = 0;

          if (Math.abs(srcAspect - targetAspect) < 0.0001) {
            this.log(`Aspect ratio matches target for ${mediaPath}, skipping crop.`);
            continue;
          }

          if (srcAspect > targetAspect) {
            // source is too wide -> reduce width, keep height
            cropW = Math.round(targetAspect * srcH);
            cropX = Math.round((srcW - cropW) / 2);
            cropH = srcH;
            cropY = 0;
          } else {
            // source is too tall -> reduce height, keep width
            cropH = Math.round(srcW / targetAspect);
            cropY = Math.round((srcH - cropH) / 2);
            cropW = srcW;
            cropX = 0;
          }

          if (cropW === srcW && cropH === srcH) {
            continue;
          }

          const ext = path.extname(mediaPath);
          const base = path.basename(mediaPath, ext);
          const outPath = path.join(path.dirname(mediaPath), `${base}.cropped${ext}`);

          const cmd = `ffmpeg -i "${mediaPath}" -filter:v "crop=${cropW}:${cropH}:${cropX}:${cropY}" -c:v libx264 -preset veryfast -crf 23 -c:a copy "${outPath}" -y`;

          this.log(`Cropping ${mediaPath} -> ${outPath} crop=${cropW}x${cropH}+${cropX}+${cropY}`);
          await FFMpegUtils.execute(cmd);

          // keep original reference and replace path
          mediaObj._originalPath = mediaObj.path;
          mediaObj.path = outPath;
        } catch (err) {
          this.log(`Error cropping media ${mediaObj?.path}: ${err}`);
        }
      }
    }
  }
}