3 Commits
31 changed files with 300 additions and 91 deletions

No files matched your search

+1 -1
View File
@@ -24,7 +24,7 @@ npm run check
python tools/build.py
```
`tools/build.py` собирает небольшой `dist/YouTubeDL-1.4.5.zip` без EXE.
`tools/build.py` собирает небольшой `dist/YouTubeDL-1.4.7.zip` без EXE.
Для сборки расширения не требуется предварительно собирать помощник.
## Браузерные проверки
+6 -6
View File
@@ -3,10 +3,10 @@
Нужны **Windows 10/11 x64** и **Microsoft Edge или Google Chrome 120+**.
Firefox, macOS, Linux и Windows ARM не входят в эту сборку.
Скачивания доступны в [релизах проекта](https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/latest).
Скачивания доступны в [релизах проекта](https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/releases/latest).
Помощник распространяется **отдельно**: внутри архива расширения EXE нет.
1. Скачайте **YouTubeDL-1.4.5.zip** и распакуйте его в постоянную папку. Не удаляйте и не перемещайте её после установки.
1. Скачайте **YouTubeDL-1.4.7.zip** и распакуйте его в постоянную папку. Не удаляйте и не перемещайте её после установки.
2. Откройте `edge://extensions` или `chrome://extensions`. Включите **Режим разработчика**, нажмите **Загрузить распакованное расширение** и выберите папку **extension** внутри распакованного архива.
3. На странице релиза отдельно скачайте **YouTubeDL-Helper-Setup.exe**. Это автономный комплект: Python, yt-dlp, EJS, Node.js, FFmpeg и FFprobe уже внутри.
4. Запустите EXE вручную и дождитесь окончания установки. Администратор и интернет для установки комплекта не требуются. Программы устанавливаются для текущего пользователя в `%LOCALAPPDATA%\YouTubeDL\Helper`.
@@ -18,7 +18,7 @@ Firefox, macOS, Linux и Windows ARM не входят в эту сборку.
## Проверка скачанных файлов
Скачивайте файлы из релизов `git.lonarpgproxy.space/OwnerLink/YouTubeDL`.
Скачивайте файлы из релизов `git.lonarpgproxy.space/DepressedCat/YouTubeDL`.
Чтобы проверить целостность файла, сравните его SHA256 с `SHA256SUMS.txt` в том же релизе.
```powershell
@@ -51,7 +51,7 @@ Get-FileHash -Algorithm SHA256 -LiteralPath '.\YouTubeDL-Helper-Setup.exe'
| Скачивание медленное | GPU ускоряет перекодирование видео; скорость сети и ограничения YouTube от видеокарты не зависят. |
Не прикладывайте cookies, токены, личные ссылки и полные пути с именем пользователя к публичным обращениям.
Сообщить об ошибке: [Задачи проекта](https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/issues).
Сообщить об ошибке: [Задачи проекта](https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/issues).
## Диски и временные файлы
@@ -59,7 +59,7 @@ Get-FileHash -Algorithm SHA256 -LiteralPath '.\YouTubeDL-Helper-Setup.exe'
При разных дисках результат сначала копируется во временную папку на диске сохранения, затем публикуется под итоговым именем. Прогресс копирования виден в уведомлении; частичный файл не появляется под готовым именем. При отмене помощник завершает группу процессов данной задачи и удаляет её временные папки.
Для отдельной рабочей папки, подробного прогресса слияния и исправленной отмены установите помощник 1.3.2 и расширение 1.4.5. Обновление компонентов не заменяет программный код помощника; для этой версии запустите новый установщик помощника.
Для отдельной рабочей папки, подробного прогресса слияния и исправленной отмены установите помощник 1.3.3 и расширение 1.4.7. Обновление компонентов не заменяет программный код помощника; для этой версии запустите новый установщик помощника.
## История и ошибки
@@ -69,7 +69,7 @@ Get-FileHash -Algorithm SHA256 -LiteralPath '.\YouTubeDL-Helper-Setup.exe'
## Совместимость версий
В настройках и мастере первого запуска отображаются версии расширения и помощника. Расширение 1.4.5 поддерживает помощник 1.3.2. При старом помощнике появится кнопка «Обновить помощник», при более новом помощнике или устаревшем расширении — «Обновить расширение». Пока совместимость не подтверждена, новая загрузка не запускается.
В настройках и мастере первого запуска отображаются версии расширения и помощника. Расширение 1.4.7 поддерживает помощник 1.3.3. При старом помощнике появится кнопка «Обновить помощник», при более новом помощнике или устаревшем расширении — «Обновить расширение». Пока совместимость не подтверждена, новая загрузка не запускается.
После установки помощника нажмите «Проверить»: статус изменится без перезагрузки страницы. После замены файлов расширения нажмите «Обновить» на странице расширений браузера и перезагрузите вкладку YouTube. Обновление компонентов обновляет инструменты внутри комплекта и не заменяет само расширение или код помощника.
+26 -20
View File
@@ -1,8 +1,11 @@
ОГРАНИЧЕННАЯ ПОЛЬЗОВАТЕЛЬСКАЯ ЛИЦЕНЗИЯ YOUTUBEDL
Версия 1.0, 8 октября 2026 года
ЛИЦЕНЗИЯ SOURCE AVAILABLE — YouTubeDL
Версия 1.1, 11 октября 2026 года
Copyright (c) 2026 OwnerLink. Все права сохранены.
Правообладатель: OwnerLink (https://git.lonarpgproxy.space/OwnerLink).
Copyright (c) 2026 DepressedCat. Все права сохранены.
Правообладатель: DepressedCat (https://git.lonarpgproxy.space/DepressedCat).
Исходный код доступен для ознакомления (Source Available). Права на использование,
изменение и распространение определяются условиями этой лицензии.
1. ОБЛАСТЬ ДЕЙСТВИЯ
@@ -10,7 +13,7 @@ Copyright (c) 2026 OwnerLink. Все права сохранены.
с этой лицензией: самому браузерному расширению, собственному коду локального
помощника, установщика и инструментов, документации, сайту и оригинальным
материалам оформления (далее — «Программа»).
Исключения для сторонних компонентов и прав платформы
Исключения для сторонних компонентов и применимого законодательства
указаны в разделе 4 и имеют приоритет над ограничениями раздела 3.
2. РАЗРЕШЁННОЕ ИСПОЛЬЗОВАНИЕ
@@ -22,7 +25,8 @@ Copyright (c) 2026 OwnerLink. Все права сохранены.
выбирать дорожки и параметры, объединять, конвертировать и сохранять файлы
при наличии права сохранять соответствующий контент.
Разрешены необходимые для этого установочные и резервные копии, сборка
Разрешены просмотр и изучение опубликованных исходников, их локальная копия
для ознакомления и сборки, необходимые установочные и резервные копии, сборка
неизменённой официальной поставки для собственного использования, настройка
через штатные средства, установка официальных обновлений и удаление Программы.
Это не предоставляет права изменять собственные файлы кода и дизайна.
@@ -42,25 +46,27 @@ Copyright (c) 2026 OwnerLink. Все права сохранены.
- удалять или заменять уведомления об авторстве, правообладателе и лицензии.
Все права, прямо не предоставленные этой лицензией, сохранены за правообладателем.
Для отдельного разрешения обращайтесь к OwnerLink через профиль Gitea проекта.
Для отдельного разрешения обращайтесь к DepressedCat через профиль Gitea проекта.
4. ИСКЛЮЧЕНИЯ
4.1. Python, Node.js, yt-dlp, EJS, FFmpeg, FFprobe и другие сторонние компоненты,
их исходники и лицензионные тексты регулируются лицензиями их правообладателей.
Эта лицензия не ограничивает предоставленные ими права, в том числе права
на изменение, форки и распространение по GPL, MIT и другим лицензиям.
Состав и сведения о лицензиях приведены в THIRD_PARTY.md и материалах релиза.
4.1. Python, Node.js, yt-dlp, EJS, FFmpeg, FFprobe и другие сторонние компоненты
регулируются условиями собственных лицензий, установленными их правообладателями.
4.2. Публикация репозитория на GitHub сопровождается правами, предусмотренными
условиями GitHub. В частности, для публичных репозиториев GitHub предусматривает
просмотр и создание копий через функцию fork внутри платформы. Эта лицензия
не отменяет эти права и не является технической блокировкой функции fork.
Ограничения раздела 3 действуют в части, не покрытой отдельно предоставленными
правами платформы и применимого законодательства.
Условия: https://docs.github.com/en/site-policy/github-terms/github-terms-of-service
Настоящая лицензия не ограничивает права, предоставленные пользователям лицензиями
указанных сторонних компонентов, включая права на изменение, создание производных
работ и распространение, если такие действия разрешены соответствующими лицензиями.
4.3. Права, которые применимое законодательство не позволяет ограничивать
Указанные права относятся к соответствующим сторонним компонентам и не предоставляют
пользователю дополнительных прав на авторские части Программы. Их изменение, создание
производных версий, форков и распространение запрещены без разрешения правообладателя,
за исключением случаев, предусмотренных настоящей лицензией, обязательными нормами
законодательства или применимыми лицензиями сторонних компонентов.
Перечень сторонних компонентов и сведения об их лицензиях приведены в файле
THIRD_PARTY.md и материалах релиза.
4.2. Права, которые применимое законодательство не позволяет ограничивать
договором, сохраняются. Эта лицензия не предоставляет прав на чужой контент
YouTube и не заменяет правила соответствующих сервисов.
+1 -1
View File
@@ -1,6 +1,6 @@
# Обработка данных
Актуально для расширения **1.4.5** и помощника **1.3.2**. Исходники доступны для просмотра,
Актуально для расширения **1.4.7** и помощника **1.3.3**. Исходники доступны для просмотра,
условия использования собственного кода ограничены [лицензией](LICENSE).
У проекта нет собственного сервера обработки видео, аккаунтов, телеметрии, рекламы или аналитики.
Это не означает отсутствие обработки данных: необходимые сведения используются на вашем компьютере,
+7 -7
View File
@@ -7,12 +7,12 @@
а **⚙ рядом** открывает меню. Обработка и объединение дорожек выполняются на вашем компьютере.
**[Сайт проекта](https://git.lonarpgproxy.space/extention/youtubedl/)** ·
**[Скачать расширение и помощник](https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/latest)** ·
**[Скачать расширение и помощник](https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/releases/latest)** ·
**[Инструкция](INSTALL.md)** · **[Обработка данных](PRIVACY.md)** ·
**[Сообщить об ошибке](https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/issues)**
**[Сообщить об ошибке](https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/issues)**
Windows 10/11 x64 · Microsoft Edge / Google Chrome 120+ · расширение **1.4.5** · помощник **1.3.2**.
Расширение устанавливается распакованным, через режим разработчика. В магазинах браузеров пока не опубликовано.
Windows 10/11 x64 · Microsoft Edge / Google Chrome 120+ · расширение **1.4.7** · помощник **1.3.3**.
Расширение устанавливается распакованным, через режим разработчика. Прямая поставка через Gitea выбрана из-за ограничений на скачивание контента в [правилах Chrome Web Store](https://developer.chrome.com/docs/webstore/program-policies/malicious-and-prohibited) и [условиях YouTube](https://www.youtube.com/static?template=terms).
## Что умеет
@@ -38,7 +38,7 @@ DRM-контент и гарантированный обход ограниче
## Установка: два отдельных скачивания
1. Откройте [последний релиз](https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/latest), скачайте **YouTubeDL-1.4.5.zip** и распакуйте в постоянную папку.
1. Откройте [последний релиз](https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/releases/latest), скачайте **YouTubeDL-1.4.7.zip** и распакуйте в постоянную папку.
2. В `edge://extensions` или `chrome://extensions` включите **Режим разработчика**, нажмите **Загрузить распакованное расширение** и выберите папку **extension**.
3. **Отдельно скачайте YouTubeDL-Helper-Setup.exe из того же релиза** и запустите его вручную. EXE не встроен в расширение: кнопка установки открывает страницу релизов.
4. Обновите YouTube, откройте **⚙** и пройдите мастер. Выберите папку и параметры; зелёный статус подтверждает связь с помощником.
@@ -102,8 +102,8 @@ Python остаётся в ветке 3.13, Node.js — в 24 LTS.
## Лицензии и разработка
Новые поставки собственного кода — по **[ограниченной пользовательской лицензии](LICENSE)**:
разрешены установка и использование по назначению. Изменение, самостоятельные форки,
Новые поставки собственного кода — по **[лицензии Source Available](LICENSE)**:
разрешены просмотр исходников, сборка неизменённой версии, установка и использование по назначению. Изменение, самостоятельные форки,
производные версии и распространение без письменного разрешения правообладателя запрещены,
с учётом исключений лицензии. Это распространяется и на само расширение.
+3 -2
View File
@@ -5,8 +5,9 @@
Встроенные компоненты помощника имеют **свои** лицензии; ограничения YouTubeDL
не заменяют и не ограничивают их, в том числе права по GPL и MIT.
Помощник вызывает FFmpeg отдельным процессом.
Mutagen не входит в комплект 1.3.3 и не устанавливается при обновлении его компонентов.
Автономный комплект помощника 1.3.2:
Автономный комплект помощника 1.3.3:
| Компонент | Версия | Проект / лицензия |
| --- | --- | --- |
@@ -17,7 +18,7 @@
| FFmpeg / FFprobe | N-127236-g17ec998942-20261007, shared win64 GPL | [FFmpeg](https://github.com/FFmpeg/FFmpeg/tree/17ec998942), GPL v3 or later для этой сборки; [сценарии FFmpeg-Builds](https://github.com/yt-dlp/FFmpeg-Builds) |
Python-пакеты комплекта: brotli 1.2.0, certifi 2026.7.22, charset-normalizer 3.5.2,
idna 3.20, mutagen 1.48.1, pycryptodomex 3.24.0, requests 2.34.2, urllib3 2.8.0,
idna 3.20, pycryptodomex 3.24.0, requests 2.34.2, urllib3 2.8.0,
websockets 17.2. Их `METADATA` и лицензии находятся рядом с библиотеками в
`runtime/python/Lib/site-packages/*.dist-info` установленного помощника.
Тексты Python/Node.js/FFmpeg также входят в установленный комплект.
+1 -1
View File
@@ -20,6 +20,6 @@
В релиз также включены манифест помощника, **YouTubeDL-ThirdParty-Materials.zip**, пользовательские документы и **SHA256SUMS.txt**.
[Сайт](https://git.lonarpgproxy.space/extention/youtubedl/) · [Инструкция](https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/src/branch/main/INSTALL.md) · [Данные](https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/src/branch/main/PRIVACY.md) · [Лицензия](https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/src/branch/main/LICENSE) · [Сторонние компоненты](https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/src/branch/main/THIRD_PARTY.md)
[Сайт](https://git.lonarpgproxy.space/extention/youtubedl/) · [Инструкция](https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/src/branch/main/INSTALL.md) · [Данные](https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/src/branch/main/PRIVACY.md) · [Лицензия](https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/src/branch/main/LICENSE) · [Сторонние компоненты](https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/src/branch/main/THIRD_PARTY.md)
Собственный код распространяется по ограниченной пользовательской лицензии: разрешено использование по назначению, изменения и распространение требуют письменного разрешения правообладателя. Сторонние компоненты сохраняют свои лицензии.
+15
View File
@@ -0,0 +1,15 @@
# YouTubeDL 1.4.6
Расширение **1.4.6** для Edge и Chrome 120+. Совместимый локальный помощник — **1.3.2**.
- Исправлено перекрытие: кнопки на обычных превью и Shorts больше не появляются поверх уведомлений и меню YouTube.
- Значок настроек заменён симметричной шестерёнкой с окружностью по центру на всех кнопках, включая превью видео и Shorts.
- Кнопки остаются доступны при замене превью Shorts проигрываемым видео.
## Обновление
1. Скачайте **YouTubeDL-1.4.6.zip** и распакуйте в постоянную папку, заменив прежнюю папку `extension`.
2. Откройте `chrome://extensions` или `edge://extensions` и перезагрузите YouTubeDL. Если устанавливаете впервые, включите режим разработчика и выберите «Загрузить распакованное расширение» → папку `extension`.
3. Обновите уже открытые страницы YouTube. В настройках расширения должна отображаться версия **1.4.6**.
Установленный помощник **1.3.2** обновлять не требуется. Для первой установки его EXE доступен отдельно в этом релизе.
+16
View File
@@ -0,0 +1,16 @@
# YouTubeDL 1.4.7
Расширение **1.4.7** для Edge и Chrome 120+. Локальный помощник — **1.3.3**.
- Собственный код, документация и оформление распространяются по лицензии **Source Available**: разрешены просмотр исходников, сборка неизменённой версии и использование по назначению. Изменения, форки и распространение требуют письменного согласия DepressedCat с учётом исключений лицензии.
- Неиспользуемый Mutagen исключён из автономного помощника и списка обновляемых Python-пакетов. Объединение дорожек, конвертация, аудио и аппаратное ускорение продолжают выполняться через FFmpeg.
- Проверка состава Python-пакетов выполняется при сборке, проверке комплекта и обновлении. Пакеты, требующие отдельной проверки совместимости лицензии, отклоняются до активации; рабочий комплект сохраняется.
- Лицензии и исходные материалы сторонних компонентов доступны отдельно в служебном разделе загрузок.
## Установка и обновление
1. Скачайте **YouTubeDL-1.4.7.zip**, распакуйте в постоянную папку и загрузите папку `extension` через режим разработчика в `edge://extensions` или `chrome://extensions`.
2. Скачайте **YouTubeDL-Helper-Setup.exe** из этого релиза и обновите помощник до **1.3.3**. Обычное обновление компонентов не заменяет код самого помощника.
3. Перезагрузите расширение и открытые страницы YouTube, затем проверьте подключение помощника.
Если расширение уже установлено, замените файлы прежней папки `extension` и нажмите «Перезагрузить» на странице расширений браузера.
+7 -4
View File
@@ -6,8 +6,11 @@
const previewResize = new ResizeObserver(schedulePreviewLayout);
let watchButton = null, shortsButton = null, shortsRenderer = null;
let menu = null, menuAnchor = null, menuSize = { width: 270, height: 142 };
// Above hover playback, below YouTube's masthead (2020) and popups (2202).
// Shorts controls live at the document root to survive preview replacement.
const pageControlLayer = 2000;
const icon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v12m-5-5 5 5 5-5M4 17v4h16v-4"/></svg>';
const gear = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 3-.6 2.4-2.1 1.2L4 6l-2 3.5 1.8 1.7v2.4L2 15.3 4 19l2.3-.6 2.1 1.2L9 22h4l.6-2.4 2.1-1.2 2.3.6 2-3.7-1.8-1.7v-2.4L20 9.5 18 6l-2.3.6-2.1-1.2L13 3Z" transform="translate(1 -.5) scale(.95)"/><circle cx="12" cy="12" r="3"/></svg>';
const gear = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M8.939 4.609L10.2 4.205L10.264 2.152L13.736 2.152L13.8 4.205L15.061 4.609L16.239 5.216L17.736 3.808L20.192 6.264L18.784 7.761L19.391 8.939L19.795 10.2L21.848 10.264L21.848 13.736L19.795 13.8L19.391 15.061L18.784 16.239L20.192 17.736L17.736 20.192L16.239 18.784L15.061 19.391L13.8 19.795L13.736 21.848L10.264 21.848L10.2 19.795L8.939 19.391L7.761 18.784L6.264 20.192L3.808 17.736L5.216 16.239L4.609 15.061L4.205 13.8L2.152 13.736L2.152 10.264L4.205 10.2L4.609 8.939L5.216 7.761L3.808 6.264L6.264 3.808L7.761 5.216L8.939 4.609Z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>";
const themeStyle = ':host{font-family:Roboto,"Segoe UI",sans-serif;--bg:#fff;--fg:#0f0f0f;--muted:#606060;--button:#0000000d;--hover:#0000001a;--pressed:#00000026;--line:#0000001a}:host([dark]){--bg:#212121;--fg:#f1f1f1;--muted:#aaa;--button:#ffffff1a;--hover:#ffffff33;--pressed:#ffffff40;--line:#ffffff1a}*{box-sizing:border-box}button{font:inherit;cursor:pointer}';
const dark = () => document.documentElement.hasAttribute('dark') || document.documentElement.dataset.theme === 'dark';
function canonical(value) {
@@ -73,7 +76,7 @@
function makeControl(kind, resolveUrl) {
const host = document.createElement('span'); host.dataset.ytdlControl = 'true'; host.dataset.kind = kind;
const shadow = host.attachShadow({ mode: 'open' });
shadow.innerHTML = `<style>${themeStyle}.pair{display:flex;align-items:center;gap:6px}button{display:flex;align-items:center;justify-content:center;gap:6px;height:36px;padding:0 16px;border:0;border-radius:999px;background:var(--button);color:var(--fg);font-size:14px;font-weight:500;white-space:nowrap}button:hover{background:var(--hover)}button:active{background:var(--pressed)}button:focus-visible{outline:2px solid var(--fg);outline-offset:2px}.glyph{display:grid;place-items:center}svg{width:20px;height:20px}.settings{width:36px;padding:0}:host([data-kind=thumbnail]) button{width:34px;height:34px;padding:0;background:#0f0f0fbb;color:#fff;box-shadow:0 1px 5px #0003}:host([data-kind=thumbnail]) button:hover{background:#303030}:host([data-kind=thumbnail]) .label{display:none}:host([data-kind=shorts]) .pair{flex-direction:column;gap:9px}:host([data-kind=shorts]) .download{background:transparent;height:auto;padding:0;flex-direction:column;gap:6px;color:var(--fg)}:host([data-kind=shorts]) .glyph{width:48px;height:48px;border-radius:50%;background:var(--button)}:host([data-kind=shorts]) .download:hover .glyph{background:var(--hover)}:host([data-kind=shorts]) .download:active .glyph{background:var(--pressed)}:host([data-kind=shorts]) svg{width:24px;height:24px}:host([data-kind=shorts]) .settings svg{width:19px;height:19px}:host([data-kind=shorts]) .label{font-size:12px}</style><div class="pair"><button class="download" type="button" title="Скачать по сохранённым настройкам" aria-label="Скачать по сохранённым настройкам"><span class="glyph">${icon}</span><span class="label">Скачать</span></button><button class="settings" type="button" title="Настройки скачивания" aria-label="Настройки скачивания">${gear}</button></div>`;
shadow.innerHTML = `<style>${themeStyle}.pair{display:flex;align-items:center;gap:6px}button{display:flex;align-items:center;justify-content:center;gap:6px;height:36px;padding:0 16px;border:0;border-radius:999px;background:var(--button);color:var(--fg);font-size:14px;font-weight:500;white-space:nowrap}button:hover{background:var(--hover)}button:active{background:var(--pressed)}button:focus-visible{outline:2px solid var(--fg);outline-offset:2px}.glyph{display:grid;place-items:center}svg{display:block;flex-shrink:0;width:20px;height:20px}.settings{width:36px;padding:0}:host([data-kind=thumbnail]) button{width:34px;height:34px;padding:0;background:#0f0f0fbb;color:#fff;box-shadow:0 1px 5px #0003}:host([data-kind=thumbnail]) button:hover{background:#303030}:host([data-kind=thumbnail]) .label{display:none}:host([data-kind=shorts]) .pair{flex-direction:column;gap:9px}:host([data-kind=shorts]) .download{background:transparent;height:auto;padding:0;flex-direction:column;gap:6px;color:var(--fg)}:host([data-kind=shorts]) .glyph{width:48px;height:48px;border-radius:50%;background:var(--button)}:host([data-kind=shorts]) .download:hover .glyph{background:var(--hover)}:host([data-kind=shorts]) .download:active .glyph{background:var(--pressed)}:host([data-kind=shorts]) svg{width:24px;height:24px}:host([data-kind=shorts]) .settings svg{width:19px;height:19px}:host([data-kind=shorts]) .label{font-size:12px}</style><div class="pair"><button class="download" type="button" title="Скачать по сохранённым настройкам" aria-label="Скачать по сохранённым настройкам"><span class="glyph">${icon}</span><span class="label">Скачать</span></button><button class="settings" type="button" title="Настройки скачивания" aria-label="Настройки скачивания">${gear}</button></div>`;
for (const button of shadow.querySelectorAll('button')) {
button.addEventListener('pointerdown', event => { event.stopPropagation(); });
button.addEventListener('keydown', event => { event.stopPropagation(); });
@@ -206,7 +209,7 @@
});
host.className = 'ytdl-thumbnail-download';
const key = `ytdl-preview-${++previewSequence}`; root.dataset.ytdlPreview = key; host.dataset.ytdlPreviewFor = key;
host.style.cssText = `position:${card ? 'fixed' : 'absolute'};left:0;top:0;width:74px;height:34px;margin:0;padding:0;line-height:1;z-index:2147483644;display:inline-flex`;
host.style.cssText = `position:${card ? 'fixed' : 'absolute'};left:0;top:0;width:74px;height:34px;margin:0;padding:0;line-height:1;z-index:${pageControlLayer};display:inline-flex`;
if (card) document.documentElement.append(host);
else {
if (getComputedStyle(root).position === 'static') root.style.position = 'relative';
@@ -233,7 +236,7 @@
shortsButton.style.cssText = 'display:flex;justify-content:center;margin-top:16px;flex-shrink:0';
rail.append(shortsButton);
} else {
shortsButton.style.cssText = 'position:fixed;right:16px;bottom:180px;z-index:2147483645;display:inline-flex';
shortsButton.style.cssText = `position:fixed;right:16px;bottom:180px;z-index:${pageControlLayer};display:inline-flex`;
document.documentElement.append(shortsButton);
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "YouTubeDL",
"description": "Скачать видео и аудио с YouTube: качество, формат, папка, NVIDIA NVENC и AMD AMF.",
"version": "1.4.5",
"version": "1.4.7",
"minimum_chrome_version": "120",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6bCUFVUmC9OqpL0+/vrQDKEesuiylybHxRWJXfMV7VfctMbDjAQzActxImzMGIviC8PJrCHT86v/erppUhIgqFrSftHVPPWmYbVJ/g6KBu1bRNIG5J1WDUnMO2E4hGNEJQe+6ezqGuGdk659qQOqP5khGbnog9wUs+1nPpolaXbXsUudn5leaxJ0SrdFT9oWjq59bAc7raEKjR1chU9pB9g9alEA8lqpcKUU8j7f/KJC9YvkOlTNEsLXhuLLzcC7Aw/eI3lgBqBLFWIcrEYrBPVFeIlNaVLdl6b8YqAD+eBaT1xdpT6VbGh/d3lf/Vq/TBzTzCplcMO2vIFGDKTNhQIDAQAB",
"permissions": [
+1 -1
View File
@@ -12,7 +12,7 @@ export function stabilizeDownloadSpeed(job, previous, now = Date.now()) {
&& now >= previous.speedUpdatedAt && now - previous.speedUpdatedAt < 1500;
return { ...job, speed: recent ? previous.speed : null, speedUpdatedAt: recent ? previous.speedUpdatedAt : null };
}
export const SUPPORTED_HELPER_VERSION = "1.3.2";
export const SUPPORTED_HELPER_VERSION = "1.3.3";
const MINIMUM_EXTENSION_VERSION = "1.4.5";
function compareVersions(left, right) {
if (![left, right].every(value => typeof value === "string" && /^\d+(?:\.\d+){1,3}$/.test(value))) return null;
+2
View File
@@ -7,6 +7,7 @@ from pathlib import Path
from host.bundle import ROOT, executable, manifest
from host.common import EXTENSION_ID, HOST_NAME
from host.dependencies import status
from host.python_packages import validate_installed
def verify():
@@ -14,6 +15,7 @@ def verify():
raise ValueError("Autonomous bundle metadata is missing.")
if Path(sys.executable).resolve() != Path(executable("python")):
raise ValueError("The helper must use its embedded Python.")
validate_installed(ROOT / "runtime/python/Lib/site-packages")
measured = status()
if any(item["status"] != "installed" for item in measured):
raise ValueError("Bundled components failed verification: " + json.dumps(measured))
+1 -1
View File
@@ -6,7 +6,7 @@ from pathlib import Path
from urllib.parse import parse_qs, urlsplit
HOST_NAME = "com.youtubedl.native"
VERSION = "1.3.2"
VERSION = "1.3.3"
EXTENSION_ID = "ndnoeiaecgmjnjneekaeapeecmmjheli"
VIDEO_CONTAINERS = {"mp4", "mkv", "webm"}
AUDIO_CONTAINERS = {"m4a", "mp3", "opus", "flac", "wav"}
+3 -2
View File
@@ -64,6 +64,7 @@ def install(key, progress, workspace=None):
raise ValueError("Нажмите «Обновить компоненты» для установки новых версий из интернета или «Восстановить» для переустановки встроенного комплекта.")
if key not in {"node", "ffmpeg", "yt_dlp", "ejs"}:
raise ValueError("Неизвестный компонент.")
from host.python_packages import requirements
runtime = ROOT / ".runtime"
runtime.mkdir(exist_ok=True)
cfg = config()
@@ -73,8 +74,8 @@ def install(key, progress, workspace=None):
workspace(str(scratch))
if key in {"yt_dlp", "ejs"}:
progress(None, "Устанавливаю Python-пакет")
package = "yt-dlp[default]>=2026.8.19" if key == "yt_dlp" else "yt-dlp-ejs"
measured = subprocess.run([sys.executable, "-m", "pip", "install", "--disable-pip-version-check", "--upgrade", package],
packages = requirements() if key == "yt_dlp" else ["yt-dlp-ejs"]
measured = subprocess.run([sys.executable, "-m", "pip", "install", "--disable-pip-version-check", "--upgrade", *packages],
stdin=subprocess.DEVNULL, capture_output=True, text=True, encoding="utf-8", errors="replace", creationflags=NO_WINDOW, timeout=600)
if measured.returncode:
raise ValueError("Не удалось установить компонент. " + measured.stderr[-1200:])
+41
View File
@@ -0,0 +1,41 @@
"""Python dependencies used by the helper; optional cover tagging is excluded."""
from __future__ import annotations
import importlib.metadata
import re
from pathlib import Path
def requirements(yt_dlp="yt-dlp>=2026.8.19", ejs="yt-dlp-ejs"):
return [yt_dlp, ejs, "brotli", "certifi", "pycryptodomex",
"requests>=2.32.2,<3", "urllib3>=2,<3", "websockets>=13"]
def validate_metadata(records):
"""Reject strong copyleft Python libraries before activating a bundle.
FFmpeg is a separately executed program, so this policy concerns Python
libraries loaded into the helper process, not the FFmpeg distribution.
"""
for metadata in records:
name = re.sub(r"[-_.]+", "-", metadata.get("name", "").lower())
licenses = [metadata.get("license_expression") or "", metadata.get("license") or "",
*metadata.get("classifier", [])]
incompatible = name == "mutagen" or any(
re.search(r"(?<![A-Z])(?:A?GPL)(?:[-\s\dv]|$)|GNU (?:Affero )?General Public License",
license, re.IGNORECASE) for license in licenses)
if incompatible:
raise ValueError(f"Python-пакет {name} требует отдельной проверки совместимости лицензии; комплект не изменён.")
def validate_installed(directory: Path):
records = []
for distribution in importlib.metadata.distributions(path=[str(directory)]):
metadata = distribution.metadata
records.append({"name": metadata.get("Name", ""),
"license_expression": metadata.get("License-Expression", ""),
"license": metadata.get("License", ""),
"classifier": metadata.get_all("Classifier") or []})
validate_metadata(records)
return {distribution.metadata["Name"]: distribution.version
for distribution in importlib.metadata.distributions(path=[str(directory)])}
+4 -1
View File
@@ -19,6 +19,7 @@ from urllib.parse import urlsplit
import requests
from host.bundle import ROOT, installation_root, manifest
from host.media import NO_WINDOW, run_small
from host.python_packages import requirements, validate_metadata, validate_installed
ALLOWED_HOSTS = {"www.python.org", "nodejs.org", "api.github.com", "github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com", "pypi.org", "files.pythonhosted.org"}
FFMPEG_ASSET = "ffmpeg-master-latest-win64-gpl-shared.zip"
@@ -183,11 +184,12 @@ def resolve_packages(stage, work, progress):
report = work / "python-packages.json"
script = "import sys;sys.path.insert(0,sys.argv.pop(1));from pip._internal.cli.main import main;raise SystemExit(main(sys.argv[1:]))"
command = [str(stage / "runtime/python/python.exe"), "-I", "-X", "utf8", "-B", "-c", script, str(pip), "--isolated", "--disable-pip-version-check", "install",
"--dry-run", "--ignore-installed", "--only-binary=:all:", "--no-cache-dir", "--quiet", "--report", str(report), "--index-url", "https://pypi.org/simple", "yt-dlp[default]", "yt-dlp-ejs"]
"--dry-run", "--ignore-installed", "--only-binary=:all:", "--no-cache-dir", "--quiet", "--report", str(report), "--index-url", "https://pypi.org/simple", *requirements()]
result = subprocess.run(command, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=600, creationflags=NO_WINDOW,
env={**os.environ, "TEMP": str(work), "TMP": str(work)}, stdin=subprocess.DEVNULL)
if result.returncode: raise ValueError("Не удалось подобрать совместимые Python-пакеты. " + result.stderr[-1200:])
records = json.loads(report.read_text(encoding="utf-8"))["install"]
validate_metadata([record["metadata"] for record in records])
packages = stage / "runtime/python/Lib/site-packages"
if packages.exists(): shutil.rmtree(packages)
packages.mkdir(parents=True)
@@ -206,6 +208,7 @@ def resolve_packages(stage, work, progress):
if packages.resolve() not in destination.parents: raise ValueError("Некорректный путь Python-пакета.")
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(wheel.read(member))
validate_installed(packages)
return {item["metadata"]["name"]: item["metadata"]["version"] for item in records}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "youtubedl",
"version": "1.4.5",
"version": "1.4.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "youtubedl",
"version": "1.4.5",
"version": "1.4.7",
"license": "SEE LICENSE IN LICENSE",
"devDependencies": {
"playwright": "^1.64.0"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "youtubedl",
"version": "1.4.5",
"version": "1.4.7",
"license": "SEE LICENSE IN LICENSE",
"private": true,
"type": "module",
+8 -1
View File
@@ -1 +1,8 @@
yt-dlp[default]>=2026.8.19
yt-dlp>=2026.8.19
yt-dlp-ejs
brotli
certifi
pycryptodomex
requests>=2.32.2,<3
urllib3>=2,<3
websockets>=13
+13 -12
View File
@@ -25,7 +25,7 @@
<header class="site-header">
<div class="wrap header-inner">
<a class="brand" href="#" aria-label="YouTubeDL — начало"><img src="assets/mark.svg" alt="" width="34" height="34"><span>YouTubeDL</span></a>
<nav aria-label="Основная навигация"><a href="#features">Возможности</a><a href="#install">Установка</a><a href="#privacy">Данные</a><a href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL" target="_blank" rel="noopener noreferrer">Gitea <span aria-hidden="true">↗</span></a></nav>
<nav aria-label="Основная навигация"><a href="#features">Возможности</a><a href="#install">Установка</a><a href="#privacy">Данные</a><a href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL" target="_blank" rel="noopener noreferrer">Gitea <span aria-hidden="true">↗</span></a></nav>
<button class="theme-button" id="theme-toggle" type="button" aria-label="Переключить тему" title="Переключить тему"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><path d="M20.4 13A8.5 8.5 0 0 1 11 3.6 8.5 8.5 0 1 0 20.4 13Z"/></svg></button>
</div>
</header>
@@ -40,7 +40,7 @@
<div class="hero-actions"><a class="button primary" href="#install">Установить расширение <span aria-hidden="true">↓</span></a><a class="hero-link" href="#features">Посмотреть возможности <span aria-hidden="true">↗</span></a></div>
<p class="hero-note">Бесплатно · Для Windows · Обработка на вашем компьютере</p>
</div>
<div class="hero-bottom"><span>ВИДЕО. ЗВУК. ВАШ ВЫБОР.</span><a href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/tag/v1.4.5">Выпуск 1.4.5 <span aria-hidden="true">↗</span></a></div>
<div class="hero-bottom"><span>ВИДЕО. ЗВУК. ВАШ ВЫБОР.</span><a href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/releases/tag/v1.4.7">Выпуск 1.4.7 <span aria-hidden="true">↗</span></a></div>
</div>
</section>
<div class="format-strip"><div class="wrap"><p>Один ролик.<br><strong>Ваш формат.</strong></p><div class="format-list" aria-label="Форматы"><span>MP4</span><span>MKV</span><span>WebM</span><i aria-hidden="true"></i><span>MP3</span><span>M4A</span><span>Opus</span><span>FLAC</span><span>WAV</span></div></div></div>
@@ -70,33 +70,34 @@
<section class="install-section" id="install" aria-labelledby="install-title"><div class="wrap">
<div class="install-heading reveal"><div><p class="overline">04 / НАЧНЁМ</p><h2 id="install-title">Два скачивания.<br><em>Один комплект.</em></h2></div><p>Windows 10/11 x64<br>Edge или Chrome 120+<br><span>Без прав администратора</span></p></div>
<div class="download-grid reveal">
<article class="download-card"><div class="download-card-top"><span class="package-number">1</span><span class="package-tag">БРАУЗЕР</span><span class="version">v1.4.5</span></div><h3>Расширение</h3><p>Кнопки на YouTube, настройки и уведомления. Устанавливается распакованным.</p><a class="button primary package-link" data-download="extension" href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/download/v1.4.5/YouTubeDL-1.4.5.zip">Скачать расширение <span aria-hidden="true">↓</span></a><small>ZIP · <span id="extension-size">Небольшой архив</span></small></article>
<article class="download-card helper-card"><div class="download-card-top"><span class="package-number">2</span><span class="package-tag">КОМПЬЮТЕР</span><span class="version">v1.3.2</span></div><h3>Локальный помощник</h3><p>Скачивает, соединяет и конвертирует. Все необходимые компоненты уже внутри.</p><a class="button secondary package-link" data-download="helper" href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/download/v1.4.5/YouTubeDL-Helper-Setup.exe">Скачать помощник <span aria-hidden="true">↓</span></a><small>EXE · 124,3 МиБ · устанавливается отдельно</small></article>
<article class="download-card"><div class="download-card-top"><span class="package-number">1</span><span class="package-tag">БРАУЗЕР</span><span class="version">v1.4.7</span></div><h3>Расширение</h3><p>Кнопки на YouTube, настройки и уведомления. Устанавливается распакованным.</p><a class="button primary package-link" data-download="extension" href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/releases/download/v1.4.7/YouTubeDL-1.4.7.zip">Скачать расширение <span aria-hidden="true">↓</span></a><small>ZIP · <span id="extension-size">Небольшой архив</span></small></article>
<article class="download-card helper-card"><div class="download-card-top"><span class="package-number">2</span><span class="package-tag">КОМПЬЮТЕР</span><span class="version">v1.3.3</span></div><h3>Локальный помощник</h3><p>Скачивает, соединяет и конвертирует. Все необходимые компоненты уже внутри.</p><a class="button secondary package-link" data-download="helper" href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/releases/download/v1.4.7/YouTubeDL-Helper-Setup.exe">Скачать помощник <span aria-hidden="true">↓</span></a><small>EXE · 124,1 МиБ · устанавливается отдельно</small></article>
</div>
<p class="download-note">Оба файла размещены в <a href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/latest">релизах Gitea ↗</a>. Помощник не встроен в ZIP. <a href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/src/branch/main/INSTALL.md#проверка-скачанных-файлов">Проверка SHA256 и подробности</a>.</p>
<p class="download-note">Оба файла размещены в <a href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/releases/latest">релизах Gitea ↗</a>. Помощник не встроен в ZIP. <a href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/src/branch/main/INSTALL.md#проверка-скачанных-файлов">Проверка SHA256 и подробности</a>.</p>
<ol class="install-steps reveal">
<li><span>01</span><div><h3>Распакуйте и загрузите расширение</h3><p>Оставьте распакованный ZIP в постоянной папке. Откройте страницу расширений, включите «Режим разработчика» → «Загрузить распакованное расширение» → выберите папку <code>extension</code>.</p><div class="browser-addresses"><button type="button" class="copy-address" data-copy="edge://extensions">edge://extensions <span>Копировать</span></button><button type="button" class="copy-address" data-copy="chrome://extensions">chrome://extensions <span>Копировать</span></button></div></div></li>
<li><span>02</span><div><h3>Запустите помощник</h3><p>Откройте отдельно скачанный <code>YouTubeDL-Helper-Setup.exe</code>. Дождитесь установки. Python, yt-dlp, EJS, Node.js, FFmpeg и FFprobe уже в комплекте; для установки после скачивания интернет не нужен.</p></div></li>
<li><span>03</span><div><h3>Обновите YouTube и пройдите мастер</h3><p>Откройте ⚙, нажмите «Проверить подключение», выберите папку, качество и формат. Зелёный индикатор подтверждает реальный ответ помощника. Затем загрузчик будет использовать эти настройки.</p></div></li>
</ol>
<p class="full-guide"><a href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/src/branch/main/INSTALL.md">Полная инструкция, обновление и удаление <span aria-hidden="true">↗</span></a></p>
<p class="full-guide"><a href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/src/branch/main/INSTALL.md">Полная инструкция, обновление и удаление <span aria-hidden="true">↗</span></a></p>
</div></section>
<section class="section wrap privacy-section" id="privacy" aria-labelledby="privacy-title">
<div class="reveal"><p class="overline">05 / ПОНЯТНО О ДАННЫХ</p><h2 id="privacy-title">Ваш компьютер.<br><em>Ваша обработка.</em></h2><p class="section-lead">У проекта нет аналитики, рекламы и сервера для конвертации ваших роликов. Исходники доступны для просмотра.</p><a class="text-link" href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/src/branch/main/PRIVACY.md">Прочитать полный список данных и разрешений ↗</a></div>
<dl class="privacy-facts reveal"><div><dt>Что остаётся локально</dt><dd>Настройки, папка и до 30 последних операций в браузере: названия, ссылки, статусы, пути и ошибки. Помощник отдельно хранит до 30 готовых загрузок. GPU и версии компонентов проверяются для работы приложения.</dd></div><div><dt>Вход — только по согласию</dt><dd>Доступ к cookies YouTube необязателен. Они передаются локальному помощнику и используются в запросах к YouTube; в файлы cookies и историю не записываются. Разрешение можно отозвать.</dd></div><div><dt>Куда идут запросы</dt><dd>К YouTube и его медиасерверам, к официальным источникам обновлений и GitHub для скачиваний и сайта. Эти сервисы видят обычные данные сетевого запроса, включая IP.</dd></div></dl>
<div class="reveal"><p class="overline">05 / ПОНЯТНО О ДАННЫХ</p><h2 id="privacy-title">Ваш компьютер.<br><em>Ваша обработка.</em></h2><p class="section-lead">У проекта нет аналитики, рекламы и сервера для конвертации ваших роликов. Исходники доступны для просмотра.</p><a class="text-link" href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/src/branch/main/PRIVACY.md">Прочитать полный список данных и разрешений ↗</a></div>
<dl class="privacy-facts reveal"><div><dt>Что остаётся локально</dt><dd>Настройки, папка и до 30 последних операций в браузере: названия, ссылки, статусы, пути и ошибки. Помощник отдельно хранит до 30 готовых загрузок. GPU и версии компонентов проверяются для работы приложения.</dd></div><div><dt>Вход — только по согласию</dt><dd>Доступ к cookies YouTube необязателен. Они передаются локальному помощнику и используются в запросах к YouTube; в файлы cookies и историю не записываются. Разрешение можно отозвать.</dd></div><div><dt>Куда идут запросы</dt><dd>К YouTube и его медиасерверам, к Gitea проекта и официальным источникам обновлений компонентов. Эти сервисы видят обычные данные сетевого запроса, включая IP.</dd></div></dl>
</section>
<section class="faq-section"><div class="wrap faq-inner"><div><p class="overline">ОСТАЛИСЬ ДЕТАЛИ?</p><h2>Частые вопросы.</h2><p class="quiet">Если ответа здесь нет,<br><a href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/issues">напишите в задачи проекта ↗</a></p></div><div class="faqs">
<section class="faq-section"><div class="wrap faq-inner"><div><p class="overline">ОСТАЛИСЬ ДЕТАЛИ?</p><h2>Частые вопросы.</h2><p class="quiet">Если ответа здесь нет,<br><a href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/issues">напишите в задачи проекта ↗</a></p></div><div class="faqs">
<details><summary>Почему нужен отдельный помощник?<span aria-hidden="true">+</span></summary><p>Браузерное расширение не может запускать FFmpeg и напрямую работать с произвольной папкой Windows. Локальный Native Messaging помощник выполняет скачивание и обработку. Все зависимости собраны в один EXE; отдельно устанавливать Python или FFmpeg не нужно.</p></details>
<details><summary>Как работают обновления?<span aria-hidden="true">+</span></summary><p>Обновление компонентов проверяет и скачивает совместимые обновления Python, Node.js, yt-dlp с библиотеками, EJS и FFmpeg/FFprobe. Автопроверка включена по умолчанию: раз в сутки, пока помощник работает, между операциями. Её можно выключить. Новые версии самого расширения и кода помощника устанавливаются вручную из Releases.</p></details>
<details><summary>Нужно входить в YouTube?<span aria-hidden="true">+</span></summary><p>Для многих открытых роликов — нет. Если YouTube требует вход или проверку, откройте ролик в текущем профиле и при необходимости включите «Использовать вход в YouTube». Вход на сайте сам по себе не авторизует отдельный загрузчик. Авторизация не гарантирует обход ограничений.</p></details>
<details><summary>Будет ли работать 4K, 8K и HDR?<span aria-hidden="true">+</span></summary><p>Если эти дорожки есть у источника. Разрешение не увеличивается искусственно. Для HDR используйте исходный кодек, HEVC или AV1; HDR → H.264 без тонального преобразования отклоняется. FLAC/WAV тоже не восстанавливают уже потерянное качество звука.</p></details>
<details><summary>Как удалить всё?<span aria-hidden="true">+</span></summary><p>Удалите помощник кнопкой в меню или через приложения Windows, затем расширение из браузера. Скачанные файлы и история помощника сохраняются. Для удаления истории после удаления помощника удалите <code>%LOCALAPPDATA%\YouTubeDL\Helper\data</code>. Настройки расширения удаляются вместе с расширением.</p></details>
<details><summary>Есть другие ОС или магазин расширений?<span aria-hidden="true">+</span></summary><p>Сейчас опубликована сборка для Windows 10/11 x64 и Edge/Chrome 120+. Firefox, macOS, Linux и Windows ARM не входят в неё. В магазинах браузеров расширение пока не опубликовано. Плейлисты, скачивание каналов и DRM-контент не заявляются.</p></details>
<details><summary>Можно изменять расширение и публиковать свои версии?<span aria-hidden="true">+</span></summary><p><a href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/src/branch/main/LICENSE">Лицензия</a> разрешает использование по назначению и запрещает изменения, самостоятельные форки и распространение собственного кода без письменного разрешения, с учётом исключений в её тексте. Сторонние компоненты сохраняют свои лицензии.</p></details>
<details><summary>Почему расширения нет в Chrome Web Store?<span aria-hidden="true">+</span></summary><p>YouTubeDL распространяется через релизы Gitea из-за ограничений на скачивание контента. <a href="https://developer.chrome.com/docs/webstore/program-policies/malicious-and-prohibited" target="_blank" rel="noopener noreferrer">Правила Chrome Web Store</a> запрещают инструменты для неразрешённого скачивания защищённого контента, а <a href="https://www.youtube.com/static?template=terms" target="_blank" rel="noopener noreferrer">условия YouTube</a> ограничивают скачивание разрешёнными сервисом способами или письменным разрешением YouTube и соответствующих правообладателей. Поэтому для YouTubeDL выбрана ручная установка распакованного расширения из официального релиза.</p></details>
<details><summary>Какие ОС поддерживаются?<span aria-hidden="true">+</span></summary><p>Сейчас опубликована сборка для Windows 10/11 x64 и Edge/Chrome 120+. Firefox, macOS, Linux и Windows ARM не входят в неё. Плейлисты, скачивание каналов и DRM-контент не заявляются.</p></details>
<details><summary>Можно изменять расширение и публиковать свои версии?<span aria-hidden="true">+</span></summary><p><a href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/src/branch/main/LICENSE">Лицензия Source Available</a> разрешает просмотр исходников, сборку неизменённой версии и использование по назначению и запрещает изменения, самостоятельные форки и распространение собственного кода без письменного разрешения, с учётом исключений в её тексте. Сторонние компоненты сохраняют свои лицензии.</p></details>
</div></div></section>
<section class="closing"><div class="wrap"><img src="assets/mark.svg" alt="" width="48" height="48"><h2>Видео. Звук.<br><span>Ваш выбор.</span></h2><a class="button primary" href="#install">Скачать YouTubeDL <span aria-hidden="true">↓</span></a></div></section>
</main>
<footer class="site-footer"><div class="wrap footer-top"><a class="brand" href="#"><img src="assets/mark.svg" alt="" width="28" height="28"><span>YouTubeDL</span></a><div><a href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL">Исходники ↗</a><a href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/src/branch/main/PRIVACY.md">Данные</a><a href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/src/branch/main/LICENSE">Лицензия</a><a href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/src/branch/main/THIRD_PARTY.md">Компоненты</a><a href="https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/issues">Поддержка</a></div></div><div class="wrap footer-bottom"><p>© 2026 OwnerLink. YouTubeDL распространяется по ограниченной пользовательской лицензии.</p><p>Независимый проект. Не связан с YouTube/Google, NVIDIA или AMD. Сохраняйте контент, который вы вправе скачивать.</p><p>На сайте нет счётчиков и собственных cookies. Хостинг — собственный сервер проекта.</p></div></footer>
<footer class="site-footer"><div class="wrap footer-top"><a class="brand" href="#"><img src="assets/mark.svg" alt="" width="28" height="28"><span>YouTubeDL</span></a><div><a href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL">Исходники ↗</a><a href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/src/branch/main/PRIVACY.md">Данные</a><a href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/src/branch/main/LICENSE">Лицензия</a><a href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/src/branch/main/THIRD_PARTY.md">Компоненты</a><a href="https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/issues">Поддержка</a></div></div><div class="wrap footer-bottom"><p>© 2026 DepressedCat. YouTubeDL распространяется по лицензии Source Available.</p><p>Независимый проект. Не связан с YouTube/Google, NVIDIA или AMD. Сохраняйте контент, который вы вправе скачивать.</p><p>На сайте нет счётчиков и собственных cookies. Хостинг — собственный сервер проекта.</p></div></footer>
<div id="copy-status" class="copy-status" role="status" aria-live="polite" hidden></div>
</body>
</html>
+6 -6
View File
@@ -55,19 +55,19 @@ test("brief missing speed samples do not flicker, expire and reset between track
assert.equal(stabilizeDownloadSpeed({ ...sample, stage: "merging", speed: "8x" }, previous, 1200).speedUpdatedAt, null);
});
test("helper compatibility detects outdated, newer and unidentifiable versions in both directions", () => {
assert.equal(checkHelperCompatibility({ version: "1.3.2" }, "1.4.5").compatible, true);
assert.equal(checkHelperCompatibility({ bundle: { version: "1.3.2" } }, "1.4.5").compatible, true);
assert.equal(checkHelperCompatibility({ version: "1.3.2.0" }, "1.4.5").compatible, true);
assert.equal(checkHelperCompatibility({ version: "1.3.3" }, "1.4.5").compatible, true);
assert.equal(checkHelperCompatibility({ bundle: { version: "1.3.3" } }, "1.4.5").compatible, true);
assert.equal(checkHelperCompatibility({ version: "1.3.3.0" }, "1.4.5").compatible, true);
for (const version of ["1.3.0", "1.2.10", undefined, "unknown"]) {
const result = checkHelperCompatibility({ version }, "1.4.5");
assert.equal(result.compatible, false);
assert.equal(result.update, "helper");
}
for (const version of ["1.3.3", "1.3.20", "2.0.0"]) {
for (const version of ["1.3.4", "1.3.30", "2.0.0"]) {
const result = checkHelperCompatibility({ version }, "1.4.5");
assert.equal(result.compatible, false);
assert.equal(result.update, "extension");
}
assert.equal(checkHelperCompatibility({ version: "1.3.2" }, "1.4.2").update, "extension");
assert.equal(checkHelperCompatibility({ version: "1.3.2", minimumExtensionVersion: "1.4.6" }, "1.4.5").update, "extension");
assert.equal(checkHelperCompatibility({ version: "1.3.3" }, "1.4.2").update, "extension");
assert.equal(checkHelperCompatibility({ version: "1.3.3", minimumExtensionVersion: "1.4.7" }, "1.4.5").update, "extension");
});
+1 -1
View File
@@ -16,7 +16,7 @@ test("history connects without a helper, opens a tab, and retains failures befor
return { onMessage: { addListener: listener => { receive = listener; } }, onDisconnect: noEvent, disconnect() {},
postMessage(message) {
nativeRequests.push(message);
queueMicrotask(() => receive({ id: message.id, ok: message.action !== "inspect", result: message.action === "hello" ? { version: "1.3.2" } : { alive: true }, error: "Видео недоступно" }));
queueMicrotask(() => receive({ id: message.id, ok: message.action !== "inspect", result: message.action === "hello" ? { version: "1.3.3" } : { alive: true }, error: "Видео недоступно" }));
} };
} };
globalThis.chrome = { runtime, storage: { local: { get: async () => structuredClone(storage), set: async data => Object.assign(storage, structuredClone(data)) } },
+3 -3
View File
@@ -4,7 +4,7 @@ import assert from "node:assert/strict";
test("native lifecycle recovers stale ports and publishes install/remove/update state to all menus", async () => {
const ports = [], listeners = {}, storage = {}, pending = new Map(), updates = [];
let installed = false;
let helperVersion = "1.3.2";
let helperVersion = "1.3.3";
let componentsReady = true;
const noEvent = { addListener() {} };
const runtime = { id: "test-id", getManifest: () => ({ version: "1.4.5" }), getURL: name => `chrome-extension://test-id/${name}`,
@@ -94,7 +94,7 @@ test("native lifecycle recovers stale ports and publishes install/remove/update
await new Promise(resolve => setTimeout(resolve, 20));
assert.equal((await call("state")).result.helper.hardware.ready, true, "Restoring a component refreshes readiness without reload");
assert.ok(fresh.sent.some(message => message.action === "helper_uninstall"));
for (const [version, update] of [["1.3.0", "helper"], ["1.3.3", "extension"]]) {
for (const [version, update] of [["1.3.0", "helper"], ["1.3.4", "extension"]]) {
helperVersion = version;
ports.at(-1).disconnect();
await new Promise(resolve => setImmediate(resolve));
@@ -107,6 +107,6 @@ test("native lifecycle recovers stale ports and publishes install/remove/update
assert.equal((await call("download", { url: "https://youtu.be/jNQXAC9IVRw" })).ok, false, 'Incompatible versions cannot launch a download');
assert.ok(!port.sent.some(message => message.action === "download"));
}
helperVersion = "1.3.2";
helperVersion = "1.3.3";
assert.equal((await call("helper_status")).result.status, "running", 'Version repair is detected by the Check button without reloading');
});
+1 -1
View File
@@ -38,7 +38,7 @@ test("background passes fresh YouTube cookies to both worker actions and never p
onMessage: { addListener(listener) { nativeListener = listener; } }, onDisconnect: noEvent, disconnect() {},
postMessage(message) {
requests.push(message);
const result = message.action === "hello" ? { version: "1.3.2", ready: true, supportsBrowserCookies: true } : message.action === "ping" ? { alive: true }
const result = message.action === "hello" ? { version: "1.3.3", ready: true, supportsBrowserCookies: true } : message.action === "ping" ? { alive: true }
: message.action === "download" ? { jobId: "auth-test-job" } : { title: "Synthetic video", videos: [], audios: [] };
queueMicrotask(() => nativeListener({ id: message.id, ok: true, result }));
},
+2 -2
View File
@@ -34,8 +34,8 @@ try {
await page.locator(".hero .primary").click();
assert.equal(new URL(page.url()).hash,"#install");
assert.ok(await page.locator(".download-grid").isVisible());
assert.equal(await page.locator('[data-download="extension"]').getAttribute("href"),`https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/download/v${version}/YouTubeDL-${version}.zip`);
assert.equal(await page.locator('[data-download="helper"]').getAttribute("href"),"https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/download/v1.4.5/YouTubeDL-Helper-Setup.exe");
assert.equal(await page.locator('[data-download="extension"]').getAttribute("href"),`https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/releases/download/v${version}/YouTubeDL-${version}.zip`);
assert.equal(await page.locator('[data-download="helper"]').getAttribute("href"),"https://git.lonarpgproxy.space/DepressedCat/YouTubeDL/releases/download/v1.4.7/YouTubeDL-Helper-Setup.exe");
await page.locator('[data-screenshot="light"]').click();
await page.locator("#menu-screenshot").evaluate(image => image.decode());
assert.match(await page.locator("#menu-screenshot").getAttribute("src"), /menu-light\.png$/);
+53
View File
@@ -0,0 +1,53 @@
import json
import tempfile
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch
from host.python_packages import validate_metadata
from host.updates import resolve_packages
class PythonPackageLicenseTests(unittest.TestCase):
def test_permissive_and_weak_copyleft_metadata_is_accepted(self):
validate_metadata([{"name": "yt-dlp", "license_expression": "Unlicense"},
{"name": "certifi", "license_expression": "MPL-2.0"},
{"name": "library", "license_expression": "MIT"},
{"name": "library", "license_expression": "LGPL-2.1-or-later"}])
def test_strong_copyleft_is_rejected_using_expression_or_classifiers(self):
for metadata in [{"name": "mutagen"},
{"name": "dependency", "license_expression": "GPL-3.0-or-later"},
{"name": "dependency", "license_expression": "AGPL-3.0-only"},
{"name": "dependency", "classifier": ["License :: OSI Approved :: GNU General Public License v2 (GPLv2)"]}]:
with self.subTest(metadata=metadata), self.assertRaises(ValueError):
validate_metadata([metadata])
def test_incompatible_resolution_does_not_delete_the_existing_packages(self):
with tempfile.TemporaryDirectory() as temporary:
stage = Path(temporary) / "stage"
packages = stage / "runtime/python/Lib/site-packages"
packages.mkdir(parents=True)
sentinel = packages / "existing.py"
sentinel.write_text("original package", encoding="utf8")
work = Path(temporary) / "work"
work.mkdir()
def resolve(command, **kwargs):
report = Path(command[command.index("--report") + 1])
report.write_text(json.dumps({"install": [{"metadata": {
"name": "mutagen", "license_expression": "GPL-2.0-or-later"}}]}), encoding="utf8")
self.assertNotIn("yt-dlp[default]", command)
return SimpleNamespace(returncode=0)
response = {"urls": [{"filename": "pip-1-py3-none-any.whl", "url": "https://files.pythonhosted.org/pip.whl", "digests": {"sha256": "a" * 64}}]}
with patch("host.updates.get", return_value=SimpleNamespace(json=lambda: response)), \
patch("host.updates.fetch"), patch("host.updates.subprocess.run", side_effect=resolve):
with self.assertRaisesRegex(ValueError, "mutagen"):
resolve_packages(stage, work, lambda *args: None)
self.assertEqual(sentinel.read_text(), "original package")
if __name__ == "__main__":
unittest.main()
+57 -2
View File
@@ -8,7 +8,7 @@ const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
const artifacts = path.join(root, "test-artifacts");
await fs.mkdir(artifacts, { recursive: true });
const browser = await chromium.launch({ headless: true, executablePath: "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe" });
const context = await browser.newContext({ viewport: { width: 1200, height: 800 }, reducedMotion: "reduce" });
const context = await browser.newContext({ viewport: { width: 1200, height: 800 }, reducedMotion: "reduce", deviceScaleFactor: Number(process.env.YTDL_TEST_SCALE || 1) });
await context.addInitScript({ path: path.join(root, "tests/ui-fixture.js") });
const poster = 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="330" height="186"><defs><linearGradient id="g"><stop stop-color="#096574"/><stop offset="1" stop-color="#192636"/></linearGradient></defs><path fill="url(#g)" d="M0 0h330v186H0z"/></svg>');
await context.route("https://www.youtube.com/**", route => route.fulfill({ contentType: "text/html", body: `<!doctype html><html dark><head><meta charset="utf-8"><style>
@@ -42,6 +42,42 @@ try {
for (const id of ["short-card", "legacy-card", "modern-card"]) {
await page.locator(`#${id} [data-ytdl-preview], #${id}[data-ytdl-preview]`).waitFor(); await assertInside(id);
}
// YouTube's native notification/account dropdowns are fixed at z-index 2202.
// Both the Shorts portal and regular thumbnail controls must stay beneath them.
await page.evaluate(() => {
const container = document.createElement('ytd-popup-container');
container.style.cssText = 'position:absolute;inset:0 0 auto';
container.innerHTML = '<tp-yt-iron-dropdown id="native-popup" style="position:fixed;left:12px;top:56px;width:1100px;height:300px;z-index:2202;background:#282828;color:white;border-radius:12px;padding:16px;box-sizing:border-box"><h2>Уведомления YouTube</h2><p>Новые видео и трансляции</p></tp-yt-iron-dropdown>';
document.body.append(container);
});
for (const id of ['short-card', 'legacy-card', 'modern-card']) {
const covered = await page.locator(`#${id}`).evaluate(card => {
const owner = card.dataset.ytdlPreview || card.querySelector('[data-ytdl-preview]')?.dataset.ytdlPreview;
const host = document.querySelector(`[data-ytdl-preview-for="${owner}"]`);
return [...host.shadowRoot.querySelectorAll('button')].every(button => {
const rect = button.getBoundingClientRect();
return !!document.elementFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2)?.closest('#native-popup');
});
});
assert.equal(covered, true, `Native YouTube popup must cover both controls on ${id}`);
}
await page.screenshot({ path: path.join(artifacts, 'thumbnail-native-popup.png'), animations: 'disabled' });
await page.evaluate(() => document.querySelector('ytd-popup-container').remove());
// Closing the popup must restore interaction without a page refresh.
const legacyOwner = await page.locator('#legacy-card [data-ytdl-preview]').getAttribute('data-ytdl-preview');
await page.locator(`[data-ytdl-preview-for="${legacyOwner}"] .download`).click();
assert.equal(await page.evaluate(() => window.__quickRequests.at(-1)), 'https://www.youtube.com/watch?v=jNQXAC9IVRw');
// Compare the actual rendered centres, including any SVG transforms.
const gearCentres = await page.locator('.ytdl-thumbnail-download .settings svg').evaluateAll(svgs => svgs.map(svg => {
const outline = svg.querySelector('path'), circle = svg.querySelector('circle'), box = outline.getBBox();
const outer = new DOMPoint(box.x + box.width / 2, box.y + box.height / 2).matrixTransform(outline.getCTM());
const inner = new DOMPoint(circle.cx.baseVal.value, circle.cy.baseVal.value).matrixTransform(circle.getCTM());
const button = svg.closest('button').getBoundingClientRect(), hole = circle.getBoundingClientRect();
return { outer: { x: outer.x, y: outer.y }, inner: { x: inner.x, y: inner.y }, button: { x: button.x + button.width / 2, y: button.y + button.height / 2 }, hole: { x: hole.x + hole.width / 2, y: hole.y + hole.height / 2 } };
}));
assert.equal(gearCentres.length, 3, 'Check regular, modern and Shorts thumbnail gears');
assert.ok(gearCentres.every(gear => Math.abs(gear.outer.x - gear.inner.x) < .01 && Math.abs(gear.outer.y - gear.inner.y) < .01 && Math.abs(gear.button.x - gear.hole.x) < .01 && Math.abs(gear.button.y - gear.hole.y) < .01),
`Settings gear must have concentric outline and hole: ${JSON.stringify(gearCentres)}`);
// The real YouTube app is absolutely positioned: BODY has zero height, and
// its scroll/auto overflow is propagated to the viewport, not a zero-size clip.
await page.evaluate(() => {
@@ -134,6 +170,25 @@ try {
assert.equal(await shortButtons.isVisible(),true);
await page.evaluate(() => document.querySelector('#short-card').remove());
await shortButtons.waitFor({state:'detached'});
// The standalone Shorts page can temporarily lack its native action rail.
// Its fixed fallback controls must follow the same popup layering rule.
await page.setViewportSize({width:1200,height:800});
await page.goto('https://www.youtube.com/shorts/Mf9GCn_LsUI');
await page.addScriptTag({ path: path.join(root, 'extension/controls.js') });
const fallback = page.locator('#ytdl-shorts-download');
await fallback.waitFor({state:'visible'});
await page.evaluate(() => {
const popup = document.createElement('tp-yt-iron-dropdown'); popup.id = 'native-popup';
popup.style.cssText = 'position:fixed;inset:56px 0 0;z-index:2202;background:#282828';
document.body.append(popup);
});
assert.equal(await fallback.evaluate(host => [...host.shadowRoot.querySelectorAll('button')].every(button => {
const rect = button.getBoundingClientRect();
return document.elementFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2)?.id === 'native-popup';
})), true, 'Native popup must also cover standalone Shorts fallback controls');
await page.evaluate(() => document.querySelector('#native-popup').remove());
await fallback.locator('.download').click();
assert.equal(await page.evaluate(() => window.__quickRequests.at(-1)), 'https://www.youtube.com/watch?v=Mf9GCn_LsUI');
assert.deepEqual(errors, []);
console.log('Thumbnail placement passed: real YouTube zero-height BODY, image bounds, title links, padding, borders, scaled layout, replaced/hidden/removed hover previews, responsive/lazy sizing, scroll, clipped shelves and recycled URLs.');
console.log('Thumbnail placement passed: native popup occlusion, centred settings gear, real YouTube zero-height BODY, image bounds, title links, padding, borders, scaled layout, replaced/hidden/removed hover previews, responsive/lazy sizing, scroll, clipped shelves and recycled URLs.');
} finally { await browser.close(); }
+2 -2
View File
@@ -18,7 +18,7 @@
window.__contentMessage = message => contentListeners.forEach(listener => listener(message));
let autoUpdate = true;
window.__updateRequests = [];
const hardware = () => ({ version: window.__helperVersion || "1.3.2", bundle: { mode: "autonomous", version: window.__helperVersion || "1.3.2", updateTogether: true }, supportsComponentUpdates: true, supportsWorkFolder: true, updateSettings: { enabled: autoUpdate },
const hardware = () => ({ version: window.__helperVersion || "1.3.3", bundle: { mode: "autonomous", version: window.__helperVersion || "1.3.3", updateTogether: true }, supportsComponentUpdates: true, supportsWorkFolder: true, updateSettings: { enabled: autoUpdate },
gpu: window.__amdOnly ? "AMD Radeon RX 7900 XTX" : "NVIDIA GeForce RTX 5080", nvenc: window.__dependencyMissing || window.__amdOnly ? [] : ["h264", "hevc", "av1"],
amf: window.__amdOnly ? ["h264", "hevc", "av1"] : [], amf10bit: window.__amdOnly ? ["hevc", "av1"] : [], defaultFolder: "D:\\Videos", ready: !window.__dependencyMissing,
dependencies: ["python", "yt_dlp", "ejs", "node", "ffmpeg"].map(id => ({ id, status: window.__dependencyMissing && id === "ffmpeg" ? "missing" : "installed", version: "test-version" })) });
@@ -31,7 +31,7 @@
], audios: [{ id: "251", codec: "opus", extension: "webm", bitrate: 160, language: "en", note: "English", size: 5000000 }] };
window.chrome = {
runtime: {
getManifest: () => ({ version: window.__extensionVersion || "1.4.5" }),
getManifest: () => ({ version: window.__extensionVersion || "1.4.7" }),
getURL: file => `https://ui.ytdl.test/${file}`,
sendMessage: async message => {
if (message.type === "QUICK_DOWNLOAD") { window.__quickRequests.push(message.url); return { ok: true, jobId: "fixture-job" }; }
+7 -7
View File
@@ -20,8 +20,8 @@ try {
page.on("pageerror", error => errors.push(error.message));
await page.goto("https://ui.ytdl.test/downloader.html?theme=dark&url=https://www.youtube.com/watch?v=aircAruvnKk");
await page.locator('#helper-panel[data-state="incompatible"]').waitFor();
assert.match(await page.locator('#helper-message').textContent(), /1\.3\.0.*1\.4\.5/);
assert.match(await page.locator('#helper-versions').textContent(), /Расширение 1\.4\.5.*Помощник 1\.3\.0/);
assert.match(await page.locator('#helper-message').textContent(), /1\.3\.0.*1\.4\.7/);
assert.match(await page.locator('#helper-versions').textContent(), /Расширение 1\.4\.7.*Помощник 1\.3\.0/);
assert.equal(await page.locator('#helper-install').textContent(), 'Обновить помощник');
assert.equal(await page.locator('#download').isDisabled(), true);
assert.equal(await page.locator('#download-form').isVisible(), false);
@@ -30,13 +30,13 @@ try {
await page.locator('#setup-helper[data-state="incompatible"]').waitFor();
assert.equal(await page.locator('#setup-next').isDisabled(), true);
assert.equal(await page.locator('#setup-install').textContent(), 'Обновить помощник');
assert.match(await page.locator('#setup-versions').textContent(), /1\.4\.5.*1\.3\.0/);
await page.evaluate(() => { window.__helperVersion = '1.3.2'; });
assert.match(await page.locator('#setup-versions').textContent(), /1\.4\.7.*1\.3\.0/);
await page.evaluate(() => { window.__helperVersion = '1.3.3'; });
await page.locator('#setup-check').click();
await page.locator('#setup-helper[data-state="running"]').waitFor();
assert.match(await page.locator('#setup-versions').textContent(), /Версии совместимы/);
await page.locator('#setup-next:not([disabled])').waitFor();
await page.evaluate(() => { window.__helperVersion = '1.3.3'; });
await page.evaluate(() => { window.__helperVersion = '1.3.4'; });
await page.locator('#setup-check').click();
await page.locator('#setup-helper[data-state="incompatible"]').waitFor();
assert.equal(await page.locator('#setup-next').isDisabled(), true);
@@ -44,9 +44,9 @@ try {
assert.equal(await page.locator('#setup-install-steps').isVisible(), false);
await page.locator('#setup-close').click();
assert.equal(await page.locator('#helper-install').textContent(), 'Обновить расширение');
assert.match(await page.locator('#helper-message').textContent(), /Помощник 1\.3\.3.*расширением 1\.4\.5/);
assert.match(await page.locator('#helper-message').textContent(), /Помощник 1\.3\.4.*расширением 1\.4\.7/);
assert.equal(await page.locator('#download').isDisabled(), true);
await page.evaluate(() => { window.__helperVersion = '1.3.2'; });
await page.evaluate(() => { window.__helperVersion = '1.3.3'; });
await page.locator('#helper-start').click();
await page.locator('#helper-panel[data-state="running"]').waitFor();
await page.locator('#download:not([disabled])').waitFor();
+8 -3
View File
@@ -26,6 +26,8 @@ PYTHON_SHA256 = "90b4e5b9898b72d744650524bff92377c367f44bd5fbd09e3148656c080ad90
NODE = "v24.21.0"
FFMPEG_ARCHIVE = "ffmpeg-master-latest-win64-gpl-shared.zip"
FFMPEG_SHA256 = "eb7afd4ecced72ab7d5d6fdd36dbc359173aa2daf6081817f7b678345898838b"
sys.path.insert(0, str(ROOT))
from host.python_packages import validate_installed
def digest(path):
@@ -107,10 +109,11 @@ def prepare():
wheels = CACHE / "wheels"; wheels.mkdir(exist_ok=True)
options = ["--platform", "win_amd64", "--python-version", "3.13", "--implementation", "cp",
"--abi", "cp313", "--abi", "abi3", "--abi", "none", "--only-binary", ":all:"]
requirement = "yt-dlp[default]==2026.8.19"
subprocess.run([sys.executable, "-m", "pip", "download", "--disable-pip-version-check", "-d", str(wheels), *options, requirement], check=True)
requirements = ["yt-dlp==2026.8.19", "yt-dlp-ejs==0.8.0", "brotli==1.2.0", "certifi==2026.7.22", "pycryptodomex==3.24.0", "requests==2.34.2", "urllib3==2.8.0", "websockets==17.2", "charset-normalizer==3.5.2", "idna==3.20"]
subprocess.run([sys.executable, "-m", "pip", "download", "--disable-pip-version-check", "-d", str(wheels), *options, *requirements], check=True)
subprocess.run([sys.executable, "-m", "pip", "install", "--disable-pip-version-check", "--no-index", "--find-links", str(wheels),
"--target", str(python / "Lib" / "site-packages"), "--no-compile", "--ignore-requires-python", *options, requirement], check=True)
"--target", str(python / "Lib" / "site-packages"), "--no-compile", "--ignore-requires-python", *options, *requirements], check=True)
validate_installed(python / "Lib/site-packages")
(licenses / "THIRD-PARTY.txt").write_text(
"Python: https://www.python.org/ ; license: runtime/python/LICENSE.txt\n"
"Node.js: https://nodejs.org/ ; license: Node-LICENSE.txt\n"
@@ -130,6 +133,8 @@ def build():
# pip's generated EXE launchers refer to the build interpreter. The helper
# imports these libraries directly, so no console launchers are shipped.
shutil.rmtree(scripts)
validate_installed(KIT / "runtime/python/Lib/site-packages")
shutil.copy2(ROOT / "LICENSE", KIT / "LICENSE")
shutil.copytree(ROOT / "host", KIT / "host", dirs_exist_ok=True, ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
sys.path.insert(0, str(ROOT))
from host.common import VERSION