File size: 3,165 Bytes
44b5f05
 
93a2c43
3165afb
 
 
9bf9d23
44b5f05
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93a2c43
9061179
93a2c43
 
 
44b5f05
93a2c43
9061179
 
44b5f05
 
 
 
9061179
44b5f05
 
 
 
 
 
 
 
 
 
 
 
 
93a2c43
 
9061179
93a2c43
 
 
9061179
93a2c43
44b5f05
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { join } from "node:path"

import { ClapProject, ClapSegment, ClapSegmentCategory, ClapSegmentFilteringMode, filterSegments } from "@aitube/clap"
import { extractBase64 } from "@aitube/encoders"
import { deleteFile, writeBase64ToFile } from "@aitube/io"
// import { addTextToVideo, concatenateVideosWithAudio } from "@aitube/ffmpeg"
import { addTextToVideo, concatenateVideosWithAudio, scaleVideo } from "../../bug-in-bun/aitube_ffmpeg"

export async function videoSegmentToVideoFile({
  clap,
  segment,
  outputDir,
}: {
  clap: ClapProject
  segment: ClapSegment
  outputDir: string
}): Promise<string> {

  const base64Info = extractBase64(segment.assetUrl)

  // we write it to the disk *unconverted* (it might be a mp4, a webm or something else)
  let videoSegmentFilePath = await writeBase64ToFile(
    segment.assetUrl,
    join(outputDir, `tmp_asset_${segment.id}.${base64Info.extension}`)
  )
  const interfaceSegments = filterSegments(
    ClapSegmentFilteringMode.BOTH,
    segment,
    clap.segments,
    ClapSegmentCategory.INTERFACE
  )
  
  console.log(`clapWithVideoToVideoFile: got ${interfaceSegments.length} interface segments for shot ${segment.id} [${segment.startTimeInMs}:${segment.endTimeInMs}]`)

  const interfaceSegment = interfaceSegments.at(0)
  if (interfaceSegment) {
    // here we are free to use mp4, since this is an internal intermediary format
    const videoSegmentWithOverlayFilePath = join(outputDir, `tmp_asset_${segment.id}_with_interface.mp4`)
    
    await addTextToVideo({
      inputVideoPath: videoSegmentFilePath,
      outputVideoPath: videoSegmentWithOverlayFilePath,
      text: interfaceSegment.assetUrl.startsWith("data:text/")
        ? atob(extractBase64(interfaceSegment.assetUrl).data)
        : interfaceSegment.assetUrl,
      width: clap.meta.width,
      height: clap.meta.height,
    })
    // we overwrite
    await deleteFile(videoSegmentFilePath)
    videoSegmentFilePath = videoSegmentWithOverlayFilePath
  }

  const dialogueSegments = filterSegments(
    ClapSegmentFilteringMode.BOTH,
    segment,
    clap.segments,
    ClapSegmentCategory.DIALOGUE
  ).filter(s => s.assetUrl.startsWith("data:audio/"))

  const dialogueSegment = dialogueSegments.at(0)
  if (dialogueSegment) {
    extractBase64(dialogueSegment.assetUrl)
    const base64Info = extractBase64(dialogueSegment.assetUrl)
    const dialogueSegmentFilePath = await writeBase64ToFile(
      dialogueSegment.assetUrl,
      join(outputDir, `tmp_asset_${segment.id}_dialogue.${base64Info.extension}`)
    )

    const finalFilePathOfVideoWithSound = await concatenateVideosWithAudio({
      output: join(outputDir, `${segment.id}_video_with_audio.mp4`),
      audioFilePath: dialogueSegmentFilePath,
      videoFilePaths: [videoSegmentFilePath],
      // videos are silent, so they can stay at 0
      videoTracksVolume: 0.0,
      audioTrackVolume: 1.0,
    })
    // we delete the temporary dialogue file
    await deleteFile(dialogueSegmentFilePath)
    // we overwrite the video segment
    await deleteFile(videoSegmentFilePath)
    videoSegmentFilePath = finalFilePathOfVideoWithSound
  }

  return videoSegmentFilePath
}