ai-tube-clap-exporter / src /core /exporters /videoSegmentToVideoFile.ts
jbilcke-hf's picture
jbilcke-hf HF staff
try to fix the text overlay bug
9bf9d23
raw
history blame
3.17 kB
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
}