Buckets:
| import argparse, os, subprocess, sys, glob | |
| from pathlib import Path | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('--images-dir', required=True) | |
| parser.add_argument('--audio', default='') | |
| parser.add_argument('--output', required=True) | |
| parser.add_argument('--fps', type=int, default=24) | |
| parser.add_argument('--duration-per-image', type=float, default=3.0) | |
| args = parser.parse_args() | |
| image_exts = ('*.png', '*.jpg', '*.jpeg', '*.webp') | |
| images = [] | |
| for ext in image_exts: | |
| images.extend(sorted(glob.glob(os.path.join(args.images_dir, '**', ext), recursive=True))) | |
| if not images: | |
| print(f"ERROR: No images found in {args.images_dir}") | |
| sys.exit(1) | |
| print(f" Images: {len(images)} files") | |
| os.makedirs(os.path.dirname(args.output), exist_ok=True) | |
| # Create video with ffmpeg | |
| # Each image displayed for N seconds, then concat | |
| ffmpeg = "ffmpeg" | |
| if os.name == 'nt': | |
| ffmpeg = "ffmpeg.exe" | |
| cmd = [ffmpeg, '-y'] | |
| # Input each image | |
| for img in images: | |
| cmd.extend(['-loop', '1', '-i', img]) | |
| # Use concat demuxer for simpler reliable approach | |
| # Create temp file listing all images with duration | |
| concat_file = os.path.join(os.path.dirname(args.output), '_concat.txt') | |
| with open(concat_file, 'w') as f: | |
| for img in images: | |
| f.write(f"file '{img}'\n") | |
| f.write(f"duration {args.duration_per_image}\n") | |
| cmd = [ffmpeg, '-y', '-f', 'concat', '-safe', '0', '-i', concat_file] | |
| # Scale to 1920x1080 | |
| cmd.extend(['-vf', 'scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,fps=24', | |
| '-c:v', 'libx264', '-preset', 'medium', '-crf', '23']) | |
| # Add audio if provided | |
| if args.audio and os.path.exists(args.audio): | |
| cmd.extend(['-i', args.audio, '-c:a', 'aac', '-b:a', '128k', '-shortest']) | |
| else: | |
| cmd.extend(['-an']) | |
| cmd.extend(['-pix_fmt', 'yuv420p', '-r', str(args.fps), '-preset', 'medium', args.output]) | |
| print(f" Running: {' '.join(cmd[:4])} ...") | |
| result = subprocess.run(cmd, capture_output=True, text=True) | |
| if result.returncode == 0: | |
| size = os.path.getsize(args.output) | |
| print(f" -> Video saved: {args.output} ({size/1024/1024:.1f} MB)") | |
| else: | |
| print(f" ERROR: {result.stderr}") | |
| sys.exit(1) | |
Xet Storage Details
- Size:
- 2.19 kB
- Xet hash:
- 341c3cca67adce0a0ecd1e11754ca37b42202336349d0d3f40536ca743aeefbd
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.