GeminiService extracts multiple-choice flashcards from exam images using the Google Gemini API. It is implemented in both the Python desktop app (services/gemini_service.py, using the google-genai SDK) and the .NET desktop app (Services/GeminiService.cs, using the Gemini REST API via HttpClient). Both implementations expose identical behavior: single-image scanning, PDF-batch mode, and parallel multi-key mode with automatic rate limiting and model fallback.
The API surface documented here reflects the Python implementation. The C# version exposes the same methods in PascalCase (ProcessImage, ProcessImagesAsPdfBatches, ProcessImagesParallel, ValidateKeysParallel).
Constructor and setup
GeminiService()
Creates a new instance with an empty key list and no active log callback.
set_keys(keys, start_from=0)
Set the API keys to use for requests. Keys are round-robin rotated across calls.
List of Google Gemini API key strings. Empty strings are stripped automatically.
Index of the key to start from. Useful after
validate_keys_parallel sets _start_from to spread load evenly.set_log_callback(callback)
Register a function to receive real-time progress and status messages during scanning.
A callable that accepts a single string argument. Pass
None to disable logging. print works as a quick option.set_stop_event(event)
Provide a threading.Event that, when set, interrupts any in-progress sleeps (rate-limit waits, retry delays). This allows a UI cancel button to stop a long scan cleanly.
A
threading.Event instance. Set it from another thread to interrupt the service.Key management
validate_key(api_key) → Tuple[bool, str]
Test a single API key by sending a trivial prompt ("Say OK in one word.") to each model in MODEL_LIST until one responds or all fail.
The Gemini API key to validate.
A tuple of
(valid, message). valid is True if any model accepted the key. message indicates the working model name or the error.validate_keys_parallel(keys, on_log=None) → List[str]
Test all provided keys simultaneously in parallel threads. Dead keys are excluded from the returned list. Also sets an internal _start_from index so the first scan request is sent to the key that had the longest recovery time since validation.
Keys to test. Typically the same list passed to
set_keys.Optional callback to receive per-key validation results in real time.
Ordered list of keys that passed validation. Keys that failed or returned errors are omitted.
Scanning
process_image(image_path, max_retries=5) → Optional[Flashcard]
Send a single image to Gemini and parse the returned JSON into a Flashcard. Handles 429 rate-limit errors (waits and rotates keys), 404 model-not-found errors (falls back to next model in MODEL_LIST), and 5xx server errors.
Absolute or relative path to the image file. Supported formats:
.jpg, .jpeg, .png, .webp, .bmp.Maximum number of attempts before raising a
RuntimeError.A
Flashcard object if a question was found and parsed, or None if the image contained no question (blank page, logo, diagram only, etc.).process_images_batch(image_paths, on_progress=None, on_error=None, stop_event=None, pause_event=None) → List[Optional[Flashcard]]
Scan images one by one, inserting a request_delay pause between each call to respect rate limits. Use this mode for small sets or when PDF conversion is not desired.
Ordered list of image file paths to scan.
Called after each image with
(completed, total, card_or_none).Called on failure with
(index, image_path, error_message).When set, the loop exits after the current image finishes.
When set, the loop pauses between images until the event is cleared.
One entry per input image in the same order.
None for images where extraction failed or no question was found.process_images_as_pdf_batches(image_paths, batch_size=50, on_progress=None, on_error=None, stop_event=None, pause_event=None) → List[Optional[Flashcard]]
The recommended mode for large image sets. Images are grouped into batches of up to batch_size, merged into an in-memory PDF, and sent to Gemini as a single request using the PDF_BATCH_PROMPT. Gemini processes all pages in one call and returns a JSON array with one result per page.
Ordered list of image paths. All images in a batch are merged into one PDF in this order.
Maximum pages per PDF batch request. Defaults to
PDF_BATCH_PAGES (50).Progress callback
(completed, total, card_or_none) fired for each individual image as results come in.Error callback
(index, image_path, error_message) fired if an entire batch fails.When set, batch processing stops after the current batch completes.
When set, processing pauses between batches until cleared.
One entry per input image, in original order.
None for pages with no question or for all pages in a failed batch.process_images_parallel(image_paths, keys, batch_size=50, on_progress=None, on_error=None, stop_event=None, pause_event=None) → List[Optional[Flashcard]]
Split images across multiple API keys and process each share on a dedicated background thread. Each thread runs process_images_as_pdf_batches internally. Results are reassembled into the original image order. This mode achieves the highest throughput when multiple valid keys are available.
Full list of images to process.
API keys to parallelize across. The list is split into N roughly-equal packs where N equals the number of keys.
Sub-batch size used within each worker thread’s PDF batching.
Shared progress callback across all worker threads. Thread-safe via a lock.
Error callback fired per failed sub-batch image.
When set, all worker threads stop after their current sub-batch.
When set, workers pause between sub-batches.
Merged results in the original image order, one entry per image.
Constants
| Constant | Value | Description |
|---|---|---|
MODEL_LIST | See below | Priority-ordered list of Gemini model IDs |
SAFE_RPM | 8 | Maximum requests per minute per API key |
PDF_BATCH_PAGES | 50 | Maximum pages merged into one PDF batch request |
MODEL_LIST order:
gemini-2.5-flash(recommended stable, 2026)gemini-2.5-flash-litegemini-3-flash-previewgemini-3.1-flash-lite-previewgemini-flash-latestgemini-flash-lite-latest
request_delay property
Returns the number of seconds to wait between requests to stay within the per-key rate limit:
n is the number of configured keys. With one key this is 7.5 seconds; with four keys it is 1.875 seconds (floored to 1.0).
Usage example
process_images_as_pdf_batches is preferred over process_images_batch for any set larger than a few images. A single PDF request processes up to 50 pages simultaneously, dramatically reducing wall-clock time and total API calls.SyncService
Upload and sync extracted decks to Google Drive.
ExportService
Export decks to Quizlet-compatible text files.