Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# app.py - Professional Gym Workout Generator (
|
| 2 |
|
| 3 |
import gradio as gr
|
| 4 |
from fpdf import FPDF
|
|
@@ -13,7 +13,7 @@ import numpy as np
|
|
| 13 |
try:
|
| 14 |
from moviepy.editor import (
|
| 15 |
ImageClip, concatenate_videoclips, TextClip, CompositeVideoClip,
|
| 16 |
-
ColorClip, vfx
|
| 17 |
)
|
| 18 |
VIDEO_ENABLED = True
|
| 19 |
except ImportError:
|
|
@@ -35,12 +35,11 @@ def clean_text(text, max_length=60):
|
|
| 35 |
if not text:
|
| 36 |
return "Workout Session"
|
| 37 |
text = str(text)
|
| 38 |
-
# Remove emojis
|
| 39 |
emoji_pattern = re.compile("["
|
| 40 |
-
u"\U0001F600-\U0001F64F"
|
| 41 |
-
u"\U0001F300-\U0001F5FF"
|
| 42 |
-
u"\U0001F680-\U0001F6FF"
|
| 43 |
-
u"\U0001F1E0-\U0001F1FF"
|
| 44 |
"]+", flags=re.UNICODE)
|
| 45 |
text = emoji_pattern.sub('', text)
|
| 46 |
text = re.sub(r'[^\w\s\-.,!?\(\)]', '', text)
|
|
@@ -53,12 +52,9 @@ def create_premium_template(image_path, workout_name, workout_num, main_title):
|
|
| 53 |
img = Image.open(image_path)
|
| 54 |
img = img.resize((1920, 1080), Image.Resampling.LANCZOS)
|
| 55 |
|
| 56 |
-
|
| 57 |
-
template = Image.new('RGB', (1920, 1080), color=(25, 25, 25)) # Dark background
|
| 58 |
-
|
| 59 |
draw = ImageDraw.Draw(template)
|
| 60 |
|
| 61 |
-
# Try premium fonts
|
| 62 |
try:
|
| 63 |
font_title = ImageFont.truetype("arial.ttf", 80)
|
| 64 |
font_workout = ImageFont.truetype("arial.ttf", 60)
|
|
@@ -71,25 +67,18 @@ def create_premium_template(image_path, workout_name, workout_num, main_title):
|
|
| 71 |
clean_name = clean_text(workout_name)
|
| 72 |
clean_title = clean_text(main_title)
|
| 73 |
|
| 74 |
-
# Header overlay
|
| 75 |
header_bg = Image.new('RGBA', (1920, 200), (0, 100, 0, 200))
|
| 76 |
template.paste(header_bg, (0, 0), header_bg)
|
| 77 |
|
| 78 |
-
# Workout title
|
| 79 |
draw.text((100, 50), f"WORKOUT SESSION {workout_num}", fill="white", font=font_title)
|
| 80 |
draw.text((100, 130), clean_name.upper(), fill="#FFD700", font=font_workout)
|
| 81 |
-
|
| 82 |
-
# Program info
|
| 83 |
draw.text((100, 200), f"PROGRAM: {clean_title}", fill="white", font=font_sub)
|
| 84 |
|
| 85 |
-
# Main image with border
|
| 86 |
img_resized = img.resize((1600, 800), Image.Resampling.LANCZOS)
|
| 87 |
template.paste(img_resized, (160, 300))
|
| 88 |
|
| 89 |
-
# Green border
|
| 90 |
draw.rectangle([160, 300, 1760, 1100], outline="#00FF00", width=5)
|
| 91 |
|
| 92 |
-
# Footer with workout details
|
| 93 |
footer_bg = Image.new('RGBA', (1920, 150), (0, 0, 0, 180))
|
| 94 |
template.paste(footer_bg, (0, 930), footer_bg)
|
| 95 |
|
|
@@ -97,7 +86,6 @@ def create_premium_template(image_path, workout_name, workout_num, main_title):
|
|
| 97 |
draw.text((100, 990), "SETS | REPS | REST | PROGRESS", fill="white", font=font_sub)
|
| 98 |
draw.text((100, 1030), "PRINT β’ TRAIN β’ TRACK β’ REPEAT", fill="#FFD700", font=font_sub)
|
| 99 |
|
| 100 |
-
# Save HD template
|
| 101 |
template_path = f"hd_template_{workout_num}.jpg"
|
| 102 |
template.save(template_path, "JPEG", quality=95, optimize=False)
|
| 103 |
return template_path
|
|
@@ -145,13 +133,10 @@ def generate_professional_content(main_title, workout_names, image_files):
|
|
| 145 |
pdf.cell(0, 10, f"CREATED: {datetime.now().strftime('%d/%m/%Y %H:%M')}", ln=1, align='C')
|
| 146 |
pdf.cell(0, 10, "PROFESSIONAL GYM CERTIFIED", ln=1, align='C')
|
| 147 |
|
| 148 |
-
# Professional workout pages
|
| 149 |
hd_templates = []
|
| 150 |
|
| 151 |
for i, workout_name in enumerate(names_list):
|
| 152 |
pdf.add_page()
|
| 153 |
-
|
| 154 |
-
# Workout header
|
| 155 |
pdf.set_font('Arial', 'B', 20)
|
| 156 |
pdf.set_text_color(0, 128, 0)
|
| 157 |
pdf.cell(0, 18, f"SESSION {i+1}", ln=1, align='C')
|
|
@@ -160,7 +145,6 @@ def generate_professional_content(main_title, workout_names, image_files):
|
|
| 160 |
pdf.set_font('Arial', 'B', 16)
|
| 161 |
pdf.multi_cell(0, 14, clean_name.upper(), align='C')
|
| 162 |
|
| 163 |
-
# Professional workout template
|
| 164 |
pdf.ln(5)
|
| 165 |
pdf.set_font('Arial', 'B', 14)
|
| 166 |
pdf.set_text_color(0, 128, 0)
|
|
@@ -188,7 +172,6 @@ def generate_professional_content(main_title, workout_names, image_files):
|
|
| 188 |
pdf.ln(6)
|
| 189 |
pdf.cell(0, 8, detail, ln=1, align='L')
|
| 190 |
|
| 191 |
-
# Image/Template
|
| 192 |
img_path = image_files[i] if i < available_images else None
|
| 193 |
template_path = None
|
| 194 |
|
|
@@ -198,7 +181,6 @@ def generate_professional_content(main_title, workout_names, image_files):
|
|
| 198 |
)
|
| 199 |
hd_templates.append(template_path)
|
| 200 |
|
| 201 |
-
# Add to PDF
|
| 202 |
img_width = 140
|
| 203 |
x_pos = (pdf.w - img_width) / 2
|
| 204 |
try:
|
|
@@ -214,19 +196,16 @@ def generate_professional_content(main_title, workout_names, image_files):
|
|
| 214 |
pdf.set_text_color(0, 128, 0)
|
| 215 |
pdf.cell(0, 10, "SIGNATURE: ________________ Date: ________", ln=1, align='C')
|
| 216 |
|
| 217 |
-
# Save PDF
|
| 218 |
timestamp = int(datetime.now().timestamp())
|
| 219 |
pdf_path = f"professional_workout_{timestamp}.pdf"
|
| 220 |
pdf.output(pdf_path)
|
| 221 |
|
| 222 |
-
# HD VIDEO GENERATION
|
| 223 |
video_path = None
|
| 224 |
if VIDEO_ENABLED and hd_templates:
|
| 225 |
try:
|
| 226 |
clips = []
|
| 227 |
-
duration_per_slide = 8
|
| 228 |
|
| 229 |
-
# Title slide
|
| 230 |
title_bg = ColorClip(size=(1920, 1080), color=(0, 50, 0)).set_duration(5)
|
| 231 |
title_text = TextClip(
|
| 232 |
f"PROFESSIONAL GYM PROGRAM\n{clean_main_title.upper()}",
|
|
@@ -236,39 +215,23 @@ def generate_professional_content(main_title, workout_names, image_files):
|
|
| 236 |
title_slide = CompositeVideoClip([title_bg, title_text]).set_duration(5)
|
| 237 |
clips.append(title_slide)
|
| 238 |
|
| 239 |
-
# Workout slides with smooth transitions
|
| 240 |
for template_img in hd_templates:
|
| 241 |
if os.path.exists(template_img):
|
| 242 |
-
# Load HD template
|
| 243 |
img_clip = (ImageClip(template_img)
|
| 244 |
.set_duration(duration_per_slide)
|
| 245 |
.resize(height=1080)
|
| 246 |
.fx(vfx.fadein, 1)
|
| 247 |
.fx(vfx.fadeout, 1))
|
| 248 |
-
|
| 249 |
-
# Add zoom effect
|
| 250 |
-
img_clip = img_clip.fx(vfx.zoom_in, duration=2, apply_to='mask')
|
| 251 |
-
|
| 252 |
clips.append(img_clip)
|
| 253 |
|
| 254 |
-
|
| 255 |
-
final_clips = []
|
| 256 |
-
for i, clip in enumerate(clips):
|
| 257 |
-
if i > 0:
|
| 258 |
-
# Crossfade transition
|
| 259 |
-
transition = ColorClip(size=(1920, 1080), color=(0, 0, 0)).set_duration(0.5)
|
| 260 |
-
final_clips.append(transition)
|
| 261 |
-
final_clips.append(clip)
|
| 262 |
-
|
| 263 |
-
video = concatenate_videoclips(final_clips, method="compose")
|
| 264 |
-
|
| 265 |
video_path = f"hd_workout_video_{timestamp}.mp4"
|
| 266 |
video.write_videofile(
|
| 267 |
video_path,
|
| 268 |
fps=30,
|
| 269 |
codec='libx264',
|
| 270 |
audio=False,
|
| 271 |
-
bitrate="8000k",
|
| 272 |
verbose=False,
|
| 273 |
logger=None
|
| 274 |
)
|
|
@@ -278,23 +241,20 @@ def generate_professional_content(main_title, workout_names, image_files):
|
|
| 278 |
print(f"Video error: {e}")
|
| 279 |
video_path = None
|
| 280 |
|
| 281 |
-
# Cleanup
|
| 282 |
for path in hd_templates:
|
| 283 |
try:
|
| 284 |
os.remove(path)
|
| 285 |
except:
|
| 286 |
pass
|
| 287 |
|
| 288 |
-
video_status = "β
HD VIDEO GENERATED (1920x1080)" if video_path else "β Video Failed
|
| 289 |
success_msg = f"""
|
| 290 |
-
π―
|
| 291 |
-
|
| 292 |
-
π **PDF**: {len(names_list) + 1} premium pages
|
| 293 |
-
π₯ **{video_status}**
|
| 294 |
-
πͺ **Workouts**: {len(names_list)} sessions
|
| 295 |
-
πΌοΈ **Images**: {available_images} HD templates created
|
| 296 |
|
| 297 |
-
|
|
|
|
|
|
|
|
|
|
| 298 |
"""
|
| 299 |
|
| 300 |
return pdf_path, video_path, success_msg
|
|
@@ -302,7 +262,7 @@ def generate_professional_content(main_title, workout_names, image_files):
|
|
| 302 |
except Exception as e:
|
| 303 |
return None, None, f"β Error: {str(e)}"
|
| 304 |
|
| 305 |
-
#
|
| 306 |
with gr.Blocks(
|
| 307 |
title="Professional Gym Workout Generator",
|
| 308 |
theme=gr.themes.Soft(primary_hue="green"),
|
|
@@ -317,16 +277,16 @@ with gr.Blocks(
|
|
| 317 |
gr.HTML("""
|
| 318 |
<div class="header">
|
| 319 |
<h1>ποΈ PROFESSIONAL GYM GENERATOR</h1>
|
| 320 |
-
<p><strong>HD Video (1920x1080) + Premium PDF
|
| 321 |
</div>
|
| 322 |
""")
|
| 323 |
|
| 324 |
gr.Markdown("""
|
| 325 |
-
### π
|
| 326 |
-
-
|
| 327 |
-
-
|
| 328 |
-
-
|
| 329 |
-
-
|
| 330 |
""")
|
| 331 |
|
| 332 |
with gr.Row():
|
|
@@ -343,12 +303,11 @@ with gr.Blocks(
|
|
| 343 |
elem_classes="input-box"
|
| 344 |
)
|
| 345 |
with gr.Column(scale=1):
|
| 346 |
-
images_input = gr.File(
|
| 347 |
label="πΈ Workout Images *",
|
| 348 |
file_count="multiple",
|
| 349 |
file_types=["image"],
|
| 350 |
-
elem_classes="input-box"
|
| 351 |
-
info="Upload exercise images - each gets HD template with workout name overlay"
|
| 352 |
)
|
| 353 |
|
| 354 |
generate_btn = gr.Button("π¬ Generate HD Video + Premium PDF", variant="primary", elem_classes="btn-pro")
|
|
|
|
| 1 |
+
# app.py - Professional Gym Workout Generator (FIXED - No 'info' parameter)
|
| 2 |
|
| 3 |
import gradio as gr
|
| 4 |
from fpdf import FPDF
|
|
|
|
| 13 |
try:
|
| 14 |
from moviepy.editor import (
|
| 15 |
ImageClip, concatenate_videoclips, TextClip, CompositeVideoClip,
|
| 16 |
+
ColorClip, vfx
|
| 17 |
)
|
| 18 |
VIDEO_ENABLED = True
|
| 19 |
except ImportError:
|
|
|
|
| 35 |
if not text:
|
| 36 |
return "Workout Session"
|
| 37 |
text = str(text)
|
|
|
|
| 38 |
emoji_pattern = re.compile("["
|
| 39 |
+
u"\U0001F600-\U0001F64F"
|
| 40 |
+
u"\U0001F300-\U0001F5FF"
|
| 41 |
+
u"\U0001F680-\U0001F6FF"
|
| 42 |
+
u"\U0001F1E0-\U0001F1FF"
|
| 43 |
"]+", flags=re.UNICODE)
|
| 44 |
text = emoji_pattern.sub('', text)
|
| 45 |
text = re.sub(r'[^\w\s\-.,!?\(\)]', '', text)
|
|
|
|
| 52 |
img = Image.open(image_path)
|
| 53 |
img = img.resize((1920, 1080), Image.Resampling.LANCZOS)
|
| 54 |
|
| 55 |
+
template = Image.new('RGB', (1920, 1080), color=(25, 25, 25))
|
|
|
|
|
|
|
| 56 |
draw = ImageDraw.Draw(template)
|
| 57 |
|
|
|
|
| 58 |
try:
|
| 59 |
font_title = ImageFont.truetype("arial.ttf", 80)
|
| 60 |
font_workout = ImageFont.truetype("arial.ttf", 60)
|
|
|
|
| 67 |
clean_name = clean_text(workout_name)
|
| 68 |
clean_title = clean_text(main_title)
|
| 69 |
|
|
|
|
| 70 |
header_bg = Image.new('RGBA', (1920, 200), (0, 100, 0, 200))
|
| 71 |
template.paste(header_bg, (0, 0), header_bg)
|
| 72 |
|
|
|
|
| 73 |
draw.text((100, 50), f"WORKOUT SESSION {workout_num}", fill="white", font=font_title)
|
| 74 |
draw.text((100, 130), clean_name.upper(), fill="#FFD700", font=font_workout)
|
|
|
|
|
|
|
| 75 |
draw.text((100, 200), f"PROGRAM: {clean_title}", fill="white", font=font_sub)
|
| 76 |
|
|
|
|
| 77 |
img_resized = img.resize((1600, 800), Image.Resampling.LANCZOS)
|
| 78 |
template.paste(img_resized, (160, 300))
|
| 79 |
|
|
|
|
| 80 |
draw.rectangle([160, 300, 1760, 1100], outline="#00FF00", width=5)
|
| 81 |
|
|
|
|
| 82 |
footer_bg = Image.new('RGBA', (1920, 150), (0, 0, 0, 180))
|
| 83 |
template.paste(footer_bg, (0, 930), footer_bg)
|
| 84 |
|
|
|
|
| 86 |
draw.text((100, 990), "SETS | REPS | REST | PROGRESS", fill="white", font=font_sub)
|
| 87 |
draw.text((100, 1030), "PRINT β’ TRAIN β’ TRACK β’ REPEAT", fill="#FFD700", font=font_sub)
|
| 88 |
|
|
|
|
| 89 |
template_path = f"hd_template_{workout_num}.jpg"
|
| 90 |
template.save(template_path, "JPEG", quality=95, optimize=False)
|
| 91 |
return template_path
|
|
|
|
| 133 |
pdf.cell(0, 10, f"CREATED: {datetime.now().strftime('%d/%m/%Y %H:%M')}", ln=1, align='C')
|
| 134 |
pdf.cell(0, 10, "PROFESSIONAL GYM CERTIFIED", ln=1, align='C')
|
| 135 |
|
|
|
|
| 136 |
hd_templates = []
|
| 137 |
|
| 138 |
for i, workout_name in enumerate(names_list):
|
| 139 |
pdf.add_page()
|
|
|
|
|
|
|
| 140 |
pdf.set_font('Arial', 'B', 20)
|
| 141 |
pdf.set_text_color(0, 128, 0)
|
| 142 |
pdf.cell(0, 18, f"SESSION {i+1}", ln=1, align='C')
|
|
|
|
| 145 |
pdf.set_font('Arial', 'B', 16)
|
| 146 |
pdf.multi_cell(0, 14, clean_name.upper(), align='C')
|
| 147 |
|
|
|
|
| 148 |
pdf.ln(5)
|
| 149 |
pdf.set_font('Arial', 'B', 14)
|
| 150 |
pdf.set_text_color(0, 128, 0)
|
|
|
|
| 172 |
pdf.ln(6)
|
| 173 |
pdf.cell(0, 8, detail, ln=1, align='L')
|
| 174 |
|
|
|
|
| 175 |
img_path = image_files[i] if i < available_images else None
|
| 176 |
template_path = None
|
| 177 |
|
|
|
|
| 181 |
)
|
| 182 |
hd_templates.append(template_path)
|
| 183 |
|
|
|
|
| 184 |
img_width = 140
|
| 185 |
x_pos = (pdf.w - img_width) / 2
|
| 186 |
try:
|
|
|
|
| 196 |
pdf.set_text_color(0, 128, 0)
|
| 197 |
pdf.cell(0, 10, "SIGNATURE: ________________ Date: ________", ln=1, align='C')
|
| 198 |
|
|
|
|
| 199 |
timestamp = int(datetime.now().timestamp())
|
| 200 |
pdf_path = f"professional_workout_{timestamp}.pdf"
|
| 201 |
pdf.output(pdf_path)
|
| 202 |
|
|
|
|
| 203 |
video_path = None
|
| 204 |
if VIDEO_ENABLED and hd_templates:
|
| 205 |
try:
|
| 206 |
clips = []
|
| 207 |
+
duration_per_slide = 8
|
| 208 |
|
|
|
|
| 209 |
title_bg = ColorClip(size=(1920, 1080), color=(0, 50, 0)).set_duration(5)
|
| 210 |
title_text = TextClip(
|
| 211 |
f"PROFESSIONAL GYM PROGRAM\n{clean_main_title.upper()}",
|
|
|
|
| 215 |
title_slide = CompositeVideoClip([title_bg, title_text]).set_duration(5)
|
| 216 |
clips.append(title_slide)
|
| 217 |
|
|
|
|
| 218 |
for template_img in hd_templates:
|
| 219 |
if os.path.exists(template_img):
|
|
|
|
| 220 |
img_clip = (ImageClip(template_img)
|
| 221 |
.set_duration(duration_per_slide)
|
| 222 |
.resize(height=1080)
|
| 223 |
.fx(vfx.fadein, 1)
|
| 224 |
.fx(vfx.fadeout, 1))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
clips.append(img_clip)
|
| 226 |
|
| 227 |
+
video = concatenate_videoclips(clips, method="compose")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
video_path = f"hd_workout_video_{timestamp}.mp4"
|
| 229 |
video.write_videofile(
|
| 230 |
video_path,
|
| 231 |
fps=30,
|
| 232 |
codec='libx264',
|
| 233 |
audio=False,
|
| 234 |
+
bitrate="8000k",
|
| 235 |
verbose=False,
|
| 236 |
logger=None
|
| 237 |
)
|
|
|
|
| 241 |
print(f"Video error: {e}")
|
| 242 |
video_path = None
|
| 243 |
|
|
|
|
| 244 |
for path in hd_templates:
|
| 245 |
try:
|
| 246 |
os.remove(path)
|
| 247 |
except:
|
| 248 |
pass
|
| 249 |
|
| 250 |
+
video_status = "β
HD VIDEO GENERATED (1920x1080)" if video_path else "β Video Failed"
|
| 251 |
success_msg = f"""
|
| 252 |
+
π― PROFESSIONAL GYM CONTENT GENERATED!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
|
| 254 |
+
π PDF: {len(names_list) + 1} premium pages
|
| 255 |
+
π₯ {video_status}
|
| 256 |
+
πͺ Workouts: {len(names_list)} sessions
|
| 257 |
+
πΌοΈ Images: {available_images} HD templates
|
| 258 |
"""
|
| 259 |
|
| 260 |
return pdf_path, video_path, success_msg
|
|
|
|
| 262 |
except Exception as e:
|
| 263 |
return None, None, f"β Error: {str(e)}"
|
| 264 |
|
| 265 |
+
# FIXED UI - REMOVED 'info' parameter
|
| 266 |
with gr.Blocks(
|
| 267 |
title="Professional Gym Workout Generator",
|
| 268 |
theme=gr.themes.Soft(primary_hue="green"),
|
|
|
|
| 277 |
gr.HTML("""
|
| 278 |
<div class="header">
|
| 279 |
<h1>ποΈ PROFESSIONAL GYM GENERATOR</h1>
|
| 280 |
+
<p><strong>HD Video (1920x1080) + Premium PDF</strong></p>
|
| 281 |
</div>
|
| 282 |
""")
|
| 283 |
|
| 284 |
gr.Markdown("""
|
| 285 |
+
### π Features:
|
| 286 |
+
- HD Video: 1920x1080 with workout names on each slide
|
| 287 |
+
- Premium PDF: Professional execution protocols
|
| 288 |
+
- Auto-generates gym templates from your images
|
| 289 |
+
- Each slide shows: WORKOUT # + NAME + Your image
|
| 290 |
""")
|
| 291 |
|
| 292 |
with gr.Row():
|
|
|
|
| 303 |
elem_classes="input-box"
|
| 304 |
)
|
| 305 |
with gr.Column(scale=1):
|
| 306 |
+
images_input = gr.File( # β
REMOVED 'info' parameter
|
| 307 |
label="πΈ Workout Images *",
|
| 308 |
file_count="multiple",
|
| 309 |
file_types=["image"],
|
| 310 |
+
elem_classes="input-box"
|
|
|
|
| 311 |
)
|
| 312 |
|
| 313 |
generate_btn = gr.Button("π¬ Generate HD Video + Premium PDF", variant="primary", elem_classes="btn-pro")
|