Download video thumbnails asynchronously in GUI

Refactored thumbnail downloading to use a QThread for asynchronous operation, preventing UI blocking. Added ThumbnailDownloadThread class and updated VideoInfoMixin to handle threaded download and error reporting.
This commit is contained in:
oop7
2026-01-21 17:16:35 +02:00
parent 0d1eb35ba2
commit a92ffd3b01
+36 -9
View File
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, cast
import requests import requests
from PIL import Image from PIL import Image
from PySide6.QtCore import Qt from PySide6.QtCore import Qt, QThread, Signal
from PySide6.QtGui import QPixmap from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
@@ -21,6 +21,26 @@ if TYPE_CHECKING:
from src.gui.ytsage_gui_main import YTSageApp from src.gui.ytsage_gui_main import YTSageApp
class ThumbnailDownloadThread(QThread):
"""Thread to download thumbnail image asynchronously."""
finished = Signal(bytes)
error = Signal(str)
def __init__(self, url):
super().__init__()
self.url = url
def run(self):
try:
response = requests.get(self.url, timeout=10)
if response.status_code == 200:
self.finished.emit(response.content)
else:
self.error.emit(f"HTTP Error: {response.status_code}")
except Exception as e:
self.error.emit(str(e))
class VideoInfoMixin: class VideoInfoMixin:
def setup_video_info_section(self) -> QHBoxLayout: def setup_video_info_section(self) -> QHBoxLayout:
self = cast("YTSageApp", self) # for autocompletion and type inference. self = cast("YTSageApp", self) # for autocompletion and type inference.
@@ -368,14 +388,21 @@ class VideoInfoMixin:
def download_thumbnail(self, url) -> None: def download_thumbnail(self, url) -> None:
self = cast("YTSageApp", self) # for autocompletion and type inference. self = cast("YTSageApp", self) # for autocompletion and type inference.
try: # Store both thumbnail URL and video URL
# Store both thumbnail URL and video URL self.thumbnail_url = url
self.thumbnail_url = url self.video_url = self.url_input.text() # Get actual video URL
self.video_url = self.url_input.text() # Get actual video URL
# Download thumbnail but don't save yet # Create and start loader thread
response = requests.get(url) # Keep reference to avoid garbage collection
self.thumbnail_image = Image.open(BytesIO(response.content)) self.thumbnail_thread = ThumbnailDownloadThread(url)
self.thumbnail_thread.finished.connect(self._on_thumbnail_downloaded)
self.thumbnail_thread.error.connect(lambda e: logger.error(f"Error loading thumbnail: {e}"))
self.thumbnail_thread.start()
def _on_thumbnail_downloaded(self, content: bytes) -> None:
self = cast("YTSageApp", self)
try:
self.thumbnail_image = Image.open(BytesIO(content))
# Display thumbnail # Display thumbnail
image = self.thumbnail_image.resize((320, 180), Image.Resampling.LANCZOS) image = self.thumbnail_image.resize((320, 180), Image.Resampling.LANCZOS)
@@ -385,7 +412,7 @@ class VideoInfoMixin:
pixmap.loadFromData(img_byte_arr.getvalue()) pixmap.loadFromData(img_byte_arr.getvalue())
self.thumbnail_label.setPixmap(pixmap) self.thumbnail_label.setPixmap(pixmap)
except Exception as e: except Exception as e:
logger.exception(f"Error loading thumbnail: {e}") logger.exception(f"Error processing thumbnail image: {e}")
def download_thumbnail_file(self, video_url, path) -> bool: def download_thumbnail_file(self, video_url, path) -> bool:
self = cast("YTSageApp", self) # for autocompletion and type inference. self = cast("YTSageApp", self) # for autocompletion and type inference.