update short scripts
This commit is contained in:
Vendored
+4
-1
@@ -9,7 +9,10 @@
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"args": []
|
||||
"args": [
|
||||
"--convert",
|
||||
"/home/richard/downloader/save/clips/VibrantKathishLobsterPunchTrees/VibrantKathishLobsterPunchTrees.mp4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@ from datetime import date as datetime_date
|
||||
from pathlib import Path
|
||||
|
||||
class Database:
|
||||
def __init__(self, table):
|
||||
def __init__(self, table: str):
|
||||
self.table = table
|
||||
self.columns = ""
|
||||
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ from whisper.utils import get_writer
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
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...")
|
||||
result = model.transcribe(audio_path)
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@ def run_linux_command(command: str):
|
||||
# Handles errors if the Linux command returns a non-zero exit code
|
||||
return {"success": False, "stdout": e.stdout, "stderr": e.stderr}
|
||||
|
||||
def transcribe(slug: str):
|
||||
def transcribe(id: str):
|
||||
"""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
|
||||
if not os.path.exists(video_file):
|
||||
@@ -39,18 +39,18 @@ def transcribe(slug: str):
|
||||
return False
|
||||
|
||||
# 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:")
|
||||
return True
|
||||
|
||||
transcribe_video.extract_audio(video_file, f"save/{DB.table}/{slug}/temp_{slug}_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.extract_audio(video_file, f"save/{DB.table}/{id}/temp_{id}_audio.wav")
|
||||
transcribe_video.transcribe_to_srt(f"save/{DB.table}/{id}/temp_{id}_audio.wav", f"save/{DB.table}/{id}/", f"transcribe_{id}")
|
||||
|
||||
return True
|
||||
|
||||
def top_hashtags(slug: str):
|
||||
def top_hashtags(id: str):
|
||||
""""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 not os.path.exists(file_srt):
|
||||
@@ -99,7 +99,7 @@ def upload():
|
||||
|
||||
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 = ""
|
||||
title = ""
|
||||
description = ""
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ nltk.download('punkt', quiet=True)
|
||||
nltk.download('stopwords', 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:
|
||||
content = file.read()
|
||||
# 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)
|
||||
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)
|
||||
|
||||
# Lowercase and remove punctuation
|
||||
|
||||
+162
-14
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user