commit a8076bc9d06eb0396a10e11dd93c72014ae24207 Author: Andrey K. Choi Date: Sun Sep 28 09:18:03 2025 +0900 init commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b3ea97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.venv/ +.env +downloads/ +__pycache__/ +*.pyc +*.pyo +*.pyd +*.sqlite3 +instance/ +*.log +*.pot +*.mo +*.swp +.DS_Store +.idea/ +.vscode/ diff --git a/.history/.gitignore_20250928091714 b/.history/.gitignore_20250928091714 new file mode 100644 index 0000000..e69de29 diff --git a/.history/.gitignore_20250928091756 b/.history/.gitignore_20250928091756 new file mode 100644 index 0000000..9b3ea97 --- /dev/null +++ b/.history/.gitignore_20250928091756 @@ -0,0 +1,16 @@ +.venv/ +.env +downloads/ +__pycache__/ +*.pyc +*.pyo +*.pyd +*.sqlite3 +instance/ +*.log +*.pot +*.mo +*.swp +.DS_Store +.idea/ +.vscode/ diff --git a/.history/BATCH_GUIDE_20250928091617.md b/.history/BATCH_GUIDE_20250928091617.md new file mode 100644 index 0000000..ab6288a --- /dev/null +++ b/.history/BATCH_GUIDE_20250928091617.md @@ -0,0 +1,125 @@ +# πŸ“‹ Руководство ΠΏΠΎ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ YouTube Downloader + +## 🎯 Бпособы ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + +### 1. Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со списком URL + +Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ тСкстовый Ρ„Π°ΠΉΠ» с URL (ΠΎΠ΄ΠΈΠ½ Π½Π° строку): + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон +python3 utils.py template batch_urls.txt + +# ΠžΡ‚Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ» ΠΈ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ URL +nano batch_urls.txt + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ всС URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch batch_urls.txt --audio-only +``` + +**ΠŸΡ€ΠΈΠΌΠ΅Ρ€ содСрТимого Ρ„Π°ΠΉΠ»Π°:** +``` +# Мои Π»ΡŽΠ±ΠΈΠΌΡ‹Π΅ Π²ΠΈΠ΄Π΅ΠΎ +https://www.youtube.com/watch?v=dQw4w9WgXcQ +https://www.youtube.com/watch?v=VIDEO_ID_2 +https://www.youtube.com/watch?v=VIDEO_ID_3 + +# ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚ (ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ с --playlist) +https://www.youtube.com/playlist?list=PLAYLIST_ID +``` + +### 2. Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL Ρ‡Π΅Ρ€Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΡƒΡŽ строку + +```bash +python3 main.py --urls URL1 URL2 URL3 --quality 720p +``` + +## πŸ›  ΠžΠΏΡ†ΠΈΠΈ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹: +- `--batch FILE` - Π€Π°ΠΉΠ» со списком URL +- `--urls URL1 URL2...` - НСсколько URL Ρ‡Π΅Ρ€Π΅Π· ΠΏΡ€ΠΎΠ±Π΅Π» +- `--continue-on-error` - ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Ρ‚ΡŒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… +- `--quality QUALITY` - ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ для всСх Π²ΠΈΠ΄Π΅ΠΎ +- `--audio-only` - Волько Π°ΡƒΠ΄ΠΈΠΎ для всСх +- `--output DIR` - Папка сохранСния + +### ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ ΠΊΠΎΠΌΠ°Π½Π΄: + +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ· Ρ„Π°ΠΉΠ»Π°, ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Ρ‚ΡŒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… +python3 main.py --batch my_urls.txt --audio-only --continue-on-error + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ нСсколько Π²ΠΈΠ΄Π΅ΠΎ Π² 720p +python3 main.py --urls URL1 URL2 URL3 --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch playlists.txt --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ ΠΏΠ΅Ρ€Π²ΠΎΠΌ URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch urls.txt --info + +# Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΡƒΡŽ ΠΏΠ°ΠΏΠΊΡƒ +python3 main.py --batch urls.txt --output /path/to/folder +``` + +## πŸ“Š ΠžΡ‚Ρ‡Π΅Ρ‚Ρ‹ ΠΎ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ + +ПослС ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ отобраТаСтся: +- βœ… ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ ΡƒΡΠΏΠ΅ΡˆΠ½Ρ‹Ρ… Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- ❌ ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ ошибок +- πŸ“‹ Бписок Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹Ρ… URL (Π΄ΠΎ 5 ΠΏΠ΅Ρ€Π²Ρ‹Ρ…) +- πŸ“ ΠŸΡƒΡ‚ΡŒ ΠΊ сохранСнным Ρ„Π°ΠΉΠ»Π°ΠΌ + +## πŸ”§ Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ для тСстирования + +```bash +make create-batch-template # Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон +make test-batch # ВСст с ΠΏΠΎΠΊΠ°Π·ΠΎΠΌ info +make demo-batch # Π”Π΅ΠΌΠΎ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π°ΡƒΠ΄ΠΈΠΎ +``` + +## πŸ’‘ Π‘ΠΎΠ²Π΅Ρ‚Ρ‹ ΠΈ Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°Ρ†ΠΈΠΈ + +### Π€ΠΎΡ€ΠΌΠ°Ρ‚ Ρ„Π°ΠΉΠ»Π° со списком URL: +- Один URL Π½Π° строку +- Π‘Ρ‚Ρ€ΠΎΠΊΠΈ с `#` - ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ (ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ) +- ΠŸΡƒΡΡ‚Ρ‹Π΅ строки ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ +- UTF-8 ΠΊΠΎΠ΄ΠΈΡ€ΠΎΠ²ΠΊΠ° + +### ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ошибок: +- По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ остановка Π½Π° ΠΏΠ΅Ρ€Π²ΠΎΠΉ ошибкС +- `--continue-on-error` - ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠΈΡ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ Π΄Ρ€ΡƒΠ³ΠΈΡ… URL +- ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½Ρ‹Π΅ сообщСния ΠΎΠ± ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… Π² Π²Ρ‹Π²ΠΎΠ΄Π΅ + +### ΠŸΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: +- Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΏΠΎ ΠΎΠ΄Π½ΠΎΠΌΡƒ URL (ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ) +- АвтоматичСскиС Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠΈ ΠΌΠ΅ΠΆΠ΄Ρƒ запросами +- Fallback стратСгии ΠΏΡ€ΠΈ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ°Ρ… + +## ❗ Π’Π°ΠΆΠ½Ρ‹Π΅ ΠΌΠΎΠΌΠ΅Π½Ρ‚Ρ‹ + +1. **Π›ΠΈΠΌΠΈΡ‚Ρ‹ YouTube**: НС злоупотрСбляйтС ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΎΠΉ +2. **ΠŸΡ€Π°Π²Π° Π°Π²Ρ‚ΠΎΡ€ΠΎΠ²**: Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ авторскиС ΠΏΡ€Π°Π²Π° +3. **ДисковоС пространство**: ΠšΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΠΈΡ€ΡƒΠΉΡ‚Π΅ объСм Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +4. **БСтСвая Π½Π°Π³Ρ€ΡƒΠ·ΠΊΠ°**: Π£Ρ‡ΠΈΡ‚Ρ‹Π²Π°ΠΉΡ‚Π΅ Ρ‚Ρ€Π°Ρ„ΠΈΠΊ ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π° + +## πŸ› Troubleshooting + +**ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΠ°**: НСкоторыС URL Π½Π΅ Π·Π°Π³Ρ€ΡƒΠΆΠ°ΡŽΡ‚ΡΡ +```bash +# РСшСниС: Π²ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ΅Π½ΠΈΠ΅ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… +python3 main.py --batch urls.txt --continue-on-error +``` + +**ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΠ°**: МСдлСнная Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° +```bash +# РСшСниС: ΠΏΠΎΠΏΡ€ΠΎΠ±ΠΎΠ²Π°Ρ‚ΡŒ Π±ΠΎΠ»Π΅Π΅ Π½ΠΈΠ·ΠΊΠΎΠ΅ качСство +python3 main.py --batch urls.txt --quality 480p +``` + +**ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΠ°**: Ошибки 403 +```bash +# РСшСниС: ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ автоматичСски ΠΏΡ€ΠΈΠΌΠ΅Π½ΠΈΡ‚ fallback стратСгии +# ΠŸΡ€ΠΎΡΡ‚ΠΎ Π΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ΡΡŒ ΠΏΠΎΠ²Ρ‚ΠΎΡ€Π½Ρ‹Ρ… ΠΏΠΎΠΏΡ‹Ρ‚ΠΎΠΊ +``` \ No newline at end of file diff --git a/.history/BATCH_GUIDE_20250928091648.md b/.history/BATCH_GUIDE_20250928091648.md new file mode 100644 index 0000000..ab6288a --- /dev/null +++ b/.history/BATCH_GUIDE_20250928091648.md @@ -0,0 +1,125 @@ +# πŸ“‹ Руководство ΠΏΠΎ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ YouTube Downloader + +## 🎯 Бпособы ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + +### 1. Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со списком URL + +Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ тСкстовый Ρ„Π°ΠΉΠ» с URL (ΠΎΠ΄ΠΈΠ½ Π½Π° строку): + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон +python3 utils.py template batch_urls.txt + +# ΠžΡ‚Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ» ΠΈ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ URL +nano batch_urls.txt + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ всС URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch batch_urls.txt --audio-only +``` + +**ΠŸΡ€ΠΈΠΌΠ΅Ρ€ содСрТимого Ρ„Π°ΠΉΠ»Π°:** +``` +# Мои Π»ΡŽΠ±ΠΈΠΌΡ‹Π΅ Π²ΠΈΠ΄Π΅ΠΎ +https://www.youtube.com/watch?v=dQw4w9WgXcQ +https://www.youtube.com/watch?v=VIDEO_ID_2 +https://www.youtube.com/watch?v=VIDEO_ID_3 + +# ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚ (ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ с --playlist) +https://www.youtube.com/playlist?list=PLAYLIST_ID +``` + +### 2. Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL Ρ‡Π΅Ρ€Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΡƒΡŽ строку + +```bash +python3 main.py --urls URL1 URL2 URL3 --quality 720p +``` + +## πŸ›  ΠžΠΏΡ†ΠΈΠΈ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹: +- `--batch FILE` - Π€Π°ΠΉΠ» со списком URL +- `--urls URL1 URL2...` - НСсколько URL Ρ‡Π΅Ρ€Π΅Π· ΠΏΡ€ΠΎΠ±Π΅Π» +- `--continue-on-error` - ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Ρ‚ΡŒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… +- `--quality QUALITY` - ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ для всСх Π²ΠΈΠ΄Π΅ΠΎ +- `--audio-only` - Волько Π°ΡƒΠ΄ΠΈΠΎ для всСх +- `--output DIR` - Папка сохранСния + +### ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ ΠΊΠΎΠΌΠ°Π½Π΄: + +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ· Ρ„Π°ΠΉΠ»Π°, ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Ρ‚ΡŒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… +python3 main.py --batch my_urls.txt --audio-only --continue-on-error + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ нСсколько Π²ΠΈΠ΄Π΅ΠΎ Π² 720p +python3 main.py --urls URL1 URL2 URL3 --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch playlists.txt --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ ΠΏΠ΅Ρ€Π²ΠΎΠΌ URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch urls.txt --info + +# Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΡƒΡŽ ΠΏΠ°ΠΏΠΊΡƒ +python3 main.py --batch urls.txt --output /path/to/folder +``` + +## πŸ“Š ΠžΡ‚Ρ‡Π΅Ρ‚Ρ‹ ΠΎ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ + +ПослС ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ отобраТаСтся: +- βœ… ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ ΡƒΡΠΏΠ΅ΡˆΠ½Ρ‹Ρ… Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- ❌ ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ ошибок +- πŸ“‹ Бписок Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹Ρ… URL (Π΄ΠΎ 5 ΠΏΠ΅Ρ€Π²Ρ‹Ρ…) +- πŸ“ ΠŸΡƒΡ‚ΡŒ ΠΊ сохранСнным Ρ„Π°ΠΉΠ»Π°ΠΌ + +## πŸ”§ Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ для тСстирования + +```bash +make create-batch-template # Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон +make test-batch # ВСст с ΠΏΠΎΠΊΠ°Π·ΠΎΠΌ info +make demo-batch # Π”Π΅ΠΌΠΎ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π°ΡƒΠ΄ΠΈΠΎ +``` + +## πŸ’‘ Π‘ΠΎΠ²Π΅Ρ‚Ρ‹ ΠΈ Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°Ρ†ΠΈΠΈ + +### Π€ΠΎΡ€ΠΌΠ°Ρ‚ Ρ„Π°ΠΉΠ»Π° со списком URL: +- Один URL Π½Π° строку +- Π‘Ρ‚Ρ€ΠΎΠΊΠΈ с `#` - ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ (ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ) +- ΠŸΡƒΡΡ‚Ρ‹Π΅ строки ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ +- UTF-8 ΠΊΠΎΠ΄ΠΈΡ€ΠΎΠ²ΠΊΠ° + +### ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ошибок: +- По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ остановка Π½Π° ΠΏΠ΅Ρ€Π²ΠΎΠΉ ошибкС +- `--continue-on-error` - ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠΈΡ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ Π΄Ρ€ΡƒΠ³ΠΈΡ… URL +- ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½Ρ‹Π΅ сообщСния ΠΎΠ± ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… Π² Π²Ρ‹Π²ΠΎΠ΄Π΅ + +### ΠŸΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: +- Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΏΠΎ ΠΎΠ΄Π½ΠΎΠΌΡƒ URL (ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ) +- АвтоматичСскиС Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠΈ ΠΌΠ΅ΠΆΠ΄Ρƒ запросами +- Fallback стратСгии ΠΏΡ€ΠΈ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ°Ρ… + +## ❗ Π’Π°ΠΆΠ½Ρ‹Π΅ ΠΌΠΎΠΌΠ΅Π½Ρ‚Ρ‹ + +1. **Π›ΠΈΠΌΠΈΡ‚Ρ‹ YouTube**: НС злоупотрСбляйтС ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΎΠΉ +2. **ΠŸΡ€Π°Π²Π° Π°Π²Ρ‚ΠΎΡ€ΠΎΠ²**: Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ авторскиС ΠΏΡ€Π°Π²Π° +3. **ДисковоС пространство**: ΠšΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΠΈΡ€ΡƒΠΉΡ‚Π΅ объСм Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +4. **БСтСвая Π½Π°Π³Ρ€ΡƒΠ·ΠΊΠ°**: Π£Ρ‡ΠΈΡ‚Ρ‹Π²Π°ΠΉΡ‚Π΅ Ρ‚Ρ€Π°Ρ„ΠΈΠΊ ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π° + +## πŸ› Troubleshooting + +**ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΠ°**: НСкоторыС URL Π½Π΅ Π·Π°Π³Ρ€ΡƒΠΆΠ°ΡŽΡ‚ΡΡ +```bash +# РСшСниС: Π²ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ΅Π½ΠΈΠ΅ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… +python3 main.py --batch urls.txt --continue-on-error +``` + +**ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΠ°**: МСдлСнная Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° +```bash +# РСшСниС: ΠΏΠΎΠΏΡ€ΠΎΠ±ΠΎΠ²Π°Ρ‚ΡŒ Π±ΠΎΠ»Π΅Π΅ Π½ΠΈΠ·ΠΊΠΎΠ΅ качСство +python3 main.py --batch urls.txt --quality 480p +``` + +**ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΠ°**: Ошибки 403 +```bash +# РСшСниС: ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ автоматичСски ΠΏΡ€ΠΈΠΌΠ΅Π½ΠΈΡ‚ fallback стратСгии +# ΠŸΡ€ΠΎΡΡ‚ΠΎ Π΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ΡΡŒ ΠΏΠΎΠ²Ρ‚ΠΎΡ€Π½Ρ‹Ρ… ΠΏΠΎΠΏΡ‹Ρ‚ΠΎΠΊ +``` \ No newline at end of file diff --git a/.history/Makefile_20250928084550 b/.history/Makefile_20250928084550 new file mode 100644 index 0000000..2e588c6 --- /dev/null +++ b/.history/Makefile_20250928084550 @@ -0,0 +1,58 @@ +# Makefile for YouTube Downloader + +.PHONY: install run-examples help clean test config + +# Установка зависимостСй +install: + pip3 install -r requirements.txt + +# Запуск ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠ² +run-examples: + python3 examples.py + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку +help: + python3 main.py --help + +# ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ² +clean: + find . -type f -name "*.pyc" -delete + find . -type d -name "__pycache__" -delete + rm -f config.json + +# ВСстированиС URL Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ +test: + python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation test:', d.validate_url('https://www.youtube.com/watch?v=dQw4w9WgXcQ'))" + +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +config: + python3 main.py configure --output-dir downloads --video-quality best --audio-format mp3 + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ +show-config: + python3 main.py show-config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ тСстовоС Π²ΠΈΠ΄Π΅ΠΎ (информация) +demo-info: + python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +demo-formats: + python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --formats + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +all: install config run-examples + +# Π‘ΠΏΡ€Π°Π²ΠΊΠ° ΠΏΠΎ Makefile +usage: + @echo "ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" + @echo " install - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ зависимости" + @echo " run-examples - Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + @echo " help - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ΅" + @echo " config - ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ" + @echo " show-config - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ" + @echo " test - ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL" + @echo " demo-info - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ" + @echo " demo-formats - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ" + @echo " clean - ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹" + @echo " all - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ всС ΠΈ Π·Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" \ No newline at end of file diff --git a/.history/Makefile_20250928085321 b/.history/Makefile_20250928085321 new file mode 100644 index 0000000..2e588c6 --- /dev/null +++ b/.history/Makefile_20250928085321 @@ -0,0 +1,58 @@ +# Makefile for YouTube Downloader + +.PHONY: install run-examples help clean test config + +# Установка зависимостСй +install: + pip3 install -r requirements.txt + +# Запуск ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠ² +run-examples: + python3 examples.py + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку +help: + python3 main.py --help + +# ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ² +clean: + find . -type f -name "*.pyc" -delete + find . -type d -name "__pycache__" -delete + rm -f config.json + +# ВСстированиС URL Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ +test: + python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation test:', d.validate_url('https://www.youtube.com/watch?v=dQw4w9WgXcQ'))" + +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +config: + python3 main.py configure --output-dir downloads --video-quality best --audio-format mp3 + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ +show-config: + python3 main.py show-config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ тСстовоС Π²ΠΈΠ΄Π΅ΠΎ (информация) +demo-info: + python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +demo-formats: + python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --formats + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +all: install config run-examples + +# Π‘ΠΏΡ€Π°Π²ΠΊΠ° ΠΏΠΎ Makefile +usage: + @echo "ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" + @echo " install - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ зависимости" + @echo " run-examples - Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + @echo " help - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ΅" + @echo " config - ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ" + @echo " show-config - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ" + @echo " test - ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL" + @echo " demo-info - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ" + @echo " demo-formats - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ" + @echo " clean - ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹" + @echo " all - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ всС ΠΈ Π·Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" \ No newline at end of file diff --git a/.history/Makefile_20250928085725 b/.history/Makefile_20250928085725 new file mode 100644 index 0000000..7df21c3 --- /dev/null +++ b/.history/Makefile_20250928085725 @@ -0,0 +1,65 @@ +# Makefile for YouTube Downloader + +.PHONY: install run-examples help clean test config update-ytdlp + +# Установка зависимостСй +install: + pip3 install -r requirements.txt + +# ОбновлСниС yt-dlp Π΄ΠΎ послСднСй вСрсии +update-ytdlp: + pip3 install --upgrade yt-dlp + +# Установка с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ +install-fresh: update-ytdlp install + +# Запуск ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠ² +run-examples: + python3 examples.py + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку +help: + python3 main.py --help + +# ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ² +clean: + find . -type f -name "*.pyc" -delete + find . -type d -name "__pycache__" -delete + rm -f config.json + +# ВСстированиС URL Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ +test: + python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation test:', d.validate_url('https://www.youtube.com/watch?v=dQw4w9WgXcQ'))" + +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +config: + python3 main.py configure --output-dir downloads --video-quality best --audio-format mp3 + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ +show-config: + python3 main.py show-config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ тСстовоС Π²ΠΈΠ΄Π΅ΠΎ (информация) +demo-info: + python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +demo-formats: + python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --formats + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +all: install config run-examples + +# Π‘ΠΏΡ€Π°Π²ΠΊΠ° ΠΏΠΎ Makefile +usage: + @echo "ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" + @echo " install - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ зависимости" + @echo " run-examples - Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + @echo " help - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ΅" + @echo " config - ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ" + @echo " show-config - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ" + @echo " test - ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL" + @echo " demo-info - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ" + @echo " demo-formats - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ" + @echo " clean - ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹" + @echo " all - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ всС ΠΈ Π·Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" \ No newline at end of file diff --git a/.history/Makefile_20250928090451 b/.history/Makefile_20250928090451 new file mode 100644 index 0000000..6d0db94 --- /dev/null +++ b/.history/Makefile_20250928090451 @@ -0,0 +1,72 @@ +# Makefile for YouTube Downloader + +.PHONY: install run-examples help clean test config update-ytdlp + +# Установка зависимостСй +install: + pip3 install -r requirements.txt + +# ОбновлСниС yt-dlp Π΄ΠΎ послСднСй вСрсии +update-ytdlp: + pip3 install --upgrade yt-dlp + +# Установка с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ +install-fresh: update-ytdlp install + +# Запуск ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠ² +run-examples: + python3 examples.py + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку +help: + python3 main.py --help + +# ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ² +clean: + find . -type f -name "*.pyc" -delete + find . -type d -name "__pycache__" -delete + rm -f config.json + +# ВСстированиС URL Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ +test: + python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation test:', d.validate_url('https://www.youtube.com/watch?v=dQw4w9WgXcQ'))" + +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +config: + python3 main.py configure --output-dir downloads --video-quality best --audio-format mp3 + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ +show-config: + python3 main.py show-config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ тСстовоС Π²ΠΈΠ΄Π΅ΠΎ (информация) +demo-info: + python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +demo-formats: + python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --formats + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +all: install config run-examples + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +usage: + @echo "ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" + @echo " install - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ зависимости" + @echo " install-fresh - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp" + @echo " update-ytdlp - ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии" + @echo " run-examples - Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + @echo " help - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ΅" + @echo " config - ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ" + @echo " show-config - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ" + @echo " test - ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL" + @echo " demo-info - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ" + @echo " demo-formats - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ" + @echo " clean - ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹" + @echo " install-ffmpeg - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ" + @echo " all - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ всС ΠΈ Π·Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + +# Установка ffmpeg +install-ffmpeg: + ./install_ffmpeg.sh \ No newline at end of file diff --git a/.history/Makefile_20250928090643 b/.history/Makefile_20250928090643 new file mode 100644 index 0000000..6d0db94 --- /dev/null +++ b/.history/Makefile_20250928090643 @@ -0,0 +1,72 @@ +# Makefile for YouTube Downloader + +.PHONY: install run-examples help clean test config update-ytdlp + +# Установка зависимостСй +install: + pip3 install -r requirements.txt + +# ОбновлСниС yt-dlp Π΄ΠΎ послСднСй вСрсии +update-ytdlp: + pip3 install --upgrade yt-dlp + +# Установка с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ +install-fresh: update-ytdlp install + +# Запуск ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠ² +run-examples: + python3 examples.py + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку +help: + python3 main.py --help + +# ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ² +clean: + find . -type f -name "*.pyc" -delete + find . -type d -name "__pycache__" -delete + rm -f config.json + +# ВСстированиС URL Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ +test: + python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation test:', d.validate_url('https://www.youtube.com/watch?v=dQw4w9WgXcQ'))" + +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +config: + python3 main.py configure --output-dir downloads --video-quality best --audio-format mp3 + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ +show-config: + python3 main.py show-config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ тСстовоС Π²ΠΈΠ΄Π΅ΠΎ (информация) +demo-info: + python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +demo-formats: + python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --formats + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +all: install config run-examples + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +usage: + @echo "ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" + @echo " install - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ зависимости" + @echo " install-fresh - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp" + @echo " update-ytdlp - ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии" + @echo " run-examples - Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + @echo " help - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ΅" + @echo " config - ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ" + @echo " show-config - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ" + @echo " test - ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL" + @echo " demo-info - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ" + @echo " demo-formats - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ" + @echo " clean - ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹" + @echo " install-ffmpeg - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ" + @echo " all - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ всС ΠΈ Π·Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + +# Установка ffmpeg +install-ffmpeg: + ./install_ffmpeg.sh \ No newline at end of file diff --git a/.history/Makefile_20250928090919 b/.history/Makefile_20250928090919 new file mode 100644 index 0000000..5f97dee --- /dev/null +++ b/.history/Makefile_20250928090919 @@ -0,0 +1,76 @@ +# Makefile for YouTube Downloader + +.PHONY: install run-examples help clean test config update-ytdlp + +# Установка зависимостСй +install: + pip3 install -r requirements.txt + +# ОбновлСниС yt-dlp Π΄ΠΎ послСднСй вСрсии +update-ytdlp: + pip3 install --upgrade yt-dlp + +# Установка с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ +install-fresh: update-ytdlp install + +# Запуск ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠ² +run-examples: + python3 examples.py + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку +help: + python3 main.py --help + +# ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ² +clean: + find . -type f -name "*.pyc" -delete + find . -type d -name "__pycache__" -delete + rm -f config.json + +# ВСстированиС URL Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ +test: + python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation test:', d.validate_url('https://www.youtube.com/watch?v=dQw4w9WgXcQ'))" + +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +config: + python3 main.py configure --output-dir downloads --video-quality best --audio-format mp3 + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ +show-config: + python3 main.py show-config + +# ВСстированиС ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +test-batch: + python3 main.py --batch batch_urls.txt --info + +# Π”Π΅ΠΌΠΎ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +demo-batch: + python3 main.py --batch batch_urls.txt --audio-only --continue-on-error + +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +create-batch-template: + python3 utils.py template batch_template.txt + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +all: install config run-examples + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +usage: + @echo "ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" + @echo " install - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ зависимости" + @echo " install-fresh - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp" + @echo " update-ytdlp - ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии" + @echo " run-examples - Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + @echo " help - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ΅" + @echo " config - ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ" + @echo " show-config - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ" + @echo " test - ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL" + @echo " demo-info - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ" + @echo " demo-formats - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ" + @echo " clean - ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹" + @echo " install-ffmpeg - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ" + @echo " all - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ всС ΠΈ Π·Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + +# Установка ffmpeg +install-ffmpeg: + ./install_ffmpeg.sh \ No newline at end of file diff --git a/.history/Makefile_20250928090936 b/.history/Makefile_20250928090936 new file mode 100644 index 0000000..1d2745d --- /dev/null +++ b/.history/Makefile_20250928090936 @@ -0,0 +1,77 @@ +# Makefile for YouTube Downloader + +.PHONY: install run-examples help clean test config update-ytdlp + +# Установка зависимостСй +install: + pip3 install -r requirements.txt + +# ОбновлСниС yt-dlp Π΄ΠΎ послСднСй вСрсии +update-ytdlp: + pip3 install --upgrade yt-dlp + +# Установка с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ +install-fresh: update-ytdlp install + +# Запуск ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠ² +run-examples: + python3 examples.py + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку +help: + python3 main.py --help + +# ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ² +clean: + find . -type f -name "*.pyc" -delete + find . -type d -name "__pycache__" -delete + rm -f config.json + +# ВСстированиС URL Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ +test: + python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation test:', d.validate_url('https://www.youtube.com/watch?v=dQw4w9WgXcQ'))" + +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +config: + python3 main.py configure --output-dir downloads --video-quality best --audio-format mp3 + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ +show-config: + python3 main.py show-config + +# ВСстированиС ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +test-batch: + python3 main.py --batch batch_urls.txt --info + +# Π”Π΅ΠΌΠΎ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +demo-batch: + python3 main.py --batch batch_urls.txt --audio-only --continue-on-error + +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +create-batch-template: + python3 utils.py template batch_template.txt + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +all: install config run-examples + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +usage: + @echo "ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" + @echo " install - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ зависимости" + @echo " install-fresh - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp" + @echo " update-ytdlp - ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии" + @echo " run-examples - Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + @echo " help - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ΅" + @echo " config - ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ" + @echo " show-config - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ" + @echo " test - ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL" + @echo " test-batch - ВСст ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ info)" + @echo " demo-batch - Π”Π΅ΠΌΠΎ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π°ΡƒΠ΄ΠΈΠΎ" + @echo " create-batch-template - Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ" + @echo " clean - ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹" + @echo " install-ffmpeg - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ" + @echo " all - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ всС ΠΈ Π·Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + +# Установка ffmpeg +install-ffmpeg: + ./install_ffmpeg.sh \ No newline at end of file diff --git a/.history/Makefile_20250928091647 b/.history/Makefile_20250928091647 new file mode 100644 index 0000000..1d2745d --- /dev/null +++ b/.history/Makefile_20250928091647 @@ -0,0 +1,77 @@ +# Makefile for YouTube Downloader + +.PHONY: install run-examples help clean test config update-ytdlp + +# Установка зависимостСй +install: + pip3 install -r requirements.txt + +# ОбновлСниС yt-dlp Π΄ΠΎ послСднСй вСрсии +update-ytdlp: + pip3 install --upgrade yt-dlp + +# Установка с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ +install-fresh: update-ytdlp install + +# Запуск ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠ² +run-examples: + python3 examples.py + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку +help: + python3 main.py --help + +# ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ² +clean: + find . -type f -name "*.pyc" -delete + find . -type d -name "__pycache__" -delete + rm -f config.json + +# ВСстированиС URL Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ +test: + python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation test:', d.validate_url('https://www.youtube.com/watch?v=dQw4w9WgXcQ'))" + +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +config: + python3 main.py configure --output-dir downloads --video-quality best --audio-format mp3 + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ +show-config: + python3 main.py show-config + +# ВСстированиС ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +test-batch: + python3 main.py --batch batch_urls.txt --info + +# Π”Π΅ΠΌΠΎ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +demo-batch: + python3 main.py --batch batch_urls.txt --audio-only --continue-on-error + +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +create-batch-template: + python3 utils.py template batch_template.txt + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +all: install config run-examples + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +usage: + @echo "ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" + @echo " install - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ зависимости" + @echo " install-fresh - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp" + @echo " update-ytdlp - ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии" + @echo " run-examples - Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + @echo " help - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ΅" + @echo " config - ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ" + @echo " show-config - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ" + @echo " test - ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL" + @echo " test-batch - ВСст ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ info)" + @echo " demo-batch - Π”Π΅ΠΌΠΎ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π°ΡƒΠ΄ΠΈΠΎ" + @echo " create-batch-template - Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ" + @echo " clean - ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹" + @echo " install-ffmpeg - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ" + @echo " all - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ всС ΠΈ Π·Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + +# Установка ffmpeg +install-ffmpeg: + ./install_ffmpeg.sh \ No newline at end of file diff --git a/.history/README_20250928083717.md b/.history/README_20250928083717.md new file mode 100644 index 0000000..a859682 --- /dev/null +++ b/.history/README_20250928083717.md @@ -0,0 +1,64 @@ +# YouTube Downloader + +ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube с использованиСм Python ΠΈ yt-dlp. + +## Установка + +1. ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ: +```bash +git clone +cd youtube_downloader +``` + +2. УстановитС зависимости: +```bash +pip install -r requirements.txt +``` + +## ИспользованиС + +### Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ ΠΎΠ΄Π½ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: +```bash +python main.py https://www.youtube.com/watch?v=VIDEO_ID +``` + +### Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ с ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½Ρ‹ΠΌ качСством: +```bash +python main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p +``` + +### Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ: +```bash +python main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only +``` + +### Π£ΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: +```bash +python main.py https://www.youtube.com/watch?v=VIDEO_ID --output downloads/ +``` + +## ВозмоТности + +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Π²ΠΈΠ΄Π΅ΠΎ Π² Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡΡ… +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +- βœ… ΠŸΡ€ΠΎΠ³Ρ€Π΅ΡΡ-Π±Π°Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +- βœ… НастраиваСмая ΠΏΠ°ΠΏΠΊΠ° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° плСйлистов +- βœ… Валидация URL + +## ВрСбования + +- Python 3.7+ +- Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС +- ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ) + +## Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +youtube_downloader/ +β”œβ”€β”€ main.py # Π’ΠΎΡ‡ΠΊΠ° Π²Ρ…ΠΎΠ΄Π° Π² ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ +β”œβ”€β”€ downloader.py # Основной класс Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° +β”œβ”€β”€ config.py # ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ прилоТСния +β”œβ”€β”€ requirements.txt # Зависимости Python +└── README.md # ДокумСнтация +``` \ No newline at end of file diff --git a/.history/README_20250928085030.md b/.history/README_20250928085030.md new file mode 100644 index 0000000..bbd2137 --- /dev/null +++ b/.history/README_20250928085030.md @@ -0,0 +1,215 @@ +# YouTube Downloader + +ΠœΠΎΡ‰Π½ΠΎΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube с использованиСм Python ΠΈ yt-dlp. + +## πŸš€ ВозмоТности + +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Π²ΠΈΠ΄Π΅ΠΎ Π² Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡΡ… (360p, 480p, 720p, 1080p, best) +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ MP3/AAC +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° плСйлистов +- βœ… ΠŸΡ€ΠΎΠ³Ρ€Π΅ΡΡ-Π±Π°Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Ρ†Π²Π΅Ρ‚Π½Ρ‹ΠΌ Π²Ρ‹Π²ΠΎΠ΄ΠΎΠΌ +- βœ… НастраиваСмая конфигурация +- βœ… Валидация URL +- βœ… ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +- βœ… Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- βœ… CLI интСрфСйс с мноТСством ΠΎΠΏΡ†ΠΈΠΉ +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° субтитров + +## πŸ“¦ Установка + +1. ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ: +```bash +git clone +cd youtube_downloader +``` + +2. УстановитС зависимости: +```bash +make install +# ΠΈΠ»ΠΈ +pip3 install -r requirements.txt +``` + +## 🎯 Быстрый старт + +```bash +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ +make config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist +``` + +## πŸ“‹ ИспользованиС CLI + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹: +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π£ΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --output downloads/ + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --formats +``` + +### ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ: +```bash +# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +python3 main.py configure --output-dir downloads --video-quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +python3 main.py show-config +``` + +## πŸ”§ Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 utils.py template + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 utils.py batch --file urls.txt --quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py history + +# ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py clean +``` + +## πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ + +```bash +make install # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ зависимости +make config # ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +make show-config # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +make test # ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL +make demo-info # Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ +make demo-formats # Π€ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +make run-examples # Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ +make clean # ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ +make usage # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ Makefile +``` + +## πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +youtube_downloader/ +β”œβ”€β”€ main.py # ОсновноС CLI ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ +β”œβ”€β”€ downloader.py # Класс Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° YouTube +β”œβ”€β”€ config.py # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ +β”œβ”€β”€ utils.py # Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ +β”œβ”€β”€ examples.py # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования +β”œβ”€β”€ requirements.txt # Зависимости Python +β”œβ”€β”€ Makefile # Автоматизация Π·Π°Π΄Π°Ρ‡ +β”œβ”€β”€ README.md # ДокумСнтация +β”œβ”€β”€ config.json # Π€Π°ΠΉΠ» ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ (создаСтся автоматичСски) +└── urls.txt # Π¨Π°Π±Π»ΠΎΠ½ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +``` + +## βš™ ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ + +Настройки ΡΠΎΡ…Ρ€Π°Π½ΡΡŽΡ‚ΡΡ Π² `config.json`: + +```json +{ + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": true, + "add_metadata": true, + "subtitle_languages": ["ru", "en"], + "download_subtitles": false +} +``` + +## πŸ“ ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + +### Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС Π² ΠΊΠΎΠ΄Π΅: +```python +from downloader import YouTubeDownloader +from config import Config + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° +config = Config() +downloader = YouTubeDownloader(config) + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ +success = downloader.download_video( + url="https://www.youtube.com/watch?v=VIDEO_ID", + quality="720p", + audio_only=False +) +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```python +from utils import BatchDownloader + +batch = BatchDownloader() +batch.download_from_file("urls.txt", quality="best", audio_only=False) +``` + +## 🚨 ВрСбования + +- Python 3.7+ +- Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС +- ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ) + +## πŸ” Troubleshooting + +### Ошибки yt-dlp +Если Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‚ ошибки с yt-dlp, ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚Π΅ Π΄ΠΎ послСднСй вСрсии: +```bash +pip3 install --upgrade yt-dlp +``` + +### ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с ffmpeg +УстановитС ffmpeg: +```bash +# Ubuntu/Debian +sudo apt install ffmpeg + +# macOS +brew install ffmpeg + +# Windows +# Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/ +``` + +## πŸ“„ ЛицСнзия + +ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ создан для ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ†Π΅Π»Π΅ΠΉ. Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ условия использования YouTube. + +## 🀝 Π’ΠΊΠ»Π°Π΄ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ + +1. Π€ΠΎΡ€ΠΊΠ½ΠΈΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ +2. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ Π²Π΅Ρ‚ΠΊΡƒ для Π½ΠΎΠ²ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ +3. Π‘Π΄Π΅Π»Π°ΠΉΡ‚Π΅ измСнСния +4. ΠžΡ‚ΠΏΡ€Π°Π²ΡŒΡ‚Π΅ Pull Request + +--- + +**Автор:** GitHub Copilot +**ВСрсия:** 1.0.0 \ No newline at end of file diff --git a/.history/README_20250928085321.md b/.history/README_20250928085321.md new file mode 100644 index 0000000..bbd2137 --- /dev/null +++ b/.history/README_20250928085321.md @@ -0,0 +1,215 @@ +# YouTube Downloader + +ΠœΠΎΡ‰Π½ΠΎΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube с использованиСм Python ΠΈ yt-dlp. + +## πŸš€ ВозмоТности + +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Π²ΠΈΠ΄Π΅ΠΎ Π² Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡΡ… (360p, 480p, 720p, 1080p, best) +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ MP3/AAC +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° плСйлистов +- βœ… ΠŸΡ€ΠΎΠ³Ρ€Π΅ΡΡ-Π±Π°Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Ρ†Π²Π΅Ρ‚Π½Ρ‹ΠΌ Π²Ρ‹Π²ΠΎΠ΄ΠΎΠΌ +- βœ… НастраиваСмая конфигурация +- βœ… Валидация URL +- βœ… ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +- βœ… Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- βœ… CLI интСрфСйс с мноТСством ΠΎΠΏΡ†ΠΈΠΉ +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° субтитров + +## πŸ“¦ Установка + +1. ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ: +```bash +git clone +cd youtube_downloader +``` + +2. УстановитС зависимости: +```bash +make install +# ΠΈΠ»ΠΈ +pip3 install -r requirements.txt +``` + +## 🎯 Быстрый старт + +```bash +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ +make config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist +``` + +## πŸ“‹ ИспользованиС CLI + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹: +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π£ΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --output downloads/ + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --formats +``` + +### ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ: +```bash +# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +python3 main.py configure --output-dir downloads --video-quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +python3 main.py show-config +``` + +## πŸ”§ Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 utils.py template + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 utils.py batch --file urls.txt --quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py history + +# ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py clean +``` + +## πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ + +```bash +make install # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ зависимости +make config # ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +make show-config # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +make test # ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL +make demo-info # Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ +make demo-formats # Π€ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +make run-examples # Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ +make clean # ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ +make usage # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ Makefile +``` + +## πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +youtube_downloader/ +β”œβ”€β”€ main.py # ОсновноС CLI ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ +β”œβ”€β”€ downloader.py # Класс Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° YouTube +β”œβ”€β”€ config.py # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ +β”œβ”€β”€ utils.py # Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ +β”œβ”€β”€ examples.py # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования +β”œβ”€β”€ requirements.txt # Зависимости Python +β”œβ”€β”€ Makefile # Автоматизация Π·Π°Π΄Π°Ρ‡ +β”œβ”€β”€ README.md # ДокумСнтация +β”œβ”€β”€ config.json # Π€Π°ΠΉΠ» ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ (создаСтся автоматичСски) +└── urls.txt # Π¨Π°Π±Π»ΠΎΠ½ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +``` + +## βš™ ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ + +Настройки ΡΠΎΡ…Ρ€Π°Π½ΡΡŽΡ‚ΡΡ Π² `config.json`: + +```json +{ + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": true, + "add_metadata": true, + "subtitle_languages": ["ru", "en"], + "download_subtitles": false +} +``` + +## πŸ“ ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + +### Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС Π² ΠΊΠΎΠ΄Π΅: +```python +from downloader import YouTubeDownloader +from config import Config + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° +config = Config() +downloader = YouTubeDownloader(config) + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ +success = downloader.download_video( + url="https://www.youtube.com/watch?v=VIDEO_ID", + quality="720p", + audio_only=False +) +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```python +from utils import BatchDownloader + +batch = BatchDownloader() +batch.download_from_file("urls.txt", quality="best", audio_only=False) +``` + +## 🚨 ВрСбования + +- Python 3.7+ +- Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС +- ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ) + +## πŸ” Troubleshooting + +### Ошибки yt-dlp +Если Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‚ ошибки с yt-dlp, ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚Π΅ Π΄ΠΎ послСднСй вСрсии: +```bash +pip3 install --upgrade yt-dlp +``` + +### ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с ffmpeg +УстановитС ffmpeg: +```bash +# Ubuntu/Debian +sudo apt install ffmpeg + +# macOS +brew install ffmpeg + +# Windows +# Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/ +``` + +## πŸ“„ ЛицСнзия + +ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ создан для ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ†Π΅Π»Π΅ΠΉ. Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ условия использования YouTube. + +## 🀝 Π’ΠΊΠ»Π°Π΄ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ + +1. Π€ΠΎΡ€ΠΊΠ½ΠΈΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ +2. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ Π²Π΅Ρ‚ΠΊΡƒ для Π½ΠΎΠ²ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ +3. Π‘Π΄Π΅Π»Π°ΠΉΡ‚Π΅ измСнСния +4. ΠžΡ‚ΠΏΡ€Π°Π²ΡŒΡ‚Π΅ Pull Request + +--- + +**Автор:** GitHub Copilot +**ВСрсия:** 1.0.0 \ No newline at end of file diff --git a/.history/README_20250928090503.md b/.history/README_20250928090503.md new file mode 100644 index 0000000..8c4da3b --- /dev/null +++ b/.history/README_20250928090503.md @@ -0,0 +1,224 @@ +# YouTube Downloader + +ΠœΠΎΡ‰Π½ΠΎΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube с использованиСм Python ΠΈ yt-dlp. + +## πŸš€ ВозмоТности + +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Π²ΠΈΠ΄Π΅ΠΎ Π² Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡΡ… (360p, 480p, 720p, 1080p, best) +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ MP3/AAC +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° плСйлистов +- βœ… ΠŸΡ€ΠΎΠ³Ρ€Π΅ΡΡ-Π±Π°Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Ρ†Π²Π΅Ρ‚Π½Ρ‹ΠΌ Π²Ρ‹Π²ΠΎΠ΄ΠΎΠΌ +- βœ… НастраиваСмая конфигурация +- βœ… Валидация URL +- βœ… ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +- βœ… Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- βœ… CLI интСрфСйс с мноТСством ΠΎΠΏΡ†ΠΈΠΉ +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° субтитров + +## πŸ“¦ Установка + +1. ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ: +```bash +git clone +cd youtube_downloader +``` + +2. УстановитС зависимости: +```bash +make install-fresh +# ΠΈΠ»ΠΈ +pip3 install -r requirements.txt +``` + +3. УстановитС ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ): +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: скачайтС с https://ffmpeg.org/ +``` + +## 🎯 Быстрый старт + +```bash +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ +make config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist +``` + +## πŸ“‹ ИспользованиС CLI + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹: +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π£ΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --output downloads/ + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --formats +``` + +### ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ: +```bash +# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +python3 main.py configure --output-dir downloads --video-quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +python3 main.py show-config +``` + +## πŸ”§ Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 utils.py template + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 utils.py batch --file urls.txt --quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py history + +# ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py clean +``` + +## πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ + +```bash +make install # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ зависимости +make config # ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +make show-config # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +make test # ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL +make demo-info # Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ +make demo-formats # Π€ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +make run-examples # Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ +make clean # ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ +make usage # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ Makefile +``` + +## πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +youtube_downloader/ +β”œβ”€β”€ main.py # ОсновноС CLI ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ +β”œβ”€β”€ downloader.py # Класс Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° YouTube +β”œβ”€β”€ config.py # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ +β”œβ”€β”€ utils.py # Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ +β”œβ”€β”€ examples.py # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования +β”œβ”€β”€ requirements.txt # Зависимости Python +β”œβ”€β”€ Makefile # Автоматизация Π·Π°Π΄Π°Ρ‡ +β”œβ”€β”€ README.md # ДокумСнтация +β”œβ”€β”€ config.json # Π€Π°ΠΉΠ» ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ (создаСтся автоматичСски) +└── urls.txt # Π¨Π°Π±Π»ΠΎΠ½ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +``` + +## βš™ ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ + +Настройки ΡΠΎΡ…Ρ€Π°Π½ΡΡŽΡ‚ΡΡ Π² `config.json`: + +```json +{ + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": true, + "add_metadata": true, + "subtitle_languages": ["ru", "en"], + "download_subtitles": false +} +``` + +## πŸ“ ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + +### Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС Π² ΠΊΠΎΠ΄Π΅: +```python +from downloader import YouTubeDownloader +from config import Config + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° +config = Config() +downloader = YouTubeDownloader(config) + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ +success = downloader.download_video( + url="https://www.youtube.com/watch?v=VIDEO_ID", + quality="720p", + audio_only=False +) +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```python +from utils import BatchDownloader + +batch = BatchDownloader() +batch.download_from_file("urls.txt", quality="best", audio_only=False) +``` + +## 🚨 ВрСбования + +- Python 3.7+ +- Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС +- ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ) + +## πŸ” Troubleshooting + +### Ошибки yt-dlp +Если Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‚ ошибки с yt-dlp, ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚Π΅ Π΄ΠΎ послСднСй вСрсии: +```bash +pip3 install --upgrade yt-dlp +``` + +### ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с ffmpeg +УстановитС ffmpeg: +```bash +# Ubuntu/Debian +sudo apt install ffmpeg + +# macOS +brew install ffmpeg + +# Windows +# Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/ +``` + +## πŸ“„ ЛицСнзия + +ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ создан для ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ†Π΅Π»Π΅ΠΉ. Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ условия использования YouTube. + +## 🀝 Π’ΠΊΠ»Π°Π΄ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ + +1. Π€ΠΎΡ€ΠΊΠ½ΠΈΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ +2. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ Π²Π΅Ρ‚ΠΊΡƒ для Π½ΠΎΠ²ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ +3. Π‘Π΄Π΅Π»Π°ΠΉΡ‚Π΅ измСнСния +4. ΠžΡ‚ΠΏΡ€Π°Π²ΡŒΡ‚Π΅ Pull Request + +--- + +**Автор:** GitHub Copilot +**ВСрсия:** 1.0.0 \ No newline at end of file diff --git a/.history/README_20250928090513.md b/.history/README_20250928090513.md new file mode 100644 index 0000000..d6a62a2 --- /dev/null +++ b/.history/README_20250928090513.md @@ -0,0 +1,226 @@ +# YouTube Downloader + +ΠœΠΎΡ‰Π½ΠΎΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube с использованиСм Python ΠΈ yt-dlp. + +## πŸš€ ВозмоТности + +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Π²ΠΈΠ΄Π΅ΠΎ Π² Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡΡ… (360p, 480p, 720p, 1080p, best) +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ MP3/AAC +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° плСйлистов +- βœ… ΠŸΡ€ΠΎΠ³Ρ€Π΅ΡΡ-Π±Π°Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Ρ†Π²Π΅Ρ‚Π½Ρ‹ΠΌ Π²Ρ‹Π²ΠΎΠ΄ΠΎΠΌ +- βœ… НастраиваСмая конфигурация +- βœ… Валидация URL +- βœ… ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +- βœ… Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- βœ… CLI интСрфСйс с мноТСством ΠΎΠΏΡ†ΠΈΠΉ +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° субтитров + +## πŸ“¦ Установка + +1. ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ: +```bash +git clone +cd youtube_downloader +``` + +2. УстановитС зависимости: +```bash +make install-fresh +# ΠΈΠ»ΠΈ +pip3 install -r requirements.txt +``` + +3. УстановитС ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ): +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: скачайтС с https://ffmpeg.org/ +``` + +## 🎯 Быстрый старт + +```bash +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ +make config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist +``` + +## πŸ“‹ ИспользованиС CLI + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹: +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π£ΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --output downloads/ + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --formats +``` + +### ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ: +```bash +# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +python3 main.py configure --output-dir downloads --video-quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +python3 main.py show-config +``` + +## πŸ”§ Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 utils.py template + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 utils.py batch --file urls.txt --quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py history + +# ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py clean +``` + +## πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ + +```bash +make install-fresh # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp +make install-ffmpeg # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ +make config # ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +make show-config # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +make update-ytdlp # ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии +make test # ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL +make demo-info # Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ +make demo-formats # Π€ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +make run-examples # Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ +make clean # ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ +make usage # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ Makefile +``` + +## πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +youtube_downloader/ +β”œβ”€β”€ main.py # ОсновноС CLI ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ +β”œβ”€β”€ downloader.py # Класс Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° YouTube +β”œβ”€β”€ config.py # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ +β”œβ”€β”€ utils.py # Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ +β”œβ”€β”€ examples.py # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования +β”œβ”€β”€ requirements.txt # Зависимости Python +β”œβ”€β”€ Makefile # Автоматизация Π·Π°Π΄Π°Ρ‡ +β”œβ”€β”€ README.md # ДокумСнтация +β”œβ”€β”€ config.json # Π€Π°ΠΉΠ» ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ (создаСтся автоматичСски) +└── urls.txt # Π¨Π°Π±Π»ΠΎΠ½ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +``` + +## βš™ ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ + +Настройки ΡΠΎΡ…Ρ€Π°Π½ΡΡŽΡ‚ΡΡ Π² `config.json`: + +```json +{ + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": true, + "add_metadata": true, + "subtitle_languages": ["ru", "en"], + "download_subtitles": false +} +``` + +## πŸ“ ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + +### Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС Π² ΠΊΠΎΠ΄Π΅: +```python +from downloader import YouTubeDownloader +from config import Config + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° +config = Config() +downloader = YouTubeDownloader(config) + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ +success = downloader.download_video( + url="https://www.youtube.com/watch?v=VIDEO_ID", + quality="720p", + audio_only=False +) +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```python +from utils import BatchDownloader + +batch = BatchDownloader() +batch.download_from_file("urls.txt", quality="best", audio_only=False) +``` + +## 🚨 ВрСбования + +- Python 3.7+ +- Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС +- ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ) + +## πŸ” Troubleshooting + +### Ошибки yt-dlp +Если Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‚ ошибки с yt-dlp, ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚Π΅ Π΄ΠΎ послСднСй вСрсии: +```bash +pip3 install --upgrade yt-dlp +``` + +### ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с ffmpeg +УстановитС ffmpeg: +```bash +# Ubuntu/Debian +sudo apt install ffmpeg + +# macOS +brew install ffmpeg + +# Windows +# Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/ +``` + +## πŸ“„ ЛицСнзия + +ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ создан для ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ†Π΅Π»Π΅ΠΉ. Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ условия использования YouTube. + +## 🀝 Π’ΠΊΠ»Π°Π΄ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ + +1. Π€ΠΎΡ€ΠΊΠ½ΠΈΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ +2. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ Π²Π΅Ρ‚ΠΊΡƒ для Π½ΠΎΠ²ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ +3. Π‘Π΄Π΅Π»Π°ΠΉΡ‚Π΅ измСнСния +4. ΠžΡ‚ΠΏΡ€Π°Π²ΡŒΡ‚Π΅ Pull Request + +--- + +**Автор:** GitHub Copilot +**ВСрсия:** 1.0.0 \ No newline at end of file diff --git a/.history/README_20250928090529.md b/.history/README_20250928090529.md new file mode 100644 index 0000000..e36309f --- /dev/null +++ b/.history/README_20250928090529.md @@ -0,0 +1,237 @@ +# YouTube Downloader + +ΠœΠΎΡ‰Π½ΠΎΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube с использованиСм Python ΠΈ yt-dlp. + +## πŸš€ ВозмоТности + +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Π²ΠΈΠ΄Π΅ΠΎ Π² Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡΡ… (360p, 480p, 720p, 1080p, best) +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ MP3/AAC +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° плСйлистов +- βœ… ΠŸΡ€ΠΎΠ³Ρ€Π΅ΡΡ-Π±Π°Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Ρ†Π²Π΅Ρ‚Π½Ρ‹ΠΌ Π²Ρ‹Π²ΠΎΠ΄ΠΎΠΌ +- βœ… НастраиваСмая конфигурация +- βœ… Валидация URL +- βœ… ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +- βœ… Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- βœ… CLI интСрфСйс с мноТСством ΠΎΠΏΡ†ΠΈΠΉ +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° субтитров + +## πŸ“¦ Установка + +1. ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ: +```bash +git clone +cd youtube_downloader +``` + +2. УстановитС зависимости: +```bash +make install-fresh +# ΠΈΠ»ΠΈ +pip3 install -r requirements.txt +``` + +3. УстановитС ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ): +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: скачайтС с https://ffmpeg.org/ +``` + +## 🎯 Быстрый старт + +```bash +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ +make config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist +``` + +## πŸ“‹ ИспользованиС CLI + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹: +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π£ΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --output downloads/ + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --formats +``` + +### ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ: +```bash +# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +python3 main.py configure --output-dir downloads --video-quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +python3 main.py show-config +``` + +## πŸ”§ Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 utils.py template + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 utils.py batch --file urls.txt --quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py history + +# ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py clean +``` + +## πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ + +```bash +make install-fresh # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp +make install-ffmpeg # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ +make config # ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +make show-config # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +make update-ytdlp # ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии +make test # ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL +make demo-info # Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ +make demo-formats # Π€ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +make run-examples # Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ +make clean # ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ +make usage # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ Makefile +``` + +## πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +youtube_downloader/ +β”œβ”€β”€ main.py # ОсновноС CLI ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ +β”œβ”€β”€ downloader.py # Класс Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° YouTube +β”œβ”€β”€ config.py # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ +β”œβ”€β”€ utils.py # Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ +β”œβ”€β”€ examples.py # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования +β”œβ”€β”€ requirements.txt # Зависимости Python +β”œβ”€β”€ Makefile # Автоматизация Π·Π°Π΄Π°Ρ‡ +β”œβ”€β”€ README.md # ДокумСнтация +β”œβ”€β”€ config.json # Π€Π°ΠΉΠ» ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ (создаСтся автоматичСски) +└── urls.txt # Π¨Π°Π±Π»ΠΎΠ½ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +``` + +## βš™ ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ + +Настройки ΡΠΎΡ…Ρ€Π°Π½ΡΡŽΡ‚ΡΡ Π² `config.json`: + +```json +{ + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": true, + "add_metadata": true, + "subtitle_languages": ["ru", "en"], + "download_subtitles": false +} +``` + +## πŸ“ ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + +### Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС Π² ΠΊΠΎΠ΄Π΅: +```python +from downloader import YouTubeDownloader +from config import Config + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° +config = Config() +downloader = YouTubeDownloader(config) + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ +success = downloader.download_video( + url="https://www.youtube.com/watch?v=VIDEO_ID", + quality="720p", + audio_only=False +) +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```python +from utils import BatchDownloader + +batch = BatchDownloader() +batch.download_from_file("urls.txt", quality="best", audio_only=False) +``` + +## 🚨 ВрСбования + +- Python 3.7+ +- Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС +- ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ) + +## πŸ” Troubleshooting + +### Ошибки yt-dlp +Если Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‚ ошибки с yt-dlp, ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚Π΅ Π΄ΠΎ послСднСй вСрсии: +```bash +make update-ytdlp +# ΠΈΠ»ΠΈ +pip3 install --upgrade yt-dlp +``` + +### HTTP Error 403: Forbidden +ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ автоматичСски ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ нСсколько стратСгий ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ: +- ΠΠ»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ User-Agent Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ +- ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½Ρ‹Π΅ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ с Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠ°ΠΌΠΈ +- Π Π°Π·Π»ΠΈΡ‡Π½Ρ‹Π΅ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +- Fallback настройки + +### ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с ffmpeg +УстановитС ffmpeg для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ: +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/ +``` + +### МСдлСнная Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° +- ΠŸΠΎΠΏΡ€ΠΎΠ±ΡƒΠΉΡ‚Π΅ ΡΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² Π±ΠΎΠ»Π΅Π΅ Π½ΠΈΠ·ΠΊΠΎΠΌ качСствС: `--quality 480p` +- Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ: `--audio-only` +- ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС + +## πŸ“„ ЛицСнзия + +ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ создан для ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ†Π΅Π»Π΅ΠΉ. Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ условия использования YouTube. + +## 🀝 Π’ΠΊΠ»Π°Π΄ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ + +1. Π€ΠΎΡ€ΠΊΠ½ΠΈΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ +2. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ Π²Π΅Ρ‚ΠΊΡƒ для Π½ΠΎΠ²ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ +3. Π‘Π΄Π΅Π»Π°ΠΉΡ‚Π΅ измСнСния +4. ΠžΡ‚ΠΏΡ€Π°Π²ΡŒΡ‚Π΅ Pull Request + +--- + +**Автор:** GitHub Copilot +**ВСрсия:** 1.0.0 \ No newline at end of file diff --git a/.history/README_20250928090643.md b/.history/README_20250928090643.md new file mode 100644 index 0000000..e36309f --- /dev/null +++ b/.history/README_20250928090643.md @@ -0,0 +1,237 @@ +# YouTube Downloader + +ΠœΠΎΡ‰Π½ΠΎΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube с использованиСм Python ΠΈ yt-dlp. + +## πŸš€ ВозмоТности + +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Π²ΠΈΠ΄Π΅ΠΎ Π² Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡΡ… (360p, 480p, 720p, 1080p, best) +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ MP3/AAC +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° плСйлистов +- βœ… ΠŸΡ€ΠΎΠ³Ρ€Π΅ΡΡ-Π±Π°Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Ρ†Π²Π΅Ρ‚Π½Ρ‹ΠΌ Π²Ρ‹Π²ΠΎΠ΄ΠΎΠΌ +- βœ… НастраиваСмая конфигурация +- βœ… Валидация URL +- βœ… ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +- βœ… Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- βœ… CLI интСрфСйс с мноТСством ΠΎΠΏΡ†ΠΈΠΉ +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° субтитров + +## πŸ“¦ Установка + +1. ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ: +```bash +git clone +cd youtube_downloader +``` + +2. УстановитС зависимости: +```bash +make install-fresh +# ΠΈΠ»ΠΈ +pip3 install -r requirements.txt +``` + +3. УстановитС ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ): +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: скачайтС с https://ffmpeg.org/ +``` + +## 🎯 Быстрый старт + +```bash +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ +make config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist +``` + +## πŸ“‹ ИспользованиС CLI + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹: +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π£ΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --output downloads/ + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --formats +``` + +### ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ: +```bash +# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +python3 main.py configure --output-dir downloads --video-quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +python3 main.py show-config +``` + +## πŸ”§ Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 utils.py template + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 utils.py batch --file urls.txt --quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py history + +# ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py clean +``` + +## πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ + +```bash +make install-fresh # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp +make install-ffmpeg # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ +make config # ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +make show-config # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +make update-ytdlp # ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии +make test # ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL +make demo-info # Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ +make demo-formats # Π€ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +make run-examples # Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ +make clean # ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ +make usage # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ Makefile +``` + +## πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +youtube_downloader/ +β”œβ”€β”€ main.py # ОсновноС CLI ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ +β”œβ”€β”€ downloader.py # Класс Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° YouTube +β”œβ”€β”€ config.py # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ +β”œβ”€β”€ utils.py # Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ +β”œβ”€β”€ examples.py # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования +β”œβ”€β”€ requirements.txt # Зависимости Python +β”œβ”€β”€ Makefile # Автоматизация Π·Π°Π΄Π°Ρ‡ +β”œβ”€β”€ README.md # ДокумСнтация +β”œβ”€β”€ config.json # Π€Π°ΠΉΠ» ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ (создаСтся автоматичСски) +└── urls.txt # Π¨Π°Π±Π»ΠΎΠ½ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +``` + +## βš™ ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ + +Настройки ΡΠΎΡ…Ρ€Π°Π½ΡΡŽΡ‚ΡΡ Π² `config.json`: + +```json +{ + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": true, + "add_metadata": true, + "subtitle_languages": ["ru", "en"], + "download_subtitles": false +} +``` + +## πŸ“ ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + +### Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС Π² ΠΊΠΎΠ΄Π΅: +```python +from downloader import YouTubeDownloader +from config import Config + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° +config = Config() +downloader = YouTubeDownloader(config) + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ +success = downloader.download_video( + url="https://www.youtube.com/watch?v=VIDEO_ID", + quality="720p", + audio_only=False +) +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```python +from utils import BatchDownloader + +batch = BatchDownloader() +batch.download_from_file("urls.txt", quality="best", audio_only=False) +``` + +## 🚨 ВрСбования + +- Python 3.7+ +- Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС +- ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ) + +## πŸ” Troubleshooting + +### Ошибки yt-dlp +Если Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‚ ошибки с yt-dlp, ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚Π΅ Π΄ΠΎ послСднСй вСрсии: +```bash +make update-ytdlp +# ΠΈΠ»ΠΈ +pip3 install --upgrade yt-dlp +``` + +### HTTP Error 403: Forbidden +ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ автоматичСски ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ нСсколько стратСгий ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ: +- ΠΠ»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ User-Agent Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ +- ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½Ρ‹Π΅ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ с Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠ°ΠΌΠΈ +- Π Π°Π·Π»ΠΈΡ‡Π½Ρ‹Π΅ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +- Fallback настройки + +### ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с ffmpeg +УстановитС ffmpeg для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ: +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/ +``` + +### МСдлСнная Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° +- ΠŸΠΎΠΏΡ€ΠΎΠ±ΡƒΠΉΡ‚Π΅ ΡΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² Π±ΠΎΠ»Π΅Π΅ Π½ΠΈΠ·ΠΊΠΎΠΌ качСствС: `--quality 480p` +- Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ: `--audio-only` +- ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС + +## πŸ“„ ЛицСнзия + +ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ создан для ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ†Π΅Π»Π΅ΠΉ. Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ условия использования YouTube. + +## 🀝 Π’ΠΊΠ»Π°Π΄ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ + +1. Π€ΠΎΡ€ΠΊΠ½ΠΈΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ +2. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ Π²Π΅Ρ‚ΠΊΡƒ для Π½ΠΎΠ²ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ +3. Π‘Π΄Π΅Π»Π°ΠΉΡ‚Π΅ измСнСния +4. ΠžΡ‚ΠΏΡ€Π°Π²ΡŒΡ‚Π΅ Pull Request + +--- + +**Автор:** GitHub Copilot +**ВСрсия:** 1.0.0 \ No newline at end of file diff --git a/.history/README_20250928091227.md b/.history/README_20250928091227.md new file mode 100644 index 0000000..37b5de4 --- /dev/null +++ b/.history/README_20250928091227.md @@ -0,0 +1,243 @@ +# YouTube Downloader + +ΠœΠΎΡ‰Π½ΠΎΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube с использованиСм Python ΠΈ yt-dlp. + +## πŸš€ ВозмоТности + +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Π²ΠΈΠ΄Π΅ΠΎ Π² Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡΡ… (360p, 480p, 720p, 1080p, best) +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ MP3/AAC +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° плСйлистов +- βœ… ΠŸΡ€ΠΎΠ³Ρ€Π΅ΡΡ-Π±Π°Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Ρ†Π²Π΅Ρ‚Π½Ρ‹ΠΌ Π²Ρ‹Π²ΠΎΠ΄ΠΎΠΌ +- βœ… НастраиваСмая конфигурация +- βœ… Валидация URL +- βœ… ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +- βœ… Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- βœ… CLI интСрфСйс с мноТСством ΠΎΠΏΡ†ΠΈΠΉ +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° субтитров + +## πŸ“¦ Установка + +1. ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ: +```bash +git clone +cd youtube_downloader +``` + +2. УстановитС зависимости: +```bash +make install-fresh +# ΠΈΠ»ΠΈ +pip3 install -r requirements.txt +``` + +3. УстановитС ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ): +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: скачайтС с https://ffmpeg.org/ +``` + +## 🎯 Быстрый старт + +```bash +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ +make config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch batch_urls.txt --audio-only + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL +python3 main.py --urls URL1 URL2 URL3 --quality 720p +``` + +## πŸ“‹ ИспользованиС CLI + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹: +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π£ΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --output downloads/ + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --formats +``` + +### ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ: +```bash +# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +python3 main.py configure --output-dir downloads --video-quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +python3 main.py show-config +``` + +## πŸ”§ Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 utils.py template + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 utils.py batch --file urls.txt --quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py history + +# ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py clean +``` + +## πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ + +```bash +make install-fresh # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp +make install-ffmpeg # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ +make config # ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +make show-config # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +make update-ytdlp # ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии +make test # ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL +make demo-info # Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ +make demo-formats # Π€ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +make run-examples # Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ +make clean # ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ +make usage # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ Makefile +``` + +## πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +youtube_downloader/ +β”œβ”€β”€ main.py # ОсновноС CLI ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ +β”œβ”€β”€ downloader.py # Класс Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° YouTube +β”œβ”€β”€ config.py # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ +β”œβ”€β”€ utils.py # Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ +β”œβ”€β”€ examples.py # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования +β”œβ”€β”€ requirements.txt # Зависимости Python +β”œβ”€β”€ Makefile # Автоматизация Π·Π°Π΄Π°Ρ‡ +β”œβ”€β”€ README.md # ДокумСнтация +β”œβ”€β”€ config.json # Π€Π°ΠΉΠ» ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ (создаСтся автоматичСски) +└── urls.txt # Π¨Π°Π±Π»ΠΎΠ½ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +``` + +## βš™ ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ + +Настройки ΡΠΎΡ…Ρ€Π°Π½ΡΡŽΡ‚ΡΡ Π² `config.json`: + +```json +{ + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": true, + "add_metadata": true, + "subtitle_languages": ["ru", "en"], + "download_subtitles": false +} +``` + +## πŸ“ ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + +### Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС Π² ΠΊΠΎΠ΄Π΅: +```python +from downloader import YouTubeDownloader +from config import Config + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° +config = Config() +downloader = YouTubeDownloader(config) + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ +success = downloader.download_video( + url="https://www.youtube.com/watch?v=VIDEO_ID", + quality="720p", + audio_only=False +) +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```python +from utils import BatchDownloader + +batch = BatchDownloader() +batch.download_from_file("urls.txt", quality="best", audio_only=False) +``` + +## 🚨 ВрСбования + +- Python 3.7+ +- Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС +- ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ) + +## πŸ” Troubleshooting + +### Ошибки yt-dlp +Если Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‚ ошибки с yt-dlp, ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚Π΅ Π΄ΠΎ послСднСй вСрсии: +```bash +make update-ytdlp +# ΠΈΠ»ΠΈ +pip3 install --upgrade yt-dlp +``` + +### HTTP Error 403: Forbidden +ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ автоматичСски ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ нСсколько стратСгий ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ: +- ΠΠ»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ User-Agent Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ +- ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½Ρ‹Π΅ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ с Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠ°ΠΌΠΈ +- Π Π°Π·Π»ΠΈΡ‡Π½Ρ‹Π΅ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +- Fallback настройки + +### ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с ffmpeg +УстановитС ffmpeg для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ: +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/ +``` + +### МСдлСнная Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° +- ΠŸΠΎΠΏΡ€ΠΎΠ±ΡƒΠΉΡ‚Π΅ ΡΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² Π±ΠΎΠ»Π΅Π΅ Π½ΠΈΠ·ΠΊΠΎΠΌ качСствС: `--quality 480p` +- Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ: `--audio-only` +- ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС + +## πŸ“„ ЛицСнзия + +ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ создан для ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ†Π΅Π»Π΅ΠΉ. Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ условия использования YouTube. + +## 🀝 Π’ΠΊΠ»Π°Π΄ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ + +1. Π€ΠΎΡ€ΠΊΠ½ΠΈΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ +2. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ Π²Π΅Ρ‚ΠΊΡƒ для Π½ΠΎΠ²ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ +3. Π‘Π΄Π΅Π»Π°ΠΉΡ‚Π΅ измСнСния +4. ΠžΡ‚ΠΏΡ€Π°Π²ΡŒΡ‚Π΅ Pull Request + +--- + +**Автор:** GitHub Copilot +**ВСрсия:** 1.0.0 \ No newline at end of file diff --git a/.history/README_20250928091244.md b/.history/README_20250928091244.md new file mode 100644 index 0000000..c14bffd --- /dev/null +++ b/.history/README_20250928091244.md @@ -0,0 +1,258 @@ +# YouTube Downloader + +ΠœΠΎΡ‰Π½ΠΎΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube с использованиСм Python ΠΈ yt-dlp. + +## πŸš€ ВозмоТности + +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Π²ΠΈΠ΄Π΅ΠΎ Π² Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡΡ… (360p, 480p, 720p, 1080p, best) +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ MP3/AAC +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° плСйлистов +- βœ… ΠŸΡ€ΠΎΠ³Ρ€Π΅ΡΡ-Π±Π°Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Ρ†Π²Π΅Ρ‚Π½Ρ‹ΠΌ Π²Ρ‹Π²ΠΎΠ΄ΠΎΠΌ +- βœ… НастраиваСмая конфигурация +- βœ… Валидация URL +- βœ… ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +- βœ… Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- βœ… CLI интСрфСйс с мноТСством ΠΎΠΏΡ†ΠΈΠΉ +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° субтитров + +## πŸ“¦ Установка + +1. ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ: +```bash +git clone +cd youtube_downloader +``` + +2. УстановитС зависимости: +```bash +make install-fresh +# ΠΈΠ»ΠΈ +pip3 install -r requirements.txt +``` + +3. УстановитС ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ): +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: скачайтС с https://ffmpeg.org/ +``` + +## 🎯 Быстрый старт + +```bash +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ +make config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch batch_urls.txt --audio-only + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL +python3 main.py --urls URL1 URL2 URL3 --quality 720p +``` + +## πŸ“‹ ИспользованиС CLI + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹: +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π£ΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --output downloads/ + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --formats +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон Ρ„Π°ΠΉΠ»Π° со списком URL +make create-batch-template + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со списком URL +python3 main.py --batch urls.txt --quality 720p --continue-on-error + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL Ρ‡Π΅Ρ€Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΡƒΡŽ строку +python3 main.py --urls URL1 URL2 URL3 --audio-only + +# ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠΈΡ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… +python3 main.py --batch urls.txt --continue-on-error +``` + +### ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ: +```bash +# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +python3 main.py configure --output-dir downloads --video-quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +python3 main.py show-config +``` + +## πŸ”§ Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 utils.py template + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 utils.py batch --file urls.txt --quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py history + +# ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py clean +``` + +## πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ + +```bash +make install-fresh # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp +make install-ffmpeg # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ +make config # ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +make show-config # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +make update-ytdlp # ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии +make test # ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL +make demo-info # Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ +make demo-formats # Π€ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +make run-examples # Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ +make clean # ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ +make usage # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ Makefile +``` + +## πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +youtube_downloader/ +β”œβ”€β”€ main.py # ОсновноС CLI ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ +β”œβ”€β”€ downloader.py # Класс Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° YouTube +β”œβ”€β”€ config.py # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ +β”œβ”€β”€ utils.py # Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ +β”œβ”€β”€ examples.py # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования +β”œβ”€β”€ requirements.txt # Зависимости Python +β”œβ”€β”€ Makefile # Автоматизация Π·Π°Π΄Π°Ρ‡ +β”œβ”€β”€ README.md # ДокумСнтация +β”œβ”€β”€ config.json # Π€Π°ΠΉΠ» ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ (создаСтся автоматичСски) +└── urls.txt # Π¨Π°Π±Π»ΠΎΠ½ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +``` + +## βš™ ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ + +Настройки ΡΠΎΡ…Ρ€Π°Π½ΡΡŽΡ‚ΡΡ Π² `config.json`: + +```json +{ + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": true, + "add_metadata": true, + "subtitle_languages": ["ru", "en"], + "download_subtitles": false +} +``` + +## πŸ“ ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + +### Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС Π² ΠΊΠΎΠ΄Π΅: +```python +from downloader import YouTubeDownloader +from config import Config + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° +config = Config() +downloader = YouTubeDownloader(config) + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ +success = downloader.download_video( + url="https://www.youtube.com/watch?v=VIDEO_ID", + quality="720p", + audio_only=False +) +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```python +from utils import BatchDownloader + +batch = BatchDownloader() +batch.download_from_file("urls.txt", quality="best", audio_only=False) +``` + +## 🚨 ВрСбования + +- Python 3.7+ +- Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС +- ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ) + +## πŸ” Troubleshooting + +### Ошибки yt-dlp +Если Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‚ ошибки с yt-dlp, ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚Π΅ Π΄ΠΎ послСднСй вСрсии: +```bash +make update-ytdlp +# ΠΈΠ»ΠΈ +pip3 install --upgrade yt-dlp +``` + +### HTTP Error 403: Forbidden +ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ автоматичСски ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ нСсколько стратСгий ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ: +- ΠΠ»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ User-Agent Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ +- ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½Ρ‹Π΅ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ с Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠ°ΠΌΠΈ +- Π Π°Π·Π»ΠΈΡ‡Π½Ρ‹Π΅ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +- Fallback настройки + +### ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с ffmpeg +УстановитС ffmpeg для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ: +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/ +``` + +### МСдлСнная Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° +- ΠŸΠΎΠΏΡ€ΠΎΠ±ΡƒΠΉΡ‚Π΅ ΡΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² Π±ΠΎΠ»Π΅Π΅ Π½ΠΈΠ·ΠΊΠΎΠΌ качСствС: `--quality 480p` +- Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ: `--audio-only` +- ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС + +## πŸ“„ ЛицСнзия + +ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ создан для ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ†Π΅Π»Π΅ΠΉ. Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ условия использования YouTube. + +## 🀝 Π’ΠΊΠ»Π°Π΄ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ + +1. Π€ΠΎΡ€ΠΊΠ½ΠΈΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ +2. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ Π²Π΅Ρ‚ΠΊΡƒ для Π½ΠΎΠ²ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ +3. Π‘Π΄Π΅Π»Π°ΠΉΡ‚Π΅ измСнСния +4. ΠžΡ‚ΠΏΡ€Π°Π²ΡŒΡ‚Π΅ Pull Request + +--- + +**Автор:** GitHub Copilot +**ВСрсия:** 1.0.0 \ No newline at end of file diff --git a/.history/README_20250928091258.md b/.history/README_20250928091258.md new file mode 100644 index 0000000..6f4ca60 --- /dev/null +++ b/.history/README_20250928091258.md @@ -0,0 +1,261 @@ +# YouTube Downloader + +ΠœΠΎΡ‰Π½ΠΎΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube с использованиСм Python ΠΈ yt-dlp. + +## πŸš€ ВозмоТности + +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Π²ΠΈΠ΄Π΅ΠΎ Π² Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡΡ… (360p, 480p, 720p, 1080p, best) +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ MP3/AAC +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° плСйлистов +- βœ… **ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со списком URL** +- βœ… **Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL Ρ‡Π΅Ρ€Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΡƒΡŽ строку** +- βœ… **ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ΅Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… (--continue-on-error)** +- βœ… ΠŸΡ€ΠΎΠ³Ρ€Π΅ΡΡ-Π±Π°Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Ρ†Π²Π΅Ρ‚Π½Ρ‹ΠΌ Π²Ρ‹Π²ΠΎΠ΄ΠΎΠΌ +- βœ… НастраиваСмая конфигурация +- βœ… Валидация URL +- βœ… Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- βœ… CLI интСрфСйс с мноТСством ΠΎΠΏΡ†ΠΈΠΉ +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° субтитров +- βœ… АвтоматичСскоС исправлСниС ошибок 403/Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + +## πŸ“¦ Установка + +1. ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ: +```bash +git clone +cd youtube_downloader +``` + +2. УстановитС зависимости: +```bash +make install-fresh +# ΠΈΠ»ΠΈ +pip3 install -r requirements.txt +``` + +3. УстановитС ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ): +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: скачайтС с https://ffmpeg.org/ +``` + +## 🎯 Быстрый старт + +```bash +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ +make config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch batch_urls.txt --audio-only + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL +python3 main.py --urls URL1 URL2 URL3 --quality 720p +``` + +## πŸ“‹ ИспользованиС CLI + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹: +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π£ΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --output downloads/ + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --formats +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон Ρ„Π°ΠΉΠ»Π° со списком URL +make create-batch-template + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со списком URL +python3 main.py --batch urls.txt --quality 720p --continue-on-error + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL Ρ‡Π΅Ρ€Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΡƒΡŽ строку +python3 main.py --urls URL1 URL2 URL3 --audio-only + +# ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠΈΡ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… +python3 main.py --batch urls.txt --continue-on-error +``` + +### ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ: +```bash +# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +python3 main.py configure --output-dir downloads --video-quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +python3 main.py show-config +``` + +## πŸ”§ Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 utils.py template + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 utils.py batch --file urls.txt --quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py history + +# ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py clean +``` + +## πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ + +```bash +make install-fresh # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp +make install-ffmpeg # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ +make config # ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +make show-config # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +make update-ytdlp # ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии +make test # ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL +make demo-info # Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ +make demo-formats # Π€ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +make run-examples # Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ +make clean # ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ +make usage # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ Makefile +``` + +## πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +youtube_downloader/ +β”œβ”€β”€ main.py # ОсновноС CLI ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ +β”œβ”€β”€ downloader.py # Класс Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° YouTube +β”œβ”€β”€ config.py # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ +β”œβ”€β”€ utils.py # Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ +β”œβ”€β”€ examples.py # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования +β”œβ”€β”€ requirements.txt # Зависимости Python +β”œβ”€β”€ Makefile # Автоматизация Π·Π°Π΄Π°Ρ‡ +β”œβ”€β”€ README.md # ДокумСнтация +β”œβ”€β”€ config.json # Π€Π°ΠΉΠ» ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ (создаСтся автоматичСски) +└── urls.txt # Π¨Π°Π±Π»ΠΎΠ½ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +``` + +## βš™ ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ + +Настройки ΡΠΎΡ…Ρ€Π°Π½ΡΡŽΡ‚ΡΡ Π² `config.json`: + +```json +{ + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": true, + "add_metadata": true, + "subtitle_languages": ["ru", "en"], + "download_subtitles": false +} +``` + +## πŸ“ ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + +### Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС Π² ΠΊΠΎΠ΄Π΅: +```python +from downloader import YouTubeDownloader +from config import Config + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° +config = Config() +downloader = YouTubeDownloader(config) + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ +success = downloader.download_video( + url="https://www.youtube.com/watch?v=VIDEO_ID", + quality="720p", + audio_only=False +) +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```python +from utils import BatchDownloader + +batch = BatchDownloader() +batch.download_from_file("urls.txt", quality="best", audio_only=False) +``` + +## 🚨 ВрСбования + +- Python 3.7+ +- Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС +- ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ) + +## πŸ” Troubleshooting + +### Ошибки yt-dlp +Если Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‚ ошибки с yt-dlp, ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚Π΅ Π΄ΠΎ послСднСй вСрсии: +```bash +make update-ytdlp +# ΠΈΠ»ΠΈ +pip3 install --upgrade yt-dlp +``` + +### HTTP Error 403: Forbidden +ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ автоматичСски ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ нСсколько стратСгий ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ: +- ΠΠ»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ User-Agent Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ +- ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½Ρ‹Π΅ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ с Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠ°ΠΌΠΈ +- Π Π°Π·Π»ΠΈΡ‡Π½Ρ‹Π΅ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +- Fallback настройки + +### ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с ffmpeg +УстановитС ffmpeg для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ: +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/ +``` + +### МСдлСнная Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° +- ΠŸΠΎΠΏΡ€ΠΎΠ±ΡƒΠΉΡ‚Π΅ ΡΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² Π±ΠΎΠ»Π΅Π΅ Π½ΠΈΠ·ΠΊΠΎΠΌ качСствС: `--quality 480p` +- Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ: `--audio-only` +- ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС + +## πŸ“„ ЛицСнзия + +ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ создан для ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ†Π΅Π»Π΅ΠΉ. Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ условия использования YouTube. + +## 🀝 Π’ΠΊΠ»Π°Π΄ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ + +1. Π€ΠΎΡ€ΠΊΠ½ΠΈΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ +2. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ Π²Π΅Ρ‚ΠΊΡƒ для Π½ΠΎΠ²ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ +3. Π‘Π΄Π΅Π»Π°ΠΉΡ‚Π΅ измСнСния +4. ΠžΡ‚ΠΏΡ€Π°Π²ΡŒΡ‚Π΅ Pull Request + +--- + +**Автор:** GitHub Copilot +**ВСрсия:** 1.0.0 \ No newline at end of file diff --git a/.history/README_20250928091647.md b/.history/README_20250928091647.md new file mode 100644 index 0000000..6f4ca60 --- /dev/null +++ b/.history/README_20250928091647.md @@ -0,0 +1,261 @@ +# YouTube Downloader + +ΠœΠΎΡ‰Π½ΠΎΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube с использованиСм Python ΠΈ yt-dlp. + +## πŸš€ ВозмоТности + +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Π²ΠΈΠ΄Π΅ΠΎ Π² Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡΡ… (360p, 480p, 720p, 1080p, best) +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ MP3/AAC +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° плСйлистов +- βœ… **ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со списком URL** +- βœ… **Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL Ρ‡Π΅Ρ€Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΡƒΡŽ строку** +- βœ… **ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ΅Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… (--continue-on-error)** +- βœ… ΠŸΡ€ΠΎΠ³Ρ€Π΅ΡΡ-Π±Π°Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Ρ†Π²Π΅Ρ‚Π½Ρ‹ΠΌ Π²Ρ‹Π²ΠΎΠ΄ΠΎΠΌ +- βœ… НастраиваСмая конфигурация +- βœ… Валидация URL +- βœ… Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- βœ… CLI интСрфСйс с мноТСством ΠΎΠΏΡ†ΠΈΠΉ +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° субтитров +- βœ… АвтоматичСскоС исправлСниС ошибок 403/Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + +## πŸ“¦ Установка + +1. ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ: +```bash +git clone +cd youtube_downloader +``` + +2. УстановитС зависимости: +```bash +make install-fresh +# ΠΈΠ»ΠΈ +pip3 install -r requirements.txt +``` + +3. УстановитС ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ): +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: скачайтС с https://ffmpeg.org/ +``` + +## 🎯 Быстрый старт + +```bash +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ +make config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch batch_urls.txt --audio-only + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL +python3 main.py --urls URL1 URL2 URL3 --quality 720p +``` + +## πŸ“‹ ИспользованиС CLI + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹: +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π£ΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --output downloads/ + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --formats +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон Ρ„Π°ΠΉΠ»Π° со списком URL +make create-batch-template + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со списком URL +python3 main.py --batch urls.txt --quality 720p --continue-on-error + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL Ρ‡Π΅Ρ€Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΡƒΡŽ строку +python3 main.py --urls URL1 URL2 URL3 --audio-only + +# ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠΈΡ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… +python3 main.py --batch urls.txt --continue-on-error +``` + +### ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ: +```bash +# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +python3 main.py configure --output-dir downloads --video-quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +python3 main.py show-config +``` + +## πŸ”§ Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 utils.py template + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 utils.py batch --file urls.txt --quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py history + +# ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py clean +``` + +## πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ + +```bash +make install-fresh # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp +make install-ffmpeg # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ +make config # ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +make show-config # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +make update-ytdlp # ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии +make test # ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL +make demo-info # Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ +make demo-formats # Π€ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +make run-examples # Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ +make clean # ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ +make usage # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ Makefile +``` + +## πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +youtube_downloader/ +β”œβ”€β”€ main.py # ОсновноС CLI ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ +β”œβ”€β”€ downloader.py # Класс Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° YouTube +β”œβ”€β”€ config.py # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ +β”œβ”€β”€ utils.py # Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ +β”œβ”€β”€ examples.py # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования +β”œβ”€β”€ requirements.txt # Зависимости Python +β”œβ”€β”€ Makefile # Автоматизация Π·Π°Π΄Π°Ρ‡ +β”œβ”€β”€ README.md # ДокумСнтация +β”œβ”€β”€ config.json # Π€Π°ΠΉΠ» ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ (создаСтся автоматичСски) +└── urls.txt # Π¨Π°Π±Π»ΠΎΠ½ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +``` + +## βš™ ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ + +Настройки ΡΠΎΡ…Ρ€Π°Π½ΡΡŽΡ‚ΡΡ Π² `config.json`: + +```json +{ + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": true, + "add_metadata": true, + "subtitle_languages": ["ru", "en"], + "download_subtitles": false +} +``` + +## πŸ“ ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + +### Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС Π² ΠΊΠΎΠ΄Π΅: +```python +from downloader import YouTubeDownloader +from config import Config + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° +config = Config() +downloader = YouTubeDownloader(config) + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ +success = downloader.download_video( + url="https://www.youtube.com/watch?v=VIDEO_ID", + quality="720p", + audio_only=False +) +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```python +from utils import BatchDownloader + +batch = BatchDownloader() +batch.download_from_file("urls.txt", quality="best", audio_only=False) +``` + +## 🚨 ВрСбования + +- Python 3.7+ +- Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС +- ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ) + +## πŸ” Troubleshooting + +### Ошибки yt-dlp +Если Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‚ ошибки с yt-dlp, ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚Π΅ Π΄ΠΎ послСднСй вСрсии: +```bash +make update-ytdlp +# ΠΈΠ»ΠΈ +pip3 install --upgrade yt-dlp +``` + +### HTTP Error 403: Forbidden +ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ автоматичСски ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ нСсколько стратСгий ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ: +- ΠΠ»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ User-Agent Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ +- ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½Ρ‹Π΅ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ с Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠ°ΠΌΠΈ +- Π Π°Π·Π»ΠΈΡ‡Π½Ρ‹Π΅ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +- Fallback настройки + +### ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с ffmpeg +УстановитС ffmpeg для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ: +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/ +``` + +### МСдлСнная Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° +- ΠŸΠΎΠΏΡ€ΠΎΠ±ΡƒΠΉΡ‚Π΅ ΡΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² Π±ΠΎΠ»Π΅Π΅ Π½ΠΈΠ·ΠΊΠΎΠΌ качСствС: `--quality 480p` +- Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ: `--audio-only` +- ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС + +## πŸ“„ ЛицСнзия + +ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ создан для ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ†Π΅Π»Π΅ΠΉ. Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ условия использования YouTube. + +## 🀝 Π’ΠΊΠ»Π°Π΄ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ + +1. Π€ΠΎΡ€ΠΊΠ½ΠΈΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ +2. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ Π²Π΅Ρ‚ΠΊΡƒ для Π½ΠΎΠ²ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ +3. Π‘Π΄Π΅Π»Π°ΠΉΡ‚Π΅ измСнСния +4. ΠžΡ‚ΠΏΡ€Π°Π²ΡŒΡ‚Π΅ Pull Request + +--- + +**Автор:** GitHub Copilot +**ВСрсия:** 1.0.0 \ No newline at end of file diff --git a/.history/batch_urls_20250928090854.txt b/.history/batch_urls_20250928090854.txt new file mode 100644 index 0000000..ce1ee39 --- /dev/null +++ b/.history/batch_urls_20250928090854.txt @@ -0,0 +1,14 @@ +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Ρ„Π°ΠΉΠ»Π° со списком URL для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ YouTube Downloader +# ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ URL Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ Π½Π° ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠΉ строкС +# Π‘Ρ‚Ρ€ΠΎΠΊΠΈ, Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰ΠΈΠ΅ΡΡ с #, ΡΠ²Π»ΡΡŽΡ‚ΡΡ коммСнтариями ΠΈ ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ Π²ΠΈΠ΄Π΅ΠΎ для тСстирования: +https://www.youtube.com/watch?v=dQw4w9WgXcQ +https://www.youtube.com/watch?v=etuo7AA6rmY + +# МоТно Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ большС URL: +# https://www.youtube.com/watch?v=ANOTHER_VIDEO_ID +# https://www.youtube.com/watch?v=YET_ANOTHER_VIDEO_ID + +# ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚Ρ‹ Ρ‚ΠΎΠΆΠ΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‚ΡΡ (ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ --playlist): +# https://www.youtube.com/playlist?list=PLAYLIST_ID \ No newline at end of file diff --git a/.history/batch_urls_20250928091648.txt b/.history/batch_urls_20250928091648.txt new file mode 100644 index 0000000..ce1ee39 --- /dev/null +++ b/.history/batch_urls_20250928091648.txt @@ -0,0 +1,14 @@ +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Ρ„Π°ΠΉΠ»Π° со списком URL для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ YouTube Downloader +# ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ URL Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ Π½Π° ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠΉ строкС +# Π‘Ρ‚Ρ€ΠΎΠΊΠΈ, Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰ΠΈΠ΅ΡΡ с #, ΡΠ²Π»ΡΡŽΡ‚ΡΡ коммСнтариями ΠΈ ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ Π²ΠΈΠ΄Π΅ΠΎ для тСстирования: +https://www.youtube.com/watch?v=dQw4w9WgXcQ +https://www.youtube.com/watch?v=etuo7AA6rmY + +# МоТно Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ большС URL: +# https://www.youtube.com/watch?v=ANOTHER_VIDEO_ID +# https://www.youtube.com/watch?v=YET_ANOTHER_VIDEO_ID + +# ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚Ρ‹ Ρ‚ΠΎΠΆΠ΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‚ΡΡ (ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ --playlist): +# https://www.youtube.com/playlist?list=PLAYLIST_ID \ No newline at end of file diff --git a/.history/config_20250928083741.py b/.history/config_20250928083741.py new file mode 100644 index 0000000..8f61968 --- /dev/null +++ b/.history/config_20250928083741.py @@ -0,0 +1,55 @@ +import os +import json + +class Config: + """Класс для управлСния ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ прилоТСния""" + + DEFAULT_CONFIG = { + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": True, + "add_metadata": True, + "subtitle_languages": ["ru", "en"], + "download_subtitles": False + } + + def __init__(self, config_file="config.json"): + self.config_file = config_file + self.config = self.load_config() + + def load_config(self): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΈΠ· Ρ„Π°ΠΉΠ»Π° ΠΈΠ»ΠΈ создаСт ΡΡ‚Π°Π½Π΄Π°Ρ€Ρ‚Π½ΡƒΡŽ""" + if os.path.exists(self.config_file): + try: + with open(self.config_file, 'r', encoding='utf-8') as f: + return {**self.DEFAULT_CONFIG, **json.load(f)} + except (json.JSONDecodeError, IOError): + print(f"Ошибка чтСния ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΈΠ· {self.config_file}. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ стандартная конфигурация.") + + return self.DEFAULT_CONFIG.copy() + + def save_config(self): + """БохраняСт Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ Π² Ρ„Π°ΠΉΠ»""" + try: + with open(self.config_file, 'w', encoding='utf-8') as f: + json.dump(self.config, f, indent=4, ensure_ascii=False) + except IOError as e: + print(f"Ошибка сохранСния ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ: {e}") + + def get(self, key, default=None): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ""" + return self.config.get(key, default) + + def set(self, key, value): + """УстанавливаСт Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ""" + self.config[key] = value + + def create_output_directory(self): + """Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ, Ссли ΠΎΠ½Π° Π½Π΅ сущСствуСт""" + output_dir = self.get("output_directory") + if not os.path.exists(output_dir): + os.makedirs(output_dir) + print(f"Π‘ΠΎΠ·Π΄Π°Π½Π° ΠΏΠ°ΠΏΠΊΠ° для Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ: {output_dir}") + return output_dir \ No newline at end of file diff --git a/.history/config_20250928083750.py b/.history/config_20250928083750.py new file mode 100644 index 0000000..1fd3948 --- /dev/null +++ b/.history/config_20250928083750.py @@ -0,0 +1,55 @@ +import os +import json + +class Config: + """Класс для управлСния ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ прилоТСния""" + + DEFAULT_CONFIG = { + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": True, + "add_metadata": True, + "subtitle_languages": ["ru", "en"], + "download_subtitles": False + } + + def __init__(self, config_file="config.json"): + self.config_file = config_file + self.config = self.load_config() + + def load_config(self): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΈΠ· Ρ„Π°ΠΉΠ»Π° ΠΈΠ»ΠΈ создаСт ΡΡ‚Π°Π½Π΄Π°Ρ€Ρ‚Π½ΡƒΡŽ""" + if os.path.exists(self.config_file): + try: + with open(self.config_file, 'r', encoding='utf-8') as f: + return {**self.DEFAULT_CONFIG, **json.load(f)} + except (json.JSONDecodeError, IOError): + print(f"Ошибка чтСния ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΈΠ· {self.config_file}. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ стандартная конфигурация.") + + return self.DEFAULT_CONFIG.copy() + + def save_config(self): + """БохраняСт Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ Π² Ρ„Π°ΠΉΠ»""" + try: + with open(self.config_file, 'w', encoding='utf-8') as f: + json.dump(self.config, f, indent=4, ensure_ascii=False) + except IOError as e: + print(f"Ошибка сохранСния ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ: {e}") + + def get(self, key, default=None): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ""" + return self.config.get(key, default) + + def set(self, key, value): + """УстанавливаСт Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ""" + self.config[key] = value + + def create_output_directory(self): + """Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ, Ссли ΠΎΠ½Π° Π½Π΅ сущСствуСт""" + output_dir = self.get("output_directory", "downloads") + if not os.path.exists(output_dir): + os.makedirs(output_dir) + print(f"Π‘ΠΎΠ·Π΄Π°Π½Π° ΠΏΠ°ΠΏΠΊΠ° для Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ: {output_dir}") + return output_dir \ No newline at end of file diff --git a/.history/config_20250928085321.py b/.history/config_20250928085321.py new file mode 100644 index 0000000..1fd3948 --- /dev/null +++ b/.history/config_20250928085321.py @@ -0,0 +1,55 @@ +import os +import json + +class Config: + """Класс для управлСния ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ прилоТСния""" + + DEFAULT_CONFIG = { + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": True, + "add_metadata": True, + "subtitle_languages": ["ru", "en"], + "download_subtitles": False + } + + def __init__(self, config_file="config.json"): + self.config_file = config_file + self.config = self.load_config() + + def load_config(self): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΈΠ· Ρ„Π°ΠΉΠ»Π° ΠΈΠ»ΠΈ создаСт ΡΡ‚Π°Π½Π΄Π°Ρ€Ρ‚Π½ΡƒΡŽ""" + if os.path.exists(self.config_file): + try: + with open(self.config_file, 'r', encoding='utf-8') as f: + return {**self.DEFAULT_CONFIG, **json.load(f)} + except (json.JSONDecodeError, IOError): + print(f"Ошибка чтСния ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΈΠ· {self.config_file}. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ стандартная конфигурация.") + + return self.DEFAULT_CONFIG.copy() + + def save_config(self): + """БохраняСт Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ Π² Ρ„Π°ΠΉΠ»""" + try: + with open(self.config_file, 'w', encoding='utf-8') as f: + json.dump(self.config, f, indent=4, ensure_ascii=False) + except IOError as e: + print(f"Ошибка сохранСния ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ: {e}") + + def get(self, key, default=None): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ""" + return self.config.get(key, default) + + def set(self, key, value): + """УстанавливаСт Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ""" + self.config[key] = value + + def create_output_directory(self): + """Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ, Ссли ΠΎΠ½Π° Π½Π΅ сущСствуСт""" + output_dir = self.get("output_directory", "downloads") + if not os.path.exists(output_dir): + os.makedirs(output_dir) + print(f"Π‘ΠΎΠ·Π΄Π°Π½Π° ΠΏΠ°ΠΏΠΊΠ° для Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ: {output_dir}") + return output_dir \ No newline at end of file diff --git a/.history/downloader_20250928083918.py b/.history/downloader_20250928083918.py new file mode 100644 index 0000000..b6a8778 --- /dev/null +++ b/.history/downloader_20250928083918.py @@ -0,0 +1,213 @@ +import os +import re +import yt_dlp +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - self.progress_bar.last_downloaded) + else: + self.progress_bar.update(downloaded) + self.progress_bar.last_downloaded = downloaded + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp + ydl_opts = { + 'outtmpl': os.path.join(output_path, '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {str(e)}{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(output_path, '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + for f in info.get('formats', []): + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/.history/downloader_20250928083931.py b/.history/downloader_20250928083931.py new file mode 100644 index 0000000..7be47d1 --- /dev/null +++ b/.history/downloader_20250928083931.py @@ -0,0 +1,215 @@ +import os +import re +import yt_dlp +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + # Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ hasattr для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - getattr(self.progress_bar, 'last_downloaded', 0)) + else: + self.progress_bar.update(downloaded) + # УстанавливаСм custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ + setattr(self.progress_bar, 'last_downloaded', downloaded) + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp + ydl_opts = { + 'outtmpl': os.path.join(output_path, '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {str(e)}{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(output_path, '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + for f in info.get('formats', []): + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/.history/downloader_20250928084005.py b/.history/downloader_20250928084005.py new file mode 100644 index 0000000..c285ba6 --- /dev/null +++ b/.history/downloader_20250928084005.py @@ -0,0 +1,215 @@ +import os +import re +import yt_dlp +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + # Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ hasattr для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - getattr(self.progress_bar, 'last_downloaded', 0)) + else: + self.progress_bar.update(downloaded) + # УстанавливаСм custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ + setattr(self.progress_bar, 'last_downloaded', downloaded) + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {str(e)}{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(output_path, '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + for f in info.get('formats', []): + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/.history/downloader_20250928084015.py b/.history/downloader_20250928084015.py new file mode 100644 index 0000000..e727659 --- /dev/null +++ b/.history/downloader_20250928084015.py @@ -0,0 +1,215 @@ +import os +import re +import yt_dlp +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + # Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ hasattr для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - getattr(self.progress_bar, 'last_downloaded', 0)) + else: + self.progress_bar.update(downloaded) + # УстанавливаСм custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ + setattr(self.progress_bar, 'last_downloaded', downloaded) + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {str(e)}{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + for f in info.get('formats', []): + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/.history/downloader_20250928084028.py b/.history/downloader_20250928084028.py new file mode 100644 index 0000000..3dc0cbc --- /dev/null +++ b/.history/downloader_20250928084028.py @@ -0,0 +1,216 @@ +import os +import re +import yt_dlp +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + # Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ hasattr для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - getattr(self.progress_bar, 'last_downloaded', 0)) + else: + self.progress_bar.update(downloaded) + # УстанавливаСм custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ + setattr(self.progress_bar, 'last_downloaded', downloaded) + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {str(e)}{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + if info and 'formats' in info: + for f in info.get('formats', []): + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/.history/downloader_20250928084040.py b/.history/downloader_20250928084040.py new file mode 100644 index 0000000..4317f04 --- /dev/null +++ b/.history/downloader_20250928084040.py @@ -0,0 +1,218 @@ +import os +import re +import yt_dlp +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + # Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ hasattr для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - getattr(self.progress_bar, 'last_downloaded', 0)) + else: + self.progress_bar.update(downloaded) + # УстанавливаСм custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ + setattr(self.progress_bar, 'last_downloaded', downloaded) + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {str(e)}{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + if info and isinstance(info, dict) and 'formats' in info: + formats_list = info.get('formats') + if formats_list: + for f in formats_list: + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/.history/downloader_20250928085321.py b/.history/downloader_20250928085321.py new file mode 100644 index 0000000..4317f04 --- /dev/null +++ b/.history/downloader_20250928085321.py @@ -0,0 +1,218 @@ +import os +import re +import yt_dlp +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + # Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ hasattr для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - getattr(self.progress_bar, 'last_downloaded', 0)) + else: + self.progress_bar.update(downloaded) + # УстанавливаСм custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ + setattr(self.progress_bar, 'last_downloaded', downloaded) + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {str(e)}{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + if info and isinstance(info, dict) and 'formats' in info: + formats_list = info.get('formats') + if formats_list: + for f in formats_list: + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/.history/downloader_20250928085551.py b/.history/downloader_20250928085551.py new file mode 100644 index 0000000..a58a4d2 --- /dev/null +++ b/.history/downloader_20250928085551.py @@ -0,0 +1,243 @@ +import os +import re +import yt_dlp +import time +import random +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config +from fake_useragent import UserAgent + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + # Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ hasattr для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - getattr(self.progress_bar, 'last_downloaded', 0)) + else: + self.progress_bar.update(downloaded) + # УстанавливаСм custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ + setattr(self.progress_bar, 'last_downloaded', downloaded) + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp с ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½Π½Ρ‹ΠΌ ΠΎΠ±Ρ…ΠΎΠ΄ΠΎΠΌ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + ua = UserAgent() + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + # Настройки для ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + 'http_headers': { + 'User-Agent': ua.random, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip, deflate', + 'DNT': '1', + 'Connection': 'keep-alive', + 'Upgrade-Insecure-Requests': '1', + }, + 'extractor_retries': 3, + 'file_access_retries': 3, + 'fragment_retries': 3, + 'retry_sleep_functions': { + 'http': lambda n: min(4 * 2**n, 30), + 'fragment': lambda n: min(4 * 2**n, 30), + 'file_access': lambda n: min(4 * 2**n, 30), + }, + 'sleep_interval_requests': 1, + 'sleep_interval': 0, + 'max_sleep_interval': 5, + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {str(e)}{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + if info and isinstance(info, dict) and 'formats' in info: + formats_list = info.get('formats') + if formats_list: + for f in formats_list: + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/.history/downloader_20250928085626.py b/.history/downloader_20250928085626.py new file mode 100644 index 0000000..95b9619 --- /dev/null +++ b/.history/downloader_20250928085626.py @@ -0,0 +1,303 @@ +import os +import re +import yt_dlp +import time +import random +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config +from fake_useragent import UserAgent + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + # Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ hasattr для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - getattr(self.progress_bar, 'last_downloaded', 0)) + else: + self.progress_bar.update(downloaded) + # УстанавливаСм custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ + setattr(self.progress_bar, 'last_downloaded', downloaded) + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp с ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½Π½Ρ‹ΠΌ ΠΎΠ±Ρ…ΠΎΠ΄ΠΎΠΌ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + ua = UserAgent() + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + # Настройки для ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + 'http_headers': { + 'User-Agent': ua.random, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip, deflate', + 'DNT': '1', + 'Connection': 'keep-alive', + 'Upgrade-Insecure-Requests': '1', + }, + 'extractor_retries': 3, + 'file_access_retries': 3, + 'fragment_retries': 3, + 'retry_sleep_functions': { + 'http': lambda n: min(4 * 2**n, 30), + 'fragment': lambda n: min(4 * 2**n, 30), + 'file_access': lambda n: min(4 * 2**n, 30), + }, + 'sleep_interval_requests': 1, + 'sleep_interval': 0, + 'max_sleep_interval': 5, + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + # ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° с ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹ΠΌΠΈ настройками + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except yt_dlp.DownloadError as e: + error_msg = str(e) + print(f"{Fore.YELLOW}ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Π°: {error_msg}{Style.RESET_ALL}") + + # Если ошибка 403 ΠΈΠ»ΠΈ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ°, ΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ настройки + if "403" in error_msg or "Forbidden" in error_msg or "throttl" in error_msg: + return self._retry_download_with_fallback(url, ydl_opts) + else: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {error_msg}{Style.RESET_ALL}") + return False + + except Exception as e: + print(f"{Fore.RED}НСоТиданная ошибка: {str(e)}{Style.RESET_ALL}") + return False + + def _retry_download_with_fallback(self, url, base_opts): + """ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΌΠΈ настройками""" + fallback_strategies = [ + { + 'name': 'ΠΠ»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΉ User-Agent', + 'opts': { + **base_opts, + 'http_headers': { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } + } + }, + { + 'name': 'Π‘Π΅Π· ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Ρ…', + 'opts': { + **{k: v for k, v in base_opts.items() if k not in ['writeinfojson', 'writesubtitles', 'writeautomaticsub']}, + 'writeinfojson': False, + 'writesubtitles': False, + 'writeautomaticsub': False + } + }, + { + 'name': 'Волько Π°ΡƒΠ΄ΠΈΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚', + 'opts': { + **base_opts, + 'format': 'bestaudio[ext=m4a]/bestaudio/best[height<=480]' + } + } + ] + + for i, strategy in enumerate(fallback_strategies, 1): + print(f"{Fore.CYAN}ΠŸΠΎΠΏΡ‹Ρ‚ΠΊΠ° {i+1}: {strategy['name']}{Style.RESET_ALL}") + try: + # ДобавляСм Π·Π°Π΄Π΅Ρ€ΠΆΠΊΡƒ ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ°ΠΌΠΈ + time.sleep(random.uniform(2, 5)) + + with yt_dlp.YoutubeDL(strategy['opts']) as ydl: + ydl.download([url]) + print(f"{Fore.GREEN}βœ“ УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ с настройкой: {strategy['name']}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}βœ— {strategy['name']}: {str(e)}{Style.RESET_ALL}") + continue + + print(f"{Fore.RED}ВсС ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + if info and isinstance(info, dict) and 'formats' in info: + formats_list = info.get('formats') + if formats_list: + for f in formats_list: + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/.history/downloader_20250928085636.py b/.history/downloader_20250928085636.py new file mode 100644 index 0000000..5faed7c --- /dev/null +++ b/.history/downloader_20250928085636.py @@ -0,0 +1,303 @@ +import os +import re +import yt_dlp +import time +import random +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config +from fake_useragent import UserAgent + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + # Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ hasattr для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - getattr(self.progress_bar, 'last_downloaded', 0)) + else: + self.progress_bar.update(downloaded) + # УстанавливаСм custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ + setattr(self.progress_bar, 'last_downloaded', downloaded) + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp с ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½Π½Ρ‹ΠΌ ΠΎΠ±Ρ…ΠΎΠ΄ΠΎΠΌ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + ua = UserAgent() + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + # Настройки для ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + 'http_headers': { + 'User-Agent': ua.random, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip, deflate', + 'DNT': '1', + 'Connection': 'keep-alive', + 'Upgrade-Insecure-Requests': '1', + }, + 'extractor_retries': 3, + 'file_access_retries': 3, + 'fragment_retries': 3, + 'retry_sleep_functions': { + 'http': lambda n: min(4 * 2**n, 30), + 'fragment': lambda n: min(4 * 2**n, 30), + 'file_access': lambda n: min(4 * 2**n, 30), + }, + 'sleep_interval_requests': 1, + 'sleep_interval': 0, + 'max_sleep_interval': 5, + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + # ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° с ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹ΠΌΠΈ настройками + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except yt_dlp.utils.DownloadError as e: + error_msg = str(e) + print(f"{Fore.YELLOW}ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Π°: {error_msg}{Style.RESET_ALL}") + + # Если ошибка 403 ΠΈΠ»ΠΈ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ°, ΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ настройки + if "403" in error_msg or "Forbidden" in error_msg or "throttl" in error_msg: + return self._retry_download_with_fallback(url, ydl_opts) + else: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {error_msg}{Style.RESET_ALL}") + return False + + except Exception as e: + print(f"{Fore.RED}НСоТиданная ошибка: {str(e)}{Style.RESET_ALL}") + return False + + def _retry_download_with_fallback(self, url, base_opts): + """ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΌΠΈ настройками""" + fallback_strategies = [ + { + 'name': 'ΠΠ»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΉ User-Agent', + 'opts': { + **base_opts, + 'http_headers': { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } + } + }, + { + 'name': 'Π‘Π΅Π· ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Ρ…', + 'opts': { + **{k: v for k, v in base_opts.items() if k not in ['writeinfojson', 'writesubtitles', 'writeautomaticsub']}, + 'writeinfojson': False, + 'writesubtitles': False, + 'writeautomaticsub': False + } + }, + { + 'name': 'Волько Π°ΡƒΠ΄ΠΈΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚', + 'opts': { + **base_opts, + 'format': 'bestaudio[ext=m4a]/bestaudio/best[height<=480]' + } + } + ] + + for i, strategy in enumerate(fallback_strategies, 1): + print(f"{Fore.CYAN}ΠŸΠΎΠΏΡ‹Ρ‚ΠΊΠ° {i+1}: {strategy['name']}{Style.RESET_ALL}") + try: + # ДобавляСм Π·Π°Π΄Π΅Ρ€ΠΆΠΊΡƒ ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ°ΠΌΠΈ + time.sleep(random.uniform(2, 5)) + + with yt_dlp.YoutubeDL(strategy['opts']) as ydl: + ydl.download([url]) + print(f"{Fore.GREEN}βœ“ УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ с настройкой: {strategy['name']}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}βœ— {strategy['name']}: {str(e)}{Style.RESET_ALL}") + continue + + print(f"{Fore.RED}ВсС ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + if info and isinstance(info, dict) and 'formats' in info: + formats_list = info.get('formats') + if formats_list: + for f in formats_list: + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/.history/downloader_20250928085650.py b/.history/downloader_20250928085650.py new file mode 100644 index 0000000..147f731 --- /dev/null +++ b/.history/downloader_20250928085650.py @@ -0,0 +1,303 @@ +import os +import re +import yt_dlp +import time +import random +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config +from fake_useragent import UserAgent + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + # Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ hasattr для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - getattr(self.progress_bar, 'last_downloaded', 0)) + else: + self.progress_bar.update(downloaded) + # УстанавливаСм custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ + setattr(self.progress_bar, 'last_downloaded', downloaded) + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp с ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½Π½Ρ‹ΠΌ ΠΎΠ±Ρ…ΠΎΠ΄ΠΎΠΌ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + ua = UserAgent() + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + # Настройки для ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + 'http_headers': { + 'User-Agent': ua.random, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip, deflate', + 'DNT': '1', + 'Connection': 'keep-alive', + 'Upgrade-Insecure-Requests': '1', + }, + 'extractor_retries': 3, + 'file_access_retries': 3, + 'fragment_retries': 3, + 'retry_sleep_functions': { + 'http': lambda n: min(4 * 2**n, 30), + 'fragment': lambda n: min(4 * 2**n, 30), + 'file_access': lambda n: min(4 * 2**n, 30), + }, + 'sleep_interval_requests': 1, + 'sleep_interval': 0, + 'max_sleep_interval': 5, + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + # ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° с ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹ΠΌΠΈ настройками + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except Exception as e: + error_msg = str(e) + print(f"{Fore.YELLOW}ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Π°: {error_msg}{Style.RESET_ALL}") + + # Если ошибка 403 ΠΈΠ»ΠΈ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ°, ΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ настройки + if "403" in error_msg or "Forbidden" in error_msg or "throttl" in error_msg or "HTTP Error" in error_msg: + return self._retry_download_with_fallback(url, ydl_opts) + else: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {error_msg}{Style.RESET_ALL}") + return False + + except Exception as e: + print(f"{Fore.RED}НСоТиданная ошибка: {str(e)}{Style.RESET_ALL}") + return False + + def _retry_download_with_fallback(self, url, base_opts): + """ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΌΠΈ настройками""" + fallback_strategies = [ + { + 'name': 'ΠΠ»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΉ User-Agent', + 'opts': { + **base_opts, + 'http_headers': { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } + } + }, + { + 'name': 'Π‘Π΅Π· ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Ρ…', + 'opts': { + **{k: v for k, v in base_opts.items() if k not in ['writeinfojson', 'writesubtitles', 'writeautomaticsub']}, + 'writeinfojson': False, + 'writesubtitles': False, + 'writeautomaticsub': False + } + }, + { + 'name': 'Волько Π°ΡƒΠ΄ΠΈΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚', + 'opts': { + **base_opts, + 'format': 'bestaudio[ext=m4a]/bestaudio/best[height<=480]' + } + } + ] + + for i, strategy in enumerate(fallback_strategies, 1): + print(f"{Fore.CYAN}ΠŸΠΎΠΏΡ‹Ρ‚ΠΊΠ° {i+1}: {strategy['name']}{Style.RESET_ALL}") + try: + # ДобавляСм Π·Π°Π΄Π΅Ρ€ΠΆΠΊΡƒ ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ°ΠΌΠΈ + time.sleep(random.uniform(2, 5)) + + with yt_dlp.YoutubeDL(strategy['opts']) as ydl: + ydl.download([url]) + print(f"{Fore.GREEN}βœ“ УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ с настройкой: {strategy['name']}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}βœ— {strategy['name']}: {str(e)}{Style.RESET_ALL}") + continue + + print(f"{Fore.RED}ВсС ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + if info and isinstance(info, dict) and 'formats' in info: + formats_list = info.get('formats') + if formats_list: + for f in formats_list: + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/.history/downloader_20250928085708.py b/.history/downloader_20250928085708.py new file mode 100644 index 0000000..1851150 --- /dev/null +++ b/.history/downloader_20250928085708.py @@ -0,0 +1,299 @@ +import os +import re +import yt_dlp +import time +import random +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config +from fake_useragent import UserAgent + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + # Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ hasattr для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - getattr(self.progress_bar, 'last_downloaded', 0)) + else: + self.progress_bar.update(downloaded) + # УстанавливаСм custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ + setattr(self.progress_bar, 'last_downloaded', downloaded) + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp с ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½Π½Ρ‹ΠΌ ΠΎΠ±Ρ…ΠΎΠ΄ΠΎΠΌ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + ua = UserAgent() + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + # Настройки для ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + 'http_headers': { + 'User-Agent': ua.random, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip, deflate', + 'DNT': '1', + 'Connection': 'keep-alive', + 'Upgrade-Insecure-Requests': '1', + }, + 'extractor_retries': 3, + 'file_access_retries': 3, + 'fragment_retries': 3, + 'retry_sleep_functions': { + 'http': lambda n: min(4 * 2**n, 30), + 'fragment': lambda n: min(4 * 2**n, 30), + 'file_access': lambda n: min(4 * 2**n, 30), + }, + 'sleep_interval_requests': 1, + 'sleep_interval': 0, + 'max_sleep_interval': 5, + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + # ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° с ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹ΠΌΠΈ настройками + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except Exception as e: + error_msg = str(e) + print(f"{Fore.YELLOW}ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Π°: {error_msg}{Style.RESET_ALL}") + + # Если ошибка 403 ΠΈΠ»ΠΈ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ°, ΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ настройки + if "403" in error_msg or "Forbidden" in error_msg or "throttl" in error_msg or "HTTP Error" in error_msg: + return self._retry_download_with_fallback(url, ydl_opts) + else: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {error_msg}{Style.RESET_ALL}") + return False + + def _retry_download_with_fallback(self, url, base_opts): + """ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΌΠΈ настройками""" + fallback_strategies = [ + { + 'name': 'ΠΠ»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΉ User-Agent', + 'opts': { + **base_opts, + 'http_headers': { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } + } + }, + { + 'name': 'Π‘Π΅Π· ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Ρ…', + 'opts': { + **{k: v for k, v in base_opts.items() if k not in ['writeinfojson', 'writesubtitles', 'writeautomaticsub']}, + 'writeinfojson': False, + 'writesubtitles': False, + 'writeautomaticsub': False + } + }, + { + 'name': 'Волько Π°ΡƒΠ΄ΠΈΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚', + 'opts': { + **base_opts, + 'format': 'bestaudio[ext=m4a]/bestaudio/best[height<=480]' + } + } + ] + + for i, strategy in enumerate(fallback_strategies, 1): + print(f"{Fore.CYAN}ΠŸΠΎΠΏΡ‹Ρ‚ΠΊΠ° {i+1}: {strategy['name']}{Style.RESET_ALL}") + try: + # ДобавляСм Π·Π°Π΄Π΅Ρ€ΠΆΠΊΡƒ ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ°ΠΌΠΈ + time.sleep(random.uniform(2, 5)) + + with yt_dlp.YoutubeDL(strategy['opts']) as ydl: + ydl.download([url]) + print(f"{Fore.GREEN}βœ“ УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ с настройкой: {strategy['name']}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}βœ— {strategy['name']}: {str(e)}{Style.RESET_ALL}") + continue + + print(f"{Fore.RED}ВсС ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + if info and isinstance(info, dict) and 'formats' in info: + formats_list = info.get('formats') + if formats_list: + for f in formats_list: + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/.history/downloader_20250928090643.py b/.history/downloader_20250928090643.py new file mode 100644 index 0000000..1851150 --- /dev/null +++ b/.history/downloader_20250928090643.py @@ -0,0 +1,299 @@ +import os +import re +import yt_dlp +import time +import random +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config +from fake_useragent import UserAgent + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + # Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ hasattr для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - getattr(self.progress_bar, 'last_downloaded', 0)) + else: + self.progress_bar.update(downloaded) + # УстанавливаСм custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ + setattr(self.progress_bar, 'last_downloaded', downloaded) + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp с ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½Π½Ρ‹ΠΌ ΠΎΠ±Ρ…ΠΎΠ΄ΠΎΠΌ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + ua = UserAgent() + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + # Настройки для ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + 'http_headers': { + 'User-Agent': ua.random, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip, deflate', + 'DNT': '1', + 'Connection': 'keep-alive', + 'Upgrade-Insecure-Requests': '1', + }, + 'extractor_retries': 3, + 'file_access_retries': 3, + 'fragment_retries': 3, + 'retry_sleep_functions': { + 'http': lambda n: min(4 * 2**n, 30), + 'fragment': lambda n: min(4 * 2**n, 30), + 'file_access': lambda n: min(4 * 2**n, 30), + }, + 'sleep_interval_requests': 1, + 'sleep_interval': 0, + 'max_sleep_interval': 5, + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + # ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° с ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹ΠΌΠΈ настройками + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except Exception as e: + error_msg = str(e) + print(f"{Fore.YELLOW}ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Π°: {error_msg}{Style.RESET_ALL}") + + # Если ошибка 403 ΠΈΠ»ΠΈ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ°, ΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ настройки + if "403" in error_msg or "Forbidden" in error_msg or "throttl" in error_msg or "HTTP Error" in error_msg: + return self._retry_download_with_fallback(url, ydl_opts) + else: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {error_msg}{Style.RESET_ALL}") + return False + + def _retry_download_with_fallback(self, url, base_opts): + """ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΌΠΈ настройками""" + fallback_strategies = [ + { + 'name': 'ΠΠ»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΉ User-Agent', + 'opts': { + **base_opts, + 'http_headers': { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } + } + }, + { + 'name': 'Π‘Π΅Π· ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Ρ…', + 'opts': { + **{k: v for k, v in base_opts.items() if k not in ['writeinfojson', 'writesubtitles', 'writeautomaticsub']}, + 'writeinfojson': False, + 'writesubtitles': False, + 'writeautomaticsub': False + } + }, + { + 'name': 'Волько Π°ΡƒΠ΄ΠΈΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚', + 'opts': { + **base_opts, + 'format': 'bestaudio[ext=m4a]/bestaudio/best[height<=480]' + } + } + ] + + for i, strategy in enumerate(fallback_strategies, 1): + print(f"{Fore.CYAN}ΠŸΠΎΠΏΡ‹Ρ‚ΠΊΠ° {i+1}: {strategy['name']}{Style.RESET_ALL}") + try: + # ДобавляСм Π·Π°Π΄Π΅Ρ€ΠΆΠΊΡƒ ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ°ΠΌΠΈ + time.sleep(random.uniform(2, 5)) + + with yt_dlp.YoutubeDL(strategy['opts']) as ydl: + ydl.download([url]) + print(f"{Fore.GREEN}βœ“ УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ с настройкой: {strategy['name']}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}βœ— {strategy['name']}: {str(e)}{Style.RESET_ALL}") + continue + + print(f"{Fore.RED}ВсС ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + if info and isinstance(info, dict) and 'formats' in info: + formats_list = info.get('formats') + if formats_list: + for f in formats_list: + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/.history/examples_20250928084356.py b/.history/examples_20250928084356.py new file mode 100644 index 0000000..b979a1f --- /dev/null +++ b/.history/examples_20250928084356.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +""" +ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования YouTube Downloader +""" + +from downloader import YouTubeDownloader +from config import Config +from colorama import Fore, Style + +def example_download_video(): + """ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΎΠ΄Π½ΠΎΠ³ΠΎ Π²ΠΈΠ΄Π΅ΠΎ""" + print(f"{Fore.CYAN}=== ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π²ΠΈΠ΄Π΅ΠΎ ==={Style.RESET_ALL}") + + # URL тСстового Π²ΠΈΠ΄Π΅ΠΎ (ΠΊΠΎΡ€ΠΎΡ‚ΠΊΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ с YouTube) + url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" # Rick Roll для тСста + + config = Config() + downloader = YouTubeDownloader(config) + + # ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + print(f"{Fore.YELLOW}ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ...{Style.RESET_ALL}") + info = downloader.get_video_info(url) + + if info: + print(f"НазваниС: {info['title']}") + print(f"Автор: {info['uploader']}") + print(f"Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {info['duration']} сСк") + + # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + print(f"\n{Fore.YELLOW}ДоступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹:{Style.RESET_ALL}") + formats = downloader.get_available_formats(url) + for fmt in formats[:5]: # ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠ΅Ρ€Π²Ρ‹Π΅ 5 Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² + print(f" β€’ {fmt['quality']} ({fmt['ext']})") + + return url + +def example_download_audio(): + """ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ""" + print(f"\n{Fore.CYAN}=== ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π°ΡƒΠ΄ΠΈΠΎ ==={Style.RESET_ALL}") + + url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + + config = Config() + downloader = YouTubeDownloader(config) + + print("Для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ audio_only=True") + print("ΠŸΡ€ΠΈΠΌΠ΅Ρ€: downloader.download_video(url, audio_only=True)") + +def show_cli_examples(): + """ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования CLI""" + print(f"\n{Fore.CYAN}=== ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования CLI ==={Style.RESET_ALL}") + + examples = [ + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID", + "", + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² 720p", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p", + "", + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only", + "", + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΡƒΡŽ ΠΏΠ°ΠΏΠΊΡƒ", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --output /path/to/downloads/", + "", + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист", + "python main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist", + "", + "# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --info", + "", + "# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --formats", + "", + "# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ", + "python main.py configure --output-dir downloads --video-quality 720p", + "", + "# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки", + "python main.py show-config" + ] + + for example in examples: + if example.startswith("#"): + print(f"{Fore.GREEN}{example}{Style.RESET_ALL}") + elif example == "": + print() + else: + print(f"{Fore.WHITE}{example}{Style.RESET_ALL}") + +def test_url_validation(): + """ВСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL""" + print(f"\n{Fore.CYAN}=== ВСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL ==={Style.RESET_ALL}") + + downloader = YouTubeDownloader() + + test_urls = [ + ("https://www.youtube.com/watch?v=dQw4w9WgXcQ", True), + ("https://youtu.be/dQw4w9WgXcQ", True), + ("https://youtube.com/watch?v=dQw4w9WgXcQ", True), + ("https://www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMt6VEY", True), + ("https://example.com/video", False), + ("not_a_url", False), + ] + + for url, expected in test_urls: + result = downloader.validate_url(url) + status = "βœ“" if result == expected else "βœ—" + color = Fore.GREEN if result == expected else Fore.RED + print(f"{color}{status} {url} -> {result}{Style.RESET_ALL}") + +if __name__ == "__main__": + print(f"{Fore.CYAN}YouTube Downloader - ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования{Style.RESET_ALL}") + print("=" * 60) + + # ВСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL + test_url_validation() + + # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + test_url = example_download_video() + example_download_audio() + + # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ CLI + show_cli_examples() + + print(f"\n{Fore.GREEN}ВсС ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ ΠΏΠΎΠΊΠ°Π·Π°Π½Ρ‹. Для Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ main.py{Style.RESET_ALL}") + print(f"{Fore.YELLOW}Для Π½Π°Ρ‡Π°Π»Π° Ρ€Π°Π±ΠΎΡ‚Ρ‹ установитС зависимости: pip install -r requirements.txt{Style.RESET_ALL}") \ No newline at end of file diff --git a/.history/examples_20250928085321.py b/.history/examples_20250928085321.py new file mode 100644 index 0000000..b979a1f --- /dev/null +++ b/.history/examples_20250928085321.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +""" +ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования YouTube Downloader +""" + +from downloader import YouTubeDownloader +from config import Config +from colorama import Fore, Style + +def example_download_video(): + """ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΎΠ΄Π½ΠΎΠ³ΠΎ Π²ΠΈΠ΄Π΅ΠΎ""" + print(f"{Fore.CYAN}=== ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π²ΠΈΠ΄Π΅ΠΎ ==={Style.RESET_ALL}") + + # URL тСстового Π²ΠΈΠ΄Π΅ΠΎ (ΠΊΠΎΡ€ΠΎΡ‚ΠΊΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ с YouTube) + url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" # Rick Roll для тСста + + config = Config() + downloader = YouTubeDownloader(config) + + # ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + print(f"{Fore.YELLOW}ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ...{Style.RESET_ALL}") + info = downloader.get_video_info(url) + + if info: + print(f"НазваниС: {info['title']}") + print(f"Автор: {info['uploader']}") + print(f"Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {info['duration']} сСк") + + # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + print(f"\n{Fore.YELLOW}ДоступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹:{Style.RESET_ALL}") + formats = downloader.get_available_formats(url) + for fmt in formats[:5]: # ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠ΅Ρ€Π²Ρ‹Π΅ 5 Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² + print(f" β€’ {fmt['quality']} ({fmt['ext']})") + + return url + +def example_download_audio(): + """ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ""" + print(f"\n{Fore.CYAN}=== ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π°ΡƒΠ΄ΠΈΠΎ ==={Style.RESET_ALL}") + + url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + + config = Config() + downloader = YouTubeDownloader(config) + + print("Для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ audio_only=True") + print("ΠŸΡ€ΠΈΠΌΠ΅Ρ€: downloader.download_video(url, audio_only=True)") + +def show_cli_examples(): + """ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования CLI""" + print(f"\n{Fore.CYAN}=== ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования CLI ==={Style.RESET_ALL}") + + examples = [ + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID", + "", + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² 720p", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p", + "", + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only", + "", + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΡƒΡŽ ΠΏΠ°ΠΏΠΊΡƒ", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --output /path/to/downloads/", + "", + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист", + "python main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist", + "", + "# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --info", + "", + "# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --formats", + "", + "# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ", + "python main.py configure --output-dir downloads --video-quality 720p", + "", + "# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки", + "python main.py show-config" + ] + + for example in examples: + if example.startswith("#"): + print(f"{Fore.GREEN}{example}{Style.RESET_ALL}") + elif example == "": + print() + else: + print(f"{Fore.WHITE}{example}{Style.RESET_ALL}") + +def test_url_validation(): + """ВСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL""" + print(f"\n{Fore.CYAN}=== ВСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL ==={Style.RESET_ALL}") + + downloader = YouTubeDownloader() + + test_urls = [ + ("https://www.youtube.com/watch?v=dQw4w9WgXcQ", True), + ("https://youtu.be/dQw4w9WgXcQ", True), + ("https://youtube.com/watch?v=dQw4w9WgXcQ", True), + ("https://www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMt6VEY", True), + ("https://example.com/video", False), + ("not_a_url", False), + ] + + for url, expected in test_urls: + result = downloader.validate_url(url) + status = "βœ“" if result == expected else "βœ—" + color = Fore.GREEN if result == expected else Fore.RED + print(f"{color}{status} {url} -> {result}{Style.RESET_ALL}") + +if __name__ == "__main__": + print(f"{Fore.CYAN}YouTube Downloader - ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования{Style.RESET_ALL}") + print("=" * 60) + + # ВСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL + test_url_validation() + + # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + test_url = example_download_video() + example_download_audio() + + # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ CLI + show_cli_examples() + + print(f"\n{Fore.GREEN}ВсС ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ ΠΏΠΎΠΊΠ°Π·Π°Π½Ρ‹. Для Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ main.py{Style.RESET_ALL}") + print(f"{Fore.YELLOW}Для Π½Π°Ρ‡Π°Π»Π° Ρ€Π°Π±ΠΎΡ‚Ρ‹ установитС зависимости: pip install -r requirements.txt{Style.RESET_ALL}") \ No newline at end of file diff --git a/.history/install_ffmpeg_20250928085959.sh b/.history/install_ffmpeg_20250928085959.sh new file mode 100644 index 0000000..f0c8700 --- /dev/null +++ b/.history/install_ffmpeg_20250928085959.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Π‘ΠΊΡ€ΠΈΠΏΡ‚ установки ffmpeg для YouTube Downloader + +echo "🎬 Установка ffmpeg для YouTube Downloader" +echo "==========================================" + +# ΠžΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ОБ +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo "🐧 Linux ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠ΅Π½" + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° наличия apt + if command -v apt &> /dev/null; then + echo "πŸ“¦ Π£ΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°ΡŽ ffmpeg Ρ‡Π΅Ρ€Π΅Π· apt..." + sudo apt update && sudo apt install -y ffmpeg + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° наличия yum + elif command -v yum &> /dev/null; then + echo "πŸ“¦ Π£ΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°ΡŽ ffmpeg Ρ‡Π΅Ρ€Π΅Π· yum..." + sudo yum install -y ffmpeg + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° наличия dnf + elif command -v dnf &> /dev/null; then + echo "πŸ“¦ Π£ΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°ΡŽ ffmpeg Ρ‡Π΅Ρ€Π΅Π· dnf..." + sudo dnf install -y ffmpeg + + else + echo "❌ ΠœΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€ ΠΏΠ°ΠΊΠ΅Ρ‚ΠΎΠ² Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. УстановитС ffmpeg Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ." + exit 1 + fi + +elif [[ "$OSTYPE" == "darwin"* ]]; then + echo "🍎 macOS ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠ΅Π½" + + if command -v brew &> /dev/null; then + echo "πŸ“¦ Π£ΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°ΡŽ ffmpeg Ρ‡Π΅Ρ€Π΅Π· Homebrew..." + brew install ffmpeg + else + echo "❌ Homebrew Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. УстановитС Π΅Π³ΠΎ сначала:" + echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" + exit 1 + fi + +else + echo "❓ НСизвСстная ОБ: $OSTYPE" + echo "УстановитС ffmpeg Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ:" + echo " - Windows: Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/" + echo " - Linux: sudo apt install ffmpeg" + echo " - macOS: brew install ffmpeg" + exit 1 +fi + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° установки +if command -v ffmpeg &> /dev/null; then + echo "βœ… ffmpeg ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ установлСн!" + ffmpeg -version | head -1 + + echo "" + echo "🎯 Π’Π΅ΠΏΠ΅Ρ€ΡŒ ΠΌΠΎΠΆΠ½ΠΎ ΡΠΊΠ°Ρ‡ΠΈΠ²Π°Ρ‚ΡŒ Π°ΡƒΠ΄ΠΈΠΎ Π² MP3:" + echo " python3 main.py URL --audio-only" +else + echo "❌ Ошибка установки ffmpeg" + exit 1 +fi \ No newline at end of file diff --git a/.history/install_ffmpeg_20250928090643.sh b/.history/install_ffmpeg_20250928090643.sh new file mode 100644 index 0000000..f0c8700 --- /dev/null +++ b/.history/install_ffmpeg_20250928090643.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Π‘ΠΊΡ€ΠΈΠΏΡ‚ установки ffmpeg для YouTube Downloader + +echo "🎬 Установка ffmpeg для YouTube Downloader" +echo "==========================================" + +# ΠžΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ОБ +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo "🐧 Linux ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠ΅Π½" + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° наличия apt + if command -v apt &> /dev/null; then + echo "πŸ“¦ Π£ΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°ΡŽ ffmpeg Ρ‡Π΅Ρ€Π΅Π· apt..." + sudo apt update && sudo apt install -y ffmpeg + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° наличия yum + elif command -v yum &> /dev/null; then + echo "πŸ“¦ Π£ΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°ΡŽ ffmpeg Ρ‡Π΅Ρ€Π΅Π· yum..." + sudo yum install -y ffmpeg + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° наличия dnf + elif command -v dnf &> /dev/null; then + echo "πŸ“¦ Π£ΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°ΡŽ ffmpeg Ρ‡Π΅Ρ€Π΅Π· dnf..." + sudo dnf install -y ffmpeg + + else + echo "❌ ΠœΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€ ΠΏΠ°ΠΊΠ΅Ρ‚ΠΎΠ² Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. УстановитС ffmpeg Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ." + exit 1 + fi + +elif [[ "$OSTYPE" == "darwin"* ]]; then + echo "🍎 macOS ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠ΅Π½" + + if command -v brew &> /dev/null; then + echo "πŸ“¦ Π£ΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°ΡŽ ffmpeg Ρ‡Π΅Ρ€Π΅Π· Homebrew..." + brew install ffmpeg + else + echo "❌ Homebrew Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. УстановитС Π΅Π³ΠΎ сначала:" + echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" + exit 1 + fi + +else + echo "❓ НСизвСстная ОБ: $OSTYPE" + echo "УстановитС ffmpeg Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ:" + echo " - Windows: Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/" + echo " - Linux: sudo apt install ffmpeg" + echo " - macOS: brew install ffmpeg" + exit 1 +fi + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° установки +if command -v ffmpeg &> /dev/null; then + echo "βœ… ffmpeg ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ установлСн!" + ffmpeg -version | head -1 + + echo "" + echo "🎯 Π’Π΅ΠΏΠ΅Ρ€ΡŒ ΠΌΠΎΠΆΠ½ΠΎ ΡΠΊΠ°Ρ‡ΠΈΠ²Π°Ρ‚ΡŒ Π°ΡƒΠ΄ΠΈΠΎ Π² MP3:" + echo " python3 main.py URL --audio-only" +else + echo "❌ Ошибка установки ffmpeg" + exit 1 +fi \ No newline at end of file diff --git a/.history/main_20250928084237.py b/.history/main_20250928084237.py new file mode 100644 index 0000000..cee278e --- /dev/null +++ b/.history/main_20250928084237.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +""" +YouTube Downloader - ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube +Автор: GitHub Copilot +""" + +import sys +import click +from colorama import Fore, Style +from downloader import YouTubeDownloader +from config import Config + +@click.command() +@click.argument('url', required=True) +@click.option('--quality', '-q', default='best', + help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ (best, 1080p, 720p, 480p, 360p)') +@click.option('--audio-only', '-a', is_flag=True, + help='Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ') +@click.option('--output', '-o', default=None, + help='Папка для сохранСния Ρ„Π°ΠΉΠ»ΠΎΠ²') +@click.option('--playlist', '-p', is_flag=True, + help='Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ вСсь плСйлист') +@click.option('--info', '-i', is_flag=True, + help='ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') +@click.option('--formats', '-f', is_flag=True, + help='ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹') +@click.option('--config', '-c', default='config.json', + help='ΠŸΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ') +def main(url, quality, audio_only, output, playlist, info, formats, config): + """ + YouTube Downloader - скачиваниС Π²ΠΈΠ΄Π΅ΠΎ с YouTube + + URL: Бсылка Π½Π° YouTube Π²ΠΈΠ΄Π΅ΠΎ ΠΈΠ»ΠΈ плСйлист + """ + # Π’Ρ‹Π²ΠΎΠ΄ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ° + print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'YouTube Downloader':^60}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}") + + try: + # Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΈ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° + app_config = Config(config) + downloader = YouTubeDownloader(app_config) + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° URL + if not downloader.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + sys.exit(1) + + # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + if info: + video_info = downloader.get_video_info(url) + if video_info: + print(f"\n{Fore.GREEN}Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ:{Style.RESET_ALL}") + print(f"{Fore.YELLOW}НазваниС:{Style.RESET_ALL} {video_info['title']}") + print(f"{Fore.YELLOW}Автор:{Style.RESET_ALL} {video_info['uploader']}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.YELLOW}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ:{Style.RESET_ALL} {duration_str}") + + if video_info['view_count']: + print(f"{Fore.YELLOW}ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ΠΎΠ²:{Style.RESET_ALL} {video_info['view_count']:,}") + + if video_info['upload_date']: + date_str = video_info['upload_date'] + formatted_date = f"{date_str[6:8]}.{date_str[4:6]}.{date_str[:4]}" + print(f"{Fore.YELLOW}Π”Π°Ρ‚Π° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:{Style.RESET_ALL} {formatted_date}") + + if video_info['description']: + desc = video_info['description'][:200] + "..." if len(video_info['description']) > 200 else video_info['description'] + print(f"{Fore.YELLOW}ОписаниС:{Style.RESET_ALL} {desc}") + return + + # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + if formats: + print(f"\n{Fore.GREEN}ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²...{Style.RESET_ALL}") + available_formats = downloader.get_available_formats(url) + if available_formats: + print(f"\n{Fore.GREEN}ДоступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹:{Style.RESET_ALL}") + for fmt in available_formats: + size_str = f" ({fmt['filesize']//1024//1024} MB)" if fmt['filesize'] else "" + print(f" β€’ {fmt['quality']} ({fmt['ext']}){size_str}") + else: + print(f"{Fore.RED}НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π°Ρ…{Style.RESET_ALL}") + return + + # Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ + print(f"\n{Fore.GREEN}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΡƒ...{Style.RESET_ALL}") + + success = False + if playlist: + success = downloader.download_playlist(url, quality, audio_only, output) + else: + success = downloader.download_video(url, quality, audio_only, output) + + if success: + print(f"\n{Fore.GREEN}{'='*60}{Style.RESET_ALL}") + print(f"{Fore.GREEN}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°!{Style.RESET_ALL}") + output_path = output or app_config.get("output_directory", "downloads") + print(f"{Fore.CYAN}Π€Π°ΠΉΠ»Ρ‹ сохранСны Π²: {output_path}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{'='*60}{Style.RESET_ALL}") + else: + print(f"\n{Fore.RED}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ»Π°ΡΡŒ с ошибкой{Style.RESET_ALL}") + sys.exit(1) + + except KeyboardInterrupt: + print(f"\n{Fore.YELLOW}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΏΡ€Π΅Ρ€Π²Π°Π½Π° ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΌ{Style.RESET_ALL}") + sys.exit(0) + except Exception as e: + print(f"\n{Fore.RED}ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° нСоТиданная ошибка: {str(e)}{Style.RESET_ALL}") + sys.exit(1) + +@click.group() +def cli(): + """YouTube Downloader - ΡƒΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ настройками ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ""" + pass + +@cli.command() +@click.option('--output-dir', default='downloads', help='Папка для Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--video-quality', default='best', help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--audio-format', default='mp3', help='Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π°ΡƒΠ΄ΠΈΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--video-format', default='mp4', help='Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +def configure(output_dir, video_quality, audio_format, video_format): + """Настройка ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ""" + config = Config() + + config.set('output_directory', output_dir) + config.set('video_quality', video_quality) + config.set('audio_format', audio_format) + config.set('video_format', video_format) + + config.save_config() + + print(f"{Fore.GREEN}ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ сохранСна:{Style.RESET_ALL}") + print(f" Папка Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ: {output_dir}") + print(f" ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {video_quality}") + print(f" Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π°ΡƒΠ΄ΠΈΠΎ: {audio_format}") + print(f" Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ: {video_format}") + +@cli.command() +def show_config(): + """ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки""" + config = Config() + + print(f"{Fore.CYAN}ВСкущая конфигурация:{Style.RESET_ALL}") + for key, value in config.config.items(): + print(f" {key}: {value}") + +if __name__ == '__main__': + # Если Π·Π°ΠΏΡƒΡ‰Π΅Π½ Π±Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΎΡΠ½ΠΎΠ²Π½ΡƒΡŽ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ + if len(sys.argv) == 1 or (len(sys.argv) > 1 and not sys.argv[1] in ['configure', 'show-config']): + main() + else: + cli() \ No newline at end of file diff --git a/.history/main_20250928085321.py b/.history/main_20250928085321.py new file mode 100644 index 0000000..cee278e --- /dev/null +++ b/.history/main_20250928085321.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +""" +YouTube Downloader - ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube +Автор: GitHub Copilot +""" + +import sys +import click +from colorama import Fore, Style +from downloader import YouTubeDownloader +from config import Config + +@click.command() +@click.argument('url', required=True) +@click.option('--quality', '-q', default='best', + help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ (best, 1080p, 720p, 480p, 360p)') +@click.option('--audio-only', '-a', is_flag=True, + help='Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ') +@click.option('--output', '-o', default=None, + help='Папка для сохранСния Ρ„Π°ΠΉΠ»ΠΎΠ²') +@click.option('--playlist', '-p', is_flag=True, + help='Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ вСсь плСйлист') +@click.option('--info', '-i', is_flag=True, + help='ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') +@click.option('--formats', '-f', is_flag=True, + help='ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹') +@click.option('--config', '-c', default='config.json', + help='ΠŸΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ') +def main(url, quality, audio_only, output, playlist, info, formats, config): + """ + YouTube Downloader - скачиваниС Π²ΠΈΠ΄Π΅ΠΎ с YouTube + + URL: Бсылка Π½Π° YouTube Π²ΠΈΠ΄Π΅ΠΎ ΠΈΠ»ΠΈ плСйлист + """ + # Π’Ρ‹Π²ΠΎΠ΄ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ° + print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'YouTube Downloader':^60}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}") + + try: + # Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΈ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° + app_config = Config(config) + downloader = YouTubeDownloader(app_config) + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° URL + if not downloader.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + sys.exit(1) + + # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + if info: + video_info = downloader.get_video_info(url) + if video_info: + print(f"\n{Fore.GREEN}Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ:{Style.RESET_ALL}") + print(f"{Fore.YELLOW}НазваниС:{Style.RESET_ALL} {video_info['title']}") + print(f"{Fore.YELLOW}Автор:{Style.RESET_ALL} {video_info['uploader']}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.YELLOW}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ:{Style.RESET_ALL} {duration_str}") + + if video_info['view_count']: + print(f"{Fore.YELLOW}ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ΠΎΠ²:{Style.RESET_ALL} {video_info['view_count']:,}") + + if video_info['upload_date']: + date_str = video_info['upload_date'] + formatted_date = f"{date_str[6:8]}.{date_str[4:6]}.{date_str[:4]}" + print(f"{Fore.YELLOW}Π”Π°Ρ‚Π° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:{Style.RESET_ALL} {formatted_date}") + + if video_info['description']: + desc = video_info['description'][:200] + "..." if len(video_info['description']) > 200 else video_info['description'] + print(f"{Fore.YELLOW}ОписаниС:{Style.RESET_ALL} {desc}") + return + + # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + if formats: + print(f"\n{Fore.GREEN}ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²...{Style.RESET_ALL}") + available_formats = downloader.get_available_formats(url) + if available_formats: + print(f"\n{Fore.GREEN}ДоступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹:{Style.RESET_ALL}") + for fmt in available_formats: + size_str = f" ({fmt['filesize']//1024//1024} MB)" if fmt['filesize'] else "" + print(f" β€’ {fmt['quality']} ({fmt['ext']}){size_str}") + else: + print(f"{Fore.RED}НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π°Ρ…{Style.RESET_ALL}") + return + + # Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ + print(f"\n{Fore.GREEN}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΡƒ...{Style.RESET_ALL}") + + success = False + if playlist: + success = downloader.download_playlist(url, quality, audio_only, output) + else: + success = downloader.download_video(url, quality, audio_only, output) + + if success: + print(f"\n{Fore.GREEN}{'='*60}{Style.RESET_ALL}") + print(f"{Fore.GREEN}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°!{Style.RESET_ALL}") + output_path = output or app_config.get("output_directory", "downloads") + print(f"{Fore.CYAN}Π€Π°ΠΉΠ»Ρ‹ сохранСны Π²: {output_path}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{'='*60}{Style.RESET_ALL}") + else: + print(f"\n{Fore.RED}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ»Π°ΡΡŒ с ошибкой{Style.RESET_ALL}") + sys.exit(1) + + except KeyboardInterrupt: + print(f"\n{Fore.YELLOW}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΏΡ€Π΅Ρ€Π²Π°Π½Π° ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΌ{Style.RESET_ALL}") + sys.exit(0) + except Exception as e: + print(f"\n{Fore.RED}ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° нСоТиданная ошибка: {str(e)}{Style.RESET_ALL}") + sys.exit(1) + +@click.group() +def cli(): + """YouTube Downloader - ΡƒΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ настройками ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ""" + pass + +@cli.command() +@click.option('--output-dir', default='downloads', help='Папка для Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--video-quality', default='best', help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--audio-format', default='mp3', help='Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π°ΡƒΠ΄ΠΈΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--video-format', default='mp4', help='Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +def configure(output_dir, video_quality, audio_format, video_format): + """Настройка ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ""" + config = Config() + + config.set('output_directory', output_dir) + config.set('video_quality', video_quality) + config.set('audio_format', audio_format) + config.set('video_format', video_format) + + config.save_config() + + print(f"{Fore.GREEN}ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ сохранСна:{Style.RESET_ALL}") + print(f" Папка Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ: {output_dir}") + print(f" ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {video_quality}") + print(f" Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π°ΡƒΠ΄ΠΈΠΎ: {audio_format}") + print(f" Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ: {video_format}") + +@cli.command() +def show_config(): + """ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки""" + config = Config() + + print(f"{Fore.CYAN}ВСкущая конфигурация:{Style.RESET_ALL}") + for key, value in config.config.items(): + print(f" {key}: {value}") + +if __name__ == '__main__': + # Если Π·Π°ΠΏΡƒΡ‰Π΅Π½ Π±Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΎΡΠ½ΠΎΠ²Π½ΡƒΡŽ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ + if len(sys.argv) == 1 or (len(sys.argv) > 1 and not sys.argv[1] in ['configure', 'show-config']): + main() + else: + cli() \ No newline at end of file diff --git a/.history/main_20250928090815.py b/.history/main_20250928090815.py new file mode 100644 index 0000000..8552d6c --- /dev/null +++ b/.history/main_20250928090815.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +""" +YouTube Downloader - ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube +Автор: GitHub Copilot +""" + +import sys +import click +from colorama import Fore, Style +from downloader import YouTubeDownloader +from config import Config + +@click.command() +@click.argument('url', required=False) +@click.option('--quality', '-q', default='best', + help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ (best, 1080p, 720p, 480p, 360p)') +@click.option('--audio-only', '-a', is_flag=True, + help='Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ') +@click.option('--output', '-o', default=None, + help='Папка для сохранСния Ρ„Π°ΠΉΠ»ΠΎΠ²') +@click.option('--playlist', '-p', is_flag=True, + help='Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ вСсь плСйлист') +@click.option('--info', '-i', is_flag=True, + help='ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') +@click.option('--formats', '-f', is_flag=True, + help='ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹') +@click.option('--config', '-c', default='config.json', + help='ΠŸΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ') +@click.option('--batch', '-b', default=None, type=click.Path(exists=True), + help='Π€Π°ΠΉΠ» со списком URL для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') +@click.option('--urls', multiple=True, + help='НСсколько URL Ρ‡Π΅Ρ€Π΅Π· ΠΏΡ€ΠΎΠ±Π΅Π»') +@click.option('--continue-on-error', is_flag=True, + help='ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Ρ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ…') +def main(url, quality, audio_only, output, playlist, info, formats, config, batch, urls, continue_on_error): + """ + YouTube Downloader - скачиваниС Π²ΠΈΠ΄Π΅ΠΎ с YouTube + + URL: Бсылка Π½Π° YouTube Π²ΠΈΠ΄Π΅ΠΎ ΠΈΠ»ΠΈ плСйлист (ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ ΠΏΡ€ΠΈ использовании --batch ΠΈΠ»ΠΈ --urls) + """ + # Π’Ρ‹Π²ΠΎΠ΄ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ° + print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'YouTube Downloader':^60}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}") + + try: + # Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΈ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° + app_config = Config(config) + downloader = YouTubeDownloader(app_config) + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ список URL для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ + urls_to_process = [] + + if batch: + # ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° + print(f"{Fore.GREEN}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π°: {batch}{Style.RESET_ALL}") + urls_to_process = load_urls_from_file(batch) + elif urls: + # ΠœΠ½ΠΎΠΆΠ΅ΡΡ‚Π²Π΅Π½Π½Ρ‹Π΅ URL ΠΈΠ· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΠΎΠΉ строки + urls_to_process = list(urls) + print(f"{Fore.GREEN}ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° {len(urls_to_process)} URL ΠΈΠ· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΠΎΠΉ строки{Style.RESET_ALL}") + elif url: + # Один URL + urls_to_process = [url] + else: + print(f"{Fore.RED}Ошибка: НСобходимо ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ URL, --batch Ρ„Π°ΠΉΠ» ΠΈΠ»ΠΈ --urls{Style.RESET_ALL}") + sys.exit(1) + + # ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° списка URL + if info or formats: + # Для info ΠΈ formats ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ URL + process_single_url = urls_to_process[0] if urls_to_process else None + if not process_single_url: + print(f"{Fore.RED}Ошибка: НСт URL для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ{Style.RESET_ALL}") + sys.exit(1) + + if not downloader.validate_url(process_single_url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + sys.exit(1) + + if info: + show_video_info(downloader, process_single_url) + return + + if formats: + show_video_formats(downloader, process_single_url) + return + + # ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° + print(f"\n{Fore.GREEN}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΡƒ {len(urls_to_process)} URL...{Style.RESET_ALL}") + + successful = 0 + failed = 0 + failed_urls = [] + + for i, current_url in enumerate(urls_to_process, 1): + print(f"\n{Fore.CYAN}[{i}/{len(urls_to_process)}] ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ°: {current_url}{Style.RESET_ALL}") + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° URL + if not downloader.validate_url(current_url): + print(f"{Fore.RED}βœ— НСкоррСктный URL, пропускаСм{Style.RESET_ALL}") + failed += 1 + failed_urls.append((current_url, "НСкоррСктный URL")) + if not continue_on_error: + break + continue + + try: + success = False + if playlist: + success = downloader.download_playlist(current_url, quality, audio_only, output) + else: + success = downloader.download_video(current_url, quality, audio_only, output) + + if success: + successful += 1 + print(f"{Fore.GREEN}βœ“ [{i}/{len(urls_to_process)}] УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ{Style.RESET_ALL}") + else: + failed += 1 + failed_urls.append((current_url, "Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ")) + print(f"{Fore.RED}βœ— [{i}/{len(urls_to_process)}] Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ{Style.RESET_ALL}") + if not continue_on_error: + break + + except Exception as e: + failed += 1 + error_msg = str(e)[:100] + "..." if len(str(e)) > 100 else str(e) + failed_urls.append((current_url, error_msg)) + print(f"{Fore.RED}βœ— [{i}/{len(urls_to_process)}] Π˜ΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅: {error_msg}{Style.RESET_ALL}") + if not continue_on_error: + break + + # Показ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² + print(f"\n{Fore.GREEN}{'='*60}{Style.RESET_ALL}") + if len(urls_to_process) > 1: + print(f"{Fore.GREEN}ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°!{Style.RESET_ALL}") + print(f"{Fore.CYAN}УспСшно: {successful}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Ошибок: {failed}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ВсСго: {len(urls_to_process)}{Style.RESET_ALL}") + + if failed_urls: + print(f"\n{Fore.YELLOW}НСудачныС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:{Style.RESET_ALL}") + for url_item, error in failed_urls[:5]: # ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠ΅Ρ€Π²Ρ‹Π΅ 5 + short_url = url_item[:50] + "..." if len(url_item) > 50 else url_item + print(f" βœ— {short_url}: {error}") + if len(failed_urls) > 5: + print(f" ... ΠΈ Π΅Ρ‰Π΅ {len(failed_urls) - 5} ошибок") + else: + if successful > 0: + print(f"{Fore.GREEN}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°!{Style.RESET_ALL}") + else: + print(f"{Fore.RED}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ»Π°ΡΡŒ с ошибкой{Style.RESET_ALL}") + + output_path = output or app_config.get("output_directory", "downloads") + print(f"{Fore.CYAN}Π€Π°ΠΉΠ»Ρ‹ сохранСны Π²: {output_path}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{'='*60}{Style.RESET_ALL}") + + if failed > 0 and not continue_on_error: + sys.exit(1) + + except KeyboardInterrupt: + print(f"\n{Fore.YELLOW}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΏΡ€Π΅Ρ€Π²Π°Π½Π° ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΌ{Style.RESET_ALL}") + sys.exit(0) + except Exception as e: + print(f"\n{Fore.RED}ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° нСоТиданная ошибка: {str(e)}{Style.RESET_ALL}") + sys.exit(1) + +@click.group() +def cli(): + """YouTube Downloader - ΡƒΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ настройками ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ""" + pass + +@cli.command() +@click.option('--output-dir', default='downloads', help='Папка для Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--video-quality', default='best', help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--audio-format', default='mp3', help='Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π°ΡƒΠ΄ΠΈΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--video-format', default='mp4', help='Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +def configure(output_dir, video_quality, audio_format, video_format): + """Настройка ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ""" + config = Config() + + config.set('output_directory', output_dir) + config.set('video_quality', video_quality) + config.set('audio_format', audio_format) + config.set('video_format', video_format) + + config.save_config() + + print(f"{Fore.GREEN}ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ сохранСна:{Style.RESET_ALL}") + print(f" Папка Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ: {output_dir}") + print(f" ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {video_quality}") + print(f" Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π°ΡƒΠ΄ΠΈΠΎ: {audio_format}") + print(f" Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ: {video_format}") + +@cli.command() +def show_config(): + """ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки""" + config = Config() + + print(f"{Fore.CYAN}ВСкущая конфигурация:{Style.RESET_ALL}") + for key, value in config.config.items(): + print(f" {key}: {value}") + +if __name__ == '__main__': + # Если Π·Π°ΠΏΡƒΡ‰Π΅Π½ Π±Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΎΡΠ½ΠΎΠ²Π½ΡƒΡŽ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ + if len(sys.argv) == 1 or (len(sys.argv) > 1 and not sys.argv[1] in ['configure', 'show-config']): + main() + else: + cli() \ No newline at end of file diff --git a/.history/main_20250928090840.py b/.history/main_20250928090840.py new file mode 100644 index 0000000..1898b3f --- /dev/null +++ b/.history/main_20250928090840.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 +""" +YouTube Downloader - ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube +Автор: GitHub Copilot +""" + +import sys +import click +from colorama import Fore, Style +from downloader import YouTubeDownloader +from config import Config + +def load_urls_from_file(file_path): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ список URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π°""" + urls = [] + try: + with open(file_path, 'r', encoding='utf-8') as f: + for line_num, line in enumerate(f, 1): + line = line.strip() + # ΠŸΡ€ΠΎΠΏΡƒΡΠΊΠ°Π΅ΠΌ пустыС строки ΠΈ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ + if line and not line.startswith('#'): + urls.append(line) + print(f"Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ {len(urls)} URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π° {file_path}") + return urls + except Exception as e: + print(f"{Fore.RED}Ошибка чтСния Ρ„Π°ΠΉΠ»Π° {file_path}: {str(e)}{Style.RESET_ALL}") + return [] + +def show_video_info(downloader, url): + """ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ""" + video_info = downloader.get_video_info(url) + if video_info: + print(f"\n{Fore.GREEN}Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ:{Style.RESET_ALL}") + print(f"{Fore.YELLOW}НазваниС:{Style.RESET_ALL} {video_info['title']}") + print(f"{Fore.YELLOW}Автор:{Style.RESET_ALL} {video_info['uploader']}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.YELLOW}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ:{Style.RESET_ALL} {duration_str}") + + if video_info['view_count']: + print(f"{Fore.YELLOW}ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ΠΎΠ²:{Style.RESET_ALL} {video_info['view_count']:,}") + + if video_info['upload_date']: + date_str = video_info['upload_date'] + formatted_date = f"{date_str[6:8]}.{date_str[4:6]}.{date_str[:4]}" + print(f"{Fore.YELLOW}Π”Π°Ρ‚Π° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:{Style.RESET_ALL} {formatted_date}") + + if video_info['description']: + desc = video_info['description'][:200] + "..." if len(video_info['description']) > 200 else video_info['description'] + print(f"{Fore.YELLOW}ОписаниС:{Style.RESET_ALL} {desc}") + +def show_video_formats(downloader, url): + """ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ Π²ΠΈΠ΄Π΅ΠΎ""" + print(f"\n{Fore.GREEN}ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²...{Style.RESET_ALL}") + available_formats = downloader.get_available_formats(url) + if available_formats: + print(f"\n{Fore.GREEN}ДоступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹:{Style.RESET_ALL}") + for fmt in available_formats: + size_str = f" ({fmt['filesize']//1024//1024} MB)" if fmt['filesize'] else "" + print(f" β€’ {fmt['quality']} ({fmt['ext']}){size_str}") + else: + print(f"{Fore.RED}НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π°Ρ…{Style.RESET_ALL}") + +@click.command() +@click.argument('url', required=False) +@click.option('--quality', '-q', default='best', + help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ (best, 1080p, 720p, 480p, 360p)') +@click.option('--audio-only', '-a', is_flag=True, + help='Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ') +@click.option('--output', '-o', default=None, + help='Папка для сохранСния Ρ„Π°ΠΉΠ»ΠΎΠ²') +@click.option('--playlist', '-p', is_flag=True, + help='Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ вСсь плСйлист') +@click.option('--info', '-i', is_flag=True, + help='ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') +@click.option('--formats', '-f', is_flag=True, + help='ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹') +@click.option('--config', '-c', default='config.json', + help='ΠŸΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ') +@click.option('--batch', '-b', default=None, type=click.Path(exists=True), + help='Π€Π°ΠΉΠ» со списком URL для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') +@click.option('--urls', multiple=True, + help='НСсколько URL Ρ‡Π΅Ρ€Π΅Π· ΠΏΡ€ΠΎΠ±Π΅Π»') +@click.option('--continue-on-error', is_flag=True, + help='ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Ρ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ…') +def main(url, quality, audio_only, output, playlist, info, formats, config, batch, urls, continue_on_error): + """ + YouTube Downloader - скачиваниС Π²ΠΈΠ΄Π΅ΠΎ с YouTube + + URL: Бсылка Π½Π° YouTube Π²ΠΈΠ΄Π΅ΠΎ ΠΈΠ»ΠΈ плСйлист (ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ ΠΏΡ€ΠΈ использовании --batch ΠΈΠ»ΠΈ --urls) + """ + # Π’Ρ‹Π²ΠΎΠ΄ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ° + print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'YouTube Downloader':^60}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}") + + try: + # Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΈ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° + app_config = Config(config) + downloader = YouTubeDownloader(app_config) + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ список URL для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ + urls_to_process = [] + + if batch: + # ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° + print(f"{Fore.GREEN}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π°: {batch}{Style.RESET_ALL}") + urls_to_process = load_urls_from_file(batch) + elif urls: + # ΠœΠ½ΠΎΠΆΠ΅ΡΡ‚Π²Π΅Π½Π½Ρ‹Π΅ URL ΠΈΠ· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΠΎΠΉ строки + urls_to_process = list(urls) + print(f"{Fore.GREEN}ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° {len(urls_to_process)} URL ΠΈΠ· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΠΎΠΉ строки{Style.RESET_ALL}") + elif url: + # Один URL + urls_to_process = [url] + else: + print(f"{Fore.RED}Ошибка: НСобходимо ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ URL, --batch Ρ„Π°ΠΉΠ» ΠΈΠ»ΠΈ --urls{Style.RESET_ALL}") + sys.exit(1) + + # ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° списка URL + if info or formats: + # Для info ΠΈ formats ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ URL + process_single_url = urls_to_process[0] if urls_to_process else None + if not process_single_url: + print(f"{Fore.RED}Ошибка: НСт URL для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ{Style.RESET_ALL}") + sys.exit(1) + + if not downloader.validate_url(process_single_url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + sys.exit(1) + + if info: + show_video_info(downloader, process_single_url) + return + + if formats: + show_video_formats(downloader, process_single_url) + return + + # ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° + print(f"\n{Fore.GREEN}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΡƒ {len(urls_to_process)} URL...{Style.RESET_ALL}") + + successful = 0 + failed = 0 + failed_urls = [] + + for i, current_url in enumerate(urls_to_process, 1): + print(f"\n{Fore.CYAN}[{i}/{len(urls_to_process)}] ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ°: {current_url}{Style.RESET_ALL}") + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° URL + if not downloader.validate_url(current_url): + print(f"{Fore.RED}βœ— НСкоррСктный URL, пропускаСм{Style.RESET_ALL}") + failed += 1 + failed_urls.append((current_url, "НСкоррСктный URL")) + if not continue_on_error: + break + continue + + try: + success = False + if playlist: + success = downloader.download_playlist(current_url, quality, audio_only, output) + else: + success = downloader.download_video(current_url, quality, audio_only, output) + + if success: + successful += 1 + print(f"{Fore.GREEN}βœ“ [{i}/{len(urls_to_process)}] УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ{Style.RESET_ALL}") + else: + failed += 1 + failed_urls.append((current_url, "Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ")) + print(f"{Fore.RED}βœ— [{i}/{len(urls_to_process)}] Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ{Style.RESET_ALL}") + if not continue_on_error: + break + + except Exception as e: + failed += 1 + error_msg = str(e)[:100] + "..." if len(str(e)) > 100 else str(e) + failed_urls.append((current_url, error_msg)) + print(f"{Fore.RED}βœ— [{i}/{len(urls_to_process)}] Π˜ΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅: {error_msg}{Style.RESET_ALL}") + if not continue_on_error: + break + + # Показ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² + print(f"\n{Fore.GREEN}{'='*60}{Style.RESET_ALL}") + if len(urls_to_process) > 1: + print(f"{Fore.GREEN}ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°!{Style.RESET_ALL}") + print(f"{Fore.CYAN}УспСшно: {successful}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Ошибок: {failed}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ВсСго: {len(urls_to_process)}{Style.RESET_ALL}") + + if failed_urls: + print(f"\n{Fore.YELLOW}НСудачныС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:{Style.RESET_ALL}") + for url_item, error in failed_urls[:5]: # ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠ΅Ρ€Π²Ρ‹Π΅ 5 + short_url = url_item[:50] + "..." if len(url_item) > 50 else url_item + print(f" βœ— {short_url}: {error}") + if len(failed_urls) > 5: + print(f" ... ΠΈ Π΅Ρ‰Π΅ {len(failed_urls) - 5} ошибок") + else: + if successful > 0: + print(f"{Fore.GREEN}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°!{Style.RESET_ALL}") + else: + print(f"{Fore.RED}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ»Π°ΡΡŒ с ошибкой{Style.RESET_ALL}") + + output_path = output or app_config.get("output_directory", "downloads") + print(f"{Fore.CYAN}Π€Π°ΠΉΠ»Ρ‹ сохранСны Π²: {output_path}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{'='*60}{Style.RESET_ALL}") + + if failed > 0 and not continue_on_error: + sys.exit(1) + + except KeyboardInterrupt: + print(f"\n{Fore.YELLOW}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΏΡ€Π΅Ρ€Π²Π°Π½Π° ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΌ{Style.RESET_ALL}") + sys.exit(0) + except Exception as e: + print(f"\n{Fore.RED}ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° нСоТиданная ошибка: {str(e)}{Style.RESET_ALL}") + sys.exit(1) + +@click.group() +def cli(): + """YouTube Downloader - ΡƒΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ настройками ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ""" + pass + +@cli.command() +@click.option('--output-dir', default='downloads', help='Папка для Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--video-quality', default='best', help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--audio-format', default='mp3', help='Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π°ΡƒΠ΄ΠΈΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--video-format', default='mp4', help='Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +def configure(output_dir, video_quality, audio_format, video_format): + """Настройка ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ""" + config = Config() + + config.set('output_directory', output_dir) + config.set('video_quality', video_quality) + config.set('audio_format', audio_format) + config.set('video_format', video_format) + + config.save_config() + + print(f"{Fore.GREEN}ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ сохранСна:{Style.RESET_ALL}") + print(f" Папка Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ: {output_dir}") + print(f" ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {video_quality}") + print(f" Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π°ΡƒΠ΄ΠΈΠΎ: {audio_format}") + print(f" Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ: {video_format}") + +@cli.command() +def show_config(): + """ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки""" + config = Config() + + print(f"{Fore.CYAN}ВСкущая конфигурация:{Style.RESET_ALL}") + for key, value in config.config.items(): + print(f" {key}: {value}") + +if __name__ == '__main__': + # Если Π·Π°ΠΏΡƒΡ‰Π΅Π½ Π±Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΎΡΠ½ΠΎΠ²Π½ΡƒΡŽ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ + if len(sys.argv) == 1 or (len(sys.argv) > 1 and not sys.argv[1] in ['configure', 'show-config']): + main() + else: + cli() \ No newline at end of file diff --git a/.history/main_20250928091647.py b/.history/main_20250928091647.py new file mode 100644 index 0000000..1898b3f --- /dev/null +++ b/.history/main_20250928091647.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 +""" +YouTube Downloader - ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube +Автор: GitHub Copilot +""" + +import sys +import click +from colorama import Fore, Style +from downloader import YouTubeDownloader +from config import Config + +def load_urls_from_file(file_path): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ список URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π°""" + urls = [] + try: + with open(file_path, 'r', encoding='utf-8') as f: + for line_num, line in enumerate(f, 1): + line = line.strip() + # ΠŸΡ€ΠΎΠΏΡƒΡΠΊΠ°Π΅ΠΌ пустыС строки ΠΈ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ + if line and not line.startswith('#'): + urls.append(line) + print(f"Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ {len(urls)} URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π° {file_path}") + return urls + except Exception as e: + print(f"{Fore.RED}Ошибка чтСния Ρ„Π°ΠΉΠ»Π° {file_path}: {str(e)}{Style.RESET_ALL}") + return [] + +def show_video_info(downloader, url): + """ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ""" + video_info = downloader.get_video_info(url) + if video_info: + print(f"\n{Fore.GREEN}Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ:{Style.RESET_ALL}") + print(f"{Fore.YELLOW}НазваниС:{Style.RESET_ALL} {video_info['title']}") + print(f"{Fore.YELLOW}Автор:{Style.RESET_ALL} {video_info['uploader']}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.YELLOW}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ:{Style.RESET_ALL} {duration_str}") + + if video_info['view_count']: + print(f"{Fore.YELLOW}ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ΠΎΠ²:{Style.RESET_ALL} {video_info['view_count']:,}") + + if video_info['upload_date']: + date_str = video_info['upload_date'] + formatted_date = f"{date_str[6:8]}.{date_str[4:6]}.{date_str[:4]}" + print(f"{Fore.YELLOW}Π”Π°Ρ‚Π° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:{Style.RESET_ALL} {formatted_date}") + + if video_info['description']: + desc = video_info['description'][:200] + "..." if len(video_info['description']) > 200 else video_info['description'] + print(f"{Fore.YELLOW}ОписаниС:{Style.RESET_ALL} {desc}") + +def show_video_formats(downloader, url): + """ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ Π²ΠΈΠ΄Π΅ΠΎ""" + print(f"\n{Fore.GREEN}ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²...{Style.RESET_ALL}") + available_formats = downloader.get_available_formats(url) + if available_formats: + print(f"\n{Fore.GREEN}ДоступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹:{Style.RESET_ALL}") + for fmt in available_formats: + size_str = f" ({fmt['filesize']//1024//1024} MB)" if fmt['filesize'] else "" + print(f" β€’ {fmt['quality']} ({fmt['ext']}){size_str}") + else: + print(f"{Fore.RED}НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π°Ρ…{Style.RESET_ALL}") + +@click.command() +@click.argument('url', required=False) +@click.option('--quality', '-q', default='best', + help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ (best, 1080p, 720p, 480p, 360p)') +@click.option('--audio-only', '-a', is_flag=True, + help='Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ') +@click.option('--output', '-o', default=None, + help='Папка для сохранСния Ρ„Π°ΠΉΠ»ΠΎΠ²') +@click.option('--playlist', '-p', is_flag=True, + help='Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ вСсь плСйлист') +@click.option('--info', '-i', is_flag=True, + help='ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') +@click.option('--formats', '-f', is_flag=True, + help='ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹') +@click.option('--config', '-c', default='config.json', + help='ΠŸΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ') +@click.option('--batch', '-b', default=None, type=click.Path(exists=True), + help='Π€Π°ΠΉΠ» со списком URL для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') +@click.option('--urls', multiple=True, + help='НСсколько URL Ρ‡Π΅Ρ€Π΅Π· ΠΏΡ€ΠΎΠ±Π΅Π»') +@click.option('--continue-on-error', is_flag=True, + help='ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Ρ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ…') +def main(url, quality, audio_only, output, playlist, info, formats, config, batch, urls, continue_on_error): + """ + YouTube Downloader - скачиваниС Π²ΠΈΠ΄Π΅ΠΎ с YouTube + + URL: Бсылка Π½Π° YouTube Π²ΠΈΠ΄Π΅ΠΎ ΠΈΠ»ΠΈ плСйлист (ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ ΠΏΡ€ΠΈ использовании --batch ΠΈΠ»ΠΈ --urls) + """ + # Π’Ρ‹Π²ΠΎΠ΄ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ° + print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'YouTube Downloader':^60}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}") + + try: + # Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΈ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° + app_config = Config(config) + downloader = YouTubeDownloader(app_config) + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ список URL для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ + urls_to_process = [] + + if batch: + # ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° + print(f"{Fore.GREEN}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π°: {batch}{Style.RESET_ALL}") + urls_to_process = load_urls_from_file(batch) + elif urls: + # ΠœΠ½ΠΎΠΆΠ΅ΡΡ‚Π²Π΅Π½Π½Ρ‹Π΅ URL ΠΈΠ· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΠΎΠΉ строки + urls_to_process = list(urls) + print(f"{Fore.GREEN}ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° {len(urls_to_process)} URL ΠΈΠ· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΠΎΠΉ строки{Style.RESET_ALL}") + elif url: + # Один URL + urls_to_process = [url] + else: + print(f"{Fore.RED}Ошибка: НСобходимо ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ URL, --batch Ρ„Π°ΠΉΠ» ΠΈΠ»ΠΈ --urls{Style.RESET_ALL}") + sys.exit(1) + + # ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° списка URL + if info or formats: + # Для info ΠΈ formats ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ URL + process_single_url = urls_to_process[0] if urls_to_process else None + if not process_single_url: + print(f"{Fore.RED}Ошибка: НСт URL для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ{Style.RESET_ALL}") + sys.exit(1) + + if not downloader.validate_url(process_single_url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + sys.exit(1) + + if info: + show_video_info(downloader, process_single_url) + return + + if formats: + show_video_formats(downloader, process_single_url) + return + + # ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° + print(f"\n{Fore.GREEN}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΡƒ {len(urls_to_process)} URL...{Style.RESET_ALL}") + + successful = 0 + failed = 0 + failed_urls = [] + + for i, current_url in enumerate(urls_to_process, 1): + print(f"\n{Fore.CYAN}[{i}/{len(urls_to_process)}] ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ°: {current_url}{Style.RESET_ALL}") + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° URL + if not downloader.validate_url(current_url): + print(f"{Fore.RED}βœ— НСкоррСктный URL, пропускаСм{Style.RESET_ALL}") + failed += 1 + failed_urls.append((current_url, "НСкоррСктный URL")) + if not continue_on_error: + break + continue + + try: + success = False + if playlist: + success = downloader.download_playlist(current_url, quality, audio_only, output) + else: + success = downloader.download_video(current_url, quality, audio_only, output) + + if success: + successful += 1 + print(f"{Fore.GREEN}βœ“ [{i}/{len(urls_to_process)}] УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ{Style.RESET_ALL}") + else: + failed += 1 + failed_urls.append((current_url, "Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ")) + print(f"{Fore.RED}βœ— [{i}/{len(urls_to_process)}] Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ{Style.RESET_ALL}") + if not continue_on_error: + break + + except Exception as e: + failed += 1 + error_msg = str(e)[:100] + "..." if len(str(e)) > 100 else str(e) + failed_urls.append((current_url, error_msg)) + print(f"{Fore.RED}βœ— [{i}/{len(urls_to_process)}] Π˜ΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅: {error_msg}{Style.RESET_ALL}") + if not continue_on_error: + break + + # Показ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² + print(f"\n{Fore.GREEN}{'='*60}{Style.RESET_ALL}") + if len(urls_to_process) > 1: + print(f"{Fore.GREEN}ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°!{Style.RESET_ALL}") + print(f"{Fore.CYAN}УспСшно: {successful}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Ошибок: {failed}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ВсСго: {len(urls_to_process)}{Style.RESET_ALL}") + + if failed_urls: + print(f"\n{Fore.YELLOW}НСудачныС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:{Style.RESET_ALL}") + for url_item, error in failed_urls[:5]: # ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠ΅Ρ€Π²Ρ‹Π΅ 5 + short_url = url_item[:50] + "..." if len(url_item) > 50 else url_item + print(f" βœ— {short_url}: {error}") + if len(failed_urls) > 5: + print(f" ... ΠΈ Π΅Ρ‰Π΅ {len(failed_urls) - 5} ошибок") + else: + if successful > 0: + print(f"{Fore.GREEN}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°!{Style.RESET_ALL}") + else: + print(f"{Fore.RED}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ»Π°ΡΡŒ с ошибкой{Style.RESET_ALL}") + + output_path = output or app_config.get("output_directory", "downloads") + print(f"{Fore.CYAN}Π€Π°ΠΉΠ»Ρ‹ сохранСны Π²: {output_path}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{'='*60}{Style.RESET_ALL}") + + if failed > 0 and not continue_on_error: + sys.exit(1) + + except KeyboardInterrupt: + print(f"\n{Fore.YELLOW}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΏΡ€Π΅Ρ€Π²Π°Π½Π° ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΌ{Style.RESET_ALL}") + sys.exit(0) + except Exception as e: + print(f"\n{Fore.RED}ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° нСоТиданная ошибка: {str(e)}{Style.RESET_ALL}") + sys.exit(1) + +@click.group() +def cli(): + """YouTube Downloader - ΡƒΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ настройками ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ""" + pass + +@cli.command() +@click.option('--output-dir', default='downloads', help='Папка для Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--video-quality', default='best', help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--audio-format', default='mp3', help='Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π°ΡƒΠ΄ΠΈΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--video-format', default='mp4', help='Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +def configure(output_dir, video_quality, audio_format, video_format): + """Настройка ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ""" + config = Config() + + config.set('output_directory', output_dir) + config.set('video_quality', video_quality) + config.set('audio_format', audio_format) + config.set('video_format', video_format) + + config.save_config() + + print(f"{Fore.GREEN}ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ сохранСна:{Style.RESET_ALL}") + print(f" Папка Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ: {output_dir}") + print(f" ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {video_quality}") + print(f" Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π°ΡƒΠ΄ΠΈΠΎ: {audio_format}") + print(f" Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ: {video_format}") + +@cli.command() +def show_config(): + """ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки""" + config = Config() + + print(f"{Fore.CYAN}ВСкущая конфигурация:{Style.RESET_ALL}") + for key, value in config.config.items(): + print(f" {key}: {value}") + +if __name__ == '__main__': + # Если Π·Π°ΠΏΡƒΡ‰Π΅Π½ Π±Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΎΡΠ½ΠΎΠ²Π½ΡƒΡŽ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ + if len(sys.argv) == 1 or (len(sys.argv) > 1 and not sys.argv[1] in ['configure', 'show-config']): + main() + else: + cli() \ No newline at end of file diff --git a/.history/requirements_20250928083659.txt b/.history/requirements_20250928083659.txt new file mode 100644 index 0000000..053c45b --- /dev/null +++ b/.history/requirements_20250928083659.txt @@ -0,0 +1,4 @@ +yt-dlp==2023.12.30 +click==8.1.7 +tqdm==4.66.1 +colorama==0.4.6 \ No newline at end of file diff --git a/.history/requirements_20250928085321.txt b/.history/requirements_20250928085321.txt new file mode 100644 index 0000000..053c45b --- /dev/null +++ b/.history/requirements_20250928085321.txt @@ -0,0 +1,4 @@ +yt-dlp==2023.12.30 +click==8.1.7 +tqdm==4.66.1 +colorama==0.4.6 \ No newline at end of file diff --git a/.history/requirements_20250928085551.txt b/.history/requirements_20250928085551.txt new file mode 100644 index 0000000..4e6a7bf --- /dev/null +++ b/.history/requirements_20250928085551.txt @@ -0,0 +1,6 @@ +yt-dlp>=2024.1.1 +click==8.1.7 +tqdm==4.66.1 +colorama==0.4.6 +requests>=2.31.0 +fake-useragent>=1.4.0 \ No newline at end of file diff --git a/.history/requirements_20250928090643.txt b/.history/requirements_20250928090643.txt new file mode 100644 index 0000000..4e6a7bf --- /dev/null +++ b/.history/requirements_20250928090643.txt @@ -0,0 +1,6 @@ +yt-dlp>=2024.1.1 +click==8.1.7 +tqdm==4.66.1 +colorama==0.4.6 +requests>=2.31.0 +fake-useragent>=1.4.0 \ No newline at end of file diff --git a/.history/start_20250928085105.sh b/.history/start_20250928085105.sh new file mode 100644 index 0000000..ad55872 --- /dev/null +++ b/.history/start_20250928085105.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# YouTube Downloader Quick Start Script + +echo "🎬 YouTube Downloader - Быстрый старт" +echo "========================================" + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Python +if ! command -v python3 &> /dev/null; then + echo "❌ Python3 Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. УстановитС Python 3.7 ΠΈΠ»ΠΈ Π½ΠΎΠ²Π΅Π΅." + exit 1 +fi + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° зависимостСй +if ! python3 -c "import yt_dlp" 2>/dev/null; then + echo "πŸ“¦ Установка зависимостСй..." + pip3 install -r requirements.txt +fi + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΠ°ΠΏΠΊΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +mkdir -p downloads + +echo "" +echo "βœ… Π“ΠΎΡ‚ΠΎΠ²ΠΎ! ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" +echo "" +echo "πŸ“Ή Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID" +echo "" +echo "🎡 Волько Π°ΡƒΠ΄ΠΈΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only" +echo "" +echo "πŸ“‹ Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info" +echo "" +echo "βš™οΈ Настройка:" +echo " python3 main.py configure" +echo "" +echo "πŸ“š Π‘ΠΏΡ€Π°Π²ΠΊΠ°:" +echo " python3 main.py --help" +echo "" +echo "πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" +echo " make usage" +echo "" + +# ВСст Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ +echo "πŸ§ͺ ВСст систСмы..." +if python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation:', d.validate_url('https://www.youtube.com/watch?v=test'))" 2>/dev/null; then + echo "βœ… БистСма Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎ!" +else + echo "❌ ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с систСмой. ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ зависимости." +fi + +echo "" +echo "🎯 ΠŸΡ€ΠΈΠΌΠ΅Ρ€ использования:" +echo " python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --info" \ No newline at end of file diff --git a/.history/start_20250928085321.sh b/.history/start_20250928085321.sh new file mode 100644 index 0000000..ad55872 --- /dev/null +++ b/.history/start_20250928085321.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# YouTube Downloader Quick Start Script + +echo "🎬 YouTube Downloader - Быстрый старт" +echo "========================================" + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Python +if ! command -v python3 &> /dev/null; then + echo "❌ Python3 Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. УстановитС Python 3.7 ΠΈΠ»ΠΈ Π½ΠΎΠ²Π΅Π΅." + exit 1 +fi + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° зависимостСй +if ! python3 -c "import yt_dlp" 2>/dev/null; then + echo "πŸ“¦ Установка зависимостСй..." + pip3 install -r requirements.txt +fi + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΠ°ΠΏΠΊΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +mkdir -p downloads + +echo "" +echo "βœ… Π“ΠΎΡ‚ΠΎΠ²ΠΎ! ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" +echo "" +echo "πŸ“Ή Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID" +echo "" +echo "🎡 Волько Π°ΡƒΠ΄ΠΈΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only" +echo "" +echo "πŸ“‹ Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info" +echo "" +echo "βš™οΈ Настройка:" +echo " python3 main.py configure" +echo "" +echo "πŸ“š Π‘ΠΏΡ€Π°Π²ΠΊΠ°:" +echo " python3 main.py --help" +echo "" +echo "πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" +echo " make usage" +echo "" + +# ВСст Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ +echo "πŸ§ͺ ВСст систСмы..." +if python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation:', d.validate_url('https://www.youtube.com/watch?v=test'))" 2>/dev/null; then + echo "βœ… БистСма Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎ!" +else + echo "❌ ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с систСмой. ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ зависимости." +fi + +echo "" +echo "🎯 ΠŸΡ€ΠΈΠΌΠ΅Ρ€ использования:" +echo " python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --info" \ No newline at end of file diff --git a/.history/start_20250928090541.sh b/.history/start_20250928090541.sh new file mode 100644 index 0000000..b782683 --- /dev/null +++ b/.history/start_20250928090541.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# YouTube Downloader Quick Start Script + +echo "🎬 YouTube Downloader - Быстрый старт" +echo "========================================" + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Python +if ! command -v python3 &> /dev/null; then + echo "❌ Python3 Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. УстановитС Python 3.7 ΠΈΠ»ΠΈ Π½ΠΎΠ²Π΅Π΅." + exit 1 +fi + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° зависимостСй +if ! python3 -c "import yt_dlp" 2>/dev/null; then + echo "πŸ“¦ Установка зависимостСй..." + pip3 install -r requirements.txt +fi + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ffmpeg +if ! command -v ffmpeg &> /dev/null; then + echo "⚠️ ffmpeg Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. Для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ Π² MP3 установитС ffmpeg:" + echo " ./install_ffmpeg.sh" +fi + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΠ°ΠΏΠΊΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +mkdir -p downloads + +echo "" +echo "βœ… Π“ΠΎΡ‚ΠΎΠ²ΠΎ! ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" +echo "" +echo "πŸ“Ή Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID" +echo "" +echo "🎡 Волько Π°ΡƒΠ΄ΠΈΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only" +echo "" +echo "πŸ“‹ Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info" +echo "" +echo "βš™οΈ Настройка:" +echo " python3 main.py configure" +echo "" +echo "πŸ“š Π‘ΠΏΡ€Π°Π²ΠΊΠ°:" +echo " python3 main.py --help" +echo "" +echo "πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" +echo " make usage" +echo "" + +# ВСст Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ +echo "πŸ§ͺ ВСст систСмы..." +if python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation:', d.validate_url('https://www.youtube.com/watch?v=test'))" 2>/dev/null; then + echo "βœ… БистСма Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎ!" +else + echo "❌ ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с систСмой. ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ зависимости." +fi + +echo "" +echo "🎯 ΠŸΡ€ΠΈΠΌΠ΅Ρ€ использования:" +echo " python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --info" \ No newline at end of file diff --git a/.history/start_20250928090643.sh b/.history/start_20250928090643.sh new file mode 100644 index 0000000..b782683 --- /dev/null +++ b/.history/start_20250928090643.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# YouTube Downloader Quick Start Script + +echo "🎬 YouTube Downloader - Быстрый старт" +echo "========================================" + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Python +if ! command -v python3 &> /dev/null; then + echo "❌ Python3 Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. УстановитС Python 3.7 ΠΈΠ»ΠΈ Π½ΠΎΠ²Π΅Π΅." + exit 1 +fi + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° зависимостСй +if ! python3 -c "import yt_dlp" 2>/dev/null; then + echo "πŸ“¦ Установка зависимостСй..." + pip3 install -r requirements.txt +fi + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ffmpeg +if ! command -v ffmpeg &> /dev/null; then + echo "⚠️ ffmpeg Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. Для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ Π² MP3 установитС ffmpeg:" + echo " ./install_ffmpeg.sh" +fi + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΠ°ΠΏΠΊΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +mkdir -p downloads + +echo "" +echo "βœ… Π“ΠΎΡ‚ΠΎΠ²ΠΎ! ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" +echo "" +echo "πŸ“Ή Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID" +echo "" +echo "🎡 Волько Π°ΡƒΠ΄ΠΈΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only" +echo "" +echo "πŸ“‹ Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info" +echo "" +echo "βš™οΈ Настройка:" +echo " python3 main.py configure" +echo "" +echo "πŸ“š Π‘ΠΏΡ€Π°Π²ΠΊΠ°:" +echo " python3 main.py --help" +echo "" +echo "πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" +echo " make usage" +echo "" + +# ВСст Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ +echo "πŸ§ͺ ВСст систСмы..." +if python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation:', d.validate_url('https://www.youtube.com/watch?v=test'))" 2>/dev/null; then + echo "βœ… БистСма Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎ!" +else + echo "❌ ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с систСмой. ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ зависимости." +fi + +echo "" +echo "🎯 ΠŸΡ€ΠΈΠΌΠ΅Ρ€ использования:" +echo " python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --info" \ No newline at end of file diff --git a/.history/utils_20250928084712.py b/.history/utils_20250928084712.py new file mode 100644 index 0000000..66e306b --- /dev/null +++ b/.history/utils_20250928084712.py @@ -0,0 +1,211 @@ +""" +Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ для YouTube Downloader +""" + +import os +import json +import argparse +from datetime import datetime +from config import Config +from downloader import YouTubeDownloader + +class DownloadLogger: + """Класс для логирования Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + + def __init__(self, log_file="download_history.json"): + self.log_file = log_file + self.history = self.load_history() + + def load_history(self): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + if os.path.exists(self.log_file): + try: + with open(self.log_file, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, IOError): + return [] + return [] + + def save_history(self): + """БохраняСт ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + try: + with open(self.log_file, 'w', encoding='utf-8') as f: + json.dump(self.history, f, indent=2, ensure_ascii=False) + except IOError as e: + print(f"Ошибка сохранСния истории: {e}") + + def add_download(self, url, title, success=True, error=None): + """ДобавляСт запись ΠΎ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅""" + record = { + 'timestamp': datetime.now().isoformat(), + 'url': url, + 'title': title, + 'success': success, + 'error': error + } + self.history.append(record) + self.save_history() + + def get_recent_downloads(self, count=10): + """Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ послСдниС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + return self.history[-count:] if len(self.history) >= count else self.history + + def get_failed_downloads(self): + """Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹Π΅ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + return [record for record in self.history if not record['success']] + +class BatchDownloader: + """Класс для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со ссылками""" + + def __init__(self, config=None): + self.config = config or Config() + self.downloader = YouTubeDownloader(self.config) + self.logger = DownloadLogger() + + def download_from_file(self, file_path, quality='best', audio_only=False): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ всС URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π°""" + if not os.path.exists(file_path): + print(f"Π€Π°ΠΉΠ» {file_path} Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½") + return False + + with open(file_path, 'r', encoding='utf-8') as f: + urls = [line.strip() for line in f if line.strip() and not line.startswith('#')] + + print(f"НайдСно {len(urls)} URL для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ") + + successful = 0 + failed = 0 + + for i, url in enumerate(urls, 1): + print(f"\n[{i}/{len(urls)}] Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: {url}") + + try: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + info = self.downloader.get_video_info(url) + title = info['title'] if info else 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅' + + # Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ + success = self.downloader.download_video(url, quality, audio_only) + + if success: + successful += 1 + self.logger.add_download(url, title, True) + print(f"βœ“ УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ: {title}") + else: + failed += 1 + self.logger.add_download(url, title, False, "Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ") + print(f"βœ— Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {title}") + + except Exception as e: + failed += 1 + self.logger.add_download(url, "НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅", False, str(e)) + print(f"βœ— Π˜ΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ {url}: {str(e)}") + + print(f"\nΠ Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:") + print(f"УспСшно: {successful}") + print(f"Ошибок: {failed}") + print(f"ВсСго: {len(urls)}") + + return failed == 0 + +def create_url_list_template(): + """Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ шаблон Ρ„Π°ΠΉΠ»Π° со ссылками""" + template = """# YouTube URL List Template +# Π”ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ ΠΏΠΎ ΠΎΠ΄Π½ΠΎΠΉ ссылкС YouTube Π½Π° ΠΊΠ°ΠΆΠ΄ΠΎΠΉ строкС +# Π‘Ρ‚Ρ€ΠΎΠΊΠΈ, Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰ΠΈΠ΅ΡΡ с #, ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ (ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ) + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π²ΠΈΠ΄Π΅ΠΎ: +# https://www.youtube.com/watch?v=dQw4w9WgXcQ + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ плСйлиста: +# https://www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMt6VEY + +# Π”ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ ваши ссылки Π½ΠΈΠΆΠ΅: +""" + + with open('urls.txt', 'w', encoding='utf-8') as f: + f.write(template) + + print("Π‘ΠΎΠ·Π΄Π°Π½ Ρ„Π°ΠΉΠ» urls.txt с шаблоном ссылок") + +def show_download_history(): + """ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + logger = DownloadLogger() + recent = logger.get_recent_downloads(20) + + if not recent: + print("Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ пуста") + return + + print("ПослСдниС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:") + print("-" * 80) + + for record in recent: + timestamp = record['timestamp'][:19] # Π£Π±ΠΈΡ€Π°Π΅ΠΌ микросСкунды + status = "βœ“" if record['success'] else "βœ—" + title = record['title'][:50] + "..." if len(record['title']) > 50 else record['title'] + print(f"{status} {timestamp} | {title}") + if not record['success'] and record.get('error'): + print(f" Ошибка: {record['error']}") + +def clean_downloads_folder(): + """ΠžΡ‡ΠΈΡ‰Π°Π΅Ρ‚ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + config = Config() + downloads_dir = config.get('output_directory', 'downloads') + + if not os.path.exists(downloads_dir): + print(f"Папка {downloads_dir} Π½Π΅ сущСствуСт") + return + + files = os.listdir(downloads_dir) + if not files: + print(f"Папка {downloads_dir} ΡƒΠΆΠ΅ пуста") + return + + print(f"НайдСно {len(files)} Ρ„Π°ΠΉΠ»ΠΎΠ² Π² {downloads_dir}") + confirm = input("Π£Π΄Π°Π»ΠΈΡ‚ΡŒ всС Ρ„Π°ΠΉΠ»Ρ‹? (yes/no): ") + + if confirm.lower() in ['yes', 'y', 'Π΄Π°', 'Π΄']: + for file in files: + file_path = os.path.join(downloads_dir, file) + try: + if os.path.isfile(file_path): + os.remove(file_path) + print(f"Π£Π΄Π°Π»Π΅Π½: {file}") + except Exception as e: + print(f"Ошибка удалСния {file}: {e}") + print("ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°") + else: + print("ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° ΠΎΡ‚ΠΌΠ΅Π½Π΅Π½Π°") + +def main(): + parser = argparse.ArgumentParser(description='YouTube Downloader - Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹') + parser.add_argument('action', choices=[ + 'batch', 'history', 'clean', 'template' + ], help='ДСйствиС для выполнСния') + + parser.add_argument('--file', '-f', help='Π€Π°ΠΉΠ» со ссылками для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') + parser.add_argument('--quality', '-q', default='best', help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ') + parser.add_argument('--audio-only', '-a', action='store_true', help='Волько Π°ΡƒΠ΄ΠΈΠΎ') + + args = parser.parse_args() + + if args.action == 'batch': + if not args.file: + print("Для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ» со ссылками (--file)") + return + + batch = BatchDownloader() + batch.download_from_file(args.file, args.quality, args.audio_only) + + elif args.action == 'history': + show_download_history() + + elif args.action == 'clean': + clean_downloads_folder() + + elif args.action == 'template': + create_url_list_template() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/.history/utils_20250928084729.py b/.history/utils_20250928084729.py new file mode 100644 index 0000000..445022f --- /dev/null +++ b/.history/utils_20250928084729.py @@ -0,0 +1,211 @@ +""" +Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ для YouTube Downloader +""" + +import os +import json +import argparse +from datetime import datetime +from config import Config +from downloader import YouTubeDownloader + +class DownloadLogger: + """Класс для логирования Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + + def __init__(self, log_file="download_history.json"): + self.log_file = log_file + self.history = self.load_history() + + def load_history(self): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + if os.path.exists(self.log_file): + try: + with open(self.log_file, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, IOError): + return [] + return [] + + def save_history(self): + """БохраняСт ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + try: + with open(self.log_file, 'w', encoding='utf-8') as f: + json.dump(self.history, f, indent=2, ensure_ascii=False) + except IOError as e: + print(f"Ошибка сохранСния истории: {e}") + + def add_download(self, url, title, success=True, error=None): + """ДобавляСт запись ΠΎ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅""" + record = { + 'timestamp': datetime.now().isoformat(), + 'url': url, + 'title': title, + 'success': success, + 'error': error + } + self.history.append(record) + self.save_history() + + def get_recent_downloads(self, count=10): + """Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ послСдниС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + return self.history[-count:] if len(self.history) >= count else self.history + + def get_failed_downloads(self): + """Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹Π΅ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + return [record for record in self.history if not record['success']] + +class BatchDownloader: + """Класс для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со ссылками""" + + def __init__(self, config=None): + self.config = config or Config() + self.downloader = YouTubeDownloader(self.config) + self.logger = DownloadLogger() + + def download_from_file(self, file_path, quality='best', audio_only=False): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ всС URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π°""" + if not os.path.exists(file_path): + print(f"Π€Π°ΠΉΠ» {file_path} Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½") + return False + + with open(file_path, 'r', encoding='utf-8') as f: + urls = [line.strip() for line in f if line.strip() and not line.startswith('#')] + + print(f"НайдСно {len(urls)} URL для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ") + + successful = 0 + failed = 0 + + for i, url in enumerate(urls, 1): + print(f"\n[{i}/{len(urls)}] Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: {url}") + + try: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + info = self.downloader.get_video_info(url) + title = info['title'] if info else 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅' + + # Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ + success = self.downloader.download_video(url, quality, audio_only) + + if success: + successful += 1 + self.logger.add_download(url, title, True) + print(f"βœ“ УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ: {title}") + else: + failed += 1 + self.logger.add_download(url, title, False, "Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ") + print(f"βœ— Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {title}") + + except Exception as e: + failed += 1 + self.logger.add_download(url, "НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅", False, str(e)) + print(f"βœ— Π˜ΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ {url}: {str(e)}") + + print(f"\nΠ Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:") + print(f"УспСшно: {successful}") + print(f"Ошибок: {failed}") + print(f"ВсСго: {len(urls)}") + + return failed == 0 + +def create_url_list_template(): + """Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ шаблон Ρ„Π°ΠΉΠ»Π° со ссылками""" + template = """# YouTube URL List Template +# Π”ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ ΠΏΠΎ ΠΎΠ΄Π½ΠΎΠΉ ссылкС YouTube Π½Π° ΠΊΠ°ΠΆΠ΄ΠΎΠΉ строкС +# Π‘Ρ‚Ρ€ΠΎΠΊΠΈ, Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰ΠΈΠ΅ΡΡ с #, ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ (ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ) + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π²ΠΈΠ΄Π΅ΠΎ: +# https://www.youtube.com/watch?v=dQw4w9WgXcQ + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ плСйлиста: +# https://www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMt6VEY + +# Π”ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ ваши ссылки Π½ΠΈΠΆΠ΅: +""" + + with open('urls.txt', 'w', encoding='utf-8') as f: + f.write(template) + + print("Π‘ΠΎΠ·Π΄Π°Π½ Ρ„Π°ΠΉΠ» urls.txt с шаблоном ссылок") + +def show_download_history(): + """ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + logger = DownloadLogger() + recent = logger.get_recent_downloads(20) + + if not recent: + print("Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ пуста") + return + + print("ПослСдниС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:") + print("-" * 80) + + for record in recent: + timestamp = record['timestamp'][:19] # Π£Π±ΠΈΡ€Π°Π΅ΠΌ микросСкунды + status = "βœ“" if record['success'] else "βœ—" + title = record['title'][:50] + "..." if len(record['title']) > 50 else record['title'] + print(f"{status} {timestamp} | {title}") + if not record['success'] and record.get('error'): + print(f" Ошибка: {record['error']}") + +def clean_downloads_folder(): + """ΠžΡ‡ΠΈΡ‰Π°Π΅Ρ‚ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + config = Config() + downloads_dir = config.get('output_directory', 'downloads') + + if not downloads_dir or not os.path.exists(downloads_dir): + print(f"Папка {downloads_dir} Π½Π΅ сущСствуСт") + return + + files = os.listdir(downloads_dir) + if not files: + print(f"Папка {downloads_dir} ΡƒΠΆΠ΅ пуста") + return + + print(f"НайдСно {len(files)} Ρ„Π°ΠΉΠ»ΠΎΠ² Π² {downloads_dir}") + confirm = input("Π£Π΄Π°Π»ΠΈΡ‚ΡŒ всС Ρ„Π°ΠΉΠ»Ρ‹? (yes/no): ") + + if confirm.lower() in ['yes', 'y', 'Π΄Π°', 'Π΄']: + for file in files: + file_path = os.path.join(str(downloads_dir), file) + try: + if os.path.isfile(file_path): + os.remove(file_path) + print(f"Π£Π΄Π°Π»Π΅Π½: {file}") + except Exception as e: + print(f"Ошибка удалСния {file}: {e}") + print("ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°") + else: + print("ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° ΠΎΡ‚ΠΌΠ΅Π½Π΅Π½Π°") + +def main(): + parser = argparse.ArgumentParser(description='YouTube Downloader - Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹') + parser.add_argument('action', choices=[ + 'batch', 'history', 'clean', 'template' + ], help='ДСйствиС для выполнСния') + + parser.add_argument('--file', '-f', help='Π€Π°ΠΉΠ» со ссылками для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') + parser.add_argument('--quality', '-q', default='best', help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ') + parser.add_argument('--audio-only', '-a', action='store_true', help='Волько Π°ΡƒΠ΄ΠΈΠΎ') + + args = parser.parse_args() + + if args.action == 'batch': + if not args.file: + print("Для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ» со ссылками (--file)") + return + + batch = BatchDownloader() + batch.download_from_file(args.file, args.quality, args.audio_only) + + elif args.action == 'history': + show_download_history() + + elif args.action == 'clean': + clean_downloads_folder() + + elif args.action == 'template': + create_url_list_template() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/.history/utils_20250928085321.py b/.history/utils_20250928085321.py new file mode 100644 index 0000000..445022f --- /dev/null +++ b/.history/utils_20250928085321.py @@ -0,0 +1,211 @@ +""" +Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ для YouTube Downloader +""" + +import os +import json +import argparse +from datetime import datetime +from config import Config +from downloader import YouTubeDownloader + +class DownloadLogger: + """Класс для логирования Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + + def __init__(self, log_file="download_history.json"): + self.log_file = log_file + self.history = self.load_history() + + def load_history(self): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + if os.path.exists(self.log_file): + try: + with open(self.log_file, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, IOError): + return [] + return [] + + def save_history(self): + """БохраняСт ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + try: + with open(self.log_file, 'w', encoding='utf-8') as f: + json.dump(self.history, f, indent=2, ensure_ascii=False) + except IOError as e: + print(f"Ошибка сохранСния истории: {e}") + + def add_download(self, url, title, success=True, error=None): + """ДобавляСт запись ΠΎ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅""" + record = { + 'timestamp': datetime.now().isoformat(), + 'url': url, + 'title': title, + 'success': success, + 'error': error + } + self.history.append(record) + self.save_history() + + def get_recent_downloads(self, count=10): + """Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ послСдниС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + return self.history[-count:] if len(self.history) >= count else self.history + + def get_failed_downloads(self): + """Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹Π΅ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + return [record for record in self.history if not record['success']] + +class BatchDownloader: + """Класс для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со ссылками""" + + def __init__(self, config=None): + self.config = config or Config() + self.downloader = YouTubeDownloader(self.config) + self.logger = DownloadLogger() + + def download_from_file(self, file_path, quality='best', audio_only=False): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ всС URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π°""" + if not os.path.exists(file_path): + print(f"Π€Π°ΠΉΠ» {file_path} Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½") + return False + + with open(file_path, 'r', encoding='utf-8') as f: + urls = [line.strip() for line in f if line.strip() and not line.startswith('#')] + + print(f"НайдСно {len(urls)} URL для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ") + + successful = 0 + failed = 0 + + for i, url in enumerate(urls, 1): + print(f"\n[{i}/{len(urls)}] Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: {url}") + + try: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + info = self.downloader.get_video_info(url) + title = info['title'] if info else 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅' + + # Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ + success = self.downloader.download_video(url, quality, audio_only) + + if success: + successful += 1 + self.logger.add_download(url, title, True) + print(f"βœ“ УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ: {title}") + else: + failed += 1 + self.logger.add_download(url, title, False, "Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ") + print(f"βœ— Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {title}") + + except Exception as e: + failed += 1 + self.logger.add_download(url, "НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅", False, str(e)) + print(f"βœ— Π˜ΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ {url}: {str(e)}") + + print(f"\nΠ Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:") + print(f"УспСшно: {successful}") + print(f"Ошибок: {failed}") + print(f"ВсСго: {len(urls)}") + + return failed == 0 + +def create_url_list_template(): + """Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ шаблон Ρ„Π°ΠΉΠ»Π° со ссылками""" + template = """# YouTube URL List Template +# Π”ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ ΠΏΠΎ ΠΎΠ΄Π½ΠΎΠΉ ссылкС YouTube Π½Π° ΠΊΠ°ΠΆΠ΄ΠΎΠΉ строкС +# Π‘Ρ‚Ρ€ΠΎΠΊΠΈ, Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰ΠΈΠ΅ΡΡ с #, ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ (ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ) + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π²ΠΈΠ΄Π΅ΠΎ: +# https://www.youtube.com/watch?v=dQw4w9WgXcQ + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ плСйлиста: +# https://www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMt6VEY + +# Π”ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ ваши ссылки Π½ΠΈΠΆΠ΅: +""" + + with open('urls.txt', 'w', encoding='utf-8') as f: + f.write(template) + + print("Π‘ΠΎΠ·Π΄Π°Π½ Ρ„Π°ΠΉΠ» urls.txt с шаблоном ссылок") + +def show_download_history(): + """ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + logger = DownloadLogger() + recent = logger.get_recent_downloads(20) + + if not recent: + print("Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ пуста") + return + + print("ПослСдниС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:") + print("-" * 80) + + for record in recent: + timestamp = record['timestamp'][:19] # Π£Π±ΠΈΡ€Π°Π΅ΠΌ микросСкунды + status = "βœ“" if record['success'] else "βœ—" + title = record['title'][:50] + "..." if len(record['title']) > 50 else record['title'] + print(f"{status} {timestamp} | {title}") + if not record['success'] and record.get('error'): + print(f" Ошибка: {record['error']}") + +def clean_downloads_folder(): + """ΠžΡ‡ΠΈΡ‰Π°Π΅Ρ‚ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + config = Config() + downloads_dir = config.get('output_directory', 'downloads') + + if not downloads_dir or not os.path.exists(downloads_dir): + print(f"Папка {downloads_dir} Π½Π΅ сущСствуСт") + return + + files = os.listdir(downloads_dir) + if not files: + print(f"Папка {downloads_dir} ΡƒΠΆΠ΅ пуста") + return + + print(f"НайдСно {len(files)} Ρ„Π°ΠΉΠ»ΠΎΠ² Π² {downloads_dir}") + confirm = input("Π£Π΄Π°Π»ΠΈΡ‚ΡŒ всС Ρ„Π°ΠΉΠ»Ρ‹? (yes/no): ") + + if confirm.lower() in ['yes', 'y', 'Π΄Π°', 'Π΄']: + for file in files: + file_path = os.path.join(str(downloads_dir), file) + try: + if os.path.isfile(file_path): + os.remove(file_path) + print(f"Π£Π΄Π°Π»Π΅Π½: {file}") + except Exception as e: + print(f"Ошибка удалСния {file}: {e}") + print("ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°") + else: + print("ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° ΠΎΡ‚ΠΌΠ΅Π½Π΅Π½Π°") + +def main(): + parser = argparse.ArgumentParser(description='YouTube Downloader - Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹') + parser.add_argument('action', choices=[ + 'batch', 'history', 'clean', 'template' + ], help='ДСйствиС для выполнСния') + + parser.add_argument('--file', '-f', help='Π€Π°ΠΉΠ» со ссылками для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') + parser.add_argument('--quality', '-q', default='best', help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ') + parser.add_argument('--audio-only', '-a', action='store_true', help='Волько Π°ΡƒΠ΄ΠΈΠΎ') + + args = parser.parse_args() + + if args.action == 'batch': + if not args.file: + print("Для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ» со ссылками (--file)") + return + + batch = BatchDownloader() + batch.download_from_file(args.file, args.quality, args.audio_only) + + elif args.action == 'history': + show_download_history() + + elif args.action == 'clean': + clean_downloads_folder() + + elif args.action == 'template': + create_url_list_template() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/.history/utils_20250928090908.py b/.history/utils_20250928090908.py new file mode 100644 index 0000000..d9652fe --- /dev/null +++ b/.history/utils_20250928090908.py @@ -0,0 +1,211 @@ +""" +Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ для YouTube Downloader +""" + +import os +import json +import argparse +from datetime import datetime +from config import Config +from downloader import YouTubeDownloader + +class DownloadLogger: + """Класс для логирования Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + + def __init__(self, log_file="download_history.json"): + self.log_file = log_file + self.history = self.load_history() + + def load_history(self): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + if os.path.exists(self.log_file): + try: + with open(self.log_file, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, IOError): + return [] + return [] + + def save_history(self): + """БохраняСт ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + try: + with open(self.log_file, 'w', encoding='utf-8') as f: + json.dump(self.history, f, indent=2, ensure_ascii=False) + except IOError as e: + print(f"Ошибка сохранСния истории: {e}") + + def add_download(self, url, title, success=True, error=None): + """ДобавляСт запись ΠΎ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅""" + record = { + 'timestamp': datetime.now().isoformat(), + 'url': url, + 'title': title, + 'success': success, + 'error': error + } + self.history.append(record) + self.save_history() + + def get_recent_downloads(self, count=10): + """Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ послСдниС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + return self.history[-count:] if len(self.history) >= count else self.history + + def get_failed_downloads(self): + """Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹Π΅ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + return [record for record in self.history if not record['success']] + +class BatchDownloader: + """Класс для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со ссылками""" + + def __init__(self, config=None): + self.config = config or Config() + self.downloader = YouTubeDownloader(self.config) + self.logger = DownloadLogger() + + def download_from_file(self, file_path, quality='best', audio_only=False): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ всС URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π°""" + if not os.path.exists(file_path): + print(f"Π€Π°ΠΉΠ» {file_path} Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½") + return False + + with open(file_path, 'r', encoding='utf-8') as f: + urls = [line.strip() for line in f if line.strip() and not line.startswith('#')] + + print(f"НайдСно {len(urls)} URL для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ") + + successful = 0 + failed = 0 + + for i, url in enumerate(urls, 1): + print(f"\n[{i}/{len(urls)}] Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: {url}") + + try: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + info = self.downloader.get_video_info(url) + title = info['title'] if info else 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅' + + # Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ + success = self.downloader.download_video(url, quality, audio_only) + + if success: + successful += 1 + self.logger.add_download(url, title, True) + print(f"βœ“ УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ: {title}") + else: + failed += 1 + self.logger.add_download(url, title, False, "Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ") + print(f"βœ— Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {title}") + + except Exception as e: + failed += 1 + self.logger.add_download(url, "НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅", False, str(e)) + print(f"βœ— Π˜ΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ {url}: {str(e)}") + + print(f"\nΠ Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:") + print(f"УспСшно: {successful}") + print(f"Ошибок: {failed}") + print(f"ВсСго: {len(urls)}") + + return failed == 0 + +def create_url_list_template(filename="batch_urls.txt"): + """Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ шаблон Ρ„Π°ΠΉΠ»Π° со ссылками""" + template = """# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Ρ„Π°ΠΉΠ»Π° со списком URL для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ YouTube Downloader +# ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ URL Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ Π½Π° ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠΉ строкС +# Π‘Ρ‚Ρ€ΠΎΠΊΠΈ, Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰ΠΈΠ΅ΡΡ с #, ΡΠ²Π»ΡΡŽΡ‚ΡΡ коммСнтариями ΠΈ ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ Π²ΠΈΠ΄Π΅ΠΎ для тСстирования: +# https://www.youtube.com/watch?v=dQw4w9WgXcQ + +# ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚Ρ‹ Ρ‚ΠΎΠΆΠ΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‚ΡΡ: +# https://www.youtube.com/playlist?list=PLAYLIST_ID + +# Π”ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ ваши ссылки Π½ΠΈΠΆΠ΅: +""" + + with open(filename, 'w', encoding='utf-8') as f: + f.write(template) + + print(f"Π‘ΠΎΠ·Π΄Π°Π½ Ρ„Π°ΠΉΠ» {filename} с шаблоном ссылок") + +def show_download_history(): + """ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + logger = DownloadLogger() + recent = logger.get_recent_downloads(20) + + if not recent: + print("Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ пуста") + return + + print("ПослСдниС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:") + print("-" * 80) + + for record in recent: + timestamp = record['timestamp'][:19] # Π£Π±ΠΈΡ€Π°Π΅ΠΌ микросСкунды + status = "βœ“" if record['success'] else "βœ—" + title = record['title'][:50] + "..." if len(record['title']) > 50 else record['title'] + print(f"{status} {timestamp} | {title}") + if not record['success'] and record.get('error'): + print(f" Ошибка: {record['error']}") + +def clean_downloads_folder(): + """ΠžΡ‡ΠΈΡ‰Π°Π΅Ρ‚ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + config = Config() + downloads_dir = config.get('output_directory', 'downloads') + + if not downloads_dir or not os.path.exists(downloads_dir): + print(f"Папка {downloads_dir} Π½Π΅ сущСствуСт") + return + + files = os.listdir(downloads_dir) + if not files: + print(f"Папка {downloads_dir} ΡƒΠΆΠ΅ пуста") + return + + print(f"НайдСно {len(files)} Ρ„Π°ΠΉΠ»ΠΎΠ² Π² {downloads_dir}") + confirm = input("Π£Π΄Π°Π»ΠΈΡ‚ΡŒ всС Ρ„Π°ΠΉΠ»Ρ‹? (yes/no): ") + + if confirm.lower() in ['yes', 'y', 'Π΄Π°', 'Π΄']: + for file in files: + file_path = os.path.join(str(downloads_dir), file) + try: + if os.path.isfile(file_path): + os.remove(file_path) + print(f"Π£Π΄Π°Π»Π΅Π½: {file}") + except Exception as e: + print(f"Ошибка удалСния {file}: {e}") + print("ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°") + else: + print("ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° ΠΎΡ‚ΠΌΠ΅Π½Π΅Π½Π°") + +def main(): + parser = argparse.ArgumentParser(description='YouTube Downloader - Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹') + parser.add_argument('action', choices=[ + 'batch', 'history', 'clean', 'template' + ], help='ДСйствиС для выполнСния') + + parser.add_argument('--file', '-f', help='Π€Π°ΠΉΠ» со ссылками для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') + parser.add_argument('--quality', '-q', default='best', help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ') + parser.add_argument('--audio-only', '-a', action='store_true', help='Волько Π°ΡƒΠ΄ΠΈΠΎ') + + args = parser.parse_args() + + if args.action == 'batch': + if not args.file: + print("Для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ» со ссылками (--file)") + return + + batch = BatchDownloader() + batch.download_from_file(args.file, args.quality, args.audio_only) + + elif args.action == 'history': + show_download_history() + + elif args.action == 'clean': + clean_downloads_folder() + + elif args.action == 'template': + create_url_list_template() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/.history/utils_20250928091647.py b/.history/utils_20250928091647.py new file mode 100644 index 0000000..d9652fe --- /dev/null +++ b/.history/utils_20250928091647.py @@ -0,0 +1,211 @@ +""" +Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ для YouTube Downloader +""" + +import os +import json +import argparse +from datetime import datetime +from config import Config +from downloader import YouTubeDownloader + +class DownloadLogger: + """Класс для логирования Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + + def __init__(self, log_file="download_history.json"): + self.log_file = log_file + self.history = self.load_history() + + def load_history(self): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + if os.path.exists(self.log_file): + try: + with open(self.log_file, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, IOError): + return [] + return [] + + def save_history(self): + """БохраняСт ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + try: + with open(self.log_file, 'w', encoding='utf-8') as f: + json.dump(self.history, f, indent=2, ensure_ascii=False) + except IOError as e: + print(f"Ошибка сохранСния истории: {e}") + + def add_download(self, url, title, success=True, error=None): + """ДобавляСт запись ΠΎ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅""" + record = { + 'timestamp': datetime.now().isoformat(), + 'url': url, + 'title': title, + 'success': success, + 'error': error + } + self.history.append(record) + self.save_history() + + def get_recent_downloads(self, count=10): + """Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ послСдниС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + return self.history[-count:] if len(self.history) >= count else self.history + + def get_failed_downloads(self): + """Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹Π΅ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + return [record for record in self.history if not record['success']] + +class BatchDownloader: + """Класс для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со ссылками""" + + def __init__(self, config=None): + self.config = config or Config() + self.downloader = YouTubeDownloader(self.config) + self.logger = DownloadLogger() + + def download_from_file(self, file_path, quality='best', audio_only=False): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ всС URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π°""" + if not os.path.exists(file_path): + print(f"Π€Π°ΠΉΠ» {file_path} Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½") + return False + + with open(file_path, 'r', encoding='utf-8') as f: + urls = [line.strip() for line in f if line.strip() and not line.startswith('#')] + + print(f"НайдСно {len(urls)} URL для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ") + + successful = 0 + failed = 0 + + for i, url in enumerate(urls, 1): + print(f"\n[{i}/{len(urls)}] Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: {url}") + + try: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + info = self.downloader.get_video_info(url) + title = info['title'] if info else 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅' + + # Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ + success = self.downloader.download_video(url, quality, audio_only) + + if success: + successful += 1 + self.logger.add_download(url, title, True) + print(f"βœ“ УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ: {title}") + else: + failed += 1 + self.logger.add_download(url, title, False, "Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ") + print(f"βœ— Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {title}") + + except Exception as e: + failed += 1 + self.logger.add_download(url, "НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅", False, str(e)) + print(f"βœ— Π˜ΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ {url}: {str(e)}") + + print(f"\nΠ Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:") + print(f"УспСшно: {successful}") + print(f"Ошибок: {failed}") + print(f"ВсСго: {len(urls)}") + + return failed == 0 + +def create_url_list_template(filename="batch_urls.txt"): + """Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ шаблон Ρ„Π°ΠΉΠ»Π° со ссылками""" + template = """# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Ρ„Π°ΠΉΠ»Π° со списком URL для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ YouTube Downloader +# ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ URL Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ Π½Π° ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠΉ строкС +# Π‘Ρ‚Ρ€ΠΎΠΊΠΈ, Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰ΠΈΠ΅ΡΡ с #, ΡΠ²Π»ΡΡŽΡ‚ΡΡ коммСнтариями ΠΈ ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ Π²ΠΈΠ΄Π΅ΠΎ для тСстирования: +# https://www.youtube.com/watch?v=dQw4w9WgXcQ + +# ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚Ρ‹ Ρ‚ΠΎΠΆΠ΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‚ΡΡ: +# https://www.youtube.com/playlist?list=PLAYLIST_ID + +# Π”ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ ваши ссылки Π½ΠΈΠΆΠ΅: +""" + + with open(filename, 'w', encoding='utf-8') as f: + f.write(template) + + print(f"Π‘ΠΎΠ·Π΄Π°Π½ Ρ„Π°ΠΉΠ» {filename} с шаблоном ссылок") + +def show_download_history(): + """ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + logger = DownloadLogger() + recent = logger.get_recent_downloads(20) + + if not recent: + print("Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ пуста") + return + + print("ПослСдниС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:") + print("-" * 80) + + for record in recent: + timestamp = record['timestamp'][:19] # Π£Π±ΠΈΡ€Π°Π΅ΠΌ микросСкунды + status = "βœ“" if record['success'] else "βœ—" + title = record['title'][:50] + "..." if len(record['title']) > 50 else record['title'] + print(f"{status} {timestamp} | {title}") + if not record['success'] and record.get('error'): + print(f" Ошибка: {record['error']}") + +def clean_downloads_folder(): + """ΠžΡ‡ΠΈΡ‰Π°Π΅Ρ‚ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + config = Config() + downloads_dir = config.get('output_directory', 'downloads') + + if not downloads_dir or not os.path.exists(downloads_dir): + print(f"Папка {downloads_dir} Π½Π΅ сущСствуСт") + return + + files = os.listdir(downloads_dir) + if not files: + print(f"Папка {downloads_dir} ΡƒΠΆΠ΅ пуста") + return + + print(f"НайдСно {len(files)} Ρ„Π°ΠΉΠ»ΠΎΠ² Π² {downloads_dir}") + confirm = input("Π£Π΄Π°Π»ΠΈΡ‚ΡŒ всС Ρ„Π°ΠΉΠ»Ρ‹? (yes/no): ") + + if confirm.lower() in ['yes', 'y', 'Π΄Π°', 'Π΄']: + for file in files: + file_path = os.path.join(str(downloads_dir), file) + try: + if os.path.isfile(file_path): + os.remove(file_path) + print(f"Π£Π΄Π°Π»Π΅Π½: {file}") + except Exception as e: + print(f"Ошибка удалСния {file}: {e}") + print("ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°") + else: + print("ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° ΠΎΡ‚ΠΌΠ΅Π½Π΅Π½Π°") + +def main(): + parser = argparse.ArgumentParser(description='YouTube Downloader - Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹') + parser.add_argument('action', choices=[ + 'batch', 'history', 'clean', 'template' + ], help='ДСйствиС для выполнСния') + + parser.add_argument('--file', '-f', help='Π€Π°ΠΉΠ» со ссылками для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') + parser.add_argument('--quality', '-q', default='best', help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ') + parser.add_argument('--audio-only', '-a', action='store_true', help='Волько Π°ΡƒΠ΄ΠΈΠΎ') + + args = parser.parse_args() + + if args.action == 'batch': + if not args.file: + print("Для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ» со ссылками (--file)") + return + + batch = BatchDownloader() + batch.download_from_file(args.file, args.quality, args.audio_only) + + elif args.action == 'history': + show_download_history() + + elif args.action == 'clean': + clean_downloads_folder() + + elif args.action == 'template': + create_url_list_template() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/BATCH_GUIDE.md b/BATCH_GUIDE.md new file mode 100644 index 0000000..ab6288a --- /dev/null +++ b/BATCH_GUIDE.md @@ -0,0 +1,125 @@ +# πŸ“‹ Руководство ΠΏΠΎ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ YouTube Downloader + +## 🎯 Бпособы ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + +### 1. Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со списком URL + +Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ тСкстовый Ρ„Π°ΠΉΠ» с URL (ΠΎΠ΄ΠΈΠ½ Π½Π° строку): + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон +python3 utils.py template batch_urls.txt + +# ΠžΡ‚Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ» ΠΈ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ URL +nano batch_urls.txt + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ всС URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch batch_urls.txt --audio-only +``` + +**ΠŸΡ€ΠΈΠΌΠ΅Ρ€ содСрТимого Ρ„Π°ΠΉΠ»Π°:** +``` +# Мои Π»ΡŽΠ±ΠΈΠΌΡ‹Π΅ Π²ΠΈΠ΄Π΅ΠΎ +https://www.youtube.com/watch?v=dQw4w9WgXcQ +https://www.youtube.com/watch?v=VIDEO_ID_2 +https://www.youtube.com/watch?v=VIDEO_ID_3 + +# ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚ (ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ с --playlist) +https://www.youtube.com/playlist?list=PLAYLIST_ID +``` + +### 2. Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL Ρ‡Π΅Ρ€Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΡƒΡŽ строку + +```bash +python3 main.py --urls URL1 URL2 URL3 --quality 720p +``` + +## πŸ›  ΠžΠΏΡ†ΠΈΠΈ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹: +- `--batch FILE` - Π€Π°ΠΉΠ» со списком URL +- `--urls URL1 URL2...` - НСсколько URL Ρ‡Π΅Ρ€Π΅Π· ΠΏΡ€ΠΎΠ±Π΅Π» +- `--continue-on-error` - ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Ρ‚ΡŒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… +- `--quality QUALITY` - ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ для всСх Π²ΠΈΠ΄Π΅ΠΎ +- `--audio-only` - Волько Π°ΡƒΠ΄ΠΈΠΎ для всСх +- `--output DIR` - Папка сохранСния + +### ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ ΠΊΠΎΠΌΠ°Π½Π΄: + +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ· Ρ„Π°ΠΉΠ»Π°, ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Ρ‚ΡŒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… +python3 main.py --batch my_urls.txt --audio-only --continue-on-error + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ нСсколько Π²ΠΈΠ΄Π΅ΠΎ Π² 720p +python3 main.py --urls URL1 URL2 URL3 --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch playlists.txt --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ ΠΏΠ΅Ρ€Π²ΠΎΠΌ URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch urls.txt --info + +# Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΡƒΡŽ ΠΏΠ°ΠΏΠΊΡƒ +python3 main.py --batch urls.txt --output /path/to/folder +``` + +## πŸ“Š ΠžΡ‚Ρ‡Π΅Ρ‚Ρ‹ ΠΎ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ + +ПослС ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ отобраТаСтся: +- βœ… ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ ΡƒΡΠΏΠ΅ΡˆΠ½Ρ‹Ρ… Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- ❌ ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ ошибок +- πŸ“‹ Бписок Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹Ρ… URL (Π΄ΠΎ 5 ΠΏΠ΅Ρ€Π²Ρ‹Ρ…) +- πŸ“ ΠŸΡƒΡ‚ΡŒ ΠΊ сохранСнным Ρ„Π°ΠΉΠ»Π°ΠΌ + +## πŸ”§ Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ для тСстирования + +```bash +make create-batch-template # Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон +make test-batch # ВСст с ΠΏΠΎΠΊΠ°Π·ΠΎΠΌ info +make demo-batch # Π”Π΅ΠΌΠΎ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π°ΡƒΠ΄ΠΈΠΎ +``` + +## πŸ’‘ Π‘ΠΎΠ²Π΅Ρ‚Ρ‹ ΠΈ Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°Ρ†ΠΈΠΈ + +### Π€ΠΎΡ€ΠΌΠ°Ρ‚ Ρ„Π°ΠΉΠ»Π° со списком URL: +- Один URL Π½Π° строку +- Π‘Ρ‚Ρ€ΠΎΠΊΠΈ с `#` - ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ (ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ) +- ΠŸΡƒΡΡ‚Ρ‹Π΅ строки ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ +- UTF-8 ΠΊΠΎΠ΄ΠΈΡ€ΠΎΠ²ΠΊΠ° + +### ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ошибок: +- По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ остановка Π½Π° ΠΏΠ΅Ρ€Π²ΠΎΠΉ ошибкС +- `--continue-on-error` - ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠΈΡ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ Π΄Ρ€ΡƒΠ³ΠΈΡ… URL +- ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½Ρ‹Π΅ сообщСния ΠΎΠ± ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… Π² Π²Ρ‹Π²ΠΎΠ΄Π΅ + +### ΠŸΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: +- Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΏΠΎ ΠΎΠ΄Π½ΠΎΠΌΡƒ URL (ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ) +- АвтоматичСскиС Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠΈ ΠΌΠ΅ΠΆΠ΄Ρƒ запросами +- Fallback стратСгии ΠΏΡ€ΠΈ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ°Ρ… + +## ❗ Π’Π°ΠΆΠ½Ρ‹Π΅ ΠΌΠΎΠΌΠ΅Π½Ρ‚Ρ‹ + +1. **Π›ΠΈΠΌΠΈΡ‚Ρ‹ YouTube**: НС злоупотрСбляйтС ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΎΠΉ +2. **ΠŸΡ€Π°Π²Π° Π°Π²Ρ‚ΠΎΡ€ΠΎΠ²**: Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ авторскиС ΠΏΡ€Π°Π²Π° +3. **ДисковоС пространство**: ΠšΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΠΈΡ€ΡƒΠΉΡ‚Π΅ объСм Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +4. **БСтСвая Π½Π°Π³Ρ€ΡƒΠ·ΠΊΠ°**: Π£Ρ‡ΠΈΡ‚Ρ‹Π²Π°ΠΉΡ‚Π΅ Ρ‚Ρ€Π°Ρ„ΠΈΠΊ ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π° + +## πŸ› Troubleshooting + +**ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΠ°**: НСкоторыС URL Π½Π΅ Π·Π°Π³Ρ€ΡƒΠΆΠ°ΡŽΡ‚ΡΡ +```bash +# РСшСниС: Π²ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ΅Π½ΠΈΠ΅ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… +python3 main.py --batch urls.txt --continue-on-error +``` + +**ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΠ°**: МСдлСнная Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° +```bash +# РСшСниС: ΠΏΠΎΠΏΡ€ΠΎΠ±ΠΎΠ²Π°Ρ‚ΡŒ Π±ΠΎΠ»Π΅Π΅ Π½ΠΈΠ·ΠΊΠΎΠ΅ качСство +python3 main.py --batch urls.txt --quality 480p +``` + +**ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΠ°**: Ошибки 403 +```bash +# РСшСниС: ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ автоматичСски ΠΏΡ€ΠΈΠΌΠ΅Π½ΠΈΡ‚ fallback стратСгии +# ΠŸΡ€ΠΎΡΡ‚ΠΎ Π΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ΡΡŒ ΠΏΠΎΠ²Ρ‚ΠΎΡ€Π½Ρ‹Ρ… ΠΏΠΎΠΏΡ‹Ρ‚ΠΎΠΊ +``` \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1d2745d --- /dev/null +++ b/Makefile @@ -0,0 +1,77 @@ +# Makefile for YouTube Downloader + +.PHONY: install run-examples help clean test config update-ytdlp + +# Установка зависимостСй +install: + pip3 install -r requirements.txt + +# ОбновлСниС yt-dlp Π΄ΠΎ послСднСй вСрсии +update-ytdlp: + pip3 install --upgrade yt-dlp + +# Установка с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ +install-fresh: update-ytdlp install + +# Запуск ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠ² +run-examples: + python3 examples.py + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку +help: + python3 main.py --help + +# ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ² +clean: + find . -type f -name "*.pyc" -delete + find . -type d -name "__pycache__" -delete + rm -f config.json + +# ВСстированиС URL Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ +test: + python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation test:', d.validate_url('https://www.youtube.com/watch?v=dQw4w9WgXcQ'))" + +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +config: + python3 main.py configure --output-dir downloads --video-quality best --audio-format mp3 + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ +show-config: + python3 main.py show-config + +# ВСстированиС ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +test-batch: + python3 main.py --batch batch_urls.txt --info + +# Π”Π΅ΠΌΠΎ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +demo-batch: + python3 main.py --batch batch_urls.txt --audio-only --continue-on-error + +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +create-batch-template: + python3 utils.py template batch_template.txt + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +all: install config run-examples + +# ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ +usage: + @echo "ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" + @echo " install - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ зависимости" + @echo " install-fresh - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp" + @echo " update-ytdlp - ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии" + @echo " run-examples - Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + @echo " help - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ΅" + @echo " config - ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ" + @echo " show-config - ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ" + @echo " test - ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL" + @echo " test-batch - ВСст ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ info)" + @echo " demo-batch - Π”Π΅ΠΌΠΎ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π°ΡƒΠ΄ΠΈΠΎ" + @echo " create-batch-template - Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ" + @echo " clean - ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹" + @echo " install-ffmpeg - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ" + @echo " all - Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ всС ΠΈ Π·Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹" + +# Установка ffmpeg +install-ffmpeg: + ./install_ffmpeg.sh \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6f4ca60 --- /dev/null +++ b/README.md @@ -0,0 +1,261 @@ +# YouTube Downloader + +ΠœΠΎΡ‰Π½ΠΎΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube с использованиСм Python ΠΈ yt-dlp. + +## πŸš€ ВозмоТности + +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Π²ΠΈΠ΄Π΅ΠΎ Π² Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡΡ… (360p, 480p, 720p, 1080p, best) +- βœ… Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ MP3/AAC +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° плСйлистов +- βœ… **ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со списком URL** +- βœ… **Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL Ρ‡Π΅Ρ€Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΡƒΡŽ строку** +- βœ… **ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ΅Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… (--continue-on-error)** +- βœ… ΠŸΡ€ΠΎΠ³Ρ€Π΅ΡΡ-Π±Π°Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Ρ†Π²Π΅Ρ‚Π½Ρ‹ΠΌ Π²Ρ‹Π²ΠΎΠ΄ΠΎΠΌ +- βœ… НастраиваСмая конфигурация +- βœ… Валидация URL +- βœ… Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +- βœ… CLI интСрфСйс с мноТСством ΠΎΠΏΡ†ΠΈΠΉ +- βœ… ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° субтитров +- βœ… АвтоматичСскоС исправлСниС ошибок 403/Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + +## πŸ“¦ Установка + +1. ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ: +```bash +git clone +cd youtube_downloader +``` + +2. УстановитС зависимости: +```bash +make install-fresh +# ΠΈΠ»ΠΈ +pip3 install -r requirements.txt +``` + +3. УстановитС ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ): +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: скачайтС с https://ffmpeg.org/ +``` + +## 🎯 Быстрый старт + +```bash +# Настройка ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ +make config + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 main.py --batch batch_urls.txt --audio-only + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL +python3 main.py --urls URL1 URL2 URL3 --quality 720p +``` + +## πŸ“‹ ИспользованиС CLI + +### ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹: +```bash +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΌ качСствС +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only + +# Π£ΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --output downloads/ + +# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист +python3 main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ +python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --formats +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон Ρ„Π°ΠΉΠ»Π° со списком URL +make create-batch-template + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со списком URL +python3 main.py --batch urls.txt --quality 720p --continue-on-error + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… URL Ρ‡Π΅Ρ€Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΡƒΡŽ строку +python3 main.py --urls URL1 URL2 URL3 --audio-only + +# ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠΈΡ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… +python3 main.py --batch urls.txt --continue-on-error +``` + +### ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ: +```bash +# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +python3 main.py configure --output-dir downloads --video-quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +python3 main.py show-config +``` + +## πŸ”§ Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ + +```bash +# Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ шаблон для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +python3 utils.py template + +# ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° +python3 utils.py batch --file urls.txt --quality 720p + +# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py history + +# ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +python3 utils.py clean +``` + +## πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ + +```bash +make install-fresh # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ с ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ yt-dlp +make install-ffmpeg # Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ffmpeg для Π°ΡƒΠ΄ΠΈΠΎ +make config # ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ +make show-config # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки +make update-ytdlp # ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ yt-dlp Π΄ΠΎ послСднСй вСрсии +make test # ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ тСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL +make demo-info # Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ тСстовом Π²ΠΈΠ΄Π΅ΠΎ +make demo-formats # Π€ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ тСстового Π²ΠΈΠ΄Π΅ΠΎ +make run-examples # Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ +make clean # ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ +make usage # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ справку ΠΏΠΎ Makefile +``` + +## πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° + +``` +youtube_downloader/ +β”œβ”€β”€ main.py # ОсновноС CLI ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ +β”œβ”€β”€ downloader.py # Класс Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° YouTube +β”œβ”€β”€ config.py # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ +β”œβ”€β”€ utils.py # Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ +β”œβ”€β”€ examples.py # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования +β”œβ”€β”€ requirements.txt # Зависимости Python +β”œβ”€β”€ Makefile # Автоматизация Π·Π°Π΄Π°Ρ‡ +β”œβ”€β”€ README.md # ДокумСнтация +β”œβ”€β”€ config.json # Π€Π°ΠΉΠ» ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ (создаСтся автоматичСски) +└── urls.txt # Π¨Π°Π±Π»ΠΎΠ½ для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +``` + +## βš™ ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ + +Настройки ΡΠΎΡ…Ρ€Π°Π½ΡΡŽΡ‚ΡΡ Π² `config.json`: + +```json +{ + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": true, + "add_metadata": true, + "subtitle_languages": ["ru", "en"], + "download_subtitles": false +} +``` + +## πŸ“ ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ + +### Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС Π² ΠΊΠΎΠ΄Π΅: +```python +from downloader import YouTubeDownloader +from config import Config + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° +config = Config() +downloader = YouTubeDownloader(config) + +# Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ +success = downloader.download_video( + url="https://www.youtube.com/watch?v=VIDEO_ID", + quality="720p", + audio_only=False +) +``` + +### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: +```python +from utils import BatchDownloader + +batch = BatchDownloader() +batch.download_from_file("urls.txt", quality="best", audio_only=False) +``` + +## 🚨 ВрСбования + +- Python 3.7+ +- Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС +- ffmpeg (для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ) + +## πŸ” Troubleshooting + +### Ошибки yt-dlp +Если Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‚ ошибки с yt-dlp, ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚Π΅ Π΄ΠΎ послСднСй вСрсии: +```bash +make update-ytdlp +# ΠΈΠ»ΠΈ +pip3 install --upgrade yt-dlp +``` + +### HTTP Error 403: Forbidden +ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ автоматичСски ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ нСсколько стратСгий ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ: +- ΠΠ»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ User-Agent Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ +- ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½Ρ‹Π΅ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ с Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠ°ΠΌΠΈ +- Π Π°Π·Π»ΠΈΡ‡Π½Ρ‹Π΅ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ +- Fallback настройки + +### ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с ffmpeg +УстановитС ffmpeg для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ: +```bash +make install-ffmpeg +# ΠΈΠ»ΠΈ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ: +# Ubuntu/Debian: sudo apt install ffmpeg +# macOS: brew install ffmpeg +# Windows: Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/ +``` + +### МСдлСнная Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° +- ΠŸΠΎΠΏΡ€ΠΎΠ±ΡƒΠΉΡ‚Π΅ ΡΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² Π±ΠΎΠ»Π΅Π΅ Π½ΠΈΠ·ΠΊΠΎΠΌ качСствС: `--quality 480p` +- Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ: `--audio-only` +- ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ соСдинСниС + +## πŸ“„ ЛицСнзия + +ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ создан для ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ†Π΅Π»Π΅ΠΉ. Π‘ΠΎΠ±Π»ΡŽΠ΄Π°ΠΉΡ‚Π΅ условия использования YouTube. + +## 🀝 Π’ΠΊΠ»Π°Π΄ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ + +1. Π€ΠΎΡ€ΠΊΠ½ΠΈΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ +2. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ Π²Π΅Ρ‚ΠΊΡƒ для Π½ΠΎΠ²ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ +3. Π‘Π΄Π΅Π»Π°ΠΉΡ‚Π΅ измСнСния +4. ΠžΡ‚ΠΏΡ€Π°Π²ΡŒΡ‚Π΅ Pull Request + +--- + +**Автор:** GitHub Copilot +**ВСрсия:** 1.0.0 \ No newline at end of file diff --git a/batch_urls.txt b/batch_urls.txt new file mode 100644 index 0000000..ce1ee39 --- /dev/null +++ b/batch_urls.txt @@ -0,0 +1,14 @@ +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Ρ„Π°ΠΉΠ»Π° со списком URL для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ YouTube Downloader +# ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ URL Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ Π½Π° ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠΉ строкС +# Π‘Ρ‚Ρ€ΠΎΠΊΠΈ, Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰ΠΈΠ΅ΡΡ с #, ΡΠ²Π»ΡΡŽΡ‚ΡΡ коммСнтариями ΠΈ ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ Π²ΠΈΠ΄Π΅ΠΎ для тСстирования: +https://www.youtube.com/watch?v=dQw4w9WgXcQ +https://www.youtube.com/watch?v=etuo7AA6rmY + +# МоТно Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ большС URL: +# https://www.youtube.com/watch?v=ANOTHER_VIDEO_ID +# https://www.youtube.com/watch?v=YET_ANOTHER_VIDEO_ID + +# ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚Ρ‹ Ρ‚ΠΎΠΆΠ΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‚ΡΡ (ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ --playlist): +# https://www.youtube.com/playlist?list=PLAYLIST_ID \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..bbaa510 --- /dev/null +++ b/config.json @@ -0,0 +1,13 @@ +{ + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": true, + "add_metadata": true, + "subtitle_languages": [ + "ru", + "en" + ], + "download_subtitles": false +} \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..1fd3948 --- /dev/null +++ b/config.py @@ -0,0 +1,55 @@ +import os +import json + +class Config: + """Класс для управлСния ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ прилоТСния""" + + DEFAULT_CONFIG = { + "output_directory": "downloads", + "video_quality": "best", + "audio_format": "mp3", + "video_format": "mp4", + "create_subdirs": True, + "add_metadata": True, + "subtitle_languages": ["ru", "en"], + "download_subtitles": False + } + + def __init__(self, config_file="config.json"): + self.config_file = config_file + self.config = self.load_config() + + def load_config(self): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΈΠ· Ρ„Π°ΠΉΠ»Π° ΠΈΠ»ΠΈ создаСт ΡΡ‚Π°Π½Π΄Π°Ρ€Ρ‚Π½ΡƒΡŽ""" + if os.path.exists(self.config_file): + try: + with open(self.config_file, 'r', encoding='utf-8') as f: + return {**self.DEFAULT_CONFIG, **json.load(f)} + except (json.JSONDecodeError, IOError): + print(f"Ошибка чтСния ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΈΠ· {self.config_file}. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ стандартная конфигурация.") + + return self.DEFAULT_CONFIG.copy() + + def save_config(self): + """БохраняСт Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ Π² Ρ„Π°ΠΉΠ»""" + try: + with open(self.config_file, 'w', encoding='utf-8') as f: + json.dump(self.config, f, indent=4, ensure_ascii=False) + except IOError as e: + print(f"Ошибка сохранСния ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ: {e}") + + def get(self, key, default=None): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ""" + return self.config.get(key, default) + + def set(self, key, value): + """УстанавливаСт Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ""" + self.config[key] = value + + def create_output_directory(self): + """Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ, Ссли ΠΎΠ½Π° Π½Π΅ сущСствуСт""" + output_dir = self.get("output_directory", "downloads") + if not os.path.exists(output_dir): + os.makedirs(output_dir) + print(f"Π‘ΠΎΠ·Π΄Π°Π½Π° ΠΏΠ°ΠΏΠΊΠ° для Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ: {output_dir}") + return output_dir \ No newline at end of file diff --git a/downloader.py b/downloader.py new file mode 100644 index 0000000..1851150 --- /dev/null +++ b/downloader.py @@ -0,0 +1,299 @@ +import os +import re +import yt_dlp +import time +import random +from tqdm import tqdm +from colorama import Fore, Style, init +from config import Config +from fake_useragent import UserAgent + +# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ colorama для кроссплатформСнной Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ†Π²Π΅Ρ‚Π°ΠΌΠΈ +init(autoreset=True) + +class YouTubeDownloader: + """Основной класс для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube""" + + def __init__(self, config=None): + self.config = config or Config() + self.progress_bar = None + + def validate_url(self, url): + """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ YouTube URL""" + youtube_regex = re.compile( + r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' + r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' + ) + return youtube_regex.match(url) is not None + + def progress_hook(self, d): + """Π₯ΡƒΠΊ для отобраТСния прогрСсса Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + if d['status'] == 'downloading': + if self.progress_bar is None: + total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate') + if total_bytes: + self.progress_bar = tqdm( + total=total_bytes, + unit='B', + unit_scale=True, + desc=f"{Fore.BLUE}Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅{Style.RESET_ALL}" + ) + + if self.progress_bar and 'downloaded_bytes' in d: + downloaded = d['downloaded_bytes'] + # Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ hasattr для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° + if hasattr(self.progress_bar, 'last_downloaded'): + self.progress_bar.update(downloaded - getattr(self.progress_bar, 'last_downloaded', 0)) + else: + self.progress_bar.update(downloaded) + # УстанавливаСм custom Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ + setattr(self.progress_bar, 'last_downloaded', downloaded) + + elif d['status'] == 'finished': + if self.progress_bar: + self.progress_bar.close() + self.progress_bar = None + print(f"{Fore.GREEN}βœ“ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°: {d['filename']}{Style.RESET_ALL}") + + def get_video_info(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + ydl_opts = { + 'quiet': True, + 'no_warnings': True, + } + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + return { + 'title': info.get('title', 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅'), + 'duration': info.get('duration', 0), + 'uploader': info.get('uploader', 'НСизвСстный Π°Π²Ρ‚ΠΎΡ€'), + 'view_count': info.get('view_count', 0), + 'upload_date': info.get('upload_date', ''), + 'description': info.get('description', ''), + 'formats': info.get('formats', []) + } + except Exception as e: + print(f"{Fore.RED}Ошибка получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {str(e)}{Style.RESET_ALL}") + return None + + def download_video(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ URL""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + return False + + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + video_info = self.get_video_info(url) + if not video_info: + return False + + print(f"{Fore.CYAN}НазваниС: {video_info['title']}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Автор: {video_info['uploader']}{Style.RESET_ALL}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.CYAN}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {duration_str}{Style.RESET_ALL}") + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΏΠ°ΠΏΠΊΡƒ для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + output_path = output_dir or self.config.create_output_directory() + + # Настройки для yt-dlp с ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½Π½Ρ‹ΠΌ ΠΎΠ±Ρ…ΠΎΠ΄ΠΎΠΌ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + ua = UserAgent() + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'writesubtitles': self.config.get('download_subtitles', False), + 'writeautomaticsub': self.config.get('download_subtitles', False), + # Настройки для ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ + 'http_headers': { + 'User-Agent': ua.random, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip, deflate', + 'DNT': '1', + 'Connection': 'keep-alive', + 'Upgrade-Insecure-Requests': '1', + }, + 'extractor_retries': 3, + 'file_access_retries': 3, + 'fragment_retries': 3, + 'retry_sleep_functions': { + 'http': lambda n: min(4 * 2**n, 30), + 'fragment': lambda n: min(4 * 2**n, 30), + 'file_access': lambda n: min(4 * 2**n, 30), + }, + 'sleep_interval_requests': 1, + 'sleep_interval': 0, + 'max_sleep_interval': 5, + } + + # Настройки качСства ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + print(f"{Fore.YELLOW}Π Π΅ΠΆΠΈΠΌ: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ({self.config.get('audio_format', 'mp3')}){Style.RESET_ALL}") + else: + if quality == 'best': + format_string = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + format_string = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + format_string = 'best[ext=mp4]/best' + + ydl_opts['format'] = format_string + print(f"{Fore.YELLOW}ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ: {quality}{Style.RESET_ALL}") + + # Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° субтитров + if self.config.get('download_subtitles', False): + ydl_opts['subtitleslangs'] = self.config.get('subtitle_languages', ['ru', 'en']) + + try: + # ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° с ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹ΠΌΠΈ настройками + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ...{Style.RESET_ALL}") + ydl.download([url]) + return True + + except Exception as e: + error_msg = str(e) + print(f"{Fore.YELLOW}ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Π°: {error_msg}{Style.RESET_ALL}") + + # Если ошибка 403 ΠΈΠ»ΠΈ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ°, ΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ настройки + if "403" in error_msg or "Forbidden" in error_msg or "throttl" in error_msg or "HTTP Error" in error_msg: + return self._retry_download_with_fallback(url, ydl_opts) + else: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {error_msg}{Style.RESET_ALL}") + return False + + def _retry_download_with_fallback(self, url, base_opts): + """ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½Π°Ρ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ с Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΌΠΈ настройками""" + fallback_strategies = [ + { + 'name': 'ΠΠ»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΉ User-Agent', + 'opts': { + **base_opts, + 'http_headers': { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } + } + }, + { + 'name': 'Π‘Π΅Π· ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Ρ…', + 'opts': { + **{k: v for k, v in base_opts.items() if k not in ['writeinfojson', 'writesubtitles', 'writeautomaticsub']}, + 'writeinfojson': False, + 'writesubtitles': False, + 'writeautomaticsub': False + } + }, + { + 'name': 'Волько Π°ΡƒΠ΄ΠΈΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚', + 'opts': { + **base_opts, + 'format': 'bestaudio[ext=m4a]/bestaudio/best[height<=480]' + } + } + ] + + for i, strategy in enumerate(fallback_strategies, 1): + print(f"{Fore.CYAN}ΠŸΠΎΠΏΡ‹Ρ‚ΠΊΠ° {i+1}: {strategy['name']}{Style.RESET_ALL}") + try: + # ДобавляСм Π·Π°Π΄Π΅Ρ€ΠΆΠΊΡƒ ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ°ΠΌΠΈ + time.sleep(random.uniform(2, 5)) + + with yt_dlp.YoutubeDL(strategy['opts']) as ydl: + ydl.download([url]) + print(f"{Fore.GREEN}βœ“ УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ с настройкой: {strategy['name']}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}βœ— {strategy['name']}: {str(e)}{Style.RESET_ALL}") + continue + + print(f"{Fore.RED}ВсС ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹{Style.RESET_ALL}") + return False + + def download_playlist(self, url, quality='best', audio_only=False, output_dir=None): + """Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅Ρ‚ плСйлист""" + if not self.validate_url(url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL{Style.RESET_ALL}") + return False + + output_path = output_dir or self.config.create_output_directory() + + ydl_opts = { + 'outtmpl': os.path.join(str(output_path), '%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s'), + 'progress_hooks': [self.progress_hook], + 'writeinfojson': self.config.get('add_metadata', True), + 'noplaylist': False, # Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста + } + + # Настройки для Π°ΡƒΠ΄ΠΈΠΎ ΠΈΠ»ΠΈ Π²ΠΈΠ΄Π΅ΠΎ + if audio_only: + ydl_opts.update({ + 'format': 'bestaudio/best', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': self.config.get('audio_format', 'mp3'), + 'preferredquality': '192', + }], + }) + else: + if quality == 'best': + ydl_opts['format'] = 'best[ext=mp4]/best' + elif quality.endswith('p'): + height = quality[:-1] + ydl_opts['format'] = f'best[height<={height}][ext=mp4]/best[height<={height}]/best[ext=mp4]/best' + else: + ydl_opts['format'] = 'best[ext=mp4]/best' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ плСйлистС + playlist_info = ydl.extract_info(url, download=False) + if 'entries' in playlist_info: + print(f"{Fore.CYAN}ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚: {playlist_info.get('title', 'НСизвСстный плСйлист')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {len(playlist_info['entries'])}{Style.RESET_ALL}") + + print(f"{Fore.BLUE}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ плСйлиста...{Style.RESET_ALL}") + ydl.download([url]) + return True + else: + # Π­Ρ‚ΠΎ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½ΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ, Π° Π½Π΅ плСйлист + return self.download_video(url, quality, audio_only, output_dir) + + except Exception as e: + print(f"{Fore.RED}Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ плСйлиста: {str(e)}{Style.RESET_ALL}") + return False + + def get_available_formats(self, url): + """ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² для Π²ΠΈΠ΄Π΅ΠΎ""" + try: + with yt_dlp.YoutubeDL({'quiet': True}) as ydl: + info = ydl.extract_info(url, download=False) + formats = [] + if info and isinstance(info, dict) and 'formats' in info: + formats_list = info.get('formats') + if formats_list: + for f in formats_list: + if f.get('height'): # Волько Π²ΠΈΠ΄Π΅ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + formats.append({ + 'quality': f"{f.get('height')}p", + 'ext': f.get('ext', ''), + 'filesize': f.get('filesize', 0), + 'format_id': f.get('format_id', '') + }) + return formats + except Exception as e: + print(f"{Fore.RED}Ошибка получСния Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²: {str(e)}{Style.RESET_ALL}") + return [] \ No newline at end of file diff --git a/examples.py b/examples.py new file mode 100644 index 0000000..b979a1f --- /dev/null +++ b/examples.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +""" +ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования YouTube Downloader +""" + +from downloader import YouTubeDownloader +from config import Config +from colorama import Fore, Style + +def example_download_video(): + """ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΎΠ΄Π½ΠΎΠ³ΠΎ Π²ΠΈΠ΄Π΅ΠΎ""" + print(f"{Fore.CYAN}=== ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π²ΠΈΠ΄Π΅ΠΎ ==={Style.RESET_ALL}") + + # URL тСстового Π²ΠΈΠ΄Π΅ΠΎ (ΠΊΠΎΡ€ΠΎΡ‚ΠΊΠΎΠ΅ Π²ΠΈΠ΄Π΅ΠΎ с YouTube) + url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" # Rick Roll для тСста + + config = Config() + downloader = YouTubeDownloader(config) + + # ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + print(f"{Fore.YELLOW}ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ...{Style.RESET_ALL}") + info = downloader.get_video_info(url) + + if info: + print(f"НазваниС: {info['title']}") + print(f"Автор: {info['uploader']}") + print(f"Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ: {info['duration']} сСк") + + # ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ + print(f"\n{Fore.YELLOW}ДоступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹:{Style.RESET_ALL}") + formats = downloader.get_available_formats(url) + for fmt in formats[:5]: # ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠ΅Ρ€Π²Ρ‹Π΅ 5 Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ² + print(f" β€’ {fmt['quality']} ({fmt['ext']})") + + return url + +def example_download_audio(): + """ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ""" + print(f"\n{Fore.CYAN}=== ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π°ΡƒΠ΄ΠΈΠΎ ==={Style.RESET_ALL}") + + url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + + config = Config() + downloader = YouTubeDownloader(config) + + print("Для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ audio_only=True") + print("ΠŸΡ€ΠΈΠΌΠ΅Ρ€: downloader.download_video(url, audio_only=True)") + +def show_cli_examples(): + """ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования CLI""" + print(f"\n{Fore.CYAN}=== ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования CLI ==={Style.RESET_ALL}") + + examples = [ + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² Π»ΡƒΡ‡ΡˆΠ΅ΠΌ качСствС", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID", + "", + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ Π² 720p", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --quality 720p", + "", + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only", + "", + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΡƒΡŽ ΠΏΠ°ΠΏΠΊΡƒ", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --output /path/to/downloads/", + "", + "# Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ плСйлист", + "python main.py https://www.youtube.com/playlist?list=PLAYLIST_ID --playlist", + "", + "# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --info", + "", + "# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹", + "python main.py https://www.youtube.com/watch?v=VIDEO_ID --formats", + "", + "# ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ", + "python main.py configure --output-dir downloads --video-quality 720p", + "", + "# ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки", + "python main.py show-config" + ] + + for example in examples: + if example.startswith("#"): + print(f"{Fore.GREEN}{example}{Style.RESET_ALL}") + elif example == "": + print() + else: + print(f"{Fore.WHITE}{example}{Style.RESET_ALL}") + +def test_url_validation(): + """ВСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL""" + print(f"\n{Fore.CYAN}=== ВСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL ==={Style.RESET_ALL}") + + downloader = YouTubeDownloader() + + test_urls = [ + ("https://www.youtube.com/watch?v=dQw4w9WgXcQ", True), + ("https://youtu.be/dQw4w9WgXcQ", True), + ("https://youtube.com/watch?v=dQw4w9WgXcQ", True), + ("https://www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMt6VEY", True), + ("https://example.com/video", False), + ("not_a_url", False), + ] + + for url, expected in test_urls: + result = downloader.validate_url(url) + status = "βœ“" if result == expected else "βœ—" + color = Fore.GREEN if result == expected else Fore.RED + print(f"{color}{status} {url} -> {result}{Style.RESET_ALL}") + +if __name__ == "__main__": + print(f"{Fore.CYAN}YouTube Downloader - ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования{Style.RESET_ALL}") + print("=" * 60) + + # ВСст Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ URL + test_url_validation() + + # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ + test_url = example_download_video() + example_download_audio() + + # ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ CLI + show_cli_examples() + + print(f"\n{Fore.GREEN}ВсС ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ ΠΏΠΎΠΊΠ°Π·Π°Π½Ρ‹. Для Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ main.py{Style.RESET_ALL}") + print(f"{Fore.YELLOW}Для Π½Π°Ρ‡Π°Π»Π° Ρ€Π°Π±ΠΎΡ‚Ρ‹ установитС зависимости: pip install -r requirements.txt{Style.RESET_ALL}") \ No newline at end of file diff --git a/install_ffmpeg.sh b/install_ffmpeg.sh new file mode 100755 index 0000000..f0c8700 --- /dev/null +++ b/install_ffmpeg.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Π‘ΠΊΡ€ΠΈΠΏΡ‚ установки ffmpeg для YouTube Downloader + +echo "🎬 Установка ffmpeg для YouTube Downloader" +echo "==========================================" + +# ΠžΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ОБ +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo "🐧 Linux ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠ΅Π½" + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° наличия apt + if command -v apt &> /dev/null; then + echo "πŸ“¦ Π£ΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°ΡŽ ffmpeg Ρ‡Π΅Ρ€Π΅Π· apt..." + sudo apt update && sudo apt install -y ffmpeg + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° наличия yum + elif command -v yum &> /dev/null; then + echo "πŸ“¦ Π£ΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°ΡŽ ffmpeg Ρ‡Π΅Ρ€Π΅Π· yum..." + sudo yum install -y ffmpeg + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° наличия dnf + elif command -v dnf &> /dev/null; then + echo "πŸ“¦ Π£ΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°ΡŽ ffmpeg Ρ‡Π΅Ρ€Π΅Π· dnf..." + sudo dnf install -y ffmpeg + + else + echo "❌ ΠœΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€ ΠΏΠ°ΠΊΠ΅Ρ‚ΠΎΠ² Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. УстановитС ffmpeg Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ." + exit 1 + fi + +elif [[ "$OSTYPE" == "darwin"* ]]; then + echo "🍎 macOS ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠ΅Π½" + + if command -v brew &> /dev/null; then + echo "πŸ“¦ Π£ΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°ΡŽ ffmpeg Ρ‡Π΅Ρ€Π΅Π· Homebrew..." + brew install ffmpeg + else + echo "❌ Homebrew Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. УстановитС Π΅Π³ΠΎ сначала:" + echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" + exit 1 + fi + +else + echo "❓ НСизвСстная ОБ: $OSTYPE" + echo "УстановитС ffmpeg Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ:" + echo " - Windows: Π‘ΠΊΠ°Ρ‡Π°ΠΉΡ‚Π΅ с https://ffmpeg.org/" + echo " - Linux: sudo apt install ffmpeg" + echo " - macOS: brew install ffmpeg" + exit 1 +fi + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° установки +if command -v ffmpeg &> /dev/null; then + echo "βœ… ffmpeg ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ установлСн!" + ffmpeg -version | head -1 + + echo "" + echo "🎯 Π’Π΅ΠΏΠ΅Ρ€ΡŒ ΠΌΠΎΠΆΠ½ΠΎ ΡΠΊΠ°Ρ‡ΠΈΠ²Π°Ρ‚ΡŒ Π°ΡƒΠ΄ΠΈΠΎ Π² MP3:" + echo " python3 main.py URL --audio-only" +else + echo "❌ Ошибка установки ffmpeg" + exit 1 +fi \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..1898b3f --- /dev/null +++ b/main.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 +""" +YouTube Downloader - ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для скачивания Π²ΠΈΠ΄Π΅ΠΎ с YouTube +Автор: GitHub Copilot +""" + +import sys +import click +from colorama import Fore, Style +from downloader import YouTubeDownloader +from config import Config + +def load_urls_from_file(file_path): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ список URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π°""" + urls = [] + try: + with open(file_path, 'r', encoding='utf-8') as f: + for line_num, line in enumerate(f, 1): + line = line.strip() + # ΠŸΡ€ΠΎΠΏΡƒΡΠΊΠ°Π΅ΠΌ пустыС строки ΠΈ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ + if line and not line.startswith('#'): + urls.append(line) + print(f"Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ {len(urls)} URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π° {file_path}") + return urls + except Exception as e: + print(f"{Fore.RED}Ошибка чтСния Ρ„Π°ΠΉΠ»Π° {file_path}: {str(e)}{Style.RESET_ALL}") + return [] + +def show_video_info(downloader, url): + """ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ""" + video_info = downloader.get_video_info(url) + if video_info: + print(f"\n{Fore.GREEN}Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ:{Style.RESET_ALL}") + print(f"{Fore.YELLOW}НазваниС:{Style.RESET_ALL} {video_info['title']}") + print(f"{Fore.YELLOW}Автор:{Style.RESET_ALL} {video_info['uploader']}") + + if video_info['duration']: + duration_str = f"{video_info['duration']//60}:{video_info['duration']%60:02d}" + print(f"{Fore.YELLOW}Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ:{Style.RESET_ALL} {duration_str}") + + if video_info['view_count']: + print(f"{Fore.YELLOW}ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ΠΎΠ²:{Style.RESET_ALL} {video_info['view_count']:,}") + + if video_info['upload_date']: + date_str = video_info['upload_date'] + formatted_date = f"{date_str[6:8]}.{date_str[4:6]}.{date_str[:4]}" + print(f"{Fore.YELLOW}Π”Π°Ρ‚Π° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:{Style.RESET_ALL} {formatted_date}") + + if video_info['description']: + desc = video_info['description'][:200] + "..." if len(video_info['description']) > 200 else video_info['description'] + print(f"{Fore.YELLOW}ОписаниС:{Style.RESET_ALL} {desc}") + +def show_video_formats(downloader, url): + """ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ Π²ΠΈΠ΄Π΅ΠΎ""" + print(f"\n{Fore.GREEN}ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ доступных Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΎΠ²...{Style.RESET_ALL}") + available_formats = downloader.get_available_formats(url) + if available_formats: + print(f"\n{Fore.GREEN}ДоступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹:{Style.RESET_ALL}") + for fmt in available_formats: + size_str = f" ({fmt['filesize']//1024//1024} MB)" if fmt['filesize'] else "" + print(f" β€’ {fmt['quality']} ({fmt['ext']}){size_str}") + else: + print(f"{Fore.RED}НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π°Ρ…{Style.RESET_ALL}") + +@click.command() +@click.argument('url', required=False) +@click.option('--quality', '-q', default='best', + help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ (best, 1080p, 720p, 480p, 360p)') +@click.option('--audio-only', '-a', is_flag=True, + help='Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°ΡƒΠ΄ΠΈΠΎ') +@click.option('--output', '-o', default=None, + help='Папка для сохранСния Ρ„Π°ΠΉΠ»ΠΎΠ²') +@click.option('--playlist', '-p', is_flag=True, + help='Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ вСсь плСйлист') +@click.option('--info', '-i', is_flag=True, + help='ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ Π±Π΅Π· Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') +@click.option('--formats', '-f', is_flag=True, + help='ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ доступныС Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹') +@click.option('--config', '-c', default='config.json', + help='ΠŸΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ') +@click.option('--batch', '-b', default=None, type=click.Path(exists=True), + help='Π€Π°ΠΉΠ» со списком URL для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') +@click.option('--urls', multiple=True, + help='НСсколько URL Ρ‡Π΅Ρ€Π΅Π· ΠΏΡ€ΠΎΠ±Π΅Π»') +@click.option('--continue-on-error', is_flag=True, + help='ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Ρ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ…') +def main(url, quality, audio_only, output, playlist, info, formats, config, batch, urls, continue_on_error): + """ + YouTube Downloader - скачиваниС Π²ΠΈΠ΄Π΅ΠΎ с YouTube + + URL: Бсылка Π½Π° YouTube Π²ΠΈΠ΄Π΅ΠΎ ΠΈΠ»ΠΈ плСйлист (ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ ΠΏΡ€ΠΈ использовании --batch ΠΈΠ»ΠΈ --urls) + """ + # Π’Ρ‹Π²ΠΎΠ΄ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ° + print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'YouTube Downloader':^60}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}") + + try: + # Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΈ Π·Π°Π³Ρ€ΡƒΠ·Ρ‡ΠΈΠΊΠ° + app_config = Config(config) + downloader = YouTubeDownloader(app_config) + + # ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ список URL для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ + urls_to_process = [] + + if batch: + # ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈΠ· Ρ„Π°ΠΉΠ»Π° + print(f"{Fore.GREEN}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π°: {batch}{Style.RESET_ALL}") + urls_to_process = load_urls_from_file(batch) + elif urls: + # ΠœΠ½ΠΎΠΆΠ΅ΡΡ‚Π²Π΅Π½Π½Ρ‹Π΅ URL ΠΈΠ· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΠΎΠΉ строки + urls_to_process = list(urls) + print(f"{Fore.GREEN}ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° {len(urls_to_process)} URL ΠΈΠ· ΠΊΠΎΠΌΠ°Π½Π΄Π½ΠΎΠΉ строки{Style.RESET_ALL}") + elif url: + # Один URL + urls_to_process = [url] + else: + print(f"{Fore.RED}Ошибка: НСобходимо ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ URL, --batch Ρ„Π°ΠΉΠ» ΠΈΠ»ΠΈ --urls{Style.RESET_ALL}") + sys.exit(1) + + # ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° списка URL + if info or formats: + # Для info ΠΈ formats ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ URL + process_single_url = urls_to_process[0] if urls_to_process else None + if not process_single_url: + print(f"{Fore.RED}Ошибка: НСт URL для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ{Style.RESET_ALL}") + sys.exit(1) + + if not downloader.validate_url(process_single_url): + print(f"{Fore.RED}Ошибка: НСкоррСктный URL YouTube{Style.RESET_ALL}") + sys.exit(1) + + if info: + show_video_info(downloader, process_single_url) + return + + if formats: + show_video_formats(downloader, process_single_url) + return + + # ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° + print(f"\n{Fore.GREEN}ΠΠ°Ρ‡ΠΈΠ½Π°ΡŽ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΡƒ {len(urls_to_process)} URL...{Style.RESET_ALL}") + + successful = 0 + failed = 0 + failed_urls = [] + + for i, current_url in enumerate(urls_to_process, 1): + print(f"\n{Fore.CYAN}[{i}/{len(urls_to_process)}] ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ°: {current_url}{Style.RESET_ALL}") + + # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° URL + if not downloader.validate_url(current_url): + print(f"{Fore.RED}βœ— НСкоррСктный URL, пропускаСм{Style.RESET_ALL}") + failed += 1 + failed_urls.append((current_url, "НСкоррСктный URL")) + if not continue_on_error: + break + continue + + try: + success = False + if playlist: + success = downloader.download_playlist(current_url, quality, audio_only, output) + else: + success = downloader.download_video(current_url, quality, audio_only, output) + + if success: + successful += 1 + print(f"{Fore.GREEN}βœ“ [{i}/{len(urls_to_process)}] УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ{Style.RESET_ALL}") + else: + failed += 1 + failed_urls.append((current_url, "Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ")) + print(f"{Fore.RED}βœ— [{i}/{len(urls_to_process)}] Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ{Style.RESET_ALL}") + if not continue_on_error: + break + + except Exception as e: + failed += 1 + error_msg = str(e)[:100] + "..." if len(str(e)) > 100 else str(e) + failed_urls.append((current_url, error_msg)) + print(f"{Fore.RED}βœ— [{i}/{len(urls_to_process)}] Π˜ΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅: {error_msg}{Style.RESET_ALL}") + if not continue_on_error: + break + + # Показ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² + print(f"\n{Fore.GREEN}{'='*60}{Style.RESET_ALL}") + if len(urls_to_process) > 1: + print(f"{Fore.GREEN}ΠŸΠ°ΠΊΠ΅Ρ‚Π½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°!{Style.RESET_ALL}") + print(f"{Fore.CYAN}УспСшно: {successful}{Style.RESET_ALL}") + print(f"{Fore.CYAN}Ошибок: {failed}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ВсСго: {len(urls_to_process)}{Style.RESET_ALL}") + + if failed_urls: + print(f"\n{Fore.YELLOW}НСудачныС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:{Style.RESET_ALL}") + for url_item, error in failed_urls[:5]: # ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠ΅Ρ€Π²Ρ‹Π΅ 5 + short_url = url_item[:50] + "..." if len(url_item) > 50 else url_item + print(f" βœ— {short_url}: {error}") + if len(failed_urls) > 5: + print(f" ... ΠΈ Π΅Ρ‰Π΅ {len(failed_urls) - 5} ошибок") + else: + if successful > 0: + print(f"{Fore.GREEN}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°!{Style.RESET_ALL}") + else: + print(f"{Fore.RED}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ»Π°ΡΡŒ с ошибкой{Style.RESET_ALL}") + + output_path = output or app_config.get("output_directory", "downloads") + print(f"{Fore.CYAN}Π€Π°ΠΉΠ»Ρ‹ сохранСны Π²: {output_path}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{'='*60}{Style.RESET_ALL}") + + if failed > 0 and not continue_on_error: + sys.exit(1) + + except KeyboardInterrupt: + print(f"\n{Fore.YELLOW}Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΏΡ€Π΅Ρ€Π²Π°Π½Π° ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΌ{Style.RESET_ALL}") + sys.exit(0) + except Exception as e: + print(f"\n{Fore.RED}ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° нСоТиданная ошибка: {str(e)}{Style.RESET_ALL}") + sys.exit(1) + +@click.group() +def cli(): + """YouTube Downloader - ΡƒΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ настройками ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π²ΠΈΠ΄Π΅ΠΎ""" + pass + +@cli.command() +@click.option('--output-dir', default='downloads', help='Папка для Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--video-quality', default='best', help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--audio-format', default='mp3', help='Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π°ΡƒΠ΄ΠΈΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +@click.option('--video-format', default='mp4', help='Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ') +def configure(output_dir, video_quality, audio_format, video_format): + """Настройка ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ""" + config = Config() + + config.set('output_directory', output_dir) + config.set('video_quality', video_quality) + config.set('audio_format', audio_format) + config.set('video_format', video_format) + + config.save_config() + + print(f"{Fore.GREEN}ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ сохранСна:{Style.RESET_ALL}") + print(f" Папка Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ: {output_dir}") + print(f" ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ: {video_quality}") + print(f" Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π°ΡƒΠ΄ΠΈΠΎ: {audio_format}") + print(f" Π€ΠΎΡ€ΠΌΠ°Ρ‚ Π²ΠΈΠ΄Π΅ΠΎ: {video_format}") + +@cli.command() +def show_config(): + """ΠŸΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ настройки""" + config = Config() + + print(f"{Fore.CYAN}ВСкущая конфигурация:{Style.RESET_ALL}") + for key, value in config.config.items(): + print(f" {key}: {value}") + +if __name__ == '__main__': + # Если Π·Π°ΠΏΡƒΡ‰Π΅Π½ Π±Π΅Π· ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΎΡΠ½ΠΎΠ²Π½ΡƒΡŽ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ + if len(sys.argv) == 1 or (len(sys.argv) > 1 and not sys.argv[1] in ['configure', 'show-config']): + main() + else: + cli() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4e6a7bf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +yt-dlp>=2024.1.1 +click==8.1.7 +tqdm==4.66.1 +colorama==0.4.6 +requests>=2.31.0 +fake-useragent>=1.4.0 \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..b782683 --- /dev/null +++ b/start.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# YouTube Downloader Quick Start Script + +echo "🎬 YouTube Downloader - Быстрый старт" +echo "========================================" + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Python +if ! command -v python3 &> /dev/null; then + echo "❌ Python3 Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. УстановитС Python 3.7 ΠΈΠ»ΠΈ Π½ΠΎΠ²Π΅Π΅." + exit 1 +fi + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° зависимостСй +if ! python3 -c "import yt_dlp" 2>/dev/null; then + echo "πŸ“¦ Установка зависимостСй..." + pip3 install -r requirements.txt +fi + +# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ffmpeg +if ! command -v ffmpeg &> /dev/null; then + echo "⚠️ ffmpeg Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½. Для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π°ΡƒΠ΄ΠΈΠΎ Π² MP3 установитС ffmpeg:" + echo " ./install_ffmpeg.sh" +fi + +# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΠ°ΠΏΠΊΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ +mkdir -p downloads + +echo "" +echo "βœ… Π“ΠΎΡ‚ΠΎΠ²ΠΎ! ДоступныС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" +echo "" +echo "πŸ“Ή Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π²ΠΈΠ΄Π΅ΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID" +echo "" +echo "🎡 Волько Π°ΡƒΠ΄ΠΈΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --audio-only" +echo "" +echo "πŸ“‹ Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ:" +echo " python3 main.py https://www.youtube.com/watch?v=VIDEO_ID --info" +echo "" +echo "βš™οΈ Настройка:" +echo " python3 main.py configure" +echo "" +echo "πŸ“š Π‘ΠΏΡ€Π°Π²ΠΊΠ°:" +echo " python3 main.py --help" +echo "" +echo "πŸ›  Make ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:" +echo " make usage" +echo "" + +# ВСст Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ +echo "πŸ§ͺ ВСст систСмы..." +if python3 -c "from downloader import YouTubeDownloader; d = YouTubeDownloader(); print('URL validation:', d.validate_url('https://www.youtube.com/watch?v=test'))" 2>/dev/null; then + echo "βœ… БистСма Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎ!" +else + echo "❌ ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с систСмой. ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ зависимости." +fi + +echo "" +echo "🎯 ΠŸΡ€ΠΈΠΌΠ΅Ρ€ использования:" +echo " python3 main.py https://www.youtube.com/watch?v=dQw4w9WgXcQ --info" \ No newline at end of file diff --git a/urls.txt b/urls.txt new file mode 100644 index 0000000..a56d948 --- /dev/null +++ b/urls.txt @@ -0,0 +1,11 @@ +# YouTube URL List Template +# Π”ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ ΠΏΠΎ ΠΎΠ΄Π½ΠΎΠΉ ссылкС YouTube Π½Π° ΠΊΠ°ΠΆΠ΄ΠΎΠΉ строкС +# Π‘Ρ‚Ρ€ΠΎΠΊΠΈ, Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰ΠΈΠ΅ΡΡ с #, ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ (ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ) + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π²ΠΈΠ΄Π΅ΠΎ: +# https://www.youtube.com/watch?v=dQw4w9WgXcQ + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ плСйлиста: +# https://www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMt6VEY + +# Π”ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ ваши ссылки Π½ΠΈΠΆΠ΅: diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..d9652fe --- /dev/null +++ b/utils.py @@ -0,0 +1,211 @@ +""" +Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ для YouTube Downloader +""" + +import os +import json +import argparse +from datetime import datetime +from config import Config +from downloader import YouTubeDownloader + +class DownloadLogger: + """Класс для логирования Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + + def __init__(self, log_file="download_history.json"): + self.log_file = log_file + self.history = self.load_history() + + def load_history(self): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + if os.path.exists(self.log_file): + try: + with open(self.log_file, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, IOError): + return [] + return [] + + def save_history(self): + """БохраняСт ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + try: + with open(self.log_file, 'w', encoding='utf-8') as f: + json.dump(self.history, f, indent=2, ensure_ascii=False) + except IOError as e: + print(f"Ошибка сохранСния истории: {e}") + + def add_download(self, url, title, success=True, error=None): + """ДобавляСт запись ΠΎ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅""" + record = { + 'timestamp': datetime.now().isoformat(), + 'url': url, + 'title': title, + 'success': success, + 'error': error + } + self.history.append(record) + self.save_history() + + def get_recent_downloads(self, count=10): + """Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ послСдниС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + return self.history[-count:] if len(self.history) >= count else self.history + + def get_failed_downloads(self): + """Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹Π΅ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ""" + return [record for record in self.history if not record['success']] + +class BatchDownloader: + """Класс для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈΠ· Ρ„Π°ΠΉΠ»Π° со ссылками""" + + def __init__(self, config=None): + self.config = config or Config() + self.downloader = YouTubeDownloader(self.config) + self.logger = DownloadLogger() + + def download_from_file(self, file_path, quality='best', audio_only=False): + """Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ всС URL ΠΈΠ· Ρ„Π°ΠΉΠ»Π°""" + if not os.path.exists(file_path): + print(f"Π€Π°ΠΉΠ» {file_path} Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½") + return False + + with open(file_path, 'r', encoding='utf-8') as f: + urls = [line.strip() for line in f if line.strip() and not line.startswith('#')] + + print(f"НайдСно {len(urls)} URL для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ") + + successful = 0 + failed = 0 + + for i, url in enumerate(urls, 1): + print(f"\n[{i}/{len(urls)}] Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ°: {url}") + + try: + # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΈΠ΄Π΅ΠΎ + info = self.downloader.get_video_info(url) + title = info['title'] if info else 'НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅' + + # Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ + success = self.downloader.download_video(url, quality, audio_only) + + if success: + successful += 1 + self.logger.add_download(url, title, True) + print(f"βœ“ УспСшно Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ: {title}") + else: + failed += 1 + self.logger.add_download(url, title, False, "Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ") + print(f"βœ— Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: {title}") + + except Exception as e: + failed += 1 + self.logger.add_download(url, "НСизвСстноС Π½Π°Π·Π²Π°Π½ΠΈΠ΅", False, str(e)) + print(f"βœ— Π˜ΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ {url}: {str(e)}") + + print(f"\nΠ Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:") + print(f"УспСшно: {successful}") + print(f"Ошибок: {failed}") + print(f"ВсСго: {len(urls)}") + + return failed == 0 + +def create_url_list_template(filename="batch_urls.txt"): + """Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ шаблон Ρ„Π°ΠΉΠ»Π° со ссылками""" + template = """# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Ρ„Π°ΠΉΠ»Π° со списком URL для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ YouTube Downloader +# ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ URL Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ Π½Π° ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠΉ строкС +# Π‘Ρ‚Ρ€ΠΎΠΊΠΈ, Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰ΠΈΠ΅ΡΡ с #, ΡΠ²Π»ΡΡŽΡ‚ΡΡ коммСнтариями ΠΈ ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ + +# ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ Π²ΠΈΠ΄Π΅ΠΎ для тСстирования: +# https://www.youtube.com/watch?v=dQw4w9WgXcQ + +# ΠŸΠ»Π΅ΠΉΠ»ΠΈΡΡ‚Ρ‹ Ρ‚ΠΎΠΆΠ΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‚ΡΡ: +# https://www.youtube.com/playlist?list=PLAYLIST_ID + +# Π”ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ ваши ссылки Π½ΠΈΠΆΠ΅: +""" + + with open(filename, 'w', encoding='utf-8') as f: + f.write(template) + + print(f"Π‘ΠΎΠ·Π΄Π°Π½ Ρ„Π°ΠΉΠ» {filename} с шаблоном ссылок") + +def show_download_history(): + """ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + logger = DownloadLogger() + recent = logger.get_recent_downloads(20) + + if not recent: + print("Π˜ΡΡ‚ΠΎΡ€ΠΈΡ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ пуста") + return + + print("ПослСдниС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:") + print("-" * 80) + + for record in recent: + timestamp = record['timestamp'][:19] # Π£Π±ΠΈΡ€Π°Π΅ΠΌ микросСкунды + status = "βœ“" if record['success'] else "βœ—" + title = record['title'][:50] + "..." if len(record['title']) > 50 else record['title'] + print(f"{status} {timestamp} | {title}") + if not record['success'] and record.get('error'): + print(f" Ошибка: {record['error']}") + +def clean_downloads_folder(): + """ΠžΡ‡ΠΈΡ‰Π°Π΅Ρ‚ ΠΏΠ°ΠΏΠΊΡƒ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ""" + config = Config() + downloads_dir = config.get('output_directory', 'downloads') + + if not downloads_dir or not os.path.exists(downloads_dir): + print(f"Папка {downloads_dir} Π½Π΅ сущСствуСт") + return + + files = os.listdir(downloads_dir) + if not files: + print(f"Папка {downloads_dir} ΡƒΠΆΠ΅ пуста") + return + + print(f"НайдСно {len(files)} Ρ„Π°ΠΉΠ»ΠΎΠ² Π² {downloads_dir}") + confirm = input("Π£Π΄Π°Π»ΠΈΡ‚ΡŒ всС Ρ„Π°ΠΉΠ»Ρ‹? (yes/no): ") + + if confirm.lower() in ['yes', 'y', 'Π΄Π°', 'Π΄']: + for file in files: + file_path = os.path.join(str(downloads_dir), file) + try: + if os.path.isfile(file_path): + os.remove(file_path) + print(f"Π£Π΄Π°Π»Π΅Π½: {file}") + except Exception as e: + print(f"Ошибка удалСния {file}: {e}") + print("ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°") + else: + print("ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° ΠΎΡ‚ΠΌΠ΅Π½Π΅Π½Π°") + +def main(): + parser = argparse.ArgumentParser(description='YouTube Downloader - Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹') + parser.add_argument('action', choices=[ + 'batch', 'history', 'clean', 'template' + ], help='ДСйствиС для выполнСния') + + parser.add_argument('--file', '-f', help='Π€Π°ΠΉΠ» со ссылками для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ') + parser.add_argument('--quality', '-q', default='best', help='ΠšΠ°Ρ‡Π΅ΡΡ‚Π²ΠΎ Π²ΠΈΠ΄Π΅ΠΎ') + parser.add_argument('--audio-only', '-a', action='store_true', help='Волько Π°ΡƒΠ΄ΠΈΠΎ') + + args = parser.parse_args() + + if args.action == 'batch': + if not args.file: + print("Для ΠΏΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ» со ссылками (--file)") + return + + batch = BatchDownloader() + batch.download_from_file(args.file, args.quality, args.audio_only) + + elif args.action == 'history': + show_download_history() + + elif args.action == 'clean': + clean_downloads_folder() + + elif args.action == 'template': + create_url_list_template() + +if __name__ == '__main__': + main() \ No newline at end of file