initial commit

This commit is contained in:
2025-11-12 21:40:44 -05:00
commit 2f0154b20f
8 changed files with 967 additions and 0 deletions

70
Dockerfile Normal file
View File

@@ -0,0 +1,70 @@
# QR Code Generator - Dockerfile
# Multi-stage build for optimized container size
FROM python:3.11-slim as builder
# Set working directory
WORKDIR /app
# Install system dependencies for Pillow and cairosvg
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libjpeg-dev \
zlib1g-dev \
libcairo2-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements file
COPY requirements.txt .
# Install Python dependencies
RUN pip install --no-cache-dir --user -r requirements.txt
# Final stage
FROM python:3.11-slim
# Set working directory
WORKDIR /app
# Install runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libcairo2 \
libjpeg62-turbo \
&& rm -rf /var/lib/apt/lists/*
# Copy Python dependencies from builder
COPY --from=builder /root/.local /root/.local
# Copy application code
COPY app.py .
COPY utils/ ./utils/
# Create directory for Streamlit config
RUN mkdir -p /root/.streamlit
# Configure Streamlit
RUN echo '\
[server]\n\
headless = true\n\
address = "0.0.0.0"\n\
port = 8501\n\
enableCORS = false\n\
enableXsrfProtection = true\n\
\n\
[browser]\n\
gatherUsageStats = false\n\
' > /root/.streamlit/config.toml
# Update PATH to include local Python packages
ENV PATH=/root/.local/bin:$PATH
# Expose Streamlit port
EXPOSE 8501
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:8501/_stcore/health')"
# Run the application
ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501"]

301
README.md Normal file
View File

@@ -0,0 +1,301 @@
# QR Code Generator
A professional web application for generating QR codes with optional logo overlay and export capabilities in multiple formats and resolutions.
## Features
- **Dual QR Code Types**:
- **URL QR Codes**: Convert any URL into a scannable QR code
- **WiFi QR Codes**: Generate QR codes for WiFi network credentials (WPA/WPA2, WEP, or Open networks)
- **Logo Overlay**: Add your company logo or custom image to the center of the QR code
- **Multiple Export Formats**:
- PNG (Portable Network Graphics)
- JPEG (Joint Photographic Experts Group)
- SVG (Scalable Vector Graphics - perfect for print)
- **Flexible Resolution Options**: Export in various resolutions from 500px to 4000px
- **Customizable Appearance**: Choose QR code and background colors
- **Error Correction Levels**: Support for L, M, Q, and H error correction levels
- **WiFi Network Support**: Supports WPA/WPA2, WEP, and open networks with hidden network option
- **Responsive UI**: Clean, intuitive interface built with Streamlit
- **Containerized Deployment**: Ready-to-use Podman/Docker configuration
## Tech Stack
- **Python 3.11**: Core programming language
- **Streamlit**: Web application framework
- **qrcode**: QR code generation library
- **segno**: Advanced QR code library with SVG support
- **Pillow (PIL)**: Image processing
- **CairoSVG**: SVG rendering
- **Podman/Docker**: Containerization
## Prerequisites
### Local Installation
- Python 3.11 or higher
- pip (Python package manager)
### Container Installation
- Podman or Docker installed on your system
## Installation
### Option 1: Local Development
1. Clone the repository or download the files
2. Create a virtual environment (recommended):
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
4. Run the application:
```bash
streamlit run app.py
```
5. Open your browser to `http://localhost:8501`
### Option 2: Podman Container
1. Build and run using the provided script:
```bash
./run-podman.sh
```
2. Or manually:
```bash
# Build the image
podman build -t qr-code-generator .
# Run the container
podman run -d --name qr-app -p 8501:8501 qr-code-generator
```
3. Access the application at `http://localhost:8501`
### Option 3: Docker Container
Replace `podman` with `docker` in the commands above:
```bash
docker build -t qr-code-generator .
docker run -d --name qr-app -p 8501:8501 qr-code-generator
```
## Usage Guide
### URL QR Code Generation
1. **Select Mode**: Choose "URL" from the QR Code Type options
2. **Enter URL**: Type or paste the URL you want to encode
3. **Configure Settings** (optional):
- Adjust error correction level (use H for logos)
- Customize colors
- Select export format and resolution
4. **Generate**: Click the "Generate QR Code" button
5. **Download**: Use the download button to save your QR code
### WiFi QR Code Generation
1. **Select Mode**: Choose "WiFi" from the QR Code Type options
2. **Enter Network Details**:
- **Network Name (SSID)**: Your WiFi network name
- **Password**: Your WiFi password (leave empty for open networks)
- **Security Type**: Select WPA/WPA2, WEP, or None (Open)
- **Hidden Network**: Check if your network is hidden
3. **Configure Settings** (optional):
- Adjust error correction level
- Customize colors
- Select export format and resolution
4. **Generate**: Click the "Generate QR Code" button
5. **Share**: Users can scan the QR code to automatically connect to your WiFi network
**Note**: WiFi QR codes work on most modern smartphones (iOS 11+, Android 10+). The device will automatically detect the QR code contains WiFi credentials and prompt to connect.
### Adding a Logo
1. Follow steps 1-2 from basic generation
2. **Upload Logo**: Click "Upload logo image" and select your image file
3. **Adjust Size**: Use the slider to set the logo size ratio (0.1 - 0.4)
4. **Generate**: Click "Generate QR Code"
5. **Download**: Save your branded QR code
### Error Correction Levels
- **L (7%)**: Suitable for clean environments, smallest QR code
- **M (15%)**: Default level, balanced
- **Q (25%)**: Better damage resistance
- **H (30%)**: Recommended for QR codes with logos, highest damage resistance
### Format Selection
- **PNG**: Best for web use, supports transparency
- **JPEG**: Smaller file size, good for photos, no transparency
- **SVG**: Vector format, infinitely scalable, perfect for print
## Project Structure
```
qr-code-generator/
├── app.py # Main Streamlit application
├── utils/
│ ├── __init__.py # Package initializer
│ ├── qr_generator.py # QR code generation utilities
│ └── wifi_qr.py # WiFi QR code utilities
├── requirements.txt # Python dependencies
├── Dockerfile # Container definition
├── .dockerignore # Docker ignore rules
├── run-podman.sh # Podman run script
├── .gitignore # Git ignore rules
└── README.md # This file
```
## Configuration Options
### Streamlit Configuration
The application uses default Streamlit settings. To customize:
Create `.streamlit/config.toml`:
```toml
[server]
port = 8501
headless = true
[theme]
primaryColor = "#FF4B4B"
backgroundColor = "#FFFFFF"
secondaryBackgroundColor = "#F0F2F6"
textColor = "#262730"
font = "sans serif"
```
### Environment Variables
For containerized deployment, you can customize:
- `STREAMLIT_SERVER_PORT`: Change the port (default: 8501)
- `STREAMLIT_BROWSER_GATHER_USAGE_STATS`: Disable telemetry
## Development
### Code Style
This project follows Python best practices:
- PEP 8 style guide
- Type hints for function parameters
- Comprehensive docstrings
- Modular, reusable code structure
### Adding New Features
1. **New Export Format**: Update `utils/qr_generator.py` and add format handler
2. **UI Enhancements**: Modify `app.py` Streamlit components
3. **New QR Styles**: Extend `QRCodeGenerator` class methods
### Testing
Run manual tests by generating QR codes with various configurations:
- Different URLs (short, long, special characters)
- With and without logos
- All export formats
- Various resolutions
- Different error correction levels
## Container Management
### Podman Commands
```bash
# View logs
podman logs qr-app
# Stop container
podman stop qr-app
# Start container
podman start qr-app
# Remove container
podman rm qr-app
# Remove image
podman rmi qr-code-generator
```
### Docker Commands
Replace `podman` with `docker` in the commands above.
## Troubleshooting
### Issue: QR Code Won't Scan
- **Solution**: Increase error correction level to H
- Reduce logo size ratio
- Ensure sufficient contrast between QR code and background colors
### Issue: Logo Appears Distorted
- **Solution**: Use square or nearly square logos
- Try a smaller logo size ratio
- Ensure logo has sufficient resolution
### Issue: Container Won't Start
- **Solution**: Check if port 8501 is already in use
- Verify Podman/Docker is running
- Check container logs: `podman logs qr-app`
### Issue: Dependencies Install Failure
- **Solution**: Update pip: `pip install --upgrade pip`
- Install system dependencies (for Pillow): `apt-get install libjpeg-dev zlib1g-dev`
- Use Python 3.11 or higher
## Performance Considerations
- **High-Resolution Exports**: May take a few seconds to generate
- **SVG Format**: Fastest generation, smallest file size, infinite scalability
- **Logo Processing**: Larger logos require more processing time
## Security Notes
- This application runs locally by default
- When deploying publicly, consider:
- Adding authentication
- Implementing rate limiting
- Validating URL inputs
- Setting up HTTPS
## Contributing
To contribute to this project:
1. Follow the existing code style and structure
2. Add appropriate documentation
3. Test thoroughly before submitting
4. Keep commits focused and descriptive
## License
This project is provided as-is for educational and commercial use.
## Acknowledgments
- Built with [Streamlit](https://streamlit.io/)
- QR code generation by [qrcode](https://github.com/lincolnloop/python-qrcode) and [segno](https://github.com/heuer/segno)
- Image processing by [Pillow](https://python-pillow.org/)
## Support
For issues, questions, or suggestions:
- Check the troubleshooting section
- Review the usage guide
- Examine the code documentation in `utils/qr_generator.py`
---
**Version**: 1.0
**Last Updated**: 2025-11
**Python Version**: 3.11+

285
app.py Normal file
View File

@@ -0,0 +1,285 @@
"""
QR Code Generator - Streamlit Application
A web application for generating QR codes with optional logo overlay
and export in multiple formats and resolutions.
"""
import streamlit as st
from PIL import Image
import io
from utils.qr_generator import QRCodeGenerator
from utils.wifi_qr import WiFiQRCode
# Page configuration
st.set_page_config(
page_title="QR Code Generator",
page_icon="📱",
layout="centered",
initial_sidebar_state="expanded"
)
def main():
"""Main application function."""
st.title("QR Code Generator")
st.markdown("Generate professional QR codes with optional logo overlay")
# Sidebar configuration
with st.sidebar:
st.header("Configuration")
# Error correction level
error_correction = st.selectbox(
"Error Correction Level",
options=["L (7%)", "M (15%)", "Q (25%)", "H (30%)"],
index=3,
help="Higher levels allow more damage/obscurity. Use H (30%) for logos."
)
error_level = error_correction[0]
# QR code appearance
st.subheader("Appearance")
fill_color = st.color_picker("QR Code Color", "#000000")
back_color = st.color_picker("Background Color", "#FFFFFF")
# Export settings
st.subheader("Export Settings")
export_format = st.selectbox(
"Format",
options=["PNG", "JPEG", "SVG"],
help="SVG is vector format (scalable), PNG/JPEG are raster formats"
)
if export_format in ["PNG", "JPEG"]:
# Define resolution options
resolution_labels = {
500: "Low (500px)",
1000: "Medium (1000px)",
2000: "High (2000px)",
4000: "Ultra (4000px)"
}
target_size = st.select_slider(
"Resolution",
options=[500, 1000, 2000, 4000],
value=2000, # Default to High
format_func=lambda x: resolution_labels[x]
)
else:
svg_scale = st.slider(
"SVG Scale",
min_value=5,
max_value=20,
value=10,
help="Scale factor for SVG output"
)
# Main content area
st.divider()
# Mode selection
qr_mode = st.radio(
"QR Code Type",
options=["URL", "WiFi"],
horizontal=True,
help="Choose what type of QR code to generate"
)
st.divider()
# Content input based on mode
if qr_mode == "URL":
# URL input
url = st.text_input(
"Enter URL",
placeholder="https://example.com",
help="Enter the URL you want to encode in the QR code"
)
qr_content = url
else:
# WiFi input fields
st.subheader("WiFi Network Details")
col1, col2 = st.columns(2)
with col1:
wifi_ssid = st.text_input(
"Network Name (SSID)",
placeholder="MyWiFiNetwork",
help="The name of your WiFi network"
)
wifi_security = st.selectbox(
"Security Type",
options=["WPA/WPA2", "WEP", "None (Open)"],
help="WiFi security/encryption type"
)
with col2:
wifi_password = st.text_input(
"Password",
type="password",
placeholder="Enter WiFi password",
help="Leave empty for open networks"
)
wifi_hidden = st.checkbox(
"Hidden Network",
value=False,
help="Check if this is a hidden network"
)
# Map security display to QR code format
security_map = {
"WPA/WPA2": "WPA",
"WEP": "WEP",
"None (Open)": "nopass"
}
# Generate WiFi QR string
if wifi_ssid:
wifi_qr = WiFiQRCode(
ssid=wifi_ssid,
password=wifi_password if wifi_security != "None (Open)" else "",
security=security_map[wifi_security],
hidden=wifi_hidden
)
qr_content = wifi_qr.to_string()
else:
qr_content = ""
# Logo upload (optional)
st.subheader("Logo (Optional)")
logo_file = st.file_uploader(
"Upload logo image",
type=["png", "jpg", "jpeg"],
help="Upload a logo to place in the center of the QR code"
)
if logo_file:
logo_size_ratio = st.slider(
"Logo Size",
min_value=0.1,
max_value=0.4,
value=0.3,
step=0.05,
help="Logo size as ratio of QR code size"
)
# Generate button
if st.button("Generate QR Code", type="primary", use_container_width=True):
if not qr_content:
if qr_mode == "URL":
st.error("Please enter a URL")
else:
st.error("Please enter WiFi network name (SSID)")
return
try:
with st.spinner("Generating QR code..."):
# Initialize generator
generator = QRCodeGenerator(qr_content, error_level)
# Generate QR code
if export_format == "SVG":
# Generate SVG directly
svg_data = generator.generate_svg(scale=svg_scale)
# Display preview (SVG as image)
st.success("QR Code generated successfully!")
st.image(svg_data, caption="QR Code Preview")
# Download button
file_prefix = "wifi_qr" if qr_mode == "WiFi" else "url_qr"
st.download_button(
label=f"Download QR Code (SVG)",
data=svg_data,
file_name=f"{file_prefix}.svg",
mime="image/svg+xml",
use_container_width=True
)
else:
# Generate raster image (PNG/JPEG)
qr_image = generator.generate_qr_image(
box_size=20,
border=4,
fill_color=fill_color,
back_color=back_color
)
# Add logo if provided
if logo_file:
logo_image = Image.open(logo_file)
qr_image = generator.add_logo(
qr_image,
logo_image,
logo_size_ratio
)
# Resize to target resolution
qr_image = generator.resize_image(
qr_image,
(target_size, target_size)
)
# Export image
image_data = generator.export_image(
qr_image,
format=export_format,
quality=95
)
# Display preview
st.success("QR Code generated successfully!")
st.image(qr_image, caption="QR Code Preview")
# Download button
mime_types = {
"PNG": "image/png",
"JPEG": "image/jpeg"
}
file_prefix = "wifi_qr" if qr_mode == "WiFi" else "url_qr"
st.download_button(
label=f"Download QR Code ({export_format}, {target_size}px)",
data=image_data,
file_name=f"{file_prefix}.{export_format.lower()}",
mime=mime_types[export_format],
use_container_width=True
)
# Display QR code info
with st.expander("QR Code Information"):
st.write(f"**Type:** {qr_mode}")
if qr_mode == "URL":
st.write(f"**URL:** {qr_content}")
else:
st.write(f"**Network:** {wifi_ssid}")
st.write(f"**Security:** {wifi_security}")
st.write(f"**Hidden:** {'Yes' if wifi_hidden else 'No'}")
st.write(f"**Error Correction:** {error_correction}")
st.write(f"**Format:** {export_format}")
if export_format in ["PNG", "JPEG"]:
st.write(f"**Resolution:** {target_size}x{target_size}px")
st.write(f"**Logo:** {'Yes' if logo_file else 'No'}")
except Exception as e:
st.error(f"Error generating QR code: {str(e)}")
st.exception(e)
# Footer
st.divider()
st.markdown(
"""
<div style='text-align: center; color: gray; font-size: 0.9em;'>
<p>Built with Streamlit | QR Code Generator v1.0</p>
<p>Tip: Use high error correction (H) when adding logos</p>
</div>
""",
unsafe_allow_html=True
)
if __name__ == "__main__":
main()

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
streamlit==1.31.1
qrcode[pil]==7.4.2
Pillow==10.2.0
segno==1.6.1
cairosvg==2.7.1

31
run-podman.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
# Script to build and run QR Code Generator with Podman
set -e
IMAGE_NAME="qr-code-generator"
CONTAINER_NAME="qr-code-generator-app"
PORT=8501
echo "Building QR Code Generator container..."
podman build -t $IMAGE_NAME .
echo "Stopping existing container if running..."
podman stop $CONTAINER_NAME 2>/dev/null || true
podman rm $CONTAINER_NAME 2>/dev/null || true
echo "Starting QR Code Generator container..."
podman run -d \
--name $CONTAINER_NAME \
-p $PORT:8501 \
--restart unless-stopped \
$IMAGE_NAME
echo "Container started successfully!"
echo "Access the application at: http://localhost:$PORT"
echo ""
echo "Useful commands:"
echo " podman logs $CONTAINER_NAME # View logs"
echo " podman stop $CONTAINER_NAME # Stop container"
echo " podman start $CONTAINER_NAME # Start container"
echo " podman rm $CONTAINER_NAME # Remove container"

1
utils/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""Utility modules for QR code generation."""

175
utils/qr_generator.py Normal file
View File

@@ -0,0 +1,175 @@
"""QR code generation utilities with logo overlay support."""
import io
from typing import Optional, Tuple
from PIL import Image, ImageDraw
import qrcode
import segno
class QRCodeGenerator:
"""Generate QR codes with optional logo overlay and multiple export formats."""
def __init__(self, url: str, error_correction: str = "H"):
"""
Initialize QR code generator.
Args:
url: The URL to encode in the QR code
error_correction: Error correction level (L, M, Q, H)
H (30%) recommended for logo overlay
"""
self.url = url
self.error_correction = self._get_error_correction(error_correction)
def _get_error_correction(self, level: str) -> int:
"""Convert error correction level string to qrcode constant."""
levels = {
"L": qrcode.constants.ERROR_CORRECT_L,
"M": qrcode.constants.ERROR_CORRECT_M,
"Q": qrcode.constants.ERROR_CORRECT_Q,
"H": qrcode.constants.ERROR_CORRECT_H,
}
return levels.get(level, qrcode.constants.ERROR_CORRECT_H)
def generate_qr_image(
self,
box_size: int = 10,
border: int = 4,
fill_color: str = "black",
back_color: str = "white"
) -> Image.Image:
"""
Generate QR code as PIL Image.
Args:
box_size: Size of each box in pixels
border: Border size in boxes
fill_color: QR code color
back_color: Background color
Returns:
PIL Image object of the QR code
"""
qr = qrcode.QRCode(
version=1,
error_correction=self.error_correction,
box_size=box_size,
border=border,
)
qr.add_data(self.url)
qr.make(fit=True)
img = qr.make_image(fill_color=fill_color, back_color=back_color)
return img.convert("RGB")
def add_logo(
self,
qr_image: Image.Image,
logo_image: Image.Image,
logo_size_ratio: float = 0.3
) -> Image.Image:
"""
Add logo to center of QR code.
Args:
qr_image: QR code image
logo_image: Logo image to overlay
logo_size_ratio: Logo size as ratio of QR code size (default 0.3)
Returns:
QR code image with logo overlay
"""
# Calculate logo size
qr_width, qr_height = qr_image.size
logo_max_size = int(min(qr_width, qr_height) * logo_size_ratio)
# Resize logo while maintaining aspect ratio
logo_image = logo_image.convert("RGBA")
logo_image.thumbnail((logo_max_size, logo_max_size), Image.Resampling.LANCZOS)
# Create white background for logo
logo_bg_size = int(logo_max_size * 1.1)
logo_bg = Image.new("RGB", (logo_bg_size, logo_bg_size), "white")
# Calculate positions
logo_bg_pos = (
(qr_width - logo_bg_size) // 2,
(qr_height - logo_bg_size) // 2
)
logo_pos = (
(logo_bg_size - logo_image.size[0]) // 2,
(logo_bg_size - logo_image.size[1]) // 2
)
# Paste logo background onto QR code
qr_image.paste(logo_bg, logo_bg_pos)
# Paste logo with transparency
absolute_logo_pos = (
logo_bg_pos[0] + logo_pos[0],
logo_bg_pos[1] + logo_pos[1]
)
qr_image.paste(logo_image, absolute_logo_pos, logo_image)
return qr_image
def generate_svg(self, scale: int = 10) -> bytes:
"""
Generate QR code as SVG.
Args:
scale: Scale factor for SVG
Returns:
SVG data as bytes
"""
qr = segno.make(self.url, error="h")
buffer = io.BytesIO()
qr.save(buffer, kind="svg", scale=scale, border=4)
return buffer.getvalue()
def resize_image(
self,
image: Image.Image,
target_size: Tuple[int, int]
) -> Image.Image:
"""
Resize image to target size.
Args:
image: PIL Image to resize
target_size: Target (width, height) in pixels
Returns:
Resized PIL Image
"""
return image.resize(target_size, Image.Resampling.LANCZOS)
def export_image(
self,
image: Image.Image,
format: str = "PNG",
quality: int = 95
) -> bytes:
"""
Export image to specified format.
Args:
image: PIL Image to export
format: Output format (PNG, JPEG, etc.)
quality: Quality for JPEG (1-100)
Returns:
Image data as bytes
"""
buffer = io.BytesIO()
if format.upper() == "JPEG":
# JPEG doesn't support transparency, convert to RGB
image = image.convert("RGB")
image.save(buffer, format=format, quality=quality, optimize=True)
else:
image.save(buffer, format=format, optimize=True)
return buffer.getvalue()

99
utils/wifi_qr.py Normal file
View File

@@ -0,0 +1,99 @@
"""WiFi QR code generation utilities."""
from typing import Optional
class WiFiQRCode:
"""Generate WiFi QR code strings in the standard format."""
def __init__(
self,
ssid: str,
password: str,
security: str = "WPA",
hidden: bool = False
):
"""
Initialize WiFi QR code generator.
Args:
ssid: WiFi network name (SSID)
password: WiFi password
security: Security type (WPA, WEP, or nopass)
hidden: Whether the network is hidden
"""
self.ssid = ssid
self.password = password
self.security = security.upper()
self.hidden = hidden
@staticmethod
def _escape_special_chars(text: str) -> str:
"""
Escape special characters for WiFi QR code format.
Special characters that need escaping: \ ; , " :
Args:
text: Text to escape
Returns:
Escaped text
"""
# Characters that need to be escaped with backslash
special_chars = ['\\', ';', ',', '"', ':']
result = text
for char in special_chars:
result = result.replace(char, f'\\{char}')
return result
def generate_wifi_string(self) -> str:
"""
Generate WiFi QR code string in standard format.
Format: WIFI:T:<WPA/WEP/nopass>;S:<SSID>;P:<password>;H:<true/false>;;
Returns:
Formatted WiFi QR code string
"""
# Escape special characters in SSID and password
escaped_ssid = self._escape_special_chars(self.ssid)
escaped_password = self._escape_special_chars(self.password)
# Build WiFi string
wifi_string = f"WIFI:T:{self.security};"
wifi_string += f"S:{escaped_ssid};"
# Only add password if security is not 'nopass'
if self.security.upper() != "NOPASS":
wifi_string += f"P:{escaped_password};"
# Add hidden flag
wifi_string += f"H:{'true' if self.hidden else 'false'};;"
return wifi_string
@staticmethod
def validate_security_type(security: str) -> bool:
"""
Validate security type.
Args:
security: Security type to validate
Returns:
True if valid, False otherwise
"""
valid_types = ["WPA", "WEP", "NOPASS"]
return security.upper() in valid_types
def to_string(self) -> str:
"""
Convert WiFi credentials to QR code string.
Returns:
WiFi QR code string
"""
return self.generate_wifi_string()