From 9ad18a90041b1a3d2ab7de378c8916c675a1cb92 Mon Sep 17 00:00:00 2001 From: HallowedDust5 Date: Fri, 10 Jun 2022 16:36:44 -0400 Subject: [PATCH] Started refactoring --- main.py | 166 ++++++++++++++++------------- reddit/subreddit.py | 95 ++++++++++------- setup.py | 195 +++++++++++++++++++--------------- video_creation/final_video.py | 82 +++++++++----- 4 files changed, 317 insertions(+), 221 deletions(-) diff --git a/main.py b/main.py index 56dd3ac..b7f10e0 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,5 @@ # Main from utils.console import print_markdown -from utils.console import print_step -from utils.console import print_substep from rich.console import Console import time from reddit.subreddit import get_subreddit_threads @@ -9,95 +7,113 @@ from video_creation.background import download_background, chop_background_video from video_creation.voices import save_text_to_mp3 from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts from video_creation.final_video import make_final_video -from utils.loader import Loader from dotenv import load_dotenv from pathlib import Path - -console = Console() from dotenv import load_dotenv -import os, time, shutil - -configured = True -REQUIRED_VALUES = [ - "REDDIT_CLIENT_ID", - "REDDIT_CLIENT_SECRET", - "REDDIT_USERNAME", - "REDDIT_PASSWORD", - "OPACITY", -] +import os, time +"""TODO +- Refactor all .py files +- Write tests in tests/ -print_markdown( - "### Thanks for using this tool! [Feel free to contribute to this project on GitHub!](https://lewismenelaws.com) If you have any questions, feel free to reach out to me on Twitter or submit a GitHub issue." -) """ -Load .env file if exists. If it doesnt exist, print a warning and launch the setup wizard. -If there is a .env file, check if the required variables are set. If not, print a warning and launch the setup wizard. -""" -client_id = os.getenv("REDDIT_CLIENT_ID") -client_secret = os.getenv("REDDIT_CLIENT_SECRET") -username = os.getenv("REDDIT_USERNAME") -password = os.getenv("REDDIT_PASSWORD") -reddit2fa = os.getenv("REDDIT_2FA") -load_dotenv() +def main(): + if envConfigured(): + generateVideo() -console.log("[bold green]Checking environment variables...") -time.sleep(1) -if not Path(".env").is_file(): - configured = False - console.log("[red] Your .env file is invalid, or was never created. Standby.") - -# Checks to see if all values in .env are provided - # If they aren't, then asks to launch setup wizard -for val in REQUIRED_VALUES: - # print(os.getenv(val)) - if val not in os.environ or not os.getenv(val): - console.log(f'[bold red]Missing Variable: "{val}"') - configured = False - console.log( - "[red]Looks like you need to set your Reddit credentials in the .env file. Please follow the instructions in the README.md file to set them up." - ) - time.sleep(0.5) - console.log( - "[red]We can also launch the easy setup wizard. type yes to launch it, or no to quit the program." - ) - while True: #Asks user whether they want to launch setup wizard until an understandable input is given - setup_ask = input("Launch setup wizard? > ") - if setup_ask.casefold() == "yes": - console.log("[bold green]Here goes nothing! Launching setup wizard...") - time.sleep(0.5) - os.system("python3 setup.py") - - elif setup_ask.casefold() == "no": - console.print("[red]Exiting...") - time.sleep(0.5) - exit() - - console.print("[red]I don't understand that.") - time.sleep(0.5) -try: - float(os.getenv("OPACITY")) -except: - console.log( - f"[red]Please ensure that OPACITY is set between 0 and 1 in your .env file" - ) - configured = False - exit() -console.log("[bold green]Enviroment Variables are set! Continuing...") - -if configured: +def generateVideo(): # Video generation reddit_object = get_subreddit_threads() length, number_of_comments = save_text_to_mp3(reddit_object) download_screenshots_of_reddit_posts( - reddit_object, number_of_comments, os.getenv("THEME", "light") - ) + reddit_object, number_of_comments, os.getenv("THEME", "light") + ) download_background() chop_background_video(length) - final_video = make_final_video(number_of_comments) + make_final_video(number_of_comments) + +def envConfigured(): + console = Console() + + configured = True + REQUIRED_VALUES = [ + "REDDIT_CLIENT_ID", + "REDDIT_CLIENT_SECRET", + "REDDIT_USERNAME", + "REDDIT_PASSWORD", + "OPACITY", + ] + + + print_markdown( + "### Thanks for using this tool! [Feel free to contribute to this project on GitHub!](https://lewismenelaws.com) If you have any questions, feel free to reach out to me on Twitter or submit a GitHub issue." + ) + + """ + Load .env file if exists. If it doesnt exist, print a warning and launch the setup wizard. + If there is a .env file, check if the required variables are set. If not, print a warning and launch the setup wizard. + """ + + client_id = os.getenv("REDDIT_CLIENT_ID") + client_secret = os.getenv("REDDIT_CLIENT_SECRET") + username = os.getenv("REDDIT_USERNAME") + password = os.getenv("REDDIT_PASSWORD") + reddit2fa = os.getenv("REDDIT_2FA") + + load_dotenv() + + console.log("[bold green]Checking environment variables...") + time.sleep(1) + + if not Path(".env").is_file(): + configured = False + console.log("[red] Your .env file is invalid, or was never created. Standby.") + + # Checks to see if all values in .env are provided + # If they aren't, then asks to launch setup wizard + for val in REQUIRED_VALUES: + # print(os.getenv(val)) + if val not in os.environ or not os.getenv(val): + console.log(f'[bold red]Missing Variable: "{val}"') + configured = False + console.log( + "[red]Looks like you need to set your Reddit credentials in the .env file. Please follow the instructions in the README.md file to set them up." + ) + time.sleep(0.5) + console.log( + "[red]We can also launch the easy setup wizard. type yes to launch it, or no to quit the program." + ) + while True: #Asks user whether they want to launch setup wizard until an understandable input is given + setup_ask = input("Launch setup wizard? > ") + if setup_ask.casefold() == "yes": + console.log("[bold green]Here goes nothing! Launching setup wizard...") + time.sleep(0.5) + os.system("python3 setup.py") + + elif setup_ask.casefold() == "no": + console.print("[red]Exiting...") + time.sleep(0.5) + exit() + + console.print("[red]I don't understand that.") + time.sleep(0.5) + try: + float(os.getenv("OPACITY")) + except: + console.log( + f"[red]Please ensure that OPACITY is set between 0 and 1 in your .env file" + ) + configured = False + exit() + console.log("[bold green]Enviroment Variables are set! Continuing...") + return configured + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/reddit/subreddit.py b/reddit/subreddit.py index c560293..68ff8e7 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -7,23 +7,29 @@ import os, random, praw, re def get_subreddit_threads(): - global submission - """ - Returns a list of threads from the AskReddit subreddit. - """ + """Selects subreddit threads and returns them + + Returns: + dict[str]: Object of all the selected threads' data: + { + thread_post : str, + thread_title : str, + thread_post : str, + comments : list[dict], + } + + `comments` is structured as follows: + { + comment_body : str, + comment_url : str, + comment_id : str, + } + """ + global submission load_dotenv() - if os.getenv("REDDIT_2FA", default="no").casefold() == "yes": - print( - "\nEnter your two-factor authentication code from your authenticator app.\n" - ) - code = input("> ") - print() - pw = os.getenv("REDDIT_PASSWORD") - passkey = f"{pw}:{code}" - else: - passkey = os.getenv("REDDIT_PASSWORD") + passkey = configurePasskey() content = {} reddit = praw.Reddit( @@ -34,28 +40,7 @@ def get_subreddit_threads(): password=passkey, ) - # If the user specifies that he doesnt want a random thread, or if he doesn't insert the "RANDOM_THREAD" variable at all, ask the thread link - if not os.getenv("RANDOM_THREAD") or os.getenv("RANDOM_THREAD") == "no": - print_substep("Insert the full thread link:", style="bold green") - thread_link = input() - print_step(f"Getting the inserted thread...") - submission = reddit.submission(url=thread_link) - else: - # Otherwise, picks a random thread from the inserted subreddit - if os.getenv("SUBREDDIT"): - subreddit = reddit.subreddit(re.sub(r"r\/", "", os.getenv("SUBREDDIT"))) - else: - # ! Prompt the user to enter a subreddit - try: - subreddit = reddit.subreddit( - re.sub(r"r\/", "",input("What subreddit would you like to pull from? ")) - ) - except ValueError: - subreddit = reddit.subreddit("askreddit") - print_substep("Subreddit not defined. Using AskReddit.") - - threads = subreddit.hot(limit=25) - submission = list(threads)[random.randrange(0, 25)] + pickThread(reddit) print_substep(f"Video will be: {submission.title} :thumbsup:") console.log("Getting video comments...") @@ -77,6 +62,44 @@ def get_subreddit_threads(): except AttributeError as e: pass + print_substep("Received AskReddit threads successfully.", style="bold green") return content + +def pickThread(reddit): + # If the user specifies that he doesnt want a random thread, or if he doesn't insert the "RANDOM_THREAD" variable at all, ask the thread link + if not os.getenv("RANDOM_THREAD") or os.getenv("RANDOM_THREAD") == "no": + print_substep("Insert the full thread link:", style="bold green") + thread_link = input() + print_step(f"Getting the inserted thread...") + submission = reddit.submission(url=thread_link) + else: + # Otherwise, picks a random thread from the inserted subreddit + if os.getenv("SUBREDDIT"): + subreddit = reddit.subreddit(re.sub(r"r\/", "", os.getenv("SUBREDDIT"))) + else: + # ! Prompt the user to enter a subreddit + try: + subreddit = reddit.subreddit( + re.sub(r"r\/", "",input("What subreddit would you like to pull from? ")) + ) + except ValueError: + subreddit = reddit.subreddit("askreddit") + print_substep("Subreddit not defined. Using AskReddit.") + + threads = subreddit.hot(limit=25) + submission = list(threads)[random.randrange(0, 25)] + +def configurePasskey(): + if os.getenv("REDDIT_2FA", default="no").casefold() == "yes": + print( + "\nEnter your two-factor authentication code from your authenticator app.\n" + ) + code = input("> ") + print() + pw = os.getenv("REDDIT_PASSWORD") + passkey = f"{pw}:{code}" + else: + passkey = os.getenv("REDDIT_PASSWORD") + return passkey diff --git a/setup.py b/setup.py index e6833e1..5f83cea 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,8 @@ Setup Script for RedditVideoMakerBot # Imports import os import time + +from setuptools import setup from utils.console import print_markdown from utils.console import print_step from utils.console import print_substep @@ -15,108 +17,129 @@ from utils.loader import Loader from pathlib import Path console = Console() -setup_done = Path(".setup-done-before").is_file() -if setup_done == True: - console.log("[red]Setup was already completed! Please make sure you have to run this script again. If you have to, please delete the file .setup-done-before") - exit() +def main(): + setup_done = Path(".setup-done-before").is_file() + + if setup_done == True: + console.log("[red]Setup was already completed! Please make sure you have to run this script again. If you have to, please delete the file .setup-done-before") + exit() + + # These lines ensure the user: + # - knows they are in setup mode + # - knows that they are about to erase any other setup files/data. -# These lines ensure the user: -# - knows they are in setup mode -# - knows that they are about to erase any other setup files/data. + print_step("Setup Assistant") -print_step("Setup Assistant") + print_markdown( + "### You're in the setup wizard. Ensure you're supposed to be here, then type yes to continue. If you're not sure, type no to quit." + ) -print_markdown( - "### You're in the setup wizard. Ensure you're supposed to be here, then type yes to continue. If you're not sure, type no to quit." -) -# This Input is used to ensure the user is sure they want to continue. -ensureSetupIsRequired = input("Are you sure you want to continue? > ").casefold() -if ensureSetupIsRequired != "yes": - console.print("[red]Exiting...") + ensureSetup() + + console.log("Ensure you have the following ready to enter:") + console.log("[bold green]Reddit Client ID") + console.log("[bold green]Reddit Client Secret") + console.log("[bold green]Reddit Username") + console.log("[bold green]Reddit Password") + console.log("[bold green]Reddit 2FA (yes or no)") + console.log("[bold green]Opacity (range of 0-1, decimals are OK)") + console.log("[bold green]Subreddit (without r/ or /r/)") + console.log("[bold green]Theme (light or dark)") time.sleep(0.5) - exit() -else: - # Again, let them know they are about to erase all other setup data. - console.print("[bold red] This will overwrite your current settings. Are you sure you want to continue? [bold green]yes/no") - overwriteSettings = input("Are you sure you want to continue? > ").casefold() - if overwriteSettings != "yes": - console.print("[red]Abort mission! Exiting...") - time.sleep(0.5) + console.print( + "[green]If you don't have these, please follow the instructions in the README.md file to set them up.") + console.print( + "[green]If you do have these, type yes to continue. If you dont, go ahead and grab those quickly and come back.") + confirmUserHasCredentials = input( + "Are you sure you have the credentials? > ").casefold() + if confirmUserHasCredentials != "yes": + console.print("[red]I don't understand that.") + console.print("[red]Exiting...") exit() else: - # Once they confirm, move on with the script. console.print("[bold green]Alright! Let's get started!") time.sleep(1) -console.log("Ensure you have the following ready to enter:") -console.log("[bold green]Reddit Client ID") -console.log("[bold green]Reddit Client Secret") -console.log("[bold green]Reddit Username") -console.log("[bold green]Reddit Password") -console.log("[bold green]Reddit 2FA (yes or no)") -console.log("[bold green]Opacity (range of 0-1, decimals are OK)") -console.log("[bold green]Subreddit (without r/ or /r/)") -console.log("[bold green]Theme (light or dark)") -time.sleep(0.5) -console.print("[green]If you don't have these, please follow the instructions in the README.md file to set them up.") -console.print("[green]If you do have these, type yes to continue. If you dont, go ahead and grab those quickly and come back.") -confirmUserHasCredentials = input("Are you sure you have the credentials? > ").casefold() -if confirmUserHasCredentials != "yes": - console.print("[red]I don't understand that.") - console.print("[red]Exiting...") - exit() -else: - console.print("[bold green]Alright! Let's get started!") - time.sleep(1) + setup() + + console.log("[bold green]Setup Complete! Returning...") + + # Post-Setup: send message and try to run main.py again. + os.system("python3 main.py") + +def setup(): + """ + Begin the setup process. + """ + + console.log("Enter your credentials now.") + cliID = input("Client ID > ") + cliSec = input("Client Secret > ") + user = input("Username > ") + passw = input("Password > ") + twofactor = input("2fa Enabled? (yes/no) > ") + opacity = input("Opacity? (range of 0-1) > ") + subreddit = input("Subreddit (without r/) > ") + theme = input("Theme? (light or dark) > ") + console.log("Attempting to save your credentials...") + loader = Loader("Saving Credentials...", "Done!").start() +# you can also put a while loop here, e.g. while VideoIsBeingMade == True: ... + time.sleep(0.5) + console.log("Removing old .env file...") + os.remove(".env") + time.sleep(0.5) + console.log("Creating new .env file...") + with open('.env', 'a') as f: + f.write(f'REDDIT_CLIENT_ID="{cliID}"\n') + time.sleep(0.5) + f.write(f'REDDIT_CLIENT_SECRET="{cliSec}"\n') + time.sleep(0.5) + f.write(f'REDDIT_USERNAME="{user}"\n') + time.sleep(0.5) + f.write(f'REDDIT_PASSWORD="{passw}"\n') + time.sleep(0.5) + f.write(f'REDDIT_2FA="{twofactor}"\n') + time.sleep(0.5) + f.write(f'THEME="{theme}"\n') + time.sleep(0.5) + f.write(f'SUBREDDIT="{subreddit}"\n') + time.sleep(0.5) + f.write(f'OPACITY="{opacity}"\n') -""" + with open('.setup-done-before', 'a') as f: + f.write( + "This file blocks the setup assistant from running again. Delete this file to run setup again.") -Begin the setup process. + loader.stop() -""" -console.log("Enter your credentials now.") -cliID = input("Client ID > ") -cliSec = input("Client Secret > ") -user = input("Username > ") -passw = input("Password > ") -twofactor = input("2fa Enabled? (yes/no) > ") -opacity = input("Opacity? (range of 0-1) > ") -subreddit = input("Subreddit (without r/) > ") -theme = input("Theme? (light or dark) > ") -console.log("Attempting to save your credentials...") -loader = Loader("Saving Credentials...", "Done!").start() - # you can also put a while loop here, e.g. while VideoIsBeingMade == True: ... -time.sleep(0.5) -console.log("Removing old .env file...") -os.remove(".env") -time.sleep(0.5) -console.log("Creating new .env file...") -with open('.env', 'a') as f: - f.write(f'REDDIT_CLIENT_ID="{cliID}"\n') - time.sleep(0.5) - f.write(f'REDDIT_CLIENT_SECRET="{cliSec}"\n') - time.sleep(0.5) - f.write(f'REDDIT_USERNAME="{user}"\n') - time.sleep(0.5) - f.write(f'REDDIT_PASSWORD="{passw}"\n') - time.sleep(0.5) - f.write(f'REDDIT_2FA="{twofactor}"\n') - time.sleep(0.5) - f.write(f'THEME="{theme}"\n') - time.sleep(0.5) - f.write(f'SUBREDDIT="{subreddit}"\n') - time.sleep(0.5) - f.write(f'OPACITY="{opacity}"\n') +def ensureSetup(): + # This Input is used to ensure the user is sure they want to continue. + ensureSetupIsRequired = input( + "Are you sure you want to continue? > ").casefold() + if ensureSetupIsRequired != "yes": + console.print("[red]Exiting...") + time.sleep(0.5) + exit() + else: + # Again, let them know they are about to erase all other setup data. + console.print( + "[bold red] This will overwrite your current settings. Are you sure you want to continue? [bold green]yes/no") + overwriteSettings = input( + "Are you sure you want to continue? > ").casefold() + if overwriteSettings != "yes": + console.print("[red]Abort mission! Exiting...") + time.sleep(0.5) + exit() + else: + # Once they confirm, move on with the script. + console.print("[bold green]Alright! Let's get started!") + time.sleep(1) -with open('.setup-done-before', 'a') as f: - f.write("This file blocks the setup assistant from running again. Delete this file to run setup again.") -loader.stop() -console.log("[bold green]Setup Complete! Returning...") -# Post-Setup: send message and try to run main.py again. -os.system("python3 main.py") +if __name__ == '__main__': + main() diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 001de06..b3550e9 100644 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -19,7 +19,11 @@ W, H = 1080, 1920 def make_final_video(number_of_clips): - + """Generates and saves final video to assets/generated-videos + + Args: + number_of_clips (int): The index of clips(comments) to go up to + """ # Calls opacity from the .env load_dotenv() opacity = os.getenv('OPACITY') @@ -36,28 +40,11 @@ def make_final_video(number_of_clips): .crop(x1=1166.6, y1=0, x2=2246.6, y2=1920) ) - # Gather all audio clips - audio_clips = [] - for i in range(0, number_of_clips): - audio_clips.append(AudioFileClip(f"assets/mp3/{i}.mp3")) - audio_clips.insert(0, AudioFileClip(f"assets/mp3/title.mp3")) - try: - audio_clips.insert(1, AudioFileClip(f"assets/mp3/posttext.mp3")) - except: - OSError() - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) + audio_clips, audio_composite = gatherAudioClips(number_of_clips) + + image_clips = gatherImages(number_of_clips, opacity, audio_clips) + - # Gather all images - image_clips = [] - for i in range(0, number_of_clips): - image_clips.append( - ImageClip(f"assets/png/comment_{i}.png") - .set_duration(audio_clips[i + 1].duration) - .set_position("center") - .resize(width=W - 100) - .set_opacity(float(opacity)), - ) if Path(f"assets/mp3/posttext.mp3").is_file(): image_clips.insert( 0, @@ -82,10 +69,57 @@ def make_final_video(number_of_clips): image_concat.audio = audio_composite final = CompositeVideoClip([background_clip, image_concat]) + saveVideo(final) + +def saveVideo(final): + """Writes video to assets/generated videos + + Args: + final (CompositeVideoClip): Final video object + """ + + if not Path("assets/generated-videos").is_dir(): Path.mkdir("assets/generated-videos") - raw_filename = Path("assets", "generated-videos") / (reddit.subreddit.submission.title + ".mp4") cleaned_filename = (re.sub('[?\"%*:|<>]', '', str(raw_filename))) - final.write_videofile(cleaned_filename, fps=30, audio_codec="aac", audio_bitrate="192k") \ No newline at end of file + final.write_videofile(cleaned_filename, fps=30, audio_codec="aac", audio_bitrate="192k") + +def gatherImages(number_of_clips, opacity, audio_clips): + """Gathers thread screenshots from assets/png + + Args: + number_of_clips (int): number of screenshots to be put into the video + opacity (float): how clear the comment is going to be in the video + audio_clips (list[AudioFileClip]): TTS of the thread + + Returns: + list[ImageClip]: all the comments with TTS over it + """ + # Gather all images + image_clips = [] + for i in range(0, number_of_clips): + image_clips.append( + ImageClip(f"assets/png/comment_{i}.png") + .set_duration(audio_clips[i + 1].duration) + .set_position("center") + .resize(width=W - 100) + .set_opacity(float(opacity)), + ) + + return image_clips + +def gatherAudioClips(number_of_clips): + # Gather all audio clips + audio_clips = [] + for i in range(0, number_of_clips): + audio_clips.append(AudioFileClip(f"assets/mp3/{i}.mp3")) + audio_clips.insert(0, AudioFileClip(f"assets/mp3/title.mp3")) + try: + audio_clips.insert(1, AudioFileClip(f"assets/mp3/posttext.mp3")) + except: + OSError() + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + return audio_clips,audio_composite \ No newline at end of file