File size: 3,553 Bytes
2cae2a9
 
2f67628
 
2cae2a9
 
 
 
 
46fcec6
2ed47da
 
2cae2a9
 
2ed47da
2cae2a9
 
 
 
46fcec6
 
 
 
 
 
2cae2a9
46fcec6
 
 
 
 
 
 
 
 
 
 
2cae2a9
 
46fcec6
2ed47da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2cae2a9
 
46fcec6
2cae2a9
46fcec6
2cae2a9
 
 
 
46fcec6
 
 
 
2cae2a9
 
 
 
46fcec6
2cae2a9
 
 
 
 
46fcec6
2cae2a9
 
 
 
46fcec6
 
2cae2a9
46fcec6
2cae2a9
46fcec6
 
2cae2a9
 
46fcec6
 
 
 
2cae2a9
46fcec6
 
 
 
2cae2a9
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
110
import { join } from "node:path"

import { ClapProject } from "@aitube/clap";

import { concatenateAudio } from "./core/ffmpeg/concatenateAudio.mts";
import { concatenateVideosWithAudio } from "./core/ffmpeg/concatenateVideosWithAudio.mts";
import { writeBase64ToFile } from "./core/files/writeBase64ToFile.mts";
import { concatenateVideos } from "./core/ffmpeg/concatenateVideos.mts"
import { deleteFilesWithName } from "./core/files/deleteFileWithName.mts"
import { getRandomDirectory } from "./core/files/getRandomDirectory.mts";
import { clapWithVideosToVideoFile } from "./core/exporters/clapWithVideosToVideoFile.mts";
import { clapWithStoryboardsToVideoFile } from "./core/exporters/clapWithStoryboardsToVideoFile.mts";

/**
 * Generate a .mp4 video inside a directory (if none is provided, it will be created in /tmp)
 * 
 * @param clap 
 * @returns file path to the final .mp4
 */
export async function clapToTmpVideoFilePath({
  clap,
  outputDir = "",
  clearTmpFilesAtEnd = false
}: {
  clap: ClapProject

  outputDir?: string

  // if you leave this to false, you will have to clear files yourself
  // (eg. after sending the final video file over)
  clearTmpFilesAtEnd?: boolean
}): Promise<{
  tmpWorkDir: string
  outputFilePath: string
}> {

  outputDir = outputDir || (await getRandomDirectory())

  const videoFilePaths: string[] = []

  const videoSegments = clap.segments.filter(s => s.category === "video" && s.assetUrl.startsWith("data:video/"))
  const storyboardSegments = clap.segments.filter(s => s.category === "storyboard" && s.assetUrl.startsWith("data:image/"))

  const canUseVideos = videoSegments.length > 0
  const canUseStoryboards = !canUseVideos && storyboardSegments.length > 0

  // two possibilities:
  // we can either generate from the video files, or from the storyboards
  // the storyboard video will be a bit more boring, but at least it should process faster
  if (canUseVideos) {
    await clapWithVideosToVideoFile({
      clap,
      videoSegments,
      outputDir,
    })
  } else if (canUseStoryboards) {
    await clapWithStoryboardsToVideoFile({
      clap,
      storyboardSegments,
      outputDir,
    })
  } else {
    throw new Error(`the provided Clap doesn't contain any video or storyboard`)
  }

  const concatenatedVideosNoMusic = await concatenateVideos({
    videoFilePaths,
    output: join(outputDir, `tmp_asset_concatenated_videos.mp4`)
  })

  const audioTracks: string[] = []

  const musicSegments = clap.segments.filter(s =>
      s.category === "music" &&
      s.assetUrl.startsWith("data:audio/")
  )
  for (const segment of musicSegments) {
    audioTracks.push(
      await writeBase64ToFile(
        segment.assetUrl,
        join(outputDir, `tmp_asset_${segment.id}.wav`)
      )
    )
  }

  const concatenatedAudio = await concatenateAudio({
    output: join(outputDir, `tmp_asset_concatenated_audio.wav`),
    audioTracks,
    crossfadeDurationInSec: 2 // 2 seconds
  })

  const finalFilePathOfVideoWithMusic = await concatenateVideosWithAudio({
    output: join(outputDir, `final_video.mp4`),
    audioFilePath: concatenatedAudio.filepath,
    videoFilePaths: [concatenatedVideosNoMusic.filepath],
    // videos are silent, so they can stay at 0
    videoTracksVolume: 0.85,
    audioTrackVolume: 0.15, // let's keep the music volume low
  })

  if (clearTmpFilesAtEnd) {
    // we delete all the temporary assets
    await deleteFilesWithName(outputDir, `tmp_asset_`)
  }

  return {
    tmpWorkDir: outputDir,
    outputFilePath: finalFilePathOfVideoWithMusic
  }
}