saq1b commited on
Commit
5f93144
·
verified ·
1 Parent(s): 8941369

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +87 -44
main.py CHANGED
@@ -56,19 +56,22 @@ async def download_file(url, local_path):
56
  return False
57
 
58
  async def create_slideshow(image_paths, audio_path, output_path, duration, zoom=False, zoom_ratio=0.04):
59
- """Generate slideshow from images and audio using ffmpeg asynchronously, with optional zoom effect"""
 
 
 
60
  if not zoom:
61
- # Create temporary file list for ffmpeg concat
62
- concat_file = "temp_concat.txt"
63
  async with aiofiles.open(concat_file, "w") as f:
64
  for img in image_paths:
65
  await f.write(f"file '{img}'\n")
66
  await f.write(f"duration {duration}\n")
 
67
  # Add the last image again without duration (required by ffmpeg)
68
  if image_paths:
69
  await f.write(f"file '{image_paths[-1]}'\n")
70
 
71
- # Run ffmpeg command to create slideshow with audio (no zoom)
72
  total_duration = len(image_paths) * duration
73
  cmd = [
74
  "ffmpeg",
@@ -85,41 +88,76 @@ async def create_slideshow(image_paths, audio_path, output_path, duration, zoom=
85
  output_path
86
  ]
87
  else:
88
- # Generate slideshow with zoom effect using ffmpeg zoompan filter.
89
- # For each image, loop it for the specified duration and apply zoompan.
90
- fps = 25 # frames per second
91
- cmd = ["ffmpeg"]
92
- # Add each image as an input that loops for 'duration' seconds.
93
- for img in image_paths:
94
- cmd.extend(["-loop", "1", "-t", str(duration), "-i", img])
95
- # Append the audio input. Its index will be len(image_paths)
96
- cmd.extend(["-i", audio_path])
97
-
98
- # Build filter_complex for zoompan on each image.
99
- filter_complex = ""
100
- for i in range(len(image_paths)):
101
- # Each input's video stream is processed with zoompan.
102
- # The zoom increases by zoom_ratio per frame (starting at 1.0) and centers the image.
103
- filter_complex += (
104
- f"[{i}:v]zoompan=z='if(eq(on,0),1,zoom+{zoom_ratio})':x='iw/2-(iw/zoom)/2':y='ih/2-(ih/zoom)/2':"
105
- f"d={duration*fps}:s=hd720, setpts=PTS-STARTPTS[v{i}];"
106
- )
107
- # Concatenate all processed video segments.
108
- inputs = "".join(f"[v{i}]" for i in range(len(image_paths)))
109
- filter_complex += f"{inputs}concat=n={len(image_paths)}:v=1:a=0,format=yuv420p[v]"
110
 
111
- # Map the concatenated video and the audio stream (audio input is at index len(image_paths))
112
- cmd = cmd + [
113
- "-filter_complex", filter_complex,
114
- "-map", "[v]",
115
- "-map", f"{len(image_paths)}:a",
116
- "-c:v", "libx264",
117
- "-pix_fmt", "yuv420p",
118
- "-c:a", "aac",
119
- "-shortest",
120
- "-y",
121
- output_path
122
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
  try:
125
  process = await asyncio.create_subprocess_exec(
@@ -127,11 +165,14 @@ async def create_slideshow(image_paths, audio_path, output_path, duration, zoom=
127
  stdout=asyncio.subprocess.PIPE,
128
  stderr=asyncio.subprocess.PIPE
129
  )
130
- _, stderr = await process.communicate()
 
 
 
131
 
132
- # Remove temporary concat file if it exists
133
- if not zoom and os.path.exists("temp_concat.txt"):
134
- os.remove("temp_concat.txt")
135
 
136
  if process.returncode != 0:
137
  print(f"FFmpeg error: {stderr.decode()}")
@@ -139,8 +180,10 @@ async def create_slideshow(image_paths, audio_path, output_path, duration, zoom=
139
  return True
140
  except Exception as e:
141
  print(f"FFmpeg error: {str(e)}")
142
- if not zoom and os.path.exists("temp_concat.txt"):
143
- os.remove("temp_concat.txt")
 
 
144
  return False
145
 
146
  @app.post("/make_slideshow")
 
56
  return False
57
 
58
  async def create_slideshow(image_paths, audio_path, output_path, duration, zoom=False, zoom_ratio=0.04):
59
+ """Generate slideshow from images and audio using ffmpeg asynchronously"""
60
+ # Create temporary file list for ffmpeg concat
61
+ concat_file = "temp_concat.txt"
62
+
63
  if not zoom:
64
+ # Original implementation without zoom effect
 
65
  async with aiofiles.open(concat_file, "w") as f:
66
  for img in image_paths:
67
  await f.write(f"file '{img}'\n")
68
  await f.write(f"duration {duration}\n")
69
+
70
  # Add the last image again without duration (required by ffmpeg)
71
  if image_paths:
72
  await f.write(f"file '{image_paths[-1]}'\n")
73
 
74
+ # Run ffmpeg command to create slideshow with audio
75
  total_duration = len(image_paths) * duration
76
  cmd = [
77
  "ffmpeg",
 
88
  output_path
89
  ]
90
  else:
91
+ # Implementation with zoom effect
92
+ temp_dir = os.path.dirname(output_path) + "/temp"
93
+ os.makedirs(temp_dir, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
+ # Generate a video clip for each image with zoom effect
96
+ video_parts = []
97
+ try:
98
+ for i, img_path in enumerate(image_paths):
99
+ out_clip = f"{temp_dir}/clip_{i:03d}.mp4"
100
+ video_parts.append(out_clip)
101
+
102
+ # Calculate zoompan parameters
103
+ fps = 30
104
+ frames = duration * fps
105
+
106
+ # Start at zoom=1 and gradually increase over time
107
+ zoom_expr = f"'1+{zoom_ratio}*t/{duration}'"
108
+
109
+ # Center the zoom effect
110
+ x_expr = "'iw/2-(iw/zoom/2)'"
111
+ y_expr = "'ih/2-(ih/zoom/2)'"
112
+
113
+ cmd = [
114
+ "ffmpeg",
115
+ "-loop", "1",
116
+ "-i", img_path,
117
+ "-t", str(duration),
118
+ "-filter_complex", f"zoompan=z={zoom_expr}:x={x_expr}:y={y_expr}:d={frames}:s='iw:ih':fps={fps}",
119
+ "-c:v", "libx264",
120
+ "-pix_fmt", "yuv420p",
121
+ "-y",
122
+ out_clip
123
+ ]
124
+
125
+ process = await asyncio.create_subprocess_exec(
126
+ *cmd,
127
+ stdout=asyncio.subprocess.PIPE,
128
+ stderr=asyncio.subprocess.PIPE
129
+ )
130
+ _, stderr = await process.communicate()
131
+
132
+ if process.returncode != 0:
133
+ print(f"FFmpeg error on clip {i}: {stderr.decode()}")
134
+ raise Exception(f"Failed to create zoomed clip {i}")
135
+
136
+ # Create a concat file for all the video parts
137
+ async with aiofiles.open(concat_file, "w") as f:
138
+ for video in video_parts:
139
+ await f.write(f"file '{video}'\n")
140
+
141
+ # Combine all clips with audio
142
+ cmd = [
143
+ "ffmpeg",
144
+ "-f", "concat",
145
+ "-safe", "0",
146
+ "-i", concat_file,
147
+ "-i", audio_path,
148
+ "-c:v", "copy",
149
+ "-c:a", "aac",
150
+ "-shortest",
151
+ "-y",
152
+ output_path
153
+ ]
154
+ except Exception as e:
155
+ # Clean up on error
156
+ if os.path.exists(temp_dir):
157
+ shutil.rmtree(temp_dir)
158
+ if os.path.exists(concat_file):
159
+ os.remove(concat_file)
160
+ raise e
161
 
162
  try:
163
  process = await asyncio.create_subprocess_exec(
 
165
  stdout=asyncio.subprocess.PIPE,
166
  stderr=asyncio.subprocess.PIPE
167
  )
168
+ stdout, stderr = await process.communicate()
169
+
170
+ if os.path.exists(concat_file):
171
+ os.remove(concat_file)
172
 
173
+ # Clean up temporary directory if zoom was used
174
+ if zoom and os.path.exists(temp_dir):
175
+ shutil.rmtree(temp_dir)
176
 
177
  if process.returncode != 0:
178
  print(f"FFmpeg error: {stderr.decode()}")
 
180
  return True
181
  except Exception as e:
182
  print(f"FFmpeg error: {str(e)}")
183
+ if os.path.exists(concat_file):
184
+ os.remove(concat_file)
185
+ if zoom and os.path.exists(temp_dir):
186
+ shutil.rmtree(temp_dir)
187
  return False
188
 
189
  @app.post("/make_slideshow")