diff --git a/requirements.txt b/requirements.txt
index c2bddc36..e4afacdc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -14,4 +14,5 @@ pydub==0.25.1
mutagen==1.47.0
numpy==1.26.4
ffmpeg-python==0.2.0
+Send2Trash==1.8.3
vtf2img==0.1.0
diff --git a/tagstudio/resources/qt/videos/placeholder.mp4 b/tagstudio/resources/qt/videos/placeholder.mp4
new file mode 100644
index 00000000..1e22e4c7
Binary files /dev/null and b/tagstudio/resources/qt/videos/placeholder.mp4 differ
diff --git a/tagstudio/src/core/media_types.py b/tagstudio/src/core/media_types.py
index 6eca3e12..24e08e6e 100644
--- a/tagstudio/src/core/media_types.py
+++ b/tagstudio/src/core/media_types.py
@@ -23,6 +23,7 @@ class MediaType(str, Enum):
DISK_IMAGE: str = "disk_image"
DOCUMENT: str = "document"
FONT: str = "font"
+ IMAGE_ANIMATED: str = "image_animated"
IMAGE_RAW: str = "image_raw"
IMAGE_VECTOR: str = "image_vector"
IMAGE: str = "image"
@@ -169,6 +170,12 @@ class MediaCategories:
".woff",
".woff2",
}
+ _IMAGE_ANIMATED_SET: set[str] = {
+ ".apng",
+ ".gif",
+ ".webp",
+ ".jxl",
+ }
_IMAGE_RAW_SET: set[str] = {
".arw",
".cr2",
@@ -335,6 +342,11 @@ class MediaCategories:
extensions=_FONT_SET,
is_iana=True,
)
+ IMAGE_ANIMATED_TYPES: MediaCategory = MediaCategory(
+ media_type=MediaType.IMAGE_ANIMATED,
+ extensions=_IMAGE_ANIMATED_SET,
+ is_iana=False,
+ )
IMAGE_RAW_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.IMAGE_RAW,
extensions=_IMAGE_RAW_SET,
@@ -427,6 +439,7 @@ class MediaCategories:
DISK_IMAGE_TYPES,
DOCUMENT_TYPES,
FONT_TYPES,
+ IMAGE_ANIMATED_TYPES,
IMAGE_RAW_TYPES,
IMAGE_TYPES,
IMAGE_VECTOR_TYPES,
diff --git a/tagstudio/src/qt/helpers/file_deleter.py b/tagstudio/src/qt/helpers/file_deleter.py
new file mode 100644
index 00000000..8047c4ce
--- /dev/null
+++ b/tagstudio/src/qt/helpers/file_deleter.py
@@ -0,0 +1,30 @@
+# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
+# Licensed under the GPL-3.0 License.
+# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
+
+import logging
+from pathlib import Path
+
+from send2trash import send2trash
+
+logging.basicConfig(format="%(message)s", level=logging.INFO)
+
+
+def delete_file(path: str | Path) -> bool:
+ """Sends a file to the system trash.
+
+ Args:
+ path (str | Path): The path of the file to delete.
+ """
+ _path = Path(path)
+ try:
+ logging.info(f"[delete_file] Sending to Trash: {_path}")
+ send2trash(_path)
+ return True
+ except PermissionError as e:
+ logging.error(f"[delete_file][ERROR] PermissionError: {e}")
+ except FileNotFoundError:
+ logging.error(f"[delete_file][ERROR] File Not Found: {_path}")
+ except Exception as e:
+ logging.error(e)
+ return False
diff --git a/tagstudio/src/qt/resource_manager.py b/tagstudio/src/qt/resource_manager.py
index 3c4e8099..c38d382a 100644
--- a/tagstudio/src/qt/resource_manager.py
+++ b/tagstudio/src/qt/resource_manager.py
@@ -31,6 +31,20 @@ class ResourceManager:
)
ResourceManager._initialized = True
+ @staticmethod
+ def get_path(id: str) -> Path | None:
+ """Get a resource's path from the ResourceManager.
+ Args:
+ id (str): The name of the resource.
+
+ Returns:
+ Path: The resource path if found, else None.
+ """
+ res: dict = ResourceManager._map.get(id)
+ if res:
+ return Path(__file__).parents[2] / "resources" / res.get("path")
+ return None
+
def get(self, id: str) -> Any:
"""Get a resource from the ResourceManager.
This can include resources inside and outside of QResources, and will return
diff --git a/tagstudio/src/qt/resources.json b/tagstudio/src/qt/resources.json
index ef007b6f..e5857909 100644
--- a/tagstudio/src/qt/resources.json
+++ b/tagstudio/src/qt/resources.json
@@ -90,5 +90,9 @@
"thumb_loading": {
"path": "qt/images/thumb_loading.png",
"mode": "pil"
+ },
+ "placeholder_mp4": {
+ "path": "qt/videos/placeholder.mp4",
+ "mode": "rb"
}
}
diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py
index 41874188..13cf1cc0 100644
--- a/tagstudio/src/qt/ts_qt.py
+++ b/tagstudio/src/qt/ts_qt.py
@@ -12,6 +12,7 @@ import copy
import logging
import math
import os
+import platform
import sys
import time
import typing
@@ -53,6 +54,7 @@ from PySide6.QtWidgets import (
QMenu,
QMenuBar,
QComboBox,
+ QMessageBox,
)
from humanfriendly import format_timespan
@@ -69,9 +71,11 @@ from src.core.constants import (
TAG_FAVORITE,
TAG_ARCHIVED,
)
+from src.core.palette import ColorType, get_ui_color
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.file_deleter import delete_file
from src.qt.helpers.function_iterator import FunctionIterator
from src.qt.helpers.custom_runnable import CustomRunnable
from src.qt.resource_manager import ResourceManager
@@ -446,6 +450,15 @@ class QtDriver(QObject):
edit_menu.addSeparator()
+ self.delete_file_action = QAction("Delete Selected File(s)", menu_bar)
+ self.delete_file_action.triggered.connect(
+ lambda f="": self.delete_files_callback(f)
+ )
+ self.delete_file_action.setShortcut(QtCore.Qt.Key.Key_Delete)
+ edit_menu.addAction(self.delete_file_action)
+
+ edit_menu.addSeparator()
+
manage_file_extensions_action = QAction("Manage File Extensions", menu_bar)
manage_file_extensions_action.triggered.connect(
lambda: self.show_file_extension_modal()
@@ -532,13 +545,13 @@ class QtDriver(QObject):
folders_to_tags_action.triggered.connect(lambda: ftt_modal.show())
macros_menu.addAction(folders_to_tags_action)
- # Help Menu ==========================================================
+ # Help Menu ============================================================
self.repo_action = QAction("Visit GitHub Repository", menu_bar)
self.repo_action.triggered.connect(
lambda: webbrowser.open("https://github.com/TagStudioDev/TagStudio")
)
help_menu.addAction(self.repo_action)
- self.set_macro_menu_viability()
+ self.set_menu_action_viability()
self.update_clipboard_actions()
@@ -549,6 +562,9 @@ class QtDriver(QObject):
menu_bar.addMenu(window_menu)
menu_bar.addMenu(help_menu)
+ # ======================================================================
+
+ # Preview Panel --------------------------------------------------------
self.preview_panel = PreviewPanel(self.lib, self)
l: QHBoxLayout = self.main_window.splitter
l.addWidget(self.preview_panel)
@@ -758,7 +774,7 @@ class QtDriver(QObject):
self.copied_fields.clear()
self.is_buffer_merged = False
self.update_clipboard_actions()
- self.set_macro_menu_viability()
+ self.set_menu_action_viability()
self.preview_panel.update_widgets()
self.filter_items()
self.main_window.toggle_landing_page(True)
@@ -796,7 +812,7 @@ class QtDriver(QObject):
self.selected.append((item.mode, item.item_id))
item.thumb_button.set_selected(True)
- self.set_macro_menu_viability()
+ self.set_menu_action_viability()
self.preview_panel.update_widgets()
def clear_select_action_callback(self):
@@ -804,7 +820,7 @@ class QtDriver(QObject):
for item in self.item_thumbs:
item.thumb_button.set_selected(False)
- self.set_macro_menu_viability()
+ self.set_menu_action_viability()
self.preview_panel.update_widgets()
def show_tag_database(self):
@@ -827,6 +843,114 @@ class QtDriver(QObject):
self.modal.saved.connect(lambda: (panel.save(), self.filter_items("")))
self.modal.show()
+ def delete_files_callback(self, origin_path: str | Path):
+ """Callback to send on or more files to the system trash.
+
+ If 0-1 items are currently selected, the origin_path is used to delete the file
+ from the originating context menu item.
+ If there are currently multiple items selected,
+ then the selection buffer is used to determine the files to be deleted.
+
+ Args:
+ origin_path(str): The file path associated with the widget making the call.
+ May or may not be the file targeted, depending on the selection rules.
+ """
+ entry = None
+ pending: list[Path] = []
+ deleted_count: int = 0
+
+ if len(self.selected) <= 1 and origin_path:
+ pending.append(Path(origin_path))
+ elif (len(self.selected) > 1) or (len(self.selected) <= 1 and not origin_path):
+ for i, item_pair in enumerate(self.selected):
+ if item_pair[0] == ItemType.ENTRY:
+ entry = self.lib.get_entry(item_pair[1])
+ filepath: Path = self.lib.library_dir / entry.path / entry.filename
+ pending.append(filepath)
+
+ if pending:
+ return_code = self.delete_file_confirmation(len(pending), pending[0])
+ logging.info(return_code)
+ # If there was a confirmation and not a cancellation
+ if return_code == 2 and return_code != 3:
+ for i, f in enumerate(pending):
+ if (origin_path == f) or (not origin_path):
+ self.preview_panel.stop_file_use()
+ if delete_file(f):
+ self.main_window.statusbar.showMessage(
+ f'Deleting file [{i}/{len(pending)}]: "{f}"...'
+ )
+ self.main_window.statusbar.repaint()
+
+ entry_id = self.lib.get_entry_id_from_filepath(f)
+ self.lib.remove_entry(entry_id)
+ self.purge_item_from_navigation(ItemType.ENTRY, entry_id)
+ deleted_count += 1
+ self.selected.clear()
+
+ if deleted_count > 0:
+ self.filter_items()
+ self.preview_panel.update_widgets()
+
+ if len(self.selected) <= 1 and deleted_count == 0:
+ self.main_window.statusbar.showMessage("No files deleted.")
+ elif len(self.selected) <= 1 and deleted_count == 1:
+ self.main_window.statusbar.showMessage(f"Deleted {deleted_count} file!")
+ elif len(self.selected) > 1 and deleted_count == 0:
+ self.main_window.statusbar.showMessage("No files deleted.")
+ elif len(self.selected) > 1 and deleted_count < len(self.selected):
+ self.main_window.statusbar.showMessage(
+ f"Only deleted {deleted_count} file{'' if deleted_count == 1 else 's'}! Check if any of the files are currently missing or in use."
+ )
+ elif len(self.selected) > 1 and deleted_count == len(self.selected):
+ self.main_window.statusbar.showMessage(f"Deleted {deleted_count} files!")
+ self.main_window.statusbar.repaint()
+
+ def delete_file_confirmation(self, count: int, filename: Path | None = None) -> int:
+ """A confirmation dialogue box for deleting files.
+
+ Args:
+ count(int): The number of files to be deleted.
+ filename(Path | None): The filename to show if only one file is to be deleted.
+ """
+ trash_term: str = "Trash"
+ if platform.system() == "Windows":
+ trash_term = "Recycle Bin"
+ # NOTE: Windows + send2trash will PERMANENTLY delete files which cannot be moved to the Recycle Bin.
+ # This is done without any warning, so this message is currently the best way I've got to inform the user.
+ # https://github.com/arsenetar/send2trash/issues/28
+ # This warning is applied to all platforms until at least macOS and Linux can be verified to
+ # not exhibit this same behavior.
+ perm_warning: str = (
+ f"
"
+ f"WARNING! If this file can't be moved to the {trash_term}, "
+ f"it will be permanently deleted!
"
+ )
+
+ msg = QMessageBox()
+ msg.setTextFormat(Qt.TextFormat.RichText)
+ msg.setWindowTitle("Delete File" if count == 1 else "Delete Files")
+ msg.setIcon(QMessageBox.Icon.Warning)
+ if count <= 1:
+ msg.setText(
+ f"Are you sure you want to move this file to the {trash_term}?
"
+ "This will remove it from TagStudio AND your file system!
"
+ f"{filename if filename else ''}"
+ f"{perm_warning}
"
+ )
+ elif count > 1:
+ msg.setText(
+ f"Are you sure you want to move these {count} files to the {trash_term}?
"
+ "This will remove them from TagStudio AND your file system!
"
+ f"{perm_warning}
"
+ )
+
+ yes_button: QPushButton = msg.addButton("&Yes", QMessageBox.ButtonRole.YesRole)
+ msg.addButton("&No", QMessageBox.ButtonRole.NoRole)
+ msg.setDefaultButton(yes_button)
+
+ return msg.exec()
+
def add_new_files_callback(self):
"""Runs when user initiates adding new files to the Library."""
# # if self.lib.files_not_in_library:
@@ -1397,17 +1521,19 @@ class QtDriver(QObject):
if it.mode == type and it.item_id == id:
self.preview_panel.set_tags_updated_slot(it.update_badges)
- self.set_macro_menu_viability()
+ self.set_menu_action_viability()
self.update_clipboard_actions()
self.preview_panel.update_widgets()
- def set_macro_menu_viability(self):
+ def set_menu_action_viability(self):
if len([x[1] for x in self.selected if x[0] == ItemType.ENTRY]) == 0:
self.autofill_action.setDisabled(True)
self.sort_fields_action.setDisabled(True)
+ self.delete_file_action.setDisabled(True)
else:
self.autofill_action.setDisabled(False)
self.sort_fields_action.setDisabled(False)
+ self.delete_file_action.setDisabled(False)
def update_thumbs(self):
"""Updates search thumbnails."""
@@ -1456,12 +1582,20 @@ class QtDriver(QObject):
for i, item_thumb in enumerate(self.item_thumbs, start=0):
if i < len(self.nav_frames[self.cur_frame_idx].contents):
- filepath = ""
+ filepath: Path = None # Initialize
if self.nav_frames[self.cur_frame_idx].contents[i][0] == ItemType.ENTRY:
entry = self.lib.get_entry(
self.nav_frames[self.cur_frame_idx].contents[i][1]
)
- filepath = self.lib.library_dir / entry.path / entry.filename
+ filepath: Path = self.lib.library_dir / entry.path / entry.filename
+
+ try:
+ item_thumb.delete_action.triggered.disconnect()
+ except RuntimeWarning:
+ pass
+ item_thumb.delete_action.triggered.connect(
+ lambda checked=False, f=filepath: self.delete_files_callback(f)
+ )
item_thumb.set_item_id(entry.id)
item_thumb.assign_archived(entry.has_tag(self.lib, TAG_ARCHIVED))
@@ -1506,7 +1640,9 @@ class QtDriver(QObject):
else collation.e_ids_and_pages[0][0]
)
cover_e = self.lib.get_entry(cover_id)
- filepath = self.lib.library_dir / cover_e.path / cover_e.filename
+ filepath: Path = (
+ self.lib.library_dir / cover_e.path / cover_e.filename
+ )
item_thumb.set_count(str(len(collation.e_ids_and_pages)))
item_thumb.update_clickable(
clickable=(
diff --git a/tagstudio/src/qt/widgets/fields.py b/tagstudio/src/qt/widgets/fields.py
index dffca2a5..9bdc6794 100644
--- a/tagstudio/src/qt/widgets/fields.py
+++ b/tagstudio/src/qt/widgets/fields.py
@@ -124,6 +124,7 @@ class FieldContainer(QWidget):
def set_copy_callback(self, callback: Optional[MethodType]):
if self.copy_button.is_connected:
self.copy_button.clicked.disconnect()
+ self.copy_button.is_connected = False
self.copy_callback = callback
self.copy_button.clicked.connect(callback)
@@ -133,6 +134,7 @@ class FieldContainer(QWidget):
def set_edit_callback(self, callback: Optional[MethodType]):
if self.edit_button.is_connected:
self.edit_button.clicked.disconnect()
+ self.edit_button.is_connected = False
self.edit_callback = callback
self.edit_button.clicked.connect(callback)
@@ -142,10 +144,12 @@ class FieldContainer(QWidget):
def set_remove_callback(self, callback: Optional[Callable]):
if self.remove_button.is_connected:
self.remove_button.clicked.disconnect()
+ self.remove_button.is_connected = False
self.remove_callback = callback
self.remove_button.clicked.connect(callback)
- self.remove_button.is_connected = True
+ if callback is not None:
+ self.remove_button.is_connected = True
def set_inner_widget(self, widget: "FieldWidget"):
if self.field_layout.itemAt(0):
diff --git a/tagstudio/src/qt/widgets/item_thumb.py b/tagstudio/src/qt/widgets/item_thumb.py
index eda6196d..1057e70a 100644
--- a/tagstudio/src/qt/widgets/item_thumb.py
+++ b/tagstudio/src/qt/widgets/item_thumb.py
@@ -9,6 +9,7 @@ import time
import typing
from pathlib import Path
from typing import Optional
+import platform
from PIL import Image, ImageQt
from PySide6.QtCore import Qt, QSize, QEvent, QMimeData, QUrl
@@ -207,8 +208,15 @@ class ItemThumb(FlowWidget):
open_explorer_action = QAction("Open in Explorer", self)
open_explorer_action.triggered.connect(self.opener.open_explorer)
+
+ trash_term: str = "Trash"
+ if platform.system() == "Windows":
+ trash_term = "Recycle Bin"
+ self.delete_action = QAction(f"Send file to {trash_term}", self)
+
self.thumb_button.addAction(open_file_action)
self.thumb_button.addAction(open_explorer_action)
+ self.thumb_button.addAction(self.delete_action)
# Static Badges ========================================================
diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py
index 24cb42ce..9dc68db4 100644
--- a/tagstudio/src/qt/widgets/preview_panel.py
+++ b/tagstudio/src/qt/widgets/preview_panel.py
@@ -12,7 +12,7 @@ import cv2
import rawpy
from PIL import Image, UnidentifiedImageError, ImageFont
from PIL.Image import DecompressionBombError
-from PySide6.QtCore import QModelIndex, Signal, Qt, QSize
+from PySide6.QtCore import QModelIndex, Signal, Qt, QSize, QByteArray, QBuffer
from PySide6.QtGui import QGuiApplication, QResizeEvent, QAction, QMovie
from PySide6.QtWidgets import (
QWidget,
@@ -46,6 +46,7 @@ from src.qt.widgets.text_line_edit import EditTextLine
from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper
from src.qt.widgets.video_player import VideoPlayer
from src.qt.helpers.file_tester import is_readable_video
+from src.qt.resource_manager import ResourceManager
# Only import for type checking/autocompletion, will not be imported at runtime.
@@ -98,14 +99,17 @@ class PreviewPanel(QWidget):
image_layout.setContentsMargins(0, 0, 0, 0)
self.open_file_action = QAction("Open file", self)
+ self.trash_term: str = "Trash"
+ if platform.system() == "Windows":
+ self.trash_term = "Recycle Bin"
+ self.delete_action = QAction(f"Send file to {self.trash_term}", self)
- system = platform.system()
self.open_explorer_action = QAction(
"Open in explorer", self
- ) # Default (mainly going to be for linux)
- if system == "Darwin":
+ ) # Default text (Linux, etc.)
+ if platform.system() == "Darwin":
self.open_explorer_action = QAction("Reveal in Finder", self)
- elif system == "Windows":
+ elif platform.system() == "Windows":
self.open_explorer_action = QAction("Open in Explorer", self)
self.preview_img = QPushButtonWrapper()
@@ -114,6 +118,7 @@ class PreviewPanel(QWidget):
self.preview_img.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
self.preview_img.addAction(self.open_file_action)
self.preview_img.addAction(self.open_explorer_action)
+ self.preview_img.addAction(self.delete_action)
self.preview_gif = QLabel()
self.preview_gif.setMinimumSize(*self.img_button_size)
@@ -121,10 +126,13 @@ class PreviewPanel(QWidget):
self.preview_gif.setCursor(Qt.CursorShape.ArrowCursor)
self.preview_gif.addAction(self.open_file_action)
self.preview_gif.addAction(self.open_explorer_action)
+ self.preview_gif.addAction(self.delete_action)
self.preview_gif.hide()
+ self.gif_buffer: QBuffer = QBuffer()
self.preview_vid = VideoPlayer(driver)
self.preview_vid.hide()
+ self.preview_vid.addAction(self.delete_action)
self.thumb_renderer = ThumbRenderer()
self.thumb_renderer.updated.connect(
lambda ts, i, s: (self.preview_img.setIcon(i))
@@ -499,6 +507,14 @@ class PreviewPanel(QWidget):
)
if self.preview_img.is_connected:
self.preview_img.clicked.disconnect()
+ self.preview_img.is_connected = False
+
+ try:
+ self.delete_action.triggered.disconnect()
+ except RuntimeWarning:
+ pass
+ self.delete_action.setEnabled(False)
+
for i, c in enumerate(self.containers):
c.setHidden(True)
self.preview_img.show()
@@ -538,6 +554,17 @@ class PreviewPanel(QWidget):
)
self.preview_img.setCursor(Qt.CursorShape.PointingHandCursor)
+ try:
+ self.delete_action.triggered.disconnect()
+ except RuntimeError:
+ pass
+ self.delete_action.setText(f"Send file to {self.trash_term}")
+ self.delete_action.triggered.connect(
+ lambda checked=False,
+ f=filepath: self.driver.delete_files_callback(f)
+ )
+ self.delete_action.setEnabled(True)
+
self.opener = FileOpenerHelper(filepath)
self.open_file_action.triggered.connect(self.opener.open_file)
self.open_explorer_action.triggered.connect(
@@ -548,16 +575,24 @@ class PreviewPanel(QWidget):
ext: str = filepath.suffix.lower()
try:
if filepath.suffix.lower() in [".gif"]:
- movie = QMovie(str(filepath))
+ with open(filepath, mode="rb") as f:
+ if self.preview_gif.movie():
+ self.preview_gif.movie().stop()
+ self.gif_buffer.close()
+
+ ba = f.read()
+ self.gif_buffer.setData(ba)
+ movie = QMovie(self.gif_buffer, QByteArray())
+ self.preview_gif.setMovie(movie)
+ movie.start()
+
image = Image.open(str(filepath))
- self.preview_gif.setMovie(movie)
self.resizeEvent(
QResizeEvent(
QSize(image.width, image.height),
QSize(image.width, image.height),
)
)
- movie.start()
self.preview_img.hide()
self.preview_vid.hide()
self.preview_gif.show()
@@ -669,6 +704,7 @@ class PreviewPanel(QWidget):
# TODO: Implement a clickable label to use for the GIF preview.
if self.preview_img.is_connected:
self.preview_img.clicked.disconnect()
+ self.preview_img.is_connected = False
self.preview_img.clicked.connect(
lambda checked=False, filepath=filepath: open_file(filepath)
)
@@ -710,6 +746,16 @@ class PreviewPanel(QWidget):
)
self.preview_img.setCursor(Qt.CursorShape.ArrowCursor)
+ try:
+ self.delete_action.triggered.disconnect()
+ except RuntimeError:
+ pass
+ self.delete_action.setText(f"Send files to {self.trash_term}")
+ self.delete_action.triggered.connect(
+ lambda checked=False, f=None: self.driver.delete_files_callback(f)
+ )
+ self.delete_action.setEnabled(True)
+
ratio: float = self.devicePixelRatio()
self.thumb_renderer.render(
time.time(),
@@ -1149,3 +1195,26 @@ class PreviewPanel(QWidget):
# logging.info(result)
if result == 3:
callback()
+
+ def stop_file_use(self):
+ """Stops the use of the currently previewed file. Used to release file permissions."""
+ logging.info("[PreviewPanel] Stopping file use in video playback...")
+ # This swaps the video out for a placeholder so the previous video's file
+ # is no longer in use by this object.
+ self.preview_vid.play(ResourceManager.get_path("placeholder_mp4"), QSize(8, 8))
+ self.preview_vid.hide()
+
+ # NOTE: I'm keeping this here until #357 is merged in the case it still needs to be used.
+ # logging.info("[PreviewPanel] Stopping file use for animated image playback...")
+ # logging.info(self.preview_gif.movie())
+ # if self.preview_gif.movie():
+ # self.preview_gif.movie().stop()
+ # with open(ResourceManager.get_path("placeholder_gif"), mode="rb") as f:
+ # ba = f.read()
+ # self.gif_buffer.setData(ba)
+ # movie = QMovie(self.gif_buffer, QByteArray())
+ # self.preview_gif.setMovie(movie)
+ # movie.start()
+
+ # self.preview_gif.hide()
+ # logging.info(self.preview_gif.movie())