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

261 lines
12 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.

#!/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()