Files
batch-speech-utils/example/example-jambonz-api.js
T
2024-07-19 02:32:07 -04:00

251 lines
8.1 KiB
JavaScript

const { request } = require('undici');
const { pipeline } = require('stream');
const fs = require('fs');
const path = require('path');
const os = require('os');
const fsP = require('fs').promises;
const createAudioService = require('..');
const audioService = createAudioService();
// Constants
const JAMBONZ_API_BASE_URL = 'https://jambonz.one/api/v1';
// Utility functions
async function readJSONFile(filePath) {
try {
const data = await fsP.readFile(filePath, 'utf8');
return JSON.parse(data);
} catch (err) {
throw new Error(`Error reading JSON file: ${err.message}`);
}
}
async function writeJSONFile(filePath, data) {
try {
await fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
} catch (err) {
throw new Error(`Error writing JSON file: ${err.message}`);
}
}
function extractJambonzTrace(data, audioService) {
const participants = [];
let duration = 0;
const uniqueNumbers = new Set();
data.resourceSpans.forEach((resourceSpan) => {
resourceSpan.instrumentationLibrarySpans.forEach((librarySpan) => {
librarySpan.spans.forEach((span) => {
span.attributes.forEach((attribute) => {
if (attribute.value && attribute.value.stringValue) {
switch (attribute.key) {
case 'from':
// Ensure we're only adding unique 'from' numbers
if (!uniqueNumbers.has(attribute.value.stringValue)) {
uniqueNumbers.add(attribute.value.stringValue);
participants.push({
'type': 'human',
'initiatedConversation': true,
'id': {
'name': null, // Assuming there's no name information available
'phone': attribute.value.stringValue
}
});
}
break;
case 'duration':
// Parse the duration as an integer if it's a string
duration += parseInt(attribute.value.stringValue || '0', 10);
break;
case 'to':
// Conditionally add 'to' participants if they represent the machine
if (!uniqueNumbers.has(attribute.value.stringValue)) {
uniqueNumbers.add(attribute.value.stringValue);
participants.push({
'type': 'machine',
'initiatedConversation': false,
'id': {
'name': 'jambonz.one', // Use the provided audioService if relevant
'phone': attribute.value.stringValue
}
});
}
break;
}
}
});
});
});
});
return {'participants': participants, 'duration': duration};
}
async function fetchCallDetails(callId) {
const headers = {
'Authorization': `Bearer ${process.env.JAMBONZ_API_TOKEN}`,
'Accept': '*/*',
};
const url = `${JAMBONZ_API_BASE_URL}/Accounts/${process.env.ACCOUNT_SID}/RecentCalls?page=1&count=25&filter=${callId}`;
try {
const { statusCode, body } = await request(url, { headers });
if (statusCode !== 200) {
throw new Error(`Request failed with status code: ${statusCode}`);
}
var data = await body.json();
data = data.data[0];
const response = {
'call_start' : data.answered_at,
'duration_ms':data.duration * 1000,
'trace_id': data.trace_id,
'recording_url' : data.recording_url
};
// await writeJSONFile('response_call_details.json', targetCall);
console.log('Call details has been received');
return response;
} catch (error) {
console.error('Error fetching data:', error);
throw error;
}
}
async function fetchJambonzTrace(trace_id) {
const url = `${JAMBONZ_API_BASE_URL}/Accounts/${process.env.ACCOUNT_SID}/RecentCalls/trace/${trace_id}`;
const headers = {
'Authorization': `Bearer ${process.env.JAMBONZ_API_TOKEN}`,
'Accept': '*/*',
};
try {
const { statusCode, body } = await request(url, { headers });
if (statusCode !== 200) {
throw new Error(`Request failed with status code: ${statusCode}`);
}
const data = await body.json();
await writeJSONFile('trace.json', data);
console.log('Trace received');
const jsonData = await readJSONFile('./trace.json');
return extractJambonzTrace(jsonData, audioService);
} catch (error) {
console.error('Error fetching trace data:', error);
throw error;
}
}
async function downloadFile(url, tempFilePath, finalFilePath, token, callDetails) {
try {
const { statusCode, body } = await request(url, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Accept': '*/*'
}
});
if (statusCode !== 200) {
throw new Error(`Failed to download file: ${statusCode}`);
}
const tempWriteStream = fs.createWriteStream(tempFilePath);
pipeline(
body,
tempWriteStream,
async(err) => {
if (err) {
console.error('Pipeline failed:', err);
} else {
try {
await processAndRedactFile(tempFilePath, finalFilePath, callDetails);
} catch (processingError) {
console.error('Error processing file:', processingError);
} finally {
// Clean up the temporary file
fs.unlink(tempFilePath, (err) => {
if (err) {
console.error('Error deleting temp file:', err);
} else {
console.log('Temporary file deleted.');
}
});
}
}
}
);
} catch (error) {
console.error('Error fetching file:', error);
}
}
async function processAndRedactFile(audioFilePath, outputFilePath, callDetails) {
const credentials = { 'vendor': 'deepgram', 'apiKey': process.env.DEEPGRAM_API_KEY };
try {
const transcriptionResults = await audioService.transcribe(credentials, audioFilePath);
const timestampsToRedact = transcriptionResults.redactionTimestamps;
await audioService.redact({
credentials: credentials,
transcriptionData: timestampsToRedact,
audioPath: audioFilePath,
audioOutputPath: outputFilePath
});
const jambonzTrace = await fetchJambonzTrace(callDetails.trace_id);
const {'redactionTimestamps':_, ...transcript} = transcriptionResults;
jambonzTrace.duration = callDetails.duration_ms;
jambonzTrace.call_start = callDetails.call_start;
jambonzTrace.transcript = transcript;
const callStartMillis = new Date(callDetails.call_start).getTime();
jambonzTrace.transcript.speechEvents = jambonzTrace.transcript.speechEvents.map(event => {
const startTimestamp = new Date(callStartMillis + event.start * 1000).toISOString();
// const endTimestamp = new Date(callStartMillis + event.end * 1000).toISOString();
return {
spokenAt: startTimestamp,
...event
// start: startTimestamp,
// end: endTimestamp,
};
});
await writeJSONFile(`${process.env.OUTPUT_PATH}/transcription.json`, jambonzTrace);
console.log('File redacted and saved successfully.');
} catch (error) {
console.error('Failed to redact audio:', error);
throw error;
}
}
// Main execution
async function main() {
try {
const [,, callId] = process.argv;
if (!callId) {
throw new Error('Missing required command line arguments. Usage: node app.js <callId>');
}
const callDetails = await fetchCallDetails(callId);
const dateFormat = callDetails.recording_url.split('record/')[1];
const url = `${JAMBONZ_API_BASE_URL}/Accounts/${process.env.ACCOUNT_SID}/RecentCalls/${callId}/record/${dateFormat}`;
//
//
// // console.log(callDetails);
const finalFilePath = path.resolve(__dirname, path.join(process.env.OUTPUT_PATH, 'redacted_audio.wav'));
const tempFolder = process.env.TEMP_FOLDER || os.tmpdir();
const tempFilePath = path.join(tempFolder, 'tempDownloadedFile.mp3');
// //
// //
await downloadFile(url, tempFilePath, finalFilePath, process.env.JAMBONZ_API_TOKEN, callDetails);
} catch (error) {
console.error('Error in main process:', error);
process.exit(1);
}
}
main();