lokesh341 commited on
Commit
3d661a3
Β·
verified Β·
1 Parent(s): 8e263bd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +215 -63
app.py CHANGED
@@ -1,17 +1,23 @@
1
- # app.py - Gym Workout Plan PDF + Video (Fixed Errors)
2
 
3
  import gradio as gr
4
  from fpdf import FPDF
5
  import os
6
  import re
7
  from datetime import datetime
 
 
 
 
8
 
9
- # Video generation
10
  try:
11
- from moviepy.editor import ImageClip, concatenate_videoclips, TextClip, CompositeVideoClip
 
12
  VIDEO_ENABLED = True
13
  except ImportError:
14
  VIDEO_ENABLED = False
 
15
 
16
  class GymWorkoutPDF(FPDF):
17
  def footer(self):
@@ -20,6 +26,7 @@ class GymWorkoutPDF(FPDF):
20
  self.cell(0, 8, f'GYM WORKOUT PLAN | Page {self.page_no()}', 0, 0, 'C')
21
 
22
  def clean_text(text, max_length=45):
 
23
  if not text:
24
  return "Workout"
25
  text = str(text)
@@ -42,27 +49,86 @@ def clean_text(text, max_length=45):
42
  text = re.sub(r'\s+', ' ', text.strip())
43
  return text[:max_length]
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  def generate_content(main_title, workout_names, image_files):
46
  if not main_title.strip():
47
- return None, None, "Error: Main Title Required!"
48
 
49
  names_list = [n.strip() for n in workout_names.split(',') if n.strip()]
50
 
51
  if not names_list:
52
- return None, None, "Error: Add Workout Names!"
53
 
54
  if len(names_list) > 30:
55
- return None, None, "Error: Max 30 Workouts!"
56
 
57
- if not image_files or len(names_list) != len(image_files):
58
- return None, None, f"Error: Number of workout names ({len(names_list)}) must match images ({len(image_files)})!"
 
 
 
 
59
 
60
  try:
61
  clean_main_title = clean_text(main_title, 50)
62
  pdf = GymWorkoutPDF()
63
  pdf.set_margins(18, 18, 18)
64
 
65
- # PDF
66
  pdf.add_page()
67
  pdf.set_font('Arial', 'B', 20)
68
  pdf.set_text_color(0, 128, 0)
@@ -75,7 +141,9 @@ def generate_content(main_title, workout_names, image_files):
75
  pdf.cell(0, 10, f"TOTAL WORKOUTS: {len(names_list)}", ln=1, align='C')
76
  pdf.cell(0, 10, f"CREATED: {datetime.now().strftime('%d/%m/%Y')}", ln=1, align='C')
77
 
78
- for i, (workout_name, img_path) in enumerate(zip(names_list, image_files)):
 
 
79
  pdf.add_page()
80
  pdf.set_font('Arial', 'B', 16)
81
  pdf.set_text_color(0, 128, 0)
@@ -85,86 +153,170 @@ def generate_content(main_title, workout_names, image_files):
85
  pdf.set_font('Arial', 'B', 14)
86
  pdf.multi_cell(0, 12, clean_name.upper(), align='C')
87
 
88
- if os.path.exists(img_path):
 
 
 
 
89
  try:
 
 
 
 
 
 
 
90
  img_width = 110
91
  x_pos = (pdf.w - img_width) / 2
92
- pdf.image(img_path, x=x_pos, y=pdf.get_y(), w=img_width, h=130)
93
  pdf.set_draw_color(0, 128, 0)
94
  pdf.rect(x_pos, pdf.get_y(), img_width, 130, 'D')
95
  except:
96
- pass
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  timestamp = int(datetime.now().timestamp())
99
  pdf_path = f"workout_plan_{timestamp}.pdf"
100
  pdf.output(pdf_path)
101
 
102
- # VIDEO
103
  video_path = None
104
- if VIDEO_ENABLED:
105
- clips = []
106
- hd_width, hd_height = 1920, 1080
107
- duration_per_slide = 5
108
-
109
- # Title slide
110
- title_text = TextClip(
111
- f"GYM WORKOUT PROGRAM\n{clean_main_title.upper()}",
112
- fontsize=80, color='white', font='Arial-Bold',
113
- size=(hd_width, 300), bg_color='darkgreen'
114
- ).set_duration(3).set_position('center')
115
- title_clip = CompositeVideoClip([title_text], size=(hd_width, hd_height))
116
- clips.append(title_clip)
117
-
118
- # Workout slides
119
- for i, (workout_name, img_path) in enumerate(zip(names_list, image_files)):
120
- clean_name = clean_text(workout_name, 40)
121
- img_clip = ImageClip(img_path).resize(height=hd_height-200)
122
- img_clip = img_clip.set_position('center').set_duration(duration_per_slide)
123
 
124
- text_clip = TextClip(
125
- f"WORKOUT {i+1}\n{clean_name.upper()}",
126
- fontsize=60, color='white', font='Arial-Bold',
127
- size=(hd_width-400, 200), bg_color='rgba(0,128,0,0.7)'
128
- ).set_position(('center', hd_height-250)).set_duration(duration_per_slide)
 
 
 
 
129
 
