Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Commit
·
4cb7ad9
1
Parent(s):
89f3b87
make the format a parameter
Browse files- package-lock.json +47 -0
- package.json +1 -0
- src/core/ffmpeg/concatenateVideosWithAudio.mts +8 -3
- src/index.mts +16 -2
- src/main.mts +6 -2
package-lock.json
CHANGED
@@ -21,6 +21,7 @@
|
|
21 |
"mime-types": "^2.1.35",
|
22 |
"node-fetch": "^3.3.1",
|
23 |
"puppeteer": "^22.7.0",
|
|
|
24 |
"sharp": "^0.33.3",
|
25 |
"temp-dir": "^3.0.0",
|
26 |
"ts-node": "^10.9.1",
|
@@ -1543,6 +1544,14 @@
|
|
1543 |
"ms": "2.0.0"
|
1544 |
}
|
1545 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1546 |
"node_modules/define-data-property": {
|
1547 |
"version": "1.1.4",
|
1548 |
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
@@ -1921,6 +1930,17 @@
|
|
1921 |
"node": "^12.20 || >= 14.13"
|
1922 |
}
|
1923 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1924 |
"node_modules/finalhandler": {
|
1925 |
"version": "1.2.0",
|
1926 |
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
@@ -2789,6 +2809,22 @@
|
|
2789 |
"url": "https://github.com/sponsors/ljharb"
|
2790 |
}
|
2791 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2792 |
"node_modules/queue-tick": {
|
2793 |
"version": "1.0.1",
|
2794 |
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
|
@@ -3087,6 +3123,17 @@
|
|
3087 |
"node": ">=0.10.0"
|
3088 |
}
|
3089 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3090 |
"node_modules/sprintf-js": {
|
3091 |
"version": "1.1.3",
|
3092 |
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
|
|
21 |
"mime-types": "^2.1.35",
|
22 |
"node-fetch": "^3.3.1",
|
23 |
"puppeteer": "^22.7.0",
|
24 |
+
"query-string": "^9.0.0",
|
25 |
"sharp": "^0.33.3",
|
26 |
"temp-dir": "^3.0.0",
|
27 |
"ts-node": "^10.9.1",
|
|
|
1544 |
"ms": "2.0.0"
|
1545 |
}
|
1546 |
},
|
1547 |
+
"node_modules/decode-uri-component": {
|
1548 |
+
"version": "0.4.1",
|
1549 |
+
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.4.1.tgz",
|
1550 |
+
"integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==",
|
1551 |
+
"engines": {
|
1552 |
+
"node": ">=14.16"
|
1553 |
+
}
|
1554 |
+
},
|
1555 |
"node_modules/define-data-property": {
|
1556 |
"version": "1.1.4",
|
1557 |
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
|
|
1930 |
"node": "^12.20 || >= 14.13"
|
1931 |
}
|
1932 |
},
|
1933 |
+
"node_modules/filter-obj": {
|
1934 |
+
"version": "5.1.0",
|
1935 |
+
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz",
|
1936 |
+
"integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==",
|
1937 |
+
"engines": {
|
1938 |
+
"node": ">=14.16"
|
1939 |
+
},
|
1940 |
+
"funding": {
|
1941 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
1942 |
+
}
|
1943 |
+
},
|
1944 |
"node_modules/finalhandler": {
|
1945 |
"version": "1.2.0",
|
1946 |
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
|
|
2809 |
"url": "https://github.com/sponsors/ljharb"
|
2810 |
}
|
2811 |
},
|
2812 |
+
"node_modules/query-string": {
|
2813 |
+
"version": "9.0.0",
|
2814 |
+
"resolved": "https://registry.npmjs.org/query-string/-/query-string-9.0.0.tgz",
|
2815 |
+
"integrity": "sha512-4EWwcRGsO2H+yzq6ddHcVqkCQ2EFUSfDMEjF8ryp8ReymyZhIuaFRGLomeOQLkrzacMHoyky2HW0Qe30UbzkKw==",
|
2816 |
+
"dependencies": {
|
2817 |
+
"decode-uri-component": "^0.4.1",
|
2818 |
+
"filter-obj": "^5.1.0",
|
2819 |
+
"split-on-first": "^3.0.0"
|
2820 |
+
},
|
2821 |
+
"engines": {
|
2822 |
+
"node": ">=18"
|
2823 |
+
},
|
2824 |
+
"funding": {
|
2825 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
2826 |
+
}
|
2827 |
+
},
|
2828 |
"node_modules/queue-tick": {
|
2829 |
"version": "1.0.1",
|
2830 |
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
|
|
|
3123 |
"node": ">=0.10.0"
|
3124 |
}
|
3125 |
},
|
3126 |
+
"node_modules/split-on-first": {
|
3127 |
+
"version": "3.0.0",
|
3128 |
+
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-3.0.0.tgz",
|
3129 |
+
"integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==",
|
3130 |
+
"engines": {
|
3131 |
+
"node": ">=12"
|
3132 |
+
},
|
3133 |
+
"funding": {
|
3134 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
3135 |
+
}
|
3136 |
+
},
|
3137 |
"node_modules/sprintf-js": {
|
3138 |
"version": "1.1.3",
|
3139 |
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
package.json
CHANGED
@@ -26,6 +26,7 @@
|
|
26 |
"mime-types": "^2.1.35",
|
27 |
"node-fetch": "^3.3.1",
|
28 |
"puppeteer": "^22.7.0",
|
|
|
29 |
"sharp": "^0.33.3",
|
30 |
"temp-dir": "^3.0.0",
|
31 |
"ts-node": "^10.9.1",
|
|
|
26 |
"mime-types": "^2.1.35",
|
27 |
"node-fetch": "^3.3.1",
|
28 |
"puppeteer": "^22.7.0",
|
29 |
+
"query-string": "^9.0.0",
|
30 |
"sharp": "^0.33.3",
|
31 |
"temp-dir": "^3.0.0",
|
32 |
"ts-node": "^10.9.1",
|
src/core/ffmpeg/concatenateVideosWithAudio.mts
CHANGED
@@ -10,8 +10,12 @@ import { getMediaInfo } from "./getMediaInfo.mts";
|
|
10 |
import { removeTemporaryFiles } from "../files/removeTmpFiles.mts";
|
11 |
import { addBase64Header } from "../base64/addBase64.mts";
|
12 |
|
13 |
-
type
|
|
|
|
|
|
|
14 |
output?: string;
|
|
|
15 |
audioTrack?: string; // base64
|
16 |
audioFilePath?: string; // path
|
17 |
videoTracks?: string[]; // base64
|
@@ -24,6 +28,7 @@ type ConcatenateVideoWithAudioOptions = {
|
|
24 |
|
25 |
export const concatenateVideosWithAudio = async ({
|
26 |
output,
|
|
|
27 |
audioTrack = "",
|
28 |
audioFilePath = "",
|
29 |
videoTracks = [],
|
@@ -65,7 +70,7 @@ export const concatenateVideosWithAudio = async ({
|
|
65 |
const tempMediaInfo = await getMediaInfo(tempFilePath.filepath);
|
66 |
const hasOriginalAudio = tempMediaInfo.hasAudio;
|
67 |
|
68 |
-
const finalOutputFilePath = output || path.join(tempDir, `${uuidv4()}
|
69 |
|
70 |
// Begin ffmpeg command configuration
|
71 |
let cmd = ffmpeg();
|
@@ -133,7 +138,7 @@ export const concatenateVideosWithAudio = async ({
|
|
133 |
if (asBase64) {
|
134 |
try {
|
135 |
const outputBuffer = await fs.readFile(finalOutputFilePath);
|
136 |
-
const outputBase64 = addBase64Header(outputBuffer.toString("base64"),
|
137 |
resolve(outputBase64);
|
138 |
} catch (error) {
|
139 |
reject(new Error(`Error reading output video file: ${(error as Error).message}`));
|
|
|
10 |
import { removeTemporaryFiles } from "../files/removeTmpFiles.mts";
|
11 |
import { addBase64Header } from "../base64/addBase64.mts";
|
12 |
|
13 |
+
export type SupportedExportFormat = "mp4" | "webm"
|
14 |
+
export const defaultExportFormat = "mp4"
|
15 |
+
|
16 |
+
export type ConcatenateVideoWithAudioOptions = {
|
17 |
output?: string;
|
18 |
+
format?: SupportedExportFormat;
|
19 |
audioTrack?: string; // base64
|
20 |
audioFilePath?: string; // path
|
21 |
videoTracks?: string[]; // base64
|
|
|
28 |
|
29 |
export const concatenateVideosWithAudio = async ({
|
30 |
output,
|
31 |
+
format = defaultExportFormat,
|
32 |
audioTrack = "",
|
33 |
audioFilePath = "",
|
34 |
videoTracks = [],
|
|
|
70 |
const tempMediaInfo = await getMediaInfo(tempFilePath.filepath);
|
71 |
const hasOriginalAudio = tempMediaInfo.hasAudio;
|
72 |
|
73 |
+
const finalOutputFilePath = output || path.join(tempDir, `${uuidv4()}.${format}`);
|
74 |
|
75 |
// Begin ffmpeg command configuration
|
76 |
let cmd = ffmpeg();
|
|
|
138 |
if (asBase64) {
|
139 |
try {
|
140 |
const outputBuffer = await fs.readFile(finalOutputFilePath);
|
141 |
+
const outputBase64 = addBase64Header(outputBuffer.toString("base64"), format)
|
142 |
resolve(outputBase64);
|
143 |
} catch (error) {
|
144 |
reject(new Error(`Error reading output video file: ${(error as Error).message}`));
|
src/index.mts
CHANGED
@@ -6,6 +6,8 @@ import { parseClap, ClapProject } from "@aitube/clap"
|
|
6 |
|
7 |
import { clapToTmpVideoFilePath } from "./main.mts"
|
8 |
import { deleteFile } from "./core/files/deleteFile.mts"
|
|
|
|
|
9 |
|
10 |
const app = express()
|
11 |
const port = 7860
|
@@ -39,9 +41,21 @@ app.get("/", async (req, res) => {
|
|
39 |
res.end()
|
40 |
})
|
41 |
|
|
|
42 |
// the export robot has only one job: to export .clap files
|
43 |
app.post("/", async (req, res) => {
|
44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
let data: Uint8Array[] = [];
|
46 |
|
47 |
req.on("data", (chunk) => {
|
@@ -59,8 +73,8 @@ app.post("/", async (req, res) => {
|
|
59 |
const {
|
60 |
tmpWorkDir,
|
61 |
outputFilePath,
|
62 |
-
} = await clapToTmpVideoFilePath({ clap })
|
63 |
-
console.log(
|
64 |
|
65 |
res.download(outputFilePath, async () => {
|
66 |
// clean-up after ourselves (we clear the whole tmp directory)
|
|
|
6 |
|
7 |
import { clapToTmpVideoFilePath } from "./main.mts"
|
8 |
import { deleteFile } from "./core/files/deleteFile.mts"
|
9 |
+
import queryString from "query-string"
|
10 |
+
import { defaultExportFormat, SupportedExportFormat } from "./core/ffmpeg/concatenateVideosWithAudio.mts"
|
11 |
|
12 |
const app = express()
|
13 |
const port = 7860
|
|
|
41 |
res.end()
|
42 |
})
|
43 |
|
44 |
+
|
45 |
// the export robot has only one job: to export .clap files
|
46 |
app.post("/", async (req, res) => {
|
47 |
|
48 |
+
const qs = queryString.parseUrl(req.url || "")
|
49 |
+
const query = (qs || {}).query
|
50 |
+
|
51 |
+
let format: SupportedExportFormat = defaultExportFormat
|
52 |
+
try {
|
53 |
+
format = decodeURIComponent(query?.f?.toString() || defaultExportFormat).trim() as SupportedExportFormat
|
54 |
+
if (format !== "mp4" && format !== "webm") {
|
55 |
+
format = defaultExportFormat
|
56 |
+
}
|
57 |
+
} catch (err) {}
|
58 |
+
|
59 |
let data: Uint8Array[] = [];
|
60 |
|
61 |
req.on("data", (chunk) => {
|
|
|
73 |
const {
|
74 |
tmpWorkDir,
|
75 |
outputFilePath,
|
76 |
+
} = await clapToTmpVideoFilePath({ clap, format })
|
77 |
+
console.log(`got an output ${format} file at:`, outputFilePath)
|
78 |
|
79 |
res.download(outputFilePath, async () => {
|
80 |
// clean-up after ourselves (we clear the whole tmp directory)
|
src/main.mts
CHANGED
@@ -3,7 +3,7 @@ import { join } from "node:path"
|
|
3 |
import { ClapProject } from "@aitube/clap";
|
4 |
|
5 |
import { concatenateAudio } from "./core/ffmpeg/concatenateAudio.mts";
|
6 |
-
import { concatenateVideosWithAudio } from "./core/ffmpeg/concatenateVideosWithAudio.mts";
|
7 |
import { writeBase64ToFile } from "./core/files/writeBase64ToFile.mts";
|
8 |
import { concatenateVideos } from "./core/ffmpeg/concatenateVideos.mts"
|
9 |
import { deleteFilesWithName } from "./core/files/deleteFileWithName.mts"
|
@@ -11,6 +11,7 @@ import { getRandomDirectory } from "./core/files/getRandomDirectory.mts";
|
|
11 |
import { clapWithVideosToVideoFile } from "./core/exporters/clapWithVideosToVideoFile.mts";
|
12 |
import { clapWithStoryboardsToVideoFile } from "./core/exporters/clapWithStoryboardsToVideoFile.mts";
|
13 |
|
|
|
14 |
/**
|
15 |
* Generate a .mp4 video inside a directory (if none is provided, it will be created in /tmp)
|
16 |
*
|
@@ -19,11 +20,13 @@ import { clapWithStoryboardsToVideoFile } from "./core/exporters/clapWithStorybo
|
|
19 |
*/
|
20 |
export async function clapToTmpVideoFilePath({
|
21 |
clap,
|
|
|
22 |
outputDir = "",
|
23 |
clearTmpFilesAtEnd = false
|
24 |
}: {
|
25 |
clap: ClapProject
|
26 |
|
|
|
27 |
outputDir?: string
|
28 |
|
29 |
// if you leave this to false, you will have to clear files yourself
|
@@ -90,7 +93,8 @@ export async function clapToTmpVideoFilePath({
|
|
90 |
})
|
91 |
|
92 |
const finalFilePathOfVideoWithMusic = await concatenateVideosWithAudio({
|
93 |
-
output: join(outputDir, `final_video
|
|
|
94 |
audioFilePath: concatenatedAudio.filepath,
|
95 |
videoFilePaths: [concatenatedVideosNoMusic.filepath],
|
96 |
// videos are silent, so they can stay at 0
|
|
|
3 |
import { ClapProject } from "@aitube/clap";
|
4 |
|
5 |
import { concatenateAudio } from "./core/ffmpeg/concatenateAudio.mts";
|
6 |
+
import { concatenateVideosWithAudio, defaultExportFormat, SupportedExportFormat } from "./core/ffmpeg/concatenateVideosWithAudio.mts";
|
7 |
import { writeBase64ToFile } from "./core/files/writeBase64ToFile.mts";
|
8 |
import { concatenateVideos } from "./core/ffmpeg/concatenateVideos.mts"
|
9 |
import { deleteFilesWithName } from "./core/files/deleteFileWithName.mts"
|
|
|
11 |
import { clapWithVideosToVideoFile } from "./core/exporters/clapWithVideosToVideoFile.mts";
|
12 |
import { clapWithStoryboardsToVideoFile } from "./core/exporters/clapWithStoryboardsToVideoFile.mts";
|
13 |
|
14 |
+
|
15 |
/**
|
16 |
* Generate a .mp4 video inside a directory (if none is provided, it will be created in /tmp)
|
17 |
*
|
|
|
20 |
*/
|
21 |
export async function clapToTmpVideoFilePath({
|
22 |
clap,
|
23 |
+
format = defaultExportFormat,
|
24 |
outputDir = "",
|
25 |
clearTmpFilesAtEnd = false
|
26 |
}: {
|
27 |
clap: ClapProject
|
28 |
|
29 |
+
format?: SupportedExportFormat
|
30 |
outputDir?: string
|
31 |
|
32 |
// if you leave this to false, you will have to clear files yourself
|
|
|
93 |
})
|
94 |
|
95 |
const finalFilePathOfVideoWithMusic = await concatenateVideosWithAudio({
|
96 |
+
output: join(outputDir, `final_video.${format}`),
|
97 |
+
format,
|
98 |
audioFilePath: concatenatedAudio.filepath,
|
99 |
videoFilePaths: [concatenatedVideosNoMusic.filepath],
|
100 |
// videos are silent, so they can stay at 0
|