Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
File size: 3,760 Bytes
2cae2a9 |
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
import { existsSync, promises as fs } from "node:fs"
import os from "node:os"
import path from "node:path"
import { v4 as uuidv4 } from "uuid";
import ffmpeg, { FfmpegCommand } from "fluent-ffmpeg";
import { writeBase64ToFile } from "../files/writeBase64ToFile.mts";
import { getMediaInfo } from "./getMediaInfo.mts";
import { removeTemporaryFiles } from "../files/removeTmpFiles.mts";
import { addBase64Header } from "../base64/addBase64.mts";
export type ConcatenateAudioOptions = {
// those are base64 audio strings!
audioTracks?: string[]; // base64
audioFilePaths?: string[]; // path
crossfadeDurationInSec?: number;
outputFormat?: string; // "wav" or "mp3"
output?: string;
}
export type ConcatenateAudioOutput = {
filepath: string;
durationInSec: number;
}
export async function concatenateAudio({
output,
audioTracks = [],
audioFilePaths = [],
crossfadeDurationInSec = 10,
outputFormat = "wav"
}: ConcatenateAudioOptions): Promise<ConcatenateAudioOutput> {
if (!Array.isArray(audioTracks)) {
throw new Error("Audios must be provided in an array");
}
const tempDir = path.join(os.tmpdir(), uuidv4());
await fs.mkdir(tempDir);
// console.log(" |- created tmp dir")
// trivial case: there is only one audio to concatenate!
if (audioTracks.length === 1 && audioTracks[0]) {
const audioTrack = audioTracks[0]
const outputFilePath = path.join(tempDir, `audio_0.${outputFormat}`);
await writeBase64ToFile(addBase64Header(audioTrack, "wav"), outputFilePath);
// console.log(" |- there is only one track! so.. returning that")
const { durationInSec } = await getMediaInfo(outputFilePath);
return { filepath: outputFilePath, durationInSec };
}
if (audioFilePaths.length === 1) {
throw new Error("concatenating a single audio file path is not implemented yet")
}
try {
let i = 0
for (const track of audioTracks) {
if (!track) { continue }
const audioFilePath = path.join(tempDir, `audio_${++i}.wav`);
await writeBase64ToFile(addBase64Header(track, "wav"), audioFilePath);
audioFilePaths.push(audioFilePath);
}
audioFilePaths = audioFilePaths.filter((audio) => existsSync(audio))
const outputFilePath = output ?? path.join(tempDir, `${uuidv4()}.${outputFormat}`);
let filterComplex = "";
let prevLabel = "0";
for (let i = 0; i < audioFilePaths.length - 1; i++) {
const nextLabel = `a${i}`;
filterComplex += `[${prevLabel}][${i + 1}]acrossfade=d=${crossfadeDurationInSec}:c1=tri:c2=tri[${nextLabel}];`;
prevLabel = nextLabel;
}
console.log(" |- concatenateAudio(): DEBUG:", {
tempDir,
audioFilePaths,
outputFilePath,
filterComplex,
prevLabel
})
let cmd: FfmpegCommand = ffmpeg() // .outputOptions('-vn');
audioFilePaths.forEach((audio, i) => {
cmd = cmd.input(audio);
});
const promise = new Promise<ConcatenateAudioOutput>((resolve, reject) => {
cmd = cmd
.on('error', reject)
.on('end', async () => {
try {
const { durationInSec } = await getMediaInfo(outputFilePath);
// console.log("concatenation ended! see ->", outputFilePath)
resolve({ filepath: outputFilePath, durationInSec });
} catch (err) {
reject(err);
}
})
.complexFilter(filterComplex, prevLabel)
.save(outputFilePath);
});
const result = await promise
return result
} catch (error) {
console.error(`Failed to assemble audio!`)
console.error(error)
throw new Error(`Failed to assemble audio: ${(error as Error)?.message || error}`);
} finally {
await removeTemporaryFiles(audioFilePaths)
}
}
|