Skip to main content

Encode and decode files via QR code video streams using LT fountain codes

Project description

QRStream

中文文档

Transfer arbitrary files through QR code video streams. Built on LT Fountain Codes (Luby Transform) for reliable, feedback-free data transmission — the original file can be fully recovered even if some frames are lost.

How It Works

Encoder                                     Decoder
┌──────────┐   LT Fountain    ┌──────────┐   Screen cap   ┌──────────┐   QR detect    ┌──────────┐
│   File    │ ────────────── → │ QR Video │ ──────────── → │  Video   │ ────────────→ │ Recovered│
└──────────┘   zlib + base45  └──────────┘                └──────────┘   LT decode    │   File   │
                                                                                       └──────────┘
  1. Encode: Split the file (optionally zlib-compressed) into blocks, generate redundant coded blocks via LT fountain codes, serialize each into a V3 protocol frame, base45-encode into QR alphanumeric-mode symbols, and output an MP4 video.
  2. Decode: Extract QR codes from video using zxing-cpp (fast, robust, crash-free), base45-decode, CRC32-validate to discard corrupted frames, feed into the LT decoder for belief propagation (peeling), and reconstruct the original file. Legacy base64 and COBS videos (pre-v0.6) continue to decode via a fallback path.

Key Features:

  • LT Fountain Codes: Rateless erasure codes — naturally tolerant of frame loss, blur, and occlusion
  • Base45 + QR Alphanumeric Mode: RFC 9285 base45 packs data into QR alphanumeric mode (5.5 bits/char vs 8 for byte mode); smaller and faster than base64 at the same QR version
  • zxing-cpp Detector: Native C++ QR detector (replaces WeChatQRCode since v0.9) — releases the GIL for true parallel detection, reentrant and crash-free on noisy inputs, 4–10× faster with equivalent detection rate
  • Adaptive Sample Rate: Automatically selects optimal sampling strategy based on detection rate and frame repetition
  • Targeted Recovery: After initial scan, precisely re-scans video segments where missing blocks are expected
  • Low-Memory Paths: mmap-backed encoding and streaming decode-to-file for large inputs

Installation

From PyPI with pip

pip install qrstream

Use either command after installation:

qrstream <command> [options]
# or
qrs <command> [options]

You can also run it as a module:

python -m qrstream <command> [options]

From PyPI with uv

uv tool install qrstream

Then run:

qrstream <command> [options]

For one-off execution without a persistent install:

uvx qrstream <command> [options]

Development Install

git clone https://github.com/ddddavid-he/qrstream-enhanced.git && cd qrstream-enhanced
uv sync --dev

Requirements

  • Python >= 3.10 (3.10 – 3.14 tested)
  • Dependencies: opencv-contrib-python, numpy, rich, zxing-cpp

Usage

qrstream <command> [options]
qrstream -V
qrstream --version

qrs is kept as a short alias, and python -m qrstream works as well.

Encode (File → QR Video)

qrstream encode <file> -o output.mp4 [options]
Option Default Description
<file> - Input file path
-o, --output required Output video path
--overhead 2.0 Encoding redundancy ratio (multiple of source block count)
--fps 10 Output video frame rate
--ec-level 1 Deprecated (hidden since v0.9, will be removed in v0.10.0): QR error correction level. Redundant — LT --overhead already handles frame loss. Existing scripts continue to work but should stop using this option.
--qr-version 25 QR code version 1-40 (higher = denser)
--border standard 4-module quiet zone Quiet-zone width as a percentage of QR content width (--border 10 = 10%, --border 0 disables it)
--lead-in-seconds 0.0 Insert white lead-in frames before the first QR frame
--no-compress - Disable zlib compression
--force-compress - Force compression for large V3 inputs (higher memory usage)
--qr-mode alphanumeric QR payload encoding: alphanumeric (base45, default, denser) or base64 (byte mode, fallback)
--legacy-qr - Accepted but ignored (kept for CLI backward compatibility)
--codec h264 Video codec: h264 (default, good compression), mp4v, or mjpeg (faster encode, larger files)
-w, --workers min(CPU count, 4) Parallel worker threads for QR generation. The auto-picked default is capped at 4 because, although QR matrix generation (zxingcpp.create_barcode()) is native C++ (GIL-free), the full pipeline is typically video-writer-bound. Pass a larger value explicitly to override the cap on CPU-rich machines if profiling shows benefit.
--output-mode auto Progress/status rendering: auto (Rich interactive on TTY, log otherwise), log (append-only key=value lines for CI), quiet (errors and final path only), verbose (full diagnostic output)
-v, --verbose - Alias for --output-mode verbose (kept for backward compatibility)

