261 lines
12 KiB
Python
261 lines
12 KiB
Python
#!/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() |