From 1f0af1c20bdf49fd6f946732758ea59d83b08130 Mon Sep 17 00:00:00 2001 From: Jason <66544866+JasonLovesDoggo@users.noreply.github.com> Date: Sun, 12 Jun 2022 14:12:05 -0400 Subject: [PATCH 01/49] Update README.md Shamelessly added myself to the developers --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 56f4581..a677cf8 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ Please read our [contributing guidelines](CONTRIBUTING.md) for more detailed inf Elebumm (Lewis#6305) - https://github.com/elebumm (Founder) +Jason (JasonLovesDoggo#1904) - https://github.com/JasonLovesDoggo + CallumIO - https://github.com/CallumIO HarryDaDev (hrvyy#9677) - https://github.com/ImmaHarry From 267bbddfd520cfa9f1bdbd7e3bd6a6566c1c41ae Mon Sep 17 00:00:00 2001 From: Harper <106035102+rarehp@users.noreply.github.com> Date: Mon, 13 Jun 2022 01:46:55 -0400 Subject: [PATCH 02/49] typo --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index feaa344..e6e13f1 100644 --- a/setup.py +++ b/setup.py @@ -88,7 +88,7 @@ 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: ... +# 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") From 28e139a2ca1e0d01c40ba2c574f7e8708e406858 Mon Sep 17 00:00:00 2001 From: Semen Zhydenko Date: Mon, 13 Jun 2022 16:18:18 +0200 Subject: [PATCH 03/49] FIX: typo reproducable -> reproducible --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad3adb0..6743487 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,7 +52,7 @@ A good bug report shouldn't leave others needing to chase you up for more inform - OS, Platform and Version (Windows, Linux, macOS, x86, ARM) - Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. - Your input and the output - - Is the issue reproducable? Does it exist in previous versions? + - Is the issue reproducible? Does it exist in previous versions? #### How Do I Submit a Good Bug Report? From da0964ed251c9c5306df04d66e2c63440cf4f696 Mon Sep 17 00:00:00 2001 From: Semen Zhydenko Date: Mon, 13 Jun 2022 16:18:43 +0200 Subject: [PATCH 04/49] FIX: typo recieve -> receive --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6743487..50a52a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,7 +66,7 @@ We use GitHub issues to track bugs and errors. If you run into an issue with the Once it's filed: - The project team will label the issue accordingly. -- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will try to support you as best as they can, but you may not recieve an instant. +- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will try to support you as best as they can, but you may not receive an instant. - If the team discovers that this is an issue it will be marked `bug` or `error`, as well as possibly other tags relating to the nature of the error), and the issue will be left to be [implemented by someone](#your-first-code-contribution). ### Suggesting Enhancements From b36d59698d48428e714e6883d1fb7fa05fbaf9d3 Mon Sep 17 00:00:00 2001 From: Jason <66544866+JasonLovesDoggo@users.noreply.github.com> Date: Mon, 13 Jun 2022 16:27:20 -0400 Subject: [PATCH 05/49] Update main.py I believe this fixes an eror. --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 9aabe78..a36c535 100644 --- a/main.py +++ b/main.py @@ -36,6 +36,7 @@ Load .env file if exists. If it doesnt exist, print a warning and launch the set If there is a .env file, check if the required variables are set. If not, print a warning and launch the setup wizard. """ +load_dotenv() client_id = os.getenv("REDDIT_CLIENT_ID") client_secret = os.getenv("REDDIT_CLIENT_SECRET") @@ -43,7 +44,6 @@ 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) From ef9ac9de1721789c2e609d6d6b8a713844ed1875 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 13 Jun 2022 22:28:50 -0400 Subject: [PATCH 06/49] sample config module --- utils/config.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 utils/config.py diff --git a/utils/config.py b/utils/config.py new file mode 100644 index 0000000..78beb99 --- /dev/null +++ b/utils/config.py @@ -0,0 +1,30 @@ +# write a class that takes .env file and parses it into a dictionary + +from dotenv import dotenv_values + +DEFAULTS = {'SUBREDDIT': "AskReddit", 'ALLOW_NSFW': "False", 'POST_ID': "", 'THEME': "DARK", 'REDDIT_2FA': "no", + 'TIMES_TO_RUN': "", 'MAX_COMMENT_LENGTH': "500", 'OPACITY': "1", 'VOICE': "en_us_001", 'STORYMODE': "False"} + + +class Config: + def __init__(self): + self.raw = dotenv_values("../.env") + self.load_attrs() + + def __getattr__(self, attr): # code completion for attributes fix. + return getattr(self, attr) + + def load_attrs(self): + for key, value in self.raw.items(): + self.add_attr(key, value) + + def add_attr(self, key, value): + if value is None or value == "": + setattr(self, key, DEFAULTS[key]) + else: + setattr(self, key, str(value)) + + +config = Config() + +print(config.SUBREDDIT) From c0a3475d315d97157fe46a015d225550205eec5c Mon Sep 17 00:00:00 2001 From: Lewis Menelaws Date: Tue, 14 Jun 2022 12:44:58 -0400 Subject: [PATCH 07/49] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index a677cf8..68e873c 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ Created by Lewis Menelaws & [TMRRW](https://tmrrwinc.ca) ](https://tmrrwinc.ca) +## Video Explainer +[![lewisthumbnail](https://user-images.githubusercontent.com/6053155/173631669-1d1b14ad-c478-4010-b57d-d79592a789f2.png) +](https://www.youtube.com/watch?v=3gjcY_00U1w) + ## Motivation πŸ€” These videos on TikTok, YouTube and Instagram get MILLIONS of views across all platforms and require very little effort. The only original thing being done is the editing and gathering of all materials... From 809bae88c930426c7bdbd25de5542dfde605f0fa Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 14 Jun 2022 22:42:01 -0400 Subject: [PATCH 08/49] fixes desyncing issue part of the credit goes to https://github.com/RebelPorp --- video_creation/final_video.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 7fec8d0..f9550da 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -88,20 +88,21 @@ def make_final_video(number_of_clips, length): .resize(width=W - 100) .set_opacity(float(opacity)), ) - if os.path.exists("assets/mp3/posttext.mp3"): - image_clips.insert( - 0, - ImageClip("assets/png/title.png") - .set_duration(audio_clips[0].duration + audio_clips[1].duration) - .set_position("center") - .resize(width=W - 100) - .set_opacity(float(opacity)), - ) - else: - image_clips.insert( + #if os.path.exists("assets/mp3/posttext.mp3"): + # image_clips.insert( + # 0, + # ImageClip("assets/png/title.png") + # .set_duration(audio_clips[0].duration + audio_clips[1].duration) + # .set_position("center") + # .resize(width=W - 100) + # .set_opacity(float(opacity)), + # ) + #else: + image_clips.insert( 0, ImageClip("assets/temp/png/title.png") .set_duration(audio_clips[0].duration) + #.set_duration(audixc vcco_clips[0].duration) .set_position("center") .resize(width=W - 100) .set_opacity(float(opacity)), From 5dd3c2fe9c978323129f226ee7bae0fceb03f9f0 Mon Sep 17 00:00:00 2001 From: Owen Gaspard Date: Thu, 16 Jun 2022 00:59:31 -0500 Subject: [PATCH 09/49] Fix broken TMRRW logo Add href to TMRRW logo to properly redirect without other characters displaying --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 68e873c..1b3816a 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,14 @@ All done WITHOUT video editing or asset compiling. Just pure ✨programming magi Created by Lewis Menelaws & [TMRRW](https://tmrrwinc.ca) -[ - + + -](https://tmrrwinc.ca) + + + ## Video Explainer [![lewisthumbnail](https://user-images.githubusercontent.com/6053155/173631669-1d1b14ad-c478-4010-b57d-d79592a789f2.png) From f142df81b18dc47f3ccfb8de379058ea8d17acd7 Mon Sep 17 00:00:00 2001 From: Kazi Fariadul Islam <53252317+KaziFariad@users.noreply.github.com> Date: Thu, 16 Jun 2022 14:33:22 +0530 Subject: [PATCH 10/49] Update print in subreddit.py to show correct subreddit name --- reddit/subreddit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit/subreddit.py b/reddit/subreddit.py index c560293..666c817 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -77,6 +77,6 @@ def get_subreddit_threads(): except AttributeError as e: pass - print_substep("Received AskReddit threads successfully.", style="bold green") + print_substep(f"Received {subreddit} threads successfully.", style="bold green") return content From 6937f10e1be38ab0fd719563b93a8044a3ca56b1 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 13:36:35 -0400 Subject: [PATCH 11/49] Improved download and annoying user fixes/redundancies --- video_creation/background.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/video_creation/background.py b/video_creation/background.py index 162380e..533db04 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -7,9 +7,6 @@ from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip from moviepy.editor import VideoFileClip from utils.console import print_step, print_substep -import datetime - - def get_start_and_end_times(video_length, length_of_clip): random_time = randrange(180, int(length_of_clip) - int(video_length)) return random_time, random_time + video_length @@ -20,14 +17,14 @@ def download_background(): Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True) background_options = [ # uri , filename , credit ("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"), - ( - "https://www.youtube.com/watch?v=2X9QGY__0II", - "rocket_league.mp4", - "Orbital Gameplay", - ), + #( + # "https://www.youtube.com/watch?v=2X9QGY__0II", + # "rocket_league.mp4", + # "Orbital Gameplay", + #), ] - # note: make sure the file name doesn't include a - in it - if len(listdir("./assets/backgrounds")) != len( + # note: make sure the file name doesn't include an - in it + if not len(listdir("./assets/backgrounds")) <= len( background_options ): # if there are any background videos not installed print_step( @@ -35,6 +32,8 @@ def download_background(): ) print_substep("Downloading the backgrounds videos... please be patient πŸ™ ") for uri, filename, credit in background_options: + if Path(f'assets/backgrounds/{credit}-{filename}').is_file(): + continue # adds check to see if file exists before downloading print_substep(f"Downloading {filename} from {uri}") YouTube(uri).streams.filter(res="1080p").first().download( "assets/backgrounds", filename=f"{credit}-{filename}" @@ -60,5 +59,4 @@ def chop_background_video(video_length): targetname="assets/temp/background.mp4", ) print_substep("Background video chopped successfully!", style="bold green") - noerror = True - return noerror + return True From 819e642b863ba0368eabc5acd1a6f2afc96bdec7 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 13:36:44 -0400 Subject: [PATCH 12/49] added a secret value --- .env.template | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.template b/.env.template index cb2fa3b..b66ec3e 100644 --- a/.env.template +++ b/.env.template @@ -14,7 +14,7 @@ RANDOM_THREAD="" COMMENT_LENGTH_RANGE = "min,max" # The absolute path of the folder where you want to save the final video -# If empty or wrong, the path will be 'assets/' +# If empty or wrong, the path will be 'results/' FINAL_VIDEO_PATH="" # Valid options are "yes" and "no" for the variable below REDDIT_2FA="" @@ -35,4 +35,5 @@ OPACITY="1" VOICE="en_us_001" # e.g. en_us_002 # IN-PROGRESS - not yet implemented +TTsChoice="TikTok" # todo add docs STORYMODE="False" From b8c6ae2dc1d4b7e450480531d1ab1e6b5b46c7fe Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 13:45:19 -0400 Subject: [PATCH 13/49] added luke's chanmges --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 278dbb5..f9b2aee 100644 --- a/README.md +++ b/README.md @@ -72,10 +72,10 @@ Elebumm (Lewis#6305) - https://github.com/elebumm (Founder) Jason (JasonLovesDoggo#1904) - https://github.com/JasonLovesDoggo -CallumIO - https://github.com/CallumIO +CallumIO (c.#6837) - https://github.com/CallumIO HarryDaDev (hrvyy#9677) - https://github.com/ImmaHarry LukaHietala (Pix.#0001) - https://github.com/LukaHietala -Freebiell - https://github.com/FreebieII +Freebiell (Freebie#6429) - https://github.com/FreebieII From dcb42a4939111fd8b4572c2fd0b78845afe9dfca Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 15:11:51 -0400 Subject: [PATCH 14/49] reworked the tts system --- TTS/GTTS.py | 7 +++++++ video_creation/TTSwrapper.py => TTS/TikTok.py | 2 +- TTS/swapper.py | 20 +++++++++++++++++++ main.py | 2 +- video_creation/voices.py | 20 +++++++++---------- 5 files changed, 38 insertions(+), 13 deletions(-) create mode 100644 TTS/GTTS.py rename video_creation/TTSwrapper.py => TTS/TikTok.py (98%) create mode 100644 TTS/swapper.py diff --git a/TTS/GTTS.py b/TTS/GTTS.py new file mode 100644 index 0000000..20890ed --- /dev/null +++ b/TTS/GTTS.py @@ -0,0 +1,7 @@ +from gtts import gTTS + + +class GTTS: + def tts(self, req_text: str = "Google Text To Speech", filename: str = "title.mp3", random_speaker=False, censer=False): + tts = gTTS(text=req_text, lang="en", slow=False) + tts.save(f"{filename}") diff --git a/video_creation/TTSwrapper.py b/TTS/TikTok.py similarity index 98% rename from video_creation/TTSwrapper.py rename to TTS/TikTok.py index fc104dc..9ec9341 100644 --- a/video_creation/TTSwrapper.py +++ b/TTS/TikTok.py @@ -65,7 +65,7 @@ noneng = [ # 'ok': ['en_au_002', 'en_uk_001']} # less en_us_stormtrooper more less en_us_rocket en_us_ghostface -class TTTTSWrapper: # TikTok Text-to-Speech Wrapper +class TikTok: # TikTok Text-to-Speech Wrapper def __init__(self): self.URI_BASE = "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=" diff --git a/TTS/swapper.py b/TTS/swapper.py new file mode 100644 index 0000000..5b01a30 --- /dev/null +++ b/TTS/swapper.py @@ -0,0 +1,20 @@ +from os import getenv + +from dotenv import load_dotenv + +from TTS.GTTS import GTTS +from TTS.TikTok import TikTok + +CHOICE_DIR = { + 'tiktok': TikTok, + 'gtts': GTTS +} + +class TTS: + def __new__(cls): + load_dotenv() + CHOICE = getenv('TTsChoice').casefold() + valid_keys = [key.lower() for key in CHOICE_DIR.keys()] + if CHOICE not in valid_keys: + raise ValueError(f'{CHOICE} is not valid. Please use one of these {valid_keys} options') + return CHOICE_DIR.get(CHOICE)() diff --git a/main.py b/main.py index 4f74a74..1c6316d 100755 --- a/main.py +++ b/main.py @@ -23,7 +23,7 @@ print(banner) load_dotenv() # Modified by JasonLovesDoggo 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." + "### 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. You can find solutions to many common problems in the [Documentation](https://luka-hietala.gitbook.io/documentation-for-the-reddit-bot/)" ) time.sleep(1) diff --git a/video_creation/voices.py b/video_creation/voices.py index 7912314..76c4dc3 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -1,20 +1,19 @@ #!/usr/bin/env python3 -from gtts import gTTS +from os import getenv from pathlib import Path -from os import getenv, name import sox from mutagen import MutagenError from mutagen.mp3 import MP3, HeaderNotFoundError -from rich.progress import track from rich.console import Console +from rich.progress import track + +from TTS.swapper import TTS console = Console() -import re from utils.console import print_step, print_substep from utils.voice import sanitize_text -from video_creation.TTSwrapper import TTTTSWrapper VIDEO_LENGTH: int = 40 # secs @@ -29,9 +28,8 @@ def save_text_to_mp3(reddit_obj): # Create a folder for the mp3 files. Path("assets/temp/mp3").mkdir(parents=True, exist_ok=True) - - ttttsw = TTTTSWrapper() # tiktok text to speech wrapper - ttttsw.tts( + TextToSpeech = TTS() + TextToSpeech.tts( sanitize_text(reddit_obj["thread_title"]), filename=f"assets/temp/mp3/title.mp3", random_speaker=False, @@ -41,19 +39,19 @@ def save_text_to_mp3(reddit_obj): except HeaderNotFoundError: # note to self AudioFileClip length += sox.file_info.duration(f"assets/temp/mp3/title.mp3") if getenv("STORYMODE").casefold() == "true": - ttttsw.tts( + TextToSpeech.tts( sanitize_text(reddit_obj["thread_content"]), filename=f"assets/temp/mp3/story_content.mp3", random_speaker=False, ) - #'story_content' + # 'story_content' com = 0 for comment in track((reddit_obj["comments"]), "Saving..."): # ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer, but this is just a good_voices starting point if length > VIDEO_LENGTH: break - ttttsw.tts( + TextToSpeech.tts( sanitize_text(comment["comment_body"]), filename=f"assets/temp/mp3/{com}.mp3", random_speaker=False, From d1faf4824c764eedea8a466a27027f95bc95ff95 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 15:17:57 -0400 Subject: [PATCH 15/49] refactored using black --- TTS/GTTS.py | 8 +++++++- TTS/TikTok.py | 14 +++++++------- TTS/swapper.py | 12 ++++++------ utils/config.py | 14 ++++++++++++-- utils/loader.py | 1 - utils/subreddit.py | 1 + utils/videos.py | 4 +++- video_creation/background.py | 7 ++++--- video_creation/final_video.py | 17 +++++++---------- 9 files changed, 47 insertions(+), 31 deletions(-) diff --git a/TTS/GTTS.py b/TTS/GTTS.py index 20890ed..9100dd8 100644 --- a/TTS/GTTS.py +++ b/TTS/GTTS.py @@ -2,6 +2,12 @@ from gtts import gTTS class GTTS: - def tts(self, req_text: str = "Google Text To Speech", filename: str = "title.mp3", random_speaker=False, censer=False): + def tts( + self, + req_text: str = "Google Text To Speech", + filename: str = "title.mp3", + random_speaker=False, + censer=False, + ): tts = gTTS(text=req_text, lang="en", slow=False) tts.save(f"{filename}") diff --git a/TTS/TikTok.py b/TTS/TikTok.py index 9ec9341..2024ec9 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -70,11 +70,11 @@ class TikTok: # TikTok Text-to-Speech Wrapper self.URI_BASE = "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=" def tts( - self, - req_text: str = "TikTok Text To Speech", - filename: str = "title.mp3", - random_speaker: bool = False, - censer=False, + self, + req_text: str = "TikTok Text To Speech", + filename: str = "title.mp3", + random_speaker: bool = False, + censer=False, ): req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and") if censer: @@ -92,7 +92,7 @@ class TikTok: # TikTok Text-to-Speech Wrapper audio_clips = [] cbn = sox.Combiner() - #cbn.set_input_format(file_type=["mp3" for _ in chunks]) + # cbn.set_input_format(file_type=["mp3" for _ in chunks]) chunkId = 0 for chunk in chunks: @@ -130,7 +130,7 @@ class TikTok: # TikTok Text-to-Speech Wrapper for clip in audio_clips: i = audio_clips.index(clip) # get the index of the clip audio_clips = ( - audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1:] + audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] ) # replace the clip with an AudioFileClip audio_concat = concatenate_audioclips(audio_clips) audio_composite = CompositeAudioClip([audio_concat]) diff --git a/TTS/swapper.py b/TTS/swapper.py index 5b01a30..cd18223 100644 --- a/TTS/swapper.py +++ b/TTS/swapper.py @@ -5,16 +5,16 @@ from dotenv import load_dotenv from TTS.GTTS import GTTS from TTS.TikTok import TikTok -CHOICE_DIR = { - 'tiktok': TikTok, - 'gtts': GTTS -} +CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS} + class TTS: def __new__(cls): load_dotenv() - CHOICE = getenv('TTsChoice').casefold() + CHOICE = getenv("TTsChoice").casefold() valid_keys = [key.lower() for key in CHOICE_DIR.keys()] if CHOICE not in valid_keys: - raise ValueError(f'{CHOICE} is not valid. Please use one of these {valid_keys} options') + raise ValueError( + f"{CHOICE} is not valid. Please use one of these {valid_keys} options" + ) return CHOICE_DIR.get(CHOICE)() diff --git a/utils/config.py b/utils/config.py index 78beb99..000b615 100644 --- a/utils/config.py +++ b/utils/config.py @@ -2,8 +2,18 @@ from dotenv import dotenv_values -DEFAULTS = {'SUBREDDIT': "AskReddit", 'ALLOW_NSFW': "False", 'POST_ID': "", 'THEME': "DARK", 'REDDIT_2FA': "no", - 'TIMES_TO_RUN': "", 'MAX_COMMENT_LENGTH': "500", 'OPACITY': "1", 'VOICE': "en_us_001", 'STORYMODE': "False"} +DEFAULTS = { + "SUBREDDIT": "AskReddit", + "ALLOW_NSFW": "False", + "POST_ID": "", + "THEME": "DARK", + "REDDIT_2FA": "no", + "TIMES_TO_RUN": "", + "MAX_COMMENT_LENGTH": "500", + "OPACITY": "1", + "VOICE": "en_us_001", + "STORYMODE": "False", +} class Config: diff --git a/utils/loader.py b/utils/loader.py index 46c40fe..f23a716 100644 --- a/utils/loader.py +++ b/utils/loader.py @@ -1,4 +1,3 @@ - # Okay, have to admit. This code is from StackOverflow. It's so efficient, that it's probably the best way to do it. # Although, it is edited to use less threads. diff --git a/utils/subreddit.py b/utils/subreddit.py index 7a7dca0..035aed7 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -3,6 +3,7 @@ import json from os import getenv from utils.console import print_substep + def get_subreddit_undone(submissions: List, subreddit): """ recursively checks if the top submission in the list was already done. diff --git a/utils/videos.py b/utils/videos.py index a7b2012..51a2704 100755 --- a/utils/videos.py +++ b/utils/videos.py @@ -4,7 +4,9 @@ from os import getenv from utils.console import print_step -def check_done(redditobj): # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack +def check_done( + redditobj, +): # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack """params: reddit_object: The Reddit Object you received in askreddit.py""" with open("./video_creation/data/videos.json", "r") as done_vids_raw: diff --git a/video_creation/background.py b/video_creation/background.py index 533db04..e8f8ad1 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -7,6 +7,7 @@ from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip from moviepy.editor import VideoFileClip from utils.console import print_step, print_substep + def get_start_and_end_times(video_length, length_of_clip): random_time = randrange(180, int(length_of_clip) - int(video_length)) return random_time, random_time + video_length @@ -17,11 +18,11 @@ def download_background(): Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True) background_options = [ # uri , filename , credit ("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"), - #( + # ( # "https://www.youtube.com/watch?v=2X9QGY__0II", # "rocket_league.mp4", # "Orbital Gameplay", - #), + # ), ] # note: make sure the file name doesn't include an - in it if not len(listdir("./assets/backgrounds")) <= len( @@ -32,7 +33,7 @@ def download_background(): ) print_substep("Downloading the backgrounds videos... please be patient πŸ™ ") for uri, filename, credit in background_options: - if Path(f'assets/backgrounds/{credit}-{filename}').is_file(): + if Path(f"assets/backgrounds/{credit}-{filename}").is_file(): continue # adds check to see if file exists before downloading print_substep(f"Downloading {filename} from {uri}") YouTube(uri).streams.filter(res="1080p").first().download( diff --git a/video_creation/final_video.py b/video_creation/final_video.py index f9550da..3a199f7 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -88,7 +88,7 @@ def make_final_video(number_of_clips, length): .resize(width=W - 100) .set_opacity(float(opacity)), ) - #if os.path.exists("assets/mp3/posttext.mp3"): + # if os.path.exists("assets/mp3/posttext.mp3"): # image_clips.insert( # 0, # ImageClip("assets/png/title.png") @@ -97,16 +97,13 @@ def make_final_video(number_of_clips, length): # .resize(width=W - 100) # .set_opacity(float(opacity)), # ) - #else: + # else: image_clips.insert( - 0, - ImageClip("assets/temp/png/title.png") - .set_duration(audio_clips[0].duration) - #.set_duration(audixc vcco_clips[0].duration) - .set_position("center") - .resize(width=W - 100) - .set_opacity(float(opacity)), - ) + 0, + ImageClip("assets/temp/png/title.png").set_duration(audio_clips[0].duration) + # .set_duration(audixc vcco_clips[0].duration) + .set_position("center").resize(width=W - 100).set_opacity(float(opacity)), + ) image_concat = concatenate_videoclips(image_clips).set_position( ("center", "center") ) From 7eb8ace07eab80481037663be2ecc886db70a117 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 16:06:59 -0400 Subject: [PATCH 16/49] fixed file not found error --- TTS/TikTok.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/TTS/TikTok.py b/TTS/TikTok.py index 2024ec9..8ed9f8e 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -125,8 +125,7 @@ class TikTok: # TikTok Text-to-Speech Wrapper cbn.build(audio_clips, filename, "concatenate") else: os.rename(audio_clips[0], filename) - - except sox.core.SoxError: # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 + except (sox.core.SoxError, FileNotFoundError): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 for clip in audio_clips: i = audio_clips.index(clip) # get the index of the clip audio_clips = ( From c58efb23fc30d64bca7a1eb6b5615101b00eb829 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 17:40:33 -0400 Subject: [PATCH 17/49] think I fixed polly needs testing tho --- TTS/POLLY.py | 115 +++++++++++++++++++++++++++++++++++++++++++++++++ TTS/TikTok.py | 4 ++ TTS/swapper.py | 3 +- 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 TTS/POLLY.py diff --git a/TTS/POLLY.py b/TTS/POLLY.py new file mode 100644 index 0000000..79a6f91 --- /dev/null +++ b/TTS/POLLY.py @@ -0,0 +1,115 @@ +import os +import random +import re + +import requests +import sox +from moviepy.audio.AudioClip import concatenate_audioclips, CompositeAudioClip +from moviepy.audio.io.AudioFileClip import AudioFileClip +voices = {'neural': [ + 'Ivy', + 'Joanna', + 'Kendra', + 'Kimberly', + 'Salli', + 'Joey', + 'Justin', + 'Matthew', + 'Amy', + 'Emma', + 'Brian' + +], 'standard': [ + 'Ivy', + 'Joanna', + 'Kendra', + 'Kimberly', + 'Salli', + 'Joey', + 'Justin', + 'Matthew', + "Russell", + "Nicole", + "Amy", + "Emma", + "Brian", + "Aditi", + "Raveena", + "Geraint" +]} + + +# valid voices https://lazypy.ro/tts/ + + +class POLLY: + def __init__(self): + self.url = 'https://streamlabs.com/polly/speak' + + def tts( + self, + req_text: str = "Amazon Text To Speech", + filename: str = "title.mp3", + random_speaker=False, + censer=False, + ): + if random_speaker: + voice = self.randomvoice() + else: + if not os.getenv('VOICE'): + return ValueError('Please set the environment variable VOICE to a valid voice. options are: {}'.format(voices)) + voice = (os.getenv("VOICE")) + body = {'voice': voice, 'text': req_text} + response = requests.post(self.url, data=body) + try: + voice_data = requests.get(response.json()['speak_url']) + with open(filename, 'wb') as f: + f.write(voice_data.content) + except KeyError: + if response.json()['error'] == 'Text length is too long!': + chunks = [ + m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text) + ] + + audio_clips = [] + cbn = sox.Combiner() + + chunkId = 0 + for chunk in chunks: + body = {'voice': 'Brian', 'text': chunk} + resp = requests.post(self.url, data=body) + voice_data = requests.get(resp.json()['speak_url']) + with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: + out.write(voice_data.content) + + audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) + + chunkId = chunkId + 1 + try: + if len(audio_clips) > 1: + cbn.convert(samplerate=44100, n_channels=2) + cbn.build(audio_clips, filename, "concatenate") + else: + os.rename(audio_clips[0], filename) + except (sox.core.SoxError, + FileNotFoundError): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 + for clip in audio_clips: + i = audio_clips.index(clip) # get the index of the clip + audio_clips = ( + audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1:] + ) # replace the clip with an AudioFileClip + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + audio_composite.write_audiofile(filename, 44100, 2, 2000, None) + + def make_readable(self, text): + """ + Amazon Polly fails to read some symbols properly such as '& (and)'. + So we normalize input text before passing it to the service + """ + text = text.replace('&', 'and') + return text + + def randomvoice(self): + valid = voices['neural'] + voices['standard'] + return random.choice(valid) diff --git a/TTS/TikTok.py b/TTS/TikTok.py index 8ed9f8e..79d472a 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -110,6 +110,7 @@ class TikTok: # TikTok Text-to-Speech Wrapper r = session.post( f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0" ) + print(r.text) vstr = [r.json()["data"]["v_str"]][0] b64d = base64.b64decode(vstr) @@ -141,3 +142,6 @@ class TikTok: # TikTok Text-to-Speech Wrapper if ok_or_good == 1: # 1/10 chance of ok voice return random.choice(voices) return random.choice(human) # 9/10 chance of good voice + + +TikTok().tts('Hello World', '../TTS/hello.mp3') diff --git a/TTS/swapper.py b/TTS/swapper.py index cd18223..f4717b1 100644 --- a/TTS/swapper.py +++ b/TTS/swapper.py @@ -3,9 +3,10 @@ from os import getenv from dotenv import load_dotenv from TTS.GTTS import GTTS +from TTS.POLLY import POLLY from TTS.TikTok import TikTok -CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS} +CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS, 'polly': POLLY} class TTS: From ebe6974cc5c5d0e7ae1f9da11b069cb67738d0d6 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 17:44:22 -0400 Subject: [PATCH 18/49] left debug thing. --- .env.template | 4 ++-- TTS/TikTok.py | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.env.template b/.env.template index b66ec3e..d5e82fd 100644 --- a/.env.template +++ b/.env.template @@ -32,8 +32,8 @@ MAX_COMMENT_LENGTH="500" OPACITY="1" # see TTSwrapper.py for all valid options -VOICE="en_us_001" # e.g. en_us_002 +VOICE="" # e.g. en_us_002 # IN-PROGRESS - not yet implemented -TTsChoice="TikTok" # todo add docs +TTsChoice="polly" # todo add docs STORYMODE="False" diff --git a/TTS/TikTok.py b/TTS/TikTok.py index 79d472a..a299216 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -142,6 +142,3 @@ class TikTok: # TikTok Text-to-Speech Wrapper if ok_or_good == 1: # 1/10 chance of ok voice return random.choice(voices) return random.choice(human) # 9/10 chance of good voice - - -TikTok().tts('Hello World', '../TTS/hello.mp3') From c3279f7995bade66837ea9001eada77b7ad50b9d Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 17:45:48 -0400 Subject: [PATCH 19/49] removed tts choice from debug --- .env.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.template b/.env.template index d5e82fd..bb9dced 100644 --- a/.env.template +++ b/.env.template @@ -33,7 +33,7 @@ OPACITY="1" # see TTSwrapper.py for all valid options VOICE="" # e.g. en_us_002 +TTsChoice="polly" # todo add docs # IN-PROGRESS - not yet implemented -TTsChoice="polly" # todo add docs STORYMODE="False" From 045d7b2b011acf80551488a3f0101c625de6564d Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 17:49:24 -0400 Subject: [PATCH 20/49] fixed capitalization issue --- .env.template | 2 +- TTS/POLLY.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.template b/.env.template index bb9dced..6bf7e4f 100644 --- a/.env.template +++ b/.env.template @@ -32,7 +32,7 @@ MAX_COMMENT_LENGTH="500" OPACITY="1" # see TTSwrapper.py for all valid options -VOICE="" # e.g. en_us_002 +VOICE="Matthew" # e.g. en_us_002 TTsChoice="polly" # todo add docs # IN-PROGRESS - not yet implemented diff --git a/TTS/POLLY.py b/TTS/POLLY.py index 79a6f91..360f98f 100644 --- a/TTS/POLLY.py +++ b/TTS/POLLY.py @@ -58,7 +58,7 @@ class POLLY: else: if not os.getenv('VOICE'): return ValueError('Please set the environment variable VOICE to a valid voice. options are: {}'.format(voices)) - voice = (os.getenv("VOICE")) + voice = str(os.getenv("VOICE")).capitalize() body = {'voice': voice, 'text': req_text} response = requests.post(self.url, data=body) try: From e96a4a5859af321c6771959b5239b7798153d5db Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 17:50:03 -0400 Subject: [PATCH 21/49] changed regex splitter --- TTS/POLLY.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TTS/POLLY.py b/TTS/POLLY.py index 360f98f..4696508 100644 --- a/TTS/POLLY.py +++ b/TTS/POLLY.py @@ -68,7 +68,7 @@ class POLLY: except KeyError: if response.json()['error'] == 'Text length is too long!': chunks = [ - m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text) + m.group().strip() for m in re.finditer(r" *((.{0,540})(\.|.$))", req_text) ] audio_clips = [] From bb2e02288fb15b3d3e426036a9369ed7ca080b48 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 17:51:42 -0400 Subject: [PATCH 22/49] changed regex splitter --- TTS/POLLY.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TTS/POLLY.py b/TTS/POLLY.py index 4696508..a32510f 100644 --- a/TTS/POLLY.py +++ b/TTS/POLLY.py @@ -68,7 +68,7 @@ class POLLY: except KeyError: if response.json()['error'] == 'Text length is too long!': chunks = [ - m.group().strip() for m in re.finditer(r" *((.{0,540})(\.|.$))", req_text) + m.group().strip() for m in re.finditer(r" *((.{0,530})(\.|.$))", req_text) ] audio_clips = [] From d9015df8ed6cc162915ebe074f567592c20fb523 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 18:00:44 -0400 Subject: [PATCH 23/49] fixed shirts i think --- video_creation/background.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/video_creation/background.py b/video_creation/background.py index e8f8ad1..d1a7948 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -14,7 +14,7 @@ def get_start_and_end_times(video_length, length_of_clip): def download_background(): - """Downloads the backgrounds/s video from youtube.""" + """Downloads the backgrounds/s video from YouTube.""" Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True) background_options = [ # uri , filename , credit ("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"), @@ -25,7 +25,7 @@ def download_background(): # ), ] # note: make sure the file name doesn't include an - in it - if not len(listdir("./assets/backgrounds")) <= len( + if not len(listdir("./assets/backgrounds")) >= len( background_options ): # if there are any background videos not installed print_step( From c0db4c8f1e83370e6621e3395326de44306148d9 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 18:46:19 -0400 Subject: [PATCH 24/49] added doc statment --- utils/subreddit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/subreddit.py b/utils/subreddit.py index 035aed7..2a1114a 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -18,6 +18,7 @@ def get_subreddit_undone(submissions: List, subreddit): print_substep("NSFW Post Detected. Skipping...") continue return submission + print('all submissions have been done going by top submission order') return get_subreddit_undone( subreddit.top(time_filter="hour"), subreddit ) # all of the videos in hot have already been done From 63ca264ddacb458b86274b74371f5a6e38f42c81 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 22:59:32 -0400 Subject: [PATCH 25/49] FIXES audio/image de-syncing issues --- video_creation/final_video.py | 49 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 3a199f7..26a02ac 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -13,17 +13,12 @@ from moviepy.editor import ( CompositeAudioClip, CompositeVideoClip, ) -import reddit.subreddit -import re -from utils.console import print_step, print_substep -from dotenv import load_dotenv -import os from moviepy.video.io import ffmpeg_tools +from rich.console import Console from reddit import subreddit from utils.cleanup import cleanup from utils.console import print_step, print_substep -from rich.console import Console console = Console() @@ -57,11 +52,31 @@ def make_final_video(number_of_clips, length): # Output Length console.log(f"[bold green] Video Will Be: {int_total_length} Seconds Long") - # Gather all images + # add title to video image_clips = [] + # Gather all images + if ( + opacity is None or float(opacity) >= 1 + ): # opacity not set or is set to one OR MORE + image_clips.insert( + 0, + ImageClip(f"assets/temp/png/title.png") + .set_duration(audio_clips[0].duration) + .set_position("center") + .resize(width=W - 100) + .set_opacity(float(opacity)), + ) + else: + image_clips.insert( + 0, + ImageClip(f"assets/temp/png/title.png") + .set_duration(audio_clips[0].duration) + .set_position("center") + .resize(width=W - 100)) + for i in range(0, number_of_clips): if ( - opacity is None or float(opacity) >= 1 + opacity is None or float(opacity) >= 1 ): # opacity not set or is set to one OR MORE image_clips.append( ImageClip(f"assets/temp/png/comment_{i}.png") @@ -77,17 +92,7 @@ def make_final_video(number_of_clips, length): .resize(width=W - 100) .set_opacity(float(opacity)), ) - if ( - opacity is None or float(opacity) >= 1 - ): # opacity not set or is set to one OR MORE - image_clips.insert( - 0, - ImageClip(f"assets/temp/png/title.png") - .set_duration(audio_clips[0].duration) - .set_position("center") - .resize(width=W - 100) - .set_opacity(float(opacity)), - ) + # if os.path.exists("assets/mp3/posttext.mp3"): # image_clips.insert( # 0, @@ -98,12 +103,6 @@ def make_final_video(number_of_clips, length): # .set_opacity(float(opacity)), # ) # else: - image_clips.insert( - 0, - ImageClip("assets/temp/png/title.png").set_duration(audio_clips[0].duration) - # .set_duration(audixc vcco_clips[0].duration) - .set_position("center").resize(width=W - 100).set_opacity(float(opacity)), - ) image_concat = concatenate_videoclips(image_clips).set_position( ("center", "center") ) From 9859e4a0bc81354c5af23c7868e0d0403abdb100 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 23:07:51 -0400 Subject: [PATCH 26/49] fix: accidentally left the voice hardcoded to brian --- TTS/POLLY.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TTS/POLLY.py b/TTS/POLLY.py index a32510f..95db5e6 100644 --- a/TTS/POLLY.py +++ b/TTS/POLLY.py @@ -76,7 +76,7 @@ class POLLY: chunkId = 0 for chunk in chunks: - body = {'voice': 'Brian', 'text': chunk} + body = {'voice': voice, 'text': chunk} resp = requests.post(self.url, data=body) voice_data = requests.get(resp.json()['speak_url']) with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: From 3778ca2a1413f94f8b432f843c468f4a774a74c3 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Jun 2022 23:25:51 -0400 Subject: [PATCH 27/49] fix: fixed language options --- TTS/POLLY.py | 39 ++++----------------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/TTS/POLLY.py b/TTS/POLLY.py index 95db5e6..55e7471 100644 --- a/TTS/POLLY.py +++ b/TTS/POLLY.py @@ -6,37 +6,7 @@ import requests import sox from moviepy.audio.AudioClip import concatenate_audioclips, CompositeAudioClip from moviepy.audio.io.AudioFileClip import AudioFileClip -voices = {'neural': [ - 'Ivy', - 'Joanna', - 'Kendra', - 'Kimberly', - 'Salli', - 'Joey', - 'Justin', - 'Matthew', - 'Amy', - 'Emma', - 'Brian' - -], 'standard': [ - 'Ivy', - 'Joanna', - 'Kendra', - 'Kimberly', - 'Salli', - 'Joey', - 'Justin', - 'Matthew', - "Russell", - "Nicole", - "Amy", - "Emma", - "Brian", - "Aditi", - "Raveena", - "Geraint" -]} +voices = ['Brian', 'Emma', 'Russell', 'Joey', 'Matthew', 'Joanna', 'Kimberly', 'Amy', 'Geraint', 'Nicole', 'Justin', 'Ivy', 'Kendra', 'Salli', 'Raveena'] # valid voices https://lazypy.ro/tts/ @@ -59,7 +29,7 @@ class POLLY: if not os.getenv('VOICE'): return ValueError('Please set the environment variable VOICE to a valid voice. options are: {}'.format(voices)) voice = str(os.getenv("VOICE")).capitalize() - body = {'voice': voice, 'text': req_text} + body = {'voice': voice, 'text': req_text, 'service': 'polly'} response = requests.post(self.url, data=body) try: voice_data = requests.get(response.json()['speak_url']) @@ -76,7 +46,7 @@ class POLLY: chunkId = 0 for chunk in chunks: - body = {'voice': voice, 'text': chunk} + body = {'voice': voice, 'text': chunk, 'service': 'polly'} resp = requests.post(self.url, data=body) voice_data = requests.get(resp.json()['speak_url']) with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: @@ -111,5 +81,4 @@ class POLLY: return text def randomvoice(self): - valid = voices['neural'] + voices['standard'] - return random.choice(valid) + return random.choice(voices) From 2f87ee4a56bc67d7da22f3004c5efb7d679bf93d Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 17 Jun 2022 19:35:42 -0400 Subject: [PATCH 28/49] updates exceptions and audio splitter/merger --- TTS/POLLY.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/TTS/POLLY.py b/TTS/POLLY.py index 55e7471..b8f6c91 100644 --- a/TTS/POLLY.py +++ b/TTS/POLLY.py @@ -6,6 +6,8 @@ import requests import sox from moviepy.audio.AudioClip import concatenate_audioclips, CompositeAudioClip from moviepy.audio.io.AudioFileClip import AudioFileClip +from requests.exceptions import JSONDecodeError + voices = ['Brian', 'Emma', 'Russell', 'Joey', 'Matthew', 'Joanna', 'Kimberly', 'Amy', 'Geraint', 'Nicole', 'Justin', 'Ivy', 'Kendra', 'Salli', 'Raveena'] @@ -35,10 +37,10 @@ class POLLY: voice_data = requests.get(response.json()['speak_url']) with open(filename, 'wb') as f: f.write(voice_data.content) - except KeyError: + except (KeyError, JSONDecodeError): if response.json()['error'] == 'Text length is too long!': chunks = [ - m.group().strip() for m in re.finditer(r" *((.{0,530})(\.|.$))", req_text) + m.group().strip() for m in re.finditer(r" *((.{0,499})(\.|.$))", req_text) ] audio_clips = [] From 4c3f9545dcf67b87cf9aca481e7ce70a82c62a4c Mon Sep 17 00:00:00 2001 From: Daud AHN <72173079+daud-ahn@users.noreply.github.com> Date: Sat, 18 Jun 2022 11:27:35 +0700 Subject: [PATCH 29/49] Fix a typo in `.env.template` --- .env.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.template b/.env.template index 6bf7e4f..173bacf 100644 --- a/.env.template +++ b/.env.template @@ -8,7 +8,7 @@ REDDIT_PASSWORD="" # If no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: "no" RANDOM_THREAD="" -# Filters the comments by range of lenght (min and max characters) +# Filters the comments by range of length (min and max characters) # Min has to be less or equal to max # DO NOT INSERT ANY SPACES BETWEEN THE COMMA AND THE VALUES COMMENT_LENGTH_RANGE = "min,max" From 173311b1d4812f7f3c5580c664f815764c5728a3 Mon Sep 17 00:00:00 2001 From: Avin-T Date: Sat, 18 Jun 2022 16:26:17 +0545 Subject: [PATCH 30/49] Fixed README typo --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c37aa6f..13248b1 100644 --- a/README.md +++ b/README.md @@ -43,16 +43,16 @@ The only original thing being done is the editing and gathering of all materials 2b **Manual Install**: Rename `.env.template` to `.env` and replace all values with the appropriate fields. To get Reddit keys (**required**), visit [the Reddit Apps page.](https://www.reddit.com/prefs/apps) TL;DR set up an app that is a "script". Copy your keys into the `.env` file, along with whether your account uses two-factor authentication. -3. install [SoX](https://sourceforge.net/projects/sox/files/sox/) -4. -5. Run `pip3 install -r requirements.txt` +3. Install [SoX](https://sourceforge.net/projects/sox/files/sox/) + +4. Run `pip3 install -r requirements.txt` -6. Run `playwright install` and `playwright install-deps`. +5. Run `playwright install` and `playwright install-deps`. -7. Run `python3 main.py` (unless you chose automatic install, then the installer will automatically run main.py) +6. Run `python3 main.py` (unless you chose automatic install, then the installer will automatically run main.py) required\*\*), visit [the Reddit Apps page.](https://www.reddit.com/prefs/apps) TL;DR set up an app that is a "script". Copy your keys into the `.env` file, along with whether your account uses two-factor authentication. -8. Enjoy 😎 +7. Enjoy 😎 ## Video @@ -71,7 +71,7 @@ I have tried to simplify the code so anyone can read it and start contributing a - [x] Allowing users to change voice. - [x] Checks if a video has already been created - [x] Light and Dark modes -- [x] Nsfw post filter +- [x] NSFW post filter Please read our [contributing guidelines](CONTRIBUTING.md) for more detailed information. From 8b0be933028493cd0f1d2fd1a0290631e2ac8177 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Sat, 18 Jun 2022 17:37:34 +0300 Subject: [PATCH 31/49] Some refactors, removed unnecessary f-strings and never referenced variables --- TTS/GTTS.py | 2 +- TTS/POLLY.py | 60 ++++++++++++++++++++++++----------- TTS/TikTok.py | 25 ++++++--------- main.py | 9 +++--- reddit/subreddit.py | 20 ++++-------- video_creation/final_video.py | 27 ++++++---------- video_creation/voices.py | 16 ++++++---- 7 files changed, 82 insertions(+), 77 deletions(-) diff --git a/TTS/GTTS.py b/TTS/GTTS.py index 9100dd8..fcbcb9b 100644 --- a/TTS/GTTS.py +++ b/TTS/GTTS.py @@ -7,7 +7,7 @@ class GTTS: req_text: str = "Google Text To Speech", filename: str = "title.mp3", random_speaker=False, - censer=False, + censor=False, ): tts = gTTS(text=req_text, lang="en", slow=False) tts.save(f"{filename}") diff --git a/TTS/POLLY.py b/TTS/POLLY.py index b8f6c91..54cddca 100644 --- a/TTS/POLLY.py +++ b/TTS/POLLY.py @@ -8,7 +8,23 @@ from moviepy.audio.AudioClip import concatenate_audioclips, CompositeAudioClip from moviepy.audio.io.AudioFileClip import AudioFileClip from requests.exceptions import JSONDecodeError -voices = ['Brian', 'Emma', 'Russell', 'Joey', 'Matthew', 'Joanna', 'Kimberly', 'Amy', 'Geraint', 'Nicole', 'Justin', 'Ivy', 'Kendra', 'Salli', 'Raveena'] +voices = [ + "Brian", + "Emma", + "Russell", + "Joey", + "Matthew", + "Joanna", + "Kimberly", + "Amy", + "Geraint", + "Nicole", + "Justin", + "Ivy", + "Kendra", + "Salli", + "Raveena", +] # valid voices https://lazypy.ro/tts/ @@ -16,29 +32,33 @@ voices = ['Brian', 'Emma', 'Russell', 'Joey', 'Matthew', 'Joanna', 'Kimberly', ' class POLLY: def __init__(self): - self.url = 'https://streamlabs.com/polly/speak' + self.url = "https://streamlabs.com/polly/speak" def tts( - self, - req_text: str = "Amazon Text To Speech", - filename: str = "title.mp3", - random_speaker=False, - censer=False, + self, + req_text: str = "Amazon Text To Speech", + filename: str = "title.mp3", + random_speaker=False, + censor=False, ): if random_speaker: voice = self.randomvoice() else: - if not os.getenv('VOICE'): - return ValueError('Please set the environment variable VOICE to a valid voice. options are: {}'.format(voices)) + if not os.getenv("VOICE"): + return ValueError( + "Please set the environment variable VOICE to a valid voice. options are: {}".format( + voices + ) + ) voice = str(os.getenv("VOICE")).capitalize() - body = {'voice': voice, 'text': req_text, 'service': 'polly'} + body = {"voice": voice, "text": req_text, "service": "polly"} response = requests.post(self.url, data=body) try: - voice_data = requests.get(response.json()['speak_url']) - with open(filename, 'wb') as f: + voice_data = requests.get(response.json()["speak_url"]) + with open(filename, "wb") as f: f.write(voice_data.content) except (KeyError, JSONDecodeError): - if response.json()['error'] == 'Text length is too long!': + if response.json()["error"] == "Text length is too long!": chunks = [ m.group().strip() for m in re.finditer(r" *((.{0,499})(\.|.$))", req_text) ] @@ -48,9 +68,9 @@ class POLLY: chunkId = 0 for chunk in chunks: - body = {'voice': voice, 'text': chunk, 'service': 'polly'} + body = {"voice": voice, "text": chunk, "service": "polly"} resp = requests.post(self.url, data=body) - voice_data = requests.get(resp.json()['speak_url']) + voice_data = requests.get(resp.json()["speak_url"]) with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: out.write(voice_data.content) @@ -63,12 +83,14 @@ class POLLY: cbn.build(audio_clips, filename, "concatenate") else: os.rename(audio_clips[0], filename) - except (sox.core.SoxError, - FileNotFoundError): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 + except ( + sox.core.SoxError, + FileNotFoundError, + ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 for clip in audio_clips: i = audio_clips.index(clip) # get the index of the clip audio_clips = ( - audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1:] + audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] ) # replace the clip with an AudioFileClip audio_concat = concatenate_audioclips(audio_clips) audio_composite = CompositeAudioClip([audio_concat]) @@ -79,7 +101,7 @@ class POLLY: Amazon Polly fails to read some symbols properly such as '& (and)'. So we normalize input text before passing it to the service """ - text = text.replace('&', 'and') + text = text.replace("&", "and") return text def randomvoice(self): diff --git a/TTS/TikTok.py b/TTS/TikTok.py index a299216..b9d1928 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -74,21 +74,17 @@ class TikTok: # TikTok Text-to-Speech Wrapper req_text: str = "TikTok Text To Speech", filename: str = "title.mp3", random_speaker: bool = False, - censer=False, + censor=False, ): req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and") - if censer: + if censor: # req_text = pf.censor(req_text) pass voice = ( - self.randomvoice() - if random_speaker - else (os.getenv("VOICE") or random.choice(human)) + self.randomvoice() if random_speaker else (os.getenv("VOICE") or random.choice(human)) ) - chunks = [ - m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text) - ] + chunks = [m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text)] audio_clips = [] cbn = sox.Combiner() @@ -97,9 +93,7 @@ class TikTok: # TikTok Text-to-Speech Wrapper chunkId = 0 for chunk in chunks: try: - r = requests.post( - f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0" - ) + r = requests.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") except requests.exceptions.SSLError: # https://stackoverflow.com/a/47475019/18516611 session = requests.Session() @@ -107,9 +101,7 @@ class TikTok: # TikTok Text-to-Speech Wrapper adapter = HTTPAdapter(max_retries=retry) session.mount("http://", adapter) session.mount("https://", adapter) - r = session.post( - f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0" - ) + r = session.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") print(r.text) vstr = [r.json()["data"]["v_str"]][0] b64d = base64.b64decode(vstr) @@ -126,7 +118,10 @@ class TikTok: # TikTok Text-to-Speech Wrapper cbn.build(audio_clips, filename, "concatenate") else: os.rename(audio_clips[0], filename) - except (sox.core.SoxError, FileNotFoundError): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 + except ( + sox.core.SoxError, + FileNotFoundError, + ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 for clip in audio_clips: i = audio_clips.index(clip) # get the index of the clip audio_clips = ( diff --git a/main.py b/main.py index 1c6316d..b4e1123 100755 --- a/main.py +++ b/main.py @@ -11,15 +11,16 @@ from video_creation.final_video import make_final_video from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts from video_creation.voices import save_text_to_mp3 -banner = """ -β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— +print( + """ +β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β•šβ•β•β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β• β•šβ•β•β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β•β•šβ•β• β•šβ•β• """ -print(banner) +) load_dotenv() # Modified by JasonLovesDoggo print_markdown( @@ -47,7 +48,7 @@ def main(): download_screenshots_of_reddit_posts(reddit_object, number_of_comments) download_background() chop_background_video(length) - final_video = make_final_video(number_of_comments, length) + make_final_video(number_of_comments, length) def run_many(times): diff --git a/reddit/subreddit.py b/reddit/subreddit.py index 273eb27..dc0d8ae 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -23,9 +23,7 @@ def get_subreddit_threads(): content = {} if str(getenv("REDDIT_2FA")).casefold() == "yes": - print( - "\nEnter your two-factor authentication code from your authenticator app.\n" - ) + print("\nEnter your two-factor authentication code from your authenticator app.\n") code = input("> ") print() pw = getenv("REDDIT_PASSWORD") @@ -51,9 +49,7 @@ def get_subreddit_threads(): input("What subreddit would you like to pull from? ") ) # if the env isnt set, ask user else: - print_substep( - f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config" - ) + print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config") subreddit = reddit.subreddit( getenv("SUBREDDIT") ) # Allows you to specify in .env. Done for automation purposes. @@ -71,14 +67,10 @@ def get_subreddit_threads(): num_comments = submission.num_comments print_substep(f"Video will be: {submission.title} :thumbsup:", style="bold green") - print_substep(f"Thread has " + str(upvotes) + " upvotes", style="bold blue") - print_substep( - f"Thread has a upvote ratio of " + str(ratio) + "%", style="bold blue" - ) - print_substep(f"Thread has " + str(num_comments) + " comments", style="bold blue") - environ["VIDEO_TITLE"] = str( - textify(submission.title) - ) # todo use global instend of env vars + print_substep(f"Thread has {upvotes} upvotes", style="bold blue") + print_substep(f"Thread has a upvote ratio of {ratio}%", style="bold blue") + print_substep(f"Thread has {num_comments} comments", style="bold blue") + environ["VIDEO_TITLE"] = str(textify(submission.title)) # todo use global instend of env vars environ["VIDEO_ID"] = str(textify(submission.id)) content["thread_url"] = f"https://reddit.com{submission.permalink}" diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 26a02ac..60a1f6a 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -41,7 +41,7 @@ def make_final_video(number_of_clips, length): audio_clips = [] for i in range(0, number_of_clips): audio_clips.append(AudioFileClip(f"assets/temp/mp3/{i}.mp3")) - audio_clips.insert(0, AudioFileClip(f"assets/temp/mp3/title.mp3")) + audio_clips.insert(0, AudioFileClip("assets/temp/mp3/title.mp3")) audio_concat = concatenate_audioclips(audio_clips) audio_composite = CompositeAudioClip([audio_concat]) @@ -55,12 +55,10 @@ def make_final_video(number_of_clips, length): # add title to video image_clips = [] # Gather all images - if ( - opacity is None or float(opacity) >= 1 - ): # opacity not set or is set to one OR MORE + if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE image_clips.insert( 0, - ImageClip(f"assets/temp/png/title.png") + ImageClip("assets/temp/png/title.png") .set_duration(audio_clips[0].duration) .set_position("center") .resize(width=W - 100) @@ -69,15 +67,14 @@ def make_final_video(number_of_clips, length): else: image_clips.insert( 0, - ImageClip(f"assets/temp/png/title.png") + ImageClip("assets/temp/png/title.png") .set_duration(audio_clips[0].duration) .set_position("center") - .resize(width=W - 100)) + .resize(width=W - 100), + ) for i in range(0, number_of_clips): - if ( - opacity is None or float(opacity) >= 1 - ): # opacity not set or is set to one OR MORE + if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE image_clips.append( ImageClip(f"assets/temp/png/comment_{i}.png") .set_duration(audio_clips[i + 1].duration) @@ -103,9 +100,7 @@ def make_final_video(number_of_clips, length): # .set_opacity(float(opacity)), # ) # else: - image_concat = concatenate_videoclips(image_clips).set_position( - ("center", "center") - ) + image_concat = concatenate_videoclips(image_clips).set_position(("center", "center")) image_concat.audio = audio_composite final = CompositeVideoClip([background_clip, image_concat]) @@ -139,9 +134,7 @@ def make_final_video(number_of_clips, length): print_substep("the results folder didn't exist so I made it") os.mkdir("./results") - final.write_videofile( - "assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k" - ) + final.write_videofile("assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k") ffmpeg_tools.ffmpeg_extract_subclip( "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" ) @@ -150,7 +143,7 @@ def make_final_video(number_of_clips, length): print_step("Removing temporary files πŸ—‘") cleanups = cleanup() print_substep(f"Removed {cleanups} temporary files πŸ—‘") - print_substep(f"See result in the results folder!") + print_substep("See result in the results folder!") print_step( f"Reddit title: {os.getenv('VIDEO_TITLE')} \n Background Credit: {os.getenv('background_credit')}" diff --git a/video_creation/voices.py b/video_creation/voices.py index 76c4dc3..b345079 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -10,11 +10,12 @@ from rich.progress import track from TTS.swapper import TTS -console = Console() - from utils.console import print_step, print_substep from utils.voice import sanitize_text +console = Console() + + VIDEO_LENGTH: int = 40 # secs @@ -31,23 +32,24 @@ def save_text_to_mp3(reddit_obj): TextToSpeech = TTS() TextToSpeech.tts( sanitize_text(reddit_obj["thread_title"]), - filename=f"assets/temp/mp3/title.mp3", + filename="assets/temp/mp3/title.mp3", random_speaker=False, ) try: - length += MP3(f"assets/temp/mp3/title.mp3").info.length + length += MP3("assets/temp/mp3/title.mp3").info.length except HeaderNotFoundError: # note to self AudioFileClip - length += sox.file_info.duration(f"assets/temp/mp3/title.mp3") + length += sox.file_info.duration("assets/temp/mp3/title.mp3") if getenv("STORYMODE").casefold() == "true": TextToSpeech.tts( sanitize_text(reddit_obj["thread_content"]), - filename=f"assets/temp/mp3/story_content.mp3", + filename="assets/temp/mp3/story_content.mp3", random_speaker=False, ) # 'story_content' com = 0 for comment in track((reddit_obj["comments"]), "Saving..."): - # ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer, but this is just a good_voices starting point + # ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer + # but this is just a good_voices starting point if length > VIDEO_LENGTH: break From 7b56495c5927e01c8eb2aff0fba84485fd4d7f9d Mon Sep 17 00:00:00 2001 From: Josh Creek Date: Sat, 18 Jun 2022 18:01:22 +0100 Subject: [PATCH 32/49] chore(*): Add devcontainer vscode support --- .devcontainer/devcontainer.json | 19 +++++++++++++++++++ .gitattributes | 1 + .vscode/launch.json | 13 +++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .gitattributes create mode 100644 .vscode/launch.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..1f86901 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,19 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.238.0/containers/docker-existing-dockerfile +{ + "name": "Existing Dockerfile", + + // Sets the run context to one level up instead of the .devcontainer folder. + "context": "..", + + // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. + "dockerFile": "../Dockerfile", + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python" + ], + + // Install OS dependencies + "postCreateCommand": "apt-get update && apt -qq install -y sox && apt-get install -y libsox-fmt-all" +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0d2e482 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Main.py", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/main.py", + "console": "integratedTerminal", + "justMyCode": true + } + ] +} \ No newline at end of file From d7aa5d0cfbcc050ede82ba64bafde9e74051e6e9 Mon Sep 17 00:00:00 2001 From: Owen Gaspard Date: Sat, 18 Jun 2022 13:38:50 -0500 Subject: [PATCH 33/49] Check for missing environment variables Checks your .env and matches it to the .env.template to find missing variables. --- .gitignore | 3 ++- main.py | 3 +++ utils/envUpdate.py | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 utils/envUpdate.py diff --git a/.gitignore b/.gitignore index 2701ca5..d365d9e 100644 --- a/.gitignore +++ b/.gitignore @@ -169,4 +169,5 @@ results/* reddit-bot-351418-5560ebc49cac.json /.idea *.pyc -/video_creation/data/videos.json \ No newline at end of file +/video_creation/data/videos.json +utils/envUpdate.py.old \ No newline at end of file diff --git a/main.py b/main.py index b4e1123..92395b1 100755 --- a/main.py +++ b/main.py @@ -6,6 +6,7 @@ from os import getenv, name from reddit.subreddit import get_subreddit_threads from utils.cleanup import cleanup from utils.console import print_markdown, print_step +from utils.envUpdate import checkUpdate from video_creation.background import download_background, chop_background_video from video_creation.final_video import make_final_video from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts @@ -22,6 +23,7 @@ print( """ ) load_dotenv() +envUpdate() # Modified by JasonLovesDoggo 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. You can find solutions to many common problems in the [Documentation](https://luka-hietala.gitbook.io/documentation-for-the-reddit-bot/)" @@ -37,6 +39,7 @@ reddit2fa = getenv("REDDIT_2FA") def main(): + checkUpdate() cleanup() def get_obj(): diff --git a/utils/envUpdate.py b/utils/envUpdate.py new file mode 100644 index 0000000..a95b48f --- /dev/null +++ b/utils/envUpdate.py @@ -0,0 +1,37 @@ +import os +import subprocess +import tempfile +from os import path +import logging + +log = logging.getLogger(__name__) + + +def envUpdate(): + if path.exists(".env.template"): + envTemplate = subprocess.check_output( + "awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", # noqa + shell=True, + ) + tempEnv = tempfile.TemporaryFile() + tempEnv.write(envTemplate) + tempEnv.seek(0) + envVars = tempEnv.readlines() + + missing = [] + isMissingEnvs = False + for env in envVars: + try: + env = env.decode("utf-8").strip() + except AttributeError: + env = env.strip() + + if env not in os.environ: + isMissingEnvs = True + missing.append(env) + + if isMissingEnvs: + log.error( + f"[ERROR] The following environment variables are missing: {missing}.)" + ) + exit(-1) \ No newline at end of file From 565a8dd58f8db2fa90e98ff819f79fa5eb9a61c0 Mon Sep 17 00:00:00 2001 From: Owen Gaspard Date: Sat, 18 Jun 2022 13:47:46 -0500 Subject: [PATCH 34/49] Fix incorrect call I changed the name of a function but forgot to update it in the main file. It should work now. --- main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index 92395b1..bfb3ba3 100755 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ from os import getenv, name from reddit.subreddit import get_subreddit_threads from utils.cleanup import cleanup from utils.console import print_markdown, print_step -from utils.envUpdate import checkUpdate +from utils.envUpdate import envUpdate from video_creation.background import download_background, chop_background_video from video_creation.final_video import make_final_video from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts @@ -23,7 +23,6 @@ print( """ ) load_dotenv() -envUpdate() # Modified by JasonLovesDoggo 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. You can find solutions to many common problems in the [Documentation](https://luka-hietala.gitbook.io/documentation-for-the-reddit-bot/)" @@ -39,7 +38,7 @@ reddit2fa = getenv("REDDIT_2FA") def main(): - checkUpdate() + envUpdate() cleanup() def get_obj(): From a56e818e263b113bc7f335e4e03b5cd13b88f506 Mon Sep 17 00:00:00 2001 From: ART3MISTICAL Date: Sun, 19 Jun 2022 02:11:08 +0530 Subject: [PATCH 35/49] Converted spaces into tabs for all files --- .github/dependabot.yml | 8 +- .github/workflows/codeql-analysis.yml | 96 ++++---- TTS/GTTS.py | 18 +- TTS/POLLY.py | 166 ++++++------- TTS/TikTok.py | 216 ++++++++--------- TTS/swapper.py | 18 +- reddit/subreddit.py | 150 ++++++------ setup.py | 204 ++++++++-------- utils/cleanup.py | 38 +-- utils/config.py | 52 ++--- utils/console.py | 16 +- utils/loader.py | 80 +++---- utils/subreddit.py | 42 ++-- utils/videos.py | 30 +-- utils/voice.py | 28 +-- video_creation/background.py | 92 ++++---- video_creation/cookies.json | 8 +- video_creation/data/cookie-dark-mode.json | 16 +- video_creation/data/cookie-light-mode.json | 8 +- video_creation/final_video.py | 258 ++++++++++----------- video_creation/screenshot_downloader.py | 100 ++++---- video_creation/voices.py | 108 ++++----- 22 files changed, 876 insertions(+), 876 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ba1c6b8..4c75954 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: "pip" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "daily" + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c04b714..835b4fb 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -12,61 +12,61 @@ name: "CodeQL" on: - push: - branches: [ "master" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "master" ] - schedule: - - cron: '16 14 * * 3' + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '16 14 * * 3' jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write - strategy: - fail-fast: false - matrix: - language: [ 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - steps: - - name: Checkout repository - uses: actions/checkout@v3 + steps: + - name: Checkout repository + uses: actions/checkout@v3 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 - # ℹ️ Command-line programs to run using the OS shell. - # πŸ“š See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + # ℹ️ Command-line programs to run using the OS shell. + # πŸ“š See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/TTS/GTTS.py b/TTS/GTTS.py index fcbcb9b..e38353c 100644 --- a/TTS/GTTS.py +++ b/TTS/GTTS.py @@ -2,12 +2,12 @@ from gtts import gTTS class GTTS: - def tts( - self, - req_text: str = "Google Text To Speech", - filename: str = "title.mp3", - random_speaker=False, - censor=False, - ): - tts = gTTS(text=req_text, lang="en", slow=False) - tts.save(f"{filename}") + def tts( + self, + req_text: str = "Google Text To Speech", + filename: str = "title.mp3", + random_speaker=False, + censor=False, + ): + tts = gTTS(text=req_text, lang="en", slow=False) + tts.save(f"{filename}") diff --git a/TTS/POLLY.py b/TTS/POLLY.py index 54cddca..4f8650c 100644 --- a/TTS/POLLY.py +++ b/TTS/POLLY.py @@ -9,21 +9,21 @@ from moviepy.audio.io.AudioFileClip import AudioFileClip from requests.exceptions import JSONDecodeError voices = [ - "Brian", - "Emma", - "Russell", - "Joey", - "Matthew", - "Joanna", - "Kimberly", - "Amy", - "Geraint", - "Nicole", - "Justin", - "Ivy", - "Kendra", - "Salli", - "Raveena", + "Brian", + "Emma", + "Russell", + "Joey", + "Matthew", + "Joanna", + "Kimberly", + "Amy", + "Geraint", + "Nicole", + "Justin", + "Ivy", + "Kendra", + "Salli", + "Raveena", ] @@ -31,78 +31,78 @@ voices = [ class POLLY: - def __init__(self): - self.url = "https://streamlabs.com/polly/speak" + def __init__(self): + self.url = "https://streamlabs.com/polly/speak" - def tts( - self, - req_text: str = "Amazon Text To Speech", - filename: str = "title.mp3", - random_speaker=False, - censor=False, - ): - if random_speaker: - voice = self.randomvoice() - else: - if not os.getenv("VOICE"): - return ValueError( - "Please set the environment variable VOICE to a valid voice. options are: {}".format( - voices - ) - ) - voice = str(os.getenv("VOICE")).capitalize() - body = {"voice": voice, "text": req_text, "service": "polly"} - response = requests.post(self.url, data=body) - try: - voice_data = requests.get(response.json()["speak_url"]) - with open(filename, "wb") as f: - f.write(voice_data.content) - except (KeyError, JSONDecodeError): - if response.json()["error"] == "Text length is too long!": - chunks = [ - m.group().strip() for m in re.finditer(r" *((.{0,499})(\.|.$))", req_text) - ] + def tts( + self, + req_text: str = "Amazon Text To Speech", + filename: str = "title.mp3", + random_speaker=False, + censor=False, + ): + if random_speaker: + voice = self.randomvoice() + else: + if not os.getenv("VOICE"): + return ValueError( + "Please set the environment variable VOICE to a valid voice. options are: {}".format( + voices + ) + ) + voice = str(os.getenv("VOICE")).capitalize() + body = {"voice": voice, "text": req_text, "service": "polly"} + response = requests.post(self.url, data=body) + try: + voice_data = requests.get(response.json()["speak_url"]) + with open(filename, "wb") as f: + f.write(voice_data.content) + except (KeyError, JSONDecodeError): + if response.json()["error"] == "Text length is too long!": + chunks = [ + m.group().strip() for m in re.finditer(r" *((.{0,499})(\.|.$))", req_text) + ] - audio_clips = [] - cbn = sox.Combiner() + audio_clips = [] + cbn = sox.Combiner() - chunkId = 0 - for chunk in chunks: - body = {"voice": voice, "text": chunk, "service": "polly"} - resp = requests.post(self.url, data=body) - voice_data = requests.get(resp.json()["speak_url"]) - with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: - out.write(voice_data.content) + chunkId = 0 + for chunk in chunks: + body = {"voice": voice, "text": chunk, "service": "polly"} + resp = requests.post(self.url, data=body) + voice_data = requests.get(resp.json()["speak_url"]) + with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: + out.write(voice_data.content) - audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) + audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) - chunkId = chunkId + 1 - try: - if len(audio_clips) > 1: - cbn.convert(samplerate=44100, n_channels=2) - cbn.build(audio_clips, filename, "concatenate") - else: - os.rename(audio_clips[0], filename) - except ( - sox.core.SoxError, - FileNotFoundError, - ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 - for clip in audio_clips: - i = audio_clips.index(clip) # get the index of the clip - audio_clips = ( - audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] - ) # replace the clip with an AudioFileClip - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) - audio_composite.write_audiofile(filename, 44100, 2, 2000, None) + chunkId = chunkId + 1 + try: + if len(audio_clips) > 1: + cbn.convert(samplerate=44100, n_channels=2) + cbn.build(audio_clips, filename, "concatenate") + else: + os.rename(audio_clips[0], filename) + except ( + sox.core.SoxError, + FileNotFoundError, + ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 + for clip in audio_clips: + i = audio_clips.index(clip) # get the index of the clip + audio_clips = ( + audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] + ) # replace the clip with an AudioFileClip + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + audio_composite.write_audiofile(filename, 44100, 2, 2000, None) - def make_readable(self, text): - """ - Amazon Polly fails to read some symbols properly such as '& (and)'. - So we normalize input text before passing it to the service - """ - text = text.replace("&", "and") - return text + def make_readable(self, text): + """ + Amazon Polly fails to read some symbols properly such as '& (and)'. + So we normalize input text before passing it to the service + """ + text = text.replace("&", "and") + return text - def randomvoice(self): - return random.choice(voices) + def randomvoice(self): + return random.choice(voices) diff --git a/TTS/TikTok.py b/TTS/TikTok.py index b9d1928..5b31c00 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -15,49 +15,49 @@ from requests.adapters import HTTPAdapter, Retry # https://twitter.com/scanlime/status/1512598559769702406 nonhuman = [ # DISNEY VOICES - "en_us_ghostface", # Ghost Face - "en_us_chewbacca", # Chewbacca - "en_us_c3po", # C3PO - "en_us_stitch", # Stitch - "en_us_stormtrooper", # Stormtrooper - "en_us_rocket", # Rocket - # ENGLISH VOICES + "en_us_ghostface", # Ghost Face + "en_us_chewbacca", # Chewbacca + "en_us_c3po", # C3PO + "en_us_stitch", # Stitch + "en_us_stormtrooper", # Stormtrooper + "en_us_rocket", # Rocket + # ENGLISH VOICES ] human = [ - "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 (Int. 1) - "en_us_002", # English US - Female (Int. 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", + "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 (Int. 1) + "en_us_002", # English US - Female (Int. 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", ] voices = nonhuman + human noneng = [ - "fr_001", # French - Male 1 - "fr_002", # French - Male 2 - "de_001", # German - Female - "de_002", # German - Male - "es_002", # Spanish - Male - # AMERICA VOICES - "es_mx_002", # Spanish MX - Male - "br_001", # Portuguese BR - Female 1 - "br_003", # Portuguese BR - Female 2 - "br_004", # Portuguese BR - Female 3 - "br_005", # Portuguese BR - Male - # ASIA VOICES - "id_001", # Indonesian - Female - "jp_001", # Japanese - Female 1 - "jp_003", # Japanese - Female 2 - "jp_005", # Japanese - Female 3 - "jp_006", # Japanese - Male - "kr_002", # Korean - Male 1 - "kr_003", # Korean - Female - "kr_004", # Korean - Male 2 + "fr_001", # French - Male 1 + "fr_002", # French - Male 2 + "de_001", # German - Female + "de_002", # German - Male + "es_002", # Spanish - Male + # AMERICA VOICES + "es_mx_002", # Spanish MX - Male + "br_001", # Portuguese BR - Female 1 + "br_003", # Portuguese BR - Female 2 + "br_004", # Portuguese BR - Female 3 + "br_005", # Portuguese BR - Male + # ASIA VOICES + "id_001", # Indonesian - Female + "jp_001", # Japanese - Female 1 + "jp_003", # Japanese - Female 2 + "jp_005", # Japanese - Female 3 + "jp_006", # Japanese - Male + "kr_002", # Korean - Male 1 + "kr_003", # Korean - Female + "kr_004", # Korean - Male 2 ] @@ -66,74 +66,74 @@ noneng = [ class TikTok: # TikTok Text-to-Speech Wrapper - def __init__(self): - self.URI_BASE = "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=" - - def tts( - self, - req_text: str = "TikTok Text To Speech", - filename: str = "title.mp3", - random_speaker: bool = False, - censor=False, - ): - req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and") - if censor: - # req_text = pf.censor(req_text) - pass - voice = ( - self.randomvoice() if random_speaker else (os.getenv("VOICE") or random.choice(human)) - ) - - chunks = [m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text)] - - audio_clips = [] - cbn = sox.Combiner() - # cbn.set_input_format(file_type=["mp3" for _ in chunks]) - - chunkId = 0 - for chunk in chunks: - try: - r = requests.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") - except requests.exceptions.SSLError: - # https://stackoverflow.com/a/47475019/18516611 - session = requests.Session() - retry = Retry(connect=3, backoff_factor=0.5) - adapter = HTTPAdapter(max_retries=retry) - session.mount("http://", adapter) - session.mount("https://", adapter) - r = session.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") - print(r.text) - vstr = [r.json()["data"]["v_str"]][0] - b64d = base64.b64decode(vstr) - - with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: - out.write(b64d) - - audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) - - chunkId = chunkId + 1 - try: - if len(audio_clips) > 1: - cbn.convert(samplerate=44100, n_channels=2) - cbn.build(audio_clips, filename, "concatenate") - else: - os.rename(audio_clips[0], filename) - except ( - sox.core.SoxError, - FileNotFoundError, - ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 - for clip in audio_clips: - i = audio_clips.index(clip) # get the index of the clip - audio_clips = ( - audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] - ) # replace the clip with an AudioFileClip - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) - audio_composite.write_audiofile(filename, 44100, 2, 2000, None) - - @staticmethod - def randomvoice(): - ok_or_good = random.randrange(1, 10) - if ok_or_good == 1: # 1/10 chance of ok voice - return random.choice(voices) - return random.choice(human) # 9/10 chance of good voice + def __init__(self): + self.URI_BASE = "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=" + + def tts( + self, + req_text: str = "TikTok Text To Speech", + filename: str = "title.mp3", + random_speaker: bool = False, + censor=False, + ): + req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and") + if censor: + # req_text = pf.censor(req_text) + pass + voice = ( + self.randomvoice() if random_speaker else (os.getenv("VOICE") or random.choice(human)) + ) + + chunks = [m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text)] + + audio_clips = [] + cbn = sox.Combiner() + # cbn.set_input_format(file_type=["mp3" for _ in chunks]) + + chunkId = 0 + for chunk in chunks: + try: + r = requests.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") + except requests.exceptions.SSLError: + # https://stackoverflow.com/a/47475019/18516611 + session = requests.Session() + retry = Retry(connect=3, backoff_factor=0.5) + adapter = HTTPAdapter(max_retries=retry) + session.mount("http://", adapter) + session.mount("https://", adapter) + r = session.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") + print(r.text) + vstr = [r.json()["data"]["v_str"]][0] + b64d = base64.b64decode(vstr) + + with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: + out.write(b64d) + + audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) + + chunkId = chunkId + 1 + try: + if len(audio_clips) > 1: + cbn.convert(samplerate=44100, n_channels=2) + cbn.build(audio_clips, filename, "concatenate") + else: + os.rename(audio_clips[0], filename) + except ( + sox.core.SoxError, + FileNotFoundError, + ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 + for clip in audio_clips: + i = audio_clips.index(clip) # get the index of the clip + audio_clips = ( + audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] + ) # replace the clip with an AudioFileClip + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + audio_composite.write_audiofile(filename, 44100, 2, 2000, None) + + @staticmethod + def randomvoice(): + ok_or_good = random.randrange(1, 10) + if ok_or_good == 1: # 1/10 chance of ok voice + return random.choice(voices) + return random.choice(human) # 9/10 chance of good voice diff --git a/TTS/swapper.py b/TTS/swapper.py index f4717b1..7eebd3e 100644 --- a/TTS/swapper.py +++ b/TTS/swapper.py @@ -10,12 +10,12 @@ CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS, 'polly': POLLY} class TTS: - def __new__(cls): - load_dotenv() - CHOICE = getenv("TTsChoice").casefold() - valid_keys = [key.lower() for key in CHOICE_DIR.keys()] - if CHOICE not in valid_keys: - raise ValueError( - f"{CHOICE} is not valid. Please use one of these {valid_keys} options" - ) - return CHOICE_DIR.get(CHOICE)() + def __new__(cls): + load_dotenv() + CHOICE = getenv("TTsChoice").casefold() + valid_keys = [key.lower() for key in CHOICE_DIR.keys()] + if CHOICE not in valid_keys: + raise ValueError( + f"{CHOICE} is not valid. Please use one of these {valid_keys} options" + ) + return CHOICE_DIR.get(CHOICE)() diff --git a/reddit/subreddit.py b/reddit/subreddit.py index dc0d8ae..f08cefa 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -11,85 +11,85 @@ TEXT_WHITELIST = set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234 def textify(text): - return "".join(filter(TEXT_WHITELIST.__contains__, text)) + return "".join(filter(TEXT_WHITELIST.__contains__, text)) def get_subreddit_threads(): - """ - Returns a list of threads from the AskReddit subreddit. - """ - global submission - print_substep("Logging into Reddit.") + """ + Returns a list of threads from the AskReddit subreddit. + """ + global submission + print_substep("Logging into Reddit.") - content = {} - if str(getenv("REDDIT_2FA")).casefold() == "yes": - print("\nEnter your two-factor authentication code from your authenticator app.\n") - code = input("> ") - print() - pw = getenv("REDDIT_PASSWORD") - passkey = f"{pw}:{code}" - else: - passkey = getenv("REDDIT_PASSWORD") - reddit = praw.Reddit( - client_id=getenv("REDDIT_CLIENT_ID"), - client_secret=getenv("REDDIT_CLIENT_SECRET"), - user_agent="Accessing Reddit threads", - username=getenv("REDDIT_USERNAME"), - passkey=passkey, - check_for_async=False, - ) - """ - Ask user for subreddit input - """ - print_step("Getting subreddit threads...") - if not getenv( - "SUBREDDIT" - ): # note to self. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") - subreddit = reddit.subreddit( - input("What subreddit would you like to pull from? ") - ) # if the env isnt set, ask user - else: - print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config") - subreddit = reddit.subreddit( - getenv("SUBREDDIT") - ) # Allows you to specify in .env. Done for automation purposes. + content = {} + if str(getenv("REDDIT_2FA")).casefold() == "yes": + print("\nEnter your two-factor authentication code from your authenticator app.\n") + code = input("> ") + print() + pw = getenv("REDDIT_PASSWORD") + passkey = f"{pw}:{code}" + else: + passkey = getenv("REDDIT_PASSWORD") + reddit = praw.Reddit( + client_id=getenv("REDDIT_CLIENT_ID"), + client_secret=getenv("REDDIT_CLIENT_SECRET"), + user_agent="Accessing Reddit threads", + username=getenv("REDDIT_USERNAME"), + passkey=passkey, + check_for_async=False, + ) + """ + Ask user for subreddit input + """ + print_step("Getting subreddit threads...") + if not getenv( + "SUBREDDIT" + ): # note to self. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") + subreddit = reddit.subreddit( + input("What subreddit would you like to pull from? ") + ) # if the env isnt set, ask user + else: + print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config") + subreddit = reddit.subreddit( + getenv("SUBREDDIT") + ) # Allows you to specify in .env. Done for automation purposes. - if getenv("POST_ID"): - submission = reddit.submission(id=getenv("POST_ID")) - else: - threads = subreddit.hot(limit=25) - submission = get_subreddit_undone(threads, subreddit) - submission = check_done(submission) # double checking - if submission is None: - return get_subreddit_threads() # submission already done. rerun - upvotes = submission.score - ratio = submission.upvote_ratio * 100 - num_comments = submission.num_comments + if getenv("POST_ID"): + submission = reddit.submission(id=getenv("POST_ID")) + else: + threads = subreddit.hot(limit=25) + submission = get_subreddit_undone(threads, subreddit) + submission = check_done(submission) # double checking + if submission is None: + return get_subreddit_threads() # submission already done. rerun + upvotes = submission.score + ratio = submission.upvote_ratio * 100 + num_comments = submission.num_comments - print_substep(f"Video will be: {submission.title} :thumbsup:", style="bold green") - print_substep(f"Thread has {upvotes} upvotes", style="bold blue") - print_substep(f"Thread has a upvote ratio of {ratio}%", style="bold blue") - print_substep(f"Thread has {num_comments} comments", style="bold blue") - environ["VIDEO_TITLE"] = str(textify(submission.title)) # todo use global instend of env vars - environ["VIDEO_ID"] = str(textify(submission.id)) + print_substep(f"Video will be: {submission.title} :thumbsup:", style="bold green") + print_substep(f"Thread has {upvotes} upvotes", style="bold blue") + print_substep(f"Thread has a upvote ratio of {ratio}%", style="bold blue") + print_substep(f"Thread has {num_comments} comments", style="bold blue") + environ["VIDEO_TITLE"] = str(textify(submission.title)) # todo use global instend of env vars + environ["VIDEO_ID"] = str(textify(submission.id)) - content["thread_url"] = f"https://reddit.com{submission.permalink}" - content["thread_title"] = submission.title - # content["thread_content"] = submission.content - content["comments"] = [] - for top_level_comment in submission.comments: - if isinstance(top_level_comment, MoreComments): - continue - if top_level_comment.body in ["[removed]", "[deleted]"]: - continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 - if not top_level_comment.stickied: - if len(top_level_comment.body) <= int(environ["MAX_COMMENT_LENGTH"]): - content["comments"].append( - { - "comment_body": top_level_comment.body, - "comment_url": top_level_comment.permalink, - "comment_id": top_level_comment.id, - } - ) - print_substep("Received subreddit threads Successfully.", style="bold green") - return content + content["thread_url"] = f"https://reddit.com{submission.permalink}" + content["thread_title"] = submission.title + # content["thread_content"] = submission.content + content["comments"] = [] + for top_level_comment in submission.comments: + if isinstance(top_level_comment, MoreComments): + continue + if top_level_comment.body in ["[removed]", "[deleted]"]: + continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 + if not top_level_comment.stickied: + if len(top_level_comment.body) <= int(environ["MAX_COMMENT_LENGTH"]): + content["comments"].append( + { + "comment_body": top_level_comment.body, + "comment_url": top_level_comment.permalink, + "comment_id": top_level_comment.id, + } + ) + print_substep("Received subreddit threads Successfully.", style="bold green") + return content diff --git a/setup.py b/setup.py index 640cd26..b000ce8 100755 --- a/setup.py +++ b/setup.py @@ -15,52 +15,52 @@ console = Console() def handle_input( - message: str = "", - check_type=False, - match: str = "", - err_message: str = "", - nmin=None, - nmax=None, - oob_error="", + message: str = "", + check_type=False, + match: str = "", + err_message: str = "", + nmin=None, + nmax=None, + oob_error="", ): - match = re.compile(match + "$") - while True: - user_input = input(message + "\n> ").strip() - if re.match(match, user_input) is not None: - if check_type is not False: - try: - user_input = check_type(user_input) - if nmin is not None and user_input < nmin: - console.log("[red]" + oob_error) # Input too low failstate - continue - if nmax is not None and user_input > nmax: - console.log("[red]" + oob_error) # Input too high - continue - break # Successful type conversion and number in bounds - except ValueError: - console.log("[red]" + err_message) # Type conversion failed - continue - if ( - nmin is not None and len(user_input) < nmin - ): # Check if string is long enough - console.log("[red]" + oob_error) - continue - if ( - nmax is not None and len(user_input) > nmax - ): # Check if string is not too long - console.log("[red]" + oob_error) - continue - break - console.log("[red]" + err_message) - - return user_input + match = re.compile(match + "$") + while True: + user_input = input(message + "\n> ").strip() + if re.match(match, user_input) is not None: + if check_type is not False: + try: + user_input = check_type(user_input) + if nmin is not None and user_input < nmin: + console.log("[red]" + oob_error) # Input too low failstate + continue + if nmax is not None and user_input > nmax: + console.log("[red]" + oob_error) # Input too high + continue + break # Successful type conversion and number in bounds + except ValueError: + console.log("[red]" + err_message) # Type conversion failed + continue + if ( + nmin is not None and len(user_input) < nmin + ): # Check if string is long enough + console.log("[red]" + oob_error) + continue + if ( + nmax is not None and len(user_input) > nmax + ): # Check if string is not too long + console.log("[red]" + oob_error) + continue + break + console.log("[red]" + err_message) + + return user_input if os.path.isfile(".setup-done-before"): - console.log( - "[red]Setup was already completed! Please make sure you have to run this script again. If that is such, delete the file .setup-done-before" - ) - exit() + console.log( + "[red]Setup was already completed! Please make sure you have to run this script again. If that is such, delete the file .setup-done-before" + ) + exit() # These lines ensure the user: # - knows they are in setup mode @@ -68,26 +68,26 @@ if os.path.isfile(".setup-done-before"): 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." + "### 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. if input("Are you sure you want to continue? > ").strip().casefold() != "yes": - console.print("[red]Exiting...") - exit() + console.print("[red]Exiting...") + exit() # This code is inaccessible if the prior check fails, and thus an else statement is unnecessary # 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" + "[bold red] This will overwrite your current settings. Are you sure you want to continue? [bold green]yes/no" ) if input("Are you sure you want to continue? > ").strip().casefold() != "yes": - console.print("[red]Abort mission! Exiting...") - exit() + console.print("[red]Abort mission! Exiting...") + exit() # This is once again inaccessible if the prior checks fail # Once they confirm, move on with the script. console.print("[bold green]Alright! Let's get started!") @@ -103,18 +103,18 @@ 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)") console.print( - "[green]If you don't have these, please follow the instructions in the README.md file to set them up." + "[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." + "[green]If you do have these, type yes to continue. If you dont, go ahead and grab those quickly and come back." ) print() if input("Are you sure you have the credentials? > ").strip().casefold() != "yes": - console.print("[red]I don't understand that.") - console.print("[red]Exiting...") - exit() + console.print("[red]I don't understand that.") + console.print("[red]Exiting...") + exit() console.print("[bold green]Alright! Let's get started!") @@ -123,69 +123,69 @@ console.print("[bold green]Alright! Let's get started!") console.log("Enter your credentials now.") client_id = handle_input( - "Client ID > ", - False, - "[-a-zA-Z0-9._~+/]+=*", - "That is somehow not a correct ID, try again.", - 12, - 30, - "The ID should be over 12 and under 30 characters, double check your input.", + "Client ID > ", + False, + "[-a-zA-Z0-9._~+/]+=*", + "That is somehow not a correct ID, try again.", + 12, + 30, + "The ID should be over 12 and under 30 characters, double check your input.", ) client_sec = handle_input( - "Client Secret > ", - False, - "[-a-zA-Z0-9._~+/]+=*", - "That is somehow not a correct secret, try again.", - 20, - 40, - "The secret should be over 20 and under 40 characters, double check your input.", + "Client Secret > ", + False, + "[-a-zA-Z0-9._~+/]+=*", + "That is somehow not a correct secret, try again.", + 20, + 40, + "The secret should be over 20 and under 40 characters, double check your input.", ) user = handle_input( - "Username > ", - False, - r"[_0-9a-zA-Z]+", - "That is not a valid user", - 3, - 20, - "A username HAS to be between 3 and 20 characters", + "Username > ", + False, + r"[_0-9a-zA-Z]+", + "That is not a valid user", + 3, + 20, + "A username HAS to be between 3 and 20 characters", ) passw = handle_input("Password > ", False, ".*", "", 8, None, "Password too short") twofactor = handle_input( - "2fa Enabled? (yes/no) > ", - False, - r"(yes)|(no)", - "You need to input either yes or no", + "2fa Enabled? (yes/no) > ", + False, + r"(yes)|(no)", + "You need to input either yes or no", ) opacity = handle_input( - "Opacity? (range of 0-1) > ", - float, - ".*", - "You need to input a number between 0 and 1", - 0, - 1, - "Your number is not between 0 and 1", + "Opacity? (range of 0-1) > ", + float, + ".*", + "You need to input a number between 0 and 1", + 0, + 1, + "Your number is not between 0 and 1", ) subreddit = handle_input( - "Subreddit (without r/) > ", - False, - r"[_0-9a-zA-Z]+", - "This subreddit cannot exist, make sure you typed it in correctly and removed the r/ (or /r/).", - 3, - 20, - "A subreddit name HAS to be between 3 and 20 characters", + "Subreddit (without r/) > ", + False, + r"[_0-9a-zA-Z]+", + "This subreddit cannot exist, make sure you typed it in correctly and removed the r/ (or /r/).", + 3, + 20, + "A subreddit name HAS to be between 3 and 20 characters", ) theme = handle_input( - "Theme? (light or dark) > ", - False, - r"(light)|(dark)", - "You need to input 'light' or 'dark'", + "Theme? (light or dark) > ", + False, + r"(light)|(dark)", + "You need to input 'light' or 'dark'", ) loader = Loader("Attempting to save your credentials...", "Done!").start() # you can also put a while loop here, e.g. while VideoIsBeingMade == True: ... console.log("Writing to the .env file...") with open(".env", "w") as f: - f.write( - f"""REDDIT_CLIENT_ID="{client_id}" + f.write( + f"""REDDIT_CLIENT_ID="{client_id}" REDDIT_CLIENT_SECRET="{client_sec}" REDDIT_USERNAME="{user}" REDDIT_PASSWORD="{passw}" @@ -194,12 +194,12 @@ THEME="{theme}" SUBREDDIT="{subreddit}" OPACITY={opacity} """ - ) + ) with open(".setup-done-before", "w") as f: - f.write( - "This file blocks the setup assistant from running again. Delete this file to run setup again." - ) + f.write( + "This file blocks the setup assistant from running again. Delete this file to run setup again." + ) loader.stop() diff --git a/utils/cleanup.py b/utils/cleanup.py index f3a6c61..320ce49 100644 --- a/utils/cleanup.py +++ b/utils/cleanup.py @@ -3,22 +3,22 @@ from os.path import exists def cleanup() -> int: - if exists("./assets/temp"): - count = 0 - files = [ - f for f in os.listdir(".") if f.endswith(".mp4") and "temp" in f.lower() - ] - count += len(files) - for f in files: - os.remove(f) - try: - for file in os.listdir("./assets/temp/mp4"): - count += 1 - os.remove("./assets/temp/mp4/" + file) - except FileNotFoundError: - pass - for file in os.listdir("./assets/temp/mp3"): - count += 1 - os.remove("./assets/temp/mp3/" + file) - return count - return 0 + if exists("./assets/temp"): + count = 0 + files = [ + f for f in os.listdir(".") if f.endswith(".mp4") and "temp" in f.lower() + ] + count += len(files) + for f in files: + os.remove(f) + try: + for file in os.listdir("./assets/temp/mp4"): + count += 1 + os.remove("./assets/temp/mp4/" + file) + except FileNotFoundError: + pass + for file in os.listdir("./assets/temp/mp3"): + count += 1 + os.remove("./assets/temp/mp3/" + file) + return count + return 0 diff --git a/utils/config.py b/utils/config.py index 000b615..0216141 100644 --- a/utils/config.py +++ b/utils/config.py @@ -3,36 +3,36 @@ from dotenv import dotenv_values DEFAULTS = { - "SUBREDDIT": "AskReddit", - "ALLOW_NSFW": "False", - "POST_ID": "", - "THEME": "DARK", - "REDDIT_2FA": "no", - "TIMES_TO_RUN": "", - "MAX_COMMENT_LENGTH": "500", - "OPACITY": "1", - "VOICE": "en_us_001", - "STORYMODE": "False", + "SUBREDDIT": "AskReddit", + "ALLOW_NSFW": "False", + "POST_ID": "", + "THEME": "DARK", + "REDDIT_2FA": "no", + "TIMES_TO_RUN": "", + "MAX_COMMENT_LENGTH": "500", + "OPACITY": "1", + "VOICE": "en_us_001", + "STORYMODE": "False", } class Config: - def __init__(self): - self.raw = dotenv_values("../.env") - self.load_attrs() - - def __getattr__(self, attr): # code completion for attributes fix. - return getattr(self, attr) - - def load_attrs(self): - for key, value in self.raw.items(): - self.add_attr(key, value) - - def add_attr(self, key, value): - if value is None or value == "": - setattr(self, key, DEFAULTS[key]) - else: - setattr(self, key, str(value)) + def __init__(self): + self.raw = dotenv_values("../.env") + self.load_attrs() + + def __getattr__(self, attr): # code completion for attributes fix. + return getattr(self, attr) + + def load_attrs(self): + for key, value in self.raw.items(): + self.add_attr(key, value) + + def add_attr(self, key, value): + if value is None or value == "": + setattr(self, key, DEFAULTS[key]) + else: + setattr(self, key, str(value)) config = Config() diff --git a/utils/console.py b/utils/console.py index 11ee429..1b4e3cb 100644 --- a/utils/console.py +++ b/utils/console.py @@ -9,19 +9,19 @@ console = Console() def print_markdown(text): - """Prints a rich info message. Support Markdown syntax.""" + """Prints a rich info message. Support Markdown syntax.""" - md = Padding(Markdown(text), 2) - console.print(md) + md = Padding(Markdown(text), 2) + console.print(md) def print_step(text): - """Prints a rich info message.""" + """Prints a rich info message.""" - panel = Panel(Text(text, justify="left")) - console.print(panel) + panel = Panel(Text(text, justify="left")) + console.print(panel) def print_substep(text, style=""): - """Prints a rich info message without the panelling.""" - console.print(text, style=style) + """Prints a rich info message without the panelling.""" + console.print(text, style=style) diff --git a/utils/loader.py b/utils/loader.py index f23a716..ac1c535 100644 --- a/utils/loader.py +++ b/utils/loader.py @@ -9,43 +9,43 @@ from time import sleep class Loader: - def __init__(self, desc="Loading...", end="Done!", timeout=0.1): - """ - A loader-like context manager - - Args: - desc (str, optional): The loader's description. Defaults to "Loading...". - end (str, optional): Final print. Defaults to "Done!". - timeout (float, optional): Sleep time between prints. Defaults to 0.1. - """ - self.desc = desc - self.end = end - self.timeout = timeout - - self._thread = Thread(target=self._animate, daemon=True) - self.steps = ["β’Ώ", "β£»", "β£½", "β£Ύ", "β£·", "β£―", "⣟", "β‘Ώ"] - self.done = False - - def start(self): - self._thread.start() - return self - - def _animate(self): - for c in cycle(self.steps): - if self.done: - break - print(f"\r{self.desc} {c}", flush=True, end="") - sleep(self.timeout) - - def __enter__(self): - self.start() - - def stop(self): - self.done = True - cols = get_terminal_size((80, 20)).columns - print("\r" + " " * cols, end="", flush=True) - print(f"\r{self.end}", flush=True) - - def __exit__(self, exc_type, exc_value, tb): - # handle exceptions with those variables ^ - self.stop() + def __init__(self, desc="Loading...", end="Done!", timeout=0.1): + """ + A loader-like context manager + + Args: + desc (str, optional): The loader's description. Defaults to "Loading...". + end (str, optional): Final print. Defaults to "Done!". + timeout (float, optional): Sleep time between prints. Defaults to 0.1. + """ + self.desc = desc + self.end = end + self.timeout = timeout + + self._thread = Thread(target=self._animate, daemon=True) + self.steps = ["β’Ώ", "β£»", "β£½", "β£Ύ", "β£·", "β£―", "⣟", "β‘Ώ"] + self.done = False + + def start(self): + self._thread.start() + return self + + def _animate(self): + for c in cycle(self.steps): + if self.done: + break + print(f"\r{self.desc} {c}", flush=True, end="") + sleep(self.timeout) + + def __enter__(self): + self.start() + + def stop(self): + self.done = True + cols = get_terminal_size((80, 20)).columns + print("\r" + " " * cols, end="", flush=True) + print(f"\r{self.end}", flush=True) + + def __exit__(self, exc_type, exc_value, tb): + # handle exceptions with those variables ^ + self.stop() diff --git a/utils/subreddit.py b/utils/subreddit.py index 2a1114a..15185e2 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -5,28 +5,28 @@ from utils.console import print_substep def get_subreddit_undone(submissions: List, subreddit): - """ - recursively checks if the top submission in the list was already done. - """ - with open("./video_creation/data/videos.json", "r") as done_vids_raw: - done_videos = json.load(done_vids_raw) - for submission in submissions: - if already_done(done_videos, submission): - continue - if submission.over_18: - if getenv("ALLOW_NSFW").casefold() == "false": - print_substep("NSFW Post Detected. Skipping...") - continue - return submission - print('all submissions have been done going by top submission order') - return get_subreddit_undone( - subreddit.top(time_filter="hour"), subreddit - ) # all of the videos in hot have already been done + """ + recursively checks if the top submission in the list was already done. + """ + with open("./video_creation/data/videos.json", "r") as done_vids_raw: + done_videos = json.load(done_vids_raw) + for submission in submissions: + if already_done(done_videos, submission): + continue + if submission.over_18: + if getenv("ALLOW_NSFW").casefold() == "false": + print_substep("NSFW Post Detected. Skipping...") + continue + return submission + print('all submissions have been done going by top submission order') + return get_subreddit_undone( + subreddit.top(time_filter="hour"), subreddit + ) # all of the videos in hot have already been done def already_done(done_videos: list, submission): - for video in done_videos: - if video["id"] == str(submission): - return True - return False + for video in done_videos: + if video["id"] == str(submission): + return True + return False diff --git a/utils/videos.py b/utils/videos.py index 51a2704..0c1a27f 100755 --- a/utils/videos.py +++ b/utils/videos.py @@ -5,19 +5,19 @@ from utils.console import print_step def check_done( - redditobj, + redditobj, ): # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack - """params: - reddit_object: The Reddit Object you received in askreddit.py""" - with open("./video_creation/data/videos.json", "r") as done_vids_raw: - done_videos = json.load(done_vids_raw) - for video in done_videos: - if video["id"] == str(redditobj): - if getenv("POST_ID"): - print_step( - "You already have done this video but since it was declared specifically in the .env file the program will continue" - ) - return redditobj - print_step("Getting new post as the current one has already been done") - return None - return redditobj + """params: + reddit_object: The Reddit Object you received in askreddit.py""" + with open("./video_creation/data/videos.json", "r") as done_vids_raw: + done_videos = json.load(done_vids_raw) + for video in done_videos: + if video["id"] == str(redditobj): + if getenv("POST_ID"): + print_step( + "You already have done this video but since it was declared specifically in the .env file the program will continue" + ) + return redditobj + print_step("Getting new post as the current one has already been done") + return None + return redditobj diff --git a/utils/voice.py b/utils/voice.py index 120ee60..32333dc 100644 --- a/utils/voice.py +++ b/utils/voice.py @@ -2,21 +2,21 @@ import re def sanitize_text(text): - """ - Sanitizes the text for tts. - What gets removed: - - following characters`^_~@!&;#:-%β€œβ€β€˜"%*/{}[]()\|<>?=+` - - any http or https links - """ + """ + Sanitizes the text for tts. + What gets removed: + - following characters`^_~@!&;#:-%β€œβ€β€˜"%*/{}[]()\|<>?=+` + - any http or https links + """ - # remove any urls from the text - regex_urls = r"((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*" + # remove any urls from the text + regex_urls = r"((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*" - result = re.sub(regex_urls, " ", text) + result = re.sub(regex_urls, " ", text) - # note: not removing apostrophes - regex_expr = r"\s['|’]|['|’]\s|[\^_~@!&;#:\-%β€œβ€β€˜\"%\*/{}\[\]\(\)\\|<>=+]" - result = re.sub(regex_expr, " ", result) + # note: not removing apostrophes + regex_expr = r"\s['|’]|['|’]\s|[\^_~@!&;#:\-%β€œβ€β€˜\"%\*/{}\[\]\(\)\\|<>=+]" + result = re.sub(regex_expr, " ", result) - # remove extra whitespace - return " ".join(result.split()) + # remove extra whitespace + return " ".join(result.split()) diff --git a/video_creation/background.py b/video_creation/background.py index d1a7948..146a63f 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -9,55 +9,55 @@ from utils.console import print_step, print_substep def get_start_and_end_times(video_length, length_of_clip): - random_time = randrange(180, int(length_of_clip) - int(video_length)) - return random_time, random_time + video_length + random_time = randrange(180, int(length_of_clip) - int(video_length)) + return random_time, random_time + video_length def download_background(): - """Downloads the backgrounds/s video from YouTube.""" - Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True) - background_options = [ # uri , filename , credit - ("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"), - # ( - # "https://www.youtube.com/watch?v=2X9QGY__0II", - # "rocket_league.mp4", - # "Orbital Gameplay", - # ), - ] - # note: make sure the file name doesn't include an - in it - if not len(listdir("./assets/backgrounds")) >= len( - background_options - ): # if there are any background videos not installed - print_step( - "We need to download the backgrounds videos. they are fairly large but it's only done once. 😎" - ) - print_substep("Downloading the backgrounds videos... please be patient πŸ™ ") - for uri, filename, credit in background_options: - if Path(f"assets/backgrounds/{credit}-{filename}").is_file(): - continue # adds check to see if file exists before downloading - print_substep(f"Downloading {filename} from {uri}") - YouTube(uri).streams.filter(res="1080p").first().download( - "assets/backgrounds", filename=f"{credit}-{filename}" - ) - - print_substep( - "Background videos downloaded successfully! πŸŽ‰", style="bold green" - ) + """Downloads the backgrounds/s video from YouTube.""" + Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True) + background_options = [ # uri , filename , credit + ("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"), + # ( + # "https://www.youtube.com/watch?v=2X9QGY__0II", + # "rocket_league.mp4", + # "Orbital Gameplay", + # ), + ] + # note: make sure the file name doesn't include an - in it + if not len(listdir("./assets/backgrounds")) >= len( + background_options + ): # if there are any background videos not installed + print_step( + "We need to download the backgrounds videos. they are fairly large but it's only done once. 😎" + ) + print_substep("Downloading the backgrounds videos... please be patient πŸ™ ") + for uri, filename, credit in background_options: + if Path(f"assets/backgrounds/{credit}-{filename}").is_file(): + continue # adds check to see if file exists before downloading + print_substep(f"Downloading {filename} from {uri}") + YouTube(uri).streams.filter(res="1080p").first().download( + "assets/backgrounds", filename=f"{credit}-{filename}" + ) + + print_substep( + "Background videos downloaded successfully! πŸŽ‰", style="bold green" + ) def chop_background_video(video_length): - print_step("Finding a spot in the backgrounds video to chop...βœ‚οΈ") - choice = random.choice(listdir("assets/backgrounds")) - environ["background_credit"] = choice.split("-")[0] - - background = VideoFileClip(f"assets/backgrounds/{choice}") - - start_time, end_time = get_start_and_end_times(video_length, background.duration) - ffmpeg_extract_subclip( - f"assets/backgrounds/{choice}", - start_time, - end_time, - targetname="assets/temp/background.mp4", - ) - print_substep("Background video chopped successfully!", style="bold green") - return True + print_step("Finding a spot in the backgrounds video to chop...βœ‚οΈ") + choice = random.choice(listdir("assets/backgrounds")) + environ["background_credit"] = choice.split("-")[0] + + background = VideoFileClip(f"assets/backgrounds/{choice}") + + start_time, end_time = get_start_and_end_times(video_length, background.duration) + ffmpeg_extract_subclip( + f"assets/backgrounds/{choice}", + start_time, + end_time, + targetname="assets/temp/background.mp4", + ) + print_substep("Background video chopped successfully!", style="bold green") + return True diff --git a/video_creation/cookies.json b/video_creation/cookies.json index 2e4e116..829ad0e 100644 --- a/video_creation/cookies.json +++ b/video_creation/cookies.json @@ -1,8 +1,8 @@ [ { - "name": "USER", - "value": "eyJwcmVmcyI6eyJ0b3BDb250ZW50RGlzbWlzc2FsVGltZSI6MCwiZ2xvYmFsVGhlbWUiOiJSRURESVQiLCJuaWdodG1vZGUiOnRydWUsImNvbGxhcHNlZFRyYXlTZWN0aW9ucyI6eyJmYXZvcml0ZXMiOmZhbHNlLCJtdWx0aXMiOmZhbHNlLCJtb2RlcmF0aW5nIjpmYWxzZSwic3Vic2NyaXB0aW9ucyI6ZmFsc2UsInByb2ZpbGVzIjpmYWxzZX0sInRvcENvbnRlbnRUaW1lc0Rpc21pc3NlZCI6MH19", - "domain": ".reddit.com", - "path": "/" + "name": "USER", + "value": "eyJwcmVmcyI6eyJ0b3BDb250ZW50RGlzbWlzc2FsVGltZSI6MCwiZ2xvYmFsVGhlbWUiOiJSRURESVQiLCJuaWdodG1vZGUiOnRydWUsImNvbGxhcHNlZFRyYXlTZWN0aW9ucyI6eyJmYXZvcml0ZXMiOmZhbHNlLCJtdWx0aXMiOmZhbHNlLCJtb2RlcmF0aW5nIjpmYWxzZSwic3Vic2NyaXB0aW9ucyI6ZmFsc2UsInByb2ZpbGVzIjpmYWxzZX0sInRvcENvbnRlbnRUaW1lc0Rpc21pc3NlZCI6MH19", + "domain": ".reddit.com", + "path": "/" } ] diff --git a/video_creation/data/cookie-dark-mode.json b/video_creation/data/cookie-dark-mode.json index 1ed51a9..774f4cc 100644 --- a/video_creation/data/cookie-dark-mode.json +++ b/video_creation/data/cookie-dark-mode.json @@ -1,14 +1,14 @@ [ { - "name": "USER", - "value": "eyJwcmVmcyI6eyJ0b3BDb250ZW50RGlzbWlzc2FsVGltZSI6MCwiZ2xvYmFsVGhlbWUiOiJSRURESVQiLCJuaWdodG1vZGUiOnRydWUsImNvbGxhcHNlZFRyYXlTZWN0aW9ucyI6eyJmYXZvcml0ZXMiOmZhbHNlLCJtdWx0aXMiOmZhbHNlLCJtb2RlcmF0aW5nIjpmYWxzZSwic3Vic2NyaXB0aW9ucyI6ZmFsc2UsInByb2ZpbGVzIjpmYWxzZX0sInRvcENvbnRlbnRUaW1lc0Rpc21pc3NlZCI6MH19", - "domain": ".reddit.com", - "path": "/" + "name": "USER", + "value": "eyJwcmVmcyI6eyJ0b3BDb250ZW50RGlzbWlzc2FsVGltZSI6MCwiZ2xvYmFsVGhlbWUiOiJSRURESVQiLCJuaWdodG1vZGUiOnRydWUsImNvbGxhcHNlZFRyYXlTZWN0aW9ucyI6eyJmYXZvcml0ZXMiOmZhbHNlLCJtdWx0aXMiOmZhbHNlLCJtb2RlcmF0aW5nIjpmYWxzZSwic3Vic2NyaXB0aW9ucyI6ZmFsc2UsInByb2ZpbGVzIjpmYWxzZX0sInRvcENvbnRlbnRUaW1lc0Rpc21pc3NlZCI6MH19", + "domain": ".reddit.com", + "path": "/" }, { - "name": "eu_cookie", - "value": "{%22opted%22:true%2C%22nonessential%22:false}", - "domain": ".reddit.com", - "path": "/" + "name": "eu_cookie", + "value": "{%22opted%22:true%2C%22nonessential%22:false}", + "domain": ".reddit.com", + "path": "/" } ] diff --git a/video_creation/data/cookie-light-mode.json b/video_creation/data/cookie-light-mode.json index 87eeec9..048a3e3 100644 --- a/video_creation/data/cookie-light-mode.json +++ b/video_creation/data/cookie-light-mode.json @@ -1,8 +1,8 @@ [ { - "name": "eu_cookie", - "value": "{%22opted%22:true%2C%22nonessential%22:false}", - "domain": ".reddit.com", - "path": "/" + "name": "eu_cookie", + "value": "{%22opted%22:true%2C%22nonessential%22:false}", + "domain": ".reddit.com", + "path": "/" } ] diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 60a1f6a..1eef620 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -5,13 +5,13 @@ import time from os.path import exists from moviepy.editor import ( - VideoFileClip, - AudioFileClip, - ImageClip, - concatenate_videoclips, - concatenate_audioclips, - CompositeAudioClip, - CompositeVideoClip, + VideoFileClip, + AudioFileClip, + ImageClip, + concatenate_videoclips, + concatenate_audioclips, + CompositeAudioClip, + CompositeVideoClip, ) from moviepy.video.io import ffmpeg_tools from rich.console import Console @@ -26,125 +26,125 @@ W, H = 1080, 1920 def make_final_video(number_of_clips, length): - print_step("Creating the final video πŸŽ₯") - VideoFileClip.reW = lambda clip: clip.resize(width=W) - VideoFileClip.reH = lambda clip: clip.resize(width=H) - opacity = os.getenv("OPACITY") - background_clip = ( - VideoFileClip("assets/temp/background.mp4") - .without_audio() - .resize(height=H) - .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/temp/mp3/{i}.mp3")) - audio_clips.insert(0, AudioFileClip("assets/temp/mp3/title.mp3")) - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) - - # Get sum of all clip lengths - total_length = sum([clip.duration for clip in audio_clips]) - # round total_length to an integer - int_total_length = round(total_length) - # Output Length - console.log(f"[bold green] Video Will Be: {int_total_length} Seconds Long") - - # add title to video - image_clips = [] - # Gather all images - if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE - image_clips.insert( - 0, - ImageClip("assets/temp/png/title.png") - .set_duration(audio_clips[0].duration) - .set_position("center") - .resize(width=W - 100) - .set_opacity(float(opacity)), - ) - else: - image_clips.insert( - 0, - ImageClip("assets/temp/png/title.png") - .set_duration(audio_clips[0].duration) - .set_position("center") - .resize(width=W - 100), - ) - - for i in range(0, number_of_clips): - if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE - image_clips.append( - ImageClip(f"assets/temp/png/comment_{i}.png") - .set_duration(audio_clips[i + 1].duration) - .set_position("center") - .resize(width=W - 100), - ) - else: - image_clips.append( - ImageClip(f"assets/temp/png/comment_{i}.png") - .set_duration(audio_clips[i + 1].duration) - .set_position("center") - .resize(width=W - 100) - .set_opacity(float(opacity)), - ) - - # if os.path.exists("assets/mp3/posttext.mp3"): - # image_clips.insert( - # 0, - # ImageClip("assets/png/title.png") - # .set_duration(audio_clips[0].duration + audio_clips[1].duration) - # .set_position("center") - # .resize(width=W - 100) - # .set_opacity(float(opacity)), - # ) - # else: - image_concat = concatenate_videoclips(image_clips).set_position(("center", "center")) - image_concat.audio = audio_composite - final = CompositeVideoClip([background_clip, image_concat]) - - def get_video_title() -> str: - title = os.getenv("VIDEO_TITLE") or "final_video" - if len(title) <= 35: - return title - else: - return title[0:30] + "..." - - filename = f"{get_video_title()}.mp4" - - def save_data(): - with open("./video_creation/data/videos.json", "r+") as raw_vids: - done_vids = json.load(raw_vids) - if str(subreddit.submission.id) in [video["id"] for video in done_vids]: - return # video already done but was specified to continue anyway in the .env file - payload = { - "id": str(os.getenv("VIDEO_ID")), - "time": str(int(time.time())), - "background_credit": str(os.getenv("background_credit")), - "reddit_title": str(os.getenv("VIDEO_TITLE")), - "filename": filename, - } - done_vids.append(payload) - raw_vids.seek(0) - json.dump(done_vids, raw_vids, ensure_ascii=False, indent=4) - - save_data() - if not exists("./results"): - print_substep("the results folder didn't exist so I made it") - os.mkdir("./results") - - final.write_videofile("assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k") - ffmpeg_tools.ffmpeg_extract_subclip( - "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" - ) - # os.remove("assets/temp/temp.mp4") - - print_step("Removing temporary files πŸ—‘") - cleanups = cleanup() - print_substep(f"Removed {cleanups} temporary files πŸ—‘") - print_substep("See result in the results folder!") - - print_step( - f"Reddit title: {os.getenv('VIDEO_TITLE')} \n Background Credit: {os.getenv('background_credit')}" - ) + print_step("Creating the final video πŸŽ₯") + VideoFileClip.reW = lambda clip: clip.resize(width=W) + VideoFileClip.reH = lambda clip: clip.resize(width=H) + opacity = os.getenv("OPACITY") + background_clip = ( + VideoFileClip("assets/temp/background.mp4") + .without_audio() + .resize(height=H) + .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/temp/mp3/{i}.mp3")) + audio_clips.insert(0, AudioFileClip("assets/temp/mp3/title.mp3")) + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + + # Get sum of all clip lengths + total_length = sum([clip.duration for clip in audio_clips]) + # round total_length to an integer + int_total_length = round(total_length) + # Output Length + console.log(f"[bold green] Video Will Be: {int_total_length} Seconds Long") + + # add title to video + image_clips = [] + # Gather all images + if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE + image_clips.insert( + 0, + ImageClip("assets/temp/png/title.png") + .set_duration(audio_clips[0].duration) + .set_position("center") + .resize(width=W - 100) + .set_opacity(float(opacity)), + ) + else: + image_clips.insert( + 0, + ImageClip("assets/temp/png/title.png") + .set_duration(audio_clips[0].duration) + .set_position("center") + .resize(width=W - 100), + ) + + for i in range(0, number_of_clips): + if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE + image_clips.append( + ImageClip(f"assets/temp/png/comment_{i}.png") + .set_duration(audio_clips[i + 1].duration) + .set_position("center") + .resize(width=W - 100), + ) + else: + image_clips.append( + ImageClip(f"assets/temp/png/comment_{i}.png") + .set_duration(audio_clips[i + 1].duration) + .set_position("center") + .resize(width=W - 100) + .set_opacity(float(opacity)), + ) + + # if os.path.exists("assets/mp3/posttext.mp3"): + # image_clips.insert( + # 0, + # ImageClip("assets/png/title.png") + # .set_duration(audio_clips[0].duration + audio_clips[1].duration) + # .set_position("center") + # .resize(width=W - 100) + # .set_opacity(float(opacity)), + # ) + # else: + image_concat = concatenate_videoclips(image_clips).set_position(("center", "center")) + image_concat.audio = audio_composite + final = CompositeVideoClip([background_clip, image_concat]) + + def get_video_title() -> str: + title = os.getenv("VIDEO_TITLE") or "final_video" + if len(title) <= 35: + return title + else: + return title[0:30] + "..." + + filename = f"{get_video_title()}.mp4" + + def save_data(): + with open("./video_creation/data/videos.json", "r+") as raw_vids: + done_vids = json.load(raw_vids) + if str(subreddit.submission.id) in [video["id"] for video in done_vids]: + return # video already done but was specified to continue anyway in the .env file + payload = { + "id": str(os.getenv("VIDEO_ID")), + "time": str(int(time.time())), + "background_credit": str(os.getenv("background_credit")), + "reddit_title": str(os.getenv("VIDEO_TITLE")), + "filename": filename, + } + done_vids.append(payload) + raw_vids.seek(0) + json.dump(done_vids, raw_vids, ensure_ascii=False, indent=4) + + save_data() + if not exists("./results"): + print_substep("the results folder didn't exist so I made it") + os.mkdir("./results") + + final.write_videofile("assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k") + ffmpeg_tools.ffmpeg_extract_subclip( + "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" + ) + # os.remove("assets/temp/temp.mp4") + + print_step("Removing temporary files πŸ—‘") + cleanups = cleanup() + print_substep(f"Removed {cleanups} temporary files πŸ—‘") + print_substep("See result in the results folder!") + + print_step( + f"Reddit title: {os.getenv('VIDEO_TITLE')} \n Background Credit: {os.getenv('background_credit')}" + ) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 4095f72..25241e1 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -16,62 +16,62 @@ storymode = False def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): - """Downloads screenshots of reddit posts as they are seen on the web. - Args: - reddit_object: The Reddit Object you received in askreddit.py - screenshot_num: The number of screenshots you want to download. - """ - print_step("Downloading screenshots of reddit posts...") + """Downloads screenshots of reddit posts as they are seen on the web. + Args: + reddit_object: The Reddit Object you received in askreddit.py + screenshot_num: The number of screenshots you want to download. + """ + print_step("Downloading screenshots of reddit posts...") - # ! Make sure the reddit screenshots folder exists - Path("assets/temp/png").mkdir(parents=True, exist_ok=True) + # ! Make sure the reddit screenshots folder exists + Path("assets/temp/png").mkdir(parents=True, exist_ok=True) - with sync_playwright() as p: - print_substep("Launching Headless Browser...") + with sync_playwright() as p: + print_substep("Launching Headless Browser...") - browser = p.chromium.launch() - context = browser.new_context() + browser = p.chromium.launch() + context = browser.new_context() - if getenv("THEME").upper() == "DARK": - cookie_file = open("./video_creation/data/cookie-dark-mode.json") - else: - cookie_file = open("./video_creation/data/cookie-light-mode.json") - cookies = json.load(cookie_file) - context.add_cookies(cookies) # load preference cookies - # Get the thread screenshot - page = context.new_page() - page.goto(reddit_object["thread_url"]) - page.set_viewport_size(ViewportSize(width=1920, height=1080)) - if page.locator('[data-testid="content-gate"]').is_visible(): - # This means the post is NSFW and requires to click the proceed button. + if getenv("THEME").upper() == "DARK": + cookie_file = open("./video_creation/data/cookie-dark-mode.json") + else: + cookie_file = open("./video_creation/data/cookie-light-mode.json") + cookies = json.load(cookie_file) + context.add_cookies(cookies) # load preference cookies + # Get the thread screenshot + page = context.new_page() + page.goto(reddit_object["thread_url"]) + page.set_viewport_size(ViewportSize(width=1920, height=1080)) + if page.locator('[data-testid="content-gate"]').is_visible(): + # This means the post is NSFW and requires to click the proceed button. - print_substep("Post is NSFW. You are spicy...") - page.locator('[data-testid="content-gate"] button').click() - page.locator( - '[data-click-id="text"] button' - ).click() # Remove "Click to see nsfw" Button in Screenshot + print_substep("Post is NSFW. You are spicy...") + page.locator('[data-testid="content-gate"] button').click() + page.locator( + '[data-click-id="text"] button' + ).click() # Remove "Click to see nsfw" Button in Screenshot - page.locator('[data-test-id="post-content"]').screenshot( - path="assets/temp/png/title.png" - ) - if storymode: - page.locator('[data-click-id="text"]').screenshot( - path="assets/temp/png/story_content.png" - ) - else: - for idx, comment in track( - enumerate(reddit_object["comments"]), "Downloading screenshots..." - ): + page.locator('[data-test-id="post-content"]').screenshot( + path="assets/temp/png/title.png" + ) + if storymode: + page.locator('[data-click-id="text"]').screenshot( + path="assets/temp/png/story_content.png" + ) + else: + for idx, comment in track( + enumerate(reddit_object["comments"]), "Downloading screenshots..." + ): - # Stop if we have reached the screenshot_num - if idx >= screenshot_num: - break + # Stop if we have reached the screenshot_num + if idx >= screenshot_num: + break - if page.locator('[data-testid="content-gate"]').is_visible(): - page.locator('[data-testid="content-gate"] button').click() + if page.locator('[data-testid="content-gate"]').is_visible(): + page.locator('[data-testid="content-gate"] button').click() - page.goto(f'https://reddit.com{comment["comment_url"]}') - page.locator(f"#t1_{comment['comment_id']}").screenshot( - path=f"assets/temp/png/comment_{idx}.png" - ) - print_substep("Screenshots downloaded Successfully.", style="bold green") + page.goto(f'https://reddit.com{comment["comment_url"]}') + page.locator(f"#t1_{comment['comment_id']}").screenshot( + path=f"assets/temp/png/comment_{idx}.png" + ) + print_substep("Screenshots downloaded Successfully.", style="bold green") diff --git a/video_creation/voices.py b/video_creation/voices.py index b345079..960b5fa 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -20,60 +20,60 @@ VIDEO_LENGTH: int = 40 # secs def save_text_to_mp3(reddit_obj): - """Saves Text to MP3 files. - Args: - reddit_obj : The reddit object you received from the reddit API in the askreddit.py file. - """ - print_step("Saving Text to MP3 files...") - length = 0 + """Saves Text to MP3 files. + Args: + reddit_obj : The reddit object you received from the reddit API in the askreddit.py file. + """ + print_step("Saving Text to MP3 files...") + length = 0 - # Create a folder for the mp3 files. - Path("assets/temp/mp3").mkdir(parents=True, exist_ok=True) - TextToSpeech = TTS() - TextToSpeech.tts( - sanitize_text(reddit_obj["thread_title"]), - filename="assets/temp/mp3/title.mp3", - random_speaker=False, - ) - try: - length += MP3("assets/temp/mp3/title.mp3").info.length - except HeaderNotFoundError: # note to self AudioFileClip - length += sox.file_info.duration("assets/temp/mp3/title.mp3") - if getenv("STORYMODE").casefold() == "true": - TextToSpeech.tts( - sanitize_text(reddit_obj["thread_content"]), - filename="assets/temp/mp3/story_content.mp3", - random_speaker=False, - ) - # 'story_content' - com = 0 - for comment in track((reddit_obj["comments"]), "Saving..."): - # ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer - # but this is just a good_voices starting point - if length > VIDEO_LENGTH: - break + # Create a folder for the mp3 files. + Path("assets/temp/mp3").mkdir(parents=True, exist_ok=True) + TextToSpeech = TTS() + TextToSpeech.tts( + sanitize_text(reddit_obj["thread_title"]), + filename="assets/temp/mp3/title.mp3", + random_speaker=False, + ) + try: + length += MP3("assets/temp/mp3/title.mp3").info.length + except HeaderNotFoundError: # note to self AudioFileClip + length += sox.file_info.duration("assets/temp/mp3/title.mp3") + if getenv("STORYMODE").casefold() == "true": + TextToSpeech.tts( + sanitize_text(reddit_obj["thread_content"]), + filename="assets/temp/mp3/story_content.mp3", + random_speaker=False, + ) + # 'story_content' + com = 0 + for comment in track((reddit_obj["comments"]), "Saving..."): + # ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer + # but this is just a good_voices starting point + if length > VIDEO_LENGTH: + break - TextToSpeech.tts( - sanitize_text(comment["comment_body"]), - filename=f"assets/temp/mp3/{com}.mp3", - random_speaker=False, - ) - try: - length += MP3(f"assets/temp/mp3/{com}.mp3").info.length - com += 1 - except (HeaderNotFoundError, MutagenError, Exception): - try: - length += sox.file_info.duration(f"assets/temp/mp3/{com}.mp3") - com += 1 - except (OSError, IOError): - print( - "would have removed" - f"assets/temp/mp3/{com}.mp3" - f"assets/temp/png/comment_{com}.png" - ) - # remove(f"assets/temp/mp3/{com}.mp3") - # remove(f"assets/temp/png/comment_{com}.png")# todo might cause odd un-syncing + TextToSpeech.tts( + sanitize_text(comment["comment_body"]), + filename=f"assets/temp/mp3/{com}.mp3", + random_speaker=False, + ) + try: + length += MP3(f"assets/temp/mp3/{com}.mp3").info.length + com += 1 + except (HeaderNotFoundError, MutagenError, Exception): + try: + length += sox.file_info.duration(f"assets/temp/mp3/{com}.mp3") + com += 1 + except (OSError, IOError): + print( + "would have removed" + f"assets/temp/mp3/{com}.mp3" + f"assets/temp/png/comment_{com}.png" + ) + # remove(f"assets/temp/mp3/{com}.mp3") + # remove(f"assets/temp/png/comment_{com}.png")# todo might cause odd un-syncing - print_substep("Saved Text to MP3 files Successfully.", style="bold green") - # ! Return the index, so we know how many screenshots of comments we need to make. - return length, com + print_substep("Saved Text to MP3 files Successfully.", style="bold green") + # ! Return the index, so we know how many screenshots of comments we need to make. + return length, com From c4274d1d149f33df63ce192d13333b2e64f9c4f6 Mon Sep 17 00:00:00 2001 From: owengaspard Date: Sat, 18 Jun 2022 15:57:54 -0500 Subject: [PATCH 36/49] Add OS check, PS script (unfinished) Added a check for your operating system and a PowerShell script which is unfinished at the moment. --- utils/envUpdate.py | 24 ++++++++++++++++++------ utils/envUpdateWin.ps1 | 2 ++ 2 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 utils/envUpdateWin.ps1 diff --git a/utils/envUpdate.py b/utils/envUpdate.py index a95b48f..0b53ab3 100644 --- a/utils/envUpdate.py +++ b/utils/envUpdate.py @@ -1,18 +1,30 @@ import os import subprocess import tempfile -from os import path import logging -log = logging.getLogger(__name__) +from os import path +from sys import platform +log = logging.getLogger(__name__) def envUpdate(): if path.exists(".env.template"): - envTemplate = subprocess.check_output( - "awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", # noqa - shell=True, - ) + if platform == "win32" or platform == "cygwin": + #envTemplate = subprocess.run( + # [r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe', + # r'.\envUpdateWin.ps1'], + # stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + # shell=True + #) + print("Updating is not yet supported on Windows.") + return + elif platform == "darwin" or platform == "linux": + envTemplate = subprocess.check_output( + "awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", + shell=True, + ) + return tempEnv = tempfile.TemporaryFile() tempEnv.write(envTemplate) tempEnv.seek(0) diff --git a/utils/envUpdateWin.ps1 b/utils/envUpdateWin.ps1 new file mode 100644 index 0000000..a045a4b --- /dev/null +++ b/utils/envUpdateWin.ps1 @@ -0,0 +1,2 @@ +$envVars = Get-Content '.\.env' +$envVars -split "=" \ No newline at end of file From 928e50e08fe5c3e98c9a00bfea68e8835e194b04 Mon Sep 17 00:00:00 2001 From: owengaspard Date: Sat, 18 Jun 2022 16:05:18 -0500 Subject: [PATCH 37/49] Added PowerShell Script --- utils/envUpdate.py | 14 +++++++------- utils/envUpdateWin.ps1 | 5 +++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/utils/envUpdate.py b/utils/envUpdate.py index 0b53ab3..05b08da 100644 --- a/utils/envUpdate.py +++ b/utils/envUpdate.py @@ -11,13 +11,13 @@ log = logging.getLogger(__name__) def envUpdate(): if path.exists(".env.template"): if platform == "win32" or platform == "cygwin": - #envTemplate = subprocess.run( - # [r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe', - # r'.\envUpdateWin.ps1'], - # stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - # shell=True - #) - print("Updating is not yet supported on Windows.") + envTemplate = subprocess.run( + [r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe', + r'.\envUpdateWin.ps1'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + shell=True + print("Updating .env is unsupported on Windows.") + ) return elif platform == "darwin" or platform == "linux": envTemplate = subprocess.check_output( diff --git a/utils/envUpdateWin.ps1 b/utils/envUpdateWin.ps1 index a045a4b..79c8a04 100644 --- a/utils/envUpdateWin.ps1 +++ b/utils/envUpdateWin.ps1 @@ -1,2 +1,3 @@ -$envVars = Get-Content '.\.env' -$envVars -split "=" \ No newline at end of file +#$envVars = Get-Content '.\.env' +#$envVars -split "=" +Write-Host 'Updating the env is unsupported on Windows.' \ No newline at end of file From ddc402c48d79bbe26cbf93e76f9e3eb401581175 Mon Sep 17 00:00:00 2001 From: owengaspard Date: Sat, 18 Jun 2022 17:16:07 -0500 Subject: [PATCH 38/49] Added Windows support I have added Windows support using PowerShell. I made a script that does effectively what the GNU awk and grep commands do but in PowerShell for Windows compatibility. --- .env.template | 2 +- .gitignore | 3 ++- utils/envUpdate.py | 19 +++++++++---------- utils/envUpdateWin.ps1 | 12 +++++++++--- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.env.template b/.env.template index 173bacf..c94fa6a 100644 --- a/.env.template +++ b/.env.template @@ -36,4 +36,4 @@ VOICE="Matthew" # e.g. en_us_002 TTsChoice="polly" # todo add docs # IN-PROGRESS - not yet implemented -STORYMODE="False" +STORYMODE="False" \ No newline at end of file diff --git a/.gitignore b/.gitignore index d365d9e..1c28a20 100644 --- a/.gitignore +++ b/.gitignore @@ -170,4 +170,5 @@ reddit-bot-351418-5560ebc49cac.json /.idea *.pyc /video_creation/data/videos.json -utils/envUpdate.py.old \ No newline at end of file +utils/envUpdate.py.old +envVars.txt \ No newline at end of file diff --git a/utils/envUpdate.py b/utils/envUpdate.py index 05b08da..f225419 100644 --- a/utils/envUpdate.py +++ b/utils/envUpdate.py @@ -4,21 +4,16 @@ import tempfile import logging from os import path -from sys import platform +from sys import platform, stderr log = logging.getLogger(__name__) def envUpdate(): if path.exists(".env.template"): if platform == "win32" or platform == "cygwin": - envTemplate = subprocess.run( - [r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe', - r'.\envUpdateWin.ps1'], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - shell=True - print("Updating .env is unsupported on Windows.") - ) - return + runPS('utils\envUpdateWin.ps1') + f = open("envVars.txt", "rb") + envTemplate = f.read() elif platform == "darwin" or platform == "linux": envTemplate = subprocess.check_output( "awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", @@ -46,4 +41,8 @@ def envUpdate(): log.error( f"[ERROR] The following environment variables are missing: {missing}.)" ) - exit(-1) \ No newline at end of file + exit(-1) + +def runPS(cmd): + completed = subprocess.run(["powershell", "-Command", cmd], capture_output=True) + return completed \ No newline at end of file diff --git a/utils/envUpdateWin.ps1 b/utils/envUpdateWin.ps1 index 79c8a04..1eb8f64 100644 --- a/utils/envUpdateWin.ps1 +++ b/utils/envUpdateWin.ps1 @@ -1,3 +1,9 @@ -#$envVars = Get-Content '.\.env' -#$envVars -split "=" -Write-Host 'Updating the env is unsupported on Windows.' \ No newline at end of file +$envFile = Get-Content ".\.env.template" + +$envFile -split "=" | Where-Object {$_ -notmatch '\"'} | Set-Content ".\envVarsbefSpl.txt" +Get-Content ".\envVarsbefSpl.txt" | Where-Object {$_ -notmatch '\#'} | Set-Content ".\envVarsN.txt" +Get-Content ".\envVarsN.txt" | Where-Object {$_ -ne ''} | Set-Content ".\envVars.txt" +Remove-Item ".\envVarsbefSpl.txt" +Remove-Item ".\envVarsN.txt" + +Write-Host $nowSplit \ No newline at end of file From e6501f43376b133e82ad2c2e6a46d126201c14a9 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 18:28:25 -0400 Subject: [PATCH 39/49] fixed title opacity error --- video_creation/final_video.py | 258 +++++++++++++++++----------------- 1 file changed, 129 insertions(+), 129 deletions(-) diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 1eef620..37b1ac2 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -5,13 +5,13 @@ import time from os.path import exists from moviepy.editor import ( - VideoFileClip, - AudioFileClip, - ImageClip, - concatenate_videoclips, - concatenate_audioclips, - CompositeAudioClip, - CompositeVideoClip, + VideoFileClip, + AudioFileClip, + ImageClip, + concatenate_videoclips, + concatenate_audioclips, + CompositeAudioClip, + CompositeVideoClip, ) from moviepy.video.io import ffmpeg_tools from rich.console import Console @@ -26,125 +26,125 @@ W, H = 1080, 1920 def make_final_video(number_of_clips, length): - print_step("Creating the final video πŸŽ₯") - VideoFileClip.reW = lambda clip: clip.resize(width=W) - VideoFileClip.reH = lambda clip: clip.resize(width=H) - opacity = os.getenv("OPACITY") - background_clip = ( - VideoFileClip("assets/temp/background.mp4") - .without_audio() - .resize(height=H) - .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/temp/mp3/{i}.mp3")) - audio_clips.insert(0, AudioFileClip("assets/temp/mp3/title.mp3")) - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) - - # Get sum of all clip lengths - total_length = sum([clip.duration for clip in audio_clips]) - # round total_length to an integer - int_total_length = round(total_length) - # Output Length - console.log(f"[bold green] Video Will Be: {int_total_length} Seconds Long") - - # add title to video - image_clips = [] - # Gather all images - if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE - image_clips.insert( - 0, - ImageClip("assets/temp/png/title.png") - .set_duration(audio_clips[0].duration) - .set_position("center") - .resize(width=W - 100) - .set_opacity(float(opacity)), - ) - else: - image_clips.insert( - 0, - ImageClip("assets/temp/png/title.png") - .set_duration(audio_clips[0].duration) - .set_position("center") - .resize(width=W - 100), - ) - - for i in range(0, number_of_clips): - if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE - image_clips.append( - ImageClip(f"assets/temp/png/comment_{i}.png") - .set_duration(audio_clips[i + 1].duration) - .set_position("center") - .resize(width=W - 100), - ) - else: - image_clips.append( - ImageClip(f"assets/temp/png/comment_{i}.png") - .set_duration(audio_clips[i + 1].duration) - .set_position("center") - .resize(width=W - 100) - .set_opacity(float(opacity)), - ) - - # if os.path.exists("assets/mp3/posttext.mp3"): - # image_clips.insert( - # 0, - # ImageClip("assets/png/title.png") - # .set_duration(audio_clips[0].duration + audio_clips[1].duration) - # .set_position("center") - # .resize(width=W - 100) - # .set_opacity(float(opacity)), - # ) - # else: - image_concat = concatenate_videoclips(image_clips).set_position(("center", "center")) - image_concat.audio = audio_composite - final = CompositeVideoClip([background_clip, image_concat]) - - def get_video_title() -> str: - title = os.getenv("VIDEO_TITLE") or "final_video" - if len(title) <= 35: - return title - else: - return title[0:30] + "..." - - filename = f"{get_video_title()}.mp4" - - def save_data(): - with open("./video_creation/data/videos.json", "r+") as raw_vids: - done_vids = json.load(raw_vids) - if str(subreddit.submission.id) in [video["id"] for video in done_vids]: - return # video already done but was specified to continue anyway in the .env file - payload = { - "id": str(os.getenv("VIDEO_ID")), - "time": str(int(time.time())), - "background_credit": str(os.getenv("background_credit")), - "reddit_title": str(os.getenv("VIDEO_TITLE")), - "filename": filename, - } - done_vids.append(payload) - raw_vids.seek(0) - json.dump(done_vids, raw_vids, ensure_ascii=False, indent=4) - - save_data() - if not exists("./results"): - print_substep("the results folder didn't exist so I made it") - os.mkdir("./results") - - final.write_videofile("assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k") - ffmpeg_tools.ffmpeg_extract_subclip( - "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" - ) - # os.remove("assets/temp/temp.mp4") - - print_step("Removing temporary files πŸ—‘") - cleanups = cleanup() - print_substep(f"Removed {cleanups} temporary files πŸ—‘") - print_substep("See result in the results folder!") - - print_step( - f"Reddit title: {os.getenv('VIDEO_TITLE')} \n Background Credit: {os.getenv('background_credit')}" - ) + print_step("Creating the final video πŸŽ₯") + VideoFileClip.reW = lambda clip: clip.resize(width=W) + VideoFileClip.reH = lambda clip: clip.resize(width=H) + opacity = os.getenv("OPACITY") + background_clip = ( + VideoFileClip("assets/temp/background.mp4") + .without_audio() + .resize(height=H) + .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/temp/mp3/{i}.mp3")) + audio_clips.insert(0, AudioFileClip("assets/temp/mp3/title.mp3")) + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + + # Get sum of all clip lengths + total_length = sum([clip.duration for clip in audio_clips]) + # round total_length to an integer + int_total_length = round(total_length) + # Output Length + console.log(f"[bold green] Video Will Be: {int_total_length} Seconds Long") + + # add title to video + image_clips = [] + # Gather all images + if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE + image_clips.insert( + 0, + ImageClip("assets/temp/png/title.png") + .set_duration(audio_clips[0].duration) + .set_position("center") + .resize(width=W - 100), + ) + else: + image_clips.insert( + 0, + ImageClip("assets/temp/png/title.png") + .set_duration(audio_clips[0].duration) + .set_position("center") + .resize(width=W - 100) + .set_opacity(float(opacity)), + ) + + for i in range(0, number_of_clips): + if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE + image_clips.append( + ImageClip(f"assets/temp/png/comment_{i}.png") + .set_duration(audio_clips[i + 1].duration) + .set_position("center") + .resize(width=W - 100), + ) + else: + image_clips.append( + ImageClip(f"assets/temp/png/comment_{i}.png") + .set_duration(audio_clips[i + 1].duration) + .set_position("center") + .resize(width=W - 100) + .set_opacity(float(opacity)), + ) + + # if os.path.exists("assets/mp3/posttext.mp3"): + # image_clips.insert( + # 0, + # ImageClip("assets/png/title.png") + # .set_duration(audio_clips[0].duration + audio_clips[1].duration) + # .set_position("center") + # .resize(width=W - 100) + # .set_opacity(float(opacity)), + # ) + # else: + image_concat = concatenate_videoclips(image_clips).set_position(("center", "center")) + image_concat.audio = audio_composite + final = CompositeVideoClip([background_clip, image_concat]) + + def get_video_title() -> str: + title = os.getenv("VIDEO_TITLE") or "final_video" + if len(title) <= 35: + return title + else: + return title[0:30] + "..." + + filename = f"{get_video_title()}.mp4" + + def save_data(): + with open("./video_creation/data/videos.json", "r+") as raw_vids: + done_vids = json.load(raw_vids) + if str(subreddit.submission.id) in [video["id"] for video in done_vids]: + return # video already done but was specified to continue anyway in the .env file + payload = { + "id": str(os.getenv("VIDEO_ID")), + "time": str(int(time.time())), + "background_credit": str(os.getenv("background_credit")), + "reddit_title": str(os.getenv("VIDEO_TITLE")), + "filename": filename, + } + done_vids.append(payload) + raw_vids.seek(0) + json.dump(done_vids, raw_vids, ensure_ascii=False, indent=4) + + save_data() + if not exists("./results"): + print_substep("the results folder didn't exist so I made it") + os.mkdir("./results") + + final.write_videofile("assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k") + ffmpeg_tools.ffmpeg_extract_subclip( + "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" + ) + # os.remove("assets/temp/temp.mp4") + + print_step("Removing temporary files πŸ—‘") + cleanups = cleanup() + print_substep(f"Removed {cleanups} temporary files πŸ—‘") + print_substep("See result in the results folder!") + + print_step( + f"Reddit title: {os.getenv('VIDEO_TITLE')} \n Background Credit: {os.getenv('background_credit')}" + ) From c482266e5cb6edc695234f3130eae926a261e872 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 18:28:48 -0400 Subject: [PATCH 40/49] reformatted using black command "black . --line-length 101" --- TTS/GTTS.py | 18 +- TTS/POLLY.py | 164 +++++++++--------- TTS/TikTok.py | 218 ++++++++++++------------ TTS/swapper.py | 18 +- reddit/subreddit.py | 146 ++++++++-------- setup.py | 200 +++++++++++----------- utils/cleanup.py | 36 ++-- utils/config.py | 52 +++--- utils/console.py | 16 +- utils/loader.py | 80 ++++----- utils/subreddit.py | 42 ++--- utils/videos.py | 30 ++-- utils/voice.py | 28 +-- video_creation/background.py | 90 +++++----- video_creation/screenshot_downloader.py | 98 ++++++----- video_creation/voices.py | 108 ++++++------ 16 files changed, 666 insertions(+), 678 deletions(-) diff --git a/TTS/GTTS.py b/TTS/GTTS.py index e38353c..fcbcb9b 100644 --- a/TTS/GTTS.py +++ b/TTS/GTTS.py @@ -2,12 +2,12 @@ from gtts import gTTS class GTTS: - def tts( - self, - req_text: str = "Google Text To Speech", - filename: str = "title.mp3", - random_speaker=False, - censor=False, - ): - tts = gTTS(text=req_text, lang="en", slow=False) - tts.save(f"{filename}") + def tts( + self, + req_text: str = "Google Text To Speech", + filename: str = "title.mp3", + random_speaker=False, + censor=False, + ): + tts = gTTS(text=req_text, lang="en", slow=False) + tts.save(f"{filename}") diff --git a/TTS/POLLY.py b/TTS/POLLY.py index 4f8650c..da1fae0 100644 --- a/TTS/POLLY.py +++ b/TTS/POLLY.py @@ -9,21 +9,21 @@ from moviepy.audio.io.AudioFileClip import AudioFileClip from requests.exceptions import JSONDecodeError voices = [ - "Brian", - "Emma", - "Russell", - "Joey", - "Matthew", - "Joanna", - "Kimberly", - "Amy", - "Geraint", - "Nicole", - "Justin", - "Ivy", - "Kendra", - "Salli", - "Raveena", + "Brian", + "Emma", + "Russell", + "Joey", + "Matthew", + "Joanna", + "Kimberly", + "Amy", + "Geraint", + "Nicole", + "Justin", + "Ivy", + "Kendra", + "Salli", + "Raveena", ] @@ -31,78 +31,76 @@ voices = [ class POLLY: - def __init__(self): - self.url = "https://streamlabs.com/polly/speak" + def __init__(self): + self.url = "https://streamlabs.com/polly/speak" - def tts( - self, - req_text: str = "Amazon Text To Speech", - filename: str = "title.mp3", - random_speaker=False, - censor=False, - ): - if random_speaker: - voice = self.randomvoice() - else: - if not os.getenv("VOICE"): - return ValueError( - "Please set the environment variable VOICE to a valid voice. options are: {}".format( - voices - ) - ) - voice = str(os.getenv("VOICE")).capitalize() - body = {"voice": voice, "text": req_text, "service": "polly"} - response = requests.post(self.url, data=body) - try: - voice_data = requests.get(response.json()["speak_url"]) - with open(filename, "wb") as f: - f.write(voice_data.content) - except (KeyError, JSONDecodeError): - if response.json()["error"] == "Text length is too long!": - chunks = [ - m.group().strip() for m in re.finditer(r" *((.{0,499})(\.|.$))", req_text) - ] + def tts( + self, + req_text: str = "Amazon Text To Speech", + filename: str = "title.mp3", + random_speaker=False, + censor=False, + ): + if random_speaker: + voice = self.randomvoice() + else: + if not os.getenv("VOICE"): + return ValueError( + "Please set the environment variable VOICE to a valid voice. options are: {}".format( + voices + ) + ) + voice = str(os.getenv("VOICE")).capitalize() + body = {"voice": voice, "text": req_text, "service": "polly"} + response = requests.post(self.url, data=body) + try: + voice_data = requests.get(response.json()["speak_url"]) + with open(filename, "wb") as f: + f.write(voice_data.content) + except (KeyError, JSONDecodeError): + if response.json()["error"] == "Text length is too long!": + chunks = [m.group().strip() for m in re.finditer(r" *((.{0,499})(\.|.$))", req_text)] - audio_clips = [] - cbn = sox.Combiner() + audio_clips = [] + cbn = sox.Combiner() - chunkId = 0 - for chunk in chunks: - body = {"voice": voice, "text": chunk, "service": "polly"} - resp = requests.post(self.url, data=body) - voice_data = requests.get(resp.json()["speak_url"]) - with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: - out.write(voice_data.content) + chunkId = 0 + for chunk in chunks: + body = {"voice": voice, "text": chunk, "service": "polly"} + resp = requests.post(self.url, data=body) + voice_data = requests.get(resp.json()["speak_url"]) + with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: + out.write(voice_data.content) - audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) + audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) - chunkId = chunkId + 1 - try: - if len(audio_clips) > 1: - cbn.convert(samplerate=44100, n_channels=2) - cbn.build(audio_clips, filename, "concatenate") - else: - os.rename(audio_clips[0], filename) - except ( - sox.core.SoxError, - FileNotFoundError, - ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 - for clip in audio_clips: - i = audio_clips.index(clip) # get the index of the clip - audio_clips = ( - audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] - ) # replace the clip with an AudioFileClip - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) - audio_composite.write_audiofile(filename, 44100, 2, 2000, None) + chunkId = chunkId + 1 + try: + if len(audio_clips) > 1: + cbn.convert(samplerate=44100, n_channels=2) + cbn.build(audio_clips, filename, "concatenate") + else: + os.rename(audio_clips[0], filename) + except ( + sox.core.SoxError, + FileNotFoundError, + ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 + for clip in audio_clips: + i = audio_clips.index(clip) # get the index of the clip + audio_clips = ( + audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] + ) # replace the clip with an AudioFileClip + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + audio_composite.write_audiofile(filename, 44100, 2, 2000, None) - def make_readable(self, text): - """ - Amazon Polly fails to read some symbols properly such as '& (and)'. - So we normalize input text before passing it to the service - """ - text = text.replace("&", "and") - return text + def make_readable(self, text): + """ + Amazon Polly fails to read some symbols properly such as '& (and)'. + So we normalize input text before passing it to the service + """ + text = text.replace("&", "and") + return text - def randomvoice(self): - return random.choice(voices) + def randomvoice(self): + return random.choice(voices) diff --git a/TTS/TikTok.py b/TTS/TikTok.py index 5b31c00..662e498 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -15,49 +15,49 @@ from requests.adapters import HTTPAdapter, Retry # https://twitter.com/scanlime/status/1512598559769702406 nonhuman = [ # DISNEY VOICES - "en_us_ghostface", # Ghost Face - "en_us_chewbacca", # Chewbacca - "en_us_c3po", # C3PO - "en_us_stitch", # Stitch - "en_us_stormtrooper", # Stormtrooper - "en_us_rocket", # Rocket - # ENGLISH VOICES + "en_us_ghostface", # Ghost Face + "en_us_chewbacca", # Chewbacca + "en_us_c3po", # C3PO + "en_us_stitch", # Stitch + "en_us_stormtrooper", # Stormtrooper + "en_us_rocket", # Rocket + # ENGLISH VOICES ] human = [ - "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 (Int. 1) - "en_us_002", # English US - Female (Int. 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", + "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 (Int. 1) + "en_us_002", # English US - Female (Int. 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", ] voices = nonhuman + human noneng = [ - "fr_001", # French - Male 1 - "fr_002", # French - Male 2 - "de_001", # German - Female - "de_002", # German - Male - "es_002", # Spanish - Male - # AMERICA VOICES - "es_mx_002", # Spanish MX - Male - "br_001", # Portuguese BR - Female 1 - "br_003", # Portuguese BR - Female 2 - "br_004", # Portuguese BR - Female 3 - "br_005", # Portuguese BR - Male - # ASIA VOICES - "id_001", # Indonesian - Female - "jp_001", # Japanese - Female 1 - "jp_003", # Japanese - Female 2 - "jp_005", # Japanese - Female 3 - "jp_006", # Japanese - Male - "kr_002", # Korean - Male 1 - "kr_003", # Korean - Female - "kr_004", # Korean - Male 2 + "fr_001", # French - Male 1 + "fr_002", # French - Male 2 + "de_001", # German - Female + "de_002", # German - Male + "es_002", # Spanish - Male + # AMERICA VOICES + "es_mx_002", # Spanish MX - Male + "br_001", # Portuguese BR - Female 1 + "br_003", # Portuguese BR - Female 2 + "br_004", # Portuguese BR - Female 3 + "br_005", # Portuguese BR - Male + # ASIA VOICES + "id_001", # Indonesian - Female + "jp_001", # Japanese - Female 1 + "jp_003", # Japanese - Female 2 + "jp_005", # Japanese - Female 3 + "jp_006", # Japanese - Male + "kr_002", # Korean - Male 1 + "kr_003", # Korean - Female + "kr_004", # Korean - Male 2 ] @@ -66,74 +66,76 @@ noneng = [ class TikTok: # TikTok Text-to-Speech Wrapper - def __init__(self): - self.URI_BASE = "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=" - - def tts( - self, - req_text: str = "TikTok Text To Speech", - filename: str = "title.mp3", - random_speaker: bool = False, - censor=False, - ): - req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and") - if censor: - # req_text = pf.censor(req_text) - pass - voice = ( - self.randomvoice() if random_speaker else (os.getenv("VOICE") or random.choice(human)) - ) - - chunks = [m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text)] - - audio_clips = [] - cbn = sox.Combiner() - # cbn.set_input_format(file_type=["mp3" for _ in chunks]) - - chunkId = 0 - for chunk in chunks: - try: - r = requests.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") - except requests.exceptions.SSLError: - # https://stackoverflow.com/a/47475019/18516611 - session = requests.Session() - retry = Retry(connect=3, backoff_factor=0.5) - adapter = HTTPAdapter(max_retries=retry) - session.mount("http://", adapter) - session.mount("https://", adapter) - r = session.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") - print(r.text) - vstr = [r.json()["data"]["v_str"]][0] - b64d = base64.b64decode(vstr) - - with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: - out.write(b64d) - - audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) - - chunkId = chunkId + 1 - try: - if len(audio_clips) > 1: - cbn.convert(samplerate=44100, n_channels=2) - cbn.build(audio_clips, filename, "concatenate") - else: - os.rename(audio_clips[0], filename) - except ( - sox.core.SoxError, - FileNotFoundError, - ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 - for clip in audio_clips: - i = audio_clips.index(clip) # get the index of the clip - audio_clips = ( - audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] - ) # replace the clip with an AudioFileClip - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) - audio_composite.write_audiofile(filename, 44100, 2, 2000, None) - - @staticmethod - def randomvoice(): - ok_or_good = random.randrange(1, 10) - if ok_or_good == 1: # 1/10 chance of ok voice - return random.choice(voices) - return random.choice(human) # 9/10 chance of good voice + def __init__(self): + self.URI_BASE = ( + "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=" + ) + + def tts( + self, + req_text: str = "TikTok Text To Speech", + filename: str = "title.mp3", + random_speaker: bool = False, + censor=False, + ): + req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and") + if censor: + # req_text = pf.censor(req_text) + pass + voice = ( + self.randomvoice() if random_speaker else (os.getenv("VOICE") or random.choice(human)) + ) + + chunks = [m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text)] + + audio_clips = [] + cbn = sox.Combiner() + # cbn.set_input_format(file_type=["mp3" for _ in chunks]) + + chunkId = 0 + for chunk in chunks: + try: + r = requests.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") + except requests.exceptions.SSLError: + # https://stackoverflow.com/a/47475019/18516611 + session = requests.Session() + retry = Retry(connect=3, backoff_factor=0.5) + adapter = HTTPAdapter(max_retries=retry) + session.mount("http://", adapter) + session.mount("https://", adapter) + r = session.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") + print(r.text) + vstr = [r.json()["data"]["v_str"]][0] + b64d = base64.b64decode(vstr) + + with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: + out.write(b64d) + + audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) + + chunkId = chunkId + 1 + try: + if len(audio_clips) > 1: + cbn.convert(samplerate=44100, n_channels=2) + cbn.build(audio_clips, filename, "concatenate") + else: + os.rename(audio_clips[0], filename) + except ( + sox.core.SoxError, + FileNotFoundError, + ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 + for clip in audio_clips: + i = audio_clips.index(clip) # get the index of the clip + audio_clips = ( + audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] + ) # replace the clip with an AudioFileClip + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + audio_composite.write_audiofile(filename, 44100, 2, 2000, None) + + @staticmethod + def randomvoice(): + ok_or_good = random.randrange(1, 10) + if ok_or_good == 1: # 1/10 chance of ok voice + return random.choice(voices) + return random.choice(human) # 9/10 chance of good voice diff --git a/TTS/swapper.py b/TTS/swapper.py index 7eebd3e..025275a 100644 --- a/TTS/swapper.py +++ b/TTS/swapper.py @@ -6,16 +6,14 @@ from TTS.GTTS import GTTS from TTS.POLLY import POLLY from TTS.TikTok import TikTok -CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS, 'polly': POLLY} +CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS, "polly": POLLY} class TTS: - def __new__(cls): - load_dotenv() - CHOICE = getenv("TTsChoice").casefold() - valid_keys = [key.lower() for key in CHOICE_DIR.keys()] - if CHOICE not in valid_keys: - raise ValueError( - f"{CHOICE} is not valid. Please use one of these {valid_keys} options" - ) - return CHOICE_DIR.get(CHOICE)() + def __new__(cls): + load_dotenv() + CHOICE = getenv("TTsChoice").casefold() + valid_keys = [key.lower() for key in CHOICE_DIR.keys()] + if CHOICE not in valid_keys: + raise ValueError(f"{CHOICE} is not valid. Please use one of these {valid_keys} options") + return CHOICE_DIR.get(CHOICE)() diff --git a/reddit/subreddit.py b/reddit/subreddit.py index f08cefa..964862f 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -11,85 +11,85 @@ TEXT_WHITELIST = set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234 def textify(text): - return "".join(filter(TEXT_WHITELIST.__contains__, text)) + return "".join(filter(TEXT_WHITELIST.__contains__, text)) def get_subreddit_threads(): - """ - Returns a list of threads from the AskReddit subreddit. - """ - global submission - print_substep("Logging into Reddit.") + """ + Returns a list of threads from the AskReddit subreddit. + """ + global submission + print_substep("Logging into Reddit.") - content = {} - if str(getenv("REDDIT_2FA")).casefold() == "yes": - print("\nEnter your two-factor authentication code from your authenticator app.\n") - code = input("> ") - print() - pw = getenv("REDDIT_PASSWORD") - passkey = f"{pw}:{code}" - else: - passkey = getenv("REDDIT_PASSWORD") - reddit = praw.Reddit( - client_id=getenv("REDDIT_CLIENT_ID"), - client_secret=getenv("REDDIT_CLIENT_SECRET"), - user_agent="Accessing Reddit threads", - username=getenv("REDDIT_USERNAME"), - passkey=passkey, - check_for_async=False, - ) - """ + content = {} + if str(getenv("REDDIT_2FA")).casefold() == "yes": + print("\nEnter your two-factor authentication code from your authenticator app.\n") + code = input("> ") + print() + pw = getenv("REDDIT_PASSWORD") + passkey = f"{pw}:{code}" + else: + passkey = getenv("REDDIT_PASSWORD") + reddit = praw.Reddit( + client_id=getenv("REDDIT_CLIENT_ID"), + client_secret=getenv("REDDIT_CLIENT_SECRET"), + user_agent="Accessing Reddit threads", + username=getenv("REDDIT_USERNAME"), + passkey=passkey, + check_for_async=False, + ) + """ Ask user for subreddit input """ - print_step("Getting subreddit threads...") - if not getenv( - "SUBREDDIT" - ): # note to self. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") - subreddit = reddit.subreddit( - input("What subreddit would you like to pull from? ") - ) # if the env isnt set, ask user - else: - print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config") - subreddit = reddit.subreddit( - getenv("SUBREDDIT") - ) # Allows you to specify in .env. Done for automation purposes. + print_step("Getting subreddit threads...") + if not getenv( + "SUBREDDIT" + ): # note to self. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") + subreddit = reddit.subreddit( + input("What subreddit would you like to pull from? ") + ) # if the env isnt set, ask user + else: + print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config") + subreddit = reddit.subreddit( + getenv("SUBREDDIT") + ) # Allows you to specify in .env. Done for automation purposes. - if getenv("POST_ID"): - submission = reddit.submission(id=getenv("POST_ID")) - else: - threads = subreddit.hot(limit=25) - submission = get_subreddit_undone(threads, subreddit) - submission = check_done(submission) # double checking - if submission is None: - return get_subreddit_threads() # submission already done. rerun - upvotes = submission.score - ratio = submission.upvote_ratio * 100 - num_comments = submission.num_comments + if getenv("POST_ID"): + submission = reddit.submission(id=getenv("POST_ID")) + else: + threads = subreddit.hot(limit=25) + submission = get_subreddit_undone(threads, subreddit) + submission = check_done(submission) # double checking + if submission is None: + return get_subreddit_threads() # submission already done. rerun + upvotes = submission.score + ratio = submission.upvote_ratio * 100 + num_comments = submission.num_comments - print_substep(f"Video will be: {submission.title} :thumbsup:", style="bold green") - print_substep(f"Thread has {upvotes} upvotes", style="bold blue") - print_substep(f"Thread has a upvote ratio of {ratio}%", style="bold blue") - print_substep(f"Thread has {num_comments} comments", style="bold blue") - environ["VIDEO_TITLE"] = str(textify(submission.title)) # todo use global instend of env vars - environ["VIDEO_ID"] = str(textify(submission.id)) + print_substep(f"Video will be: {submission.title} :thumbsup:", style="bold green") + print_substep(f"Thread has {upvotes} upvotes", style="bold blue") + print_substep(f"Thread has a upvote ratio of {ratio}%", style="bold blue") + print_substep(f"Thread has {num_comments} comments", style="bold blue") + environ["VIDEO_TITLE"] = str(textify(submission.title)) # todo use global instend of env vars + environ["VIDEO_ID"] = str(textify(submission.id)) - content["thread_url"] = f"https://reddit.com{submission.permalink}" - content["thread_title"] = submission.title - # content["thread_content"] = submission.content - content["comments"] = [] - for top_level_comment in submission.comments: - if isinstance(top_level_comment, MoreComments): - continue - if top_level_comment.body in ["[removed]", "[deleted]"]: - continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 - if not top_level_comment.stickied: - if len(top_level_comment.body) <= int(environ["MAX_COMMENT_LENGTH"]): - content["comments"].append( - { - "comment_body": top_level_comment.body, - "comment_url": top_level_comment.permalink, - "comment_id": top_level_comment.id, - } - ) - print_substep("Received subreddit threads Successfully.", style="bold green") - return content + content["thread_url"] = f"https://reddit.com{submission.permalink}" + content["thread_title"] = submission.title + # content["thread_content"] = submission.content + content["comments"] = [] + for top_level_comment in submission.comments: + if isinstance(top_level_comment, MoreComments): + continue + if top_level_comment.body in ["[removed]", "[deleted]"]: + continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 + if not top_level_comment.stickied: + if len(top_level_comment.body) <= int(environ["MAX_COMMENT_LENGTH"]): + content["comments"].append( + { + "comment_body": top_level_comment.body, + "comment_url": top_level_comment.permalink, + "comment_id": top_level_comment.id, + } + ) + print_substep("Received subreddit threads Successfully.", style="bold green") + return content diff --git a/setup.py b/setup.py index b000ce8..a8d7f12 100755 --- a/setup.py +++ b/setup.py @@ -15,52 +15,48 @@ console = Console() def handle_input( - message: str = "", - check_type=False, - match: str = "", - err_message: str = "", - nmin=None, - nmax=None, - oob_error="", + message: str = "", + check_type=False, + match: str = "", + err_message: str = "", + nmin=None, + nmax=None, + oob_error="", ): - match = re.compile(match + "$") - while True: - user_input = input(message + "\n> ").strip() - if re.match(match, user_input) is not None: - if check_type is not False: - try: - user_input = check_type(user_input) - if nmin is not None and user_input < nmin: - console.log("[red]" + oob_error) # Input too low failstate - continue - if nmax is not None and user_input > nmax: - console.log("[red]" + oob_error) # Input too high - continue - break # Successful type conversion and number in bounds - except ValueError: - console.log("[red]" + err_message) # Type conversion failed - continue - if ( - nmin is not None and len(user_input) < nmin - ): # Check if string is long enough - console.log("[red]" + oob_error) - continue - if ( - nmax is not None and len(user_input) > nmax - ): # Check if string is not too long - console.log("[red]" + oob_error) - continue - break - console.log("[red]" + err_message) - - return user_input + match = re.compile(match + "$") + while True: + user_input = input(message + "\n> ").strip() + if re.match(match, user_input) is not None: + if check_type is not False: + try: + user_input = check_type(user_input) + if nmin is not None and user_input < nmin: + console.log("[red]" + oob_error) # Input too low failstate + continue + if nmax is not None and user_input > nmax: + console.log("[red]" + oob_error) # Input too high + continue + break # Successful type conversion and number in bounds + except ValueError: + console.log("[red]" + err_message) # Type conversion failed + continue + if nmin is not None and len(user_input) < nmin: # Check if string is long enough + console.log("[red]" + oob_error) + continue + if nmax is not None and len(user_input) > nmax: # Check if string is not too long + console.log("[red]" + oob_error) + continue + break + console.log("[red]" + err_message) + + return user_input if os.path.isfile(".setup-done-before"): - console.log( - "[red]Setup was already completed! Please make sure you have to run this script again. If that is such, delete the file .setup-done-before" - ) - exit() + console.log( + "[red]Setup was already completed! Please make sure you have to run this script again. If that is such, delete the file .setup-done-before" + ) + exit() # These lines ensure the user: # - knows they are in setup mode @@ -68,26 +64,26 @@ if os.path.isfile(".setup-done-before"): 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." + "### 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. if input("Are you sure you want to continue? > ").strip().casefold() != "yes": - console.print("[red]Exiting...") - exit() + console.print("[red]Exiting...") + exit() # This code is inaccessible if the prior check fails, and thus an else statement is unnecessary # 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" + "[bold red] This will overwrite your current settings. Are you sure you want to continue? [bold green]yes/no" ) if input("Are you sure you want to continue? > ").strip().casefold() != "yes": - console.print("[red]Abort mission! Exiting...") - exit() + console.print("[red]Abort mission! Exiting...") + exit() # This is once again inaccessible if the prior checks fail # Once they confirm, move on with the script. console.print("[bold green]Alright! Let's get started!") @@ -103,18 +99,18 @@ 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)") console.print( - "[green]If you don't have these, please follow the instructions in the README.md file to set them up." + "[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." + "[green]If you do have these, type yes to continue. If you dont, go ahead and grab those quickly and come back." ) print() if input("Are you sure you have the credentials? > ").strip().casefold() != "yes": - console.print("[red]I don't understand that.") - console.print("[red]Exiting...") - exit() + console.print("[red]I don't understand that.") + console.print("[red]Exiting...") + exit() console.print("[bold green]Alright! Let's get started!") @@ -123,69 +119,69 @@ console.print("[bold green]Alright! Let's get started!") console.log("Enter your credentials now.") client_id = handle_input( - "Client ID > ", - False, - "[-a-zA-Z0-9._~+/]+=*", - "That is somehow not a correct ID, try again.", - 12, - 30, - "The ID should be over 12 and under 30 characters, double check your input.", + "Client ID > ", + False, + "[-a-zA-Z0-9._~+/]+=*", + "That is somehow not a correct ID, try again.", + 12, + 30, + "The ID should be over 12 and under 30 characters, double check your input.", ) client_sec = handle_input( - "Client Secret > ", - False, - "[-a-zA-Z0-9._~+/]+=*", - "That is somehow not a correct secret, try again.", - 20, - 40, - "The secret should be over 20 and under 40 characters, double check your input.", + "Client Secret > ", + False, + "[-a-zA-Z0-9._~+/]+=*", + "That is somehow not a correct secret, try again.", + 20, + 40, + "The secret should be over 20 and under 40 characters, double check your input.", ) user = handle_input( - "Username > ", - False, - r"[_0-9a-zA-Z]+", - "That is not a valid user", - 3, - 20, - "A username HAS to be between 3 and 20 characters", + "Username > ", + False, + r"[_0-9a-zA-Z]+", + "That is not a valid user", + 3, + 20, + "A username HAS to be between 3 and 20 characters", ) passw = handle_input("Password > ", False, ".*", "", 8, None, "Password too short") twofactor = handle_input( - "2fa Enabled? (yes/no) > ", - False, - r"(yes)|(no)", - "You need to input either yes or no", + "2fa Enabled? (yes/no) > ", + False, + r"(yes)|(no)", + "You need to input either yes or no", ) opacity = handle_input( - "Opacity? (range of 0-1) > ", - float, - ".*", - "You need to input a number between 0 and 1", - 0, - 1, - "Your number is not between 0 and 1", + "Opacity? (range of 0-1) > ", + float, + ".*", + "You need to input a number between 0 and 1", + 0, + 1, + "Your number is not between 0 and 1", ) subreddit = handle_input( - "Subreddit (without r/) > ", - False, - r"[_0-9a-zA-Z]+", - "This subreddit cannot exist, make sure you typed it in correctly and removed the r/ (or /r/).", - 3, - 20, - "A subreddit name HAS to be between 3 and 20 characters", + "Subreddit (without r/) > ", + False, + r"[_0-9a-zA-Z]+", + "This subreddit cannot exist, make sure you typed it in correctly and removed the r/ (or /r/).", + 3, + 20, + "A subreddit name HAS to be between 3 and 20 characters", ) theme = handle_input( - "Theme? (light or dark) > ", - False, - r"(light)|(dark)", - "You need to input 'light' or 'dark'", + "Theme? (light or dark) > ", + False, + r"(light)|(dark)", + "You need to input 'light' or 'dark'", ) loader = Loader("Attempting to save your credentials...", "Done!").start() # you can also put a while loop here, e.g. while VideoIsBeingMade == True: ... console.log("Writing to the .env file...") with open(".env", "w") as f: - f.write( - f"""REDDIT_CLIENT_ID="{client_id}" + f.write( + f"""REDDIT_CLIENT_ID="{client_id}" REDDIT_CLIENT_SECRET="{client_sec}" REDDIT_USERNAME="{user}" REDDIT_PASSWORD="{passw}" @@ -194,12 +190,12 @@ THEME="{theme}" SUBREDDIT="{subreddit}" OPACITY={opacity} """ - ) + ) with open(".setup-done-before", "w") as f: - f.write( - "This file blocks the setup assistant from running again. Delete this file to run setup again." - ) + f.write( + "This file blocks the setup assistant from running again. Delete this file to run setup again." + ) loader.stop() diff --git a/utils/cleanup.py b/utils/cleanup.py index 320ce49..9490b6d 100644 --- a/utils/cleanup.py +++ b/utils/cleanup.py @@ -3,22 +3,20 @@ from os.path import exists def cleanup() -> int: - if exists("./assets/temp"): - count = 0 - files = [ - f for f in os.listdir(".") if f.endswith(".mp4") and "temp" in f.lower() - ] - count += len(files) - for f in files: - os.remove(f) - try: - for file in os.listdir("./assets/temp/mp4"): - count += 1 - os.remove("./assets/temp/mp4/" + file) - except FileNotFoundError: - pass - for file in os.listdir("./assets/temp/mp3"): - count += 1 - os.remove("./assets/temp/mp3/" + file) - return count - return 0 + if exists("./assets/temp"): + count = 0 + files = [f for f in os.listdir(".") if f.endswith(".mp4") and "temp" in f.lower()] + count += len(files) + for f in files: + os.remove(f) + try: + for file in os.listdir("./assets/temp/mp4"): + count += 1 + os.remove("./assets/temp/mp4/" + file) + except FileNotFoundError: + pass + for file in os.listdir("./assets/temp/mp3"): + count += 1 + os.remove("./assets/temp/mp3/" + file) + return count + return 0 diff --git a/utils/config.py b/utils/config.py index 0216141..000b615 100644 --- a/utils/config.py +++ b/utils/config.py @@ -3,36 +3,36 @@ from dotenv import dotenv_values DEFAULTS = { - "SUBREDDIT": "AskReddit", - "ALLOW_NSFW": "False", - "POST_ID": "", - "THEME": "DARK", - "REDDIT_2FA": "no", - "TIMES_TO_RUN": "", - "MAX_COMMENT_LENGTH": "500", - "OPACITY": "1", - "VOICE": "en_us_001", - "STORYMODE": "False", + "SUBREDDIT": "AskReddit", + "ALLOW_NSFW": "False", + "POST_ID": "", + "THEME": "DARK", + "REDDIT_2FA": "no", + "TIMES_TO_RUN": "", + "MAX_COMMENT_LENGTH": "500", + "OPACITY": "1", + "VOICE": "en_us_001", + "STORYMODE": "False", } class Config: - def __init__(self): - self.raw = dotenv_values("../.env") - self.load_attrs() - - def __getattr__(self, attr): # code completion for attributes fix. - return getattr(self, attr) - - def load_attrs(self): - for key, value in self.raw.items(): - self.add_attr(key, value) - - def add_attr(self, key, value): - if value is None or value == "": - setattr(self, key, DEFAULTS[key]) - else: - setattr(self, key, str(value)) + def __init__(self): + self.raw = dotenv_values("../.env") + self.load_attrs() + + def __getattr__(self, attr): # code completion for attributes fix. + return getattr(self, attr) + + def load_attrs(self): + for key, value in self.raw.items(): + self.add_attr(key, value) + + def add_attr(self, key, value): + if value is None or value == "": + setattr(self, key, DEFAULTS[key]) + else: + setattr(self, key, str(value)) config = Config() diff --git a/utils/console.py b/utils/console.py index 1b4e3cb..11ee429 100644 --- a/utils/console.py +++ b/utils/console.py @@ -9,19 +9,19 @@ console = Console() def print_markdown(text): - """Prints a rich info message. Support Markdown syntax.""" + """Prints a rich info message. Support Markdown syntax.""" - md = Padding(Markdown(text), 2) - console.print(md) + md = Padding(Markdown(text), 2) + console.print(md) def print_step(text): - """Prints a rich info message.""" + """Prints a rich info message.""" - panel = Panel(Text(text, justify="left")) - console.print(panel) + panel = Panel(Text(text, justify="left")) + console.print(panel) def print_substep(text, style=""): - """Prints a rich info message without the panelling.""" - console.print(text, style=style) + """Prints a rich info message without the panelling.""" + console.print(text, style=style) diff --git a/utils/loader.py b/utils/loader.py index ac1c535..b9dc276 100644 --- a/utils/loader.py +++ b/utils/loader.py @@ -9,43 +9,43 @@ from time import sleep class Loader: - def __init__(self, desc="Loading...", end="Done!", timeout=0.1): - """ - A loader-like context manager - - Args: - desc (str, optional): The loader's description. Defaults to "Loading...". - end (str, optional): Final print. Defaults to "Done!". - timeout (float, optional): Sleep time between prints. Defaults to 0.1. - """ - self.desc = desc - self.end = end - self.timeout = timeout - - self._thread = Thread(target=self._animate, daemon=True) - self.steps = ["β’Ώ", "β£»", "β£½", "β£Ύ", "β£·", "β£―", "⣟", "β‘Ώ"] - self.done = False - - def start(self): - self._thread.start() - return self - - def _animate(self): - for c in cycle(self.steps): - if self.done: - break - print(f"\r{self.desc} {c}", flush=True, end="") - sleep(self.timeout) - - def __enter__(self): - self.start() - - def stop(self): - self.done = True - cols = get_terminal_size((80, 20)).columns - print("\r" + " " * cols, end="", flush=True) - print(f"\r{self.end}", flush=True) - - def __exit__(self, exc_type, exc_value, tb): - # handle exceptions with those variables ^ - self.stop() + def __init__(self, desc="Loading...", end="Done!", timeout=0.1): + """ + A loader-like context manager + + Args: + desc (str, optional): The loader's description. Defaults to "Loading...". + end (str, optional): Final print. Defaults to "Done!". + timeout (float, optional): Sleep time between prints. Defaults to 0.1. + """ + self.desc = desc + self.end = end + self.timeout = timeout + + self._thread = Thread(target=self._animate, daemon=True) + self.steps = ["β’Ώ", "β£»", "β£½", "β£Ύ", "β£·", "β£―", "⣟", "β‘Ώ"] + self.done = False + + def start(self): + self._thread.start() + return self + + def _animate(self): + for c in cycle(self.steps): + if self.done: + break + print(f"\r{self.desc} {c}", flush=True, end="") + sleep(self.timeout) + + def __enter__(self): + self.start() + + def stop(self): + self.done = True + cols = get_terminal_size((80, 20)).columns + print("\r" + " " * cols, end="", flush=True) + print(f"\r{self.end}", flush=True) + + def __exit__(self, exc_type, exc_value, tb): + # handle exceptions with those variables ^ + self.stop() diff --git a/utils/subreddit.py b/utils/subreddit.py index 15185e2..d12228f 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -5,28 +5,28 @@ from utils.console import print_substep def get_subreddit_undone(submissions: List, subreddit): - """ - recursively checks if the top submission in the list was already done. - """ - with open("./video_creation/data/videos.json", "r") as done_vids_raw: - done_videos = json.load(done_vids_raw) - for submission in submissions: - if already_done(done_videos, submission): - continue - if submission.over_18: - if getenv("ALLOW_NSFW").casefold() == "false": - print_substep("NSFW Post Detected. Skipping...") - continue - return submission - print('all submissions have been done going by top submission order') - return get_subreddit_undone( - subreddit.top(time_filter="hour"), subreddit - ) # all of the videos in hot have already been done + """ + recursively checks if the top submission in the list was already done. + """ + with open("./video_creation/data/videos.json", "r") as done_vids_raw: + done_videos = json.load(done_vids_raw) + for submission in submissions: + if already_done(done_videos, submission): + continue + if submission.over_18: + if getenv("ALLOW_NSFW").casefold() == "false": + print_substep("NSFW Post Detected. Skipping...") + continue + return submission + print("all submissions have been done going by top submission order") + return get_subreddit_undone( + subreddit.top(time_filter="hour"), subreddit + ) # all of the videos in hot have already been done def already_done(done_videos: list, submission): - for video in done_videos: - if video["id"] == str(submission): - return True - return False + for video in done_videos: + if video["id"] == str(submission): + return True + return False diff --git a/utils/videos.py b/utils/videos.py index 0c1a27f..51a2704 100755 --- a/utils/videos.py +++ b/utils/videos.py @@ -5,19 +5,19 @@ from utils.console import print_step def check_done( - redditobj, + redditobj, ): # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack - """params: - reddit_object: The Reddit Object you received in askreddit.py""" - with open("./video_creation/data/videos.json", "r") as done_vids_raw: - done_videos = json.load(done_vids_raw) - for video in done_videos: - if video["id"] == str(redditobj): - if getenv("POST_ID"): - print_step( - "You already have done this video but since it was declared specifically in the .env file the program will continue" - ) - return redditobj - print_step("Getting new post as the current one has already been done") - return None - return redditobj + """params: + reddit_object: The Reddit Object you received in askreddit.py""" + with open("./video_creation/data/videos.json", "r") as done_vids_raw: + done_videos = json.load(done_vids_raw) + for video in done_videos: + if video["id"] == str(redditobj): + if getenv("POST_ID"): + print_step( + "You already have done this video but since it was declared specifically in the .env file the program will continue" + ) + return redditobj + print_step("Getting new post as the current one has already been done") + return None + return redditobj diff --git a/utils/voice.py b/utils/voice.py index 32333dc..120ee60 100644 --- a/utils/voice.py +++ b/utils/voice.py @@ -2,21 +2,21 @@ import re def sanitize_text(text): - """ - Sanitizes the text for tts. - What gets removed: - - following characters`^_~@!&;#:-%β€œβ€β€˜"%*/{}[]()\|<>?=+` - - any http or https links - """ + """ + Sanitizes the text for tts. + What gets removed: + - following characters`^_~@!&;#:-%β€œβ€β€˜"%*/{}[]()\|<>?=+` + - any http or https links + """ - # remove any urls from the text - regex_urls = r"((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*" + # remove any urls from the text + regex_urls = r"((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*" - result = re.sub(regex_urls, " ", text) + result = re.sub(regex_urls, " ", text) - # note: not removing apostrophes - regex_expr = r"\s['|’]|['|’]\s|[\^_~@!&;#:\-%β€œβ€β€˜\"%\*/{}\[\]\(\)\\|<>=+]" - result = re.sub(regex_expr, " ", result) + # note: not removing apostrophes + regex_expr = r"\s['|’]|['|’]\s|[\^_~@!&;#:\-%β€œβ€β€˜\"%\*/{}\[\]\(\)\\|<>=+]" + result = re.sub(regex_expr, " ", result) - # remove extra whitespace - return " ".join(result.split()) + # remove extra whitespace + return " ".join(result.split()) diff --git a/video_creation/background.py b/video_creation/background.py index 146a63f..fb300e6 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -9,55 +9,53 @@ from utils.console import print_step, print_substep def get_start_and_end_times(video_length, length_of_clip): - random_time = randrange(180, int(length_of_clip) - int(video_length)) - return random_time, random_time + video_length + random_time = randrange(180, int(length_of_clip) - int(video_length)) + return random_time, random_time + video_length def download_background(): - """Downloads the backgrounds/s video from YouTube.""" - Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True) - background_options = [ # uri , filename , credit - ("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"), - # ( - # "https://www.youtube.com/watch?v=2X9QGY__0II", - # "rocket_league.mp4", - # "Orbital Gameplay", - # ), - ] - # note: make sure the file name doesn't include an - in it - if not len(listdir("./assets/backgrounds")) >= len( - background_options - ): # if there are any background videos not installed - print_step( - "We need to download the backgrounds videos. they are fairly large but it's only done once. 😎" - ) - print_substep("Downloading the backgrounds videos... please be patient πŸ™ ") - for uri, filename, credit in background_options: - if Path(f"assets/backgrounds/{credit}-{filename}").is_file(): - continue # adds check to see if file exists before downloading - print_substep(f"Downloading {filename} from {uri}") - YouTube(uri).streams.filter(res="1080p").first().download( - "assets/backgrounds", filename=f"{credit}-{filename}" - ) - - print_substep( - "Background videos downloaded successfully! πŸŽ‰", style="bold green" - ) + """Downloads the backgrounds/s video from YouTube.""" + Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True) + background_options = [ # uri , filename , credit + ("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"), + # ( + # "https://www.youtube.com/watch?v=2X9QGY__0II", + # "rocket_league.mp4", + # "Orbital Gameplay", + # ), + ] + # note: make sure the file name doesn't include an - in it + if not len(listdir("./assets/backgrounds")) >= len( + background_options + ): # if there are any background videos not installed + print_step( + "We need to download the backgrounds videos. they are fairly large but it's only done once. 😎" + ) + print_substep("Downloading the backgrounds videos... please be patient πŸ™ ") + for uri, filename, credit in background_options: + if Path(f"assets/backgrounds/{credit}-{filename}").is_file(): + continue # adds check to see if file exists before downloading + print_substep(f"Downloading {filename} from {uri}") + YouTube(uri).streams.filter(res="1080p").first().download( + "assets/backgrounds", filename=f"{credit}-{filename}" + ) + + print_substep("Background videos downloaded successfully! πŸŽ‰", style="bold green") def chop_background_video(video_length): - print_step("Finding a spot in the backgrounds video to chop...βœ‚οΈ") - choice = random.choice(listdir("assets/backgrounds")) - environ["background_credit"] = choice.split("-")[0] - - background = VideoFileClip(f"assets/backgrounds/{choice}") - - start_time, end_time = get_start_and_end_times(video_length, background.duration) - ffmpeg_extract_subclip( - f"assets/backgrounds/{choice}", - start_time, - end_time, - targetname="assets/temp/background.mp4", - ) - print_substep("Background video chopped successfully!", style="bold green") - return True + print_step("Finding a spot in the backgrounds video to chop...βœ‚οΈ") + choice = random.choice(listdir("assets/backgrounds")) + environ["background_credit"] = choice.split("-")[0] + + background = VideoFileClip(f"assets/backgrounds/{choice}") + + start_time, end_time = get_start_and_end_times(video_length, background.duration) + ffmpeg_extract_subclip( + f"assets/backgrounds/{choice}", + start_time, + end_time, + targetname="assets/temp/background.mp4", + ) + print_substep("Background video chopped successfully!", style="bold green") + return True diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 25241e1..e27a2be 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -16,62 +16,60 @@ storymode = False def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): - """Downloads screenshots of reddit posts as they are seen on the web. - Args: - reddit_object: The Reddit Object you received in askreddit.py - screenshot_num: The number of screenshots you want to download. - """ - print_step("Downloading screenshots of reddit posts...") + """Downloads screenshots of reddit posts as they are seen on the web. + Args: + reddit_object: The Reddit Object you received in askreddit.py + screenshot_num: The number of screenshots you want to download. + """ + print_step("Downloading screenshots of reddit posts...") - # ! Make sure the reddit screenshots folder exists - Path("assets/temp/png").mkdir(parents=True, exist_ok=True) + # ! Make sure the reddit screenshots folder exists + Path("assets/temp/png").mkdir(parents=True, exist_ok=True) - with sync_playwright() as p: - print_substep("Launching Headless Browser...") + with sync_playwright() as p: + print_substep("Launching Headless Browser...") - browser = p.chromium.launch() - context = browser.new_context() + browser = p.chromium.launch() + context = browser.new_context() - if getenv("THEME").upper() == "DARK": - cookie_file = open("./video_creation/data/cookie-dark-mode.json") - else: - cookie_file = open("./video_creation/data/cookie-light-mode.json") - cookies = json.load(cookie_file) - context.add_cookies(cookies) # load preference cookies - # Get the thread screenshot - page = context.new_page() - page.goto(reddit_object["thread_url"]) - page.set_viewport_size(ViewportSize(width=1920, height=1080)) - if page.locator('[data-testid="content-gate"]').is_visible(): - # This means the post is NSFW and requires to click the proceed button. + if getenv("THEME").upper() == "DARK": + cookie_file = open("./video_creation/data/cookie-dark-mode.json") + else: + cookie_file = open("./video_creation/data/cookie-light-mode.json") + cookies = json.load(cookie_file) + context.add_cookies(cookies) # load preference cookies + # Get the thread screenshot + page = context.new_page() + page.goto(reddit_object["thread_url"]) + page.set_viewport_size(ViewportSize(width=1920, height=1080)) + if page.locator('[data-testid="content-gate"]').is_visible(): + # This means the post is NSFW and requires to click the proceed button. - print_substep("Post is NSFW. You are spicy...") - page.locator('[data-testid="content-gate"] button').click() - page.locator( - '[data-click-id="text"] button' - ).click() # Remove "Click to see nsfw" Button in Screenshot + print_substep("Post is NSFW. You are spicy...") + page.locator('[data-testid="content-gate"] button').click() + page.locator( + '[data-click-id="text"] button' + ).click() # Remove "Click to see nsfw" Button in Screenshot - page.locator('[data-test-id="post-content"]').screenshot( - path="assets/temp/png/title.png" - ) - if storymode: - page.locator('[data-click-id="text"]').screenshot( - path="assets/temp/png/story_content.png" - ) - else: - for idx, comment in track( - enumerate(reddit_object["comments"]), "Downloading screenshots..." - ): + page.locator('[data-test-id="post-content"]').screenshot(path="assets/temp/png/title.png") + if storymode: + page.locator('[data-click-id="text"]').screenshot( + path="assets/temp/png/story_content.png" + ) + else: + for idx, comment in track( + enumerate(reddit_object["comments"]), "Downloading screenshots..." + ): - # Stop if we have reached the screenshot_num - if idx >= screenshot_num: - break + # Stop if we have reached the screenshot_num + if idx >= screenshot_num: + break - if page.locator('[data-testid="content-gate"]').is_visible(): - page.locator('[data-testid="content-gate"] button').click() + if page.locator('[data-testid="content-gate"]').is_visible(): + page.locator('[data-testid="content-gate"] button').click() - page.goto(f'https://reddit.com{comment["comment_url"]}') - page.locator(f"#t1_{comment['comment_id']}").screenshot( - path=f"assets/temp/png/comment_{idx}.png" - ) - print_substep("Screenshots downloaded Successfully.", style="bold green") + page.goto(f'https://reddit.com{comment["comment_url"]}') + page.locator(f"#t1_{comment['comment_id']}").screenshot( + path=f"assets/temp/png/comment_{idx}.png" + ) + print_substep("Screenshots downloaded Successfully.", style="bold green") diff --git a/video_creation/voices.py b/video_creation/voices.py index 960b5fa..be7da96 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -20,60 +20,60 @@ VIDEO_LENGTH: int = 40 # secs def save_text_to_mp3(reddit_obj): - """Saves Text to MP3 files. - Args: - reddit_obj : The reddit object you received from the reddit API in the askreddit.py file. - """ - print_step("Saving Text to MP3 files...") - length = 0 + """Saves Text to MP3 files. + Args: + reddit_obj : The reddit object you received from the reddit API in the askreddit.py file. + """ + print_step("Saving Text to MP3 files...") + length = 0 - # Create a folder for the mp3 files. - Path("assets/temp/mp3").mkdir(parents=True, exist_ok=True) - TextToSpeech = TTS() - TextToSpeech.tts( - sanitize_text(reddit_obj["thread_title"]), - filename="assets/temp/mp3/title.mp3", - random_speaker=False, - ) - try: - length += MP3("assets/temp/mp3/title.mp3").info.length - except HeaderNotFoundError: # note to self AudioFileClip - length += sox.file_info.duration("assets/temp/mp3/title.mp3") - if getenv("STORYMODE").casefold() == "true": - TextToSpeech.tts( - sanitize_text(reddit_obj["thread_content"]), - filename="assets/temp/mp3/story_content.mp3", - random_speaker=False, - ) - # 'story_content' - com = 0 - for comment in track((reddit_obj["comments"]), "Saving..."): - # ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer - # but this is just a good_voices starting point - if length > VIDEO_LENGTH: - break + # Create a folder for the mp3 files. + Path("assets/temp/mp3").mkdir(parents=True, exist_ok=True) + TextToSpeech = TTS() + TextToSpeech.tts( + sanitize_text(reddit_obj["thread_title"]), + filename="assets/temp/mp3/title.mp3", + random_speaker=False, + ) + try: + length += MP3("assets/temp/mp3/title.mp3").info.length + except HeaderNotFoundError: # note to self AudioFileClip + length += sox.file_info.duration("assets/temp/mp3/title.mp3") + if getenv("STORYMODE").casefold() == "true": + TextToSpeech.tts( + sanitize_text(reddit_obj["thread_content"]), + filename="assets/temp/mp3/story_content.mp3", + random_speaker=False, + ) + # 'story_content' + com = 0 + for comment in track((reddit_obj["comments"]), "Saving..."): + # ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer + # but this is just a good_voices starting point + if length > VIDEO_LENGTH: + break - TextToSpeech.tts( - sanitize_text(comment["comment_body"]), - filename=f"assets/temp/mp3/{com}.mp3", - random_speaker=False, - ) - try: - length += MP3(f"assets/temp/mp3/{com}.mp3").info.length - com += 1 - except (HeaderNotFoundError, MutagenError, Exception): - try: - length += sox.file_info.duration(f"assets/temp/mp3/{com}.mp3") - com += 1 - except (OSError, IOError): - print( - "would have removed" - f"assets/temp/mp3/{com}.mp3" - f"assets/temp/png/comment_{com}.png" - ) - # remove(f"assets/temp/mp3/{com}.mp3") - # remove(f"assets/temp/png/comment_{com}.png")# todo might cause odd un-syncing + TextToSpeech.tts( + sanitize_text(comment["comment_body"]), + filename=f"assets/temp/mp3/{com}.mp3", + random_speaker=False, + ) + try: + length += MP3(f"assets/temp/mp3/{com}.mp3").info.length + com += 1 + except (HeaderNotFoundError, MutagenError, Exception): + try: + length += sox.file_info.duration(f"assets/temp/mp3/{com}.mp3") + com += 1 + except (OSError, IOError): + print( + "would have removed" + f"assets/temp/mp3/{com}.mp3" + f"assets/temp/png/comment_{com}.png" + ) + # remove(f"assets/temp/mp3/{com}.mp3") + # remove(f"assets/temp/png/comment_{com}.png")# todo might cause odd un-syncing - print_substep("Saved Text to MP3 files Successfully.", style="bold green") - # ! Return the index, so we know how many screenshots of comments we need to make. - return length, com + print_substep("Saved Text to MP3 files Successfully.", style="bold green") + # ! Return the index, so we know how many screenshots of comments we need to make. + return length, com From 09ba69c5bc53a79c0163b07c6260685a0424513f Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:13:19 -0400 Subject: [PATCH 41/49] removed unused .env members --- .env.template | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/.env.template b/.env.template index c94fa6a..2e1ec9e 100644 --- a/.env.template +++ b/.env.template @@ -1,4 +1,3 @@ -# This can be found in the email that reddit sent you when you created the app REDDIT_CLIENT_ID="" REDDIT_CLIENT_SECRET="" @@ -6,16 +5,8 @@ REDDIT_USERNAME="" REDDIT_PASSWORD="" # If no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: "no" -RANDOM_THREAD="" +RANDOM_THREAD="no" -# Filters the comments by range of length (min and max characters) -# Min has to be less or equal to max -# DO NOT INSERT ANY SPACES BETWEEN THE COMMA AND THE VALUES -COMMENT_LENGTH_RANGE = "min,max" - -# The absolute path of the folder where you want to save the final video -# If empty or wrong, the path will be 'results/' -FINAL_VIDEO_PATH="" # Valid options are "yes" and "no" for the variable below REDDIT_2FA="" SUBREDDIT="AskReddit" @@ -25,15 +16,16 @@ ALLOW_NSFW="False" POST_ID="" #set to either LIGHT or DARK THEME="LIGHT" -# used if you want to run multiple times. set to an int e.g. 4 or 29 and leave blank for once +# used if you want to run multiple times. set to an int e.g. 4 or 29 and leave blank for 1 TIMES_TO_RUN="" +# max number of characters a comment can have. MAX_COMMENT_LENGTH="500" # Range is 0 -> 1 recommended around 0.8-0.9 OPACITY="1" -# see TTSwrapper.py for all valid options +# see different voice options: todo: add docs VOICE="Matthew" # e.g. en_us_002 -TTsChoice="polly" # todo add docs +TTsChoice="polly" # IN-PROGRESS - not yet implemented -STORYMODE="False" \ No newline at end of file +STORYMODE="False" From 76bff1a02d7edfceb4336bf0160b55a274753684 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:17:36 -0400 Subject: [PATCH 42/49] fixed #553 --- .devcontainer/devcontainer.json | 19 ---- .gitignore | 87 +++++++++++++++++-- .vscode/launch.json | 13 --- main.py | 2 +- utils/checker.py | 62 +++++++++++++ utils/envUpdate.py | 48 ---------- utils/scripts/FileGrabber.ps1 | 9 ++ .../FileGrabberenv.ps1} | 6 +- video_creation/data/.gitignore | 2 - 9 files changed, 153 insertions(+), 95 deletions(-) delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .vscode/launch.json create mode 100644 utils/checker.py delete mode 100644 utils/envUpdate.py create mode 100644 utils/scripts/FileGrabber.ps1 rename utils/{envUpdateWin.ps1 => scripts/FileGrabberenv.ps1} (77%) delete mode 100644 video_creation/data/.gitignore diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 1f86901..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,19 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.238.0/containers/docker-existing-dockerfile -{ - "name": "Existing Dockerfile", - - // Sets the run context to one level up instead of the .devcontainer folder. - "context": "..", - - // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. - "dockerFile": "../Dockerfile", - - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-python.python" - ], - - // Install OS dependencies - "postCreateCommand": "apt-get update && apt -qq install -y sox && apt-get install -y libsox-fmt-all" -} diff --git a/.gitignore b/.gitignore index 1c28a20..9a8b2b2 100644 --- a/.gitignore +++ b/.gitignore @@ -153,22 +153,91 @@ dmypy.json cython_debug/ # PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -.idea/ +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser assets/ out .DS_Store .setup-done-before -assets/ results/* -.env reddit-bot-351418-5560ebc49cac.json /.idea *.pyc /video_creation/data/videos.json -utils/envUpdate.py.old -envVars.txt \ No newline at end of file +/video_creation/data/envvars.txt diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 0d2e482..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Python: Main.py", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/main.py", - "console": "integratedTerminal", - "justMyCode": true - } - ] -} \ No newline at end of file diff --git a/main.py b/main.py index bfb3ba3..6f478b1 100755 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ from os import getenv, name from reddit.subreddit import get_subreddit_threads from utils.cleanup import cleanup from utils.console import print_markdown, print_step -from utils.envUpdate import envUpdate +from utils.checker import envUpdate from video_creation.background import download_background, chop_background_video from video_creation.final_video import make_final_video from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts diff --git a/utils/checker.py b/utils/checker.py new file mode 100644 index 0000000..0372514 --- /dev/null +++ b/utils/checker.py @@ -0,0 +1,62 @@ +import os +import subprocess +import tempfile +from os import path +from sys import platform + +ACCEPTABLE_TO_BE_LEFT_BLANK = ['RANDOM_THREAD', 'TIMES_TO_RUN'] + + +def envUpdate(): + if path.exists(".env.template"): # if .env.template exists and .env does not exist + if platform == "win32" or platform == "cygwin": + runPS('utils/scripts/FileGrabber.ps1') + with open(".\\video_creation\\data\\envvars.txt", "rb") as f: + envTemplate = f.read() + elif platform == "darwin" or platform == "linux": + envTemplate = subprocess.check_output("awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", + shell=True, ) + else: + raise OSError("Unsupported platform") + elif path.exists(".env"): + if platform == "win32" or platform == "cygwin": + runPS('utils/scripts/FileGrabberenv.ps1') + with open(".\\video_creation\\data\\envvars.txt", "rb") as f: + envTemplate = f.read() + elif platform == "darwin" or platform == "linux": + envTemplate = subprocess.check_output("awk -F '=' 'NF {print $1}' .env | grep --regexp=^[a-zA-Z]", + shell=True, ) + else: + raise OSError("Unsupported platform") + else: + raise FileNotFoundError("No .env or .env.template file found") + tempEnv = tempfile.TemporaryFile() + tempEnv.write(envTemplate) + tempEnv.seek(0) + envVars = tempEnv.readlines() + + + missing = [] + isMissingEnvs = False + for env in envVars: + try: + env = env.decode("utf-8").strip() + except AttributeError: + env = env.strip() + + if env not in os.environ: + if str(env) in ACCEPTABLE_TO_BE_LEFT_BLANK: + continue + isMissingEnvs = True + missing.append(env) + + if isMissingEnvs: + printstr = '' + [printstr + str(var) for var in missing] + print(f"The following environment variables are missing: {printstr}. Please add them to the .env file.") + exit(-1) + + +def runPS(cmd): + completed = subprocess.run(["powershell", "-Command", cmd], capture_output=True) + return completed diff --git a/utils/envUpdate.py b/utils/envUpdate.py deleted file mode 100644 index f225419..0000000 --- a/utils/envUpdate.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -import subprocess -import tempfile -import logging - -from os import path -from sys import platform, stderr - -log = logging.getLogger(__name__) - -def envUpdate(): - if path.exists(".env.template"): - if platform == "win32" or platform == "cygwin": - runPS('utils\envUpdateWin.ps1') - f = open("envVars.txt", "rb") - envTemplate = f.read() - elif platform == "darwin" or platform == "linux": - envTemplate = subprocess.check_output( - "awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", - shell=True, - ) - return - tempEnv = tempfile.TemporaryFile() - tempEnv.write(envTemplate) - tempEnv.seek(0) - envVars = tempEnv.readlines() - - missing = [] - isMissingEnvs = False - for env in envVars: - try: - env = env.decode("utf-8").strip() - except AttributeError: - env = env.strip() - - if env not in os.environ: - isMissingEnvs = True - missing.append(env) - - if isMissingEnvs: - log.error( - f"[ERROR] The following environment variables are missing: {missing}.)" - ) - exit(-1) - -def runPS(cmd): - completed = subprocess.run(["powershell", "-Command", cmd], capture_output=True) - return completed \ No newline at end of file diff --git a/utils/scripts/FileGrabber.ps1 b/utils/scripts/FileGrabber.ps1 new file mode 100644 index 0000000..a820d2e --- /dev/null +++ b/utils/scripts/FileGrabber.ps1 @@ -0,0 +1,9 @@ +$envFile = Get-Content ".\.env.template" + +$envFile -split "=" | Where-Object {$_ -notmatch '\"'} | Set-Content ".\envVarsbefSpl.txt" +Get-Content ".\envVarsbefSpl.txt" | Where-Object {$_ -notmatch '\#'} | Set-Content ".\envVarsN.txt" +Get-Content ".\envVarsN.txt" | Where-Object {$_ -ne ''} | Set-Content ".\video_creation\data\envvars.txt" +Remove-Item ".\envVarsbefSpl.txt" +Remove-Item ".\envVarsN.txt" + +Write-Host $nowSplit diff --git a/utils/envUpdateWin.ps1 b/utils/scripts/FileGrabberenv.ps1 similarity index 77% rename from utils/envUpdateWin.ps1 rename to utils/scripts/FileGrabberenv.ps1 index 1eb8f64..ffb021b 100644 --- a/utils/envUpdateWin.ps1 +++ b/utils/scripts/FileGrabberenv.ps1 @@ -1,9 +1,9 @@ -$envFile = Get-Content ".\.env.template" +$envFile = Get-Content ".\.env" $envFile -split "=" | Where-Object {$_ -notmatch '\"'} | Set-Content ".\envVarsbefSpl.txt" Get-Content ".\envVarsbefSpl.txt" | Where-Object {$_ -notmatch '\#'} | Set-Content ".\envVarsN.txt" -Get-Content ".\envVarsN.txt" | Where-Object {$_ -ne ''} | Set-Content ".\envVars.txt" +Get-Content ".\envVarsN.txt" | Where-Object {$_ -ne ''} | Set-Content ".\video_creation\data\envvars.txt" Remove-Item ".\envVarsbefSpl.txt" Remove-Item ".\envVarsN.txt" -Write-Host $nowSplit \ No newline at end of file +Write-Host $nowSplit diff --git a/video_creation/data/.gitignore b/video_creation/data/.gitignore deleted file mode 100644 index a8731e5..0000000 --- a/video_creation/data/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -videos.json - #todo add videos on github From 99978b1cf1189ae1008772731cffd31f40462631 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:29:59 -0400 Subject: [PATCH 43/49] fixed readme as seen in #556 --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 13248b1..26b5814 100644 --- a/README.md +++ b/README.md @@ -39,21 +39,22 @@ The only original thing being done is the editing and gathering of all materials ## Installation πŸ‘©β€πŸ’» 1. Clone this repository -2. 2a **Automatic Install**: Run `python3 main.py` and type 'yes' to activate the setup assistant. +2. 2a **Automatic Install**: Run `python main.py` and type 'yes' to activate the setup assistant. 2b **Manual Install**: Rename `.env.template` to `.env` and replace all values with the appropriate fields. To get Reddit keys (**required**), visit [the Reddit Apps page.](https://www.reddit.com/prefs/apps) TL;DR set up an app that is a "script". Copy your keys into the `.env` file, along with whether your account uses two-factor authentication. 3. Install [SoX](https://sourceforge.net/projects/sox/files/sox/) -4. Run `pip3 install -r requirements.txt` +4. Run `pip install -r requirements.txt` -5. Run `playwright install` and `playwright install-deps`. +5. Run `playwright install` and `playwright install-deps`. (if this fails try adding python -m to the front of the command) -6. Run `python3 main.py` (unless you chose automatic install, then the installer will automatically run main.py) +6. Run `python main.py` (unless you chose automatic install, then the installer will automatically run main.py) required\*\*), visit [the Reddit Apps page.](https://www.reddit.com/prefs/apps) TL;DR set up an app that is a "script". Copy your keys into the `.env` file, along with whether your account uses two-factor authentication. 7. Enjoy 😎 +(Note if you got an error installing or running the bot try first rerunning the command with a three after the name e.g. python3 or pip3) ## Video https://user-images.githubusercontent.com/66544866/173453972-6526e4e6-c6ef-41c5-ab40-5d275e724e7c.mp4 From 56ad55933986a115bb3411d45ae55520895c6bc9 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:37:00 -0400 Subject: [PATCH 44/49] added the changes of #549 --- .gitignore | 4 ++-- TTS/swapper.py | 7 ++++++- reddit/subreddit.py | 13 +++++++++---- utils/subreddit.py | 9 ++++++--- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 9a8b2b2..4ee3693 100644 --- a/.gitignore +++ b/.gitignore @@ -239,5 +239,5 @@ results/* reddit-bot-351418-5560ebc49cac.json /.idea *.pyc -/video_creation/data/videos.json -/video_creation/data/envvars.txt +video_creation/data/videos.json +video_creation/data/envvars.txt diff --git a/TTS/swapper.py b/TTS/swapper.py index 025275a..c5f6776 100644 --- a/TTS/swapper.py +++ b/TTS/swapper.py @@ -5,6 +5,7 @@ from dotenv import load_dotenv from TTS.GTTS import GTTS from TTS.POLLY import POLLY from TTS.TikTok import TikTok +from utils.console import print_substep CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS, "polly": POLLY} @@ -12,7 +13,11 @@ CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS, "polly": POLLY} class TTS: def __new__(cls): load_dotenv() - CHOICE = getenv("TTsChoice").casefold() + try: + CHOICE = getenv("TTsChoice").casefold() + except AttributeError: + print_substep("None defined. Defaulting to 'polly.'") + CHOICE = "polly" valid_keys = [key.lower() for key in CHOICE_DIR.keys()] if CHOICE not in valid_keys: raise ValueError(f"{CHOICE} is not valid. Please use one of these {valid_keys} options") diff --git a/reddit/subreddit.py b/reddit/subreddit.py index 964862f..92c6d7a 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -1,3 +1,4 @@ +import re from os import getenv, environ import praw @@ -44,10 +45,14 @@ def get_subreddit_threads(): print_step("Getting subreddit threads...") if not getenv( "SUBREDDIT" - ): # note to self. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") - subreddit = reddit.subreddit( - input("What subreddit would you like to pull from? ") - ) # if the env isnt set, ask user + ): # note to user. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") + try: + subreddit = reddit.subreddit( + re.sub(r"r\/", "", input("What subreddit would you like to pull from? ")) # removes the r/ from the input + ) + except ValueError: + subreddit = reddit.subreddit("askreddit") + print_substep("Subreddit not defined. Using AskReddit.") else: print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config") subreddit = reddit.subreddit( diff --git a/utils/subreddit.py b/utils/subreddit.py index d12228f..e05c136 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -14,9 +14,12 @@ def get_subreddit_undone(submissions: List, subreddit): if already_done(done_videos, submission): continue if submission.over_18: - if getenv("ALLOW_NSFW").casefold() == "false": - print_substep("NSFW Post Detected. Skipping...") - continue + try: + if getenv("ALLOW_NSFW").casefold() == "false": + print_substep("NSFW Post Detected. Skipping...") + continue + except AttributeError: + print_substep("NSFW settings not defined. Skipping NSFW post...") return submission print("all submissions have been done going by top submission order") return get_subreddit_undone( From 894bc306c31fe7e901fc734e71d3a5aca28cfce1 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:38:21 -0400 Subject: [PATCH 45/49] reformatted again --- reddit/subreddit.py | 3 ++- utils/checker.py | 25 +++++++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/reddit/subreddit.py b/reddit/subreddit.py index 92c6d7a..34a8e17 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -48,7 +48,8 @@ def get_subreddit_threads(): ): # note to user. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") try: subreddit = reddit.subreddit( - re.sub(r"r\/", "", input("What subreddit would you like to pull from? ")) # removes the r/ from the input + re.sub(r"r\/", "", input("What subreddit would you like to pull from? ")) + # removes the r/ from the input ) except ValueError: subreddit = reddit.subreddit("askreddit") diff --git a/utils/checker.py b/utils/checker.py index 0372514..05f1412 100644 --- a/utils/checker.py +++ b/utils/checker.py @@ -4,28 +4,32 @@ import tempfile from os import path from sys import platform -ACCEPTABLE_TO_BE_LEFT_BLANK = ['RANDOM_THREAD', 'TIMES_TO_RUN'] +ACCEPTABLE_TO_BE_LEFT_BLANK = ["RANDOM_THREAD", "TIMES_TO_RUN"] def envUpdate(): if path.exists(".env.template"): # if .env.template exists and .env does not exist if platform == "win32" or platform == "cygwin": - runPS('utils/scripts/FileGrabber.ps1') + runPS("utils/scripts/FileGrabber.ps1") with open(".\\video_creation\\data\\envvars.txt", "rb") as f: envTemplate = f.read() elif platform == "darwin" or platform == "linux": - envTemplate = subprocess.check_output("awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", - shell=True, ) + envTemplate = subprocess.check_output( + "awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", + shell=True, + ) else: raise OSError("Unsupported platform") elif path.exists(".env"): if platform == "win32" or platform == "cygwin": - runPS('utils/scripts/FileGrabberenv.ps1') + runPS("utils/scripts/FileGrabberenv.ps1") with open(".\\video_creation\\data\\envvars.txt", "rb") as f: envTemplate = f.read() elif platform == "darwin" or platform == "linux": - envTemplate = subprocess.check_output("awk -F '=' 'NF {print $1}' .env | grep --regexp=^[a-zA-Z]", - shell=True, ) + envTemplate = subprocess.check_output( + "awk -F '=' 'NF {print $1}' .env | grep --regexp=^[a-zA-Z]", + shell=True, + ) else: raise OSError("Unsupported platform") else: @@ -35,7 +39,6 @@ def envUpdate(): tempEnv.seek(0) envVars = tempEnv.readlines() - missing = [] isMissingEnvs = False for env in envVars: @@ -51,9 +54,11 @@ def envUpdate(): missing.append(env) if isMissingEnvs: - printstr = '' + printstr = "" [printstr + str(var) for var in missing] - print(f"The following environment variables are missing: {printstr}. Please add them to the .env file.") + print( + f"The following environment variables are missing: {printstr}. Please add them to the .env file." + ) exit(-1) From 886b8114a522be1280e80579fd6839f016bc7e24 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:47:40 -0400 Subject: [PATCH 46/49] fixes #557 stops it from ever happening again --- .env.template | 2 +- reddit/subreddit.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.env.template b/.env.template index 2e1ec9e..bcd326d 100644 --- a/.env.template +++ b/.env.template @@ -19,7 +19,7 @@ THEME="LIGHT" # used if you want to run multiple times. set to an int e.g. 4 or 29 and leave blank for 1 TIMES_TO_RUN="" # max number of characters a comment can have. -MAX_COMMENT_LENGTH="500" +MAX_COMMENT_LENGTH="500" # default is 500 # Range is 0 -> 1 recommended around 0.8-0.9 OPACITY="1" diff --git a/reddit/subreddit.py b/reddit/subreddit.py index 34a8e17..7c5db91 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -15,6 +15,13 @@ def textify(text): return "".join(filter(TEXT_WHITELIST.__contains__, text)) +def try_env(param, backup): + try: + return environ[param] + except KeyError: + return backup + + def get_subreddit_threads(): """ Returns a list of threads from the AskReddit subreddit. @@ -89,7 +96,7 @@ def get_subreddit_threads(): if top_level_comment.body in ["[removed]", "[deleted]"]: continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 if not top_level_comment.stickied: - if len(top_level_comment.body) <= int(environ["MAX_COMMENT_LENGTH"]): + if len(top_level_comment.body) <= int(try_env("MAX_COMMENT_LENGTH", 500)): content["comments"].append( { "comment_body": top_level_comment.body, From a36aca273e497cc14d9c1b48dc2ddc154b411a83 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:51:38 -0400 Subject: [PATCH 47/49] added init files to stop python from whining --- TTS/__init__.py | 0 utils/__init__.py | 0 video_creation/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 TTS/__init__.py create mode 100644 utils/__init__.py create mode 100644 video_creation/__init__.py diff --git a/TTS/__init__.py b/TTS/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/video_creation/__init__.py b/video_creation/__init__.py new file mode 100644 index 0000000..e69de29 From 56635835645a4b34d6605db098576893099bd80e Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:57:05 -0400 Subject: [PATCH 48/49] added timeout prevention --- video_creation/screenshot_downloader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index e27a2be..6147dff 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -40,7 +40,7 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): context.add_cookies(cookies) # load preference cookies # Get the thread screenshot page = context.new_page() - page.goto(reddit_object["thread_url"]) + page.goto(reddit_object["thread_url"], timeout=0) page.set_viewport_size(ViewportSize(width=1920, height=1080)) if page.locator('[data-testid="content-gate"]').is_visible(): # This means the post is NSFW and requires to click the proceed button. @@ -68,7 +68,7 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): if page.locator('[data-testid="content-gate"]').is_visible(): page.locator('[data-testid="content-gate"] button').click() - page.goto(f'https://reddit.com{comment["comment_url"]}') + page.goto(f'https://reddit.com{comment["comment_url"]}', timeout=0) page.locator(f"#t1_{comment['comment_id']}").screenshot( path=f"assets/temp/png/comment_{idx}.png" ) From a8b309765185afc8e7f87b8a4812cdf6db2e129e Mon Sep 17 00:00:00 2001 From: Jason <66544866+JasonLovesDoggo@users.noreply.github.com> Date: Sat, 18 Jun 2022 22:05:34 -0400 Subject: [PATCH 49/49] Update main.py Added version tag --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 6f478b1..f62e877 100755 --- a/main.py +++ b/main.py @@ -11,7 +11,7 @@ from video_creation.background import download_background, chop_background_video from video_creation.final_video import make_final_video from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts from video_creation.voices import save_text_to_mp3 - +VERSION = 2.1 print( """ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—