Decode (QR Video → File)

qrstream decode <video> -o output_file [options]
Option Default Description
<video> - Input video path (MP4, MOV, etc.)
-o, --output required Output file path
-s, --sample-rate 0 (auto) Sample every Nth frame (0 = adaptive probing)
-w, --workers All CPU cores Parallel worker threads for QR detection. zxing-cpp is native C++ and releases the GIL during detection, so more threads scale close to linearly on multi-core machines.
--output-mode auto Progress/status rendering: auto, log, quiet, or verbose (same as encode)
-v, --verbose - Alias for --output-mode verbose (kept for backward compatibility)

Examples

# Encode a PDF (default: base45 alphanumeric mode, 2x redundancy, h264)
qrstream encode report.pdf -o report.mp4 --overhead 2.0 --output-mode verbose

# Decode video (adaptive sample rate + targeted recovery)
qrstream decode report.mp4 -o report_recovered.pdf --output-mode verbose

# Encode with high QR version for phone screen capture
qrstream encode data.bin -o data.mp4 --qr-version 20

# Add a larger quiet zone and white lead-in before recording
qrstream encode slides.zip -o slides.mp4 --border 10 --lead-in-seconds 1.5

# CI-friendly decode with log output
qrstream decode recording.mov -o out.bin --output-mode log

Python API

from qrstream.encoder import encode_to_video
from qrstream.decoder import extract_qr_from_video, decode_blocks, decode_blocks_to_file

# Encode (default: base45 alphanumeric mode)
encode_to_video("input.bin", "output.mp4", overhead=2.0, verbose=True)

# Add recording-friendly quiet zone and white lead-in
encode_to_video("input.bin", "output.mp4", border=10.0, lead_in_seconds=1.5)

# Decode to memory
blocks = extract_qr_from_video("output.mp4", verbose=True)
result = decode_blocks(blocks, verbose=True)

# Better for large files: stream directly to file with incremental decompression
written = decode_blocks_to_file(blocks, "recovered.bin", verbose=True)
print(f"wrote {written} bytes")

Project Structure

project-root/
├── pyproject.toml             # Project config & dependencies
├── src/qrstream/
│   ├── cli.py                 # CLI entry (encode/decode subcommands)
│   ├── encoder.py             # LT encode → QR frame generation → MP4 video
│   ├── decoder.py             # Video frame extraction → QR detect → LT decode → file rebuild
│   ├── lt_codec.py            # LT fountain code primitives (PRNG, RSD, BlockGraph)
│   ├── protocol.py            # V3 protocol serialization + base45 codec (legacy base64/COBS decode supported)
│   └── qr_utils.py            # QR generation + detection (zxing-cpp)
├── tests/
│   ├── test_lt_codec.py       # LT codec unit tests
│   ├── test_protocol.py       # V3 protocol + base45 tests
│   ├── test_decoder.py        # Decoder validation + probe strategy tests
│   ├── test_roundtrip.py      # Pure LT codec roundtrip tests (no video I/O)
│   ├── test_qr_generate.py    # QR generation correctness + glog(0) regression
│   ├── test_e2e_encode_decode.py  # End-to-end encode→video→decode SHA256 tests
│   └── test_optimizations.py  # Perf optimizations + zxing-cpp + legacy-fallback tests
└── dev/
    ├── benchmark.py           # Performance benchmarks
    ├── perf-profile/          # cProfile hotspot analysis scripts
    ├── test-container/        # Podman test container
    └── wechatqrcode-mnn-poc/  # Historical WeChatQRCode MNN acceleration POC (archived)

Technical Details

V3 Protocol Format (24-byte header + 4-byte trailing CRC)

Offset  Size  Field
  0      1    version      0x03
  1      1    flags        bit0=zlib compressed, bit1=high-density mode (base45 alphanumeric)
  2      8    filesize     uint64 BE (encoded payload size; compressed size when zlib is on)
 10      2    blocksize    uint16 BE
 12      4    block_count  uint32 BE  K = ceil(filesize / blocksize)
 16      4    seed         uint32 BE  PRNG seed
 20      2    block_seq    uint16 BE  monotonically increasing sequence number
 22      2    reserved     reserved (currently 0)
 24      ...  data         blocksize bytes of encoded data
 ...     4    crc32        CRC32(header[0:24] + data)
  • Default encoding uses V3 + base45 alphanumeric QR.
  • The decoder tries base45 → base64 → COBS in order, preserving compatibility with pre-v0.6 videos.
  • V3 extends filesize to uint64 and block_count to uint32, supporting larger files and block counts.

Encoding Modes

