Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
File size: 3,651 Bytes
2ed47da 2f67628 62e8997 2ed47da 62e8997 2ed47da bb503b1 2ed47da bb503b1 2ed47da |
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 |
import { join } from "node:path"
import { ClapProject, ClapSegment } from "@aitube/clap"
import { concatenateVideosWithAudio } from "../ffmpeg/concatenateVideosWithAudio.mts"
import { writeBase64ToFile } from "../files/writeBase64ToFile.mts"
import { getRandomDirectory } from "../files/getRandomDirectory.mts"
import { addTextToVideo } from "../ffmpeg/addTextToVideo.mts"
import { startOfSegment1IsWithinSegment2 } from "../utils/startOfSegment1IsWithinSegment2.mts"
import { deleteFile } from "../files/deleteFile.mts"
import { extractBase64 } from "../base64/extractBase64.mts"
export async function clapWithVideosToVideoFile({
clap,
videoSegments = [],
outputDir = "",
}: {
clap: ClapProject
videoSegments: ClapSegment[]
outputDir?: string
}): Promise<{
outputDir: string
videoFilePaths: string[]
}> {
outputDir = outputDir || (await getRandomDirectory())
const videoFilePaths: string[] = []
for (const segment of videoSegments) {
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 = clap.segments.filter(s =>
// nope, not all interfaces asset have the assetUrl
// although in the future.. we might want to
// s.assetUrl.startsWith("data:text/") &&
s.category === "interface" &&
startOfSegment1IsWithinSegment2(s, segment)
)
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 = clap.segments.filter(s =>
s.assetUrl.startsWith("data:audio/") &&
s.category === "dialogue" &&
startOfSegment1IsWithinSegment2(s, segment)
)
const dialogueSegment = dialogueSegments.at(0)
if (dialogueSegment) {
extractBase64(dialogueSegment.assetUrl)
const base64Info = extractBase64(segment.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
}
videoFilePaths.push(videoSegmentFilePath)
}
return {
outputDir,
videoFilePaths,
}
} |