|
7 | 7 | """ |
8 | 8 |
|
9 | 9 | import os |
10 | | -import subprocess |
11 | 10 |
|
12 | 11 | import numpy as np |
13 | 12 |
|
@@ -198,49 +197,22 @@ def render_mp4(varname, steps, output, fps=10, # pylint: disable=too-many-argum |
198 | 197 | elif assembled.ndim == 3: |
199 | 198 | render_3d_slice(assembled, varname, step, frame_path, **opts) |
200 | 199 |
|
201 | | - # Combine frames into MP4 using ffmpeg, or fall back to GIF via Pillow |
202 | | - frame_pattern = os.path.join(viz_dir, '%06d.png') |
203 | | - ffmpeg_cmd = [ |
204 | | - 'ffmpeg', '-y', |
205 | | - '-framerate', str(fps), |
206 | | - '-i', frame_pattern, |
207 | | - '-c:v', 'libx264', |
208 | | - '-pix_fmt', 'yuv420p', |
209 | | - '-vf', 'pad=ceil(iw/2)*2:ceil(ih/2)*2', |
210 | | - output, |
211 | | - ] |
| 200 | + # Combine frames into MP4 using imageio + imageio-ffmpeg (bundled ffmpeg) |
| 201 | + frame_files = sorted(f for f in os.listdir(viz_dir) if f.endswith('.png')) |
212 | 202 |
|
213 | 203 | success = False |
214 | 204 | try: |
215 | | - subprocess.run(ffmpeg_cmd, check=True, capture_output=True) |
| 205 | + import imageio # pylint: disable=import-outside-toplevel |
| 206 | + writer = imageio.get_writer(output, fps=fps, codec='libx264', |
| 207 | + pixelformat='yuv420p', macro_block_size=2) |
| 208 | + for fname in frame_files: |
| 209 | + writer.append_data(imageio.imread(os.path.join(viz_dir, fname))) |
| 210 | + writer.close() |
216 | 211 | success = True |
217 | | - except FileNotFoundError: |
| 212 | + except ImportError: |
218 | 213 | pass |
219 | | - except subprocess.CalledProcessError as e: |
220 | | - print(f"ffmpeg failed: {e.stderr.decode()}") |
221 | | - |
222 | | - if not success: |
223 | | - # Fall back to GIF via Pillow |
224 | | - gif_output = output.rsplit('.', 1)[0] + '.gif' |
225 | | - try: |
226 | | - from PIL import Image # pylint: disable=import-outside-toplevel |
227 | | - frames = [] |
228 | | - frame_files = sorted(f for f in os.listdir(viz_dir) if f.endswith('.png')) |
229 | | - for fname in frame_files: |
230 | | - img = Image.open(os.path.join(viz_dir, fname)) |
231 | | - frames.append(img.copy()) |
232 | | - img.close() |
233 | | - if frames: |
234 | | - duration = max(int(1000 / fps), 1) |
235 | | - frames[0].save(gif_output, save_all=True, append_images=frames[1:], |
236 | | - duration=duration, loop=0) |
237 | | - output = gif_output |
238 | | - success = True |
239 | | - print(f"ffmpeg not found; saved GIF to {gif_output}") |
240 | | - except ImportError: |
241 | | - print(f"Neither ffmpeg nor Pillow available. Frames saved to {viz_dir}/") |
242 | | - print(f"To create video: ffmpeg -framerate {fps} " |
243 | | - f"-i {frame_pattern} -c:v libx264 -pix_fmt yuv420p {output}") |
| 214 | + except Exception as exc: # pylint: disable=broad-except |
| 215 | + print(f"imageio MP4 write failed: {exc}") |
244 | 216 |
|
245 | 217 | # Clean up frames |
246 | 218 | if success: |
|
0 commit comments