GYMPDF / app.py
lokesh341's picture
Update app.py
83d8a45 verified
# app.py - Gym Workout Generator (Fixed Video + Generated Templates in PDF/Video)
import gradio as gr
from fpdf import FPDF
import os
import re
from datetime import datetime
import tempfile
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont
# Video generation
try:
from moviepy.editor import ImageClip, concatenate_videoclips, TextClip, CompositeVideoClip, ColorClip
from moviepy.video.fx.all import crop
VIDEO_ENABLED = True
except ImportError:
VIDEO_ENABLED = False
print("MoviePy not available - using basic video generation")
class GymWorkoutPDF(FPDF):
def footer(self):
self.set_y(-10)
self.set_font('Arial', 'I', 7)
self.cell(0, 8, f'GYM WORKOUT PLAN | Page {self.page_no()}', 0, 0, 'C')
def clean_text(text, max_length=45):
if not text:
return "Workout"
text = str(text)
emoji_pattern = re.compile(
"["
u"\U0001F600-\U0001F64F"
u"\U0001F300-\U0001F5FF"
u"\U0001F680-\U0001F6FF"
u"\U0001F1E0-\U0001F1FF"
"]+",
flags=re.UNICODE
)
text = emoji_pattern.sub('', text)
replacements = {
'–': '-', 'β€”': '-', 'β€œ': '"', '”': '"', 'β€’': '*', 'Β°': 'deg'
}
for old, new in replacements.items():
text = text.replace(old, new)
text = re.sub(r'[^\x00-\x7F\u0020-\u007E]', '', text)
text = re.sub(r'\s+', ' ', text.strip())
return text[:max_length]
def create_screenshot_template(image_path, workout_name, workout_num, main_title):
try:
# Load image
img = Image.open(image_path)
img = img.resize((800, 600), Image.Resampling.LANCZOS)
# Create template
template = Image.new('RGB', (800, 600), color=(34, 139, 34))
draw = ImageDraw.Draw(template)
# Fallback fonts
try:
font_large = ImageFont.truetype("arial.ttf", 48)
font_medium = ImageFont.truetype("arial.ttf", 36)
font_small = ImageFont.truetype("arial.ttf", 24)
except:
font_large = ImageFont.load_default()
font_medium = ImageFont.load_default()
font_small = ImageFont.load_default()
clean_name = clean_text(workout_name)
clean_title = clean_text(main_title)
# Add text
draw.text((50, 20), f"WORKOUT {workout_num}", fill="white", font=font_large)
draw.text((50, 80), clean_name.upper(), fill="yellow", font=font_medium)
draw.text((50, 120), f"PROGRAM: {clean_title}", fill="white", font=font_small)
# Paste image
img_resized = img.resize((700, 400), Image.Resampling.LANCZOS)
template.paste(img_resized, (50, 150))
# Footer
draw.text((50, 540), "Workout Execution Plan - Print & Train!", fill="white", font=font_small)
# Save to temp
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp_file:
template_path = tmp_file.name
template.save(template_path, "JPEG", quality=95)
return template_path
except Exception as e:
print(f"Template error: {e}")
return image_path
def generate_content(main_title, workout_names, image_files):
if not main_title.strip():
return None, None, "Error: Main Title Required!"
names_list = [n.strip() for n in workout_names.split(',') if n.strip()]
if not names_list:
return None, None, "Error: Add Workout Names!"
if len(names_list) > 30:
return None, None, "Error: Max 30 Workouts!"
if not image_files:
return None, None, "Error: Upload at least 1 image!"
available_images = len(image_files)
status_msg = f"Using {available_images} images for {len(names_list)} workouts (placeholders for missing)"
try:
clean_main_title = clean_text(main_title, 50)
pdf = GymWorkoutPDF()
pdf.set_margins(18, 18, 18)
# PDF Title
pdf.add_page()
pdf.set_font('Arial', 'B', 20)
pdf.set_text_color(0, 128, 0)
pdf.cell(0, 18, "GYM WORKOUT PROGRAM", ln=1, align='C')
pdf.set_font('Arial', 'B', 16)
pdf.cell(0, 15, clean_main_title.upper(), ln=1, align='C')
pdf.ln(15)
pdf.set_font('Arial', '', 11)
pdf.cell(0, 10, f"TOTAL WORKOUTS: {len(names_list)}", ln=1, align='C')
pdf.cell(0, 10, f"CREATED: {datetime.now().strftime('%d/%m/%Y')}", ln=1, align='C')
template_paths = []
for i, workout_name in enumerate(names_list):
pdf.add_page()
pdf.set_font('Arial', 'B', 16)
pdf.set_text_color(0, 128, 0)
pdf.cell(0, 14, f"WORKOUT {i+1}", ln=1, align='C')
clean_name = clean_text(workout_name, 40)
pdf.set_font('Arial', 'B', 14)
pdf.multi_cell(0, 12, clean_name.upper(), align='C')
img_path = image_files[i] if i < available_images else None
template_path = None
if img_path and os.path.exists(img_path):
template_path = create_screenshot_template(
img_path, workout_name, i+1, main_title
)
template_paths.append(template_path)
# Add to PDF
img_width = 110
x_pos = (pdf.w - img_width) / 2
pdf.image(template_path, x=x_pos, y=pdf.get_y(), w=img_width, h=130)
pdf.set_draw_color(0, 128, 0)
pdf.rect(x_pos, pdf.get_y(), img_width, 130, 'D')
else:
pdf.set_fill_color(240, 248, 240)
pdf.set_draw_color(0, 128, 0)
pdf.rect(x_pos, pdf.get_y(), img_width, 130, 'FD')
pdf.set_font('Arial', 'B', 11)
pdf.set_xy(x_pos + 10, pdf.get_y() + 50)
pdf.cell(90, 10, f"WORKOUT {i+1}", ln=1, align='C')
# Save PDF to temp
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp_pdf:
pdf_path = tmp_pdf.name
pdf.output(pdf_path)
# VIDEO GENERATION
video_path = None
if VIDEO_ENABLED and template_paths:
try:
clips = []
hd_width, hd_height = 1920, 1080
duration_per_slide = 6
# Title slide
title_clip = ColorClip(size=(hd_width, hd_height), color=(0, 128, 0)).set_duration(4)
title_text = TextClip(
f"GYM WORKOUT PROGRAM\n{clean_main_title.upper()}",
fontsize=100, color='white', font='Arial-Bold'
).set_position('center').set_duration(4)
title_slide = CompositeVideoClip([title_clip, title_text])
clips.append(title_slide)
# Workout slides
for i, template_img in enumerate(template_paths):
if os.path.exists(template_img):
img_clip = ImageClip(template_img).resize(height=hd_height)
img_clip = img_clip.set_duration(duration_per_slide)
img_clip = img_clip.fx(crop, x1=100, y1=50, x2=hd_width-100, y2=hd_height-100)
clips.append(img_clip)
if clips:
video = concatenate_videoclips(clips, method="compose")
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp_video:
video_path = tmp_video.name
video.write_videofile(
video_path,
fps=24,
codec='libx264',
audio=False,
verbose=False,
logger=None
)
video.close()
except Exception as e:
print(f"Video error: {e}")
video_path = None
# Cleanup
for path in template_paths:
try:
os.remove(path)
except:
pass
video_status = "HD Video Generated!" if video_path else "Video Generation Failed - Check MoviePy/FFmpeg"
success_msg = f"""
SUCCESS! Generated for {len(names_list)} workouts!
PDF with generated screenshot templates
{video_status}
Images processed: {available_images}
"""
return pdf_path, video_path, success_msg
except Exception as e:
return None, None, f"Error: {str(e)}"
# UI
with gr.Blocks(title="Gym Workout Generator") as demo:
gr.Markdown("# Gym Workout Generator\nPDF + HD Video + Screenshot Templates")
gr.Markdown("""
### How to Use:
1. Enter Title
2. Add Workout Names (comma separated)
3. Upload Images/Screenshots (optional - placeholders for missing)
4. Get PDF + Video with generated templates!
Tip: Uploaded images get auto-converted to gym screenshot templates!
""")
main_title = gr.Textbox(label="MAIN PROGRAM TITLE *")
workout_names = gr.Textbox(label="WORKOUT NAMES * (Comma separated)")
images_input = gr.File(label="Images/Screenshots (Optional)", file_count="multiple", file_types=["image"])
generate_btn = gr.Button("Generate")
pdf_output = gr.File(label="PDF")
video_output = gr.File(label="HD Video")
status = gr.Markdown("Ready...")
generate_btn.click(
fn=generate_content,
inputs=[main_title, workout_names, images_input],
outputs=[pdf_output, video_output, status]
)
if __name__ == "__main__":
demo.launch()