130
- slide = CompositeVideoClip([img_clip, text_clip], size=(hd_width, hd_height))
131
- clips.append(slide)
132
-
133
- video = concatenate_videoclips(clips, method="compose")
134
- video_path = f"workout_video_{timestamp}.mp4"
135
- video.write_videofile(
136
- video_path,
137
- fps=24,
138
- codec='libx264',
139
- audio=False
140
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
- success_msg = f"SUCCESS! Generated PDF and {'Video' if video_path else 'No Video (Install MoviePy)'} for {len(names_list)} workouts!"
143
- return pdf_path, video_path, success_msg
 
 
 
 
 
 
 
144
 
145
  except Exception as e:
146
- return None, None, f"Error: {str(e)}"
147
 
148
- # UI
149
- with gr.Blocks(title="Gym Workout Generator") as demo:
150
- gr.Markdown("# Gym Workout Generator\nPDF + HD Video | No Errors")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
  with gr.Row():
153
- main_title = gr.Textbox(label="MAIN TITLE *")
154
- workout_names = gr.Textbox(label="WORKOUT NAMES * (Comma separated)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
- images_input = gr.File(label="Images * (Match workout count)", file_count="multiple", file_types=["image"])
157
 
158
- generate_btn = gr.Button("Generate")
 
 
159
 
160
- pdf_output = gr.File(label="PDF")
161
- video_output = gr.File(label="HD Video")
162
- status = gr.Markdown("Ready...")
163
 
164
  generate_btn.click(
165
  fn=generate_content,
166
  inputs=[main_title, workout_names, images_input],
167
- outputs=[pdf_output, video_output, status]
168
  )
169
 
170
  if __name__ == "__main__":
 
1
+ # app.py - Gym Workout Generator (PDF + Screenshot Template + Fixed Video)
2
 
3
  import gradio as gr
4
  from fpdf import FPDF
5
  import os
6
  import re
7
  from datetime import datetime
8
+ import tempfile
9
+ import base64
10
+ from io import BytesIO
11
+ from PIL import Image
12
 
13
+ # Video generation with fallback
14
  try:
15
+ from moviepy.editor import ImageClip, concatenate_videoclips, TextClip, CompositeVideoClip, ColorClip
16
+ from moviepy.video.fx.all import crop
17
  VIDEO_ENABLED = True
18
  except ImportError:
19
  VIDEO_ENABLED = False
20
+ print("MoviePy not available - using basic video generation")
21
 
22
  class GymWorkoutPDF(FPDF):
23
  def footer(self):
 
26
  self.cell(0, 8, f'GYM WORKOUT PLAN | Page {self.page_no()}', 0, 0, 'C')
27
 
28
  def clean_text(text, max_length=45):
29
+ """Clean text for PDF and video"""
30
  if not text:
31
  return "Workout"
32
  text = str(text)
 
49
  text = re.sub(r'\s+', ' ', text.strip())
50
  return text[:max_length]
51
 
52
+ def create_screenshot_template(image_path, workout_name, workout_num, main_title):
53
+ """Create screenshot-style template image"""
54
+ try:
55
+ # Load and resize image
56
+ img = Image.open(image_path)
57
+ img = img.resize((800, 600), Image.Resampling.LANCZOS)
58
+
59
+ # Create template
60
+ template = Image.new('RGB', (800, 600), color=(34, 139, 34)) # Dark green background
61
+
62
+ # Add workout overlay
63
+ from PIL import ImageDraw, ImageFont
64
+
65
+ draw = ImageDraw.Draw(template)
66
+
67
+ # Title background
68
+ title_bg = Image.new('RGBA', (800, 120), color=(0, 100, 0, 200))
69
+ template.paste(title_bg, (0, 0))
70
+
71
+ # Fonts (fallback to default)
72
+ try:
73
+ font_large = ImageFont.truetype("arial.ttf", 48)
74
+ font_medium = ImageFont.truetype("arial.ttf", 36)
75
+ font_small = ImageFont.truetype("arial.ttf", 24)
76
+ except:
77
+ font_large = ImageFont.load_default()
78
+ font_medium = ImageFont.load_default()
79
+ font_small = ImageFont.load_default()
80
+
81
+ # Draw text
82
+ clean_name = clean_text(workout_name)
83
+ clean_title = clean_text(main_title)
84
+
85
+ draw.text((50, 20), f"WORKOUT {workout_num}", fill="white", font=font_large)
86
+ draw.text((50, 80), clean_name.upper(), fill="yellow", font=font_medium)
87
+ draw.text((50, 120), f"PROGRAM: {clean_title}", fill="white", font=font_small)
88
+
89
+ # Add image
90
+ img_resized = img.resize((700, 400), Image.Resampling.LANCZOS)
91
+ template.paste(img_resized, (50, 150))
92
+
93
+ # Footer
94
+ footer_bg = Image.new('RGBA', (800, 80), color=(0, 0, 0, 150))
95
+ template.paste(footer_bg, (0, 520))
96
+ draw.text((50, 540), f"Workout Execution Plan - Print & Train!", fill="white", font=font_small)
97
+
98
+ # Save template
99
+ template_path = f"template_{workout_num}.jpg"
100
+ template.save(template_path, "JPEG", quality=95)
101
+ return template_path
102
+
103
+ except Exception as e:
104
+ print(f"Template creation failed: {e}")
105
+ return image_path # Return original if template fails
106
+
107
  def generate_content(main_title, workout_names, image_files):
108
  if not main_title.strip():
109
+ return None, None, gr.update(), "Error: Main Title Required!"
110
 
111
  names_list = [n.strip() for n in workout_names.split(',') if n.strip()]
112
 
113
  if not names_list:
114
+ return None, None, gr.update(), "Error: Add Workout Names!"
115
 
116
  if len(names_list) > 30:
117
+ return None, None, gr.update(), "Error: Max 30 Workouts!"
118
 
119
+ if not image_files:
120
+ return None, None, gr.update(), "Error: Upload at least 1 image!"
121
+
122
+ # Allow flexible image count - use available images
123
+ available_images = min(len(image_files), len(names_list))
124
+ status_msg = f"Using {available_images} images for {len(names_list)} workouts"
125
 
126
  try:
127
  clean_main_title = clean_text(main_title, 50)
128
  pdf = GymWorkoutPDF()
129
  pdf.set_margins(18, 18, 18)
130
 
131
+ # PDF Generation
132
  pdf.add_page()
133
  pdf.set_font('Arial', 'B', 20)
134
  pdf.set_text_color(0, 128, 0)
 
141
  pdf.cell(0, 10, f"TOTAL WORKOUTS: {len(names_list)}", ln=1, align='C')
142
  pdf.cell(0, 10, f"CREATED: {datetime.now().strftime('%d/%m/%Y')}", ln=1, align='C')
143
 
144
+ template_paths = []
145
+
146
+ for i, workout_name in enumerate(names_list):
147
  pdf.add_page()
148
  pdf.set_font('Arial', 'B', 16)
149
  pdf.set_text_color(0, 128, 0)
 
153
  pdf.set_font('Arial', 'B', 14)
154
  pdf.multi_cell(0, 12, clean_name.upper(), align='C')
155
 
156
+ # Use available images or placeholder
157
+ img_path = image_files[i] if i < len(image_files) else None
158
+ template_path = None
159
+
160
+ if img_path and os.path.exists(img_path):
161
  try:
162
+ # Create screenshot template
163
+ template_path = create_screenshot_template(
164
+ img_path, workout_name, i+1, main_title
165
+ )
166
+ template_paths.append(template_path)
167
+
168
+ # Add to PDF
169
  img_width = 110
170
  x_pos = (pdf.w - img_width) / 2
171
+ pdf.image(template_path, x=x_pos, y=pdf.get_y(), w=img_width, h=130)
172
  pdf.set_draw_color(0, 128, 0)
173
  pdf.rect(x_pos, pdf.get_y(), img_width, 130, 'D')
174
  except:
175
+ # Fallback to original image
176
+ try:
177
+ pdf.image(img_path, x=x_pos, y=pdf.get_y(), w=img_width, h=130)
178
+ except:
179
+ pass
180
+ else:
181
+ # No image - placeholder
182
+ pdf.set_fill_color(240, 248, 240)
183
+ pdf.set_draw_color(0, 128, 0)
184
+ pdf.rect(x_pos, pdf.get_y(), img_width, 130, 'FD')
185
+ pdf.set_font('Arial', 'B', 11)
186
+ pdf.set_xy(x_pos + 10, pdf.get_y() + 50)
187
+ pdf.cell(90, 10, f"WORKOUT {i+1}", ln=1, align='C')
188
 
189
  timestamp = int(datetime.now().timestamp())
190
  pdf_path = f"workout_plan_{timestamp}.pdf"
191
  pdf.output(pdf_path)
192
 
193
+ # VIDEO GENERATION
194
  video_path = None
195
+ if VIDEO_ENABLED and template_paths:
196
+ try:
197
+ clips = []
198
+ hd_width, hd_height = 1920, 1080
199
+ duration_per_slide = 6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
+ # Title slide
202
+ title_clip = ColorClip(size=(hd_width, hd_height), color=(0, 128, 0)).set_duration(4)
203
+ title_text = TextClip(
204
+ f"GYM WORKOUT PROGRAM\n{clean_main_title.upper()}",
205
+ fontsize=100, color='white', font='Arial-Bold',
206
+ size=(hd_width-400, None)
207
+ ).set_position('center').set_duration(4)
208
+ title_slide = CompositeVideoClip([title_clip, title_text])
209
+ clips.append(title_slide)
210
 
211
+ # Workout slides with templates
212
+ for template_img in template_paths[:10]: # Limit to 10 for video
213
+ if os.path.exists(template_img):
214
+ img_clip = ImageClip(template_img).resize(height=hd_height)
215
+ img_clip = img_clip.set_duration(duration_per_slide)
216
+
217
+ # Add zoom effect
218
+ img_clip = img_clip.fx(crop, x1=100, y1=50, x2=hd_width-100, y2=hd_height-100)
219
+
220
+ clips.append(img_clip)
221
+
222
+ if clips:
223
+ video = concatenate_videoclips(clips, method="compose")
224
+ video_path = f"workout_video_{timestamp}.mp4"
225
+ video.write_videofile(
226
+ video_path,
227
+ fps=24,
228
+ codec='libx264',
229
+ audio=False,
230
+ verbose=False,
231
+ logger=None
232
+ )
233
+ video.close()
234
+
235
+ except Exception as e:
236
+ print(f"Video error: {e}")
237
+ video_path = None
238
+
239
+ # Cleanup templates
240
+ for path in template_paths:
241
+ try:
242
+ os.remove(path)
243
+ except:
244
+ pass
245
 
246
+ video_status = "HD Video Generated!" if video_path else "Video Disabled (MoviePy required)"
247
+ success_msg = f"""
248
+ βœ… SUCCESS! Generated content for {len(names_list)} workouts!
249
+ πŸ“„ PDF: {len(names_list) + 1} pages with screenshot templates
250
+ πŸŽ₯ {video_status}
251
+ πŸ’ͺ Images processed: {available_images}/{len(names_list)}
252
+ """
253
+
254
+ return pdf_path, video_path, gr.update(), success_msg
255
 
256
  except Exception as e:
257
+ return None, None, gr.update(), f"Error: {str(e)}"
258
 
259
+ # Enhanced UI
260
+ with gr.Blocks(
261
+ title="Gym Workout Generator",
262
+ theme=gr.themes.Soft(primary_hue="green"),
263
+ css="""
264
+ .header {background: linear-gradient(135deg, #16a34a, #15803d); color: white; padding: 20px; border-radius: 10px;}
265
+ .input-box {background: #f0fdf4; padding: 15px; border-radius: 8px; border: 2px solid #dcfce7;}
266
+ .status-success {background: #dcfce7; color: #166534; padding: 15px; border-radius: 8px;}
267
+ """
268
+ ) as demo:
269
+
270
+ gr.HTML("""
271
+ <div class="header">
272
+ <h2>πŸ‹οΈ Gym Workout Generator</h2>
273
+ <p>PDF + HD Video + Screenshot Templates</p>
274
+ </div>
275
+ """)
276
+
277
+ gr.Markdown("""
278
+ ### πŸ“‹ How to Use:
279
+ 1. Enter **Program Title**
280
+ 2. Add **Workout Names** (comma separated)
281
+ 3. Upload **Images/Screenshots** (can be fewer than workouts)
282
+ 4. Get **PDF** with screenshot-style templates + **HD Video**
283
+
284
+ πŸ’‘ **Tip**: Upload exercise screenshots - they get automatic gym templates!
285
+ """)
286
 
287
  with gr.Row():
288
+ with gr.Column(scale=2):
289
+ main_title = gr.Textbox(
290
+ label="MAIN PROGRAM TITLE *",
291
+ placeholder="12 Week Strength Program",
292
+ elem_classes="input-box"
293
+ )
294
+ workout_names = gr.Textbox(
295
+ label="WORKOUT NAMES * (Comma separated)",
296
+ placeholder="Chest Day, Leg Day, Back Workout, Arms, Cardio",
297
+ lines=3,
298
+ elem_classes="input-box"
299
+ )
300
+ with gr.Column(scale=1):
301
+ images_input = gr.File(
302
+ label="Images/Screenshots (Optional)",
303
+ file_count="multiple",
304
+ file_types=["image"],
305
+ elem_classes="input-box"
306
+ )
307
 
308
+ generate_btn = gr.Button("πŸš€ Generate PDF + Video", variant="primary")
309
 
310
+ with gr.Row():
311
+ pdf_output = gr.File(label="πŸ“„ Download PDF")
312
+ video_output = gr.File(label="πŸŽ₯ Download HD Video")
313
 
314
+ status = gr.Markdown("Ready to generate professional gym content...")
 
 
315
 
316
  generate_btn.click(
317
  fn=generate_content,
318
  inputs=[main_title, workout_names, images_input],
319
+ outputs=[pdf_output, video_output, video_output, status]
320
  )
321
 
322
  if __name__ == "__main__":