{"id":13674617,"url":"https://github.com/cbenning/fussel","last_synced_at":"2026-04-02T12:04:38.043Z","repository":{"id":39373847,"uuid":"261644894","full_name":"cbenning/fussel","owner":"cbenning","description":"A static photo gallery generator","archived":false,"fork":false,"pushed_at":"2026-03-23T04:01:17.000Z","size":6061,"stargazers_count":298,"open_issues_count":21,"forks_count":21,"subscribers_count":5,"default_branch":"main","last_synced_at":"2026-03-24T00:42:04.513Z","etag":null,"topics":["docker","gallery","photos","static-site-generator"],"latest_commit_sha":null,"homepage":"https://github.com/cbenning/fussel","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cbenning.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-05-06T03:43:27.000Z","updated_at":"2026-03-23T02:31:36.000Z","dependencies_parsed_at":"2024-04-03T16:41:54.305Z","dependency_job_id":"8c718c52-56ce-466d-9f95-e3c15d0a998c","html_url":"https://github.com/cbenning/fussel","commit_stats":null,"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/cbenning/fussel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbenning%2Ffussel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbenning%2Ffussel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbenning%2Ffussel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbenning%2Ffussel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cbenning","download_url":"https://codeload.github.com/cbenning/fussel/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbenning%2Ffussel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31305972,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T09:48:21.550Z","status":"ssl_error","status_checked_at":"2026-04-02T09:48:19.196Z","response_time":89,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","gallery","photos","static-site-generator"],"created_at":"2024-08-02T11:00:55.310Z","updated_at":"2026-04-02T12:04:38.036Z","avatar_url":"https://github.com/cbenning.png","language":"Python","funding_links":[],"categories":["Software"],"sub_categories":["Photo and Video Galleries"],"readme":"# Fussel\n\n![License Badge](https://img.shields.io/github/license/cbenning/fussel)\n![Version Badge](https://img.shields.io/github/v/release/cbenning/fussel)\n\n**Fussel** is a static photo gallery generator that builds beautiful, mobile-friendly photo galleries from a directory of photos. Once generated, your gallery is a completely static site with no server-side code required.\n\n**[View Demo Site](https://benninger.ca/fussel-demo/)**\n\n## ✨ Features\n\n- 🖼️ **Static Site Generation** - No server-side code required once generated\n- 📷 **EXIF Info Panel** - View camera, lens, shot settings, and GPS data for each photo\n- 👥 **People Detection** - Automatically creates galleries for people found in XMP face tags\n- 🏷️ **Face Tag Overlay** - Face rectangles displayed over photos in the modal viewer\n- 🔍 **Zoom \u0026 Pan** - Pinch/scroll to zoom and drag to pan photos in the viewer\n- 🎨 **Watermarking** - Add watermarks to protect your photos\n- 📱 **Mobile Friendly** - Responsive design that works on all devices\n- 🌙 **Dark Mode** - Automatic dark mode support\n- 🔄 **EXIF Transpose** - Uses EXIF data to automatically rotate photos\n- 🔗 **Clean URLs** - Predictable slug-based URLs for easy sharing\n- ⚡ **Fast Generation** - Parallel processing for quick builds\n- ⬇️ **Download Control** - Optionally allow/prevent original photo downloads\n\n## 📸 Screenshots\n\n| Albums View | Album View |\n|-------------|------------|\n| ![Albums Screenshot](https://user-images.githubusercontent.com/153700/81897761-1e904780-956c-11ea-9450-fbdb286b95fc.png?raw=true \"Albums Screenshot\") | ![Album Screenshot](https://user-images.githubusercontent.com/153700/81897716-120bef00-956c-11ea-9204-b8e90ffb24f8.png?raw=true \"Album Screenshot\") |\n\n| People View | Person View |\n|-------------|-------------|\n| ![People Screenshot](https://user-images.githubusercontent.com/153700/81897685-fef91f00-956b-11ea-8df6-9c23fad83bb2.png?raw=true \"People Screenshot\") | ![Person Screenshot](https://user-images.githubusercontent.com/153700/81897698-091b1d80-956c-11ea-9acb-6195d9673407.png?raw=true \"Person Screenshot\") |\n\n## 🎬 Demo\n\n![Demo Gif](https://user-images.githubusercontent.com/153700/81898094-d58cc300-956c-11ea-90eb-f8ce5561f63d.gif?raw=true \"Modal Screenshot\")\n\n## 🚀 Quick Start\n\n### Option 1: Using Docker (Recommended - No Dependencies Required)\n\nDocker is the easiest way to run Fussel without installing any dependencies locally.\n\n#### Using Docker Compose (Easiest)\n\n1. **Set your paths** using environment variables:\n   ```bash\n   export INPUT_DIR=/absolute/path/to/your/photos\n   export OUTPUT_DIR=/absolute/path/to/output\n   docker-compose up\n   ```\n\n   Or edit `docker-compose.yml` directly and replace the path placeholders.\n\n\n2. **Optional: Customize settings** via environment variables or `.env` file:\n   ```bash\n   # Example .env file (or export these before running docker-compose)\n   INPUT_DIR=/home/user/photos\n   OUTPUT_DIR=/home/user/gallery-output\n   PUID=1000              # Your user ID (run 'id -u' to find it)\n   PGID=1000              # Your group ID (run 'id -g' to find it)\n   PARALLEL_TASKS=4\n   FACE_TAG_ENABLE=True\n   WATERMARK_ENABLE=True\n   ```\n\n3. **After generation completes**, your gallery will be in the `OUTPUT_DIR` you specified. You can preview it locally or upload it to a web host.\n\n#### Using Docker Run\n\n```bash\ndocker run \\\n  -v \u003cinput-dir\u003e:/input:ro \\\n  -v \u003coutput-dir\u003e:/output \\\n  -e PGID=$(id -g) \\\n  -e PUID=$(id -u) \\\n  -e INPUT_PATH=\"/input\" \\\n  -e OUTPUT_PATH=\"/output\" \\\n  -e PARALLEL_TASKS=\"4\" \\\n  ghcr.io/cbenning/fussel:latest\n```\n\n**Notes:**\n- Replace `\u003cinput-dir\u003e` and `\u003coutput-dir\u003e` with absolute paths to your directories\n- The `PGID` and `PUID` environment variables set the output folder permissions to match your user, preventing root-owned files\n- After the container completes, your generated gallery will be in `\u003coutput-dir\u003e`\n\nSee [Docker Configuration](#-docker-configuration) below for all available options.\n\n### Option 2: Local Installation\n\nIf you prefer not to use Docker or want to develop Fussel, you can install it locally.\n\n#### Prerequisites\n\n- **Python** 3.10+\n- **uv** - Python package manager ([install](https://docs.astral.sh/uv/getting-started/installation/))\n- **Node.js** v18+ (LTS recommended)\n- **Yarn** 1.22+ (required)\n- **Make** (optional, but recommended for easier setup)\n\n#### Installation Steps\n\n1. **Clone the repository:**\n   ```bash\n   git clone https://github.com/cbenning/fussel.git\n   cd fussel\n   ```\n\n2. **Install dependencies:**\n   ```bash\n   make install\n   ```\n\n3. **Configure Fussel:**\n   ```bash\n   cp sample_config.yml config.yml\n   ```\n\n   Edit `config.yml` and set at minimum:\n   - `gallery.input_path` - Path to your photos directory\n   - `gallery.output_path` - Where to generate the site (default: `site/`)\n\n4. **Generate your gallery:**\n   ```bash\n   make generate\n   ```\n\n5. **Preview your site:**\n   ```bash\n   make serve\n   ```\n\n   Then visit `http://localhost:8000` in your browser.\n\n## 📁 Organizing Your Photos\n\nYour photo directory structure determines your album structure. Each subfolder becomes an album. This section explains how to organize your photos before generating your gallery.\n\n### Directory Structure\n\nPoint `gallery.input_path` to a directory containing subfolders, where each subfolder name becomes an album name:\n\n```\n/home/user/Photos/gallery/\n├── Album 1/\n│   ├── photo1.jpg\n│   └── photo2.jpg\n├── Album 2/\n│   ├── Sub Album 1/\n│   │   └── photo3.jpg\n│   └── photo4.jpg\n└── Album 3/\n    └── Sub Album 2/\n        └── photo5.jpg\n```\n\n### Supported Image Formats\n\nFussel supports common image formats:\n- JPEG (`.jpg`, `.jpeg`)\n- PNG (`.png`)\n- GIF (`.gif`)\n\n## ⚙️ Configuration\n\nThe `config.yml` file (or Docker environment variables) controls all aspects of gallery generation.\n\n### Gallery Settings\n\n```yaml\ngallery:\n  input_path: \"/path/to/photos\"      # Required: Your photos directory\n  output_path: \"site/\"               # Where to generate the site\n  overwrite: False                   # Force rebuild all photos\n  parallel_tasks: 4                  # Parallel processing workers\n  exif_transpose: False              # Use EXIF rotation data\n  allow_download: True               # Allow downloading original photos\n```\n\n### Album Settings\n\n```yaml\ngallery:\n  albums:\n    enable: True                     # Show Albums navigation button\n    recursive: True                  # Process subfolders as albums\n    recursive_name_pattern: \"{parent_album} \u003e {album}\"  # Sub-album naming\n```\n\n### Photos Settings\n\n```yaml\ngallery:\n  photos:\n    enable: True                     # Show Photos navigation button (all photos view)\n    sort_by: \"date\"                  # Default sort: 'date' or 'filename'\n    sort_order: \"desc\"               # Default order: 'asc' or 'desc'\n```\n\n### People/Face Detection\n\n```yaml\ngallery:\n  people:\n    enable: True                     # Enable face detection from XMP tags\n```\n\n### Watermark Settings\n\n```yaml\ngallery:\n  watermark:\n    enable: True                     # Enable watermarks\n    path: \"web/src/images/fussel-watermark.png\"  # Watermark image\n    size_ratio: 0.3                  # Watermark size (0.0-1.0)\n```\n\n### Site Settings\n\n```yaml\nsite:\n  http_root: \"/\"                     # URL root (include trailing slash)\n  title: \"Fussel Gallery\"            # Browser tab title\n```\n\n## 🐳 Docker Configuration\n\nThis section provides detailed information about Docker configuration options. For a quick start, see the [Docker Quick Start](#option-1-using-docker-recommended---no-dependencies-required) section above.\n\n### Available Environment Variables\n\nSee `docker/template_config.yml` for all available configuration options. Key variables:\n\n- `INPUT_PATH` - Path to input photos (inside container)\n- `OUTPUT_PATH` - Path to output directory (inside container)\n- `PARALLEL_TASKS` - Number of parallel workers (default: 1)\n- `OVERWRITE` - Force rebuild of all photos (default: False)\n- `EXIF_TRANSPOSE` - Use EXIF data for rotation (default: False)\n- `ALLOW_DOWNLOAD` - Allow downloading original photos (default: True)\n- `FACE_TAG_ENABLE` - Enable face detection (default: True)\n- `WATERMARK_ENABLE` - Enable watermarks (default: True)\n- `SITE_TITLE` - Gallery title (default: \"Fussel Gallery\")\n- `SITE_ROOT` - HTTP root path (default: \"/\")\n\n### Complete Docker Run Example\n\nFor advanced users who want to customize all options:\n\n```bash\ndocker run \\\n  -v \u003cinput-dir\u003e:/input:ro \\\n  -v \u003coutput-dir\u003e:/output \\\n  -e PGID=$(id -g) \\\n  -e PUID=$(id -u) \\\n  -e INPUT_PATH=\"/input\" \\\n  -e OUTPUT_PATH=\"/output\" \\\n  -e PARALLEL_TASKS=\"4\" \\\n  -e OVERWRITE=\"False\" \\\n  -e EXIF_TRANSPOSE=\"False\" \\\n  -e ALLOW_DOWNLOAD=\"True\" \\\n  -e RECURSIVE=\"True\" \\\n  -e RECURSIVE_NAME_PATTERN=\"{parent_album} \u003e {album}\" \\\n  -e FACE_TAG_ENABLE=\"True\" \\\n  -e WATERMARK_ENABLE=\"True\" \\\n  -e WATERMARK_PATH=\"web/src/images/fussel-watermark.png\" \\\n  -e WATERMARK_SIZE_RATIO=\"0.3\" \\\n  -e SITE_ROOT=\"/\" \\\n  -e SITE_TITLE=\"Fussel Gallery\" \\\n  ghcr.io/cbenning/fussel:latest\n```\n\n## 🌐 Hosting Your Gallery\n\nOnce generated, your gallery is a static site. You can host it anywhere:\n\n1. **Upload to any web host** - Copy the contents of `gallery.output_path` to your web server's document root\n2. **Use GitHub Pages** - Push the output directory to a GitHub repository and enable Pages\n3. **Use a CDN** - Upload to services like Netlify, Vercel, or Cloudflare Pages\n4. **Local preview** - Use `make serve` or Python's built-in server:\n   ```bash\n   make serve\n   ```\n\n   Or manually:\n   ```bash\n   python -m http.server --directory \u003coutput_path\u003e\n   ```\n\n## 🛠️ Development\n\n### Development Mode\n\nRun the web app in development/watch mode with hot reload:\n\n```bash\nmake dev\n```\n\nOr manually:\n```bash\ncd fussel/web \u0026\u0026 yarn start\n```\n\n### Running Tests\n\n```bash\nmake test\n```\n\nThis runs:\n- Python tests via pytest with coverage (output in `htmlcov/`)\n- JavaScript tests via Vitest (`cd fussel/web \u0026\u0026 yarn test`)\n\n### Code Formatting \u0026 Linting\n\n```bash\nmake fmt     # Auto-format Python with ruff\nmake lint    # Check Python formatting without changes\n```\n\n### Project Structure\n\n```\nfussel/\n├── fussel/              # Main Python package\n│   ├── generator/       # Gallery generation logic\n│   └── web/             # Vite/React frontend\n│       ├── src/\n│       │   └── component/  # React components + tests\n│       └── vite.config.js\n├── tests/               # Python test suite\n├── docker/              # Docker configuration\n├── config.yml           # Your configuration (not in git)\n└── sample_config.yml    # Configuration template\n```\n\n## ⬆️ Migrating from v2 to v3\n\nv3 introduces new features and updated tooling. The steps below cover everything you need to do after pulling v3.\n\n### 1. Update Python dependencies\n\nv3 uses [uv](https://docs.astral.sh/uv/getting-started/installation/) instead of pip. Install it, then:\n\n```bash\nmake install\n```\n\nIf you previously had a `venv/` or `.venv/`, remove it first — uv manages its own `.venv`.\n\n### 2. Update JavaScript dependencies\n\nThe build toolchain has changed from `react-scripts` (Create React App) to Vite. A fresh install is required:\n\n```bash\ncd fussel/web\nrm -rf node_modules\nyarn install\n```\n\n### 3. Update your `config.yml`\n\nSeveral new configuration keys are available in v3. Add any you want to use — all are optional and have sensible defaults:\n\n```yaml\ngallery:\n  allow_download: True       # NEW: allow/prevent original photo downloads\n\n  albums:\n    enable: True             # NEW: show/hide Albums navigation button\n\n  photos:                    # NEW section: all-photos view\n    enable: True\n    sort_by: \"date\"\n    sort_order: \"desc\"\n```\n\nCopy from `sample_config.yml` for the full reference.\n\n### 4. Regenerate your gallery\n\n```bash\nmake generate\n```\n\nYour existing `output_path` will be updated in-place.\n\n### Breaking changes summary\n\n| Area | v2 | v3 |\n|------|----|----|\n| Python package manager | pip / requirements.txt | uv / pyproject.toml |\n| JS build tool | react-scripts (CRA) | Vite |\n| JS test runner | Jest | Vitest |\n| `massedit` dependency | required | removed |\n| Python version | 3.8+ | 3.9+ |\n\n## ❓ FAQ\n\n### How do I update Fussel?\n\nIf installed via `make install`:\n```bash\ngit pull\nmake install\n```\n\nIf using Docker:\n```bash\ndocker pull ghcr.io/cbenning/fussel:latest\n```\n\n### Can I customize the gallery appearance?\n\nThe gallery uses a React-based frontend built with Vite. You can modify styles and components in `fussel/web/src/` and rebuild with `make generate` or `cd fussel/web \u0026\u0026 yarn build`.\n\n### Does Fussel modify my original photos?\n\nNo. Fussel only reads from your input directory and writes to your output directory. Your original photos are never modified.\n\n### How does the EXIF info panel work?\n\nWhen viewing a photo in the modal, click the **ⓘ** button in the toolbar to open the info panel. It displays camera make/model, lens, shot settings (exposure, aperture, ISO, focal length), and GPS coordinates if present in the photo's EXIF data.\n\n### Why don't some photos show EXIF data?\n\nEXIF data must be embedded in the photo file. Some tools strip EXIF on export (e.g. certain social media downloads, some editors). Photos taken with a smartphone or dedicated camera typically have full EXIF data.\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## 🙏 Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## 🔗 Links\n\n- [Demo Site](https://benninger.ca/fussel-demo/)\n- [GitHub Repository](https://github.com/cbenning/fussel)\n- [Issue Tracker](https://github.com/cbenning/fussel/issues)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcbenning%2Ffussel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcbenning%2Ffussel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcbenning%2Ffussel/lists"}