@aj-archipelago/cortex 1.3.10 → 1.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.js +15 -0
- package/helper-apps/cortex-file-handler/.env.test +7 -0
- package/helper-apps/cortex-file-handler/.env.test.azure +6 -0
- package/helper-apps/cortex-file-handler/.env.test.gcs +9 -0
- package/helper-apps/cortex-file-handler/blobHandler.js +263 -179
- package/helper-apps/cortex-file-handler/constants.js +107 -0
- package/helper-apps/cortex-file-handler/docHelper.js +4 -1
- package/helper-apps/cortex-file-handler/fileChunker.js +171 -109
- package/helper-apps/cortex-file-handler/helper.js +39 -17
- package/helper-apps/cortex-file-handler/index.js +230 -138
- package/helper-apps/cortex-file-handler/localFileHandler.js +21 -3
- package/helper-apps/cortex-file-handler/package-lock.json +2622 -51
- package/helper-apps/cortex-file-handler/package.json +24 -4
- package/helper-apps/cortex-file-handler/redis.js +9 -18
- package/helper-apps/cortex-file-handler/scripts/setup-azure-container.js +22 -0
- package/helper-apps/cortex-file-handler/scripts/setup-test-containers.js +49 -0
- package/helper-apps/cortex-file-handler/scripts/test-azure.sh +34 -0
- package/helper-apps/cortex-file-handler/scripts/test-gcs.sh +49 -0
- package/helper-apps/cortex-file-handler/start.js +26 -4
- package/helper-apps/cortex-file-handler/tests/docHelper.test.js +148 -0
- package/helper-apps/cortex-file-handler/tests/fileChunker.test.js +322 -0
- package/helper-apps/cortex-file-handler/tests/start.test.js +928 -0
- package/helper-apps/cortex-realtime-voice-server/client/src/chat/components/ScreenshotCapture.tsx +57 -9
- package/helper-apps/cortex-realtime-voice-server/src/SocketServer.ts +35 -22
- package/helper-apps/cortex-realtime-voice-server/src/Tools.ts +65 -14
- package/helper-apps/cortex-realtime-voice-server/src/realtime/client.ts +10 -10
- package/helper-apps/cortex-realtime-voice-server/src/realtime/socket.ts +2 -1
- package/package.json +1 -1
- package/pathways/system/entity/sys_entity_continue.js +1 -1
- package/pathways/system/entity/sys_entity_start.js +1 -0
- package/pathways/system/entity/sys_generator_reasoning.js +1 -1
- package/pathways/system/entity/sys_generator_video_vision.js +2 -1
- package/pathways/system/entity/sys_router_tool.js +6 -4
- package/pathways/system/rest_streaming/sys_openai_chat_o1.js +19 -0
- package/pathways/system/rest_streaming/sys_openai_chat_o1_mini.js +19 -0
- package/server/plugins/openAiReasoningPlugin.js +11 -2
- package/server/plugins/openAiWhisperPlugin.js +9 -13
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export const DOC_EXTENSIONS = [
|
|
2
|
+
".txt",
|
|
3
|
+
".json",
|
|
4
|
+
".csv",
|
|
5
|
+
".md",
|
|
6
|
+
".xml",
|
|
7
|
+
".js",
|
|
8
|
+
".html",
|
|
9
|
+
".css",
|
|
10
|
+
".doc",
|
|
11
|
+
".docx",
|
|
12
|
+
".xls",
|
|
13
|
+
".xlsx"
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export const IMAGE_EXTENSIONS = [
|
|
17
|
+
".jpg",
|
|
18
|
+
".jpeg",
|
|
19
|
+
".png",
|
|
20
|
+
".webp",
|
|
21
|
+
".heic",
|
|
22
|
+
".heif",
|
|
23
|
+
".pdf"
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export const VIDEO_EXTENSIONS = [
|
|
27
|
+
".mp4",
|
|
28
|
+
".mpeg",
|
|
29
|
+
".mov",
|
|
30
|
+
".avi",
|
|
31
|
+
".flv",
|
|
32
|
+
".mpg",
|
|
33
|
+
".webm",
|
|
34
|
+
".wmv",
|
|
35
|
+
".3gp"
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export const AUDIO_EXTENSIONS = [
|
|
39
|
+
".wav",
|
|
40
|
+
".mp3",
|
|
41
|
+
".aac",
|
|
42
|
+
".ogg",
|
|
43
|
+
".flac"
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
export const ACCEPTED_MIME_TYPES = {
|
|
47
|
+
// Document types
|
|
48
|
+
'text/plain': ['.txt'],
|
|
49
|
+
'application/json': ['.json'],
|
|
50
|
+
'text/csv': ['.csv'],
|
|
51
|
+
'text/markdown': ['.md'],
|
|
52
|
+
'application/xml': ['.xml'],
|
|
53
|
+
'text/javascript': ['.js'],
|
|
54
|
+
'text/html': ['.html'],
|
|
55
|
+
'text/css': ['.css'],
|
|
56
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
|
|
57
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'],
|
|
58
|
+
'application/msword': ['.doc'],
|
|
59
|
+
'application/vnd.ms-excel': ['.xls'],
|
|
60
|
+
'application/vnd.ms-word.document.macroEnabled.12': ['.docm'],
|
|
61
|
+
'application/vnd.ms-excel.sheet.macroEnabled.12': ['.xlsm'],
|
|
62
|
+
'application/vnd.ms-word.template.macroEnabled.12': ['.dotm'],
|
|
63
|
+
'application/vnd.ms-excel.template.macroEnabled.12': ['.xltm'],
|
|
64
|
+
|
|
65
|
+
// Image types
|
|
66
|
+
'image/jpeg': ['.jpg', '.jpeg'],
|
|
67
|
+
'image/png': ['.png'],
|
|
68
|
+
'image/webp': ['.webp'],
|
|
69
|
+
'image/heic': ['.heic'],
|
|
70
|
+
'image/heif': ['.heif'],
|
|
71
|
+
'application/pdf': ['.pdf'],
|
|
72
|
+
|
|
73
|
+
// Audio types
|
|
74
|
+
'audio/wav': ['.wav'],
|
|
75
|
+
'audio/mpeg': ['.mp3'],
|
|
76
|
+
'audio/aac': ['.aac'],
|
|
77
|
+
'audio/ogg': ['.ogg'],
|
|
78
|
+
'audio/flac': ['.flac'],
|
|
79
|
+
|
|
80
|
+
// Video types
|
|
81
|
+
'video/mp4': ['.mp4'],
|
|
82
|
+
'video/mpeg': ['.mpeg', '.mpg'],
|
|
83
|
+
'video/quicktime': ['.mov'],
|
|
84
|
+
'video/x-msvideo': ['.avi'],
|
|
85
|
+
'video/x-flv': ['.flv'],
|
|
86
|
+
'video/webm': ['.webm'],
|
|
87
|
+
'video/x-ms-wmv': ['.wmv'],
|
|
88
|
+
'video/3gpp': ['.3gp']
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Helper function to check if a mime type is accepted
|
|
92
|
+
export function isAcceptedMimeType(mimeType) {
|
|
93
|
+
return mimeType in ACCEPTED_MIME_TYPES;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Helper function to get accepted extensions for a mime type
|
|
97
|
+
export function getExtensionsForMimeType(mimeType) {
|
|
98
|
+
return ACCEPTED_MIME_TYPES[mimeType] || [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Helper function to check if an extension is accepted
|
|
102
|
+
export function isAcceptedExtension(extension) {
|
|
103
|
+
return DOC_EXTENSIONS.includes(extension) ||
|
|
104
|
+
IMAGE_EXTENSIONS.includes(extension) ||
|
|
105
|
+
VIDEO_EXTENSIONS.includes(extension) ||
|
|
106
|
+
AUDIO_EXTENSIONS.includes(extension);
|
|
107
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import pdfjsLib from 'pdfjs-dist';
|
|
1
|
+
import * as pdfjsLib from 'pdfjs-dist';
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
3
|
import mammoth from 'mammoth';
|
|
4
4
|
import XLSX from 'xlsx';
|
|
@@ -95,10 +95,13 @@ export async function documentToText(filePath) {
|
|
|
95
95
|
case 'pdf':
|
|
96
96
|
return pdfToText(filePath);
|
|
97
97
|
case 'txt':
|
|
98
|
+
case 'html':
|
|
98
99
|
return txtToText(filePath);
|
|
99
100
|
case 'docx':
|
|
101
|
+
case 'doc':
|
|
100
102
|
return docxToText(filePath);
|
|
101
103
|
case 'xlsx':
|
|
104
|
+
case 'xls':
|
|
102
105
|
return xlsxToText(filePath);
|
|
103
106
|
case 'csv':
|
|
104
107
|
return csvToText(filePath);
|
|
@@ -6,170 +6,232 @@ import os from 'os';
|
|
|
6
6
|
import { promisify } from 'util';
|
|
7
7
|
import axios from 'axios';
|
|
8
8
|
import { ensureEncoded } from './helper.js';
|
|
9
|
-
import
|
|
10
|
-
|
|
9
|
+
import http from 'http';
|
|
10
|
+
import https from 'https';
|
|
11
|
+
import { pipeline } from 'stream/promises';
|
|
11
12
|
|
|
12
13
|
const ffmpegProbe = promisify(ffmpeg.ffprobe);
|
|
13
14
|
|
|
15
|
+
// Temp file management
|
|
16
|
+
const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
17
|
+
const tempDirectories = new Map(); // dir -> { createdAt, requestId }
|
|
18
|
+
|
|
19
|
+
// Temp directory cleanup
|
|
20
|
+
async function cleanupTempDirectories() {
|
|
21
|
+
const tempDir = os.tmpdir();
|
|
22
|
+
|
|
23
|
+
for (const [dir, info] of tempDirectories) {
|
|
24
|
+
try {
|
|
25
|
+
// Cleanup directories older than 1 hour
|
|
26
|
+
if (Date.now() - info.createdAt > 60 * 60 * 1000) {
|
|
27
|
+
await fs.promises.rm(dir, { recursive: true, force: true });
|
|
28
|
+
tempDirectories.delete(dir);
|
|
29
|
+
console.log(`Cleaned up old temp directory: ${dir}`);
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
// Directory might be gone
|
|
33
|
+
tempDirectories.delete(dir);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Setup periodic cleanup
|
|
39
|
+
setInterval(async () => {
|
|
40
|
+
try {
|
|
41
|
+
await cleanupTempDirectories();
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error('Error during periodic cleanup:', err);
|
|
44
|
+
}
|
|
45
|
+
}, CLEANUP_INTERVAL_MS);
|
|
14
46
|
|
|
47
|
+
// Process a single chunk with streaming
|
|
15
48
|
async function processChunk(inputPath, outputFileName, start, duration) {
|
|
16
49
|
return new Promise((resolve, reject) => {
|
|
17
|
-
ffmpeg(inputPath)
|
|
50
|
+
const command = ffmpeg(inputPath)
|
|
18
51
|
.seekInput(start)
|
|
19
52
|
.duration(duration)
|
|
20
53
|
.format('mp3')
|
|
21
54
|
.audioCodec('libmp3lame')
|
|
22
55
|
.audioBitrate(128)
|
|
23
|
-
.on('start', (
|
|
24
|
-
console.log(`
|
|
56
|
+
.on('start', () => {
|
|
57
|
+
console.log(`Processing chunk: ${start}s -> ${start + duration}s`);
|
|
25
58
|
})
|
|
26
|
-
.on('
|
|
27
|
-
|
|
59
|
+
.on('progress', (progress) => {
|
|
60
|
+
if (progress.percent) {
|
|
61
|
+
console.log(`Chunk progress: ${progress.percent}%`);
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
.on('error', (err, stdout, stderr) => {
|
|
65
|
+
console.error('FFmpeg error:', err.message);
|
|
66
|
+
if (stdout) console.log('FFmpeg stdout:', stdout);
|
|
67
|
+
if (stderr) console.error('FFmpeg stderr:', stderr);
|
|
28
68
|
reject(err);
|
|
29
69
|
})
|
|
30
70
|
.on('end', () => {
|
|
31
|
-
console.log(`
|
|
71
|
+
console.log(`Chunk complete: ${outputFileName}`);
|
|
32
72
|
resolve(outputFileName);
|
|
33
|
-
})
|
|
34
|
-
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Use pipe() to handle streaming
|
|
76
|
+
command.pipe(fs.createWriteStream(outputFileName), { end: true });
|
|
35
77
|
});
|
|
36
78
|
}
|
|
37
79
|
|
|
38
80
|
const generateUniqueFolderName = () => {
|
|
39
81
|
const uniqueFolderName = uuidv4();
|
|
40
|
-
const tempFolderPath = os.tmpdir();
|
|
41
|
-
|
|
42
|
-
return uniqueOutputPath;
|
|
82
|
+
const tempFolderPath = os.tmpdir();
|
|
83
|
+
return path.join(tempFolderPath, uniqueFolderName);
|
|
43
84
|
}
|
|
44
85
|
|
|
45
86
|
async function downloadFile(url, outputPath) {
|
|
46
87
|
try {
|
|
47
88
|
let response;
|
|
48
89
|
try {
|
|
49
|
-
response = await axios.get(decodeURIComponent(url), {
|
|
90
|
+
response = await axios.get(decodeURIComponent(url), {
|
|
91
|
+
responseType: 'stream',
|
|
92
|
+
// Add timeout and maxContentLength
|
|
93
|
+
timeout: 30000,
|
|
94
|
+
maxContentLength: Infinity,
|
|
95
|
+
// Enable streaming download
|
|
96
|
+
decompress: true,
|
|
97
|
+
// Use a smaller chunk size for better memory usage
|
|
98
|
+
httpAgent: new http.Agent({ keepAlive: true }),
|
|
99
|
+
httpsAgent: new https.Agent({ keepAlive: true })
|
|
100
|
+
});
|
|
50
101
|
} catch (error) {
|
|
51
|
-
response = await axios.get(url, {
|
|
102
|
+
response = await axios.get(url, {
|
|
103
|
+
responseType: 'stream',
|
|
104
|
+
timeout: 30000,
|
|
105
|
+
maxContentLength: Infinity,
|
|
106
|
+
decompress: true,
|
|
107
|
+
httpAgent: new http.Agent({ keepAlive: true }),
|
|
108
|
+
httpsAgent: new https.Agent({ keepAlive: true })
|
|
109
|
+
});
|
|
52
110
|
}
|
|
53
111
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// Pipe the response data into the file stream
|
|
60
|
-
response.data.pipe(fileStream);
|
|
61
|
-
|
|
62
|
-
// Wait for the file stream to finish writing
|
|
63
|
-
await new Promise((resolve, reject) => {
|
|
64
|
-
fileStream.on('finish', resolve);
|
|
65
|
-
fileStream.on('error', reject);
|
|
66
|
-
});
|
|
112
|
+
const writer = fs.createWriteStream(outputPath);
|
|
113
|
+
|
|
114
|
+
// Use pipeline for better error handling and memory management
|
|
115
|
+
await pipeline(response.data, writer);
|
|
67
116
|
|
|
68
|
-
|
|
117
|
+
if (!fs.existsSync(outputPath) || fs.statSync(outputPath).size === 0) {
|
|
118
|
+
throw new Error('Download failed or file is empty');
|
|
119
|
+
}
|
|
69
120
|
} catch (error) {
|
|
70
|
-
|
|
121
|
+
if (fs.existsSync(outputPath)) {
|
|
122
|
+
fs.unlinkSync(outputPath);
|
|
123
|
+
}
|
|
71
124
|
throw error;
|
|
72
125
|
}
|
|
73
126
|
}
|
|
74
127
|
|
|
75
|
-
|
|
76
|
-
|
|
128
|
+
async function splitMediaFile(inputPath, chunkDurationInSeconds = 500, requestId = uuidv4()) {
|
|
129
|
+
let tempPath = null;
|
|
130
|
+
let uniqueOutputPath = null;
|
|
131
|
+
let inputStream = null;
|
|
132
|
+
|
|
77
133
|
try {
|
|
78
|
-
|
|
79
|
-
const uniqueOutputPath = generateUniqueFolderName();
|
|
134
|
+
uniqueOutputPath = generateUniqueFolderName();
|
|
80
135
|
fs.mkdirSync(uniqueOutputPath, { recursive: true });
|
|
136
|
+
|
|
137
|
+
tempDirectories.set(uniqueOutputPath, {
|
|
138
|
+
createdAt: Date.now(),
|
|
139
|
+
requestId
|
|
140
|
+
});
|
|
81
141
|
|
|
82
|
-
//
|
|
142
|
+
// Handle URL downloads with streaming
|
|
83
143
|
const isUrl = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i.test(inputPath);
|
|
84
144
|
if (isUrl) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const extension = path.extname(originalFileName); // Preserve the file extension
|
|
93
|
-
const basename = path.basename(originalFileName, extension); // Get the filename without the extension
|
|
94
|
-
truncatedFileName = basename.substring(0, maxLength) + extension; // Truncate the filename and append the extension
|
|
95
|
-
}
|
|
145
|
+
const urlObj = new URL(ensureEncoded(inputPath));
|
|
146
|
+
const originalFileName = path.basename(urlObj.pathname) || 'downloaded_file';
|
|
147
|
+
tempPath = path.join(uniqueOutputPath, originalFileName);
|
|
148
|
+
console.log('Downloading file to:', tempPath);
|
|
149
|
+
await downloadFile(inputPath, tempPath);
|
|
150
|
+
inputPath = tempPath;
|
|
151
|
+
}
|
|
96
152
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
inputPath = downloadPath;
|
|
153
|
+
inputPath = path.resolve(inputPath);
|
|
154
|
+
if (!fs.existsSync(inputPath)) {
|
|
155
|
+
throw new Error(`Input file not found: ${inputPath}`);
|
|
101
156
|
}
|
|
102
157
|
|
|
158
|
+
// Use a larger chunk size for better throughput while still managing memory
|
|
159
|
+
inputStream = fs.createReadStream(inputPath, {
|
|
160
|
+
highWaterMark: 4 * 1024 * 1024, // 4MB chunks
|
|
161
|
+
autoClose: true
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
console.log('Probing file:', inputPath);
|
|
103
165
|
const metadata = await ffmpegProbe(inputPath);
|
|
166
|
+
if (!metadata?.format?.duration) {
|
|
167
|
+
throw new Error('Invalid media file or unable to determine duration');
|
|
168
|
+
}
|
|
169
|
+
|
|
104
170
|
const duration = metadata.format.duration;
|
|
105
171
|
const numChunks = Math.ceil((duration - 1) / chunkDurationInSeconds);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
172
|
+
console.log(`Processing ${numChunks} chunks of ${chunkDurationInSeconds} seconds each`);
|
|
173
|
+
|
|
174
|
+
const chunkResults = new Array(numChunks); // Pre-allocate array to maintain order
|
|
175
|
+
const chunkOffsets = new Array(numChunks); // Pre-allocate offsets array
|
|
176
|
+
|
|
177
|
+
// Process chunks in parallel with a concurrency limit
|
|
178
|
+
const CONCURRENT_CHUNKS = 3; // Process 3 chunks at a time
|
|
179
|
+
for (let i = 0; i < numChunks; i += CONCURRENT_CHUNKS) {
|
|
180
|
+
const chunkBatch = [];
|
|
181
|
+
for (let j = 0; j < CONCURRENT_CHUNKS && i + j < numChunks; j++) {
|
|
182
|
+
const chunkIndex = i + j;
|
|
183
|
+
const outputFileName = path.join(uniqueOutputPath, `chunk-${chunkIndex + 1}-${path.parse(inputPath).name}.mp3`);
|
|
184
|
+
const offset = chunkIndex * chunkDurationInSeconds;
|
|
185
|
+
|
|
186
|
+
chunkBatch.push(processChunk(inputPath, outputFileName, offset, chunkDurationInSeconds)
|
|
187
|
+
.then(result => {
|
|
188
|
+
chunkResults[chunkIndex] = result; // Store in correct position
|
|
189
|
+
chunkOffsets[chunkIndex] = offset; // Store offset in correct position
|
|
190
|
+
console.log(`Completed chunk ${chunkIndex + 1}/${numChunks}`);
|
|
191
|
+
return result;
|
|
192
|
+
})
|
|
193
|
+
.catch(error => {
|
|
194
|
+
console.error(`Failed to process chunk ${chunkIndex + 1}:`, error);
|
|
195
|
+
return null;
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
115
198
|
|
|
116
|
-
|
|
117
|
-
|
|
199
|
+
// Wait for the current batch to complete before starting the next
|
|
200
|
+
await Promise.all(chunkBatch);
|
|
118
201
|
}
|
|
119
202
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
console.error(msg, err);
|
|
124
|
-
throw new Error(msg);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const ytdlDownload = async (url, filename, video = false) => {
|
|
129
|
-
return new Promise((resolve, reject) => {
|
|
130
|
-
const videoOptions = video
|
|
131
|
-
? { filter: 'audioandvideo' } // audio and video
|
|
132
|
-
: { quality: 'highestaudio' }; // audio only
|
|
133
|
-
|
|
134
|
-
const encodedUrl = encodeURI(url);
|
|
135
|
-
const videoStream = ytdl(encodedUrl, videoOptions);
|
|
136
|
-
let lastLoggedTime = Date.now();
|
|
203
|
+
// Filter out any failed chunks
|
|
204
|
+
const validChunks = chunkResults.filter(Boolean);
|
|
205
|
+
const validOffsets = chunkOffsets.filter((_, index) => chunkResults[index]);
|
|
137
206
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
207
|
+
if (validChunks.length === 0) {
|
|
208
|
+
throw new Error('No chunks were successfully processed');
|
|
209
|
+
}
|
|
141
210
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
211
|
+
return { chunkPromises: validChunks, chunkOffsets: validOffsets, uniqueOutputPath };
|
|
212
|
+
} catch (err) {
|
|
213
|
+
if (uniqueOutputPath && fs.existsSync(uniqueOutputPath)) {
|
|
214
|
+
try {
|
|
215
|
+
fs.rmSync(uniqueOutputPath, { recursive: true, force: true });
|
|
216
|
+
tempDirectories.delete(uniqueOutputPath);
|
|
217
|
+
} catch (cleanupErr) {
|
|
218
|
+
console.error('Error during cleanup:', cleanupErr);
|
|
148
219
|
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function processYoutubeUrl(url, video=false) {
|
|
162
|
-
try {
|
|
163
|
-
const outputFormat = video ? '.mp4' : '.mp3';
|
|
164
|
-
const outputFileName = path.join(os.tmpdir(), `${uuidv4()}${outputFormat}`);
|
|
165
|
-
await ytdlDownload(url, outputFileName, video);
|
|
166
|
-
return outputFileName;
|
|
167
|
-
} catch (e) {
|
|
168
|
-
console.log(e);
|
|
169
|
-
throw new Error(`Error processing YouTube video, YouTube downloader might be outdated or blocked. ${e.message}`);
|
|
220
|
+
}
|
|
221
|
+
console.error('Error in splitMediaFile:', err);
|
|
222
|
+
throw new Error(`Error processing media file: ${err.message}`);
|
|
223
|
+
} finally {
|
|
224
|
+
if (inputStream) {
|
|
225
|
+
try {
|
|
226
|
+
inputStream.destroy();
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.error('Error closing input stream:', err);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
170
231
|
}
|
|
171
232
|
}
|
|
172
233
|
|
|
173
234
|
export {
|
|
174
|
-
splitMediaFile,
|
|
235
|
+
splitMediaFile,
|
|
236
|
+
downloadFile
|
|
175
237
|
};
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
+
import { ACCEPTED_MIME_TYPES } from './constants.js';
|
|
3
|
+
import path from 'path';
|
|
2
4
|
|
|
3
|
-
function
|
|
4
|
-
const regex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/.+$/;
|
|
5
|
-
return regex.test(url);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
async function deleteTempPath(path) {
|
|
5
|
+
export async function deleteTempPath(path) {
|
|
9
6
|
try {
|
|
10
7
|
if (!path) {
|
|
11
8
|
console.log('Temporary path is not defined.');
|
|
@@ -28,18 +25,43 @@ async function deleteTempPath(path) {
|
|
|
28
25
|
}
|
|
29
26
|
}
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
// Get the first extension for a given mime type
|
|
29
|
+
export function getExtensionForMimeType(mimeType) {
|
|
30
|
+
if (!mimeType) return '';
|
|
31
|
+
const cleanMimeType = mimeType.split(';')[0].trim();
|
|
32
|
+
const extensions = ACCEPTED_MIME_TYPES[cleanMimeType];
|
|
33
|
+
return extensions ? extensions[0] : '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Ensure a filename has the correct extension based on its mime type
|
|
37
|
+
export function ensureFileExtension(filename, mimeType) {
|
|
38
|
+
if (!mimeType) return filename;
|
|
39
|
+
|
|
40
|
+
const extension = getExtensionForMimeType(mimeType);
|
|
41
|
+
if (!extension) return filename;
|
|
42
|
+
|
|
43
|
+
// If filename already has this extension, return as is
|
|
44
|
+
if (filename.toLowerCase().endsWith(extension)) {
|
|
45
|
+
return filename;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Get the current extension if any
|
|
49
|
+
const currentExt = path.extname(filename);
|
|
50
|
+
|
|
51
|
+
// If there's no current extension, just append the new one
|
|
52
|
+
if (!currentExt) {
|
|
53
|
+
return `${filename}${extension}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Replace the current extension with the new one
|
|
57
|
+
return filename.slice(0, -currentExt.length) + extension;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function ensureEncoded(url) {
|
|
32
61
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
return url;
|
|
38
|
-
} catch (e) {
|
|
62
|
+
return decodeURIComponent(url) !== url ? url : encodeURI(url);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('Error encoding URL:', error);
|
|
39
65
|
return url;
|
|
40
66
|
}
|
|
41
67
|
}
|
|
42
|
-
|
|
43
|
-
export {
|
|
44
|
-
isValidYoutubeUrl, deleteTempPath, ensureEncoded
|
|
45
|
-
}
|