File size: 2,418 Bytes
051e9e4
 
 
6966d41
051e9e4
 
6966d41
051e9e4
6966d41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
051e9e4
6966d41
 
 
 
 
 
051e9e4
6966d41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { promises as fs, existsSync } from 'node:fs'
import path from 'node:path'
import os from 'node:os'

import { UUID } from '@aitube/clap'
import ffmpeg from 'fluent-ffmpeg'

const validFormats = ['jpeg', 'png', 'webp']

/**
 * Extract the first frame from a video
 * 
 * @param param0
 * @returns 
 */
export async function extractFirstFrame({
  inputVideo,
  outputFormat = 'jpeg'
}: {
  inputVideo?: string
  outputFormat?: "jpeg" | "png" | "webp"

}) {
  if (!inputVideo) {
    throw new Error(`inputVideo must be a file path or a base64 data-uri`);
  }

  // Validate output format
  if (!validFormats.includes(outputFormat)) {
    throw new Error(`Invalid output format. Choose one of: ${validFormats.join(', ')}`);
  }

  // Handle base64 input
  let videoFilePath = inputVideo;
  if (inputVideo.startsWith('data:')) {
    const matches = inputVideo.match(/^data:video\/(\w+);base64,(.*)$/);
    if (!matches) {
      throw new Error('Invalid base64 input provided.');
    }
    const extension = matches[1];
    const base64Content = matches[2];
    
    videoFilePath = path.join(os.tmpdir(), `${UUID()}_inputVideo.${extension}`);
    await fs.writeFile(videoFilePath, base64Content, 'base64');
  } else if (!existsSync(videoFilePath)) {
    throw new Error('Video file does not exist.');
  }

  // Create a temporary output file
  const outputImagePath = path.join(os.tmpdir(), `${UUID()}.${outputFormat}`);

  return new Promise((resolve, reject) => {
    ffmpeg()
      .input(videoFilePath)
      .outputOptions([
        '-vframes', '1',  // Extract only one frame
        '-f', 'image2',   // Output format for the frame as image
        '-an'             // Disable audio
      ])
      .output(outputImagePath)
      .on('error', (err) => {
        reject(new Error(`FFmpeg error: ${err.message}`));
      })
      .on('end', async () => {
        try {
          const imageBuffer = await fs.readFile(outputImagePath);
          const imageBase64 = `data:image/${outputFormat};base64,${imageBuffer.toString('base64')}`;
          resolve(imageBase64);
        } catch (error) {
          reject(new Error(`Error reading the image file: ${error}`));
        } finally {
          // Clean up temporary files
          if (inputVideo.startsWith('data:')) {
            await fs.unlink(videoFilePath);
          }
          await fs.unlink(outputImagePath);
        }
      })
      .run();
  });
}