update short scripts

This commit is contained in:
2026-07-21 16:50:05 -04:00
parent 19e525d64c
commit 8e4a34dce1
6 changed files with 179 additions and 28 deletions
+162 -14
View File
@@ -1,22 +1,157 @@
#!/usr/bin/env python3
import argparse
import sys
import subprocess
import tempfile
from pathlib import Path
from moviepy import VideoFileClip
from moviepy import VideoFileClip, ColorClip, CompositeVideoClip
from moviepy.video.VideoClip import TextClip
def convert_to_short(input_path):
from pathlib import Path
import numpy as np
from PIL import Image, ImageFilter
def apply_gaussian_blur(frame, radius: int = 30):
"""
Transforms a single NumPy array frame using PIL's true GaussianBlur filter.
"""
# Convert numpy array to PIL Image
image = Image.fromarray(frame)
# Apply high-quality true Gaussian Blur
blurred_image = image.filter(ImageFilter.GaussianBlur(radius=radius))
# Return back as a numpy array for MoviePy
return np.array(blurred_image)
def fit_to_9_16_letterbox(input_path: str, top_text: str = "TOP TEXT", bottom_text: str = "BOTTOM TEXT", use_blur: bool = True):
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}"
output_path = f"{input_file.stem}_shorts{input_file.suffix}"
print("🧼 Sanitizing video metadata streams inside an automated safe context...")
with tempfile.NamedTemporaryFile(suffix=input_file.suffix, delete=False) as temp_file:
temp_path = temp_file.name
# Load the video file
clip = VideoFileClip(input_path)
bg_scaled = None
bg_cropped = None
background_layer = None
try:
cleanup_cmd = [
"ffmpeg", "-y", "-i", str(input_file),
"-map_chapters", "-1", "-sn",
"-c", "copy", temp_path
]
subprocess.run(cleanup_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# Load video
clip = VideoFileClip(temp_path)
# Standard vertical 9:16 canvas sizes
canvas_w = 1080
canvas_h = 1920
# Background logic
if use_blur:
print("📐 Scaling, cropping, and blurring background layer...")
bg_scaled = clip.resized(height=canvas_h)
bg_cropped = bg_scaled.cropped(width=canvas_w, x_center=bg_scaled.w / 2)
background_layer = bg_cropped.transform(lambda gf, t: apply_gaussian_blur(gf(t), radius=35))
else:
print("⚫ Creating solid black background canvas...")
background_layer = ColorClip(size=(canvas_w, canvas_h), color=(0, 0, 0), duration=clip.duration)
print("📐 Shrinking foreground video width to fit the 1080 wide canvas...")
foreground_clip = clip.resized(width=canvas_w)
print("✍️ Creating multi-line top title text clip...")
# FIXED: Added 'method="caption"' and increased vertical size to 300
title_clip = TextClip(
text=top_text,
font_size=55, # Slightly smaller to accommodate paragraphs comfortably
color="white",
font="DejaVuSans-Bold",
text_align="center",
size=(canvas_w - 100, 300), # Subtracted 100px for safety margins on left/right edges
method="caption", # Forces text to wrap cleanly onto a new line
duration=clip.duration
)
# Position adjusted to center the taller 300px box in the upper section
positioned_top_text = title_clip.with_position(("center", 180))
print("✍️ Creating multi-line bottom text clip...")
# FIXED: Added 'method="caption"' and increased vertical size to 300
bottom_clip = TextClip(
text=bottom_text,
font_size=55,
color="white",
font="DejaVuSans-Bold",
text_align="center",
size=(canvas_w - 100, 300), # Left/right margins included
method="caption", # Forces text to wrap cleanly onto a new line
duration=clip.duration
)
# Position adjusted to center the taller 300px box in the lower section
positioned_bottom_text = bottom_clip.with_position(("center", 1430))
# Composite layers
final_clip = CompositeVideoClip(
[
background_layer,
foreground_clip.with_position("center"),
positioned_top_text,
positioned_bottom_text
]
).with_audio(clip.audio)
print("🎬 Rendering final vertical composition...")
final_clip.write_videofile(
str(output_path),
codec="libx264",
audio_codec="aac",
fps=clip.fps
)
# Clean up file locks safely
clip.close()
if bg_scaled: bg_scaled.close()
if bg_cropped: bg_cropped.close()
background_layer.close()
foreground_clip.close()
title_clip.close()
bottom_clip.close()
final_clip.close()
finally:
temp_file_path = Path(temp_path)
if temp_file_path.exists():
temp_file_path.unlink()
print(f"🎉 Text overlay video saved to: {output_path}")
def convert_to_short(input_path: str):
input_file = Path(input_path)
# Generate paths using pathlib
sanitized_input_path = input_file.parent / f"{input_file.stem}_clean{input_file.suffix}"
output_path = input_file.parent / f"{input_file.stem}_shorts{input_file.suffix}"
print("Sanitizing video metadata for MoviePy parser...")
cleanup_cmd = [
"ffmpeg", "-y", "-i", str(input_file),
"-map_chapters", "-1", "-sn",
"-c", "copy", str(sanitized_input_path)
]
subprocess.run(cleanup_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# Load the sanitized video file
clip = VideoFileClip(str(sanitized_input_path))
# 1. Check video duration
if clip.duration >= 60:
print(f"❌ Error: Video is {clip.duration:.2f}s long. It must be under 60 seconds.")
clip.close()
if sanitized_input_path.exists():
sanitized_input_path.unlink()
return False
print(f"✅ Video length check passed ({clip.duration:.2f}s). Processing...")
@@ -25,28 +160,39 @@ def convert_to_short(input_path):
target_height = clip.h
target_width = int(target_height * (9 / 16))
# 3. Crop the center of the video
# x_center centers the crop horizontally
short_clip = clip.crop(width=target_width, height=target_height, x_center=clip.w / 2, y_center=clip.h / 2)
# 3. FIX FOR v2.x: Use native .cropped() method directly on the clip
print("🎬 Cropping video to 9:16 vertical view...")
short_clip = clip.cropped(
width=target_width,
height=target_height,
x_center=clip.w / 2,
y_center=clip.h / 2
)
# Optional: Resize to standard YouTube Short resolution (1080x1920)
# 4. FIX FOR v2.x: Use native .resized() method directly on the clip
if short_clip.h != 1920:
short_clip = short_clip.resize(newsize=(1080, 1920))
print("📐 Resizing frame resolution to standard 1080x1920 Shorts format...")
short_clip = short_clip.resized(new_size=(1080, 1920))
# 4. Export the final video file
# 5. Export the final video file
short_clip.write_videofile(
output_path,
str(output_path),
codec="libx264",
audio_codec="aac",
fps=clip.fps
)
# Clean up files
# Clean up files and close streams
clip.close()
short_clip.close()
if sanitized_input_path.exists():
sanitized_input_path.unlink()
print(f"🎉 Success! Short saved as: {output_path}")
return True
def upload_shorts():
"""Loops over the downloaded videos entries and uploaded them to youtube."""
print("Uploading Shorts...")
@@ -90,7 +236,9 @@ def main():
# Execute conversion step if path is provided
if args.convert:
convert_to_short(args.convert)
clip_by = "joelmckinney"
#convert_to_short(args.convert)
fit_to_9_16_letterbox(args.convert, "TeamPGP Live on Twitch...\n Every Friday and Sunday @7:30 ET.\n twitch.tv/teampgp", f"Clipped By: {clip_by}.", True)
# Execute database upload step if flag is provided
if args.upload: