initial commit
This commit is contained in:
70
Dockerfile
Normal file
70
Dockerfile
Normal 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
301
README.md
Normal 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
285
app.py
Normal 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
5
requirements.txt
Normal 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
31
run-podman.sh
Executable 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
1
utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Utility modules for QR code generation."""
|
||||||
175
utils/qr_generator.py
Normal file
175
utils/qr_generator.py
Normal 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
99
utils/wifi_qr.py
Normal 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()
|
||||||
Reference in New Issue
Block a user