No description
  • Python 81.1%
  • Dockerfile 16%
  • Shell 2.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-07-20 16:51:23 +02:00
src save progress periodically 2026-07-20 16:51:23 +02:00
.gitignore update gitignore 2026-07-18 03:27:03 +02:00
docker-compose.yml working version 2026-07-18 02:10:37 +02:00
Dockerfile working version 2026-07-18 02:10:37 +02:00
LICENSE.txt add license 2026-07-18 02:12:39 +02:00
prompt.txt check filenames 2026-07-19 22:01:51 +02:00
README.md save progress periodically 2026-07-20 16:51:23 +02:00
requirements.txt fix requirements warning for yolov8 2026-07-19 23:27:37 +02:00
run make run write logs to console too 2026-07-19 23:18:19 +02:00

docqr - Containerized CPU-Only QR Code Extractor

docqr is a containerized, CPU-optimized CLI tool built to recursively scan a directory of photos, detect and decode any text-based QR codes within them using the qreader library, and output a structured JSON report.

It is designed to run efficiently in batch mode on servers without hardware acceleration (CPU-only).


Features & Highlights

  • Robust QR Detection: Leverages the YOLOv8-powered qreader library for state-of-the-art QR code detection and decoding (even for skewed, blurry, or low-resolution images).
  • Format Agnostic: Natively supports jpg, png, and heic (via pillow-heif) along with standard formats like webp, bmp, tiff, and gif.
  • Incremental Runs (Skip Logic): By default, the tool matches absolute paths inside the container against previous runs in cache.json to skip hash computation entirely. If not matched, or if --no-check-filenames is specified, it falls back to computing and checking a BLAKE2b hash for each scanned photo. Already-processed files are skipped, saving execution time and resources.
  • Offline & Compact Build:
    • Uses the PyTorch CPU-only build to reduce image size (saves gigabytes of CUDA packages).
    • Uses OpenCV Headless (opencv-python-headless) to avoid GUI/X11 system library dependencies.
    • Pre-downloads (bakes) the default YOLOv8 model weights during build, allowing it to run entirely offline at runtime.
  • Safe & Non-Destructive: Mounts the input photo directory as read-only (ro) to ensure your pictures are never modified.

Project Structure

docqr/
├── Dockerfile                  # Debian Trixie slim build file
├── docker-compose.yml          # Services and volume mount maps
├── requirements.txt            # Python dependencies (qreader, pillow-heif, etc.)
├── README.md                   # Setup and usage guide (this file)
├── run                         # CLI entrypoint wrapper script
└── src/
    └── main.py                 # Core scanner and detection Python script

Getting Started

1. Prerequisites

Make sure you have Docker and Docker Compose installed on your machine.

2. Run the Container in the Background

Start the container using Compose. This will build the Docker image (which takes a few minutes on the first run as it compiles dependencies and bakes the model weights) and keep it running in the background:

docker compose up -d

3. Run the QR Analysis

Execute the scanner inside the running container:

docker compose exec docqr run

You can optionally disable filename-skipping and force hash computation for all files with the --no-check-filenames flag:

docker compose exec docqr run --no-check-filenames

You can also adjust how often the cache and report are saved to disk during a long run (default is every 50 images) to prevent data loss in case the process is killed:

docker compose exec docqr run -r 100

You will see live logs on stdout showing progress:

Scanning '/data/photos' recursively for supported images...
Found 4 photos matching supported extensions.
Initializing QReader detector...
[1/4] [Analyzed] /data/photos/COW13GAS/20260718_002502.heic (took 0.72s)
[2/4] [Skipped] /data/photos/valid2/20260717_030551.jpg
[3/4] [Analyzed] /data/photos/valid2/Screenshot_20260717_030810_QR & Barcode Scanner.jpg (took 0.35s)
[4/4] [Skipped] /data/photos/SKO4-multiple/20260718_004223.jpg
------------------------------------------------------------
Execution complete.
Total photos found: 4
Analyzed:           2
Skipped:            2
Total time taken:   1.08s
Report written to:  /data/output/report.json
------------------------------------------------------------

4. Stopping the Container

If you want to stop the background service, run:

docker compose down

Configuration

To point the tool to your own photos, modify the docker-compose.yml file's volume mounts:

volumes:
  # Change "./test-data" to point to your local photo folder (e.g., "/home/user/my-photos")
  - /home/user/my-photos:/data/photos:ro
  - ./output:/data/output

Output Files

Inside the ./output/ directory, you will find two files:

1. report.json

A pretty-printed report of the current run, detailing every image's container path, status (analyzed, skipped, or failed), and decoded QR contents.

[
    {
        "path": "/data/photos/COW13GAS/20260718_002502.heic",
        "status": "analyzed",
        "qr_content": [
            "https://example.com/item-123"
        ]
    },
    {
        "path": "/data/photos/valid2/20260717_030551.jpg",
        "status": "skipped",
        "qr_content": [
            "Sample QR Code Text"
        ]
    }
]

2. cache.json

A persistent file mapping image BLAKE2b hashes to their decoded QR content. Do not delete this file if you wish to keep skipping already processed files.

  • Filename checking (default): Matches the path of the file against the "path" field of cache entries. If a match is found, it skips hash computation entirely.
  • Hash checking fallback (or when --no-check-filenames is active): If the path is not found in the cache or filename checking is disabled, the tool computes the BLAKE2b hash. If the hash exists in the cache, the tool detects it (e.g., if a file is renamed or moved), updates its path in the cache, and skips re-analysis.

Test files

Download a set of 16 test photos at https://vitapavlik.cz/hiddenfiles/docqr-test-data-2026-07-18.zip

Disclaimer

This project was entirely vibe-coded with Gemini 3.5 Flash (high thinking effort setting). See prompt.txt.