Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Commit
·
46fcec6
1
Parent(s):
1fc1c4d
another step for the Stories Factory (mp4 generation)
Browse files- src/core/base64/extractBase64.mts +5 -0
- src/core/converters/htmlToBase64Png.mts +1 -1
- src/core/ffmpeg/addTextToVideo.mts +28 -14
- src/core/files/deleteFile.mts +14 -0
- src/core/files/deleteFileWithName.mts +2 -8
- src/core/files/getRandomDirectory.mts +8 -0
- src/core/files/removeTmpFiles.mts +9 -13
- src/core/utils/startOfSegment1IsWithinSegment2.mts +6 -0
- src/index.mts +19 -8
- src/main.mts +108 -23
src/core/base64/extractBase64.mts
CHANGED
@@ -2,8 +2,13 @@
|
|
2 |
* break a base64 string into sub-components
|
3 |
*/
|
4 |
export function extractBase64(base64: string = ""): {
|
|
|
|
|
5 |
mimetype: string;
|
|
|
|
|
6 |
extension: string;
|
|
|
7 |
data: string;
|
8 |
buffer: Buffer;
|
9 |
blob: Blob;
|
|
|
2 |
* break a base64 string into sub-components
|
3 |
*/
|
4 |
export function extractBase64(base64: string = ""): {
|
5 |
+
|
6 |
+
// file format eg. video/mp4 text/html audio/wave
|
7 |
mimetype: string;
|
8 |
+
|
9 |
+
// file extension eg. .mp4 .html .wav
|
10 |
extension: string;
|
11 |
+
|
12 |
data: string;
|
13 |
buffer: Buffer;
|
14 |
blob: Blob;
|
src/core/converters/htmlToBase64Png.mts
CHANGED
@@ -28,7 +28,7 @@ export async function htmlToBase64Png({
|
|
28 |
}
|
29 |
|
30 |
const browser = await puppeteer.launch({
|
31 |
-
headless:
|
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
|
|
|
28 |
}
|
29 |
|
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
|
src/core/ffmpeg/addTextToVideo.mts
CHANGED
@@ -1,23 +1,37 @@
|
|
1 |
-
import { createTextOverlayImage } from "./createTextOverlayImage.mts"
|
2 |
-
import { addImageToVideo } from "./addImageToVideo.mts"
|
|
|
3 |
|
4 |
-
export async function addTextToVideo(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
|
6 |
-
const
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
width: 1024 ,
|
11 |
-
height: 576,
|
12 |
})
|
13 |
-
console.log("filePath:", filePath)
|
14 |
|
15 |
-
|
|
|
16 |
const pathToVideo = await addImageToVideo({
|
17 |
inputVideoPath,
|
18 |
-
inputImagePath:
|
|
|
19 |
})
|
20 |
|
21 |
-
|
22 |
-
|
|
|
|
|
23 |
}
|
|
|
1 |
+
import { createTextOverlayImage } from "./createTextOverlayImage.mts"
|
2 |
+
import { addImageToVideo } from "./addImageToVideo.mts"
|
3 |
+
import { deleteFile } from "../files/deleteFile.mts"
|
4 |
|
5 |
+
export async function addTextToVideo({
|
6 |
+
inputVideoPath,
|
7 |
+
outputVideoPath,
|
8 |
+
text,
|
9 |
+
width,
|
10 |
+
height,
|
11 |
+
}: {
|
12 |
+
inputVideoPath: string
|
13 |
+
outputVideoPath: string
|
14 |
+
text: string
|
15 |
+
width: number
|
16 |
+
height: number
|
17 |
+
}): Promise<string> {
|
18 |
|
19 |
+
const { filePath: temporaryImageOverlayFilePath } = await createTextOverlayImage({
|
20 |
+
text,
|
21 |
+
width,
|
22 |
+
height,
|
|
|
|
|
23 |
})
|
|
|
24 |
|
25 |
+
console.log("addTextToVideo: temporaryImageOverlayFilePath:", temporaryImageOverlayFilePath)
|
26 |
+
|
27 |
const pathToVideo = await addImageToVideo({
|
28 |
inputVideoPath,
|
29 |
+
inputImagePath: temporaryImageOverlayFilePath,
|
30 |
+
outputVideoPath,
|
31 |
})
|
32 |
|
33 |
+
await deleteFile(temporaryImageOverlayFilePath)
|
34 |
+
|
35 |
+
console.log("addTextToVideo: outputVideoPath:", outputVideoPath)
|
36 |
+
return outputVideoPath
|
37 |
}
|
src/core/files/deleteFile.mts
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { unlink, rm } from "node:fs/promises"
|
2 |
+
|
3 |
+
export async function deleteFile(filePath: string, debug?: boolean): Promise<boolean> {
|
4 |
+
try {
|
5 |
+
await rm(filePath, { recursive: true, force: true })
|
6 |
+
// await unlink(filePath)
|
7 |
+
return true
|
8 |
+
} catch (err) {
|
9 |
+
if (debug) {
|
10 |
+
console.error(`failed to unlink file at ${filePath}: ${err}`)
|
11 |
+
}
|
12 |
+
}
|
13 |
+
return false
|
14 |
+
}
|
src/core/files/deleteFileWithName.mts
CHANGED
@@ -1,17 +1,11 @@
|
|
1 |
import { promises as fs } from "node:fs"
|
2 |
import path from "node:path"
|
|
|
3 |
|
4 |
export const deleteFilesWithName = async (dir: string, name: string, debug?: boolean) => {
|
5 |
for (const file of await fs.readdir(dir)) {
|
6 |
if (file.includes(name)) {
|
7 |
-
|
8 |
-
try {
|
9 |
-
await fs.unlink(filePath)
|
10 |
-
} catch (err) {
|
11 |
-
if (debug) {
|
12 |
-
console.error(`failed to unlink file in ${filePath}: ${err}`)
|
13 |
-
}
|
14 |
-
}
|
15 |
}
|
16 |
}
|
17 |
}
|
|
|
1 |
import { promises as fs } from "node:fs"
|
2 |
import path from "node:path"
|
3 |
+
import { deleteFile } from "./deleteFile.mts"
|
4 |
|
5 |
export const deleteFilesWithName = async (dir: string, name: string, debug?: boolean) => {
|
6 |
for (const file of await fs.readdir(dir)) {
|
7 |
if (file.includes(name)) {
|
8 |
+
await deleteFile(path.join(dir, file))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
}
|
10 |
}
|
11 |
}
|
src/core/files/getRandomDirectory.mts
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { tmpdir } from "node:os"
|
2 |
+
import { join } from "node:path"
|
3 |
+
import { mkdtemp } from "node:fs/promises"
|
4 |
+
import { v4 as uuidv4 } from "uuid"
|
5 |
+
|
6 |
+
export async function getRandomDirectory(): Promise<string> {
|
7 |
+
return mkdtemp(join(tmpdir(), uuidv4()))
|
8 |
+
}
|
src/core/files/removeTmpFiles.mts
CHANGED
@@ -1,22 +1,18 @@
|
|
1 |
import { existsSync, promises as fs } from "node:fs"
|
2 |
|
3 |
-
import { keepTemporaryFiles } from "../config.mts"
|
4 |
-
|
5 |
// note: this function will never fail
|
6 |
export async function removeTemporaryFiles(filesPaths: string[]) {
|
7 |
try {
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
await fs.unlink(filePath)
|
14 |
-
}
|
15 |
-
} catch (err) {
|
16 |
-
//
|
17 |
}
|
18 |
-
})
|
19 |
-
|
|
|
|
|
20 |
} catch (err) {
|
21 |
// no big deal, except a bit of tmp file leak
|
22 |
// although.. if delete failed, it could also indicate
|
|
|
1 |
import { existsSync, promises as fs } from "node:fs"
|
2 |
|
|
|
|
|
3 |
// note: this function will never fail
|
4 |
export async function removeTemporaryFiles(filesPaths: string[]) {
|
5 |
try {
|
6 |
+
// Cleanup temporary files - you could choose to do this or leave it to the user
|
7 |
+
await Promise.all(filesPaths.map(async (filePath) => {
|
8 |
+
try {
|
9 |
+
if (existsSync(filePath)) {
|
10 |
+
await fs.rm(filePath)
|
|
|
|
|
|
|
|
|
11 |
}
|
12 |
+
} catch (err) {
|
13 |
+
//
|
14 |
+
}
|
15 |
+
}))
|
16 |
} catch (err) {
|
17 |
// no big deal, except a bit of tmp file leak
|
18 |
// although.. if delete failed, it could also indicate
|
src/core/utils/startOfSegment1IsWithinSegment2.mts
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ClapSegment } from "../clap/types.mts";
|
2 |
+
|
3 |
+
export function startOfSegment1IsWithinSegment2(s1: ClapSegment, s2: ClapSegment) {
|
4 |
+
const startOfSegment1 = s1.startTimeInMs
|
5 |
+
return s2.startTimeInMs <= startOfSegment1 && startOfSegment1 <= s2.endTimeInMs
|
6 |
+
}
|
src/index.mts
CHANGED
@@ -4,6 +4,8 @@ import { Blob } from "buffer"
|
|
4 |
|
5 |
import { parseClap } from "./core/clap/parseClap.mts"
|
6 |
import { ClapProject } from "./core/clap/types.mts"
|
|
|
|
|
7 |
|
8 |
const app = express()
|
9 |
const port = 7860
|
@@ -49,10 +51,23 @@ app.post("/", async (req, res) => {
|
|
49 |
req.on("end", async () => {
|
50 |
let clapProject: ClapProject
|
51 |
try {
|
52 |
-
let fileData = Buffer.concat(data)
|
53 |
-
|
54 |
-
|
55 |
-
console.log("got a clap project
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
} catch (err) {
|
57 |
console.error(`failed to parse the request: ${err}`)
|
58 |
res.status(500)
|
@@ -60,10 +75,6 @@ app.post("/", async (req, res) => {
|
|
60 |
res.end()
|
61 |
return
|
62 |
}
|
63 |
-
// TODO read the mp4 file and convert it to
|
64 |
-
res.status(200)
|
65 |
-
res.write("TODO")
|
66 |
-
res.end()
|
67 |
});
|
68 |
})
|
69 |
|
|
|
4 |
|
5 |
import { parseClap } from "./core/clap/parseClap.mts"
|
6 |
import { ClapProject } from "./core/clap/types.mts"
|
7 |
+
import { clapToTmpVideoFilePath } from "./main.mts"
|
8 |
+
import { deleteFile } from "./core/files/deleteFile.mts"
|
9 |
|
10 |
const app = express()
|
11 |
const port = 7860
|
|
|
51 |
req.on("end", async () => {
|
52 |
let clapProject: ClapProject
|
53 |
try {
|
54 |
+
let fileData = Buffer.concat(data)
|
55 |
+
|
56 |
+
const clap = await parseClap(new Blob([fileData]));
|
57 |
+
console.log("got a clap project:", clapProject)
|
58 |
+
|
59 |
+
const {
|
60 |
+
tmpWorkDir,
|
61 |
+
outputFilePath,
|
62 |
+
} = await clapToTmpVideoFilePath({ clap })
|
63 |
+
console.log("got an output file at:", outputFilePath)
|
64 |
+
|
65 |
+
res.download(outputFilePath, async () => {
|
66 |
+
// clean-up after ourselves (we clear the whole tmp directory)
|
67 |
+
await deleteFile(tmpWorkDir)
|
68 |
+
console.log("cleared the temporary folder")
|
69 |
+
})
|
70 |
+
return
|
71 |
} catch (err) {
|
72 |
console.error(`failed to parse the request: ${err}`)
|
73 |
res.status(500)
|
|
|
75 |
res.end()
|
76 |
return
|
77 |
}
|
|
|
|
|
|
|
|
|
78 |
});
|
79 |
})
|
80 |
|
src/main.mts
CHANGED
@@ -1,7 +1,4 @@
|
|
1 |
-
import { tmpdir } from "node:os"
|
2 |
import { join } from "node:path"
|
3 |
-
import { mkdtemp } from "node:fs/promises"
|
4 |
-
import { v4 as uuidv4 } from "uuid"
|
5 |
|
6 |
import { ClapProject } from "./core/clap/types.mts";
|
7 |
import { concatenateAudio } from "./core/ffmpeg/concatenateAudio.mts";
|
@@ -9,6 +6,11 @@ import { concatenateVideosWithAudio } from "./core/ffmpeg/concatenateVideosWithA
|
|
9 |
import { writeBase64ToFile } from "./core/files/writeBase64ToFile.mts";
|
10 |
import { concatenateVideos } from "./core/ffmpeg/concatenateVideos.mts"
|
11 |
import { deleteFilesWithName } from "./core/files/deleteFileWithName.mts"
|
|
|
|
|
|
|
|
|
|
|
12 |
|
13 |
/**
|
14 |
* Generate a .mp4 video inside a direcory (if none is provided, it will be created in /tmp)
|
@@ -16,56 +18,139 @@ import { deleteFilesWithName } from "./core/files/deleteFileWithName.mts"
|
|
16 |
* @param clap
|
17 |
* @returns file path to the final .mp4
|
18 |
*/
|
19 |
-
export async function clapToTmpVideoFilePath(
|
|
|
|
|
|
|
|
|
|
|
20 |
|
21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
|
23 |
const videoFilePaths: string[] = []
|
24 |
const videoSegments = clap.segments.filter(s => s.category === "video" && s.assetUrl.startsWith("data:video/"))
|
25 |
|
26 |
for (const segment of videoSegments) {
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
}
|
34 |
|
35 |
-
const
|
36 |
videoFilePaths,
|
37 |
-
output: join(
|
38 |
})
|
39 |
|
40 |
const audioTracks: string[] = []
|
41 |
|
42 |
-
const musicSegments = clap.segments.filter(s =>
|
|
|
|
|
|
|
43 |
for (const segment of musicSegments) {
|
44 |
audioTracks.push(
|
45 |
await writeBase64ToFile(
|
46 |
segment.assetUrl,
|
47 |
-
join(
|
48 |
)
|
49 |
)
|
50 |
}
|
51 |
|
52 |
const concatenatedAudio = await concatenateAudio({
|
53 |
-
output: join(
|
54 |
audioTracks,
|
55 |
crossfadeDurationInSec: 2 // 2 seconds
|
56 |
})
|
57 |
|
58 |
-
const
|
59 |
-
output: join(
|
60 |
audioFilePath: concatenatedAudio.filepath,
|
61 |
-
videoFilePaths: [
|
62 |
// videos are silent, so they can stay at 0
|
63 |
-
videoTracksVolume: 0.
|
64 |
-
audioTrackVolume:
|
65 |
})
|
66 |
|
67 |
-
|
68 |
-
|
|
|
|
|
69 |
|
70 |
-
return
|
|
|
|
|
|
|
71 |
}
|
|
|
|
|
1 |
import { join } from "node:path"
|
|
|
|
|
2 |
|
3 |
import { ClapProject } from "./core/clap/types.mts";
|
4 |
import { concatenateAudio } from "./core/ffmpeg/concatenateAudio.mts";
|
|
|
6 |
import { writeBase64ToFile } from "./core/files/writeBase64ToFile.mts";
|
7 |
import { concatenateVideos } from "./core/ffmpeg/concatenateVideos.mts"
|
8 |
import { deleteFilesWithName } from "./core/files/deleteFileWithName.mts"
|
9 |
+
import { getRandomDirectory } from "./core/files/getRandomDirectory.mts";
|
10 |
+
import { addTextToVideo } from "./core/ffmpeg/addTextToVideo.mts";
|
11 |
+
import { startOfSegment1IsWithinSegment2 } from "./core/utils/startOfSegment1IsWithinSegment2.mts";
|
12 |
+
import { deleteFile } from "./core/files/deleteFile.mts";
|
13 |
+
import { extractBase64 } from "./core/base64/extractBase64.mts";
|
14 |
|
15 |
/**
|
16 |
* Generate a .mp4 video inside a direcory (if none is provided, it will be created in /tmp)
|
|
|
18 |
* @param clap
|
19 |
* @returns file path to the final .mp4
|
20 |
*/
|
21 |
+
export async function clapToTmpVideoFilePath({
|
22 |
+
clap,
|
23 |
+
outputDir = "",
|
24 |
+
clearTmpFilesAtEnd = false
|
25 |
+
}: {
|
26 |
+
clap: ClapProject
|
27 |
|
28 |
+
outputDir?: string
|
29 |
+
|
30 |
+
// if you leave this to false, you will have to clear files yourself
|
31 |
+
// (eg. after sending the final video file over)
|
32 |
+
clearTmpFilesAtEnd?: boolean
|
33 |
+
}): Promise<{
|
34 |
+
tmpWorkDir: string
|
35 |
+
outputFilePath: string
|
36 |
+
}> {
|
37 |
+
|
38 |
+
outputDir = outputDir || (await getRandomDirectory())
|
39 |
|
40 |
const videoFilePaths: string[] = []
|
41 |
const videoSegments = clap.segments.filter(s => s.category === "video" && s.assetUrl.startsWith("data:video/"))
|
42 |
|
43 |
for (const segment of videoSegments) {
|
44 |
+
|
45 |
+
const base64Info = extractBase64(segment.assetUrl)
|
46 |
+
|
47 |
+
// we write it to the disk *unconverted* (it might be a mp4, a webm or something else)
|
48 |
+
let videoSegmentFilePath = await writeBase64ToFile(
|
49 |
+
segment.assetUrl,
|
50 |
+
join(outputDir, `tmp_asset_${segment.id}.${base64Info.extension}`)
|
51 |
+
)
|
52 |
+
|
53 |
+
const interfaceSegments = clap.segments.filter(s =>
|
54 |
+
s.assetUrl.startsWith("data:text/") &&
|
55 |
+
s.category === "interface" &&
|
56 |
+
startOfSegment1IsWithinSegment2(s, segment)
|
57 |
+
)
|
58 |
+
const interfaceSegment = interfaceSegments.at(0)
|
59 |
+
if (interfaceSegment) {
|
60 |
+
// here we are free to use mp4, since this is an internal intermediary format
|
61 |
+
const videoSegmentWithOverlayFilePath = join(outputDir, `tmp_asset_${segment.id}_with_interface.mp4`)
|
62 |
+
|
63 |
+
await addTextToVideo({
|
64 |
+
inputVideoPath: videoSegmentFilePath,
|
65 |
+
outputVideoPath: videoSegmentWithOverlayFilePath,
|
66 |
+
text: atob(extractBase64(interfaceSegment.assetUrl).data),
|
67 |
+
width: clap.meta.width,
|
68 |
+
height: clap.meta.height,
|
69 |
+
})
|
70 |
+
|
71 |
+
// we overwrite
|
72 |
+
await deleteFile(videoSegmentFilePath)
|
73 |
+
videoSegmentFilePath = videoSegmentWithOverlayFilePath
|
74 |
+
}
|
75 |
+
|
76 |
+
const dialogueSegments = clap.segments.filter(s =>
|
77 |
+
s.assetUrl.startsWith("data:audio/") &&
|
78 |
+
s.category === "dialogue" &&
|
79 |
+
startOfSegment1IsWithinSegment2(s, segment)
|
80 |
)
|
81 |
+
const dialogueSegment = dialogueSegments.at(0)
|
82 |
+
if (dialogueSegment) {
|
83 |
+
extractBase64(dialogueSegment.assetUrl)
|
84 |
+
const base64Info = extractBase64(segment.assetUrl)
|
85 |
+
|
86 |
+
const dialogueSegmentFilePath = await writeBase64ToFile(
|
87 |
+
dialogueSegment.assetUrl,
|
88 |
+
join(outputDir, `tmp_asset_${segment.id}_dialogue.${base64Info.extension}`)
|
89 |
+
)
|
90 |
+
|
91 |
+
const finalFilePathOfVideoWithSound = await concatenateVideosWithAudio({
|
92 |
+
output: join(outputDir, `${segment.id}_video_with_audio.mp4`),
|
93 |
+
audioFilePath: dialogueSegmentFilePath,
|
94 |
+
videoFilePaths: [videoSegmentFilePath],
|
95 |
+
// videos are silent, so they can stay at 0
|
96 |
+
videoTracksVolume: 0.0,
|
97 |
+
audioTrackVolume: 1.0,
|
98 |
+
})
|
99 |
+
|
100 |
+
// we delete the temporary dialogue file
|
101 |
+
await deleteFile(dialogueSegmentFilePath)
|
102 |
+
|
103 |
+
// we overwrite the video segment
|
104 |
+
await deleteFile(videoSegmentFilePath)
|
105 |
+
|
106 |
+
videoSegmentFilePath = finalFilePathOfVideoWithSound
|
107 |
+
}
|
108 |
+
|
109 |
+
videoFilePaths.push(videoSegmentFilePath)
|
110 |
}
|
111 |
|
112 |
+
const concatenatedVideosNoMusic = await concatenateVideos({
|
113 |
videoFilePaths,
|
114 |
+
output: join(outputDir, `tmp_asset_concatenated_videos.mp4`)
|
115 |
})
|
116 |
|
117 |
const audioTracks: string[] = []
|
118 |
|
119 |
+
const musicSegments = clap.segments.filter(s =>
|
120 |
+
s.category === "music" &&
|
121 |
+
s.assetUrl.startsWith("data:audio/")
|
122 |
+
)
|
123 |
for (const segment of musicSegments) {
|
124 |
audioTracks.push(
|
125 |
await writeBase64ToFile(
|
126 |
segment.assetUrl,
|
127 |
+
join(outputDir, `tmp_asset_${segment.id}.wav`)
|
128 |
)
|
129 |
)
|
130 |
}
|
131 |
|
132 |
const concatenatedAudio = await concatenateAudio({
|
133 |
+
output: join(outputDir, `tmp_asset_concatenated_audio.wav`),
|
134 |
audioTracks,
|
135 |
crossfadeDurationInSec: 2 // 2 seconds
|
136 |
})
|
137 |
|
138 |
+
const finalFilePathOfVideoWithMusic = await concatenateVideosWithAudio({
|
139 |
+
output: join(outputDir, `final_video.mp4`),
|
140 |
audioFilePath: concatenatedAudio.filepath,
|
141 |
+
videoFilePaths: [concatenatedVideosNoMusic.filepath],
|
142 |
// videos are silent, so they can stay at 0
|
143 |
+
videoTracksVolume: 0.85,
|
144 |
+
audioTrackVolume: 0.15, // let's keep the music volume low
|
145 |
})
|
146 |
|
147 |
+
if (clearTmpFilesAtEnd) {
|
148 |
+
// we delete all the temporary assets
|
149 |
+
await deleteFilesWithName(outputDir, `tmp_asset_`)
|
150 |
+
}
|
151 |
|
152 |
+
return {
|
153 |
+
tmpWorkDir: outputDir,
|
154 |
+
outputFilePath: finalFilePathOfVideoWithMusic
|
155 |
+
}
|
156 |
}
|