Enhance Search GUI and SearchStats with photos without tags feature
This commit introduces functionality to search for and display photos that lack associated tags in the SearchGUI and SearchStats classes. The SearchGUI has been updated to manage the visibility of relevant columns based on the selected search type, ensuring a seamless user experience. Additionally, the SearchStats class now retrieves photos without tags from the database, improving the overall search capabilities of the application.
This commit is contained in:
parent
8a9834b056
commit
1c8856209a
@ -295,6 +295,10 @@ class PhotoTagger:
|
||||
|
||||
def main():
|
||||
"""Main CLI interface"""
|
||||
# Suppress pkg_resources deprecation warning from face_recognition library
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", message="pkg_resources is deprecated", category=UserWarning)
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="PunimTag CLI - Simple photo face tagger (Refactored)",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
|
||||
@ -26,7 +26,7 @@ class SearchGUI:
|
||||
"Most common tags (planned)",
|
||||
"Most photographed people (planned)",
|
||||
"Photos without faces",
|
||||
"Photos without tags (planned)",
|
||||
"Photos without tags",
|
||||
"Duplicate faces (planned)",
|
||||
"Face quality distribution (planned)",
|
||||
]
|
||||
@ -227,6 +227,21 @@ class SearchGUI:
|
||||
tree.heading("open_photo", text="")
|
||||
# Also hide the columns from display
|
||||
tree["displaycolumns"] = ("select", "tags", "open_dir", "path")
|
||||
# Auto-run search for photos without faces
|
||||
do_search()
|
||||
elif choice == self.SEARCH_TYPES[7]: # Photos without tags
|
||||
# No input needed for this search type
|
||||
search_btn.configure(state="normal")
|
||||
# Show person column since photos without tags might still have people
|
||||
tree.column("person", width=180, minwidth=50, anchor="w")
|
||||
tree.heading("person", text="Person", command=lambda: sort_treeview("person"))
|
||||
# Show the people icon column since there might be faces/people
|
||||
tree.column("open_photo", width=50, minwidth=50, anchor="center")
|
||||
tree.heading("open_photo", text="👤")
|
||||
# Show all columns since we want to display person info and tags (which will be empty)
|
||||
tree["displaycolumns"] = ("select", "person", "tags", "open_dir", "open_photo", "path")
|
||||
# Auto-run search for photos without tags
|
||||
do_search()
|
||||
else:
|
||||
planned_label.pack(anchor="w")
|
||||
name_entry.configure(state="disabled")
|
||||
@ -269,6 +284,12 @@ class SearchGUI:
|
||||
path, tag_info = row
|
||||
photo_tags = get_photo_tags_for_display(path)
|
||||
tree.insert("", tk.END, values=("☐", "", photo_tags, "📁", "", path))
|
||||
elif search_type_var.get() == self.SEARCH_TYPES[7]: # Photos without tags
|
||||
# For photos without tags: (path, filename) - show all columns but tags will be empty
|
||||
path, filename = row
|
||||
person_name = get_person_name_for_photo(path)
|
||||
photo_tags = get_photo_tags_for_display(path) # Will be "No tags"
|
||||
tree.insert("", tk.END, values=("☐", person_name, photo_tags, "📁", "👤", path))
|
||||
else:
|
||||
# For name search: (full_name, path) - show person column
|
||||
full_name, p = row
|
||||
@ -284,6 +305,9 @@ class SearchGUI:
|
||||
elif search_type_var.get() == self.SEARCH_TYPES[6]: # Photos without faces
|
||||
# Sort by path column for photos without faces
|
||||
self.sort_column = "path"
|
||||
elif search_type_var.get() == self.SEARCH_TYPES[7]: # Photos without tags
|
||||
# Sort by person column for photos without tags
|
||||
self.sort_column = "person"
|
||||
else:
|
||||
# Sort by person column for name search
|
||||
self.sort_column = "person"
|
||||
@ -339,6 +363,15 @@ class SearchGUI:
|
||||
# For photos without faces, we don't have person info, so we use empty string
|
||||
formatted_rows = [(path, "") for path, filename in rows]
|
||||
add_results(formatted_rows)
|
||||
elif choice == self.SEARCH_TYPES[7]: # Photos without tags
|
||||
rows = self.search_stats.get_photos_without_tags()
|
||||
if not rows:
|
||||
messagebox.showinfo("Search", "No photos without tags found.", parent=root)
|
||||
else:
|
||||
# Convert to the format expected by add_results: (path, filename)
|
||||
# For photos without tags, we have both path and filename
|
||||
formatted_rows = [(path, filename) for path, filename in rows]
|
||||
add_results(formatted_rows)
|
||||
|
||||
def open_dir(path: str):
|
||||
try:
|
||||
@ -899,6 +932,7 @@ class SearchGUI:
|
||||
# Determine column offsets based on search type
|
||||
is_tag_search = (search_type_var.get() == self.SEARCH_TYPES[2])
|
||||
is_photos_without_faces = (search_type_var.get() == self.SEARCH_TYPES[6])
|
||||
is_photos_without_tags = (search_type_var.get() == self.SEARCH_TYPES[7])
|
||||
if is_tag_search:
|
||||
# Tag search: person column is hidden, select is first
|
||||
select_col = "#1" # select is now column 1
|
||||
@ -913,6 +947,13 @@ class SearchGUI:
|
||||
face_col = "#4" # open_photo is now column 4 (but hidden)
|
||||
path_col = "#4" # path is now column 4 (since people icon is hidden)
|
||||
path_index = 5 # path is at index 5 in values array
|
||||
elif is_photos_without_tags:
|
||||
# Photos without tags: all columns visible, same as name search
|
||||
select_col = "#1" # select is now column 1
|
||||
open_dir_col = "#4" # open_dir is column 4
|
||||
face_col = "#5" # open_photo is column 5
|
||||
path_col = "#6" # path is column 6
|
||||
path_index = 5 # path is at index 5 in values array
|
||||
else:
|
||||
# Name search: all columns visible, select is first
|
||||
select_col = "#1" # select is now column 1
|
||||
@ -977,6 +1018,7 @@ class SearchGUI:
|
||||
# Determine column offsets based on search type
|
||||
is_tag_search = (search_type_var.get() == self.SEARCH_TYPES[2])
|
||||
is_photos_without_faces = (search_type_var.get() == self.SEARCH_TYPES[6])
|
||||
is_photos_without_tags = (search_type_var.get() == self.SEARCH_TYPES[7])
|
||||
if is_tag_search:
|
||||
# Tag search: person column is hidden, select is first
|
||||
tags_col = "#2" # tags is now column 2
|
||||
@ -991,6 +1033,13 @@ class SearchGUI:
|
||||
face_col = "#4" # open_photo is now column 4 (but hidden)
|
||||
path_col = "#4" # path is now column 4 (since people icon is hidden)
|
||||
path_index = 5 # path is at index 5 in values array
|
||||
elif is_photos_without_tags:
|
||||
# Photos without tags: all columns visible, same as name search
|
||||
tags_col = "#3" # tags is column 3
|
||||
open_dir_col = "#4" # open_dir is column 4
|
||||
face_col = "#5" # open_photo is column 5
|
||||
path_col = "#6" # path is column 6
|
||||
path_index = 5 # path is at index 5 in values array
|
||||
else:
|
||||
# Name search: all columns visible, select is first
|
||||
tags_col = "#3" # tags is column 3
|
||||
|
||||
@ -303,10 +303,31 @@ class SearchStats:
|
||||
return results
|
||||
|
||||
def get_photos_without_tags(self) -> List[Tuple]:
|
||||
"""Get photos that have no tags"""
|
||||
# This would need to be implemented in the database module
|
||||
# For now, return empty list
|
||||
return []
|
||||
"""Get photos that have no tags
|
||||
|
||||
Returns:
|
||||
List of tuples: (photo_path, filename)
|
||||
"""
|
||||
results = []
|
||||
try:
|
||||
with self.db.get_db_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
# Find photos that have no tags associated with them
|
||||
cursor.execute('''
|
||||
SELECT p.path, p.filename
|
||||
FROM photos p
|
||||
LEFT JOIN phototaglinkage ptl ON p.id = ptl.photo_id
|
||||
WHERE ptl.photo_id IS NULL
|
||||
ORDER BY p.filename
|
||||
''')
|
||||
for row in cursor.fetchall():
|
||||
if row and row[0]:
|
||||
results.append((row[0], row[1]))
|
||||
except Exception as e:
|
||||
if self.verbose > 0:
|
||||
print(f"Error searching photos without tags: {e}")
|
||||
|
||||
return results
|
||||
|
||||
def get_duplicate_faces(self, tolerance: float = 0.6) -> List[Dict]:
|
||||
"""Get potential duplicate faces (same person, different photos)"""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user