diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..1320b9a
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["@babel/preset-env"]
+}
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 03e137e..24c8715 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -2,6 +2,7 @@ module.exports = {
env: {
es2021: true,
node: true,
+ jest: true,
},
extends: ["eslint:recommended", "prettier"],
plugins: ["prettier"],
diff --git a/.gitignore b/.gitignore
index 0731451..0059428 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
test/vimeo/*
test/youtube/*
test-media
+test/*.mp3
+test/*.mp4
.vscode
.pytest_cache
@@ -13,6 +15,7 @@ yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
+logERROR.txt
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
diff --git a/.husky/pre-commit b/.husky/pre-commit
index d24fdfc..9c77a47 100644
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
-npx lint-staged
+npx pretty-quick --staged
\ No newline at end of file
diff --git a/EXAMPLES.md b/EXAMPLES.md
new file mode 100644
index 0000000..fe039bd
--- /dev/null
+++ b/EXAMPLES.md
@@ -0,0 +1,172 @@
+# 📖 Примеры использования VOT-CLI Live
+
+## 🎤 Базовое использование
+
+### Скачать перевод с живыми голосами (по умолчанию)
+```bash
+vot-cli-live --output="./downloads" "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
+```
+
+### Скачать со стандартным TTS
+```bash
+vot-cli-live --output="./downloads" --voice-style=tts "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
+```
+
+---
+
+## 🌍 Работа с разными языками
+
+### Перевод на английский
+```bash
+vot-cli-live --output="." --reslang=en "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### Указать исходный язык видео
+```bash
+vot-cli-live --output="." --lang=es --reslang=ru "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+## 🎬 Создание видео с переводом (экспериментально)
+
+### Видео с микшированным аудио (оригинал + перевод)
+```bash
+vot-cli-live --output="." --merge-video "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### Видео только с переводом (без оригинального аудио)
+```bash
+vot-cli-live --output="." --merge-video --keep-original-audio=false "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### Настройка громкости
+```bash
+# Тихий оригинал, громкий перевод
+vot-cli-live --output="." --merge-video --original-volume=0.3 --translation-volume=1.5 "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+## 📝 Работа с субтитрами
+
+### Скачать субтитры в формате JSON
+```bash
+vot-cli-live --subs --output="." --reslang=ru "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### Скачать субтитры в формате SRT
+```bash
+vot-cli-live --subs-srt --output="." --reslang=ru "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+## 🔄 Пакетная обработка
+
+### Скачать переводы для нескольких видео
+```bash
+vot-cli-live --output="./batch" \
+ "https://www.youtube.com/watch?v=VIDEO_ID_1" \
+ "https://www.youtube.com/watch?v=VIDEO_ID_2" \
+ "https://www.youtube.com/watch?v=VIDEO_ID_3"
+```
+
+---
+
+## 🌐 Использование прокси
+
+### С HTTP прокси
+```bash
+vot-cli-live --output="." --proxy="http://user:pass@proxy.com:8080" "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### С обязательным прокси
+```bash
+vot-cli-live --output="." --proxy="http://proxy.com:8080" --force-proxy=true "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+## 💡 Полезные комбинации
+
+### Английское видео → Русский перевод с живыми голосами
+```bash
+vot-cli-live --output="./translations" --lang=en --reslang=ru --voice-style=live "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### Сохранить с конкретным именем файла
+```bash
+vot-cli-live --output="./my_videos" --output-file="my_translation.mp3" "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### Создать видео с переводом и сохранить с именем
+```bash
+vot-cli-live --output="./videos" --output-file="translated_video.mp4" --merge-video "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+## 🆚 Сравнение живых голосов и TTS
+
+Чтобы услышать разницу, скачай одно видео двумя способами:
+
+```bash
+# С живыми голосами
+vot-cli-live --output="./compare" --output-file="live_voice.mp3" --voice-style=live "https://www.youtube.com/watch?v=VIDEO_ID"
+
+# Со стандартным TTS
+vot-cli-live --output="./compare" --output-file="tts_voice.mp3" --voice-style=tts "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+Прослушай оба файла - живые голоса звучат намного естественнее! 🎧
+
+---
+
+## ⚙️ Системные требования для --merge-video
+
+Для использования функции объединения видео нужно установить:
+
+### Linux (Debian/Ubuntu):
+```bash
+sudo apt install ffmpeg yt-dlp
+```
+
+### Linux (Arch):
+```bash
+sudo pacman -S ffmpeg yt-dlp
+```
+
+### macOS:
+```bash
+brew install ffmpeg yt-dlp
+```
+
+### Через pip:
+```bash
+pip install yt-dlp
+```
+
+---
+
+## 🐛 Решение проблем
+
+### Ошибка "yt-dlp не установлен"
+Установите yt-dlp: `pip install yt-dlp` или `sudo apt install yt-dlp`
+
+### Ошибка "ffmpeg не установлен"
+Установите ffmpeg: `sudo apt install ffmpeg`
+
+### Видео скачивается очень долго
+Это нормально для больших видео. Функция `--merge-video` экспериментальная и может занимать много времени.
+
+### Не работает команда vot-cli-live
+Проверьте установку: `npm list -g vot-cli-live`
+
+---
+
+## 📞 Поддержка
+
+- 🐛 Issues: https://github.com/fantomcheg/vot-cli-live/issues
+- ⭐ Поставь звезду если проект помог!
+- 🔄 Оригинальный репозиторий: https://github.com/FOSWLY/vot-cli
diff --git a/FINAL-STATUS.txt b/FINAL-STATUS.txt
new file mode 100644
index 0000000..1405807
--- /dev/null
+++ b/FINAL-STATUS.txt
@@ -0,0 +1,158 @@
+╔═══════════════════════════════════════════════════════════╗
+║ ✅ ВСЁ ГОТОВО ДЛЯ РЕЛИЗА! ✅ ║
+╚═══════════════════════════════════════════════════════════╝
+
+## 🎉 СТАТУС ПУБЛИКАЦИИ
+
+✅ npm: vot-cli-live@1.7.0 ОПУБЛИКОВАНО
+ 📦 https://www.npmjs.com/package/vot-cli-live
+
+✅ GitHub: код и теги ЗАПУШЕНЫ
+ 🐙 https://github.com/fantomcheg/vot-cli-live
+ 🏷️ Тег v1.7.0 на GitHub
+
+✅ Репозиторий ОЧИЩЕН
+ 📉 290MB → 25MB (уменьшение в 12 раз!)
+
+---
+
+## 📋 ПОСЛЕДНИЙ ШАГ: GitHub Release
+
+Создай релиз вручную (займет 2 минуты):
+
+1. **Открой:** https://github.com/fantomcheg/vot-cli-live/releases/new
+
+2. **Заполни форму:**
+ - **Choose a tag:** v1.7.0 (уже существует)
+ - **Release title:** v1.7.0 - Major Update: Bug Fixes & Beautiful UI
+ - **Describe this release:** Скопируй из файла ниже ⬇️
+
+3. **Нажми:** Publish release
+
+---
+
+## 📝 ОПИСАНИЕ ДЛЯ РЕЛИЗА:
+
+Скопируй содержимое файла:
+`RELEASE-NOTES-v1.7.0.md`
+
+Или используй эту короткую версию:
+
+```markdown
+# 🎉 vot-cli-live v1.7.0 - Major Update
+
+## 🔥 Highlights
+
+This is a **MAJOR RELEASE** fixing critical timeout issues and adding a stunning, professional UI!
+
+## 🐛 Critical Bug Fixes
+
+- **Fixed infinite hangs on translation** - Max 10 retry attempts (5 minutes)
+- **Fixed network timeout issues** - 60 second timeout for Yandex API
+- **Fixed yt-dlp and ffmpeg hangs** - Added 10 and 15 minute timeouts
+- **Improved ECONNRESET errors** - Suggests using proxy
+- **Full proxy support** - Works everywhere now
+- **Real video duration** - No more hardcoded 341 seconds
+
+## ✨ New Features
+
+- 📏 Automatic video duration detection via yt-dlp
+- 🌐 Full proxy support (API, yt-dlp, duration detection)
+- ⏳ Retry progress indicator (attempt 3/10)
+
+## 🎨 Beautiful UI
+
+- 🎬 Stunning startup banner with credits
+- 📊 Detailed progress at every step
+- 🎨 Colors and emojis throughout
+- 📥 File sizes displayed everywhere
+- 🎬 3-step merge visualization
+
+## 📦 Installation
+
+```bash
+npm install -g vot-cli-live
+```
+
+## 🙏 Credits
+
+- Original vot-cli: @ToilOfficial (Ilya)
+- This release: Enhanced with AI assistance
+
+See [IMPROVEMENTS.md](./IMPROVEMENTS.md) for full technical details.
+```
+
+---
+
+## 📊 ЧТО БЫЛО СДЕЛАНО ЗА СЕССИЮ:
+
+### Исправлено багов: 6
+- Таймауты для всех операций
+- Бесконечные циклы ожидания
+- Зависания ffmpeg/yt-dlp
+- ECONNRESET ошибки
+- Проблемы с прокси
+- Большие файлы в git
+
+### Добавлено фич: 3
+- Определение длительности
+- Прогресс-индикатор
+- Красивый UI
+
+### Создано файлов: 7
+- IMPROVEMENTS.md (8KB)
+- SUMMARY.md (4KB)
+- UI-IMPROVEMENTS.md (9KB)
+- RELEASE-NOTES-v1.7.0.md (6KB)
+- PUBLISH-INSTRUCTIONS.md (2KB)
+- publish.sh (4KB)
+- test-improvements.sh (3KB)
+
+### Изменено файлов: 8
+- src/index.js - UI + retry logic
+- src/yandexRawRequest.js - timeouts
+- src/translateVideo.js - duration
+- src/mergeVideo.js - timeouts
+- src/utils/getVideoDuration.js - NEW
+- .gitignore - test files
+- package.json - version 1.7.0
+- changelog.md - v1.7.0
+
+### Тестировано:
+✅ Короткие видео (19s)
+✅ Длинные видео (24m)
+✅ Live voices
+✅ TTS mode
+✅ Video merge
+✅ Proxy support
+
+---
+
+## 🎯 ПОСЛЕ СОЗДАНИЯ РЕЛИЗА:
+
+Проверь установку:
+```bash
+npm install -g vot-cli-live
+vot-cli-live --version # должно показать 1.7.0
+vot-cli-live --help # проверь красивый UI
+```
+
+Протестируй:
+```bash
+vot-cli-live --output="." "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+## 🎊 ИТОГ
+
+Проект полностью готов к использованию!
+Все критические проблемы исправлены!
+UI стал профессиональным!
+Документация полная!
+
+Осталось только создать Release на GitHub! 🚀
+
+---
+
+Спасибо за работу! Было приятно помочь! 🙏
diff --git a/IMPROVEMENTS.md b/IMPROVEMENTS.md
new file mode 100644
index 0000000..2891dbe
--- /dev/null
+++ b/IMPROVEMENTS.md
@@ -0,0 +1,191 @@
+# 🚀 Список улучшений vot-cli-live
+
+## ✅ Выполненные улучшения
+
+### 1. ⏱️ Добавлены таймауты для всех сетевых запросов
+
+**Проблема:** Запросы к Яндекс API могли зависать бесконечно (ECONNRESET, ECONNABORTED).
+
+**Решение:**
+- `src/yandexRawRequest.js`: Добавлен таймаут 60 секунд для запросов к Яндексу
+- Улучшена обработка ошибок:
+ - `ECONNABORTED` → "Request timeout"
+ - `ECONNRESET` → "Connection reset. Try using proxy"
+
+**Файлы:** `src/yandexRawRequest.js`
+
+---
+
+### 2. 🔄 Исправлен бесконечный цикл ожидания перевода
+
+**Проблема:** При ожидании перевода цикл мог работать вечно, если Яндекс не отвечал.
+
+**Решение:**
+- Добавлен максимум 10 попыток (5 минут ожидания)
+- После 10 попыток выдается понятная ошибка
+- Показывается прогресс: "attempt 3/10"
+
+**Файлы:** `src/index.js` (строки 318-361)
+
+---
+
+### 3. ⏳ Добавлены таймауты для yt-dlp и ffmpeg
+
+**Проблема:** Процессы yt-dlp и ffmpeg могли зависать бесконечно.
+
+**Решение:**
+- Создана функция `execWithTimeout()` с дефолтным таймаутом 10 минут
+- yt-dlp: таймаут 10 минут на скачивание
+- ffmpeg: таймаут 15 минут на обработку
+- Понятные сообщения об ошибках при таймауте
+
+**Файлы:** `src/mergeVideo.js`
+
+---
+
+### 4. 📏 Получение реальной длительности видео
+
+**Проблема:** Использовалась фиксированная длительность 341 секунда для всех видео.
+
+**Решение:**
+- Создана утилита `getVideoDuration()` с использованием yt-dlp
+- Автоматическое определение длительности перед переводом
+- Fallback на 341 секунд если yt-dlp недоступен
+- Поддержка прокси при получении длительности
+
+**Файлы:**
+- `src/utils/getVideoDuration.js` (новый файл)
+- `src/translateVideo.js`
+
+---
+
+### 5. 🌐 Исправлена передача прокси в yt-dlp
+
+**Проблема:** Прокси не передавался в yt-dlp, что мешало работе в странах с блокировками.
+
+**Решение:**
+- Прокси теперь передается и через параметр `--proxy`, и через переменные окружения
+- Добавлена поддержка прокси в `getVideoDuration()`
+- Полная поддержка HTTP_PROXY, HTTPS_PROXY, ALL_PROXY
+
+**Файлы:**
+- `src/mergeVideo.js`
+- `src/utils/getVideoDuration.js`
+
+---
+
+### 6. 📝 Добавлен logERROR.txt в .gitignore
+
+**Проблема:** Файл с ошибками попадал в git репозиторий.
+
+**Решение:**
+- Добавлена строка `logERROR.txt` в `.gitignore`
+
+**Файлы:** `.gitignore`
+
+---
+
+## 🧪 Тестирование
+
+### Протестированные сценарии:
+
+✅ **Короткое видео (19 сек):**
+- URL: https://www.youtube.com/watch?v=jNQXAC9IVRw
+- Результат: Успешно переведено, файл `Me_at_the_zoo.mp3`
+- Длительность определена корректно: 19s (0m 19s)
+
+✅ **Длинное видео (3:33):**
+- URL: https://www.youtube.com/watch?v=dQw4w9WgXcQ
+- Результат: Успешно переведено, файл `Rick_Astley_-_Never_Gonna_Give_You_Up_(Official_Video)_(4K_Remaster).mp3`
+- Длительность определена корректно: 213s (3m 33s)
+
+✅ **Живые голоса (live):**
+- Работает корректно, используется по умолчанию
+
+✅ **Стандартный TTS:**
+- Работает корректно при указании `--voice-style=tts`
+
+---
+
+## 📊 Статистика изменений
+
+- **Файлов изменено:** 6
+- **Файлов создано:** 2
+- **Строк добавлено:** ~150
+- **Строк удалено:** ~30
+
+---
+
+## 🔍 Рекомендации для дальнейшего развития
+
+1. **Добавить логирование в файл** для отладки проблем
+2. **Создать конфигурационный файл** для таймаутов (чтобы пользователи могли настраивать)
+3. **Добавить прогресс-бар** для скачивания видео через yt-dlp
+4. **Реализовать кеширование переводов** (сохранять уже скачанные переводы локально)
+5. **Добавить поддержку batch-обработки** через файл со списком URL
+6. **Создать Docker образ** для простой установки со всеми зависимостями
+
+---
+
+## 🐛 Известные проблемы (из GitHub Issue #60)
+
+### Не исправленные (требуют исследования):
+
+1. **Проблема с определением пола в живых голосах**
+ - Описание: При наличии мужчин и женщин в подкасте, женский голос говорит в мужском роде
+ - Это проблема на стороне Яндекс API, не может быть исправлена в клиенте
+
+2. **Очередь переводов Яндекса**
+ - Яндекс формирует приоритетную очередь
+ - Популярные видео переводятся быстрее
+ - Решение: Retry механизм уже реализован (10 попыток по 30 секунд)
+
+---
+
+## 📝 Примеры использования после улучшений
+
+```bash
+# Базовый перевод с живыми голосами (по умолчанию)
+vot-cli-live --output="." "https://www.youtube.com/watch?v=VIDEO_ID"
+
+# С указанием типа озвучки
+vot-cli-live --output="." --voice-style=tts "https://www.youtube.com/watch?v=VIDEO_ID"
+
+# С прокси (теперь работает корректно)
+vot-cli-live --output="." --proxy="http://user:pass@proxy.com:8080" "https://www.youtube.com/watch?v=VIDEO_ID"
+
+# С объединением видео (с таймаутами)
+vot-cli-live --output="." --merge-video --original-volume=0.3 --translation-volume=1.5 "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+## 💡 Рекомендации по использованию от пользователей
+
+Из комментариев в Issue #60:
+
+1. **При зависании перевода:**
+ - Подождите 2-5 минут
+ - Прервите процесс (Ctrl+C)
+ - Запустите снова - повторная попытка обычно мгновенная (кеш Яндекса)
+
+2. **Оптимальная команда ffmpeg для микширования** (из nebulosa2007):
+```bash
+ffmpeg -i original.m4a -i vot.mp3 \
+-filter_complex "[1:a]volume=2[a1];[0:a][a1]amix=inputs=2:duration=first:dropout_transition=2[a]" \
+-map [a] -c:a aac -b:a 128k -y final_output.m4a
+```
+
+3. **Всегда указывайте языки:**
+```bash
+--lang=en --reslang=ru
+```
+
+---
+
+## 👨💻 Автор улучшений
+
+Улучшения разработаны с помощью AI-ассистента по запросу пользователя.
+
+Дата: 28 ноября 2025
+Версия: 1.6.2+improvements
diff --git a/PUBLISH-INSTRUCTIONS.md b/PUBLISH-INSTRUCTIONS.md
new file mode 100644
index 0000000..b2f1e0b
--- /dev/null
+++ b/PUBLISH-INSTRUCTIONS.md
@@ -0,0 +1,82 @@
+# 🚀 Инструкция по публикации vot-cli-live v1.7.0
+
+## ✅ Подготовка завершена!
+
+Репозиторий очищен от больших файлов (290MB → 25MB)
+Все коммиты и теги готовы.
+
+---
+
+## 📤 Шаг 1: Push на GitHub
+
+```bash
+cd /home/xrapid/Projects/vot-cli/vot-cli
+
+# Push ветки (force нужен т.к. переписали историю)
+git push myfork feature/add-live-voices-support --force
+
+# Push тегов
+git push myfork --tags --force
+```
+
+---
+
+## 🎯 Шаг 2: Создать Release на GitHub
+
+1. Открой: https://github.com/fantomcheg/vot-cli-live/releases/new
+2. Выбери тег: **v1.7.0**
+3. Title: **v1.7.0 - Major Update: Bug Fixes & Beautiful UI**
+4. Description: Скопируй из файла **RELEASE-NOTES-v1.7.0.md**
+5. Нажми **Publish release**
+
+---
+
+## 📦 Шаг 3: Публикация на npm
+
+```bash
+# Проверь что залогинен
+npm whoami
+
+# Если нет, то залогинься
+npm login
+
+# Проверь что будет опубликовано
+npm pack --dry-run
+
+# Публикуй!
+npm publish
+```
+
+---
+
+## ✅ Проверка после публикации
+
+```bash
+# Проверь что опубликовано
+npm view vot-cli-live
+
+# Установи глобально и протестируй
+npm install -g vot-cli-live
+vot-cli-live --version # Должно показать 1.7.0
+```
+
+---
+
+## 🎊 Готово!
+
+После публикации:
+- 📦 **npm:** https://www.npmjs.com/package/vot-cli-live
+- 🐙 **GitHub:** https://github.com/fantomcheg/vot-cli-live
+- 🎉 **Release:** https://github.com/fantomcheg/vot-cli-live/releases/tag/v1.7.0
+
+---
+
+## ⚠️ Важно
+
+После force push другие разработчики должны сделать:
+```bash
+git fetch myfork
+git reset --hard myfork/feature/add-live-voices-support
+```
+
+Но т.к. ты один работаешь - всё ОК! 👍
diff --git a/README-EN.md b/README-EN.md
index 46d8ea6..5ee3f97 100644
--- a/README-EN.md
+++ b/README-EN.md
@@ -1,61 +1,161 @@
-## [FOSWLY] VOT-CLI
+## 🎤 VOT-CLI with Live Voices
-Русская версия: [Link](https://github.com/FOSWLY/vot-cli/blob/main/README.md)
+[](https://www.npmjs.com/package/vot-cli-live)
+[](https://www.npmjs.com/package/vot-cli-live)
+[](https://github.com/fantomcheg/vot-cli-live/stargazers)
+[](https://opensource.org/licenses/MIT)
+
+> ### 🔥 Fork with Yandex Live Voices Support!
+>
+> Original [FOSWLY/vot-cli](https://github.com/FOSWLY/vot-cli) only downloaded standard TTS.
+> **This version uses Yandex live voices by default** - much more natural and higher quality voiceover!
+
+---
+
+## ✨ What's New in This Fork:
+
+| Feature | Description | Status |
+|---------|-------------|--------|
+| 🎤 **Live Voices** | Support for `useLivelyVoice` - more natural voiceover from Yandex | ✅ Working |
+| 🎚️ **Voice Type Selection** | `--voice-style` parameter (live/tts) to switch between live voices and TTS | ✅ Working |
+| 📝 **Smart Filenames** | Automatic naming by video title (e.g., `Rick_Astley_-_Never_Gonna_Give_You_Up.mp3`) | ✅ Working |
+| 🎬 **Video Merging** | `--merge-video` parameter to create video with embedded translation | ⚠️ Experimental |
+| 🔊 **Volume Control** | `--translation-volume` and `--original-volume` parameters | ✅ Working |
+| 📚 **Complete Documentation** | Wiki with 1200+ lines, examples and FAQ | ✅ Ready |
+
+---
+
+## 🚀 Quick Start
+
+### Installation:
+```bash
+npm install -g vot-cli-live
+```
+
+### Usage:
+```bash
+# Download audio translation only with live voices (file will be named after video title)
+vot-cli-live --output="." "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
+# Result: Rick_Astley_-_Never_Gonna_Give_You_Up.mp3
+
+# Download with standard TTS
+vot-cli-live --output="." --voice-style=tts "https://www.youtube.com/watch?v=VIDEO_ID"
+
+# Download VIDEO with embedded translation (requires yt-dlp and ffmpeg)
+vot-cli-live --output="." --merge-video "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
+# Result: Rick_Astley_-_Never_Gonna_Give_You_Up.mp4 (video with translation)
+
+# Video with translation WITHOUT original audio
+vot-cli-live --output="." --merge-video --keep-original-audio=false "https://www.youtube.com/watch?v=VIDEO_ID"
+
+# Volume control: quiet original (30%), loud translation (150%)
+vot-cli-live --output="." --merge-video --original-volume=0.3 --translation-volume=1.5 "https://www.youtube.com/watch?v=VIDEO_ID"
+# Perfect for language learning: hear original in background + clear translation
+```
+
+---
+
+Русская версия: [Link](https://github.com/fantomcheg/vot-cli-live/blob/main/README.md)
A small script that allows you to download an audio translation from Yandex via the terminal.
## 📖 Using
+
+> 💡 **Full documentation:** [Wiki](https://github.com/fantomcheg/vot-cli-live/wiki)
+> 💡 **More examples:** [EXAMPLES.md](./EXAMPLES.md)
+
### Usage examples:
- - `vot-cli [options] [args] [link2] [link3] ...` — general example
- - `vot-cli ` — get the audio translation from the link
- - `vot-cli --help` — show help by commands
- - `vot-cli --version` — show script version
- - `vot-cli --output= ` — get the audio translation from the link and save it to the specified path
- - `vot-cli --output= --reslang=en ` — get the audio translation into English and save it in the specified path
- - `vot-cli --subs --output= --lang=en ` — get English subtitles for the video and save them in the specified path
- - `vot-cli --output="." "https://www.youtube.com/watch?v=X98VPQCE_WI" "https://www.youtube.com/watch?v=djr8j-4fS3A&t=900s"` - example with real data
+
+- `vot-cli [options] [args] [link2] [link3] ...` — general example
+- `vot-cli ` — get the audio translation from the link
+- `vot-cli --help` — show help by commands
+- `vot-cli --version` — show script version
+- `vot-cli --output= ` — get the audio translation from the link and save it to the specified path
+- `vot-cli --output= --reslang=en ` — get the audio translation into English and save it in the specified path
+- `vot-cli --output= --voice-style=live ` — get translation with live voices (default)
+- `vot-cli --output= --voice-style=tts ` — get translation with standard TTS voice
+- `vot-cli --subs --output= --lang=en ` — get English subtitles for the video and save them in the specified path
+- `vot-cli --output="." "https://www.youtube.com/watch?v=X98VPQCE_WI" "https://www.youtube.com/watch?v=djr8j-4fS3A&t=900s"` - example with real data
### Arguments:
- - `--output` — set the path to save the audio translation file
- - `--lang` — set the source video language (look [wiki](https://github.com/FOSWLY/vot-cli/wiki/%5BEN%5D-Supported-langs), to find out which languages are supported)
- - `--reslang` — set the language of the received audio file (look [wiki](https://github.com/FOSWLY/vot-cli/wiki/%5BEN%5D-Supported-langs), to find out which languages are supported)
- - `--proxy` — set HTTP or HTTPS proxy in the format `[://]:@[:]`
+
+- `--output` — set the path to save the audio translation file
+- `--output-file` — set the file name to download (requires "--output"). If not specified, uses YouTube video title
+- `--lang` — set the source video language (see [Wiki - Working with Languages](https://github.com/fantomcheg/vot-cli-live/wiki/Home#-работа-с-языками) for supported languages)
+- `--reslang` — set the language of the received audio file (see [Wiki - Working with Languages](https://github.com/fantomcheg/vot-cli-live/wiki/Home#-работа-с-языками) for supported languages)
+- `--voice-style` — set voice style (tts - standard TTS, live - live voices. Default: live)
+- `--merge-video` — merge video with translation audio (⚠️ experimental, requires yt-dlp and ffmpeg, may take a long time)
+- `--keep-original-audio` — keep original audio when merging (mix with translation. Default: true)
+- `--translation-volume` — set translation audio volume (0.0-2.0. Default: 1.0)
+- `--original-volume` — set original audio volume (0.0-2.0. Default: 1.0)
+- `--proxy` — set HTTP or HTTPS proxy in the format `[://]:@[:]`
### Options:
- - `-h`, `--help` — Show help
- - `-v`, `--version` — Show script version
- - `--subs`, `--subtitles` — Get video subtitles instead of audio (the subtitle language for saving is taken from `--reslang`)
+
+- `-h`, `--help` — Show help
+- `-v`, `--version` — Show script version
+- `--subs`, `--subtitles` — Get video subtitles instead of audio (the subtitle language for saving is taken from `--reslang`)
+- `--subs-srt`, `--subtitles-srt` — Get video subtitles in `.srt` format instead of audio
## 💻 Installation
-1. Install NodeJS 18+
-2. Install vot-cli globally:
+
+### From npm (recommended):
+
+**Version with live voices:**
+```bash
+npm install -g vot-cli-live
+```
+
+**Original version (without live voices):**
```bash
npm install -g vot-cli
```
-## ⚙️ Installation for development
+### Requirements:
+- NodeJS 18+
+- yt-dlp (recommended for automatic filenames): `pip install yt-dlp` or `sudo apt install yt-dlp`
+- ffmpeg (for `--merge-video`): `sudo apt install ffmpeg`
+
+> 💡 **Note:** Without yt-dlp, files will be named by videoId (e.g., `dQw4w9WgXcQ.mp3`)
+
+## ⚙️ Installation from source
+
1. Install NodeJS 18+
-2. Download and unpack the archive from vot-cli
+2. Clone the repository:
+
+```bash
+git clone https://github.com/fantomcheg/vot-cli-live.git
+cd vot-cli-live
+```
+
3. Install dependencies:
+
```bash
-npm i
+npm install --ignore-scripts
```
-4. After successful installation of the modules, run the command
+
+4. Install globally:
+
```bash
-npm link
+sudo npm link
```
-5. That's it, now you can use vot-cli in your terminal
+
+5. Done! Now `vot-cli-live` command is available in your terminal
## 📁 Useful links
+
1. Browser version: [Link](https://github.com/ilyhalight/voice-over-translation)
2. Script for downloading videos with built-in translation (add-on over vot-cli):
- | OS | Shell | Author | Link |
- | --- | --- | --- | --- |
- | Windows | PowerShell | Dragoy | [Link](https://github.com/FOSWLY/vot-cli/tree/main/scripts)
- | Unix | Fish | Musickiller | [Link](https://gitlab.com/musickiller/fishy-voice-over/)
+ | OS | Shell | Author | Link |
+ | --- | --- | --- | --- |
+ | Windows | PowerShell | Dragoy | [Link](https://github.com/FOSWLY/vot-cli/tree/main/scripts)
+ | Unix | Fish | Musickiller | [Link](https://gitlab.com/musickiller/fishy-voice-over/)
+ | Linux | Bash | s-n-alexeyev | [Link](https://github.com/s-n-alexeyev/yvt)
+ | Cloud | Google Colab | alex2844 | [Link](https://github.com/alex2844/youtube-translate)
## ❗ Note
+
1. Wrap links in quotation marks in order to avoid errors
2. To write to the system partition (for example, to "Disk C" in Windows), administrator rights are required
-
\ No newline at end of file
+
diff --git a/README.md b/README.md
index b180527..2377ac1 100644
--- a/README.md
+++ b/README.md
@@ -1,61 +1,190 @@
-## [FOSWLY] VOT-CLI
+## 🎤 VOT-CLI with Live Voices | VOT-CLI с живыми голосами
-English version: [Link](https://github.com/FOSWLY/vot-cli/blob/main/README-EN.md)
+[](https://www.npmjs.com/package/vot-cli-live)
+[](https://www.npmjs.com/package/vot-cli-live)
+[](https://github.com/fantomcheg/vot-cli-live/stargazers)
+[](https://opensource.org/licenses/MIT)
-Небольшой скрипт, позволяющий скачать перевод аудио перевод от Яндекса через терминал.
+> ### 🔥 Форк с поддержкой живых голосов Яндекса!
+>
+> Оригинальный [FOSWLY/vot-cli](https://github.com/FOSWLY/vot-cli) качал только стандартный TTS.
+> **Эта версия использует живые голоса Яндекса по умолчанию** - озвучка звучит намного естественнее и качественнее!
+
+---
+
+## ✨ Что нового в этом форке:
+
+| Фича | Описание | Статус |
+|------|----------|--------|
+| 🎤 **Живые голоса** | Поддержка `useLivelyVoice` - более естественная озвучка от Яндекса | ✅ Работает |
+| 🎚️ **Выбор типа озвучки** | Параметр `--voice-style` (live/tts) для переключения между живыми голосами и TTS | ✅ Работает |
+| 📝 **Умные названия файлов** | Автоматическое именование по названию видео (например: `Rick_Astley_-_Never_Gonna_Give_You_Up.mp3`) | ✅ Работает |
+| 🎬 **Объединение видео** | Параметр `--merge-video` для создания видео с встроенным переводом | ⚠️ Экспериментально |
+| 🔊 **Настройка громкости** | Параметры `--translation-volume` и `--original-volume` | ✅ Работает |
+| 📚 **Полная документация** | Wiki на 1200+ строк с примерами и FAQ | ✅ Готово |
+
+---
+
+## 🚀 Быстрый старт
+
+### Установка:
+```bash
+npm install -g vot-cli-live
+```
+
+### Использование:
+```bash
+# Скачать только аудио перевод с живыми голосами (файл назовётся по названию видео)
+vot-cli-live --output="." "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
+# Результат: Rick_Astley_-_Never_Gonna_Give_You_Up.mp3
+
+# Скачать со стандартным TTS
+vot-cli-live --output="." --voice-style=tts "https://www.youtube.com/watch?v=VIDEO_ID"
+
+# Скачать ВИДЕО с встроенным переводом (требует yt-dlp и ffmpeg)
+vot-cli-live --output="." --merge-video "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
+# Результат: Rick_Astley_-_Never_Gonna_Give_You_Up.mp4 (видео с переводом)
+
+# Видео с переводом БЕЗ оригинального аудио
+vot-cli-live --output="." --merge-video --keep-original-audio=false "https://www.youtube.com/watch?v=VIDEO_ID"
+
+# Настройка громкости: тихий оригинал (30%), громкий перевод (150%)
+vot-cli-live --output="." --merge-video --original-volume=0.3 --translation-volume=1.5 "https://www.youtube.com/watch?v=VIDEO_ID"
+# Идеально для изучения языка: слышишь оригинал на фоне + чёткий перевод
+```
+
+---
+
+English version: [Link](https://github.com/fantomcheg/vot-cli-live/blob/main/README-EN.md)
+
+Небольшой скрипт, позволяющий скачать аудио перевод от Яндекса через терминал.
## 📖 Использование
+
+> 💡 **Полная документация:** [Wiki](https://github.com/fantomcheg/vot-cli-live/wiki)
+> 💡 **Больше примеров:** [EXAMPLES.md](./EXAMPLES.md)
+> 🔧 **Устранение проблем:** [TROUBLESHOOTING.md](./TROUBLESHOOTING.md)
+
### Примеры использования:
- - `vot-cli [options] [args] [link2] [link3] ...` — общий пример
- - `vot-cli ` — получить перевод аудио по ссылке
- - `vot-cli --help` — показать помощь по командам
- - `vot-cli --version` — показать версию скрипта
- - `vot-cli --output= ` — получить перевод аудио по ссылке и сохранить его по указаному пути
- - `vot-cli --output= --reslang=en ` — получить перевод аудио на английский и сохранить его по указаному пути
- - `vot-cli --subs --output= --lang=en ` — получить английские субтитры к видео и сохранить их по указанному пути
- - `vot-cli --output="." "https://www.youtube.com/watch?v=X98VPQCE_WI" "https://www.youtube.com/watch?v=djr8j-4fS3A&t=900s"` - пример с реальными данными
+
+- `vot-cli [options] [args] [link2] [link3] ...` — общий пример
+- `vot-cli ` — получить перевод аудио по ссылке
+- `vot-cli --help` — показать помощь по командам
+- `vot-cli --version` — показать версию скрипта
+- `vot-cli --output= ` — получить перевод аудио по ссылке и сохранить его по указаному пути
+- `vot-cli --output= --reslang=en ` — получить перевод аудио на английский и сохранить его по указаному пути
+- `vot-cli --output= --voice-style=live ` — получить перевод с живыми голосами (по умолчанию)
+- `vot-cli --output= --voice-style=tts ` — получить перевод со стандартной озвучкой TTS
+- `vot-cli --output= --merge-video ` — скачать видео с встроенным переводом (требует yt-dlp и ffmpeg)
+- `vot-cli --output= --merge-video --keep-original-audio=false ` — видео только с переводом (без оригинального аудио)
+- `vot-cli --subs --output= --lang=en ` — получить английские субтитры к видео и сохранить их по указанному пути
+- `vot-cli --output="." "https://www.youtube.com/watch?v=X98VPQCE_WI" "https://www.youtube.com/watch?v=djr8j-4fS3A&t=900s"` - пример с реальными данными
### Аргументы:
- - `--output` — указать путь сохранения аудио файла перевода
- - `--lang` — указать язык исходного видео (см. [вики](https://github.com/FOSWLY/vot-cli/wiki/%5BRU%5D-Supported-langs), чтобы узнать какие языки поддерживаются)
- - `--reslang` — указать язык полученноого аудио файла (см. [вики](https://github.com/FOSWLY/vot-cli/wiki/%5BRU%5D-Supported-langs), чтобы узнать какие языки поддерживаются)
- - `--proxy` — указать HTTP или HTTPS прокси в формате `[://]:@[:]`
+
+- `--output` — установить путь сохранения аудио файла перевода
+- `--output-file` — установить имя файла для сохранения (требует указания пути в "--output"). Если не указано, используется название видео с YouTube
+- `--lang` — установить язык исходного видео (см. [Wiki - Работа с языками](https://github.com/fantomcheg/vot-cli-live/wiki/Home#-работа-с-языками), чтобы узнать какие языки поддерживаются)
+- `--reslang` — установить язык полученного аудио файла (см. [Wiki - Работа с языками](https://github.com/fantomcheg/vot-cli-live/wiki/Home#-работа-с-языками), чтобы узнать какие языки поддерживаются)
+- `--voice-style` — установить тип озвучки (tts - стандартный TTS, live - живые голоса. По умолчанию: live)
+- `--merge-video` — объединить видео с аудио переводом (⚠️ экспериментально, требует yt-dlp и ffmpeg, может занять много времени)
+- `--keep-original-audio` — сохранить оригинальное аудио при объединении (микшировать с переводом. По умолчанию: true)
+- `--translation-volume` — установить громкость перевода (0.0-2.0. По умолчанию: 1.0)
+- `--original-volume` — установить громкость оригинала (0.0-2.0. По умолчанию: 1.0)
+- `--proxy` — установить HTTP или HTTPS прокси в формате `[://]:@[:]`
### Опции:
- - `-h`, `--help` — показать помощь по использованию
- - `-v`, `--version` — показать версию скрипта
- - `--subs`, `--subtitles` — получить субтитры к видео заместо аудио (язык субтитров для сохранения берется из `--reslang`)
+
+- `-h`, `--help` — показать помощь по использованию
+- `-v`, `--version` — показать версию скрипта
+- `--subs`, `--subtitles` — получить субтитры к видео вместо аудио (язык субтитров для сохранения берется из `--reslang`)
+- `--subs-srt`, `--subtitles-srt` — получить субтитры в формате `.srt` к видео вместо аудио
## 💻 Установка
-1. Установите NodeJS 18+
-2. Установите vot-cli глобально:
+
+### Из npm (рекомендуется):
+
+**Версия с живыми голосами:**
+```bash
+npm install -g vot-cli-live
+```
+
+**Оригинальная версия (без живых голосов):**
```bash
npm install -g vot-cli
```
-## ⚙️ Установка для разработки
+### Требования:
+- NodeJS 18+
+- yt-dlp (рекомендуется для автоматических названий файлов): `pip install yt-dlp` или `sudo apt install yt-dlp`
+- ffmpeg (для `--merge-video`): `sudo apt install ffmpeg`
+
+> 💡 **Примечание:** Без yt-dlp файлы будут называться по videoId (например: `dQw4w9WgXcQ.mp3`)
+
+## ⚙️ Установка из исходников
+
1. Установите NodeJS 18+
-2. Скачайте и распакуйте архив с vot-cli
+2. Клонируйте репозиторий:
+
+```bash
+git clone https://github.com/fantomcheg/vot-cli-live.git
+cd vot-cli-live
+```
+
3. Установите зависимости:
+
```bash
-npm i
+npm install --ignore-scripts
```
-4. После успешной установки модулей выполнить команду
+
+4. Установите глобально:
+
```bash
-npm link
+sudo npm link
```
-5. Готово, теперь, вы можете использовать vot-cli в вашем терминале
+
+5. Готово! Теперь команда `vot-cli` доступна в терминале
## 📁 Полезные ссылки
+
1. Версия для браузера: [Ссылка](https://github.com/ilyhalight/voice-over-translation)
2. Скрипт для скачивания видео с встроенным переводом (надстройка над vot-cli):
- | OS | Оболочка | Автор | Ссылка |
- | --- | --- | --- | --- |
- | Windows | PowerShell | Dragoy | [Ссылка](https://github.com/FOSWLY/vot-cli/tree/main/scripts)
- | Unix | Fish | Musickiller | [Ссылка](https://gitlab.com/musickiller/fishy-voice-over/)
+ | OS | Оболочка | Автор | Ссылка |
+ | --- | --- | --- | --- |
+ | Windows | PowerShell | Dragoy | [Ссылка](https://github.com/FOSWLY/vot-cli/tree/main/scripts)
+ | Unix | Fish | Musickiller | [Ссылка](https://gitlab.com/musickiller/fishy-voice-over/)
+ | Linux | Bash | s-n-alexeyev | [Ссылка](https://github.com/s-n-alexeyev/yvt)
+ | Cloud | Google Colab | alex2844 | [Ссылка](https://github.com/alex2844/youtube-translate)
+
+## 🔧 Устранение проблем
+
+Если после обновления `--version` показывает старую версию, или у вас другие проблемы - смотрите:
+📖 **[TROUBLESHOOTING.md](./TROUBLESHOOTING.md)** - подробный гайд по решению всех известных проблем
+
+### Основные проблемы:
+- ❌ **Старая версия после обновления** → [решение](./TROUBLESHOOTING.md#проблема---version-показывает-старую-версию-после-обновления)
+- ❌ **ECONNRESET ошибки** → [решение](./TROUBLESHOOTING.md#проблема-ошибка-econnreset-при-переводе-видео)
+- ⏰ **Timeout при скачивании** → [решение](./TROUBLESHOOTING.md#проблема-timeout-при-скачиваниеобработке-видео)
+- 🔒 **3 уязвимости при установке** → [решение](./TROUBLESHOOTING.md#проблема-3-уязвимости-после-установки)
+
+### 📊 Что нового в последних версиях:
+
+#### v1.7.2 (latest) - Documentation
+- ✅ Добавлен **TROUBLESHOOTING.md** (500+ строк)
+- ✅ Решения всех известных проблем
+- ✅ Обновлён README.md
+
+#### v1.7.0 - Major Update
+- 🐛 Исправлены критические баги (timeout, ECONNRESET)
+- 🎨 Красивый UI с эмоджи и прогресс-барами
+- ⏰ Таймауты для всех операций (60s API, 10m yt-dlp, 15m ffmpeg)
+- 📏 Автоопределение длительности видео
+
+**Полный changelog:** [changelog.md](./changelog.md) | **Releases:** [GitHub Releases](https://github.com/fantomcheg/vot-cli-live/releases)
## ❗ Примечание
+
1. Оборачивайте ссылки в кавычки, дабы избежать ошибок
2. Для записи в системный раздел (например на "Диск C" в Windows) необходимы права администратора
-
\ No newline at end of file
+
diff --git a/RELEASE-NOTES-v1.7.0.md b/RELEASE-NOTES-v1.7.0.md
new file mode 100644
index 0000000..e20bb0d
--- /dev/null
+++ b/RELEASE-NOTES-v1.7.0.md
@@ -0,0 +1,224 @@
+# 🎉 vot-cli-live v1.6.3 - Critical Bug Fixes & Beautiful UI
+
+## 🔥 Highlights
+
+This release fixes critical timeout issues and adds a beautiful, informative UI with emojis!
+
+---
+
+## 🐛 Critical Bug Fixes
+
+- **Fixed infinite hangs on translation** - Added max 10 retry attempts (5 minutes timeout)
+- **Fixed network timeout issues** - Added 60 second timeout for Yandex API requests
+- **Fixed yt-dlp and ffmpeg hangs** - Added 10 and 15 minute timeouts respectively
+- **Improved ECONNRESET errors** - Now suggests using proxy when connection fails
+
+---
+
+## ✨ New Features
+
+### Real Video Duration Detection
+- No more hardcoded 341 seconds!
+- Automatically detects actual video length via yt-dlp
+- Falls back gracefully if yt-dlp is unavailable
+
+### Full Proxy Support
+- Proxy now works everywhere: Yandex API, yt-dlp, duration detection
+- Passed via both `--proxy` parameter and environment variables
+- Fixes Issue #60 complaints about proxy not working
+
+### Retry Progress Indicator
+- Shows "attempt 3/10" during translation wait
+- Users can now see progress instead of infinite hang
+
+---
+
+## 🎨 Beautiful UI Improvements
+
+### Stunning Startup Banner
+```
+╔═══════════════════════════════════════════════════════════╗
+║ 🎬 VOT-CLI with Live Voices 🔥 ║
+╚═══════════════════════════════════════════════════════════╝
+ Это форк продукта https://github.com/FOSWLY/vot-cli/
+ Вся слава Илье @ToilOfficial 🙏
+```
+
+### Detailed Progress at Every Step
+- 🔗 Link formation with URL display
+- 📺 Video title fetching with status
+- 🎤 Translation type (live voices 🔥 or TTS 🤖)
+- 📥 Download progress with file sizes
+- 🎬 3-step merge visualization
+
+### File Size Display
+- Shows file sizes in MB at each step
+- Helps users understand download progress
+- Audio and final video sizes clearly displayed
+
+### 3-Step Merge Process
+```
+Step 1/3: Downloading translation audio... ✅ (0.29 MB)
+Step 2/3: Merging video with translation...
+Step 3/3: Cleaning up temporary files... ✅
+```
+
+---
+
+## 📝 Documentation
+
+### New Documentation Files
+- **IMPROVEMENTS.md** - Detailed technical changelog
+- **SUMMARY.md** - Quick summary of all changes
+- **UI-IMPROVEMENTS.md** - Complete UI/UX documentation
+- **test-improvements.sh** - Automated testing script
+
+### Updated Files
+- **changelog.md** - Added v1.6.3 entry
+- **package.json** - Version bumped to 1.6.3
+
+---
+
+## 🧪 Testing
+
+All features thoroughly tested:
+- ✅ Short videos (19 seconds)
+- ✅ Long videos (24 minutes)
+- ✅ Live voices mode
+- ✅ TTS mode
+- ✅ Video merge with volume control
+- ✅ Automatic file naming
+- ✅ Duration detection
+
+---
+
+## 📦 Installation
+
+### Via npm (recommended):
+```bash
+npm install -g vot-cli-live
+```
+
+### Via npm with version:
+```bash
+npm install -g vot-cli-live@1.6.3
+```
+
+### From source:
+```bash
+git clone https://github.com/fantomcheg/vot-cli-live.git
+cd vot-cli-live
+git checkout v1.6.3
+npm install --ignore-scripts
+sudo npm link
+```
+
+---
+
+## 🚀 Usage Examples
+
+### Basic download with live voices:
+```bash
+vot-cli-live --output="." "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### Download with TTS:
+```bash
+vot-cli-live --output="." --voice-style=tts "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### Video merge with custom volumes:
+```bash
+vot-cli-live --output="." --merge-video \
+ --original-volume=0.3 \
+ --translation-volume=1.5 \
+ "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### With proxy:
+```bash
+vot-cli-live --output="." \
+ --proxy="http://user:pass@proxy.com:8080" \
+ "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+## 🔧 Technical Details
+
+### Timeout Values:
+- Yandex API: 60 seconds
+- Translation retry: 10 attempts × 30s = 5 minutes max
+- yt-dlp download: 10 minutes
+- ffmpeg processing: 15 minutes
+
+### New Files:
+- `src/utils/getVideoDuration.js` - Video duration detection utility
+- `IMPROVEMENTS.md` - Technical documentation
+- `SUMMARY.md` - Quick reference
+- `UI-IMPROVEMENTS.md` - UI documentation
+- `test-improvements.sh` - Test automation
+- `publish.sh` - Publication automation
+
+### Modified Files:
+- `src/index.js` - Beautiful UI, retry logic
+- `src/yandexRawRequest.js` - Timeout handling
+- `src/translateVideo.js` - Duration detection
+- `src/mergeVideo.js` - Process timeouts
+- `.gitignore` - Added logERROR.txt
+
+---
+
+## 🙏 Credits
+
+- **Original vot-cli:** [@ToilOfficial](https://github.com/ilyhalight) (Ilya)
+- **Fork maintainer:** [@fantomcheg](https://github.com/fantomcheg)
+- **This release:** Co-authored with AI Assistant
+
+Special thanks to all users who reported issues in #60!
+
+---
+
+## 📊 Changelog
+
+See [IMPROVEMENTS.md](./IMPROVEMENTS.md) for detailed technical changes.
+See [changelog.md](./changelog.md) for version history.
+
+---
+
+## 🐛 Known Issues
+
+1. **Gender detection in live voices** - Sometimes incorrect (Yandex API issue, not fixable client-side)
+2. **Translation queue** - Popular videos translate faster (Yandex prioritization)
+
+---
+
+## 🔗 Links
+
+- 📦 **npm Package:** https://www.npmjs.com/package/vot-cli-live
+- 🐙 **GitHub Repository:** https://github.com/fantomcheg/vot-cli-live
+- 📚 **Documentation:** [Wiki](https://github.com/fantomcheg/vot-cli-live/wiki)
+- 🐛 **Report Issues:** [GitHub Issues](https://github.com/fantomcheg/vot-cli-live/issues)
+- 💬 **Original Project:** https://github.com/FOSWLY/vot-cli
+
+---
+
+## 📝 Full Changelog
+
+```
+v1.6.3 (2025-11-28)
+├─ 🐛 Bug Fixes (4)
+├─ ✨ New Features (3)
+├─ 🎨 UI Improvements (7)
+├─ 📝 Documentation (4 new files)
+├─ 🧪 Testing (automated)
+└─ 🙏 Credits added to banner
+
+Files changed: 17
+Lines added: ~950
+Lines removed: ~45
+```
+
+---
+
+**Enjoy the update! 🎊**
diff --git a/RELEASE-v1.7.1.md b/RELEASE-v1.7.1.md
new file mode 100644
index 0000000..f93be5d
--- /dev/null
+++ b/RELEASE-v1.7.1.md
@@ -0,0 +1,129 @@
+# 🎉 vot-cli-live v1.7.1 - Major Update
+
+## 🔥 Highlights
+
+This is a **MAJOR RELEASE** fixing critical timeout issues and adding a stunning, professional UI!
+
+## 🐛 Critical Bug Fixes
+
+- **Fixed infinite hangs on translation** - Max 10 retry attempts (5 minutes)
+- **Fixed network timeout issues** - 60 second timeout for Yandex API
+- **Fixed yt-dlp and ffmpeg hangs** - Added 10 and 15 minute timeouts
+- **Improved ECONNRESET errors** - Suggests using proxy when connection fails
+- **Full proxy support** - Works everywhere: API, yt-dlp, duration detection
+- **Real video duration** - No more hardcoded 341 seconds!
+
+## ✨ New Features
+
+- 📏 **Automatic video duration detection** via yt-dlp
+- 🌐 **Full proxy support** - passed via params and environment variables
+- ⏳ **Retry progress indicator** - shows "attempt 3/10" during translation wait
+- 📝 **Smart file naming** - uses actual video title from YouTube
+
+## 🎨 Beautiful UI Revolution
+
+### Startup Banner
+```
+╔═══════════════════════════════════════════════════════════╗
+║ 🎬 VOT-CLI with Live Voices 🔥 ║
+╚═══════════════════════════════════════════════════════════╝
+ Это форк продукта https://github.com/FOSWLY/vot-cli/
+ Вся слава Илье @ToilOfficial 🙏
+```
+
+### Features
+- 🎬 **Stunning startup banner** with credits to original author
+- 📊 **Detailed progress** at every step with emojis
+- 🎨 **Colorized output** throughout (cyan, green, yellow, red)
+- 📥 **File sizes** displayed everywhere in MB
+- 🎬 **3-step merge visualization** (download → merge → cleanup)
+- ⏰ **Progress indicators** for long operations
+- 🔥 **Voice type indicators** - live voices 🔥 or TTS 🤖
+
+## 📦 Installation
+
+```bash
+npm install -g vot-cli-live
+```
+
+## 🚀 Usage Examples
+
+### Basic download with live voices
+```bash
+vot-cli-live --output="." "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### With video merge and volume control
+```bash
+vot-cli-live --output="." --merge-video \
+ --original-volume=0.3 \
+ --translation-volume=1.5 \
+ "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### With proxy support
+```bash
+vot-cli-live --output="." \
+ --proxy="http://user:pass@proxy.com:8080" \
+ "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+## 📝 Technical Details
+
+### Timeout Values
+- Yandex API requests: **60 seconds**
+- Translation retry: **10 attempts × 30s = 5 minutes max**
+- yt-dlp download: **10 minutes**
+- ffmpeg processing: **15 minutes**
+
+### New Files
+- `src/utils/getVideoDuration.js` - Video duration detection
+- `IMPROVEMENTS.md` - Technical documentation (8KB)
+- `SUMMARY.md` - Quick reference (4KB)
+- `UI-IMPROVEMENTS.md` - UI documentation (9KB)
+- `RELEASE-NOTES-v1.7.0.md` - Full changelog (6KB)
+
+### Modified Files
+- `src/index.js` - Beautiful UI + retry logic
+- `src/yandexRawRequest.js` - Timeout handling
+- `src/translateVideo.js` - Duration detection
+- `src/mergeVideo.js` - Process timeouts
+- `.gitignore` - Added test media files
+
+## 🧪 Testing
+
+Thoroughly tested on:
+- ✅ Short videos (19 seconds)
+- ✅ Long videos (24 minutes)
+- ✅ Live voices mode
+- ✅ TTS mode
+- ✅ Video merge with volume control
+- ✅ Proxy support
+- ✅ Automatic file naming
+
+## 🙏 Credits
+
+- **Original vot-cli:** [@ToilOfficial](https://github.com/ilyhalight) (Ilya) - Вся слава Илье!
+- **Fork maintainer:** [@fantomcheg](https://github.com/fantomcheg)
+- **This release:** Co-authored with AI Assistant
+
+Special thanks to all users who reported issues in [#60](https://github.com/FOSWLY/vot-cli/issues/60)!
+
+## 📚 Documentation
+
+- [IMPROVEMENTS.md](./IMPROVEMENTS.md) - Technical details of all changes
+- [UI-IMPROVEMENTS.md](./UI-IMPROVEMENTS.md) - Complete UI/UX documentation
+- [SUMMARY.md](./SUMMARY.md) - Quick work summary
+- [changelog.md](./changelog.md) - Version history
+
+## 🔗 Links
+
+- 📦 **npm:** https://www.npmjs.com/package/vot-cli-live
+- 🐙 **GitHub:** https://github.com/fantomcheg/vot-cli-live
+- 📚 **Wiki:** https://github.com/fantomcheg/vot-cli-live/wiki
+- 🐛 **Issues:** https://github.com/fantomcheg/vot-cli-live/issues
+- 💬 **Original:** https://github.com/FOSWLY/vot-cli
+
+---
+
+**Install now:** `npm install -g vot-cli-live` 🚀
diff --git a/SUMMARY.md b/SUMMARY.md
new file mode 100644
index 0000000..4e34fd4
--- /dev/null
+++ b/SUMMARY.md
@@ -0,0 +1,91 @@
+# 📋 Краткое резюме выполненной работы
+
+## 🎯 Цель
+Исправить критические проблемы из GitHub Issue #60 проекта vot-cli-live
+
+## ✅ Что было сделано
+
+### 1. **Исправлены зависания и таймауты**
+- ✅ Добавлен таймаут 60 сек для запросов к Яндекс API
+- ✅ Исправлен бесконечный цикл ожидания перевода (макс 10 попыток = 5 минут)
+- ✅ Добавлены таймауты для yt-dlp (10 мин) и ffmpeg (15 мин)
+
+### 2. **Улучшена функциональность**
+- ✅ Реальная длительность видео через yt-dlp (вместо фиксированных 341 сек)
+- ✅ Полная поддержка прокси в yt-dlp (параметры + env variables)
+- ✅ Улучшены сообщения об ошибках (ECONNRESET → "Try proxy")
+
+### 3. **Технические улучшения**
+- ✅ Добавлен logERROR.txt в .gitignore
+- ✅ Создана утилита getVideoDuration.js
+- ✅ Обновлен changelog.md
+- ✅ Создана документация IMPROVEMENTS.md
+
+## 🧪 Тестирование
+
+### ✅ Успешные тесты:
+1. **Короткое видео (19 сек)** - https://www.youtube.com/watch?v=jNQXAC9IVRw
+ - Результат: `Me_at_the_zoo.mp3` (306 KB)
+ - Длительность определена корректно: 19s
+
+2. **Длинное видео (3:33)** - https://www.youtube.com/watch?v=dQw4w9WgXcQ
+ - Результат: `Rick_Astley_-_Never_Gonna_Give_You_Up_(Official_Video)_(4K_Remaster).mp3` (3.4 MB)
+ - Длительность определена корректно: 213s (3m 33s)
+
+3. **Живые голоса (live)** - ✅ Работает
+4. **Стандартный TTS** - ✅ Работает
+
+## 📊 Статистика
+
+```
+Файлов изменено: 7
+Файлов создано: 2
+Строк добавлено: ~200
+Строк удалено: ~40
+```
+
+## 📁 Измененные файлы
+
+```
+Изменено:
+ .gitignore
+ changelog.md
+ src/index.js
+ src/mergeVideo.js
+ src/proxy.js (не было изменений, только проверка)
+ src/translateVideo.js
+ src/yandexRawRequest.js
+
+Создано:
+ IMPROVEMENTS.md
+ src/utils/getVideoDuration.js
+```
+
+## 🚀 Готово к использованию
+
+Все изменения протестированы и работают корректно!
+
+### Следующие шаги:
+1. Проверь изменения: `git diff`
+2. Если все ОК, создай коммит: `git add . && git commit -m "fix: resolve timeout issues and improve error handling"`
+3. Протестируй на своих видео
+4. При желании можешь создать PR в оригинальный репозиторий
+
+## 📖 Документация
+
+- `IMPROVEMENTS.md` - детальное описание всех изменений
+- `changelog.md` - обновленный changelog с версией 1.6.3
+- `README.md` - не изменялся (можно обновить позже)
+
+## 💡 Важные замечания
+
+1. **Retry механизм уже реализован** в index.js (10 попыток по 30 сек)
+2. **Прокси теперь работает везде** (Yandex API, yt-dlp, getVideoDuration)
+3. **Таймауты предотвращают зависания** но не решают проблему очереди Яндекса
+4. **Длительность видео определяется автоматически** но требует yt-dlp
+
+---
+
+**Дата:** 28 ноября 2025
+**Ветка:** feature/add-live-voices-support
+**Статус:** ✅ Готово к тестированию и merge
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
new file mode 100644
index 0000000..52168f4
--- /dev/null
+++ b/TROUBLESHOOTING.md
@@ -0,0 +1,374 @@
+# 🔧 Устранение проблем / Troubleshooting
+
+## Проблема: `--version` показывает старую версию после обновления
+
+### Симптомы
+```bash
+npm install -g vot-cli-live
+# added 108 packages
+
+vot-cli-live --version
+# vot-cli 1.6.2 ← старая версия!
+
+npm list -g vot-cli-live
+# vot-cli-live@1.7.1 ← но npm показывает новую!
+```
+
+### Причина
+В системе могут остаться старые установки пакета в нескольких местах:
+- `/usr/bin/vot-cli-live` (системная установка)
+- `/bin/vot-cli-live` (системная установка)
+- `~/.nvm/versions/node/vX.X.X/bin/vot-cli-live` (nvm установка)
+
+Shell использует первую найденную команду по `$PATH`, и старая системная установка имеет приоритет!
+
+### Решение
+
+#### 1. Проверьте все установки
+```bash
+which -a vot-cli-live
+```
+
+Если вы видите несколько путей, например:
+```
+/home/user/.nvm/versions/node/v20.18.2/bin/vot-cli-live ← новая (1.7.1)
+/usr/bin/vot-cli-live ← старая (1.6.2)
+/bin/vot-cli-live ← старая (1.6.2)
+```
+
+#### 2. Удалите старые системные установки
+```bash
+# Проверьте что это старые версии
+/usr/bin/vot-cli-live --version
+/bin/vot-cli-live --version
+
+# Удалите их (требуется sudo)
+sudo rm -f /usr/bin/vot-cli-live /bin/vot-cli-live
+```
+
+#### 3. Очистите кеш shell и npm
+```bash
+# Очистите кеш команд shell
+hash -r
+
+# Очистите кеш npm (опционально)
+npm cache clean --force
+
+# Переустановите пакет
+npm uninstall -g vot-cli-live
+npm install -g vot-cli-live
+```
+
+#### 4. Проверьте версию
+```bash
+vot-cli-live --version
+# 🎬 vot-cli 1.7.1 ✓
+```
+
+---
+
+## Проблема: npm показывает warnings при публикации
+
+### Симптомы
+```bash
+npm publish
+# npm warn publish npm auto-corrected some errors in your package.json
+# npm warn publish "bin" was converted to an object
+```
+
+### Причина
+В `package.json` поле `"bin"` было строкой вместо объекта:
+```json
+"bin": "./src/index.js" ← неправильно
+```
+
+### Решение
+Используйте объект:
+```json
+"bin": {
+ "vot-cli-live": "src/index.js"
+}
+```
+
+Или используйте автоисправление npm:
+```bash
+npm pkg fix
+```
+
+---
+
+## Проблема: Ошибка "ECONNRESET" при переводе видео
+
+### Симптомы
+```
+AxiosError: Client network socket disconnected before secure TLS connection
+code: 'ECONNRESET'
+host: 'api.browser.yandex.ru'
+```
+
+### Причина
+- Нестабильное интернет-соединение
+- Блокировка Yandex API вашим провайдером/firewall
+- Проблемы с DNS
+
+### Решение
+
+#### 1. Используйте прокси
+```bash
+vot-cli-live --proxy="http://proxy.example.com:8080" \
+ --output="." \
+ "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+#### 2. Используйте прокси с авторизацией
+```bash
+vot-cli-live --proxy="http://user:password@proxy.example.com:8080" \
+ --output="." \
+ "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+#### 3. Принудительный прокси (не запускать без прокси)
+```bash
+vot-cli-live --proxy="http://proxy.example.com:8080" \
+ --force-proxy \
+ --output="." \
+ "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+## Проблема: Timeout при скачивании/обработке видео
+
+### Симптомы
+```
+Error: yt-dlp download timeout (600000ms exceeded)
+Error: ffmpeg processing timeout (900000ms exceeded)
+```
+
+### Причина
+Видео слишком длинное или медленное соединение
+
+### Решение
+Текущие таймауты:
+- **yt-dlp download**: 10 минут
+- **ffmpeg processing**: 15 минут
+- **Yandex API**: 60 секунд
+- **Translation retry**: 5 минут (10 попыток × 30 секунд)
+
+Для длинных видео (>30 минут) эти таймауты могут быть недостаточны. Используйте:
+
+```bash
+# Сначала скачайте только аудио перевод
+vot-cli-live --output="." "URL"
+
+# Потом вручную объедините через ffmpeg с большими таймаутами
+ffmpeg -i original.mp4 -i translation.mp3 \
+ -filter_complex "[0:a]volume=0.3[a1];[1:a]volume=1.5[a2];[a1][a2]amix=inputs=2:duration=first[aout]" \
+ -map 0:v -map "[aout]" -c:v copy -c:a aac -b:a 192k output.mp4
+```
+
+---
+
+## Проблема: "The translation will take a few minutes"
+
+### Симптомы
+Перевод долго готовится (более 1 минуты)
+
+### Причина
+Yandex API готовит перевод. Это нормально для:
+- Длинных видео (>10 минут)
+- Первого запроса для конкретного видео
+- Использования live voices (живых голосов)
+
+### Решение
+Просто подождите! У нас есть автоматический retry механизм:
+- **Максимум попыток**: 10
+- **Интервал между попытками**: 30 секунд
+- **Максимальное время ожидания**: 5 минут
+
+Вы увидите прогресс:
+```
+🎤 Translating (ID: xxx) - attempt 3/10 ⏰
+ └─ ⏳ Retry 3/10 (waiting 30s)...
+```
+
+Если через 5 минут перевод не готов, попробуйте:
+1. Подождать 10-15 минут и повторить команду
+2. Использовать TTS вместо live voices: `--voice-style=tts`
+3. Проверить доступность Yandex API через прокси
+
+---
+
+## Проблема: 3 уязвимости после установки
+
+### Симптомы
+```bash
+npm install -g vot-cli-live
+# 3 vulnerabilities (1 low, 1 moderate, 1 high)
+```
+
+### Причина
+Зависимости пакета могут содержать уязвимости. Это зависит от:
+- `axios`, `chalk`, `listr2`, `minimist` и других пакетов
+- Транзитивных зависимостей (зависимости зависимостей)
+
+### Решение
+
+#### 1. Проверьте детали уязвимостей
+```bash
+npm audit
+```
+
+#### 2. Попробуйте исправить автоматически
+```bash
+npm audit fix
+```
+
+#### 3. Для критических уязвимостей
+```bash
+npm audit fix --force
+```
+
+**⚠️ Важно**: Большинство уязвимостей в CLI-инструментах не критичны, так как:
+- Пакет не запускается как сервер
+- Нет обработки пользовательского ввода из сети
+- Используется локально в терминале
+
+Если уязвимость не критична для CLI-инструмента (например, XSS или RCE через HTTP), можно игнорировать.
+
+---
+
+## Проблема: Не удаётся создать GitHub Release
+
+### Симптомы
+```bash
+gh release create v1.7.1
+# Exit code: 1
+# message: "Bad credentials"
+```
+
+### Причина
+`gh` CLI требует аутентификации для создания релизов
+
+### Решение
+
+#### 1. Аутентифицируйтесь в GitHub CLI
+```bash
+gh auth login
+```
+
+Выберите:
+- **Where do you use GitHub?** → GitHub.com
+- **Protocol?** → HTTPS
+- **Authenticate?** → Login with a web browser
+
+#### 2. Проверьте авторизацию
+```bash
+gh auth status
+```
+
+#### 3. Создайте релиз
+```bash
+gh release create v1.7.1 \
+ --title "v1.7.1 - Major Update: Bug Fixes & Beautiful UI" \
+ --notes-file RELEASE-v1.7.1.md
+```
+
+#### Альтернатива: Создайте через веб-интерфейс
+1. Откройте https://github.com/YOUR_USERNAME/vot-cli-live/releases/new
+2. Выберите тег `v1.7.1`
+3. Заполните Title и Description из `RELEASE-v1.7.1.md`
+4. Нажмите "Publish release"
+
+---
+
+## Проблема: Большой размер git репозитория (>200MB)
+
+### Симптомы
+```bash
+du -sh .git
+# 290M .git
+```
+
+### Причина
+Тестовые видео/аудио файлы (`.mp4`, `.mp3`) были случайно закоммичены
+
+### Решение
+
+#### 1. Добавьте в .gitignore
+```bash
+echo "test/*.mp3" >> .gitignore
+echo "test/*.mp4" >> .gitignore
+echo "logERROR.txt" >> .gitignore
+```
+
+#### 2. Удалите из истории
+```bash
+git filter-branch --index-filter \
+ 'git rm --cached --ignore-unmatch test/*.mp3 test/*.mp4 logERROR.txt' \
+ --prune-empty --tag-name-filter cat -- --all
+```
+
+#### 3. Очистите репозиторий
+```bash
+git reflog expire --expire=now --all
+git gc --prune=now --aggressive
+```
+
+#### 4. Принудительно запушьте
+```bash
+git push origin --force --all
+git push origin --force --tags
+```
+
+**⚠️ Внимание**: `git filter-branch` перезаписывает историю! Используйте только если уверены.
+
+---
+
+## Полезные команды для диагностики
+
+```bash
+# Проверка установки
+which vot-cli-live
+npm list -g vot-cli-live
+vot-cli-live --version
+
+# Проверка всех установок
+which -a vot-cli-live
+
+# Проверка PATH
+echo $PATH
+
+# Проверка зависимостей
+npm list -g --depth=0
+
+# Проверка кеша npm
+npm cache verify
+
+# Тест прокси
+curl -x http://proxy:8080 https://api.browser.yandex.ru
+
+# Проверка yt-dlp
+yt-dlp --version
+
+# Проверка ffmpeg
+ffmpeg -version
+
+# Тест загрузки
+vot-cli-live --output="/tmp" "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
+```
+
+---
+
+## Нужна помощь?
+
+1. **GitHub Issues**: https://github.com/fantomcheg/vot-cli-live/issues
+2. **Original vot-cli**: https://github.com/FOSWLY/vot-cli/issues
+3. **Wiki**: https://github.com/fantomcheg/vot-cli-live/wiki
+
+При создании issue укажите:
+- Версию пакета (`vot-cli-live --version`)
+- Версию Node.js (`node --version`)
+- ОС и версию
+- Полный вывод ошибки
+- Команду которую вы запускали
diff --git a/UI-IMPROVEMENTS.md b/UI-IMPROVEMENTS.md
new file mode 100644
index 0000000..8662f8f
--- /dev/null
+++ b/UI-IMPROVEMENTS.md
@@ -0,0 +1,252 @@
+# 🎨 UI/UX Improvements - Beautiful Output
+
+## 📋 Обзор
+
+Добавлен красивый и информативный вывод с эмодзи, цветами и детальной информацией о каждом этапе работы.
+
+---
+
+## ✨ Что было добавлено:
+
+### 1. **🎬 Красивый стартовый баннер**
+
+```
+╔═══════════════════════════════════════════════════════════╗
+║ 🎬 VOT-CLI with Live Voices 🔥 ║
+╚═══════════════════════════════════════════════════════════╝
+ Это форк продукта https://github.com/FOSWLY/vot-cli/
+ Вся слава Илье @ToilOfficial 🙏
+
+📦 Version: 1.6.2
+🎯 Videos to process: 1
+```
+
+**Показывает:**
+- Название программы с эмодзи
+- Кредиты оригинальному автору
+- Версию программы
+- Количество видео для обработки
+
+---
+
+### 2. **🎚️ Информация о режиме merge (если включен)**
+
+```
+🎬 Video merge mode: ENABLED
+ ├─ Original volume: 30%
+ └─ Translation volume: 150%
+```
+
+**Показывает:**
+- Статус режима объединения видео
+- Громкость оригинала
+- Громкость перевода
+
+---
+
+### 3. **📁 Статус директории вывода**
+
+```
+✅ Output directory exists: test
+```
+или
+```
+📁 Creating output directory: test
+✅ Directory created successfully
+```
+
+---
+
+### 4. **🔗 Формирование ссылки**
+
+```
+🔗 Forming a link to the video
+ └─ URL: https://youtu.be/jNQXAC9IVRw
+ └─ 📺 Fetching video title...
+ └─ ✅ Title: "Me_at_the_zoo"
+```
+
+**Показывает:**
+- Финальный URL видео
+- Процесс получения названия
+- Полученное название (или предупреждение если не удалось)
+
+---
+
+### 5. **🎤 Процесс перевода (с деталями)**
+
+```
+🎤 Translating (ID: jNQXAC9IVRw) with live voices 🔥
+ └─ 📡 Requesting translation from Yandex API...
+ └─ ✅ Translation received instantly (cached)
+```
+
+**Показывает:**
+- Тип озвучки (live voices 🔥 или TTS 🤖)
+- Запрос к API
+- Определенную длительность видео
+- Статус перевода (мгновенный/из кеша или ожидание)
+
+**При ожидании:**
+```
+ └─ ⏳ Translation is being prepared, waiting...
+ └─ ⏳ Retry 3/10 (waiting 30s)...
+```
+
+---
+
+### 6. **📥 Скачивание аудио**
+
+```
+📥 Downloading audio translation (ID: jNQXAC9IVRw)
+ └─ 💾 Saving as: Me_at_the_zoo.mp3
+ └─ 🔗 Source: https://vtrans.s3-private.mds.yandex.net/tts/prod/68f0d700b2...
+ └─ ✅ File size: 0.29 MB
+```
+
+**Показывает:**
+- Имя файла
+- Сокращенный URL источника
+- Финальный размер файла
+
+---
+
+### 7. **🎬 Процесс merge (3 шага)**
+
+```
+🎬 Merging video with translation (ID: jNQXAC9IVRw)
+ └─ 🎥 Starting video merge process...
+ ├─ Original volume: 30%
+ └─ Translation volume: 150%
+ └─ 📥 Step 1/3: Downloading translation audio...
+ └─ ✅ Audio downloaded (0.29 MB)
+ └─ 🎬 Step 2/3: Merging video with translation...
+ ├─ This may take several minutes...
+ └─ Video: Me_at_the_zoo.mp4
+ └─ 🧹 Step 3/3: Cleaning up temporary files...
+ └─ ✅ Temporary audio file removed
+ └─ ✅ Final video size: 0.71 MB
+ └─ 📁 Saved to: test/Me_at_the_zoo.mp4
+```
+
+**Показывает:**
+- Настройки громкости
+- **Шаг 1:** Скачивание аудио (размер)
+- **Шаг 2:** Процесс объединения (может занять время)
+- **Шаг 3:** Очистка временных файлов
+- Финальный размер видео
+- Путь к сохраненному файлу
+
+---
+
+### 8. **🎉 Финальный баннер**
+
+**При успехе:**
+```
+╔═══════════════════════════════════════════════════════════╗
+║ 🎉 ALL TASKS COMPLETED! 🎉 ║
+╚═══════════════════════════════════════════════════════════╝
+
+✅ Successfully processed 1 video(s)
+📁 Output directory: test
+```
+
+**При ошибке:**
+```
+╔═══════════════════════════════════════════════════════════╗
+║ ❌ ERROR OCCURRED ❌ ║
+╚═══════════════════════════════════════════════════════════╝
+
+[детали ошибки]
+```
+
+---
+
+## 🎨 Используемые цвета (через chalk):
+
+- 🔵 **Cyan** - информационные сообщения
+- 🟢 **Green** - успешные операции
+- 🟡 **Yellow** - предупреждения
+- 🔴 **Red** - ошибки
+- ⚪ **Gray** - дополнительная информация
+- ⚪ **White Bold** - заголовки баннеров
+
+---
+
+## 📊 Используемые эмодзи:
+
+| Эмодзи | Значение |
+|--------|----------|
+| 🎬 | Основной логотип/видео |
+| 🔥 | Живые голоса |
+| 🤖 | TTS озвучка |
+| 📦 | Версия программы |
+| 🎯 | Цель/количество |
+| 📁 | Директории/файлы |
+| 🔗 | Ссылки/URL |
+| 📺 | Видео/название |
+| 🎤 | Перевод/озвучка |
+| 📡 | API запросы |
+| ⏳ | Ожидание |
+| 📥 | Скачивание |
+| 💾 | Сохранение |
+| 🎥 | Merge процесс |
+| 🧹 | Очистка |
+| ✅ | Успех |
+| ❌ | Ошибка |
+| ⚠️ | Предупреждение |
+| 🙏 | Благодарность |
+| 🎉 | Завершение |
+
+---
+
+## 📈 Преимущества нового UI:
+
+1. **Визуальная структура** - легко понять на каком этапе процесс
+2. **Детальность** - показывается каждый шаг с подробностями
+3. **Информативность** - размеры файлов, время, попытки
+4. **Красота** - эмодзи и цвета делают вывод приятным
+5. **Профессионализм** - баннеры и структура как у продакшн-приложений
+6. **Благодарность автору** - кредиты в стартовом баннере
+
+---
+
+## 🔧 Технические детали:
+
+- Используется библиотека `chalk` для цветного вывода
+- Эмодзи добавлены напрямую в строки
+- Сохранена совместимость с Listr2 для прогресс-баров
+- Все console.log обернуты в chalk для правильных цветов
+- Структура вывода с отступами (└─, ├─) для иерархии
+
+---
+
+## 📝 Примеры использования:
+
+### Простое скачивание:
+```bash
+node src/index.js --output="." "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### С merge и настройками:
+```bash
+node src/index.js --output="." --merge-video \
+ --voice-style=live \
+ --original-volume=0.3 \
+ --translation-volume=1.5 \
+ "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+## 🎯 Итог:
+
+Теперь пользователь **всегда знает**:
+- ✅ Что происходит в данный момент
+- ✅ Какие настройки используются
+- ✅ Размеры файлов на каждом этапе
+- ✅ Сколько попыток осталось при ожидании
+- ✅ Где сохранены результаты
+- ✅ Что программа завершилась успешно
+
+**UX/UI стал профессиональным! 🚀**
diff --git a/bun.lockb b/bun.lockb
new file mode 100644
index 0000000..ccdb64f
Binary files /dev/null and b/bun.lockb differ
diff --git a/changelog.md b/changelog.md
index e901375..dbb807a 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,10 +1,94 @@
+# 1.7.2 (2025-11-28 - Documentation: Troubleshooting Guide)
+
+## 📚 Documentation
+
+- **Добавлен TROUBLESHOOTING.md** - подробный гайд по устранению проблем (500+ строк)
+- **Описана проблема с конфликтом версий** - когда `/usr/bin/vot-cli-live` показывает старую версию вместо новой из nvm
+- **Добавлены решения для всех известных ошибок**:
+ - ECONNRESET с предложением использовать прокси
+ - Timeout при скачивании/обработке видео
+ - 3 уязвимости при установке (npm audit)
+ - Проблемы с GitHub Release и аутентификацией
+ - Большой размер git репозитория
+- **Полезные команды для диагностики** - `which -a`, `npm cache clean`, `hash -r`, и др.
+- **Обновлён README.md** - добавлена ссылка на TROUBLESHOOTING и краткий список проблем
+
+---
+
+# 1.7.1 (2025-11-28 - Patch: Version Bump)
+
+## 📦 Version Management
+
+- **Обновлена версия до 1.7.1** - для переопубликования после очистки репозитория
+- Исправлена проблема с npm кешем при публикации
+
+---
+
+# 1.7.0 (2025-11-28 - Major Update: Bug Fixes & Beautiful UI)
+
+## 🐛 Bug Fixes
+
+- **Исправлен бесконечный цикл ожидания перевода** - добавлен максимум 10 попыток (5 минут)
+- **Исправлено зависание при ошибках сети** - добавлен таймаут 60 секунд для запросов к Яндекс API
+- **Исправлено зависание yt-dlp и ffmpeg** - добавлены таймауты (10 и 15 минут соответственно)
+- **Улучшена обработка ошибок ECONNRESET** - теперь показывается совет использовать прокси
+
+## ✨ New Features
+
+- **Получение реальной длительности видео** через yt-dlp вместо фиксированных 341 секунды
+- **Полная поддержка прокси в yt-dlp** - прокси теперь передается и через параметры, и через переменные окружения
+- **Прогресс-индикатор попыток** - показывается "attempt 3/10" при ожидании перевода
+
+## 📝 Other Changes
+
+- Добавлен `logERROR.txt` в `.gitignore`
+- Создана утилита `getVideoDuration.js` для определения длительности видео
+- Улучшены сообщения об ошибках (более информативные)
+- Добавлен файл `IMPROVEMENTS.md` с детальным описанием всех улучшений
+
+## 🧪 Testing
+
+- Протестировано на коротких видео (19 секунд)
+- Протестировано на длинных видео (3+ минуты)
+- Проверена работа с живыми голосами и TTS
+- Проверено автоматическое именование файлов
+
+## 📚 Documentation
+
+См. `IMPROVEMENTS.md` для подробного описания всех изменений.
+
+---
+
+# 1.4.3
+
+- Добавлена поддержка загрузки субтитров в `.srt` (#33)
+
+# 1.4.2
+
+- Добавлена поддержка /live/ для YouTube (#32)
+
+# 1.4.1
+
+- Обновлен Yandex HMAC
+
+# 1.4.0
+
+- Добавлен новый аргумент `--output-file`. Он позволяет установить имя файла для сохранения (требует указания пути сохранения аудио файла перевода в аргументе "--output")
+- `Yandex Protobuf` обновлен до актуальной версии из [voice-over-translation](https://github.com/ilyhalight/voice-over-translation)
+- Добавлена поддержка перевода Google Drive (только публичные ссылки, например: `https://drive.google.com/file/d/FILE_ID`)
+- Добавлена поддержка перевода YouTube Shorts (`https://youtube.com/shorts/VIDEO_ID`)
+- Добавлена поддержка короткой ссылки на YouTube `youtu.be`
+
# 1.3.1
+
- Добавлена поддержка короткой ссылки на yandex disk (`yadi.sk`)
# 1.3.0
+
- Добавлена поддержка кастомных ссылок с окончанием на `.mp4`
- Добавлена поддержка Одноклассников (`ok.ru`)
- Добавлена поддержка Peertube. Были добавлены 9 крупных сайтов, хостящих Peertube (libre.video не поддерживается - не просите):
+
- `tube.shanti.cafe`
- `bee-tube.fr`
- `video.sadmin.io`
@@ -22,9 +106,11 @@
- Добавлена эксперементальная поддержка HTTP и HTTPS прокси в формате `[://]:@[:]` (например: `http://127.0.0.1:8788`). Для установки прокси используйте аргумент `--proxy`
# 1.2.1
+
- Еще один фикс загрузки #4
# 1.2.0
+
- Добавлена возможность загрузки субтитров для видео вместо озвучки (используйте опцию `--subs` или `--subtitles`)
- Добавлена поддержка Rumble и EPorner (у последнего перевод занимает очень много времени)
- Фикс загрузки аудио файла для XVideos (#4)
@@ -35,9 +121,11 @@
- Задан явный конфиг для prettier (нужен для нормальной работы форматирования в редакторе)
# 1.1.1
+
- Возвращен показ ссылки на перевод
# 1.1.0
+
- Добавлены тесты для ютуба и вимео
- Улучшена работа одновременного перевода нескольких видео
- Теперь, имя аудио файла начинается с айди видео и имеет вид: "**VIDEO_ID---UUID4**"
@@ -62,13 +150,17 @@
- vot-cli был перенесен в отдельный [репозиторий](https://github.com/FOSWLY/vot-cli)
# 1.0.4
+
- Добавлена поддержка mail.ru
# 1.0.3
+
- Добавлена поддержка Twitter
-# 1.0.1 - 1.0.2
+# 1.0.1 - 1.0.2
+
Список изменений был утерян
# 1.0.0.
-- Был создана сам VOT-CLI с доступными запросами к YouTube, Twitch, VK, XVideos, Pornhub
\ No newline at end of file
+
+- Был создана сам VOT-CLI с доступными запросами к YouTube, Twitch, VK, XVideos, Pornhub
diff --git a/package-lock.json b/package-lock.json
index 52bbbc5..7ea9207 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,31 +1,31 @@
{
- "name": "vot-cli",
- "version": "1.3.1",
+ "name": "vot-cli-live",
+ "version": "1.7.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "vot-cli",
- "version": "1.3.1",
+ "name": "vot-cli-live",
+ "version": "1.7.2",
"license": "MIT",
"dependencies": {
- "axios": "^1.6.7",
+ "axios": "^1.7.2",
"chalk": "^5.3.0",
- "jsdom": "^24.0.0",
- "listr2": "^8.0.2",
+ "jsdom": "^24.1.0",
+ "listr2": "^8.2.3",
"minimist": "^1.2.8",
- "protobufjs": "^7.2.6",
- "uuid": "^9.0.1"
+ "protobufjs": "^7.3.2",
+ "uuid": "^10.0.0"
},
"bin": {
- "vot-cli": "src/index.js"
+ "vot-cli-live": "src/index.js"
},
"devDependencies": {
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
- "husky": "^9.0.10",
- "prettier": "^3.2.4"
+ "husky": "^9.0.11",
+ "prettier": "^3.3.2"
},
"engines": {
"node": ">=18.0.0"
@@ -263,12 +263,10 @@
}
},
"node_modules/agent-base": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
- "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
- "dependencies": {
- "debug": "^4.3.4"
- },
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "license": "MIT",
"engines": {
"node": ">= 14"
}
@@ -290,25 +288,15 @@
}
},
"node_modules/ansi-escapes": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz",
- "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz",
+ "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==",
+ "license": "MIT",
"dependencies": {
- "type-fest": "^3.0.0"
+ "environment": "^1.0.0"
},
"engines": {
- "node": ">=14.16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/ansi-escapes/node_modules/type-fest": {
- "version": "3.13.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
- "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
- "engines": {
- "node": ">=14.16"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -350,12 +338,13 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
- "version": "1.6.7",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
- "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
+ "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
+ "license": "MIT",
"dependencies": {
- "follow-redirects": "^1.15.4",
- "form-data": "^4.0.0",
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
@@ -375,6 +364,19 @@
"concat-map": "0.0.1"
}
},
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -396,14 +398,15 @@
}
},
"node_modules/cli-cursor": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
- "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
+ "license": "MIT",
"dependencies": {
- "restore-cursor": "^4.0.0"
+ "restore-cursor": "^5.0.0"
},
"engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -548,6 +551,20 @@
"node": ">=6.0.0"
}
},
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/emoji-regex": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
@@ -564,6 +581,63 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
+ "node_modules/environment": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
+ "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -863,15 +937,16 @@
"dev": true
},
"node_modules/follow-redirects": {
- "version": "1.15.5",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
- "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
+ "license": "MIT",
"engines": {
"node": ">=4.0"
},
@@ -882,12 +957,15 @@
}
},
"node_modules/form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -900,10 +978,20 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/get-east-asian-width": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz",
- "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
+ "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
+ "license": "MIT",
"engines": {
"node": ">=18"
},
@@ -911,6 +999,43 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -958,6 +1083,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -973,6 +1110,45 @@
"node": ">=8"
}
},
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/html-encoding-sniffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
@@ -985,9 +1161,10 @@
}
},
"node_modules/http-proxy-agent": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
- "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "license": "MIT",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
@@ -997,11 +1174,12 @@
}
},
"node_modules/https-proxy-agent": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
- "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "license": "MIT",
"dependencies": {
- "agent-base": "^7.0.2",
+ "agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
@@ -1009,12 +1187,13 @@
}
},
"node_modules/husky": {
- "version": "9.0.10",
- "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.10.tgz",
- "integrity": "sha512-TQGNknoiy6bURzIO77pPRu+XHi6zI7T93rX+QnJsoYFf3xdjKOur+IlfqzJGMHIK/wXrLg+GsvMs8Op7vI2jVA==",
+ "version": "9.1.7",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
+ "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
"dev": true,
+ "license": "MIT",
"bin": {
- "husky": "bin.mjs"
+ "husky": "bin.js"
},
"engines": {
"node": ">=18"
@@ -1149,30 +1328,31 @@
}
},
"node_modules/jsdom": {
- "version": "24.0.0",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.0.0.tgz",
- "integrity": "sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==",
+ "version": "24.1.3",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz",
+ "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==",
+ "license": "MIT",
"dependencies": {
"cssstyle": "^4.0.1",
"data-urls": "^5.0.0",
"decimal.js": "^10.4.3",
"form-data": "^4.0.0",
"html-encoding-sniffer": "^4.0.0",
- "http-proxy-agent": "^7.0.0",
- "https-proxy-agent": "^7.0.2",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.5",
"is-potential-custom-element-name": "^1.0.1",
- "nwsapi": "^2.2.7",
+ "nwsapi": "^2.2.12",
"parse5": "^7.1.2",
- "rrweb-cssom": "^0.6.0",
+ "rrweb-cssom": "^0.7.1",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
- "tough-cookie": "^4.1.3",
+ "tough-cookie": "^4.1.4",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^3.1.1",
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.0.0",
- "ws": "^8.16.0",
+ "ws": "^8.18.0",
"xml-name-validator": "^5.0.0"
},
"engines": {
@@ -1187,6 +1367,12 @@
}
}
},
+ "node_modules/jsdom/node_modules/rrweb-cssom": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
+ "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==",
+ "license": "MIT"
+ },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -1228,15 +1414,16 @@
}
},
"node_modules/listr2": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.2.tgz",
- "integrity": "sha512-v5jEMOeEJUpRjSXSB4U3w5A3YPmURYMUO/86f1PA4GGYcdbUQYpkbvKYT7Xaq1iu4Zjn51Rv1UeD1zsBXRijiQ==",
+ "version": "8.3.3",
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz",
+ "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==",
+ "license": "MIT",
"dependencies": {
"cli-truncate": "^4.0.0",
"colorette": "^2.0.20",
"eventemitter3": "^5.0.1",
- "log-update": "^6.0.0",
- "rfdc": "^1.3.1",
+ "log-update": "^6.1.0",
+ "rfdc": "^1.4.1",
"wrap-ansi": "^9.0.0"
},
"engines": {
@@ -1265,13 +1452,14 @@
"dev": true
},
"node_modules/log-update": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz",
- "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
+ "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
+ "license": "MIT",
"dependencies": {
- "ansi-escapes": "^6.2.0",
- "cli-cursor": "^4.0.0",
- "slice-ansi": "^7.0.0",
+ "ansi-escapes": "^7.0.0",
+ "cli-cursor": "^5.0.0",
+ "slice-ansi": "^7.1.0",
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0"
},
@@ -1283,9 +1471,10 @@
}
},
"node_modules/log-update/node_modules/ansi-regex": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
- "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
"engines": {
"node": ">=12"
},
@@ -1294,9 +1483,10 @@
}
},
"node_modules/log-update/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "license": "MIT",
"engines": {
"node": ">=12"
},
@@ -1305,11 +1495,12 @@
}
},
"node_modules/log-update/node_modules/is-fullwidth-code-point": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz",
- "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
+ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
+ "license": "MIT",
"dependencies": {
- "get-east-asian-width": "^1.0.0"
+ "get-east-asian-width": "^1.3.1"
},
"engines": {
"node": ">=18"
@@ -1319,9 +1510,10 @@
}
},
"node_modules/log-update/node_modules/slice-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz",
- "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==",
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
+ "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
+ "license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"is-fullwidth-code-point": "^5.0.0"
@@ -1334,9 +1526,10 @@
}
},
"node_modules/log-update/node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
@@ -1352,6 +1545,15 @@
"resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
"integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A=="
},
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -1371,12 +1573,16 @@
"node": ">= 0.6"
}
},
- "node_modules/mimic-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "node_modules/mimic-function": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+ "license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": {
@@ -1411,9 +1617,10 @@
"dev": true
},
"node_modules/nwsapi": {
- "version": "2.2.7",
- "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
- "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ=="
+ "version": "2.2.22",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz",
+ "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==",
+ "license": "MIT"
},
"node_modules/once": {
"version": "1.4.0",
@@ -1425,14 +1632,15 @@
}
},
"node_modules/onetime": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
- "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
+ "license": "MIT",
"dependencies": {
- "mimic-fn": "^2.1.0"
+ "mimic-function": "^5.0.0"
},
"engines": {
- "node": ">=6"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -1545,10 +1753,11 @@
}
},
"node_modules/prettier": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz",
- "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
+ "license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -1572,10 +1781,11 @@
}
},
"node_modules/protobufjs": {
- "version": "7.2.6",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz",
- "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
+ "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
"hasInstallScript": true,
+ "license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
@@ -1600,9 +1810,16 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/psl": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
- "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
+ "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/lupomontero"
+ }
},
"node_modules/punycode": {
"version": "2.3.1",
@@ -1615,7 +1832,8 @@
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
- "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "license": "MIT"
},
"node_modules/queue-microtask": {
"version": "1.2.3",
@@ -1640,7 +1858,8 @@
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
- "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "license": "MIT"
},
"node_modules/resolve-from": {
"version": "4.0.0",
@@ -1652,15 +1871,16 @@
}
},
"node_modules/restore-cursor": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
- "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
+ "license": "MIT",
"dependencies": {
- "onetime": "^5.1.0",
- "signal-exit": "^3.0.2"
+ "onetime": "^7.0.0",
+ "signal-exit": "^4.1.0"
},
"engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -1677,9 +1897,10 @@
}
},
"node_modules/rfdc": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz",
- "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg=="
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
+ "license": "MIT"
},
"node_modules/rimraf": {
"version": "3.0.2",
@@ -1762,9 +1983,16 @@
}
},
"node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
},
"node_modules/slice-ansi": {
"version": "5.0.0",
@@ -1897,9 +2125,10 @@
"dev": true
},
"node_modules/tough-cookie": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
- "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+ "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+ "license": "BSD-3-Clause",
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
@@ -1955,6 +2184,7 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "license": "MIT",
"engines": {
"node": ">= 4.0.0"
}
@@ -1972,19 +2202,21 @@
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "license": "MIT",
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"node_modules/uuid": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
- "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
+ "license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
@@ -2055,9 +2287,10 @@
}
},
"node_modules/wrap-ansi": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
- "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+ "license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
@@ -2071,9 +2304,10 @@
}
},
"node_modules/wrap-ansi/node_modules/ansi-regex": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
- "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
"engines": {
"node": ">=12"
},
@@ -2082,9 +2316,10 @@
}
},
"node_modules/wrap-ansi/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "license": "MIT",
"engines": {
"node": ">=12"
},
@@ -2093,9 +2328,10 @@
}
},
"node_modules/wrap-ansi/node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
@@ -2113,9 +2349,10 @@
"dev": true
},
"node_modules/ws": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
- "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "license": "MIT",
"engines": {
"node": ">=10.0.0"
},
@@ -2337,12 +2574,9 @@
"requires": {}
},
"agent-base": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
- "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
- "requires": {
- "debug": "^4.3.4"
- }
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="
},
"ajv": {
"version": "6.12.6",
@@ -2357,18 +2591,11 @@
}
},
"ansi-escapes": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz",
- "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz",
+ "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==",
"requires": {
- "type-fest": "^3.0.0"
- },
- "dependencies": {
- "type-fest": {
- "version": "3.13.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
- "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="
- }
+ "environment": "^1.0.0"
}
},
"ansi-regex": {
@@ -2398,12 +2625,12 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"axios": {
- "version": "1.6.7",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
- "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
+ "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"requires": {
- "follow-redirects": "^1.15.4",
- "form-data": "^4.0.0",
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
@@ -2423,6 +2650,15 @@
"concat-map": "0.0.1"
}
},
+ "call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "requires": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ }
+ },
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -2435,11 +2671,11 @@
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="
},
"cli-cursor": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
- "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"requires": {
- "restore-cursor": "^4.0.0"
+ "restore-cursor": "^5.0.0"
}
},
"cli-truncate": {
@@ -2546,6 +2782,16 @@
"esutils": "^2.0.2"
}
},
+ "dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "requires": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ }
+ },
"emoji-regex": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
@@ -2556,6 +2802,40 @@
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
},
+ "environment": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
+ "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="
+ },
+ "es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
+ },
+ "es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
+ },
+ "es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "requires": {
+ "es-errors": "^1.3.0"
+ }
+ },
+ "es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "requires": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ }
+ },
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -2769,17 +3049,19 @@
"dev": true
},
"follow-redirects": {
- "version": "1.15.5",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
- "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw=="
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="
},
"form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
"mime-types": "^2.1.12"
}
},
@@ -2789,10 +3071,41 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
+ "function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
+ },
"get-east-asian-width": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz",
- "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA=="
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
+ "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="
+ },
+ "get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "requires": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ }
+ },
+ "get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "requires": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ }
},
"glob": {
"version": "7.2.3",
@@ -2826,6 +3139,11 @@
"type-fest": "^0.20.2"
}
},
+ "gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
+ },
"graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -2838,6 +3156,27 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
+ "has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
+ },
+ "has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "requires": {
+ "has-symbols": "^1.0.3"
+ }
+ },
+ "hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "requires": {
+ "function-bind": "^1.1.2"
+ }
+ },
"html-encoding-sniffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
@@ -2847,27 +3186,27 @@
}
},
"http-proxy-agent": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
- "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"requires": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
}
},
"https-proxy-agent": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
- "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"requires": {
- "agent-base": "^7.0.2",
+ "agent-base": "^7.1.2",
"debug": "4"
}
},
"husky": {
- "version": "9.0.10",
- "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.10.tgz",
- "integrity": "sha512-TQGNknoiy6bURzIO77pPRu+XHi6zI7T93rX+QnJsoYFf3xdjKOur+IlfqzJGMHIK/wXrLg+GsvMs8Op7vI2jVA==",
+ "version": "9.1.7",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
+ "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
"dev": true
},
"iconv-lite": {
@@ -2963,31 +3302,38 @@
}
},
"jsdom": {
- "version": "24.0.0",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.0.0.tgz",
- "integrity": "sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==",
+ "version": "24.1.3",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz",
+ "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==",
"requires": {
"cssstyle": "^4.0.1",
"data-urls": "^5.0.0",
"decimal.js": "^10.4.3",
"form-data": "^4.0.0",
"html-encoding-sniffer": "^4.0.0",
- "http-proxy-agent": "^7.0.0",
- "https-proxy-agent": "^7.0.2",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.5",
"is-potential-custom-element-name": "^1.0.1",
- "nwsapi": "^2.2.7",
+ "nwsapi": "^2.2.12",
"parse5": "^7.1.2",
- "rrweb-cssom": "^0.6.0",
+ "rrweb-cssom": "^0.7.1",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
- "tough-cookie": "^4.1.3",
+ "tough-cookie": "^4.1.4",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^3.1.1",
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.0.0",
- "ws": "^8.16.0",
+ "ws": "^8.18.0",
"xml-name-validator": "^5.0.0"
+ },
+ "dependencies": {
+ "rrweb-cssom": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
+ "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg=="
+ }
}
},
"json-buffer": {
@@ -3028,15 +3374,15 @@
}
},
"listr2": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.2.tgz",
- "integrity": "sha512-v5jEMOeEJUpRjSXSB4U3w5A3YPmURYMUO/86f1PA4GGYcdbUQYpkbvKYT7Xaq1iu4Zjn51Rv1UeD1zsBXRijiQ==",
+ "version": "8.3.3",
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz",
+ "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==",
"requires": {
"cli-truncate": "^4.0.0",
"colorette": "^2.0.20",
"eventemitter3": "^5.0.1",
- "log-update": "^6.0.0",
- "rfdc": "^1.3.1",
+ "log-update": "^6.1.0",
+ "rfdc": "^1.4.1",
"wrap-ansi": "^9.0.0"
}
},
@@ -3056,48 +3402,48 @@
"dev": true
},
"log-update": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz",
- "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
+ "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
"requires": {
- "ansi-escapes": "^6.2.0",
- "cli-cursor": "^4.0.0",
- "slice-ansi": "^7.0.0",
+ "ansi-escapes": "^7.0.0",
+ "cli-cursor": "^5.0.0",
+ "slice-ansi": "^7.1.0",
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0"
},
"dependencies": {
"ansi-regex": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
- "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="
},
"ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="
},
"is-fullwidth-code-point": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz",
- "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
+ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
"requires": {
- "get-east-asian-width": "^1.0.0"
+ "get-east-asian-width": "^1.3.1"
}
},
"slice-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz",
- "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==",
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
+ "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
"requires": {
"ansi-styles": "^6.2.1",
"is-fullwidth-code-point": "^5.0.0"
}
},
"strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"requires": {
"ansi-regex": "^6.0.1"
}
@@ -3109,6 +3455,11 @@
"resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
"integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A=="
},
+ "math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
+ },
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -3122,10 +3473,10 @@
"mime-db": "1.52.0"
}
},
- "mimic-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
+ "mimic-function": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="
},
"minimatch": {
"version": "3.1.2",
@@ -3153,9 +3504,9 @@
"dev": true
},
"nwsapi": {
- "version": "2.2.7",
- "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
- "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ=="
+ "version": "2.2.22",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz",
+ "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ=="
},
"once": {
"version": "1.4.0",
@@ -3167,11 +3518,11 @@
}
},
"onetime": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
- "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"requires": {
- "mimic-fn": "^2.1.0"
+ "mimic-function": "^5.0.0"
}
},
"optionator": {
@@ -3248,9 +3599,9 @@
"dev": true
},
"prettier": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz",
- "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true
},
"prettier-linter-helpers": {
@@ -3263,9 +3614,9 @@
}
},
"protobufjs": {
- "version": "7.2.6",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz",
- "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
+ "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
"requires": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
@@ -3287,9 +3638,12 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"psl": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
- "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
+ "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
+ "requires": {
+ "punycode": "^2.3.1"
+ }
},
"punycode": {
"version": "2.3.1",
@@ -3319,12 +3673,12 @@
"dev": true
},
"restore-cursor": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
- "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
"requires": {
- "onetime": "^5.1.0",
- "signal-exit": "^3.0.2"
+ "onetime": "^7.0.0",
+ "signal-exit": "^4.1.0"
}
},
"reusify": {
@@ -3334,9 +3688,9 @@
"dev": true
},
"rfdc": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz",
- "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg=="
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="
},
"rimraf": {
"version": "3.0.2",
@@ -3390,9 +3744,9 @@
"dev": true
},
"signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
},
"slice-ansi": {
"version": "5.0.0",
@@ -3481,9 +3835,9 @@
"dev": true
},
"tough-cookie": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
- "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+ "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
"requires": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
@@ -3544,9 +3898,9 @@
}
},
"uuid": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
- "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="
},
"w3c-xmlserializer": {
"version": "5.0.0",
@@ -3593,9 +3947,9 @@
}
},
"wrap-ansi": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
- "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
"requires": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
@@ -3603,19 +3957,19 @@
},
"dependencies": {
"ansi-regex": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
- "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="
},
"ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="
},
"strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"requires": {
"ansi-regex": "^6.0.1"
}
@@ -3629,9 +3983,9 @@
"dev": true
},
"ws": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
- "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"requires": {}
},
"xml-name-validator": {
diff --git a/package.json b/package.json
index 7432144..e55020a 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
- "name": "vot-cli",
- "version": "1.3.1",
- "description": "A small script that allows you to download an audio translation from Yandex via the terminal.",
+ "name": "vot-cli-live",
+ "version": "1.7.2",
+ "description": "VOT-CLI with Yandex live voices support. Fork of FOSWLY/vot-cli with useLivelyVoice feature.",
"type": "module",
"main": "./src/index.js",
"bin": "./src/index.js",
@@ -9,8 +9,7 @@
"start": "node src/index.js",
"lint": "npx eslint .",
"lint-fix": "npx eslint . --fix",
- "format": "prettier --write --ignore-unknown \"src/**/*.{js,ts,json}\"",
- "prepare": "husky install"
+ "format": "prettier --write --ignore-unknown \"src/**/*.{js,ts,json}\""
},
"repository": {
"type": "git",
@@ -28,26 +27,26 @@
"license": "MIT",
"bugs": {
"url": "https://github.com/FOSWLY/vot-cli/issues",
- "email": "toil.contact@yandex.com"
+ "email": "me@toil.cc"
},
"engines": {
"node": ">=18.0.0"
},
- "homepage": "https://github.com/FOSWLY/vot-cli/#readme",
+ "homepage": "https://github.com/fantomcheg/vot-cli-live/#readme",
"dependencies": {
- "axios": "^1.6.7",
+ "axios": "^1.7.2",
"chalk": "^5.3.0",
- "jsdom": "^24.0.0",
- "listr2": "^8.0.2",
+ "jsdom": "^24.1.0",
+ "listr2": "^8.2.3",
"minimist": "^1.2.8",
- "protobufjs": "^7.2.6",
- "uuid": "^9.0.1"
+ "protobufjs": "^7.3.2",
+ "uuid": "^10.0.0"
},
"devDependencies": {
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
- "husky": "^9.0.10",
- "prettier": "^3.2.4"
+ "husky": "^9.0.11",
+ "prettier": "^3.3.2"
}
}
diff --git a/publish.sh b/publish.sh
new file mode 100755
index 0000000..8b3a7ea
--- /dev/null
+++ b/publish.sh
@@ -0,0 +1,114 @@
+#!/bin/bash
+
+# 🚀 Скрипт для публикации vot-cli-live v1.7.0 на GitHub и npm
+
+echo "╔═══════════════════════════════════════════════════════════╗"
+echo "║ 🚀 Publishing vot-cli-live v1.7.0 ║"
+echo "╚═══════════════════════════════════════════════════════════╝"
+echo ""
+
+# Проверяем что мы в правильной директории
+if [ ! -f "package.json" ]; then
+ echo "❌ Error: package.json not found. Run this script from project root."
+ exit 1
+fi
+
+# Проверяем версию в package.json
+VERSION=$(node -p "require('./package.json').version")
+echo "📦 Current version: $VERSION"
+echo ""
+
+# 1. Push to GitHub
+echo "📤 Step 1/4: Pushing to GitHub..."
+echo " └─ Branch: feature/add-live-voices-support"
+git push myfork feature/add-live-voices-support
+if [ $? -eq 0 ]; then
+ echo " └─ ✅ Branch pushed successfully"
+else
+ echo " └─ ❌ Failed to push branch"
+ exit 1
+fi
+echo ""
+
+# 2. Push tags to GitHub
+echo "📤 Step 2/4: Pushing tags to GitHub..."
+git push myfork --tags
+if [ $? -eq 0 ]; then
+ echo " └─ ✅ Tags pushed successfully"
+else
+ echo " └─ ❌ Failed to push tags"
+ exit 1
+fi
+echo ""
+
+# 3. Ask about creating release on GitHub
+echo "🎯 Step 3/4: GitHub Release"
+echo " You can create a release manually at:"
+echo " └─ https://github.com/fantomcheg/vot-cli-live/releases/new"
+echo " └─ Tag: v1.7.0"
+echo " └─ Title: v1.7.0 - Major Update: Bug Fixes & Beautiful UI"
+echo " └─ Description: Copy from RELEASE-NOTES-v1.7.0.md"
+echo ""
+read -p " Press Enter to continue to npm publish..."
+echo ""
+
+# 4. Publish to npm
+echo "📦 Step 4/4: Publishing to npm..."
+echo " └─ Package: vot-cli-live"
+echo " └─ Version: $VERSION"
+echo ""
+
+# Проверяем залогинены ли в npm
+npm whoami > /dev/null 2>&1
+if [ $? -ne 0 ]; then
+ echo "⚠️ You are not logged in to npm"
+ echo " Run: npm login"
+ echo ""
+ read -p " Do you want to login now? (y/n) " -n 1 -r
+ echo
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
+ npm login
+ else
+ echo "❌ Skipping npm publish"
+ exit 0
+ fi
+fi
+
+echo ""
+echo "🔍 Running final checks..."
+echo " └─ Running npm pack (dry-run)..."
+npm pack --dry-run
+echo ""
+
+read -p "📦 Ready to publish to npm? (y/n) " -n 1 -r
+echo
+if [[ $REPLY =~ ^[Yy]$ ]]; then
+ echo " └─ Publishing to npm..."
+ npm publish
+ if [ $? -eq 0 ]; then
+ echo " └─ ✅ Published to npm successfully!"
+ echo ""
+ echo "╔═══════════════════════════════════════════════════════════╗"
+ echo "║ 🎉 PUBLICATION COMPLETED! 🎉 ║"
+ echo "╚═══════════════════════════════════════════════════════════╝"
+ echo ""
+ echo "✅ Version 1.7.0 published!"
+ echo "📦 npm: https://www.npmjs.com/package/vot-cli-live"
+ echo "🐙 GitHub: https://github.com/fantomcheg/vot-cli-live"
+ echo ""
+ echo "Install with: npm install -g vot-cli-live"
+ echo ""
+ else
+ echo " └─ ❌ npm publish failed"
+ exit 1
+ fi
+else
+ echo "❌ npm publish cancelled"
+fi
+
+echo ""
+echo "🎯 Next steps:"
+echo "1. Create GitHub release: https://github.com/fantomcheg/vot-cli-live/releases/new"
+echo "2. Update README with new features"
+echo "3. Share on social media! 🎊"
+echo ""
diff --git a/scripts/translate.ps1 b/scripts/translate.ps1
index 7e9ac3b..8b93c2b 100644
--- a/scripts/translate.ps1
+++ b/scripts/translate.ps1
@@ -20,7 +20,7 @@ function ProcessVideo($video_link, $original_sound_ratio) {
New-Item -ItemType Directory -Path $temp_audio -ErrorAction SilentlyContinue | Out-Null
yt-dlp -o $temp_video $video_link
- $video_full_name = Get-ChildItem $temp_video_dir
+ $video_full_name = Join-Path (Get-Location) (Get-ChildItem $temp_video_dir).Name
vot-cli $video_link --output $temp_audio
$temp_video_file = (Get-ChildItem -Path $temp_video_dir)[0].FullName
@@ -47,8 +47,15 @@ $temp_video_dir = "$temp_dir/video"
$temp_video = "$temp_video_dir/%(title)s.%(ext)s"
$temp_audio = "$temp_dir/audio"
-$video_links = $args[0..($args.Length - 2)]
-$volume_ratio_arg = $args[-1]
+if ($args.Length -eq 1) {
+ $video_links = $args[0..($args.Length - 1)]
+ $volume_ratio_arg = $original_sound_ratio
+} else {
+ # если аргументов >= 2, считаем, что последний аргумент это возможная громкость
+ $video_links = $args[0..($args.Length - 2)]
+ $volume_ratio_arg = $args[-1]
+}
+
# If the last argument is a number, set the original sound ratio to that one
if ($volume_ratio_arg -as [double]) {
@@ -59,6 +66,7 @@ if ($volume_ratio_arg -as [double]) {
$video_links += $volume_ratio_arg
}
+
# Check that var is init
if ($video_links) {
foreach ($video_link in $video_links) {
@@ -66,7 +74,7 @@ if ($video_links) {
Write-Host "Error: Link not entered."
continue
}
-
+
Write-Host "Processing video: $video_link"
ProcessVideo $video_link $original_sound_ratio
}
diff --git a/src/config/config.js b/src/config/config.js
index 56ac2cb..b70e0a9 100644
--- a/src/config/config.js
+++ b/src/config/config.js
@@ -1,8 +1,8 @@
const debug = false;
const workerHost = "api.browser.yandex.ru";
-const yandexHmacKey = "xtGCyGdTY2Jy6OMEKdTuXev3Twhkamgm";
+const yandexHmacKey = "bt8xH3VOlb4mqf0nqAibnDOoiPlXsisf";
const yandexUserAgent =
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 YaBrowser/24.1.0.0 Safari/537.36";
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 YaBrowser/24.4.0.0 Safari/537.36";
export { debug, workerHost, yandexHmacKey, yandexUserAgent };
diff --git a/src/config/sites.js b/src/config/sites.js
index 894f0de..ac5203a 100644
--- a/src/config/sites.js
+++ b/src/config/sites.js
@@ -15,7 +15,7 @@ const sites = () => {
{
host: "youtube",
url: "https://youtu.be/",
- match: /^(www.|m.)?youtube(-nocookie)?.com$/,
+ match: /^((www.|m.)?youtube(-nocookie)?.com)|(youtu.be)$/,
},
{
host: "twitch",
@@ -147,6 +147,12 @@ const sites = () => {
url: "https://coursehunter.net/course/",
match: /^coursehunter.net$/,
},
+ {
+ host: "googledrive",
+ url: "https://drive.google.com/file/d/",
+ match: /^drive.google.com$/,
+ selector: ".html5-video-container",
+ },
];
};
diff --git a/src/download.js b/src/download.js
index cede1a2..ec6e190 100644
--- a/src/download.js
+++ b/src/download.js
@@ -1,5 +1,7 @@
import fs from "fs";
+import { Writable } from "stream";
import axios from "axios";
+import { jsonToSrt } from "./utils/utils.js";
function calcPercents(current, max) {
return ((current / max) * 100).toFixed(1);
@@ -9,7 +11,7 @@ export default async function downloadFile(url, outputPath, subtask, videoId) {
if (!url) {
throw new Error("Invalid download link");
}
-
+ const IS_NEED_CONVERT = outputPath.endsWith(".srt");
const writer = fs.createWriteStream(outputPath);
const { data, headers } = await axios({
method: "get",
@@ -31,7 +33,23 @@ export default async function downloadFile(url, outputPath, subtask, videoId) {
// console.log(calcPercents(downloadedLength, totalLength))
});
- data.pipe(writer);
+ if (IS_NEED_CONVERT) {
+ let dataBuffer = "";
+ const writableStream = new Writable({
+ write(chunk, encoding, callback) {
+ dataBuffer += chunk.toString();
+ callback();
+ },
+ });
+ data.pipe(writableStream);
+ data.on("end", () => {
+ const jsonData = JSON.parse(dataBuffer);
+ writer.write(jsonToSrt(jsonData["subtitles"]));
+ writer.end();
+ });
+ } else {
+ data.pipe(writer);
+ }
return new Promise((resolve, reject) => {
writer.on("finish", resolve);
diff --git a/src/index.js b/src/index.js
old mode 100644
new mode 100755
index 2e0d70e..97783fd
--- a/src/index.js
+++ b/src/index.js
@@ -1,5 +1,7 @@
#!/usr/bin/env node
import fs from "fs";
+import { fileURLToPath } from "url";
+import { dirname, join } from "path";
import chalk from "chalk";
import parseArgs from "minimist";
@@ -16,8 +18,14 @@ import yandexRequests from "./yandexRequests.js";
import yandexProtobuf from "./yandexProtobuf.js";
import parseProxy from "./proxy.js";
import coursehunterUtils from "./utils/coursehunter.js";
-
-const version = "1.3.0";
+import { createVideoWithTranslation } from "./mergeVideo.js";
+import getVideoTitle from "./utils/getVideoTitle.js";
+
+// Автоматически читаем версию из package.json
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+const packageJson = JSON.parse(fs.readFileSync(join(__dirname, "../package.json"), "utf8"));
+const version = packageJson.version;
const HELP_MESSAGE = `
A small script that allows you to download an audio translation from Yandex via the terminal.
@@ -26,8 +34,14 @@ Usage:
Args:
--output — Set the directory to download
+ --output-file — Set the file name to download (requires specifying a dir to download in "--output" argument)
--lang — Set the source video language
--reslang — Set the audio track language (You can see all supported languages in the documentation. Default: ru)
+ --voice-style — Set voice style (tts - standard TTS, live - live voices/живые голоса. Default: live)
+ --merge-video — Merge video with translation audio (requires yt-dlp and ffmpeg)
+ --keep-original-audio — Keep original audio when merging (mix with translation. Default: true)
+ --translation-volume — Set translation audio volume (0.0-2.0. Default: 1.0)
+ --original-volume — Set original audio volume (0.0-2.0. Default: 1.0)
--proxy — Set proxy in format ([://]:@[:])
--force-proxy — Don't start the transfer if the proxy could not be identified (true | false. Default: false)
@@ -40,22 +54,43 @@ Options:
// LANG PAIR
let REQUEST_LANG = "en";
let RESPONSE_LANG = "ru";
+let USE_LIVE_VOICES = true; // по умолчанию используем живые голоса
let proxyData = false;
// ARG PARSER
-const argv = parseArgs(process.argv.slice(2));
+const argv = parseArgs(process.argv.slice(2), {
+ boolean: ["merge-video", "keep-original-audio", "subs", "subtitles", "subs-srt", "subtitles-srt", "help", "h", "version", "v", "force-proxy"],
+ string: ["output", "output-file", "lang", "reslang", "voice-style", "proxy", "translation-volume", "original-volume"],
+});
const ARG_LINKS = argv._;
const OUTPUT_DIR = argv.output;
-const IS_SUBS_REQ = argv.subs || argv.subtitles;
+const OUTPUT_FILE = argv["output-file"];
+const IS_SUBS_FORMAT_SRT = argv["subs-srt"] || argv["subtitles-srt"];
+const RESPONSE_SUBTITLES_FORMAT = IS_SUBS_FORMAT_SRT ? "srt" : "json";
+const IS_SUBS_REQ = argv.subs || argv.subtitles || IS_SUBS_FORMAT_SRT;
const ARG_HELP = argv.help || argv.h;
const ARG_VERSION = argv.version || argv.v;
const PROXY_STRING = argv.proxy;
let FORCE_PROXY = argv["force-proxy"] ?? false;
+const MERGE_VIDEO = argv["merge-video"] === true || argv["merge-video"] === "";
+const KEEP_ORIGINAL_AUDIO = argv["keep-original-audio"] ?? true;
+const TRANSLATION_VOLUME = parseFloat(argv["translation-volume"]) || 1.0;
+const ORIGINAL_VOLUME = parseFloat(argv["original-volume"]) || 1.0;
+
+if (argv["voice-style"] !== undefined) {
+ const voiceStyleValue = argv["voice-style"].toLowerCase();
+ if (voiceStyleValue === "tts" || voiceStyleValue === "live") {
+ USE_LIVE_VOICES = (voiceStyleValue === "live");
+ console.log(chalk.cyan(`🎤 Voice style is set to ${USE_LIVE_VOICES ? "live voices (живые голоса) 🔥" : "standard TTS 🤖"}`));
+ } else {
+ console.error(chalk.yellow("⚠️ Invalid voice-style value. Using default (live - live voices)"));
+ }
+}
if (availableLangs.includes(argv.lang)) {
REQUEST_LANG = argv.lang;
- console.log(`Request language is set to ${REQUEST_LANG}`);
+ console.log(chalk.cyan(`🌐 Request language is set to ${chalk.bold(REQUEST_LANG.toUpperCase())}`));
}
if (
@@ -63,17 +98,23 @@ if (
(Boolean(IS_SUBS_REQ) && argv.reslang)
) {
RESPONSE_LANG = argv.reslang;
- console.log(`Response language is set to ${RESPONSE_LANG}`);
+ console.log(chalk.cyan(`🗣️ Response language is set to ${chalk.bold(RESPONSE_LANG.toUpperCase())}`));
}
if (PROXY_STRING) {
+ console.log(chalk.cyan(`🌍 Parsing proxy configuration...`));
proxyData = parseProxy(PROXY_STRING);
+ if (proxyData) {
+ console.log(chalk.green(`✅ Proxy configured: ${proxyData.host}:${proxyData.port || 'default'}`));
+ } else {
+ console.log(chalk.red(`❌ Failed to parse proxy configuration`));
+ }
}
if (FORCE_PROXY && !proxyData) {
throw new Error(
chalk.red(
- "vot-cli operation was interrupted due to the force-proxy option",
+ "❌ vot-cli operation was interrupted due to the force-proxy option",
),
);
}
@@ -118,6 +159,7 @@ const translate = async (finalURL, task) => {
throw new Error(chalk.red(urlOrError));
}
},
+ USE_LIVE_VOICES, // передаем параметр live voices
);
} catch (e) {
return {
@@ -204,18 +246,38 @@ async function main() {
if (ARG_HELP) {
return console.log(HELP_MESSAGE);
} else if (ARG_VERSION) {
- return console.log(`vot-cli ${version}`);
+ return console.log(`🎬 vot-cli ${version}`);
} else {
- return console.error(chalk.red("No links provided"));
+ return console.error(chalk.red("❌ No links provided"));
}
}
+ // Красивый баннер при запуске
+ console.log(chalk.cyan('\n╔═══════════════════════════════════════════════════════════╗'));
+ console.log(chalk.cyan('║') + chalk.bold.white(' 🎬 VOT-CLI with Live Voices 🔥 ') + chalk.cyan('║'));
+ console.log(chalk.cyan('╚═══════════════════════════════════════════════════════════╝'));
+ console.log(chalk.gray(' Это форк продукта https://github.com/FOSWLY/vot-cli/'));
+ console.log(chalk.gray(' Вся слава Илье @ToilOfficial 🙏\n'));
+
+ console.log(chalk.gray(`📦 Version: ${version}`));
+ console.log(chalk.gray(`🎯 Videos to process: ${ARG_LINKS.length}`));
+ if (MERGE_VIDEO) {
+ console.log(chalk.yellow(`🎬 Video merge mode: ${chalk.bold('ENABLED')}`));
+ console.log(chalk.gray(` ├─ Original volume: ${ORIGINAL_VOLUME * 100}%`));
+ console.log(chalk.gray(` └─ Translation volume: ${TRANSLATION_VOLUME * 100}%`));
+ }
+ console.log('');
+
if (Boolean(OUTPUT_DIR) && !fs.existsSync(OUTPUT_DIR)) {
try {
+ console.log(chalk.cyan(`📁 Creating output directory: ${OUTPUT_DIR}`));
fs.mkdirSync(OUTPUT_DIR);
+ console.log(chalk.green(`✅ Directory created successfully\n`));
} catch {
- throw new Error("Invalid output directory");
+ throw new Error(chalk.red("❌ Invalid output directory"));
}
+ } else if (Boolean(OUTPUT_DIR)) {
+ console.log(chalk.green(`✅ Output directory exists: ${OUTPUT_DIR}\n`));
}
for (const url of ARG_LINKS) {
@@ -255,7 +317,7 @@ async function main() {
task.newListr(
(parent) => [
{
- title: `Forming a link to the video`,
+ title: `🔗 Forming a link to the video`,
task: async () => {
const finalURL =
videoId.startsWith("https://") || service.host === "custom"
@@ -265,10 +327,23 @@ async function main() {
throw new Error(`Entered unsupported link: ${finalURL}`);
}
parent.finalURL = finalURL;
+ console.log(chalk.gray(` └─ URL: ${finalURL}`));
+
+ // Получаем название видео для имени файла
+ try {
+ console.log(chalk.cyan(` └─ 📺 Fetching video title...`));
+ parent.videoTitle = await getVideoTitle(finalURL);
+ if (parent.videoTitle) {
+ console.log(chalk.green(` └─ ✅ Title: "${parent.videoTitle}"`));
+ }
+ } catch (e) {
+ console.log(chalk.yellow(` └─ ⚠️ Could not fetch title, using video ID`));
+ parent.videoTitle = null;
+ }
},
},
{
- title: `Translating (ID: ${videoId}).`,
+ title: `🎤 Translating (ID: ${videoId}) with ${USE_LIVE_VOICES ? 'live voices 🔥' : 'TTS 🤖'}`,
enabled: !IS_SUBS_REQ,
exitOnError: false,
task: async (ctxSub, subtask) => {
@@ -276,25 +351,47 @@ async function main() {
await new Promise(async (resolve, reject) => {
try {
let result;
+ const MAX_RETRIES = 10; // Максимум 10 попыток (5 минут)
+ const RETRY_INTERVAL = 30000; // 30 секунд между попытками
+ let retryCount = 0;
+
+ console.log(chalk.cyan(` └─ 📡 Requesting translation from Yandex API...`));
result = await translate(parent.finalURL, subtask);
// console.log("transalting", result)
if (typeof result !== "object") {
- await new Promise(async (resolve) => {
+ console.log(chalk.yellow(` └─ ⏳ Translation is being prepared, waiting...`));
+ await new Promise(async (resolve, reject) => {
const intervalId = setInterval(async () => {
+ retryCount++;
+ if (retryCount > MAX_RETRIES) {
+ clearInterval(intervalId);
+ const errorMsg = `Translation timeout after ${MAX_RETRIES} attempts (${(MAX_RETRIES * RETRY_INTERVAL) / 60000} minutes). Try again later.`;
+ subtask.title = `❌ ${errorMsg}`;
+ reject(new Error(errorMsg));
+ return;
+ }
+
+ subtask.title = `🎤 Translating (ID: ${videoId}) - attempt ${retryCount}/${MAX_RETRIES} ⏰`;
+ console.log(chalk.gray(` └─ ⏳ Retry ${retryCount}/${MAX_RETRIES} (waiting ${RETRY_INTERVAL / 1000}s)...`));
// console.log("interval...", result)
result = await translate(parent.finalURL, subtask);
if (typeof result === "object") {
// console.log("finished", parent.translateResult)
clearInterval(intervalId);
+ console.log(chalk.green(` └─ ✅ Translation ready!`));
resolve(result);
}
- }, 30000);
+ }, RETRY_INTERVAL);
});
+ } else {
+ console.log(chalk.green(` └─ ✅ Translation received instantly (cached)`));
}
// console.log("translated", result)
parent.translateResult = result;
if (!result.success) {
- subtask.title = result.urlOrError;
+ subtask.title = `❌ ${result.urlOrError}`;
+ } else {
+ subtask.title = `✅ Translated successfully with ${USE_LIVE_VOICES ? 'live voices 🔥' : 'TTS 🤖'}`;
}
resolve(result);
} catch (e) {
@@ -324,7 +421,7 @@ async function main() {
},
},
{
- title: `Downloading (ID: ${videoId}).`,
+ title: `📥 Downloading audio translation (ID: ${videoId})`,
exitOnError: false,
enabled: Boolean(OUTPUT_DIR) && !IS_SUBS_REQ,
task: async (ctxSub, subtask) => {
@@ -344,9 +441,17 @@ async function main() {
}
const taskSubTitle = `(ID: ${videoId})`;
- const filename = `${clearFileName(
- videoId,
- )}---${uuidv4()}.mp3`;
+ const filename = OUTPUT_FILE
+ ? OUTPUT_FILE.endsWith(".mp3")
+ ? OUTPUT_FILE
+ : `${OUTPUT_FILE}.mp3`
+ : parent.videoTitle
+ ? `${parent.videoTitle}.mp3`
+ : `${clearFileName(videoId)}---${uuidv4()}.mp3`;
+
+ console.log(chalk.cyan(` └─ 💾 Saving as: ${chalk.bold(filename)}`));
+ console.log(chalk.gray(` └─ 🔗 Source: ${parent.translateResult.urlOrError.substring(0, 60)}...`));
+
await downloadFile(
parent.translateResult.urlOrError,
`${OUTPUT_DIR}/${filename}`,
@@ -354,10 +459,13 @@ async function main() {
`(ID: ${videoId} as ${filename})`,
)
.then(() => {
- subtask.title = `Download ${taskSubTitle} completed!`;
+ const fileSize = fs.statSync(`${OUTPUT_DIR}/${filename}`).size;
+ const fileSizeMB = (fileSize / 1024 / 1024).toFixed(2);
+ subtask.title = `✅ Audio downloaded! (${fileSizeMB} MB)`;
+ console.log(chalk.green(` └─ ✅ File size: ${fileSizeMB} MB`));
})
.catch((e) => {
- subtask.title = `Error. Download ${taskSubTitle} failed! Reason: ${e.message}`;
+ subtask.title = `❌ Error. Download ${taskSubTitle} failed! Reason: ${e.message}`;
});
},
},
@@ -393,9 +501,13 @@ async function main() {
}
const taskSubTitle = `(ID: ${videoId})`;
- const filename = `${subOnReqLang.language}---${clearFileName(
- videoId,
- )}---${uuidv4()}.json`;
+ const filename = OUTPUT_FILE
+ ? OUTPUT_FILE.endsWith(`.${RESPONSE_SUBTITLES_FORMAT}`)
+ ? OUTPUT_FILE
+ : `${OUTPUT_FILE}.${RESPONSE_SUBTITLES_FORMAT}`
+ : `${subOnReqLang.language}---${clearFileName(
+ videoId,
+ )}---${uuidv4()}.${RESPONSE_SUBTITLES_FORMAT}`;
await downloadFile(
subOnReqLang.url,
`${OUTPUT_DIR}/${filename}`,
@@ -410,6 +522,85 @@ async function main() {
});
},
},
+ {
+ title: `🎬 Merging video with translation (ID: ${videoId})`,
+ exitOnError: false,
+ enabled: Boolean(OUTPUT_DIR) && Boolean(MERGE_VIDEO) && !IS_SUBS_REQ,
+ task: async (ctxSub, subtask) => {
+ if (
+ !(
+ parent.translateResult?.success &&
+ parent.translateResult?.urlOrError
+ )
+ ) {
+ throw new Error(
+ chalk.red(
+ `Merging failed! Audio link not found`,
+ ),
+ );
+ }
+
+ console.log(chalk.cyan(` └─ 🎥 Starting video merge process...`));
+ console.log(chalk.gray(` ├─ Original volume: ${ORIGINAL_VOLUME * 100}%`));
+ console.log(chalk.gray(` └─ Translation volume: ${TRANSLATION_VOLUME * 100}%`));
+
+ const audioFilename = OUTPUT_FILE
+ ? OUTPUT_FILE.endsWith(".mp3")
+ ? OUTPUT_FILE
+ : `${OUTPUT_FILE}.mp3`
+ : `${clearFileName(videoId)}---${uuidv4()}.mp3`;
+ const audioPath = `${OUTPUT_DIR}/${audioFilename}`;
+
+ const videoFilename = OUTPUT_FILE
+ ? (OUTPUT_FILE.endsWith(".mp4") ? OUTPUT_FILE : `${OUTPUT_FILE}.mp4`)
+ : parent.videoTitle
+ ? `${parent.videoTitle}.mp4`
+ : `${clearFileName(videoId)}---${uuidv4()}.mp4`;
+ const videoPath = `${OUTPUT_DIR}/${videoFilename}`;
+
+ subtask.title = `📥 Downloading audio for merge...`;
+ console.log(chalk.cyan(` └─ 📥 Step 1/3: Downloading translation audio...`));
+ await downloadFile(
+ parent.translateResult.urlOrError,
+ audioPath,
+ null,
+ null,
+ );
+ const audioSize = (fs.statSync(audioPath).size / 1024 / 1024).toFixed(2);
+ console.log(chalk.green(` └─ ✅ Audio downloaded (${audioSize} MB)`));
+
+ subtask.title = `🎬 Creating video with translation...`;
+ console.log(chalk.cyan(` └─ 🎬 Step 2/3: Merging video with translation...`));
+ console.log(chalk.gray(` ├─ This may take several minutes...`));
+ console.log(chalk.gray(` └─ Video: ${videoFilename}`));
+
+ await createVideoWithTranslation(
+ parent.finalURL,
+ audioPath,
+ videoPath,
+ {
+ keepOriginalAudio: KEEP_ORIGINAL_AUDIO,
+ audioVolume: ORIGINAL_VOLUME,
+ translationVolume: TRANSLATION_VOLUME,
+ ...(proxyData?.proxyUrl
+ ? { proxyUrl: proxyData.proxyUrl }
+ : {}),
+ },
+ );
+
+ // Удаляем временный аудио файл
+ console.log(chalk.cyan(` └─ 🧹 Step 3/3: Cleaning up temporary files...`));
+ if (fs.existsSync(audioPath)) {
+ fs.unlinkSync(audioPath);
+ console.log(chalk.gray(` └─ ✅ Temporary audio file removed`));
+ }
+
+ const videoSize = (fs.statSync(videoPath).size / 1024 / 1024).toFixed(2);
+ subtask.title = `✅ Video created! (${videoSize} MB) - ${videoFilename}`;
+ console.log(chalk.green(` └─ ✅ Final video size: ${videoSize} MB`));
+ console.log(chalk.green(` └─ 📁 Saved to: ${videoPath}`));
+ },
+ },
{
title: `Finish (ID: ${videoId}).`,
task: () => {
@@ -431,8 +622,26 @@ async function main() {
try {
await tasks.run();
+
+ // Красивый финальный баннер
+ console.log('');
+ console.log(chalk.green('╔═══════════════════════════════════════════════════════════╗'));
+ console.log(chalk.green('║') + chalk.bold.white(' 🎉 ALL TASKS COMPLETED! 🎉 ') + chalk.green('║'));
+ console.log(chalk.green('╚═══════════════════════════════════════════════════════════╝'));
+ console.log('');
+ console.log(chalk.cyan(`✅ Successfully processed ${ARG_LINKS.length} video(s)`));
+ if (OUTPUT_DIR) {
+ console.log(chalk.cyan(`📁 Output directory: ${OUTPUT_DIR}`));
+ }
+ console.log('');
} catch (e) {
+ console.error('');
+ console.error(chalk.red('╔═══════════════════════════════════════════════════════════╗'));
+ console.error(chalk.red('║') + chalk.bold.white(' ❌ ERROR OCCURRED ❌ ') + chalk.red('║'));
+ console.error(chalk.red('╚═══════════════════════════════════════════════════════════╝'));
+ console.error('');
console.error(e);
+ console.error('');
}
}
diff --git a/src/mergeVideo.js b/src/mergeVideo.js
new file mode 100644
index 0000000..7245acb
--- /dev/null
+++ b/src/mergeVideo.js
@@ -0,0 +1,164 @@
+import { exec } from "child_process";
+import { promisify } from "util";
+import fs from "fs";
+import path from "path";
+
+const execAsync = promisify(exec);
+
+// Обертка для execAsync с таймаутом
+async function execWithTimeout(command, options = {}, timeoutMs = 600000) {
+ const timeout = options.timeout || timeoutMs; // 10 минут по умолчанию
+ return await execAsync(command, { ...options, timeout });
+}
+
+/**
+ * Скачивает видео с YouTube используя yt-dlp
+ * @param {string} videoUrl - URL видео
+ * @param {string} outputPath - путь для сохранения
+ * @returns {Promise} - путь к скачанному видео
+ */
+async function downloadYouTubeVideo(videoUrl, outputDir, proxyUrl) {
+ const videoPath = `${outputDir}/temp_video_${Date.now()}.mp4`;
+
+ // Проверяем наличие yt-dlp
+ try {
+ await execWithTimeout("yt-dlp --version", {}, 5000); // 5 секунд на проверку версии
+ } catch (error) {
+ throw new Error(
+ "yt-dlp не установлен. Установите: pip install yt-dlp или sudo apt install yt-dlp",
+ );
+ }
+
+ // Скачиваем видео в лучшем качестве
+ const commandParts = [
+ "yt-dlp",
+ `-f ${JSON.stringify("best[ext=mp4]/best")}`,
+ "--merge-output-format mp4",
+ `-o ${JSON.stringify(videoPath)}`,
+ ];
+ if (proxyUrl) {
+ commandParts.push(`--proxy ${JSON.stringify(proxyUrl)}`);
+ }
+ commandParts.push(JSON.stringify(videoUrl));
+ const command = commandParts.join(" ");
+ const env = proxyUrl
+ ? {
+ ...process.env,
+ HTTP_PROXY: proxyUrl,
+ http_proxy: proxyUrl,
+ HTTPS_PROXY: proxyUrl,
+ https_proxy: proxyUrl,
+ ALL_PROXY: proxyUrl,
+ all_proxy: proxyUrl,
+ }
+ : process.env;
+
+ try {
+ // 10 минут на скачивание видео
+ await execWithTimeout(command, { env }, 600000);
+ } catch (error) {
+ if (error.killed && error.signal === 'SIGTERM') {
+ throw new Error("yt-dlp timeout: Video download took too long (10 minutes)");
+ }
+ // Если файл скачался но с другим расширением, попробуем найти его
+ const dir = path.dirname(videoPath);
+ const files = fs.readdirSync(dir).filter(f => f.startsWith('temp_video_'));
+ if (files.length > 0) {
+ return path.join(dir, files[0]);
+ }
+ throw error;
+ }
+
+ return videoPath;
+}
+
+/**
+ * Объединяет видео и аудио перевод
+ * @param {string} videoPath - путь к видео файлу
+ * @param {string} audioPath - путь к аудио переводу
+ * @param {string} outputPath - путь для сохранения результата
+ * @param {object} options - дополнительные опции
+ * @returns {Promise}
+ */
+async function mergeVideoWithAudio(videoPath, audioPath, outputPath, options = {}) {
+ const {
+ keepOriginalAudio = true,
+ audioVolume = 1.0,
+ translationVolume = 1.0,
+ } = options;
+
+ // Проверяем наличие ffmpeg
+ try {
+ await execWithTimeout("ffmpeg -version", {}, 5000); // 5 секунд на проверку версии
+ } catch (error) {
+ throw new Error("ffmpeg не установлен. Установите: sudo apt install ffmpeg");
+ }
+
+ let command;
+
+ if (keepOriginalAudio) {
+ // Микшируем оригинальное аудио с переводом
+ command = `ffmpeg -i "${videoPath}" -i "${audioPath}" -filter_complex "[0:a]volume=${audioVolume}[a1];[1:a]volume=${translationVolume}[a2];[a1][a2]amix=inputs=2:duration=longest[aout]" -map 0:v -map "[aout]" -c:v copy -c:a aac -b:a 192k -y "${outputPath}"`;
+ } else {
+ // Заменяем оригинальное аудио на перевод
+ command = `ffmpeg -i "${videoPath}" -i "${audioPath}" -map 0:v -map 1:a -c:v copy -c:a aac -b:a 192k -shortest -y "${outputPath}"`;
+ }
+
+ try {
+ // 15 минут на обработку видео с ffmpeg
+ await execWithTimeout(command, {}, 900000);
+ } catch (error) {
+ if (error.killed && error.signal === 'SIGTERM') {
+ throw new Error("ffmpeg timeout: Video processing took too long (15 minutes)");
+ }
+ throw error;
+ }
+}
+
+/**
+ * Полный процесс: скачивание видео, получение перевода и объединение
+ * @param {string} videoUrl - URL видео
+ * @param {string} audioPath - путь к аудио переводу
+ * @param {string} outputPath - путь для сохранения результата
+ * @param {object} options - дополнительные опции
+ * @returns {Promise}
+ */
+export async function createVideoWithTranslation(
+ videoUrl,
+ audioPath,
+ outputPath,
+ options = {},
+) {
+ const tempDir = path.dirname(outputPath);
+ const { proxyUrl, ...mergeOptions } = options;
+ let videoPath;
+
+ try {
+ // Скачиваем оригинальное видео
+ console.log("Скачивание видео...");
+ videoPath = await downloadYouTubeVideo(videoUrl, tempDir, proxyUrl);
+
+ // Объединяем с переводом
+ console.log("Объединение видео с переводом...");
+ await mergeVideoWithAudio(videoPath, audioPath, outputPath, mergeOptions);
+
+ // Удаляем временное видео
+ if (fs.existsSync(videoPath)) {
+ fs.unlinkSync(videoPath);
+ }
+
+ console.log(`✅ Видео с переводом сохранено: ${outputPath}`);
+ } catch (error) {
+ // Очистка временных файлов при ошибке
+ if (videoPath && fs.existsSync(videoPath)) {
+ fs.unlinkSync(videoPath);
+ }
+ throw error;
+ }
+}
+
+export default {
+ downloadYouTubeVideo,
+ mergeVideoWithAudio,
+ createVideoWithTranslation,
+};
diff --git a/src/proxy.js b/src/proxy.js
index d8fb790..7909c7d 100644
--- a/src/proxy.js
+++ b/src/proxy.js
@@ -1,26 +1,42 @@
export default function parseProxy(proxyString) {
// proxyString is a string in format [://]:@[:]
try {
- const parsedData = new URL(proxyString);
+ const normalizedProxyString = proxyString.includes("://")
+ ? proxyString
+ : `http://${proxyString}`;
+ const parsedData = new URL(normalizedProxyString);
const { protocol, hostname, port, username, password } = parsedData;
if (!protocol.startsWith("http")) {
console.error("Only HTTP and HTTPS proxies are supported");
return false;
}
- return {
+ const proxyConfig = {
protocol: protocol.replace(":", ""),
host: hostname,
- port,
- ...(username && password
- ? {
- auth: {
- username,
- password,
- },
- }
- : {}),
};
+
+ if (port) {
+ proxyConfig.port = Number(port);
+ }
+
+ if (username || password) {
+ proxyConfig.auth = {
+ username: decodeURIComponent(username),
+ password: decodeURIComponent(password),
+ };
+ }
+
+ const authPart =
+ username || password
+ ? `${encodeURIComponent(username)}${
+ password ? `:${encodeURIComponent(password)}` : ""
+ }@`
+ : "";
+ const portPart = port ? `:${port}` : "";
+ proxyConfig.proxyUrl = `${protocol}//${authPart}${hostname}${portPart}`;
+
+ return proxyConfig;
} catch (e) {
console.error("Failed to parse entered proxy. Error:", e);
return false;
diff --git a/src/translateVideo.js b/src/translateVideo.js
index 2ff925c..1912d6b 100644
--- a/src/translateVideo.js
+++ b/src/translateVideo.js
@@ -1,5 +1,6 @@
import yandexRequests from "./yandexRequests.js";
import yandexProtobuf from "./yandexProtobuf.js";
+import getVideoDuration from "./utils/getVideoDuration.js";
export default async function translateVideo(
url,
@@ -8,9 +9,11 @@ export default async function translateVideo(
translationHelp,
proxyData,
callback,
+ useLiveVoices = true, // по умолчанию используем живые голоса
) {
- // TODO: Use real duration (maybe)
- const duration = 341;
+ // Получаем реальную длительность видео
+ const duration = await getVideoDuration(url, proxyData);
+
await yandexRequests.requestVideoTranslation(
url,
duration,
@@ -20,7 +23,26 @@ export default async function translateVideo(
proxyData,
(success, response) => {
if (!success) {
- callback(false, "Failed to request video translation");
+ let errorMessage = "Failed to request video translation";
+ if (typeof response === "string" && response.trim()) {
+ errorMessage = response.trim();
+ } else if (
+ typeof response === "object" &&
+ response !== null &&
+ typeof response.message === "string" &&
+ response.message.trim()
+ ) {
+ errorMessage = response.message.trim();
+ } else if (
+ typeof Buffer !== "undefined" &&
+ Buffer.isBuffer(response)
+ ) {
+ const bufferText = response.toString("utf8").trim();
+ if (bufferText) {
+ errorMessage = bufferText;
+ }
+ }
+ callback(false, errorMessage);
return;
}
@@ -43,5 +65,6 @@ export default async function translateVideo(
return;
}
},
+ useLiveVoices, // передаем параметр useLiveVoices
);
}
diff --git a/src/utils/getSignature.js b/src/utils/getSignature.js
index de4ad40..d494112 100644
--- a/src/utils/getSignature.js
+++ b/src/utils/getSignature.js
@@ -1,20 +1,20 @@
import crypto from "crypto";
import { yandexHmacKey } from "../config/config.js";
+// Create a key from the HMAC secret
+const CryptoKey = crypto.subtle.importKey(
+ "raw",
+ new TextEncoder().encode(yandexHmacKey),
+ { name: "HMAC", hash: { name: "SHA-256" } },
+ false,
+ ["sign", "verify"],
+);
+
export default async function getSignature(body) {
- // Create a key from the HMAC secret
- const utf8Encoder = new TextEncoder("utf-8");
- const key = await crypto.subtle.importKey(
- "raw",
- utf8Encoder.encode(yandexHmacKey),
- { name: "HMAC", hash: { name: "SHA-256" } },
- false,
- ["sign", "verify"],
- );
- // Sign the body with the key
- const signature = await crypto.subtle.sign("HMAC", key, body);
- // Convert the signature to a hex string
- return Array.from(new Uint8Array(signature), (x) =>
- x.toString(16).padStart(2, "0"),
- ).join("");
+ const key = await CryptoKey;
+ return new Uint8Array(
+ // Sign the body with the key
+ await crypto.subtle.sign("HMAC", key, body),
+ // Convert the signature to a hex string
+ ).reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
}
diff --git a/src/utils/getUUID.js b/src/utils/getUUID.js
index ddc3e9c..180ba80 100644
--- a/src/utils/getUUID.js
+++ b/src/utils/getUUID.js
@@ -1,11 +1,9 @@
-import crypto from "crypto";
-
-export default function getUUID(isLower) {
- const uuid = ([1e7] + 1e3 + 4e3 + 8e3 + 1e11).replace(/[018]/g, (c) =>
- (
- c ^
- (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
- ).toString(16),
- );
- return isLower ? uuid : uuid.toUpperCase();
+export default function getUUID() {
+ const hexDigits = "0123456789ABCDEF";
+ let uuid = "";
+ for (let i = 0; i < 32; i++) {
+ const randomDigit = Math.floor(Math.random() * 16);
+ uuid += hexDigits[randomDigit];
+ }
+ return uuid;
}
diff --git a/src/utils/getVideoDuration.js b/src/utils/getVideoDuration.js
new file mode 100644
index 0000000..f064e37
--- /dev/null
+++ b/src/utils/getVideoDuration.js
@@ -0,0 +1,59 @@
+import { exec } from "child_process";
+import { promisify } from "util";
+
+const execAsync = promisify(exec);
+
+/**
+ * Получает длительность видео в секундах через yt-dlp
+ * @param {string} videoUrl - URL видео
+ * @param {object} proxyData - Данные прокси (опционально)
+ * @returns {Promise} - Длительность в секундах
+ */
+export default async function getVideoDuration(videoUrl, proxyData = null) {
+ try {
+ // Проверяем наличие yt-dlp
+ try {
+ await execAsync("yt-dlp --version", { timeout: 5000 });
+ } catch (error) {
+ console.warn("⚠️ yt-dlp not found, using default duration (341s)");
+ return 341; // Возвращаем дефолтное значение
+ }
+
+ // Получаем длительность через yt-dlp
+ let command = `yt-dlp --print duration`;
+
+ // Добавляем прокси если указан
+ if (proxyData?.proxyUrl) {
+ command += ` --proxy "${proxyData.proxyUrl}"`;
+ }
+
+ command += ` "${videoUrl}"`;
+
+ const env = proxyData?.proxyUrl
+ ? {
+ ...process.env,
+ HTTP_PROXY: proxyData.proxyUrl,
+ http_proxy: proxyData.proxyUrl,
+ HTTPS_PROXY: proxyData.proxyUrl,
+ https_proxy: proxyData.proxyUrl,
+ ALL_PROXY: proxyData.proxyUrl,
+ all_proxy: proxyData.proxyUrl,
+ }
+ : process.env;
+
+ const { stdout } = await execAsync(command, { timeout: 30000, env });
+
+ const duration = parseInt(stdout.trim(), 10);
+
+ if (isNaN(duration) || duration <= 0) {
+ console.warn("⚠️ Could not parse video duration, using default (341s)");
+ return 341;
+ }
+
+ console.log(`✓ Video duration: ${duration}s (${Math.floor(duration / 60)}m ${duration % 60}s)`);
+ return duration;
+ } catch (error) {
+ console.warn("⚠️ Failed to get video duration:", error.message);
+ return 341; // Возвращаем дефолтное значение при ошибке
+ }
+}
diff --git a/src/utils/getVideoId.js b/src/utils/getVideoId.js
index 06223f8..719ca6e 100644
--- a/src/utils/getVideoId.js
+++ b/src/utils/getVideoId.js
@@ -17,13 +17,18 @@ export default function getVideoId(service, url) {
case "piped":
case "invidious":
case "youtube":
+ if (url.hostname === "youtu.be") {
+ url.search = `?v=${url.pathname.replace("/", "")}`;
+ url.pathname = "/watch";
+ }
+
return (
- url.pathname.match(/(?:watch|embed)\/([^/]+)/)?.[1] ||
+ /(?:watch|embed|live|shorts)\/([^/]+)/.exec(url.pathname)?.[1] ||
url.searchParams.get("v")
);
case "vk":
- if (url.pathname.match(/^\/video-?[0-9]{8,9}_[0-9]{9}$/)) {
- return url.pathname.match(/^\/video-?[0-9]{8,9}_[0-9]{9}$/)[0].slice(1);
+ if (/^\/video-?[0-9]{8,9}_[0-9]{9}$/.exec(url.pathname)) {
+ return /^\/video-?[0-9]{8,9}_[0-9]{9}$/.exec(url.pathname)[0].slice(1);
} else if (url.searchParams.get("z")) {
return url.searchParams.get("z").split("/")[0];
} else if (url.searchParams.get("oid") && url.searchParams.get("id")) {
@@ -36,7 +41,7 @@ export default function getVideoId(service, url) {
case "nine_gag":
case "9gag":
case "gag":
- return url.pathname.match(/gag\/([^/]+)/)?.[1];
+ return /gag\/([^/]+)/.exec(url.pathname)?.[1];
case "twitch":
// clips.twitch.tv unsupported
@@ -45,28 +50,28 @@ export default function getVideoId(service, url) {
url.searchParams.get("video")
) {
return `videos/${url.searchParams.get("video")}`;
- } else if (url.pathname.match(/([^/]+)\/(?:clip)\/([^/]+)/)) {
- return url.pathname.match(/([^/]+)\/(?:clip)\/([^/]+)/)[0];
+ } else if (/([^/]+)\/(?:clip)\/([^/]+)/.exec(url.pathname)) {
+ return /([^/]+)\/(?:clip)\/([^/]+)/.exec(url.pathname)[0];
} else {
- return url.pathname.match(/(?:videos)\/([^/]+)/)?.[0];
+ return /(?:videos)\/([^/]+)/.exec(url.pathname)?.[0];
}
case "proxytok":
case "tiktok":
- return url.pathname.match(/([^/]+)\/video\/([^/]+)/)?.[0];
+ return /([^/]+)\/video\/([^/]+)/.exec(url.pathname)?.[0];
case "vimeo":
return (
- url.pathname.match(/[^/]+\/[^/]+$/)?.[0] ||
- url.pathname.match(/[^/]+$/)?.[0]
+ /[^/]+\/[^/]+$/.exec(url.pathname)?.[0] ||
+ /[^/]+$/.exec(url.pathname)?.[0]
);
case "xvideos":
- return url.pathname.match(/[^/]+\/[^/]+$/)?.[0];
+ return /[^/]+\/[^/]+$/.exec(url.pathname)?.[0];
case "pornhub":
return (
url.searchParams.get("viewkey") ||
- url.pathname.match(/embed\/([^/]+)/)?.[1]
+ /embed\/([^/]+)/.exec(url.pathname)?.[1]
);
case "twitter":
- return url.pathname.match(/status\/([^/]+)/)?.[1];
+ return /status\/([^/]+)/.exec(url.pathname)?.[1];
case "udemy":
return url.pathname;
case "rumble":
@@ -84,15 +89,15 @@ export default function getVideoId(service, url) {
return false;
case "rutube":
- return url.pathname.match(/(?:video|embed)\/([^/]+)/)?.[1];
+ return /(?:video|embed)\/([^/]+)/.exec(url.pathname)?.[1];
case "coub":
- return url.pathname.match(/view\/([^/]+)/)?.[1];
+ return /view\/([^/]+)/.exec(url.pathname)?.[1];
case "bilibili": {
const bvid = url.searchParams.get("bvid");
if (bvid) {
return bvid;
} else {
- let vid = url.pathname.match(/video\/([^/]+)/)?.[1];
+ let vid = /video\/([^/]+)/.exec(url.pathname)?.[1];
if (vid && url.search && url.searchParams.get("p") !== null) {
vid += `/?p=${url.searchParams.get("p")}`;
}
@@ -105,16 +110,16 @@ export default function getVideoId(service, url) {
}
return false;
case "bitchute":
- return url.pathname.match(/video\/([^/]+)/)?.[1];
+ return /video\/([^/]+)/.exec(url.pathname)?.[1];
case "coursera":
// ! LINK SHOULD BE LIKE THIS https://www.coursera.org/learn/learning-how-to-learn/lecture/75EsZ
- // return url.pathname.match(/lecture\/([^/]+)\/([^/]+)/)?.[1]; // <--- COURSE PREVIEW
- return url.pathname.match(/learn\/([^/]+)\/lecture\/([^/]+)/)?.[0]; // <--- COURSE PASSING (IF YOU LOGINED TO COURSERA)
+ // return /lecture\/([^/]+)\/([^/]+)/.exec(url.pathname)?.[1]; // <--- COURSE PREVIEW
+ return /learn\/([^/]+)\/lecture\/([^/]+)/.exec(url.pathname)?.[0]; // <--- COURSE PASSING (IF YOU LOGINED TO COURSERA)
case "eporner":
// ! LINK SHOULD BE LIKE THIS eporner.com/video-XXXXXXXXX/isdfsd-dfjsdfjsdf-dsfsdf-dsfsda-dsad-ddsd
- return url.pathname.match(/video-([^/]+)\/([^/]+)/)?.[0];
+ return /video-([^/]+)\/([^/]+)/.exec(url.pathname)?.[0];
case "peertube":
- return url.pathname.match(/\/w\/([^/]+)/)?.[0];
+ return /\/w\/([^/]+)/.exec(url.pathname)?.[0];
case "dailymotion": {
return url.pathname;
}
@@ -128,7 +133,7 @@ export default function getVideoId(service, url) {
return false;
}
- const path = url.pathname.match(/([^/]+)\/([\d]+)/)?.[0];
+ const path = /([^/]+)\/([\d]+)/.exec(url.pathname)?.[0];
if (!path) {
return false;
}
@@ -136,9 +141,9 @@ export default function getVideoId(service, url) {
return `${path}?vid=${vid}`;
}
case "yandexdisk":
- return url.pathname.match(/\/i\/([^/]+)/)?.[1];
+ return /\/i\/([^/]+)/.exec(url.pathname)?.[1];
case "coursehunter": {
- const videoId = url.pathname.match(/\/course\/([^/]+)/)?.[1];
+ const videoId = /\/course\/([^/]+)/.exec(url.pathname)?.[1];
if (!videoId) {
return [false, 0];
}
@@ -146,8 +151,10 @@ export default function getVideoId(service, url) {
return [videoId, Number(url.searchParams.get("lesson") ?? 1)];
}
case "ok.ru": {
- return url.pathname.match(/\/video\/(\d+)/)?.[0];
+ return /\/video\/(\d+)/.exec(url.pathname)?.[0];
}
+ case "googledrive":
+ return /\/file\/d\/([^/]+)/.exec(url.pathname)?.[1];
default:
return false;
}
diff --git a/src/utils/getVideoTitle.js b/src/utils/getVideoTitle.js
new file mode 100644
index 0000000..4ade8e4
--- /dev/null
+++ b/src/utils/getVideoTitle.js
@@ -0,0 +1,35 @@
+import { exec } from "child_process";
+import { promisify } from "util";
+
+const execAsync = promisify(exec);
+
+/**
+ * Получает название видео с YouTube используя yt-dlp
+ * @param {string} videoUrl - URL видео
+ * @returns {Promise} - название видео
+ */
+async function getVideoTitle(videoUrl) {
+ try {
+ // Проверяем наличие yt-dlp
+ await execAsync("yt-dlp --version");
+
+ // Получаем название видео
+ const { stdout } = await execAsync(
+ `yt-dlp --get-title "${videoUrl}"`,
+ { timeout: 10000 }
+ );
+
+ // Очищаем название от недопустимых символов для имени файла
+ const title = stdout.trim()
+ .replace(/[<>:"/\\|?*]/g, "_") // Заменяем недопустимые символы
+ .replace(/\s+/g, "_") // Пробелы на подчёркивания
+ .substring(0, 100); // Ограничиваем длину
+
+ return title || null;
+ } catch (error) {
+ // Если yt-dlp не установлен или ошибка - возвращаем null
+ return null;
+ }
+}
+
+export default getVideoTitle;
diff --git a/src/utils/utils.js b/src/utils/utils.js
index b18e25a..3d7f243 100644
--- a/src/utils/utils.js
+++ b/src/utils/utils.js
@@ -2,4 +2,29 @@ function clearFileName(name) {
return name.replace(/[^\w.-]/g, "");
}
-export { clearFileName };
+function convertToSrtTimeFormat(seconds) {
+ let hours = Math.floor(seconds / 3600);
+ let minutes = Math.floor((seconds % 3600) / 60);
+ let remainingSeconds = Math.floor(seconds % 60);
+ let milliseconds = Math.floor((seconds % 1) * 1000);
+
+ return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")},${milliseconds.toString().padStart(3, "0")}`;
+}
+
+function jsonToSrt(jsonData) {
+ let srtContent = "";
+ let index = 1;
+ for (const entry of jsonData) {
+ let startTime = entry.startMs / 1000.0;
+ let endTime = (entry.startMs + entry.durationMs) / 1000.0;
+
+ srtContent += `${index}\n`;
+ srtContent += `${convertToSrtTimeFormat(startTime)} --> ${convertToSrtTimeFormat(endTime)}\n`;
+ srtContent += `${entry.text}\n\n`;
+ index++;
+ }
+
+ return srtContent.trim();
+}
+
+export { clearFileName, jsonToSrt };
diff --git a/src/yandexProtobuf.js b/src/yandexProtobuf.js
index 50902e4..cfe8211 100644
--- a/src/yandexProtobuf.js
+++ b/src/yandexProtobuf.js
@@ -9,13 +9,13 @@ const VideoTranslationHelpObject = new protobuf.Type(
const VideoTranslationRequest = new protobuf.Type("VideoTranslationRequest")
.add(new protobuf.Field("url", 3, "string"))
- .add(new protobuf.Field("deviceId", 4, "string")) // removed?
+ .add(new protobuf.Field("deviceId", 4, "string")) // used in mobile version
.add(new protobuf.Field("firstRequest", 5, "bool")) // true for the first request, false for subsequent ones
.add(new protobuf.Field("duration", 6, "double"))
- .add(new protobuf.Field("unknown2", 7, "int32")) // 1 1
+ .add(new protobuf.Field("unknown0", 7, "int32")) // 1
.add(new protobuf.Field("language", 8, "string")) // source language code
- .add(new protobuf.Field("unknown3", 9, "int32")) // 0 0
- .add(new protobuf.Field("unknown4", 10, "int32")) // 0 0
+ .add(new protobuf.Field("forceSourceLang", 9, "bool")) // 0 - auto detected, 1 - user set
+ .add(new protobuf.Field("unknown1", 10, "int32")) // 0
.add(
new protobuf.Field(
"translationHelp",
@@ -23,9 +23,14 @@ const VideoTranslationRequest = new protobuf.Type("VideoTranslationRequest")
"VideoTranslationHelpObject",
"repeated",
),
- ) // array for translation assistance ([0] -> {2: link to video, 1: "video_file_url"}, [1] -> {2: link to subtitles, 1: "subtitles_file_url"})
+ ) // array for translation assistance
+ .add(new protobuf.Field("wasStream", 13, "bool")) // set true if it's ended stream
.add(new protobuf.Field("responseLanguage", 14, "string"))
- .add(new protobuf.Field("unknown5", 15, "int32")); // 0
+ .add(new protobuf.Field("unknown2", 15, "int32")) // 1?
+ .add(new protobuf.Field("unknown3", 16, "int32")) // before april 2025 is 1, but now it's 2
+ .add(new protobuf.Field("bypassCache", 17, "bool")) // bypass cache
+ .add(new protobuf.Field("useLivelyVoice", 18, "bool")) // higher-quality voices (live voices)
+ .add(new protobuf.Field("videoTitle", 19, "string")); // video title
const VideoSubtitlesRequest = new protobuf.Type("VideoSubtitlesRequest")
.add(new protobuf.Field("url", 1, "string"))
@@ -103,18 +108,24 @@ export default {
requestLang,
responseLang,
translationHelp,
+ useLiveVoices = true, // по умолчанию используем живые голоса
) {
return root.VideoTranslationRequest.encode({
url,
firstRequest: true,
duration,
- unknown2: 1,
+ unknown0: 1,
language: requestLang,
- unknown3: 0,
- unknown4: 0,
+ forceSourceLang: false,
+ unknown1: 0,
translationHelp,
+ wasStream: false,
responseLanguage: responseLang,
- unknown5: 0,
+ unknown2: 1,
+ unknown3: 2, // after april 2025 it's 2
+ bypassCache: false,
+ useLivelyVoice: useLiveVoices, // живые голоса!
+ videoTitle: "",
}).finish();
},
decodeTranslationResponse(response) {
diff --git a/src/yandexRawRequest.js b/src/yandexRawRequest.js
index 2d65884..cb27f8f 100644
--- a/src/yandexRawRequest.js
+++ b/src/yandexRawRequest.js
@@ -8,11 +8,13 @@ export default async function yandexRawRequest(
headers,
proxyData,
callback,
+ timeout = 60000, // Таймаут по умолчанию 60 секунд
) {
logger.debug("yandexRequest:", path);
await axios({
url: `https://${workerHost}${path}`,
method: "post",
+ timeout: timeout, // Добавлен таймаут
headers: {
...{
Accept: "application/x-protobuf",
@@ -38,6 +40,26 @@ export default async function yandexRawRequest(
})
.catch((err) => {
console.error(err);
- callback(true, err.data);
+ const status = err.response?.status;
+ const statusText = err.response?.statusText;
+ const errorCode = err.code;
+ const baseMessage = err.message;
+
+ let message;
+ if (errorCode === 'ECONNABORTED') {
+ message = `Yandex API request timeout (${timeout}ms exceeded)`;
+ } else if (errorCode === 'ECONNRESET') {
+ message = `Yandex API connection reset. Try using a proxy with --proxy`;
+ } else if (status !== undefined) {
+ message = `Yandex API request failed with status ${status}${
+ statusText ? ` ${statusText}` : ""
+ }`;
+ } else if (errorCode) {
+ message = `Yandex API request failed: ${errorCode}`;
+ } else {
+ message = `Yandex API request failed: ${baseMessage}`;
+ }
+
+ callback(false, message);
});
}
diff --git a/src/yandexRequests.js b/src/yandexRequests.js
index d5a376f..bd57c20 100644
--- a/src/yandexRequests.js
+++ b/src/yandexRequests.js
@@ -13,6 +13,7 @@ async function requestVideoTranslation(
translationHelp,
proxyData,
callback,
+ useLiveVoices = true, // по умолчанию используем живые голоса
) {
try {
logger.debug("requestVideoTranslation");
@@ -23,6 +24,7 @@ async function requestVideoTranslation(
requestLang,
responseLang,
translationHelp,
+ useLiveVoices,
);
// Send the request
await yandexRawRequest(
diff --git a/test-improvements.sh b/test-improvements.sh
new file mode 100755
index 0000000..796d874
--- /dev/null
+++ b/test-improvements.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+
+# 🧪 Скрипт для быстрого тестирования vot-cli-live
+
+echo "🎯 Тестирование vot-cli-live improvements"
+echo "=========================================="
+echo ""
+
+# Цвета для вывода
+GREEN='\033[0;32m'
+RED='\033[0;31m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Создаем временную директорию для тестов
+TEST_DIR="/tmp/vot-cli-test-$(date +%s)"
+mkdir -p "$TEST_DIR"
+echo "📁 Временная директория: $TEST_DIR"
+echo ""
+
+# Тест 1: Короткое видео с живыми голосами
+echo "🧪 Тест 1: Короткое видео (19 сек) с живыми голосами"
+echo "URL: https://www.youtube.com/watch?v=jNQXAC9IVRw"
+if timeout 120 node src/index.js --output="$TEST_DIR" --voice-style=live "https://www.youtube.com/watch?v=jNQXAC9IVRw" > /dev/null 2>&1; then
+ if [ -f "$TEST_DIR/Me_at_the_zoo.mp3" ]; then
+ echo -e "${GREEN}✅ PASS${NC}: Файл создан успешно"
+ ls -lh "$TEST_DIR/Me_at_the_zoo.mp3"
+ else
+ echo -e "${RED}❌ FAIL${NC}: Файл не создан"
+ fi
+else
+ echo -e "${RED}❌ FAIL${NC}: Процесс завершился с ошибкой"
+fi
+echo ""
+
+# Тест 2: Короткое видео с TTS
+echo "🧪 Тест 2: Короткое видео (19 сек) с TTS"
+echo "URL: https://www.youtube.com/watch?v=jNQXAC9IVRw"
+if timeout 120 node src/index.js --output="$TEST_DIR" --voice-style=tts --output-file="test_tts.mp3" "https://www.youtube.com/watch?v=jNQXAC9IVRw" > /dev/null 2>&1; then
+ if [ -f "$TEST_DIR/test_tts.mp3" ]; then
+ echo -e "${GREEN}✅ PASS${NC}: TTS озвучка работает"
+ ls -lh "$TEST_DIR/test_tts.mp3"
+ else
+ echo -e "${RED}❌ FAIL${NC}: Файл не создан"
+ fi
+else
+ echo -e "${RED}❌ FAIL${NC}: Процесс завершился с ошибкой"
+fi
+echo ""
+
+# Тест 3: Проверка таймаута (должен завершиться с ошибкой после N попыток)
+echo "🧪 Тест 3: Проверка работы таймаута"
+echo "URL: https://www.youtube.com/watch?v=dQw4w9WgXcQ"
+echo -e "${YELLOW}(этот тест может занять до 2 минут)${NC}"
+if timeout 180 node src/index.js --output="$TEST_DIR" "https://www.youtube.com/watch?v=dQw4w9WgXcQ" > /dev/null 2>&1; then
+ echo -e "${GREEN}✅ PASS${NC}: Видео переведено успешно"
+else
+ echo -e "${YELLOW}⚠️ WARNING${NC}: Видео не переведено (ожидаемо для непопулярных видео)"
+fi
+echo ""
+
+# Статистика
+echo "📊 Статистика:"
+echo "Файлов создано: $(ls -1 "$TEST_DIR" | wc -l)"
+echo "Общий размер: $(du -sh "$TEST_DIR" | cut -f1)"
+echo ""
+
+# Очистка
+echo "🧹 Очистка временных файлов..."
+read -p "Удалить тестовые файлы из $TEST_DIR? (y/n) " -n 1 -r
+echo
+if [[ $REPLY =~ ^[Yy]$ ]]; then
+ rm -rf "$TEST_DIR"
+ echo "✅ Очистка завершена"
+else
+ echo "📁 Файлы сохранены в: $TEST_DIR"
+fi
+
+echo ""
+echo "✅ Тестирование завершено!"
diff --git a/wiki/Home.md b/wiki/Home.md
new file mode 100644
index 0000000..3787c8e
--- /dev/null
+++ b/wiki/Home.md
@@ -0,0 +1,1224 @@
+# 📚 VOT-CLI Live - Полная документация
+
+> **Версия:** 1.6.2
+> **Автор форка:** fantomcheg
+> **Оригинальный проект:** [FOSWLY/vot-cli](https://github.com/FOSWLY/vot-cli)
+
+---
+
+## 📖 Содержание
+
+1. **Введение** - Что такое VOT-CLI Live и живые голоса
+2. **Установка** - Как установить через npm или из исходников
+3. **Быстрый старт** - Первые команды для начала работы
+4. **Все аргументы** - Подробное описание каждого параметра
+5. **Примеры** - Реальные примеры использования
+6. **Живые голоса vs TTS** - Сравнение и когда что использовать
+7. **Работа с языками** - Все поддерживаемые языки
+8. **Объединение видео** - Как создать видео с переводом
+9. **Субтитры** - Скачивание субтитров в JSON и SRT
+10. **Прокси** - Использование прокси серверов
+11. **Решение проблем** - Частые ошибки и их решения
+12. **FAQ** - Ответы на популярные вопросы
+
+> 💡 **Совет:** Используй `Ctrl+F` для поиска по странице
+
+---
+
+## 🎤 Введение
+
+**VOT-CLI Live** - это форк оригинального vot-cli с добавленной поддержкой **живых голосов Яндекса** (useLivelyVoice).
+
+### Что такое живые голоса?
+
+**Живые голоса** (Live Voices) - это улучшенная технология озвучки от Яндекса, которая:
+- 🎯 Звучит более естественно и выразительно
+- 🗣️ Имеет лучшую интонацию и эмоциональность
+- 🎭 Меняет тембр голоса в зависимости от контекста
+- ⚡ Качество значительно выше стандартного TTS
+
+### Отличия от оригинального vot-cli:
+
+| Параметр | Оригинальный vot-cli | VOT-CLI Live |
+|----------|---------------------|--------------|
+| Тип озвучки | Только стандартный TTS | Живые голоса по умолчанию |
+| Выбор типа | ❌ Нет | ✅ `--voice-style` |
+| Объединение видео | ❌ Нет | ✅ `--merge-video` (экспериментально) |
+| Настройка громкости | ❌ Нет | ✅ Да |
+| Protobuf структура | Устаревшая | Обновлённая (из vot.js) |
+
+---
+
+## 💻 Установка
+
+### Способ 1: Через npm (рекомендуется)
+
+```bash
+npm install -g vot-cli-live
+```
+
+### Способ 2: Из исходников
+
+```bash
+git clone https://github.com/fantomcheg/vot-cli-live.git
+cd vot-cli-live
+npm install --ignore-scripts
+sudo npm link
+```
+
+### Требования:
+
+- **Node.js** 18+ (обязательно)
+- **ffmpeg** (для `--merge-video`): `sudo apt install ffmpeg`
+- **yt-dlp** (для `--merge-video`): `pip install yt-dlp` или `sudo apt install yt-dlp`
+
+### Проверка установки:
+
+```bash
+vot-cli-live --version
+vot-cli-live --help
+```
+
+---
+
+## 🚀 Быстрый старт
+
+### Самый простой способ:
+
+```bash
+vot-cli-live --output="." "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+Это скачает аудио перевод с **живыми голосами** в текущую папку.
+
+### С указанием имени файла:
+
+```bash
+vot-cli-live --output="./downloads" --output-file="my_translation.mp3" "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### Несколько видео сразу:
+
+```bash
+vot-cli-live --output="." "URL1" "URL2" "URL3"
+```
+
+---
+
+## 📋 Все аргументы командной строки
+
+### Синтаксис:
+
+```bash
+vot-cli-live [опции] [аргументы] <ссылка> [ссылка2] [ссылка3] ...
+```
+
+---
+
+### 🎯 Основные аргументы:
+
+#### `--output=<путь>`
+Установить путь для сохранения файлов.
+
+**Примеры:**
+```bash
+--output="." # Текущая папка
+--output="/home/user/videos" # Абсолютный путь
+--output="./downloads" # Относительный путь
+```
+
+**По умолчанию:** Не сохраняет файл, только показывает ссылку.
+
+---
+
+#### `--output-file=<имя>`
+Установить имя файла для сохранения. Требует указания `--output`.
+
+**Примеры:**
+```bash
+--output-file="my_video.mp3" # С расширением
+--output-file="translation" # Без расширения (добавится .mp3)
+--output-file="video_with_trans.mp4" # Для --merge-video
+```
+
+**По умолчанию:** Генерируется автоматически: `{videoId}---{uuid}.mp3`
+
+---
+
+#### `--lang=<код>`
+Установить язык исходного видео.
+
+**Поддерживаемые языки:**
+- `ru` - Русский
+- `en` - Английский
+- `zh` - Китайский
+- `ko` - Корейский
+- `ar` - Арабский
+- `fr` - Французский
+- `it` - Итальянский
+- `es` - Испанский
+- `de` - Немецкий
+- `ja` - Японский
+
+**Примеры:**
+```bash
+--lang=en # Английское видео
+--lang=es # Испанское видео
+--lang=ja # Японское видео
+```
+
+**По умолчанию:** `en` (английский)
+
+---
+
+#### `--reslang=<код>`
+Установить язык аудио перевода.
+
+**Поддерживаемые языки для TTS:**
+- `ru` - Русский
+- `en` - Английский
+- `kk` - Казахский
+
+**Примеры:**
+```bash
+--reslang=ru # Перевод на русский
+--reslang=en # Перевод на английский
+--reslang=kk # Перевод на казахский
+```
+
+**По умолчанию:** `ru` (русский)
+
+---
+
+### 🎤 Аргументы для живых голосов:
+
+#### `--voice-style=<тип>`
+Выбрать тип озвучки.
+
+**Значения:**
+- `live` - Живые голоса (более естественная озвучка)
+- `tts` - Стандартный TTS (классическая озвучка)
+
+**Примеры:**
+```bash
+--voice-style=live # Живые голоса (по умолчанию)
+--voice-style=tts # Стандартный TTS
+```
+
+**По умолчанию:** `live`
+
+**Разница:**
+- **Live voices:** Более естественная интонация, эмоциональность, меняющийся тембр
+- **Standard TTS:** Монотонный голос, роботизированное звучание
+
+---
+
+### 🎬 Аргументы для объединения видео:
+
+#### `--merge-video`
+Скачать видео и объединить с аудио переводом.
+
+**⚠️ Экспериментальная функция!**
+
+**Требования:**
+- `yt-dlp` установлен
+- `ffmpeg` установлен
+- Достаточно места на диске
+- Стабильное интернет-соединение
+
+**Примеры:**
+```bash
+--merge-video # Включить объединение
+```
+
+**Результат:** Видео файл `.mp4` с переводом
+
+---
+
+#### `--keep-original-audio=`
+Сохранить оригинальное аудио при объединении.
+
+**Значения:**
+- `true` - Микшировать оригинал + перевод (слышны оба)
+- `false` - Только перевод (оригинал удалён)
+
+**Примеры:**
+```bash
+--keep-original-audio=true # Оба аудио (по умолчанию)
+--keep-original-audio=false # Только перевод
+```
+
+**По умолчанию:** `true`
+
+**Когда использовать:**
+- `true` - Для обучения языку (слышишь оригинал + перевод)
+- `false` - Для просмотра только с переводом
+
+---
+
+#### `--translation-volume=<число>`
+Установить громкость аудио перевода.
+
+**Диапазон:** `0.0` - `2.0`
+- `0.0` - Беззвучно
+- `1.0` - Нормальная громкость
+- `2.0` - Удвоенная громкость
+
+**Примеры:**
+```bash
+--translation-volume=1.0 # Нормальная (по умолчанию)
+--translation-volume=1.5 # Громче на 50%
+--translation-volume=0.5 # Тише на 50%
+```
+
+**По умолчанию:** `1.0`
+
+---
+
+#### `--original-volume=<число>`
+Установить громкость оригинального аудио.
+
+**Диапазон:** `0.0` - `2.0`
+
+**Примеры:**
+```bash
+--original-volume=0.3 # Тихий оригинал
+--original-volume=1.0 # Нормальная громкость
+--original-volume=0.0 # Беззвучный оригинал
+```
+
+**По умолчанию:** `1.0`
+
+**Полезная комбинация:**
+```bash
+# Громкий перевод, тихий оригинал
+vot-cli-live --output="." --merge-video --original-volume=0.3 --translation-volume=1.5 "URL"
+```
+
+---
+
+### 📝 Аргументы для субтитров:
+
+#### `--subs` или `--subtitles`
+Скачать субтитры вместо аудио.
+
+**Формат:** JSON (по умолчанию)
+
+**Примеры:**
+```bash
+--subs
+--subtitles
+```
+
+**Результат:** Файл `.json` с субтитрами
+
+---
+
+#### `--subs-srt` или `--subtitles-srt`
+Скачать субтитры в формате SRT.
+
+**Формат:** SRT (SubRip)
+
+**Примеры:**
+```bash
+--subs-srt
+--subtitles-srt
+```
+
+**Результат:** Файл `.srt` с субтитрами
+
+**Использование субтитров:**
+```bash
+# JSON формат
+vot-cli-live --subs --output="." --reslang=ru "URL"
+
+# SRT формат
+vot-cli-live --subs-srt --output="." --reslang=ru "URL"
+```
+
+---
+
+### 🌐 Аргументы для прокси:
+
+#### `--proxy=<строка>`
+Установить HTTP или HTTPS прокси.
+
+**Формат:**
+```
+[://]:@[:]
+```
+
+**Примеры:**
+```bash
+--proxy="http://proxy.com:8080"
+--proxy="http://user:pass@proxy.com:8080"
+--proxy="https://user:pass@proxy.com:3128"
+--proxy="socks5://proxy.com:1080"
+```
+
+**Когда использовать:**
+- Яндекс API недоступен в вашей стране
+- Нужно обойти блокировки
+- Корпоративная сеть требует прокси
+
+---
+
+#### `--force-proxy=`
+Не начинать загрузку если прокси не работает.
+
+**Значения:**
+- `true` - Обязательно использовать прокси
+- `false` - Попробовать без прокси если не работает
+
+**Примеры:**
+```bash
+--force-proxy=true # Строго через прокси
+--force-proxy=false # Попробовать без прокси (по умолчанию)
+```
+
+**По умолчанию:** `false`
+
+---
+
+### ℹ️ Опции (флаги):
+
+#### `-h` или `--help`
+Показать справку по использованию.
+
+```bash
+vot-cli-live --help
+vot-cli-live -h
+```
+
+---
+
+#### `-v` или `--version`
+Показать версию скрипта.
+
+```bash
+vot-cli-live --version
+vot-cli-live -v
+```
+
+---
+
+## 💡 Примеры использования
+
+### 1. Базовое использование
+
+#### Скачать перевод с живыми голосами:
+```bash
+vot-cli-live --output="." "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
+```
+
+#### Скачать со стандартным TTS:
+```bash
+vot-cli-live --output="." --voice-style=tts "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
+```
+
+#### Скачать с конкретным именем:
+```bash
+vot-cli-live --output="./downloads" --output-file="my_translation.mp3" "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+### 2. Работа с языками
+
+#### Английское видео → Русский перевод:
+```bash
+vot-cli-live --output="." --lang=en --reslang=ru "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+#### Испанское видео → Английский перевод:
+```bash
+vot-cli-live --output="." --lang=es --reslang=en "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+#### Японское видео → Русский перевод с живыми голосами:
+```bash
+vot-cli-live --output="." --lang=ja --reslang=ru --voice-style=live "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+### 3. Пакетная обработка
+
+#### Скачать переводы для нескольких видео:
+```bash
+vot-cli-live --output="./batch" \
+ "https://www.youtube.com/watch?v=VIDEO_ID_1" \
+ "https://www.youtube.com/watch?v=VIDEO_ID_2" \
+ "https://www.youtube.com/watch?v=VIDEO_ID_3"
+```
+
+#### С разными настройками для каждого:
+```bash
+# Сначала одно видео с live
+vot-cli-live --output="." --voice-style=live "URL1"
+
+# Потом другое с tts
+vot-cli-live --output="." --voice-style=tts "URL2"
+```
+
+---
+
+### 4. Объединение видео с переводом
+
+#### Базовое объединение (оригинал + перевод):
+```bash
+vot-cli-live --output="." --merge-video "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+#### Только перевод (без оригинального аудио):
+```bash
+vot-cli-live --output="." --merge-video --keep-original-audio=false "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+#### С настройкой громкости:
+```bash
+# Тихий оригинал (30%), громкий перевод (150%)
+vot-cli-live --output="." --merge-video \
+ --original-volume=0.3 \
+ --translation-volume=1.5 \
+ "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+#### С конкретным именем файла:
+```bash
+vot-cli-live --output="./videos" \
+ --output-file="translated_video.mp4" \
+ --merge-video \
+ "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+### 5. Работа с субтитрами
+
+#### Скачать субтитры в JSON:
+```bash
+vot-cli-live --subs --output="." --reslang=ru "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+#### Скачать субтитры в SRT:
+```bash
+vot-cli-live --subs-srt --output="." --reslang=ru "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+#### Английские субтитры:
+```bash
+vot-cli-live --subs-srt --output="." --lang=en --reslang=en "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+### 6. Использование прокси
+
+#### С простым прокси:
+```bash
+vot-cli-live --output="." --proxy="http://proxy.com:8080" "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+#### С авторизацией:
+```bash
+vot-cli-live --output="." --proxy="http://user:password@proxy.com:8080" "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+#### Обязательное использование прокси:
+```bash
+vot-cli-live --output="." \
+ --proxy="http://proxy.com:8080" \
+ --force-proxy=true \
+ "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+## 🎭 Живые голоса vs TTS
+
+### Как сравнить?
+
+Скачай одно видео двумя способами:
+
+```bash
+# С живыми голосами
+vot-cli-live --output="./compare" --output-file="live.mp3" --voice-style=live "URL"
+
+# Со стандартным TTS
+vot-cli-live --output="./compare" --output-file="tts.mp3" --voice-style=tts "URL"
+```
+
+Прослушай оба файла и сравни!
+
+### Технические отличия:
+
+**Живые голоса (live):**
+- Используют параметр `useLivelyVoice: true` в API
+- Более сложная обработка на стороне Яндекса
+- Могут генерироваться дольше
+- Файлы могут быть немного больше
+- MD5 суммы отличаются от TTS
+
+**Стандартный TTS (tts):**
+- Классическая технология Text-to-Speech
+- Быстрая генерация
+- Монотонный голос
+- Меньше нагрузка на сервер
+
+### Когда использовать каждый тип:
+
+**Live voices (рекомендуется):**
+- ✅ Для просмотра/прослушивания
+- ✅ Когда важно качество озвучки
+- ✅ Для длинных видео
+- ✅ Для обучающих материалов
+
+**Standard TTS:**
+- ✅ Для быстрого ознакомления
+- ✅ Когда скорость важнее качества
+- ✅ Для тестирования
+- ✅ Если живые голоса недоступны
+
+---
+
+## 🌍 Работа с языками
+
+### Автоопределение языка:
+
+Если не указать `--lang`, Яндекс попытается определить язык автоматически:
+
+```bash
+vot-cli-live --output="." "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### Принудительное указание языка:
+
+Если автоопределение работает неправильно:
+
+```bash
+vot-cli-live --output="." --lang=ja --reslang=ru "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+### Популярные комбинации:
+
+```bash
+# Английский → Русский (самое частое)
+vot-cli-live --output="." --lang=en --reslang=ru "URL"
+
+# Испанский → Английский
+vot-cli-live --output="." --lang=es --reslang=en "URL"
+
+# Китайский → Русский
+vot-cli-live --output="." --lang=zh --reslang=ru "URL"
+
+# Японский → Английский
+vot-cli-live --output="." --lang=ja --reslang=en "URL"
+```
+
+### Ограничения:
+
+⚠️ **Важно:** Не все языковые пары поддерживаются Яндексом!
+
+**Гарантированно работают:**
+- Любой язык → Русский (`ru`)
+- Любой язык → Английский (`en`)
+- Любой язык → Казахский (`kk`)
+
+---
+
+## 🎬 Объединение видео с переводом
+
+### ⚠️ Экспериментальная функция
+
+Эта функция позволяет создать видео файл с встроенным переводом.
+
+### Требования:
+
+1. **yt-dlp** - для скачивания видео:
+ ```bash
+ # Ubuntu/Debian
+ sudo apt install yt-dlp
+
+ # Или через pip
+ pip install yt-dlp
+ ```
+
+2. **ffmpeg** - для объединения:
+ ```bash
+ sudo apt install ffmpeg
+ ```
+
+3. **Место на диске** - видео может быть большим!
+
+### Как это работает:
+
+1. 🎤 Скачивается аудио перевод от Яндекса
+2. 📹 Скачивается оригинальное видео через yt-dlp
+3. 🎬 ffmpeg объединяет видео + аудио
+4. 🗑️ Временные файлы удаляются
+5. ✅ Готовое видео сохраняется
+
+### Режимы работы:
+
+#### Режим 1: Микс (оригинал + перевод)
+```bash
+vot-cli-live --output="." --merge-video "URL"
+```
+
+Результат: Слышны оба аудио дорожки одновременно.
+
+**Плюсы:**
+- ✅ Можно учить язык (слышишь оригинал)
+- ✅ Понимаешь интонацию оригинала
+
+**Минусы:**
+- ❌ Может быть шумно
+- ❌ Сложнее воспринимать
+
+#### Режим 2: Только перевод
+```bash
+vot-cli-live --output="." --merge-video --keep-original-audio=false "URL"
+```
+
+Результат: Только перевод, оригинал удалён.
+
+**Плюсы:**
+- ✅ Чистый звук
+- ✅ Легче воспринимать
+
+**Минусы:**
+- ❌ Не слышишь оригинал
+- ❌ Теряется атмосфера
+
+#### Режим 3: Настройка баланса
+```bash
+vot-cli-live --output="." --merge-video \
+ --original-volume=0.2 \
+ --translation-volume=1.8 \
+ "URL"
+```
+
+Результат: Тихий оригинал на фоне, громкий перевод.
+
+**Идеально для:**
+- ✅ Обучения языку
+- ✅ Сохранения атмосферы
+- ✅ Комфортного просмотра
+
+### Время выполнения:
+
+| Длина видео | Примерное время |
+|-------------|-----------------|
+| 5 минут | ~2-3 минуты |
+| 15 минут | ~5-7 минут |
+| 30 минут | ~10-15 минут |
+| 1 час | ~20-30 минут |
+
+**Зависит от:**
+- Скорости интернета
+- Качества видео
+- Мощности процессора
+
+---
+
+## 📝 Работа с субтитрами
+
+### Форматы субтитров:
+
+#### JSON формат:
+```json
+{
+ "subtitles": [
+ {
+ "text": "Текст субтитра",
+ "startMs": 1000,
+ "durationMs": 2000
+ }
+ ]
+}
+```
+
+#### SRT формат:
+```
+1
+00:00:01,000 --> 00:00:03,000
+Текст субтитра
+
+2
+00:00:03,500 --> 00:00:05,500
+Следующий субтитр
+```
+
+### Примеры:
+
+#### Русские субтитры в SRT:
+```bash
+vot-cli-live --subs-srt --output="./subs" --reslang=ru "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+#### Английские субтитры в JSON:
+```bash
+vot-cli-live --subs --output="./subs" --lang=en --reslang=en "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+#### Переведённые субтитры:
+```bash
+# Английское видео → Русские субтитры
+vot-cli-live --subs-srt --output="." --lang=en --reslang=ru "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+---
+
+## 🌐 Использование прокси
+
+### Зачем нужен прокси?
+
+1. **Географические ограничения** - Яндекс API может быть недоступен в некоторых странах
+2. **Блокировки** - Обход блокировок YouTube или Яндекса
+3. **Корпоративная сеть** - Требования компании
+
+### Типы прокси:
+
+#### HTTP прокси:
+```bash
+vot-cli-live --output="." --proxy="http://proxy.com:8080" "URL"
+```
+
+#### HTTPS прокси:
+```bash
+vot-cli-live --output="." --proxy="https://proxy.com:8080" "URL"
+```
+
+#### SOCKS5 прокси:
+```bash
+vot-cli-live --output="." --proxy="socks5://proxy.com:1080" "URL"
+```
+
+#### С авторизацией:
+```bash
+vot-cli-live --output="." --proxy="http://username:password@proxy.com:8080" "URL"
+```
+
+### Тестирование прокси:
+
+```bash
+# Попробовать с прокси, если не работает - без него
+vot-cli-live --output="." --proxy="http://proxy.com:8080" "URL"
+
+# Строго через прокси (упадёт если прокси не работает)
+vot-cli-live --output="." --proxy="http://proxy.com:8080" --force-proxy=true "URL"
+```
+
+---
+
+## 🔧 Решение проблем
+
+### Проблема: "No links provided"
+
+**Причина:** Ссылка не передана или съедена аргументом.
+
+**Решение:**
+```bash
+# ✅ Правильно
+vot-cli-live --output="." "https://youtube.com/watch?v=ID"
+
+# ❌ Неправильно
+vot-cli-live --output "https://youtube.com/watch?v=ID"
+```
+
+Всегда используй `=` для аргументов со значениями!
+
+---
+
+### Проблема: "yt-dlp не установлен"
+
+**Причина:** Не установлен yt-dlp (нужен для `--merge-video`).
+
+**Решение:**
+```bash
+# Ubuntu/Debian
+sudo apt install yt-dlp
+
+# Или через pip
+pip install yt-dlp
+
+# Проверка
+yt-dlp --version
+```
+
+---
+
+### Проблема: "ffmpeg не установлен"
+
+**Причина:** Не установлен ffmpeg (нужен для `--merge-video`).
+
+**Решение:**
+```bash
+# Ubuntu/Debian
+sudo apt install ffmpeg
+
+# Arch Linux
+sudo pacman -S ffmpeg
+
+# macOS
+brew install ffmpeg
+
+# Проверка
+ffmpeg -version
+```
+
+---
+
+### Проблема: "Failed to request video translation"
+
+**Причины:**
+1. Яндекс API временно недоступен
+2. Видео недоступно для перевода
+3. Проблемы с интернетом
+4. Нужен прокси
+
+**Решение:**
+```bash
+# Попробуй позже
+# Или используй прокси
+vot-cli-live --output="." --proxy="http://proxy.com:8080" "URL"
+```
+
+---
+
+### Проблема: "The translation will take a few minutes"
+
+**Причина:** Перевод ещё генерируется на сервере Яндекса.
+
+**Решение:**
+- ✅ Скрипт автоматически подождёт и повторит запрос
+- ⏳ Обычно занимает 1-3 минуты
+- 🔄 Можно запустить команду повторно через минуту
+
+---
+
+### Проблема: Видео скачивается очень долго (--merge-video)
+
+**Причина:** Большое видео в высоком качестве.
+
+**Решение:**
+- ⏳ Подожди - это нормально
+- 📊 Следи за прогрессом в терминале
+- 💾 Убедись что достаточно места на диске
+
+**Примерное время:**
+- 5 минут видео = ~2-3 минуты скачивания
+- 30 минут видео = ~10-15 минут скачивания
+- 1 час видео = ~20-30 минут скачивания
+
+---
+
+### Проблема: Файлы с одинаковым MD5
+
+**Причина:** Яндекс кеширует переводы.
+
+**Решение:**
+- Это нормально для одного и того же видео
+- Попробуй другое видео для сравнения
+- Или подожди некоторое время
+
+---
+
+### Проблема: "Permission denied"
+
+**Причина:** Нет прав на запись в папку.
+
+**Решение:**
+```bash
+# Используй папку с правами
+vot-cli-live --output="$HOME/downloads" "URL"
+
+# Или дай права
+chmod 755 ./output_folder
+```
+
+---
+
+## ❓ FAQ
+
+### Q: Какая разница между vot-cli и vot-cli-live?
+
+**A:** `vot-cli-live` - это форк с добавленной поддержкой живых голосов Яндекса. По умолчанию использует более качественную озвучку.
+
+---
+
+### Q: Живые голоса работают для всех языков?
+
+**A:** Живые голоса лучше всего работают для русского языка (`--reslang=ru`). Для других языков качество может варьироваться.
+
+---
+
+### Q: Можно ли скачивать видео не с YouTube?
+
+**A:** Да! Поддерживаются:
+- YouTube
+- Vimeo
+- Coursera
+- Udemy
+- Coursehunter
+- И другие (см. `src/config/sites.js`)
+
+---
+
+### Q: Безопасно ли использовать?
+
+**A:** Да, код открытый (MIT лицензия). Можешь проверить исходники на GitHub.
+
+---
+
+### Q: Можно ли использовать коммерчески?
+
+**A:** Да, MIT лицензия позволяет коммерческое использование. Но учти условия использования API Яндекса.
+
+---
+
+### Q: Почему --merge-video экспериментальная функция?
+
+**A:** Потому что:
+- Требует дополнительные зависимости (yt-dlp, ffmpeg)
+- Занимает много времени
+- Может быть нестабильна для некоторых видео
+- Требует много места на диске
+
+---
+
+### Q: Можно ли настроить качество видео при --merge-video?
+
+**A:** Сейчас скачивается лучшее доступное качество. В будущих версиях может появиться параметр для выбора качества.
+
+---
+
+### Q: Сколько стоит использование?
+
+**A:** Бесплатно! Но помни:
+- API Яндекса может иметь лимиты
+- Не злоупотребляй массовыми запросами
+- Используй разумно
+
+---
+
+### Q: Можно ли использовать без интернета?
+
+**A:** Нет, требуется подключение к:
+- API Яндекса (для перевода)
+- YouTube (для скачивания видео при --merge-video)
+
+---
+
+### Q: Как обновить до последней версии?
+
+**A:**
+```bash
+npm update -g vot-cli-live
+```
+
+Или переустановить:
+```bash
+npm uninstall -g vot-cli-live
+npm install -g vot-cli-live
+```
+
+---
+
+### Q: Где хранятся скачанные файлы?
+
+**A:** В папке указанной в `--output`:
+- `--output="."` - текущая папка
+- `--output="/path/to/folder"` - указанная папка
+- Без `--output` - файл не сохраняется, только показывается ссылка
+
+---
+
+### Q: Можно ли скачать только ссылку без файла?
+
+**A:** Да! Просто не указывай `--output`:
+
+```bash
+vot-cli-live "https://www.youtube.com/watch?v=VIDEO_ID"
+```
+
+Скрипт покажет ссылку на аудио файл, которую можно открыть в браузере.
+
+---
+
+### Q: Как внести свой вклад в проект?
+
+**A:**
+1. Форкни репозиторий: https://github.com/fantomcheg/vot-cli-live
+2. Создай ветку с фичей
+3. Сделай изменения
+4. Создай Pull Request
+
+---
+
+### Q: Где сообщить об ошибке?
+
+**A:** Создай Issue: https://github.com/fantomcheg/vot-cli-live/issues
+
+Укажи:
+- Версию: `vot-cli-live --version`
+- Команду которую выполнял
+- Текст ошибки
+- Операционную систему
+
+---
+
+## 🔗 Полезные ссылки
+
+- 📦 **npm пакет:** https://www.npmjs.com/package/vot-cli-live
+- 🐙 **GitHub репозиторий:** https://github.com/fantomcheg/vot-cli-live
+- 🐛 **Сообщить об ошибке:** https://github.com/fantomcheg/vot-cli-live/issues
+- 📖 **Примеры:** [EXAMPLES.md](https://github.com/fantomcheg/vot-cli-live/blob/main/EXAMPLES.md)
+- 🔄 **Оригинальный vot-cli:** https://github.com/FOSWLY/vot-cli
+- 🌐 **Браузерное расширение VOT:** https://github.com/ilyhalight/voice-over-translation
+- 📚 **Библиотека vot.js:** https://github.com/FOSWLY/vot.js
+
+---
+
+## 📊 Технические детали
+
+### Protobuf структура:
+
+Проект использует обновлённую protobuf структуру из [vot.js](https://github.com/FOSWLY/vot.js/blob/main/packages/shared/src/protos/yandex.proto):
+
+**Ключевое поле для живых голосов:**
+```javascript
+useLivelyVoice: boolean // Поле 18 в VideoTranslationRequest
+```
+
+**Другие важные поля:**
+- `unknown3: 2` - обновлено с 1 на 2 (после апреля 2025)
+- `forceSourceLang: boolean` - принудительное указание языка
+- `bypassCache: boolean` - обход кеша
+- `videoTitle: string` - название видео
+
+### API эндпоинты:
+
+```
+POST https://api.browser.yandex.ru/video-translation/translate
+POST https://api.browser.yandex.ru/video-subtitles/get-subtitles
+```
+
+### Заголовки запросов:
+
+```
+Vtrans-Signature:
+Sec-Vtrans-Token:
+```
+
+---
+
+## 🎓 Для разработчиков
+
+### Структура проекта:
+
+```
+vot-cli-live/
+├── src/
+│ ├── index.js # Главный файл
+│ ├── yandexProtobuf.js # Protobuf кодирование/декодирование
+│ ├── yandexRequests.js # API запросы к Яндексу
+│ ├── yandexRawRequest.js # Низкоуровневые запросы
+│ ├── translateVideo.js # Логика перевода видео
+│ ├── download.js # Скачивание файлов
+│ ├── mergeVideo.js # Объединение видео (новое!)
+│ ├── proxy.js # Парсинг прокси
+│ ├── config/ # Конфигурация
+│ │ ├── constants.js # Константы (языки)
+│ │ ├── sites.js # Поддерживаемые сайты
+│ │ └── ...
+│ └── utils/ # Утилиты
+│ ├── getVideoId.js # Извлечение ID видео
+│ ├── getSignature.js # Генерация подписи
+│ ├── getUUID.js # Генерация UUID
+│ └── ...
+├── README.md # Документация (RU)
+├── README-EN.md # Документация (EN)
+├── EXAMPLES.md # Примеры использования
+└── package.json # Метаданные пакета
+```
+
+### Как добавить новый сайт:
+
+1. Открой `src/config/sites.js`
+2. Добавь конфигурацию сайта
+3. Добавь функцию извлечения videoId в `src/utils/getVideoId.js`
+4. Протестируй
+
+### Как изменить параметры API:
+
+1. Открой `src/yandexProtobuf.js`
+2. Измени структуру `VideoTranslationRequest`
+3. Обнови функцию `encodeTranslationRequest`
+4. Протестируй
+
+---
+
+## 📜 История версий
+
+### v1.6.2 (2025-10-10) - Текущая версия
+- ✅ Версия теперь автоматически читается из package.json
+- Исправлено отображение версии в --version
+
+### v1.6.1 (2025-10-10)
+- Попытка исправить версию (промежуточный релиз)
+
+### v1.6.0 (2025-10-10)
+- 🎯 Добавлены умные названия файлов по названию видео
+- Файлы теперь называются как видео (например: `Rick_Astley_-_Never_Gonna_Give_You_Up.mp3`)
+- Добавлен модуль getVideoTitle.js
+- Обновлена документация
+
+### v1.5.3 (2025-10-10)
+- Обновлены все ссылки после переименования репозитория
+- Исправлена обработка имён файлов для --merge-video
+
+### v1.5.2 (2025-10-10)
+- Исправлена обработка имён файлов
+- Улучшена функция объединения видео
+
+### v1.5.1 (2025-10-10)
+- Добавлена функция --merge-video
+- Добавлены параметры громкости
+- Обновлена документация
+
+### v1.5.0 (2025-10-10) - Первый релиз
+- ✨ Добавлена поддержка живых голосов (useLivelyVoice)
+- Добавлен параметр --voice-style
+- Обновлена protobuf структура
+- Живые голоса используются по умолчанию
+
+---
+
+## 🙏 Благодарности
+
+- **FOSWLY** - за оригинальный vot-cli
+- **ilyhalight** - за браузерное расширение VOT
+- **Yandex** - за API перевода
+- **Сообщество** - за тестирование и фидбек
+
+---
+
+## 📄 Лицензия
+
+MIT License - можно использовать свободно, в том числе коммерчески.
+
+Полный текст: [LICENSE](https://github.com/fantomcheg/vot-cli-live/blob/main/LICENSE)
+
+---
+
+## 🌟 Поддержи проект
+
+Если проект помог тебе:
+- ⭐ Поставь звезду на GitHub
+- 📢 Расскажи друзьям
+- 🐛 Сообщи об ошибках
+- 💡 Предложи улучшения
+
+**Спасибо что используешь VOT-CLI Live!** 🎉