File size: 9,938 Bytes
83d8a45 c4f2efa ad58a48 a849d04 89973db 3d661a3 83d8a45 4351bdd 6fef8b3 83d8a45 6fef8b3 83d8a45 6fef8b3 83d8a45 c4f2efa 83d8a45 3967fb3 83d8a45 3967fb3 83d8a45 89973db 83d8a45 471964f 83d8a45 66af2f1 83d8a45 a9f2340 83d8a45 0cc9270 a849d04 83d8a45 3d661a3 83d8a45 3d661a3 83d8a45 3d661a3 4351bdd 3d661a3 83d8a45 3d661a3 83d8a45 3d661a3 83d8a45 3d661a3 83d8a45 3d661a3 83d8a45 d12fea6 83d8a45 d12fea6 83d8a45 3d661a3 4351bdd d12fea6 3d661a3 83d8a45 0cc9270 83d8a45 ad58a48 0cc9270 83d8a45 4e9c018 83d8a45 4e9c018 83d8a45 4351bdd 83d8a45 ad58a48 83d8a45 8b0ee47 83d8a45 471964f 83d8a45 4351bdd 471964f 4351bdd 83d8a45 a849d04 83d8a45 3d661a3 4351bdd ad58a48 8b0ee47 83d8a45 8b0ee47 83d8a45 8b0ee47 4351bdd 3d661a3 83d8a45 4351bdd d12fea6 83d8a45 d12fea6 83d8a45 d12fea6 83d8a45 fef25f1 83d8a45 3967fb3 83d8a45 6fef8b3 83d8a45 3d661a3 83d8a45 6fef8b3 83d8a45 4351bdd 83d8a45 4351bdd 6fef8b3 83d8a45 4351bdd 83d8a45 4351bdd 3d661a3 83d8a45 4351bdd 3d661a3 34fa595 83d8a45 3d661a3 6fef8b3 83d8a45 4351bdd 83d8a45 3d661a3 4351bdd ad58a48 83d8a45 c4f2efa 83d8a45 3d661a3 83d8a45 3d661a3 83d8a45 3d661a3 7c94bbc 83d8a45 ad58a48 83d8a45 471964f 83d8a45 471964f 83d8a45 7c94bbc ad58a48 83d8a45 4351bdd d12fea6 ad58a48 c4f2efa ad58a48 8b0ee47 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# 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() |