feat: implement automatic yt-dlp and ffmpeg dependency management and media downloading logic
This commit is contained in:
@@ -70,9 +70,9 @@ ipcMain.handle('check-dependencies', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// IPC Handler: Get Video Details
|
// IPC Handler: Get Video Details
|
||||||
ipcMain.handle('get-video-info', async (event, url) => {
|
ipcMain.handle('get-video-info', async (event, { url, browser }) => {
|
||||||
try {
|
try {
|
||||||
const info = await downloader.getVideoInfo(url);
|
const info = await downloader.getVideoInfo(url, browser);
|
||||||
return { success: true, info };
|
return { success: true, info };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
@@ -80,13 +80,24 @@ ipcMain.handle('get-video-info', async (event, url) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// IPC Handler: Download Media
|
// IPC Handler: Download Media
|
||||||
ipcMain.handle('download-media', async (event, { url, format, quality }) => {
|
ipcMain.handle('download-media', async (event, { url, format, quality, browser }) => {
|
||||||
try {
|
try {
|
||||||
await downloader.downloadMedia(url, format, quality, selectedOutputDir, (percent, status) => {
|
const stats = await downloader.downloadMedia(url, format, quality, selectedOutputDir, browser, (percent, status) => {
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.webContents.send('download-progress', { percent, status });
|
mainWindow.webContents.send('download-progress', { percent, status });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return { success: true, stats };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// IPC Handler: Open Path in File Explorer
|
||||||
|
ipcMain.handle('open-path', async (event, folderPath) => {
|
||||||
|
try {
|
||||||
|
const { shell } = require('electron');
|
||||||
|
await shell.openPath(folderPath);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
|
|||||||
+3
-2
@@ -3,11 +3,12 @@ const { contextBridge, ipcRenderer } = require('electron');
|
|||||||
contextBridge.exposeInMainWorld('api', {
|
contextBridge.exposeInMainWorld('api', {
|
||||||
checkDependencies: () => ipcRenderer.invoke('check-dependencies'),
|
checkDependencies: () => ipcRenderer.invoke('check-dependencies'),
|
||||||
onDependencyStatus: (callback) => ipcRenderer.on('dependency-status', (_, data) => callback(data)),
|
onDependencyStatus: (callback) => ipcRenderer.on('dependency-status', (_, data) => callback(data)),
|
||||||
getVideoInfo: (url) => ipcRenderer.invoke('get-video-info', url),
|
getVideoInfo: (url, browser) => ipcRenderer.invoke('get-video-info', { url, browser }),
|
||||||
downloadMedia: (url, format, quality) => ipcRenderer.invoke('download-media', { url, format, quality }),
|
downloadMedia: (url, format, quality, browser) => ipcRenderer.invoke('download-media', { url, format, quality, browser }),
|
||||||
onDownloadProgress: (callback) => ipcRenderer.on('download-progress', (_, data) => callback(data)),
|
onDownloadProgress: (callback) => ipcRenderer.on('download-progress', (_, data) => callback(data)),
|
||||||
selectDirectory: () => ipcRenderer.invoke('select-directory'),
|
selectDirectory: () => ipcRenderer.invoke('select-directory'),
|
||||||
getDownloadsFolder: () => ipcRenderer.invoke('get-downloads-folder'),
|
getDownloadsFolder: () => ipcRenderer.invoke('get-downloads-folder'),
|
||||||
|
openPath: (path) => ipcRenderer.invoke('open-path', path),
|
||||||
minimizeWindow: () => ipcRenderer.send('window-minimize'),
|
minimizeWindow: () => ipcRenderer.send('window-minimize'),
|
||||||
closeWindow: () => ipcRenderer.send('window-close')
|
closeWindow: () => ipcRenderer.send('window-close')
|
||||||
});
|
});
|
||||||
|
|||||||
+16
-3
@@ -3,11 +3,13 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>VibeDownloader - Premium YouTube & MP3 Downloader</title>
|
<title>VibeDownloader - Premium Social Media & MP3 Downloader</title>
|
||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||||
|
<!-- SweetAlert2 -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Custom Glassmorphic Titlebar -->
|
<!-- Custom Glassmorphic Titlebar -->
|
||||||
@@ -64,12 +66,12 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="glow-text">VibeDownloader</h1>
|
<h1 class="glow-text">VibeDownloader</h1>
|
||||||
<p class="subtitle">YouTube videolarını saniyeler içinde yüksek kaliteli MP4 veya MP3 olarak indirin</p>
|
<p class="subtitle">YouTube, X/Twitter, Instagram, TikTok ve tüm popüler sitelerden video/ses indirin</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="glass-card url-section">
|
<section class="glass-card url-section">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" id="url-input" placeholder="YouTube video veya oynatma listesi bağlantısı yapıştırın..." autocomplete="off">
|
<input type="text" id="url-input" placeholder="Sosyal medya veya video platformu bağlantısı yapıştırın (YouTube, Instagram, TikTok, X/Twitter vb.)..." autocomplete="off">
|
||||||
<button id="analyze-btn" class="glow-button">
|
<button id="analyze-btn" class="glow-button">
|
||||||
<span>Analiz Et</span>
|
<span>Analiz Et</span>
|
||||||
<svg class="btn-icon animate-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: none;">
|
<svg class="btn-icon animate-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: none;">
|
||||||
@@ -83,6 +85,17 @@
|
|||||||
<span id="output-path" class="folder-path">Yükleniyor...</span>
|
<span id="output-path" class="folder-path">Yükleniyor...</span>
|
||||||
<button id="change-folder-btn" class="text-btn">Değiştir</button>
|
<button id="change-folder-btn" class="text-btn">Değiştir</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="cookies-row" style="margin-top: 15px; display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 0.85em; opacity: 0.85; border-top: 1px solid rgba(255,255,255,0.06); padding-top: 12px; flex-wrap: wrap;">
|
||||||
|
<input type="checkbox" id="use-cookies" style="cursor: pointer; accent-color: #7000ff; width: 15px; height: 15px;">
|
||||||
|
<label for="use-cookies" style="cursor: pointer; color: #fff; user-select: none;">Giriş gerektiren içerikler (Örn. Instagram Hikayeleri) için tarayıcı oturumunu kullan:</label>
|
||||||
|
<select id="cookie-browser" style="background: rgba(23, 20, 38, 0.8); color: #fff; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 4px; padding: 2px 8px; cursor: pointer; outline: none; font-family: inherit; font-size: 0.95em;">
|
||||||
|
<option value="chrome">Chrome</option>
|
||||||
|
<option value="edge">Edge</option>
|
||||||
|
<option value="firefox">Firefox</option>
|
||||||
|
<option value="brave">Brave</option>
|
||||||
|
<option value="opera">Opera</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Error Display in Step 1 -->
|
<!-- Error Display in Step 1 -->
|
||||||
|
|||||||
+39
-2
@@ -9,6 +9,8 @@ const urlInput = document.getElementById('url-input');
|
|||||||
const analyzeBtn = document.getElementById('analyze-btn');
|
const analyzeBtn = document.getElementById('analyze-btn');
|
||||||
const outputPath = document.getElementById('output-path');
|
const outputPath = document.getElementById('output-path');
|
||||||
const changeFolderBtn = document.getElementById('change-folder-btn');
|
const changeFolderBtn = document.getElementById('change-folder-btn');
|
||||||
|
const useCookiesCheckbox = document.getElementById('use-cookies');
|
||||||
|
const cookieBrowserSelect = document.getElementById('cookie-browser');
|
||||||
|
|
||||||
const errorCard = document.getElementById('error-card');
|
const errorCard = document.getElementById('error-card');
|
||||||
const errorMessage = document.getElementById('error-message');
|
const errorMessage = document.getElementById('error-message');
|
||||||
@@ -167,7 +169,8 @@ analyzeBtn.addEventListener('click', async () => {
|
|||||||
analyzeIcon.style.display = 'inline-block';
|
analyzeIcon.style.display = 'inline-block';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await window.api.getVideoInfo(url);
|
const browser = useCookiesCheckbox.checked ? cookieBrowserSelect.value : null;
|
||||||
|
const result = await window.api.getVideoInfo(url, browser);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
analyzedUrl = url;
|
analyzedUrl = url;
|
||||||
currentVideoData = result.info;
|
currentVideoData = result.info;
|
||||||
@@ -245,7 +248,8 @@ downloadBtn.addEventListener('click', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await window.api.downloadMedia(analyzedUrl, currentFormat, currentQuality);
|
const browser = useCookiesCheckbox.checked ? cookieBrowserSelect.value : null;
|
||||||
|
const result = await window.api.downloadMedia(analyzedUrl, currentFormat, currentQuality, browser);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
downloadStatus.textContent = 'Tamamlandı!';
|
downloadStatus.textContent = 'Tamamlandı!';
|
||||||
downloadPercent.textContent = '100%';
|
downloadPercent.textContent = '100%';
|
||||||
@@ -263,6 +267,39 @@ downloadBtn.addEventListener('click', async () => {
|
|||||||
// Success gradient transitions
|
// Success gradient transitions
|
||||||
downloadProgressBar.style.background = 'linear-gradient(90deg, #00ff87 0%, #60efff 100%)';
|
downloadProgressBar.style.background = 'linear-gradient(90deg, #00ff87 0%, #60efff 100%)';
|
||||||
downloadProgressBar.style.boxShadow = '0 0 15px rgba(0, 255, 135, 0.6)';
|
downloadProgressBar.style.boxShadow = '0 0 15px rgba(0, 255, 135, 0.6)';
|
||||||
|
|
||||||
|
// Trigger premium SweetAlert2 modal
|
||||||
|
Swal.fire({
|
||||||
|
title: 'İndirme Tamamlandı! 🎉',
|
||||||
|
html: `
|
||||||
|
<div style="text-align: left; font-family: 'Outfit', sans-serif; color: #fff; line-height: 1.6; font-size: 14px;">
|
||||||
|
<p style="margin-bottom: 8px;"><strong>Dosya Adı:</strong> <span style="color: #60efff;">${currentVideoData.title}</span></p>
|
||||||
|
<p style="margin-bottom: 8px;"><strong>Platform / Paylaşan:</strong> <span style="color: #60efff;">${currentVideoData.uploader || 'Bilinmiyor'}</span></p>
|
||||||
|
<p style="margin-bottom: 8px;"><strong>Dosya Boyutu:</strong> <span style="color: #00ff87;">${result.stats.size}</span></p>
|
||||||
|
<p style="margin-bottom: 8px;"><strong>İndirme Süresi:</strong> <span style="color: #00ff87;">${result.stats.duration}</span></p>
|
||||||
|
<p style="margin-bottom: 8px;"><strong>Format ve Kalite:</strong> <span style="color: #ff4da6; text-transform: uppercase;">${currentFormat} (${currentQuality === '320' || currentQuality === '1080' ? 'En Yüksek' : currentQuality})</span></p>
|
||||||
|
<p style="margin-top: 15px; font-size: 12px; opacity: 0.8; word-break: break-all;"><strong>Kaydedilen Klasör:</strong> <br>${result.stats.outputDir}</p>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
icon: 'success',
|
||||||
|
background: 'rgba(23, 20, 38, 0.95)',
|
||||||
|
color: '#fff',
|
||||||
|
confirmButtonText: 'Klasörü Aç 📂',
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: 'Yeni İndirme 🔄',
|
||||||
|
confirmButtonColor: '#7000ff',
|
||||||
|
cancelButtonColor: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
backdrop: 'rgba(0, 0, 0, 0.6)'
|
||||||
|
}).then((swalResult) => {
|
||||||
|
if (swalResult.isConfirmed) {
|
||||||
|
window.api.openPath(result.stats.outputDir);
|
||||||
|
} else {
|
||||||
|
// Go back to step 1
|
||||||
|
backBtn.click();
|
||||||
|
urlInput.value = '';
|
||||||
|
downloadProgressPanel.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
showError(result.error);
|
showError(result.error);
|
||||||
downloadProgressPanel.style.display = 'none';
|
downloadProgressPanel.style.display = 'none';
|
||||||
|
|||||||
+34
-12
@@ -169,12 +169,17 @@ async function checkDependencies(onStatusUpdate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get video metadata (title, thumbnail, duration, etc.)
|
// Get video metadata (title, thumbnail, duration, etc.)
|
||||||
function getVideoInfo(url) {
|
function getVideoInfo(url, browser) {
|
||||||
console.log(`[DEBUG] Fetching info for URL: ${url}`);
|
console.log(`[DEBUG] Fetching info for URL: ${url}`);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Adding '--no-playlist' prevents yt-dlp from crawling the entire playlist (which hangs/takes extremely long)
|
let args = ['--no-playlist', '-j'];
|
||||||
console.log(`[DEBUG] Spawning process: ${YTDLP_PATH} --no-playlist -j ${url}`);
|
if (browser) {
|
||||||
const proc = spawn(YTDLP_PATH, ['--no-playlist', '-j', url]);
|
args.push('--cookies-from-browser', browser);
|
||||||
|
}
|
||||||
|
args.push(url);
|
||||||
|
|
||||||
|
console.log(`[DEBUG] Spawning process: ${YTDLP_PATH} ${args.join(' ')}`);
|
||||||
|
const proc = spawn(YTDLP_PATH, args);
|
||||||
let stdout = '';
|
let stdout = '';
|
||||||
let stderr = '';
|
let stderr = '';
|
||||||
|
|
||||||
@@ -206,11 +211,13 @@ function getVideoInfo(url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Download video or convert to MP3
|
// Download video or convert to MP3
|
||||||
function downloadMedia(url, format, quality, outputDir, onProgress) {
|
function downloadMedia(url, format, quality, outputDir, browser, onProgress) {
|
||||||
console.log(`[DEBUG] Media download requested. Format: ${format}, Quality: ${quality}, Target Dir: ${outputDir}`);
|
console.log(`[DEBUG] Media download requested. Format: ${format}, Quality: ${quality}, Target Dir: ${outputDir}, Browser Cookies: ${browser || 'none'}`);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let args = [];
|
let args = [];
|
||||||
const outputTemplate = path.join(outputDir, '%(title)s.%(ext)s');
|
const outputTemplate = path.join(outputDir, '%(title)s.%(ext)s');
|
||||||
|
const startTime = Date.now();
|
||||||
|
let totalSize = 'Bilinmiyor';
|
||||||
|
|
||||||
if (format === 'mp3') {
|
if (format === 'mp3') {
|
||||||
const q = quality ? `${quality}K` : '320K'; // '320K', '256K', '192K'
|
const q = quality ? `${quality}K` : '320K'; // '320K', '256K', '192K'
|
||||||
@@ -220,25 +227,35 @@ function downloadMedia(url, format, quality, outputDir, onProgress) {
|
|||||||
'-x',
|
'-x',
|
||||||
'--audio-format', 'mp3',
|
'--audio-format', 'mp3',
|
||||||
'--audio-quality', q,
|
'--audio-quality', q,
|
||||||
'-o', outputTemplate,
|
'-o', outputTemplate
|
||||||
url
|
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
const res = quality || '1080'; // '1080', '720', '480'
|
const res = quality || '1080'; // '1080', '720', '480'
|
||||||
args = [
|
args = [
|
||||||
'--no-playlist',
|
'--no-playlist',
|
||||||
'--ffmpeg-location', FFMPEG_PATH,
|
'--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}]`,
|
'-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,
|
'-o', outputTemplate
|
||||||
url
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
args.push('--cookies-from-browser', browser);
|
||||||
|
}
|
||||||
|
args.push(url);
|
||||||
|
|
||||||
console.log(`[DEBUG] Spawning downloader process: ${YTDLP_PATH} ${args.join(' ')}`);
|
console.log(`[DEBUG] Spawning downloader process: ${YTDLP_PATH} ${args.join(' ')}`);
|
||||||
const proc = spawn(YTDLP_PATH, args);
|
const proc = spawn(YTDLP_PATH, args);
|
||||||
|
|
||||||
proc.stdout.on('data', (data) => {
|
proc.stdout.on('data', (data) => {
|
||||||
const line = data.toString();
|
const line = data.toString();
|
||||||
|
|
||||||
|
// Parse total size: e.g. [download] 1.2% of 8.37MiB at...
|
||||||
|
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+)%/);
|
const match = line.match(/\[download\]\s+(\d+\.\d+)%/);
|
||||||
if (match && onProgress) {
|
if (match && onProgress) {
|
||||||
const percent = parseFloat(match[1]);
|
const percent = parseFloat(match[1]);
|
||||||
@@ -256,7 +273,12 @@ function downloadMedia(url, format, quality, outputDir, onProgress) {
|
|||||||
proc.on('close', (code) => {
|
proc.on('close', (code) => {
|
||||||
console.log(`[DEBUG] Downloader process exited with code ${code}`);
|
console.log(`[DEBUG] Downloader process exited with code ${code}`);
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
resolve();
|
const durationSec = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||||
|
resolve({
|
||||||
|
size: totalSize,
|
||||||
|
duration: `${durationSec} saniye`,
|
||||||
|
outputDir: outputDir
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(`Download exited with code ${code}`));
|
reject(new Error(`Download exited with code ${code}`));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user