import whisper
import os
import subprocess
from bs4 import BeautifulSoup
################----------------------------------------------Video generation-----------------------------
# ==========================================
# CONFIGURATION & PATHS
# ==========================================
output_dir = r"C:\Users\Bot\Documents\output_sections"
html_file = r"C:\Users\Bot\Documents\html_data.txt"
# Make sure this click sound exists on your PC
click_sound = r"F:\localsend\click2.wav"
thumbnail_img = os.path.join(output_dir, "genereted_image.jpg")
# Font setup exactly from your old code
font_path = "C:/Users/Bot/Documents/Baloo-Regular.ttf"
font_path_clean = font_path.replace("\\", "/")
safe_font = font_path_clean.replace(":", "\\:")
def escape_text(t):
return t.replace("'", r"\'").replace(":", r"\:").replace(",", r"\,")
def get_video_duration(filepath):
"""Uses ffprobe to get the exact length of a video in seconds."""
cmd = [
"ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", filepath
]
try:
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
return float(result.stdout.strip())
except (ValueError, Exception):
return 0.0
# ==========================================
# PART 1: EXTRACT HEADINGS FROM HTML
# ==========================================
print("--- Extracting Headings from HTML ---")
section_headings = {}
if os.path.exists(html_file):
with open(html_file, 'r', encoding='utf-8') as f:
soup = BeautifulSoup(f.read(), 'html.parser')
for div in soup.find_all('div', class_=lambda c: c and c.startswith('section_')):
sec_class = next((c for c in div.get('class', []) if c.startswith('section_') and c != 'section_heading'), None)
if sec_class:
try:
sec_num = int(sec_class.split('_')[1])
heading_div = div.find('div', class_='section_heading')
if heading_div:
section_headings[sec_num] = heading_div.get_text(strip=True)
except ValueError:
continue
# ==========================================
# PART 2: PROCESS OVERLAYS PER SECTION
# ==========================================
processed_videos = []
cumulative_video_time = 0.0 # Tracks the total timeline to fix cumulative text files
for i in range(1, 100):
base_vid = os.path.join(output_dir, f"section_{i}.mp4")
if not os.path.exists(base_vid):
continue
print(f"\n--- Processing Section {i} ---")
# Get the duration of this specific base video to add to our tracker later
current_vid_duration = get_video_duration(base_vid)
inputs = [f"-i \"{base_vid}\""]
time_ranges = []
# Find all image lines for THIS specific section
for file in sorted(os.listdir(output_dir)):
if file.startswith(f"section_{i}_line_") and file.endswith(".jpg"):
base_name = file.replace(".jpg", "")
start_file = os.path.join(output_dir, f"{base_name}_start.txt")
end_file = os.path.join(output_dir, f"{base_name}_end.txt")
image_file = os.path.join(output_dir, file)
if not (os.path.exists(start_file) and os.path.exists(end_file)):
continue
with open(start_file, "r") as sf:
raw_start = float(sf.read().strip())
with open(end_file, "r") as ef:
raw_end = float(ef.read().strip())
# --- THE MAGIC FIX: Convert Cumulative Time to Relative Time ---
# If the text file says 150s, but we are at 148s in the global timeline,
# it converts it to 2s into the current clip.
start_time = raw_start - cumulative_video_time if raw_start >= cumulative_video_time else raw_start
end_time = raw_end - cumulative_video_time if raw_end >= cumulative_video_time else raw_end
# Ensure no negative times from slight overlapping
start_time = max(0.0, start_time)
end_time = max(start_time + 1.0, end_time)
inputs.append(f"-i \"{image_file.replace(os.sep, '/')}\"")
time_ranges.append((start_time, end_time))
filter_complex_parts = []
last_video = "[0:v]"
# --- 1. BUILD IMAGE OVERLAYS ---
for idx, (start_time, end_time) in enumerate(time_ranges, start=1):
out_label = f"[v{idx}]"
overlay_cmd = (
f"[{idx}:v] scale=800:-1[scaled_{idx}];"
f"{last_video}[scaled_{idx}] overlay=enable='between(t,{start_time},{end_time})':x=1000:y=(H-h)/2+50 {out_label}"
)
filter_complex_parts.append(overlay_cmd)
last_video = out_label
# --- 2. ADD HEADING TEXT (First 5 seconds) ---
if i in section_headings:
safe_title = escape_text(section_headings[i])
# Lower third settings
LT_FONT_SIZE = 50
LT_BG_COLOR = "yellow"
LT_TEXT_COLOR = "black"
LT_PADDING_LEFT = 50
LT_PADDING_BOTTOM = 200
LT_BOX_PADDING = 20
text_out = "[vout_text]"
part = (
f"drawtext=text='{safe_title}':"
f"fontfile='{safe_font}':"
f"fontcolor={LT_TEXT_COLOR}:"
f"fontsize={LT_FONT_SIZE}:"
f"box=1:boxcolor={LT_BG_COLOR}:boxborderw={LT_BOX_PADDING}:"
f"x={LT_PADDING_LEFT}:"
f"y=h-text_h-{LT_PADDING_BOTTOM}:"
f"enable='between(t,0,5)'"
)
filter_complex_parts.append(f"{last_video} {part} {text_out}")
last_video = text_out
# --- 3. ADD CLICK SOUNDS & AUDIO MIX ---
use_clicks = os.path.exists(click_sound)
if use_clicks and time_ranges:
for _ in time_ranges:
inputs.append(f"-i \"{click_sound.replace(os.sep, '/')}\"")
audio_mix_parts = ["[0:a]"]
click_audio_index = len(time_ranges) + 1 # first click input
for idx, (start_time, _) in enumerate(time_ranges, start=1):
audio_in = f"[{click_audio_index}:a]"
delayed = f"[click{idx}]"
delay_ms = int(float(start_time) * 1000)
filter_complex_parts.append(f"{audio_in} adelay={delay_ms}|{delay_ms} {delayed}")
audio_mix_parts.append(delayed)
click_audio_index += 1
filter_complex_parts.append(f"{''.join(audio_mix_parts)} amix=inputs={len(audio_mix_parts)}:normalize=0[audio_out]")
audio_map = "-map \"[audio_out]\""
else:
audio_map = "-map 0:a"
# --- 4. RUN FFMPEG ---
output_path = os.path.join(output_dir, f"section_{i}_final.mp4").replace("\\", "/")
if filter_complex_parts:
filter_complex = "; ".join(filter_complex_parts)
cmd = (
f"ffmpeg -hwaccel cuda {' '.join(inputs)} -filter_complex \"{filter_complex}\" "
f"-map \"{last_video}\" {audio_map} -c:v h264_nvenc -preset fast -b:v 5M "
f"-c:a aac -b:a 192k \"{output_path}\" -y"
)
print(f"🔹 Running FFmpeg for Section {i}...")
subprocess.run(cmd, shell=True)
else:
print(f"🔹 No images/text for Section {i}, copying original video...")
cmd = f"ffmpeg -i \"{base_vid.replace(os.sep, '/')}\" -c copy \"{output_path}\" -y"
subprocess.run(cmd, shell=True)
processed_videos.append(output_path)
# Update cumulative time tracker to auto-correct the next section's text files
cumulative_video_time += current_vid_duration
# ==========================================
# PART 3: MERGE ALL CLIPS & THUMBNAIL
# ==========================================
print("\n--- Merging all section clips into one ---")
if processed_videos:
concat_list = os.path.join(output_dir, "concat_list.txt")
with open(concat_list, "w", encoding="utf-8") as f:
for vid in processed_videos:
f.write(f"file '{vid}'\n")
merged_sections_vid = os.path.join(output_dir, "merged_base_video.mp4").replace("\\", "/")
final_output = os.path.join(output_dir, "FINAL_YOUTUBE_VIDEO.mp4").replace("\\", "/")
thumb_video_path = os.path.join(output_dir, "thumb_clip.mp4").replace("\\", "/")
# Merge the processed sections
concat_cmd = f"ffmpeg -f concat -safe 0 -i \"{concat_list.replace(os.sep, '/')}\" -c copy \"{merged_sections_vid}\" -y"
subprocess.run(concat_cmd, shell=True)
# Append the thumbnail to the end (using your old code exactly)
if os.path.exists(thumbnail_img):
cmd_thumb = (
f"ffmpeg -loop 1 -i \"{thumbnail_img.replace(os.sep, '/')}\" "
f"-f lavfi -t 1 -i anullsrc=r=44100:cl=stereo "
f"-shortest -vf scale=1920:1080,fps=30,format=yuv420p "
f"-c:v h264_nvenc -preset fast -b:v 5M -pix_fmt yuv420p "
f"-c:a aac -b:a 192k \"{thumb_video_path}\" -y"
)
print(f"🔹 Creating thumbnail clip...")
subprocess.run(cmd_thumb, shell=True)
cmd_concat_thumb = (
f"ffmpeg -i \"{merged_sections_vid}\" -i \"{thumb_video_path}\" "
f"-filter_complex \"[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[outv][outa]\" "
f"-map \"[outv]\" -map \"[outa]\" -c:v h264_nvenc -preset fast -b:v 5M "
f"-c:a aac -b:a 192k \"{final_output}\" -y"
)
print(f"🔹 Concatenating Final Video + Thumbnail...")
subprocess.run(cmd_concat_thumb, shell=True)
print(f"✅ Final video with thumbnail saved: {final_output}")
else:
print("⚠️ Thumbnail not found, outputting merged video as final.")
os.rename(merged_sections_vid, final_output)
else:
print("❌ No videos were generated. Check your paths.")
0 comments:
Post a Comment