mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-01-31 07:10:45 +00:00
fix: renderer type fixes (#1114)
* fix: type fixes * fix: address review feedback
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
import typing
|
||||
from pathlib import Path
|
||||
from time import gmtime
|
||||
from typing import override
|
||||
from typing import cast, override
|
||||
|
||||
import structlog
|
||||
from PIL import Image, ImageDraw
|
||||
@@ -411,11 +411,11 @@ class MediaPlayer(QGraphicsView):
|
||||
self.player.play()
|
||||
|
||||
def load_toggle_play_icon(self, playing: bool) -> None:
|
||||
icon = self.driver.rm.pause_icon if playing else self.driver.rm.play_icon
|
||||
icon = cast(bytes, self.driver.rm.pause_icon if playing else self.driver.rm.play_icon)
|
||||
self.play_pause.load(icon)
|
||||
|
||||
def load_mute_unmute_icon(self, muted: bool) -> None:
|
||||
icon = self.driver.rm.volume_mute_icon if muted else self.driver.rm.volume_icon
|
||||
icon = cast(bytes, self.driver.rm.volume_mute_icon if muted else self.driver.rm.volume_icon)
|
||||
self.mute_unmute.load(icon)
|
||||
|
||||
def slider_value_changed(self, value: int) -> None:
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
import structlog
|
||||
from PySide6.QtCore import QObject, Qt, QThreadPool, Signal
|
||||
@@ -61,8 +62,8 @@ class JsonMigrationModal(QObject):
|
||||
self.path: Path = path
|
||||
|
||||
self.stack: list[PagedPanelState] = []
|
||||
self.json_lib: JsonLibrary = None
|
||||
self.sql_lib: SqliteLibrary = None
|
||||
self.json_lib: JsonLibrary = None # pyright: ignore[reportAttributeAccessIssue]
|
||||
self.sql_lib: SqliteLibrary = None # pyright: ignore[reportAttributeAccessIssue]
|
||||
self.is_migration_initialized: bool = False
|
||||
self.discrepancies: list[str] = []
|
||||
|
||||
@@ -72,7 +73,7 @@ class JsonMigrationModal(QObject):
|
||||
self.old_entry_count: int = 0
|
||||
self.old_tag_count: int = 0
|
||||
self.old_ext_count: int = 0
|
||||
self.old_ext_type: bool = None
|
||||
self.old_ext_type: bool = None # pyright: ignore[reportAttributeAccessIssue]
|
||||
|
||||
self.field_parity: bool = False
|
||||
self.path_parity: bool = False
|
||||
@@ -367,7 +368,7 @@ class JsonMigrationModal(QObject):
|
||||
minimum=0,
|
||||
maximum=0,
|
||||
)
|
||||
pb.setCancelButton(None)
|
||||
pb.setCancelButton(None) # pyright: ignore[reportArgumentType]
|
||||
self.body_wrapper_01.layout().addWidget(pb)
|
||||
|
||||
try:
|
||||
@@ -390,7 +391,7 @@ class JsonMigrationModal(QObject):
|
||||
pb.setMinimum(1), # type: ignore
|
||||
pb.setValue(1), # type: ignore
|
||||
# Enable the finish button
|
||||
self.stack[1].buttons[4].setDisabled(False),
|
||||
cast(QPushButtonWrapper, self.stack[1].buttons[4]).setDisabled(False),
|
||||
)
|
||||
)
|
||||
QThreadPool.globalInstance().start(r)
|
||||
@@ -474,7 +475,7 @@ class JsonMigrationModal(QObject):
|
||||
)
|
||||
self.update_sql_value(
|
||||
self.ext_type_row,
|
||||
self.sql_lib.prefs(LibraryPrefs.IS_EXCLUDE_LIST),
|
||||
self.sql_lib.prefs(LibraryPrefs.IS_EXCLUDE_LIST), # pyright: ignore[reportArgumentType]
|
||||
self.old_ext_type,
|
||||
)
|
||||
logger.info("Parity check complete!")
|
||||
@@ -636,18 +637,15 @@ class JsonMigrationModal(QObject):
|
||||
|
||||
def check_subtag_parity(self) -> bool:
|
||||
"""Check if all JSON parent tags match the new SQL parent tags."""
|
||||
sql_parent_tags: set[int] = None
|
||||
json_parent_tags: set[int] = None
|
||||
|
||||
with Session(self.sql_lib.engine) as session:
|
||||
for tag in self.sql_lib.tags:
|
||||
tag_id = tag.id # Tag IDs start at 0
|
||||
sql_parent_tags = set(
|
||||
sql_parent_tags: set[int] = set(
|
||||
session.scalars(select(TagParent.parent_id).where(TagParent.child_id == tag.id))
|
||||
)
|
||||
|
||||
# JSON tags allowed self-parenting; SQL tags no longer allow this.
|
||||
json_parent_tags = set(self.json_lib.get_tag(tag_id).subtag_ids)
|
||||
json_parent_tags: set[int] = set(self.json_lib.get_tag(tag_id).subtag_ids)
|
||||
json_parent_tags.discard(tag_id)
|
||||
|
||||
logger.info(
|
||||
@@ -677,16 +675,15 @@ class JsonMigrationModal(QObject):
|
||||
|
||||
def check_alias_parity(self) -> bool:
|
||||
"""Check if all JSON aliases match the new SQL aliases."""
|
||||
sql_aliases: set[str] = None
|
||||
json_aliases: set[str] = None
|
||||
|
||||
with Session(self.sql_lib.engine) as session:
|
||||
for tag in self.sql_lib.tags:
|
||||
tag_id = tag.id # Tag IDs start at 0
|
||||
sql_aliases = set(
|
||||
sql_aliases: set[str] = set(
|
||||
session.scalars(select(TagAlias.name).where(TagAlias.tag_id == tag.id))
|
||||
)
|
||||
json_aliases = set([x for x in self.json_lib.get_tag(tag_id).aliases if x])
|
||||
json_aliases: set[str] = set(
|
||||
[x for x in self.json_lib.get_tag(tag_id).aliases if x]
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"[Alias Parity]",
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
"""A pagination widget created for TagStudio."""
|
||||
|
||||
from typing import override
|
||||
from typing import cast, override
|
||||
|
||||
from PIL import Image, ImageQt
|
||||
from PySide6.QtCore import QObject, QSize, Signal
|
||||
from PySide6.QtCore import QSize, Signal
|
||||
from PySide6.QtGui import QIntValidator, QPixmap
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSizePolicy, QWidget
|
||||
|
||||
@@ -17,7 +17,7 @@ from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.qt.views.qbutton_wrapper import QPushButtonWrapper
|
||||
|
||||
|
||||
class Pagination(QWidget, QObject):
|
||||
class Pagination(QWidget):
|
||||
"""Widget containing controls for navigating between pages of items."""
|
||||
|
||||
index = Signal(int)
|
||||
@@ -160,8 +160,8 @@ class Pagination(QWidget, QObject):
|
||||
srt_scale = max(1, (7 - (end_page - index)))
|
||||
|
||||
if page_count >= 8:
|
||||
end_size = self.button_size.width() * end_scale + (3 * (end_scale - 1))
|
||||
srt_size = self.button_size.width() * srt_scale + (3 * (srt_scale - 1))
|
||||
end_size = self.button_size.width() * end_scale + (3 * (end_scale - 1)) # pyright: ignore[reportPossiblyUnboundVariable]
|
||||
srt_size = self.button_size.width() * srt_scale + (3 * (srt_scale - 1)) # pyright: ignore[reportPossiblyUnboundVariable]
|
||||
self.end_ellipses.setMinimumWidth(end_size)
|
||||
self.end_ellipses.setMaximumWidth(end_size)
|
||||
self.start_ellipses.setMinimumWidth(srt_size)
|
||||
@@ -223,7 +223,10 @@ class Pagination(QWidget, QObject):
|
||||
str(i + 1)
|
||||
)
|
||||
self._assign_click(
|
||||
self.start_buffer_layout.itemAt(i - start_offset).widget(),
|
||||
cast(
|
||||
QPushButtonWrapper,
|
||||
self.start_buffer_layout.itemAt(i - start_offset).widget(),
|
||||
),
|
||||
i,
|
||||
)
|
||||
sbc += 1
|
||||
@@ -239,7 +242,10 @@ class Pagination(QWidget, QObject):
|
||||
str(i + 1)
|
||||
)
|
||||
self._assign_click(
|
||||
self.end_buffer_layout.itemAt(i - end_offset).widget(),
|
||||
cast(
|
||||
QPushButtonWrapper,
|
||||
self.end_buffer_layout.itemAt(i - end_offset).widget(),
|
||||
),
|
||||
i,
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -21,12 +21,14 @@ import cv2
|
||||
import numpy as np
|
||||
import pillow_avif # noqa: F401 # pyright: ignore[reportUnusedImport]
|
||||
import py7zr
|
||||
import py7zr.io
|
||||
import rarfile
|
||||
import rawpy
|
||||
import srctools
|
||||
import structlog
|
||||
from cv2.typing import MatLike
|
||||
from mutagen import MutagenError, flac, id3, mp4
|
||||
from mutagen import flac, id3, mp4
|
||||
from mutagen._util import MutagenError
|
||||
from PIL import (
|
||||
Image,
|
||||
ImageChops,
|
||||
@@ -119,7 +121,7 @@ class _TarFile(tarfile.TarFile):
|
||||
return self.getnames()
|
||||
|
||||
def read(self, name: str) -> bytes:
|
||||
return self.extractfile(name).read()
|
||||
return unwrap(self.extractfile(name)).read()
|
||||
|
||||
|
||||
type _Archive_T = (
|
||||
@@ -435,9 +437,9 @@ class ThumbRenderer(QObject):
|
||||
)
|
||||
|
||||
# Get icon by name
|
||||
icon: Image.Image | None = self.rm.get(name)
|
||||
icon: Image.Image | None = self.rm.get(name) # pyright: ignore[reportAssignmentType]
|
||||
if not icon:
|
||||
icon = self.rm.get("file_generic")
|
||||
icon = self.rm.get("file_generic") # pyright: ignore[reportAssignmentType]
|
||||
if not icon:
|
||||
icon = Image.new(mode="RGBA", size=(32, 32), color="magenta")
|
||||
|
||||
@@ -533,9 +535,9 @@ class ThumbRenderer(QObject):
|
||||
)
|
||||
|
||||
# Get icon by name
|
||||
icon: Image.Image | None = self.rm.get(name)
|
||||
icon: Image.Image | None = self.rm.get(name) # pyright: ignore[reportAssignmentType]
|
||||
if not icon:
|
||||
icon = self.rm.get("file_generic")
|
||||
icon = self.rm.get("file_generic") # pyright: ignore[reportAssignmentType]
|
||||
if not icon:
|
||||
icon = Image.new(mode="RGBA", size=(32, 32), color="magenta")
|
||||
|
||||
@@ -664,14 +666,14 @@ class ThumbRenderer(QObject):
|
||||
artwork = Image.open(BytesIO(flac_covers[0].data))
|
||||
elif ext in [".mp4", ".m4a", ".aac"]:
|
||||
mp4_tags: mp4.MP4 = mp4.MP4(filepath)
|
||||
mp4_covers: list = unwrap(mp4_tags.get("covr"))
|
||||
mp4_covers: list | None = mp4_tags.get("covr") # pyright: ignore[reportAssignmentType]
|
||||
if mp4_covers:
|
||||
artwork = Image.open(BytesIO(mp4_covers[0]))
|
||||
if artwork:
|
||||
image = artwork
|
||||
except (
|
||||
FileNotFoundError,
|
||||
id3.ID3NoHeaderError,
|
||||
id3.ID3NoHeaderError, # pyright: ignore[reportPrivateImportUsage]
|
||||
mp4.MP4MetadataError,
|
||||
mp4.MP4StreamInfoError,
|
||||
MutagenError,
|
||||
@@ -768,7 +770,7 @@ class ThumbRenderer(QObject):
|
||||
return im
|
||||
|
||||
@staticmethod
|
||||
def _blender(filepath: Path) -> Image.Image:
|
||||
def _blender(filepath: Path) -> Image.Image | None:
|
||||
"""Get an emended thumbnail from a Blender file, if a thumbnail is present.
|
||||
|
||||
Args:
|
||||
@@ -956,21 +958,21 @@ class ThumbRenderer(QObject):
|
||||
cover = comic_info.find(f"./*Page[@Type='{cover_type}']")
|
||||
if cover is not None:
|
||||
pages = [f for f in archive.namelist() if f != "ComicInfo.xml"]
|
||||
page_name = pages[int(cover.get("Image"))]
|
||||
page_name = pages[int(unwrap(cover.get("Image")))]
|
||||
if page_name.endswith((".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg")):
|
||||
image_data = archive.read(page_name)
|
||||
im = Image.open(BytesIO(image_data))
|
||||
|
||||
return im
|
||||
|
||||
def _font_short_thumb(self, filepath: Path, size: int) -> Image.Image:
|
||||
def _font_short_thumb(self, filepath: Path, size: int) -> Image.Image | None:
|
||||
"""Render a small font preview ("Aa") thumbnail from a font file.
|
||||
|
||||
Args:
|
||||
filepath (Path): The path of the file.
|
||||
size (tuple[int,int]): The size of the thumbnail.
|
||||
"""
|
||||
im: Image.Image = None
|
||||
im: Image.Image | None = None
|
||||
try:
|
||||
bg = Image.new("RGB", (size, size), color="#000000")
|
||||
raw = Image.new("RGB", (size * 3, size * 3), color="#000000")
|
||||
@@ -1024,7 +1026,7 @@ class ThumbRenderer(QObject):
|
||||
return im
|
||||
|
||||
@staticmethod
|
||||
def _font_long_thumb(filepath: Path, size: int) -> Image.Image:
|
||||
def _font_long_thumb(filepath: Path, size: int) -> Image.Image | None:
|
||||
"""Render a large font preview ("Alphabet") thumbnail from a font file.
|
||||
|
||||
Args:
|
||||
@@ -1033,7 +1035,7 @@ class ThumbRenderer(QObject):
|
||||
"""
|
||||
# Scale the sample font sizes to the preview image
|
||||
# resolution,assuming the sizes are tuned for 256px.
|
||||
im: Image.Image = None
|
||||
im: Image.Image | None = None
|
||||
try:
|
||||
scaled_sizes: list[int] = [math.floor(x * (size / 256)) for x in FONT_SAMPLE_SIZES]
|
||||
bg = Image.new("RGBA", (size, size), color="#00000000")
|
||||
@@ -1044,7 +1046,10 @@ class ThumbRenderer(QObject):
|
||||
for font_size in scaled_sizes:
|
||||
font = ImageFont.truetype(filepath, size=font_size)
|
||||
text_wrapped: str = wrap_full_text(
|
||||
FONT_SAMPLE_TEXT, font=font, width=size, draw=draw
|
||||
FONT_SAMPLE_TEXT,
|
||||
font=font, # pyright: ignore[reportArgumentType]
|
||||
width=size,
|
||||
draw=draw,
|
||||
)
|
||||
draw.multiline_text((0, y_offset), text_wrapped, font=font)
|
||||
y_offset += (len(text_wrapped.split("\n")) + lines_of_padding) * draw.textbbox(
|
||||
@@ -1056,13 +1061,13 @@ class ThumbRenderer(QObject):
|
||||
return im
|
||||
|
||||
@staticmethod
|
||||
def _image_raw_thumb(filepath: Path) -> Image.Image:
|
||||
def _image_raw_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""Render a thumbnail for a RAW image type.
|
||||
|
||||
Args:
|
||||
filepath (Path): The path of the file.
|
||||
"""
|
||||
im: Image.Image = None
|
||||
im: Image.Image | None = None
|
||||
try:
|
||||
with rawpy.imread(str(filepath)) as raw:
|
||||
rgb = raw.postprocess(use_camera_wb=True)
|
||||
@@ -1074,8 +1079,8 @@ class ThumbRenderer(QObject):
|
||||
)
|
||||
except (
|
||||
DecompressionBombError,
|
||||
rawpy._rawpy.LibRawIOError,
|
||||
rawpy._rawpy.LibRawFileUnsupportedError,
|
||||
rawpy._rawpy.LibRawIOError, # pyright: ignore[reportAttributeAccessIssue]
|
||||
rawpy._rawpy.LibRawFileUnsupportedError, # pyright: ignore[reportAttributeAccessIssue]
|
||||
) as e:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
|
||||
return im
|
||||
@@ -1111,13 +1116,13 @@ class ThumbRenderer(QObject):
|
||||
return im
|
||||
|
||||
@staticmethod
|
||||
def _image_thumb(filepath: Path) -> Image.Image:
|
||||
def _image_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""Render a thumbnail for a standard image type.
|
||||
|
||||
Args:
|
||||
filepath (Path): The path of the file.
|
||||
"""
|
||||
im: Image.Image = None
|
||||
im: Image.Image | None = None
|
||||
try:
|
||||
im = Image.open(filepath)
|
||||
if im.mode != "RGB" and im.mode != "RGBA":
|
||||
@@ -1144,7 +1149,7 @@ class ThumbRenderer(QObject):
|
||||
filepath (Path): The path of the file.
|
||||
size (tuple[int,int]): The size of the thumbnail.
|
||||
"""
|
||||
im: Image.Image = None
|
||||
im: Image.Image | None = None
|
||||
# Create an image to draw the svg to and a painter to do the drawing
|
||||
q_image: QImage = QImage(size, size, QImage.Format.Format_ARGB32)
|
||||
q_image.fill("#1e1e1e")
|
||||
@@ -1174,7 +1179,7 @@ class ThumbRenderer(QObject):
|
||||
return im
|
||||
|
||||
@staticmethod
|
||||
def _iwork_thumb(filepath: Path) -> Image.Image:
|
||||
def _iwork_thumb(filepath: Path) -> Image.Image | None:
|
||||
"""Extract and render a thumbnail for an Apple iWork (Pages, Numbers, Keynote) file.
|
||||
|
||||
Args:
|
||||
@@ -1212,7 +1217,7 @@ class ThumbRenderer(QObject):
|
||||
return im
|
||||
|
||||
@staticmethod
|
||||
def _model_stl_thumb(filepath: Path, size: int) -> Image.Image:
|
||||
def _model_stl_thumb(filepath: Path, size: int) -> Image.Image | None:
|
||||
"""Render a thumbnail for an STL file.
|
||||
|
||||
Args:
|
||||
@@ -1223,7 +1228,7 @@ class ThumbRenderer(QObject):
|
||||
# The following commented code describes a method for rendering via
|
||||
# matplotlib.
|
||||
# This implementation did not play nice with multithreading.
|
||||
im: Image.Image = None
|
||||
im: Image.Image | None = None
|
||||
# # Create a new plot
|
||||
# matplotlib.use('agg')
|
||||
# figure = plt.figure()
|
||||
@@ -1245,13 +1250,13 @@ class ThumbRenderer(QObject):
|
||||
return im
|
||||
|
||||
@staticmethod
|
||||
def _pdf_thumb(filepath: Path, size: int) -> Image.Image:
|
||||
def _pdf_thumb(filepath: Path, size: int) -> Image.Image | None:
|
||||
"""Render a thumbnail for a PDF file.
|
||||
|
||||
filepath (Path): The path of the file.
|
||||
size (int): The size of the icon.
|
||||
"""
|
||||
im: Image.Image = None
|
||||
im: Image.Image | None = None
|
||||
|
||||
file: QFile = QFile(filepath)
|
||||
success: bool = file.open(
|
||||
|
||||
Reference in New Issue
Block a user