update short scripts
This commit is contained in:
Vendored
+4
-1
@@ -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
@@ -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
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user