Skip to content

Instantly share code, notes, and snippets.

@AndrewAltimit
Last active September 13, 2025 11:09
Show Gist options
  • Select an option

  • Save AndrewAltimit/579120a5dfe772b6a5c73bf810fe1974 to your computer and use it in GitHub Desktop.

Select an option

Save AndrewAltimit/579120a5dfe772b6a5c73bf810fe1974 to your computer and use it in GitHub Desktop.
QR Code File Transmitter

QR Code File Transmitter

Convert any file into a series of QR codes for transmission via photos, then reconstruct the original file from the QR code images. Perfect for air-gapped systems, conference presentations, or creative file sharing.

Quick Start

Installation

# Install required packages
pip install qrcode[pil] pyzbar opencv-python

# If you encounter issues with pyzbar on Windows, also try:
pip install pyzbar[scripts]

Basic Usage

Encode a File

# Convert a file to QR codes with default settings (safe, reliable)
python qr_transmitter.py encode myfile.py

# Encode with larger chunks (fewer QR codes, but harder to scan)
python qr_transmitter.py encode myfile.py -c 2500

# Encode with smaller chunks (more QR codes, but easier to scan)
python qr_transmitter.py encode myfile.py -c 1000

# Specify output directory
python qr_transmitter.py encode myfile.py -o ./output_folder

# Skip validation (faster, no decoder libraries needed)
python qr_transmitter.py encode myfile.py --no-validate

Decode QR Codes

# Decode by listing all QR code files (most reliable on Windows)
python qr_transmitter.py decode folder\file_qr_001_of_008.png folder\file_qr_002_of_008.png folder\file_qr_003_of_008.png folder\file_qr_004_of_008.png folder\file_qr_005_of_008.png folder\file_qr_006_of_008.png folder\file_qr_007_of_008.png folder\file_qr_008_of_008.png -o recovered.py

# For many files, you can use a batch file or script to list them

Features

Core Capabilities

  • File Splitting: Automatically splits files into optimal chunks for QR encoding
  • Metadata Management: Each QR includes sequence numbers, checksums, and file info
  • Integrity Verification: SHA-256 hashes ensure perfect reconstruction
  • Binary Support: Handles both text and binary files
  • Progress Tracking: Visual progress bars during encoding/decoding
  • Error Recovery: Can handle missing or corrupted QR codes with warnings

Technical Specifications

  • Default Chunk Size: 1,800 characters (very reliable)
  • Maximum Chunk Size: 2,953 characters (QR Version 40 limit)
  • Error Correction: Low level by default (maximizes capacity)
  • Output Format: PNG images with descriptive filenames
  • Companion Files: Each QR gets a .txt file with metadata

Phone Scanning Guide

Method 1: Direct Photo Transfer

  1. Generate QR codes on your computer
  2. Take clear photos of each QR code with your phone
  3. Transfer photos to target computer (email, USB, cloud)
  4. List all photo files in the decode command

Method 2: QR Scanner Apps

  1. Use a QR scanner app that can export data
  2. Scan each QR code in order
  3. Save the JSON data from each scan
  4. Transfer the saved data files to decode

