import filter work and cleanup
This commit is contained in:
+146
-3
@@ -244,24 +244,34 @@ def search_tags():
|
||||
Query params:
|
||||
- q: search query (matches tag name, case-insensitive)
|
||||
- limit: max results (default 10)
|
||||
- exclude_kind: comma-separated list of tag kinds to exclude (e.g., 'archive')
|
||||
"""
|
||||
query = request.args.get('q', '').strip().lower()
|
||||
limit = request.args.get('limit', 10, type=int)
|
||||
exclude_kind = request.args.get('exclude_kind', '').strip()
|
||||
exclude_kinds = [k.strip() for k in exclude_kind.split(',') if k.strip()]
|
||||
|
||||
if not query:
|
||||
# Return most-used tags when no query
|
||||
tags = (
|
||||
base_query = (
|
||||
Tag.query
|
||||
.join(image_tags)
|
||||
.group_by(Tag.id)
|
||||
)
|
||||
if exclude_kinds:
|
||||
base_query = base_query.filter(~Tag.kind.in_(exclude_kinds))
|
||||
tags = (
|
||||
base_query
|
||||
.order_by(func.count(image_tags.c.image_id).desc())
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
else:
|
||||
base_query = Tag.query.filter(Tag.name.ilike(f'%{query}%'))
|
||||
if exclude_kinds:
|
||||
base_query = base_query.filter(~Tag.kind.in_(exclude_kinds))
|
||||
tags = (
|
||||
Tag.query
|
||||
.filter(Tag.name.ilike(f'%{query}%'))
|
||||
base_query
|
||||
.order_by(Tag.name)
|
||||
.limit(limit)
|
||||
.all()
|
||||
@@ -571,3 +581,136 @@ def get_import_stats():
|
||||
return jsonify(ok=True, stats=stats)
|
||||
except Exception as e:
|
||||
return jsonify(ok=False, error=str(e)), 500
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# Image filter/cleanup endpoints
|
||||
# ----------------------------
|
||||
|
||||
@main.get("/api/filter-scan")
|
||||
def scan_filterable_images():
|
||||
"""
|
||||
Scan existing images against current filter settings.
|
||||
Returns counts and IDs of images that would be filtered.
|
||||
Query params:
|
||||
- filter_type: 'transparency' or 'dimensions'
|
||||
- threshold: for transparency (0.0-1.0), or min_width/min_height for dimensions
|
||||
- min_width: minimum width (for dimensions filter)
|
||||
- min_height: minimum height (for dimensions filter)
|
||||
- limit: max results to return (default 100)
|
||||
"""
|
||||
from app.utils.image_importer import is_mostly_transparent
|
||||
|
||||
filter_type = request.args.get("filter_type", "transparency")
|
||||
limit = request.args.get("limit", 100, type=int)
|
||||
|
||||
results = []
|
||||
|
||||
if filter_type == "transparency":
|
||||
threshold = request.args.get("threshold", 0.9, type=float)
|
||||
|
||||
# Only check images that could have transparency
|
||||
images = ImageRecord.query.filter(
|
||||
ImageRecord.filepath.ilike("%.png") |
|
||||
ImageRecord.filepath.ilike("%.gif") |
|
||||
ImageRecord.filepath.ilike("%.webp")
|
||||
).limit(limit * 5).all() # Check more to find enough matches
|
||||
|
||||
for img in images:
|
||||
if len(results) >= limit:
|
||||
break
|
||||
if img.filepath and os.path.exists(img.filepath):
|
||||
try:
|
||||
if is_mostly_transparent(img.filepath, threshold):
|
||||
results.append({
|
||||
"id": img.id,
|
||||
"filename": img.filename,
|
||||
"filepath": img.filepath,
|
||||
"thumb_url": url_for('main.serve_image',
|
||||
filename=(img.thumb_path or img.filepath).replace('/images/', ''))
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
elif filter_type == "dimensions":
|
||||
min_width = request.args.get("min_width", 0, type=int)
|
||||
min_height = request.args.get("min_height", 0, type=int)
|
||||
|
||||
if min_width <= 0 and min_height <= 0:
|
||||
return jsonify(ok=True, count=0, images=[], message="No dimension filter set")
|
||||
|
||||
query = ImageRecord.query
|
||||
if min_width > 0:
|
||||
query = query.filter(ImageRecord.width < min_width)
|
||||
if min_height > 0:
|
||||
query = query.filter(ImageRecord.height < min_height)
|
||||
|
||||
images = query.limit(limit).all()
|
||||
|
||||
for img in images:
|
||||
results.append({
|
||||
"id": img.id,
|
||||
"filename": img.filename,
|
||||
"filepath": img.filepath,
|
||||
"width": img.width,
|
||||
"height": img.height,
|
||||
"thumb_url": url_for('main.serve_image',
|
||||
filename=(img.thumb_path or img.filepath).replace('/images/', ''))
|
||||
})
|
||||
|
||||
# Get total count (without limit) for progress indication
|
||||
total_count = len(results)
|
||||
|
||||
return jsonify(ok=True, count=total_count, images=results, filter_type=filter_type)
|
||||
|
||||
|
||||
@main.post("/api/filter-delete")
|
||||
def delete_filtered_images():
|
||||
"""
|
||||
Delete images by their IDs after filter review.
|
||||
Query params:
|
||||
- image_ids: comma-separated list of image IDs to delete
|
||||
- confirm: must be 'true' to actually delete
|
||||
"""
|
||||
image_ids_str = request.form.get("image_ids", "")
|
||||
confirm = request.form.get("confirm") == "true"
|
||||
|
||||
if not confirm:
|
||||
return jsonify(ok=False, error="Confirmation required"), 400
|
||||
|
||||
if not image_ids_str:
|
||||
return jsonify(ok=False, error="No image IDs provided"), 400
|
||||
|
||||
try:
|
||||
image_ids = [int(x) for x in image_ids_str.split(",") if x.strip()]
|
||||
except ValueError:
|
||||
return jsonify(ok=False, error="Invalid image IDs"), 400
|
||||
|
||||
deleted_count = 0
|
||||
errors = []
|
||||
|
||||
for img_id in image_ids:
|
||||
img = ImageRecord.query.get(img_id)
|
||||
if not img:
|
||||
continue
|
||||
|
||||
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}")
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(
|
||||
ok=True,
|
||||
deleted_count=deleted_count,
|
||||
errors=errors if errors else None
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user