Image_manager / text_overlay.py
bakyt92's picture
refactor text_overlay.py
e85c04d
from PIL import Image, ImageDraw, ImageFont
# Add this function to get available fonts with fallbacks
def get_font_path(font_name, font_size):
"""Get font path for the selected font with fallbacks"""
font_mappings = {
"Impact": [
"impact.ttf",
"Impact.ttf",
"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
],
"Arial Bold": [
"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
"arial-bold.ttf"
],
"Times Bold": [
"/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf",
"times-bold.ttf"
],
"Comic Sans": [
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
"comic.ttf"
],
"Bebas Neue": [
"/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed-Bold.ttf",
"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf",
"bebas-neue.ttf"
]
}
# Get font paths for the selected font
font_paths = font_mappings.get(font_name, [])
# Try each path
for font_path in font_paths:
try:
font = ImageFont.truetype(font_path, font_size)
print(f"SUCCESS: Loaded {font_name} from {font_path}")
return font
except (OSError, IOError):
continue
# If no specific font found, try default system fonts
default_paths = [
"DejaVuSans-Bold.ttf",
"arial.ttf",
"/System/Library/Fonts/Arial.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
]
for font_path in default_paths:
try:
font = ImageFont.truetype(font_path, font_size)
print(f"FALLBACK: Loaded default font from {font_path} for {font_name}")
return font
except (OSError, IOError):
continue
# Final fallback
try:
font = ImageFont.load_default()
print(f"FINAL FALLBACK: Using system default for {font_name}")
return font
except Exception:
print(f"ERROR: Could not load any font for {font_name}")
return None
# Function to create template images
def create_pattern_template(pattern, width=200, height=120):
"""Create a visual template showing text placement for each pattern"""
# Create black background
img = Image.new('RGB', (width, height), color='black')
draw = ImageDraw.Draw(img)
# Define positions based on pattern
positions = []
if pattern == "2-lines-top":
positions = [(width // 2, int(height * 0.15)), (width // 2, int(height * 0.35))]
elif pattern == "2-lines-center":
positions = [(width // 2, int(height * 0.35)), (width // 2, int(height * 0.65))]
elif pattern == "2-lines-bottom":
positions = [(width // 2, int(height * 0.65)), (width // 2, int(height * 0.85))]
elif pattern == "3-lines-top":
positions = [(width // 2, int(height * 0.15)), (width // 2, int(height * 0.35)), (width // 2, int(height * 0.55))]
elif pattern == "3-lines-center":
positions = [(width // 2, int(height * 0.25)), (width // 2, int(height * 0.50)), (width // 2, int(height * 0.75))]
elif pattern == "3-lines-bottom":
positions = [(width // 2, int(height * 0.45)), (width // 2, int(height * 0.65)), (width // 2, int(height * 0.85))]
# Draw white rectangles to represent text boxes
for i, (x, y) in enumerate(positions):
# Draw white rectangle as text placeholder
box_width = 60
box_height = 12
left = x - box_width // 2
top = y - box_height // 2
right = x + box_width // 2
bottom = y + box_height // 2
draw.rectangle([left, top, right, bottom], fill='white', outline='white')
return img
def get_pattern_template(pattern):
"""Get template for pattern - create on demand to avoid initialization issues"""
return create_pattern_template(pattern)
# Text Overlay Functions
def add_text_to_image(img, pattern, line1, line2, line3, font_size, color, add_outline, font_name):
"""Overlay 2- or 3-line text on the image in a preset layout."""
print("=== DEBUG: add_text_to_image called ===")
print(f"Input image: {type(img)}")
print(f"Pattern: {pattern}")
print(f"Lines: [{line1!r}, {line2!r}, {line3!r}]")
print(f"Font size: {font_size}")
print(f"Font name: {font_name}")
print(f"Color: {color!r}")
print(f"Add outline: {add_outline}")
if img is None:
print("ERROR: No image provided")
return None, "Please supply an image first."
try:
# Create a working copy
print(f"Original image mode: {img.mode}, size: {img.size}")
image = img.convert("RGB")
print(f"Converted image mode: {image.mode}")
draw = ImageDraw.Draw(image)
print("ImageDraw created successfully")
# UPDATED: Use selected font instead of hardcoded paths
font = get_font_path(font_name, font_size)
if font is None:
return None, f"Could not load font: {font_name}"
w, h = image.size
print(f"Image dimensions: {w}x{h}")
# Pattern matching (same as before)
positions = []
detected_pattern = "unknown"
pattern_lower = pattern.lower()
if ("2-lines-top" in pattern_lower) or ("2 lines - top" in pattern_lower):
positions = [(w // 2, int(h * 0.10)), (w // 2, int(h * 0.20))]
detected_pattern = "2-lines-top"
elif ("2-lines-bottom" in pattern_lower) or ("2 lines - bottom" in pattern_lower):
positions = [(w // 2, int(h * 0.80)), (w // 2, int(h * 0.90))]
detected_pattern = "2-lines-bottom"
elif ("2-lines-center" in pattern_lower) or ("2 lines - center" in pattern_lower):
positions = [(w // 2, int(h * 0.45)), (w // 2, int(h * 0.55))]
detected_pattern = "2-lines-center"
elif ("3-lines-top" in pattern_lower) or ("3 lines - top" in pattern_lower):
positions = [(w // 2, int(h * 0.10)), (w // 2, int(h * 0.20)), (w // 2, int(h * 0.30))]
detected_pattern = "3-lines-top"
elif ("3-lines-center" in pattern_lower) or ("3 lines - center" in pattern_lower):
positions = [(w // 2, int(h * 0.40)), (w // 2, int(h * 0.50)), (w // 2, int(h * 0.60))]
detected_pattern = "3-lines-center"
elif ("3-lines-bottom" in pattern_lower) or ("3 lines - bottom" in pattern_lower):
positions = [(w // 2, int(h * 0.70)), (w // 2, int(h * 0.80)), (w // 2, int(h * 0.90))]
detected_pattern = "3-lines-bottom"
else:
print(f"WARNING: Unknown pattern '{pattern}', using 2-lines-center as default")
positions = [(w // 2, int(h * 0.45)), (w // 2, int(h * 0.55))]
detected_pattern = "2-lines-center (default)"
print(f"Detected pattern: {detected_pattern}")
print(f"Text positions calculated: {positions}")
# Color parsing (same as before)
original_color = color
final_color = (255, 255, 255)
if isinstance(color, str):
if color.startswith('rgba(') and color.endswith(')'):
print(f"Parsing RGBA string: {color}")
try:
rgba_part = color[5:-1]
values = [float(x.strip()) for x in rgba_part.split(',')]
if len(values) >= 3:
final_color = (int(round(values[0])), int(round(values[1])), int(round(values[2])))
print(f"Successfully parsed RGBA {color} to RGB: {final_color}")
except (ValueError, IndexError) as e:
print(f"Error parsing RGBA {color}: {e}, using white")
final_color = (255, 255, 255)
elif color.startswith('#'):
print(f"Converting hex color {color}")
hex_color = color.lstrip('#')
if len(hex_color) == 6:
try:
final_color = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
print(f"Successfully converted hex {color} to RGB: {final_color}")
except ValueError as e:
print(f"Invalid hex color {color}: {e}, using white")
final_color = (255, 255, 255)
print(f"Final color for drawing: {final_color}")
# Draw text (same logic as before)
lines = [line1, line2, line3]
print(f"All input lines: {lines}")
active_lines = []
for i in range(len(positions)):
if i < len(lines) and lines[i] and lines[i].strip():
active_lines.append((positions[i], lines[i].strip()))
print(f"Active lines to draw: {len(active_lines)} out of {len(positions)} positions")
text_drawn = False
for i, ((x, y), txt) in enumerate(active_lines):
print(f"Drawing line {i+1}: '{txt}' at position ({x}, {y}) with font {font_name}")
# Optional outline/stroke
if add_outline:
stroke_width = max(1, font_size // 20)
print(f"Adding outline with stroke width: {stroke_width}")
# Draw text outline (black)
for dx in [-stroke_width, 0, stroke_width]:
for dy in [-stroke_width, 0, stroke_width]:
if dx != 0 or dy != 0:
try:
draw.text((x + dx, y + dy), txt, fill=(0, 0, 0), font=font, anchor="mm")
except TypeError:
try:
bbox = draw.textbbox((0, 0), txt, font=font)
text_w = bbox[2] - bbox[0]
text_h = bbox[3] - bbox[1]
draw.text((x - text_w//2 + dx, y - text_h//2 + dy), txt, fill=(0, 0, 0), font=font)
except Exception:
draw.text((x + dx, y + dy), txt, fill=(0, 0, 0), font=font)
# Draw main text
try:
draw.text((x, y), txt, fill=final_color, font=font, anchor="mm")
print(f"SUCCESS: Main text drawn with {font_name} at ({x}, {y})")
except TypeError:
try:
bbox = draw.textbbox((0, 0), txt, font=font)
text_w = bbox[2] - bbox[0]
text_h = bbox[3] - bbox[1]
fallback_x = x - text_w//2
fallback_y = y - text_h//2
draw.text((fallback_x, fallback_y), txt, fill=final_color, font=font)
print(f"SUCCESS: Fallback text drawn with {font_name} at ({fallback_x}, {fallback_y})")
except Exception:
draw.text((x, y), txt, fill=final_color, font=font)
print(f"SUCCESS: Basic text drawn with {font_name} at ({x}, {y})")
text_drawn = True
print(f"Completed drawing line {i+1}")
if not text_drawn:
print("No text was drawn - all lines were empty")
return img, "No text to add (all lines were empty)"
print("=== Text drawing completed successfully ===")
return image, f"✅ {len(active_lines)} lines added! Font: {font_name}, Pattern: {detected_pattern}"
except Exception as e:
print(f"CRITICAL ERROR in add_text_to_image: {e}")
import traceback
traceback.print_exc()
return None, f"Error adding text: {str(e)}"