mdnazib963 commited on
Commit
f3f3c92
·
verified ·
1 Parent(s): 1016fa4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +32 -40
app.py CHANGED
@@ -18,27 +18,27 @@ def get_media_duration(file_path):
18
  return 5.0
19
 
20
  def create_static_background(image_path, output_path):
21
- """Creates a darkened, blurred background (720p optimized)."""
22
  target_size = (720, 1280)
23
  with Image.open(image_path) as img:
24
  img = img.convert("RGB")
25
  bg = ImageOps.fit(img, target_size, method=Image.Resampling.LANCZOS)
26
- bg = bg.filter(ImageFilter.GaussianBlur(radius=25))
27
  enhancer = ImageEnhance.Brightness(bg)
28
- bg = enhancer.enhance(0.6)
29
  bg.save(output_path, "JPEG", quality=95)
30
  return output_path
31
 
32
  def prepare_foreground(image_path, output_path):
33
  """
34
- Prepares foreground image.
35
- CHANGE: Resizes to 1080px width (High Res) instead of 720px.
36
- This provides extra pixels so the zoom doesn't look pixelated/stucky.
37
  """
38
- target_width = 1080
39
  with Image.open(image_path) as img:
40
  img = img.convert("RGB")
41
- # Calculate height to keep aspect ratio
42
  w_percent = (target_width / float(img.size[0]))
43
  h_size = int((float(img.size[1]) * float(w_percent)))
44
 
@@ -48,38 +48,33 @@ def prepare_foreground(image_path, output_path):
48
 
49
  def get_smooth_movement_filter(total_frames, fg_w, fg_h):
50
  """
51
- Generates a 60FPS zoompan filter.
52
- We add a buffer to 'd' (duration) to prevent end-of-clip freezing.
53
  """
54
  move_type = random.choice(["zoom_in", "zoom_out", "pan_up", "pan_down"])
55
 
56
- # We output the result at 720x1280, even if input is larger
57
- # d={total_frames + 60} provides a 1-second safety buffer so it never runs out of frames
58
- common = f"d={total_frames + 60}:s=720x1280:fps=60"
59
 
60
- # We use the EXACT duration for the math, so the movement finishes exactly when audio ends
61
  math_duration = total_frames
62
 
 
63
  if move_type == "zoom_in":
64
- # Linear Zoom In (Slow & Smooth)
65
  z = f"1+(0.15*on/{math_duration})"
66
  x = "iw/2-(iw/zoom/2)"
67
  y = "ih/2-(ih/zoom/2)"
68
 
69
  elif move_type == "zoom_out":
70
- # Linear Zoom Out
71
  z = f"1.15-(0.15*on/{math_duration})"
72
  x = "iw/2-(iw/zoom/2)"
73
  y = "ih/2-(ih/zoom/2)"
74
 
75
  elif move_type == "pan_up":
76
- # Linear Pan Up
77
  z = "1.15"
78
  x = "iw/2-(iw/zoom/2)"
79
  y = f"(ih-ih/zoom)*(1-on/{math_duration})"
80
 
81
  else: # pan_down
82
- # Linear Pan Down
83
  z = "1.15"
84
  x = "iw/2-(iw/zoom/2)"
85
  y = f"(ih-ih/zoom)*(on/{math_duration})"
@@ -90,11 +85,9 @@ def process_batch(image_files, audio_files, progress=gr.Progress()):
90
  if not image_files or not audio_files:
91
  raise gr.Error("Please upload both images and audio.")
92
 
93
- # Sort files
94
  image_files.sort(key=lambda x: x.name)
95
  audio_files.sort(key=lambda x: x.name)
96
 
97
- # Cleanup
98
  output_dir = "temp_clips"
99
  processed_img_dir = "temp_images"
100
  for d in [output_dir, processed_img_dir]:
@@ -105,29 +98,24 @@ def process_batch(image_files, audio_files, progress=gr.Progress()):
105
  clip_paths = []
106
 
107
  for i, (img_path, aud_path) in enumerate(zip(image_files, audio_files)):
108
- progress((i / len(image_files)), desc=f"Rendering Scene {i+1} (60 FPS)...")
109
 
110
  try:
111
- # Prepare Images
112
  bg_path = os.path.join(processed_img_dir, f"bg_{i}.jpg")
113
  create_static_background(img_path, bg_path)
114
 
 
115
  fg_path = os.path.join(processed_img_dir, f"fg_{i}.jpg")
116
- # fg_w/h will be High Res (1080p width)
117
  fg_w, fg_h = prepare_foreground(img_path, fg_path)
118
 
119
  duration = get_media_duration(aud_path)
120
 
121
- # CHANGE: Calculate for 60 FPS
122
- dur_frames = int(duration * 60)
123
 
