Better UI, edit tags
All checks were successful
Gitea/kapitanbooru-uploader/pipeline/head This commit looks good
All checks were successful
Gitea/kapitanbooru-uploader/pipeline/head This commit looks good
This commit is contained in:
parent
dbea14888e
commit
5571e18102
@ -1,64 +1,96 @@
|
||||
from .common import open_tag_wiki_url, open_webbrowser
|
||||
from .ProgressFile import ProgressFile
|
||||
from .autocomplete import TagManager
|
||||
from .settings import Settings
|
||||
from .tag_processing import TAG_FIXES, parse_parameters, process_tag
|
||||
from .tagger_cache import TaggerCache
|
||||
from .TagsRepo import TagsRepo
|
||||
|
||||
|
||||
import networkx as nx
|
||||
import requests
|
||||
import wdtagger as wdt
|
||||
from PIL import Image, ImageTk, PngImagePlugin
|
||||
|
||||
|
||||
import glob
|
||||
import hashlib
|
||||
import inspect
|
||||
import os
|
||||
import queue
|
||||
import threading
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog, messagebox, ttk
|
||||
from typing import Tuple
|
||||
|
||||
import networkx as nx
|
||||
import requests
|
||||
from PIL import Image, ImageTk, PngImagePlugin
|
||||
|
||||
import wdtagger as wdt
|
||||
from .ProgressFile import ProgressFile
|
||||
from .TagsRepo import TagsRepo
|
||||
from .autocomplete import TagManager
|
||||
from .common import get_auth_token, login, open_tag_wiki_url, open_webbrowser
|
||||
from .settings import Settings
|
||||
from .tag_processing import TAG_FIXES, parse_parameters, process_tag
|
||||
from .tagger_cache import TaggerCache
|
||||
|
||||
|
||||
class ProcessingDialog:
|
||||
def __init__(self, root, target_function, *args):
|
||||
self.root = root
|
||||
self.top = tk.Toplevel(root) # Create a top-level window
|
||||
self.top = tk.Toplevel(root)
|
||||
self.top.title("Processing...")
|
||||
self.top.geometry("300x150")
|
||||
self.top.protocol("WM_DELETE_WINDOW", self.on_close) # Handle close event
|
||||
self.top.protocol("WM_DELETE_WINDOW", self.on_close)
|
||||
|
||||
# Create Label and Progress Bar (or rotating animation)
|
||||
self.label = tk.Label(self.top, text="Processing, please wait...")
|
||||
self.label.pack(pady=10)
|
||||
|
||||
# Start with indeterminate progress bar
|
||||
self.progress = ttk.Progressbar(self.top, mode="indeterminate")
|
||||
self.progress.pack(pady=10, fill="x")
|
||||
self.progress.start(10) # Start animation
|
||||
self.progress.start(10)
|
||||
|
||||
# Create a thread for the target function
|
||||
# Setup communication queue and periodic checker
|
||||
self.queue = queue.Queue()
|
||||
self.running = True
|
||||
self.thread = threading.Thread(
|
||||
target=self.run_task, args=(target_function, *args)
|
||||
)
|
||||
self.thread.start()
|
||||
self.top.after(100, self.process_queue)
|
||||
|
||||
def process_queue(self):
|
||||
"""Process messages from the background thread"""
|
||||
while self.running:
|
||||
try:
|
||||
msg = self.queue.get_nowait()
|
||||
|
||||
if msg[0] == "mode":
|
||||
self.progress.config(mode=msg[1])
|
||||
if msg[1] == "determinate":
|
||||
self.progress["value"] = 0
|
||||
elif msg[0] == "max":
|
||||
self.progress["maximum"] = msg[1]
|
||||
elif msg[0] == "progress":
|
||||
self.progress["value"] = msg[1]
|
||||
elif msg[0] == "label":
|
||||
self.label.config(text=msg[1])
|
||||
|
||||
self.top.update_idletasks()
|
||||
except queue.Empty:
|
||||
break
|
||||
|
||||
if self.running:
|
||||
self.top.after(100, self.process_queue)
|
||||
|
||||
def run_task(self, target_function, *args):
|
||||
"""Execute target function with progress queue if supported"""
|
||||
try:
|
||||
target_function(*args) # Run the function
|
||||
sig = inspect.signature(target_function)
|
||||
if "progress_queue" in sig.parameters:
|
||||
target_function(*args, progress_queue=self.queue)
|
||||
else:
|
||||
target_function(*args)
|
||||
finally:
|
||||
self.close_dialog() # Close when done
|
||||
self.close_dialog()
|
||||
|
||||
def close_dialog(self):
|
||||
"""Safely close the dialog."""
|
||||
"""Safely close the dialog"""
|
||||
if self.running:
|
||||
self.running = False
|
||||
self.top.after(0, self.top.destroy) # Ensure UI updates on the main thread
|
||||
self.progress.stop()
|
||||
self.top.after(0, self.top.destroy)
|
||||
|
||||
def on_close(self):
|
||||
"""User manually closed the dialog, terminate thread."""
|
||||
"""Handle manual window closure"""
|
||||
self.running = False
|
||||
self.top.destroy()
|
||||
|
||||
@ -271,12 +303,42 @@ class ImageBrowser(tk.Tk):
|
||||
def create_menu(self):
|
||||
menubar = tk.Menu(self)
|
||||
self.config(menu=menubar)
|
||||
menubar.add_command(label="Ustawienia", command=self.open_settings)
|
||||
menubar.add_command(label="Wyczyść cache Taggera", command=self.clear_cache)
|
||||
menubar.add_command(label="Wrzuć wszystko", command=self.upload_all_files)
|
||||
menubar.add_command(
|
||||
|
||||
# Create file menu and store it as instance variable
|
||||
self.file_menu = tk.Menu(menubar, tearoff=0)
|
||||
|
||||
# File menu items - create references for items we need to control
|
||||
self.file_menu.add_command(label="Otwórz folder", command=self.select_folder)
|
||||
self.file_menu.add_separator()
|
||||
self.file_menu.add_command(
|
||||
label="Wyślij", command=self.upload_current_image, state=tk.DISABLED
|
||||
)
|
||||
self.file_menu.add_command(
|
||||
label="Wyślij wszystko", command=self.upload_all_files, state=tk.DISABLED
|
||||
)
|
||||
self.file_menu.add_separator()
|
||||
self.file_menu.add_command(
|
||||
label="Podmień tagi", command=self.edit_current_image, state=tk.DISABLED
|
||||
)
|
||||
self.file_menu.add_command(
|
||||
label="Otwórz post", command=self.view_current_post, state=tk.DISABLED
|
||||
)
|
||||
self.file_menu.add_separator()
|
||||
self.file_menu.add_command(label="Zakończ", command=self.quit)
|
||||
|
||||
menubar.add_cascade(label="Plik", menu=self.file_menu)
|
||||
|
||||
# Options menu
|
||||
options_menu = tk.Menu(menubar, tearoff=0)
|
||||
options_menu.add_command(label="Ustawienia", command=self.open_settings)
|
||||
options_menu.add_separator()
|
||||
options_menu.add_command(
|
||||
label="Wyczyść cache Taggera", command=self.clear_cache
|
||||
)
|
||||
options_menu.add_command(
|
||||
label="Zregeneruj bazę tagów", command=self.regenerate_tags_db
|
||||
)
|
||||
menubar.add_cascade(label="Opcje", menu=options_menu)
|
||||
|
||||
def regenerate_tags_db(self):
|
||||
self.processing_dialog = ProcessingDialog(self, self.tags_repo.regenerate_db)
|
||||
@ -362,7 +424,7 @@ class ImageBrowser(tk.Tk):
|
||||
main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
# Lewa kolumna – lista plików
|
||||
left_frame = tk.Frame(main_frame)
|
||||
left_frame.grid(row=0, column=0, sticky="ns")
|
||||
left_frame.grid(row=0, column=0, sticky=tk.NS)
|
||||
self.listbox = tk.Listbox(left_frame, width=30)
|
||||
self.listbox.pack(side=tk.LEFT, fill=tk.Y)
|
||||
self.listbox.bind("<<ListboxSelect>>", self.on_listbox_select)
|
||||
@ -374,7 +436,7 @@ class ImageBrowser(tk.Tk):
|
||||
|
||||
# Środkowa kolumna – podgląd obrazu
|
||||
center_frame = tk.Frame(main_frame)
|
||||
center_frame.grid(row=0, column=1, sticky="nsew", padx=10)
|
||||
center_frame.grid(row=0, column=1, sticky=tk.NSEW, padx=10)
|
||||
main_frame.grid_columnconfigure(1, weight=1)
|
||||
main_frame.grid_rowconfigure(0, weight=1)
|
||||
main_frame.grid_rowconfigure(1, weight=0)
|
||||
@ -384,7 +446,7 @@ class ImageBrowser(tk.Tk):
|
||||
|
||||
# Prawa kolumna – panel tagów i uploadu (ograniczona szerokość)
|
||||
right_frame = tk.Frame(main_frame, width=300)
|
||||
right_frame.grid(row=0, column=2, sticky="nsew", padx=5)
|
||||
right_frame.grid(row=0, column=2, sticky=tk.NSEW, padx=5)
|
||||
right_frame.grid_propagate(False)
|
||||
right_frame.grid_columnconfigure(0, weight=1)
|
||||
# Ustal wiersze:
|
||||
@ -396,33 +458,33 @@ class ImageBrowser(tk.Tk):
|
||||
|
||||
# PNG Tags – widget Text z scrollbar
|
||||
png_frame = tk.LabelFrame(right_frame, text="PNG Tags")
|
||||
png_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
|
||||
png_frame.grid(row=0, column=0, sticky=tk.EW, padx=5, pady=5)
|
||||
png_frame.grid_columnconfigure(0, weight=1)
|
||||
self.png_tags_text = tk.Text(png_frame, wrap="word")
|
||||
self.png_tags_text.grid(row=0, column=0, sticky="ew")
|
||||
self.png_tags_text = tk.Text(png_frame, wrap=tk.WORD)
|
||||
self.png_tags_text.grid(row=0, column=0, sticky=tk.EW)
|
||||
scrollbar_png = tk.Scrollbar(png_frame, command=self.png_tags_text.yview)
|
||||
scrollbar_png.grid(row=0, column=1, sticky="ns")
|
||||
scrollbar_png.grid(row=0, column=1, sticky=tk.NS)
|
||||
self.png_tags_text.config(
|
||||
yscrollcommand=scrollbar_png.set, state="disabled", height=4
|
||||
yscrollcommand=scrollbar_png.set, state=tk.DISABLED, height=4
|
||||
)
|
||||
|
||||
# Tagger Tags – widget Text z scrollbar
|
||||
tagger_frame = tk.LabelFrame(right_frame, text="Tagger Tags")
|
||||
tagger_frame.grid(row=1, column=0, sticky="ew", padx=5, pady=5)
|
||||
tagger_frame.grid(row=1, column=0, sticky=tk.EW, padx=5, pady=5)
|
||||
tagger_frame.grid_columnconfigure(0, weight=1)
|
||||
self.tagger_tags_text = tk.Text(tagger_frame, wrap="word")
|
||||
self.tagger_tags_text.grid(row=0, column=0, sticky="ew")
|
||||
self.tagger_tags_text = tk.Text(tagger_frame, wrap=tk.WORD)
|
||||
self.tagger_tags_text.grid(row=0, column=0, sticky=tk.EW)
|
||||
scrollbar_tagger = tk.Scrollbar(
|
||||
tagger_frame, command=self.tagger_tags_text.yview
|
||||
)
|
||||
scrollbar_tagger.grid(row=0, column=1, sticky="ns")
|
||||
scrollbar_tagger.grid(row=0, column=1, sticky=tk.NS)
|
||||
self.tagger_tags_text.config(
|
||||
yscrollcommand=scrollbar_tagger.set, state="disabled", height=4
|
||||
yscrollcommand=scrollbar_tagger.set, state=tk.DISABLED, height=4
|
||||
)
|
||||
|
||||
# Manual Tags – Entry (stała wysokość)
|
||||
manual_frame = tk.LabelFrame(right_frame, text="Manual Tags")
|
||||
manual_frame.grid(row=2, column=0, sticky="nsew", padx=5, pady=5)
|
||||
manual_frame.grid(row=2, column=0, sticky=tk.NSEW, padx=5, pady=5)
|
||||
self.manual_tags_manager = TagManager(
|
||||
manual_frame, self.settings, self.tags_repo
|
||||
)
|
||||
@ -436,18 +498,18 @@ class ImageBrowser(tk.Tk):
|
||||
|
||||
# Final Tags – widget Text z scrollbar, który rozszerza się
|
||||
final_frame = tk.LabelFrame(right_frame, text="Final Tags")
|
||||
final_frame.grid(row=3, column=0, sticky="nsew", padx=5, pady=5)
|
||||
final_frame.grid(row=3, column=0, sticky=tk.NSEW, padx=5, pady=5)
|
||||
final_frame.grid_rowconfigure(0, weight=1)
|
||||
final_frame.grid_columnconfigure(0, weight=1)
|
||||
self.final_tags_text = tk.Text(final_frame, state="disabled", wrap="word")
|
||||
self.final_tags_text.grid(row=0, column=0, sticky="nsew")
|
||||
self.final_tags_text = tk.Text(final_frame, state=tk.DISABLED, wrap=tk.WORD)
|
||||
self.final_tags_text.grid(row=0, column=0, sticky=tk.NSEW)
|
||||
scrollbar_final = tk.Scrollbar(final_frame, command=self.final_tags_text.yview)
|
||||
scrollbar_final.grid(row=0, column=1, sticky="ns")
|
||||
scrollbar_final.grid(row=0, column=1, sticky=tk.NS)
|
||||
self.final_tags_text.config(yscrollcommand=scrollbar_final.set)
|
||||
|
||||
# Panel uploadu i rating – nie zmienia rozmiaru pionowo
|
||||
upload_frame = tk.Frame(right_frame)
|
||||
upload_frame.grid(row=4, column=0, sticky="ew", padx=5, pady=5)
|
||||
upload_frame.grid(row=4, column=0, sticky=tk.EW, padx=5, pady=5)
|
||||
self.rating_var = tk.StringVar(value="Unrated")
|
||||
rating_options = ["General", "Sensitive", "Questionable", "Explicit", "Unrated"]
|
||||
self.rating_dropdown = tk.OptionMenu(
|
||||
@ -458,21 +520,18 @@ class ImageBrowser(tk.Tk):
|
||||
upload_frame, text="Upload", command=self.upload_current_image
|
||||
)
|
||||
self.upload_button.pack(side=tk.LEFT, padx=5)
|
||||
self.progress_var = tk.IntVar(value=0)
|
||||
self.progress_bar = ttk.Progressbar(
|
||||
upload_frame,
|
||||
orient="horizontal",
|
||||
mode="determinate",
|
||||
variable=self.progress_var,
|
||||
maximum=100,
|
||||
self.upload_button.config(state=tk.DISABLED)
|
||||
self.view_post_button = tk.Button(
|
||||
upload_frame, text="Wyświetl", command=self.view_current_post
|
||||
)
|
||||
self.progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
||||
self.view_post_button.pack(side=tk.LEFT, padx=5)
|
||||
self.view_post_button.config(state=tk.DISABLED)
|
||||
|
||||
# Na końcu okna głównego dodaj status bar:
|
||||
self.status_label = tk.Label(
|
||||
main_frame, text="", bd=1, relief=tk.SUNKEN, anchor=tk.W
|
||||
)
|
||||
self.status_label.grid(row=1, column=0, columnspan=3, sticky="ew")
|
||||
self.status_label.grid(row=1, column=0, columnspan=3, sticky=tk.EW)
|
||||
|
||||
def update_status_bar(self):
|
||||
status_text = (
|
||||
@ -585,33 +644,77 @@ class ImageBrowser(tk.Tk):
|
||||
)
|
||||
# Jeśli aktualnie wybrany plik, zmień przycisk
|
||||
if self.current_index == idx:
|
||||
self.after(0, self.set_upload_button_to_view_or_upload)
|
||||
self.after(0, self.update_button_states)
|
||||
else:
|
||||
self.uploaded[file_path] = False
|
||||
self.after(0, self.update_status_bar)
|
||||
except Exception as e:
|
||||
print("Błąd podczas sprawdzania paczki uploadu:", e)
|
||||
|
||||
def set_upload_button_to_view_or_upload(self):
|
||||
def update_button_states(self):
|
||||
"""
|
||||
Ustawia przycisk uploadu na "Wyświetl" lub "Upload" w zależności od stan
|
||||
uploadu dla aktualnie wybranego pliku.
|
||||
Update the state of UI elements based on current application state.
|
||||
|
||||
Synchronizes the enabled/disabled states of menu items and buttons with:
|
||||
- Whether an image is currently selected (current_index exists)
|
||||
- Whether the selected image has an existing post (post_id exists in uploaded)
|
||||
|
||||
State transitions:
|
||||
- When no image is selected (current_index is None):
|
||||
* Disables all upload-related buttons and menu items
|
||||
- When an image is selected:
|
||||
* Always enables 'Wyślij wszystko' (Send All)
|
||||
* Enables 'Wyślij' (Send) if no post exists for the image
|
||||
* Enables 'Podmień tagi' (Replace Tags) and 'Otwórz post' (Open Post)
|
||||
if a post exists for the image
|
||||
* Updates the upload button's text and command based on post existence
|
||||
|
||||
Modifies:
|
||||
- File menu items: 'Wyślij', 'Wyślij wszystko', 'Podmień tagi', 'Otwórz post'
|
||||
- Buttons: upload_button, view_post_button
|
||||
|
||||
Dependencies:
|
||||
- Relies on self.current_index to determine selection state
|
||||
- Checks self.uploaded against self.image_files for post existence
|
||||
"""
|
||||
has_current = self.current_index is not None
|
||||
post_id = (
|
||||
self.uploaded.get(self.image_files[self.current_index])
|
||||
if has_current
|
||||
else None
|
||||
)
|
||||
|
||||
# Common state for all elements
|
||||
send_all_state = tk.NORMAL if has_current else tk.DISABLED
|
||||
post_ops_state = tk.NORMAL if post_id else tk.DISABLED
|
||||
|
||||
# Update menu items
|
||||
self.file_menu.entryconfig("Wyślij wszystko", state=send_all_state)
|
||||
self.file_menu.entryconfig("Podmień tagi", state=post_ops_state)
|
||||
self.file_menu.entryconfig("Otwórz post", state=post_ops_state)
|
||||
|
||||
# Update buttons
|
||||
self.upload_button.config(
|
||||
state=tk.NORMAL if has_current else tk.DISABLED,
|
||||
text="Podmień tagi" if post_id else "Wyślij",
|
||||
command=self.edit_current_image if post_id else self.upload_current_image,
|
||||
)
|
||||
self.view_post_button.config(state=post_ops_state)
|
||||
|
||||
# Special case for "Wyślij" menu item
|
||||
wyślij_state = tk.DISABLED if post_id else tk.NORMAL
|
||||
self.file_menu.entryconfig(
|
||||
"Wyślij", state=wyślij_state if has_current else tk.DISABLED
|
||||
)
|
||||
|
||||
def view_current_post(self):
|
||||
"""Otwiera w przeglądarce URL posta."""
|
||||
if self.current_index is None:
|
||||
return
|
||||
post_id = self.uploaded.get(self.image_files[self.current_index])
|
||||
if post_id:
|
||||
self.upload_button.config(
|
||||
text="Wyświetl", command=lambda: self.view_post(post_id)
|
||||
)
|
||||
else:
|
||||
self.upload_button.config(text="Upload", command=self.upload_current_image)
|
||||
|
||||
def view_post(self, post_id):
|
||||
"""Otwiera w przeglądarce URL posta."""
|
||||
|
||||
url = self.settings.base_url.rstrip("/") + "/post/view/" + str(post_id)
|
||||
open_webbrowser(url, self.settings)
|
||||
url = self.settings.base_url.rstrip("/") + "/post/view/" + str(post_id)
|
||||
open_webbrowser(url, self.settings)
|
||||
|
||||
def compute_md5(self, file_path, chunk_size=8192):
|
||||
"""Oblicza MD5 dla danego pliku."""
|
||||
@ -635,7 +738,7 @@ class ImageBrowser(tk.Tk):
|
||||
index = self.listbox.curselection()[0]
|
||||
self.current_index = index
|
||||
self.show_image(index)
|
||||
self.set_upload_button_to_view_or_upload()
|
||||
self.update_button_states()
|
||||
|
||||
def upload_current_image(self):
|
||||
"""
|
||||
@ -646,13 +749,10 @@ class ImageBrowser(tk.Tk):
|
||||
file_path = self.image_files[self.current_index]
|
||||
if self.uploaded.get(file_path, False):
|
||||
# Jeśli plik już uploadowany, ustaw przycisk na "Wyświetl"
|
||||
self.set_upload_button_to_view_or_upload()
|
||||
self.update_button_states()
|
||||
return
|
||||
self.upload_button.config(state="disabled")
|
||||
self.progress_var.set(0)
|
||||
threading.Thread(
|
||||
target=self.upload_file, args=(file_path,), daemon=True
|
||||
).start()
|
||||
self.upload_button.config(state=tk.DISABLED)
|
||||
self.processing_dialog = ProcessingDialog(self, self.upload_file, file_path)
|
||||
|
||||
def show_image(self, index):
|
||||
"""
|
||||
@ -678,6 +778,18 @@ class ImageBrowser(tk.Tk):
|
||||
except Exception as e:
|
||||
messagebox.showerror("Błąd", f"Nie można załadować obrazka:\n{e}")
|
||||
|
||||
def edit_current_image(self):
|
||||
"""
|
||||
Modyfikuje obrazek na serwerze.
|
||||
Wysyła POST z md5 obrazka, tagami i ratingiem.
|
||||
"""
|
||||
file_path = self.image_files[self.current_index]
|
||||
if not self.uploaded.get(file_path, False):
|
||||
self.update_button_states()
|
||||
return
|
||||
self.upload_button.config(state=tk.DISABLED)
|
||||
self.processing_dialog = ProcessingDialog(self, self.edit_file, file_path)
|
||||
|
||||
def update_display_image(self):
|
||||
"""
|
||||
Aktualizuje obrazek na podstawie aktualnego rozmiaru okna.
|
||||
@ -756,7 +868,7 @@ class ImageBrowser(tk.Tk):
|
||||
self.listbox.select_set(new_index)
|
||||
self.listbox.activate(new_index)
|
||||
self.show_image(new_index)
|
||||
self.set_upload_button_to_view_or_upload()
|
||||
self.update_button_states()
|
||||
|
||||
def show_next(self):
|
||||
"""
|
||||
@ -772,7 +884,7 @@ class ImageBrowser(tk.Tk):
|
||||
self.listbox.select_set(new_index)
|
||||
self.listbox.activate(new_index)
|
||||
self.show_image(new_index)
|
||||
self.set_upload_button_to_view_or_upload()
|
||||
self.update_button_states()
|
||||
|
||||
# --- Metody obsługujące widgety z tagami ---
|
||||
|
||||
@ -781,7 +893,7 @@ class ImageBrowser(tk.Tk):
|
||||
Aktualizuje widget z tagami z PNG.
|
||||
Tworzy tagi jako klikalne, zaznaczone lub niezaznaczone.
|
||||
"""
|
||||
self.png_tags_text.config(state="normal")
|
||||
self.png_tags_text.config(state=tk.NORMAL)
|
||||
self.png_tags_text.delete("1.0", tk.END)
|
||||
self.png_tags_states = {}
|
||||
for tag in tags_list:
|
||||
@ -794,7 +906,7 @@ class ImageBrowser(tk.Tk):
|
||||
self.png_tags_text.tag_configure(tag_name, foreground="blue")
|
||||
self.png_tags_text.tag_bind(tag_name, "<Button-1>", self.toggle_png_tag)
|
||||
self.png_tags_text.insert(tk.INSERT, " ")
|
||||
self.png_tags_text.config(state="disabled")
|
||||
self.png_tags_text.config(state=tk.DISABLED)
|
||||
self.adjust_text_widget_height(self.png_tags_text)
|
||||
self.update_final_tags()
|
||||
|
||||
@ -933,9 +1045,9 @@ class ImageBrowser(tk.Tk):
|
||||
self.png_tags_states[tag] = tag in common
|
||||
tag_name = "png_" + tag
|
||||
color = "blue" if self.png_tags_states[tag] else "black"
|
||||
self.png_tags_text.config(state="normal")
|
||||
self.png_tags_text.config(state=tk.NORMAL)
|
||||
self.png_tags_text.tag_configure(tag_name, foreground=color)
|
||||
self.png_tags_text.config(state="disabled")
|
||||
self.png_tags_text.config(state=tk.DISABLED)
|
||||
|
||||
for tag in self.tagger_tags_states:
|
||||
if tag.startswith("character:"):
|
||||
@ -956,7 +1068,7 @@ class ImageBrowser(tk.Tk):
|
||||
Poprawia wysokość widgetu.
|
||||
Aktualizuje listę finalnych tagów.
|
||||
"""
|
||||
self.tagger_tags_text.config(state="normal")
|
||||
self.tagger_tags_text.config(state=tk.NORMAL)
|
||||
self.tagger_tags_text.delete("1.0", tk.END)
|
||||
visible_tags = self.get_visible_tags()
|
||||
|
||||
@ -974,7 +1086,7 @@ class ImageBrowser(tk.Tk):
|
||||
)
|
||||
self.tagger_tags_text.insert(tk.INSERT, " ")
|
||||
|
||||
self.tagger_tags_text.config(state="disabled")
|
||||
self.tagger_tags_text.config(state=tk.DISABLED)
|
||||
|
||||
self.adjust_text_widget_height(self.tagger_tags_text)
|
||||
self.update_final_tags()
|
||||
@ -1035,7 +1147,7 @@ class ImageBrowser(tk.Tk):
|
||||
final_set.update(manual)
|
||||
final_list = sorted(final_set)
|
||||
|
||||
self.final_tags_text.config(state="normal")
|
||||
self.final_tags_text.config(state=tk.NORMAL)
|
||||
self.final_tags_text.delete("1.0", tk.END)
|
||||
for tag in final_list:
|
||||
_, deprecated = process_tag(tag, self.tags_repo)
|
||||
@ -1061,7 +1173,7 @@ class ImageBrowser(tk.Tk):
|
||||
tag_name, "<Button-1>", self.open_final_tag_wiki_url
|
||||
)
|
||||
self.final_tags_text.insert(tk.INSERT, " ")
|
||||
self.final_tags_text.config(state="disabled")
|
||||
self.final_tags_text.config(state=tk.DISABLED)
|
||||
|
||||
def open_final_tag_wiki_url(self, event):
|
||||
"""Otwiera w przeglądarce URL strony wiki dla klikniętego tagu.
|
||||
@ -1101,10 +1213,10 @@ class ImageBrowser(tk.Tk):
|
||||
if self.current_index is None:
|
||||
return
|
||||
# Ustaw komunikat, że Tagger pracuje
|
||||
self.tagger_tags_text.config(state="normal")
|
||||
self.tagger_tags_text.config(state=tk.NORMAL)
|
||||
self.tagger_tags_text.delete("1.0", tk.END)
|
||||
self.tagger_tags_text.insert("1.0", "Tagger przetwarza...")
|
||||
self.tagger_tags_text.config(state="disabled")
|
||||
self.tagger_tags_text.config(state=tk.DISABLED)
|
||||
file_path = self.image_files[self.current_index]
|
||||
result = self.get_tagger_results(file_path)
|
||||
new_rating = self.map_tagger_rating(result)
|
||||
@ -1112,7 +1224,14 @@ class ImageBrowser(tk.Tk):
|
||||
self.after(0, lambda: self.update_tagger_tags_widget(result))
|
||||
self.update_listbox_item_color_by_rating(file_path, result.rating)
|
||||
|
||||
def upload_file(self, file_path, final_tags=None, final_rating=None):
|
||||
def upload_file(
|
||||
self, file_path, final_tags=None, final_rating=None, progress_queue=None
|
||||
):
|
||||
base_file_name = os.path.basename(file_path)
|
||||
if progress_queue:
|
||||
progress_queue.put(("mode", "determinate"))
|
||||
progress_queue.put(("max", 100))
|
||||
progress_queue.put(("label", f"Wysyłam plik {base_file_name}..."))
|
||||
url = self.settings.base_url.rstrip("/") + "/api/danbooru/add_post"
|
||||
tags = (
|
||||
self.final_tags_text.get("1.0", tk.END).strip()
|
||||
@ -1134,16 +1253,16 @@ class ImageBrowser(tk.Tk):
|
||||
total_size = os.path.getsize(file_path)
|
||||
|
||||
def progress_callback(bytes_read, total_size):
|
||||
percentage = int(bytes_read / total_size * 100)
|
||||
self.progress_bar.after(0, lambda: self.progress_var.set(percentage))
|
||||
if progress_queue:
|
||||
percentage = int(bytes_read / total_size * 100)
|
||||
progress_queue.put(("progress", percentage))
|
||||
|
||||
with open(file_path, "rb") as f:
|
||||
wrapped_file = ProgressFile(f, progress_callback, total_size)
|
||||
files = {
|
||||
"file": (os.path.basename(file_path), wrapped_file, "image/png")
|
||||
}
|
||||
files = {"file": (base_file_name, wrapped_file, "image/png")}
|
||||
response = requests.post(url, data=fields, files=files)
|
||||
self.progress_bar.after(0, lambda: self.progress_var.set(100))
|
||||
if progress_queue:
|
||||
progress_queue.put(("progress", 100))
|
||||
show_warn = False
|
||||
post_url = None
|
||||
if response.status_code in (200, 201):
|
||||
@ -1156,9 +1275,6 @@ class ImageBrowser(tk.Tk):
|
||||
else:
|
||||
message = f"Upload zakończony błędem.\nStatus: {response.status_code}\nTreść: {response.text}"
|
||||
show_warn = True
|
||||
self.upload_button.after(
|
||||
0, lambda: self.upload_button.config(state="normal")
|
||||
)
|
||||
# Aktualizacja wyglądu listy – musimy użyć domyślnych argumentów w lambdzie, aby zachować bieżący indeks
|
||||
if show_warn:
|
||||
if not final_tags:
|
||||
@ -1177,13 +1293,101 @@ class ImageBrowser(tk.Tk):
|
||||
self.uploaded[file_path] = post_id
|
||||
self.uploaded_count += 1
|
||||
self.after(0, self.update_status_bar)
|
||||
self.set_upload_button_to_view_or_upload()
|
||||
self.after(0, self.update_button_states)
|
||||
except Exception as e:
|
||||
self.upload_button.after(
|
||||
0, lambda: self.upload_button.config(state="normal")
|
||||
)
|
||||
self.upload_button.after(0, self.update_button_states)
|
||||
messagebox.showerror("Błąd uploadu", str(e))
|
||||
|
||||
def edit_file(
|
||||
self, file_path, final_tags=None, final_rating=None, progress_queue=None
|
||||
):
|
||||
"""
|
||||
Update tags and rating for an existing post without uploading the file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the image file associated with the post
|
||||
final_tags: Optional override tags (space-separated string)
|
||||
final_rating: Optional override rating
|
||||
progress_queue: Progress communication queue
|
||||
|
||||
Requires:
|
||||
- The file must have an existing post ID in self.uploaded
|
||||
"""
|
||||
base_file_name = os.path.basename(file_path)
|
||||
post_id = self.uploaded.get(file_path)
|
||||
|
||||
if not post_id:
|
||||
messagebox.showerror(
|
||||
"Błąd edycji", "Post nie został znaleziony dla tego pliku"
|
||||
)
|
||||
return
|
||||
|
||||
if progress_queue:
|
||||
progress_queue.put(("mode", "determinate"))
|
||||
progress_queue.put(("max", 100))
|
||||
progress_queue.put(("label", f"Aktualizuję tagi dla {base_file_name}..."))
|
||||
|
||||
try:
|
||||
# Get authentication session and token
|
||||
session = login(self.settings)
|
||||
auth_token = get_auth_token(session, self.settings)
|
||||
|
||||
# Prepare tags and rating
|
||||
tags = (
|
||||
self.final_tags_text.get("1.0", tk.END).strip()
|
||||
if final_tags is None
|
||||
else final_tags
|
||||
)
|
||||
|
||||
rating_value = self.rating_map.get(
|
||||
self.rating_var.get() if final_rating is None else final_rating, "?"
|
||||
)
|
||||
|
||||
# Prepare API request
|
||||
url = self.settings.base_url.rstrip("/") + "/post/set"
|
||||
payload = {
|
||||
"auth_token": auth_token,
|
||||
"image_id": post_id,
|
||||
"title": base_file_name,
|
||||
"owner": self.settings.username,
|
||||
"tags": tags,
|
||||
"source": "",
|
||||
"rating": rating_value,
|
||||
}
|
||||
|
||||
if progress_queue:
|
||||
progress_queue.put(("progress", 50))
|
||||
|
||||
# Send update request
|
||||
response = session.post(url, data=payload, allow_redirects=False)
|
||||
|
||||
# Handle 302 redirect as success case
|
||||
if response.status_code == 302:
|
||||
if progress_queue:
|
||||
progress_queue.put(("progress", 100))
|
||||
|
||||
message = "Tagi zostały zaktualizowane!"
|
||||
if not final_tags: # Only show success if not bulk operation
|
||||
messagebox.showinfo("Sukces edycji", message)
|
||||
|
||||
# Update UI state
|
||||
self.after(0, self.update_button_states)
|
||||
return
|
||||
|
||||
# Handle other status codes
|
||||
error_msg = (
|
||||
f"Błąd podczas aktualizacji tagów\nStatus: {response.status_code}"
|
||||
)
|
||||
if response.text:
|
||||
error_msg += f"\nTreść: {response.text}"
|
||||
messagebox.showerror("Błąd edycji", error_msg)
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("Krytyczny błąd edycji", str(e))
|
||||
finally:
|
||||
if progress_queue:
|
||||
progress_queue.put(("progress", 100))
|
||||
|
||||
def upload_all_files(self):
|
||||
"""
|
||||
Metoda, która po potwierdzeniu przez użytkownika uploaduje wszystkie niewrzucone pliki.
|
||||
|
@ -179,20 +179,24 @@ class TagManager(tk.Frame):
|
||||
based on custom logic.
|
||||
"""
|
||||
|
||||
def __init__(self, master, settings: Settings, tags_repo: TagsRepo, *args, **kwargs):
|
||||
def __init__(
|
||||
self, master, settings: Settings, tags_repo: TagsRepo, *args, **kwargs
|
||||
):
|
||||
super().__init__(master, *args, **kwargs)
|
||||
self.tags_repo = tags_repo
|
||||
self.settings = settings
|
||||
self.manual_tags = [] # List to hold manually entered tags
|
||||
|
||||
# Entry for new tags (with autocompletion)
|
||||
self.entry = AutocompleteEntry(self, callback=self.add_tag, tags_repo=self.tags_repo)
|
||||
self.entry = AutocompleteEntry(
|
||||
self, callback=self.add_tag, tags_repo=self.tags_repo
|
||||
)
|
||||
self.entry.pack(fill=tk.X, padx=5, pady=5)
|
||||
|
||||
# Text widget for displaying already entered tags
|
||||
self.tags_display = tk.Text(self, wrap="word", height=4)
|
||||
self.tags_display = tk.Text(self, wrap=tk.WORD, height=4)
|
||||
self.tags_display.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
self.tags_display.config(state="disabled")
|
||||
self.tags_display.config(state=tk.DISABLED)
|
||||
# (Optional: add a scrollbar if needed)
|
||||
|
||||
def add_tag(self, tag):
|
||||
@ -229,7 +233,7 @@ class TagManager(tk.Frame):
|
||||
self.tags_display.tag_bind(tag_name, "<Button-1>", self.remove_tag)
|
||||
self.tags_display.tag_bind(tag_name, "<Button-3>", self.open_tag_wiki_url)
|
||||
self.tags_display.insert(tk.INSERT, " ")
|
||||
self.tags_display.config(state="disabled")
|
||||
self.tags_display.config(state=tk.DISABLED)
|
||||
|
||||
def remove_tag(self, event):
|
||||
"""Remove the clicked tag from the list and update the display."""
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "kapitanbooru-uploader"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
description = "A GUI application for uploading images to KapitanBooru"
|
||||
authors = [
|
||||
{name = "Michał Leśniak", email = "kapitan@mlesniak.pl"}
|
||||
|
Loading…
x
Reference in New Issue
Block a user