diff --git a/requirements.txt b/requirements.txt index 5658340c5..c2bddc36a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ pydub==0.25.1 mutagen==1.47.0 numpy==1.26.4 ffmpeg-python==0.2.0 +vtf2img==0.1.0 diff --git a/tagstudio/src/core/media_types.py b/tagstudio/src/core/media_types.py index 449aa8aee..6eca3e128 100644 --- a/tagstudio/src/core/media_types.py +++ b/tagstudio/src/core/media_types.py @@ -35,6 +35,7 @@ class MediaType(str, Enum): PRESENTATION: str = "presentation" PROGRAM: str = "program" SHORTCUT: str = "shortcut" + SOURCE_ENGINE: str = "source_engine" SPREADSHEET: str = "spreadsheet" TEXT: str = "text" VIDEO: str = "video" @@ -236,6 +237,20 @@ class MediaCategories: ".ts", ".txt", ".xml", + ".vmt", + ".fgd", + ".nut", + ".cfg", + ".conf", + ".vdf", + ".vcfg", + ".gi", + ".inf", + ".vqlayout", + ".qss", + ".vsc", + ".kv3", + ".vsnd_template", } _PRESENTATION_SET: set[str] = { ".key", @@ -244,6 +259,9 @@ class MediaCategories: ".pptx", } _PROGRAM_SET: set[str] = {".app", ".exe"} + _SOURCE_ENGINE_SET: set[str] = { + ".vtf", + } _SHORTCUT_SET: set[str] = {".desktop", ".lnk", ".url"} _SPREADSHEET_SET: set[str] = { ".csv", @@ -377,6 +395,11 @@ class MediaCategories: extensions=_SHORTCUT_SET, is_iana=False, ) + SOURCE_ENGINE_TYPES: MediaCategory = MediaCategory( + media_type=MediaType.SOURCE_ENGINE, + extensions=_SOURCE_ENGINE_SET, + is_iana=False, + ) SPREADSHEET_TYPES: MediaCategory = MediaCategory( media_type=MediaType.SPREADSHEET, extensions=_SPREADSHEET_SET, @@ -416,6 +439,7 @@ class MediaCategories: PRESENTATION_TYPES, PROGRAM_TYPES, SHORTCUT_TYPES, + SOURCE_ENGINE_TYPES, SPREADSHEET_TYPES, TEXT_TYPES, VIDEO_TYPES, diff --git a/tagstudio/src/core/palette.py b/tagstudio/src/core/palette.py index 7edacc747..f170a0de4 100644 --- a/tagstudio/src/core/palette.py +++ b/tagstudio/src/core/palette.py @@ -284,6 +284,12 @@ class ColorType(int, Enum): ColorType.LIGHT_ACCENT: "#FFFFFF", ColorType.DARK_ACCENT: "#1e1e1e", }, + "red": { + ColorType.PRIMARY: "#e22c3c", + ColorType.BORDER: "#e54252", + ColorType.LIGHT_ACCENT: "#f39caa", + ColorType.DARK_ACCENT: "#440d12", + }, "green": { ColorType.PRIMARY: "#28bb48", ColorType.BORDER: "#43c568", @@ -296,12 +302,6 @@ class ColorType(int, Enum): ColorType.LIGHT_ACCENT: "#EFD4FB", ColorType.DARK_ACCENT: "#3E1555", }, - "red": { - ColorType.PRIMARY: "#e22c3c", - ColorType.BORDER: "#e54252", - ColorType.LIGHT_ACCENT: "#f39caa", - ColorType.DARK_ACCENT: "#440d12", - }, "theme_dark": { ColorType.PRIMARY: "#333333", ColorType.BORDER: "#555555", diff --git a/tagstudio/src/qt/widgets/fields.py b/tagstudio/src/qt/widgets/fields.py index 03b43d848..dffca2a56 100644 --- a/tagstudio/src/qt/widgets/fields.py +++ b/tagstudio/src/qt/widgets/fields.py @@ -47,6 +47,10 @@ def __init__(self, title: str = "Field", inline: bool = True) -> None: self.edit_icon_128 = theme_fg_overlay(FieldContainer.edit_icon_128) self.trash_icon_128 = theme_fg_overlay(FieldContainer.trash_icon_128) + self.clipboard_icon_128 = theme_fg_overlay(FieldContainer.clipboard_icon_128) + self.edit_icon_128 = theme_fg_overlay(FieldContainer.edit_icon_128) + self.trash_icon_128 = theme_fg_overlay(FieldContainer.trash_icon_128) + self.root_layout = QVBoxLayout(self) self.root_layout.setObjectName("baseLayout") self.root_layout.setContentsMargins(0, 0, 0, 0) diff --git a/tagstudio/src/qt/widgets/thumb_renderer.py b/tagstudio/src/qt/widgets/thumb_renderer.py index 4bcb4158c..a4ec4866d 100644 --- a/tagstudio/src/qt/widgets/thumb_renderer.py +++ b/tagstudio/src/qt/widgets/thumb_renderer.py @@ -8,6 +8,7 @@ from copy import deepcopy from io import BytesIO from pathlib import Path +import struct import cv2 import numpy as np @@ -39,6 +40,7 @@ from src.qt.helpers.gradient import four_corner_gradient from src.qt.helpers.text_wrapper import wrap_full_text from src.qt.resource_manager import ResourceManager +from vtf2img import Parser ImageFile.LOAD_TRUNCATED_IMAGES = True @@ -600,6 +602,33 @@ def _blender(self, filepath: Path) -> Image.Image: ) return im + def _source_engine(self, filepath: Path) -> Image.Image: + """ + This is a function to convert the VTF (Valve Texture Format) files to thumbnails, it works using the VTF2IMG library for PILLOW. + """ + parser = Parser(filepath) + im: Image.Image = None + try: + im = parser.get_image() + + except ( + AttributeError, + UnidentifiedImageError, + FileNotFoundError, + TypeError, + struct.error, + ) as e: + if str(e) == "expected string or buffer": + logging.info( + f"[ThumbRenderer][VTF][INFO] {filepath.name} Doesn't have an embedded thumbnail. ({type(e).__name__})" + ) + + else: + logging.error( + f"[ThumbRenderer][VTF][ERROR]: Couldn't render thumbnail for {filepath.name} ({type(e).__name__})" + ) + return im + def _font_short_thumb(self, filepath: Path, size: int) -> Image.Image: """Render a small font preview ("Aa") thumbnail from a font file. @@ -961,6 +990,10 @@ def render( elif MediaType.BLENDER in MediaCategories.get_types(ext): image = self._blender(_filepath) + # VTF ========================================================== + elif MediaType.SOURCE_ENGINE in MediaCategories.get_types(ext): + image = self._source_engine(_filepath) + # No Rendered Thumbnail ======================================== if not image: raise UnidentifiedImageError