From 054cc84186b75e1ea58169a0e2f8baac8f9b8573 Mon Sep 17 00:00:00 2001 From: null3000 <76852813+null3000@users.noreply.github.com> Date: Fri, 10 Jun 2022 23:15:19 +0200 Subject: [PATCH 01/78] Update subreddit.py --- reddit/subreddit.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/reddit/subreddit.py b/reddit/subreddit.py index c560293..a448dcd 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -67,13 +67,14 @@ def get_subreddit_threads(): for top_level_comment in submission.comments: if not top_level_comment.stickied: - content["comments"].append( - { - "comment_body": top_level_comment.body, - "comment_url": top_level_comment.permalink, - "comment_id": top_level_comment.id, - } - ) + if not comment.author == None: + content["comments"].append( + { + "comment_body": top_level_comment.body, + "comment_url": top_level_comment.permalink, + "comment_id": top_level_comment.id, + } + ) except AttributeError as e: pass From d907dc79177124e5e6d42ed78129816bbf10fd0e Mon Sep 17 00:00:00 2001 From: null3000 <76852813+null3000@users.noreply.github.com> Date: Mon, 13 Jun 2022 14:03:14 -0700 Subject: [PATCH 02/78] fixed error --- reddit/subreddit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit/subreddit.py b/reddit/subreddit.py index a448dcd..b01b058 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -67,7 +67,7 @@ def get_subreddit_threads(): for top_level_comment in submission.comments: if not top_level_comment.stickied: - if not comment.author == None: + if not top_level_comment.author == None: content["comments"].append( { "comment_body": top_level_comment.body, From a37096f874907b61d782381eeb42a7cb6532c405 Mon Sep 17 00:00:00 2001 From: SinecKers Date: Sun, 19 Jun 2022 04:50:45 +0300 Subject: [PATCH 03/78] test --- ' | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ' diff --git a/' b/' new file mode 100644 index 0000000..e69de29 From c50931ab6ed3e064438494b247075118b970ddf2 Mon Sep 17 00:00:00 2001 From: SinecKers Date: Sun, 19 Jun 2022 04:57:07 +0300 Subject: [PATCH 04/78] downloader mods --- video_creation/screenshot_downloader.py | 59 ++++++++++++++++++++++--- video_creation/voices.py | 36 ++++++++++----- 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 6147dff..63f1f95 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -1,5 +1,6 @@ import json from os import getenv +import os from pathlib import Path from playwright.async_api import async_playwright @@ -10,6 +11,10 @@ from utils.console import print_step, print_substep import json from rich.console import Console +import translators as ts +from PIL import Image, ImageDraw, ImageFont +import textwrap + console = Console() storymode = False @@ -18,8 +23,8 @@ 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. + 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...") @@ -40,7 +45,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"], timeout=0) + 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. @@ -51,7 +56,33 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): '[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") + page.locator('[data-test-id="post-content"]').screenshot( + path="assets/temp/png/title.png" + ) + + # translate code + + if getenv("POSTLANG"): + print_substep("Translating post...") + texts_in_tl = ts.google(reddit_object["thread_title"], to_language=os.getenv("POSTLANG")) + + img = Image.open("assets/temp/png/title.png") + + width = img.size[0] + height = img.size[1] + + d1 = ImageDraw.Draw(img) + font = ImageFont.truetype("arial.ttf", 22) + + wrapper = textwrap.TextWrapper(width=60) + wrapped_str = wrapper.fill(text=texts_in_tl) + + d1.rectangle((7, 25, width - 20, height - 35), fill='white') + d1.text((10, 30), f"{wrapped_str}", font=font, fill=(0, 0, 0)) + img.save("assets/temp/png/title.png") + else: + print_substep("Skipping translation...") + if storymode: page.locator('[data-click-id="text"]').screenshot( path="assets/temp/png/story_content.png" @@ -60,7 +91,6 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): for idx, comment in track( enumerate(reddit_object["comments"]), "Downloading screenshots..." ): - # Stop if we have reached the screenshot_num if idx >= screenshot_num: break @@ -68,8 +98,25 @@ 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"]}', timeout=0) + 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" ) + + if getenv("POSTLANG"): + img_comment = Image.open(f"assets/temp/png/comment_{idx}.png") + width2 = img_comment.size[0] + height2 = img_comment.size[1] + + comment_tl = ts.google(comment["comment_body"], to_language=os.getenv("POSTLANG")) + + wrapper1 = textwrap.TextWrapper(width=78) + wrapped_str1 = wrapper1.fill(text=comment_tl) + + d2 = ImageDraw.Draw(img_comment) + font_comment = ImageFont.truetype("arial.ttf", 16) + + d2.rectangle((30, 40, width2 - 5, height2 - 35), fill='#F5F6F6') + d2.text((40, 50), f"{wrapped_str1}", font=font_comment, fill=(0, 0, 0)) + img_comment.save(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 be7da96..b66d7f4 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -10,11 +10,13 @@ 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() - +import translators as ts +import os VIDEO_LENGTH: int = 40 # secs @@ -22,39 +24,51 @@ 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. + 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) + + if os.getenv("POSTLANG"): + print_substep("Translating Texts...") + tl_title = ts.google(reddit_obj["thread_title"], to_language=os.getenv("POSTLANG")) + else: + print_substep("Skipping Translation...") + tl_title = reddit_obj["thread_title"] + TextToSpeech = TTS() TextToSpeech.tts( - sanitize_text(reddit_obj["thread_title"]), - filename="assets/temp/mp3/title.mp3", + sanitize_text(tl_title), + filename=f"assets/temp/mp3/title.mp3", random_speaker=False, ) try: - length += MP3("assets/temp/mp3/title.mp3").info.length + length += MP3(f"assets/temp/mp3/title.mp3").info.length except HeaderNotFoundError: # note to self AudioFileClip - length += sox.file_info.duration("assets/temp/mp3/title.mp3") + length += sox.file_info.duration(f"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", + filename=f"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 + if os.getenv("POSTLANG"): + tl_comment = ts.google(comment["comment_body"], to_language=os.getenv("POSTLANG")) + else: + tl_comment = comment["comment_body"] + TextToSpeech.tts( - sanitize_text(comment["comment_body"]), + sanitize_text(tl_comment), filename=f"assets/temp/mp3/{com}.mp3", random_speaker=False, ) From 4bb8d67e686fc9f9bee644e02c942f6db4610a8c Mon Sep 17 00:00:00 2001 From: SinecKers Date: Sun, 19 Jun 2022 05:00:59 +0300 Subject: [PATCH 05/78] tts language selection --- TTS/GTTS.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TTS/GTTS.py b/TTS/GTTS.py index fcbcb9b..0017bf4 100644 --- a/TTS/GTTS.py +++ b/TTS/GTTS.py @@ -1,5 +1,5 @@ from gtts import gTTS - +import os class GTTS: def tts( @@ -9,5 +9,5 @@ class GTTS: random_speaker=False, censor=False, ): - tts = gTTS(text=req_text, lang="en", slow=False) + tts = gTTS(text=req_text, lang=os.getenv("POSTLANG") or "en", slow=False) tts.save(f"{filename}") From 2828b6718d8955fac2ce4f8238cea61894aee51b Mon Sep 17 00:00:00 2001 From: SinecKers Date: Sun, 19 Jun 2022 05:08:40 +0300 Subject: [PATCH 06/78] .env.template change --- .env.template | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.env.template b/.env.template index bcd326d..0b2af14 100644 --- a/.env.template +++ b/.env.template @@ -1,3 +1,4 @@ +# This can be found in the email that reddit sent you when you created the app REDDIT_CLIENT_ID="" REDDIT_CLIENT_SECRET="" @@ -23,9 +24,14 @@ MAX_COMMENT_LENGTH="500" # default is 500 # Range is 0 -> 1 recommended around 0.8-0.9 OPACITY="1" -# see different voice options: todo: add docs -VOICE="Matthew" # e.g. en_us_002 -TTsChoice="polly" +# If you want to translate the comments to another language, set the language code here. +# If empty, no translation will be done. +POSTLANG="es" + +# see TTSwrapper.py for all valid options +VOICE="" # e.g. en_us_002 +TTsChoice="gtts" # todo add docs +# IMPORTANT NOTE: if you use translate, you need to set this gtts or set tiktok and use custom voice in your language # IN-PROGRESS - not yet implemented STORYMODE="False" From cf92e6f4c44a659b8d81e5f1e1fbc2c5e1c227b5 Mon Sep 17 00:00:00 2001 From: SinecKers Date: Sun, 19 Jun 2022 05:16:26 +0300 Subject: [PATCH 07/78] requirements update --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 490b0c1..20e2c77 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ pytube==12.1.0 requests==2.28.0 rich==12.4.4 sox==1.4.1 +translators==5.2.2 From c58fa10f53c06a1702f169105080f5e79f77eb61 Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Sun, 19 Jun 2022 21:25:30 +0100 Subject: [PATCH 08/78] Reduced code duplication in TTS engines --- .pylintrc | 2 +- TTS/GTTS.py | 23 +++++---- TTS/POLLY.py | 106 -------------------------------------- TTS/TikTok.py | 107 ++++++++++++--------------------------- TTS/aws_polly.py | 66 ++++++++++++++++++++++++ TTS/engine_wrapper.py | 99 ++++++++++++++++++++++++++++++++++++ TTS/streamlabs_polly.py | 53 +++++++++++++++++++ TTS/swapper.py | 24 --------- utils/console.py | 7 +++ utils/voice.py | 2 +- video_creation/voices.py | 102 +++++++++++++++---------------------- 11 files changed, 316 insertions(+), 275 deletions(-) delete mode 100644 TTS/POLLY.py create mode 100644 TTS/aws_polly.py create mode 100644 TTS/engine_wrapper.py create mode 100644 TTS/streamlabs_polly.py delete mode 100644 TTS/swapper.py diff --git a/.pylintrc b/.pylintrc index e3fead7..b03c808 100644 --- a/.pylintrc +++ b/.pylintrc @@ -149,7 +149,7 @@ disable=raw-checker-failed, suppressed-message, useless-suppression, deprecated-pragma, - use-symbolic-message-instead + use-symbolic-message-instead, attribute-defined-outside-init, invalid-name, missing-docstring, diff --git a/TTS/GTTS.py b/TTS/GTTS.py index fcbcb9b..a0df172 100644 --- a/TTS/GTTS.py +++ b/TTS/GTTS.py @@ -1,13 +1,18 @@ +#!/usr/bin/env python3 +import random from gtts import gTTS +max_chars = 0 + 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 __init__(self): + self.max_chars = 0 + self.voices = [] + + def run(self, text, filepath): + tts = gTTS(text=text, lang="en", slow=False) + tts.save(filepath) + + def randomvoice(self): + return random.choice(self.voices) diff --git a/TTS/POLLY.py b/TTS/POLLY.py deleted file mode 100644 index da1fae0..0000000 --- a/TTS/POLLY.py +++ /dev/null @@ -1,106 +0,0 @@ -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 -from requests.exceptions import JSONDecodeError - -voices = [ - "Brian", - "Emma", - "Russell", - "Joey", - "Matthew", - "Joanna", - "Kimberly", - "Amy", - "Geraint", - "Nicole", - "Justin", - "Ivy", - "Kendra", - "Salli", - "Raveena", -] - - -# 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, - 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() - - 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")) - - 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): - return random.choice(voices) diff --git a/TTS/TikTok.py b/TTS/TikTok.py index 662e498..ccec427 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -1,12 +1,7 @@ import base64 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 from requests.adapters import HTTPAdapter, Retry # from profanity_filter import ProfanityFilter @@ -67,75 +62,39 @@ 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 + self.URI_BASE = "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=" + self.max_chars = 330 + self.voices = {"human": human, "nonhuman": nonhuman, "noneng": noneng} + + def run(self, text, filepath, random_voice: bool = False): + # 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_voice + else (os.getenv("VOICE") or random.choice(self.voices["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 + r = requests.post( + f"{self.URI_BASE}{voice}&req_text={text}&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={text}&speaker_map_type=0" + ) + print(r.text) + vstr = [r.json()["data"]["v_str"]][0] + b64d = base64.b64decode(vstr) + + with open(filepath, "wb") as out: + out.write(b64d) + + def randomvoice(self): + return random.choice(self.voices["human"]) diff --git a/TTS/aws_polly.py b/TTS/aws_polly.py new file mode 100644 index 0000000..3bf7090 --- /dev/null +++ b/TTS/aws_polly.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +from boto3 import Session +from botocore.exceptions import BotoCoreError, ClientError +import sys +import os +import random + +voices = [ + "Brian", + "Emma", + "Russell", + "Joey", + "Matthew", + "Joanna", + "Kimberly", + "Amy", + "Geraint", + "Nicole", + "Justin", + "Ivy", + "Kendra", + "Salli", + "Raveena", +] + + +class AWSPolly: + def __init__(self): + self.max_chars = 0 + self.voices = voices + + def run(self, text, filepath, random_voice: bool = False): + session = Session(profile_name="polly") + polly = session.client("polly") + if random_voice: + voice = self.randomvoice() + else: + if not os.getenv("VOICE"): + return ValueError( + f"Please set the environment variable VOICE to a valid voice. options are: {voices}" + ) + voice = str(os.getenv("VOICE")).capitalize() + try: + # Request speech synthesis + response = polly.synthesize_speech( + Text=text, OutputFormat="mp3", VoiceId=voice, Engine="neural" + ) + except (BotoCoreError, ClientError) as error: + # The service returned an error, exit gracefully + print(error) + sys.exit(-1) + + # Access the audio stream from the response + if "AudioStream" in response: + file = open(filepath, "wb") + file.write(response["AudioStream"].read()) + file.close() + # print_substep(f"Saved Text {idx} to MP3 files successfully.", style="bold green") + + else: + # The response didn't contain audio data, exit gracefully + print("Could not stream audio") + sys.exit(-1) + + def randomvoice(self): + return random.choice(self.voices) diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py new file mode 100644 index 0000000..4c1c2c8 --- /dev/null +++ b/TTS/engine_wrapper.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +from pathlib import Path +from typing import Tuple +import re +from os import getenv +from mutagen.mp3 import MP3 +from rich.progress import track +from moviepy.editor import AudioFileClip, CompositeAudioClip, concatenate_audioclips +from utils.console import print_step, print_substep +from utils.voice import sanitize_text + + +class TTSEngine: + + """Calls the given TTS engine to reduce code duplication and allow multiple TTS engines. + + Args: + tts_module : The TTS module. Your module should handle the TTS itself and saving to the given path under the run method. + reddit_object : The reddit object that contains the posts to read. + path (Optional) : The unix style path to save the mp3 files to. This must not have leading or trailing slashes. + max_length (Optional) : The maximum length of the mp3 files in total. + + Notes: + tts_module must take the arguments text and filepath. + """ + + def __init__( + self, + tts_module, + reddit_object: dict, + path: str = "assets/mp3", + max_length: int = 50, + ): + self.tts_module = tts_module() + self.reddit_object = reddit_object + self.path = path + self.max_length = max_length + self.length = 0 + + def run(self) -> Tuple[int, int]: + + Path(self.path).mkdir(parents=True, exist_ok=True) + + # This file needs to be removed in case this post does not use post text, so that it wont appear in the final video + try: + Path(f"{self.path}/posttext.mp3").unlink() + except OSError: + pass + + print_step("Saving Text to MP3 files...") + + self.call_tts("title", self.reddit_object["thread_title"]) + if ( + self.reddit_object["thread_post"] != "" + and getenv("STORYMODE", "").casefold() == "true" + ): + + self.call_tts("posttext", sanitize_text(self.reddit_object["thread_post"])) + + idx = None + for idx, comment in track( + enumerate(self.reddit_object["comments"]), "Saving..." + ): + # ! Stop creating mp3 files if the length is greater than max length. + if self.length > self.max_length: + break + if not self.tts_module.max_chars: + self.call_tts(f"{idx}", sanitize_text(comment["comment_body"])) + else: + self.split_post(sanitize_text(comment["comment_body"]), idx) + + print_substep("Saved Text to MP3 files successfully.", style="bold green") + return self.length, idx + + def split_post(self, text: str, idx: int) -> str: + split_files = [] + split_text = [ + x.group().strip() + for x in re.finditer( + rf" *((.{{0,{self.tts_module.max_chars}}})(\.|.$))", text + ) + ] + + idy = None + for idy, text_cut in enumerate(split_text): + print(f"{idx}-{idy}: {text_cut}\n") + self.call_tts(f"{idx}-{idy}.part", text_cut) + split_files.append(AudioFileClip(f"{self.path}/{idx}-{idy}.part.mp3")) + CompositeAudioClip([concatenate_audioclips(split_files)]).write_audiofile( + f"{self.path}/{idx}.mp3", fps=44100, verbose=False, logger=None + ) + + for i in range(0, idy + 1): + print(f"Cleaning up {self.path}/{idx}-{i}.part.mp3") + Path(f"{self.path}/{idx}-{i}.part.mp3").unlink() + + def call_tts(self, filename: str, text: str): + self.tts_module.run(text=text, filepath=f"{self.path}/{filename}.mp3") + self.length += MP3(f"{self.path}/{filename}.mp3").info.length diff --git a/TTS/streamlabs_polly.py b/TTS/streamlabs_polly.py new file mode 100644 index 0000000..07f2c17 --- /dev/null +++ b/TTS/streamlabs_polly.py @@ -0,0 +1,53 @@ +import random +import os +import requests +from requests.exceptions import JSONDecodeError + +voices = [ + "Brian", + "Emma", + "Russell", + "Joey", + "Matthew", + "Joanna", + "Kimberly", + "Amy", + "Geraint", + "Nicole", + "Justin", + "Ivy", + "Kendra", + "Salli", + "Raveena", +] + + +# valid voices https://lazypy.ro/tts/ + + +class StreamlabsPolly: + def __init__(self): + self.url = "https://streamlabs.com/polly/speak" + self.max_chars = 550 + self.voices = voices + + def run(self, text, filepath, random_voice: bool = False): + if random_voice: + voice = self.randomvoice() + else: + if not os.getenv("VOICE"): + return ValueError( + f"Please set the environment variable VOICE to a valid voice. options are: {voices}" + ) + voice = str(os.getenv("VOICE")).capitalize() + body = {"voice": voice, "text": text, "service": "polly"} + response = requests.post(self.url, data=body) + try: + voice_data = requests.get(response.json()["speak_url"]) + with open(filepath, "wb") as f: + f.write(voice_data.content) + except (KeyError, JSONDecodeError): + print("Error occured calling Streamlabs Polly") + + def randomvoice(self): + return random.choice(self.voices) diff --git a/TTS/swapper.py b/TTS/swapper.py deleted file mode 100644 index c5f6776..0000000 --- a/TTS/swapper.py +++ /dev/null @@ -1,24 +0,0 @@ -from os import getenv - -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} - - -class TTS: - def __new__(cls): - load_dotenv() - 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") - return CHOICE_DIR.get(CHOICE)() diff --git a/utils/console.py b/utils/console.py index 11ee429..842c60a 100644 --- a/utils/console.py +++ b/utils/console.py @@ -4,6 +4,7 @@ from rich.markdown import Markdown from rich.padding import Padding from rich.panel import Panel from rich.text import Text +from rich.columns import Columns console = Console() @@ -25,3 +26,9 @@ def print_step(text): def print_substep(text, style=""): """Prints a rich info message without the panelling.""" console.print(text, style=style) + + +def print_table(items): + """Prints items in a table.""" + + console.print(Columns([Panel(f"[yellow]{item}", expand=True) for item in items])) diff --git a/utils/voice.py b/utils/voice.py index 120ee60..7aed2ad 100644 --- a/utils/voice.py +++ b/utils/voice.py @@ -17,6 +17,6 @@ def sanitize_text(text): # note: not removing apostrophes regex_expr = r"\s['|’]|['|’]\s|[\^_~@!&;#:\-%“”‘\"%\*/{}\[\]\(\)\\|<>=+]" result = re.sub(regex_expr, " ", result) - + result = result.replace("+", "plus").replace(" ", "+").replace("&", "and") # remove extra whitespace return " ".join(result.split()) diff --git a/video_creation/voices.py b/video_creation/voices.py index be7da96..9407d59 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -1,20 +1,25 @@ #!/usr/bin/env python3 -from os import getenv -from pathlib import Path +import os -import sox -from mutagen import MutagenError -from mutagen.mp3 import MP3, HeaderNotFoundError from rich.console import Console -from rich.progress import track -from TTS.swapper import TTS +from TTS.engine_wrapper import TTSEngine +from TTS.GTTS import GTTS +from TTS.streamlabs_polly import StreamlabsPolly +from TTS.aws_polly import AWSPolly +from TTS.TikTok import TikTok + +from utils.console import print_table, print_step -from utils.console import print_step, print_substep -from utils.voice import sanitize_text console = Console() +TTSProviders = { + "GoogleTranslate": GTTS, + "AWSPolly": AWSPolly, + "StreamlabsPolly": StreamlabsPolly, + "TikTok": TikTok, +} VIDEO_LENGTH: int = 40 # secs @@ -22,58 +27,35 @@ 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. + 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, + env = os.getenv("TTS_PROVIDER", "") + if env in TTSProviders: + text_to_mp3 = TTSEngine(env, reddit_obj) + else: + chosen = False + choice = "" + while not chosen: + print_step("Please choose one of the following TTS providers: ") + print_table(TTSProviders) + choice = input("\n") + if choice.casefold() not in map(lambda _: _.casefold(), TTSProviders): + print("Unknown Choice") + else: + chosen = True + text_to_mp3 = TTSEngine( + get_case_insensitive_key_value(TTSProviders, choice), reddit_obj ) - # '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 + return text_to_mp3.run() + - 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 +def get_case_insensitive_key_value(input_dict, key): + return next( + ( + value + for dict_key, value in input_dict.items() + if dict_key.lower() == key.lower() + ), + None, + ) From 7765c9b0186710793158fe54b9e96fee9902e87b Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Sun, 19 Jun 2022 21:55:13 +0100 Subject: [PATCH 09/78] Fix small bugs and path issues --- TTS/engine_wrapper.py | 3 +-- reddit/subreddit.py | 18 +++++++++++++----- utils/voice.py | 2 +- video_creation/data/videos.json | 10 +++++++++- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py index 4c1c2c8..5e4ddb0 100644 --- a/TTS/engine_wrapper.py +++ b/TTS/engine_wrapper.py @@ -28,7 +28,7 @@ class TTSEngine: self, tts_module, reddit_object: dict, - path: str = "assets/mp3", + path: str = "assets/temp/mp3", max_length: int = 50, ): self.tts_module = tts_module() @@ -54,7 +54,6 @@ class TTSEngine: self.reddit_object["thread_post"] != "" and getenv("STORYMODE", "").casefold() == "true" ): - self.call_tts("posttext", sanitize_text(self.reddit_object["thread_post"])) idx = None diff --git a/reddit/subreddit.py b/reddit/subreddit.py index 7c5db91..124380b 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -31,7 +31,9 @@ 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") @@ -55,14 +57,18 @@ 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? ")) + 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") + 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. @@ -83,12 +89,14 @@ def get_subreddit_threads(): 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_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["thread_post"] = submission.selftext content["comments"] = [] for top_level_comment in submission.comments: if isinstance(top_level_comment, MoreComments): diff --git a/utils/voice.py b/utils/voice.py index 7aed2ad..63e8eff 100644 --- a/utils/voice.py +++ b/utils/voice.py @@ -17,6 +17,6 @@ def sanitize_text(text): # note: not removing apostrophes regex_expr = r"\s['|’]|['|’]\s|[\^_~@!&;#:\-%“”‘\"%\*/{}\[\]\(\)\\|<>=+]" result = re.sub(regex_expr, " ", result) - result = result.replace("+", "plus").replace(" ", "+").replace("&", "and") + result = result.replace("+", "plus").replace("&", "and") # remove extra whitespace return " ".join(result.split()) diff --git a/video_creation/data/videos.json b/video_creation/data/videos.json index fe51488..8969b7b 100644 --- a/video_creation/data/videos.json +++ b/video_creation/data/videos.json @@ -1 +1,9 @@ -[] +[ + { + "id": "vfxpph", + "time": "1655671664", + "background_credit": "bbswitzer", + "reddit_title": "What unimpressive things are people idiotically proud of", + "filename": "What unimpressive things are p....mp4" + } +] \ No newline at end of file From 4de5d83ccd16ceab75a755c93bb527020a7c2265 Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Sun, 19 Jun 2022 22:12:05 +0100 Subject: [PATCH 10/78] Actualise requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 490b0c1..c2336e7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ +boto3==1.24.12 +botocore==1.27.12 gTTS==2.2.4 moviepy==1.0.3 mutagen==1.45.1 @@ -7,4 +9,3 @@ python-dotenv==0.20.0 pytube==12.1.0 requests==2.28.0 rich==12.4.4 -sox==1.4.1 From b2b4a087417d19a3aba8b886df14fcab9a93c9a3 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Mon, 20 Jun 2022 01:34:46 +0300 Subject: [PATCH 11/78] updated checker.py, new .env.template syntax --- .env.template | 43 +++++++++++++----- main.py | 8 +++- setup.py | 67 +++++++--------------------- utils/checker.py | 76 ++++++++++++++++++++++++++++++++ utils/console.py | 39 ++++++++++++++++ utils/scripts/FileGrabber.ps1 | 9 ---- utils/scripts/FileGrabberenv.ps1 | 9 ---- 7 files changed, 168 insertions(+), 83 deletions(-) create mode 100755 utils/checker.py delete mode 100644 utils/scripts/FileGrabber.ps1 delete mode 100644 utils/scripts/FileGrabberenv.ps1 diff --git a/.env.template b/.env.template index bcd326d..6a4923c 100644 --- a/.env.template +++ b/.env.template @@ -1,31 +1,52 @@ REDDIT_CLIENT_ID="" +#EXPLANATION the ID of your Reddit app of SCRIPT type + REDDIT_CLIENT_SECRET="" +#EXPLANATION the SECRET of your Reddit app of SCRIPT type REDDIT_USERNAME="" +#EXPLANATION the username of your reddit account + REDDIT_PASSWORD="" +#EXPLANATION the password of your reddit account -# If no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: "no" +#OPTIONAL RANDOM_THREAD="no" +#EXPLANATION If set to no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: "no" -# Valid options are "yes" and "no" for the variable below REDDIT_2FA="" +#MATCH_REGEX ^(yes|no) +#EXPLANATION Whether you have Reddit 2FA enabled, Valid options are "yes" and "no" + SUBREDDIT="AskReddit" -# True or False ALLOW_NSFW="False" -# Used if you want to use a specific post. example of one is urdtfx +#EXPLANATION Whether to allow NSFW content, True or False +#MATCH_REGEX ^(True|False)$ + POST_ID="" -#set to either LIGHT or DARK +#MATCH_REGEX ^((?!://|://).)*$ +#EXPLANATION Used if you want to use a specific post. example of one is urdtfx + THEME="LIGHT" -# used if you want to run multiple times. set to an int e.g. 4 or 29 and leave blank for 1 +#EXPLANATION sets the Reddit theme, either LIGHT or DARK + TIMES_TO_RUN="" -# max number of characters a comment can have. -MAX_COMMENT_LENGTH="500" # default is 500 -# Range is 0 -> 1 recommended around 0.8-0.9 +#EXPLANATION used if you want to run multiple times. set to an int e.g. 4 or 29 and leave blank for 1 + +MAX_COMMENT_LENGTH="500" +#EXPLANATION max number of characters a comment can have. default is 500 + +#OPTIONAL OPACITY="1" +#EXPLANATION sets the opacity of the comments, Range is 0 -> 1 recommended around 0.8-0.9 # see different voice options: todo: add docs -VOICE="Matthew" # e.g. en_us_002 +VOICE="Matthew" +#EXPLANATION sets the voice the TTS uses, e.g. en_us_002 + TTsChoice="polly" +#EXPLANATION the backend used for TTS, default is polly -# IN-PROGRESS - not yet implemented +#OPTIONAL STORYMODE="False" +#EXPLANATION IN-PROGRESS - not yet implemented diff --git a/main.py b/main.py index 3ca7738..75863d9 100755 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python import time from subprocess import Popen @@ -6,11 +7,14 @@ 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.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 from video_creation.voices import save_text_to_mp3 +from utils.checker import check_env + VERSION = 2.1 print( """ @@ -28,7 +32,6 @@ 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/)" ) -time.sleep(1) client_id = getenv("REDDIT_CLIENT_ID") client_secret = getenv("REDDIT_CLIENT_SECRET") @@ -38,7 +41,8 @@ reddit2fa = getenv("REDDIT_2FA") def main(): - #envUpdate() + if check_env() is not True: + exit() cleanup() def get_obj(): diff --git a/setup.py b/setup.py index a8d7f12..6063e5a 100755 --- a/setup.py +++ b/setup.py @@ -10,51 +10,14 @@ from utils.console import print_markdown from utils.console import print_step from rich.console import Console from utils.loader import Loader +from utils.console import handle_input console = Console() -def handle_input( - 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 - - 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" + console.print( + "[red]WARNING: 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() @@ -89,15 +52,15 @@ if input("Are you sure you want to continue? > ").strip().casefold() != "yes": console.print("[bold green]Alright! Let's get started!") print() -console.log("Ensure you have the following ready to enter:") -console.log("[bold green]Reddit Client ID") -console.log("[bold green]Reddit Client Secret") -console.log("[bold green]Reddit Username") -console.log("[bold green]Reddit Password") -console.log("[bold green]Reddit 2FA (yes or no)") -console.log("[bold green]Opacity (range of 0-1, decimals are OK)") -console.log("[bold green]Subreddit (without r/ or /r/)") -console.log("[bold green]Theme (light or dark)") +console.print("Ensure you have the following ready to enter:") +console.print("[bold green]Reddit Client ID") +console.print("[bold green]Reddit Client Secret") +console.print("[bold green]Reddit Username") +console.print("[bold green]Reddit Password") +console.print("[bold green]Reddit 2FA (yes or no)") +console.print("[bold green]Opacity (range of 0-1, decimals are OK)") +console.print("[bold green]Subreddit (without r/ or /r/)") +console.print("[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." ) @@ -117,7 +80,7 @@ console.print("[bold green]Alright! Let's get started!") # Begin the setup process. -console.log("Enter your credentials now.") +console.print("Enter your credentials now.") client_id = handle_input( "Client ID > ", False, @@ -178,7 +141,7 @@ theme = handle_input( ) 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...") +console.print("Writing to the .env file...") with open(".env", "w") as f: f.write( f"""REDDIT_CLIENT_ID="{client_id}" @@ -199,7 +162,7 @@ with open(".setup-done-before", "w") as f: loader.stop() -console.log("[bold green]Setup Complete! Returning...") +console.print("[bold green]Setup Complete! Returning...") # Post-Setup: send message and try to run main.py again. subprocess.call("python3 main.py", shell=True) diff --git a/utils/checker.py b/utils/checker.py new file mode 100755 index 0000000..75617b1 --- /dev/null +++ b/utils/checker.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +import os +from rich.console import Console +import re +import dotenv +from utils.console import handle_input + +console = Console() + +success = True + + +def check_env() -> bool: + if not os.path.exists(".env.template"): + console.print("[red]Couldn't find .env.template. Unable to check variables.") + return False + with open(".env.template", "r") as template: + # req_envs = [env.split("=")[0] for env in template.readlines() if "=" in env] + matching = {} + explanations = {} + req_envs = [] + var_optional = False + for line in template.readlines(): + if "=" in line and var_optional is not True: + req_envs.append(line.split("=")[0]) + elif "#OPTIONAL" in line: + var_optional = True + elif line.startswith("#MATCH_REGEX "): + matching[req_envs[-1]] = line.removeprefix("#MATCH_REGEX ")[:-1] + var_optional = False + elif line.startswith("#EXPLANATION "): + explanations[req_envs[-1]] = line.removeprefix("#EXPLANATION ")[:-1] + var_optional = False + else: + var_optional = False + missing = [] + incorrect = [] + dotenv.load_dotenv() + for env in req_envs: + value = os.getenv(env) + if value is None: + missing.append(env) + continue + if env in matching.keys(): + env, re.match(matching[env], value) is None and incorrect.append(env) + if len(missing): + for i in range(len(missing)): + try: + missing[i] = missing[i] + ": " + explanations[missing[i]] + except KeyError: + pass + console.print( + f"[red]{'These variables are'*(len(missing) > 1) or 'This variable is'} non-optional and missing: \n\n" + + "\n\n".join(missing) + ) + success = False + if len(incorrect): + console.print( + f"[red]{'These variables are'*(len(incorrect) > 1) or 'This variable is'} set incorrectly: " + + "\n".join(incorrect) + ) + success = False + # if success is True: + # return True + # console.print("[green]Do you want to enter the missing variables by hand(y/n)") + # if not input().casefold().startswith("y"): + # console.print("[red]Aborting: Unresolved missing variables") + # return success + # with open(".env", "a") as env_file: + # for env in missing: + # pass + return success + + +if __name__ == "__main__": + check_env() diff --git a/utils/console.py b/utils/console.py index 11ee429..83736ac 100644 --- a/utils/console.py +++ b/utils/console.py @@ -4,6 +4,7 @@ from rich.markdown import Markdown from rich.padding import Padding from rich.panel import Panel from rich.text import Text +import re console = Console() @@ -25,3 +26,41 @@ def print_step(text): def print_substep(text, style=""): """Prints a rich info message without the panelling.""" console.print(text, style=style) + + +def handle_input( + 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.print("[red]" + oob_error) # Input too low failstate + continue + if nmax is not None and user_input > nmax: + console.print("[red]" + oob_error) # Input too high + continue + break # Successful type conversion and number in bounds + except ValueError: + console.print("[red]" + err_message) # Type conversion failed + continue + if nmin is not None and len(user_input) < nmin: # Check if string is long enough + console.print("[red]" + oob_error) + continue + if nmax is not None and len(user_input) > nmax: # Check if string is not too long + console.print("[red]" + oob_error) + continue + break + console.print("[red]" + err_message) + + return user_input diff --git a/utils/scripts/FileGrabber.ps1 b/utils/scripts/FileGrabber.ps1 deleted file mode 100644 index a820d2e..0000000 --- a/utils/scripts/FileGrabber.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -$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/scripts/FileGrabberenv.ps1 b/utils/scripts/FileGrabberenv.ps1 deleted file mode 100644 index ffb021b..0000000 --- a/utils/scripts/FileGrabberenv.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -$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 ".\video_creation\data\envvars.txt" -Remove-Item ".\envVarsbefSpl.txt" -Remove-Item ".\envVarsN.txt" - -Write-Host $nowSplit From 63e7dc04efeb31fa3ca6802208005d5510dfa403 Mon Sep 17 00:00:00 2001 From: SinecKers Date: Tue, 21 Jun 2022 00:07:09 +0300 Subject: [PATCH 12/78] default voice --- .env.template | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.template b/.env.template index 0b2af14..6a8a15e 100644 --- a/.env.template +++ b/.env.template @@ -26,11 +26,11 @@ OPACITY="1" # If you want to translate the comments to another language, set the language code here. # If empty, no translation will be done. -POSTLANG="es" +POSTLANG="" # see TTSwrapper.py for all valid options -VOICE="" # e.g. en_us_002 -TTsChoice="gtts" # todo add docs +VOICE="Matthew" # e.g. en_us_002 +TTsChoice="polly" # todo add docs # IMPORTANT NOTE: if you use translate, you need to set this gtts or set tiktok and use custom voice in your language # IN-PROGRESS - not yet implemented From fe97a006483da75eb73abe111d6d5e4162e38cfb Mon Sep 17 00:00:00 2001 From: SinecKers Date: Tue, 21 Jun 2022 00:18:08 +0300 Subject: [PATCH 13/78] forgot smt --- video_creation/screenshot_downloader.py | 26 +++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 63f1f95..1caa909 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -45,7 +45,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. @@ -77,8 +77,15 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): wrapper = textwrap.TextWrapper(width=60) wrapped_str = wrapper.fill(text=texts_in_tl) - d1.rectangle((7, 25, width - 20, height - 35), fill='white') - d1.text((10, 30), f"{wrapped_str}", font=font, fill=(0, 0, 0)) + if (getenv("THEME").upper() == "DARK"): + fillmode = "#1a1a1b" + textmode = (255, 255, 255) + else: + fillmode = "whiite" + textmode = (0, 0, 0) + + d1.rectangle((7, 25, width - 20, height - 35), fill=fillmode) + d1.text((10, 30), f"{wrapped_str}", font=font, fill=textmode) img.save("assets/temp/png/title.png") else: print_substep("Skipping translation...") @@ -98,7 +105,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" ) @@ -116,7 +123,14 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): d2 = ImageDraw.Draw(img_comment) font_comment = ImageFont.truetype("arial.ttf", 16) - d2.rectangle((30, 40, width2 - 5, height2 - 35), fill='#F5F6F6') - d2.text((40, 50), f"{wrapped_str1}", font=font_comment, fill=(0, 0, 0)) + if (getenv("THEME").upper() == "DARK"): + fillmode1 = "#242426" + textmode1 = (255, 255, 255) + else: + fillmode1 = "#F5F6F6" + textmode1 = (0, 0, 0) + + d2.rectangle((30, 40, width2 - 5, height2 - 35), fill=fillmode1) + d2.text((40, 50), f"{wrapped_str1}", font=font_comment, fill=textmode1) img_comment.save(f"assets/temp/png/comment_{idx}.png") print_substep("Screenshots downloaded Successfully.", style="bold green") From eef5ca32aaff327d39bcd5c8bd8fc1d5bb8326a7 Mon Sep 17 00:00:00 2001 From: CJthisis <99442347+CJthisis@users.noreply.github.com> Date: Tue, 21 Jun 2022 13:18:56 +0200 Subject: [PATCH 14/78] Sox and Path variables Many people seem to have problems with SoX and the associated path variables, this would (hopefully) fix the issue. --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 26b5814..c85b377 100644 --- a/README.md +++ b/README.md @@ -44,15 +44,18 @@ 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. Add the directory of your SoX installation to the Path system variables (see https://github.com/elebumm/RedditVideoMakerBot/issues/613) + and move the added directory all the way to the top of the list of directories. -4. Run `pip install -r requirements.txt` +5. Run `pip install -r requirements.txt` -5. Run `playwright install` and `playwright install-deps`. (if this fails try adding python -m to the front of the command) +6. Run `playwright install` and `playwright install-deps`. (if this fails try adding python -m to the front of the command) -6. Run `python main.py` (unless you chose automatic install, then the installer will automatically run main.py) +7. 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 😎 +8. 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 From 629b74c5d476051a171bc07566aa712a6aa4ed05 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Tue, 21 Jun 2022 16:26:06 +0300 Subject: [PATCH 15/78] completely revampted .env.template syntax, shiny new checker, better formatting and more --- .env.template | 30 ++++- .github/workflows/codeql-analysis.yml | 97 +++++++-------- main.py | 13 +- utils/checker.py | 169 +++++++++++++++++++++----- utils/console.py | 5 +- 5 files changed, 220 insertions(+), 94 deletions(-) diff --git a/.env.template b/.env.template index 6a4923c..ed85764 100644 --- a/.env.template +++ b/.env.template @@ -1,24 +1,40 @@ REDDIT_CLIENT_ID="" #EXPLANATION the ID of your Reddit app of SCRIPT type +#RANGE 12:30 +#MATCH_REGEX [-a-zA-Z0-9._~+/]+=*$ +#OOB_ERROR The ID should be over 12 and under 30 characters, double check your input. REDDIT_CLIENT_SECRET="" #EXPLANATION the SECRET of your Reddit app of SCRIPT type +#RANGE 20:40 +#MATCH_REGEX [-a-zA-Z0-9._~+/]+=*$ +#OOB_ERROR The secret should be over 20 and under 40 characters, double check your input. REDDIT_USERNAME="" #EXPLANATION the username of your reddit account +#RANGE 3:20 +#MATCH_REGEX [_0-9a-zA-Z]+$ +#OOB_ERROR A username HAS to be between 3 and 20 characters REDDIT_PASSWORD="" #EXPLANATION the password of your reddit account +#RANGE 8:None +#OOB_ERROR Password too short #OPTIONAL RANDOM_THREAD="no" -#EXPLANATION If set to no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: "no" +# If set to no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: "no" REDDIT_2FA="" #MATCH_REGEX ^(yes|no) #EXPLANATION Whether you have Reddit 2FA enabled, Valid options are "yes" and "no" SUBREDDIT="AskReddit" +#EXPLANATION what subreddit to pull posts from, the name of the sub, not the URL +#RANGE 3:20 +#MATCH_REGEX [_0-9a-zA-Z]+$ +#OOB_ERROR A subreddit name HAS to be between 3 and 20 characters + ALLOW_NSFW="False" #EXPLANATION Whether to allow NSFW content, True or False #MATCH_REGEX ^(True|False)$ @@ -29,16 +45,22 @@ POST_ID="" THEME="LIGHT" #EXPLANATION sets the Reddit theme, either LIGHT or DARK +#MATCH_REGEX ^(dark|light|DARK|LIGHT)$ TIMES_TO_RUN="" #EXPLANATION used if you want to run multiple times. set to an int e.g. 4 or 29 and leave blank for 1 MAX_COMMENT_LENGTH="500" #EXPLANATION max number of characters a comment can have. default is 500 +#RANGE 0:10000 +#MATCH_TYPE int +#OOB_ERROR the max comment length should be between 0 and 10000 -#OPTIONAL OPACITY="1" -#EXPLANATION sets the opacity of the comments, Range is 0 -> 1 recommended around 0.8-0.9 +#EXPLANATION Sets the opacity of the comments when overlayed over the background +#RANGE 0:1 +#MATCH_TYPE float +#OOB_ERROR The opacity HAS to be between 0 and 1 # see different voice options: todo: add docs VOICE="Matthew" @@ -49,4 +71,4 @@ TTsChoice="polly" #OPTIONAL STORYMODE="False" -#EXPLANATION IN-PROGRESS - not yet implemented +# IN-PROGRESS - not yet implemented diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 835b4fb..238dad4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,3 +1,4 @@ + # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # @@ -12,61 +13,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 + + steps: + - name: Checkout repository + uses: actions/checkout@v3 - 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 + # 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. - steps: - - name: Checkout repository - uses: actions/checkout@v3 + # 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/main.py b/main.py index 75863d9..9998f44 100755 --- a/main.py +++ b/main.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -import time from subprocess import Popen from dotenv import load_dotenv @@ -26,23 +25,16 @@ print( ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ """ ) -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. You can find solutions to many common problems in the [Documentation](https://luka-hietala.gitbook.io/documentation-for-the-reddit-bot/)" ) -client_id = getenv("REDDIT_CLIENT_ID") -client_secret = getenv("REDDIT_CLIENT_SECRET") -username = getenv("REDDIT_USERNAME") -password = getenv("REDDIT_PASSWORD") -reddit2fa = getenv("REDDIT_2FA") - - def main(): if check_env() is not True: exit() + load_dotenv() cleanup() def get_obj(): @@ -58,8 +50,7 @@ def main(): def run_many(times): - for x in range(times): - x = x + 1 + for x in range(1, times + 1): print_step( f'on the {x}{("st" if x == 1 else ("nd" if x == 2 else ("rd" if x == 3 else "th")))} iteration of {times}' ) # correct 1st 2nd 3rd 4th 5th.... diff --git a/utils/checker.py b/utils/checker.py index 75617b1..5735bda 100755 --- a/utils/checker.py +++ b/utils/checker.py @@ -1,75 +1,184 @@ #!/usr/bin/env python import os from rich.console import Console +from rich.table import Table +from rich import box import re import dotenv from utils.console import handle_input console = Console() -success = True - def check_env() -> bool: if not os.path.exists(".env.template"): console.print("[red]Couldn't find .env.template. Unable to check variables.") - return False + return True + if not os.path.exists(".env"): + console.print("[red]Couldn't find the .env file, creating one now.") + with open(".env", "x") as file: + file.write("") with open(".env.template", "r") as template: # req_envs = [env.split("=")[0] for env in template.readlines() if "=" in env] matching = {} explanations = {} + bounds = {} + types = {} + oob_errors = {} req_envs = [] var_optional = False for line in template.readlines(): - if "=" in line and var_optional is not True: + if line.startswith("#") is not True and "=" in line and var_optional is not True: req_envs.append(line.split("=")[0]) elif "#OPTIONAL" in line: var_optional = True elif line.startswith("#MATCH_REGEX "): matching[req_envs[-1]] = line.removeprefix("#MATCH_REGEX ")[:-1] var_optional = False + elif line.startswith("#OOB_ERROR "): + oob_errors[req_envs[-1]] = line.removeprefix("#OOB_ERROR ")[:-1] + var_optional = False + elif line.startswith("#RANGE "): + bounds[req_envs[-1]] = tuple( + map( + lambda x: float(x) if x != "None" else None, + line.removeprefix("#RANGE ")[:-1].split(":"), + ) + ) + var_optional = False + elif line.startswith("#MATCH_TYPE "): + types[req_envs[-1]] = eval(line.removeprefix("#MATCH_TYPE ")[:-1].split()[0]) + var_optional = False elif line.startswith("#EXPLANATION "): explanations[req_envs[-1]] = line.removeprefix("#EXPLANATION ")[:-1] var_optional = False else: var_optional = False - missing = [] - incorrect = [] + missing = set() + incorrect = set() dotenv.load_dotenv() for env in req_envs: value = os.getenv(env) if value is None: - missing.append(env) + missing.add(env) continue if env in matching.keys(): - env, re.match(matching[env], value) is None and incorrect.append(env) - if len(missing): - for i in range(len(missing)): + re.match(matching[env], value) is None and incorrect.add(env) + if env in bounds.keys() and env not in types.keys(): + len(value) >= bounds[env][0] or ( + len(bounds[env]) > 1 and bounds[env][1] >= len(value) + ) or incorrect.add(env) + continue + if env in types.keys(): try: - missing[i] = missing[i] + ": " + explanations[missing[i]] - except KeyError: - pass - console.print( - f"[red]{'These variables are'*(len(missing) > 1) or 'This variable is'} non-optional and missing: \n\n" - + "\n\n".join(missing) + temp = types[env](value) + if env in bounds.keys(): + (bounds[env][0] <= temp or incorrect.add(env)) and len(bounds[env]) > 1 and ( + bounds[env][1] >= temp or incorrect.add(env) + ) + except ValueError: + incorrect.add(env) + + if len(missing): + table = Table( + title="Missing variables", + highlight=True, + show_lines=True, + box=box.ROUNDED, + border_style="#414868", + header_style="#C0CAF5 bold", + title_justify="left", + title_style="#C0CAF5 bold", ) + table.add_column("Variable", justify="left", style="#7AA2F7 bold", no_wrap=True) + table.add_column("Explanation", justify="left", style="#BB9AF7", no_wrap=False) + table.add_column("Type", justify="center", style="#F7768E", no_wrap=True) + table.add_column("Min", justify="right", style="#F7768E", no_wrap=True) + table.add_column("Max", justify="left", style="#F7768E", no_wrap=True) + for env in missing: + table.add_row( + env, + explanations[env] if env in explanations.keys() else "No explanation given", + str(types[env].__name__) if env in types.keys() else "str", + str(bounds[env][0]) if env in bounds.keys() and bounds[env][1] is not None else "", + str(bounds[env][1]) + if env in bounds.keys() and len(bounds[env]) > 1 and bounds[env][1] is not None + else "", + ) + console.print(table) success = False if len(incorrect): - console.print( - f"[red]{'These variables are'*(len(incorrect) > 1) or 'This variable is'} set incorrectly: " - + "\n".join(incorrect) + table = Table( + title="Incorrect variables", + highlight=True, + show_lines=True, + box=box.ROUNDED, + border_style="#414868", + header_style="#C0CAF5 bold", + title_justify="left", + title_style="#C0CAF5 bold", ) - success = False - # if success is True: - # return True - # console.print("[green]Do you want to enter the missing variables by hand(y/n)") - # if not input().casefold().startswith("y"): - # console.print("[red]Aborting: Unresolved missing variables") - # return success - # with open(".env", "a") as env_file: - # for env in missing: - # pass - return success + table.add_column("Variable", justify="left", style="#7AA2F7 bold", no_wrap=True) + table.add_column("Current value", justify="left", style="#F7768E", no_wrap=False) + table.add_column("Explanation", justify="left", style="#BB9AF7", no_wrap=False) + table.add_column("Type", justify="center", style="#F7768E", no_wrap=True) + table.add_column("Min", justify="right", style="#F7768E", no_wrap=True) + table.add_column("Max", justify="left", style="#F7768E", no_wrap=True) + for env in incorrect: + table.add_row( + env, + os.getenv(env), + explanations[env] if env in explanations.keys() else "No explanation given", + str(types[env].__name__) if env in types.keys() else "str", + str(bounds[env][0]) if env in bounds.keys() else "None", + str(bounds[env][1]) if env in bounds.keys() and len(bounds[env]) > 1 else "None", + ) + missing.add(env) + console.print(table) + if success is True: + return True + console.print( + "[green]Do you want to automatically overwrite incorrect variables and add the missing variables? (y/n)" + ) + if not input().casefold().startswith("y"): + console.print("[red]Aborting: Unresolved missing variables") + return False + if len(incorrect): + with open(".env", "r+") as env_file: + lines = [] + for line in env_file.readlines(): + line.split("=")[0].strip() not in incorrect and lines.append(line) + env_file.seek(0) + env_file.write("\n".join(lines)) + env_file.truncate() + console.print("[green]Successfully removed incorrectly set variables from .env") + with open(".env", "a") as env_file: + for env in missing: + env_file.write( + env + + "=" + + ('"' if env not in types.keys() else "") + + str( + handle_input( + "[#F7768E bold]" + env + "[#C0CAF5 bold]=", + types[env] if env in types.keys() else False, + matching[env] if env in matching.keys() else ".*", + explanations[env] + if env in explanations.keys() + else "Incorrect input. Try again.", + bounds[env][0] if env in bounds.keys() else None, + bounds[env][1] if env in bounds.keys() and len(bounds[env]) > 1 else None, + oob_errors[env] if env in oob_errors.keys() else "Input too long/short.", + extra_info="[#C0CAF5 bold]⮶ " + + ( + explanations[env] if env in explanations.keys() else "No info available" + ), + ) + ) + + ('"' if env not in types.keys() else "") + + "\n" + ) + return True if __name__ == "__main__": diff --git a/utils/console.py b/utils/console.py index 83736ac..905b2aa 100644 --- a/utils/console.py +++ b/utils/console.py @@ -36,10 +36,13 @@ def handle_input( nmin=None, nmax=None, oob_error="", + extra_info="", ): match = re.compile(match + "$") + console.print(extra_info, no_wrap=True) while True: - user_input = input(message + "\n> ").strip() + console.print(message, end="") + user_input = input("").strip() if re.match(match, user_input) is not None: if check_type is not False: try: From dd56fc87dfec93ddbc2a4f6033b5a34589b09641 Mon Sep 17 00:00:00 2001 From: CordlessCoder <42666308+CordlessCoder@users.noreply.github.com> Date: Tue, 21 Jun 2022 17:41:26 +0300 Subject: [PATCH 16/78] Added example support --- utils/checker.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/utils/checker.py b/utils/checker.py index 5735bda..07b1e15 100755 --- a/utils/checker.py +++ b/utils/checker.py @@ -18,6 +18,7 @@ def check_env() -> bool: console.print("[red]Couldn't find the .env file, creating one now.") with open(".env", "x") as file: file.write("") + success = True with open(".env.template", "r") as template: # req_envs = [env.split("=")[0] for env in template.readlines() if "=" in env] matching = {} @@ -25,11 +26,14 @@ def check_env() -> bool: bounds = {} types = {} oob_errors = {} + examples = {} req_envs = [] var_optional = False for line in template.readlines(): if line.startswith("#") is not True and "=" in line and var_optional is not True: req_envs.append(line.split("=")[0]) + if "#" in line: + examples[line.split("=")[0]] = "#".join(line.split("#")[1:]).strip() elif "#OPTIONAL" in line: var_optional = True elif line.startswith("#MATCH_REGEX "): @@ -92,14 +96,14 @@ def check_env() -> bool: ) table.add_column("Variable", justify="left", style="#7AA2F7 bold", no_wrap=True) table.add_column("Explanation", justify="left", style="#BB9AF7", no_wrap=False) - table.add_column("Type", justify="center", style="#F7768E", no_wrap=True) + table.add_column("Example", justify="center", style="#F7768E", no_wrap=True) table.add_column("Min", justify="right", style="#F7768E", no_wrap=True) table.add_column("Max", justify="left", style="#F7768E", no_wrap=True) for env in missing: table.add_row( env, explanations[env] if env in explanations.keys() else "No explanation given", - str(types[env].__name__) if env in types.keys() else "str", + examples[env] if env in examples.keys() else "", str(bounds[env][0]) if env in bounds.keys() and bounds[env][1] is not None else "", str(bounds[env][1]) if env in bounds.keys() and len(bounds[env]) > 1 and bounds[env][1] is not None @@ -121,7 +125,7 @@ def check_env() -> bool: table.add_column("Variable", justify="left", style="#7AA2F7 bold", no_wrap=True) table.add_column("Current value", justify="left", style="#F7768E", no_wrap=False) table.add_column("Explanation", justify="left", style="#BB9AF7", no_wrap=False) - table.add_column("Type", justify="center", style="#F7768E", no_wrap=True) + table.add_column("Example", justify="center", style="#F7768E", no_wrap=True) table.add_column("Min", justify="right", style="#F7768E", no_wrap=True) table.add_column("Max", justify="left", style="#F7768E", no_wrap=True) for env in incorrect: @@ -135,6 +139,7 @@ def check_env() -> bool: ) missing.add(env) console.print(table) + success = False if success is True: return True console.print( From eb0094a2ec52a9f3eecdd903de6eeeb73e562366 Mon Sep 17 00:00:00 2001 From: CordlessCoder <42666308+CordlessCoder@users.noreply.github.com> Date: Tue, 21 Jun 2022 17:41:45 +0300 Subject: [PATCH 17/78] Update .env.template --- .env.template | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.env.template b/.env.template index ed85764..fcfc739 100644 --- a/.env.template +++ b/.env.template @@ -1,22 +1,22 @@ -REDDIT_CLIENT_ID="" +REDDIT_CLIENT_ID="" #fFAGRNJru1FTz70BzhT3Zg #EXPLANATION the ID of your Reddit app of SCRIPT type #RANGE 12:30 #MATCH_REGEX [-a-zA-Z0-9._~+/]+=*$ #OOB_ERROR The ID should be over 12 and under 30 characters, double check your input. -REDDIT_CLIENT_SECRET="" +REDDIT_CLIENT_SECRET="" #fFAGRNJru1FTz70BzhT3Zg #EXPLANATION the SECRET of your Reddit app of SCRIPT type #RANGE 20:40 #MATCH_REGEX [-a-zA-Z0-9._~+/]+=*$ #OOB_ERROR The secret should be over 20 and under 40 characters, double check your input. -REDDIT_USERNAME="" +REDDIT_USERNAME="" #asdfghjkl #EXPLANATION the username of your reddit account #RANGE 3:20 #MATCH_REGEX [_0-9a-zA-Z]+$ #OOB_ERROR A username HAS to be between 3 and 20 characters -REDDIT_PASSWORD="" +REDDIT_PASSWORD="" #fFAGRNJru1FTz70BzhT3Zg #EXPLANATION the password of your reddit account #RANGE 8:None #OOB_ERROR Password too short @@ -25,7 +25,7 @@ REDDIT_PASSWORD="" RANDOM_THREAD="no" # If set to no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: "no" -REDDIT_2FA="" +REDDIT_2FA="" #no #MATCH_REGEX ^(yes|no) #EXPLANATION Whether you have Reddit 2FA enabled, Valid options are "yes" and "no" @@ -43,30 +43,30 @@ POST_ID="" #MATCH_REGEX ^((?!://|://).)*$ #EXPLANATION Used if you want to use a specific post. example of one is urdtfx -THEME="LIGHT" +THEME="LIGHT" #dark #EXPLANATION sets the Reddit theme, either LIGHT or DARK #MATCH_REGEX ^(dark|light|DARK|LIGHT)$ -TIMES_TO_RUN="" +TIMES_TO_RUN="" #2 #EXPLANATION used if you want to run multiple times. set to an int e.g. 4 or 29 and leave blank for 1 -MAX_COMMENT_LENGTH="500" +MAX_COMMENT_LENGTH="500" #500 #EXPLANATION max number of characters a comment can have. default is 500 #RANGE 0:10000 #MATCH_TYPE int #OOB_ERROR the max comment length should be between 0 and 10000 -OPACITY="1" +OPACITY="1" #.8 #EXPLANATION Sets the opacity of the comments when overlayed over the background #RANGE 0:1 #MATCH_TYPE float #OOB_ERROR The opacity HAS to be between 0 and 1 # see different voice options: todo: add docs -VOICE="Matthew" -#EXPLANATION sets the voice the TTS uses, e.g. en_us_002 +VOICE="Matthew" #en_us_002 +#EXPLANATION sets the voice the TTS uses -TTsChoice="polly" +TTsChoice="polly" #polly #EXPLANATION the backend used for TTS, default is polly #OPTIONAL From 138522b18702a5dab0931ec653e6eee32bb959e8 Mon Sep 17 00:00:00 2001 From: SinecKers Date: Wed, 22 Jun 2022 00:15:55 +0300 Subject: [PATCH 18/78] wrap fix --- video_creation/screenshot_downloader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 1caa909..c833abe 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -74,7 +74,7 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): d1 = ImageDraw.Draw(img) font = ImageFont.truetype("arial.ttf", 22) - wrapper = textwrap.TextWrapper(width=60) + wrapper = textwrap.TextWrapper(width=50) wrapped_str = wrapper.fill(text=texts_in_tl) if (getenv("THEME").upper() == "DARK"): @@ -117,7 +117,7 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): comment_tl = ts.google(comment["comment_body"], to_language=os.getenv("POSTLANG")) - wrapper1 = textwrap.TextWrapper(width=78) + wrapper1 = textwrap.TextWrapper(width=70) wrapped_str1 = wrapper1.fill(text=comment_tl) d2 = ImageDraw.Draw(img_comment) @@ -131,6 +131,6 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): textmode1 = (0, 0, 0) d2.rectangle((30, 40, width2 - 5, height2 - 35), fill=fillmode1) - d2.text((40, 50), f"{wrapped_str1}", font=font_comment, fill=textmode1) + d2.text((35, 45), f"{wrapped_str1}", font=font_comment, fill=textmode1) img_comment.save(f"assets/temp/png/comment_{idx}.png") print_substep("Screenshots downloaded Successfully.", style="bold green") From 06f259a7c4249654f9aa8d6878704c84b484811d Mon Sep 17 00:00:00 2001 From: SinecKers Date: Wed, 22 Jun 2022 01:45:54 +0300 Subject: [PATCH 19/78] removed old img modify, added textcontent change --- video_creation/screenshot_downloader.py | 67 +++++++------------------ 1 file changed, 17 insertions(+), 50 deletions(-) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index c833abe..f954201 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -12,8 +12,6 @@ import json from rich.console import Console import translators as ts -from PIL import Image, ImageDraw, ImageFont -import textwrap console = Console() @@ -56,40 +54,22 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): '[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" - ) - # translate code if getenv("POSTLANG"): print_substep("Translating post...") texts_in_tl = ts.google(reddit_object["thread_title"], to_language=os.getenv("POSTLANG")) - img = Image.open("assets/temp/png/title.png") - - width = img.size[0] - height = img.size[1] - - d1 = ImageDraw.Draw(img) - font = ImageFont.truetype("arial.ttf", 22) - - wrapper = textwrap.TextWrapper(width=50) - wrapped_str = wrapper.fill(text=texts_in_tl) - - if (getenv("THEME").upper() == "DARK"): - fillmode = "#1a1a1b" - textmode = (255, 255, 255) - else: - fillmode = "whiite" - textmode = (0, 0, 0) - - d1.rectangle((7, 25, width - 20, height - 35), fill=fillmode) - d1.text((10, 30), f"{wrapped_str}", font=font, fill=textmode) - img.save("assets/temp/png/title.png") + page.evaluate( + 'tl_content => document.querySelector(\'[data-test-id="post-content"] > div:nth-child(3) > div > div\').textContent = tl_content', texts_in_tl + ) else: print_substep("Skipping translation...") + 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" @@ -106,31 +86,18 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): page.locator('[data-testid="content-gate"] button').click() page.goto(f'https://reddit.com{comment["comment_url"]}', timeout=0) + + # translate code + + if getenv("POSTLANG"): + comment_tl = ts.google(comment["comment_body"], to_language=os.getenv("POSTLANG")) + print_substep("comment_tl: " + comment_tl) + page.evaluate( + '([tl_content, tl_id]) => document.querySelector(`#t1_${tl_id} > div:nth-child(2) > div > div[data-testid="comment"] > div`).textContent = tl_content', [comment_tl, comment['comment_id']] + ) + page.locator(f"#t1_{comment['comment_id']}").screenshot( path=f"assets/temp/png/comment_{idx}.png" ) - if getenv("POSTLANG"): - img_comment = Image.open(f"assets/temp/png/comment_{idx}.png") - width2 = img_comment.size[0] - height2 = img_comment.size[1] - - comment_tl = ts.google(comment["comment_body"], to_language=os.getenv("POSTLANG")) - - wrapper1 = textwrap.TextWrapper(width=70) - wrapped_str1 = wrapper1.fill(text=comment_tl) - - d2 = ImageDraw.Draw(img_comment) - font_comment = ImageFont.truetype("arial.ttf", 16) - - if (getenv("THEME").upper() == "DARK"): - fillmode1 = "#242426" - textmode1 = (255, 255, 255) - else: - fillmode1 = "#F5F6F6" - textmode1 = (0, 0, 0) - - d2.rectangle((30, 40, width2 - 5, height2 - 35), fill=fillmode1) - d2.text((35, 45), f"{wrapped_str1}", font=font_comment, fill=textmode1) - img_comment.save(f"assets/temp/png/comment_{idx}.png") print_substep("Screenshots downloaded Successfully.", style="bold green") From 84bd523ed29f4995c4c899fc4868a79be52b005c Mon Sep 17 00:00:00 2001 From: SinecKers Date: Wed, 22 Jun 2022 01:49:08 +0300 Subject: [PATCH 20/78] remove unnecessary print --- video_creation/screenshot_downloader.py | 1 - 1 file changed, 1 deletion(-) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index f954201..469daf4 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -91,7 +91,6 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): if getenv("POSTLANG"): comment_tl = ts.google(comment["comment_body"], to_language=os.getenv("POSTLANG")) - print_substep("comment_tl: " + comment_tl) page.evaluate( '([tl_content, tl_id]) => document.querySelector(`#t1_${tl_id} > div:nth-child(2) > div > div[data-testid="comment"] > div`).textContent = tl_content', [comment_tl, comment['comment_id']] ) From c9e497e5cfd20378d92658b0dce5b2b0f2e719d1 Mon Sep 17 00:00:00 2001 From: HallowedDust5 Date: Tue, 21 Jun 2022 21:37:08 -0400 Subject: [PATCH 21/78] Added function comment --- video_creation/voices.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/video_creation/voices.py b/video_creation/voices.py index be7da96..aaadd42 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -20,9 +20,13 @@ VIDEO_LENGTH: int = 40 # secs def save_text_to_mp3(reddit_obj): - """Saves Text to MP3 files. + """Saves text to MP3 files. Goes through the reddit_obj and generates the title MP3 file and a certain number of comments until the total amount of time exceeds VIDEO_LENGTH seconds. + Args: - reddit_obj : The reddit object you received from the reddit API in the askreddit.py file. + reddit_obj (dict[str]): Reddit object received from reddit API in askreddit.py + + Returns: + tuple[int,int]: (total length of the audio, the number of comments audio was generated for) """ print_step("Saving Text to MP3 files...") length = 0 From 3ec194510b464f0e2f4395cbf66b246ef71666b9 Mon Sep 17 00:00:00 2001 From: HallowedDust5 Date: Tue, 21 Jun 2022 21:37:41 -0400 Subject: [PATCH 22/78] Removed redundant function --- main.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 9998f44..5f01e5f 100755 --- a/main.py +++ b/main.py @@ -37,11 +37,9 @@ def main(): load_dotenv() cleanup() - def get_obj(): - reddit_obj = get_subreddit_threads() - return reddit_obj - reddit_object = get_obj() + + reddit_object = get_subreddit_threads() length, number_of_comments = save_text_to_mp3(reddit_object) download_screenshots_of_reddit_posts(reddit_object, number_of_comments) download_background() From 96d77f7ae0e32d0e44ebb9dc01a8b41200d3b391 Mon Sep 17 00:00:00 2001 From: HallowedDust5 Date: Tue, 21 Jun 2022 21:43:38 -0400 Subject: [PATCH 23/78] Added typing and redid function comment --- video_creation/screenshot_downloader.py | 12 +++++++----- video_creation/voices.py | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 6147dff..452bcd9 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -15,12 +15,14 @@ console = Console() storymode = False -def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): - """Downloads screenshots of reddit posts as they are seen on the web. +def download_screenshots_of_reddit_posts(reddit_object:dict[str], screenshot_num:int): + """Downloads screenshots of reddit posts as seen on the web. Downloads to assets/temp/png + Args: - reddit_object: The Reddit Object you received in askreddit.py - screenshot_num: The number of screenshots you want to download. - """ + reddit_object (dict[str]): Reddit object received from subreddit.py + screenshot_num (int): Number of screenshots to downlaod + """ + print_step("Downloading screenshots of reddit posts...") # ! Make sure the reddit screenshots folder exists diff --git a/video_creation/voices.py b/video_creation/voices.py index aaadd42..97e5917 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -23,7 +23,7 @@ def save_text_to_mp3(reddit_obj): """Saves text to MP3 files. Goes through the reddit_obj and generates the title MP3 file and a certain number of comments until the total amount of time exceeds VIDEO_LENGTH seconds. Args: - reddit_obj (dict[str]): Reddit object received from reddit API in askreddit.py + reddit_obj (dict[str]): Reddit object received from reddit API in subreddit.py Returns: tuple[int,int]: (total length of the audio, the number of comments audio was generated for) From b38184bc32ae150651622c74fe7466f470908202 Mon Sep 17 00:00:00 2001 From: HallowedDust5 Date: Tue, 21 Jun 2022 21:59:54 -0400 Subject: [PATCH 24/78] Updated doc comments, added typing in function declaration --- video_creation/final_video.py | 67 ++++++++++++++++++++++------------- video_creation/voices.py | 2 +- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 37b1ac2..53ad5db 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -25,7 +25,13 @@ console = Console() W, H = 1080, 1920 -def make_final_video(number_of_clips, length): +def make_final_video(number_of_clips:int, length:int): + """Gathers audio clips, gathers all screenshots, stitches them together and saves the final video to assets/temp + + Args: + number_of_clips (int): Index to end at when going through the screenshots + length (int): Length of the video + """ print_step("Creating the final video 🎥") VideoFileClip.reW = lambda clip: clip.resize(width=W) VideoFileClip.reH = lambda clip: clip.resize(width=H) @@ -104,32 +110,12 @@ def make_final_video(number_of_clips, length): 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() + + save_data(filename) + if not exists("./results"): print_substep("the results folder didn't exist so I made it") os.mkdir("./results") @@ -148,3 +134,36 @@ def make_final_video(number_of_clips, length): print_step( f"Reddit title: {os.getenv('VIDEO_TITLE')} \n Background Credit: {os.getenv('background_credit')}" ) + +def save_data(filename:str): + """Saves the videos that have already been generated to a JSON file in video_creation/data/videos.json + + Args: + filename (str): The finished video title name + """ + 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) + +def get_video_title() -> str: + """Gets video title from env variable or gives it the name "final_video" + + Returns: + str: Video title + """ + title = os.getenv("VIDEO_TITLE") or "final_video" + if len(title) <= 35: + return title + else: + return title[0:30] + "..." \ No newline at end of file diff --git a/video_creation/voices.py b/video_creation/voices.py index 97e5917..f68c20b 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -19,7 +19,7 @@ console = Console() VIDEO_LENGTH: int = 40 # secs -def save_text_to_mp3(reddit_obj): +def save_text_to_mp3(reddit_obj:dict[str])->tuple[int,int]: """Saves text to MP3 files. Goes through the reddit_obj and generates the title MP3 file and a certain number of comments until the total amount of time exceeds VIDEO_LENGTH seconds. Args: From 07466097a3eb98b44100ed026f2fda2f7300a8ef Mon Sep 17 00:00:00 2001 From: HallowedDust5 Date: Tue, 21 Jun 2022 22:58:33 -0400 Subject: [PATCH 25/78] Added docs and typing in function declarations --- utils/checker.py | 5 +++++ utils/cleanup.py | 5 +++++ utils/subreddit.py | 22 ++++++++++++++++++++-- utils/videos.py | 15 +++++++++++---- utils/voice.py | 17 +++++++++++------ video_creation/background.py | 19 ++++++++++++++++--- 6 files changed, 68 insertions(+), 15 deletions(-) diff --git a/utils/checker.py b/utils/checker.py index 07b1e15..668b40f 100755 --- a/utils/checker.py +++ b/utils/checker.py @@ -11,6 +11,11 @@ console = Console() def check_env() -> bool: + """Checks to see what's been put in .env + + Returns: + bool: Whether or not everything was put in properly + """ if not os.path.exists(".env.template"): console.print("[red]Couldn't find .env.template. Unable to check variables.") return True diff --git a/utils/cleanup.py b/utils/cleanup.py index 9490b6d..858cfe9 100644 --- a/utils/cleanup.py +++ b/utils/cleanup.py @@ -3,6 +3,11 @@ from os.path import exists def cleanup() -> int: + """Deletes all temporary assets in assets/temp + + Returns: + int: How many files were deleted + """ if exists("./assets/temp"): count = 0 files = [f for f in os.listdir(".") if f.endswith(".mp4") and "temp" in f.lower()] diff --git a/utils/subreddit.py b/utils/subreddit.py index e05c136..3c5cb22 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -4,7 +4,16 @@ from os import getenv from utils.console import print_substep -def get_subreddit_undone(submissions: List, subreddit): +def get_subreddit_undone(submissions: list, subreddit): + """_summary_ + + Args: + submissions (list): List of posts that are going to potentially be generated into a video + subreddit (praw.Reddit.SubredditHelper): Chosen subreddit + + Returns: + Any: The submission that has not been done + """ """ recursively checks if the top submission in the list was already done. """ @@ -27,7 +36,16 @@ def get_subreddit_undone(submissions: List, subreddit): ) # all of the videos in hot have already been done -def already_done(done_videos: list, submission): +def already_done(done_videos: list, submission)->bool: + """Checks to see if the given submission is in the list of videos + + Args: + done_videos (list): Finished videos + submission (Any): The submission + + Returns: + Boolean: Whether the video was found in the list + """ for video in done_videos: if video["id"] == str(submission): diff --git a/utils/videos.py b/utils/videos.py index 51a2704..e6510fe 100755 --- a/utils/videos.py +++ b/utils/videos.py @@ -5,10 +5,17 @@ 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 - """params: - reddit_object: The Reddit Object you received in askreddit.py""" + redditobj:dict[str], +)->dict[str]|None: # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack + """Checks if the chosen post has already been generated + + Args: + redditobj (dict[str]): Reddit object gotten from reddit/subreddit.py + + Returns: + dict[str]|None: Reddit object in args + """ + with open("./video_creation/data/videos.json", "r") as done_vids_raw: done_videos = json.load(done_vids_raw) for video in done_videos: diff --git a/utils/voice.py b/utils/voice.py index 120ee60..57b2b7e 100644 --- a/utils/voice.py +++ b/utils/voice.py @@ -1,12 +1,17 @@ import re -def sanitize_text(text): - """ - Sanitizes the text for tts. - What gets removed: - - following characters`^_~@!&;#:-%“”‘"%*/{}[]()\|<>?=+` - - any http or https links +def sanitize_text(text: str) -> str: + """Sanitizes the text for tts. + What gets removed: + - following characters`^_~@!&;#:-%“”‘"%*/{}[]()\|<>?=+` + - any http or https links + + Args: + text (str): Text to be sanitized + + Returns: + str: Sanitized text """ # remove any urls from the text diff --git a/video_creation/background.py b/video_creation/background.py index fb300e6..7236ecd 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -8,7 +8,16 @@ from moviepy.editor import VideoFileClip from utils.console import print_step, print_substep -def get_start_and_end_times(video_length, length_of_clip): +def get_start_and_end_times(video_length:int, length_of_clip:int)->tuple[int,int]: + """Generates a random interval of time to be used as the beckground of the video. + + Args: + video_length (int): Length of the video + length_of_clip (int): Length of the video to be used as the background + + Returns: + tuple[int,int]: Start and end time of the randomized interval + """ random_time = randrange(180, int(length_of_clip) - int(video_length)) return random_time, random_time + video_length @@ -43,7 +52,12 @@ def download_background(): print_substep("Background videos downloaded successfully! 🎉", style="bold green") -def chop_background_video(video_length): +def chop_background_video(video_length:int): + """Generates the background footage to be used in the video and writes it to assets/temp/background.mp4 + + Args: + video_length (int): Length of the clip where the background footage is to be taken out of + """ print_step("Finding a spot in the backgrounds video to chop...✂️") choice = random.choice(listdir("assets/backgrounds")) environ["background_credit"] = choice.split("-")[0] @@ -58,4 +72,3 @@ def chop_background_video(video_length): targetname="assets/temp/background.mp4", ) print_substep("Background video chopped successfully!", style="bold green") - return True From 520a889cd0567ef80bc3ef63afd97388aa94a571 Mon Sep 17 00:00:00 2001 From: HallowedDust5 Date: Tue, 21 Jun 2022 23:03:19 -0400 Subject: [PATCH 26/78] Replaced subreddit.py with reddit/subreddit.py in docstring comments --- video_creation/screenshot_downloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 452bcd9..9a5266c 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -19,7 +19,7 @@ def download_screenshots_of_reddit_posts(reddit_object:dict[str], screenshot_num """Downloads screenshots of reddit posts as seen on the web. Downloads to assets/temp/png Args: - reddit_object (dict[str]): Reddit object received from subreddit.py + reddit_object (dict[str]): Reddit object received from reddit/subreddit.py screenshot_num (int): Number of screenshots to downlaod """ From e51d0a08352b548a51a8574b0e70814b4691d8dc Mon Sep 17 00:00:00 2001 From: HallowedDust5 Date: Tue, 21 Jun 2022 23:03:34 -0400 Subject: [PATCH 27/78] Last commit was meant to be this one --- video_creation/voices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video_creation/voices.py b/video_creation/voices.py index f68c20b..f740e1b 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -23,7 +23,7 @@ def save_text_to_mp3(reddit_obj:dict[str])->tuple[int,int]: """Saves text to MP3 files. Goes through the reddit_obj and generates the title MP3 file and a certain number of comments until the total amount of time exceeds VIDEO_LENGTH seconds. Args: - reddit_obj (dict[str]): Reddit object received from reddit API in subreddit.py + reddit_obj (dict[str]): Reddit object received from reddit API in reddit/subreddit.py Returns: tuple[int,int]: (total length of the audio, the number of comments audio was generated for) From 36cb5c91a599eee0357209380ce17854d39d5d1e Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Wed, 22 Jun 2022 12:43:16 +0100 Subject: [PATCH 28/78] Add optional voices for each engine --- .env.template | 13 +++++++++++-- TTS/TikTok.py | 2 +- TTS/aws_polly.py | 2 +- TTS/streamlabs_polly.py | 2 +- video_creation/voices.py | 8 +++++--- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.env.template b/.env.template index fcfc739..d9a7f2f 100644 --- a/.env.template +++ b/.env.template @@ -66,8 +66,17 @@ OPACITY="1" #.8 VOICE="Matthew" #en_us_002 #EXPLANATION sets the voice the TTS uses -TTsChoice="polly" #polly -#EXPLANATION the backend used for TTS, default is polly +TTSCHOICE="" +#EXPLANATION the backend used for TTS. Without anything specified, the user will be prompted to choose one. + +STREAMLABS_VOICE="Joanna" +#EXPLANATION Sets the voice for the Streamlabs Polly TTS Engine. Check the file for more information on different voices. + +AWS_VOICE="Joanna" +#EXPLANATION Sets the voice for the AWS Polly TTS Engine. Check the file for more information on different voices. + +TIKTOK_VOICE="en_us_006" +#EXPLANATION Sets the voice for the TikTok TTS Engine. Check the file for more information on different voices. #OPTIONAL STORYMODE="False" diff --git a/TTS/TikTok.py b/TTS/TikTok.py index ccec427..874e8dd 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -73,7 +73,7 @@ class TikTok: # TikTok Text-to-Speech Wrapper voice = ( self.randomvoice() if random_voice - else (os.getenv("VOICE") or random.choice(self.voices["human"])) + else (os.getenv("TIKTOK_VOICE") or random.choice(self.voices["human"])) ) try: r = requests.post( diff --git a/TTS/aws_polly.py b/TTS/aws_polly.py index 3bf7090..703aa6a 100644 --- a/TTS/aws_polly.py +++ b/TTS/aws_polly.py @@ -39,7 +39,7 @@ class AWSPolly: return ValueError( f"Please set the environment variable VOICE to a valid voice. options are: {voices}" ) - voice = str(os.getenv("VOICE")).capitalize() + voice = str(os.getenv("AWS_VOICE")).capitalize() try: # Request speech synthesis response = polly.synthesize_speech( diff --git a/TTS/streamlabs_polly.py b/TTS/streamlabs_polly.py index 07f2c17..500cb16 100644 --- a/TTS/streamlabs_polly.py +++ b/TTS/streamlabs_polly.py @@ -39,7 +39,7 @@ class StreamlabsPolly: return ValueError( f"Please set the environment variable VOICE to a valid voice. options are: {voices}" ) - voice = str(os.getenv("VOICE")).capitalize() + voice = str(os.getenv("STREAMLABS_VOICE")).capitalize() body = {"voice": voice, "text": text, "service": "polly"} response = requests.post(self.url, data=body) try: diff --git a/video_creation/voices.py b/video_creation/voices.py index 9407d59..bbc755e 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -29,9 +29,11 @@ def save_text_to_mp3(reddit_obj): Args: reddit_obj : The reddit object you received from the reddit API in the askreddit.py file. """ - env = os.getenv("TTS_PROVIDER", "") - if env in TTSProviders: - text_to_mp3 = TTSEngine(env, reddit_obj) + env = os.getenv("TTSCHOICE", "") + if env.casefold in map(lambda _: _.casefold(), TTSProviders): + text_to_mp3 = TTSEngine( + get_case_insensitive_key_value(TTSProviders, env), reddit_obj + ) else: chosen = False choice = "" From 36cff769848b395b0ad9abac0a72f68d4fc4e83b Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Wed, 22 Jun 2022 13:20:19 +0100 Subject: [PATCH 29/78] Fix dependabot --- .github/dependabot.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4c75954..ba1c6b8 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" From 71ae7db4d7b53aad0d7ee70fcbb1c21d8ab1031d Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Wed, 22 Jun 2022 13:21:16 +0100 Subject: [PATCH 30/78] Remove debug statements --- TTS/TikTok.py | 2 +- TTS/engine_wrapper.py | 4 ++-- video_creation/background.py | 4 +++- video_creation/final_video.py | 20 ++++++++++++++++---- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/TTS/TikTok.py b/TTS/TikTok.py index 874e8dd..27ba864 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -89,7 +89,7 @@ class TikTok: # TikTok Text-to-Speech Wrapper r = session.post( f"{self.URI_BASE}{voice}&req_text={text}&speaker_map_type=0" ) - print(r.text) + # print(r.text) vstr = [r.json()["data"]["v_str"]][0] b64d = base64.b64decode(vstr) diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py index 2a560b0..ca297e1 100644 --- a/TTS/engine_wrapper.py +++ b/TTS/engine_wrapper.py @@ -83,7 +83,7 @@ class TTSEngine: idy = None for idy, text_cut in enumerate(split_text): - print(f"{idx}-{idy}: {text_cut}\n") + # print(f"{idx}-{idy}: {text_cut}\n") self.call_tts(f"{idx}-{idy}.part", text_cut) split_files.append(AudioFileClip(f"{self.path}/{idx}-{idy}.part.mp3")) CompositeAudioClip([concatenate_audioclips(split_files)]).write_audiofile( @@ -91,7 +91,7 @@ class TTSEngine: ) for i in range(0, idy + 1): - print(f"Cleaning up {self.path}/{idx}-{i}.part.mp3") + # print(f"Cleaning up {self.path}/{idx}-{i}.part.mp3") Path(f"{self.path}/{idx}-{i}.part.mp3").unlink() def call_tts(self, filename: str, text: str): diff --git a/video_creation/background.py b/video_creation/background.py index fb300e6..d1a7948 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -40,7 +40,9 @@ def download_background(): "assets/backgrounds", filename=f"{credit}-{filename}" ) - print_substep("Background videos downloaded successfully! 🎉", style="bold green") + print_substep( + "Background videos downloaded successfully! 🎉", style="bold green" + ) def chop_background_video(video_length): diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 37b1ac2..7ca1d10 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -55,7 +55,9 @@ 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("assets/temp/png/title.png") @@ -74,7 +76,9 @@ def make_final_video(number_of_clips, length): ) 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) @@ -100,7 +104,9 @@ 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]) @@ -134,7 +140,13 @@ 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", + verbose=False, + ) ffmpeg_tools.ffmpeg_extract_subclip( "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" ) From 0b192e4c8e15fdfc1fcba466e0f6384b0b4a039f Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Wed, 22 Jun 2022 13:23:52 +0100 Subject: [PATCH 31/78] Empty videos.json --- video_creation/data/videos.json | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/video_creation/data/videos.json b/video_creation/data/videos.json index 8969b7b..fe51488 100644 --- a/video_creation/data/videos.json +++ b/video_creation/data/videos.json @@ -1,9 +1 @@ -[ - { - "id": "vfxpph", - "time": "1655671664", - "background_credit": "bbswitzer", - "reddit_title": "What unimpressive things are people idiotically proud of", - "filename": "What unimpressive things are p....mp4" - } -] \ No newline at end of file +[] From 159e0aef73e237debf14e907dec4ee4af923de17 Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Wed, 22 Jun 2022 13:27:58 +0100 Subject: [PATCH 32/78] Update default env file --- .env.template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.template b/.env.template index 25b5bd8..a7a70c9 100644 --- a/.env.template +++ b/.env.template @@ -72,9 +72,9 @@ POSTLANG="" VOICE="Matthew" # e.g. en_us_002 #EXPLANATION sets the voice the TTS uses -TTSCHOICE="" +TTSCHOICE="Polly" #EXPLANATION the backend used for TTS. Without anything specified, the user will be prompted to choose one. -# IMPORTANT NOTE: if you use translate, you need to set this gtts or set tiktok and use custom voice in your language +# IMPORTANT NOTE: if you use translate, you need to set this to googletranslate or tiktok and use custom voice in your language STREAMLABS_VOICE="Joanna" #EXPLANATION Sets the voice for the Streamlabs Polly TTS Engine. Check the file for more information on different voices. From aed514081d55ad3e6735a131a9c0064345a63951 Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Wed, 22 Jun 2022 14:32:00 +0100 Subject: [PATCH 33/78] Remove sox dependency --- README.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c85b377..77b11b2 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ The only original thing being done is the editing and gathering of all materials - Python 3.6+ - Playwright (this should install automatically in installation) -- Sox ## Installation 👩‍💻 @@ -43,21 +42,17 @@ 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/) +3. Run `pip install -r requirements.txt` -4. Add the directory of your SoX installation to the Path system variables (see https://github.com/elebumm/RedditVideoMakerBot/issues/613) - and move the added directory all the way to the top of the list of directories. - -5. Run `pip install -r requirements.txt` +4. Run `playwright install` and `playwright install-deps`. (if this fails try adding python -m to the front of the command) -6. Run `playwright install` and `playwright install-deps`. (if this fails try adding python -m to the front of the command) - -7. Run `python main.py` (unless you chose automatic install, then the installer will automatically run main.py) +5. 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. -8. Enjoy 😎 +6. 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 896ab385ffce10082c0787c205c77c11be1d618e Mon Sep 17 00:00:00 2001 From: CordlessCoder <42666308+CordlessCoder@users.noreply.github.com> Date: Wed, 22 Jun 2022 17:07:01 +0300 Subject: [PATCH 34/78] Removed unnecessary chosen flag --- video_creation/voices.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/video_creation/voices.py b/video_creation/voices.py index bbc755e..288db4d 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python + import os from rich.console import Console @@ -31,33 +32,23 @@ def save_text_to_mp3(reddit_obj): """ env = os.getenv("TTSCHOICE", "") if env.casefold in map(lambda _: _.casefold(), TTSProviders): - text_to_mp3 = TTSEngine( - get_case_insensitive_key_value(TTSProviders, env), reddit_obj - ) + text_to_mp3 = TTSEngine(get_case_insensitive_key_value(TTSProviders, env), reddit_obj) else: - chosen = False choice = "" - while not chosen: + while True: print_step("Please choose one of the following TTS providers: ") print_table(TTSProviders) choice = input("\n") - if choice.casefold() not in map(lambda _: _.casefold(), TTSProviders): - print("Unknown Choice") - else: - chosen = True - text_to_mp3 = TTSEngine( - get_case_insensitive_key_value(TTSProviders, choice), reddit_obj - ) + if choice.casefold() in map(lambda _: _.casefold(), TTSProviders): + break + print("Unknown Choice") + text_to_mp3 = TTSEngine(get_case_insensitive_key_value(TTSProviders, choice), reddit_obj) return text_to_mp3.run() def get_case_insensitive_key_value(input_dict, key): return next( - ( - value - for dict_key, value in input_dict.items() - if dict_key.lower() == key.lower() - ), + (value for dict_key, value in input_dict.items() if dict_key.lower() == key.lower()), None, ) From 4a9e2b0dbe0ee8c81e87e7ac905ec51c9b9f3769 Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Wed, 22 Jun 2022 15:07:57 +0100 Subject: [PATCH 35/78] Fixed Tiktok max characters --- TTS/TikTok.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TTS/TikTok.py b/TTS/TikTok.py index 27ba864..91bf43d 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -63,7 +63,7 @@ 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=" - self.max_chars = 330 + self.max_chars = 300 self.voices = {"human": human, "nonhuman": nonhuman, "noneng": noneng} def run(self, text, filepath, random_voice: bool = False): From 3d2004f4cc4d3a91875452252e45dc6f68fbeca6 Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Wed, 22 Jun 2022 15:10:38 +0100 Subject: [PATCH 36/78] Missing function call on casefold --- video_creation/voices.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/video_creation/voices.py b/video_creation/voices.py index 288db4d..3bbd16c 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -31,8 +31,10 @@ def save_text_to_mp3(reddit_obj): reddit_obj : The reddit object you received from the reddit API in the askreddit.py file. """ env = os.getenv("TTSCHOICE", "") - if env.casefold in map(lambda _: _.casefold(), TTSProviders): - text_to_mp3 = TTSEngine(get_case_insensitive_key_value(TTSProviders, env), reddit_obj) + if env.casefold() in map(lambda _: _.casefold(), TTSProviders): + text_to_mp3 = TTSEngine( + get_case_insensitive_key_value(TTSProviders, env), reddit_obj + ) else: choice = "" while True: @@ -42,13 +44,19 @@ def save_text_to_mp3(reddit_obj): if choice.casefold() in map(lambda _: _.casefold(), TTSProviders): break print("Unknown Choice") - text_to_mp3 = TTSEngine(get_case_insensitive_key_value(TTSProviders, choice), reddit_obj) + text_to_mp3 = TTSEngine( + get_case_insensitive_key_value(TTSProviders, choice), reddit_obj + ) return text_to_mp3.run() def get_case_insensitive_key_value(input_dict, key): return next( - (value for dict_key, value in input_dict.items() if dict_key.lower() == key.lower()), + ( + value + for dict_key, value in input_dict.items() + if dict_key.lower() == key.lower() + ), None, ) From c6815a8b7ef5934bbc4632a1ff6fae779638b64a Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Wed, 22 Jun 2022 15:27:50 +0100 Subject: [PATCH 37/78] Clarified bug report template --- .github/ISSUE_TEMPLATE/bug_report.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 05655f4..fede9f8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,10 +1,9 @@ --- name: Bug report about: Create a report to help us improve -title: '' +title: "" labels: bug -assignees: '' - +assignees: "" --- **Describe the bug** @@ -20,9 +19,10 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **System (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - App version [e.g. 22] + +- Python Version: [e.g. Python 3.6] +- OS: [e.g. Windows 11] +- App version / Branch [e.g. latest, V2.0, master, develop ] **Additional context** Add any other context about the problem here. From 76da4bc30b6113c7a2936a07a48e06536dfa1fe0 Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Wed, 22 Jun 2022 15:34:15 +0100 Subject: [PATCH 38/78] Add PR template --- .github/PULL_REQUEST_TEMPLATE.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..18e57b2 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,25 @@ +# Description + + + +# Issue Fixes + + + +None + +# Checklist: + +- [ ] I am pushing changes to the **develop** branch +- [ ] I am using the recommended development environment +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have formatted and linted my code using python-black and pylint +- [ ] I have cleaned up unnecessary files +- [ ] My changes generate no new warnings +- [ ] My changes follow the existing code-style +- [ ] My changes are relevant to the project + +# Any other information (e.g how to test the changes) + +None From c9f32cece151a65c92e24d0ba9d996912cd174c1 Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Wed, 22 Jun 2022 16:03:57 +0100 Subject: [PATCH 39/78] Add code of conduct completing community standards --- CODE_OF_CONDUCT.md | 127 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..385008b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,127 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at the [discord server](https://discord.gg/yqNvvDMYpq). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. From c29311d54a06b2a0c8f5507d9a68bebeffa315a2 Mon Sep 17 00:00:00 2001 From: RootFTW <60049112+IslaFTW@users.noreply.github.com> Date: Wed, 22 Jun 2022 10:56:25 -0700 Subject: [PATCH 40/78] Update .env.template just a typo correction --- .env.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.template b/.env.template index a7a70c9..a736b78 100644 --- a/.env.template +++ b/.env.template @@ -66,7 +66,7 @@ OPACITY="1" #.8 # If you want to translate the comments to another language, set the language code here. # If empty, no translation will be done. POSTLANG="" -#EXPLANATION Activates the translation feature, set the language code gor translate or leave blank +#EXPLANATION Activates the translation feature, set the language code for translate or leave blank # see different voice options: todo: add docs VOICE="Matthew" # e.g. en_us_002 From c9421ca4bcf8273e1eef3f27a8cb41c8627c311c Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 22 Jun 2022 22:37:07 -0400 Subject: [PATCH 41/78] fix: video chopping issue closes #385 fixes #348 --- video_creation/background.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/video_creation/background.py b/video_creation/background.py index fb300e6..d78ce15 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -2,9 +2,11 @@ import random from os import listdir, environ from pathlib import Path from random import randrange -from pytube import YouTube -from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip + from moviepy.editor import VideoFileClip +from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip +from pytube import YouTube + from utils.console import print_step, print_substep @@ -26,7 +28,7 @@ def download_background(): ] # note: make sure the file name doesn't include an - in it if not len(listdir("./assets/backgrounds")) >= len( - background_options + 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. 😎" @@ -51,11 +53,17 @@ def chop_background_video(video_length): 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", - ) + try: + ffmpeg_extract_subclip( + f"assets/backgrounds/{choice}", + start_time, + end_time, + targetname="assets/temp/background.mp4", + ) + except (OSError, IOError): # ffmpeg issue see #348 + print_substep("FFMPEG issue. Trying again...") + with VideoFileClip(f"assets/backgrounds/{choice}") as video: + new = video.subclip(start_time, end_time) + new.write_videofile("assets/temp/background.mp4") print_substep("Background video chopped successfully!", style="bold green") return True From e7d19402c7a05580da75c402af73b1e7fb8050b7 Mon Sep 17 00:00:00 2001 From: LevaniVashadze <100613979+LevaniVashadze@users.noreply.github.com> Date: Fri, 24 Jun 2022 12:57:56 +0400 Subject: [PATCH 42/78] python version update to python 3.7 playwright needs python 3.7+ as seen here https://playwright.dev/python/docs/intro#system-requirements Getting started | Playwright Python --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 77b11b2..ace74f8 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The only original thing being done is the editing and gathering of all materials ## Requirements -- Python 3.6+ +- Python 3.7+ - Playwright (this should install automatically in installation) ## Installation 👩‍💻 From 0d6e1238a7a563263b513bd614da313fccd0feab Mon Sep 17 00:00:00 2001 From: Vaughn Bosu <76852813+null3000@users.noreply.github.com> Date: Fri, 24 Jun 2022 18:32:16 -0700 Subject: [PATCH 43/78] Update final_video.py --- video_creation/final_video.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 37b1ac2..9ecb4d1 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -2,6 +2,7 @@ import json import os import time +import multiprocessing from os.path import exists from moviepy.editor import ( @@ -134,7 +135,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", verbose=False, logger=None, threads=multiprocessing.cpu_count(), fps=30, audio_codec="aac", audio_bitrate="192k") ffmpeg_tools.ffmpeg_extract_subclip( "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" ) From eb00cf319a939d1f23d35ccd0d6f283091132b07 Mon Sep 17 00:00:00 2001 From: Vaughn Bosu <76852813+null3000@users.noreply.github.com> Date: Fri, 24 Jun 2022 18:48:13 -0700 Subject: [PATCH 44/78] Update final_video.py --- video_creation/final_video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 9ecb4d1..19621ea 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -135,7 +135,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", verbose=False, logger=None, threads=multiprocessing.cpu_count(), fps=30, audio_codec="aac", audio_bitrate="192k") + final.write_videofile("assets/temp/temp.mp4", threads=multiprocessing.cpu_count(), fps=30, audio_codec="aac", audio_bitrate="192k") ffmpeg_tools.ffmpeg_extract_subclip( "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" ) From 7b8e60f8dbe83fd00527ecd613a63951c6550000 Mon Sep 17 00:00:00 2001 From: Vaughn Bosu <76852813+null3000@users.noreply.github.com> Date: Fri, 24 Jun 2022 18:50:36 -0700 Subject: [PATCH 45/78] Update final_video.py --- video_creation/final_video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 19621ea..3252d93 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -135,7 +135,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", threads=multiprocessing.cpu_count(), fps=30, audio_codec="aac", audio_bitrate="192k") + final.write_videofile("assets/temp/temp.mp4", verbose=False, threads=multiprocessing.cpu_count(), fps=30, audio_codec="aac", audio_bitrate="192k") ffmpeg_tools.ffmpeg_extract_subclip( "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" ) From acfcf2adc829556f8ef577472f79af07eccb19a4 Mon Sep 17 00:00:00 2001 From: Jason <66544866+JasonLovesDoggo@users.noreply.github.com> Date: Sat, 25 Jun 2022 16:10:20 -0400 Subject: [PATCH 46/78] Update screenshot_downloader.py --- video_creation/screenshot_downloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 4620677..9317e87 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -3,7 +3,7 @@ from os import getenv import os from pathlib import Path -from playwright.async_api import async_playwright +from playwright.async_api import async_playwright # do not remove this line from playwright.sync_api import sync_playwright, ViewportSize from rich.progress import track From f9e0c9a2f87a93c946c0a2e4787b6617992da0f7 Mon Sep 17 00:00:00 2001 From: Jason <66544866+JasonLovesDoggo@users.noreply.github.com> Date: Sat, 25 Jun 2022 16:20:50 -0400 Subject: [PATCH 47/78] Update streamlabs_polly.py Potential fix for #691 --- TTS/streamlabs_polly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TTS/streamlabs_polly.py b/TTS/streamlabs_polly.py index 500cb16..8a24364 100644 --- a/TTS/streamlabs_polly.py +++ b/TTS/streamlabs_polly.py @@ -28,7 +28,7 @@ voices = [ class StreamlabsPolly: def __init__(self): self.url = "https://streamlabs.com/polly/speak" - self.max_chars = 550 + self.max_chars = 349 self.voices = voices def run(self, text, filepath, random_voice: bool = False): From 69f4f59bb6967f6359f489fe400a8a0f8aff736e Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Mon, 27 Jun 2022 16:22:01 +0100 Subject: [PATCH 48/78] Formatted with python-black --- main.py | 2 - reddit/subreddit.py | 16 ++++---- utils/checker.py | 54 ++++++++++++++++++------- utils/cleanup.py | 6 ++- utils/console.py | 8 +++- utils/subreddit.py | 10 ++--- utils/videos.py | 6 ++- video_creation/background.py | 10 ++--- video_creation/final_video.py | 16 ++++---- video_creation/screenshot_downloader.py | 22 ++++++---- video_creation/voices.py | 4 +- 11 files changed, 95 insertions(+), 59 deletions(-) diff --git a/main.py b/main.py index 5f01e5f..1666f17 100755 --- a/main.py +++ b/main.py @@ -37,8 +37,6 @@ def main(): load_dotenv() cleanup() - - reddit_object = get_subreddit_threads() length, number_of_comments = save_text_to_mp3(reddit_object) download_screenshots_of_reddit_posts(reddit_object, number_of_comments) diff --git a/reddit/subreddit.py b/reddit/subreddit.py index fbfc285..98bf8a1 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -105,13 +105,13 @@ def get_subreddit_threads(): continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 if not top_level_comment.stickied: if len(top_level_comment.body) <= int(try_env("MAX_COMMENT_LENGTH", 500)): - if not top_level_comment.author == None: - content["comments"].append( - { - "comment_body": top_level_comment.body, - "comment_url": top_level_comment.permalink, - "comment_id": top_level_comment.id, - } - ) + if not top_level_comment.author == None: + 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/utils/checker.py b/utils/checker.py index 668b40f..9388029 100755 --- a/utils/checker.py +++ b/utils/checker.py @@ -15,7 +15,7 @@ def check_env() -> bool: Returns: bool: Whether or not everything was put in properly - """ + """ if not os.path.exists(".env.template"): console.print("[red]Couldn't find .env.template. Unable to check variables.") return True @@ -35,7 +35,11 @@ def check_env() -> bool: req_envs = [] var_optional = False for line in template.readlines(): - if line.startswith("#") is not True and "=" in line and var_optional is not True: + if ( + line.startswith("#") is not True + and "=" in line + and var_optional is not True + ): req_envs.append(line.split("=")[0]) if "#" in line: examples[line.split("=")[0]] = "#".join(line.split("#")[1:]).strip() @@ -56,7 +60,9 @@ def check_env() -> bool: ) var_optional = False elif line.startswith("#MATCH_TYPE "): - types[req_envs[-1]] = eval(line.removeprefix("#MATCH_TYPE ")[:-1].split()[0]) + types[req_envs[-1]] = eval( + line.removeprefix("#MATCH_TYPE ")[:-1].split()[0] + ) var_optional = False elif line.startswith("#EXPLANATION "): explanations[req_envs[-1]] = line.removeprefix("#EXPLANATION ")[:-1] @@ -82,9 +88,9 @@ def check_env() -> bool: try: temp = types[env](value) if env in bounds.keys(): - (bounds[env][0] <= temp or incorrect.add(env)) and len(bounds[env]) > 1 and ( - bounds[env][1] >= temp or incorrect.add(env) - ) + (bounds[env][0] <= temp or incorrect.add(env)) and len( + bounds[env] + ) > 1 and (bounds[env][1] >= temp or incorrect.add(env)) except ValueError: incorrect.add(env) @@ -107,11 +113,17 @@ def check_env() -> bool: for env in missing: table.add_row( env, - explanations[env] if env in explanations.keys() else "No explanation given", + explanations[env] + if env in explanations.keys() + else "No explanation given", examples[env] if env in examples.keys() else "", - str(bounds[env][0]) if env in bounds.keys() and bounds[env][1] is not None else "", + str(bounds[env][0]) + if env in bounds.keys() and bounds[env][1] is not None + else "", str(bounds[env][1]) - if env in bounds.keys() and len(bounds[env]) > 1 and bounds[env][1] is not None + if env in bounds.keys() + and len(bounds[env]) > 1 + and bounds[env][1] is not None else "", ) console.print(table) @@ -128,7 +140,9 @@ def check_env() -> bool: title_style="#C0CAF5 bold", ) table.add_column("Variable", justify="left", style="#7AA2F7 bold", no_wrap=True) - table.add_column("Current value", justify="left", style="#F7768E", no_wrap=False) + table.add_column( + "Current value", justify="left", style="#F7768E", no_wrap=False + ) table.add_column("Explanation", justify="left", style="#BB9AF7", no_wrap=False) table.add_column("Example", justify="center", style="#F7768E", no_wrap=True) table.add_column("Min", justify="right", style="#F7768E", no_wrap=True) @@ -137,10 +151,14 @@ def check_env() -> bool: table.add_row( env, os.getenv(env), - explanations[env] if env in explanations.keys() else "No explanation given", + explanations[env] + if env in explanations.keys() + else "No explanation given", str(types[env].__name__) if env in types.keys() else "str", str(bounds[env][0]) if env in bounds.keys() else "None", - str(bounds[env][1]) if env in bounds.keys() and len(bounds[env]) > 1 else "None", + str(bounds[env][1]) + if env in bounds.keys() and len(bounds[env]) > 1 + else "None", ) missing.add(env) console.print(table) @@ -177,11 +195,17 @@ def check_env() -> bool: if env in explanations.keys() else "Incorrect input. Try again.", bounds[env][0] if env in bounds.keys() else None, - bounds[env][1] if env in bounds.keys() and len(bounds[env]) > 1 else None, - oob_errors[env] if env in oob_errors.keys() else "Input too long/short.", + bounds[env][1] + if env in bounds.keys() and len(bounds[env]) > 1 + else None, + oob_errors[env] + if env in oob_errors.keys() + else "Input too long/short.", extra_info="[#C0CAF5 bold]⮶ " + ( - explanations[env] if env in explanations.keys() else "No info available" + explanations[env] + if env in explanations.keys() + else "No info available" ), ) ) diff --git a/utils/cleanup.py b/utils/cleanup.py index 858cfe9..44629a9 100644 --- a/utils/cleanup.py +++ b/utils/cleanup.py @@ -7,10 +7,12 @@ def cleanup() -> int: Returns: int: How many files were deleted - """ + """ if exists("./assets/temp"): count = 0 - files = [f for f in os.listdir(".") if f.endswith(".mp4") and "temp" in f.lower()] + 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) diff --git a/utils/console.py b/utils/console.py index 5a041ec..2a9296c 100644 --- a/utils/console.py +++ b/utils/console.py @@ -64,10 +64,14 @@ def handle_input( except ValueError: console.print("[red]" + err_message) # Type conversion failed continue - if nmin is not None and len(user_input) < nmin: # Check if string is long enough + if ( + nmin is not None and len(user_input) < nmin + ): # Check if string is long enough console.print("[red]" + oob_error) continue - if nmax is not None and len(user_input) > nmax: # Check if string is not too long + if ( + nmax is not None and len(user_input) > nmax + ): # Check if string is not too long console.print("[red]" + oob_error) continue break diff --git a/utils/subreddit.py b/utils/subreddit.py index 3c5cb22..a40b6c3 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -9,11 +9,11 @@ def get_subreddit_undone(submissions: list, subreddit): Args: submissions (list): List of posts that are going to potentially be generated into a video - subreddit (praw.Reddit.SubredditHelper): Chosen subreddit + subreddit (praw.Reddit.SubredditHelper): Chosen subreddit Returns: Any: The submission that has not been done - """ + """ """ recursively checks if the top submission in the list was already done. """ @@ -36,8 +36,8 @@ def get_subreddit_undone(submissions: list, subreddit): ) # all of the videos in hot have already been done -def already_done(done_videos: list, submission)->bool: - """Checks to see if the given submission is in the list of videos +def already_done(done_videos: list, submission) -> bool: + """Checks to see if the given submission is in the list of videos Args: done_videos (list): Finished videos @@ -45,7 +45,7 @@ def already_done(done_videos: list, submission)->bool: Returns: Boolean: Whether the video was found in the list - """ + """ for video in done_videos: if video["id"] == str(submission): diff --git a/utils/videos.py b/utils/videos.py index e6510fe..8d6021f 100755 --- a/utils/videos.py +++ b/utils/videos.py @@ -5,8 +5,10 @@ from utils.console import print_step def check_done( - redditobj:dict[str], -)->dict[str]|None: # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack + redditobj: dict[str], +) -> dict[ + str +] | None: # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack """Checks if the chosen post has already been generated Args: diff --git a/video_creation/background.py b/video_creation/background.py index 347ac35..a99355c 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -10,7 +10,7 @@ from pytube import YouTube from utils.console import print_step, print_substep -def get_start_and_end_times(video_length:int, length_of_clip:int)->tuple[int,int]: +def get_start_and_end_times(video_length: int, length_of_clip: int) -> tuple[int, int]: """Generates a random interval of time to be used as the beckground of the video. Args: @@ -19,7 +19,7 @@ def get_start_and_end_times(video_length:int, length_of_clip:int)->tuple[int,int Returns: tuple[int,int]: Start and end time of the randomized interval - """ + """ random_time = randrange(180, int(length_of_clip) - int(video_length)) return random_time, random_time + video_length @@ -37,7 +37,7 @@ def download_background(): ] # note: make sure the file name doesn't include an - in it if not len(listdir("./assets/backgrounds")) >= len( - background_options + 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. 😎" @@ -56,12 +56,12 @@ def download_background(): ) -def chop_background_video(video_length:int): +def chop_background_video(video_length: int): """Generates the background footage to be used in the video and writes it to assets/temp/background.mp4 Args: video_length (int): Length of the clip where the background footage is to be taken out of - """ + """ print_step("Finding a spot in the backgrounds video to chop...✂️") choice = random.choice(listdir("assets/backgrounds")) environ["background_credit"] = choice.split("-")[0] diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 146fab8..c05006c 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -26,13 +26,13 @@ console = Console() W, H = 1080, 1920 -def make_final_video(number_of_clips:int, length:int): +def make_final_video(number_of_clips: int, length: int): """Gathers audio clips, gathers all screenshots, stitches them together and saves the final video to assets/temp Args: number_of_clips (int): Index to end at when going through the screenshots length (int): Length of the video - """ + """ print_step("Creating the final video 🎥") VideoFileClip.reW = lambda clip: clip.resize(width=W) VideoFileClip.reH = lambda clip: clip.resize(width=H) @@ -117,10 +117,8 @@ def make_final_video(number_of_clips:int, length:int): image_concat.audio = audio_composite final = CompositeVideoClip([background_clip, image_concat]) - filename = f"{get_video_title()}.mp4" - save_data(filename) if not exists("./results"): @@ -149,12 +147,13 @@ def make_final_video(number_of_clips:int, length:int): f"Reddit title: {os.getenv('VIDEO_TITLE')} \n Background Credit: {os.getenv('background_credit')}" ) -def save_data(filename:str): + +def save_data(filename: str): """Saves the videos that have already been generated to a JSON file in video_creation/data/videos.json Args: filename (str): The finished video title name - """ + """ 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]: @@ -170,14 +169,15 @@ def save_data(filename:str): raw_vids.seek(0) json.dump(done_vids, raw_vids, ensure_ascii=False, indent=4) + def get_video_title() -> str: """Gets video title from env variable or gives it the name "final_video" Returns: str: Video title - """ + """ title = os.getenv("VIDEO_TITLE") or "final_video" if len(title) <= 35: return title else: - return title[0:30] + "..." \ No newline at end of file + return title[0:30] + "..." diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 9317e87..f638abc 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -3,7 +3,7 @@ from os import getenv import os from pathlib import Path -from playwright.async_api import async_playwright # do not remove this line +from playwright.async_api import async_playwright # do not remove this line from playwright.sync_api import sync_playwright, ViewportSize from rich.progress import track @@ -18,14 +18,14 @@ console = Console() storymode = False -def download_screenshots_of_reddit_posts(reddit_object:dict[str], screenshot_num:int): +def download_screenshots_of_reddit_posts(reddit_object: dict[str], screenshot_num: int): """Downloads screenshots of reddit posts as seen on the web. Downloads to assets/temp/png Args: reddit_object (dict[str]): Reddit object received from reddit/subreddit.py screenshot_num (int): Number of screenshots to downlaod - """ - + """ + print_step("Downloading screenshots of reddit posts...") # ! Make sure the reddit screenshots folder exists @@ -60,10 +60,13 @@ def download_screenshots_of_reddit_posts(reddit_object:dict[str], screenshot_num if getenv("POSTLANG"): print_substep("Translating post...") - texts_in_tl = ts.google(reddit_object["thread_title"], to_language=os.getenv("POSTLANG")) + texts_in_tl = ts.google( + reddit_object["thread_title"], to_language=os.getenv("POSTLANG") + ) page.evaluate( - 'tl_content => document.querySelector(\'[data-test-id="post-content"] > div:nth-child(3) > div > div\').textContent = tl_content', texts_in_tl + "tl_content => document.querySelector('[data-test-id=\"post-content\"] > div:nth-child(3) > div > div').textContent = tl_content", + texts_in_tl, ) else: print_substep("Skipping translation...") @@ -92,9 +95,12 @@ def download_screenshots_of_reddit_posts(reddit_object:dict[str], screenshot_num # translate code if getenv("POSTLANG"): - comment_tl = ts.google(comment["comment_body"], to_language=os.getenv("POSTLANG")) + comment_tl = ts.google( + comment["comment_body"], to_language=os.getenv("POSTLANG") + ) page.evaluate( - '([tl_content, tl_id]) => document.querySelector(`#t1_${tl_id} > div:nth-child(2) > div > div[data-testid="comment"] > div`).textContent = tl_content', [comment_tl, comment['comment_id']] + '([tl_content, tl_id]) => document.querySelector(`#t1_${tl_id} > div:nth-child(2) > div > div[data-testid="comment"] > div`).textContent = tl_content', + [comment_tl, comment["comment_id"]], ) page.locator(f"#t1_{comment['comment_id']}").screenshot( diff --git a/video_creation/voices.py b/video_creation/voices.py index f5ead42..cbe27f5 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -25,7 +25,7 @@ TTSProviders = { VIDEO_LENGTH: int = 40 # secs -def save_text_to_mp3(reddit_obj:dict[str])->tuple[int,int]: +def save_text_to_mp3(reddit_obj: dict[str]) -> tuple[int, int]: """Saves text to MP3 files. Goes through the reddit_obj and generates the title MP3 file and a certain number of comments until the total amount of time exceeds VIDEO_LENGTH seconds. Args: @@ -34,7 +34,7 @@ def save_text_to_mp3(reddit_obj:dict[str])->tuple[int,int]: Returns: tuple[int,int]: (total length of the audio, the number of comments audio was generated for) """ - + env = os.getenv("TTSCHOICE", "") if env.casefold() in map(lambda _: _.casefold(), TTSProviders): text_to_mp3 = TTSEngine( From 87d10206fbca6a4c4c0f7f622ae34decab0bbfdb Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Mon, 27 Jun 2022 16:24:12 +0100 Subject: [PATCH 49/78] Add UTF-8 encoding to all open statements --- setup.py | 4 ++-- utils/checker.py | 8 ++++---- utils/subreddit.py | 4 +++- utils/videos.py | 4 +++- video_creation/final_video.py | 2 +- video_creation/screenshot_downloader.py | 8 ++++++-- 6 files changed, 19 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index 6063e5a..e3cae6d 100755 --- a/setup.py +++ b/setup.py @@ -142,7 +142,7 @@ theme = handle_input( loader = Loader("Attempting to save your credentials...", "Done!").start() # you can also put a while loop here, e.g. while VideoIsBeingMade == True: ... console.print("Writing to the .env file...") -with open(".env", "w") as f: +with open(".env", "w", encoding="utf-8") as f: f.write( f"""REDDIT_CLIENT_ID="{client_id}" REDDIT_CLIENT_SECRET="{client_sec}" @@ -155,7 +155,7 @@ OPACITY={opacity} """ ) -with open(".setup-done-before", "w") as f: +with open(".setup-done-before", "w", encoding="utf-8") as f: f.write( "This file blocks the setup assistant from running again. Delete this file to run setup again." ) diff --git a/utils/checker.py b/utils/checker.py index 9388029..c56e063 100755 --- a/utils/checker.py +++ b/utils/checker.py @@ -21,10 +21,10 @@ def check_env() -> bool: return True if not os.path.exists(".env"): console.print("[red]Couldn't find the .env file, creating one now.") - with open(".env", "x") as file: + with open(".env", "x", encoding="utf-8") as file: file.write("") success = True - with open(".env.template", "r") as template: + with open(".env.template", "r", encoding="utf-8") as template: # req_envs = [env.split("=")[0] for env in template.readlines() if "=" in env] matching = {} explanations = {} @@ -172,7 +172,7 @@ def check_env() -> bool: console.print("[red]Aborting: Unresolved missing variables") return False if len(incorrect): - with open(".env", "r+") as env_file: + with open(".env", "r+", encoding="utf-8") as env_file: lines = [] for line in env_file.readlines(): line.split("=")[0].strip() not in incorrect and lines.append(line) @@ -180,7 +180,7 @@ def check_env() -> bool: env_file.write("\n".join(lines)) env_file.truncate() console.print("[green]Successfully removed incorrectly set variables from .env") - with open(".env", "a") as env_file: + with open(".env", "a", encoding="utf-8") as env_file: for env in missing: env_file.write( env diff --git a/utils/subreddit.py b/utils/subreddit.py index a40b6c3..94de416 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -17,7 +17,9 @@ 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: + with open( + "./video_creation/data/videos.json", "r", encoding="utf-8" + ) as done_vids_raw: done_videos = json.load(done_vids_raw) for submission in submissions: if already_done(done_videos, submission): diff --git a/utils/videos.py b/utils/videos.py index 8d6021f..b41dfa7 100755 --- a/utils/videos.py +++ b/utils/videos.py @@ -18,7 +18,9 @@ def check_done( dict[str]|None: Reddit object in args """ - with open("./video_creation/data/videos.json", "r") as done_vids_raw: + with open( + "./video_creation/data/videos.json", "r", encoding="utf-8" + ) as done_vids_raw: done_videos = json.load(done_vids_raw) for video in done_videos: if video["id"] == str(redditobj): diff --git a/video_creation/final_video.py b/video_creation/final_video.py index c05006c..e0caf10 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -154,7 +154,7 @@ def save_data(filename: str): Args: filename (str): The finished video title name """ - with open("./video_creation/data/videos.json", "r+") as raw_vids: + with open("./video_creation/data/videos.json", "r+", encoding="utf-8") 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 diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index f638abc..83dd4c6 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -38,9 +38,13 @@ def download_screenshots_of_reddit_posts(reddit_object: dict[str], screenshot_nu context = browser.new_context() if getenv("THEME").upper() == "DARK": - cookie_file = open("./video_creation/data/cookie-dark-mode.json") + cookie_file = open( + "./video_creation/data/cookie-dark-mode.json", encoding="utf-8" + ) else: - cookie_file = open("./video_creation/data/cookie-light-mode.json") + cookie_file = open( + "./video_creation/data/cookie-light-mode.json", encoding="utf-8" + ) cookies = json.load(cookie_file) context.add_cookies(cookies) # load preference cookies # Get the thread screenshot From 8e05fbf22b650bb7d23f12aa0e0bfbf02f92d03e Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Mon, 27 Jun 2022 16:26:55 +0100 Subject: [PATCH 50/78] Moved to Union over bitwise or --- utils/videos.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/videos.py b/utils/videos.py index b41dfa7..eeb87d5 100755 --- a/utils/videos.py +++ b/utils/videos.py @@ -1,4 +1,5 @@ import json +from typing import Union from os import getenv from utils.console import print_step @@ -6,9 +7,8 @@ from utils.console import print_step def check_done( redditobj: dict[str], -) -> dict[ - str -] | None: # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack +) -> Union[dict[str], None]: + # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack """Checks if the chosen post has already been generated Args: From 82c4353ec95f26c4635047feaf0943056d307df5 Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Mon, 27 Jun 2022 16:36:45 +0100 Subject: [PATCH 51/78] Remove unused imports and fix pylint errors 'async_playwright' must stay due to anomalous error --- main.py | 4 ++-- reddit/subreddit.py | 17 ++++++++--------- setup.py | 5 ++--- utils/subreddit.py | 6 ++---- utils/voice.py | 2 +- video_creation/screenshot_downloader.py | 14 ++++++-------- 6 files changed, 21 insertions(+), 27 deletions(-) diff --git a/main.py b/main.py index 1666f17..d87b4fe 100755 --- a/main.py +++ b/main.py @@ -1,18 +1,18 @@ #!/usr/bin/env python from subprocess import Popen -from dotenv import load_dotenv from os import getenv, name +from dotenv import load_dotenv from reddit.subreddit import get_subreddit_threads from utils.cleanup import cleanup from utils.console import print_markdown, print_step +from utils.checker import check_env # 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 from video_creation.voices import save_text_to_mp3 -from utils.checker import check_env VERSION = 2.1 print( diff --git a/reddit/subreddit.py b/reddit/subreddit.py index 98bf8a1..cc36fc3 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -2,11 +2,11 @@ import re from os import getenv, environ import praw +from praw.models import MoreComments from utils.console import print_step, print_substep from utils.subreddit import get_subreddit_undone from utils.videos import check_done -from praw.models import MoreComments TEXT_WHITELIST = set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890") @@ -26,7 +26,9 @@ def get_subreddit_threads(): """ Returns a list of threads from the AskReddit subreddit. """ - global submission + + submission = None + print_substep("Logging into Reddit.") content = {} @@ -48,9 +50,8 @@ def get_subreddit_threads(): passkey=passkey, check_for_async=False, ) - """ - Ask user for subreddit input - """ + + # Ask user for subreddit input print_step("Getting subreddit threads...") if not getenv( "SUBREDDIT" @@ -89,9 +90,7 @@ def get_subreddit_threads(): 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_TITLE"] = str(textify(submission.title)) environ["VIDEO_ID"] = str(textify(submission.id)) content["thread_url"] = f"https://reddit.com{submission.permalink}" @@ -105,7 +104,7 @@ def get_subreddit_threads(): continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 if not top_level_comment.stickied: if len(top_level_comment.body) <= int(try_env("MAX_COMMENT_LENGTH", 500)): - if not top_level_comment.author == None: + if not top_level_comment.author is None: content["comments"].append( { "comment_body": top_level_comment.body, diff --git a/setup.py b/setup.py index e3cae6d..5aab956 100755 --- a/setup.py +++ b/setup.py @@ -5,11 +5,10 @@ # Imports import os import subprocess -import re -from utils.console import print_markdown -from utils.console import print_step from rich.console import Console from utils.loader import Loader +from utils.console import print_markdown +from utils.console import print_step from utils.console import handle_input console = Console() diff --git a/utils/subreddit.py b/utils/subreddit.py index 94de416..ac684cb 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -1,4 +1,3 @@ -from typing import List import json from os import getenv from utils.console import print_substep @@ -14,9 +13,8 @@ def get_subreddit_undone(submissions: list, subreddit): Returns: Any: The submission that has not been done """ - """ - recursively checks if the top submission in the list was already done. - """ + # recursively checks if the top submission in the list was already done. + with open( "./video_creation/data/videos.json", "r", encoding="utf-8" ) as done_vids_raw: diff --git a/utils/voice.py b/utils/voice.py index e78ddad..c4f27bf 100644 --- a/utils/voice.py +++ b/utils/voice.py @@ -2,7 +2,7 @@ import re def sanitize_text(text: str) -> str: - """Sanitizes the text for tts. + r"""Sanitizes the text for tts. What gets removed: - following characters`^_~@!&;#:-%“”‘"%*/{}[]()\|<>?=+` - any http or https links diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 83dd4c6..b4df787 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -1,19 +1,17 @@ import json -from os import getenv import os +from os import getenv from pathlib import Path -from playwright.async_api import async_playwright # do not remove this line -from playwright.sync_api import sync_playwright, ViewportSize -from rich.progress import track +from playwright.async_api import async_playwright # pylint: disable=unused-import -from utils.console import print_step, print_substep -import json -from rich.console import Console +# do not remove the above line +from playwright.sync_api import sync_playwright, ViewportSize +from rich.progress import track import translators as ts -console = Console() +from utils.console import print_step, print_substep storymode = False From 0fa4a3f4dfbd470d8634d0ccb074aff74fa4b38b Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Mon, 27 Jun 2022 16:45:27 +0100 Subject: [PATCH 52/78] Stopped using global variable for submission as well unneeded env vars --- .pylintrc | 2 +- main.py | 2 +- reddit/subreddit.py | 21 ++++--------------- video_creation/final_video.py | 38 ++++++++++++----------------------- 4 files changed, 19 insertions(+), 44 deletions(-) diff --git a/.pylintrc b/.pylintrc index b03c808..9bb7919 100644 --- a/.pylintrc +++ b/.pylintrc @@ -60,7 +60,7 @@ ignored-modules= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). -#init-hook= +init-hook='import sys; sys.path.append("/")' # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the # number of processors available to use. diff --git a/main.py b/main.py index d87b4fe..be27079 100755 --- a/main.py +++ b/main.py @@ -42,7 +42,7 @@ def main(): download_screenshots_of_reddit_posts(reddit_object, number_of_comments) download_background() chop_background_video(length) - make_final_video(number_of_comments, length) + make_final_video(number_of_comments, length, reddit_object) def run_many(times): diff --git a/reddit/subreddit.py b/reddit/subreddit.py index cc36fc3..eeae977 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -1,5 +1,5 @@ import re -from os import getenv, environ +from os import getenv import praw from praw.models import MoreComments @@ -8,19 +8,6 @@ from utils.console import print_step, print_substep from utils.subreddit import get_subreddit_undone from utils.videos import check_done -TEXT_WHITELIST = set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890") - - -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(): """ @@ -90,20 +77,20 @@ def get_subreddit_threads(): 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)) - environ["VIDEO_ID"] = str(textify(submission.id)) content["thread_url"] = f"https://reddit.com{submission.permalink}" content["thread_title"] = submission.title content["thread_post"] = submission.selftext + content["thread_id"] = submission.id 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(try_env("MAX_COMMENT_LENGTH", 500)): + if len(top_level_comment.body) <= int(getenv("MAX_COMMENT_LENGTH", "500")): if not top_level_comment.author is None: content["comments"].append( { diff --git a/video_creation/final_video.py b/video_creation/final_video.py index e0caf10..e065d5a 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 import json -import os import time import multiprocessing +import re +import os from os.path import exists from moviepy.editor import ( @@ -17,7 +18,6 @@ from moviepy.editor import ( 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 @@ -26,7 +26,7 @@ console = Console() W, H = 1080, 1920 -def make_final_video(number_of_clips: int, length: int): +def make_final_video(number_of_clips: int, length: int, reddit_obj: dict[str]): """Gathers audio clips, gathers all screenshots, stitches them together and saves the final video to assets/temp Args: @@ -116,13 +116,14 @@ def make_final_video(number_of_clips: int, length: int): ) image_concat.audio = audio_composite final = CompositeVideoClip([background_clip, image_concat]) + title = re.sub(r"[^\w\s-]", "", reddit_obj["thread_title"]) + idx = re.sub(r"[^\w\s-]", "", reddit_obj["thread_id"]) + filename = f"{title}.mp4" - filename = f"{get_video_title()}.mp4" - - save_data(filename) + save_data(filename, title, idx) if not exists("./results"): - print_substep("the results folder didn't exist so I made it") + print_substep("The results folder didn't exist so I made it") os.mkdir("./results") final.write_videofile( @@ -144,11 +145,11 @@ def make_final_video(number_of_clips: int, length: int): print_substep("See result in the results folder!") print_step( - f"Reddit title: {os.getenv('VIDEO_TITLE')} \n Background Credit: {os.getenv('background_credit')}" + f'Reddit title: { reddit_obj["thread_title"] } \n Background Credit: {os.getenv("background_credit")}' ) -def save_data(filename: str): +def save_data(filename: str, reddit_title: str, reddit_id: str): """Saves the videos that have already been generated to a JSON file in video_creation/data/videos.json Args: @@ -156,28 +157,15 @@ def save_data(filename: str): """ with open("./video_creation/data/videos.json", "r+", encoding="utf-8") as raw_vids: done_vids = json.load(raw_vids) - if str(subreddit.submission.id) in [video["id"] for video in done_vids]: + if reddit_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")), + "id": reddit_id, "time": str(int(time.time())), "background_credit": str(os.getenv("background_credit")), - "reddit_title": str(os.getenv("VIDEO_TITLE")), + "reddit_title": reddit_title, "filename": filename, } done_vids.append(payload) raw_vids.seek(0) json.dump(done_vids, raw_vids, ensure_ascii=False, indent=4) - - -def get_video_title() -> str: - """Gets video title from env variable or gives it the name "final_video" - - Returns: - str: Video title - """ - title = os.getenv("VIDEO_TITLE") or "final_video" - if len(title) <= 35: - return title - else: - return title[0:30] + "..." From d271159ac9c022ea796b14c26770ad18725d06ca Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Tue, 28 Jun 2022 02:08:11 +0100 Subject: [PATCH 53/78] Move back to 550 character limit for Streamlabs Polly --- TTS/streamlabs_polly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TTS/streamlabs_polly.py b/TTS/streamlabs_polly.py index 8a24364..500cb16 100644 --- a/TTS/streamlabs_polly.py +++ b/TTS/streamlabs_polly.py @@ -28,7 +28,7 @@ voices = [ class StreamlabsPolly: def __init__(self): self.url = "https://streamlabs.com/polly/speak" - self.max_chars = 349 + self.max_chars = 550 self.voices = voices def run(self, text, filepath, random_voice: bool = False): From c0e8ba8f14c3b99b1e90d2898523725c7e7d6d12 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 28 Jun 2022 12:43:26 +0200 Subject: [PATCH 54/78] Fixing setup.py creation of .env file with all new parameters #685 #725 --- setup.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/setup.py b/setup.py index 5aab956..7cc284e 100755 --- a/setup.py +++ b/setup.py @@ -60,6 +60,7 @@ console.print("[bold green]Reddit 2FA (yes or no)") console.print("[bold green]Opacity (range of 0-1, decimals are OK)") console.print("[bold green]Subreddit (without r/ or /r/)") console.print("[bold green]Theme (light or dark)") +console.print("[bold green]Random Thread (yes or no)") console.print( "[green]If you don't have these, please follow the instructions in the README.md file to set them up." ) @@ -138,6 +139,13 @@ theme = handle_input( r"(light)|(dark)", "You need to input 'light' or 'dark'", ) +Random_thread = handle_input( + "Random Thread? (yes/no)", + False, + r"(yes)|(no)", + "You need to input either yes or no", +) + loader = Loader("Attempting to save your credentials...", "Done!").start() # you can also put a while loop here, e.g. while VideoIsBeingMade == True: ... console.print("Writing to the .env file...") @@ -148,9 +156,14 @@ REDDIT_CLIENT_SECRET="{client_sec}" REDDIT_USERNAME="{user}" REDDIT_PASSWORD="{passw}" REDDIT_2FA="{twofactor}" +RANDOM_THREAD="no" +RANDOM_THREAD="{Random_thread}" THEME="{theme}" SUBREDDIT="{subreddit}" OPACITY={opacity} +VOICE="Matthew" +TTsChoice="polly" +STORYMODE="False" """ ) From a904c4ab171beff68bb66a7cd821b7b5c7c613cd Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 28 Jun 2022 12:43:26 +0200 Subject: [PATCH 55/78] Fixing setup.py creation of .env file with all new parameters #685 #725 --- setup.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/setup.py b/setup.py index 5aab956..7cc284e 100755 --- a/setup.py +++ b/setup.py @@ -60,6 +60,7 @@ console.print("[bold green]Reddit 2FA (yes or no)") console.print("[bold green]Opacity (range of 0-1, decimals are OK)") console.print("[bold green]Subreddit (without r/ or /r/)") console.print("[bold green]Theme (light or dark)") +console.print("[bold green]Random Thread (yes or no)") console.print( "[green]If you don't have these, please follow the instructions in the README.md file to set them up." ) @@ -138,6 +139,13 @@ theme = handle_input( r"(light)|(dark)", "You need to input 'light' or 'dark'", ) +Random_thread = handle_input( + "Random Thread? (yes/no)", + False, + r"(yes)|(no)", + "You need to input either yes or no", +) + loader = Loader("Attempting to save your credentials...", "Done!").start() # you can also put a while loop here, e.g. while VideoIsBeingMade == True: ... console.print("Writing to the .env file...") @@ -148,9 +156,14 @@ REDDIT_CLIENT_SECRET="{client_sec}" REDDIT_USERNAME="{user}" REDDIT_PASSWORD="{passw}" REDDIT_2FA="{twofactor}" +RANDOM_THREAD="no" +RANDOM_THREAD="{Random_thread}" THEME="{theme}" SUBREDDIT="{subreddit}" OPACITY={opacity} +VOICE="Matthew" +TTsChoice="polly" +STORYMODE="False" """ ) From aa04fd28e841d0e3cf40387cacf84a13bb2c3159 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 28 Jun 2022 13:45:34 +0200 Subject: [PATCH 56/78] Update setup.py removed static RANDOM_THREAD --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 7cc284e..63ef952 100755 --- a/setup.py +++ b/setup.py @@ -156,7 +156,6 @@ REDDIT_CLIENT_SECRET="{client_sec}" REDDIT_USERNAME="{user}" REDDIT_PASSWORD="{passw}" REDDIT_2FA="{twofactor}" -RANDOM_THREAD="no" RANDOM_THREAD="{Random_thread}" THEME="{theme}" SUBREDDIT="{subreddit}" From c069d8b5d0e9b93460f29029a6a58fc0e6d322da Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 29 Jun 2022 20:12:32 +0200 Subject: [PATCH 57/78] Fixed #736 --- video_creation/final_video.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/video_creation/final_video.py b/video_creation/final_video.py index e065d5a..fca0e69 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -119,12 +119,13 @@ def make_final_video(number_of_clips: int, length: int, reddit_obj: dict[str]): title = re.sub(r"[^\w\s-]", "", reddit_obj["thread_title"]) idx = re.sub(r"[^\w\s-]", "", reddit_obj["thread_id"]) filename = f"{title}.mp4" + subreddit = os.getenv("SUBREDDIT"); save_data(filename, title, idx) - if not exists("./results"): + if not exists(f"./results/{subreddit}"): print_substep("The results folder didn't exist so I made it") - os.mkdir("./results") + os.mkdir(f"./results/{subreddit}") final.write_videofile( "assets/temp/temp.mp4", @@ -135,7 +136,7 @@ def make_final_video(number_of_clips: int, length: int, reddit_obj: dict[str]): threads=multiprocessing.cpu_count(), ) ffmpeg_tools.ffmpeg_extract_subclip( - "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" + "assets/temp/temp.mp4", 0, length, targetname=f"results/{subreddit}/{filename}" ) # os.remove("assets/temp/temp.mp4") From 1a35fd5b5f759f5971d4e6f9abbe66a68af54bc6 Mon Sep 17 00:00:00 2001 From: CordlessCoder <42666308+CordlessCoder@users.noreply.github.com> Date: Thu, 30 Jun 2022 20:59:06 +0300 Subject: [PATCH 58/78] NUKE SETUP, MAY THE BETTER ENV HANDLER RISE TO NUKE SETUP, MAY THE BETTER ENV HANDLER RISE TO ITS THRONE! --- setup.py | 179 ------------------------------------------------------- 1 file changed, 179 deletions(-) delete mode 100644 setup.py diff --git a/setup.py b/setup.py deleted file mode 100644 index 63ef952..0000000 --- a/setup.py +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/env python3 -# Setup Script for RedditVideoMakerBot - - -# Imports -import os -import subprocess -from rich.console import Console -from utils.loader import Loader -from utils.console import print_markdown -from utils.console import print_step -from utils.console import handle_input - -console = Console() - - -if os.path.isfile(".setup-done-before"): - console.print( - "[red]WARNING: 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 -# - knows that they are about to erase any other setup files/data. - -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." -) - - -# 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() -# 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" -) - - -if input("Are you sure you want to continue? > ").strip().casefold() != "yes": - 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!") - -print() -console.print("Ensure you have the following ready to enter:") -console.print("[bold green]Reddit Client ID") -console.print("[bold green]Reddit Client Secret") -console.print("[bold green]Reddit Username") -console.print("[bold green]Reddit Password") -console.print("[bold green]Reddit 2FA (yes or no)") -console.print("[bold green]Opacity (range of 0-1, decimals are OK)") -console.print("[bold green]Subreddit (without r/ or /r/)") -console.print("[bold green]Theme (light or dark)") -console.print("[bold green]Random Thread (yes or no)") -console.print( - "[green]If you don't have these, please follow the instructions in the README.md file to set them up." -) -console.print( - "[green]If you do have these, type yes to continue. If you dont, go ahead and grab those quickly and come back." -) -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("[bold green]Alright! Let's get started!") - -# Begin the setup process. - -console.print("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_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.", -) -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", -) -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", -) -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", -) -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", -) -theme = handle_input( - "Theme? (light or dark) > ", - False, - r"(light)|(dark)", - "You need to input 'light' or 'dark'", -) -Random_thread = handle_input( - "Random Thread? (yes/no)", - False, - r"(yes)|(no)", - "You need to input either yes or no", -) - -loader = Loader("Attempting to save your credentials...", "Done!").start() -# you can also put a while loop here, e.g. while VideoIsBeingMade == True: ... -console.print("Writing to the .env file...") -with open(".env", "w", encoding="utf-8") as f: - f.write( - f"""REDDIT_CLIENT_ID="{client_id}" -REDDIT_CLIENT_SECRET="{client_sec}" -REDDIT_USERNAME="{user}" -REDDIT_PASSWORD="{passw}" -REDDIT_2FA="{twofactor}" -RANDOM_THREAD="{Random_thread}" -THEME="{theme}" -SUBREDDIT="{subreddit}" -OPACITY={opacity} -VOICE="Matthew" -TTsChoice="polly" -STORYMODE="False" -""" - ) - -with open(".setup-done-before", "w", encoding="utf-8") as f: - f.write( - "This file blocks the setup assistant from running again. Delete this file to run setup again." - ) - -loader.stop() - -console.print("[bold green]Setup Complete! Returning...") - -# Post-Setup: send message and try to run main.py again. -subprocess.call("python3 main.py", shell=True) From 10fe183ac26d7d71c485fce75c48cec394d24d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Mustafa=20Ak=C5=9Fam?= Date: Thu, 30 Jun 2022 22:24:19 +0300 Subject: [PATCH 59/78] Update engine_wrapper.py Fixed not sanitizing the text issue that occurs if POSTLANG is set --- TTS/engine_wrapper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py index ca297e1..d6c3625 100644 --- a/TTS/engine_wrapper.py +++ b/TTS/engine_wrapper.py @@ -106,5 +106,6 @@ def process_text(text: str): new_text = sanitize_text(text) if lang: print_substep("Translating Text...") - new_text = ts.google(text, to_language=lang) + translated_text = ts.google(text, to_language=lang) + new_text = sanitize_text(translated_text) return new_text From 9f894c17222da5f03a85f59608e7c9764422b4e8 Mon Sep 17 00:00:00 2001 From: Cizzl Date: Fri, 1 Jul 2022 11:18:08 +0200 Subject: [PATCH 60/78] fixes #778 when result folder not created --- video_creation/final_video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video_creation/final_video.py b/video_creation/final_video.py index fca0e69..baa5a00 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -125,7 +125,7 @@ def make_final_video(number_of_clips: int, length: int, reddit_obj: dict[str]): if not exists(f"./results/{subreddit}"): print_substep("The results folder didn't exist so I made it") - os.mkdir(f"./results/{subreddit}") + os.makedirs(f"./results/{subreddit}") final.write_videofile( "assets/temp/temp.mp4", From 7d0d4d057369f4fcff4812cd2b9353c12c44d581 Mon Sep 17 00:00:00 2001 From: CordlessCoder <42666308+CordlessCoder@users.noreply.github.com> Date: Fri, 1 Jul 2022 14:20:44 +0300 Subject: [PATCH 61/78] Update README.md --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index ace74f8..bffbc8c 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,7 @@ The only original thing being done is the editing and gathering of all materials ## Installation 👩‍💻 1. Clone this repository -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. +2. **Automatic Install**: Run `python main.py` and type 'yes' to activate the setup assistant. 3. Run `pip install -r requirements.txt` @@ -48,7 +46,6 @@ The only original thing being done is the editing and gathering of all materials 5. 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. 6. 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) From 4fb89aacaeccfb23246064910478fbc53291ee25 Mon Sep 17 00:00:00 2001 From: TomixUG Date: Fri, 1 Jul 2022 19:21:14 +0200 Subject: [PATCH 62/78] fix TIMES_TO_RUN and initial .env setup --- main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index be27079..0aca634 100755 --- a/main.py +++ b/main.py @@ -32,9 +32,6 @@ print_markdown( def main(): - if check_env() is not True: - exit() - load_dotenv() cleanup() reddit_object = get_subreddit_threads() @@ -55,6 +52,9 @@ def run_many(times): if __name__ == "__main__": + if check_env() is not True: + exit() + load_dotenv() try: if getenv("TIMES_TO_RUN") and isinstance(int(getenv("TIMES_TO_RUN")), int): run_many(int(getenv("TIMES_TO_RUN"))) From 26457ed5facc1e87c9bcd1f8b519d4f7aca936a0 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 1 Jul 2022 15:53:49 -0400 Subject: [PATCH 63/78] chore: removed the unused cookies.json file --- video_creation/cookies.json | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 video_creation/cookies.json diff --git a/video_creation/cookies.json b/video_creation/cookies.json deleted file mode 100644 index 829ad0e..0000000 --- a/video_creation/cookies.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "name": "USER", - "value": "eyJwcmVmcyI6eyJ0b3BDb250ZW50RGlzbWlzc2FsVGltZSI6MCwiZ2xvYmFsVGhlbWUiOiJSRURESVQiLCJuaWdodG1vZGUiOnRydWUsImNvbGxhcHNlZFRyYXlTZWN0aW9ucyI6eyJmYXZvcml0ZXMiOmZhbHNlLCJtdWx0aXMiOmZhbHNlLCJtb2RlcmF0aW5nIjpmYWxzZSwic3Vic2NyaXB0aW9ucyI6ZmFsc2UsInByb2ZpbGVzIjpmYWxzZX0sInRvcENvbnRlbnRUaW1lc0Rpc21pc3NlZCI6MH19", - "domain": ".reddit.com", - "path": "/" - } -] From 062f64e473d6bc582b6babd62a8328c48b0dac26 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 1 Jul 2022 15:59:17 -0400 Subject: [PATCH 64/78] chore: fixed typo --- TTS/streamlabs_polly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TTS/streamlabs_polly.py b/TTS/streamlabs_polly.py index 500cb16..4e2f56f 100644 --- a/TTS/streamlabs_polly.py +++ b/TTS/streamlabs_polly.py @@ -47,7 +47,7 @@ class StreamlabsPolly: with open(filepath, "wb") as f: f.write(voice_data.content) except (KeyError, JSONDecodeError): - print("Error occured calling Streamlabs Polly") + print("Error occurred calling Streamlabs Polly") def randomvoice(self): return random.choice(self.voices) From f2c1d7de26ca15143f07555e650f9f2fa5dc4ead Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 1 Jul 2022 16:04:27 -0400 Subject: [PATCH 65/78] chore: improved typechecking --- utils/videos.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utils/videos.py b/utils/videos.py index eeb87d5..7d8d6c9 100755 --- a/utils/videos.py +++ b/utils/videos.py @@ -1,13 +1,14 @@ import json -from typing import Union from os import getenv +from praw.models import Submission + from utils.console import print_step def check_done( redditobj: dict[str], -) -> Union[dict[str], None]: +) -> Submission: # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack """Checks if the chosen post has already been generated From 33a66b0ae3d0c96cd5eb94f4c18db739f9356c4e Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 1 Jul 2022 16:05:53 -0400 Subject: [PATCH 66/78] feat: allows the subreddit choice to begin with r/ --- reddit/subreddit.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/reddit/subreddit.py b/reddit/subreddit.py index eeae977..3419fbf 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -57,8 +57,11 @@ def get_subreddit_threads(): print_substep( f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config" ) + subreddit_choice = getenv("SUBREDDIT") + if subreddit_choice.casefold().startswith('r/'): # removes the r/ from the input + subreddit_choice = subreddit_choice[2:] subreddit = reddit.subreddit( - getenv("SUBREDDIT") + subreddit_choice ) # Allows you to specify in .env. Done for automation purposes. if getenv("POST_ID"): From a5fe2eface79cf2552b5dd61f07306cbca75f496 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 1 Jul 2022 16:50:15 -0400 Subject: [PATCH 67/78] feat: allows users to queue up multiple posts by splitting the post id's with a + e.g. POST_ID="urdtfx+qrftps" would do urdtfx than qrftps note: it is incompatible with times_to_run feat: allows the username to begin with u/ closes #745 --- main.py | 12 +++++++++--- reddit/subreddit.py | 24 ++++++++++++++---------- video_creation/final_video.py | 2 +- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/main.py b/main.py index 0aca634..99d0995 100755 --- a/main.py +++ b/main.py @@ -31,10 +31,9 @@ print_markdown( ) -def main(): +def main(POST_ID=None): cleanup() - - reddit_object = get_subreddit_threads() + reddit_object = get_subreddit_threads(POST_ID) length, number_of_comments = save_text_to_mp3(reddit_object) download_screenshots_of_reddit_posts(reddit_object, number_of_comments) download_background() @@ -58,6 +57,13 @@ if __name__ == "__main__": try: if getenv("TIMES_TO_RUN") and isinstance(int(getenv("TIMES_TO_RUN")), int): run_many(int(getenv("TIMES_TO_RUN"))) + + elif len(getenv("POST_ID", '').split('+')) > 1: + for index, post_id in enumerate(getenv("POST_ID", '').split('+')): + index += 1 + print_step(f'on the {index}{("st" if index == 1 else ("nd" if index == 2 else ("rd" if index == 3 else "th")))} post of {len(getenv("POST_ID", "").split("+"))}') + main(post_id) + Popen("cls" if name == "nt" else "clear", shell=True).wait() else: main() except KeyboardInterrupt: diff --git a/reddit/subreddit.py b/reddit/subreddit.py index 3419fbf..ddbdfe1 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -9,13 +9,11 @@ from utils.subreddit import get_subreddit_undone from utils.videos import check_done -def get_subreddit_threads(): +def get_subreddit_threads(POST_ID: str): """ Returns a list of threads from the AskReddit subreddit. """ - submission = None - print_substep("Logging into Reddit.") content = {} @@ -29,11 +27,14 @@ def get_subreddit_threads(): passkey = f"{pw}:{code}" else: passkey = getenv("REDDIT_PASSWORD") + username = getenv("REDDIT_USERNAME") + if username.casefold().startswith('u/'): + username = username[2:] reddit = praw.Reddit( client_id=getenv("REDDIT_CLIENT_ID"), client_secret=getenv("REDDIT_CLIENT_SECRET"), user_agent="Accessing Reddit threads", - username=getenv("REDDIT_USERNAME"), + username=username, passkey=passkey, check_for_async=False, ) @@ -41,7 +42,7 @@ def get_subreddit_threads(): # Ask user for subreddit input print_step("Getting subreddit threads...") if not getenv( - "SUBREDDIT" + "SUBREDDIT" ): # note to user. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") try: subreddit = reddit.subreddit( @@ -58,20 +59,23 @@ def get_subreddit_threads(): f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config" ) subreddit_choice = getenv("SUBREDDIT") - if subreddit_choice.casefold().startswith('r/'): # removes the r/ from the input + if subreddit_choice.casefold().startswith('r/'): # removes the r/ from the input subreddit_choice = subreddit_choice[2:] subreddit = reddit.subreddit( subreddit_choice ) # Allows you to specify in .env. Done for automation purposes. - if getenv("POST_ID"): + if POST_ID: # would only be called if there are multiple queued posts + submission = reddit.submission(id=POST_ID) + elif getenv("POST_ID") and len(getenv("POST_ID").split('+')) == 1: 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 + return get_subreddit_threads(POST_ID) # submission already done. rerun upvotes = submission.score ratio = submission.upvote_ratio * 100 num_comments = submission.num_comments @@ -94,12 +98,12 @@ def get_subreddit_threads(): continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 if not top_level_comment.stickied: if len(top_level_comment.body) <= int(getenv("MAX_COMMENT_LENGTH", "500")): - if not top_level_comment.author is None: + if top_level_comment.author is not None: # if errors occur with this change to if not. content["comments"].append( { "comment_body": top_level_comment.body, "comment_url": top_level_comment.permalink, - "comment_id": top_level_comment.id, + "comment_id": top_level_comment.id } ) print_substep("Received subreddit threads Successfully.", style="bold green") diff --git a/video_creation/final_video.py b/video_creation/final_video.py index baa5a00..62a55e1 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -57,8 +57,8 @@ def make_final_video(number_of_clips: int, length: int, reddit_obj: dict[str]): # 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") + console.log(f"[bold green] Video Will Be: {int_total_length} Seconds Long") # add title to video image_clips = [] # Gather all images From 8dc892d1b5299b0344c00a269010017079522c53 Mon Sep 17 00:00:00 2001 From: CordlessCoder <42666308+CordlessCoder@users.noreply.github.com> Date: Sat, 2 Jul 2022 00:29:48 +0300 Subject: [PATCH 68/78] fix track(enumerate()) order --- 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 6147dff..fe9865c 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -57,8 +57,8 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): path="assets/temp/png/story_content.png" ) else: - for idx, comment in track( - enumerate(reddit_object["comments"]), "Downloading screenshots..." + for idx, comment in enumerate( + track(reddit_object["comments"], "Downloading screenshots...") ): # Stop if we have reached the screenshot_num From 03b533cca95bcd5d0c8119bb5ccc62fa6dcf3696 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 1 Jul 2022 17:58:52 -0400 Subject: [PATCH 69/78] chore: moved the VIDEO LENGTH var --- TTS/engine_wrapper.py | 4 ++-- video_creation/voices.py | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py index d6c3625..d80b0a7 100644 --- a/TTS/engine_wrapper.py +++ b/TTS/engine_wrapper.py @@ -9,7 +9,7 @@ from rich.progress import track from moviepy.editor import AudioFileClip, CompositeAudioClip, concatenate_audioclips from utils.console import print_step, print_substep from utils.voice import sanitize_text - +DEFUALT_MAX_LENGTH: int = 50 class TTSEngine: @@ -30,7 +30,7 @@ class TTSEngine: tts_module, reddit_object: dict, path: str = "assets/temp/mp3", - max_length: int = 50, + max_length: int = DEFUALT_MAX_LENGTH, ): self.tts_module = tts_module() self.reddit_object = reddit_object diff --git a/video_creation/voices.py b/video_creation/voices.py index cbe27f5..d6f3d89 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -22,11 +22,8 @@ TTSProviders = { "TikTok": TikTok, } -VIDEO_LENGTH: int = 40 # secs - - def save_text_to_mp3(reddit_obj: dict[str]) -> tuple[int, int]: - """Saves text to MP3 files. Goes through the reddit_obj and generates the title MP3 file and a certain number of comments until the total amount of time exceeds VIDEO_LENGTH seconds. + """Saves text to MP3 files. Args: reddit_obj (dict[str]): Reddit object received from reddit API in reddit/subreddit.py @@ -41,7 +38,6 @@ def save_text_to_mp3(reddit_obj: dict[str]) -> tuple[int, int]: get_case_insensitive_key_value(TTSProviders, env), reddit_obj ) else: - choice = "" while True: print_step("Please choose one of the following TTS providers: ") print_table(TTSProviders) From a34b40fb0b7c593177301ca4922d72d2ef8e1306 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 1 Jul 2022 18:16:12 -0400 Subject: [PATCH 70/78] feat: no longer will it use stickied posts Closes #657 --- utils/subreddit.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/subreddit.py b/utils/subreddit.py index ac684cb..949b5bf 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -29,6 +29,9 @@ def get_subreddit_undone(submissions: list, subreddit): continue except AttributeError: print_substep("NSFW settings not defined. Skipping NSFW post...") + if submission.stickied: + print_substep("This post was pinned by moderators. Skipping...") + continue return submission print("all submissions have been done going by top submission order") return get_subreddit_undone( From b558d9770290bdf99b448212ecb670c060acc86e Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 1 Jul 2022 18:19:24 -0400 Subject: [PATCH 71/78] fix: typo --- video_creation/background.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video_creation/background.py b/video_creation/background.py index a99355c..c88204b 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -11,7 +11,7 @@ from utils.console import print_step, print_substep def get_start_and_end_times(video_length: int, length_of_clip: int) -> tuple[int, int]: - """Generates a random interval of time to be used as the beckground of the video. + """Generates a random interval of time to be used as the background of the video. Args: video_length (int): Length of the video From f685cb26f6631cf725490452650668012522cf6c Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 1 Jul 2022 18:21:41 -0400 Subject: [PATCH 72/78] I needed to add this commit to close the issues Closes #400 Closes #379 Closes #414 Closes #433 Closes #573 Closes #603 Closes #620 --- TTS/engine_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py index d80b0a7..01d09e2 100644 --- a/TTS/engine_wrapper.py +++ b/TTS/engine_wrapper.py @@ -9,7 +9,7 @@ from rich.progress import track from moviepy.editor import AudioFileClip, CompositeAudioClip, concatenate_audioclips from utils.console import print_step, print_substep from utils.voice import sanitize_text -DEFUALT_MAX_LENGTH: int = 50 +DEFUALT_MAX_LENGTH: int = 50 # video length variable class TTSEngine: From 80407eb97e30e2eced9b8a7683d7b0065dd64892 Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Fri, 1 Jul 2022 23:53:05 +0100 Subject: [PATCH 73/78] Remove legacy VOICE option --- .env.template | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.env.template b/.env.template index a736b78..7cf7cc2 100644 --- a/.env.template +++ b/.env.template @@ -68,10 +68,6 @@ OPACITY="1" #.8 POSTLANG="" #EXPLANATION Activates the translation feature, set the language code for translate or leave blank -# see different voice options: todo: add docs -VOICE="Matthew" # e.g. en_us_002 -#EXPLANATION sets the voice the TTS uses - TTSCHOICE="Polly" #EXPLANATION the backend used for TTS. Without anything specified, the user will be prompted to choose one. # IMPORTANT NOTE: if you use translate, you need to set this to googletranslate or tiktok and use custom voice in your language From 9727f7d09f2f5e8841c24b316d9216a1e3cc1830 Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Sat, 2 Jul 2022 00:00:27 +0100 Subject: [PATCH 74/78] Remove init-hook from pylint --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 9bb7919..b03c808 100644 --- a/.pylintrc +++ b/.pylintrc @@ -60,7 +60,7 @@ ignored-modules= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). -init-hook='import sys; sys.path.append("/")' +#init-hook= # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the # number of processors available to use. From d03f05a1e4ab5f0c9bfb58655c58145e58c0b24b Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 1 Jul 2022 19:09:41 -0400 Subject: [PATCH 75/78] feat: improved err handling in streamlabs_polly.py --- TTS/streamlabs_polly.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/TTS/streamlabs_polly.py b/TTS/streamlabs_polly.py index 4e2f56f..68ec379 100644 --- a/TTS/streamlabs_polly.py +++ b/TTS/streamlabs_polly.py @@ -47,7 +47,11 @@ class StreamlabsPolly: with open(filepath, "wb") as f: f.write(voice_data.content) except (KeyError, JSONDecodeError): - print("Error occurred calling Streamlabs Polly") - + try: + if response.json()["error"] == "No text specified!": + raise ValueError('Please specify a text to convert to speech.') + except (KeyError, JSONDecodeError): + print("Error occurred calling Streamlabs Polly") def randomvoice(self): return random.choice(self.voices) +#StreamlabsPolly().run(text=str('hi hi ' * 92)[1:], filepath='hello.mp3', random_voice=True) From 804967e5e467ac400c7098d268670cdd111b269b Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 1 Jul 2022 19:13:44 -0400 Subject: [PATCH 76/78] refactor: refactored using black formatter. cmd: black . --line-length 101 --- TTS/TikTok.py | 12 +++--- TTS/engine_wrapper.py | 21 ++++------ TTS/streamlabs_polly.py | 7 +++- main.py | 8 ++-- reddit/subreddit.py | 28 ++++++------- utils/checker.py | 54 +++++++------------------ utils/cleanup.py | 4 +- utils/config.py | 8 +++- utils/console.py | 10 ++--- utils/subreddit.py | 4 +- utils/videos.py | 4 +- video_creation/background.py | 4 +- video_creation/final_video.py | 20 ++++----- video_creation/screenshot_downloader.py | 17 ++------ video_creation/voices.py | 15 ++----- 15 files changed, 77 insertions(+), 139 deletions(-) diff --git a/TTS/TikTok.py b/TTS/TikTok.py index 91bf43d..91ba526 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -62,7 +62,9 @@ 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=" + self.URI_BASE = ( + "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=" + ) self.max_chars = 300 self.voices = {"human": human, "nonhuman": nonhuman, "noneng": noneng} @@ -76,9 +78,7 @@ class TikTok: # TikTok Text-to-Speech Wrapper else (os.getenv("TIKTOK_VOICE") or random.choice(self.voices["human"])) ) try: - r = requests.post( - f"{self.URI_BASE}{voice}&req_text={text}&speaker_map_type=0" - ) + r = requests.post(f"{self.URI_BASE}{voice}&req_text={text}&speaker_map_type=0") except requests.exceptions.SSLError: # https://stackoverflow.com/a/47475019/18516611 session = requests.Session() @@ -86,9 +86,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={text}&speaker_map_type=0" - ) + r = session.post(f"{self.URI_BASE}{voice}&req_text={text}&speaker_map_type=0") # print(r.text) vstr = [r.json()["data"]["v_str"]][0] b64d = base64.b64decode(vstr) diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py index 01d09e2..bbe4e9a 100644 --- a/TTS/engine_wrapper.py +++ b/TTS/engine_wrapper.py @@ -9,7 +9,9 @@ from rich.progress import track from moviepy.editor import AudioFileClip, CompositeAudioClip, concatenate_audioclips from utils.console import print_step, print_substep from utils.voice import sanitize_text -DEFUALT_MAX_LENGTH: int = 50 # video length variable + +DEFUALT_MAX_LENGTH: int = 50 # video length variable + class TTSEngine: @@ -51,16 +53,11 @@ class TTSEngine: print_step("Saving Text to MP3 files...") self.call_tts("title", self.reddit_object["thread_title"]) - if ( - self.reddit_object["thread_post"] != "" - and getenv("STORYMODE", "").casefold() == "true" - ): + if self.reddit_object["thread_post"] != "" and getenv("STORYMODE", "").casefold() == "true": self.call_tts("posttext", self.reddit_object["thread_post"]) idx = None - for idx, comment in track( - enumerate(self.reddit_object["comments"]), "Saving..." - ): + for idx, comment in track(enumerate(self.reddit_object["comments"]), "Saving..."): # ! Stop creating mp3 files if the length is greater than max length. if self.length > self.max_length: break @@ -76,9 +73,7 @@ class TTSEngine: split_files = [] split_text = [ x.group().strip() - for x in re.finditer( - rf" *((.{{0,{self.tts_module.max_chars}}})(\.|.$))", text - ) + for x in re.finditer(rf" *((.{{0,{self.tts_module.max_chars}}})(\.|.$))", text) ] idy = None @@ -95,9 +90,7 @@ class TTSEngine: Path(f"{self.path}/{idx}-{i}.part.mp3").unlink() def call_tts(self, filename: str, text: str): - self.tts_module.run( - text=process_text(text), filepath=f"{self.path}/{filename}.mp3" - ) + self.tts_module.run(text=process_text(text), filepath=f"{self.path}/{filename}.mp3") self.length += MP3(f"{self.path}/{filename}.mp3").info.length diff --git a/TTS/streamlabs_polly.py b/TTS/streamlabs_polly.py index 68ec379..41fe269 100644 --- a/TTS/streamlabs_polly.py +++ b/TTS/streamlabs_polly.py @@ -49,9 +49,12 @@ class StreamlabsPolly: except (KeyError, JSONDecodeError): try: if response.json()["error"] == "No text specified!": - raise ValueError('Please specify a text to convert to speech.') + raise ValueError("Please specify a text to convert to speech.") except (KeyError, JSONDecodeError): print("Error occurred calling Streamlabs Polly") + def randomvoice(self): return random.choice(self.voices) -#StreamlabsPolly().run(text=str('hi hi ' * 92)[1:], filepath='hello.mp3', random_voice=True) + + +# StreamlabsPolly().run(text=str('hi hi ' * 92)[1:], filepath='hello.mp3', random_voice=True) diff --git a/main.py b/main.py index 99d0995..96cc764 100755 --- a/main.py +++ b/main.py @@ -58,10 +58,12 @@ if __name__ == "__main__": if getenv("TIMES_TO_RUN") and isinstance(int(getenv("TIMES_TO_RUN")), int): run_many(int(getenv("TIMES_TO_RUN"))) - elif len(getenv("POST_ID", '').split('+')) > 1: - for index, post_id in enumerate(getenv("POST_ID", '').split('+')): + elif len(getenv("POST_ID", "").split("+")) > 1: + for index, post_id in enumerate(getenv("POST_ID", "").split("+")): index += 1 - print_step(f'on the {index}{("st" if index == 1 else ("nd" if index == 2 else ("rd" if index == 3 else "th")))} post of {len(getenv("POST_ID", "").split("+"))}') + print_step( + f'on the {index}{("st" if index == 1 else ("nd" if index == 2 else ("rd" if index == 3 else "th")))} post of {len(getenv("POST_ID", "").split("+"))}' + ) main(post_id) Popen("cls" if name == "nt" else "clear", shell=True).wait() else: diff --git a/reddit/subreddit.py b/reddit/subreddit.py index ddbdfe1..e1f8940 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -18,9 +18,7 @@ def get_subreddit_threads(POST_ID: str): 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") @@ -28,7 +26,7 @@ def get_subreddit_threads(POST_ID: str): else: passkey = getenv("REDDIT_PASSWORD") username = getenv("REDDIT_USERNAME") - if username.casefold().startswith('u/'): + if username.casefold().startswith("u/"): username = username[2:] reddit = praw.Reddit( client_id=getenv("REDDIT_CLIENT_ID"), @@ -42,24 +40,20 @@ def get_subreddit_threads(POST_ID: str): # Ask user for subreddit input print_step("Getting subreddit threads...") if not getenv( - "SUBREDDIT" + "SUBREDDIT" ): # 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? ") - ) + 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" - ) + print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config") subreddit_choice = getenv("SUBREDDIT") - if subreddit_choice.casefold().startswith('r/'): # removes the r/ from the input + if subreddit_choice.casefold().startswith("r/"): # removes the r/ from the input subreddit_choice = subreddit_choice[2:] subreddit = reddit.subreddit( subreddit_choice @@ -67,13 +61,13 @@ def get_subreddit_threads(POST_ID: str): if POST_ID: # would only be called if there are multiple queued posts submission = reddit.submission(id=POST_ID) - elif getenv("POST_ID") and len(getenv("POST_ID").split('+')) == 1: + elif getenv("POST_ID") and len(getenv("POST_ID").split("+")) == 1: 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 + submission = check_done(submission) # double-checking if submission is None: return get_subreddit_threads(POST_ID) # submission already done. rerun upvotes = submission.score @@ -98,12 +92,14 @@ def get_subreddit_threads(POST_ID: str): continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 if not top_level_comment.stickied: if len(top_level_comment.body) <= int(getenv("MAX_COMMENT_LENGTH", "500")): - if top_level_comment.author is not None: # if errors occur with this change to if not. + if ( + top_level_comment.author is not None + ): # if errors occur with this change to if not. content["comments"].append( { "comment_body": top_level_comment.body, "comment_url": top_level_comment.permalink, - "comment_id": top_level_comment.id + "comment_id": top_level_comment.id, } ) print_substep("Received subreddit threads Successfully.", style="bold green") diff --git a/utils/checker.py b/utils/checker.py index c56e063..791a376 100755 --- a/utils/checker.py +++ b/utils/checker.py @@ -35,11 +35,7 @@ def check_env() -> bool: req_envs = [] var_optional = False for line in template.readlines(): - if ( - line.startswith("#") is not True - and "=" in line - and var_optional is not True - ): + if line.startswith("#") is not True and "=" in line and var_optional is not True: req_envs.append(line.split("=")[0]) if "#" in line: examples[line.split("=")[0]] = "#".join(line.split("#")[1:]).strip() @@ -60,9 +56,7 @@ def check_env() -> bool: ) var_optional = False elif line.startswith("#MATCH_TYPE "): - types[req_envs[-1]] = eval( - line.removeprefix("#MATCH_TYPE ")[:-1].split()[0] - ) + types[req_envs[-1]] = eval(line.removeprefix("#MATCH_TYPE ")[:-1].split()[0]) var_optional = False elif line.startswith("#EXPLANATION "): explanations[req_envs[-1]] = line.removeprefix("#EXPLANATION ")[:-1] @@ -88,9 +82,9 @@ def check_env() -> bool: try: temp = types[env](value) if env in bounds.keys(): - (bounds[env][0] <= temp or incorrect.add(env)) and len( - bounds[env] - ) > 1 and (bounds[env][1] >= temp or incorrect.add(env)) + (bounds[env][0] <= temp or incorrect.add(env)) and len(bounds[env]) > 1 and ( + bounds[env][1] >= temp or incorrect.add(env) + ) except ValueError: incorrect.add(env) @@ -113,17 +107,11 @@ def check_env() -> bool: for env in missing: table.add_row( env, - explanations[env] - if env in explanations.keys() - else "No explanation given", + explanations[env] if env in explanations.keys() else "No explanation given", examples[env] if env in examples.keys() else "", - str(bounds[env][0]) - if env in bounds.keys() and bounds[env][1] is not None - else "", + str(bounds[env][0]) if env in bounds.keys() and bounds[env][1] is not None else "", str(bounds[env][1]) - if env in bounds.keys() - and len(bounds[env]) > 1 - and bounds[env][1] is not None + if env in bounds.keys() and len(bounds[env]) > 1 and bounds[env][1] is not None else "", ) console.print(table) @@ -140,9 +128,7 @@ def check_env() -> bool: title_style="#C0CAF5 bold", ) table.add_column("Variable", justify="left", style="#7AA2F7 bold", no_wrap=True) - table.add_column( - "Current value", justify="left", style="#F7768E", no_wrap=False - ) + table.add_column("Current value", justify="left", style="#F7768E", no_wrap=False) table.add_column("Explanation", justify="left", style="#BB9AF7", no_wrap=False) table.add_column("Example", justify="center", style="#F7768E", no_wrap=True) table.add_column("Min", justify="right", style="#F7768E", no_wrap=True) @@ -151,14 +137,10 @@ def check_env() -> bool: table.add_row( env, os.getenv(env), - explanations[env] - if env in explanations.keys() - else "No explanation given", + explanations[env] if env in explanations.keys() else "No explanation given", str(types[env].__name__) if env in types.keys() else "str", str(bounds[env][0]) if env in bounds.keys() else "None", - str(bounds[env][1]) - if env in bounds.keys() and len(bounds[env]) > 1 - else "None", + str(bounds[env][1]) if env in bounds.keys() and len(bounds[env]) > 1 else "None", ) missing.add(env) console.print(table) @@ -195,18 +177,10 @@ def check_env() -> bool: if env in explanations.keys() else "Incorrect input. Try again.", bounds[env][0] if env in bounds.keys() else None, - bounds[env][1] - if env in bounds.keys() and len(bounds[env]) > 1 - else None, - oob_errors[env] - if env in oob_errors.keys() - else "Input too long/short.", + bounds[env][1] if env in bounds.keys() and len(bounds[env]) > 1 else None, + oob_errors[env] if env in oob_errors.keys() else "Input too long/short.", extra_info="[#C0CAF5 bold]⮶ " - + ( - explanations[env] - if env in explanations.keys() - else "No info available" - ), + + (explanations[env] if env in explanations.keys() else "No info available"), ) ) + ('"' if env not in types.keys() else "") diff --git a/utils/cleanup.py b/utils/cleanup.py index 44629a9..ef4fc44 100644 --- a/utils/cleanup.py +++ b/utils/cleanup.py @@ -10,9 +10,7 @@ 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() - ] + 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) diff --git a/utils/config.py b/utils/config.py index 000b615..29cbb79 100644 --- a/utils/config.py +++ b/utils/config.py @@ -1,5 +1,4 @@ # write a class that takes .env file and parses it into a dictionary - from dotenv import dotenv_values DEFAULTS = { @@ -38,3 +37,10 @@ class Config: config = Config() print(config.SUBREDDIT) +# def temp(): +# root = '' +# if isinstance(root, praw.models.Submission): +# root_type = 'submission' +# elif isinstance(root, praw.models.Comment): +# root_type = 'comment' +# diff --git a/utils/console.py b/utils/console.py index 2a9296c..cd72fd0 100644 --- a/utils/console.py +++ b/utils/console.py @@ -53,7 +53,7 @@ def handle_input( if re.match(match, user_input) is not None: if check_type is not False: try: - user_input = check_type(user_input) + user_input = check_type(user_input) # this line is fine if nmin is not None and user_input < nmin: console.print("[red]" + oob_error) # Input too low failstate continue @@ -64,14 +64,10 @@ def handle_input( except ValueError: console.print("[red]" + err_message) # Type conversion failed continue - if ( - nmin is not None and len(user_input) < nmin - ): # Check if string is long enough + if nmin is not None and len(user_input) < nmin: # Check if string is long enough console.print("[red]" + oob_error) continue - if ( - nmax is not None and len(user_input) > nmax - ): # Check if string is not too long + if nmax is not None and len(user_input) > nmax: # Check if string is not too long console.print("[red]" + oob_error) continue break diff --git a/utils/subreddit.py b/utils/subreddit.py index 949b5bf..f6ca686 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -15,9 +15,7 @@ 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", encoding="utf-8" - ) as done_vids_raw: + with open("./video_creation/data/videos.json", "r", encoding="utf-8") as done_vids_raw: done_videos = json.load(done_vids_raw) for submission in submissions: if already_done(done_videos, submission): diff --git a/utils/videos.py b/utils/videos.py index 7d8d6c9..ecbca7e 100755 --- a/utils/videos.py +++ b/utils/videos.py @@ -19,9 +19,7 @@ def check_done( dict[str]|None: Reddit object in args """ - with open( - "./video_creation/data/videos.json", "r", encoding="utf-8" - ) as done_vids_raw: + with open("./video_creation/data/videos.json", "r", encoding="utf-8") as done_vids_raw: done_videos = json.load(done_vids_raw) for video in done_videos: if video["id"] == str(redditobj): diff --git a/video_creation/background.py b/video_creation/background.py index c88204b..7bf6ae2 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -51,9 +51,7 @@ def download_background(): "assets/backgrounds", filename=f"{credit}-{filename}" ) - print_substep( - "Background videos downloaded successfully! 🎉", style="bold green" - ) + print_substep("Background videos downloaded successfully! 🎉", style="bold green") def chop_background_video(video_length: int): diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 62a55e1..5c6fd15 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 import json -import time import multiprocessing -import re import os +import re +import time from os.path import exists from moviepy.editor import ( @@ -62,9 +62,7 @@ def make_final_video(number_of_clips: int, length: int, reddit_obj: dict[str]): # 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("assets/temp/png/title.png") @@ -83,9 +81,7 @@ def make_final_video(number_of_clips: int, length: int, reddit_obj: dict[str]): ) 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) @@ -111,15 +107,13 @@ def make_final_video(number_of_clips: int, length: int, reddit_obj: dict[str]): # .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]) title = re.sub(r"[^\w\s-]", "", reddit_obj["thread_title"]) idx = re.sub(r"[^\w\s-]", "", reddit_obj["thread_id"]) filename = f"{title}.mp4" - subreddit = os.getenv("SUBREDDIT"); + subreddit = os.getenv("SUBREDDIT") save_data(filename, title, idx) @@ -146,7 +140,7 @@ def make_final_video(number_of_clips: int, length: int, reddit_obj: dict[str]): print_substep("See result in the results folder!") print_step( - f'Reddit title: { reddit_obj["thread_title"] } \n Background Credit: {os.getenv("background_credit")}' + f'Reddit title: {reddit_obj["thread_title"]} \n Background Credit: {os.getenv("background_credit")}' ) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index a9daf40..c93427a 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -4,7 +4,6 @@ from os import getenv from pathlib import Path from playwright.async_api import async_playwright # pylint: disable=unused-import - # do not remove the above line from playwright.sync_api import sync_playwright, ViewportSize @@ -36,13 +35,9 @@ def download_screenshots_of_reddit_posts(reddit_object: dict[str], screenshot_nu context = browser.new_context() if getenv("THEME").upper() == "DARK": - cookie_file = open( - "./video_creation/data/cookie-dark-mode.json", encoding="utf-8" - ) + cookie_file = open("./video_creation/data/cookie-dark-mode.json", encoding="utf-8") else: - cookie_file = open( - "./video_creation/data/cookie-light-mode.json", encoding="utf-8" - ) + cookie_file = open("./video_creation/data/cookie-light-mode.json", encoding="utf-8") cookies = json.load(cookie_file) context.add_cookies(cookies) # load preference cookies # Get the thread screenshot @@ -62,9 +57,7 @@ def download_screenshots_of_reddit_posts(reddit_object: dict[str], screenshot_nu if getenv("POSTLANG"): print_substep("Translating post...") - texts_in_tl = ts.google( - reddit_object["thread_title"], to_language=os.getenv("POSTLANG") - ) + texts_in_tl = ts.google(reddit_object["thread_title"], to_language=os.getenv("POSTLANG")) page.evaluate( "tl_content => document.querySelector('[data-test-id=\"post-content\"] > div:nth-child(3) > div > div').textContent = tl_content", @@ -73,9 +66,7 @@ def download_screenshots_of_reddit_posts(reddit_object: dict[str], screenshot_nu else: print_substep("Skipping translation...") - page.locator('[data-test-id="post-content"]').screenshot( - path="assets/temp/png/title.png" - ) + page.locator('[data-test-id="post-content"]').screenshot(path="assets/temp/png/title.png") if storymode: page.locator('[data-click-id="text"]').screenshot( diff --git a/video_creation/voices.py b/video_creation/voices.py index d6f3d89..240c851 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -22,6 +22,7 @@ TTSProviders = { "TikTok": TikTok, } + def save_text_to_mp3(reddit_obj: dict[str]) -> tuple[int, int]: """Saves text to MP3 files. @@ -34,9 +35,7 @@ def save_text_to_mp3(reddit_obj: dict[str]) -> tuple[int, int]: env = os.getenv("TTSCHOICE", "") if env.casefold() in map(lambda _: _.casefold(), TTSProviders): - text_to_mp3 = TTSEngine( - get_case_insensitive_key_value(TTSProviders, env), reddit_obj - ) + text_to_mp3 = TTSEngine(get_case_insensitive_key_value(TTSProviders, env), reddit_obj) else: while True: print_step("Please choose one of the following TTS providers: ") @@ -45,19 +44,13 @@ def save_text_to_mp3(reddit_obj: dict[str]) -> tuple[int, int]: if choice.casefold() in map(lambda _: _.casefold(), TTSProviders): break print("Unknown Choice") - text_to_mp3 = TTSEngine( - get_case_insensitive_key_value(TTSProviders, choice), reddit_obj - ) + text_to_mp3 = TTSEngine(get_case_insensitive_key_value(TTSProviders, choice), reddit_obj) return text_to_mp3.run() def get_case_insensitive_key_value(input_dict, key): return next( - ( - value - for dict_key, value in input_dict.items() - if dict_key.lower() == key.lower() - ), + (value for dict_key, value in input_dict.items() if dict_key.lower() == key.lower()), None, ) From 7e2af9e063e85502f42773ec3c20a58b1cda38b7 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 1 Jul 2022 19:31:28 -0400 Subject: [PATCH 77/78] refactor: moved save_data func to utils/videos.py from final_video.py refactor: refactored using black formatter. cmd: black . --line-length 101 --- utils/console.py | 2 +- video_creation/final_video.py | 25 +------------------------ video_creation/screenshot_downloader.py | 1 + 3 files changed, 3 insertions(+), 25 deletions(-) diff --git a/utils/console.py b/utils/console.py index cd72fd0..5b91fef 100644 --- a/utils/console.py +++ b/utils/console.py @@ -53,7 +53,7 @@ def handle_input( if re.match(match, user_input) is not None: if check_type is not False: try: - user_input = check_type(user_input) # this line is fine + user_input = check_type(user_input) # this line is fine if nmin is not None and user_input < nmin: console.print("[red]" + oob_error) # Input too low failstate continue diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 5c6fd15..d170169 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -1,9 +1,7 @@ #!/usr/bin/env python3 -import json import multiprocessing import os import re -import time from os.path import exists from moviepy.editor import ( @@ -20,6 +18,7 @@ from rich.console import Console from utils.cleanup import cleanup from utils.console import print_step, print_substep +from utils.videos import save_data console = Console() @@ -142,25 +141,3 @@ def make_final_video(number_of_clips: int, length: int, reddit_obj: dict[str]): print_step( f'Reddit title: {reddit_obj["thread_title"]} \n Background Credit: {os.getenv("background_credit")}' ) - - -def save_data(filename: str, reddit_title: str, reddit_id: str): - """Saves the videos that have already been generated to a JSON file in video_creation/data/videos.json - - Args: - filename (str): The finished video title name - """ - with open("./video_creation/data/videos.json", "r+", encoding="utf-8") as raw_vids: - done_vids = json.load(raw_vids) - if reddit_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": reddit_id, - "time": str(int(time.time())), - "background_credit": str(os.getenv("background_credit")), - "reddit_title": reddit_title, - "filename": filename, - } - done_vids.append(payload) - raw_vids.seek(0) - json.dump(done_vids, raw_vids, ensure_ascii=False, indent=4) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index c93427a..aa1c9d9 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -4,6 +4,7 @@ from os import getenv from pathlib import Path from playwright.async_api import async_playwright # pylint: disable=unused-import + # do not remove the above line from playwright.sync_api import sync_playwright, ViewportSize From cdcd9d3d9afceccaa4d60be0e8822270a86c7c7f Mon Sep 17 00:00:00 2001 From: Callum Leslie Date: Sat, 2 Jul 2022 00:39:25 +0100 Subject: [PATCH 78/78] Add stale pr/issue action --- .github/workflows/stale.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..9e645bb --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,30 @@ +name: 'Stale issue handler' +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@main + id: stale + with: + stale-issue-message: 'This issue is stale because it has been open 7 days with no activity. Remove stale label or comment, or this will be closed in 10 days.' + stale-pr-message: 'This pull request is stale as it has been open for 7 days with no activity. Remove stale label or comment, or this will be closed in 10 days.' + close-pr-message: 'Pull request closed due to being stale.' + close-issue-message: 'Issue closed due to being stale. Please reopen if issue persists in latest version.' + days-before-stale: 7 + days-before-close: 10 + stale-issue-label: 'stale' + close-pr-label: 'outdated' + close-issue-label: 'outdated' + stale-pr-label: 'stale' + exempt-pr-labes: 'keep,blocked,before next release,after next release' + exempt-issue-labels: 'enhancement,keep,blocked' + exempt-all-pr-milestones: true + exempt-all-issue-milestones: true + - name: Print outputs + run: echo ${{ join(steps.stale.outputs.*, ',') }} +