QSL Card Generator

Below is an example of a complete Python program that may meet the requirements:

  1. Template Selection: The user can either choose a default QSL card template or provide a custom image file. In the custom case, the program prompts the user for the coordinates where each piece of form data should be placed.
  2. Form Data: The user is prompted to input details (such as callsign, date, frequency, location, etc.) that are then overlaid on the chosen template.
  3. Image Generation: Using the Python Imaging Library (PIL/Pillow), the program opens the template image and draws the provided text at the designated coordinates. The output is saved as a new image (e.g., generated_qsl.jpg).
  4. Email Sending: The program can email the generated QSL card image as an attachment using Python’s built-in SMTP libraries. (In this sample, Gmail’s SMTP server is used with SSL.)

Below is the full code with inline comments explaining each section. You can modify the fields or add more according to your needs:


#!/usr/bin/env python3
import os
from PIL import Image, ImageDraw, ImageFont
import smtplib
from email.message import EmailMessage

def generate_qsl_card(template_path, output_path, form_data, coordinates, font_path="arial.ttf", font_size=24):
    """
    Generates a QSL card by overlaying text onto a template image.

    Parameters:
      - template_path: path of the image template.
      - output_path: where to save the generated QSL card.
      - form_data: dictionary of field names and user-provided data.
      - coordinates: dictionary of field names and (x, y) coordinates.
      - font_path: path to the TrueType font file to use.
      - font_size: size of the text.
    
    Returns:
      - The output file path of the generated image.
    """
    try:
        base = Image.open(template_path).convert("RGB")
    except Exception as e:
        print(f"Error opening template image: {e}")
        return None

    draw = ImageDraw.Draw(base)
    try:
        font = ImageFont.truetype(font_path, font_size)
    except Exception as e:
        print(f"Error loading font '{font_path}': {e}")
        print("Falling back to default font.")
        font = ImageFont.load_default()

    for field, text in form_data.items():
        if field in coordinates:
            pos = coordinates[field]
            draw.text(pos, text, font=font, fill="black")
        else:
            print(f"Warning: No coordinates provided for field '{field}'.")

    base.save(output_path)
    return output_path

def send_email(sender_email, sender_password, recipient_email, subject, body, attachment_path):
    """
    Sends an email with an attachment using Gmail's SMTP server.
    
    Parameters:
      - sender_email: your email address.
      - sender_password: your email password (or app-specific password).
      - recipient_email: the email address of the recipient.
      - subject: subject line of the email.
      - body: body text of the email.
      - attachment_path: path to the file that will be attached.
    """
    msg = EmailMessage()
    msg['Subject'] = subject
    msg['From'] = sender_email
    msg['To'] = recipient_email
    msg.set_content(body)
    
    try:
        with open(attachment_path, 'rb') as f:
            file_data = f.read()
            file_name = os.path.basename(attachment_path)
        # Assuming the image is a JPEG; adjust type if needed
        msg.add_attachment(file_data, maintype="image", subtype="jpeg", filename=file_name)
    except Exception as e:
        print(f"Error reading attachment file: {e}")
        return False

    try:
        # Using Gmail's SMTP server with SSL; change these if you use another email provider
        with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
            smtp.login(sender_email, sender_password)
            smtp.send_message(msg)
        print("Email sent successfully!")
        return True
    except Exception as e:
        print(f"Error sending email: {e}")
        return False

def main():
    print("Welcome to the QSL Card Generator!")
    
    # Template Selection
    choice = input("Enter 'default' to use the default template or 'custom' to use your own: ").strip().lower()
    if choice == 'default':
        # Ensure that default_template.jpg exists in the working directory.
        template_path = "default_template.jpg"
        # Coordinates for default template fields (adjust these as per your template design)
        coordinates = {
            "callsign": (100, 150),
            "date": (100, 200),
            "frequency": (100, 250),
            "location": (100, 300)
        }
    elif choice == 'custom':
        template_path = input("Enter the full path to your custom template image: ").strip()
        coordinates = {}
        print("Enter coordinates (x y) for each field. For example, type '100 150' for callsign position.")
        for field in ["callsign", "date", "frequency", "location"]:
            coords = input(f"Enter x and y coordinates for '{field}': ").strip().split()
            if len(coords) != 2:
                print("Invalid coordinates provided. Exiting.")
                return
            try:
                coordinates[field] = (int(coords[0]), int(coords[1]))
            except ValueError:
                print("Coordinates must be integers. Exiting.")
                return
    else:
        print("Invalid choice. Exiting.")
        return

    # Gather Form Data
    form_data = {}
    form_data["callsign"] = input("Enter your callsign: ").strip()
    form_data["date"] = input("Enter the contact date (YYYY-MM-DD): ").strip()
    form_data["frequency"] = input("Enter the frequency (MHz): ").strip()
    form_data["location"] = input("Enter your location: ").strip()
    
    output_path = "generated_qsl.jpg"
    font_path = "arial.ttf"  # Make sure the font file exists or use an alternative TTF font file.
    font_size = 24

    print("Generating your QSL card...")
    generated_card = generate_qsl_card(template_path, output_path, form_data, coordinates, font_path, font_size)
    if generated_card:
        print(f"QSL card generated successfully at '{generated_card}'.")
    else:
        print("Failed to generate QSL card.")
        return

    # Email Option
    email_choice = input("Would you like to email the QSL card? (y/n): ").strip().lower()
    if email_choice == 'y':
        sender_email = input("Enter your email address (sender): ").strip()
        sender_password = input("Enter your email password (or app-specific password): ").strip()
        recipient_email = input("Enter recipient email address: ").strip()
        subject = "Your Electronic QSL Card"
        body = "Attached is the QSL card you generated using our service."
        send_email(sender_email, sender_password, recipient_email, subject, body, generated_card)
    else:
        print("Email skipped. Your QSL card is ready.")

if __name__ == "__main__":
    main()

Explanation

  1. Template and Customization:

    • When the program runs, it first asks if you want to use a default template or provide your own.
    • In the default case, the program expects a file called default_template.jpg in the current directory and uses preset coordinates to position the text.
    • For a custom template, you provide your image file’s path, and the program prompts you to enter the coordinates for each field (callsign, date, frequency, location). This makes it flexible enough for any custom design.
  2. Form Data Collection:

    • The program collects user input for various fields (for example, callsign and contact details) that are to be inserted on the card.
  3. Image Generation with PIL:

    • The generate_qsl_card function opens the template image, uses ImageDraw to overlay text input at the designated coordinates, and saves the resulting image.
  4. Email Sending:

    • If you choose to email your electronic QSL card, the program uses Python’s smtplib and EmailMessage to send the generated image as an attachment. In this example, Gmail’s SMTP server is used (remember that you might need to generate an app-specific password depending on your security settings).
  5. Modifications and Next Steps:

    • You can enhance this sample program by adding error handling, a graphic user interface (using frameworks like Tkinter or Flask), and even support for multiple QSL card templates.
    • You may also incorporate configuration files (using JSON or YAML) to save template setups and coordinate mappings for reuse.

This example is a starting point that meets the outlined requirements. There’s plenty of room to extend the functionality—for instance, adding a preview feature for the QSL card or supporting other file formats for the template image. Enjoy coding and feel free to ask if you’d like to explore further enhancements or modifications!