124
- # Get the Fixed Filter
125
  movement_filter = get_smooth_movement_filter(dur_frames, fg_w, fg_h)
126
 
127
- # Filter Complex:
128
- # 1. Background is input 0
129
- # 2. Foreground is input 1 -> goes into zoompan -> becomes [fg_move]
130
- # 3. [fg_move] is overlayed on [0:v]
131
  filter_complex = (
132
  f"[1:v]{movement_filter}[fg_move];"
133
  f"[0:v][fg_move]overlay=(W-w)/2:(H-h)/2,"
@@ -139,20 +127,24 @@ def process_batch(image_files, audio_files, progress=gr.Progress()):
139
  cmd = [
140
  "ffmpeg", "-y", "-hide_banner", "-loglevel", "error",
141
  "-loop", "1", "-i", bg_path,
 
 
 
142
  "-loop", "1", "-i", fg_path,
 
143
  "-i", aud_path,
144
  "-filter_complex", filter_complex,
145
  "-map", "[v]", "-map", "2:a",
146
- "-r", "60", # CHANGE: 60 FPS output
 
147
  "-c:v", "libx264",
148
- "-crf", "23", # CHANGE: Slightly higher quality (lower CRF)
149
- "-preset", "veryfast", # CHANGE: veryfast is smoother than ultrafast
150
- "-c:a", "aac", "-b:a", "192k",
151
  "-shortest",
152
  clip_name
153
  ]
154
 
155
- # Run
156
  result = subprocess.run(cmd, capture_output=True, text=True)
157
  if result.returncode != 0:
158
  print(f"FFmpeg Error: {result.stderr}")
@@ -169,7 +161,7 @@ def process_batch(image_files, audio_files, progress=gr.Progress()):
169
  for clip in clip_paths:
170
  f.write(f"file '{clip}'\n")
171
 
172
- final_output = "final_smooth_60fps.mp4"
173
  subprocess.run([
174
  "ffmpeg", "-y", "-f", "concat", "-safe", "0",
175
  "-i", list_file, "-c", "copy", final_output
@@ -182,15 +174,15 @@ css = """
182
  .gradio-container {background-color: #222; color: #eee}
183
  """
184
 
185
- with gr.Blocks(title="Smooth Video Maker 60FPS", css=css) as app:
186
- gr.Markdown("## 🎬 Super Smooth Video Generator (60 FPS)")
187
- gr.Markdown("Generates High-Res 60FPS video to eliminate stutter.")
188
 
189
  with gr.Row():
190
  img_in = gr.File(label="Images", file_count="multiple", file_types=["image"])
191
  aud_in = gr.File(label="Audio", file_count="multiple", file_types=["audio"])
192
 
193
- btn = gr.Button("Generate Smooth Video", variant="primary")
194
  out = gr.Video()
195
 
196
  btn.click(process_batch, [img_in, aud_in], out)
 
18
  return 5.0
19
 
20
  def create_static_background(image_path, output_path):
21
+ """Creates a darkened, blurred background (720p)."""
22
  target_size = (720, 1280)
23
  with Image.open(image_path) as img:
24
  img = img.convert("RGB")
25
  bg = ImageOps.fit(img, target_size, method=Image.Resampling.LANCZOS)
26
+ bg = bg.filter(ImageFilter.GaussianBlur(radius=30))
27
  enhancer = ImageEnhance.Brightness(bg)
28
+ bg = enhancer.enhance(0.5)
29
  bg.save(output_path, "JPEG", quality=95)
30
  return output_path
31
 
32
  def prepare_foreground(image_path, output_path):
33
  """
34
+ CRITICAL FOR SMOOTHNESS:
35
+ Upscales image to 2560px (2K) even for 30 FPS output.
36
+ This creates a high-res grid so movement doesn't snap to pixels.
37
  """
38
+ target_width = 2560
39
  with Image.open(image_path) as img:
40
  img = img.convert("RGB")
41
+
42
  w_percent = (target_width / float(img.size[0]))
43
  h_size = int((float(img.size[1]) * float(w_percent)))
44
 
 
48
 
49
  def get_smooth_movement_filter(total_frames, fg_w, fg_h):
50
  """
51
+ Generates 30 FPS zoompan filter.
 
52
  """
53
  move_type = random.choice(["zoom_in", "zoom_out", "pan_up", "pan_down"])
54
 
55
+ # 30 FPS buffer (add 30 frames extra to prevent end freeze)
56
+ # s=1280x720: Downscales the high-res movement to 720p smoothly
57
+ common = f"d={total_frames + 30}:s=1280x720:fps=30"
58
 
 
59
  math_duration = total_frames
60
 
61
+ # 0.15 zoom factor is the sweet spot for 30fps smoothness
62
  if move_type == "zoom_in":
 
63
  z = f"1+(0.15*on/{math_duration})"
64
  x = "iw/2-(iw/zoom/2)"
65
  y = "ih/2-(ih/zoom/2)"
66
 
67
  elif move_type == "zoom_out":
 
68
  z = f"1.15-(0.15*on/{math_duration})"
69
  x = "iw/2-(iw/zoom/2)"
70
  y = "ih/2-(ih/zoom/2)"
71
 
72
  elif move_type == "pan_up":
 
73
  z = "1.15"
74
  x = "iw/2-(iw/zoom/2)"
75
  y = f"(ih-ih/zoom)*(1-on/{math_duration})"
76
 
77
  else: # pan_down
 
78
  z = "1.15"
79
  x = "iw/2-(iw/zoom/2)"
80
  y = f"(ih-ih/zoom)*(on/{math_duration})"
 
85
  if not image_files or not audio_files:
86
  raise gr.Error("Please upload both images and audio.")
87
 
 
88
  image_files.sort(key=lambda x: x.name)
89
  audio_files.sort(key=lambda x: x.name)
90
 
 
91
  output_dir = "temp_clips"
92
  processed_img_dir = "temp_images"
93
  for d in [output_dir, processed_img_dir]:
 
98
  clip_paths = []
99
 
100
  for i, (img_path, aud_path) in enumerate(zip(image_files, audio_files)):
101
+ progress((i / len(image_files)), desc=f"Rendering Scene {i+1} (30 FPS High-Res)...")
102
 
103
  try:
104
+ # 1. Background
105
  bg_path = os.path.join(processed_img_dir, f"bg_{i}.jpg")
106
  create_static_background(img_path, bg_path)
107
 
108
+ # 2. Foreground (High Res 2K)
109
  fg_path = os.path.join(processed_img_dir, f"fg_{i}.jpg")
 
110
  fg_w, fg_h = prepare_foreground(img_path, fg_path)
111
 
112
  duration = get_media_duration(aud_path)
113
 
114
+ # CHANGE: Calculate frames for 30 FPS
115
+ dur_frames = int(duration * 30)
116
 
 
117
  movement_filter = get_smooth_movement_filter(dur_frames, fg_w, fg_h)
118
 
 
 
 
 
119
  filter_complex = (
120
  f"[1:v]{movement_filter}[fg_move];"
121
  f"[0:v][fg_move]overlay=(W-w)/2:(H-h)/2,"
 
127
  cmd = [
128
  "ffmpeg", "-y", "-hide_banner", "-loglevel", "error",
129
  "-loop", "1", "-i", bg_path,
130
+
131
+ # Input framerate 30 to match output
132
+ "-framerate", "30",
133
  "-loop", "1", "-i", fg_path,
134
+
135
  "-i", aud_path,
136
  "-filter_complex", filter_complex,
137
  "-map", "[v]", "-map", "2:a",
138
+
139
+ "-r", "30", # 30 FPS Output
140
  "-c:v", "libx264",
141
+ "-crf", "18", # HIGH BITRATE (Visual Lossless)
142
+ "-preset", "slow", # BETTER COMPRESSION/QUALITY
143
+ "-c:a", "aac", "-b:a", "320k", # High Quality Audio
144
  "-shortest",
145
  clip_name
146
  ]
147
 
 
148
  result = subprocess.run(cmd, capture_output=True, text=True)
149
  if result.returncode != 0:
150
  print(f"FFmpeg Error: {result.stderr}")
 
161
  for clip in clip_paths:
162
  f.write(f"file '{clip}'\n")
163
 
164
+ final_output = "final_high_bitrate_30fps.mp4"
165
  subprocess.run([
166
  "ffmpeg", "-y", "-f", "concat", "-safe", "0",
167
  "-i", list_file, "-c", "copy", final_output
 
174
  .gradio-container {background-color: #222; color: #eee}
175
  """
176
 
177
+ with gr.Blocks(title="High Bitrate 30FPS Maker", css=css) as app:
178
+ gr.Markdown("## 🎬 High Quality Video Generator (30 FPS)")
179
+ gr.Markdown("Optimized for **High Bitrate** and Smooth Motion at 30fps.")
180
 
181
  with gr.Row():
182
  img_in = gr.File(label="Images", file_count="multiple", file_types=["image"])
183
  aud_in = gr.File(label="Audio", file_count="multiple", file_types=["audio"])
184
 
185
+ btn = gr.Button("Generate HQ Video", variant="primary")
186
  out = gr.Video()
187
 
188
  btn.click(process_batch, [img_in, aud_in], out)