Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Commit
·
74dba9a
1
Parent(s):
d5d9687
fixed bugs
Browse files- package-lock.json +3 -3
- src/bug-in-bun/aitube_ffmpeg/concatenate/concatenateAudio.ts +1 -1
- src/bug-in-bun/aitube_ffmpeg/concatenate/concatenateVideosWithAudio.ts +6 -4
- src/bug-in-bun/aitube_ffmpeg/overlay/htmlToBase64Png.ts +13 -2
- src/core/exporters/storyboardSegmentToVideoFile.ts +24 -15
- src/index.ts +1 -1
- src/main.ts +29 -14
package-lock.json
CHANGED
@@ -2049,9 +2049,9 @@
|
|
2049 |
}
|
2050 |
},
|
2051 |
"node_modules/get-tsconfig": {
|
2052 |
-
"version": "4.7.
|
2053 |
-
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.
|
2054 |
-
"integrity": "sha512-
|
2055 |
"dev": true,
|
2056 |
"dependencies": {
|
2057 |
"resolve-pkg-maps": "^1.0.0"
|
|
|
2049 |
}
|
2050 |
},
|
2051 |
"node_modules/get-tsconfig": {
|
2052 |
+
"version": "4.7.5",
|
2053 |
+
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz",
|
2054 |
+
"integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==",
|
2055 |
"dev": true,
|
2056 |
"dependencies": {
|
2057 |
"resolve-pkg-maps": "^1.0.0"
|
src/bug-in-bun/aitube_ffmpeg/concatenate/concatenateAudio.ts
CHANGED
@@ -86,7 +86,7 @@ export async function concatenateAudio({
|
|
86 |
prevLabel
|
87 |
})
|
88 |
*/
|
89 |
-
|
90 |
let cmd: FfmpegCommand = ffmpeg() // .outputOptions('-vn');
|
91 |
|
92 |
audioFilePaths.forEach((audio, i) => {
|
|
|
86 |
prevLabel
|
87 |
})
|
88 |
*/
|
89 |
+
|
90 |
let cmd: FfmpegCommand = ffmpeg() // .outputOptions('-vn');
|
91 |
|
92 |
audioFilePaths.forEach((audio, i) => {
|
src/bug-in-bun/aitube_ffmpeg/concatenate/concatenateVideosWithAudio.ts
CHANGED
@@ -38,6 +38,7 @@ export const concatenateVideosWithAudio = async ({
|
|
38 |
asBase64 = false,
|
39 |
}: ConcatenateVideoWithAudioOptions): Promise<string> => {
|
40 |
|
|
|
41 |
try {
|
42 |
// Prepare temporary directories
|
43 |
const tempDir = await getRandomDirectory()
|
@@ -113,6 +114,7 @@ export const concatenateVideosWithAudio = async ({
|
|
113 |
'-map', '[a]',
|
114 |
'-c:v', 'copy',
|
115 |
'-c:a', 'aac',
|
|
|
116 |
]);
|
117 |
} else {
|
118 |
// console.log(`concatenateVideosWithAudio: case 2: additional audio was provided, but we don't already have audio: we overwrite`)
|
@@ -123,6 +125,7 @@ export const concatenateVideosWithAudio = async ({
|
|
123 |
'-map', '1:a',
|
124 |
'-c:v', 'copy',
|
125 |
'-c:a', 'aac',
|
|
|
126 |
]);
|
127 |
}
|
128 |
} else {
|
@@ -135,7 +138,6 @@ export const concatenateVideosWithAudio = async ({
|
|
135 |
]);
|
136 |
}
|
137 |
|
138 |
-
|
139 |
/*
|
140 |
console.log("concatenateVideosWithAudio: DEBUG:", {
|
141 |
videoTracksVolume,
|
@@ -147,9 +149,9 @@ export const concatenateVideosWithAudio = async ({
|
|
147 |
audioFilePath,
|
148 |
// additionalAudioVolume,
|
149 |
finalOutputFilePath
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
// Set up event handlers for ffmpeg processing
|
154 |
const promise = new Promise<string>((resolve, reject) => {
|
155 |
ffmpegCommand.on('start', function(commandLine) {
|
|
|
38 |
asBase64 = false,
|
39 |
}: ConcatenateVideoWithAudioOptions): Promise<string> => {
|
40 |
|
41 |
+
console.log(`concatenateVideosWithAudio()`)
|
42 |
try {
|
43 |
// Prepare temporary directories
|
44 |
const tempDir = await getRandomDirectory()
|
|
|
114 |
'-map', '[a]',
|
115 |
'-c:v', 'copy',
|
116 |
'-c:a', 'aac',
|
117 |
+
'-shortest'
|
118 |
]);
|
119 |
} else {
|
120 |
// console.log(`concatenateVideosWithAudio: case 2: additional audio was provided, but we don't already have audio: we overwrite`)
|
|
|
125 |
'-map', '1:a',
|
126 |
'-c:v', 'copy',
|
127 |
'-c:a', 'aac',
|
128 |
+
'-shortest'
|
129 |
]);
|
130 |
}
|
131 |
} else {
|
|
|
138 |
]);
|
139 |
}
|
140 |
|
|
|
141 |
/*
|
142 |
console.log("concatenateVideosWithAudio: DEBUG:", {
|
143 |
videoTracksVolume,
|
|
|
149 |
audioFilePath,
|
150 |
// additionalAudioVolume,
|
151 |
finalOutputFilePath
|
152 |
+
})
|
153 |
+
*/
|
154 |
+
|
155 |
// Set up event handlers for ffmpeg processing
|
156 |
const promise = new Promise<string>((resolve, reject) => {
|
157 |
ffmpegCommand.on('start', function(commandLine) {
|
src/bug-in-bun/aitube_ffmpeg/overlay/htmlToBase64Png.ts
CHANGED
@@ -30,11 +30,22 @@ export async function htmlToBase64Png({
|
|
30 |
const browser = await puppeteer.launch({
|
31 |
headless: true,
|
32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
// apparently we need those, see:
|
34 |
// https://unix.stackexchange.com/questions/694734/puppeteer-in-alpine-docker-with-chromium-headless-dosent-seems-to-work
|
35 |
-
|
|
|
|
|
|
|
|
|
|
|
36 |
args: [
|
37 |
-
'--no-sandbox',
|
38 |
'--headless',
|
39 |
'--disable-gpu',
|
40 |
'--disable-dev-shm-usage'
|
|
|
30 |
const browser = await puppeteer.launch({
|
31 |
headless: true,
|
32 |
|
33 |
+
// for macOS do this (yeah.. with the "no quarantine"..)
|
34 |
+
// brew install chromium --no-quarantine
|
35 |
+
// and:
|
36 |
+
// which chromium
|
37 |
+
// to detect where the executable path is
|
38 |
+
|
39 |
// apparently we need those, see:
|
40 |
// https://unix.stackexchange.com/questions/694734/puppeteer-in-alpine-docker-with-chromium-headless-dosent-seems-to-work
|
41 |
+
// https://stackoverflow.com/questions/59979188/error-failed-to-launch-the-browser-process-puppeteer
|
42 |
+
executablePath:
|
43 |
+
os.type() === "Darwin"
|
44 |
+
? '/opt/homebrew/bin/chromium'
|
45 |
+
: '/usr/bin/chromium-browser',
|
46 |
+
|
47 |
args: [
|
48 |
+
'--no-sandbox', // for alpine
|
49 |
'--headless',
|
50 |
'--disable-gpu',
|
51 |
'--disable-dev-shm-usage'
|
src/core/exporters/storyboardSegmentToVideoFile.ts
CHANGED
@@ -41,21 +41,30 @@ export async function storyboardSegmentToVideoFile({
|
|
41 |
const interfaceSegment = interfaceSegments.at(0)
|
42 |
if (interfaceSegment) {
|
43 |
// here we are free to use mp4, since this is an internal intermediary format
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
:
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
}
|
60 |
|
61 |
const dialogueSegments = filterSegments(
|
|
|
41 |
const interfaceSegment = interfaceSegments.at(0)
|
42 |
if (interfaceSegment) {
|
43 |
// here we are free to use mp4, since this is an internal intermediary format
|
44 |
+
let videoSegmentWithOverlayFilePath = join(outputDir, `tmp_asset_${segment.id}_with_interface.mp4`)
|
45 |
+
|
46 |
+
let success = false
|
47 |
+
// this can fail due to puppeteer
|
48 |
+
try {
|
49 |
+
await addTextToVideo({
|
50 |
+
inputVideoPath: storyboardSegmentVideoFilePath,
|
51 |
+
outputVideoPath: videoSegmentWithOverlayFilePath,
|
52 |
+
text: interfaceSegment.assetUrl.startsWith("data:text/")
|
53 |
+
? atob(extractBase64(interfaceSegment.assetUrl).data)
|
54 |
+
: interfaceSegment.assetUrl,
|
55 |
+
width: clap.meta.width,
|
56 |
+
height: clap.meta.height,
|
57 |
+
})
|
58 |
+
success = true
|
59 |
+
} catch (err) {
|
60 |
+
console.error(`failed to add text to the video: ${err}`)
|
61 |
+
success = false
|
62 |
+
}
|
63 |
+
if (success) {
|
64 |
+
// we overwrite
|
65 |
+
await deleteFile(storyboardSegmentVideoFilePath)
|
66 |
+
storyboardSegmentVideoFilePath = videoSegmentWithOverlayFilePath
|
67 |
+
}
|
68 |
}
|
69 |
|
70 |
const dialogueSegments = filterSegments(
|
src/index.ts
CHANGED
@@ -82,7 +82,7 @@ app.post("/", async (req, res) => {
|
|
82 |
res.download(outputFilePath, async () => {
|
83 |
// clean-up after ourselves (we clear the whole tmp directory)
|
84 |
await deleteFile(tmpWorkDir)
|
85 |
-
console.log("cleared the temporary folder")
|
86 |
})
|
87 |
return
|
88 |
} catch (err) {
|
|
|
82 |
res.download(outputFilePath, async () => {
|
83 |
// clean-up after ourselves (we clear the whole tmp directory)
|
84 |
await deleteFile(tmpWorkDir)
|
85 |
+
// console.log("cleared the temporary folder")
|
86 |
})
|
87 |
return
|
88 |
} catch (err) {
|
src/main.ts
CHANGED
@@ -41,6 +41,7 @@ export async function clapToTmpVideoFilePath({
|
|
41 |
tmpWorkDir: string
|
42 |
outputFilePath: string
|
43 |
}> {
|
|
|
44 |
|
45 |
// in case we have an issue with the format
|
46 |
if (format !== "mp4" && format !== "webm") {
|
@@ -104,7 +105,7 @@ export async function clapToTmpVideoFilePath({
|
|
104 |
|
105 |
console.log(`clapToTmpVideoFilePath: concatenatedVideosNoMusic`, concatenatedVideosNoMusic)
|
106 |
|
107 |
-
const
|
108 |
|
109 |
const musicSegments = clap.segments.filter(s =>
|
110 |
s.category === ClapSegmentCategory.MUSIC &&
|
@@ -137,56 +138,67 @@ export async function clapToTmpVideoFilePath({
|
|
137 |
continue
|
138 |
}
|
139 |
|
140 |
-
const
|
141 |
segment.assetUrl,
|
142 |
join(outputDir, `tmp_asset_${segment.id}.${analysis.extension}`)
|
143 |
)
|
144 |
|
145 |
-
|
|
|
146 |
|
147 |
availableMusicDurationInMs += durationInMs
|
148 |
}
|
149 |
|
150 |
let concatenatedAudio: ConcatenateAudioOutput | undefined = undefined
|
151 |
|
152 |
-
if (
|
153 |
-
console.log(`clapToTmpVideoFilePath: calling concatenateAudio over ${
|
154 |
|
155 |
if (!detectedMusicTrackFormat) {
|
156 |
throw new Error(`uh that's weird, we couldn't detect the audio type`)
|
157 |
}
|
158 |
|
159 |
-
const
|
160 |
|
161 |
// if we don't have enough music audio content
|
162 |
while (availableMusicDurationInMs < totalDurationInMs) {
|
163 |
-
let
|
164 |
|
165 |
// abort if there are no available tracks (for some reason)
|
166 |
-
if (!
|
167 |
|
168 |
-
|
169 |
|
170 |
// we artificially duplicate it (note: this will be cross-faded)
|
171 |
-
const { durationInMs } = await getMediaInfo(
|
172 |
|
173 |
// let's abord if we have bad data
|
174 |
if (!durationInMs || durationInMs < 1000) { break }
|
175 |
|
176 |
-
|
177 |
|
178 |
availableMusicDurationInMs += durationInMs
|
179 |
}
|
180 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
181 |
concatenatedAudio = await concatenateAudio({
|
182 |
output: join(outputDir, `tmp_asset_concatenated_audio.${detectedMusicTrackFormat}`),
|
183 |
-
|
184 |
crossfadeDurationInSec: 2, // 2 seconds
|
185 |
outputFormat: detectedMusicTrackFormat
|
186 |
})
|
187 |
-
console.log(`clapToTmpVideoFilePath: concatenatedAudio = ${concatenatedAudio}`)
|
188 |
}
|
189 |
|
|
|
190 |
console.log(`calling concatenateVideosWithAudio: `, {
|
191 |
output: join(outputDir, `final_video.${format}`),
|
192 |
format,
|
@@ -196,6 +208,7 @@ export async function clapToTmpVideoFilePath({
|
|
196 |
videoTracksVolume: concatenatedAudio ? 0.85 : 1.0,
|
197 |
audioTrackVolume: concatenatedAudio ? 0.15 : 0.0, // let's keep the music volume low
|
198 |
})
|
|
|
199 |
|
200 |
const finalFilePathOfVideoWithMusic = await concatenateVideosWithAudio({
|
201 |
output: join(outputDir, `final_video.${format}`),
|
@@ -211,14 +224,16 @@ export async function clapToTmpVideoFilePath({
|
|
211 |
|
212 |
if (clearTmpFilesAtEnd) {
|
213 |
// we delete all the temporary assets
|
214 |
-
console.log(`clapToTmpVideoFilePath: calling deleteFilesWithName(${outputDir}, 'tmp_asset_')`)
|
215 |
await deleteFilesWithName(outputDir, `tmp_asset_`)
|
216 |
}
|
217 |
|
|
|
218 |
console.log(`clapToTmpVideoFilePath: returning ${JSON.stringify( {
|
219 |
tmpWorkDir: outputDir,
|
220 |
outputFilePath: finalFilePathOfVideoWithMusic
|
221 |
}, null, 2)}`)
|
|
|
222 |
|
223 |
return {
|
224 |
tmpWorkDir: outputDir,
|
|
|
41 |
tmpWorkDir: string
|
42 |
outputFilePath: string
|
43 |
}> {
|
44 |
+
console.log(`clapToTmpVideoFilePath()`)
|
45 |
|
46 |
// in case we have an issue with the format
|
47 |
if (format !== "mp4" && format !== "webm") {
|
|
|
105 |
|
106 |
console.log(`clapToTmpVideoFilePath: concatenatedVideosNoMusic`, concatenatedVideosNoMusic)
|
107 |
|
108 |
+
const musicFilePaths: string[] = []
|
109 |
|
110 |
const musicSegments = clap.segments.filter(s =>
|
111 |
s.category === ClapSegmentCategory.MUSIC &&
|
|
|
138 |
continue
|
139 |
}
|
140 |
|
141 |
+
const newMusicFilePath = await writeBase64ToFile(
|
142 |
segment.assetUrl,
|
143 |
join(outputDir, `tmp_asset_${segment.id}.${analysis.extension}`)
|
144 |
)
|
145 |
|
146 |
+
// console.log("wrote music to " + newMusicFilePath)
|
147 |
+
musicFilePaths.push(newMusicFilePath)
|
148 |
|
149 |
availableMusicDurationInMs += durationInMs
|
150 |
}
|
151 |
|
152 |
let concatenatedAudio: ConcatenateAudioOutput | undefined = undefined
|
153 |
|
154 |
+
if (musicFilePaths.length > 0) {
|
155 |
+
// console.log(`clapToTmpVideoFilePath: calling concatenateAudio over ${musicFilePaths.length} audio tracks`)
|
156 |
|
157 |
if (!detectedMusicTrackFormat) {
|
158 |
throw new Error(`uh that's weird, we couldn't detect the audio type`)
|
159 |
}
|
160 |
|
161 |
+
const availableMusicFilePaths = [...musicFilePaths]
|
162 |
|
163 |
// if we don't have enough music audio content
|
164 |
while (availableMusicDurationInMs < totalDurationInMs) {
|
165 |
+
let musicFilePathToRepeat = availableMusicFilePaths.shift()
|
166 |
|
167 |
// abort if there are no available tracks (for some reason)
|
168 |
+
if (!musicFilePathToRepeat) { break }
|
169 |
|
170 |
+
availableMusicFilePaths.push(musicFilePathToRepeat)
|
171 |
|
172 |
// we artificially duplicate it (note: this will be cross-faded)
|
173 |
+
const { durationInMs } = await getMediaInfo(musicFilePathToRepeat)
|
174 |
|
175 |
// let's abord if we have bad data
|
176 |
if (!durationInMs || durationInMs < 1000) { break }
|
177 |
|
178 |
+
musicFilePaths.push(musicFilePathToRepeat)
|
179 |
|
180 |
availableMusicDurationInMs += durationInMs
|
181 |
}
|
182 |
|
183 |
+
/*
|
184 |
+
console.log("DEBUG:", {
|
185 |
+
musicFilePaths,
|
186 |
+
availableMusicFilePaths,
|
187 |
+
availableMusicDurationInMs
|
188 |
+
})
|
189 |
+
*/
|
190 |
+
|
191 |
+
|
192 |
concatenatedAudio = await concatenateAudio({
|
193 |
output: join(outputDir, `tmp_asset_concatenated_audio.${detectedMusicTrackFormat}`),
|
194 |
+
audioFilePaths: musicFilePaths,
|
195 |
crossfadeDurationInSec: 2, // 2 seconds
|
196 |
outputFormat: detectedMusicTrackFormat
|
197 |
})
|
198 |
+
// console.log(`clapToTmpVideoFilePath: concatenatedAudio = ${concatenatedAudio}`)
|
199 |
}
|
200 |
|
201 |
+
/*
|
202 |
console.log(`calling concatenateVideosWithAudio: `, {
|
203 |
output: join(outputDir, `final_video.${format}`),
|
204 |
format,
|
|
|
208 |
videoTracksVolume: concatenatedAudio ? 0.85 : 1.0,
|
209 |
audioTrackVolume: concatenatedAudio ? 0.15 : 0.0, // let's keep the music volume low
|
210 |
})
|
211 |
+
*/
|
212 |
|
213 |
const finalFilePathOfVideoWithMusic = await concatenateVideosWithAudio({
|
214 |
output: join(outputDir, `final_video.${format}`),
|
|
|
224 |
|
225 |
if (clearTmpFilesAtEnd) {
|
226 |
// we delete all the temporary assets
|
227 |
+
// console.log(`clapToTmpVideoFilePath: calling deleteFilesWithName(${outputDir}, 'tmp_asset_')`)
|
228 |
await deleteFilesWithName(outputDir, `tmp_asset_`)
|
229 |
}
|
230 |
|
231 |
+
/*
|
232 |
console.log(`clapToTmpVideoFilePath: returning ${JSON.stringify( {
|
233 |
tmpWorkDir: outputDir,
|
234 |
outputFilePath: finalFilePathOfVideoWithMusic
|
235 |
}, null, 2)}`)
|
236 |
+
*/
|
237 |
|
238 |
return {
|
239 |
tmpWorkDir: outputDir,
|