Честно, я устал каждый месяц искать новый способ для скачивания различного контента с youtube. Что сегодня работает — завтра уже не подает признаков жизни. После очередной смерти одного из загрузчиков — я понял, что проще уже сделать свой, чем вот так тратить время на постоянные поиски.
Зачем, если уже есть готовые решения?
Тут все просто. Просмотрев некоторые варианты, я понял, что выбирать не приходиться. Интерфейсы есть, но они довольно ограничены в функционале и больше подходят для тех, кто хочет просто скачать пару видео в дорогу. Мне же нужен был полноценный инструмент, который закроет все мои нужды, как монтажера.
Что именно я хотел получить:
-
Загрузка видео в разных форматах + форматирование
-
Возможность скачать аудио отдельно
-
Скачивание отдельной части видео или аудио
-
Работа со всеми видами ссылок (в том числе плейлисты, джемы, live и shorts)
-
Отслеживание кодека и битрейта перед скачиванием
Ну и конечно, чтобы это визуально не выглядело — как взлом терминала из fallout.
Почему именно electron?
Давно хотел попробовать что-нибудь кроме python, и раз уж тут подвернулась такая возможность — то почему мы бы и нет.
Хватит лишней болтовни — переходим к главному.
И да, интерфейс пока работает только на WINDOWS.
Интерфейс
Встречают по одежке, как говорится.
Поначалу не хотел тратить много времени и остановиться на чем-нибудь однотонном. Но в процессе увлекся и меня немного занесло. Сделал две темы (светлая и тёмная).
В центр разместил забавную гифку, чтобы было не так грустно. Так же дополнительно сделал меню настроек. Туда вывел выбор папки для скачивания, выбор языка, темы, настройку -geopass (выключена по умолчанию) и уведомления.
Сам коддля «настроек» вывел в отдельные файлы (settings.js и settings.html) чтобы не путаться.
Далее идет основной интерфейс для выбора настроек скачивания. Я не буду показывать каждую кнопку, чтобы не растягивать статью.
Основной код
Приложение построил на Electron для десктопа и React для фронтенда. Electron обрабатывает IPC, процессы и внешние вызовы, React — UI с состояниями.
Функционал
-
Проверка URL видео
Функция проверки URL реализована в
main.jsчерез IPC-событиеcheck-url. Она использует yt-dlp для получения метаданных видео.
ipcMain.handle('check-url', async (event, url) => { try { const ytDlpPath = getYtDlpPath(); await fs.access(ytDlpPath); // Проверка наличия yt-dlp.exe const args = ['--dump-single-json', url]; const process = spawn(ytDlpPath, args, { shell: true }); let output = ''; process.stdout.on('data', (data) => { output += data.toString(); }); return new Promise((resolve, reject) => { process.on('close', (code) => { if (code === 0) { try { const json = JSON.parse(output); resolve(json); // Возвращает метаданные: миниатюра, форматы, длительность } catch (err) { reject(new Error('Ошибка парсинга JSON')); } } else { reject(new Error(`yt-dlp завершился с кодом ${code}`)); } }); }); } catch (err) { console.error('Ошибка проверки URL:', err); throw err; } });
Вызывается yt-dlp с параметром --dump-single-json, который возвращает JSON с информацией о видео (название, форматы, длительность). В App.jsx результат отображается в интерфейсе (миниатюра, список форматов).
-
Скачивание
Скачивание реализовано в main.js через IPC start-download. Формируются аргументы для yt-dlp и запускается процесс.
ipcMain.handle('start-download', async (event, options) => { const { url, format, quality, startTime, endTime, downloadMode } = options; const ytDlpPath = getYtDlpPath(); const ffmpegPath = getFfmpegPath(); let args = [url, '-o', '%(title)s.%(ext)s']; if (format === 'mp3' || format === 'm4a') { args.push('-x', `--audio-format=${format}`, `--audio-quality=${quality || 0}`); } else { args.push(`-f bestvideo[height<=${quality}]+bestaudio/best[height<=${quality}]`); } if (startTime && endTime) { args.push(`--download-sections=*${startTime}-${endTime}`); } const process = spawn(ytDlpPath, args, { shell: true, env: { ...process.env, PATH: `${process.env.PATH};${path.dirname(ffmpegPath)}` } }); currentDownloadProcess = process; process.stdout.on('data', (data) => { const output = data.toString(); const match = output.match(/(\d+\.\d)%/); // Парсинг прогресса if (match) { mainWindow.webContents.send('download-progress', { percent: parseFloat(match[1]) }); } }); return new Promise((resolve, reject) => { process.on('close', (code) => { if (code === 0) { mainWindow.webContents.send('download-finished'); resolve(); } else if (!isCancelled) { mainWindow.webContents.send('download-error', { message: `Ошибка скачивания, код ${code}` }); reject(new Error(`Ошибка скачивания, код ${code}`)); } }); }); });
Формируются аргументы для yt-dlp в зависимости от формата и качества. Прогресс парсится из stdout через regex. События download-progress и download-finished отправляются в React для обновления UI.
И на сладкое — скачивание аудио или видео частями.
Буду честен — я до конца не верил, что смогу такое реализовать. Штука крайне удобная, но не без минусов. Она хорошо себя показывает в длинных роликах (от 30–40 минут), но в коротких проще скачать видео полностью и отрезать лишнее (короткий ролик загрузиться быстрее, чем его часть. Окак 🙂
Принцип работы
Интерфейс (фронтенд):
-
Пользователь включает опцию «Скачать часть» кнопкой, активируя слайдер (
react-range). -
Слайдер задаёт
startTimeиendTime(в секундах) в пределах длительности видео (videoInfo.duration). -
Временные метки отображаются в формате
HH:MM:SS.
const [rangeValues, setRangeValues] = useState([0, 0]); const [showRange, setShowRange] = useState(false); <Range values={rangeValues} step={1} min={0} max={videoInfo?.duration || 100} onChange={(values) => setRangeValues(values)} />
При нажатии «Скачать» данные передаются в бэкенд через IPC:
const handleDownload = async () => { const options = { url, format, quality, startTime: showRange ? Math.floor(rangeValues[0]) : null, endTime: showRange ? Math.floor(rangeValues[1]) : null }; await window.electronAPI.startDownload(options); };
Бэкенд (main.js):
-
Получает
startTimeиendTimeчерез IPCstart-download. -
Добавляет аргумент
--download-sections=*${startTime}-${endTime}для yt-dlp. -
Запускает yt-dlp с ffmpeg для обрезки.
Зачем ffmpeg?
ffmpeg (бинарник в bin/) используется yt-dlp для обрезки медиа и конвертации (например, видео в MP3). Он обеспечивает точную обрезку, но границы могут слегка варьироваться из-за ключевых кадров
Cookies
Чтобы получать корректные данные о видео приходится цеплять cookies файл через встроенный electron браузер (вход в аккаунт google). Я пытался обойти это сменой способа извлечения данных о видео с tv_embedded на mweb. Два дня адских мучений спустя (связанных с интеграцией плагина для извлечения PO Token сначала на VPS, а потом и в само приложение) я решил отказаться от этой затеи.
Итоги
Это были веселые пару недель в мои жизни, будем честны. Все функции, которые хотел увидеть — я смог реализовать. Да, есть некоторые некритичные баги, которые нужно в скором времени починить. Если кому-то будет интересно протестировать GUI — буду только рад.
Из того, что хотел бы ещё добавить в будущем — это поддержка linux/mac и возможность полноценной работы с плейлистами (пока можно скачивать только конкретное видео из плейлиста)
Проект планируется платным, но для желающих потыкаться — PROMO_0Y1NE52T
P.S — Для использования приложения на территории РФ необходимо включать методы для обхода замедления (галочка в настройках не спасает)
Полезные ссылки:
ссылка на оригинал статьи https://habr.com/ru/articles/937952/
Добавить комментарий