Photo to Flashcards (Gemini AI Vision)

AnkiWeb addon 2021931128

Converts photos of textbook pages, worksheets, and handwritten notes into flashcards using Google's Gemini vision models and a user-supplied API key, with review, duplicate checks, and one-click undo.
AI-generated summary; may contain mistakes.

card-creationaiduplicates

Open on AnkiWeb GitHub Ask about alternatives

AnkiWeb

Rating
1 (πŸ‘ 1 Β· πŸ‘Ž 0)
Updated
2026-09-28
Anki versions
2.1.50~
Description language
en

Maintenance

active

  • Last update or commit was 3 days before the snapshot (2026-09-28).
  • The repository has 3 test files.
  • The repository has 2 GitHub Actions workflows.

Will it work on my Anki?

Version branches
Min AnkiMax AnkiUpdated
2.1.50+2026-09-28
History across monthly snapshots

Loading…

Similar addons

Anki Gemini Vision (AI Image to Text)

Extracts text, KaTeX math, and structured lists from right-clicked images in Anki using Gemini 2.5 Flash, with API key and proxy configuration.

card-creationai

Rating 0 πŸ‘ 0 Β· πŸ‘Ž 0 Anki 23.12.1~ Updated 2026-02-25
Ashki

Adds an AI chat side panel to Anki for card help, note generation, flashcard creation, voice Q&A, and customizable themes, using your own API key privately.

card-creationai

Rating 0 πŸ‘ 0 Β· πŸ‘Ž 0 Anki 2.1.1~ Updated 2026-07-13
AnkiAI-ImageAddon

Automatically attaches images and GIFs to Anki vocabulary cards using a 14-group classification pipeline with 21 image sources.

card-creationaibeta

Rating 1 πŸ‘ 0 Β· πŸ‘Ž 1 ⭐ 0 Anki 25.09.3~ Updated 2026-08-23

README

Photo to Flashcards

CI License: MIT

An Anki add-on that turns a photo of a textbook page, worksheet, or handwritten notes into flashcards, using Google's Gemini vision models.

Each user supplies their own API key. No key ships with the add-on, and there is no server in between.

Why bring-your-own-key

The obvious design β€” embed one API key in the add-on β€” fails twice over:

  1. It can't stay secret. An add-on is a zip of readable .py files. Obfuscation doesn't help: the key must exist in memory to be sent, and it goes on the wire where any proxy tool reads it.
  2. It wouldn't work anyway. Free-tier rate limits are scoped to the key. One embedded key means every user in the world shares ~15 requests/minute β€” unusable past about a dozen active users, and one abusive user kills it for everyone.

Because the free tier is per-user, asking each person for their own key gives every user their own independent quota. That scales without limit, costs the maintainer nothing, and removes the secret entirely.

If you later want a hosted option, core/provider.py already has the seam β€” see Adding a backend.

Install

From a release (recommended). Grab photo2cards.ankiaddon from the latest release and open it, or use Anki β†’ Tools β†’ Add-ons β†’ Install from file…. Restart Anki.

From source.

python build.py            # -> dist/photo2cards.ankiaddon

A .ankiaddon file is a zip with the add-on's files at the top level of the archive β€” not nested in a folder. build.py handles that, and packaging by hand usually gets it wrong.

For development, point Anki's add-ons folder at your working tree instead, so edits take effect on the next Anki restart with no rebuild. Close Anki first β€” it scans the folder at startup.

:: Windows. /J makes a junction, which needs no elevated prompt (/D would).
mklink /J "%APPDATA%\Anki2\addons21\photo2cards" "%CD%\src\photo2cards"
# macOS
ln -s "$PWD/src/photo2cards" ~/Library/Application\ Support/Anki2/addons21/photo2cards
# Linux
ln -s "$PWD/src/photo2cards" ~/.local/share/Anki2/addons21/photo2cards

Note that Anki will write meta.json β€” containing your API key β€” into the linked source folder. It's gitignored and excluded from the build, which is why both of those exist.

First run

Tools β†’ Photo to Flashcards β†’ Settings…

  1. Click Get a free API key… β€” opens Google AI Studio.
  2. Paste the key.
  3. Click Verify key & load models and pick one.
  4. Optionally set Card level: High school (the default) or University, which asks for the precise definitions, conditions and distinctions an exam at that level expects.

That verify step is a single ListModels call. It confirms the key works and discovers which model ids the key can actually reach β€” so the add-on never hardcodes a model name that has since been renamed or retired.

Use

Action Where
From image files Tools β†’ Photo to Flashcards β†’ From image file(s)…
From clipboard Ctrl+Shift+V, or the menu

Ctrl+Shift+V is bound on Anki's main window only. Inside the editor (Add, Browse) the same keys still mean Anki's own "paste without formatting".

Both open a review dialog. Nothing is written to your collection until you approve it there β€” edit fronts, backs, and tags inline, untick anything you don't want, then choose a deck and note type. Selecting a row shows the verbatim text from the page that the card came from, so you can check the model's work.

Every card is ticked to begin with, so approving the batch is one click. A row whose front or back you empty unticks itself and greys out, rather than silently vanishing when you add. Photos are not attached by default; you can toggle photo attachment for the whole batch or check the "Photo" column for specific cards (such as diagrams). Attached photos are kept inside a collapsible "Source photo" disclosure on the card so they never clutter normal review.

