Files
youtube_downloader/.history/downloader_20250928084005.py
2025-09-28 09:18:03 +09:00

215 lines
9.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 []