import 'dotenv/config'; import axios from 'axios'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const API_KEY = process.env.ASSEMBLYAI_API_KEY; const BASE_URL = 'https://api.assemblyai.com/v2'; /** * Uploads audio file to AssemblyAI */ async function uploadAudio(audioPath: string): Promise { const audioData = fs.readFileSync(audioPath); const response = await axios.post<{ upload_url: string }>(`${BASE_URL}/upload`, audioData, { headers: { 'authorization': API_KEY, 'content-type': 'application/octet-stream' } }); return response.data.upload_url; } /** * Extract a session id (basename without extension) from a local path or a URL */ function getSessionId(inputPath: string): string { try { const parsed = new URL(inputPath); const base = path.basename(parsed.pathname); return base.replace(/\.[^.]+$/, ''); } catch (err) { // not a URL, treat as local path return path.basename(inputPath, path.extname(inputPath)); } } /** * Creates transcription job with speaker diarization */ async function createTranscript(audioUrl: string): Promise { const response = await axios.post<{ id: string }>(`${BASE_URL}/transcript`, { audio_url: audioUrl, speaker_labels: true, language_detection: true }, { headers: { 'authorization': API_KEY, 'content-type': 'application/json' } }); return response.data.id; } /** * Polls transcript status until completed */ async function pollTranscript(transcriptId: string): Promise { while (true) { const response = await axios.get(`${BASE_URL}/transcript/${transcriptId}`, { headers: { 'authorization': API_KEY } }); const status = response.data.status; if (status === 'completed') { return response.data; } else if (status === 'error') { throw new Error(`Transcription failed: ${response.data.error}`); } // Wait 3 seconds before next poll await new Promise(resolve => setTimeout(resolve, 3000)); } } /** * Saves transcript to storage */ function saveTranscript(transcript: any, sessionId: string): void { const outputDir = path.join(__dirname, '..', '..', '..', 'storage', 'transcripts'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } const outputPath = path.join(outputDir, `${sessionId}.json`); fs.writeFileSync(outputPath, JSON.stringify(transcript, null, 2)); console.log(`✅ Transcript saved: ${outputPath}`); } export default { name: "assembly", type: "transcription", displayname: "AssemblyAI", run: async (audioPath: string) => { try { // Determine if audioPath is an external URL or a local file let audioUrl: string; if (/^https?:\/\//i.test(audioPath)) { console.log('🔗 Using external audio URL...'); audioUrl = audioPath; } else { console.log('🔄 Uploading local audio...'); if (!fs.existsSync(audioPath)) { throw new Error(`Audio file not found: ${audioPath}`); } audioUrl = await uploadAudio(audioPath); } console.log('🔄 Creating transcript job...'); const transcriptId = await createTranscript(audioUrl); console.log('⏳ Waiting for transcription...'); const transcript = await pollTranscript(transcriptId); const sessionId = getSessionId(audioPath); saveTranscript(transcript, sessionId); return transcript; } catch (error: any) { console.error('❌ Transcription error:', error.message); throw error; } } };