Skip to content

Commit

Permalink
✨Add ffmpeg command(v2)
Browse files Browse the repository at this point in the history
  • Loading branch information
7rikazhexde committed Jul 31, 2024
1 parent 273b5a9 commit 6497fed
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 0 deletions.
55 changes: 55 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,49 @@ def test_create_ffmpeg_command_output_size_calculation(
assert "-s 3840x2160" in result


def test_create_ffmpeg_command_v2_empty_input() -> None:
result = main.create_ffmpeg_command_v2([], "output.mp4", True)
assert result == ""


def test_create_ffmpeg_command_v2_single_input(mock_get_video_size: Any) -> None:
result = main.create_ffmpeg_command_v2(["input1.mp4"], "output.mp4", True)
expected = (
f'ffmpeg -y -i input1.mp4 -filter_complex "[0:v]scale=1920:1080:force_original_aspect_ratio=decrease,'
f"pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1[v0]; [v0]hstack=inputs=1[row0]; "
f'[row0]vstack=inputs=1[vstack]" -map "[vstack]" -map 0:a -c:v libx264 -preset veryfast '
f"-crf 23 -c:a aac -b:a 128k -threads 0 -loglevel {ffmpeg_loglevel} -s 1920x1080 output.mp4"
)
assert result == expected


def test_create_ffmpeg_command_v2_multiple_inputs(mock_get_video_size: Any) -> None:
result = main.create_ffmpeg_command_v2(
["input1.mp4", "input2.mp4", "input3.mp4", "input4.mp4"], "output.mp4", True
)
expected = (
f"ffmpeg -y -i input1.mp4 -i input2.mp4 -i input3.mp4 -i input4.mp4 "
f'-filter_complex "[0:v]scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1[v0]; '
f"[1:v]scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1[v1]; "
f"[2:v]scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1[v2]; "
f"[3:v]scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1[v3]; "
f"[v0][v1]hstack=inputs=2[row0]; [v2][v3]hstack=inputs=2[row1]; "
f'[row0][row1]vstack=inputs=2[vstack]" '
f'-map "[vstack]" -map 0:a -map 1:a -map 2:a -map 3:a -c:v libx264 -preset veryfast '
f"-crf 23 -c:a aac -b:a 128k -threads 0 -loglevel {ffmpeg_loglevel} -s 3840x2160 output.mp4"
)
assert result == expected


def test_create_ffmpeg_command_v2_output_size_calculation(
mock_get_video_size: Any,
) -> None:
result = main.create_ffmpeg_command_v2(
["input1.mp4", "input2.mp4", "input3.mp4", "input4.mp4"], "output.mp4", True
)
assert "-s 3840x2160" in result


@pytest.fixture
def mock_file_operations(monkeypatch: Any) -> None:
def mock_rename_files_with_spaces(directory: str) -> None:
Expand Down Expand Up @@ -646,6 +689,13 @@ def mock_create_ffmpeg_command(
) -> str:
return "ffmpeg_command"

"""for V2
def mock_create_ffmpeg_command_v2(
input_files: List[str], output_path: str, match_input_resolution_flag: bool
) -> str:
return "ffmpeg_command"
"""

def mock_subprocess_run(ffmpeg_command: str, shell: bool) -> None:
pass

Expand Down Expand Up @@ -680,6 +730,11 @@ def mock_listdir(directory: str) -> List[str]:
monkeypatch.setattr(
"video_grid_merge.__main__.create_ffmpeg_command", mock_create_ffmpeg_command
)
"""for v2
monkeypatch.setattr(
"video_grid_merge.__main__.create_ffmpeg_command_v2", mock_create_ffmpeg_command_v2
)
"""
monkeypatch.setattr(subprocess, "run", mock_subprocess_run)
monkeypatch.setattr(
"video_grid_merge.__main__.dlf.delete_files_in_folder",
Expand Down
59 changes: 59 additions & 0 deletions video_grid_merge/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,58 @@ def create_ffmpeg_command(
)


def create_ffmpeg_command_v2(
input_files: list[str], output_path: str, match_input_resolution_flag: bool
) -> str:
"""
Create an optimized ffmpeg command to merge multiple videos into a grid layout.
Args:
input_files (list[str]): A list of input video file paths.
output_path (str): The path for the output video file.
match_input_resolution_flag (bool): Whether to match the input video resolution.
Returns:
str: The optimized ffmpeg command string.
"""
if not input_files:
return ""

video_size = get_video_size(input_files[0]) if match_input_resolution_flag else None
video_width, video_height = video_size or (640, 480)

N = len(input_files)
sqrt_N = int(math.sqrt(N))

output_width = video_width * sqrt_N
output_height = video_height * sqrt_N

filter_complex = "".join(
[
f"[{i}:v]scale={video_width}:{video_height}:force_original_aspect_ratio=decrease,pad={video_width}:{video_height}:(ow-iw)/2:(oh-ih)/2,setsar=1[v{i}]; "
for i in range(N)
]
)
filter_complex += "".join(
[
f'{"".join([f"[v{i*sqrt_N+j}]" for j in range(sqrt_N)])}hstack=inputs={sqrt_N}[row{i}]; '
for i in range(sqrt_N)
]
)
filter_complex += (
f'{"".join([f"[row{i}]" for i in range(sqrt_N)])}vstack=inputs={sqrt_N}[vstack]'
)

return (
f"ffmpeg -y {' '.join([f'-i {input_file}' for input_file in input_files])} "
f'-filter_complex "{filter_complex}" '
f'-map "[vstack]" {" ".join([f"-map {i}:a" for i in range(len(input_files))])} '
f"-c:v libx264 -preset veryfast -crf 23 -c:a aac -b:a 128k "
f"-threads 0 -loglevel {ffmpeg_loglevel} "
f"-s {output_width}x{output_height} {output_path}"
)


def main(
input_folder: Optional[str] = None, output_folder: Optional[str] = None
) -> None:
Expand Down Expand Up @@ -361,9 +413,16 @@ def main(

create_target_video(input_folder, video_files)
input_files = get_target_files(input_folder, sorted(os.listdir(input_folder)))
# """
ffmpeg_command = create_ffmpeg_command(
input_files, output_path, match_input_resolution_flag
)
# """
"""
ffmpeg_command = create_ffmpeg_command_v2(
input_files, output_path, match_input_resolution_flag
)
"""

print("Video Grid Merge Start")
subprocess.run(ffmpeg_command, shell=True)
Expand Down

0 comments on commit 6497fed

Please sign in to comment.