This repository has been archived on 2026-05-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
imagerepo/summary.md
T

408 lines
16 KiB
Markdown

# ImageRepo - Codebase Summary
A Flask-based image repository with Celery task processing for importing, organizing, and managing images/videos with tagging, duplicate detection, and Gallery-DL metadata integration.
---
## Architecture Overview
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Flask Web │ │ Celery Worker │ │ Celery Beat │
│ (port 5000) │ │ (task queues) │ │ (scheduler) │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└───────────────────────┼───────────────────────┘
┌──────────────────┼──────────────────┐
│ │ │
┌─────▼─────┐ ┌──────▼──────┐ ┌─────▼─────┐
│ PostgreSQL│ │ Redis │ │ /images │
│ (database)│ │ (broker) │ │ /import │
└───────────┘ └─────────────┘ └───────────┘
```
---
## File Structure
### Core Application Files
| File | Purpose |
|------|---------|
| `run.py` | Entry point - creates Flask app via `create_app()` |
| `config.py` | Configuration class with DB, Redis, Celery settings |
| `docker-compose.yml` | Container orchestration for all services |
### Application Package (`app/`)
| File | Purpose |
|------|---------|
| `app/__init__.py:56-86` | Flask app factory `create_app()`, CLI commands, initializes extensions |
| `app/main.py` | Blueprint with all HTTP routes and API endpoints |
| `app/models.py` | SQLAlchemy models for database schema |
| `app/celery_app.py` | Celery configuration and task routing |
---
## Database Models (`app/models.py`)
### ImageRecord (lines 19-40)
Primary model for images/videos.
```
Fields: id, filename, filepath, thumb_path, hash (SHA256), perceptual_hash (256-bit),
file_size, width, height, format, camera_model, taken_at, imported_at, is_thumbnail
Relationships: tags (many-to-many via image_tags)
```
### Tag (lines 42-51)
Tagging system for organization.
```
Fields: id, name (unique), kind (artist|archive|user|source|post|character|series|rating)
Relationships: images (many-to-many)
```
### ArchiveRecord (lines 53-66)
Tracks processed archives to prevent re-importing.
```
Fields: id, filename, file_size, hash, imported_at, artist, tag_id
```
### AppSettings (lines 69-78)
Key-value store for application configuration.
```
Fields: id, key, value, updated_at
```
### ImportBatch (lines 81-101)
Groups import tasks for batch tracking.
```
Fields: id, source_directory, status, total_files, processed_files,
imported_count, skipped_count, failed_count, started_at, completed_at
```
### PostMetadata (lines 104-133)
Stores metadata about posts from external platforms (Patreon, SubscribeStar, HentaiFoundry).
```
Fields: id, tag_id (FK to post tag), platform, post_id, artist,
title, description (may contain HTML), source_url,
attachment_count, published_at, created_at, updated_at
Relationships: tag (one-to-one with Tag where kind='post')
```
### SeriesPage (lines 136-168)
Links images to series tags with page numbers for ordered reading.
```
Fields: id, series_tag_id (FK to tag), image_id (FK to image, unique),
page_number, created_at, updated_at
Constraints: unique(series_tag_id, page_number), unique(image_id)
Relationships: series_tag, image
```
### ImportTask (lines 171-222)
Individual file import task tracking.
```
Fields: id, source_path, file_hash, file_size, task_type, status, celery_task_id,
context (JSON), batch_id, result_image_id, error_message, retry_count, timestamps
Status values: pending, queued, processing, complete, failed, skipped
Task types: scan, import_image, import_archive, thumbnail, sidecar
```
---
## Celery Tasks (`app/tasks/`)
### Scan Tasks (`app/tasks/scan.py`)
| Function | Line | Purpose |
|----------|------|---------|
| `scan_directory()` | 29-154 | Walks `/import` directory, creates ImportTask records, queues import tasks |
| `_create_task_if_new()` | 157-228 | Creates ImportTask if file not already processed |
| `recover_interrupted_tasks()` | 231-302 | Re-queues stuck tasks (called periodically by Beat) |
| `update_batch_stats()` | 305-349 | Updates ImportBatch statistics |
### Import Tasks (`app/tasks/import_file.py`)
| Function | Line | Purpose |
|----------|------|---------|
| `import_media_file()` | 23-252 | Main import task - filters, deduplicates, copies, creates thumbnail |
| `import_archive()` | 255-407 | Extracts archive, queues child import tasks |
| `_fail_task()` | 410-425 | Marks task as failed |
| `_skip_task()` | 428-443 | Marks task as skipped |
| `_add_artist_tag()` | 446-459 | Adds artist:name tag to image |
| `_supersede_existing()` | 472-539 | Replaces smaller image with larger version |
### Thumbnail Tasks (`app/tasks/thumbnail.py`)
| Function | Line | Purpose |
|----------|------|---------|
| `generate_thumbnail_task()` | 18-69 | Generate thumbnail for single image/video |
| `regenerate_all_thumbnails()` | 72-108 | Bulk regenerate all thumbnails |
| `regenerate_missing_thumbnails()` | 111-153 | Generate only missing thumbnails |
### Sidecar Tasks (`app/tasks/sidecar.py`)
| Function | Line | Purpose |
|----------|------|---------|
| `apply_sidecar_metadata()` | 18-97 | Apply Gallery-DL JSON metadata to image |
| `scan_and_match_sidecars()` | 100-147 | Find and match sidecar files to images |
| `reprocess_sidecars_for_tag()` | 150-181 | Re-run sidecar extraction for tagged images |
---
## Utility Functions (`app/utils/`)
### Image Importer (`app/utils/image_importer.py`)
| Function | Line | Purpose |
|----------|------|---------|
| `load_import_settings()` | 50-88 | Load filter settings from DB/file/env |
| `is_mostly_transparent()` | 91-146 | Check if image is mostly transparent |
| `is_mostly_single_color()` | 149-215 | Check if image is solid color |
| `supersede_image()` | 218-275 | Replace smaller image with larger version |
| `extract_archive_resilient()` | 359-384 | Extract archive using unar/7z fallback |
| `build_hashed_dest_path()` | 391-408 | Generate content-addressed file path |
| `generate_thumbnail()` | 415-500 | Create thumbnail for image |
| `generate_video_thumbnail_mirrored()` | 524-532 | Create thumbnail for video |
| `transcode_video_to_mp4()` | 535-609 | Transcode video to H.264 MP4 |
| `needs_transcode()` | 612-615 | Check if video needs transcoding |
| `calculate_hash()` | 618-623 | SHA256 content hash |
| `calculate_perceptual_hash()` | 626-643 | 256-bit pHash for similarity |
| `find_similar_image()` | 646-678 | Find similar images by pHash |
| `extract_metadata()` | 691-754 | Extract EXIF/video metadata |
| `is_first_volume()` | 788-823 | Check if archive is first volume of multi-part |
| `compute_next_archive_tag_name()` | 825-890 | Generate unique archive:artist/NNNN tag |
### Metadata Enrichment (`app/utils/metadata_enrichment.py`)
| Function | Line | Purpose |
|----------|------|---------|
| `find_sidecar_json()` | 27-83 | Find Gallery-DL sidecar JSON for image |
| `load_sidecar_metadata()` | 86-93 | Parse sidecar JSON file |
| `extract_platform()` | 100-117 | Get platform from metadata |
| `extract_artist()` | 120-136 | Get artist/creator name |
| `extract_post_id()` | 139-155 | Get post ID |
| `extract_post_title()` | 158-164 | Get post title |
| `extract_description()` | 167-194 | Get post description/content (HTML or plain text) |
| `extract_source_url()` | 197-228 | Get URL to original post (constructs for SS/HF) |
| `extract_attachment_count()` | 231-248 | Get number of attachments in post |
| `extract_date()` | 251-271 | Extract publication date |
| `generate_source_tags()` | 338-369 | Generate source:platform and post:... tags |
| `resolve_date_conflict()` | 376-405 | Keep earliest date |
| `enrich_from_sidecar()` | 412-470 | Main enrichment function (returns platform, artist, title, description, source_url, etc.) |
### Version Migration (`app/utils/version_migration.py`)
| Function | Line | Purpose |
|----------|------|---------|
| `get_setting()` / `set_setting()` | 43-74 | Read/write AppSettings |
| `get_setting_int/float/bool()` | 77-104 | Typed setting getters |
| `get_import_settings()` | 111-122 | Load import filter settings from DB |
| `calculate_phash_256()` | 129-139 | Calculate 256-bit perceptual hash |
| `recompute_all_phashes()` | 146-218 | Batch recompute all pHashes |
| `check_and_run_migrations()` | 221-268 | Run data migrations on version change |
---
## HTTP Routes (`app/main.py`)
### Page Routes
| Route | Line | Purpose |
|-------|------|---------|
| `GET /` | 18-30 | Showcase - random images masonry |
| `GET /gallery` | 80-124 | Main gallery with infinite scroll |
| `GET /tags` | 391-475 | Tag explorer (filterable by kind) |
| `GET /artists` | 477-480 | Redirect to tag_list?kind=artist |
| `GET /settings` | 488-490 | Settings page |
### Gallery API
| Route | Line | Purpose |
|-------|------|---------|
| `GET /api/random-images` | 33-77 | Random images for showcase shuffle |
| `GET /api/gallery/scroll` | 188-259 | Infinite scroll pagination |
| `GET /api/gallery/timeline` | 262-316 | Year-month groups for sidebar |
| `GET /api/gallery/jump` | 319-388 | Jump to specific year-month |
| `GET /images/<path:filename>` | 127-129 | Serve image files |
### Tag API
| Route | Line | Purpose |
|-------|------|---------|
| `GET /api/tags/search` | 497-537 | Tag autocomplete search |
| `GET /image/<id>/tags` | 540-543 | List tags for image |
| `POST /image/<id>/tags/add` | 546-573 | Add tag to image |
| `POST /image/<id>/tags/remove` | 576-587 | Remove tag from image |
| `GET /api/tag/<id>` | 594-605 | Get tag details |
| `GET /api/post-metadata/<id>` | 608-651 | Get post metadata for a post tag |
| `GET /api/post-metadata/by-tag-name/<name>` | 654-685 | Get post metadata by tag name |
| `POST /api/tag/<id>/update` | 688-764 | Update/merge tag |
| `POST /api/tag/<id>/delete` | 767-776 | Delete tag |
| `GET /api/tag/<id>/image-count` | 842-847 | Count images with tag |
### Bulk Operations
| Route | Line | Purpose |
|-------|------|---------|
| `POST /api/delete-by-tag` | 703-759 | Delete all images with tag |
| `POST /api/images/common-tags` | 1394-1425 | Get common tags across images |
| `POST /api/images/bulk-add-tag` | 1428-1474 | Add tag to multiple images |
| `POST /api/images/bulk-remove-tag` | 1477-1514 | Remove tag from multiple images |
### Series / Reader API
| Route | Line | Purpose |
|-------|------|---------|
| `GET /read/<tag_id>` | 1620-1650 | Reader view for a series (vertical scroll) |
| `GET /api/series/<tag_id>/pages` | 1653-1685 | Get all pages in a series |
| `POST /api/series/<tag_id>/pages/add` | 1688-1737 | Add single image to series with page number |
| `POST /api/series/<tag_id>/pages/bulk-add` | 1740-1800 | Add multiple images with sequential page numbers |
| `POST /api/series/page/<page_id>/update` | 1803-1828 | Update a page's page number |
| `POST /api/series/page/<page_id>/delete` | 1831-1841 | Remove image from series |
| `GET /api/image/<image_id>/series-info` | 1844-1858 | Check if image is in a series |
### Import Queue API
| Route | Line | Purpose |
|-------|------|---------|
| `POST /api/import/trigger` | 961-981 | Start directory scan |
| `GET /api/import/status` | 984-1042 | Queue status and recent tasks |
| `GET /api/import/task/<id>` | 1045-1082 | Task details |
| `POST /api/import/retry-failed` | 1085-1122 | Retry failed tasks |
| `POST /api/import/clear-completed` | 1125-1144 | Clear completed tasks |
| `GET /api/import/batches` | 1222-1250 | List import batches |
### Settings API
| Route | Line | Purpose |
|-------|------|---------|
| `GET /api/import-settings` | 774-788 | Get import filter settings |
| `POST /api/import-settings` | 791-821 | Save import filter settings |
| `GET /api/filter-scan` | 828-902 | Scan images against filters |
| `POST /api/filter-delete` | 905-954 | Delete filtered images |
### Thumbnail API
| Route | Line | Purpose |
|-------|------|---------|
| `POST /api/thumbnails/regenerate` | 1147-1164 | Regenerate all thumbnails |
| `POST /api/thumbnails/regenerate-missing` | 1167-1183 | Generate missing thumbnails |
### Celery Status
| Route | Line | Purpose |
|-------|------|---------|
| `GET /api/celery/status` | 1186-1219 | Worker and queue status |
### Duplicate Detection
| Route | Line | Purpose |
|-------|------|---------|
| `POST /api/duplicates/scan` | 1257-1343 | Scan for similar images by pHash |
| `POST /api/duplicates/delete` | 1346-1387 | Delete duplicate images |
---
## Frontend Templates (`app/templates/`)
| Template | Purpose |
|----------|---------|
| `layout.html` | Base template with navbar |
| `showcase.html` | Random image masonry view |
| `gallery.html` | Main gallery with infinite scroll, timeline sidebar, bulk editor, series editor |
| `reader.html` | Series reader with vertical scroll, page jump, thumbnail navigation |
| `tags_list.html` | Tag browser with preview images |
| `settings.html` | Import settings, deletion tools, duplicate detection |
| `_gallery_item.html` | Single gallery item partial |
| `_gallery_modal.html` | Image modal with tag editor |
| `_gallery_grid.html` | Gallery grid partial |
| `_pagination.html` | Pagination controls |
### JavaScript Files (`app/static/js/`)
| File | Purpose |
|------|---------|
| `gallery-infinite.js` | Infinite scroll, timeline navigation |
| `modal-pagination.js` | Modal image navigation |
| `bulk-select.js` | Multi-select and bulk tag editing |
| `tag-editor.js` | Tag autocomplete and editing |
| `showcase.js` | Showcase shuffle functionality |
---
## Docker Services
| Service | Purpose |
|---------|---------|
| `redis` | Message broker for Celery |
| `postgres` | PostgreSQL database |
| `web` | Flask application (port 5000) |
| `celery-worker` | Task processor (queues: scan, import, thumbnail, sidecar, default) |
| `celery-beat` | Periodic task scheduler |
---
## Key Concepts
### Content-Addressed Storage
Files are stored with hash suffix: `{filename}__{hash[:10]}.{ext}`
### Perceptual Hashing
256-bit pHash for duplicate detection (threshold default: 10)
### Tag Kinds
- `artist` - Artist/creator (prefix: `artist:name`)
- `archive` - Archive source (prefix: `archive:artist/NNNN`)
- `source` - Platform source (prefix: `source:platform`)
- `post` - Post reference (prefix: `post:platform:artist:id`)
- `user` - User-created tags (no prefix)
- `character`, `series`, `rating` - Content tags
### Import Pipeline
1. `scan_directory` walks `/import/{artist}/` folders
2. Creates ImportTask for each file
3. `import_media_file` or `import_archive` processes file
4. Applies filters (dimensions, transparency, duplicates)
5. Copies to `/images/{artist}/` with hash suffix
6. Generates thumbnail
7. Applies sidecar metadata if found
8. Creates ImageRecord with tags
### Sidecar Metadata
Gallery-DL JSON files provide:
- Platform (patreon, hentaifoundry, etc.)
- Artist name
- Post ID and title
- Publication date
- Source URL
---
## Environment Variables
| Variable | Default | Purpose |
|----------|---------|---------|
| `DB_USER` | imagerepo | PostgreSQL user |
| `DB_PASS` | postgres | PostgreSQL password |
| `DB_HOST` | postgres | PostgreSQL host |
| `DB_NAME` | imagerepo | Database name |
| `CELERY_BROKER_URL` | redis://redis:6379/0 | Redis broker |
| `CELERY_WORKER_CONCURRENCY` | 2 | Worker processes |
| `IMPORT_EVERY_SECONDS` | 28800 | Auto-scan interval (8h) |
| `IMPORT_MIN_WIDTH` | 0 | Minimum image width |
| `IMPORT_MIN_HEIGHT` | 0 | Minimum image height |
| `IMPORT_SKIP_TRANSPARENT` | false | Skip transparent images |
| `IMPORT_TRANSPARENCY_THRESHOLD` | 0.9 | Transparency % threshold |
| `IMPORT_PHASH_THRESHOLD` | 10 | pHash similarity threshold |
---
## CLI Commands
```bash
flask version-check # Run data migrations
flask celery-worker # Start Celery worker
flask celery-beat # Start Celery Beat scheduler
```