From ac2c15be2982583128f1b5dd2cd8f858fd2d55e3 Mon Sep 17 00:00:00 2001 From: pax Date: Sun, 5 Apr 2026 14:04:15 -0500 Subject: [PATCH] Slideshow blacklist buttons, Ctrl+C copy, fix README code blocks - BL Tag button in slideshow: opens categorized tag menu - BL Post button in slideshow: blacklists current post - Ctrl+C copies preview image to clipboard - "Copy Image to Clipboard" in grid right-click menu - Fix README code block formatting (missing closing backticks) - Add ffmpeg back to Linux install deps --- README.md | 9 +++++--- booru_viewer/gui/app.py | 41 +++++++++++++++++++++++++++++++++++++ booru_viewer/gui/preview.py | 37 +++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e472d3e..08ef551 100644 --- a/README.md +++ b/README.md @@ -91,15 +91,18 @@ Requires Python 3.11+ and pip. Most distros ship Python but you may need to inst **Arch / CachyOS:** ```sh -sudo pacman -S python python-pip qt6-base qt6-multimedia qt6-multimedia-ffmpeg``` +sudo pacman -S python python-pip qt6-base qt6-multimedia qt6-multimedia-ffmpeg ffmpeg +``` **Ubuntu / Debian (24.04+):** ```sh -sudo apt install python3 python3-pip python3-venv libqt6multimedia6 gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav``` +sudo apt install python3 python3-pip python3-venv libqt6multimedia6 gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav ffmpeg +``` **Fedora:** ```sh -sudo dnf install python3 python3-pip qt6-qtbase qt6-qtmultimedia gstreamer1-plugins-good gstreamer1-plugins-bad-free gstreamer1-libav``` +sudo dnf install python3 python3-pip qt6-qtbase qt6-qtmultimedia gstreamer1-plugins-good gstreamer1-plugins-bad-free gstreamer1-libav ffmpeg +``` Then clone and install: ```sh diff --git a/booru_viewer/gui/app.py b/booru_viewer/gui/app.py index 2f9f497..4e10cff 100644 --- a/booru_viewer/gui/app.py +++ b/booru_viewer/gui/app.py @@ -992,6 +992,8 @@ class BooruApp(QMainWindow): show = self._stack.currentIndex() != 2 self._fullscreen_window._bookmark_btn.setVisible(show) self._fullscreen_window._save_btn.setVisible(show) + self._fullscreen_window._bl_tag_btn.setVisible(show) + self._fullscreen_window._bl_post_btn.setVisible(show) if show: self._update_fullscreen_state() @@ -1041,6 +1043,7 @@ class BooruApp(QMainWindow): if saved: break self._fullscreen_window.update_state(bookmarked, saved) + self._fullscreen_window.set_post_tags(post.tag_categories, post.tag_list) else: self._fullscreen_window.update_state(False, False) @@ -1222,6 +1225,31 @@ class BooruApp(QMainWindow): else: self._save_from_preview("") + def _blacklist_tag_from_slideshow(self, tag: str) -> None: + self._db.add_blacklisted_tag(tag) + self._db.set_setting("blacklist_enabled", "1") + # Clear slideshow if previewed post has this tag + idx = self._grid.selected_index + if 0 <= idx < len(self._posts) and tag in self._posts[idx].tag_list: + self._preview.clear() + if self._fullscreen_window: + self._fullscreen_window._viewer.clear() + self._fullscreen_window._video.stop() + self._status.showMessage(f"Blacklisted: {tag}") + self._do_search() + + def _blacklist_post_from_slideshow(self) -> None: + idx = self._grid.selected_index + if 0 <= idx < len(self._posts): + post = self._posts[idx] + self._db.add_blacklisted_post(post.file_url) + self._preview.clear() + if self._fullscreen_window: + self._fullscreen_window._viewer.clear() + self._fullscreen_window._video.stop() + self._status.showMessage(f"Post #{post.id} blacklisted") + self._do_search() + def _open_fullscreen_preview(self) -> None: path = self._preview._current_path if not path: @@ -1253,6 +1281,8 @@ class BooruApp(QMainWindow): if show_actions: self._fullscreen_window.bookmark_requested.connect(self._bookmark_from_preview) self._fullscreen_window.save_toggle_requested.connect(self._save_toggle_from_slideshow) + self._fullscreen_window.blacklist_tag_requested.connect(self._blacklist_tag_from_slideshow) + self._fullscreen_window.blacklist_post_requested.connect(self._blacklist_post_from_slideshow) self._fullscreen_window.closed.connect(self._on_fullscreen_closed) self._fullscreen_window.privacy_requested.connect(self._toggle_privacy) # Sync video player state from preview to slideshow @@ -1363,6 +1393,7 @@ class BooruApp(QMainWindow): save_lib_new = save_lib_menu.addAction("+ New Folder...") unsave_lib = menu.addAction("Unsave from Library") + copy_clipboard = menu.addAction("Copy Image to Clipboard") copy_url = menu.addAction("Copy Image URL") copy_tags = menu.addAction("Copy Tags") menu.addSeparator() @@ -1415,6 +1446,8 @@ class BooruApp(QMainWindow): self._grid._thumbs[index].set_saved_locally(False) else: self._status.showMessage(f"#{post.id} not in library") + elif action == copy_clipboard: + self._copy_preview_to_clipboard() elif action == copy_url: QApplication.clipboard().setText(post.file_url) self._status.showMessage("URL copied") @@ -1848,8 +1881,16 @@ class BooruApp(QMainWindow): if self._preview._stack.currentIndex() == 1 and self._preview.underMouse(): self._preview._video_player._toggle_play() return + elif key == Qt.Key.Key_C and event.modifiers() == Qt.KeyboardModifier.ControlModifier: + self._copy_preview_to_clipboard() + return super().keyPressEvent(event) + def _copy_preview_to_clipboard(self) -> None: + if self._preview._image_viewer._pixmap and not self._preview._image_viewer._pixmap.isNull(): + QApplication.clipboard().setPixmap(self._preview._image_viewer._pixmap) + self._status.showMessage("Image copied to clipboard") + # -- Bookmarks -- def _toggle_bookmark(self, index: int) -> None: diff --git a/booru_viewer/gui/preview.py b/booru_viewer/gui/preview.py index b6a510a..f2247fe 100644 --- a/booru_viewer/gui/preview.py +++ b/booru_viewer/gui/preview.py @@ -28,6 +28,8 @@ class FullscreenPreview(QMainWindow): navigate = Signal(int) # direction: -1/+1 for left/right, -cols/+cols for up/down bookmark_requested = Signal() save_toggle_requested = Signal() # save or unsave depending on state + blacklist_tag_requested = Signal(str) # tag name + blacklist_post_requested = Signal() privacy_requested = Signal() closed = Signal() @@ -58,9 +60,23 @@ class FullscreenPreview(QMainWindow): toolbar.addWidget(self._save_btn) self._is_saved = False + self._bl_tag_btn = QPushButton("BL Tag") + self._bl_tag_btn.setFixedWidth(60) + self._bl_tag_btn.setToolTip("Blacklist a tag") + self._bl_tag_btn.clicked.connect(self._show_bl_tag_menu) + toolbar.addWidget(self._bl_tag_btn) + + self._bl_post_btn = QPushButton("BL Post") + self._bl_post_btn.setFixedWidth(65) + self._bl_post_btn.setToolTip("Blacklist this post") + self._bl_post_btn.clicked.connect(self.blacklist_post_requested) + toolbar.addWidget(self._bl_post_btn) + if not show_actions: self._bookmark_btn.hide() self._save_btn.hide() + self._bl_tag_btn.hide() + self._bl_post_btn.hide() toolbar.addStretch() @@ -100,6 +116,27 @@ class FullscreenPreview(QMainWindow): self.setGeometry(target_screen.geometry()) self.showFullScreen() + _current_tags: dict[str, list[str]] = {} + _current_tag_list: list[str] = [] + + def set_post_tags(self, tag_categories: dict[str, list[str]], tag_list: list[str]) -> None: + self._current_tags = tag_categories + self._current_tag_list = tag_list + + def _show_bl_tag_menu(self) -> None: + menu = QMenu(self) + if self._current_tags: + for category, tags in self._current_tags.items(): + cat_menu = menu.addMenu(category) + for tag in tags[:30]: + cat_menu.addAction(tag) + else: + for tag in self._current_tag_list[:30]: + menu.addAction(tag) + action = menu.exec(self._bl_tag_btn.mapToGlobal(self._bl_tag_btn.rect().bottomLeft())) + if action: + self.blacklist_tag_requested.emit(action.text()) + def update_state(self, bookmarked: bool, saved: bool) -> None: self._bookmark_btn.setText("Unbookmark" if bookmarked else "Bookmark") self._bookmark_btn.setFixedWidth(90 if bookmarked else 80)