Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Andrew Arneson
2024-05-13 20:45:47 -06:00
37 changed files with 14952 additions and 194 deletions

50
.github/workflows/apprun.yaml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: PySide App Test
on: [ push, pull_request ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install system dependencies
run: |
# dont run update, it is slow
# sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libxkbcommon-x11-0 \
x11-utils \
libyaml-dev \
libegl1-mesa \
libxcb-icccm4 \
libxcb-image0 \
libxcb-keysyms1 \
libxcb-randr0 \
libxcb-render-util0 \
libxcb-xinerama0 \
libopengl0 \
libxcb-cursor0
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: Run TagStudio app and check exit code
run: |
xvfb-run --server-args="-screen 0, 1920x1200x24 -ac +extension GLX +render -noreset" python tagstudio/tag_studio.py --ci -o /tmp/
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "TagStudio ran successfully"
else
echo "TagStudio failed with exit code $exit_code"
exit 1
fi

View File

@@ -1,2 +1,3 @@
ruff==0.4.2
pre-commit==3.7.0
Pyinstaller==6.6.0

View File

@@ -7,4 +7,3 @@ PySide6_Addons>=6.5.1.1,<=6.6.3.1
PySide6_Essentials>=6.5.1.1,<=6.6.3.1
typing_extensions>=3.10.0.0,<=4.11.0
ujson>=5.8.0,<=5.9.0
Pyinstaller==6.6.0

View File

Binary file not shown.

View File

@@ -12,6 +12,7 @@ import subprocess
import sys
import time
from PIL import Image, ImageOps, ImageChops, UnidentifiedImageError
from PIL.Image import DecompressionBombError
import pillow_avif
from pathlib import Path
import traceback
@@ -643,8 +644,12 @@ class CliDriver:
# raw.thumbnail((512, 512))
raw.thumbnail(self.external_preview_size)
raw.save(external_preview_path)
except:
print(f'{ERROR} Could not load image "{filepath}"')
except (
UnidentifiedImageError,
FileNotFoundError,
DecompressionBombError,
) as e:
print(f'{ERROR} Could not load image "{filepath} due to {e}"')
if self.args.external_preview:
self.set_external_preview_broken()
elif file_type in VIDEO_TYPES:
@@ -1109,24 +1114,34 @@ class CliDriver:
# sys.stdout.write(f'\r{INFO} Combining [{i+1}/{len(self.lib.entries)}]: {self.get_file_color(file_type)}{entry.path}{os.sep}{entry.filename}{RESET}')
# sys.stdout.flush()
if file_type in IMAGE_TYPES:
with Image.open(
os.path.normpath(
f"{self.lib.library_dir}/{entry.path}/{entry.filename}"
)
) as pic:
if keep_aspect:
pic.thumbnail((thumb_size, thumb_size))
else:
pic = pic.resize((thumb_size, thumb_size))
if data_tint_mode and color:
pic = pic.convert(mode="RGB")
pic = ImageChops.hard_light(
pic,
Image.new(
"RGB", (thumb_size, thumb_size), color
),
try:
with Image.open(
os.path.normpath(
f"{self.lib.library_dir}/{entry.path}/{entry.filename}"
)
collage.paste(pic, (y * thumb_size, x * thumb_size))
) as pic:
if keep_aspect:
pic.thumbnail((thumb_size, thumb_size))
else:
pic = pic.resize((thumb_size, thumb_size))
if data_tint_mode and color:
pic = pic.convert(mode="RGB")
pic = ImageChops.hard_light(
pic,
Image.new(
"RGB",
(thumb_size, thumb_size),
color,
),
)
collage.paste(
pic, (y * thumb_size, x * thumb_size)
)
except DecompressionBombError as e:
print(
f"[ERROR] One of the images was too big ({e})"
)
elif file_type in VIDEO_TYPES:
video = cv2.VideoCapture(filepath)
video.set(

View File

@@ -1,3 +0,0 @@
from .file_opener import open_file, FileOpenerHelper, FileOpenerLabel
from .function_iterator import FunctionIterator
from .custom_runnable import CustomRunnable

View File

@@ -17,8 +17,8 @@ from PySide6.QtCore import (QCoreApplication, QMetaObject, QRect,
from PySide6.QtGui import (QFont, QAction)
from PySide6.QtWidgets import (QComboBox, QFrame, QGridLayout,
QHBoxLayout, QVBoxLayout, QLayout, QLineEdit, QMainWindow,
QMenuBar, QPushButton, QScrollArea, QSizePolicy,
QStatusBar, QWidget, QSplitter, QMenu)
QPushButton, QScrollArea, QSizePolicy,
QStatusBar, QWidget, QSplitter)
from src.qt.pagination import Pagination
@@ -167,10 +167,10 @@ class Ui_MainWindow(QMainWindow):
self.statusbar.setSizePolicy(sizePolicy1)
MainWindow.setStatusBar(self.statusbar)
menu_bar = self.menuBar()
self.setMenuBar(menu_bar)
# menu_bar = self.menuBar()
# self.setMenuBar(menu_bar)
# self.gridLayout.addWidget(menu_bar, 4, 0, 1, 1, Qt.AlignRight)
self.frame_layout.addWidget(menu_bar)
# self.frame_layout.addWidget(menu_bar)
self.retranslateUi(MainWindow)
@@ -201,26 +201,26 @@ class Ui_MainWindow(QMainWindow):
# time.sleep(0.02) # sleep for 20ms
pass
def _createMenuBar(self, main_window):
menu_bar = QMenuBar(main_window)
file_menu = QMenu('&File', main_window)
edit_menu = QMenu('&Edit', main_window)
tools_menu = QMenu('&Tools', main_window)
macros_menu = QMenu('&Macros', main_window)
help_menu = QMenu('&Help', main_window)
# def _createMenuBar(self, main_window):
# menu_bar = QMenuBar(main_window)
# file_menu = QMenu('&File', main_window)
# edit_menu = QMenu('&Edit', main_window)
# tools_menu = QMenu('&Tools', main_window)
# macros_menu = QMenu('&Macros', main_window)
# help_menu = QMenu('&Help', main_window)
file_menu.addAction(QAction('&New Library', main_window))
file_menu.addAction(QAction('&Open Library', main_window))
file_menu.addAction(QAction('&Save Library', main_window))
file_menu.addAction(QAction('&Close Library', main_window))
# file_menu.addAction(QAction('&New Library', main_window))
# file_menu.addAction(QAction('&Open Library', main_window))
# file_menu.addAction(QAction('&Save Library', main_window))
# file_menu.addAction(QAction('&Close Library', main_window))
file_menu.addAction(QAction('&Refresh Directories', main_window))
file_menu.addAction(QAction('&Add New Files to Library', main_window))
# file_menu.addAction(QAction('&Refresh Directories', main_window))
# file_menu.addAction(QAction('&Add New Files to Library', main_window))
menu_bar.addMenu(file_menu)
menu_bar.addMenu(edit_menu)
menu_bar.addMenu(tools_menu)
menu_bar.addMenu(macros_menu)
menu_bar.addMenu(help_menu)
# menu_bar.addMenu(file_menu)
# menu_bar.addMenu(edit_menu)
# menu_bar.addMenu(tools_menu)
# menu_bar.addMenu(macros_menu)
# menu_bar.addMenu(help_menu)
main_window.setMenuBar(menu_bar)
# main_window.setMenuBar(menu_bar)

View File

@@ -1,11 +0,0 @@
from .tag_search import TagSearchPanel
from .build_tag import BuildTagPanel
from .tag_database import TagDatabasePanel
from .add_field import AddFieldModal
from .file_extension import FileExtensionModal
from .delete_unlinked import DeleteUnlinkedEntriesModal
from .relink_unlinked import RelinkUnlinkedEntries
from .fix_unlinked import FixUnlinkedEntriesModal
from .mirror_entities import MirrorEntriesModal
from .fix_dupes import FixDupeFilesModal
from .folders_to_tags import FoldersToTagsModal

View File

@@ -21,8 +21,9 @@ from PySide6.QtWidgets import (
from src.core.library import Library, Tag
from src.core.palette import ColorType, get_tag_color
from src.core.ts_core import TAG_COLORS
from src.qt.widgets import PanelWidget, PanelModal, TagWidget
from src.qt.modals import TagSearchPanel
from src.qt.widgets.panel import PanelWidget, PanelModal
from src.qt.widgets.tag import TagWidget
from src.qt.modals.tag_search import TagSearchPanel
ERROR = f"[ERROR]"

View File

@@ -16,8 +16,9 @@ from PySide6.QtWidgets import (
)
from src.core.library import ItemType, Library
from src.qt.helpers import CustomRunnable, FunctionIterator
from src.qt.widgets import ProgressWidget
from src.qt.helpers.custom_runnable import CustomRunnable
from src.qt.helpers.function_iterator import FunctionIterator
from src.qt.widgets.progress import ProgressWidget
# Only import for type checking/autocompletion, will not be imported at runtime.
if typing.TYPE_CHECKING:

View File

@@ -7,7 +7,7 @@ from PySide6.QtCore import Signal, Qt
from PySide6.QtWidgets import QVBoxLayout, QPushButton, QTableWidget, QTableWidgetItem
from src.core.library import Library
from src.qt.widgets import PanelWidget
from src.qt.widgets.panel import PanelWidget
class FileExtensionModal(PanelWidget):

View File

@@ -17,7 +17,7 @@ from PySide6.QtWidgets import (
)
from src.core.library import Library
from src.qt.modals import MirrorEntriesModal
from src.qt.modals.mirror_entities import MirrorEntriesModal
# Only import for type checking/autocompletion, will not be imported at runtime.
if typing.TYPE_CHECKING:

View File

@@ -10,9 +10,11 @@ from PySide6.QtCore import QThread, Qt, QThreadPool
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton
from src.core.library import Library
from src.qt.helpers import FunctionIterator, CustomRunnable
from src.qt.modals import DeleteUnlinkedEntriesModal, RelinkUnlinkedEntries
from src.qt.widgets import ProgressWidget
from src.qt.helpers.function_iterator import FunctionIterator
from src.qt.helpers.custom_runnable import CustomRunnable
from src.qt.modals.delete_unlinked import DeleteUnlinkedEntriesModal
from src.qt.modals.relink_unlinked import RelinkUnlinkedEntries
from src.qt.widgets.progress import ProgressWidget
# Only import for type checking/autocompletion, will not be imported at runtime.
if typing.TYPE_CHECKING:

View File

@@ -18,8 +18,9 @@ from PySide6.QtWidgets import (
)
from src.core.library import Library
from src.qt.helpers import FunctionIterator, CustomRunnable
from src.qt.widgets import ProgressWidget
from src.qt.helpers.function_iterator import FunctionIterator
from src.qt.helpers.custom_runnable import CustomRunnable
from src.qt.widgets.progress import ProgressWidget
# Only import for type checking/autocompletion, will not be imported at runtime.
if typing.TYPE_CHECKING:

View File

@@ -7,8 +7,9 @@ import typing
from PySide6.QtCore import QObject, Signal, QThreadPool
from src.core.library import Library
from src.qt.helpers import FunctionIterator, CustomRunnable
from src.qt.widgets import ProgressWidget
from src.qt.helpers.function_iterator import FunctionIterator
from src.qt.helpers.custom_runnable import CustomRunnable
from src.qt.widgets.progress import ProgressWidget
# Only import for type checking/autocompletion, will not be imported at runtime.
if typing.TYPE_CHECKING:

View File

@@ -13,8 +13,9 @@ from PySide6.QtWidgets import (
)
from src.core.library import Library
from src.qt.widgets import PanelWidget, PanelModal, TagWidget
from src.qt.modals import BuildTagPanel
from src.qt.widgets.panel import PanelWidget, PanelModal
from src.qt.widgets.tag import TagWidget
from src.qt.modals.build_tag import BuildTagPanel
class TagDatabasePanel(PanelWidget):

View File

@@ -19,7 +19,8 @@ from PySide6.QtWidgets import (
from src.core.library import Library
from src.core.palette import ColorType, get_tag_color
from src.qt.widgets import PanelWidget, TagWidget
from src.qt.widgets.panel import PanelWidget
from src.qt.widgets.tag import TagWidget
ERROR = f"[ERROR]"

View File

@@ -8,6 +8,6 @@
<!-- <file alias = "images/edit_icon_128.png">../../resources/qt/images/edit_icon_128.png</file> -->
<!-- <file alias = "images/trash_icon_128.png">../../resources/qt/images/trash_icon_128.png</file> -->
<!-- <file alias = "images/clipboard_icon_128.png">../../resources/qt/images/clipboard_icon_128.png</file> -->
<!-- <file alias = "images/splash.png">../../resources/qt/images/splash.png</file> -->
<file alias = "images/splash.png">../../resources/qt/images/splash.png</file>
</qresource>
</RCC>

File diff suppressed because it is too large Load Diff

View File

@@ -42,6 +42,7 @@ from PySide6.QtWidgets import (
QFileDialog,
QSplashScreen,
QMenu,
QMenuBar,
)
from humanfriendly import format_timespan
@@ -73,24 +74,20 @@ from src.core.ts_core import (
from src.core.utils.web import strip_web_protocol
from src.qt.flowlayout import FlowLayout
from src.qt.main_window import Ui_MainWindow
from src.qt.helpers import FunctionIterator, CustomRunnable
from src.qt.widgets import (
CollageIconRenderer,
ThumbRenderer,
PanelModal,
ProgressWidget,
PreviewPanel,
ItemThumb,
)
from src.qt.modals import (
BuildTagPanel,
TagDatabasePanel,
FileExtensionModal,
FixUnlinkedEntriesModal,
FixDupeFilesModal,
FoldersToTagsModal,
)
from src.qt.helpers.function_iterator import FunctionIterator
from src.qt.helpers.custom_runnable import CustomRunnable
from src.qt.widgets.collage_icon import CollageIconRenderer
from src.qt.widgets.panel import PanelModal
from src.qt.widgets.thumb_renderer import ThumbRenderer
from src.qt.widgets.progress import ProgressWidget
from src.qt.widgets.preview_panel import PreviewPanel
from src.qt.widgets.item_thumb import ItemThumb
from src.qt.modals.build_tag import BuildTagPanel
from src.qt.modals.tag_database import TagDatabasePanel
from src.qt.modals.file_extension import FileExtensionModal
from src.qt.modals.fix_unlinked import FixUnlinkedEntriesModal
from src.qt.modals.fix_dupes import FixDupeFilesModal
from src.qt.modals.folders_to_tags import FoldersToTagsModal
import src.qt.resources_rc
# SIGQUIT is not defined on Windows
@@ -203,6 +200,9 @@ class QtDriver(QObject):
)
max_threads = os.cpu_count()
if args.ci:
# spawn only single worker in CI environment
max_threads = 1
for i in range(max_threads):
# thread = threading.Thread(target=self.consumer, name=f'ThumbRenderer_{i}',args=(), daemon=True)
# thread.start()
@@ -246,6 +246,7 @@ class QtDriver(QObject):
# Handle OS signals
self.setup_signals()
# allow to process input from console, eg. SIGTERM
timer = QTimer()
timer.start(500)
timer.timeout.connect(lambda: None)
@@ -267,19 +268,24 @@ class QtDriver(QObject):
# self.windowFX = WindowEffect()
# self.windowFX.setAcrylicEffect(self.main_window.winId())
splash_pixmap = QPixmap(
f"{Path(__file__).parents[2]}/resources/qt/images/splash.png"
)
splash_pixmap = QPixmap(":/images/splash.png")
splash_pixmap.setDevicePixelRatio(self.main_window.devicePixelRatio())
self.splash = QSplashScreen(splash_pixmap)
self.splash = QSplashScreen(splash_pixmap, Qt.WindowStaysOnTopHint)
# self.splash.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.splash.show()
menu_bar = self.main_window.menuBar()
if os.name == "nt":
appid = "cyanvoxel.tagstudio.9"
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid)
# Allow the use of the native macOS menu bar.
# if sys.platform != "darwin":
menu_bar.setNativeMenuBar(False)
if sys.platform != "darwin":
icon = QIcon()
icon.addFile(icon_path)
app.setWindowIcon(icon)
menu_bar = QMenuBar(self.main_window)
self.main_window.setMenuBar(menu_bar)
menu_bar.setNativeMenuBar(True)
file_menu = QMenu("&File", menu_bar)
edit_menu = QMenu("&Edit", menu_bar)
@@ -445,26 +451,9 @@ class QtDriver(QObject):
menu_bar.addMenu(macros_menu)
menu_bar.addMenu(help_menu)
# self.main_window.setMenuBar(menu_bar)
# self.main_window.centralWidget().layout().addWidget(menu_bar, 0,0,1,1)
# self.main_window.tb_layout.addWidget(menu_bar)
if sys.platform != "darwin":
icon = QIcon()
icon.addFile(icon_path)
self.main_window.setWindowIcon(icon)
self.preview_panel = PreviewPanel(self.lib, self)
l: QHBoxLayout = self.main_window.splitter
l.addWidget(self.preview_panel)
# self.preview_panel.update_widgets()
# l.setEnabled(False)
# self.entry_panel.setWindowIcon(icon)
if os.name == "nt":
appid = "cyanvoxel.tagstudio.9"
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid)
app.setWindowIcon(icon)
QFontDatabase.addApplicationFont(
os.path.normpath(
@@ -477,7 +466,6 @@ class QtDriver(QObject):
self.item_thumbs: list[ItemThumb] = []
self.thumb_renderers: list[ThumbRenderer] = []
self.collation_thumb_size = math.ceil(self.thumb_size * 2)
# self.filtered_items: list[tuple[SearchItemType, int]] = []
self._init_thumb_grid()
@@ -549,7 +537,11 @@ class QtDriver(QObject):
)
self.open_library(lib)
app.exec_()
if self.args.ci:
# gracefully terminate the app in CI environment
self.thumb_job_queue.put((self.SIGTERM.emit, []))
app.exec()
self.shutdown()

View File

@@ -1,13 +0,0 @@
from .fields import FieldContainer, FieldWidget
from .collage_icon import CollageIconRenderer
from .thumb_button import ThumbButton
from .thumb_renderer import ThumbRenderer
from .panel import PanelWidget, PanelModal
from .text_box_edit import EditTextBox
from .text_line_edit import EditTextLine
from .progress import ProgressWidget
from .tag import TagWidget
from .tag_box import TagBoxWidget
from .text import TextWidget
from .item_thumb import ItemThumb
from .preview_panel import PreviewPanel

View File

@@ -9,6 +9,7 @@ from pathlib import Path
import cv2
from PIL import Image, ImageChops, UnidentifiedImageError
from PIL.Image import DecompressionBombError
from PySide6.QtCore import (
QObject,
QThread,
@@ -95,22 +96,25 @@ class CollageIconRenderer(QObject):
# sys.stdout.write(f'\r{INFO} Combining [{i+1}/{len(self.lib.entries)}]: {self.get_file_color(file_type)}{entry.path}{os.sep}{entry.filename}{RESET}')
# sys.stdout.flush()
if file_type in IMAGE_TYPES:
with Image.open(
os.path.normpath(
f"{self.lib.library_dir}/{entry.path}/{entry.filename}"
)
) as pic:
if keep_aspect:
pic.thumbnail(size)
else:
pic = pic.resize(size)
if data_tint_mode and color:
pic = pic.convert(mode="RGB")
pic = ImageChops.hard_light(
pic, Image.new("RGB", size, color)
try:
with Image.open(
os.path.normpath(
f"{self.lib.library_dir}/{entry.path}/{entry.filename}"
)
# collage.paste(pic, (y*thumb_size, x*thumb_size))
self.rendered.emit(pic)
) as pic:
if keep_aspect:
pic.thumbnail(size)
else:
pic = pic.resize(size)
if data_tint_mode and color:
pic = pic.convert(mode="RGB")
pic = ImageChops.hard_light(
pic, Image.new("RGB", size, color)
)
# collage.paste(pic, (y*thumb_size, x*thumb_size))
self.rendered.emit(pic)
except DecompressionBombError as e:
logging.info(f"[ERROR] One of the images was too big ({e})")
elif file_type in VIDEO_TYPES:
video = cv2.VideoCapture(filepath)
video.set(

View File

@@ -26,11 +26,12 @@ from PySide6.QtWidgets import (
from src.core.library import ItemType, Library, Entry
from src.core.ts_core import AUDIO_TYPES, VIDEO_TYPES, IMAGE_TYPES
from src.qt.flowlayout import FlowWidget
from src.qt.helpers import FileOpenerHelper
from src.qt.widgets import ThumbRenderer, ThumbButton
from src.qt.helpers.file_opener import FileOpenerHelper
from src.qt.widgets.thumb_renderer import ThumbRenderer
from src.qt.widgets.thumb_button import ThumbButton
if typing.TYPE_CHECKING:
from src.qt.widgets import PreviewPanel
from src.qt.widgets.preview_panel import PreviewPanel
ERROR = f"[ERROR]"
WARNING = f"[WARNING]"

View File

@@ -11,6 +11,7 @@ from datetime import datetime as dt
import cv2
from PIL import Image, UnidentifiedImageError
from PIL.Image import DecompressionBombError
from PySide6.QtCore import Signal, Qt, QSize
from PySide6.QtGui import QResizeEvent, QAction
from PySide6.QtWidgets import (
@@ -29,18 +30,16 @@ from humanfriendly import format_size
from src.core.library import Entry, ItemType, Library
from src.core.ts_core import VIDEO_TYPES, IMAGE_TYPES
from src.qt.helpers import FileOpenerLabel, FileOpenerHelper, open_file
from src.qt.modals import AddFieldModal
from src.qt.widgets import (
ThumbRenderer,
FieldContainer,
TagBoxWidget,
TextWidget,
PanelModal,
EditTextBox,
EditTextLine,
ItemThumb,
)
from src.qt.helpers.file_opener import FileOpenerLabel, FileOpenerHelper, open_file
from src.qt.modals.add_field import AddFieldModal
from src.qt.widgets.thumb_renderer import ThumbRenderer
from src.qt.widgets.fields import FieldContainer
from src.qt.widgets.tag_box import TagBoxWidget
from src.qt.widgets.text import TextWidget
from src.qt.widgets.panel import PanelModal
from src.qt.widgets.text_box_edit import EditTextBox
from src.qt.widgets.text_line_edit import EditTextLine
from src.qt.widgets.item_thumb import ItemThumb
# Only import for type checking/autocompletion, will not be imported at runtime.
if typing.TYPE_CHECKING:
@@ -405,8 +404,15 @@ class PreviewPanel(QWidget):
)
raise UnidentifiedImageError
except (UnidentifiedImageError, FileNotFoundError, cv2.error):
pass
except (
UnidentifiedImageError,
FileNotFoundError,
cv2.error,
DecompressionBombError,
) as e:
logging.info(
f"[PreviewPanel][ERROR] Couldn't Render thumbnail for {filepath} (because of {e})"
)
try:
self.preview_img.clicked.disconnect()

View File

@@ -12,8 +12,11 @@ from PySide6.QtWidgets import QPushButton
from src.core.library import Library, Tag
from src.qt.flowlayout import FlowLayout
from src.qt.widgets import FieldWidget, TagWidget, PanelModal
from src.qt.modals import BuildTagPanel, TagSearchPanel
from src.qt.widgets.fields import FieldWidget
from src.qt.widgets.tag import TagWidget
from src.qt.widgets.panel import PanelModal
from src.qt.modals.build_tag import BuildTagPanel
from src.qt.modals.tag_search import TagSearchPanel
# Only import for type checking/autocompletion, will not be imported at runtime.
if typing.TYPE_CHECKING:

View File

@@ -5,7 +5,7 @@
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QHBoxLayout, QLabel
from src.qt.widgets import FieldWidget
from src.qt.widgets.fields import FieldWidget
class TextWidget(FieldWidget):

View File

@@ -5,7 +5,7 @@
from PySide6.QtWidgets import QVBoxLayout, QPlainTextEdit
from src.qt.widgets import PanelWidget
from src.qt.widgets.panel import PanelWidget
class EditTextBox(PanelWidget):

View File

@@ -5,7 +5,7 @@ from typing import Callable
from PySide6.QtWidgets import QVBoxLayout, QLineEdit
from src.qt.widgets import PanelWidget
from src.qt.widgets.panel import PanelWidget
class EditTextLine(PanelWidget):

View File

@@ -21,6 +21,7 @@ from PIL import (
ImageOps,
ImageFile,
)
from PIL.Image import DecompressionBombError
from PySide6.QtCore import QObject, Signal, QSize
from PySide6.QtGui import QPixmap
from src.core.ts_core import PLAINTEXT_TYPES, VIDEO_TYPES, IMAGE_TYPES
@@ -138,17 +139,22 @@ class ThumbRenderer(QObject):
try:
# Images =======================================================
if extension in IMAGE_TYPES:
image = Image.open(filepath)
# image = self.thumb_debug
if image.mode == "RGBA":
# logging.info(image.getchannel(3).tobytes())
new_bg = Image.new("RGB", image.size, color="#1e1e1e")
new_bg.paste(image, mask=image.getchannel(3))
image = new_bg
if image.mode != "RGB":
image = image.convert(mode="RGB")
try:
image = Image.open(filepath)
# image = self.thumb_debug
if image.mode == "RGBA":
# logging.info(image.getchannel(3).tobytes())
new_bg = Image.new("RGB", image.size, color="#1e1e1e")
new_bg.paste(image, mask=image.getchannel(3))
image = new_bg
if image.mode != "RGB":
image = image.convert(mode="RGB")
image = ImageOps.exif_transpose(image)
image = ImageOps.exif_transpose(image)
except DecompressionBombError as e:
logging.info(
f"[ThumbRenderer][ERROR] Couldn't Render thumbnail for {filepath} (because of {e})"
)
# Videos =======================================================
elif extension in VIDEO_TYPES:
@@ -321,17 +327,22 @@ class ThumbRenderer(QObject):
try:
# Images =======================================================
if extension in IMAGE_TYPES:
image = Image.open(filepath)
# image = self.thumb_debug
if image.mode == "RGBA":
# logging.info(image.getchannel(3).tobytes())
new_bg = Image.new("RGB", image.size, color="#1e1e1e")
new_bg.paste(image, mask=image.getchannel(3))
image = new_bg
if image.mode != "RGB":
image = image.convert(mode="RGB")
try:
image = Image.open(filepath)
# image = self.thumb_debug
if image.mode == "RGBA":
# logging.info(image.getchannel(3).tobytes())
new_bg = Image.new("RGB", image.size, color="#1e1e1e")
new_bg.paste(image, mask=image.getchannel(3))
image = new_bg
if image.mode != "RGB":
image = image.convert(mode="RGB")
image = ImageOps.exif_transpose(image)
image = ImageOps.exif_transpose(image)
except DecompressionBombError as e:
logging.info(
f"[ThumbRenderer][ERROR] Couldn't Render thumbnail for {filepath} (because of {e})"
)
# Videos =======================================================
elif extension in VIDEO_TYPES:

View File

@@ -53,6 +53,11 @@ def main():
type=str,
help="User interface option for TagStudio. Options: qt, cli (Default: qt)",
)
parser.add_argument(
"--ci",
action=argparse.BooleanOptionalAction,
help="Exit the application after checking it starts without any problem. Meant for CI check.",
)
args = parser.parse_args()
core = TagStudioCore() # The TagStudio Core instance. UI agnostic.