# PhotoShuffle
[](https://hex.pm/packages/photo_shuffle)
[](https://hexdocs.pm/photo_shuffle/)
[](https://github.com/unaffiliatedstudios/photo_shuffle/blob/main/LICENSE)
A lightweight Elixir library for managing and shuffling image collections with intelligent metadata generation. Perfect for portfolio sites, galleries, and any application that needs randomized image displays.
## Features
- 🎲 **Random Shuffling** - Intelligently shuffle and select N images from collections
- 📝 **Smart Titles** - Automatically generate human-readable titles from filenames
- 📍 **Deterministic Locations** - Consistent location mapping via path hashing
- 🔌 **Pluggable File Systems** - Behaviour-based design for easy mocking and testing
- ⚡ **Zero Runtime Dependencies** - Lightweight with no external runtime deps
- 📚 **Comprehensive Documentation** - Full ExDoc coverage with examples
## Installation
Add `photo_shuffle` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:photo_shuffle, "~> 0.1.0"}
]
end
```
## Quick Start
```elixir
# Process images from a directory
{:ok, images} = PhotoShuffle.process_images("/path/to/images", "Windows")
# Shuffle and select 6 random images
selected = PhotoShuffle.shuffle_images(images, 6)
# Generate a title from a filename
title = PhotoShuffle.generate_title("IMG_5366.jpg")
# => "Professional Installation Project"
# Get deterministic location for an image
location = PhotoShuffle.smart_location("/path/to/IMG_5366.jpg", "Windows")
# => "Denver"
```
## Usage in Phoenix LiveView
```elixir
defmodule MyAppWeb.GalleryLive do
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
# Load and shuffle images
path = Application.app_dir(:my_app, "priv/static/images/gallery")
{:ok, all_images} = PhotoShuffle.process_images(path, "Gallery")
displayed_images = PhotoShuffle.shuffle_images(all_images, 8)
{:ok, assign(socket, images: displayed_images, all_images: all_images)}
end
def handle_event("refresh_gallery", _params, socket) do
# Shuffle again on button click
new_images = PhotoShuffle.shuffle_images(socket.assigns.all_images, 8)
{:noreply, assign(socket, images: new_images)}
end
def render(assigns) do
~H"""
<div class="grid grid-cols-3 gap-4">
<%= for image <- @images do %>
<div class="relative">
<img src={image.src} alt={image.title} />
<p>{image.title} - {image.location}</p>
</div>
<% end %>
</div>
<button phx-click="refresh_gallery">Shuffle Gallery</button>
"""
end
end
```
## Configuration
```elixir
# config/config.exs
config :photo_shuffle,
file_system: PhotoShuffle.FileSystem.Local,
locations: ["Denver", "Aurora", "Boulder", "Fort Collins"]
# config/test.exs
config :photo_shuffle,
file_system: MyApp.PhotoShuffleFilesystemMock
```
## Custom URL Builder
For custom path transformations:
```elixir
# Custom URL builder that strips "priv/static/" prefix
url_builder = fn path, _base_url ->
case String.split(path, "priv/static/", parts: 2) do
[_prefix, relative] -> "/" <> relative
_ -> "/" <> Path.basename(path)
end
end
{:ok, images} = PhotoShuffle.process_images(
"/app/priv/static/images",
"Portfolio",
url_builder: url_builder
)
```
## Testing with Mox
```elixir
# test/test_helper.exs
Mox.defmock(MyApp.FileSystemMock, for: PhotoShuffle.FileSystemBehaviour)
# test/my_module_test.exs
test "processes images" do
Mox.expect(MyApp.FileSystemMock, :list_images, fn _path ->
{:ok, ["/path/img1.jpg", "/path/img2.jpg"]}
end)
Mox.expect(MyApp.FileSystemMock, :basename, 2, fn path ->
Path.basename(path)
end)
{:ok, images} = MyModule.load_images()
assert length(images) == 2
end
```
## API Documentation
### Core Functions
- `PhotoShuffle.process_images/3` - Load and process images from a directory
- `PhotoShuffle.shuffle_images/2` - Randomly shuffle and select N images
- `PhotoShuffle.generate_title/1` - Generate human-readable titles
- `PhotoShuffle.smart_location/2` - Get deterministic locations
- `PhotoShuffle.hash_path/1` - Create consistent hashes for paths
See [HexDocs](https://hexdocs.pm/photo_shuffle) for full API documentation.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Run tests (`mix test`)
4. Run code quality checks (`mix credo`, `mix dialyzer`)
5. Commit your changes (`git commit -m 'Add amazing feature'`)
6. Push to the branch (`git push origin feature/amazing-feature`)
7. Open a Pull Request
## License
This project is licensed under the MIT License - see the LICENSE file for details.
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for version history.