feat: add pause, resume, and stop download IPC handlers and implement multilingual UI support
This commit is contained in:
@@ -93,6 +93,21 @@ ipcMain.handle('download-media', async (event, { items, browser }) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// IPC Handler: Pause Download
|
||||||
|
ipcMain.handle('pause-download', () => {
|
||||||
|
return downloader.pauseDownload();
|
||||||
|
});
|
||||||
|
|
||||||
|
// IPC Handler: Resume Download
|
||||||
|
ipcMain.handle('resume-download', () => {
|
||||||
|
return downloader.resumeDownload();
|
||||||
|
});
|
||||||
|
|
||||||
|
// IPC Handler: Stop Download
|
||||||
|
ipcMain.handle('stop-download', () => {
|
||||||
|
return downloader.stopDownload();
|
||||||
|
});
|
||||||
|
|
||||||
// IPC Handler: Open Path in File Explorer
|
// IPC Handler: Open Path in File Explorer
|
||||||
ipcMain.handle('open-path', async (event, folderPath) => {
|
ipcMain.handle('open-path', async (event, folderPath) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -246,6 +246,22 @@
|
|||||||
<p id="download-subtext" class="subtext" style="margin-top: 0; font-size: 0.7rem;">Lütfen bekleyin...</p>
|
<p id="download-subtext" class="subtext" style="margin-top: 0; font-size: 0.7rem;">Lütfen bekleyin...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Control Buttons (Pause / Resume / Stop) -->
|
||||||
|
<div class="download-controls-row" style="display: flex; gap: 8px; margin-top: 8px; justify-content: flex-end;">
|
||||||
|
<button id="dl-pause-btn" class="dl-ctrl-btn" style="background: rgba(249, 203, 40, 0.12); border: 1px solid rgba(249, 203, 40, 0.25); color: #f9cb28; font-size: 0.72rem; padding: 4px 10px; border-radius: 6px; cursor: pointer; display: flex; align-items: center; gap: 4px; font-weight: 600;">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="width: 11px; height: 11px;"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>
|
||||||
|
<span data-i18n="btn_pause">Duraklat</span>
|
||||||
|
</button>
|
||||||
|
<button id="dl-resume-btn" class="dl-ctrl-btn" style="background: rgba(0, 255, 135, 0.12); border: 1px solid rgba(0, 255, 135, 0.25); color: #00ff87; font-size: 0.72rem; padding: 4px 10px; border-radius: 6px; cursor: pointer; display: none; align-items: center; gap: 4px; font-weight: 600;">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="width: 11px; height: 11px;"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
|
||||||
|
<span data-i18n="btn_resume">Devam Et</span>
|
||||||
|
</button>
|
||||||
|
<button id="dl-stop-btn" class="dl-ctrl-btn" style="background: rgba(255, 77, 77, 0.12); border: 1px solid rgba(255, 77, 77, 0.25); color: #ff4d4d; font-size: 0.72rem; padding: 4px 10px; border-radius: 6px; cursor: pointer; display: flex; align-items: center; gap: 4px; font-weight: 600;">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="width: 11px; height: 11px;"><rect x="4" y="4" width="16" height="16" rx="2" ry="2"></rect></svg>
|
||||||
|
<span data-i18n="btn_stop">Durdur</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="download-btn" class="glow-button full-width">
|
<button id="download-btn" class="glow-button full-width">
|
||||||
|
|||||||
+4
-1
@@ -71,5 +71,8 @@
|
|||||||
"min_suffix_short": "د",
|
"min_suffix_short": "د",
|
||||||
"sec_suffix_short": "ث",
|
"sec_suffix_short": "ث",
|
||||||
"hr_suffix_short": "س",
|
"hr_suffix_short": "س",
|
||||||
"overall_status_label": "التقدم الإجمالي"
|
"overall_status_label": "التقدم الإجمالي",
|
||||||
|
"btn_pause": "إيقاف مؤقت",
|
||||||
|
"btn_resume": "استئناف",
|
||||||
|
"btn_stop": "إيقاف"
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -71,5 +71,8 @@
|
|||||||
"min_suffix_short": "Min.",
|
"min_suffix_short": "Min.",
|
||||||
"sec_suffix_short": "Sek.",
|
"sec_suffix_short": "Sek.",
|
||||||
"hr_suffix_short": "Std.",
|
"hr_suffix_short": "Std.",
|
||||||
"overall_status_label": "Gesamtfortschritt"
|
"overall_status_label": "Gesamtfortschritt",
|
||||||
|
"btn_pause": "Pause",
|
||||||
|
"btn_resume": "Fortsetzen",
|
||||||
|
"btn_stop": "Stoppen"
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -71,5 +71,8 @@
|
|||||||
"min_suffix_short": "min",
|
"min_suffix_short": "min",
|
||||||
"sec_suffix_short": "sec",
|
"sec_suffix_short": "sec",
|
||||||
"hr_suffix_short": "hr",
|
"hr_suffix_short": "hr",
|
||||||
"overall_status_label": "Overall Progress"
|
"overall_status_label": "Overall Progress",
|
||||||
|
"btn_pause": "Pause",
|
||||||
|
"btn_resume": "Resume",
|
||||||
|
"btn_stop": "Stop"
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -71,5 +71,8 @@
|
|||||||
"min_suffix_short": "мин.",
|
"min_suffix_short": "мин.",
|
||||||
"sec_suffix_short": "сек.",
|
"sec_suffix_short": "сек.",
|
||||||
"hr_suffix_short": "ч.",
|
"hr_suffix_short": "ч.",
|
||||||
"overall_status_label": "Общий прогресс"
|
"overall_status_label": "Общий прогресс",
|
||||||
|
"btn_pause": "Пауза",
|
||||||
|
"btn_resume": "Продолжить",
|
||||||
|
"btn_stop": "Остановить"
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -71,5 +71,8 @@
|
|||||||
"min_suffix_short": "dk",
|
"min_suffix_short": "dk",
|
||||||
"sec_suffix_short": "sn",
|
"sec_suffix_short": "sn",
|
||||||
"hr_suffix_short": "sa",
|
"hr_suffix_short": "sa",
|
||||||
"overall_status_label": "Genel İlerleme"
|
"overall_status_label": "Genel İlerleme",
|
||||||
|
"btn_pause": "Duraklat",
|
||||||
|
"btn_resume": "Devam Et",
|
||||||
|
"btn_stop": "Durdur"
|
||||||
}
|
}
|
||||||
|
|||||||
+240
-43
@@ -331,89 +331,286 @@ function downloadSingle(url, format, quality, outputDir, browser, onProgress) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download list of items sequentially
|
// Module-level Queue State variables
|
||||||
async function downloadMedia(items, outputDir, browser, onProgress) {
|
let currentProcess = null;
|
||||||
console.log(`[DEBUG] Media download requested for ${items.length} items.`);
|
let isPaused = false;
|
||||||
const results = [];
|
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;
|
||||||
|
|
||||||
|
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();
|
const startTime = Date.now();
|
||||||
let completedCount = 0;
|
let totalSize = 'Bilinmiyor';
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
if (item.format === 'mp3') {
|
||||||
const item = items[i];
|
const q = item.quality ? `${item.quality}K` : '320K';
|
||||||
console.log(`[DEBUG] Queue processing [${i+1}/${items.length}]: "${item.title}"`);
|
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
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// Notify start of this item
|
if (queueBrowser) {
|
||||||
onProgress({
|
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,
|
itemId: item.id,
|
||||||
index: i,
|
index: currentItemIndex,
|
||||||
total: items.length,
|
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: `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,
|
percent: 0,
|
||||||
status: `İndiriliyor...`,
|
status: `İndiriliyor...`,
|
||||||
title: item.title
|
title: item.title
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await downloadSingle(item.url, item.format, item.quality, outputDir, browser, (percent, status) => {
|
const result = await downloadSingleItemWithProcessRef(item);
|
||||||
onProgress({
|
|
||||||
itemId: item.id,
|
|
||||||
index: i,
|
|
||||||
total: items.length,
|
|
||||||
percent: percent,
|
|
||||||
status: status,
|
|
||||||
title: item.title
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
completedCount++;
|
queueResults.push({
|
||||||
results.push({
|
|
||||||
success: true,
|
success: true,
|
||||||
item,
|
item,
|
||||||
size: result.size,
|
size: result.size,
|
||||||
duration: result.duration
|
duration: result.duration
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify completion of this item
|
queueOnProgressCallback({
|
||||||
onProgress({
|
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
index: i,
|
index: currentItemIndex,
|
||||||
total: items.length,
|
total: downloadQueue.length,
|
||||||
percent: 100,
|
percent: 100,
|
||||||
status: `Tamamlandı`,
|
status: `Tamamlandı`,
|
||||||
title: item.title
|
title: item.title
|
||||||
});
|
});
|
||||||
|
|
||||||
|
currentItemIndex++;
|
||||||
|
processQueue(); // Process next item
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (isStopped) {
|
||||||
|
console.log('[DEBUG] Process aborted by user stop command.');
|
||||||
|
activeResolve({
|
||||||
|
success: false,
|
||||||
|
stopped: true,
|
||||||
|
stats: getQueueStats()
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPaused) {
|
||||||
|
console.log('[DEBUG] Process suspended by user pause command.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.error(`[DEBUG] Queue error on item "${item.title}":`, error);
|
console.error(`[DEBUG] Queue error on item "${item.title}":`, error);
|
||||||
results.push({
|
queueResults.push({
|
||||||
success: false,
|
success: false,
|
||||||
item,
|
item,
|
||||||
error: error.message
|
error: error.message
|
||||||
});
|
});
|
||||||
|
|
||||||
onProgress({
|
queueOnProgressCallback({
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
index: i,
|
index: currentItemIndex,
|
||||||
total: items.length,
|
total: downloadQueue.length,
|
||||||
percent: 0,
|
percent: 0,
|
||||||
status: `Hata`,
|
status: `Hata`,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
failed: true
|
failed: true
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const durationSec = ((Date.now() - startTime) / 1000).toFixed(1);
|
currentItemIndex++;
|
||||||
return {
|
processQueue(); // Continue queue on item error
|
||||||
totalItems: items.length,
|
}
|
||||||
completed: completedCount,
|
}
|
||||||
duration: `${durationSec} saniye`,
|
|
||||||
outputDir: outputDir,
|
// Download list of items sequentially
|
||||||
results: results
|
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 };
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = {
|
module.exports = {
|
||||||
checkDependencies,
|
checkDependencies,
|
||||||
getVideoInfo,
|
getVideoInfo,
|
||||||
downloadMedia
|
downloadMedia,
|
||||||
|
pauseDownload,
|
||||||
|
resumeDownload,
|
||||||
|
stopDownload
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user