106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import * as fs from "node:fs/promises";
|
|
import { constants as fsConstants } from "node:fs";
|
|
import { execFile } from "node:child_process";
|
|
import { promisify } from "node:util";
|
|
|
|
// --- Konfigurasi ---
|
|
const TEMP_DIR = "./temp-tts";
|
|
const LIST_FILE_NAME = "wav-list.txt";
|
|
|
|
const exec = promisify(execFile);
|
|
|
|
/**
|
|
* Menggabungkan daftar file WAV menggunakan ffmpeg concat demuxer.
|
|
* @param relativeFileNames Array NAMA file WAV yang berada di TEMP_DIR (misal: ['a.wav', 'b.wav']).
|
|
* @param output Path lengkap untuk menyimpan file WAV hasil gabungan.
|
|
*/
|
|
async function merge(relativeFileNames: string[], output: string): Promise<void> {
|
|
if (relativeFileNames.length === 0) {
|
|
console.warn("⚠️ Tidak ada file untuk digabungkan. Melewati proses.");
|
|
return;
|
|
}
|
|
|
|
console.log(`\n🔧 Menggabungkan ${relativeFileNames.length} file WAV menggunakan ffmpeg...`);
|
|
|
|
const listFilePath = `${TEMP_DIR}/${LIST_FILE_NAME}`;
|
|
|
|
// Perbaikan: Tulis nama file saja tanpa prefix TEMP_DIR agar path valid saat concat
|
|
const listContent = relativeFileNames
|
|
.map((f) => `file '${f.replace(/'/g, "'\\''")}'`)
|
|
.join("\n");
|
|
|
|
try {
|
|
await fs.writeFile(listFilePath, listContent, "utf-8");
|
|
} catch (err) {
|
|
console.error(`❌ Gagal menulis file list ${listFilePath}:`, err);
|
|
throw new Error(`Gagal menyiapkan file list untuk ffmpeg.`);
|
|
}
|
|
|
|
try {
|
|
await exec("ffmpeg", [
|
|
"-f", "concat",
|
|
"-safe", "0",
|
|
"-i", listFilePath,
|
|
"-c", "copy", // Menggabungkan tanpa re-encoding
|
|
output,
|
|
]);
|
|
|
|
console.log(`✅ File berhasil digabungkan dan disimpan di: ${output}\n`);
|
|
} catch (err) {
|
|
console.error("❌ Kesalahan saat menjalankan ffmpeg:", err);
|
|
if (err instanceof Error && 'stdout' in err) {
|
|
console.error('ffmpeg stderr output:', (err as any).stderr);
|
|
}
|
|
throw new Error(`Penggabungan ffmpeg gagal.`);
|
|
} finally {
|
|
try {
|
|
await fs.unlink(listFilePath);
|
|
} catch {
|
|
console.warn(`⚠️ Gagal menghapus file list ${listFilePath}.`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Fungsi Utama ---
|
|
export async function merge_wav() {
|
|
|
|
await fs.mkdir(`./output`, { recursive: true }).catch(() => {});
|
|
const output = `./output/merged.wav`;
|
|
|
|
try {
|
|
await fs.access(output, fsConstants.F_OK);
|
|
await fs.unlink(output);
|
|
} catch (error) {}
|
|
|
|
|
|
try {
|
|
// Cek apakah direktori TEMP_DIR ada
|
|
await fs.access(TEMP_DIR, fsConstants.F_OK);
|
|
} catch {
|
|
console.error(`❌ Direktori sementara ${TEMP_DIR} tidak ditemukan. Pastikan sudah membuat folder tersebut.`);
|
|
return;
|
|
}
|
|
|
|
// Baca daftar file WAV dalam TEMP_DIR
|
|
const allFiles = await fs.readdir(TEMP_DIR);
|
|
|
|
const wavFileNames = allFiles
|
|
.filter((f) => f.endsWith(".wav"))
|
|
.sort((a, b) => {
|
|
const aNum = parseInt(a?.split("_")[2] || "0");
|
|
const bNum = parseInt(b?.split("_")[2] || "0");
|
|
return aNum - bNum;
|
|
});
|
|
|
|
try {
|
|
await merge(wavFileNames, output);
|
|
} catch (err) {
|
|
console.error("⛔ Proses penggabungan utama gagal.");
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
|
|
if (import.meta.main) {
|
|
merge_wav();
|
|
} |