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 { 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(); }