[youtube] Extracting URL: https://youtu.be/lGu-scCPJSI
[youtube] lGu-scCPJSI: Downloading webpage
[youtube] lGu-scCPJSI: Downloading ios player API JSON
[youtube] lGu-scCPJSI: Downloading mweb player API JSON
WARNING: [youtube] HTTPSConnectionPool(host='www.youtube.com', port=443): Read timed out. Retrying (1/3)...
[youtube] lGu-scCPJSI: Downloading mweb player API JSON
[youtube] lGu-scCPJSI: Downloading player 89dfc5b3
WARNING: [youtube] lGu-scCPJSI: nsig extraction failed: Some formats may be missing
         n = OGq0Sl0J-DhYpq ; player = https://www.youtube.com/s/player/89dfc5b3/player_ias.vflset/en_US/base.js
[youtube] lGu-scCPJSI: Downloading player 89dfc5b3
[youtube] lGu-scCPJSI: Downloading m3u8 information
WARNING: [youtube] Failed to download m3u8 information: [SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1000)
[youtube] Extracting URL: https://youtu.be/lGu-scCPJSI
[youtube] lGu-scCPJSI: Downloading webpage
WARNING: [youtube] lGu-scCPJSI: Error reading response: HTTPSConnectionPool(host='www.youtube.com', port=443): Read timed out.
[youtube] lGu-scCPJSI: Downloading ios player API JSON
[youtube] lGu-scCPJSI: Downloading iframe API JS
WARNING: [youtube] Unable to fetch PO Token for mweb client: Missing required Visitor Data. You may need to pass Visitor Data with --extractor-args "youtube:visitor_data=XXX"
[youtube] lGu-scCPJSI: Downloading player 89dfc5b3
WARNING: [youtube] lGu-scCPJSI: Error reading response: HTTPSConnectionPool(host='www.youtube.com', port=443): Read timed out.
[youtube] lGu-scCPJSI: Downloading mweb player API JSON import os
import time
from PyQt6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QLineEdit, QPushButton,
                             QLabel, QFileDialog, QComboBox, QWidget, QMessageBox, QHBoxLayout, QProgressBar, QScrollArea,
                             QSpacerItem, QSizePolicy)
from PyQt6.QtGui import QPixmap
from PyQt6.QtCore import Qt, QThread, pyqtSignal
import requests
from io import BytesIO
import yt_dlp


