feat: add pause, resume, and stop download IPC handlers and implement multilingual UI support
This commit is contained in:
+265
-68
@@ -331,89 +331,286 @@ function downloadSingle(url, format, quality, outputDir, browser, onProgress) {
|
||||
});
|
||||
}
|
||||
|
||||
// Download list of items sequentially
|
||||
async function downloadMedia(items, outputDir, browser, onProgress) {
|
||||
console.log(`[DEBUG] Media download requested for ${items.length} items.`);
|
||||
const results = [];
|
||||
const startTime = Date.now();
|
||||
let completedCount = 0;
|
||||
// Module-level Queue State variables
|
||||
let currentProcess = null;
|
||||
let isPaused = false;
|
||||
let isStopped = false;
|
||||
let currentItemIndex = 0;
|
||||
let downloadQueue = [];
|
||||
let queueOutputDir = '';
|
||||
let queueBrowser = null;
|
||||
let queueOnProgressCallback = null;
|
||||
let queueResults = [];
|
||||
let queueStartTime = 0;
|
||||
let activeResolve = null;
|
||||
let activeReject = null;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
console.log(`[DEBUG] Queue processing [${i+1}/${items.length}]: "${item.title}"`);
|
||||
|
||||
// Notify start of this item
|
||||
onProgress({
|
||||
itemId: item.id,
|
||||
index: i,
|
||||
total: items.length,
|
||||
function getQueueStats() {
|
||||
const durationSec = ((Date.now() - queueStartTime) / 1000).toFixed(1);
|
||||
const completedCount = queueResults.filter(r => r.success).length;
|
||||
return {
|
||||
totalItems: downloadQueue.length,
|
||||
completed: completedCount,
|
||||
duration: `${durationSec} saniye`,
|
||||
outputDir: queueOutputDir,
|
||||
results: queueResults
|
||||
};
|
||||
}
|
||||
|
||||
function downloadSingleItemWithProcessRef(item) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let args = [];
|
||||
const outputTemplate = path.join(queueOutputDir, '%(title)s.%(ext)s');
|
||||
const startTime = Date.now();
|
||||
let totalSize = 'Bilinmiyor';
|
||||
|
||||
if (item.format === 'mp3') {
|
||||
const q = item.quality ? `${item.quality}K` : '320K';
|
||||
args = [
|
||||
'--no-playlist',
|
||||
'--ffmpeg-location', FFMPEG_PATH,
|
||||
'-x',
|
||||
'--audio-format', 'mp3',
|
||||
'--audio-quality', q,
|
||||
'-o', outputTemplate
|
||||
];
|
||||
} else {
|
||||
const res = item.quality || '1080';
|
||||
args = [
|
||||
'--no-playlist',
|
||||
'--ffmpeg-location', FFMPEG_PATH,
|
||||
'-f', `bv*[height<=${res}][ext=mp4]+ba[ext=m4a]/b[height<=${res}][ext=mp4]/bv*[height<=${res}]+ba/b[height<=${res}]/bestvideo+bestaudio/best`,
|
||||
'-o', outputTemplate
|
||||
];
|
||||
}
|
||||
|
||||
if (queueBrowser) {
|
||||
args.push('--cookies-from-browser', queueBrowser);
|
||||
}
|
||||
args.push(item.url);
|
||||
|
||||
console.log(`[DEBUG] Spawning single downloader process: ${YTDLP_PATH} ${args.join(' ')}`);
|
||||
currentProcess = spawn(YTDLP_PATH, args);
|
||||
|
||||
currentProcess.stdout.on('data', (data) => {
|
||||
const line = data.toString();
|
||||
|
||||
const sizeMatch = line.match(/of\s+(\d+(?:\.\d+)?\s*[a-zA-Z]+)/);
|
||||
if (sizeMatch) {
|
||||
totalSize = sizeMatch[1];
|
||||
}
|
||||
|
||||
const match = line.match(/\[download\]\s+(\d+\.\d+)%/);
|
||||
if (match) {
|
||||
const percent = parseFloat(match[1]);
|
||||
queueOnProgressCallback({
|
||||
itemId: item.id,
|
||||
index: currentItemIndex,
|
||||
total: downloadQueue.length,
|
||||
percent: percent,
|
||||
status: `İndiriliyor...`,
|
||||
title: item.title
|
||||
});
|
||||
} else if (line.includes('[ExtractAudio]') || line.includes('[Merger]')) {
|
||||
queueOnProgressCallback({
|
||||
itemId: item.id,
|
||||
index: currentItemIndex,
|
||||
total: downloadQueue.length,
|
||||
percent: 95,
|
||||
status: `İşleniyor/Dönüştürülüyor...`,
|
||||
title: item.title
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
currentProcess.stderr.on('data', (data) => {
|
||||
console.warn(`[DEBUG] [yt-dlp stderr]:`, data.toString());
|
||||
});
|
||||
|
||||
currentProcess.on('close', (code) => {
|
||||
console.log(`[DEBUG] Single downloader process exited with code ${code}`);
|
||||
currentProcess = null;
|
||||
if (code === 0) {
|
||||
const durationSec = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
resolve({
|
||||
size: totalSize,
|
||||
duration: `${durationSec} saniye`
|
||||
});
|
||||
} else {
|
||||
reject(new Error(`Download exited with code ${code}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function processQueue() {
|
||||
if (isStopped) {
|
||||
activeResolve({
|
||||
success: false,
|
||||
stopped: true,
|
||||
stats: getQueueStats()
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPaused) {
|
||||
queueOnProgressCallback({
|
||||
itemId: downloadQueue[currentItemIndex].id,
|
||||
index: currentItemIndex,
|
||||
total: downloadQueue.length,
|
||||
percent: 0,
|
||||
status: `İndiriliyor...`,
|
||||
status: `Duraklatıldı`,
|
||||
title: downloadQueue[currentItemIndex].title
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentItemIndex >= downloadQueue.length) {
|
||||
activeResolve({
|
||||
success: true,
|
||||
stats: getQueueStats()
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const item = downloadQueue[currentItemIndex];
|
||||
console.log(`[DEBUG] Queue processing [${currentItemIndex+1}/${downloadQueue.length}]: "${item.title}"`);
|
||||
|
||||
queueOnProgressCallback({
|
||||
itemId: item.id,
|
||||
index: currentItemIndex,
|
||||
total: downloadQueue.length,
|
||||
percent: 0,
|
||||
status: `İndiriliyor...`,
|
||||
title: item.title
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await downloadSingleItemWithProcessRef(item);
|
||||
|
||||
queueResults.push({
|
||||
success: true,
|
||||
item,
|
||||
size: result.size,
|
||||
duration: result.duration
|
||||
});
|
||||
|
||||
queueOnProgressCallback({
|
||||
itemId: item.id,
|
||||
index: currentItemIndex,
|
||||
total: downloadQueue.length,
|
||||
percent: 100,
|
||||
status: `Tamamlandı`,
|
||||
title: item.title
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await downloadSingle(item.url, item.format, item.quality, outputDir, browser, (percent, status) => {
|
||||
onProgress({
|
||||
itemId: item.id,
|
||||
index: i,
|
||||
total: items.length,
|
||||
percent: percent,
|
||||
status: status,
|
||||
title: item.title
|
||||
});
|
||||
});
|
||||
currentItemIndex++;
|
||||
processQueue(); // Process next item
|
||||
|
||||
completedCount++;
|
||||
results.push({
|
||||
success: true,
|
||||
item,
|
||||
size: result.size,
|
||||
duration: result.duration
|
||||
});
|
||||
|
||||
// Notify completion of this item
|
||||
onProgress({
|
||||
itemId: item.id,
|
||||
index: i,
|
||||
total: items.length,
|
||||
percent: 100,
|
||||
status: `Tamamlandı`,
|
||||
title: item.title
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[DEBUG] Queue error on item "${item.title}":`, error);
|
||||
results.push({
|
||||
} catch (error) {
|
||||
if (isStopped) {
|
||||
console.log('[DEBUG] Process aborted by user stop command.');
|
||||
activeResolve({
|
||||
success: false,
|
||||
item,
|
||||
error: error.message
|
||||
stopped: true,
|
||||
stats: getQueueStats()
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onProgress({
|
||||
itemId: item.id,
|
||||
index: i,
|
||||
total: items.length,
|
||||
percent: 0,
|
||||
status: `Hata`,
|
||||
title: item.title,
|
||||
failed: true
|
||||
});
|
||||
if (isPaused) {
|
||||
console.log('[DEBUG] Process suspended by user pause command.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(`[DEBUG] Queue error on item "${item.title}":`, error);
|
||||
queueResults.push({
|
||||
success: false,
|
||||
item,
|
||||
error: error.message
|
||||
});
|
||||
|
||||
queueOnProgressCallback({
|
||||
itemId: item.id,
|
||||
index: currentItemIndex,
|
||||
total: downloadQueue.length,
|
||||
percent: 0,
|
||||
status: `Hata`,
|
||||
title: item.title,
|
||||
failed: true
|
||||
});
|
||||
|
||||
currentItemIndex++;
|
||||
processQueue(); // Continue queue on item error
|
||||
}
|
||||
}
|
||||
|
||||
// Download list of items sequentially
|
||||
async function downloadMedia(items, outputDir, browser, onProgress) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`[DEBUG] Media download requested for ${items.length} items.`);
|
||||
|
||||
downloadQueue = items;
|
||||
queueOutputDir = outputDir;
|
||||
queueBrowser = browser;
|
||||
queueOnProgressCallback = onProgress;
|
||||
isPaused = false;
|
||||
isStopped = false;
|
||||
currentItemIndex = 0;
|
||||
queueResults = [];
|
||||
queueStartTime = Date.now();
|
||||
activeResolve = resolve;
|
||||
activeReject = reject;
|
||||
|
||||
processQueue();
|
||||
});
|
||||
}
|
||||
|
||||
function pauseDownload() {
|
||||
console.log('[DEBUG] Pause request received.');
|
||||
isPaused = true;
|
||||
if (currentProcess) {
|
||||
try {
|
||||
currentProcess.kill('SIGINT');
|
||||
} catch (e) {
|
||||
console.error('[DEBUG] Failed to interrupt process on pause:', e);
|
||||
}
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
const durationSec = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
return {
|
||||
totalItems: items.length,
|
||||
completed: completedCount,
|
||||
duration: `${durationSec} saniye`,
|
||||
outputDir: outputDir,
|
||||
results: results
|
||||
};
|
||||
function resumeDownload() {
|
||||
console.log('[DEBUG] Resume request received.');
|
||||
if (!isPaused) return { success: false, error: 'Not paused' };
|
||||
isPaused = false;
|
||||
processQueue();
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
function stopDownload() {
|
||||
console.log('[DEBUG] Stop request received.');
|
||||
isStopped = true;
|
||||
if (currentProcess) {
|
||||
try {
|
||||
currentProcess.kill('SIGINT');
|
||||
} catch (e) {
|
||||
console.error('[DEBUG] Failed to interrupt process on stop:', e);
|
||||
}
|
||||
} else if (activeResolve) {
|
||||
activeResolve({
|
||||
success: false,
|
||||
stopped: true,
|
||||
stats: getQueueStats()
|
||||
});
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkDependencies,
|
||||
getVideoInfo,
|
||||
downloadMedia
|
||||
downloadMedia,
|
||||
pauseDownload,
|
||||
resumeDownload,
|
||||
stopDownload
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user