495 lines
13 KiB
Plaintext
495 lines
13 KiB
Plaintext
# PunimTag - Intelligent Photo Management System
|
|
|
|
## Project Overview
|
|
|
|
PunimTag is an intelligent photo management system that uses face recognition to automatically organize, tag, and manage personal photo collections. It's built with Flask (Python) and vanilla JavaScript, focusing on privacy-first local processing.
|
|
|
|
## Core Value Proposition
|
|
|
|
- **Automatic Face Recognition**: Identify and tag people in photos without manual effort
|
|
- **Smart Organization**: Group photos by people, events, and locations
|
|
- **Duplicate Detection**: Find and manage duplicate photos automatically
|
|
- **Intuitive Interface**: Web-based GUI that's easy to use for non-technical users
|
|
- **Privacy-First**: Local processing, no cloud dependencies
|
|
|
|
## Technology Stack
|
|
|
|
### Backend
|
|
- **Framework**: Flask (Python web framework)
|
|
- **Database**: SQLite (lightweight, file-based)
|
|
- **Face Recognition**: dlib (C++ library with Python bindings)
|
|
- **Image Processing**: Pillow (PIL fork)
|
|
- **Data Processing**: NumPy (numerical operations)
|
|
|
|
### Frontend
|
|
- **Language**: Vanilla JavaScript (ES6+)
|
|
- **Styling**: CSS3 with Grid/Flexbox
|
|
- **HTTP Client**: Fetch API
|
|
- **Progressive Loading**: Intersection Observer API
|
|
- **No Frameworks**: Pure JavaScript for simplicity
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
PunimTag/
|
|
├── src/ # Main application source code
|
|
│ ├── backend/ # Flask backend and API
|
|
│ │ ├── app.py # Main Flask application
|
|
│ │ ├── db_manager.py # Database operations
|
|
│ │ └── visual_identifier.py # Face recognition
|
|
│ ├── frontend/ # JavaScript and UI components
|
|
│ └── utils/ # Utility functions
|
|
│ └── tag_manager.py # Tag management
|
|
├── docs/ # Documentation and steering documents
|
|
├── tests/ # Test files
|
|
├── data/ # Database files and user data
|
|
├── config/ # Configuration files
|
|
├── scripts/ # Utility scripts
|
|
├── assets/ # Static assets
|
|
├── photos/ # User photo storage
|
|
└── main.py # Application entry point
|
|
```
|
|
|
|
## Key Features
|
|
|
|
### 1. Photo Management
|
|
- Upload and organize photos by date, location, and content
|
|
- Automatic metadata extraction (EXIF data, GPS coordinates)
|
|
- Batch operations for efficiency
|
|
|
|
### 2. Face Recognition & Tagging
|
|
- Automatic face detection in photos
|
|
- Face identification and naming
|
|
- Group photos by people
|
|
- Handle multiple faces per photo
|
|
|
|
### 3. Duplicate Management
|
|
- Find duplicate photos automatically
|
|
- Visual comparison tools
|
|
- Bulk removal options
|
|
- Keep best quality versions
|
|
|
|
### 4. Search & Discovery
|
|
- Search by person name
|
|
- Filter by date ranges
|
|
- Tag-based filtering
|
|
- Similar face suggestions
|
|
|
|
### 5. User Experience
|
|
- Progressive loading for large collections
|
|
- Responsive web interface
|
|
- Custom dialogs (no browser alerts)
|
|
- Real-time notifications
|
|
|
|
## Database Schema
|
|
|
|
```sql
|
|
-- Core tables
|
|
images (id, filename, path, date_taken, metadata)
|
|
faces (id, image_id, person_id, encoding, coordinates, confidence)
|
|
people (id, name, created_date)
|
|
tags (id, name)
|
|
image_tags (image_id, tag_id)
|
|
|
|
-- Supporting tables
|
|
face_encodings (id, face_id, encoding_data)
|
|
photo_metadata (image_id, exif_data, gps_data)
|
|
```
|
|
|
|
## API Standards
|
|
|
|
### Response Format
|
|
|
|
**Success Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
// Response data here
|
|
},
|
|
"message": "Optional success message"
|
|
}
|
|
```
|
|
|
|
**Error Response:**
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": "Descriptive error message",
|
|
"code": "ERROR_CODE_OPTIONAL"
|
|
}
|
|
```
|
|
|
|
**Paginated Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"items": [...],
|
|
"pagination": {
|
|
"page": 1,
|
|
"per_page": 20,
|
|
"total": 150,
|
|
"pages": 8
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### HTTP Status Codes
|
|
- **200 OK**: Request successful
|
|
- **201 Created**: Resource created successfully
|
|
- **400 Bad Request**: Invalid request data
|
|
- **404 Not Found**: Resource not found
|
|
- **500 Internal Server Error**: Server error
|
|
|
|
### Endpoint Naming Conventions
|
|
- **GET /photos**: List photos
|
|
- **GET /photos/{id}**: Get specific photo
|
|
- **POST /photos**: Create new photo
|
|
- **PUT /photos/{id}**: Update photo
|
|
- **DELETE /photos/{id}**: Delete photo
|
|
- **POST /photos/{id}/identify**: Identify faces in photo
|
|
|
|
## Python Code Conventions
|
|
|
|
### Code Style (PEP 8)
|
|
```python
|
|
# Imports
|
|
import os
|
|
import sys
|
|
from typing import List, Dict, Optional
|
|
from flask import Flask, request, jsonify
|
|
|
|
# Constants
|
|
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
|
|
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif'}
|
|
|
|
# Functions
|
|
def process_image(image_path: str, max_size: int = MAX_FILE_SIZE) -> Dict[str, any]:
|
|
"""
|
|
Process an image file and extract metadata.
|
|
|
|
Args:
|
|
image_path: Path to the image file
|
|
max_size: Maximum file size in bytes
|
|
|
|
Returns:
|
|
Dictionary containing image metadata
|
|
|
|
Raises:
|
|
FileNotFoundError: If image file doesn't exist
|
|
ValueError: If file size exceeds limit
|
|
"""
|
|
if not os.path.exists(image_path):
|
|
raise FileNotFoundError(f"Image file not found: {image_path}")
|
|
|
|
file_size = os.path.getsize(image_path)
|
|
if file_size > max_size:
|
|
raise ValueError(f"File size {file_size} exceeds limit {max_size}")
|
|
|
|
# Process the image
|
|
metadata = extract_metadata(image_path)
|
|
return metadata
|
|
```
|
|
|
|
### Naming Conventions
|
|
- **Variables and Functions**: Use snake_case
|
|
- **Classes**: Use PascalCase
|
|
- **Constants**: Use UPPER_CASE
|
|
|
|
### Type Hints
|
|
```python
|
|
from typing import List, Dict, Optional, Union, Tuple
|
|
|
|
def get_photos(
|
|
user_id: int,
|
|
page: int = 1,
|
|
per_page: int = DEFAULT_PAGE_SIZE,
|
|
filters: Optional[Dict[str, any]] = None
|
|
) -> Dict[str, Union[List[Dict], int]]:
|
|
"""Get photos with pagination and filtering."""
|
|
pass
|
|
```
|
|
|
|
### Error Handling
|
|
```python
|
|
import logging
|
|
from typing import Optional
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def safe_operation(func):
|
|
"""Decorator for safe operation execution."""
|
|
def wrapper(*args, **kwargs):
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except Exception as e:
|
|
logger.error(f"Error in {func.__name__}: {e}")
|
|
return None
|
|
return wrapper
|
|
```
|
|
|
|
## Database Operations
|
|
|
|
### Connection Management
|
|
```python
|
|
def get_db_connection():
|
|
conn = sqlite3.connect('punimtag_simple.db')
|
|
conn.row_factory = sqlite3.Row # Enable dict-like access
|
|
return conn
|
|
|
|
# Usage in endpoint
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
# Database operations
|
|
conn.commit()
|
|
except Exception as e:
|
|
conn.rollback()
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
finally:
|
|
conn.close()
|
|
```
|
|
|
|
### Parameterized Queries
|
|
```python
|
|
# Always use parameterized queries to prevent SQL injection
|
|
cursor.execute('SELECT * FROM images WHERE id = ?', (image_id,))
|
|
cursor.execute('INSERT INTO photos (name, path) VALUES (?, ?)', (name, path))
|
|
```
|
|
|
|
## Testing Standards
|
|
|
|
### Test Organization
|
|
```
|
|
tests/
|
|
├── unit/ # Unit tests for individual functions
|
|
├── integration/ # Integration tests for API endpoints
|
|
├── e2e/ # End-to-end tests for complete workflows
|
|
├── fixtures/ # Test data and fixtures
|
|
├── utils/ # Test utilities and helpers
|
|
└── conftest.py # pytest configuration and shared fixtures
|
|
```
|
|
|
|
### Unit Test Example
|
|
```python
|
|
# tests/unit/test_face_recognition.py
|
|
import pytest
|
|
from src.utils.face_recognition import detect_faces, encode_face
|
|
|
|
def test_detect_faces_with_valid_image():
|
|
"""Test face detection with a valid image."""
|
|
image_path = "tests/fixtures/valid_face.jpg"
|
|
faces = detect_faces(image_path)
|
|
|
|
assert len(faces) > 0
|
|
assert all(hasattr(face, 'left') for face in faces)
|
|
assert all(hasattr(face, 'top') for face in faces)
|
|
```
|
|
|
|
### Integration Test Example
|
|
```python
|
|
# tests/integration/test_photo_api.py
|
|
import pytest
|
|
from src.app import app
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
"""Create a test client."""
|
|
app.config['TESTING'] = True
|
|
app.config['DATABASE'] = 'test.db'
|
|
|
|
with app.test_client() as client:
|
|
yield client
|
|
|
|
def test_get_photos_endpoint(client):
|
|
"""Test the GET /photos endpoint."""
|
|
response = client.get('/photos')
|
|
|
|
assert response.status_code == 200
|
|
data = response.get_json()
|
|
assert data['success'] == True
|
|
assert 'photos' in data
|
|
```
|
|
|
|
## JavaScript Conventions
|
|
|
|
### Code Style
|
|
```javascript
|
|
// Use ES6+ features
|
|
const API_BASE_URL = '/api';
|
|
const DEFAULT_PAGE_SIZE = 20;
|
|
|
|
// Async/await for API calls
|
|
async function fetchPhotos(page = 1, perPage = DEFAULT_PAGE_SIZE) {
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}/photos?page=${page}&per_page=${perPage}`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error fetching photos:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Event handlers
|
|
function handlePhotoClick(photoId) {
|
|
showPhotoDetails(photoId);
|
|
}
|
|
|
|
// DOM manipulation
|
|
function updatePhotoGrid(photos) {
|
|
const grid = document.getElementById('photo-grid');
|
|
grid.innerHTML = '';
|
|
|
|
photos.forEach(photo => {
|
|
const photoElement = createPhotoElement(photo);
|
|
grid.appendChild(photoElement);
|
|
});
|
|
}
|
|
```
|
|
|
|
### Error Handling
|
|
```javascript
|
|
// Global error handler
|
|
window.addEventListener('error', (event) => {
|
|
console.error('Global error:', event.error);
|
|
showErrorMessage('An unexpected error occurred');
|
|
});
|
|
|
|
// API error handling
|
|
async function safeApiCall(apiFunction, ...args) {
|
|
try {
|
|
return await apiFunction(...args);
|
|
} catch (error) {
|
|
console.error('API call failed:', error);
|
|
showErrorMessage('Failed to load data. Please try again.');
|
|
return null;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Performance Considerations
|
|
|
|
### Image Processing
|
|
- **Thumbnail Generation**: On-demand with caching
|
|
- **Face Detection**: Optimized for speed vs accuracy
|
|
- **Batch Processing**: Efficient handling of large photo sets
|
|
- **Memory Management**: Streaming for large images
|
|
|
|
### Database Optimization
|
|
- **Indexing**: Strategic indexes on frequently queried columns
|
|
- **Connection Pooling**: Efficient database connections
|
|
- **Query Optimization**: Minimize N+1 query problems
|
|
- **Data Archiving**: Move old data to separate tables
|
|
|
|
### Frontend Performance
|
|
- **Progressive Loading**: Load data in chunks
|
|
- **Image Lazy Loading**: Load images as they become visible
|
|
- **Caching**: Browser caching for static assets
|
|
- **Debouncing**: Prevent excessive API calls
|
|
|
|
## Security Considerations
|
|
|
|
### Data Protection
|
|
- **Local Storage**: No cloud dependencies
|
|
- **Input Validation**: Sanitize all user inputs
|
|
- **SQL Injection Prevention**: Parameterized queries
|
|
- **File Upload Security**: Validate file types and sizes
|
|
|
|
### Privacy
|
|
- **Face Data**: Stored locally, not shared
|
|
- **Metadata**: User controls what's stored
|
|
- **Access Control**: Local access only
|
|
- **Data Export**: User can export/delete their data
|
|
|
|
## Development Workflow
|
|
|
|
### Code Organization
|
|
- **Modular Design**: Separate concerns into modules
|
|
- **Configuration Management**: Environment-based settings
|
|
- **Error Handling**: Comprehensive error catching and logging
|
|
- **Documentation**: Inline code documentation
|
|
|
|
### Testing Strategy
|
|
- **Unit Tests**: Test individual functions and classes
|
|
- **Integration Tests**: Test API endpoints and database operations
|
|
- **End-to-End Tests**: Test complete user workflows
|
|
- **Performance Tests**: Test with large datasets
|
|
|
|
## Quick Start Commands
|
|
|
|
```bash
|
|
# Install dependencies
|
|
pip install -r requirements.txt
|
|
|
|
# Run the application
|
|
python main.py
|
|
|
|
# Access the web interface
|
|
# http://localhost:5000
|
|
|
|
# Run tests
|
|
python tests/test_main.py
|
|
|
|
# Run with pytest (if installed)
|
|
pytest tests/
|
|
```
|
|
|
|
## Common Development Tasks
|
|
|
|
### Adding New API Endpoints
|
|
1. Follow the API standards for response format
|
|
2. Use proper HTTP status codes
|
|
3. Implement error handling
|
|
4. Add parameterized queries for database operations
|
|
5. Write integration tests
|
|
|
|
### Adding New Features
|
|
1. Follow the project structure
|
|
2. Use type hints in Python
|
|
3. Follow naming conventions
|
|
4. Add comprehensive error handling
|
|
5. Write tests for new functionality
|
|
|
|
### Database Changes
|
|
1. Use parameterized queries
|
|
2. Add proper indexes
|
|
3. Handle connection management
|
|
4. Implement rollback on errors
|
|
5. Update schema documentation
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
- **Face Recognition Not Working**: Check dlib installation and CUDA setup
|
|
- **Database Errors**: Verify SQLite file permissions and schema
|
|
- **Performance Issues**: Check image sizes and database indexes
|
|
- **UI Not Loading**: Check browser console for JavaScript errors
|
|
|
|
### Debug Mode
|
|
```python
|
|
# Enable debug mode in Flask
|
|
app.run(host='0.0.0.0', port=5000, debug=True)
|
|
```
|
|
|
|
## Future Roadmap
|
|
|
|
- Cloud sync capabilities
|
|
- Mobile app companion
|
|
- Advanced AI features (emotion detection, age progression)
|
|
- Social sharing features
|
|
- Integration with existing photo services
|
|
|
|
## Support and Resources
|
|
|
|
- Check the steering documents in `docs/`
|
|
- Review existing tests in `tests/`
|
|
- Check the API standards for endpoint usage
|
|
- Follow code conventions for maintainability |