# 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()