You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RedditVideoMakerBot/main.py

237 lines
11 KiB

#!/usr/bin/env python
"""
Reddit Video Maker Bot
Generates short-form videos from Reddit posts with Qwen TTS.
"""
import math
import os
import sys
from os import name
from pathlib import Path
from subprocess import Popen
from typing import Dict, NoReturn, Optional
from reddit.subreddit import get_subreddit_threads
from reddit.scraper import RedditScraperError
from utils import settings
from utils.cleanup import cleanup
from utils.console import print_markdown, print_step, print_substep
from utils.ffmpeg_install import ffmpeg_install
from utils.id import extract_id
from utils.version import checkversion
from utils.progress import progress_tracker
from video_creation.background import (
chop_background,
download_background_audio,
download_background_video,
get_background_config,
)
from video_creation.final_video import make_final_video
from video_creation.screenshot_downloader import get_screenshots_of_reddit_posts
from video_creation.voices import save_text_to_mp3
__VERSION__ = "4.0.0"
# Check if GUI mode is enabled
GUI_MODE = os.environ.get("REDDIT_BOT_GUI", "false").lower() == "true"
print(
"""
██████╗ ███████╗██████╗ ██████╗ ██╗████████╗ ██╗ ██╗██╗██████╗ ███████╗ ██████╗ ███╗ ███╗ █████╗ ██╗ ██╗███████╗██████╗
██╔══██╗██╔════╝██╔══██╗██╔══██╗██║╚══██╔══╝ ██║ ██║██║██╔══██╗██╔════╝██╔═══██╗ ████╗ ████║██╔══██╗██║ ██╔╝██╔════╝██╔══██╗
██████╔╝█████╗ ██║ ██║██║ ██║██║ ██║ ██║ ██║██║██║ ██║█████╗ ██║ ██║ ██╔████╔██║███████║█████╔╝ █████╗ ██████╔╝
██╔══██╗██╔══╝ ██║ ██║██║ ██║██║ ██║ ╚██╗ ██╔╝██║██║ ██║██╔══╝ ██║ ██║ ██║╚██╔╝██║██╔══██║██╔═██╗ ██╔══╝ ██╔══██╗
██║ ██║███████╗██████╔╝██████╔╝██║ ██║ ╚████╔╝ ██║██████╔╝███████╗╚██████╔╝ ██║ ╚═╝ ██║██║ ██║██║ ██╗███████╗██║ ██║
╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
"""
)
print_markdown(
"### Reddit Video Maker Bot v4.0 - Now with Qwen TTS and Progress GUI!"
)
checkversion(__VERSION__)
reddit_id: str
reddit_object: Dict[str, str | list]
def main(POST_ID: Optional[str] = None) -> None:
"""Main video generation function with progress tracking."""
global reddit_id, reddit_object
try:
# Step 1: Fetch Reddit Post
progress_tracker.start_step("fetch_reddit", "Connecting to Reddit API...")
reddit_object = get_subreddit_threads(POST_ID)
reddit_id = extract_id(reddit_object)
# Start job tracking
progress_tracker.start_job(
reddit_id=reddit_id,
title=reddit_object.get("thread_title", "Unknown"),
subreddit=reddit_object.get("subreddit", "Unknown"),
)
progress_tracker.update_step_progress("fetch_reddit", 50, f"Found post: {reddit_id}")
print_substep(f"Thread ID is {reddit_id}", style="bold blue")
progress_tracker.complete_step("fetch_reddit", f"Loaded {len(reddit_object.get('comments', []))} comments")
# Step 2: Generate TTS Audio
progress_tracker.start_step("generate_tts", "Initializing TTS engine...")
length, number_of_comments = save_text_to_mp3(reddit_object)
length = math.ceil(length)
progress_tracker.complete_step("generate_tts", f"Generated audio for {number_of_comments} comments ({length}s)")
# Step 3: Capture Screenshots
progress_tracker.start_step("capture_screenshots", "Launching browser...")
get_screenshots_of_reddit_posts(reddit_object, number_of_comments)
# Set preview for screenshots
screenshot_preview = f"assets/temp/{reddit_id}/png/title.png"
if os.path.exists(screenshot_preview):
progress_tracker.set_step_preview("capture_screenshots", f"/assets/temp/{reddit_id}/png/title.png")
progress_tracker.complete_step("capture_screenshots", f"Captured {number_of_comments + 1} screenshots")
# Step 4: Download Background
progress_tracker.start_step("download_background", "Loading background config...")
bg_config = {
"video": get_background_config("video"),
"audio": get_background_config("audio"),
}
progress_tracker.update_step_progress("download_background", 30, "Downloading video background...")
download_background_video(bg_config["video"])
progress_tracker.update_step_progress("download_background", 70, "Downloading audio background...")
download_background_audio(bg_config["audio"])
progress_tracker.complete_step("download_background", "Background assets ready")
# Step 5: Process Background
progress_tracker.start_step("process_background", "Chopping background to fit...")
chop_background(bg_config, length, reddit_object)
progress_tracker.complete_step("process_background", f"Background prepared for {length}s video")
# Step 6: Compose Video
progress_tracker.start_step("compose_video", "Starting video composition...")
make_final_video(number_of_comments, length, reddit_object, bg_config)
progress_tracker.complete_step("compose_video", "Video rendered successfully")
# Step 7: Finalize
progress_tracker.start_step("finalize", "Cleaning up temporary files...")
subreddit = reddit_object.get("subreddit", "Unknown")
output_path = f"/results/{subreddit}/"
progress_tracker.complete_step("finalize", "Video generation complete!")
# Mark job as completed
progress_tracker.complete_job(output_path=output_path)
print_step("Video generation completed successfully!")
except Exception as e:
# Handle errors and update progress
current_step = progress_tracker.get_current_step()
if current_step:
progress_tracker.fail_step(current_step.id, str(e))
progress_tracker.fail_job(str(e))
raise
def run_many(times: int) -> None:
"""Run video generation multiple times."""
for x in range(1, times + 1):
print_step(
f'on the {x}{("th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th")[x % 10]} iteration of {times}'
)
main()
Popen("cls" if name == "nt" else "clear", shell=True).wait()
def shutdown() -> NoReturn:
"""Clean up and exit."""
if "reddit_id" in globals():
print_markdown("## Clearing temp files")
cleanup(reddit_id)
print("Exiting...")
sys.exit()
def start_gui_server():
"""Start the progress GUI server in background."""
from progress_gui import run_gui_background
print_step("Starting Progress GUI server...")
run_gui_background()
print_substep("Progress GUI available at http://localhost:5000", style="bold green")
if __name__ == "__main__":
if sys.version_info.major != 3 or sys.version_info.minor not in [10, 11, 12]:
print(
"This program requires Python 3.10, 3.11, or 3.12. "
"Please install a compatible Python version and try again."
)
sys.exit()
ffmpeg_install()
directory = Path().absolute()
config = settings.check_toml(
f"{directory}/utils/.config.template.toml", f"{directory}/config.toml"
)
config is False and sys.exit()
# Validate Qwen TTS settings if selected
if config["settings"]["tts"]["voice_choice"].lower() == "qwentts":
if not config["settings"]["tts"].get("qwen_email") or not config["settings"]["tts"].get("qwen_password"):
print_substep(
"Qwen TTS requires 'qwen_email' and 'qwen_password' in config! "
"Please configure these settings.",
"bold red",
)
sys.exit()
# Validate TikTok settings if selected
if (
not settings.config["settings"]["tts"]["tiktok_sessionid"]
or settings.config["settings"]["tts"]["tiktok_sessionid"] == ""
) and config["settings"]["tts"]["voice_choice"].lower() == "tiktok":
print_substep(
"TikTok voice requires a sessionid! Check documentation on how to obtain one.",
"bold red",
)
sys.exit()
# Start GUI server if enabled
if GUI_MODE:
start_gui_server()
try:
if config["reddit"]["thread"]["post_id"]:
for index, post_id in enumerate(config["reddit"]["thread"]["post_id"].split("+")):
index += 1
print_step(
f'on the {index}{("st" if index % 10 == 1 else ("nd" if index % 10 == 2 else ("rd" if index % 10 == 3 else "th")))} post of {len(config["reddit"]["thread"]["post_id"].split("+"))}'
)
main(post_id)
Popen("cls" if name == "nt" else "clear", shell=True).wait()
elif config["settings"]["times_to_run"]:
run_many(config["settings"]["times_to_run"])
else:
main()
except KeyboardInterrupt:
shutdown()
except RedditScraperError as e:
print_markdown("## Reddit Scraper Error")
print_markdown(f"Error fetching Reddit data: {e}")
print_markdown("This may be due to rate limiting. Try again later or increase request_delay in config.")
shutdown()
except Exception as err:
config["settings"]["tts"]["tiktok_sessionid"] = "REDACTED"
config["settings"]["tts"]["elevenlabs_api_key"] = "REDACTED"
config["settings"]["tts"]["openai_api_key"] = "REDACTED"
config["settings"]["tts"]["qwen_password"] = "REDACTED"
print_step(
f"Sorry, something went wrong! Try again, and feel free to report this issue on GitHub.\n"
f"Version: {__VERSION__} \n"
f"Error: {err} \n"
f'Config: {config["settings"]}'
)
raise err