Mode QR Content QR Mode Overhead Default
Base45 alphanumeric raw bytes → base45 → 0-9A-Z $%*+-./: Alphanumeric (5.5 bits/char) ~67% (but uses denser QR mode → net denser than byte mode) Yes
Base64 raw bytes → base64 string Byte (8 bits/char) ~33% No (--qr-mode base64)
COBS (legacy) raw bytes → COBS → latin-1 string Byte ~0.4% Removed in v0.6; decode-only fallback for old videos

Base45 (RFC 9285) is the default because QR's alphanumeric mode is denser per character than byte mode — at V25/M the base45 payload per frame is ~30% larger than base64, and in practice produces 20–25% smaller videos and 10–20% faster encode/decode.

Large Files & Low-Memory Paths

  • For large V3 inputs, the encoder uses mmap for random access, avoiding loading the entire file into memory.
  • When the input is large enough, V3 encoding automatically disables zlib compression to preserve the low-memory path; use --force-compress to override.
  • The decoder supports streaming writes with incremental decompression, reducing memory overhead.
  • During decode, the interactive UI shows a two-row live block: a video-scan progress bar (percent, ETA, live detection rate) and a qBittorrent-style file-recovery block map (per-bucket colour density plus an N/K blocks counter). Use --output-mode log for CI-friendly key=value lines or --output-mode quiet for scripted invocations.

Decoding Pipeline

  1. Probe phase: Sample 3 spread-out windows in the video (120 frames each by default), measure detection rate and repetition per window, pick the most conservative sample_rate; completion prints a two-line Probe + Plan summary (observations vs. derived parameters)
  2. Main scan: Detect QR codes in parallel at the adaptive sample rate, feeding into the LT decoder in real time. The interactive UI shows a Scan row (video progress / ETA / detection rate) and a File row (qBittorrent-style block map + N/K blocks counter), aligned in a shared table
  3. Targeted recovery: If the first pass didn't recover all blocks, use linear regression on observed (seed, frame) pairs to locate missing seeds and re-scan those segments precisely
  4. LT decode: Belief propagation (peeling) to recover all source blocks
  5. Output writeback: Write recovered blocks sequentially; incremental decompression in compressed mode

LT Fountain Code Parameters

Parameter Value Notes
Degree distribution Robust Soliton Distribution c=0.1, delta=0.5
PRNG SplitMix64 mixer + LCG (a=16807, m=2^31-1) Non-linear seed mixing eliminates sequential-seed correlation
XOR numpy vectorized + in-place 10-50x faster than pure Python
Decoding Belief Propagation (Peeling) Iterative elimination on bipartite graph

Testing

# Unit tests (default — fast, no video I/O)
uv run pytest tests/ -v

# End-to-end encode→video→decode tests (10 KB, 100 KB, 500 KB + glog regression)
uv run pytest -m e2e -v

# Real-world phone-recording tests (requires fixture videos)
uv run pytest -m slow -v

Utility Commands

# Display the colour palette used by the interactive UI
qrstream colors

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qrstream-0.9.0.post1.tar.gz (27.8 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qrstream-0.9.0.post1-py3-none-any.whl (84.2 kB view details)

Uploaded Python 3

File details

Details for the file qrstream-0.9.0.post1.tar.gz.

File metadata

  • Download URL: qrstream-0.9.0.post1.tar.gz
  • Upload date:
  • Size: 27.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for qrstream-0.9.0.post1.tar.gz
Algorithm Hash digest
SHA256 51365dfa97225d467b1df3898cf5f12df909e8e43750fdd326e277f9684deeac
MD5 632c896e1de8254ad1e851cf1c0be0f4
BLAKE2b-256 8931651a7481b89785a5dbd36e29edf4611c5e36e95e994ee7ea0cb1794c1551

See more details on using hashes here.

Provenance

The following attestation bundles were made for qrstream-0.9.0.post1.tar.gz:

Publisher: publish.yml on ddddavid-he/qrstream-enhanced

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file qrstream-0.9.0.post1-py3-none-any.whl.

File metadata

  • Download URL: qrstream-0.9.0.post1-py3-none-any.whl
  • Upload date:
  • Size: 84.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for qrstream-0.9.0.post1-py3-none-any.whl
Algorithm Hash digest
SHA256 4e4d2102db6b460791f8bcba0ccc9ef018d097b9a022ff637593ead4400530af
MD5 6e2b44d8c5c3587baf5236396c860c4f
BLAKE2b-256 d030d41df96493256ecbc633ca1e6c6f44dea937219e9d624d7e4a55ec19227b

See more details on using hashes here.

Provenance

The following attestation bundles were made for qrstream-0.9.0.post1-py3-none-any.whl:

Publisher: publish.yml on ddddavid-he/qrstream-enhanced

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page