Skip to content

Instantly share code, notes, and snippets.

@bonprosoft
Created June 27, 2022 15:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bonprosoft/ae56b158fd79145892d66ce5a690692f to your computer and use it in GitHub Desktop.
Save bonprosoft/ae56b158fd79145892d66ce5a690692f to your computer and use it in GitHub Desktop.
Create a tiled layout video
import argparse
import itertools
import os
import pathlib
import shlex
from typing import List
def _accumulated_size(idx: int, prefix: str) -> str:
if idx == 0:
return "0"
sizes: List[str] = [f"{prefix}{i}" for i in range(idx)]
return "+".join(sizes)
def _compose_xstack_layout(num_videos: int, stack_w: int, stack_h: int) -> str:
layouts = []
# NOTE: stack videos in horizontal order
stacks = itertools.product(range(stack_h), range(stack_w))
for ih, iw in itertools.islice(stacks, num_videos):
layouts.append(f"{_accumulated_size(iw, 'w')}_{_accumulated_size(ih, 'h')}")
return "|".join(layouts)
def compose_ffmpeg_command(
stack_w: int, stack_h: int, videos: List[pathlib.Path], output: pathlib.Path
) -> str:
num_videos = min(stack_w * stack_h, len(videos))
assert num_videos > 0
video_mapping = "".join(f"[{i}:v]" for i in range(num_videos))
layout = _compose_xstack_layout(num_videos, stack_w, stack_h)
filter_xstack = f"{video_mapping}xstack=inputs={num_videos}:layout={layout}"
video_sources = " ".join(f"-i {path}" for path in videos)
return f"ffmpeg {video_sources} -filter_complex '{filter_xstack}' {output}"
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--stack-w", type=int, default=5)
parser.add_argument("--stack-h", type=int, default=4)
parser.add_argument(
"target_dir", type=pathlib.Path, help="A directory path to glob"
)
parser.add_argument("pattern", type=str, help="A pattern to glob video files")
parser.add_argument("outpath", type=pathlib.Path, help="Output path of video file")
args = parser.parse_args()
target_dir: pathlib.Path = args.target_dir
assert target_dir.exists()
assert target_dir.is_dir()
video_files = list(target_dir.glob(args.pattern))
cmd_str = compose_ffmpeg_command(
args.stack_w, args.stack_h, video_files, args.outpath
)
print(f"> {cmd_str}")
cmd = shlex.split(cmd_str)
os.execvp(cmd[0], cmd)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment