Update tab_gallery_sorter.py

This commit is contained in:
2026-01-19 15:21:55 +01:00
parent 9c86eb4b72
commit b909069174

View File

@@ -23,16 +23,46 @@ def trigger_refresh():
if 't5_file_id' not in st.session_state: st.session_state.t5_file_id = 0
st.session_state.t5_file_id += 1
def cb_tag_image(img_path, selected_cat):
def cb_tag_image(img_path, selected_cat, index_val, path_o):
"""
Tags image with manual number.
Handles collisions by creating variants (e.g. _001_1) and warning the user.
"""
if selected_cat.startswith("---") or selected_cat == "":
st.toast("⚠️ Select a valid category first!", icon="🚫")
return
staged = SorterEngine.get_staged_data()
ext = os.path.splitext(img_path)[1]
count = len([v for v in staged.values() if v['cat'] == selected_cat]) + 1
new_name = f"{selected_cat}_{count:03d}{ext}"
base_name = f"{selected_cat}_{index_val:03d}"
new_name = f"{base_name}{ext}"
# --- COLLISION DETECTION ---
# 1. Check Staging DB
staged = SorterEngine.get_staged_data()
# Get all names currently staged for this category
staged_names = {v['name'] for v in staged.values() if v['cat'] == selected_cat}
# 2. Check Hard Drive
dest_path = os.path.join(path_o, selected_cat, new_name)
collision = False
suffix = 1
# Loop until we find a free name
while new_name in staged_names or os.path.exists(dest_path):
collision = True
new_name = f"{base_name}_{suffix}{ext}"
dest_path = os.path.join(path_o, selected_cat, new_name)
suffix += 1
# --- SAVE ---
SorterEngine.stage_image(img_path, selected_cat, new_name)
# Note: Tagging does NOT need a file re-scan, just a grid refresh.
if collision:
st.toast(f"⚠️ Conflict! Saved as variant: {new_name}", icon="🔀")
# REMOVED: st.session_state.t5_next_index += 1
# The numbers in the input boxes will now stay static.
def cb_untag_image(img_path):
SorterEngine.clear_staged_item(img_path)
@@ -138,19 +168,19 @@ def get_cached_thumbnail(path, quality, target_size, mtime):
# --- UPDATED GALLERY FRAGMENT ---
@st.fragment
def render_gallery_grid(current_batch, quality, grid_cols):
def render_gallery_grid(current_batch, quality, grid_cols, path_o): # <--- 1. Added path_o
staged = SorterEngine.get_staged_data()
history = SorterEngine.get_processed_log()
selected_cat = st.session_state.get("t5_active_cat", "Default")
tagging_disabled = selected_cat.startswith("---")
# 1. SMART RESOLUTION CALCULATION
# We assume a wide screen (approx 2400px wide for the container).
# If you have 2 cols, you get 1200px images. If 8 cols, you get 300px.
# This ensures images are always crisp but never wasteful.
# 2. Ensure global counter exists (default to 1)
if "t5_next_index" not in st.session_state: st.session_state.t5_next_index = 1
# 3. Smart Resolution (Wide screen assumption)
target_size = int(2400 / grid_cols)
# 2. PARALLEL LOAD
# 4. Parallel Load (16 threads for WebP)
import concurrent.futures
batch_cache = {}
@@ -161,15 +191,13 @@ def render_gallery_grid(current_batch, quality, grid_cols):
except:
return p, None
# We bump threads to 16 for WebP as it can be slightly more CPU intensive,
# but the smaller file size makes up for it in transfer speed.
with concurrent.futures.ThreadPoolExecutor(max_workers=16) as executor:
future_to_path = {executor.submit(fetch_one, p): p for p in current_batch}
for future in concurrent.futures.as_completed(future_to_path):
p, data = future.result()
batch_cache[p] = data
# 3. RENDER GRID
# 5. Render Grid
cols = st.columns(grid_cols)
for idx, img_path in enumerate(current_batch):
unique_key = f"frag_{os.path.basename(img_path)}"
@@ -178,39 +206,54 @@ def render_gallery_grid(current_batch, quality, grid_cols):
is_processed = img_path in history
with st.container(border=True):
# HEADER LAYOUT: [Name (4)] [Zoom (1)] [Delete (1)]
# Header: [Name] [Zoom] [Delete]
c_name, c_zoom, c_del = st.columns([4, 1, 1])
c_name.caption(os.path.basename(img_path)[:10])
# --- NEW ZOOM BUTTON ---
# When clicked, it calls the dialog function.
if c_zoom.button("🔍", key=f"zoom_{unique_key}", help="View Full Size"):
if c_zoom.button("🔍", key=f"zoom_{unique_key}"):
view_high_res(img_path)
# DELETE BUTTON
c_del.button("", key=f"del_{unique_key}", on_click=cb_delete_image, args=(img_path,))
# STATUS BANNERS...
# Status Banners
if is_staged:
st.success(f"🏷️ {staged[img_path]['cat']}")
elif is_processed:
st.info(f"{history[img_path]['action']}")
# THUMBNAIL IMAGE (Cached, Low Res)
# Image (Cached)
img_data = batch_cache.get(img_path)
if img_data:
st.image(img_data, use_container_width=True)
# Buttons
# Action Area
if not is_staged:
st.button("Tag", key=f"tag_{unique_key}", disabled=tagging_disabled, use_container_width=True,
on_click=cb_tag_image, args=(img_path, selected_cat))
# 6. Split Row: [Idx Input] [Tag Button]
c_idx, c_tag = st.columns([1, 2], vertical_alignment="bottom")
# Manual Override Box (Defaults to global session value)
card_index = c_idx.number_input(
"Idx",
min_value=1, step=1,
value=st.session_state.t5_next_index,
label_visibility="collapsed",
key=f"idx_{unique_key}"
)
# Tag Button (Passes path_o for conflict check)
c_tag.button(
"Tag",
key=f"tag_{unique_key}",
disabled=tagging_disabled,
use_container_width=True,
on_click=cb_tag_image,
# Passing card_index + path_o is vital here
args=(img_path, selected_cat, card_index, path_o)
)
else:
st.button("Untag", key=f"untag_{unique_key}", use_container_width=True,
on_click=cb_untag_image, args=(img_path,))
# ... (Batch Actions code remains exactly the same) ...
@st.fragment
def render_batch_actions(current_batch, path_o, page_num, path_s):
@@ -300,7 +343,7 @@ def render(quality, profile_name):
st.divider()
nav_controls("top")
render_gallery_grid(current_batch, quality, grid_cols)
render_gallery_grid(current_batch, quality, grid_cols, path_o)
st.divider()
nav_controls("bottom")
st.divider()