feat: dismiss update notification, change missing ffmpeg popup to banner (#1400)

* feat: dismiss update notification, change ffmpeg to banner

* chore: sort resources.pyi list

* fix: remove unused asset

* refactor: rename dismiss_update()

* fix: remove *other* unused asset
This commit is contained in:
Travis Abendshien
2026-06-29 02:18:45 -07:00
committed by GitHub
parent 4919c972da
commit 6b15beefbd
16 changed files with 142 additions and 136 deletions
+1
View File
@@ -11,6 +11,7 @@ COPYRIGHT_COMPACT: str = f"© {COPYRIGHT_YEARS} Travis Abendshien\n& TagStudio C
GITHUB_REPO_URL = "https://github.com/TagStudioDev/TagStudio"
GITHUB_RELEASE_URL = "https://github.com/TagStudioDev/TagStudio/releases/latest"
DOCS_URL = "https://docs.tagstud.io"
FFMPEG_HELP_URL = "https://docs.tagstud.io/help/ffmpeg"
DISCORD_URL = "https://discord.com/invite/hRNnVKhF2G"
# The folder & file names where TagStudio keeps its data relative to a library.
+4 -4
View File
@@ -8,7 +8,7 @@ import structlog
from PySide6.QtCore import QSettings
from tagstudio.core.constants import TS_FOLDER_NAME
from tagstudio.core.enums import SettingItems
from tagstudio.core.enums import AppCacheItems
from tagstudio.core.library.alchemy.library import LibraryStatus
from tagstudio.qt.global_settings import GlobalSettings
@@ -30,16 +30,16 @@ class DriverMixin:
logger.error("Path does not exist.", open_path=open_path)
return LibraryStatus(success=False, message="Path does not exist.")
elif self.settings.open_last_loaded_on_startup and self.cached_values.value(
SettingItems.LAST_LIBRARY
AppCacheItems.LAST_LIBRARY
):
library_path = Path(str(self.cached_values.value(SettingItems.LAST_LIBRARY)))
library_path = Path(str(self.cached_values.value(AppCacheItems.LAST_LIBRARY)))
if not (library_path / TS_FOLDER_NAME).exists():
logger.error(
"TagStudio folder does not exist.",
library_path=library_path,
ts_folder=TS_FOLDER_NAME,
)
self.cached_values.setValue(SettingItems.LAST_LIBRARY, "")
self.cached_values.setValue(AppCacheItems.LAST_LIBRARY, "")
# dont consider this a fatal error, just skip opening the library
library_path = None
+2 -1
View File
@@ -5,11 +5,12 @@
import enum
class SettingItems(enum.StrEnum):
class AppCacheItems(enum.StrEnum):
"""List of setting item names."""
LAST_LIBRARY = "last_library"
LIBS_LIST = "libs_list"
DISMISSED_UPDATE = "dismissed_update"
class ShowFilepathOption(enum.IntEnum):
@@ -1,57 +0,0 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
from shutil import which
import structlog
from PySide6.QtCore import Qt, QUrl
from PySide6.QtGui import QDesktopServices
from PySide6.QtWidgets import QMessageBox
from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
from tagstudio.qt.previews.vendored.ffmpeg import FFMPEG_CMD, FFPROBE_CMD
from tagstudio.qt.translations import Translations
logger = structlog.get_logger(__name__)
class FfmpegMissingMessageBox(QMessageBox):
"""A warning dialog for if FFmpeg is missing."""
HELP_URL = "https://docs.tagstud.io/help/ffmpeg/"
def __init__(self):
super().__init__()
ffmpeg = "FFmpeg"
ffprobe = "FFprobe"
title = Translations.format("dependency.missing.title", dependency=ffmpeg)
self.setWindowTitle(title)
self.setIcon(QMessageBox.Icon.Warning)
self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.setStandardButtons(
QMessageBox.StandardButton.Help
| QMessageBox.StandardButton.Ignore
| QMessageBox.StandardButton.Cancel
)
self.setDefaultButton(QMessageBox.StandardButton.Ignore)
# Enables the cancel button but hides it to allow for click X to close dialog
self.button(QMessageBox.StandardButton.Cancel).hide()
self.button(QMessageBox.StandardButton.Help).clicked.connect(
lambda: QDesktopServices.openUrl(QUrl(self.HELP_URL))
)
red = get_ui_color(ColorType.PRIMARY, UiColor.RED)
green = get_ui_color(ColorType.PRIMARY, UiColor.GREEN)
missing = f"<span style='color:{red}'>{Translations['generic.missing']}</span>"
found = f"<span style='color:{green}'>{Translations['about.module.found']}</span>"
status = Translations.format(
"ffmpeg.missing.status",
ffmpeg=ffmpeg,
ffmpeg_status=found if which(FFMPEG_CMD) else missing,
ffprobe=ffprobe,
ffprobe_status=found if which(FFPROBE_CMD) else missing,
)
self.setText(f"{Translations['ffmpeg.missing.description']}<br><br>{status}")
@@ -3,12 +3,14 @@
import typing
from shutil import which
from warnings import catch_warnings
from tagstudio.core.library.alchemy.fields import BaseFieldTemplate
from tagstudio.core.library.alchemy.library import Library
from tagstudio.qt.controllers.field_template_search_panel_controller import FieldTemplateSearchModal
from tagstudio.qt.controllers.tag_search_panel_controller import TagSearchModal
from tagstudio.qt.previews.vendored.ffmpeg import FFMPEG_CMD, FFPROBE_CMD
from tagstudio.qt.views.preview_panel_view import PreviewPanelView
if typing.TYPE_CHECKING:
@@ -21,6 +23,7 @@ class PreviewPanel(PreviewPanelView):
self.__add_field_modal = FieldTemplateSearchModal(self.lib, is_field_template_chooser=True)
self.__add_tag_modal = TagSearchModal(self.lib, is_tag_chooser=True)
self._thumb.check_ffmpeg.connect(self._toggle_ffmpeg_warning)
@typing.override
def _add_field_button_callback(self) -> None:
@@ -50,3 +53,10 @@ class PreviewPanel(PreviewPanelView):
self._containers.add_tags_to_selected(tag_id)
if len(self._selected) == 1:
self._containers.update_from_entry(self._selected[0])
def _toggle_ffmpeg_warning(self, enable_warning: bool = True) -> None:
if enable_warning and (not which(FFMPEG_CMD) or not which(FFPROBE_CMD)):
self._ffmpeg_warning_widget.show()
return
self._ffmpeg_warning_widget.hide()
@@ -3,11 +3,12 @@
import math
from functools import partial
import structlog
from PIL import ImageQt
from PySide6.QtCore import Qt
from PySide6.QtGui import QPixmap
from PySide6.QtGui import QDesktopServices, QPixmap
from PySide6.QtWidgets import QMessageBox
from tagstudio.core.constants import GITHUB_RELEASE_URL, VERSION
@@ -20,14 +21,13 @@ from tagstudio.qt.translations import Translations
logger = structlog.get_logger(__name__)
class OutOfDateMessageBox(QMessageBox):
class UpdateAvailableMessageBox(QMessageBox):
"""A warning dialog for if the TagStudio is not running under the latest release version."""
def __init__(self):
super().__init__()
rm = ResourceManager()
title = Translations["version_modal.title"]
self.setWindowTitle(title)
pixel_ratio = self.devicePixelRatio()
@@ -40,13 +40,19 @@ class OutOfDateMessageBox(QMessageBox):
icon.setDevicePixelRatio(pixel_ratio)
self.setIconPixmap(icon)
self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.setStyleSheet("QPushButton {padding: 3px 8px;}")
self.setStandardButtons(
QMessageBox.StandardButton.Ignore | QMessageBox.StandardButton.Cancel
QMessageBox.StandardButton.Close
| QMessageBox.StandardButton.Ignore
| QMessageBox.StandardButton.Ok
)
self.setDefaultButton(QMessageBox.StandardButton.Ignore)
# Enables the cancel button but hides it to allow for click X to close dialog
self.button(QMessageBox.StandardButton.Cancel).hide()
self.setDefaultButton(QMessageBox.StandardButton.Ok)
self.button(QMessageBox.StandardButton.Ok).setText(Translations["update.view_update"])
self.button(QMessageBox.StandardButton.Ok).clicked.connect(
partial(QDesktopServices.openUrl, GITHUB_RELEASE_URL)
)
self.button(QMessageBox.StandardButton.Ignore).setText(Translations["generic.dont_remind"])
red = get_ui_color(ColorType.PRIMARY, UiColor.RED)
green = get_ui_color(ColorType.PRIMARY, UiColor.GREEN)
+3 -3
View File
@@ -98,9 +98,9 @@ Image.MAX_IMAGE_PIXELS = None
register_heif_opener()
try:
import pillow_jxl # noqa: F401 # pyright: ignore[reportUnusedImport]
except ImportError:
logger.exception('[ThumbRenderer] Could not import the "pillow_jxl" module')
import pillow_jxl # noqa: F401 # pyright: ignore
except ImportError as e:
logger.error('[ThumbRenderer] Could not import the "pillow_jxl" module', error=e)
class _SevenZipFile(py7zr.SevenZipFile):
+1
View File
@@ -22,6 +22,7 @@ class ResourceManager:
adobe_illustrator: Image.Image
adobe_photoshop: Image.Image
affinity_photo: Image.Image
alert: QPixmap
archive: Image.Image
audio: Image.Image
broken_link_icon: Image.Image
+4
View File
@@ -15,6 +15,10 @@
"mode": "pil",
"path": "qt/images/file_icons/affinity_photo.png"
},
"alert": {
"mode": "qpixmap",
"path": "qt/images/alert.png"
},
"archive": {
"mode": "pil",
"path": "qt/images/file_icons/archive.png"
+32 -37
View File
@@ -17,9 +17,9 @@ import sys
import time
from argparse import Namespace
from collections import OrderedDict
from functools import partial
from pathlib import Path
from queue import Queue
from shutil import which
from typing import TypeVar
from warnings import catch_warnings
@@ -37,23 +37,14 @@ from PySide6.QtGui import (
QMouseEvent,
QPalette,
)
from PySide6.QtWidgets import (
QApplication,
QFileDialog,
QMessageBox,
QPushButton,
QScrollArea,
)
from PySide6.QtWidgets import QApplication, QFileDialog, QMessageBox, QPushButton, QScrollArea
# This import has side-effect of importing PySide resources
import tagstudio.qt.resources_rc # noqa: F401 # pyright: ignore[reportUnusedImport]
from tagstudio.core.constants import TAG_ARCHIVED, TAG_FAVORITE, VERSION, VERSION_BRANCH
from tagstudio.core.driver import DriverMixin
from tagstudio.core.enums import MacroID, SettingItems, ShowFilepathOption
from tagstudio.core.library.alchemy.enums import (
BrowsingState,
SortingModeEnum,
)
from tagstudio.core.enums import AppCacheItems, MacroID, ShowFilepathOption
from tagstudio.core.library.alchemy.enums import BrowsingState, SortingModeEnum
from tagstudio.core.library.alchemy.library import Library, LibraryStatus
from tagstudio.core.library.alchemy.models import Entry
from tagstudio.core.library.ignore import Ignore
@@ -64,18 +55,13 @@ from tagstudio.core.ts_core import TagStudioCore
from tagstudio.core.utils.str_formatting import is_version_outdated
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.cache_manager import CacheManager
from tagstudio.qt.controllers.ffmpeg_missing_message_box import FfmpegMissingMessageBox
from tagstudio.qt.controllers.field_template_search_panel_controller import FieldTemplateSearchPanel
from tagstudio.qt.controllers.fix_ignored_modal_controller import FixIgnoredEntriesModal
from tagstudio.qt.controllers.ignore_modal_controller import IgnoreModal
from tagstudio.qt.controllers.library_info_window_controller import LibraryInfoWindow
from tagstudio.qt.controllers.out_of_date_message_box import OutOfDateMessageBox
from tagstudio.qt.controllers.tag_search_panel_controller import TagSearchModal, TagSearchPanel
from tagstudio.qt.global_settings import (
DEFAULT_GLOBAL_SETTINGS_PATH,
GlobalSettings,
Theme,
)
from tagstudio.qt.controllers.update_available_message_box import UpdateAvailableMessageBox
from tagstudio.qt.global_settings import DEFAULT_GLOBAL_SETTINGS_PATH, GlobalSettings, Theme
from tagstudio.qt.mixed.about_modal import AboutModal
from tagstudio.qt.mixed.build_tag import BuildTagPanel
from tagstudio.qt.mixed.drop_import_modal import DropImportModal
@@ -89,7 +75,6 @@ from tagstudio.qt.mixed.settings_panel import SettingsPanel
from tagstudio.qt.mixed.tag_color_manager import TagColorManager
from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
from tagstudio.qt.platform_strings import trash_term
from tagstudio.qt.previews.vendored.ffmpeg import FFMPEG_CMD, FFPROBE_CMD
from tagstudio.qt.resource_manager import ResourceManager
from tagstudio.qt.translations import Translations
from tagstudio.qt.utils.custom_runnable import CustomRunnable
@@ -112,9 +97,9 @@ BADGE_TAGS = {
if sys.platform == "win32":
from signal import SIGINT, SIGTERM, signal
SIGQUIT = SIGTERM
SIGQUIT = SIGTERM # pyright: ignore
else:
from signal import SIGINT, SIGQUIT, SIGTERM, signal
from signal import SIGINT, SIGQUIT, SIGTERM, signal # pyright: ignore
logger = structlog.get_logger(__name__)
@@ -636,14 +621,7 @@ class QtDriver(DriverMixin, QObject):
if path_result.success and path_result.library_path:
self.open_library(path_result.library_path)
# Check if FFmpeg or FFprobe are missing and show warning if so
if not which(FFMPEG_CMD) or not which(FFPROBE_CMD):
FfmpegMissingMessageBox().show()
latest_version = TagStudioCore.get_most_recent_release_version()
if latest_version and is_version_outdated(VERSION, latest_version):
OutOfDateMessageBox().exec()
self.check_for_update()
self.app.exec()
self.shutdown()
@@ -783,7 +761,7 @@ class QtDriver(DriverMixin, QObject):
self.main_window.status_bar.showMessage(Translations["status.library_closing"])
start_time = time.time()
self.cached_values.setValue(SettingItems.LAST_LIBRARY, str(self.lib.library_dir))
self.cached_values.setValue(AppCacheItems.LAST_LIBRARY, str(self.lib.library_dir))
self.cached_values.sync()
# Reset library state
@@ -1513,7 +1491,7 @@ class QtDriver(DriverMixin, QObject):
)
def remove_recent_library(self, item_key: str):
self.cached_values.beginGroup(SettingItems.LIBS_LIST)
self.cached_values.beginGroup(AppCacheItems.LIBS_LIST)
self.cached_values.remove(item_key)
self.cached_values.endGroup()
self.cached_values.sync()
@@ -1523,7 +1501,7 @@ class QtDriver(DriverMixin, QObject):
item_limit: int = 10
path = Path(path)
self.cached_values.beginGroup(SettingItems.LIBS_LIST)
self.cached_values.beginGroup(AppCacheItems.LIBS_LIST)
all_libs = {str(time.time()): str(path)}
@@ -1550,7 +1528,7 @@ class QtDriver(DriverMixin, QObject):
lib_items: dict[str, tuple[str, str]] = {}
# get recent libraries sorted by timestamp
self.cached_values.beginGroup(SettingItems.LIBS_LIST)
self.cached_values.beginGroup(AppCacheItems.LIBS_LIST)
for item_tstamp in self.cached_values.allKeys():
val = str(self.cached_values.value(item_tstamp, type=str))
cut_val = val
@@ -1571,8 +1549,8 @@ class QtDriver(DriverMixin, QObject):
def clear_recent_libs(self):
"""Clear the list of recent libraries from the settings file."""
settings = self.cached_values
settings.beginGroup(SettingItems.LIBS_LIST)
cache = self.cached_values
cache.beginGroup(AppCacheItems.LIBS_LIST)
self.cached_values.remove("")
self.cached_values.endGroup()
self.cached_values.sync()
@@ -1581,6 +1559,23 @@ class QtDriver(DriverMixin, QObject):
def open_settings_modal(self):
SettingsPanel.build_modal(self).show()
def check_for_update(self):
"""Check for an update to TagStudio and display a message box if there is one."""
latest_version = TagStudioCore.get_most_recent_release_version()
if latest_version == str(self.cached_values.value(AppCacheItems.DISMISSED_UPDATE)):
return
if latest_version and is_version_outdated(VERSION, latest_version):
update_box = UpdateAvailableMessageBox()
update_box.button(QMessageBox.StandardButton.Ignore).clicked.connect(
partial(self.dismiss_update, str(latest_version))
)
update_box.exec()
def dismiss_update(self, version: str):
"""Dismiss an update notification for a specific new version of TagStudio."""
self.cached_values.setValue(AppCacheItems.DISMISSED_UPDATE, version)
def open_library(self, path: Path) -> None:
"""Open a TagStudio library."""
library_dir_display = (
+50 -16
View File
@@ -2,28 +2,33 @@
# SPDX-License-Identifier: GPL-3.0-only
import math
import traceback
import typing
from pathlib import Path
import structlog
from PySide6.QtCore import Qt
from PySide6.QtGui import QDesktopServices
from PySide6.QtWidgets import (
QHBoxLayout,
QLabel,
QPushButton,
QSplitter,
QVBoxLayout,
QWidget,
)
from tagstudio.core.constants import FFMPEG_HELP_URL
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.models import Entry
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.controllers.preview_thumb_controller import PreviewThumb
from tagstudio.qt.mixed.field_containers import FieldContainers
from tagstudio.qt.mixed.file_attributes import FileAttributeData, FileAttributes
from tagstudio.qt.resource_manager import ResourceManager
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.stylesheets.stylesheets import button_style
from tagstudio.qt.views.stylesheets.stylesheets import button_style, preview_warning_style
if typing.TYPE_CHECKING:
from tagstudio.qt.ts_qt import QtDriver
@@ -39,9 +44,10 @@ class PreviewPanelView(QWidget):
def __init__(self, library: Library, driver: "QtDriver") -> None:
super().__init__()
self.lib = library
rm = ResourceManager()
self.__thumb = PreviewThumb(self.lib, driver)
self.__file_attrs = FileAttributes(self.lib, driver)
self._thumb = PreviewThumb(self.lib, driver)
self._file_attrs = FileAttributes(self.lib, driver)
self._containers = FieldContainers(
self.lib, driver
) # TODO: this should be name mangled, but is still needed on the controller side atm
@@ -51,6 +57,33 @@ class PreviewPanelView(QWidget):
preview_layout.setContentsMargins(0, 0, 0, 0)
preview_layout.setSpacing(6)
self._ffmpeg_warning_widget = QWidget()
self._ffmpeg_warning_widget.setObjectName("ffmpeg_widget")
ffmpeg_warning_layout = QHBoxLayout(self._ffmpeg_warning_widget)
ffmpeg_warning_layout.setContentsMargins(3, 3, 3, 3)
self._ffmpeg_warning_widget.setStyleSheet(preview_warning_style())
ffmpeg_warning_label = QLabel(
Translations.format(
"preview.missing_module.multimedia",
module=f'<a href="{FFMPEG_HELP_URL}">FFmpeg</a>',
)
)
ffmpeg_warning_label.setWordWrap(True)
ffmpeg_warning_label.linkActivated.connect(
lambda x: QDesktopServices.openUrl(FFMPEG_HELP_URL)
)
warning_icon = QLabel()
warning_icon_pixmap = rm.alert.scaled(
math.floor(20 * self.devicePixelRatio()), math.floor(20 * self.devicePixelRatio())
)
warning_icon_pixmap.setDevicePixelRatio(self.devicePixelRatio())
warning_icon.setPixmap(warning_icon_pixmap)
ffmpeg_warning_layout.addWidget(warning_icon)
ffmpeg_warning_layout.addWidget(ffmpeg_warning_label)
ffmpeg_warning_layout.setStretch(1, 1)
self._ffmpeg_warning_widget.hide()
info_section = QWidget()
info_layout = QVBoxLayout(info_section)
info_layout.setContentsMargins(0, 0, 0, 0)
@@ -80,8 +113,9 @@ class PreviewPanelView(QWidget):
add_buttons_layout.addWidget(self.__add_tag_button)
add_buttons_layout.addWidget(self.__add_field_button)
preview_layout.addWidget(self.__thumb)
info_layout.addWidget(self.__file_attrs)
preview_layout.addWidget(self._thumb)
info_layout.addWidget(self._ffmpeg_warning_widget)
info_layout.addWidget(self._file_attrs)
info_layout.addWidget(self._containers)
splitter.addWidget(preview_section)
@@ -120,9 +154,9 @@ class PreviewPanelView(QWidget):
try:
# No Items Selected
if len(selected) == 0:
self.__thumb.hide_preview()
self.__file_attrs.update_stats()
self.__file_attrs.update_date_label()
self._thumb.hide_preview()
self._file_attrs.update_stats()
self._file_attrs.update_date_label()
self._containers.hide_containers()
self.add_buttons_enabled = False
@@ -135,9 +169,9 @@ class PreviewPanelView(QWidget):
filepath: Path = unwrap(self.lib.library_dir) / entry.path
if update_preview:
stats: FileAttributeData = self.__thumb.display_file(filepath)
self.__file_attrs.update_stats(filepath, stats)
self.__file_attrs.update_date_label(filepath)
stats: FileAttributeData = self._thumb.display_file(filepath)
self._file_attrs.update_stats(filepath, stats)
self._file_attrs.update_date_label(filepath)
self._containers.update_from_entry(entry_id)
self._set_selection_callback()
@@ -147,9 +181,9 @@ class PreviewPanelView(QWidget):
# Multiple Selected Items
elif len(selected) > 1:
# items: list[Entry] = [self.lib.get_entry_full(x) for x in self.driver.selected]
self.__thumb.hide_preview() # TODO: Render mixed selection
self.__file_attrs.update_multi_selection(len(selected))
self.__file_attrs.update_date_label()
self._thumb.hide_preview() # TODO: Render mixed selection
self._file_attrs.update_multi_selection(len(selected))
self._file_attrs.update_date_label()
self._containers.hide_containers() # TODO: Allow for mixed editing
self._set_selection_callback()
@@ -175,7 +209,7 @@ class PreviewPanelView(QWidget):
@property
def _file_attributes_widget(self) -> FileAttributes: # needed for the tests
"""Getter for the file attributes widget."""
return self.__file_attrs
return self._file_attrs
@property
def field_containers_widget(self) -> FieldContainers: # needed for the tests
@@ -184,4 +218,4 @@ class PreviewPanelView(QWidget):
@property
def preview_thumb(self) -> PreviewThumb:
return self.__thumb
return self._thumb
+6 -7
View File
@@ -8,7 +8,7 @@ from pathlib import Path
from typing import TYPE_CHECKING, override
import structlog
from PySide6.QtCore import QBuffer, QByteArray, QSize, Qt
from PySide6.QtCore import QBuffer, QByteArray, QSize, Qt, Signal
from PySide6.QtGui import QAction, QMovie, QPixmap, QResizeEvent
from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QStackedLayout, QWidget
@@ -33,6 +33,8 @@ THUMB_SIZE_FACTOR = 2
class PreviewThumbView(QWidget):
"""The Preview Panel Widget."""
check_ffmpeg = Signal(bool)
__img_button_size: tuple[int, int]
__image_ratio: float
@@ -133,12 +135,7 @@ class PreviewThumbView(QWidget):
def __thumb_renderer_updated_ratio_callback(self, ratio: float) -> None:
self.__image_ratio = ratio
self.__update_image_size(
(
self.size().width(),
self.size().height(),
)
)
self.__update_image_size((self.size().width(), self.size().height()))
def __stacked_page_setup(self, page: QWidget, widget: QWidget) -> None:
layout = QHBoxLayout(page)
@@ -186,9 +183,11 @@ class PreviewThumbView(QWidget):
if preview in [MediaType.AUDIO, MediaType.VIDEO]:
self.__media_player.show()
self.__image_layout.setCurrentWidget(self.__media_player_page)
self.check_ffmpeg.emit(True) # noqa: FBT003
else:
self.__media_player.stop()
self.__media_player.hide()
self.check_ffmpeg.emit(False) # noqa: FBT003
if preview in [MediaType.IMAGE, MediaType.AUDIO]:
self.__button_wrapper.show()
@@ -372,6 +372,15 @@ def title_line_edit_style() -> str:
"""
def preview_warning_style() -> str:
return f"""
QWidget#ffmpeg_widget {{
background: {get_ui_color(ColorType.DARK_ACCENT, UiColor.RED)};
border-radius: 6px;
}}
"""
def header(string: str, level: int, color: str | None = None) -> str:
"""Wrap a string in HTML header tags.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

