Software: Apache. PHP/8.1.30 uname -a: Linux server1.tuhinhossain.com 5.15.0-163-generic #173-Ubuntu SMP Tue Oct 14 17:51:00 UTC uid=1002(picotech) gid=1003(picotech) groups=1003(picotech),0(root) Safe-mode: OFF (not secure) /home/picotech/domains/note.picotech.app/public_html/src/services/ drwxr-xr-x | |
| Viewing file: Select action/file-type: const ffmpeg = require('fluent-ffmpeg');
const fs = require('fs').promises;
const path = require('path');
const { AudioChunk } = require('../models');
const { AppError } = require('../middleware/errorHandler');
const { decryptFile, isFileEncrypted } = require('./encryptionService');
const CHUNK_DURATION = 60; // 30 seconds per chunk
const OUTPUT_FORMAT = 'wav';
const OUTPUT_CODEC = 'pcm_s16le';
const SAMPLE_RATE = 16000; // 16kHz for better transcription quality
/**
* Check if FFmpeg is available
*/
async function checkFFmpeg() {
return new Promise((resolve, reject) => {
ffmpeg.getAvailableFormats((err, formats) => {
if (err) {
reject(new AppError('FFmpeg is not available: ' + err.message, 500));
} else {
resolve(true);
}
});
});
}
/**
* Get audio duration using FFmpeg
*/
async function getAudioDuration(filePath) {
return new Promise((resolve, reject) => {
ffmpeg.ffprobe(filePath, (err, metadata) => {
if (err) {
reject(new AppError('Failed to get audio duration: ' + err.message, 500));
} else {
const duration = metadata.format.duration * 1000; // Convert to milliseconds
resolve(Math.floor(duration));
}
});
});
}
/**
* Create audio chunks from a recording
*/
async function createChunks(recording) {
try {
let inputPath = recording.file_path;
let needsCleanup = false;
// Check if file is encrypted
const isEncrypted = await isFileEncrypted(inputPath);
if (isEncrypted) {
// Decrypt to temporary location
const tempDir = path.join(path.dirname(inputPath), 'temp');
await fs.mkdir(tempDir, { recursive: true });
const tempPath = path.join(tempDir, `decrypted_${recording.file_name}`);
await decryptFile(inputPath, tempPath);
inputPath = tempPath;
needsCleanup = true;
}
const recordingDuration = recording.duration;
const chunksDir = path.join(path.dirname(recording.file_path), 'chunks');
await fs.mkdir(chunksDir, { recursive: true });
const numChunks = Math.ceil(recordingDuration / (CHUNK_DURATION * 1000));
const chunks = [];
for (let i = 0; i < numChunks; i++) {
const startTime = i * CHUNK_DURATION;
const chunkDuration = Math.min(CHUNK_DURATION, (recordingDuration / 1000) - startTime);
const chunkFileName = `${path.parse(recording.file_name).name}_chunk_${i}.${OUTPUT_FORMAT}`;
const chunkFilePath = path.join(chunksDir, chunkFileName);
// Create chunk using FFmpeg
await createChunk(inputPath, chunkFilePath, startTime, chunkDuration);
// Get chunk file size
const stats = await fs.stat(chunkFilePath);
// Create AudioChunk record
const chunk = await AudioChunk.create({
recording_id: recording.id,
chunk_index: i,
file_path: chunkFilePath,
file_name: chunkFileName,
start_time: startTime * 1000,
end_time: (startTime + chunkDuration) * 1000,
duration: chunkDuration * 1000,
file_size: stats.size,
status: 'completed',
});
chunks.push(chunk);
}
// Clean up temporary decrypted file
if (needsCleanup) {
try {
await fs.unlink(inputPath);
} catch (error) {
console.warn('Failed to clean up temporary decrypted file:', error);
}
}
await recording.updateChunkingStatus('completed');
return chunks;
} catch (error) {
await recording.updateChunkingStatus('failed', error.message);
throw error;
}
}
/**
* Create a single audio chunk using FFmpeg
*/
async function createChunk(inputPath, outputPath, startTime, duration) {
return new Promise((resolve, reject) => {
ffmpeg(inputPath)
.setStartTime(startTime)
.setDuration(duration)
.audioCodec(OUTPUT_CODEC)
.audioFrequency(SAMPLE_RATE)
.audioChannels(1) // Mono for better processing
.toFormat(OUTPUT_FORMAT)
.on('end', () => {
resolve();
})
.on('error', (err) => {
reject(new AppError('FFmpeg chunk creation failed: ' + err.message, 500));
})
.save(outputPath);
});
}
/**
* Clean up chunks for a recording
*/
async function cleanupChunks(recordingId) {
try {
const chunks = await AudioChunk.findByRecording(recordingId);
for (const chunk of chunks) {
try {
await fs.unlink(chunk.file_path);
} catch (error) {
console.warn(`Failed to delete chunk file: ${chunk.file_path}`, error);
}
await chunk.destroy();
}
} catch (error) {
console.error('Error cleaning up chunks:', error);
}
}
/**
* Get chunking progress for a recording
*/
async function getChunkingProgress(recordingId) {
const totalChunks = await AudioChunk.count({
where: { recording_id: recordingId }
});
const completedChunks = await AudioChunk.count({
where: {
recording_id: recordingId,
status: 'completed'
}
});
return {
total: totalChunks,
completed: completedChunks,
progress: totalChunks > 0 ? (completedChunks / totalChunks) * 100 : 0
};
}
module.exports = {
checkFFmpeg,
getAudioDuration,
createChunks,
createChunk,
cleanupChunks,
getChunkingProgress,
}; |
:: Command execute :: | |
--[ c99shell v. 2.5 [PHP 8 Update] [24.05.2025] | Generation time: 0.0032 ]-- |