Files
video-pipeline/modules/title_utils.py

116 lines
3.7 KiB
Python

from pathlib import Path
import subprocess
from datetime import datetime
def parse_stream_date(clip_path: Path) -> datetime:
"""
Extracts the stream date from a montage clip path by parsing its grandparent directory name.
Assumes format: YYYY.MM.DD or YYYY.MM.DD.N
"""
parent_dir = clip_path.parents[1]
dir_name = parent_dir.name.split(".")
if len(dir_name) < 3:
raise ValueError(f"Invalid directory name format: {parent_dir.name}")
year, month, day = map(int, dir_name[:3])
return datetime(year, month, day)
def extract_session_metadata(clip_path: Path) -> str:
"""
Returns the session directory name as metadata tag (e.g. '2025.07.01' or '2025.07.01.2')
"""
return clip_path.parents[1].name
def generate_output_filename(clip_path: Path) -> str:
"""
Generates output filename from the session name, following rules:
- Vertical clips get suffix `-vert`
- Suffix .N in session becomes `-videoN`
"""
session_name = extract_session_metadata(clip_path)
date_parts = session_name.split(".")
base_date = "".join(date_parts[:3]) # e.g., 20250701
suffix = f"-video{date_parts[3]}" if len(date_parts) > 3 else ""
vert = "-vert" if clip_path.stem.endswith(("-vert", "-vertical")) else ""
return f"Fortnite-montage-{base_date}{suffix}{vert}.mp4"
def format_overlay_text(title: str, subtitle: str, date_str: str) -> list[str]:
"""
Returns three lines for the overlay text.
"""
return [title, subtitle, date_str]
def generate_title_overlay(
intro_path: Path,
overlay_text: list[str],
output_path: Path,
font_path: Path,
is_vertical: bool = False,
):
"""
Overlays title text on top of the intro clip and creates a new video segment.
The text fades out completely 0.5 seconds before the intro ends.
"""
width, height = (1080, 1920) if is_vertical else (1920, 1080)
fade_start = 4.5
fade_duration = 0.5
# Uniform visual settings
fontcolor = "#f7338f"
shadowcolor = "0x1c0c38"
boxcolor = "0x10abba@0.5"
fontsize = 64
y_offsets = [0, 80, 160] # vertical positions for each line
# Escape Windows-style font path
escaped_font_path = str(font_path).replace("\\", "\\\\")
drawtext_filters = []
for i, (line, y_offset) in enumerate(zip(overlay_text, y_offsets)):
drawtext = (
f"drawtext=text='{line}':"
f"fontfile='{escaped_font_path}':"
f"x=(w-text_w)/2:"
f"y=(h/2)-90+{y_offset}:"
f"fontsize={fontsize}:"
f"fontcolor={fontcolor}:"
f"shadowcolor={shadowcolor}:"
f"shadowx=2:shadowy=2:"
f"box=1:boxcolor={boxcolor}"
)
drawtext_filters.append(drawtext)
drawtext_filters.append(f"fade=t=out:st={fade_start}:d={fade_duration}:alpha=1")
drawtext_filter = ",".join(drawtext_filters)
ffmpeg_cmd = [
"ffmpeg",
"-y",
"-i", str(intro_path),
"-vf", drawtext_filter,
"-c:v", "libx264",
"-preset", "ultrafast",
"-t", "5",
"-pix_fmt", "yuv420p",
str(output_path)
]
subprocess.run(ffmpeg_cmd, check=True)
def generate_montage_title(session_name: str) -> str:
"""
Generates YouTube/PeerTube title for montage videos.
Example:
'#Fortnite #Solo #Zerobuild #Highlights with Gramps from July 1, 2025'
"""
parts = session_name.split(".")
year, month, day = map(int, parts[:3])
suffix = f" Video {parts[3]}" if len(parts) > 3 else ""
date_str = datetime(year, month, day).strftime("%B %d, %Y").replace(" 0", " ")
return f"#Fortnite #Solo #Zerobuild #Highlights with Gramps from {date_str}{suffix}"