+4 -1
View File
@@ -71,7 +71,6 @@
"entries.unlinked.search_and_relink": "&Search && Relink",
"entries.unlinked.title": "Fix Unlinked Entries",
"entries.unlinked.unlinked_count": "Unlinked Entries: {count}",
"ffmpeg.missing.description": "FFmpeg and/or FFprobe were not found. FFmpeg is required for multimedia playback and thumbnails.",
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
"field_template_manager.title": "Library Field Templates",
"field_template.all_field_templates": "All Field Templates",
@@ -137,6 +136,7 @@
"generic.delete_alt": "&Delete",
"generic.done": "Done",
"generic.done_alt": "&Done",
"generic.dont_remind": "Dont Remind Me Again",
"generic.edit": "Edit",
"generic.edit_alt": "&Edit",
"generic.filename": "Filename",
@@ -268,6 +268,8 @@
"namespace.new.button": "New Namespace",
"namespace.new.prompt": "Create a New Namespace to Start Adding Custom Colors!",
"preview.ignored": "Ignored",
"preview.missing_module.jxl": "{module} is required for JPEG XL previews",
"preview.missing_module.multimedia": "{module} is required for multimedia playback",
"preview.multiple_selection": "<b>{count}</b> Items Selected",
"preview.no_selection": "No Items Selected",
"preview.unlinked": "Unlinked",
@@ -372,6 +374,7 @@
"trash.dialog.title.singular": "Delete File",
"trash.name.generic": "Trash",
"trash.name.windows": "Recycle Bin",
"update.view_update": "View Update",
"version_modal.description": "A new version of TagStudio is available! You can download the latest release from <a href=\"{github_url}\">GitHub</a>.",
"version_modal.status": "Installed Version: {installed_version}<br>Latest Release Version: {latest_release_version}",
"version_modal.title": "TagStudio Update Available",
+3 -3
View File
@@ -7,7 +7,7 @@ from pathlib import Path
from PySide6.QtCore import QSettings
from tagstudio.core.driver import DriverMixin
from tagstudio.core.enums import SettingItems
from tagstudio.core.enums import AppCacheItems
from tagstudio.core.library.alchemy.library import LibraryStatus
from tagstudio.qt.global_settings import GlobalSettings
@@ -43,7 +43,7 @@ def test_evaluate_path_missing():
def test_evaluate_path_last_lib_not_exists():
# Given
cache = QSettings()
cache.setValue(SettingItems.LAST_LIBRARY, "/0/4/5/1/")
cache.setValue(AppCacheItems.LAST_LIBRARY, "/0/4/5/1/")
driver = TestDriver(GlobalSettings(), cache)
# When
@@ -57,7 +57,7 @@ def test_evaluate_path_last_lib_present(library_dir: Path):
# Given
cache_file = library_dir / "test_settings.ini"
cache = QSettings(str(cache_file), QSettings.Format.IniFormat)
cache.setValue(SettingItems.LAST_LIBRARY, library_dir)
cache.setValue(AppCacheItems.LAST_LIBRARY, library_dir)
cache.sync()
settings = GlobalSettings()