def get_random_start_point(): start_points = [...
Created on: May 8, 2025
Created on: May 8, 2025
def get_random_start_point():
start_points = [
'center', 'top_left', 'top_right', 'bottom_left', 'bottom_right',
'center_left', 'center_right', 'center_top', 'center_bottom'
]
start_point = random.choice(start_points)
textif start_point == 'center': start_x = "iw/2-(iw/zoom/2)" start_y = "ih/2-(ih/zoom/2)" elif start_point == 'top_left': start_x = "0" start_y = "0" elif start_point == 'top_right': start_x = "iw-(iw/zoom)" start_y = "0" elif start_point == 'bottom_left': start_x = "0" start_y = "ih-(ih/zoom)" elif start_point == 'bottom_right': start_x = "iw-(iw/zoom)" start_y = "ih-(ih/zoom)" elif start_point == 'center_left': start_x = "0" start_y = "ih/2-(ih/zoom/2)" elif start_point == 'center_right': start_x = "iw-(iw/zoom)" start_y = "ih/2-(ih/zoom/2)" elif start_point == 'center_top': start_x = "iw/2-(iw/zoom/2)" start_y = "0" elif start_point == 'center_bottom': start_x = "iw/2-(iw/zoom/2)" start_y = "ih-(ih/zoom)" return start_x, start_y
def process_horizontal_image_fill(image_path, video_resolution, framerate, bitrate, bufsize, temp_video_folder,
filename, duration, zoom_time, center_zoom):
if center_zoom:
start_x = "iw/2-(iw/zoom/2)"
start_y = "ih/2-(ih/zoom/2)"
else:
start_x, start_y = get_random_start_point()
texttemp_video_file = os.path.join(temp_video_folder, f'temp_{filename}.mp4') subprocess.run( ['ffmpeg', '-y', '-i', image_path, '-lavfi', f'[0:v]scale={video_resolution[0]}:{video_resolution[1]},' f'boxblur=luma_radius=min(h\,w)/20:luma_power=1:chroma_radius=min(cw\,ch)/20:chroma_power=1[bg];' f'[bg]scale={video_resolution[0]}:{video_resolution[1]}[bg_scaled];' f'[0:v]scale=1920:1080[ov];' f'[ov]crop=1720:1080:100:0[zv];' f'[bg_scaled][zv]overlay=(W-w)/2:(H-h)/2[full];' f'[full]scale=12000:-1[scaled_full];' f'[scaled_full]zoompan=z=\'min(zoom+0.0005,1.5)\':x=\'{start_x}\':y=\'{start_y}\':d={zoom_time},trim=duration={duration},' f'scale={video_resolution[0]}:{video_resolution[1]}', '-r', str(framerate), '-c:v', 'libx264', '-preset', 'ultrafast', '-b:v', f'{bitrate}k', '-maxrate', f'{bitrate}k', '-bufsize', f'{bufsize}k', temp_video_file, '-pix_fmt', 'yuv420p'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) return temp_video_file
def process_vertical_image(image_path, video_resolution, framerate, bitrate, bufsize, temp_video_folder, filename,
duration, zoom_time, center_zoom):
if center_zoom:
start_x = "iw/2-(iw/zoom/2)"
start_y = "ih/2-(ih/zoom/2)"
else:
start_x, start_y = get_random_start_point()
texttemp_video_file = os.path.join(temp_video_folder, f'temp_{filename}.mp4') subprocess.run( ['ffmpeg', '-y', '-i', image_path, '-lavfi', f'[0:v]scale={video_resolution[0]}:{video_resolution[1]},' f'boxblur=luma_radius=min(h\,w)/20:luma_power=1:chroma_radius=min(cw\,ch)/20:chroma_power=1[bg];' f'[bg]scale={video_resolution[0]}:{video_resolution[1]}[bg_scaled];' f'[0:v]scale=-1:{video_resolution[1]}[ov];' f'[bg_scaled][ov]overlay=(W-w)/2:(H-h)/2[full];' f'[full]scale=12000:-1[scaled_full];' f'[scaled_full]zoompan=z=\'min(zoom+0.0005,1.5)\':x=\'{start_x}\':y=\'{start_y}\':d={zoom_time},trim=duration={duration},' f'scale={video_resolution[0]}:{video_resolution[1]}', '-r', str(framerate), '-c:v', 'libx264', '-preset', 'ultrafast', '-b:v', f'{bitrate}k', '-maxrate', f'{bitrate}k', '-bufsize', f'{bufsize}k', temp_video_file, '-pix_fmt', 'yuv420p'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) return temp_video_file
используя эти функции - напиши мне готовый код, который будет заходить в мою папку D:\Downloads\Cat - считывать все фотофайлы различных разрешений - и на основе моих выше функций создавать видео сегменты через ффмпег и сабпроцесс. длина 10 секунд. битрейт 8mbps. разрешение full hd
положение зума - рандомное
пиши полный код.
pythonimport os import glob import random import subprocess from pathlib import Path from PIL import Image def get_random_start_point() -> tuple[str, str]: """ Случайно выбирает стартовую точку для эффекта zoom‑pan в FFmpeg. Возвращает кортеж строк‑выражений (start_x, start_y), понятных FFmpeg‑фильтру. """ start_points = [ "center", "top_left", "top_right", "bottom_left", "bottom_right", "center_left", "center_right", "center_top", "center_bottom" ] start_point = random.choice(start_points) if start_point == "center": start_x = "iw/2-(iw/zoom/2)" start_y = "ih/2-(ih/zoom/2)" elif start_point == "top_left": start_x, start_y = "0", "0" elif start_point == "top_right": start_x, start_y = "iw-(iw/zoom)", "0" elif start_point == "bottom_left": start_x, start_y = "0", "ih-(ih/zoom)" elif start_point == "bottom_right": start_x, start_y = "iw-(iw/zoom)", "ih-(ih/zoom)" elif start_point == "center_left": start_x, start_y = "0", "ih/2-(ih/zoom/2)" elif start_point == "center_right": start_x, start_y = "iw-(iw/zoom)", "ih/2-(ih/zoom/2)" elif start_point == "center_top": start_x, start_y = "iw/2-(iw/zoom/2)", "0" elif start_point == "center_bottom": start_x, start_y = "iw/2-(iw/zoom/2)", "ih-(ih/zoom)" else: # на всякий случай start_x, start_y = "iw/2-(iw/zoom/2)", "ih/2-(ih/zoom/2)" return start_x, start_y def process_horizontal_image_fill( image_path: str, video_resolution: tuple[int, int], framerate: int, bitrate: int, bufsize: int, temp_video_folder: str, filename: str, duration: int, zoom_time: int, center_zoom: bool, ) -> str: """Создать видео из горизонтального изображения, заполнив фон блюром.""" if center_zoom: start_x = "iw/2-(iw/zoom/2)" start_y = "ih/2-(ih/zoom/2)" else: start_x, start_y = get_random_start_point() temp_video_file = os.path.join(temp_video_folder, f"{filename}.mp4") subprocess.run( [ "ffmpeg", "-y", "-i", image_path, "-lavfi", ( # 0:v → [bg] размытый фон f"[0:v]scale={video_resolution[0]}:{video_resolution[1]}," f"boxblur=luma_radius=min(h\\,w)/20:luma_power=1:" f"chroma_radius=min(cw\\,ch)/20:chroma_power=1[bg];" # фон до нужного разрешения f"[bg]scale={video_resolution[0]}:{video_resolution[1]}[bg_scaled];" # оригинал в Full HD f"[0:v]scale=1920:1080[ov];" # опционально кадрируем «лишнее» (пример crop) f"[ov]crop=1720:1080:100:0[zv];" # накладываем на фон f"[bg_scaled][zv]overlay=(W-w)/2:(H-h)/2[full];" # увеличиваем «полотно» для zoom‑pan (работает быстрее, когда исходник крупнее) f"[full]scale=12000:-1[scaled_full];" # сам зум‑пан + trim до указанной длительности f"[scaled_full]zoompan=" f"z='min(zoom+0.0005,1.5)':" f"x='{start_x}':y='{start_y}':" f"d={zoom_time}," f"trim=duration={duration}," f"scale={video_resolution[0]}:{video_resolution[1]}" ), "-r", str(framerate), "-c:v", "libx264", "-preset", "ultrafast", "-b:v", f"{bitrate}k", "-maxrate", f"{bitrate}k", "-bufsize", f"{bufsize}k", "-pix_fmt", "yuv420p", temp_video_file, ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, ) return temp_video_file def process_vertical_image( image_path: str, video_resolution: tuple[int, int], framerate: int, bitrate: int, bufsize: int, temp_video_folder: str, filename: str, duration: int, zoom_time: int, center_zoom: bool, ) -> str: """Создать видео из вертикального изображения, заполнив фон блюром.""" if center_zoom: start_x = "iw/2-(iw/zoom/2)" start_y = "ih/2-(ih/zoom/2)" else: start_x, start_y = get_random_start_point() temp_video_file = os.path.join(temp_video_folder, f"{filename}.mp4") subprocess.run( [ "ffmpeg", "-y", "-i", image_path, "-lavfi", ( # 0:v → [bg] размытый фон f"[0:v]scale={video_resolution[0]}:{video_resolution[1]}," f"boxblur=luma_radius=min(h\\,w)/20:luma_power=1:" f"chroma_radius=min(cw\\,ch)/20:chroma_power=1[bg];" # фон до нужного разрешения f"[bg]scale={video_resolution[0]}:{video_resolution[1]}[bg_scaled];" # оригинал доводим по высоте f"[0:v]scale=-1:{video_resolution[1]}[ov];" # накладываем на фон f"[bg_scaled][ov]overlay=(W-w)/2:(H-h)/2[full];" # увеличиваем «полотно» для zoom‑pan f"[full]scale=12000:-1[scaled_full];" # zoom‑pan + trim f"[scaled_full]zoompan=" f"z='min(zoom+0.0005,1.5)':" f"x='{start_x}':y='{start_y}':" f"d={zoom_time}," f"trim=duration={duration}," f"scale={video_resolution[0]}:{video_resolution[1]}" ), "-r", str(framerate), "-c:v", "libx264", "-preset", "ultrafast", "-b:v", f"{bitrate}k", "-maxrate", f"{bitrate}k", "-bufsize", f"{bufsize}k", "-pix_fmt", "yuv420p", temp_video_file, ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, ) return temp_video_file def create_videos_from_folder( image_folder: str, *, duration: int = 10, framerate: int = 30, bitrate_kbps: int = 8000, resolution: tuple[int, int] = (1920, 1080), ) -> None: """Проходит по всем изображениям в папке и создаёт видео‑сегменты.""" image_folder = os.path.abspath(image_folder) output_dir = os.path.join(image_folder, "videos") os.makedirs(output_dir, exist_ok=True) bufsize = bitrate_kbps * 2 # рекомендация: x2 относительно maxrate zoom_time = duration * framerate # FFmpeg ждёт количество кадров # поддерживаемые расширения patterns = ("*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.tif", "*.tiff", "*.webp") image_files: list[Path] = [] for pattern in patterns: image_files.extend(Path(image_folder).rglob(pattern)) if not image_files: print("В указанной папке не найдено изображений.") return for img_path in image_files: # Определяем ориентацию with Image.open(img_path) as img: width, height = img.size filename = img_path.stem try: if width >= height: result = process_horizontal_image_fill( str(img_path), resolution, framerate, bitrate_kbps, bufsize, output_dir, filename, duration, zoom_time, center_zoom=False, ) else: result = process_vertical_image( str(img_path), resolution, framerate, bitrate_kbps, bufsize, output_dir, filename, duration, zoom_time, center_zoom=False, ) print(f"✅ Создано видео: {result}") except subprocess.CalledProcessError as err: print(f"❌ Ошибка FFmpeg при обработке {img_path.name}:\n{err.stderr.decode('utf‑8')}") if __name__ == "__main__": # Папка с изображениями IMAGE_DIR = r"D:\Downloads\Cat" create_videos_from_folder(IMAGE_DIR)
D:\Downloads\Cat
(вложенные папки тоже) на все популярные форматы изображений.process_horizontal_image_fill
или process_vertical_image
).videos
внутри исходной директории.Важно: убедитесь, что FFmpeg доступен в
PATH
, и установлен пакетPillow
(pip install pillow
).