additional tag edditing functionality. and delete by tag. added import tuning options to settings

This commit is contained in:
Bryan Van Deusen
2026-01-18 16:52:32 -05:00
parent 4c56b73bc9
commit b3331bad76
7 changed files with 700 additions and 32 deletions
+145 -1
View File
@@ -267,7 +267,7 @@ def search_tags():
.all()
)
return jsonify(tags=[{"name": t.name, "kind": t.kind} for t in tags])
return jsonify(tags=[{"id": t.id, "name": t.name, "kind": t.kind} for t in tags])
@main.get("/image/<int:image_id>/tags")
@@ -427,3 +427,147 @@ def delete_tag(tag_id):
db.session.commit()
return jsonify(ok=True)
# ----------------------------
# Filtered deletion endpoints
# ----------------------------
@main.post("/api/delete-by-tag")
def delete_images_by_tag():
"""
Delete all images that have a specific tag.
Also removes the image files and thumbnails from disk.
Query params:
- tag_id: ID of the tag whose images should be deleted
- tag_name: Name of the tag (must match for security validation)
- delete_tag: if "true", also delete the tag itself
"""
tag_id = request.form.get("tag_id", type=int)
tag_name = request.form.get("tag_name", "").strip()
delete_tag_also = request.form.get("delete_tag") == "true"
if not tag_id:
return jsonify(ok=False, error="tag_id is required"), 400
if not tag_name:
return jsonify(ok=False, error="tag_name confirmation is required"), 400
tag = Tag.query.get_or_404(tag_id)
# Security: verify the provided tag name matches the actual tag
if tag.name != tag_name:
return jsonify(ok=False, error="Tag name confirmation does not match"), 403
images_to_delete = list(tag.images)
deleted_count = 0
errors = []
for img in images_to_delete:
try:
# Remove image file from disk
if img.filepath and os.path.exists(img.filepath):
os.remove(img.filepath)
# Remove thumbnail if exists
if img.thumb_path and os.path.exists(img.thumb_path):
os.remove(img.thumb_path)
# Delete DB record
db.session.delete(img)
deleted_count += 1
except Exception as e:
errors.append(f"Failed to delete {img.filename}: {e}")
# Optionally delete the tag itself
if delete_tag_also:
db.session.delete(tag)
db.session.commit()
return jsonify(
ok=True,
deleted_count=deleted_count,
tag_deleted=delete_tag_also,
errors=errors if errors else None
)
@main.get("/api/tag/<int:tag_id>/image-count")
def get_tag_image_count(tag_id):
"""Get the number of images associated with a tag."""
tag = Tag.query.get_or_404(tag_id)
count = len(tag.images)
return jsonify(ok=True, count=count, tag_name=tag.name)
# ----------------------------
# Import settings endpoints
# ----------------------------
@main.get("/api/import-settings")
def get_import_settings():
"""Get current import filter settings."""
# Load from file first, fall back to env defaults
import json
settings_path = "/import/settings.json"
settings = {
"min_width": int(os.environ.get("IMPORT_MIN_WIDTH", "0")),
"min_height": int(os.environ.get("IMPORT_MIN_HEIGHT", "0")),
"skip_transparent": os.environ.get("IMPORT_SKIP_TRANSPARENT", "false").lower() == "true",
"transparency_threshold": float(os.environ.get("IMPORT_TRANSPARENCY_THRESHOLD", "0.9")),
"phash_threshold": int(os.environ.get("IMPORT_PHASH_THRESHOLD", "2")),
}
try:
if os.path.exists(settings_path):
with open(settings_path, "r") as f:
file_settings = json.load(f)
settings.update(file_settings)
except Exception:
pass
return jsonify(ok=True, settings=settings)
@main.post("/api/import-settings")
def save_import_settings():
"""
Save import filter settings.
Settings are stored in /import/settings.json and loaded by the importer.
"""
settings = {
"min_width": request.form.get("min_width", 0, type=int),
"min_height": request.form.get("min_height", 0, type=int),
"skip_transparent": request.form.get("skip_transparent") == "true",
"transparency_threshold": request.form.get("transparency_threshold", 0.9, type=float),
"phash_threshold": request.form.get("phash_threshold", 2, type=int),
}
import json
settings_path = "/import/settings.json"
try:
with open(settings_path, "w") as f:
json.dump(settings, f, indent=2)
return jsonify(ok=True, settings=settings)
except Exception as e:
return jsonify(ok=False, error=str(e)), 500
@main.get("/api/import-stats")
def get_import_stats():
"""Get import statistics including filtered image counts."""
import json
stats_path = "/import/stats.json"
try:
if os.path.exists(stats_path):
with open(stats_path, "r") as f:
stats = json.load(f)
else:
stats = {
"total_processed": 0,
"total_imported": 0,
"filtered_by_dimension": 0,
"filtered_by_transparency": 0,
"filtered_by_duplicate": 0,
}
return jsonify(ok=True, stats=stats)
except Exception as e:
return jsonify(ok=False, error=str(e)), 500