Duplicates

Photographing overlapping pages is normal, and so is running the same page twice, so the review step checks for cards you already have. A card's identity is its front and its back β€” the source quote and photo are provenance, not content, so the same card re-read off a second photo is still the same card.

Situation What happens
The same card twice in one batch Collapsed before you see it, keeping the later quote
Same question, different answer, in one batch Shown side by side and tinted; ticking both asks first
Already in the target deck, same answer Not added again. Its citation is refreshed if yours is newer
Already in the target deck, different answer A Duplicates detected dialog shows both backs and asks which to keep

Nothing already in your collection is overwritten unless you pick the replacement yourself. The deck check covers the deck you are adding to and its subdecks, for the note type you selected.

Everything a review writes β€” new cards and updated ones β€” lands as one undoable batch (Ctrl+Z).

Layout

src/photo2cards/       the add-on β€” this is what gets zipped
β”œβ”€ __init__.py         menu wiring only
β”œβ”€ manifest.json       package metadata
β”œβ”€ config.json         default settings
β”œβ”€ core/               NO anki/aqt imports β€” testable under plain pytest
β”‚  β”œβ”€ gemini.py        HTTP client + error mapping
β”‚  β”œβ”€ provider.py      backend selection (direct vs proxy)
β”‚  β”œβ”€ prompts.py       system prompt + response schema  ← iterate here
β”‚  β”œβ”€ models.py        Card / SourceImage / GenerationResult
β”‚  β”œβ”€ dedupe.py        when two cards count as the same card
β”‚  β”œβ”€ render.py        composes the note back, and takes it apart again
β”‚  β”œβ”€ imaging.py       downscale + JPEG encode (Qt)
β”‚  β”œβ”€ ratelimit.py     client-side throttle
β”‚  └─ errors.py        typed exceptions the UI branches on
β”œβ”€ ui/                 Qt + Anki layer
β”‚  β”œβ”€ setup.py         settings / first-run dialog
β”‚  β”œβ”€ capture.py       file picker, clipboard
β”‚  β”œβ”€ review.py        review-and-edit dialog
β”‚  β”œβ”€ dupes.py         "which version do you want" dialog
β”‚  β”œβ”€ ops.py           QueryOp / CollectionOp wrappers
β”‚  └─ store.py         config read/write
tests/                 pure-logic tests, no Anki needed
build.py               -> dist/photo2cards.ankiaddon

The core/ vs ui/ split is the load-bearing decision. Anki has no real test harness, so anything that can be verified without launching Anki must live where pytest can reach it. If a test in tests/ ever needs aqt, logic has leaked into the wrong layer.

ui/ is still checkable without clicking through Anki: its dialogs can be driven headlessly against a throwaway collection using Anki's own bundled interpreter. CLAUDE.md has the recipe, along with the constraints and Anki API quirks worth knowing before changing anything. Release history is in CHANGELOG.md.

Development

pip install pytest ruff
python -m pytest
python -m ruff check src tests build.py

CI runs both on every push and PR, plus a packaging check. The test matrix includes Python 3.9 deliberately: Anki has shipped a 3.9 interpreter across several release lines, and float | None in a function signature imports fine on 3.13 while raising TypeError on 3.9 β€” a bug that only appears once a real user loads the add-on. Ruff's FA rules catch most of that statically; the 3.9 leg catches the rest.

Two constraints worth remembering when adding code:

  • No compiled dependencies. The add-on ships as a single zip that must run on every OS and architecture Anki supports. That's why this talks to Google over raw HTTP with Anki's bundled requests instead of using the official SDK β€” the SDK's dependency tree includes platform-specific wheels.
  • Never block the main thread. Anki's UI is single-threaded; a synchronous HTTP call freezes the app. Network work goes through QueryOp, collection writes through CollectionOp.

Adding a backend later

If you decide to host a service (to remove the key-setup step, or to monetize), set backend to proxy in config and fill in ProxyProvider.generate_cards in core/provider.py. Your endpoint takes {image_b64, mime_type, deck_hint} and returns the same {"cards": [...]} shape. Nothing else changes β€” not the UI, not the call sites.

Non-negotiables if you do: per-user auth and quota enforced server-side, input size/MIME validation, and a hard monthly spend ceiling with alerting. An unauthenticated proxy is just a slower way to leak your key. You'd also become a data processor for images of other people's coursework, which wants a privacy policy first.

Known limits

  • One image per request. Very dense pages can exceed the reply limit β€” the add-on says so and suggests cropping.
  • Free-tier daily caps reset at midnight Pacific; the per-minute throttle can't help with those.
  • The API key is stored unencrypted, because Anki has no keychain integration. See config.md.

Privacy and terms

  • Direct communication: When using the direct backend, image and text content are sent directly from Anki to Google's Generative AI API endpoints using HTTPS. No intermediate servers or proxies are involved.
  • Google terms & human review: Under Google AI Studio's free-tier terms, submitted prompts and images may be reviewed by human annotators and used to train Google models. Do not submit sensitive, confidential, or personally identifiable information under the free tier.
  • Age requirements: Google's terms require users of the Gemini API to be at least 18 years old.
  • Refer to Google's Generative AI Additional Terms of Service and Google Privacy Policy for complete details.

Licence

MIT β€” see LICENSE.