init commit
This commit is contained in:
213
.history/downloader_20250928083918.py
Normal file
213
.history/downloader_20250928083918.py
Normal file
@@ -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 []
|
||||
Reference in New Issue
Block a user