class YouTubeDownloader(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("YouTube Downloader")
        self.setMinimumSize(600, 500)
        self.setStyleSheet("""
            QMainWindow {
                background-color: #f8f8f8;
                font-family: Arial, sans-serif;
            }
            QLabel {
                font-size: 16px;
                color: #333;
            }
            QLineEdit, QComboBox {
                font-size: 14px;
                padding: 8px;
                border: 1px solid #ccc;
                border-radius: 8px;
            }
            QPushButton {
                font-size: 16px;
                background-color: #e63946;
                color: white;
                border: none;
                padding: 12px 20px;
                border-radius: 8px;
                min-width: 150px;
            }
            QPushButton:hover {
                background-color: #d62828;
            }
            QProgressBar {
                border: 2px solid #e63946;
                border-radius: 8px;
                background-color: #f0f0f0;
            }
            QProgressBar::chunk {
                background-color: #e63946;
            }
            QScrollArea {
                background-color: #fff;
                border: 1px solid #ccc;
                border-radius: 8px;
            }
        """)

        # Central widget
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout()
        central_widget.setLayout(layout)

        # Input field for URL
        self.url_input = QLineEdit()
        self.url_input.setPlaceholderText("Введите URL видео")
        self.url_input.textChanged.connect(self.update_video_info)
        layout.addWidget(self.url_input)

        # Video preview
        self.video_preview = QLabel()
        self.video_preview.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.video_preview.setFixedSize(300, 150)
        self.video_preview.setStyleSheet("border: 1px solid #ccc; border-radius: 8px;")
        layout.addWidget(self.video_preview, alignment=Qt.AlignmentFlag.AlignCenter)

        # Quality selection
        quality_layout = QHBoxLayout()
        self.quality_label = QLabel("Качество:")
        self.quality_combo = QComboBox()
        self.quality_combo.addItem("Выберите качество")
        quality_layout.addWidget(self.quality_label)
        quality_layout.addWidget(self.quality_combo)
        layout.addLayout(quality_layout)

        # Save path selection
        path_layout = QHBoxLayout()
        self.path_input = QLineEdit()
        self.path_input.setPlaceholderText("Путь к папке для сохранения")
        self.path_button = QPushButton("Выбрать путь")
        self.path_button.clicked.connect(self.select_save_path)
        path_layout.addWidget(self.path_input)
        path_layout.addWidget(self.path_button)
        layout.addLayout(path_layout)

        # Progress bar
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 100)
        self.progress_bar.setValue(0)
        layout.addWidget(self.progress_bar)

        # Speed and estimated time
        self.speed_label = QLabel("Скорость: -- KB/s")
        self.time_label = QLabel("Оставшееся время: -- секунд")
        layout.addWidget(self.speed_label)
        layout.addWidget(self.time_label)

        # Log output area (initially collapsed)
        self.log_area = QScrollArea()
        self.log_area.setWidgetResizable(True)
        self.log_output = QLabel()
        self.log_area.setWidget(self.log_output)
        self.log_area.setFixedHeight(100)  # Set height for log area
        self.log_area.setVisible(False)  # Start hidden
        layout.addWidget(self.log_area)

        # Download button
        self.download_button = QPushButton("Скачать видео")
        self.download_button.clicked.connect(self.download_video)
        layout.addWidget(self.download_button, alignment=Qt.AlignmentFlag.AlignCenter)

        # Stop button
        self.stop_button = QPushButton("Остановить загрузку")
        self.stop_button.setEnabled(False)  # Disabled initially
        self.stop_button.clicked.connect(self.stop_download)
        layout.addWidget(self.stop_button, alignment=Qt.AlignmentFlag.AlignCenter)

        # Toggle log button
        self.toggle_log_button = QPushButton("Показать/Скрыть логи")
        self.toggle_log_button.clicked.connect(self.toggle_log_area)
        layout.addWidget(self.toggle_log_button, alignment=Qt.AlignmentFlag.AlignCenter)

        # Close button with confirmation
        self.close_button = QPushButton("Закрыть программу")
        self.close_button.clicked.connect(self.close_application)
        layout.addWidget(self.close_button, alignment=Qt.AlignmentFlag.AlignCenter)

        # Button layout (2 columns for last 4 buttons)
        button_layout = QHBoxLayout()
        button_layout.addWidget(self.stop_button)
        button_layout.addWidget(self.download_button)
        button_layout.addWidget(self.toggle_log_button)
        button_layout.addWidget(self.close_button)
        layout.addLayout(button_layout)

        # Thread for download process
        self.download_thread = None
        self.download_start_time = None

    def update_video_info(self):
        """Обновление информации о видео, включая загрузку превью."""
        url = self.url_input.text().strip()
        if "youtube.com" in url or "youtu.be" in url:
            try:
                with yt_dlp.YoutubeDL() as ydl:
                    info = ydl.extract_info(url, download=False)
                    self.load_thumbnail(info.get('thumbnail'))
                    self.populate_quality_options(info)
            except Exception as e:
                self.video_preview.clear()
                self.quality_combo.clear()
                self.quality_combo.addItem("Выберите качество")
                self.append_log(f"Ошибка: {e}")
                QMessageBox.warning(self, "Ошибка", f"Не удалось загрузить информацию о видео: {e}")

    def load_thumbnail(self, thumbnail_url):
        """Загружает и отображает превью видео."""
        try:
            response = requests.get(thumbnail_url, timeout=5)
            if response.status_code == 200:
                pixmap = QPixmap()
                pixmap.loadFromData(BytesIO(response.content).read())
                self.video_preview.setPixmap(pixmap.scaled(300, 150, Qt.AspectRatioMode.KeepAspectRatio))
            else:
                self.video_preview.clear()
        except requests.RequestException:
            self.video_preview.clear()

    def populate_quality_options(self, info):
        """Заполняет выпадающий список с доступными качествами видео."""
        self.quality_combo.clear()
        self.quality_combo.addItem("Выберите качество")
        formats = info.get('formats', [])
        for fmt in formats:
            # Фильтруем форматы, чтобы отображались все доступные качества, включая те, которые содержат и видео, и аудио в одном файле формата MP4
            if fmt.get('vcodec', 'none') != 'none' and fmt.get('acodec', 'none') != 'none' and fmt.get('ext') == 'mp4':
                resolution = fmt.get('height', 'unknown')
                self.quality_combo.addItem(f"{resolution}p (MP4)")
            elif fmt.get('vcodec', 'none') != 'none' and fmt.get('acodec', 'none') == 'none' and fmt.get('ext') == 'mp4':
                resolution = fmt.get('height', 'unknown')
                self.quality_combo.addItem(f"{resolution}p (MP4 - только видео)")
            elif fmt.get('vcodec', 'none') == 'none' and fmt.get('acodec', 'none') != 'none' and fmt.get('ext') == 'm4a':
                self.quality_combo.addItem("Аудио (M4A)")

    def select_save_path(self):
        """Открывает диалог для выбора папки сохранения."""
        folder = QFileDialog.getExistingDirectory(self, "Выберите папку для сохранения")
        if folder:
            self.path_input.setText(folder)

    def download_video(self):
        """Скачивает видео с YouTube в отдельном потоке."""
        url = self.url_input.text().strip()
        save_path = self.path_input.text().strip()
        quality = self.quality_combo.currentText()

        if not url or not save_path or quality == "Выберите качество":
            QMessageBox.warning(self, "Ошибка", "Пожалуйста, заполните все поля.")
            return

        resolution = quality.split('p')[0]  # Get resolution number from quality string
        ydl_opts = {
            'outtmpl': os.path.join(save_path, '%(title)s.%(ext)s'),
            'format': f'bestvideo[height<={resolution}][ext=mp4]+bestaudio[ext=m4a]/best[height<={resolution}][ext=mp4]',
            'progress_hooks': [self.progress_hook],  # Hook for progress updates
            'noplaylist': True,  # Ensure it doesn't download a playlist
            'merge_output_format': 'mp4',  # Merge output to MP4 format
            'ffmpeg_location': self.find_ffmpeg(),  # Specify the location of ffmpeg
        }

        self.download_thread = DownloadThread(url, ydl_opts, self.update_progress, self.append_log)
        self.download_thread.finished.connect(self.download_finished)
        self.download_thread.start()

        self.download_start_time = time.time()  # Запоминаем время начала загрузки
        self.download_button.setEnabled(False)
        self.stop_button.setEnabled(True)

    def progress_hook(self, d):
        """Hook to update download progress, speed and estimated time."""
        if d['status'] == 'downloading':
            downloaded = d.get('downloaded_bytes', 0)
            total = d.get('total_bytes', 1)  # Avoid division by zero
            progress = (downloaded / total) * 100
            speed = d.get('speed', 0) / 1024  # Speed in KB/s
            eta = d.get('eta', 0)  # Estimated time remaining in seconds
            self.update_progress(progress, speed, eta)

    def update_progress(self, progress, speed, remaining_time):
        """Обновляет прогресс-бар и выводит скорость и время оставшееся для загрузки."""
        self.progress_bar.setValue(int(progress))
        self.speed_label.setText(f"Скорость: {speed:.2f} KB/s")
        self.time_label.setText(f"Оставшееся время: {remaining_time:.0f} секунд")

    def stop_download(self):
        """Останавливает загрузку."""
        if self.download_thread:
            self.download_thread.terminate()
        self.download_button.setEnabled(True)
        self.stop_button.setEnabled(False)

    def download_finished(self):
        """Вызывается, когда загрузка завершена."""
        self.download_button.setEnabled(True)
        self.stop_button.setEnabled(False)
        self.append_log("Загрузка завершена!")

    def toggle_log_area(self):
        """Показывает или скрывает область логов."""
        self.log_area.setVisible(not self.log_area.isVisible())

    def append_log(self, message):
        """Добавляет сообщение в лог."""
        current_text = self.log_output.text()
        self.log_output.setText(f"{current_text}\n{message}")

    def close_application(self):
        """Подтверждение перед закрытием программы."""
        reply = QMessageBox.question(self, "Закрыть программу",
                                     "Вы уверены, что хотите закрыть программу?",
                                     QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
        if reply == QMessageBox.StandardButton.Yes:
            self.close()

    def find_ffmpeg(self):
        """Ищет путь к ffmpeg в системе."""
        ffmpeg_path = None
        for path in os.environ["PATH"].split(os.pathsep):
            ffmpeg_path = os.path.join(path, "ffmpeg")
            if os.path.exists(ffmpeg_path):
                return ffmpeg_path
        return None


class DownloadThread(QThread):
    def __init__(self, url, ydl_opts, progress_callback, log_callback):
        super().__init__()
        self.url = url
        self.ydl_opts = ydl_opts
        self.progress_callback = progress_callback
        self.log_callback = log_callback

    def run(self):
        """Запуск загрузки видео с помощью yt-dlp."""
        with yt_dlp.YoutubeDL(self.ydl_opts) as ydl:
            ydl.download([self.url])


if __name__ == "__main__":
    app = QApplication([])
    window = YouTubeDownloader()
    window.show()
    app.exec()