#!/usr/bin/env python3 import os import json import package.utils as utils import tempfile from pathlib import Path from moviepy.video.VideoClip import TextClip from pathlib import Path def get_video_info(input_path: Path) -> tuple: """Uses ffprobe to instantly read input video dimensions and frame rate.""" cmd = f"ffprobe -v error -select_streams v:0 -show_entries stream=width,height,r_frame_rate -of json {input_path}" # Run command and capture output (assumes linux.run_command prints or you use subprocess) import subprocess result = subprocess.run(cmd, shell=True, capture_output=True, text=True) #linux.run_command(cmd) try: data = json.loads(result.stdout) stream = data['streams'][0] w = int(stream['width']) h = int(stream['height']) # Convert fractional FPS string (e.g. "60/1" or "30000/1001") to float fps_parts = stream['r_frame_rate'].split('/') fps = float(fps_parts[0]) / float(fps_parts[1]) if len(fps_parts) > 1 else float(fps_parts[0]) return w, h, fps except Exception: return 1920, 1080, 60.0 # Safe defaults if probe fails def fit_to_9_16_letterbox(input_path: str, top_text: str = "TOP TEXT", bottom_text: str = "BOTTOM TEXT", use_blur: bool = True, force: bool = False): threads = "8" input_file = Path(input_path) output_suffix = "gaussian_9_16" if use_blur else "black_9_16" output_path = input_file.parent / f"{input_file.stem}_{output_suffix}{input_file.suffix}" if os.path.exists(output_path): if not force: print(f"❌ Error: Short video already exists: {output_path}") return else: os.remove(output_path) # 1. Probe input metadata instantly orig_w, orig_h, fps = get_video_info(input_file) canvas_w = 1080 canvas_h = 1920 print("✍️ Generating text overlay graphics via MoviePy...") # Render static images for text instead of running a video context title_clip = TextClip( text=top_text, font_size=55, color="white", font="DejaVuSans-Bold", text_align="center", size=(canvas_w - 100, 300), method="caption" ) bottom_clip = TextClip( text=bottom_text, font_size=55, color="white", font="DejaVuSans-Bold", text_align="center", size=(canvas_w - 100, 300), method="caption" ) # Save text layers to temporary PNGs top_png = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name bottom_png = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name title_clip.save_frame(top_png) bottom_clip.save_frame(bottom_png) title_clip.close() bottom_clip.close() print("🎬 Dispatching compilation workload to FFmpeg filtergraph...") # 2. Build the complex FFmpeg filtergraph # [0:v] is the raw input video stream filter_complex = [] if use_blur: # Scale height to 1920, crop center 1080x1920, apply fast boxblur (power of 3 approximates Gaussian) filter_complex.append( f"[0:v]scale=-1:{canvas_h},crop={canvas_w}:{canvas_h}:(iw-{canvas_w})/2:0,boxblur=luma_radius=35:luma_power=3[bg];" ) else: # Generate a pure black background canvas matching video frame specs filter_complex.append( f"color=c=black:s={canvas_w}x{canvas_h}:r={fps}[bg];" ) # Scale the foreground video to a clean 1080 width, keeping aspect ratio filter_complex.append( f"[0:v]scale={canvas_w}:-1[fg];" ) # Layer composition chain: # Overlay 1: Put scaled foreground onto background (centered vertically) filter_complex.append( f"[bg][fg]overlay=0:(H-h)/2[tmp1];" ) # Overlay 2: Drop top text asset onto position Y=180 filter_complex.append( f"[tmp1][1:v]overlay=(W-w)/2:180[tmp2];" ) # Overlay 3: Drop bottom text asset onto position Y=1430 filter_complex.append( f"[tmp2][2:v]overlay=(W-w)/2:1430[finalv]" ) filter_graph = "".join(filter_complex) # 3. Execute the native assembly command # -map_chapters -1 -sn: Strips unnecessary metadata chunks instantly # -c:a copy: Safely pulls original digital audio directly without decompression cycles # -threads 0: Forces FFmpeg to auto-consume all available processing cores ffmpeg_cmd = ( f'ffmpeg -y -v error -i "{input_file}" -i "{top_png}" -i "{bottom_png}" ' f'-filter_complex "{filter_graph}" ' f'-map "[finalv]" -map 0:a? -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p ' f'-c:a copy -map_chapters -1 -sn -threads {threads} "{output_path}"' ) try: utils.run_command(ffmpeg_cmd) print(f"🎉 High-speed processing complete! Video saved to: {output_path}") finally: # Clean up temporary PNG picture files safely for path in (top_png, bottom_png): if os.path.exists(path): os.unlink(path) if __name__ == "__main__": import time start_time = time.perf_counter() creator_name = "greenskiesbluegrass" top_txt = "TeamPGP Live on Twitch...\n Every Friday and Sunday @7:30 ET.\n twitch.tv/teampgp" bottom_txt = f"Clipped By: {creator_name}." fit_to_9_16_letterbox("download/clips/AbnegateAgitatedGrassPJSalt/AbnegateAgitatedGrassPJSalt.mp4", top_txt, bottom_txt, True, True) end_time = time.perf_counter() execution_time = end_time - start_time print(f"The function took {execution_time:.6f} seconds to complete.")