mot / batch_renderer.py
digidau's picture
Create batch_renderer.py
25478a4 verified
Raw
History Blame Contribute Delete
4.89 kB
# batch_renderer.py
import os
import subprocess
from pathlib import Path
from renderer import render
from usage_tracker import (
build_usage_map,
consume_scene,
)
from config import (
OUTPUT_DIR,
)
# =====================================================
# CHUNK LIST
# =====================================================
def chunk_list(
items,
size
):
for i in range(
0,
len(items),
size
):
yield items[
i:i+size
]
# =====================================================
# BUILD PART PATH
# =====================================================
def build_part_path(
index
):
return (
OUTPUT_DIR
/
f"part_{index:03d}.mp4"
)
# =====================================================
# RENDER PARTS
# =====================================================
def render_parts(
timeline,
batch_size=50
):
if not timeline:
raise ValueError(
"Timeline is empty"
)
usage_map = build_usage_map(
timeline
)
part_files = []
batches = list(
chunk_list(
timeline,
batch_size
)
)
total_batches = len(
batches
)
print()
print(
f"[Batch] "
f"Total Batches: "
f"{total_batches}"
)
print()
for idx, batch in enumerate(
batches,
start=1
):
part_path = build_part_path(
idx
)
print(
f"[Batch] "
f"Rendering "
f"{idx}/{total_batches}"
)
render(
batch,
part_path
)
part_files.append(
str(part_path)
)
# -------------------------
# CLEANUP USED VARIANTS
# -------------------------
deleted = 0
for scene in batch:
delete_file = consume_scene(
usage_map,
scene
)
if not delete_file:
continue
try:
if os.path.exists(
scene["path"]
):
os.remove(
scene["path"]
)
deleted += 1
except Exception as e:
print(
f"[Cleanup] "
f"{scene['path']}"
)
print(e)
print(
f"[Batch] "
f"Deleted "
f"{deleted} clips"
)
print()
return part_files
# =====================================================
# CONCAT PARTS
# =====================================================
def concat_parts(
part_files
):
if not part_files:
raise ValueError(
"No part files"
)
concat_txt = (
OUTPUT_DIR
/ "parts.txt"
)
with open(
concat_txt,
"w",
encoding="utf-8"
) as f:
for part in part_files:
path = (
Path(part)
.resolve()
.as_posix()
)
f.write(
f"file '{path}'\n"
)
final_video = (
OUTPUT_DIR
/ "video_only.mp4"
)
cmd = [
"ffmpeg",
"-y",
"-f",
"concat",
"-safe",
"0",
"-i",
str(concat_txt),
"-c",
"copy",
str(final_video)
]
subprocess.run(
cmd,
check=True,
capture_output=True
)
# -------------------------
# DELETE PART FILES
# -------------------------
for part in part_files:
try:
os.remove(
part
)
except:
pass
try:
os.remove(
concat_txt
)
except:
pass
return str(
final_video
)
# =====================================================
# RENDER FINAL VIDEO
# =====================================================
def render_final_video(
timeline,
batch_size=50
):
parts = render_parts(
timeline,
batch_size
)
return concat_parts(
parts
)
# =====================================================
# TEST
# =====================================================
if __name__ == "__main__":
timeline = [
{
"path": "clips/1_A.mp4",
"duration": 7.0
},
{
"path": "clips/2_A.mp4",
"duration": 7.0
},
{
"path": "clips/3_A.mp4",
"duration": 7.0
},
]
output = render_final_video(
timeline,
batch_size=2
)
print()
print(
"Output:",
output
)