Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ include = ["src/tagstudio", "tests"]
reportAny = false
reportIgnoreCommentWithoutRule = false
reportImplicitStringConcatenation = false
reportImportCycles = false
reportMissingTypeArgument = false
reportMissingTypeStubs = false
# reportOptionalMemberAccess = false
Expand All @@ -110,6 +111,7 @@ reportUnknownArgumentType = false
reportUnknownLambdaType = false
reportUnknownMemberType = false
reportUnusedCallResult = false
reportUninitializedInstanceVariable = false

[tool.ruff]
exclude = ["home_ui.py", "resources.py", "resources_rc.py"]
Expand Down
14 changes: 11 additions & 3 deletions src/tagstudio/qt/controllers/preview_thumb_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@

import io
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override

import cv2
import rawpy
import structlog
from PIL import Image, UnidentifiedImageError
from PIL.Image import DecompressionBombError
from PySide6.QtCore import QSize
from rawpy import (
LibRawFileUnsupportedError, # pyright: ignore[reportPrivateImportUsage]
LibRawIOError, # pyright: ignore[reportPrivateImportUsage]
)

from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.media_types import MediaCategories
Expand Down Expand Up @@ -50,8 +54,8 @@ def __get_image_stats(self, filepath: Path) -> FileAttributeData:
stats.width = image.width
stats.height = image.height
except (
rawpy.LibRawIOError,
rawpy.LibRawFileUnsupportedError,
LibRawIOError,
LibRawFileUnsupportedError,
FileNotFoundError,
):
pass
Expand Down Expand Up @@ -144,18 +148,22 @@ def display_file(self, filepath: Path) -> FileAttributeData:
self._display_image(filepath)
return self.__get_image_stats(filepath)

@override
def _open_file_action_callback(self):
open_file(
self.__current_file, windows_start_command=self.__driver.settings.windows_start_command
)

@override
def _open_explorer_action_callback(self):
open_file(self.__current_file, file_manager=True)

@override
def _delete_action_callback(self):
if bool(self.__current_file):
self.__driver.delete_files_callback(self.__current_file)

@override
def _button_wrapper_callback(self):
open_file(
self.__current_file, windows_start_command=self.__driver.settings.windows_start_command
Expand Down
4 changes: 2 additions & 2 deletions src/tagstudio/qt/helpers/text_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

def wrap_line(
text: str,
font: ImageFont.ImageFont,
font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
width: int = 256,
draw: ImageDraw.ImageDraw | None = None,
) -> int:
Expand All @@ -31,7 +31,7 @@ def wrap_line(

def wrap_full_text(
text: str,
font: ImageFont.ImageFont,
font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
width: int = 256,
draw: ImageDraw.ImageDraw | None = None,
) -> str:
Expand Down
59 changes: 34 additions & 25 deletions src/tagstudio/qt/previews/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
UnidentifiedImageError,
)
from PIL.Image import DecompressionBombError
from pillow_heif import register_heif_opener
from pillow_heif import register_heif_opener # pyright: ignore[reportUnknownVariableType]
from PySide6.QtCore import (
QBuffer,
QFile,
Expand All @@ -58,6 +58,10 @@
from PySide6.QtGui import QGuiApplication, QImage, QPainter, QPixmap
from PySide6.QtPdf import QPdfDocument, QPdfDocumentRenderOptions
from PySide6.QtSvg import QSvgRenderer
from rawpy import (
LibRawFileUnsupportedError, # pyright: ignore[reportPrivateImportUsage]
LibRawIOError, # pyright: ignore[reportPrivateImportUsage]
)

from tagstudio.core.constants import (
FONT_SAMPLE_SIZES,
Expand All @@ -75,9 +79,11 @@
from tagstudio.qt.helpers.image_effects import replace_transparent_pixels
from tagstudio.qt.helpers.text_wrapper import wrap_full_text
from tagstudio.qt.models.palette import UI_COLORS, ColorType, UiColor, get_ui_color
from tagstudio.qt.previews.vendored.blender_renderer import blend_thumb
from tagstudio.qt.previews.vendored.blender_renderer import (
blend_thumb, # pyright: ignore[reportUnknownVariableType]
)
from tagstudio.qt.previews.vendored.pydub.audio_segment import (
_AudioSegment as AudioSegment,
_AudioSegment as AudioSegment, # pyright: ignore[reportPrivateUsage]
)
from tagstudio.qt.resource_manager import ResourceManager

Expand Down Expand Up @@ -118,7 +124,7 @@ class _TarFile:
def __init__(self, filepath: Path, mode: Literal["r"]) -> None:
self.tar: tarfile.TarFile
self.filepath = filepath
self.mode = mode
self.mode: Literal["r"] = mode

def namelist(self) -> list[str]:
return self.tar.getnames()
Expand All @@ -127,10 +133,10 @@ def read(self, name: str) -> bytes:
return unwrap(self.tar.extractfile(name)).read()

def __enter__(self) -> "_TarFile":
self.tar = tarfile.open(self.filepath, self.mode).__enter__()
self.tar = tarfile.open(name=self.filepath, mode=self.mode).__enter__()
return self

def __exit__(self, *args) -> None:
def __exit__(self, *args) -> None: # pyright: ignore[reportUnknownParameterType, reportMissingParameterType]
self.tar.__exit__(*args)


Expand Down Expand Up @@ -293,7 +299,7 @@ def _render_mask(

im: Image.Image = Image.new(
mode="L",
size=tuple([d * smooth_factor for d in size]), # type: ignore
size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
color="black",
)
draw = ImageDraw.Draw(im)
Expand Down Expand Up @@ -324,7 +330,7 @@ def _render_edge(
# Highlight
im_hl: Image.Image = Image.new(
mode="RGBA",
size=tuple([d * smooth_factor for d in size]), # type: ignore
size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
color="#00000000",
)
draw = ImageDraw.Draw(im_hl)
Expand All @@ -343,7 +349,7 @@ def _render_edge(
# Shadow
im_sh: Image.Image = Image.new(
mode="RGBA",
size=tuple([d * smooth_factor for d in size]), # type: ignore
size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
color="#00000000",
)
draw = ImageDraw.Draw(im_sh)
Expand Down Expand Up @@ -388,21 +394,21 @@ def _render_center_icon(
# Create larger blank image based on smooth_factor
im: Image.Image = Image.new(
"RGBA",
size=tuple([d * smooth_factor for d in size]), # type: ignore
size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
color="#FF000000",
)

# Create solid background color
bg: Image.Image
bg = Image.new(
"RGB",
size=tuple([d * smooth_factor for d in size]), # type: ignore
size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
color="#000000FF",
)

# Use a background image if provided
if bg_image:
bg_im = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore
bg_im = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore # pyright: ignore[reportArgumentType]
bg_im = ImageEnhance.Brightness(bg_im).enhance(0.3) # Reduce the brightness
bg.paste(bg_im)

Expand All @@ -411,7 +417,7 @@ def _render_center_icon(
bg,
(0, 0),
mask=self._get_mask(
tuple([d * smooth_factor for d in size]), # type: ignore
tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
(pixel_ratio * smooth_factor),
),
)
Expand Down Expand Up @@ -495,19 +501,19 @@ def _render_corner_icon(
# Create larger blank image based on smooth_factor
im: Image.Image = Image.new(
"RGBA",
size=tuple([d * smooth_factor for d in size]), # type: ignore
size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
color="#00000000",
)

bg: Image.Image
# Use a background image if provided
if bg_image:
bg = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore
bg = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore # pyright: ignore[reportArgumentType]
# Create solid background color
else:
bg = Image.new(
"RGB",
size=tuple([d * smooth_factor for d in size]), # type: ignore
size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
color="#000000",
)
# Apply color overlay
Expand All @@ -521,7 +527,7 @@ def _render_corner_icon(
bg,
(0, 0),
mask=self._get_mask(
tuple([d * smooth_factor for d in size]), # type: ignore
tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
(pixel_ratio * smooth_factor),
),
)
Expand Down Expand Up @@ -662,17 +668,17 @@ def _audio_album_thumb(filepath: Path, ext: str) -> Image.Image | None:
artwork = None
if ext in [".mp3"]:
id3_tags: id3.ID3 = id3.ID3(filepath)
id3_covers: list = id3_tags.getall("APIC")
id3_covers: list = id3_tags.getall("APIC") # pyright: ignore[reportUnknownVariableType]
if id3_covers:
artwork = Image.open(BytesIO(id3_covers[0].data))
elif ext in [".flac"]:
flac_tags: flac.FLAC = flac.FLAC(filepath)
flac_covers: list = flac_tags.pictures
flac_covers: list = flac_tags.pictures # pyright: ignore[reportUnknownVariableType]
if flac_covers:
artwork = Image.open(BytesIO(flac_covers[0].data))
elif ext in [".mp4", ".m4a", ".aac"]:
mp4_tags: mp4.MP4 = mp4.MP4(filepath)
mp4_covers: list | None = mp4_tags.get("covr") # pyright: ignore[reportAssignmentType]
mp4_covers: list | None = mp4_tags.get("covr") # pyright: ignore[reportUnknownVariableType]
if mp4_covers:
artwork = Image.open(BytesIO(mp4_covers[0]))
if artwork:
Expand Down Expand Up @@ -1088,7 +1094,7 @@ def _font_long_thumb(filepath: Path, size: int) -> Image.Image | None:
font = ImageFont.truetype(filepath, size=font_size)
text_wrapped: str = wrap_full_text(
FONT_SAMPLE_TEXT,
font=font, # pyright: ignore[reportArgumentType]
font=font,
width=size,
draw=draw,
)
Expand Down Expand Up @@ -1120,8 +1126,8 @@ def _image_raw_thumb(filepath: Path) -> Image.Image | None:
)
except (
DecompressionBombError,
rawpy.LibRawIOError,
rawpy.LibRawFileUnsupportedError,
LibRawIOError,
LibRawFileUnsupportedError,
) as e:
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
return im
Expand All @@ -1137,6 +1143,7 @@ def _image_exr_thumb(filepath: Path) -> Image.Image | None:
try:
# Load the EXR data to an array and rotate the color space from BGRA -> RGBA
raw_array = cv2.imread(str(filepath), cv2.IMREAD_UNCHANGED)
assert raw_array is not None
raw_array[..., :3] = raw_array[..., 2::-1]

# Correct the gamma of the raw array
Expand Down Expand Up @@ -1209,7 +1216,7 @@ def _image_vector_thumb(filepath: Path, size: int) -> Image.Image:
# Write the image to a buffer as png
buffer: QBuffer = QBuffer()
buffer.open(QBuffer.OpenModeFlag.ReadWrite)
q_image.save(buffer, "PNG") # type: ignore[call-overload]
q_image.save(device=buffer, format="PNG") # type: ignore # pyright: ignore[reportArgumentType]

# Load the image from the buffer
im = Image.new("RGB", (size, size), color="#1e1e1e")
Expand Down Expand Up @@ -1258,7 +1265,7 @@ def get_image(path: str) -> Image.Image | None:
return im

@staticmethod
def _model_stl_thumb(filepath: Path, size: int) -> Image.Image | None:
def _model_stl_thumb(filepath: Path, size: int) -> Image.Image | None: # pyright: ignore[reportUnusedParameter]
"""Render a thumbnail for an STL file.

Args:
Expand Down Expand Up @@ -1614,6 +1621,7 @@ def render_ignored(

def fetch_cached_image(file_name: Path):
image: Image.Image | None = None
assert self.driver.cache_manager is not None
cached_path = self.driver.cache_manager.get_file_path(file_name)

if cached_path and cached_path.is_file():
Expand Down Expand Up @@ -1876,6 +1884,7 @@ def _render(
image = self._resize_image(image, (adj_size, adj_size))

if save_to_file and savable_media_type and image:
assert self.driver.cache_manager is not None
self.driver.cache_manager.save_image(image, save_to_file, mode="RGBA")

except (
Expand Down
2 changes: 1 addition & 1 deletion src/tagstudio/qt/utils/custom_runnable.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from PySide6.QtCore import QObject, QRunnable, Signal


class CustomRunnable(QRunnable, QObject):
class CustomRunnable(QRunnable, QObject): # pyright: ignore[reportUnsafeMultipleInheritance]
done = Signal()

def __init__(self, function) -> None:
Expand Down
2 changes: 1 addition & 1 deletion tests/qt/test_tag_search_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_tag_widget_actions_replaced_correctly(qtbot: QtBot, qt_driver: QtDriver
# Set the widget
tags = library.tags
panel.set_tag_widget(tags[0], 0)
tag_widget: TagWidget = panel.scroll_layout.itemAt(0).widget()
tag_widget: TagWidget = panel.scroll_layout.itemAt(0).widget() # pyright: ignore[reportAssignmentType]

should_replace_actions = {
tag_widget: ["on_edit()", "on_remove()"],
Expand Down
Loading