This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
Meet Meera. She's an imaginary photographer (but 100% inspired by real photographers). Let's take a peek into her imaginary life (which is also 100% inspired by real photographers).
It's Saturday, close to midnight, and the wedding couple have just left in a car full of flowers. Meera drives home with about 2,500 photos on her memory cards. She has promised the couple a sneak peek by Tuesday, and before she can edit a single frame she has to look at every one of them and decide which one gets to grand finale of the photo album.
Photographers call that culling, and it's the slow part. During the ring exchange Meera held the shutter down, so now there are nine shots that look almost the same. Only one of them has nobody blinking. The ceremony runs into the portraits with no clear break. Somewhere in those 2,500 files is the one photo of the bride's grandmother laughing, and Meera knows the bride will ask for it.
AI culling tools exist, but Meera wants a workflow with no account, no recurring AI bill and no photo uploads. If a tool sends images to a server, someone else's computer sees the wedding.
A tool that does the boring part fast, with no account, no upload, and an AI the photographer actually owns, sounded like a myth. So I built MythiCull.
MythiCull runs on the photographer's own computer. It uses an open model, EmbeddingGemma 2, which is downloaded once. After that, it works with the wifi switched off.
What I Built
Meera opens MythiCull and selects the folder containing Saturday's photos. MythiCull imports the photos and creates time-based chapters. Meera can browse and rate immediately. The app offers a one-time model download, which she can defer. When she clicks Analyze photos, it creates image embeddings and caches them, skipping completed batches when analysis resumes after an interruption.
The workflow brings together four things:
It splits the day into chapters. A gap longer than 15 minutes starts a new chapter by default; chapters with fewer than three photos merge into a neighbour. These are time-based sections, rather than automatic detection of each wedding activity. After analysis, MythiCull also suggests a name for each chapter, like "Ceremony" or "First dance". Meera can take the name with one click or type her own, like "Grandpa's toast".
It groups bursts. Consecutive photos no more than 10 seconds apart that have similar embeddings join the same group within a chapter. Because consecutive matches can chain together, a group can span more than 10 seconds. In Suggested view, Meera sees one photo per group with a small badge showing how many are hiding behind it. Those nine ring shots become a single choice, and she can click the badge if she wants to check the other eight.
It suggests a frame. Without a description, the suggestion is the sharpest photo in the group. This is not a blink or facial-expression detector. If Meera cares more about feeling than focus, she can enter
candid laughterunder What to look for and adjust Suggested photo priority between Sharper photo and Closer match. The pick then moves towards the photo that matches her words best.It lets her search in plain English. She types
grandmother laughing, and the closest matches come first. No tags, and no one had to label anything.
Then Meera culls with the keyboard, which is how most photographers already work. P keeps a photo, X rejects it, 0 to 5 sets stars, the arrow keys move, and Ctrl/Cmd+Z undoes a mistake. Visible preview controls offer the same decisions and stars. Z switches to cached-preview pixels for inspection, and Pin reference holds another frame beside the current photo for comparison. The preview is capped at 1600 pixels on its long edge; this is not original-resolution RAW inspection.
When she's done, she has two ways out. MythiCull can write XMP sidecars next to her RAW files, for a later editing workflow. Keeps get a Green label and at least one star; rejects get Red and zero stars. XMP export covers RAW files other than DNG, excludes JPEGs and stars-only decisions, and has not yet been validated in Lightroom Classic or darktable. Or it can copy the keepers into a new folder, with one subfolder per chapter if she wants. Copies retain their original metadata; stars are recorded in a separate manifest. Both exports cover the whole project, regardless of the current chapter, search or filter.
It never touches the originals
Wedding photos can't be reshot. So MythiCull treats them carefully:
- It never moves, renames, edits or deletes a photo. The only file it writes in the photo folder is an
.xmpsidecar. - If a sidecar is already there and MythiCull didn't make it, MythiCull skips that photo and says so in the summary.
- When copying, it never overwrites anything. If a file name is taken, the copy gets the next available numeric suffix, starting at
_1. - Meera's choices are safe too. Importing again or indexing again never changes a keep, a reject, a star, or a chapter name she typed.
How Is It Different?
The big tools are good, and they're more polished than MythiCull is today. The real difference is who owns the AI. With MythiCull, the model is a folder on the photographer's disk, the code is open, and there's no account that can be locked or cancelled.
| Feature | Photo Mechanic | Aftershoot | Narrative Select | MythiCull |
|---|---|---|---|---|
| AI suggests the best photos | β | β | β | β |
| Groups near-identical bursts | β | β | β | β |
| Splits a shoot into time-based chapters | β | β | β | β |
| Search photos in plain English | β | β | β | β |
| Choose picks using a typed mood | β | β | β | β |
| Keyboard-driven culling | β | β | β | β |
| Writes XMP sidecars for RAW photos | β | β | β | β |
| Exports keepers into chapter-based folders | β | β | β | β |
| Processes photographs locally, offline | β | β | β | β |
| Runs without a user account | β | β | β | β |
| Open-source application | β | β | β | β |
| Uses locally inspectable open AI weights | β | β | β | β |
NOTE: This comparison is based on publicly documented features as of October 2026, not hands-on testing of every tool. β indicates a documented feature; β means the feature wasn't found in the documentation reviewed. MythiCull is still experimental, and some features await real-world validation.
Who Would Pay for It?
Photographers like Meera. Solo wedding and event photographers shoot thousands of photos per wedding, often work alone, and do most of their culling late at night. Every hour spent culling is an hour they don't spend editing, replying to couples, or sleeping.
They already pay for this problem, either with their evenings or with a monthly subscription.
MythiCull runs entirely on the photographer's computer. No servers to maintain, no per-photo API bills. That means it doesn't need a subscription.
MythiCull is free and open-source today. But imagine a polished, ready-to-install version that a photographer could buy for $49, just once, and keep for good. No monthly subscriptions, no per-photo fees.
Why $49? It's a price I think could make sense for solo photographers. Low enough to be approachable, while giving me a way to support continued development. And if it saves even a few hours of culling, that one-time cost could easily be worth it.
Demo
To run it yourself (Python 3.11+):
git clone https://github.com/ru-das/mythicull && cd mythicull
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
uvicorn mythicull.main:app --port 8000
Open http://localhost:8000. Accept Download model on startup, or choose Not now and return to it when you click Analyze photos. Create a project with Choose folderβ¦, import the shoot, then analyze it. After the one-time download, the app works offline. A GPU makes indexing much faster, and you can start culling before indexing finishes.
Code
MythiCull
Local-first photo culling for wedding and event photographers. Import a shoot, let it index the photos, then cull with the keyboard. Picks export as XMP sidecars or copied files.
What it does
- Imports a folder recursively. JPEG, and RAW through the embedded preview. RAW+JPEG pairs are treated as one photo.
- Splits the shoot into story chapters by time gap (default 15 minutes). Chapters with fewer than three photos merge into a neighbour. Chapters can be renamed; renames survive re-runs. After analysis, you can accept suggested chapter names.
- Groups near-duplicate bursts (within 10 s and similar image embedding) and suggests the sharpest frame in each group. The optional βWhat to look forβ description (for example βcandid laughterβ) and βSuggested photo priorityβ slider balance sharpness with how well each frame matches the description.
- Per-camera time adjustments for shoots with multiple camera models, without changing the original files or EXIF timestamps.
- Plain-Englishβ¦
It's MIT licensed and was built during the challenge. The current suite passes 82 tests, with one real-model test deselected by default. The default tests run in seconds because they use a small fake model instead of the real one.
How I Built It
Here's the path a photo takes through MythiCull:
the photo folder (never changed)
β find JPEGs and RAWs (a RAW+JPEG pair counts as one photo)
β read the time each photo was taken
β make small previews
β split on gaps longer than 15 minutes; merge chapters under 3 photos
β EmbeddingGemma 2 turns each photo into a list of 768 numbers
β photos β€ 10 s apart with very similar numbers β one burst group
β the photographer culls β choices saved in SQLite β XMP sidecars or copied files
The back end is Python, FastAPI and SQLite. The front end is plain HTML, CSS and JavaScript with no build step. Even the fonts ship with the app, so the page never reaches out to the internet.
One model, four jobs
EmbeddingGemma 2 turns photos and words into numbers that live in the same space. That means a photo of a first dance ends up close to the words "first dance". This one idea does all the work:
| Job | What gets compared |
|---|---|
| Burst groups | each photo with the one taken just before it |
| Search | the typed words with every photo |
| Mood pick | the typed mood with each photo inside one burst |
| Chapter names | the whole chapter with 18 wedding and event labels, like "Ceremony" and "Cake cutting" |
Search is one line of math. A few thousand photos don't need a special database:
q = embedder.encode_text([query])[0]
scores = matrix[keep] @ q # vectors are unit length, so this is cosine similarity
order = np.argsort(-scores, kind="stable")[:k]
Scores are for ranking, not for trusting
The most important thing I learned about this model is that a words-to-photo score only means something next to other scores for the same words. A 0.3 for "rings" isn't the same as a 0.3 for "cake". So MythiCull never tells Meera "80% match". It only puts photos in order.
The mood pick follows the same rule. Inside a burst, it ranks the photos once by sharpness and once by mood, and then blends the two rankings rather than the raw scores:
sharp_rank = {p["id"]: r for r, p in enumerate(sorted(g, key=lambda p: -sharp(p)))}
vibe_rank = {p["id"]: r for r, p in enumerate(sorted(g, key=lambda p: -score(p)))}
return min(g, key=lambda p: (1 - weight) * sharp_rank[p["id"]] + weight * vibe_rank[p["id"]])
Photo-to-photo scores are much steadier, so bursts can use a fixed cutoff. I tested it on 354 public HDR+ frames from 40 exposure-bracket bursts, with synthetic capture times. Photos from the same burst scored between 0.973 and 0.999, while photos from different moments scored between 0.54 and 0.68. Cutoffs from 0.85 to 0.94 produced 39 groups against 40 source bursts, and MythiCull uses 0.92. These were easy bracketed frames, not a moving-subject wedding shoot; the threshold still needs validation on that kind of material.
Small things that matter
- No float16. It can break this model's numbers, so MythiCull uses float32 on a CPU and bfloat16 on a supported GPU, falling back to float32 when needed.
- Indexing can stop and restart. Progress is saved after every batch. If Meera's laptop dies halfway through, it picks up where it left off.
- Nobody waits for the AI. Culling can start right after import. Time-based chapters are available after import; analysis adds similarity groups and suggested names, while search uses the indexed photos.
- Two cameras, one timeline. If a second shooter's camera clock is a few minutes off, Meera can set an offset for that camera model and the day lines up again. The original photo time stays untouched. Two bodies of the same model currently share one offset.
Why Does Open Innovation Matter?
It's someone's wedding. These photos show families, kids, and guests who never signed up for an AI service. With MythiCull nothing is uploaded, there's no account, and there's no tracking. The photos stay with the person the couple trusted.
It works where photographers are. Meera can cull on the train home or in a hotel with bad wifi, and she never waits for an upload bar.
It keeps the price fair. A hosted model charges for every photo it looks at, which adds an ongoing operating cost. An open model on the photographer's own computer only costs electricity.
No one can take it away. The model is a folder on the photographer's disk. If a company raises its prices or shuts a model down, MythiCull keeps working the same way it did on day one.
I could control every step. With open weights I could pick the right number format for each kind of device, use the model's own search prompt, and tune the burst cutoff myself.
Where a closed model would win: a big hosted vision model might name chapters better, and it might judge facial expressions better too. But it would put an upload, an account and a bill between a photographer and their own work. For a tool that sees every photo of someone's wedding day, I don't think that trade is worth it.
What's Next
- Validate the XMP sidecars in Lightroom Classic and darktable.
- A one-click installer, so a photographer never has to open a terminal.
- Validate burst suggestions and description-based picks on a real moving-subject shoot, and time an actual photographer's cull.
Prize Categories
- Best Use of Gemma: EmbeddingGemma 2 is the only model in MythiCull, and it does four jobs: burst groups, plain-English search, the mood pick, and chapter names. It runs on the photographer's own computer (float32 on a CPU, bfloat16 on a supported GPU, with float32 fallback), is downloaded once, and then works offline.
If you know a wedding photographer, or you are one, I'd love to hear what it would take to trust a tool like this with a real cull.



Top comments (0)