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:
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user