Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ Handles file system operations and provides a secure bridge between the frontend

## Features

- Smart tagging of photos based on detected objects, faces, and their recognition
- Traditional gallery features of album management
- Advanced image analysis with object detection and facial recognition
- Privacy-focused design with offline functionality
- Efficient data handling and parallel processing
- Smart search and retrieval
- Cross-platform compatibility
- **Smart Tagging**: Automatic photo tagging based on detected objects, faces, and their recognition
- **Batch Operations**: Select multiple images and perform bulk actions (delete, tag, move to album, export)
- **Album Management**: Traditional gallery features for organizing your photos
- **Advanced Image Analysis**: Object detection and facial recognition powered by AI
- **Privacy-Focused**: Offline functionality with all processing done locally
- **Efficient Processing**: Parallel processing for handling large photo collections
- **Smart Search**: Quick retrieval with advanced search capabilities
- **Cross-Platform**: Works on Windows, macOS, and Linux
- **Keyboard Shortcuts**: Ctrl+A to toggle select/deselect all, Escape to deselect (works on all platforms including Mac)

## Technical Stack

Expand Down
94 changes: 94 additions & 0 deletions backend/app/database/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,3 +419,97 @@ def db_toggle_image_favourite_status(image_id: str) -> bool:
return False
finally:
conn.close()



def db_delete_image(image_id: ImageId) -> bool:
"""
Delete a single image from the database by its ID.
This will also delete associated records in image_classes due to CASCADE.

Args:
image_id: ID of the image to delete

Returns:
True if deletion was successful, False otherwise
"""
conn = _connect()
cursor = conn.cursor()

try:
cursor.execute("DELETE FROM images WHERE id = ?", (image_id,))
conn.commit()
return cursor.rowcount > 0
except Exception as e:
logger.error(f"Error deleting image {image_id}: {e}")
conn.rollback()
return False
finally:
conn.close()


def db_add_tags_to_image(image_id: ImageId, tags: List[str]) -> bool:
"""
Add multiple tags to an image.

Args:
image_id: ID of the image to tag
tags: List of tag names to add

Returns:
True if tagging was successful, False otherwise
"""
if not tags:
return True

conn = _connect()
cursor = conn.cursor()

try:
# First, verify the image exists
cursor.execute("SELECT id FROM images WHERE id = ?", (image_id,))
if not cursor.fetchone():
logger.warning(f"Image {image_id} not found")
return False

# Get or create class IDs for each tag
class_ids = []
for tag in tags:
# Check if tag exists in mappings
cursor.execute("SELECT class_id FROM mappings WHERE name = ?", (tag,))
result = cursor.fetchone()

if result:
class_ids.append(result[0])
else:
# Create new mapping for this tag
cursor.execute(
"INSERT INTO mappings (name) VALUES (?)",
(tag,)
)
class_ids.append(cursor.lastrowid)

# Insert image-class pairs
for class_id in class_ids:
cursor.execute(
"""
INSERT OR IGNORE INTO image_classes (image_id, class_id)
VALUES (?, ?)
""",
(image_id, class_id),
)

# Mark image as tagged
cursor.execute(
"UPDATE images SET isTagged = 1 WHERE id = ?",
(image_id,)
)

conn.commit()
return True
except Exception as e:
logger.error(f"Error adding tags to image {image_id}: {e}")
conn.rollback()
return False
finally:
conn.close()
92 changes: 92 additions & 0 deletions backend/app/routes/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,95 @@ class ImageInfoResponse(BaseModel):
isTagged: bool
isFavourite: bool
tags: Optional[List[str]] = None


# Batch Operations
from app.schemas.batch import (
BatchDeleteRequest,
BatchTagRequest,
BatchMoveRequest,
BatchOperationResponse
)
from app.database.images import db_delete_image, db_add_tags_to_image


@router.delete("/batch-delete", response_model=BatchOperationResponse)
def batch_delete_images(request: BatchDeleteRequest):
"""Delete multiple images"""
processed = 0
failed = 0
errors = []

for image_id in request.image_ids:
try:
success = db_delete_image(image_id)
if success:
processed += 1
else:
failed += 1
errors.append(f"Image {image_id} not found")
except Exception as e:
failed += 1
errors.append(f"Failed to delete {image_id}: {str(e)}")
logger.error(f"Error deleting image {image_id}: {e}")

return BatchOperationResponse(
success=failed == 0,
processed=processed,
failed=failed,
errors=errors
)


@router.post("/batch-tag", response_model=BatchOperationResponse)
def batch_tag_images(request: BatchTagRequest):
"""Add tags to multiple images"""
processed = 0
failed = 0
errors = []

for image_id in request.image_ids:
try:
success = db_add_tags_to_image(image_id, request.tags)
if success:
processed += 1
else:
failed += 1
errors.append(f"Image {image_id} not found")
except Exception as e:
failed += 1
errors.append(f"Failed to tag {image_id}: {str(e)}")
logger.error(f"Error tagging image {image_id}: {e}")

return BatchOperationResponse(
success=failed == 0,
processed=processed,
failed=failed,
errors=errors
)


@router.post("/batch-move", response_model=BatchOperationResponse)
def batch_move_to_album(request: BatchMoveRequest):
"""Move multiple images to an album"""
processed = 0
failed = 0
errors = []

# TODO: Implement album functionality
for image_id in request.image_ids:
try:
# Placeholder for album move functionality
# success = db_add_image_to_album(image_id, request.album_id)
processed += 1
except Exception as e:
failed += 1
errors.append(f"Failed to move {image_id}: {str(e)}")
logger.error(f"Error moving image {image_id}: {e}")

return BatchOperationResponse(
success=failed == 0,
processed=processed,
failed=failed,
errors=errors
)
23 changes: 23 additions & 0 deletions backend/app/schemas/batch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from pydantic import BaseModel
from typing import List

class BatchDeleteRequest(BaseModel):
"""Request model for batch delete operation"""
image_ids: List[str]

class BatchTagRequest(BaseModel):
"""Request model for batch tag operation"""
image_ids: List[str]
tags: List[str]

class BatchMoveRequest(BaseModel):
"""Request model for batch move to album operation"""
image_ids: List[str]
album_id: str

class BatchOperationResponse(BaseModel):
"""Response model for batch operations"""
success: bool
processed: int
failed: int
errors: List[str] = []
Loading