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
+4 -1
View File
@@ -9,7 +9,10 @@
"type": "debugpy", "type": "debugpy",
"request": "launch", "request": "launch",
"program": "${file}", "program": "${file}",
"args": [] "args": [
"--convert",
"/home/richard/downloader/save/clips/VibrantKathishLobsterPunchTrees/VibrantKathishLobsterPunchTrees.mp4"
]
} }
] ]
} }
+1 -1
View File
@@ -5,7 +5,7 @@ from datetime import date as datetime_date
from pathlib import Path from pathlib import Path
class Database: class Database:
def __init__(self, table): def __init__(self, table: str):
self.table = table self.table = table
self.columns = "" self.columns = ""
+2 -2
View File
@@ -7,7 +7,7 @@ from whisper.utils import get_writer
model = whisper.load_model("base") model = whisper.load_model("base")
def extract_audio(video_path, audio_temp_path): def extract_audio(video_path: str, audio_temp_path: str):
# Create a temporary path for a sanitized copy of the video # Create a temporary path for a sanitized copy of the video
sanitized_video_path = video_path.replace(".mp4", "_clean.mp4") sanitized_video_path = video_path.replace(".mp4", "_clean.mp4")
@@ -40,7 +40,7 @@ def extract_audio(video_path, audio_temp_path):
os.remove(sanitized_video_path) os.remove(sanitized_video_path)
def transcribe_to_srt(audio_path, output_directory, output_filename): def transcribe_to_srt(audio_path: str, output_directory: str, output_filename: str):
print("Transcribing audio...") print("Transcribing audio...")
result = model.transcribe(audio_path) result = model.transcribe(audio_path)
+8 -8
View File
@@ -29,9 +29,9 @@ def run_linux_command(command: str):
# Handles errors if the Linux command returns a non-zero exit code # Handles errors if the Linux command returns a non-zero exit code
return {"success": False, "stdout": e.stdout, "stderr": e.stderr} return {"success": False, "stdout": e.stdout, "stderr": e.stderr}
def transcribe(slug: str): def transcribe(id: str):
"""Transcribes the Video File.""" """Transcribes the Video File."""
video_file = f"save/{DB.table}/{slug}/{slug}.mp4" video_file = f"save/{DB.table}/{id}/{id}.mp4"
# Check if the video file exists # Check if the video file exists
if not os.path.exists(video_file): if not os.path.exists(video_file):
@@ -39,18 +39,18 @@ def transcribe(slug: str):
return False return False
# no need to continue if srt transcribe file already exists # no need to continue if srt transcribe file already exists
if os.path.exists(f"save/{DB.table}/{slug}/transcribe_{slug}.srt"): if os.path.exists(f"save/{DB.table}/{id}/transcribe_{id}.srt"):
print(f"video already transcribed:") print(f"video already transcribed:")
return True return True
transcribe_video.extract_audio(video_file, f"save/{DB.table}/{slug}/temp_{slug}_audio.wav") transcribe_video.extract_audio(video_file, f"save/{DB.table}/{id}/temp_{id}_audio.wav")
transcribe_video.transcribe_to_srt(f"save/{DB.table}/{slug}/temp_{slug}_audio.wav", f"save/{DB.table}/{slug}/", f"transcribe_{slug}") transcribe_video.transcribe_to_srt(f"save/{DB.table}/{id}/temp_{id}_audio.wav", f"save/{DB.table}/{id}/", f"transcribe_{id}")
return True return True
def top_hashtags(slug: str): def top_hashtags(id: str):
""""Hashtags from transcribed SRT file.""" """"Hashtags from transcribed SRT file."""
file_srt = f"save/{DB.table}/{slug}/transcribe_{slug}.srt" file_srt = f"save/{DB.table}/{id}/transcribe_{id}.srt"
# if srt transcribe file not exists # if srt transcribe file not exists
if not os.path.exists(file_srt): if not os.path.exists(file_srt):
@@ -99,7 +99,7 @@ def upload():
unuploaded = DB.get_unuploaded() unuploaded = DB.get_unuploaded()
twitch_datetime = " #Twitch Every Friday and Sunday @7:30 EST https://twitch.tv/teampgp" twitch_datetime = " Live on Twitch Every Friday and Sunday @7:30 ET https://twitch.tv/teampgp"
file_path = "" file_path = ""
title = "" title = ""
description = "" description = ""
+2 -2
View File
@@ -11,7 +11,7 @@ nltk.download('punkt', quiet=True)
nltk.download('stopwords', quiet=True) nltk.download('stopwords', quiet=True)
nltk.download('punkt_tab', quiet=True) nltk.download('punkt_tab', quiet=True)
def extract_text_from_srt(file_path): def extract_text_from_srt(file_path: str):
with open(file_path, 'r', encoding='utf-8') as file: with open(file_path, 'r', encoding='utf-8') as file:
content = file.read() content = file.read()
# Remove SRT timestamps and sequence numbers # Remove SRT timestamps and sequence numbers
@@ -19,7 +19,7 @@ def extract_text_from_srt(file_path):
clean_text = re.sub(r'\d+', '', clean_text) clean_text = re.sub(r'\d+', '', clean_text)
return clean_text return clean_text
def get_top_hashtags(srt_file_path, top_n=10): def get_top_hashtags(srt_file_path: str, top_n: int = 10):
raw_text = extract_text_from_srt(srt_file_path) raw_text = extract_text_from_srt(srt_file_path)
# Lowercase and remove punctuation # Lowercase and remove punctuation
+162 -14
View File
@@ -1,22 +1,157 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import sys import sys
import subprocess
import tempfile
from pathlib import Path 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}"
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
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) input_file = Path(input_path)
output_path = f"{input_file.stem}_shorts{input_file.suffix}" # 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}"
# Load the video file print("Sanitizing video metadata for MoviePy parser...")
clip = VideoFileClip(input_path) 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 # 1. Check video duration
if clip.duration >= 60: if clip.duration >= 60:
print(f"❌ Error: Video is {clip.duration:.2f}s long. It must be under 60 seconds.") print(f"❌ Error: Video is {clip.duration:.2f}s long. It must be under 60 seconds.")
clip.close() clip.close()
if sanitized_input_path.exists():
sanitized_input_path.unlink()
return False return False
print(f"✅ Video length check passed ({clip.duration:.2f}s). Processing...") 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_height = clip.h
target_width = int(target_height * (9 / 16)) target_width = int(target_height * (9 / 16))
# 3. Crop the center of the video # 3. FIX FOR v2.x: Use native .cropped() method directly on the clip
# x_center centers the crop horizontally print("🎬 Cropping video to 9:16 vertical view...")
short_clip = clip.crop(width=target_width, height=target_height, x_center=clip.w / 2, y_center=clip.h / 2) 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: 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( short_clip.write_videofile(
output_path, str(output_path),
codec="libx264", codec="libx264",
audio_codec="aac", audio_codec="aac",
fps=clip.fps fps=clip.fps
) )
# Clean up files # Clean up files and close streams
clip.close() clip.close()
short_clip.close() short_clip.close()
if sanitized_input_path.exists():
sanitized_input_path.unlink()
print(f"🎉 Success! Short saved as: {output_path}") print(f"🎉 Success! Short saved as: {output_path}")
return True return True
def upload_shorts(): def upload_shorts():
"""Loops over the downloaded videos entries and uploaded them to youtube.""" """Loops over the downloaded videos entries and uploaded them to youtube."""
print("Uploading Shorts...") print("Uploading Shorts...")
@@ -90,7 +236,9 @@ def main():
# Execute conversion step if path is provided # Execute conversion step if path is provided
if args.convert: 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 # Execute database upload step if flag is provided
if args.upload: if args.upload: