### Command Line Installation and Execution Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt Standard commands for setting up the environment, installing dependencies, and running the bot or GUI. ```bash # Basic installation and setup git clone https://github.com/elebumm/RedditVideoMakerBot.git cd RedditVideoMakerBot # Create virtual environment python3 -m venv ./venv source ./venv/bin/activate # Linux/macOS # or: .\venv\Scripts\activate # Windows # Install dependencies pip install -r requirements.txt python -m playwright install python -m playwright install-deps # Run the bot (will prompt for config on first run) python main.py # Run the web GUI for configuration python GUI.py # List available system TTS voices (for pyttsx) python ptt.py # One-liner installation script (Linux/macOS) bash <(curl -sL https://raw.githubusercontent.com/elebumm/RedditVideoMakerBot/master/install.sh) ``` -------------------------------- ### Install Dependencies Source: https://github.com/elebumm/redditvideomakerbot/blob/master/README.md Install the required Python dependencies for the bot. ```sh pip install -r requirements.txt ``` -------------------------------- ### Execute background media workflow Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt Example usage demonstrating how to configure, download, and process background media. ```python # Example usage: bg_config = { "video": get_background_config("video"), "audio": get_background_config("audio"), } download_background_video(bg_config["video"]) download_background_audio(bg_config["audio"]) chop_background(bg_config, length=60, reddit_object=reddit_obj) ``` -------------------------------- ### Run Installation Script (macOS/Linux) Source: https://github.com/elebumm/redditvideomakerbot/blob/master/README.md An experimental bash script to automatically install the bot and its dependencies on macOS and Linux. ```sh bash <(curl -sL https://raw.githubusercontent.com/elebumm/RedditVideoMakerBot/master/install.sh) ``` -------------------------------- ### Install Playwright Source: https://github.com/elebumm/redditvideomakerbot/blob/master/README.md Install Playwright and its dependencies, which are required for browser automation. ```sh python -m playwright install python -m playwright install-deps ``` -------------------------------- ### Execute the main video creation workflow Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt The main entry point orchestrates fetching, audio generation, screenshot capture, and final video rendering. ```python # main.py - Primary execution flow from reddit.subreddit import get_subreddit_threads from utils import settings from utils.cleanup import cleanup from utils.id import extract_id 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 def main(POST_ID=None): # Fetch Reddit content reddit_object = get_subreddit_threads(POST_ID) reddit_id = extract_id(reddit_object) # Generate TTS audio for all comments length, number_of_comments = save_text_to_mp3(reddit_object) # Capture screenshots of Reddit posts/comments get_screenshots_of_reddit_posts(reddit_object, number_of_comments) # Configure and download background media bg_config = { "video": get_background_config("video"), "audio": get_background_config("audio"), } download_background_video(bg_config["video"]) download_background_audio(bg_config["audio"]) # Chop background to match video length chop_background(bg_config, length, reddit_object) # Render final video make_final_video(number_of_comments, length, reddit_object, bg_config) # Run the bot if __name__ == "__main__": main() # Expected output: # - Logs into Reddit and fetches threads # - Generates MP3 files in assets/temp/{thread_id}/mp3/ # - Downloads screenshots to assets/temp/{thread_id}/png/ # - Outputs final video to results/{subreddit}/{title}.mp4 ``` -------------------------------- ### Create and Activate Virtual Environment (Windows) Source: https://github.com/elebumm/redditvideomakerbot/blob/master/README.md Create and activate a Python virtual environment on Windows. ```sh python -m venv ./venv .\venv\Scripts\activate ``` -------------------------------- ### Create and Activate Virtual Environment (macOS/Linux) Source: https://github.com/elebumm/redditvideomakerbot/blob/master/README.md Create and activate a Python virtual environment on macOS and Linux. ```sh python3 -m venv ./venv source ./venv/bin/activate ``` -------------------------------- ### Initialize Flask Web GUI Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt The GUI.py script provides a web interface for managing bot settings, backgrounds, and viewing generated content. ```python from flask import Flask, render_template, request, redirect, url_for import tomlkit app = Flask(__name__, template_folder="GUI") HOST = "localhost" PORT = 4000 @app.route("/") def index(): """Display list of generated videos.""" return render_template("index.html", file="videos.json") @app.route("/backgrounds", methods=["GET"]) def backgrounds(): """Manage background videos and audio.""" return render_template("backgrounds.html", file="backgrounds.json") @app.route("/background/add", methods=["POST"]) def background_add(): """Add a new background video from YouTube.""" youtube_uri = request.form.get("youtube_uri").strip() filename = request.form.get("filename").strip() citation = request.form.get("citation").strip() position = request.form.get("position").strip() gui.add_background(youtube_uri, filename, citation, position) return redirect(url_for("backgrounds")) @app.route("/settings", methods=["GET", "POST"]) def settings(): """View and modify bot configuration.""" config_load = tomlkit.loads(Path("config.toml").read_text()) config = gui.get_config(config_load) checks = gui.get_checks() if request.method == "POST": data = request.form.to_dict() config = gui.modify_settings(data, config_load, checks) return render_template("settings.html", file="config.toml", data=config, checks=checks) @app.route("/results/") def results(name): """Download generated videos.""" return send_from_directory("results", name, as_attachment=True) # Launch GUI: # python GUI.py # Opens browser at http://localhost:4000 if __name__ == "__main__": webbrowser.open(f"http://{HOST}:{PORT}", new=2) app.run(port=PORT) ``` -------------------------------- ### Render Final Video with ffmpeg Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt Composes the final video from generated assets. Prepares background, concatenates audio, mixes with background music, overlays screenshots with timing, and renders the final video using ffmpeg. Requires ffmpeg and Pillow libraries. ```python import ffmpeg import multiprocessing from PIL import Image, ImageDraw, ImageFont def make_final_video(number_of_clips: int, length: int, reddit_obj: dict, background_config: dict): """ Compose final video from all generated assets. Process: 1. Prepare background video (crop to portrait) 2. Concatenate all audio clips 3. Mix with background audio 4. Overlay screenshots with timing 5. Render final video with ffmpeg """ W = settings.config["settings"]["resolution_w"] # 1080 H = settings.config["settings"]["resolution_h"] # 1920 opacity = settings.config["settings"]["opacity"] reddit_id = reddit_obj["thread_id"] # Prepare background (crop to portrait aspect ratio) background_clip = ffmpeg.input(prepare_background(reddit_id, W, H)) # Collect and concatenate audio clips audio_clips = [ffmpeg.input(f"assets/temp/{reddit_id}/mp3/title.mp3")] audio_clips += [ ffmpeg.input(f"assets/temp/{reddit_id}/mp3/{i}.mp3") for i in range(number_of_clips) ] audio_concat = ffmpeg.concat(*audio_clips, a=1, v=0) ffmpeg.output(audio_concat, f"assets/temp/{reddit_id}/audio.mp3").run(quiet=True) # Mix TTS audio with background music audio = ffmpeg.input(f"assets/temp/{reddit_id}/audio.mp3") final_audio = merge_background_audio(audio, reddit_id) # Get audio durations for overlay timing audio_durations = [ float(ffmpeg.probe(f"assets/temp/{reddit_id}/mp3/{i}.mp3")["format"]["duration"]) for i in range(number_of_clips) ] # Overlay screenshots at correct times screenshot_width = int((W * 45) // 100) # 45% of video width current_time = 0 for i in range(number_of_clips + 1): image = ffmpeg.input(f"assets/temp/{reddit_id}/png/comment_{i}.png").filter("scale", screenshot_width, -1) image_with_opacity = image.filter("colorchannelmixer", aa=opacity) background_clip = background_clip.overlay( image_with_opacity, enable=f"between(t,{current_time},{current_time + audio_durations[i]})", x="(main_w-overlay_w)/2", y="(main_h-overlay_h)/2", ) current_time += audio_durations[i] # Add credit text background_clip = ffmpeg.drawtext( background_clip, text=f"Background by {background_config['video'][2]}", x="(w-text_w)", y="(h-text_h)", fontsize=5, fontcolor="White", ) # Render final video subreddit = settings.config["reddit"]["thread"]["subreddit"] filename = f"results/{subreddit}/{reddit_obj['thread_title'][:251]}.mp4" ffmpeg.output( background_clip, final_audio, filename, f="mp4", **{ "c:v": "h264_nvenc", # NVIDIA GPU encoding "b:v": "20M", "b:a": "192k", "threads": multiprocessing.cpu_count(), } ).run(quiet=True) def merge_background_audio(audio, reddit_id: str): """Mix TTS audio with background music at configured volume.""" volume = settings.config["settings"]["background"]["background_audio_volume"] if volume == 0: return audio bg_audio = ffmpeg.input(f"assets/temp/{reddit_id}/background.mp3").filter("volume", volume) return ffmpeg.filter([audio, bg_audio], "amix", duration="longest") # Output: results/{subreddit}/{title}.mp4 ``` -------------------------------- ### Load and Display Videos from JSON (jQuery) Source: https://github.com/elebumm/redditvideomakerbot/blob/master/GUI/index.html Fetches video data from 'videos.json', sorts it by time, and dynamically generates HTML to display video information. Initializes tooltips after content is loaded. ```javascript $(document).ready(function () { $.getJSON("videos.json", function (data) { data.sort((b, a) => a['time'] - b['time']) var video = ''; $.each(data, function (key, value) { video += '
'; video += '
'; //keeping original themed image card for future thumbnail usage video += 'Placeholderr/'+value.subreddit+''; video += '
'; video += '

r/' + value.subreddit + ' • ' + checkTitle(value.reddit_title, value.filename) + '

'; video += '
'; video += '
'; video += 'View'; video += 'Download'; video += '
'; video += '
'; video += ''; video += ''; video += ''; video += '
'; video += '' + timeSince(value.time) + ''; video += '
'; video += '
'; video += '
'; video += '
'; }); $('#videos').append(video); }); }); $(document).ready(function () { $('['data-toggle="tooltip"']').tooltip(); $('['data-toggle="tooltip"']').on('click', function () { $(this).tooltip('hide'); }); }); $('#copy').tooltip({ trigger: 'click', placement: 'bottom' }); ``` -------------------------------- ### Retrieve background configuration Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt Fetches the background configuration based on the provided mode, falling back to a random choice if the setting is invalid. ```python def get_background_config(mode: str): """Get background configuration for video or audio.""" choice = settings.config["settings"]["background"][f"background_{mode}"] if not choice or choice not in background_options[mode]: choice = random.choice(list(background_options[mode].keys())) return background_options[mode][choice] ``` -------------------------------- ### Run the Bot Source: https://github.com/elebumm/redditvideomakerbot/blob/master/README.md Execute the main Python script to run the Reddit Video Maker Bot. ```sh python main.py ``` -------------------------------- ### ClipboardJS Integration and Event Handling (JavaScript) Source: https://github.com/elebumm/redditvideomakerbot/blob/master/GUI/index.html Initializes ClipboardJS for handling copy-to-clipboard actions. Includes success and error event handlers to provide user feedback via tooltips. ```javascript var clipboard = new ClipboardJS('#copy'); clipboard.on('success', function (e) { e.clearSelection(); console.info('Action:', e.action); console.info('Text:', e.text); console.info('Trigger:', e.trigger); setTooltip(e.trigger, 'Copied!'); hideTooltip(e.trigger); disposeTooltip(e.trigger); }); clipboard.on('error', function (e) { console.error('Action:', e.action); console.error('Trigger:', e.trigger); setTooltip(e.trigger, fallbackMessage(e.action)); hideTooltip(e.trigger); }); ``` -------------------------------- ### Configure Reddit Video Maker Bot settings Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt Define Reddit credentials, TTS engine preferences, and video output parameters in the TOML configuration file. ```toml # config.toml - Main configuration file [reddit.creds] client_id = "your_reddit_app_client_id" client_secret = "your_reddit_app_client_secret" username = "your_reddit_username" password = "your_reddit_password" 2fa = false [reddit.thread] subreddit = "AskReddit" post_id = "" # Leave empty for random selection, or specify like "urdtfx" max_comment_length = 500 min_comment_length = 1 post_lang = "" # Translation language code, e.g., "es" for Spanish min_comments = 20 blocked_words = "" # Comma-separated: "nsfw, spoiler, politics" [ai] ai_similarity_enabled = false ai_similarity_keywords = "Elon Musk, Twitter, Stocks" [settings] allow_nsfw = false theme = "dark" # Options: "dark", "light", "transparent" times_to_run = 1 opacity = 0.9 storymode = false storymodemethod = 1 # 0 = single image, 1 = fancy animated resolution_w = 1080 resolution_h = 1920 zoom = 1 channel_name = "Reddit Tales" [settings.background] background_video = "minecraft" # Options: minecraft, gta, rocket-league, csgo-surf, etc. background_audio = "lofi" # Options: lofi, lofi-2, chill-summer background_audio_volume = 0.15 enable_extra_audio = false background_thumbnail = false [settings.tts] voice_choice = "tiktok" # Options: tiktok, elevenlabs, googletranslate, awspolly, pyttsx, OpenAI random_voice = true tiktok_voice = "en_us_001" tiktok_sessionid = "your_tiktok_session_id" elevenlabs_api_key = "" elevenlabs_voice_name = "Bella" silence_duration = 0.3 no_emojis = false ``` -------------------------------- ### Clone Repository Source: https://github.com/elebumm/redditvideomakerbot/blob/master/README.md Clone the RedditVideoMakerBot repository to your local machine. ```sh git clone https://github.com/elebumm/RedditVideoMakerBot.git cd RedditVideoMakerBot ``` -------------------------------- ### Define background audio configuration Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt A dictionary mapping identifiers to YouTube URLs, filenames, and credit names for background audio. ```python background_audio = { "lofi": ["https://youtube.com/watch?v=LTphVIore3A", "lofi.mp3", "Super Lofi World"], "lofi-2": ["https://youtube.com/watch?v=BEXL80LS0-I", "lofi-2.mp3", "stompsPlaylist"], "chill-summer": ["https://youtube.com/watch?v=EZE8JagnBI8", "chill-summer.mp3", "Mellow Vibes Radio"], } ``` -------------------------------- ### ElevenLabs Text-to-Speech Implementation Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt Wrapper for the ElevenLabs API. Requires an API key and voice configuration in the settings. ```python # TTS/elevenlabs.py - ElevenLabs voice synthesis from elevenlabs import save from elevenlabs.client import ElevenLabs class elevenlabs: """ElevenLabs Text-to-Speech wrapper.""" def __init__(self): self.max_chars = 2500 self.client = None def run(self, text: str, filepath: str, random_voice: bool = False): """Generate high-quality speech using ElevenLabs API.""" if self.client is None: self.initialize() if random_voice: voice = self.randomvoice() else: voice = settings.config["settings"]["tts"]["elevenlabs_voice_name"] audio = self.client.generate( text=text, voice=voice, model="eleven_multilingual_v1" ) save(audio=audio, filename=filepath) def initialize(self): """Initialize ElevenLabs client with API key.""" api_key = settings.config["settings"]["tts"]["elevenlabs_api_key"] if not api_key: raise ValueError("ELEVENLABS_API_KEY not set in config") self.client = ElevenLabs(api_key=api_key) def randomvoice(self): """Select a random voice from available voices.""" return random.choice(self.client.voices.get_all().voices).name # Example configuration in config.toml: # [settings.tts] # voice_choice = "elevenlabs" # elevenlabs_api_key = "your_api_key_here" # elevenlabs_voice_name = "Bella" # Options: Adam, Antoni, Arnold, Bella, Domi, Elli, Josh, Rachel, Sam ``` -------------------------------- ### Configure Reddit API App Source: https://github.com/elebumm/redditvideomakerbot/blob/master/README.md Instructions for setting up a 'script' type application on the Reddit Apps page to connect to the Reddit API. ```sh Visit [the Reddit Apps page](https://www.reddit.com/prefs/apps), and set up an app that is a "script". Paste any URL in the redirect URL field, for example: `https://jasoncameron.dev`. ``` -------------------------------- ### Manage Background Videos and Audio with yt-dlp Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt Provides a dictionary of available background videos from YouTube sources, including their URLs, local filenames, and associated channel names. Uses yt-dlp for downloading and moviepy for media manipulation. ```python # video_creation/background.py - Background video/audio handling import yt_dlp from moviepy import AudioFileClip, VideoFileClip # Available background videos (from utils/background_videos.json) background_videos = { "minecraft": ["https://youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"], "gta": ["https://youtube.com/watch?v=qGa9kWREOnE", "gta-stunt-race.mp4", "Achy Gaming"], "rocket-league": ["https://youtube.com/watch?v=2X9QGY__0II", "rocket_league.mp4", "Orbital Gameplay"], "csgo-surf": ["https://youtube.com/watch?v=E-8JlyO59Io", "csgo-surf.mp4", "Aki"], "minecraft-2": ["https://youtube.com/watch?v=Pt5_GSKIWQM", "minecraft-2.mp4", "Itslpsn"], "fall-guys": ["https://youtube.com/watch?v=oGSsgACIc6Q", "fall-guys.mp4", "Throneful"], } ``` -------------------------------- ### DOM Ready Event Handlers Source: https://github.com/elebumm/redditvideomakerbot/blob/master/GUI/settings.html Initializes UI elements and loads configuration data when the DOM is ready. Includes setting up tooltips, updating slider values, populating form fields from saved data, and handling form submission logic. ```javascript // Wait for DOM to load $(document).ready(function () { // Add tooltips $(' [data-toggle="tooltip"]').tooltip(); $(' [data-toggle="tooltip"]').on('click', function () { $(this).tooltip('hide'); }); // Update slider tooltip $(".form-range").on("input", function () { $(this).attr("value", $(this).val()); $(this).attr("data-original-title", $(this).val()); $(this).tooltip("show"); }); // Get current config var data = JSON.parse('{{data | tojson}}'); // Set current checkboxes $('.form-check-input').each(function () { $(this).prop("checked", data[$(this).prop("name")]); }); // Set current select options $('.form-select').each(function () { $(this).prop("value", data[$(this).prop("name")]); }); // Submit "False" when checkbox isn't ticked $('#settingsForm').submit(function () { $('.form-check-input').each(function () { if (!($(this).is(':checked'))) { $(this).prop("value", "False"); $(this).prop("checked", true); } }); }); }); ``` -------------------------------- ### Unified Text-to-Speech Engine Wrapper Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt Provides a unified interface for multiple TTS providers, handling audio file generation and automatic text splitting for long content. Supports TikTok, ElevenLabs, Google Translate, AWS Polly, OpenAI TTS, and pyttsx. ```python # TTS/engine_wrapper.py - Unified TTS interface from TTS.TikTok import TikTok from TTS.elevenlabs import elevenlabs from TTS.GTTS import GTTS from TTS.aws_polly import AWSPolly from TTS.openai_tts import OpenAITTS class TTSEngine: """ Wrapper for multiple TTS engines with automatic text chunking. Supported engines: - TikTok (max 200 chars) - ElevenLabs (max 2500 chars) - Google Translate - AWS Polly - OpenAI TTS - pyttsx (offline) """ def __init__(self, tts_module, reddit_object: dict, path: str = "assets/temp/", max_length: int = 50): self.tts_module = tts_module() self.reddit_object = reddit_object self.redditid = reddit_object["thread_id"] self.path = f"{path}{self.redditid}/mp3" self.max_length = max_length # Max video length in seconds self.length = 0 def run(self): """Generate MP3 files for title and all comments.""" # Generate title audio self.call_tts("title", self.reddit_object["thread_title"]) # Generate audio for each comment for idx, comment in enumerate(self.reddit_object["comments"]): if self.length > self.max_length: break if len(comment["comment_body"]) > self.tts_module.max_chars: self.split_post(comment["comment_body"], idx) else: self.call_tts(f"{idx}", comment["comment_body"]) return self.length, idx def call_tts(self, filename: str, text: str): """Call the TTS engine and track duration.""" self.tts_module.run( text, filepath=f"{self.path}/{filename}.mp3", random_voice=settings.config["settings"]["tts"]["random_voice"], ) # High-level API from video_creation/voices.py: from video_creation.voices import save_text_to_mp3 # Automatically selects TTS engine from config and generates all audio length, num_comments = save_text_to_mp3(reddit_object) # Output files created: # assets/temp/{thread_id}/mp3/title.mp3 # assets/temp/{thread_id}/mp3/0.mp3 # assets/temp/{thread_id}/mp3/1.mp3 # ... ``` -------------------------------- ### Download background media Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt Downloads background video or audio from YouTube using yt-dlp if the file does not already exist in the assets directory. ```python def download_background_video(background_config): """Download background video from YouTube if not cached.""" uri, filename, credit, _ = background_config if Path(f"assets/backgrounds/video/{credit}-{filename}").is_file(): return # Already downloaded ydl_opts = { "format": "bestvideo[height<=1080][ext=mp4]", "outtmpl": f"assets/backgrounds/video/{credit}-{filename}", "retries": 10, } with yt_dlp.YoutubeDL(ydl_opts) as ydl: ydl.download(uri) ``` ```python def download_background_audio(background_config): """Download background audio from YouTube if not cached.""" uri, filename, credit = background_config if Path(f"assets/backgrounds/audio/{credit}-{filename}").is_file(): return ydl_opts = { "outtmpl": f"./assets/backgrounds/audio/{credit}-{filename}", "format": "bestaudio/best", "extract_audio": True, } with yt_dlp.YoutubeDL(ydl_opts) as ydl: ydl.download([uri]) ``` -------------------------------- ### Reconfigure Bot Source: https://github.com/elebumm/redditvideomakerbot/blob/master/README.md To reconfigure the bot, delete the relevant lines from the `config.toml` file. The bot will prompt for reconfiguration on the next run. ```toml # config.toml # Delete lines that need to be changed for reconfiguration. ``` -------------------------------- ### Manage Background Video UI and Validation Source: https://github.com/elebumm/redditvideomakerbot/blob/master/GUI/backgrounds.html Handles fetching background data from JSON, rendering cards, managing deletion modals, filtering the list, and validating form inputs. ```javascript var keys = []; var youtube_urls = []; // Show background videos $(document).ready(function () { $.getJSON("backgrounds.json", function (data) { delete data["__comment"]; var background = ''; $.each(data, function (key, value) { // Add YT urls and keys (for validation) keys.push(key); youtube_urls.push(value[0]); background += '
'; background += '
'; background += ''; background += '
'; background += '

' + value[2] + ' • ' + key + '

'; background += '
'; background += '
'; background += ''; background += '
'; background += '
'; background += '
'; background += '
'; background += '
'; }); $('#backgrounds').append(background); }); }); // Add background key when deleting $('#deleteBtnModal').on('show.bs.modal', function (event) { var button = $(event.relatedTarget); var key = button.data('background-key'); $('#background-key').prop('value', key); }); var searchFilter = () => { const input = document.querySelector(".searchFilter"); const cards = document.getElementsByClassName("col"); console.log(cards[1]) let filter = input.value for (let i = 0; i < cards.length; i++) { let title = cards[i].querySelector(".card-text"); if (title.innerText.toLowerCase().indexOf(filter.toLowerCase()) > -1) { cards[i].classList.remove("d-none") } else { cards[i].classList.add("d-none") } } } // Validate form $("#addBgForm").submit(function (event) { $("#addBgForm input").each(function () { if (!(validate($(this)))) { event.preventDefault(); event.stopPropagation(); } }); }); $('#addBgForm input[type="text"]').on("keyup", function () { validate($(this)); }); function validate(object) { let bool = check(object.prop("name"), object.prop("value")); // Change class if (bool) { object.removeClass("is-invalid"); object.addClass("is-valid"); } else { object.removeClass("is-valid"); object.addClass("is-invalid"); } return bool; // Check values (return true/false) function check(name, value) { if (name == "youtube_uri") { // URI validation let regex = /(?:\/|%3D|v=|vi=)([0-9A-z-_]{11})(?:[%#?&]|$)/; if (!(regex.test(value))) { $("#feedbackYT").html("Invalid URI"); $("#feedbackYT").show(); return false; } // Check if this background already exists if (youtube_urls.includes(value)) { $("#feedbackYT").html("This background is already added"); $("#feedbackYT").show(); return false; } $("#feedbackYT").hide(); return true; } if (name == "filename") { // Check if key is already taken if (keys.includes(value)) { $("#feedbackFilename").html("This filename is already taken"); $("#feedbackFilename").show(); return false; } let regex = /^([a-zA-Z0-9\s_-]{1,100})$/; if (!(regex.test(value))) { return false; } return true; } if (name == "citation") { if (value.trim()) { return true; } } if (name == "position") { if (!(value == "center" || value.length == 0 || value % 1 == 0)) { return false; } return true; } } } ``` -------------------------------- ### Test Voices Button Click Handler Source: https://github.com/elebumm/redditvideomakerbot/blob/master/GUI/settings.html Handles clicks on voice test buttons to play audio previews. It manages the playing state, toggles icons, and sets the audio source. Requires an audio element and buttons with associated icons. ```javascript var playing = false; $(".voices button").click(function () { var icon = $(this).find("i"); var audio = $("audio"); if (playing) { playing.toggleClass("bi-volume-up-fill bi-stop-fill"); // Clicked the same button - stop audio if (playing.prop("id") == icon.prop("id")) { audio[0].pause(); playing = false; return; } } icon.toggleClass("bi-volume-up-fill bi-stop-fill"); let path = "voices/" + $(this).closest(".voices").find("select").prop("value").toLowerCase() + ".mp3"; audio.prop("src", path); audio[0].play(); playing = icon; audio[0].onended = function () { icon.toggleClass("bi-volume-up-fill bi-stop-fill"); playing = false; } }); ``` -------------------------------- ### Generate Copy Data String (JavaScript) Source: https://github.com/elebumm/redditvideomakerbot/blob/master/GUI/index.html Constructs a formatted string for copying, including subreddit, title, and background credit. Handles cases where subreddit might be undefined. ```javascript function getCopyData(subreddit, reddit_title, filename, background_credit) { if (subreddit == undefined) { subredditCopy = ""; } else { subredditCopy = "r/" + subreddit + "\n\n"; } const file = filename.slice(0, -4); if (reddit_title == file) { titleCopy = reddit_title; } else { titleCopy = file; } var copyData = ""; copyData += subredditCopy; copyData += titleCopy; copyData += "\n\nBackground credit: " + background_credit; return copyData; } ``` -------------------------------- ### Fetch Reddit Threads and Comments Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt Initializes the Reddit client using PRAW and fetches threads from a specified subreddit. It can retrieve a specific post by ID or a random hot post. Returns structured data including thread details and comments, suitable for video creation. ```python import praw from utils import settings def get_subreddit_threads(POST_ID: str = None): """ Fetches Reddit threads and returns structured content for video creation. Returns dict with keys: - thread_url: Full URL to the Reddit post - thread_title: Post title - thread_id: Reddit post ID - thread_post: Post body (for storymode) - is_nsfw: Boolean NSFW flag - comments: List of comment dicts with comment_body, comment_url, comment_id """ # Initialize Reddit client reddit = praw.Reddit( client_id=settings.config["reddit"]["creds"]["client_id"], client_secret=settings.config["reddit"]["creds"]["client_secret"], user_agent="Accessing Reddit threads", username=settings.config["reddit"]["creds"]["username"], passkey=settings.config["reddit"]["creds"]["password"], ) # Get subreddit and fetch posts subreddit = reddit.subreddit(settings.config["reddit"]["thread"]["subreddit"]) # Fetch specific post or get from hot posts if POST_ID: submission = reddit.submission(id=POST_ID) else: threads = subreddit.hot(limit=25) submission = get_subreddit_undone(threads, subreddit) # Build content object content = { "thread_url": f"https://new.reddit.com/{submission.permalink}", "thread_title": submission.title, "thread_id": submission.id, "is_nsfw": submission.over_18, "comments": [] } # Collect comments (non-storymode) for comment in submission.comments: if len(comment.body) <= settings.config["reddit"]["thread"]["max_comment_length"]: content["comments"].append({ "comment_body": comment.body, "comment_url": comment.permalink, "comment_id": comment.id, }) return content # Example usage: reddit_obj = get_subreddit_threads("urdtfx") # Specific post # Or reddit_obj = get_subreddit_threads() # Random from configured subreddit # Returned structure: # { # "thread_url": "https://new.reddit.com/r/AskReddit/comments/urdtfx/", # "thread_title": "What's the most interesting fact you know?", # "thread_id": "urdtfx", # "is_nsfw": False, # "comments": [ # {"comment_body": "The human brain...", "comment_url": "/r/AskReddit/...", "comment_id": "i8x123"}, # ... # ] # } ``` -------------------------------- ### Tooltip Management Functions (JavaScript) Source: https://github.com/elebumm/redditvideomakerbot/blob/master/GUI/index.html Provides utility functions for setting, hovering, hiding, and disposing of tooltips. Used for user feedback on copy actions and other interactive elements. ```javascript function setTooltip(btn, message) { $(btn).tooltip('hide') .attr('data-original-title', message) .tooltip('show'); } function hoverTooltip(btn, message) { $(btn).tooltip('hide') .attr('data-original-title', message) .tooltip('show'); } function hideTooltip(btn) { setTimeout(function () { $(btn).tooltip('hide'); }, 1000); } function disposeTooltip(btn) { setTimeout(function () { $(btn).tooltip('dispose'); }, 1500); } ``` -------------------------------- ### Check and Format Title (JavaScript) Source: https://github.com/elebumm/redditvideomakerbot/blob/master/GUI/index.html Compares the Reddit title with the filename (without extension) and returns the Reddit title if they match, otherwise returns the filename. ```javascript function checkTitle(reddit_title, filename) { const file = filename.slice(0, -4); if (reddit_title == file) { return reddit_title; } else { return file; } } ``` -------------------------------- ### Capture Reddit Post and Comment Screenshots Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt Uses Playwright to automate browser actions for capturing screenshots of Reddit posts and their comments. Supports various themes, translation, and NSFW content. ```python from playwright.sync_api import sync_playwright, ViewportSize def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int): """ Capture screenshots of Reddit posts and comments. Features: - Dark/Light/Transparent themes - Automatic translation - Configurable zoom level - NSFW content handling """ W = settings.config["settings"]["resolution_w"] # 1080 H = settings.config["settings"]["resolution_h"] # 1920 lang = settings.config["reddit"]["thread"]["post_lang"] reddit_id = reddit_object["thread_id"] # Theme configuration if settings.config["settings"]["theme"] == "dark": cookie_file = "./video_creation/data/cookie-dark-mode.json" bgcolor = (33, 33, 36, 255) else: cookie_file = "./video_creation/data/cookie-light-mode.json" bgcolor = (255, 255, 255, 255) with sync_playwright() as p: browser = p.chromium.launch(headless=True) # Calculate device scale factor for high-res screenshots dsf = (W // 600) + 1 context = browser.new_context( locale=lang or "en-CA,en;q=0.9", color_scheme="dark", viewport=ViewportSize(width=W, height=H), device_scale_factor=dsf, ) # Load theme cookies context.add_cookies(json.load(open(cookie_file))) page = context.new_page() # Login to Reddit page.goto("https://www.reddit.com/login") page.locator('input[name="username"]').fill(settings.config["reddit"]["creds"]["username"]) page.locator('input[name="password"]').fill(settings.config["reddit"]["creds"]["password"]) page.get_by_role("button", name="Log In").click() page.wait_for_timeout(5000) # Navigate to post and capture title screenshot page.goto(reddit_object["thread_url"]) page.locator('[data-test-id="post-content"]').screenshot( path=f"assets/temp/{reddit_id}/png/title.png" ) # Capture each comment for idx, comment in enumerate(reddit_object["comments"][:screenshot_num]): page.goto(f"https://new.reddit.com/{comment['comment_url']}") page.locator(f"#t1_{comment['comment_id']}").screenshot( path=f"assets/temp/{reddit_id}/png/comment_{idx}.png" ) browser.close() # Output files: # assets/temp/{thread_id}/png/title.png # assets/temp/{thread_id}/png/comment_0.png # assets/temp/{thread_id}/png/comment_1.png # ... ``` -------------------------------- ### Generate Reddit Link (JavaScript) Source: https://github.com/elebumm/redditvideomakerbot/blob/master/GUI/index.html Creates an HTML anchor tag for a Reddit post link. Returns the raw title if the subreddit is undefined. ```javascript function getLink(subreddit, id, reddit_title) { if (subreddit == undefined) { return reddit_title; } else { return "" + reddit_title + ""; } } ``` -------------------------------- ### TikTok Text-to-Speech Implementation Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt Wrapper for the TikTok TTS API. Requires a valid session ID in the application settings. ```python import base64 import requests class TikTok: """TikTok Text-to-Speech API wrapper.""" # Available English voices eng_voices = ( "en_au_001", # English AU - Female "en_au_002", # English AU - Male "en_uk_001", # English UK - Male 1 "en_uk_003", # English UK - Male 2 "en_us_001", # English US - Female 1 "en_us_002", # English US - Female 2 "en_us_006", # English US - Male 1 "en_us_007", # English US - Male 2 "en_us_009", # English US - Male 3 "en_us_010", # English US - Male 4 "en_male_narration", # Narrator "en_male_funny", # Funny ) # Character voices disney_voices = ( "en_us_ghostface", "en_us_chewbacca", "en_us_c3po", "en_us_stitch", "en_us_rocket", ) def __init__(self): self.URI_BASE = "https://api16-normal-c-useast1a.tiktokv.com/media/api/text/speech/invoke/" self.max_chars = 200 self._session = requests.Session() self._session.headers = { "User-Agent": "com.zhiliaoapp.musically/2022600030...", "Cookie": f"sessionid={settings.config['settings']['tts']['tiktok_sessionid']}", } def run(self, text: str, filepath: str, random_voice: bool = False): """Generate speech and save to file.""" voice = self.random_voice() if random_voice else settings.config["settings"]["tts"]["tiktok_voice"] data = self.get_voices(voice=voice, text=text) if data["status_code"] != 0: raise TikTokTTSException(data["status_code"], data["message"]) # Decode and save audio decoded_audio = base64.b64decode(data["data"]["v_str"]) with open(filepath, "wb") as f: f.write(decoded_audio) def get_voices(self, text: str, voice: str = None) -> dict: """Request TTS from TikTok API.""" text = text.replace("+", "plus").replace("&", "and").replace("r/", "") params = {"req_text": text, "speaker_map_type": 0, "aid": 1233} if voice: params["text_speaker"] = voice response = self._session.post(self.URI_BASE, params=params) return response.json() # Example usage: tts = TikTok() tts.run( text="What is the most interesting fact you know?", filepath="assets/temp/abc123/mp3/title.mp3", random_voice=True ) ``` -------------------------------- ### Format Time Since Timestamp (JavaScript) Source: https://github.com/elebumm/redditvideomakerbot/blob/master/GUI/index.html Calculates and formats the time elapsed since a given date. Requires a predefined 'intervals' array for time unit calculations. ```javascript const intervals = [ { label: 'year', seconds: 31536000 }, { label: 'month', seconds: 2592000 }, { label: 'day', seconds: 86400 }, { label: 'hour', seconds: 3600 }, { label: 'minute', seconds: 60 }, { label: 'second', seconds: 1 } ]; function timeSince(date) { const seconds = Math.floor((Date.now() / 1000 - date)); const interval = intervals.find(i => i.seconds < seconds); const count = Math.floor(seconds / interval.seconds); return `${count} ${interval.label}${count !== 1 ? 's' : ''} ago`; } ``` -------------------------------- ### Chop background media Source: https://context7.com/elebumm/redditvideomakerbot/llms.txt Extracts a segment from the background audio and video files to match the duration of the generated video. ```python def chop_background(background_config: dict, video_length: int, reddit_object: dict): """Extract random segment from background media matching video length.""" thread_id = reddit_object["thread_id"] # Chop audio audio_path = f"assets/backgrounds/audio/{background_config['audio'][2]}-{background_config['audio'][1]}" background_audio = AudioFileClip(audio_path) start, end = get_start_and_end_times(video_length, background_audio.duration) background_audio.subclipped(start, end).write_audiofile(f"assets/temp/{thread_id}/background.mp3") # Chop video video_path = f"assets/backgrounds/video/{background_config['video'][2]}-{background_config['video'][1]}" with VideoFileClip(video_path) as video: start, end = get_start_and_end_times(video_length, video.duration) video.subclipped(start, end).write_videofile(f"assets/temp/{thread_id}/background.mp4") ```