Tips for Best Scanning

  • Ensure good lighting and steady hand
  • Keep phone perpendicular to QR code
  • Scan in numbered order when possible
  • Verify each scan shows JSON data starting with {"metadata":

Use Cases

  1. Air-Gapped Systems: Transfer code to computers without network access
  2. Conference Presentations: Share code via projected QR codes
  3. Physical Backups: Print QR codes as paper backup of critical files
  4. Cross-Platform Transfer: Move files between incompatible systems
  5. Educational Settings: Distribute code without network setup

Command Reference

Encode Command

python qr_transmitter.py encode [options] <input_file>

Options:
  -o, --output DIR        Output directory for QR codes
  -c, --chunk-size SIZE   Characters per QR code (default: 1800, max: 2953)
  --no-validate          Skip QR code validation
  --quiet                Suppress progress output

Decode Command

python qr_transmitter.py decode [options] <qr_images...>

Options:
  -o, --output FILE      Output file path for reconstructed file
  --quiet                Suppress progress output

Arguments:
  qr_images             List of QR code image files to decode

Pro Tip: For the most reliable decoding on Windows, always list QR files explicitly rather than using directory or wildcard patterns. Consider creating a batch file to automate the file listing for large sets of QR codes.

Chunk Size Guidelines

Chunk Size QR Density Scan Difficulty Best For
1000 Low Very Easy Phone scanning, poor cameras
1800 (default) Medium Easy General use, reliable scanning
2500 High Moderate Good scanners, fewer QRs needed
2953 (max) Very High Hard Professional scanners only

Troubleshooting

Windows-Specific Issues

Problem: Directory decoding doesn't work Solution: List files explicitly in the decode command

Problem: Wildcards don't expand properly Solution: Use explicit file listing or create a batch file

Decoder Issues

Problem: "QR decoder not available" Solution: Install pyzbar and opencv-python:

pip install pyzbar opencv-python
pip install pyzbar[scripts]  # If needed on Windows

Problem: pyzbar installation fails Solution: Use --no-validate flag when encoding (skips validation)

Encoding Issues

Problem: "Invalid version" error Solution: Reduce chunk size with -c option (try 1000 or 1500)

Problem: Too many QR codes generated Solution: Increase chunk size (try 2500, max 2953)

Output Structure

When encoding myfile.py, the program creates:

myfile_qrcodes/
├── myfile_qr_001_of_005.png   # QR code image
├── myfile_qr_001_of_005.txt   # Metadata file
├── myfile_qr_002_of_005.png
├── myfile_qr_002_of_005.txt
└── ...

Data Format

Each QR contains JSON with:

  • File metadata (name, chunk index, total chunks)
  • Chunk data (portion of file content)
  • Integrity hashes (chunk and file checksums)

Examples

Example 1: Simple Text File

# Create a test file
echo "Hello World" > test.txt

# Encode it
python qr_transmitter.py encode test.txt

# Decode it
python qr_transmitter.py decode test_qrcodes\test_qr_001_of_001.png -o recovered.txt

Example 2: Python Script

# Encode a Python script with medium-sized chunks
python qr_transmitter.py encode script.py -c 2000

# Decode listing all generated QR codes
python qr_transmitter.py decode script_qrcodes\script_qr_001_of_010.png script_qrcodes\script_qr_002_of_010.png [...all files...] -o recovered_script.py

Example 3: Binary File

# Encode a binary file (automatically handled)
python qr_transmitter.py encode image.jpg

# Decode back to binary
python qr_transmitter.py decode image_qrcodes\*.png -o recovered_image.jpg

Limitations

  • Maximum file size limited by patience (more QRs = more scanning)
  • QR scanning quality depends on camera and lighting
  • Large files may generate many QR codes (adjust chunk size accordingly)
  • Directory decoding may not work on all systems (use file listing instead)

Changes to Consider

Consider adding:

  • Alternative QR decoder libraries
  • Batch processing scripts
  • GUI interface
  • Mobile app for scanning
  • Automatic file listing generation

License

Free to use and modify for any purpose.

#!/usr/bin/env python3
"""
QR Code File Transmitter
========================
A tool to convert code files into multiple QR codes for transmission via photos,
and reconstruct the original files from QR code images.
Author: Assistant
Version: 2.0.0 (Simplified)
Dependencies: qrcode, pillow, pyzbar, opencv-python
"""
import os
import sys
import json
import hashlib
import argparse
import base64
from pathlib import Path
from typing import List, Tuple, Optional
from dataclasses import dataclass, asdict
try:
import qrcode
except ImportError:
print("Please install required packages: pip install qrcode[pil]")
sys.exit(1)
try:
from pyzbar import pyzbar
import cv2
except ImportError:
DECODER_AVAILABLE = False
print("Warning: QR decoder not available. Install with: pip install pyzbar opencv-python")
else:
DECODER_AVAILABLE = True
@dataclass
class ChunkMetadata:
"""Metadata for each chunk of the file."""
filename: str
chunk_index: int
total_chunks: int
chunk_hash: str
file_hash: str
encoding: str = 'utf-8'
class QRCodeTransmitter:
"""Main class for converting files to QR codes and back."""
# QR code capacity limits (very conservative for reliability)
DEFAULT_CHUNK_SIZE = 1800 # Very safe default
MAX_CHUNK_SIZE = 2953 # QR Version 40 with Low error correction
def __init__(self, chunk_size: int = DEFAULT_CHUNK_SIZE,
error_correction: int = qrcode.constants.ERROR_CORRECT_L):
"""
Initialize the QR Code Transmitter.
Args:
chunk_size: Maximum characters per QR code (default: 1800)
error_correction: QR code error correction level
"""
self.chunk_size = min(chunk_size, self.MAX_CHUNK_SIZE)
self.error_correction = error_correction
def encode_file(self, input_file: str, output_dir: str = None,
validate: bool = True, show_progress: bool = True) -> List[str]:
"""
Convert a file into multiple QR code images.
Args:
input_file: Path to the input file
output_dir: Directory to save QR codes (default: same as input file)
validate: Whether to validate generated QR codes
show_progress: Whether to show progress indicator
Returns:
List of paths to generated QR code images
"""
input_path = Path(input_file)
if not input_path.exists():
raise FileNotFoundError(f"Input file not found: {input_file}")
# Setup output directory
if output_dir is None:
output_dir = input_path.parent / f"{input_path.stem}_qrcodes"
else:
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
# Read file content
try:
with open(input_path, 'r', encoding='utf-8') as f:
content = f.read()
except UnicodeDecodeError:
# Fall back to binary mode for non-text files
with open(input_path, 'rb') as f:
content = base64.b64encode(f.read()).decode('ascii')
# Calculate file hash for integrity
file_hash = hashlib.sha256(content.encode()).hexdigest()[:16]
# Split content into chunks
chunks = self._split_into_chunks(content, self.chunk_size)
total_chunks = len(chunks)
if show_progress:
print(f"📄 Processing: {input_path.name}")
print(f"📊 File size: {len(content):,} characters")
print(f"🔢 Splitting into {total_chunks} QR codes...")
qr_paths = []
for i, chunk_data in enumerate(chunks, 1):
if show_progress:
self._print_progress(i, total_chunks)
# Create metadata
metadata = ChunkMetadata(
filename=input_path.name,
chunk_index=i,
total_chunks=total_chunks,
chunk_hash=hashlib.sha256(chunk_data.encode()).hexdigest()[:8],
file_hash=file_hash
)
# Combine metadata and data
payload = {
'metadata': asdict(metadata),
'data': chunk_data
}
payload_json = json.dumps(payload, separators=(',', ':'))
# Generate QR code
qr_path = output_dir / f"{input_path.stem}_qr_{i:03d}_of_{total_chunks:03d}.png"
self._generate_qr_code(payload_json, qr_path, i, total_chunks)
qr_paths.append(str(qr_path))
if show_progress:
print(f"\n✅ Generated {total_chunks} QR codes in: {output_dir}")
# Validate if requested
if validate and DECODER_AVAILABLE:
if show_progress:
print("🔍 Validating QR codes...")
if self._validate_qr_codes(qr_paths):
print("✅ All QR codes validated successfully!")
else:
print("⚠️ Warning: Some QR codes may not be readable")
return qr_paths
def decode_qr_codes(self, qr_images: List[str], output_file: str = None,
show_progress: bool = True) -> str:
"""
Reconstruct a file from QR code images.
Args:
qr_images: List of paths to QR code images
output_file: Path to save reconstructed file (optional)
show_progress: Whether to show progress indicator
Returns:
Reconstructed file content as string
"""
if not DECODER_AVAILABLE:
raise RuntimeError("QR decoder not available. Install pyzbar and opencv-python")
if show_progress:
print(f"🔄 Decoding {len(qr_images)} QR codes...")
chunks_data = {}
file_metadata = None
for i, qr_path in enumerate(qr_images, 1):
if show_progress:
self._print_progress(i, len(qr_images))
# Read QR code
img = cv2.imread(qr_path)
decoded_objects = pyzbar.decode(img)
if not decoded_objects:
raise ValueError(f"Could not decode QR code: {qr_path}")
# Parse payload
payload_json = decoded_objects[0].data.decode('utf-8')
payload = json.loads(payload_json)
metadata = ChunkMetadata(**payload['metadata'])
chunk_data = payload['data']
# Verify chunk integrity
expected_hash = metadata.chunk_hash
actual_hash = hashlib.sha256(chunk_data.encode()).hexdigest()[:8]
if expected_hash != actual_hash:
raise ValueError(f"Chunk {metadata.chunk_index} integrity check failed")
chunks_data[metadata.chunk_index] = chunk_data
if file_metadata is None:
file_metadata = metadata
if show_progress:
print()
# Verify we have all chunks
if len(chunks_data) != file_metadata.total_chunks:
missing = set(range(1, file_metadata.total_chunks + 1)) - set(chunks_data.keys())
raise ValueError(f"Missing chunks: {missing}")
# Reconstruct file
content = ''.join(chunks_data[i] for i in sorted(chunks_data.keys()))
# Verify file integrity
file_hash = hashlib.sha256(content.encode()).hexdigest()[:16]
if file_hash != file_metadata.file_hash:
raise ValueError("File integrity check failed")
if show_progress:
print(f"✅ Successfully reconstructed: {file_metadata.filename}")
# Save if output file specified
if output_file:
output_path = Path(output_file)
output_path.parent.mkdir(parents=True, exist_ok=True)
try:
# Try to decode as base64 first (for binary files)
decoded_content = base64.b64decode(content)
with open(output_path, 'wb') as f:
f.write(decoded_content)
except:
# Save as text file
with open(output_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"💾 Saved to: {output_path}")
return content
def _split_into_chunks(self, content: str, chunk_size: int) -> List[str]:
"""Split content into chunks of specified size."""
# Account for JSON overhead (metadata + structure)
# Need to be very conservative due to JSON encoding expansion
overhead_estimate = 600 # Account for metadata and JSON structure
effective_chunk_size = chunk_size - overhead_estimate
# Ensure we don't have negative or zero chunk size
if effective_chunk_size <= 0:
effective_chunk_size = 500 # Minimum viable chunk size
chunks = []
for i in range(0, len(content), effective_chunk_size):
chunks.append(content[i:i + effective_chunk_size])
return chunks
def _generate_qr_code(self, data: str, output_path: Path,
chunk_num: int, total_chunks: int):
"""Generate a QR code image."""
# Create QR code
qr = qrcode.QRCode(
version=None, # Auto-determine version
error_correction=self.error_correction,
box_size=10,
border=4,
)
# Add data and generate
try:
qr.add_data(data)
qr.make(fit=True)
except Exception as e:
if "Invalid version" in str(e) or "version" in str(e).lower():
raise ValueError(f"Data too large for QR code. Chunk size: {len(data)} chars. "
f"Try reducing chunk size with -c option (e.g., -c 1000)")
raise
# Create and save image
img = qr.make_image(fill_color="black", back_color="white")
img.save(str(output_path))
# Create info file
info_path = output_path.with_suffix('.txt')
with open(info_path, 'w') as f:
f.write(f"Part {chunk_num} of {total_chunks}\n")
f.write(f"File: {output_path.name}\n")
f.write(f"Data size: {len(data)} characters\n")
def _validate_qr_codes(self, qr_paths: List[str]) -> bool:
"""Validate that QR codes can be read correctly."""
if not DECODER_AVAILABLE:
return False
for qr_path in qr_paths:
img = cv2.imread(qr_path)
decoded = pyzbar.decode(img)
if not decoded:
return False
return True
def _print_progress(self, current: int, total: int):
"""Print a progress bar."""
bar_length = 40
progress = current / total
filled = int(bar_length * progress)
bar = '█' * filled + '░' * (bar_length - filled)
percent = progress * 100
sys.stdout.write(f'\r[{bar}] {percent:.1f}% ({current}/{total})')
sys.stdout.flush()
def main():
"""Command-line interface for the QR Code Transmitter."""
parser = argparse.ArgumentParser(
description='Convert files to QR codes and back',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Encode a file to QR codes
python qr_transmitter.py encode mycode.py
# Encode with custom output directory and chunk size
python qr_transmitter.py encode mycode.py -o ./qr_output -c 1500
# Decode command
python qr_transmitter.py decode ./qr_output/*.png -o recovered_file.py
# Decode from a directory
python qr_transmitter.py decode ./qr_folder -o recovered_file.py
# Decode numbered files (1.png, 2.png, etc.)
python qr_transmitter.py decode ./images -o recovered_file.py
# Decode with wildcard patterns
python qr_transmitter.py decode "*.png" -o recovered_file.py
# Decode specific QR code images
python qr_transmitter.py decode qr_001.png qr_002.png qr_003.png
How to use with a phone:
1. Generate QR codes using 'encode' command
2. Display QR codes on screen or print them
3. Use phone's QR scanner to capture each code
4. Save the scanned text to files (qr_001.txt, qr_002.txt, etc.)
5. Transfer text files to computer
6. Use 'decode' command with the saved text files
"""
)
subparsers = parser.add_subparsers(dest='command', help='Commands')
# Encode command
encode_parser = subparsers.add_parser('encode', help='Encode file to QR codes')
encode_parser.add_argument('input', help='Input file path')
encode_parser.add_argument('-o', '--output', help='Output directory for QR codes')
encode_parser.add_argument('-c', '--chunk-size', type=int,
default=QRCodeTransmitter.DEFAULT_CHUNK_SIZE,
help=f'Characters per QR code (default: {QRCodeTransmitter.DEFAULT_CHUNK_SIZE}, max: {QRCodeTransmitter.MAX_CHUNK_SIZE})')
encode_parser.add_argument('--no-validate', action='store_true',
help='Skip QR code validation')
encode_parser.add_argument('--quiet', action='store_true',
help='Suppress progress output')
# Decode command
decode_parser = subparsers.add_parser('decode', help='Decode QR codes to file')
decode_parser.add_argument('qr_images', nargs='+', help='QR code image files')
decode_parser.add_argument('-o', '--output', help='Output file path')
decode_parser.add_argument('--quiet', action='store_true',
help='Suppress progress output')
args = parser.parse_args()
if not args.command:
parser.print_help()
return
transmitter = QRCodeTransmitter(
chunk_size=getattr(args, 'chunk_size', QRCodeTransmitter.DEFAULT_CHUNK_SIZE)
)
try:
if args.command == 'encode':
transmitter.encode_file(
args.input,
args.output,
validate=not args.no_validate,
show_progress=not args.quiet
)
elif args.command == 'decode':
transmitter.decode_qr_codes(
args.qr_images,
args.output,
show_progress=not args.quiet
)
except Exception as e:
print(f"\n❌ Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment