From 7b56495c5927e01c8eb2aff0fba84485fd4d7f9d Mon Sep 17 00:00:00 2001 From: Josh Creek Date: Sat, 18 Jun 2022 18:01:22 +0100 Subject: [PATCH 01/20] chore(*): Add devcontainer vscode support --- .devcontainer/devcontainer.json | 19 +++++++++++++++++++ .gitattributes | 1 + .vscode/launch.json | 13 +++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .gitattributes create mode 100644 .vscode/launch.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..1f86901 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,19 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.238.0/containers/docker-existing-dockerfile +{ + "name": "Existing Dockerfile", + + // Sets the run context to one level up instead of the .devcontainer folder. + "context": "..", + + // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. + "dockerFile": "../Dockerfile", + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python" + ], + + // Install OS dependencies + "postCreateCommand": "apt-get update && apt -qq install -y sox && apt-get install -y libsox-fmt-all" +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0d2e482 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Main.py", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/main.py", + "console": "integratedTerminal", + "justMyCode": true + } + ] +} \ No newline at end of file From d7aa5d0cfbcc050ede82ba64bafde9e74051e6e9 Mon Sep 17 00:00:00 2001 From: Owen Gaspard Date: Sat, 18 Jun 2022 13:38:50 -0500 Subject: [PATCH 02/20] Check for missing environment variables Checks your .env and matches it to the .env.template to find missing variables. --- .gitignore | 3 ++- main.py | 3 +++ utils/envUpdate.py | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 utils/envUpdate.py diff --git a/.gitignore b/.gitignore index 2701ca5..d365d9e 100644 --- a/.gitignore +++ b/.gitignore @@ -169,4 +169,5 @@ results/* reddit-bot-351418-5560ebc49cac.json /.idea *.pyc -/video_creation/data/videos.json \ No newline at end of file +/video_creation/data/videos.json +utils/envUpdate.py.old \ No newline at end of file diff --git a/main.py b/main.py index b4e1123..92395b1 100755 --- a/main.py +++ b/main.py @@ -6,6 +6,7 @@ from os import getenv, name from reddit.subreddit import get_subreddit_threads from utils.cleanup import cleanup from utils.console import print_markdown, print_step +from utils.envUpdate import checkUpdate from video_creation.background import download_background, chop_background_video from video_creation.final_video import make_final_video from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts @@ -22,6 +23,7 @@ print( """ ) load_dotenv() +envUpdate() # Modified by JasonLovesDoggo print_markdown( "### Thanks for using this tool! [Feel free to contribute to this project on GitHub!](https://lewismenelaws.com) If you have any questions, feel free to reach out to me on Twitter or submit a GitHub issue. You can find solutions to many common problems in the [Documentation](https://luka-hietala.gitbook.io/documentation-for-the-reddit-bot/)" @@ -37,6 +39,7 @@ reddit2fa = getenv("REDDIT_2FA") def main(): + checkUpdate() cleanup() def get_obj(): diff --git a/utils/envUpdate.py b/utils/envUpdate.py new file mode 100644 index 0000000..a95b48f --- /dev/null +++ b/utils/envUpdate.py @@ -0,0 +1,37 @@ +import os +import subprocess +import tempfile +from os import path +import logging + +log = logging.getLogger(__name__) + + +def envUpdate(): + if path.exists(".env.template"): + envTemplate = subprocess.check_output( + "awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", # noqa + shell=True, + ) + tempEnv = tempfile.TemporaryFile() + tempEnv.write(envTemplate) + tempEnv.seek(0) + envVars = tempEnv.readlines() + + missing = [] + isMissingEnvs = False + for env in envVars: + try: + env = env.decode("utf-8").strip() + except AttributeError: + env = env.strip() + + if env not in os.environ: + isMissingEnvs = True + missing.append(env) + + if isMissingEnvs: + log.error( + f"[ERROR] The following environment variables are missing: {missing}.)" + ) + exit(-1) \ No newline at end of file From 565a8dd58f8db2fa90e98ff819f79fa5eb9a61c0 Mon Sep 17 00:00:00 2001 From: Owen Gaspard Date: Sat, 18 Jun 2022 13:47:46 -0500 Subject: [PATCH 03/20] Fix incorrect call I changed the name of a function but forgot to update it in the main file. It should work now. --- main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index 92395b1..bfb3ba3 100755 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ from os import getenv, name from reddit.subreddit import get_subreddit_threads from utils.cleanup import cleanup from utils.console import print_markdown, print_step -from utils.envUpdate import checkUpdate +from utils.envUpdate import envUpdate from video_creation.background import download_background, chop_background_video from video_creation.final_video import make_final_video from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts @@ -23,7 +23,6 @@ print( """ ) load_dotenv() -envUpdate() # Modified by JasonLovesDoggo print_markdown( "### Thanks for using this tool! [Feel free to contribute to this project on GitHub!](https://lewismenelaws.com) If you have any questions, feel free to reach out to me on Twitter or submit a GitHub issue. You can find solutions to many common problems in the [Documentation](https://luka-hietala.gitbook.io/documentation-for-the-reddit-bot/)" @@ -39,7 +38,7 @@ reddit2fa = getenv("REDDIT_2FA") def main(): - checkUpdate() + envUpdate() cleanup() def get_obj(): From a56e818e263b113bc7f335e4e03b5cd13b88f506 Mon Sep 17 00:00:00 2001 From: ART3MISTICAL Date: Sun, 19 Jun 2022 02:11:08 +0530 Subject: [PATCH 04/20] Converted spaces into tabs for all files --- .github/dependabot.yml | 8 +- .github/workflows/codeql-analysis.yml | 96 ++++---- TTS/GTTS.py | 18 +- TTS/POLLY.py | 166 ++++++------- TTS/TikTok.py | 216 ++++++++--------- TTS/swapper.py | 18 +- reddit/subreddit.py | 150 ++++++------ setup.py | 204 ++++++++-------- utils/cleanup.py | 38 +-- utils/config.py | 52 ++--- utils/console.py | 16 +- utils/loader.py | 80 +++---- utils/subreddit.py | 42 ++-- utils/videos.py | 30 +-- utils/voice.py | 28 +-- video_creation/background.py | 92 ++++---- video_creation/cookies.json | 8 +- video_creation/data/cookie-dark-mode.json | 16 +- video_creation/data/cookie-light-mode.json | 8 +- video_creation/final_video.py | 258 ++++++++++----------- video_creation/screenshot_downloader.py | 100 ++++---- video_creation/voices.py | 108 ++++----- 22 files changed, 876 insertions(+), 876 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ba1c6b8..4c75954 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: "pip" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "daily" + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c04b714..835b4fb 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -12,61 +12,61 @@ name: "CodeQL" on: - push: - branches: [ "master" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "master" ] - schedule: - - cron: '16 14 * * 3' + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '16 14 * * 3' jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write - strategy: - fail-fast: false - matrix: - language: [ 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - steps: - - name: Checkout repository - uses: actions/checkout@v3 + steps: + - name: Checkout repository + uses: actions/checkout@v3 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/TTS/GTTS.py b/TTS/GTTS.py index fcbcb9b..e38353c 100644 --- a/TTS/GTTS.py +++ b/TTS/GTTS.py @@ -2,12 +2,12 @@ from gtts import gTTS class GTTS: - def tts( - self, - req_text: str = "Google Text To Speech", - filename: str = "title.mp3", - random_speaker=False, - censor=False, - ): - tts = gTTS(text=req_text, lang="en", slow=False) - tts.save(f"{filename}") + def tts( + self, + req_text: str = "Google Text To Speech", + filename: str = "title.mp3", + random_speaker=False, + censor=False, + ): + tts = gTTS(text=req_text, lang="en", slow=False) + tts.save(f"{filename}") diff --git a/TTS/POLLY.py b/TTS/POLLY.py index 54cddca..4f8650c 100644 --- a/TTS/POLLY.py +++ b/TTS/POLLY.py @@ -9,21 +9,21 @@ from moviepy.audio.io.AudioFileClip import AudioFileClip from requests.exceptions import JSONDecodeError voices = [ - "Brian", - "Emma", - "Russell", - "Joey", - "Matthew", - "Joanna", - "Kimberly", - "Amy", - "Geraint", - "Nicole", - "Justin", - "Ivy", - "Kendra", - "Salli", - "Raveena", + "Brian", + "Emma", + "Russell", + "Joey", + "Matthew", + "Joanna", + "Kimberly", + "Amy", + "Geraint", + "Nicole", + "Justin", + "Ivy", + "Kendra", + "Salli", + "Raveena", ] @@ -31,78 +31,78 @@ voices = [ class POLLY: - def __init__(self): - self.url = "https://streamlabs.com/polly/speak" + def __init__(self): + self.url = "https://streamlabs.com/polly/speak" - def tts( - self, - req_text: str = "Amazon Text To Speech", - filename: str = "title.mp3", - random_speaker=False, - censor=False, - ): - if random_speaker: - voice = self.randomvoice() - else: - if not os.getenv("VOICE"): - return ValueError( - "Please set the environment variable VOICE to a valid voice. options are: {}".format( - voices - ) - ) - voice = str(os.getenv("VOICE")).capitalize() - body = {"voice": voice, "text": req_text, "service": "polly"} - response = requests.post(self.url, data=body) - try: - voice_data = requests.get(response.json()["speak_url"]) - with open(filename, "wb") as f: - f.write(voice_data.content) - except (KeyError, JSONDecodeError): - if response.json()["error"] == "Text length is too long!": - chunks = [ - m.group().strip() for m in re.finditer(r" *((.{0,499})(\.|.$))", req_text) - ] + def tts( + self, + req_text: str = "Amazon Text To Speech", + filename: str = "title.mp3", + random_speaker=False, + censor=False, + ): + if random_speaker: + voice = self.randomvoice() + else: + if not os.getenv("VOICE"): + return ValueError( + "Please set the environment variable VOICE to a valid voice. options are: {}".format( + voices + ) + ) + voice = str(os.getenv("VOICE")).capitalize() + body = {"voice": voice, "text": req_text, "service": "polly"} + response = requests.post(self.url, data=body) + try: + voice_data = requests.get(response.json()["speak_url"]) + with open(filename, "wb") as f: + f.write(voice_data.content) + except (KeyError, JSONDecodeError): + if response.json()["error"] == "Text length is too long!": + chunks = [ + m.group().strip() for m in re.finditer(r" *((.{0,499})(\.|.$))", req_text) + ] - audio_clips = [] - cbn = sox.Combiner() + audio_clips = [] + cbn = sox.Combiner() - chunkId = 0 - for chunk in chunks: - body = {"voice": voice, "text": chunk, "service": "polly"} - resp = requests.post(self.url, data=body) - voice_data = requests.get(resp.json()["speak_url"]) - with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: - out.write(voice_data.content) + chunkId = 0 + for chunk in chunks: + body = {"voice": voice, "text": chunk, "service": "polly"} + resp = requests.post(self.url, data=body) + voice_data = requests.get(resp.json()["speak_url"]) + with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: + out.write(voice_data.content) - audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) + audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) - chunkId = chunkId + 1 - try: - if len(audio_clips) > 1: - cbn.convert(samplerate=44100, n_channels=2) - cbn.build(audio_clips, filename, "concatenate") - else: - os.rename(audio_clips[0], filename) - except ( - sox.core.SoxError, - FileNotFoundError, - ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 - for clip in audio_clips: - i = audio_clips.index(clip) # get the index of the clip - audio_clips = ( - audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] - ) # replace the clip with an AudioFileClip - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) - audio_composite.write_audiofile(filename, 44100, 2, 2000, None) + chunkId = chunkId + 1 + try: + if len(audio_clips) > 1: + cbn.convert(samplerate=44100, n_channels=2) + cbn.build(audio_clips, filename, "concatenate") + else: + os.rename(audio_clips[0], filename) + except ( + sox.core.SoxError, + FileNotFoundError, + ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 + for clip in audio_clips: + i = audio_clips.index(clip) # get the index of the clip + audio_clips = ( + audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] + ) # replace the clip with an AudioFileClip + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + audio_composite.write_audiofile(filename, 44100, 2, 2000, None) - def make_readable(self, text): - """ - Amazon Polly fails to read some symbols properly such as '& (and)'. - So we normalize input text before passing it to the service - """ - text = text.replace("&", "and") - return text + def make_readable(self, text): + """ + Amazon Polly fails to read some symbols properly such as '& (and)'. + So we normalize input text before passing it to the service + """ + text = text.replace("&", "and") + return text - def randomvoice(self): - return random.choice(voices) + def randomvoice(self): + return random.choice(voices) diff --git a/TTS/TikTok.py b/TTS/TikTok.py index b9d1928..5b31c00 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -15,49 +15,49 @@ from requests.adapters import HTTPAdapter, Retry # https://twitter.com/scanlime/status/1512598559769702406 nonhuman = [ # DISNEY VOICES - "en_us_ghostface", # Ghost Face - "en_us_chewbacca", # Chewbacca - "en_us_c3po", # C3PO - "en_us_stitch", # Stitch - "en_us_stormtrooper", # Stormtrooper - "en_us_rocket", # Rocket - # ENGLISH VOICES + "en_us_ghostface", # Ghost Face + "en_us_chewbacca", # Chewbacca + "en_us_c3po", # C3PO + "en_us_stitch", # Stitch + "en_us_stormtrooper", # Stormtrooper + "en_us_rocket", # Rocket + # ENGLISH VOICES ] human = [ - "en_au_001", # English AU - Female - "en_au_002", # English AU - Male - "en_uk_001", # English UK - Male 1 - "en_uk_003", # English UK - Male 2 - "en_us_001", # English US - Female (Int. 1) - "en_us_002", # English US - Female (Int. 2) - "en_us_006", # English US - Male 1 - "en_us_007", # English US - Male 2 - "en_us_009", # English US - Male 3 - "en_us_010", + "en_au_001", # English AU - Female + "en_au_002", # English AU - Male + "en_uk_001", # English UK - Male 1 + "en_uk_003", # English UK - Male 2 + "en_us_001", # English US - Female (Int. 1) + "en_us_002", # English US - Female (Int. 2) + "en_us_006", # English US - Male 1 + "en_us_007", # English US - Male 2 + "en_us_009", # English US - Male 3 + "en_us_010", ] voices = nonhuman + human noneng = [ - "fr_001", # French - Male 1 - "fr_002", # French - Male 2 - "de_001", # German - Female - "de_002", # German - Male - "es_002", # Spanish - Male - # AMERICA VOICES - "es_mx_002", # Spanish MX - Male - "br_001", # Portuguese BR - Female 1 - "br_003", # Portuguese BR - Female 2 - "br_004", # Portuguese BR - Female 3 - "br_005", # Portuguese BR - Male - # ASIA VOICES - "id_001", # Indonesian - Female - "jp_001", # Japanese - Female 1 - "jp_003", # Japanese - Female 2 - "jp_005", # Japanese - Female 3 - "jp_006", # Japanese - Male - "kr_002", # Korean - Male 1 - "kr_003", # Korean - Female - "kr_004", # Korean - Male 2 + "fr_001", # French - Male 1 + "fr_002", # French - Male 2 + "de_001", # German - Female + "de_002", # German - Male + "es_002", # Spanish - Male + # AMERICA VOICES + "es_mx_002", # Spanish MX - Male + "br_001", # Portuguese BR - Female 1 + "br_003", # Portuguese BR - Female 2 + "br_004", # Portuguese BR - Female 3 + "br_005", # Portuguese BR - Male + # ASIA VOICES + "id_001", # Indonesian - Female + "jp_001", # Japanese - Female 1 + "jp_003", # Japanese - Female 2 + "jp_005", # Japanese - Female 3 + "jp_006", # Japanese - Male + "kr_002", # Korean - Male 1 + "kr_003", # Korean - Female + "kr_004", # Korean - Male 2 ] @@ -66,74 +66,74 @@ noneng = [ class TikTok: # TikTok Text-to-Speech Wrapper - def __init__(self): - self.URI_BASE = "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=" - - def tts( - self, - req_text: str = "TikTok Text To Speech", - filename: str = "title.mp3", - random_speaker: bool = False, - censor=False, - ): - req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and") - if censor: - # req_text = pf.censor(req_text) - pass - voice = ( - self.randomvoice() if random_speaker else (os.getenv("VOICE") or random.choice(human)) - ) - - chunks = [m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text)] - - audio_clips = [] - cbn = sox.Combiner() - # cbn.set_input_format(file_type=["mp3" for _ in chunks]) - - chunkId = 0 - for chunk in chunks: - try: - r = requests.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") - except requests.exceptions.SSLError: - # https://stackoverflow.com/a/47475019/18516611 - session = requests.Session() - retry = Retry(connect=3, backoff_factor=0.5) - adapter = HTTPAdapter(max_retries=retry) - session.mount("http://", adapter) - session.mount("https://", adapter) - r = session.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") - print(r.text) - vstr = [r.json()["data"]["v_str"]][0] - b64d = base64.b64decode(vstr) - - with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: - out.write(b64d) - - audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) - - chunkId = chunkId + 1 - try: - if len(audio_clips) > 1: - cbn.convert(samplerate=44100, n_channels=2) - cbn.build(audio_clips, filename, "concatenate") - else: - os.rename(audio_clips[0], filename) - except ( - sox.core.SoxError, - FileNotFoundError, - ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 - for clip in audio_clips: - i = audio_clips.index(clip) # get the index of the clip - audio_clips = ( - audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] - ) # replace the clip with an AudioFileClip - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) - audio_composite.write_audiofile(filename, 44100, 2, 2000, None) - - @staticmethod - def randomvoice(): - ok_or_good = random.randrange(1, 10) - if ok_or_good == 1: # 1/10 chance of ok voice - return random.choice(voices) - return random.choice(human) # 9/10 chance of good voice + def __init__(self): + self.URI_BASE = "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=" + + def tts( + self, + req_text: str = "TikTok Text To Speech", + filename: str = "title.mp3", + random_speaker: bool = False, + censor=False, + ): + req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and") + if censor: + # req_text = pf.censor(req_text) + pass + voice = ( + self.randomvoice() if random_speaker else (os.getenv("VOICE") or random.choice(human)) + ) + + chunks = [m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text)] + + audio_clips = [] + cbn = sox.Combiner() + # cbn.set_input_format(file_type=["mp3" for _ in chunks]) + + chunkId = 0 + for chunk in chunks: + try: + r = requests.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") + except requests.exceptions.SSLError: + # https://stackoverflow.com/a/47475019/18516611 + session = requests.Session() + retry = Retry(connect=3, backoff_factor=0.5) + adapter = HTTPAdapter(max_retries=retry) + session.mount("http://", adapter) + session.mount("https://", adapter) + r = session.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") + print(r.text) + vstr = [r.json()["data"]["v_str"]][0] + b64d = base64.b64decode(vstr) + + with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: + out.write(b64d) + + audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) + + chunkId = chunkId + 1 + try: + if len(audio_clips) > 1: + cbn.convert(samplerate=44100, n_channels=2) + cbn.build(audio_clips, filename, "concatenate") + else: + os.rename(audio_clips[0], filename) + except ( + sox.core.SoxError, + FileNotFoundError, + ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 + for clip in audio_clips: + i = audio_clips.index(clip) # get the index of the clip + audio_clips = ( + audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] + ) # replace the clip with an AudioFileClip + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + audio_composite.write_audiofile(filename, 44100, 2, 2000, None) + + @staticmethod + def randomvoice(): + ok_or_good = random.randrange(1, 10) + if ok_or_good == 1: # 1/10 chance of ok voice + return random.choice(voices) + return random.choice(human) # 9/10 chance of good voice diff --git a/TTS/swapper.py b/TTS/swapper.py index f4717b1..7eebd3e 100644 --- a/TTS/swapper.py +++ b/TTS/swapper.py @@ -10,12 +10,12 @@ CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS, 'polly': POLLY} class TTS: - def __new__(cls): - load_dotenv() - CHOICE = getenv("TTsChoice").casefold() - valid_keys = [key.lower() for key in CHOICE_DIR.keys()] - if CHOICE not in valid_keys: - raise ValueError( - f"{CHOICE} is not valid. Please use one of these {valid_keys} options" - ) - return CHOICE_DIR.get(CHOICE)() + def __new__(cls): + load_dotenv() + CHOICE = getenv("TTsChoice").casefold() + valid_keys = [key.lower() for key in CHOICE_DIR.keys()] + if CHOICE not in valid_keys: + raise ValueError( + f"{CHOICE} is not valid. Please use one of these {valid_keys} options" + ) + return CHOICE_DIR.get(CHOICE)() diff --git a/reddit/subreddit.py b/reddit/subreddit.py index dc0d8ae..f08cefa 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -11,85 +11,85 @@ TEXT_WHITELIST = set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234 def textify(text): - return "".join(filter(TEXT_WHITELIST.__contains__, text)) + return "".join(filter(TEXT_WHITELIST.__contains__, text)) def get_subreddit_threads(): - """ - Returns a list of threads from the AskReddit subreddit. - """ - global submission - print_substep("Logging into Reddit.") + """ + Returns a list of threads from the AskReddit subreddit. + """ + global submission + print_substep("Logging into Reddit.") - content = {} - if str(getenv("REDDIT_2FA")).casefold() == "yes": - print("\nEnter your two-factor authentication code from your authenticator app.\n") - code = input("> ") - print() - pw = getenv("REDDIT_PASSWORD") - passkey = f"{pw}:{code}" - else: - passkey = getenv("REDDIT_PASSWORD") - reddit = praw.Reddit( - client_id=getenv("REDDIT_CLIENT_ID"), - client_secret=getenv("REDDIT_CLIENT_SECRET"), - user_agent="Accessing Reddit threads", - username=getenv("REDDIT_USERNAME"), - passkey=passkey, - check_for_async=False, - ) - """ - Ask user for subreddit input - """ - print_step("Getting subreddit threads...") - if not getenv( - "SUBREDDIT" - ): # note to self. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") - subreddit = reddit.subreddit( - input("What subreddit would you like to pull from? ") - ) # if the env isnt set, ask user - else: - print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config") - subreddit = reddit.subreddit( - getenv("SUBREDDIT") - ) # Allows you to specify in .env. Done for automation purposes. + content = {} + if str(getenv("REDDIT_2FA")).casefold() == "yes": + print("\nEnter your two-factor authentication code from your authenticator app.\n") + code = input("> ") + print() + pw = getenv("REDDIT_PASSWORD") + passkey = f"{pw}:{code}" + else: + passkey = getenv("REDDIT_PASSWORD") + reddit = praw.Reddit( + client_id=getenv("REDDIT_CLIENT_ID"), + client_secret=getenv("REDDIT_CLIENT_SECRET"), + user_agent="Accessing Reddit threads", + username=getenv("REDDIT_USERNAME"), + passkey=passkey, + check_for_async=False, + ) + """ + Ask user for subreddit input + """ + print_step("Getting subreddit threads...") + if not getenv( + "SUBREDDIT" + ): # note to self. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") + subreddit = reddit.subreddit( + input("What subreddit would you like to pull from? ") + ) # if the env isnt set, ask user + else: + print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config") + subreddit = reddit.subreddit( + getenv("SUBREDDIT") + ) # Allows you to specify in .env. Done for automation purposes. - if getenv("POST_ID"): - submission = reddit.submission(id=getenv("POST_ID")) - else: - threads = subreddit.hot(limit=25) - submission = get_subreddit_undone(threads, subreddit) - submission = check_done(submission) # double checking - if submission is None: - return get_subreddit_threads() # submission already done. rerun - upvotes = submission.score - ratio = submission.upvote_ratio * 100 - num_comments = submission.num_comments + if getenv("POST_ID"): + submission = reddit.submission(id=getenv("POST_ID")) + else: + threads = subreddit.hot(limit=25) + submission = get_subreddit_undone(threads, subreddit) + submission = check_done(submission) # double checking + if submission is None: + return get_subreddit_threads() # submission already done. rerun + upvotes = submission.score + ratio = submission.upvote_ratio * 100 + num_comments = submission.num_comments - print_substep(f"Video will be: {submission.title} :thumbsup:", style="bold green") - print_substep(f"Thread has {upvotes} upvotes", style="bold blue") - print_substep(f"Thread has a upvote ratio of {ratio}%", style="bold blue") - print_substep(f"Thread has {num_comments} comments", style="bold blue") - environ["VIDEO_TITLE"] = str(textify(submission.title)) # todo use global instend of env vars - environ["VIDEO_ID"] = str(textify(submission.id)) + print_substep(f"Video will be: {submission.title} :thumbsup:", style="bold green") + print_substep(f"Thread has {upvotes} upvotes", style="bold blue") + print_substep(f"Thread has a upvote ratio of {ratio}%", style="bold blue") + print_substep(f"Thread has {num_comments} comments", style="bold blue") + environ["VIDEO_TITLE"] = str(textify(submission.title)) # todo use global instend of env vars + environ["VIDEO_ID"] = str(textify(submission.id)) - content["thread_url"] = f"https://reddit.com{submission.permalink}" - content["thread_title"] = submission.title - # content["thread_content"] = submission.content - content["comments"] = [] - for top_level_comment in submission.comments: - if isinstance(top_level_comment, MoreComments): - continue - if top_level_comment.body in ["[removed]", "[deleted]"]: - continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 - if not top_level_comment.stickied: - if len(top_level_comment.body) <= int(environ["MAX_COMMENT_LENGTH"]): - content["comments"].append( - { - "comment_body": top_level_comment.body, - "comment_url": top_level_comment.permalink, - "comment_id": top_level_comment.id, - } - ) - print_substep("Received subreddit threads Successfully.", style="bold green") - return content + content["thread_url"] = f"https://reddit.com{submission.permalink}" + content["thread_title"] = submission.title + # content["thread_content"] = submission.content + content["comments"] = [] + for top_level_comment in submission.comments: + if isinstance(top_level_comment, MoreComments): + continue + if top_level_comment.body in ["[removed]", "[deleted]"]: + continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 + if not top_level_comment.stickied: + if len(top_level_comment.body) <= int(environ["MAX_COMMENT_LENGTH"]): + content["comments"].append( + { + "comment_body": top_level_comment.body, + "comment_url": top_level_comment.permalink, + "comment_id": top_level_comment.id, + } + ) + print_substep("Received subreddit threads Successfully.", style="bold green") + return content diff --git a/setup.py b/setup.py index 640cd26..b000ce8 100755 --- a/setup.py +++ b/setup.py @@ -15,52 +15,52 @@ console = Console() def handle_input( - message: str = "", - check_type=False, - match: str = "", - err_message: str = "", - nmin=None, - nmax=None, - oob_error="", + message: str = "", + check_type=False, + match: str = "", + err_message: str = "", + nmin=None, + nmax=None, + oob_error="", ): - match = re.compile(match + "$") - while True: - user_input = input(message + "\n> ").strip() - if re.match(match, user_input) is not None: - if check_type is not False: - try: - user_input = check_type(user_input) - if nmin is not None and user_input < nmin: - console.log("[red]" + oob_error) # Input too low failstate - continue - if nmax is not None and user_input > nmax: - console.log("[red]" + oob_error) # Input too high - continue - break # Successful type conversion and number in bounds - except ValueError: - console.log("[red]" + err_message) # Type conversion failed - continue - if ( - nmin is not None and len(user_input) < nmin - ): # Check if string is long enough - console.log("[red]" + oob_error) - continue - if ( - nmax is not None and len(user_input) > nmax - ): # Check if string is not too long - console.log("[red]" + oob_error) - continue - break - console.log("[red]" + err_message) - - return user_input + match = re.compile(match + "$") + while True: + user_input = input(message + "\n> ").strip() + if re.match(match, user_input) is not None: + if check_type is not False: + try: + user_input = check_type(user_input) + if nmin is not None and user_input < nmin: + console.log("[red]" + oob_error) # Input too low failstate + continue + if nmax is not None and user_input > nmax: + console.log("[red]" + oob_error) # Input too high + continue + break # Successful type conversion and number in bounds + except ValueError: + console.log("[red]" + err_message) # Type conversion failed + continue + if ( + nmin is not None and len(user_input) < nmin + ): # Check if string is long enough + console.log("[red]" + oob_error) + continue + if ( + nmax is not None and len(user_input) > nmax + ): # Check if string is not too long + console.log("[red]" + oob_error) + continue + break + console.log("[red]" + err_message) + + return user_input if os.path.isfile(".setup-done-before"): - console.log( - "[red]Setup was already completed! Please make sure you have to run this script again. If that is such, delete the file .setup-done-before" - ) - exit() + console.log( + "[red]Setup was already completed! Please make sure you have to run this script again. If that is such, delete the file .setup-done-before" + ) + exit() # These lines ensure the user: # - knows they are in setup mode @@ -68,26 +68,26 @@ if os.path.isfile(".setup-done-before"): print_step("Setup Assistant") print_markdown( - "### You're in the setup wizard. Ensure you're supposed to be here, then type yes to continue. If you're not sure, type no to quit." + "### You're in the setup wizard. Ensure you're supposed to be here, then type yes to continue. If you're not sure, type no to quit." ) # This Input is used to ensure the user is sure they want to continue. if input("Are you sure you want to continue? > ").strip().casefold() != "yes": - console.print("[red]Exiting...") - exit() + console.print("[red]Exiting...") + exit() # This code is inaccessible if the prior check fails, and thus an else statement is unnecessary # Again, let them know they are about to erase all other setup data. console.print( - "[bold red] This will overwrite your current settings. Are you sure you want to continue? [bold green]yes/no" + "[bold red] This will overwrite your current settings. Are you sure you want to continue? [bold green]yes/no" ) if input("Are you sure you want to continue? > ").strip().casefold() != "yes": - console.print("[red]Abort mission! Exiting...") - exit() + console.print("[red]Abort mission! Exiting...") + exit() # This is once again inaccessible if the prior checks fail # Once they confirm, move on with the script. console.print("[bold green]Alright! Let's get started!") @@ -103,18 +103,18 @@ console.log("[bold green]Opacity (range of 0-1, decimals are OK)") console.log("[bold green]Subreddit (without r/ or /r/)") console.log("[bold green]Theme (light or dark)") console.print( - "[green]If you don't have these, please follow the instructions in the README.md file to set them up." + "[green]If you don't have these, please follow the instructions in the README.md file to set them up." ) console.print( - "[green]If you do have these, type yes to continue. If you dont, go ahead and grab those quickly and come back." + "[green]If you do have these, type yes to continue. If you dont, go ahead and grab those quickly and come back." ) print() if input("Are you sure you have the credentials? > ").strip().casefold() != "yes": - console.print("[red]I don't understand that.") - console.print("[red]Exiting...") - exit() + console.print("[red]I don't understand that.") + console.print("[red]Exiting...") + exit() console.print("[bold green]Alright! Let's get started!") @@ -123,69 +123,69 @@ console.print("[bold green]Alright! Let's get started!") console.log("Enter your credentials now.") client_id = handle_input( - "Client ID > ", - False, - "[-a-zA-Z0-9._~+/]+=*", - "That is somehow not a correct ID, try again.", - 12, - 30, - "The ID should be over 12 and under 30 characters, double check your input.", + "Client ID > ", + False, + "[-a-zA-Z0-9._~+/]+=*", + "That is somehow not a correct ID, try again.", + 12, + 30, + "The ID should be over 12 and under 30 characters, double check your input.", ) client_sec = handle_input( - "Client Secret > ", - False, - "[-a-zA-Z0-9._~+/]+=*", - "That is somehow not a correct secret, try again.", - 20, - 40, - "The secret should be over 20 and under 40 characters, double check your input.", + "Client Secret > ", + False, + "[-a-zA-Z0-9._~+/]+=*", + "That is somehow not a correct secret, try again.", + 20, + 40, + "The secret should be over 20 and under 40 characters, double check your input.", ) user = handle_input( - "Username > ", - False, - r"[_0-9a-zA-Z]+", - "That is not a valid user", - 3, - 20, - "A username HAS to be between 3 and 20 characters", + "Username > ", + False, + r"[_0-9a-zA-Z]+", + "That is not a valid user", + 3, + 20, + "A username HAS to be between 3 and 20 characters", ) passw = handle_input("Password > ", False, ".*", "", 8, None, "Password too short") twofactor = handle_input( - "2fa Enabled? (yes/no) > ", - False, - r"(yes)|(no)", - "You need to input either yes or no", + "2fa Enabled? (yes/no) > ", + False, + r"(yes)|(no)", + "You need to input either yes or no", ) opacity = handle_input( - "Opacity? (range of 0-1) > ", - float, - ".*", - "You need to input a number between 0 and 1", - 0, - 1, - "Your number is not between 0 and 1", + "Opacity? (range of 0-1) > ", + float, + ".*", + "You need to input a number between 0 and 1", + 0, + 1, + "Your number is not between 0 and 1", ) subreddit = handle_input( - "Subreddit (without r/) > ", - False, - r"[_0-9a-zA-Z]+", - "This subreddit cannot exist, make sure you typed it in correctly and removed the r/ (or /r/).", - 3, - 20, - "A subreddit name HAS to be between 3 and 20 characters", + "Subreddit (without r/) > ", + False, + r"[_0-9a-zA-Z]+", + "This subreddit cannot exist, make sure you typed it in correctly and removed the r/ (or /r/).", + 3, + 20, + "A subreddit name HAS to be between 3 and 20 characters", ) theme = handle_input( - "Theme? (light or dark) > ", - False, - r"(light)|(dark)", - "You need to input 'light' or 'dark'", + "Theme? (light or dark) > ", + False, + r"(light)|(dark)", + "You need to input 'light' or 'dark'", ) loader = Loader("Attempting to save your credentials...", "Done!").start() # you can also put a while loop here, e.g. while VideoIsBeingMade == True: ... console.log("Writing to the .env file...") with open(".env", "w") as f: - f.write( - f"""REDDIT_CLIENT_ID="{client_id}" + f.write( + f"""REDDIT_CLIENT_ID="{client_id}" REDDIT_CLIENT_SECRET="{client_sec}" REDDIT_USERNAME="{user}" REDDIT_PASSWORD="{passw}" @@ -194,12 +194,12 @@ THEME="{theme}" SUBREDDIT="{subreddit}" OPACITY={opacity} """ - ) + ) with open(".setup-done-before", "w") as f: - f.write( - "This file blocks the setup assistant from running again. Delete this file to run setup again." - ) + f.write( + "This file blocks the setup assistant from running again. Delete this file to run setup again." + ) loader.stop() diff --git a/utils/cleanup.py b/utils/cleanup.py index f3a6c61..320ce49 100644 --- a/utils/cleanup.py +++ b/utils/cleanup.py @@ -3,22 +3,22 @@ from os.path import exists def cleanup() -> int: - if exists("./assets/temp"): - count = 0 - files = [ - f for f in os.listdir(".") if f.endswith(".mp4") and "temp" in f.lower() - ] - count += len(files) - for f in files: - os.remove(f) - try: - for file in os.listdir("./assets/temp/mp4"): - count += 1 - os.remove("./assets/temp/mp4/" + file) - except FileNotFoundError: - pass - for file in os.listdir("./assets/temp/mp3"): - count += 1 - os.remove("./assets/temp/mp3/" + file) - return count - return 0 + if exists("./assets/temp"): + count = 0 + files = [ + f for f in os.listdir(".") if f.endswith(".mp4") and "temp" in f.lower() + ] + count += len(files) + for f in files: + os.remove(f) + try: + for file in os.listdir("./assets/temp/mp4"): + count += 1 + os.remove("./assets/temp/mp4/" + file) + except FileNotFoundError: + pass + for file in os.listdir("./assets/temp/mp3"): + count += 1 + os.remove("./assets/temp/mp3/" + file) + return count + return 0 diff --git a/utils/config.py b/utils/config.py index 000b615..0216141 100644 --- a/utils/config.py +++ b/utils/config.py @@ -3,36 +3,36 @@ from dotenv import dotenv_values DEFAULTS = { - "SUBREDDIT": "AskReddit", - "ALLOW_NSFW": "False", - "POST_ID": "", - "THEME": "DARK", - "REDDIT_2FA": "no", - "TIMES_TO_RUN": "", - "MAX_COMMENT_LENGTH": "500", - "OPACITY": "1", - "VOICE": "en_us_001", - "STORYMODE": "False", + "SUBREDDIT": "AskReddit", + "ALLOW_NSFW": "False", + "POST_ID": "", + "THEME": "DARK", + "REDDIT_2FA": "no", + "TIMES_TO_RUN": "", + "MAX_COMMENT_LENGTH": "500", + "OPACITY": "1", + "VOICE": "en_us_001", + "STORYMODE": "False", } class Config: - def __init__(self): - self.raw = dotenv_values("../.env") - self.load_attrs() - - def __getattr__(self, attr): # code completion for attributes fix. - return getattr(self, attr) - - def load_attrs(self): - for key, value in self.raw.items(): - self.add_attr(key, value) - - def add_attr(self, key, value): - if value is None or value == "": - setattr(self, key, DEFAULTS[key]) - else: - setattr(self, key, str(value)) + def __init__(self): + self.raw = dotenv_values("../.env") + self.load_attrs() + + def __getattr__(self, attr): # code completion for attributes fix. + return getattr(self, attr) + + def load_attrs(self): + for key, value in self.raw.items(): + self.add_attr(key, value) + + def add_attr(self, key, value): + if value is None or value == "": + setattr(self, key, DEFAULTS[key]) + else: + setattr(self, key, str(value)) config = Config() diff --git a/utils/console.py b/utils/console.py index 11ee429..1b4e3cb 100644 --- a/utils/console.py +++ b/utils/console.py @@ -9,19 +9,19 @@ console = Console() def print_markdown(text): - """Prints a rich info message. Support Markdown syntax.""" + """Prints a rich info message. Support Markdown syntax.""" - md = Padding(Markdown(text), 2) - console.print(md) + md = Padding(Markdown(text), 2) + console.print(md) def print_step(text): - """Prints a rich info message.""" + """Prints a rich info message.""" - panel = Panel(Text(text, justify="left")) - console.print(panel) + panel = Panel(Text(text, justify="left")) + console.print(panel) def print_substep(text, style=""): - """Prints a rich info message without the panelling.""" - console.print(text, style=style) + """Prints a rich info message without the panelling.""" + console.print(text, style=style) diff --git a/utils/loader.py b/utils/loader.py index f23a716..ac1c535 100644 --- a/utils/loader.py +++ b/utils/loader.py @@ -9,43 +9,43 @@ from time import sleep class Loader: - def __init__(self, desc="Loading...", end="Done!", timeout=0.1): - """ - A loader-like context manager - - Args: - desc (str, optional): The loader's description. Defaults to "Loading...". - end (str, optional): Final print. Defaults to "Done!". - timeout (float, optional): Sleep time between prints. Defaults to 0.1. - """ - self.desc = desc - self.end = end - self.timeout = timeout - - self._thread = Thread(target=self._animate, daemon=True) - self.steps = ["âĸŋ", "âŖģ", "âŖŊ", "âŖž", "âŖˇ", "âŖ¯", "âŖŸ", "âĄŋ"] - self.done = False - - def start(self): - self._thread.start() - return self - - def _animate(self): - for c in cycle(self.steps): - if self.done: - break - print(f"\r{self.desc} {c}", flush=True, end="") - sleep(self.timeout) - - def __enter__(self): - self.start() - - def stop(self): - self.done = True - cols = get_terminal_size((80, 20)).columns - print("\r" + " " * cols, end="", flush=True) - print(f"\r{self.end}", flush=True) - - def __exit__(self, exc_type, exc_value, tb): - # handle exceptions with those variables ^ - self.stop() + def __init__(self, desc="Loading...", end="Done!", timeout=0.1): + """ + A loader-like context manager + + Args: + desc (str, optional): The loader's description. Defaults to "Loading...". + end (str, optional): Final print. Defaults to "Done!". + timeout (float, optional): Sleep time between prints. Defaults to 0.1. + """ + self.desc = desc + self.end = end + self.timeout = timeout + + self._thread = Thread(target=self._animate, daemon=True) + self.steps = ["âĸŋ", "âŖģ", "âŖŊ", "âŖž", "âŖˇ", "âŖ¯", "âŖŸ", "âĄŋ"] + self.done = False + + def start(self): + self._thread.start() + return self + + def _animate(self): + for c in cycle(self.steps): + if self.done: + break + print(f"\r{self.desc} {c}", flush=True, end="") + sleep(self.timeout) + + def __enter__(self): + self.start() + + def stop(self): + self.done = True + cols = get_terminal_size((80, 20)).columns + print("\r" + " " * cols, end="", flush=True) + print(f"\r{self.end}", flush=True) + + def __exit__(self, exc_type, exc_value, tb): + # handle exceptions with those variables ^ + self.stop() diff --git a/utils/subreddit.py b/utils/subreddit.py index 2a1114a..15185e2 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -5,28 +5,28 @@ from utils.console import print_substep def get_subreddit_undone(submissions: List, subreddit): - """ - recursively checks if the top submission in the list was already done. - """ - with open("./video_creation/data/videos.json", "r") as done_vids_raw: - done_videos = json.load(done_vids_raw) - for submission in submissions: - if already_done(done_videos, submission): - continue - if submission.over_18: - if getenv("ALLOW_NSFW").casefold() == "false": - print_substep("NSFW Post Detected. Skipping...") - continue - return submission - print('all submissions have been done going by top submission order') - return get_subreddit_undone( - subreddit.top(time_filter="hour"), subreddit - ) # all of the videos in hot have already been done + """ + recursively checks if the top submission in the list was already done. + """ + with open("./video_creation/data/videos.json", "r") as done_vids_raw: + done_videos = json.load(done_vids_raw) + for submission in submissions: + if already_done(done_videos, submission): + continue + if submission.over_18: + if getenv("ALLOW_NSFW").casefold() == "false": + print_substep("NSFW Post Detected. Skipping...") + continue + return submission + print('all submissions have been done going by top submission order') + return get_subreddit_undone( + subreddit.top(time_filter="hour"), subreddit + ) # all of the videos in hot have already been done def already_done(done_videos: list, submission): - for video in done_videos: - if video["id"] == str(submission): - return True - return False + for video in done_videos: + if video["id"] == str(submission): + return True + return False diff --git a/utils/videos.py b/utils/videos.py index 51a2704..0c1a27f 100755 --- a/utils/videos.py +++ b/utils/videos.py @@ -5,19 +5,19 @@ from utils.console import print_step def check_done( - redditobj, + redditobj, ): # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack - """params: - reddit_object: The Reddit Object you received in askreddit.py""" - with open("./video_creation/data/videos.json", "r") as done_vids_raw: - done_videos = json.load(done_vids_raw) - for video in done_videos: - if video["id"] == str(redditobj): - if getenv("POST_ID"): - print_step( - "You already have done this video but since it was declared specifically in the .env file the program will continue" - ) - return redditobj - print_step("Getting new post as the current one has already been done") - return None - return redditobj + """params: + reddit_object: The Reddit Object you received in askreddit.py""" + with open("./video_creation/data/videos.json", "r") as done_vids_raw: + done_videos = json.load(done_vids_raw) + for video in done_videos: + if video["id"] == str(redditobj): + if getenv("POST_ID"): + print_step( + "You already have done this video but since it was declared specifically in the .env file the program will continue" + ) + return redditobj + print_step("Getting new post as the current one has already been done") + return None + return redditobj diff --git a/utils/voice.py b/utils/voice.py index 120ee60..32333dc 100644 --- a/utils/voice.py +++ b/utils/voice.py @@ -2,21 +2,21 @@ import re def sanitize_text(text): - """ - Sanitizes the text for tts. - What gets removed: - - following characters`^_~@!&;#:-%“”‘"%*/{}[]()\|<>?=+` - - any http or https links - """ + """ + Sanitizes the text for tts. + What gets removed: + - following characters`^_~@!&;#:-%“”‘"%*/{}[]()\|<>?=+` + - any http or https links + """ - # remove any urls from the text - regex_urls = r"((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*" + # remove any urls from the text + regex_urls = r"((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*" - result = re.sub(regex_urls, " ", text) + result = re.sub(regex_urls, " ", text) - # note: not removing apostrophes - regex_expr = r"\s['|’]|['|’]\s|[\^_~@!&;#:\-%“”‘\"%\*/{}\[\]\(\)\\|<>=+]" - result = re.sub(regex_expr, " ", result) + # note: not removing apostrophes + regex_expr = r"\s['|’]|['|’]\s|[\^_~@!&;#:\-%“”‘\"%\*/{}\[\]\(\)\\|<>=+]" + result = re.sub(regex_expr, " ", result) - # remove extra whitespace - return " ".join(result.split()) + # remove extra whitespace + return " ".join(result.split()) diff --git a/video_creation/background.py b/video_creation/background.py index d1a7948..146a63f 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -9,55 +9,55 @@ from utils.console import print_step, print_substep def get_start_and_end_times(video_length, length_of_clip): - random_time = randrange(180, int(length_of_clip) - int(video_length)) - return random_time, random_time + video_length + random_time = randrange(180, int(length_of_clip) - int(video_length)) + return random_time, random_time + video_length def download_background(): - """Downloads the backgrounds/s video from YouTube.""" - Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True) - background_options = [ # uri , filename , credit - ("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"), - # ( - # "https://www.youtube.com/watch?v=2X9QGY__0II", - # "rocket_league.mp4", - # "Orbital Gameplay", - # ), - ] - # note: make sure the file name doesn't include an - in it - if not len(listdir("./assets/backgrounds")) >= len( - background_options - ): # if there are any background videos not installed - print_step( - "We need to download the backgrounds videos. they are fairly large but it's only done once. 😎" - ) - print_substep("Downloading the backgrounds videos... please be patient 🙏 ") - for uri, filename, credit in background_options: - if Path(f"assets/backgrounds/{credit}-{filename}").is_file(): - continue # adds check to see if file exists before downloading - print_substep(f"Downloading {filename} from {uri}") - YouTube(uri).streams.filter(res="1080p").first().download( - "assets/backgrounds", filename=f"{credit}-{filename}" - ) - - print_substep( - "Background videos downloaded successfully! 🎉", style="bold green" - ) + """Downloads the backgrounds/s video from YouTube.""" + Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True) + background_options = [ # uri , filename , credit + ("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"), + # ( + # "https://www.youtube.com/watch?v=2X9QGY__0II", + # "rocket_league.mp4", + # "Orbital Gameplay", + # ), + ] + # note: make sure the file name doesn't include an - in it + if not len(listdir("./assets/backgrounds")) >= len( + background_options + ): # if there are any background videos not installed + print_step( + "We need to download the backgrounds videos. they are fairly large but it's only done once. 😎" + ) + print_substep("Downloading the backgrounds videos... please be patient 🙏 ") + for uri, filename, credit in background_options: + if Path(f"assets/backgrounds/{credit}-{filename}").is_file(): + continue # adds check to see if file exists before downloading + print_substep(f"Downloading {filename} from {uri}") + YouTube(uri).streams.filter(res="1080p").first().download( + "assets/backgrounds", filename=f"{credit}-{filename}" + ) + + print_substep( + "Background videos downloaded successfully! 🎉", style="bold green" + ) def chop_background_video(video_length): - print_step("Finding a spot in the backgrounds video to chop...✂ī¸") - choice = random.choice(listdir("assets/backgrounds")) - environ["background_credit"] = choice.split("-")[0] - - background = VideoFileClip(f"assets/backgrounds/{choice}") - - start_time, end_time = get_start_and_end_times(video_length, background.duration) - ffmpeg_extract_subclip( - f"assets/backgrounds/{choice}", - start_time, - end_time, - targetname="assets/temp/background.mp4", - ) - print_substep("Background video chopped successfully!", style="bold green") - return True + print_step("Finding a spot in the backgrounds video to chop...✂ī¸") + choice = random.choice(listdir("assets/backgrounds")) + environ["background_credit"] = choice.split("-")[0] + + background = VideoFileClip(f"assets/backgrounds/{choice}") + + start_time, end_time = get_start_and_end_times(video_length, background.duration) + ffmpeg_extract_subclip( + f"assets/backgrounds/{choice}", + start_time, + end_time, + targetname="assets/temp/background.mp4", + ) + print_substep("Background video chopped successfully!", style="bold green") + return True diff --git a/video_creation/cookies.json b/video_creation/cookies.json index 2e4e116..829ad0e 100644 --- a/video_creation/cookies.json +++ b/video_creation/cookies.json @@ -1,8 +1,8 @@ [ { - "name": "USER", - "value": "eyJwcmVmcyI6eyJ0b3BDb250ZW50RGlzbWlzc2FsVGltZSI6MCwiZ2xvYmFsVGhlbWUiOiJSRURESVQiLCJuaWdodG1vZGUiOnRydWUsImNvbGxhcHNlZFRyYXlTZWN0aW9ucyI6eyJmYXZvcml0ZXMiOmZhbHNlLCJtdWx0aXMiOmZhbHNlLCJtb2RlcmF0aW5nIjpmYWxzZSwic3Vic2NyaXB0aW9ucyI6ZmFsc2UsInByb2ZpbGVzIjpmYWxzZX0sInRvcENvbnRlbnRUaW1lc0Rpc21pc3NlZCI6MH19", - "domain": ".reddit.com", - "path": "/" + "name": "USER", + "value": "eyJwcmVmcyI6eyJ0b3BDb250ZW50RGlzbWlzc2FsVGltZSI6MCwiZ2xvYmFsVGhlbWUiOiJSRURESVQiLCJuaWdodG1vZGUiOnRydWUsImNvbGxhcHNlZFRyYXlTZWN0aW9ucyI6eyJmYXZvcml0ZXMiOmZhbHNlLCJtdWx0aXMiOmZhbHNlLCJtb2RlcmF0aW5nIjpmYWxzZSwic3Vic2NyaXB0aW9ucyI6ZmFsc2UsInByb2ZpbGVzIjpmYWxzZX0sInRvcENvbnRlbnRUaW1lc0Rpc21pc3NlZCI6MH19", + "domain": ".reddit.com", + "path": "/" } ] diff --git a/video_creation/data/cookie-dark-mode.json b/video_creation/data/cookie-dark-mode.json index 1ed51a9..774f4cc 100644 --- a/video_creation/data/cookie-dark-mode.json +++ b/video_creation/data/cookie-dark-mode.json @@ -1,14 +1,14 @@ [ { - "name": "USER", - "value": "eyJwcmVmcyI6eyJ0b3BDb250ZW50RGlzbWlzc2FsVGltZSI6MCwiZ2xvYmFsVGhlbWUiOiJSRURESVQiLCJuaWdodG1vZGUiOnRydWUsImNvbGxhcHNlZFRyYXlTZWN0aW9ucyI6eyJmYXZvcml0ZXMiOmZhbHNlLCJtdWx0aXMiOmZhbHNlLCJtb2RlcmF0aW5nIjpmYWxzZSwic3Vic2NyaXB0aW9ucyI6ZmFsc2UsInByb2ZpbGVzIjpmYWxzZX0sInRvcENvbnRlbnRUaW1lc0Rpc21pc3NlZCI6MH19", - "domain": ".reddit.com", - "path": "/" + "name": "USER", + "value": "eyJwcmVmcyI6eyJ0b3BDb250ZW50RGlzbWlzc2FsVGltZSI6MCwiZ2xvYmFsVGhlbWUiOiJSRURESVQiLCJuaWdodG1vZGUiOnRydWUsImNvbGxhcHNlZFRyYXlTZWN0aW9ucyI6eyJmYXZvcml0ZXMiOmZhbHNlLCJtdWx0aXMiOmZhbHNlLCJtb2RlcmF0aW5nIjpmYWxzZSwic3Vic2NyaXB0aW9ucyI6ZmFsc2UsInByb2ZpbGVzIjpmYWxzZX0sInRvcENvbnRlbnRUaW1lc0Rpc21pc3NlZCI6MH19", + "domain": ".reddit.com", + "path": "/" }, { - "name": "eu_cookie", - "value": "{%22opted%22:true%2C%22nonessential%22:false}", - "domain": ".reddit.com", - "path": "/" + "name": "eu_cookie", + "value": "{%22opted%22:true%2C%22nonessential%22:false}", + "domain": ".reddit.com", + "path": "/" } ] diff --git a/video_creation/data/cookie-light-mode.json b/video_creation/data/cookie-light-mode.json index 87eeec9..048a3e3 100644 --- a/video_creation/data/cookie-light-mode.json +++ b/video_creation/data/cookie-light-mode.json @@ -1,8 +1,8 @@ [ { - "name": "eu_cookie", - "value": "{%22opted%22:true%2C%22nonessential%22:false}", - "domain": ".reddit.com", - "path": "/" + "name": "eu_cookie", + "value": "{%22opted%22:true%2C%22nonessential%22:false}", + "domain": ".reddit.com", + "path": "/" } ] diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 60a1f6a..1eef620 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -5,13 +5,13 @@ import time from os.path import exists from moviepy.editor import ( - VideoFileClip, - AudioFileClip, - ImageClip, - concatenate_videoclips, - concatenate_audioclips, - CompositeAudioClip, - CompositeVideoClip, + VideoFileClip, + AudioFileClip, + ImageClip, + concatenate_videoclips, + concatenate_audioclips, + CompositeAudioClip, + CompositeVideoClip, ) from moviepy.video.io import ffmpeg_tools from rich.console import Console @@ -26,125 +26,125 @@ W, H = 1080, 1920 def make_final_video(number_of_clips, length): - print_step("Creating the final video đŸŽĨ") - VideoFileClip.reW = lambda clip: clip.resize(width=W) - VideoFileClip.reH = lambda clip: clip.resize(width=H) - opacity = os.getenv("OPACITY") - background_clip = ( - VideoFileClip("assets/temp/background.mp4") - .without_audio() - .resize(height=H) - .crop(x1=1166.6, y1=0, x2=2246.6, y2=1920) - ) - - # Gather all audio clips - audio_clips = [] - for i in range(0, number_of_clips): - audio_clips.append(AudioFileClip(f"assets/temp/mp3/{i}.mp3")) - audio_clips.insert(0, AudioFileClip("assets/temp/mp3/title.mp3")) - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) - - # Get sum of all clip lengths - total_length = sum([clip.duration for clip in audio_clips]) - # round total_length to an integer - int_total_length = round(total_length) - # Output Length - console.log(f"[bold green] Video Will Be: {int_total_length} Seconds Long") - - # add title to video - image_clips = [] - # Gather all images - if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE - image_clips.insert( - 0, - ImageClip("assets/temp/png/title.png") - .set_duration(audio_clips[0].duration) - .set_position("center") - .resize(width=W - 100) - .set_opacity(float(opacity)), - ) - else: - image_clips.insert( - 0, - ImageClip("assets/temp/png/title.png") - .set_duration(audio_clips[0].duration) - .set_position("center") - .resize(width=W - 100), - ) - - for i in range(0, number_of_clips): - if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE - image_clips.append( - ImageClip(f"assets/temp/png/comment_{i}.png") - .set_duration(audio_clips[i + 1].duration) - .set_position("center") - .resize(width=W - 100), - ) - else: - image_clips.append( - ImageClip(f"assets/temp/png/comment_{i}.png") - .set_duration(audio_clips[i + 1].duration) - .set_position("center") - .resize(width=W - 100) - .set_opacity(float(opacity)), - ) - - # if os.path.exists("assets/mp3/posttext.mp3"): - # image_clips.insert( - # 0, - # ImageClip("assets/png/title.png") - # .set_duration(audio_clips[0].duration + audio_clips[1].duration) - # .set_position("center") - # .resize(width=W - 100) - # .set_opacity(float(opacity)), - # ) - # else: - image_concat = concatenate_videoclips(image_clips).set_position(("center", "center")) - image_concat.audio = audio_composite - final = CompositeVideoClip([background_clip, image_concat]) - - def get_video_title() -> str: - title = os.getenv("VIDEO_TITLE") or "final_video" - if len(title) <= 35: - return title - else: - return title[0:30] + "..." - - filename = f"{get_video_title()}.mp4" - - def save_data(): - with open("./video_creation/data/videos.json", "r+") as raw_vids: - done_vids = json.load(raw_vids) - if str(subreddit.submission.id) in [video["id"] for video in done_vids]: - return # video already done but was specified to continue anyway in the .env file - payload = { - "id": str(os.getenv("VIDEO_ID")), - "time": str(int(time.time())), - "background_credit": str(os.getenv("background_credit")), - "reddit_title": str(os.getenv("VIDEO_TITLE")), - "filename": filename, - } - done_vids.append(payload) - raw_vids.seek(0) - json.dump(done_vids, raw_vids, ensure_ascii=False, indent=4) - - save_data() - if not exists("./results"): - print_substep("the results folder didn't exist so I made it") - os.mkdir("./results") - - final.write_videofile("assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k") - ffmpeg_tools.ffmpeg_extract_subclip( - "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" - ) - # os.remove("assets/temp/temp.mp4") - - print_step("Removing temporary files 🗑") - cleanups = cleanup() - print_substep(f"Removed {cleanups} temporary files 🗑") - print_substep("See result in the results folder!") - - print_step( - f"Reddit title: {os.getenv('VIDEO_TITLE')} \n Background Credit: {os.getenv('background_credit')}" - ) + print_step("Creating the final video đŸŽĨ") + VideoFileClip.reW = lambda clip: clip.resize(width=W) + VideoFileClip.reH = lambda clip: clip.resize(width=H) + opacity = os.getenv("OPACITY") + background_clip = ( + VideoFileClip("assets/temp/background.mp4") + .without_audio() + .resize(height=H) + .crop(x1=1166.6, y1=0, x2=2246.6, y2=1920) + ) + + # Gather all audio clips + audio_clips = [] + for i in range(0, number_of_clips): + audio_clips.append(AudioFileClip(f"assets/temp/mp3/{i}.mp3")) + audio_clips.insert(0, AudioFileClip("assets/temp/mp3/title.mp3")) + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + + # Get sum of all clip lengths + total_length = sum([clip.duration for clip in audio_clips]) + # round total_length to an integer + int_total_length = round(total_length) + # Output Length + console.log(f"[bold green] Video Will Be: {int_total_length} Seconds Long") + + # add title to video + image_clips = [] + # Gather all images + if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE + image_clips.insert( + 0, + ImageClip("assets/temp/png/title.png") + .set_duration(audio_clips[0].duration) + .set_position("center") + .resize(width=W - 100) + .set_opacity(float(opacity)), + ) + else: + image_clips.insert( + 0, + ImageClip("assets/temp/png/title.png") + .set_duration(audio_clips[0].duration) + .set_position("center") + .resize(width=W - 100), + ) + + for i in range(0, number_of_clips): + if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE + image_clips.append( + ImageClip(f"assets/temp/png/comment_{i}.png") + .set_duration(audio_clips[i + 1].duration) + .set_position("center") + .resize(width=W - 100), + ) + else: + image_clips.append( + ImageClip(f"assets/temp/png/comment_{i}.png") + .set_duration(audio_clips[i + 1].duration) + .set_position("center") + .resize(width=W - 100) + .set_opacity(float(opacity)), + ) + + # if os.path.exists("assets/mp3/posttext.mp3"): + # image_clips.insert( + # 0, + # ImageClip("assets/png/title.png") + # .set_duration(audio_clips[0].duration + audio_clips[1].duration) + # .set_position("center") + # .resize(width=W - 100) + # .set_opacity(float(opacity)), + # ) + # else: + image_concat = concatenate_videoclips(image_clips).set_position(("center", "center")) + image_concat.audio = audio_composite + final = CompositeVideoClip([background_clip, image_concat]) + + def get_video_title() -> str: + title = os.getenv("VIDEO_TITLE") or "final_video" + if len(title) <= 35: + return title + else: + return title[0:30] + "..." + + filename = f"{get_video_title()}.mp4" + + def save_data(): + with open("./video_creation/data/videos.json", "r+") as raw_vids: + done_vids = json.load(raw_vids) + if str(subreddit.submission.id) in [video["id"] for video in done_vids]: + return # video already done but was specified to continue anyway in the .env file + payload = { + "id": str(os.getenv("VIDEO_ID")), + "time": str(int(time.time())), + "background_credit": str(os.getenv("background_credit")), + "reddit_title": str(os.getenv("VIDEO_TITLE")), + "filename": filename, + } + done_vids.append(payload) + raw_vids.seek(0) + json.dump(done_vids, raw_vids, ensure_ascii=False, indent=4) + + save_data() + if not exists("./results"): + print_substep("the results folder didn't exist so I made it") + os.mkdir("./results") + + final.write_videofile("assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k") + ffmpeg_tools.ffmpeg_extract_subclip( + "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" + ) + # os.remove("assets/temp/temp.mp4") + + print_step("Removing temporary files 🗑") + cleanups = cleanup() + print_substep(f"Removed {cleanups} temporary files 🗑") + print_substep("See result in the results folder!") + + print_step( + f"Reddit title: {os.getenv('VIDEO_TITLE')} \n Background Credit: {os.getenv('background_credit')}" + ) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 4095f72..25241e1 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -16,62 +16,62 @@ storymode = False def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): - """Downloads screenshots of reddit posts as they are seen on the web. - Args: - reddit_object: The Reddit Object you received in askreddit.py - screenshot_num: The number of screenshots you want to download. - """ - print_step("Downloading screenshots of reddit posts...") + """Downloads screenshots of reddit posts as they are seen on the web. + Args: + reddit_object: The Reddit Object you received in askreddit.py + screenshot_num: The number of screenshots you want to download. + """ + print_step("Downloading screenshots of reddit posts...") - # ! Make sure the reddit screenshots folder exists - Path("assets/temp/png").mkdir(parents=True, exist_ok=True) + # ! Make sure the reddit screenshots folder exists + Path("assets/temp/png").mkdir(parents=True, exist_ok=True) - with sync_playwright() as p: - print_substep("Launching Headless Browser...") + with sync_playwright() as p: + print_substep("Launching Headless Browser...") - browser = p.chromium.launch() - context = browser.new_context() + browser = p.chromium.launch() + context = browser.new_context() - if getenv("THEME").upper() == "DARK": - cookie_file = open("./video_creation/data/cookie-dark-mode.json") - else: - cookie_file = open("./video_creation/data/cookie-light-mode.json") - cookies = json.load(cookie_file) - context.add_cookies(cookies) # load preference cookies - # Get the thread screenshot - page = context.new_page() - page.goto(reddit_object["thread_url"]) - page.set_viewport_size(ViewportSize(width=1920, height=1080)) - if page.locator('[data-testid="content-gate"]').is_visible(): - # This means the post is NSFW and requires to click the proceed button. + if getenv("THEME").upper() == "DARK": + cookie_file = open("./video_creation/data/cookie-dark-mode.json") + else: + cookie_file = open("./video_creation/data/cookie-light-mode.json") + cookies = json.load(cookie_file) + context.add_cookies(cookies) # load preference cookies + # Get the thread screenshot + page = context.new_page() + page.goto(reddit_object["thread_url"]) + page.set_viewport_size(ViewportSize(width=1920, height=1080)) + if page.locator('[data-testid="content-gate"]').is_visible(): + # This means the post is NSFW and requires to click the proceed button. - print_substep("Post is NSFW. You are spicy...") - page.locator('[data-testid="content-gate"] button').click() - page.locator( - '[data-click-id="text"] button' - ).click() # Remove "Click to see nsfw" Button in Screenshot + print_substep("Post is NSFW. You are spicy...") + page.locator('[data-testid="content-gate"] button').click() + page.locator( + '[data-click-id="text"] button' + ).click() # Remove "Click to see nsfw" Button in Screenshot - page.locator('[data-test-id="post-content"]').screenshot( - path="assets/temp/png/title.png" - ) - if storymode: - page.locator('[data-click-id="text"]').screenshot( - path="assets/temp/png/story_content.png" - ) - else: - for idx, comment in track( - enumerate(reddit_object["comments"]), "Downloading screenshots..." - ): + page.locator('[data-test-id="post-content"]').screenshot( + path="assets/temp/png/title.png" + ) + if storymode: + page.locator('[data-click-id="text"]').screenshot( + path="assets/temp/png/story_content.png" + ) + else: + for idx, comment in track( + enumerate(reddit_object["comments"]), "Downloading screenshots..." + ): - # Stop if we have reached the screenshot_num - if idx >= screenshot_num: - break + # Stop if we have reached the screenshot_num + if idx >= screenshot_num: + break - if page.locator('[data-testid="content-gate"]').is_visible(): - page.locator('[data-testid="content-gate"] button').click() + if page.locator('[data-testid="content-gate"]').is_visible(): + page.locator('[data-testid="content-gate"] button').click() - page.goto(f'https://reddit.com{comment["comment_url"]}') - page.locator(f"#t1_{comment['comment_id']}").screenshot( - path=f"assets/temp/png/comment_{idx}.png" - ) - print_substep("Screenshots downloaded Successfully.", style="bold green") + page.goto(f'https://reddit.com{comment["comment_url"]}') + page.locator(f"#t1_{comment['comment_id']}").screenshot( + path=f"assets/temp/png/comment_{idx}.png" + ) + print_substep("Screenshots downloaded Successfully.", style="bold green") diff --git a/video_creation/voices.py b/video_creation/voices.py index b345079..960b5fa 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -20,60 +20,60 @@ VIDEO_LENGTH: int = 40 # secs def save_text_to_mp3(reddit_obj): - """Saves Text to MP3 files. - Args: - reddit_obj : The reddit object you received from the reddit API in the askreddit.py file. - """ - print_step("Saving Text to MP3 files...") - length = 0 + """Saves Text to MP3 files. + Args: + reddit_obj : The reddit object you received from the reddit API in the askreddit.py file. + """ + print_step("Saving Text to MP3 files...") + length = 0 - # Create a folder for the mp3 files. - Path("assets/temp/mp3").mkdir(parents=True, exist_ok=True) - TextToSpeech = TTS() - TextToSpeech.tts( - sanitize_text(reddit_obj["thread_title"]), - filename="assets/temp/mp3/title.mp3", - random_speaker=False, - ) - try: - length += MP3("assets/temp/mp3/title.mp3").info.length - except HeaderNotFoundError: # note to self AudioFileClip - length += sox.file_info.duration("assets/temp/mp3/title.mp3") - if getenv("STORYMODE").casefold() == "true": - TextToSpeech.tts( - sanitize_text(reddit_obj["thread_content"]), - filename="assets/temp/mp3/story_content.mp3", - random_speaker=False, - ) - # 'story_content' - com = 0 - for comment in track((reddit_obj["comments"]), "Saving..."): - # ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer - # but this is just a good_voices starting point - if length > VIDEO_LENGTH: - break + # Create a folder for the mp3 files. + Path("assets/temp/mp3").mkdir(parents=True, exist_ok=True) + TextToSpeech = TTS() + TextToSpeech.tts( + sanitize_text(reddit_obj["thread_title"]), + filename="assets/temp/mp3/title.mp3", + random_speaker=False, + ) + try: + length += MP3("assets/temp/mp3/title.mp3").info.length + except HeaderNotFoundError: # note to self AudioFileClip + length += sox.file_info.duration("assets/temp/mp3/title.mp3") + if getenv("STORYMODE").casefold() == "true": + TextToSpeech.tts( + sanitize_text(reddit_obj["thread_content"]), + filename="assets/temp/mp3/story_content.mp3", + random_speaker=False, + ) + # 'story_content' + com = 0 + for comment in track((reddit_obj["comments"]), "Saving..."): + # ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer + # but this is just a good_voices starting point + if length > VIDEO_LENGTH: + break - TextToSpeech.tts( - sanitize_text(comment["comment_body"]), - filename=f"assets/temp/mp3/{com}.mp3", - random_speaker=False, - ) - try: - length += MP3(f"assets/temp/mp3/{com}.mp3").info.length - com += 1 - except (HeaderNotFoundError, MutagenError, Exception): - try: - length += sox.file_info.duration(f"assets/temp/mp3/{com}.mp3") - com += 1 - except (OSError, IOError): - print( - "would have removed" - f"assets/temp/mp3/{com}.mp3" - f"assets/temp/png/comment_{com}.png" - ) - # remove(f"assets/temp/mp3/{com}.mp3") - # remove(f"assets/temp/png/comment_{com}.png")# todo might cause odd un-syncing + TextToSpeech.tts( + sanitize_text(comment["comment_body"]), + filename=f"assets/temp/mp3/{com}.mp3", + random_speaker=False, + ) + try: + length += MP3(f"assets/temp/mp3/{com}.mp3").info.length + com += 1 + except (HeaderNotFoundError, MutagenError, Exception): + try: + length += sox.file_info.duration(f"assets/temp/mp3/{com}.mp3") + com += 1 + except (OSError, IOError): + print( + "would have removed" + f"assets/temp/mp3/{com}.mp3" + f"assets/temp/png/comment_{com}.png" + ) + # remove(f"assets/temp/mp3/{com}.mp3") + # remove(f"assets/temp/png/comment_{com}.png")# todo might cause odd un-syncing - print_substep("Saved Text to MP3 files Successfully.", style="bold green") - # ! Return the index, so we know how many screenshots of comments we need to make. - return length, com + print_substep("Saved Text to MP3 files Successfully.", style="bold green") + # ! Return the index, so we know how many screenshots of comments we need to make. + return length, com From c4274d1d149f33df63ce192d13333b2e64f9c4f6 Mon Sep 17 00:00:00 2001 From: owengaspard Date: Sat, 18 Jun 2022 15:57:54 -0500 Subject: [PATCH 05/20] Add OS check, PS script (unfinished) Added a check for your operating system and a PowerShell script which is unfinished at the moment. --- utils/envUpdate.py | 24 ++++++++++++++++++------ utils/envUpdateWin.ps1 | 2 ++ 2 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 utils/envUpdateWin.ps1 diff --git a/utils/envUpdate.py b/utils/envUpdate.py index a95b48f..0b53ab3 100644 --- a/utils/envUpdate.py +++ b/utils/envUpdate.py @@ -1,18 +1,30 @@ import os import subprocess import tempfile -from os import path import logging -log = logging.getLogger(__name__) +from os import path +from sys import platform +log = logging.getLogger(__name__) def envUpdate(): if path.exists(".env.template"): - envTemplate = subprocess.check_output( - "awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", # noqa - shell=True, - ) + if platform == "win32" or platform == "cygwin": + #envTemplate = subprocess.run( + # [r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe', + # r'.\envUpdateWin.ps1'], + # stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + # shell=True + #) + print("Updating is not yet supported on Windows.") + return + elif platform == "darwin" or platform == "linux": + envTemplate = subprocess.check_output( + "awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", + shell=True, + ) + return tempEnv = tempfile.TemporaryFile() tempEnv.write(envTemplate) tempEnv.seek(0) diff --git a/utils/envUpdateWin.ps1 b/utils/envUpdateWin.ps1 new file mode 100644 index 0000000..a045a4b --- /dev/null +++ b/utils/envUpdateWin.ps1 @@ -0,0 +1,2 @@ +$envVars = Get-Content '.\.env' +$envVars -split "=" \ No newline at end of file From 928e50e08fe5c3e98c9a00bfea68e8835e194b04 Mon Sep 17 00:00:00 2001 From: owengaspard Date: Sat, 18 Jun 2022 16:05:18 -0500 Subject: [PATCH 06/20] Added PowerShell Script --- utils/envUpdate.py | 14 +++++++------- utils/envUpdateWin.ps1 | 5 +++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/utils/envUpdate.py b/utils/envUpdate.py index 0b53ab3..05b08da 100644 --- a/utils/envUpdate.py +++ b/utils/envUpdate.py @@ -11,13 +11,13 @@ log = logging.getLogger(__name__) def envUpdate(): if path.exists(".env.template"): if platform == "win32" or platform == "cygwin": - #envTemplate = subprocess.run( - # [r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe', - # r'.\envUpdateWin.ps1'], - # stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - # shell=True - #) - print("Updating is not yet supported on Windows.") + envTemplate = subprocess.run( + [r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe', + r'.\envUpdateWin.ps1'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + shell=True + print("Updating .env is unsupported on Windows.") + ) return elif platform == "darwin" or platform == "linux": envTemplate = subprocess.check_output( diff --git a/utils/envUpdateWin.ps1 b/utils/envUpdateWin.ps1 index a045a4b..79c8a04 100644 --- a/utils/envUpdateWin.ps1 +++ b/utils/envUpdateWin.ps1 @@ -1,2 +1,3 @@ -$envVars = Get-Content '.\.env' -$envVars -split "=" \ No newline at end of file +#$envVars = Get-Content '.\.env' +#$envVars -split "=" +Write-Host 'Updating the env is unsupported on Windows.' \ No newline at end of file From ddc402c48d79bbe26cbf93e76f9e3eb401581175 Mon Sep 17 00:00:00 2001 From: owengaspard Date: Sat, 18 Jun 2022 17:16:07 -0500 Subject: [PATCH 07/20] Added Windows support I have added Windows support using PowerShell. I made a script that does effectively what the GNU awk and grep commands do but in PowerShell for Windows compatibility. --- .env.template | 2 +- .gitignore | 3 ++- utils/envUpdate.py | 19 +++++++++---------- utils/envUpdateWin.ps1 | 12 +++++++++--- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.env.template b/.env.template index 173bacf..c94fa6a 100644 --- a/.env.template +++ b/.env.template @@ -36,4 +36,4 @@ VOICE="Matthew" # e.g. en_us_002 TTsChoice="polly" # todo add docs # IN-PROGRESS - not yet implemented -STORYMODE="False" +STORYMODE="False" \ No newline at end of file diff --git a/.gitignore b/.gitignore index d365d9e..1c28a20 100644 --- a/.gitignore +++ b/.gitignore @@ -170,4 +170,5 @@ reddit-bot-351418-5560ebc49cac.json /.idea *.pyc /video_creation/data/videos.json -utils/envUpdate.py.old \ No newline at end of file +utils/envUpdate.py.old +envVars.txt \ No newline at end of file diff --git a/utils/envUpdate.py b/utils/envUpdate.py index 05b08da..f225419 100644 --- a/utils/envUpdate.py +++ b/utils/envUpdate.py @@ -4,21 +4,16 @@ import tempfile import logging from os import path -from sys import platform +from sys import platform, stderr log = logging.getLogger(__name__) def envUpdate(): if path.exists(".env.template"): if platform == "win32" or platform == "cygwin": - envTemplate = subprocess.run( - [r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe', - r'.\envUpdateWin.ps1'], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - shell=True - print("Updating .env is unsupported on Windows.") - ) - return + runPS('utils\envUpdateWin.ps1') + f = open("envVars.txt", "rb") + envTemplate = f.read() elif platform == "darwin" or platform == "linux": envTemplate = subprocess.check_output( "awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", @@ -46,4 +41,8 @@ def envUpdate(): log.error( f"[ERROR] The following environment variables are missing: {missing}.)" ) - exit(-1) \ No newline at end of file + exit(-1) + +def runPS(cmd): + completed = subprocess.run(["powershell", "-Command", cmd], capture_output=True) + return completed \ No newline at end of file diff --git a/utils/envUpdateWin.ps1 b/utils/envUpdateWin.ps1 index 79c8a04..1eb8f64 100644 --- a/utils/envUpdateWin.ps1 +++ b/utils/envUpdateWin.ps1 @@ -1,3 +1,9 @@ -#$envVars = Get-Content '.\.env' -#$envVars -split "=" -Write-Host 'Updating the env is unsupported on Windows.' \ No newline at end of file +$envFile = Get-Content ".\.env.template" + +$envFile -split "=" | Where-Object {$_ -notmatch '\"'} | Set-Content ".\envVarsbefSpl.txt" +Get-Content ".\envVarsbefSpl.txt" | Where-Object {$_ -notmatch '\#'} | Set-Content ".\envVarsN.txt" +Get-Content ".\envVarsN.txt" | Where-Object {$_ -ne ''} | Set-Content ".\envVars.txt" +Remove-Item ".\envVarsbefSpl.txt" +Remove-Item ".\envVarsN.txt" + +Write-Host $nowSplit \ No newline at end of file From e6501f43376b133e82ad2c2e6a46d126201c14a9 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 18:28:25 -0400 Subject: [PATCH 08/20] fixed title opacity error --- video_creation/final_video.py | 258 +++++++++++++++++----------------- 1 file changed, 129 insertions(+), 129 deletions(-) diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 1eef620..37b1ac2 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -5,13 +5,13 @@ import time from os.path import exists from moviepy.editor import ( - VideoFileClip, - AudioFileClip, - ImageClip, - concatenate_videoclips, - concatenate_audioclips, - CompositeAudioClip, - CompositeVideoClip, + VideoFileClip, + AudioFileClip, + ImageClip, + concatenate_videoclips, + concatenate_audioclips, + CompositeAudioClip, + CompositeVideoClip, ) from moviepy.video.io import ffmpeg_tools from rich.console import Console @@ -26,125 +26,125 @@ W, H = 1080, 1920 def make_final_video(number_of_clips, length): - print_step("Creating the final video đŸŽĨ") - VideoFileClip.reW = lambda clip: clip.resize(width=W) - VideoFileClip.reH = lambda clip: clip.resize(width=H) - opacity = os.getenv("OPACITY") - background_clip = ( - VideoFileClip("assets/temp/background.mp4") - .without_audio() - .resize(height=H) - .crop(x1=1166.6, y1=0, x2=2246.6, y2=1920) - ) - - # Gather all audio clips - audio_clips = [] - for i in range(0, number_of_clips): - audio_clips.append(AudioFileClip(f"assets/temp/mp3/{i}.mp3")) - audio_clips.insert(0, AudioFileClip("assets/temp/mp3/title.mp3")) - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) - - # Get sum of all clip lengths - total_length = sum([clip.duration for clip in audio_clips]) - # round total_length to an integer - int_total_length = round(total_length) - # Output Length - console.log(f"[bold green] Video Will Be: {int_total_length} Seconds Long") - - # add title to video - image_clips = [] - # Gather all images - if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE - image_clips.insert( - 0, - ImageClip("assets/temp/png/title.png") - .set_duration(audio_clips[0].duration) - .set_position("center") - .resize(width=W - 100) - .set_opacity(float(opacity)), - ) - else: - image_clips.insert( - 0, - ImageClip("assets/temp/png/title.png") - .set_duration(audio_clips[0].duration) - .set_position("center") - .resize(width=W - 100), - ) - - for i in range(0, number_of_clips): - if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE - image_clips.append( - ImageClip(f"assets/temp/png/comment_{i}.png") - .set_duration(audio_clips[i + 1].duration) - .set_position("center") - .resize(width=W - 100), - ) - else: - image_clips.append( - ImageClip(f"assets/temp/png/comment_{i}.png") - .set_duration(audio_clips[i + 1].duration) - .set_position("center") - .resize(width=W - 100) - .set_opacity(float(opacity)), - ) - - # if os.path.exists("assets/mp3/posttext.mp3"): - # image_clips.insert( - # 0, - # ImageClip("assets/png/title.png") - # .set_duration(audio_clips[0].duration + audio_clips[1].duration) - # .set_position("center") - # .resize(width=W - 100) - # .set_opacity(float(opacity)), - # ) - # else: - image_concat = concatenate_videoclips(image_clips).set_position(("center", "center")) - image_concat.audio = audio_composite - final = CompositeVideoClip([background_clip, image_concat]) - - def get_video_title() -> str: - title = os.getenv("VIDEO_TITLE") or "final_video" - if len(title) <= 35: - return title - else: - return title[0:30] + "..." - - filename = f"{get_video_title()}.mp4" - - def save_data(): - with open("./video_creation/data/videos.json", "r+") as raw_vids: - done_vids = json.load(raw_vids) - if str(subreddit.submission.id) in [video["id"] for video in done_vids]: - return # video already done but was specified to continue anyway in the .env file - payload = { - "id": str(os.getenv("VIDEO_ID")), - "time": str(int(time.time())), - "background_credit": str(os.getenv("background_credit")), - "reddit_title": str(os.getenv("VIDEO_TITLE")), - "filename": filename, - } - done_vids.append(payload) - raw_vids.seek(0) - json.dump(done_vids, raw_vids, ensure_ascii=False, indent=4) - - save_data() - if not exists("./results"): - print_substep("the results folder didn't exist so I made it") - os.mkdir("./results") - - final.write_videofile("assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k") - ffmpeg_tools.ffmpeg_extract_subclip( - "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" - ) - # os.remove("assets/temp/temp.mp4") - - print_step("Removing temporary files 🗑") - cleanups = cleanup() - print_substep(f"Removed {cleanups} temporary files 🗑") - print_substep("See result in the results folder!") - - print_step( - f"Reddit title: {os.getenv('VIDEO_TITLE')} \n Background Credit: {os.getenv('background_credit')}" - ) + print_step("Creating the final video đŸŽĨ") + VideoFileClip.reW = lambda clip: clip.resize(width=W) + VideoFileClip.reH = lambda clip: clip.resize(width=H) + opacity = os.getenv("OPACITY") + background_clip = ( + VideoFileClip("assets/temp/background.mp4") + .without_audio() + .resize(height=H) + .crop(x1=1166.6, y1=0, x2=2246.6, y2=1920) + ) + + # Gather all audio clips + audio_clips = [] + for i in range(0, number_of_clips): + audio_clips.append(AudioFileClip(f"assets/temp/mp3/{i}.mp3")) + audio_clips.insert(0, AudioFileClip("assets/temp/mp3/title.mp3")) + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + + # Get sum of all clip lengths + total_length = sum([clip.duration for clip in audio_clips]) + # round total_length to an integer + int_total_length = round(total_length) + # Output Length + console.log(f"[bold green] Video Will Be: {int_total_length} Seconds Long") + + # add title to video + image_clips = [] + # Gather all images + if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE + image_clips.insert( + 0, + ImageClip("assets/temp/png/title.png") + .set_duration(audio_clips[0].duration) + .set_position("center") + .resize(width=W - 100), + ) + else: + image_clips.insert( + 0, + ImageClip("assets/temp/png/title.png") + .set_duration(audio_clips[0].duration) + .set_position("center") + .resize(width=W - 100) + .set_opacity(float(opacity)), + ) + + for i in range(0, number_of_clips): + if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE + image_clips.append( + ImageClip(f"assets/temp/png/comment_{i}.png") + .set_duration(audio_clips[i + 1].duration) + .set_position("center") + .resize(width=W - 100), + ) + else: + image_clips.append( + ImageClip(f"assets/temp/png/comment_{i}.png") + .set_duration(audio_clips[i + 1].duration) + .set_position("center") + .resize(width=W - 100) + .set_opacity(float(opacity)), + ) + + # if os.path.exists("assets/mp3/posttext.mp3"): + # image_clips.insert( + # 0, + # ImageClip("assets/png/title.png") + # .set_duration(audio_clips[0].duration + audio_clips[1].duration) + # .set_position("center") + # .resize(width=W - 100) + # .set_opacity(float(opacity)), + # ) + # else: + image_concat = concatenate_videoclips(image_clips).set_position(("center", "center")) + image_concat.audio = audio_composite + final = CompositeVideoClip([background_clip, image_concat]) + + def get_video_title() -> str: + title = os.getenv("VIDEO_TITLE") or "final_video" + if len(title) <= 35: + return title + else: + return title[0:30] + "..." + + filename = f"{get_video_title()}.mp4" + + def save_data(): + with open("./video_creation/data/videos.json", "r+") as raw_vids: + done_vids = json.load(raw_vids) + if str(subreddit.submission.id) in [video["id"] for video in done_vids]: + return # video already done but was specified to continue anyway in the .env file + payload = { + "id": str(os.getenv("VIDEO_ID")), + "time": str(int(time.time())), + "background_credit": str(os.getenv("background_credit")), + "reddit_title": str(os.getenv("VIDEO_TITLE")), + "filename": filename, + } + done_vids.append(payload) + raw_vids.seek(0) + json.dump(done_vids, raw_vids, ensure_ascii=False, indent=4) + + save_data() + if not exists("./results"): + print_substep("the results folder didn't exist so I made it") + os.mkdir("./results") + + final.write_videofile("assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k") + ffmpeg_tools.ffmpeg_extract_subclip( + "assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}" + ) + # os.remove("assets/temp/temp.mp4") + + print_step("Removing temporary files 🗑") + cleanups = cleanup() + print_substep(f"Removed {cleanups} temporary files 🗑") + print_substep("See result in the results folder!") + + print_step( + f"Reddit title: {os.getenv('VIDEO_TITLE')} \n Background Credit: {os.getenv('background_credit')}" + ) From c482266e5cb6edc695234f3130eae926a261e872 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 18:28:48 -0400 Subject: [PATCH 09/20] reformatted using black command "black . --line-length 101" --- TTS/GTTS.py | 18 +- TTS/POLLY.py | 164 +++++++++--------- TTS/TikTok.py | 218 ++++++++++++------------ TTS/swapper.py | 18 +- reddit/subreddit.py | 146 ++++++++-------- setup.py | 200 +++++++++++----------- utils/cleanup.py | 36 ++-- utils/config.py | 52 +++--- utils/console.py | 16 +- utils/loader.py | 80 ++++----- utils/subreddit.py | 42 ++--- utils/videos.py | 30 ++-- utils/voice.py | 28 +-- video_creation/background.py | 90 +++++----- video_creation/screenshot_downloader.py | 98 ++++++----- video_creation/voices.py | 108 ++++++------ 16 files changed, 666 insertions(+), 678 deletions(-) diff --git a/TTS/GTTS.py b/TTS/GTTS.py index e38353c..fcbcb9b 100644 --- a/TTS/GTTS.py +++ b/TTS/GTTS.py @@ -2,12 +2,12 @@ from gtts import gTTS class GTTS: - def tts( - self, - req_text: str = "Google Text To Speech", - filename: str = "title.mp3", - random_speaker=False, - censor=False, - ): - tts = gTTS(text=req_text, lang="en", slow=False) - tts.save(f"{filename}") + def tts( + self, + req_text: str = "Google Text To Speech", + filename: str = "title.mp3", + random_speaker=False, + censor=False, + ): + tts = gTTS(text=req_text, lang="en", slow=False) + tts.save(f"{filename}") diff --git a/TTS/POLLY.py b/TTS/POLLY.py index 4f8650c..da1fae0 100644 --- a/TTS/POLLY.py +++ b/TTS/POLLY.py @@ -9,21 +9,21 @@ from moviepy.audio.io.AudioFileClip import AudioFileClip from requests.exceptions import JSONDecodeError voices = [ - "Brian", - "Emma", - "Russell", - "Joey", - "Matthew", - "Joanna", - "Kimberly", - "Amy", - "Geraint", - "Nicole", - "Justin", - "Ivy", - "Kendra", - "Salli", - "Raveena", + "Brian", + "Emma", + "Russell", + "Joey", + "Matthew", + "Joanna", + "Kimberly", + "Amy", + "Geraint", + "Nicole", + "Justin", + "Ivy", + "Kendra", + "Salli", + "Raveena", ] @@ -31,78 +31,76 @@ voices = [ class POLLY: - def __init__(self): - self.url = "https://streamlabs.com/polly/speak" + def __init__(self): + self.url = "https://streamlabs.com/polly/speak" - def tts( - self, - req_text: str = "Amazon Text To Speech", - filename: str = "title.mp3", - random_speaker=False, - censor=False, - ): - if random_speaker: - voice = self.randomvoice() - else: - if not os.getenv("VOICE"): - return ValueError( - "Please set the environment variable VOICE to a valid voice. options are: {}".format( - voices - ) - ) - voice = str(os.getenv("VOICE")).capitalize() - body = {"voice": voice, "text": req_text, "service": "polly"} - response = requests.post(self.url, data=body) - try: - voice_data = requests.get(response.json()["speak_url"]) - with open(filename, "wb") as f: - f.write(voice_data.content) - except (KeyError, JSONDecodeError): - if response.json()["error"] == "Text length is too long!": - chunks = [ - m.group().strip() for m in re.finditer(r" *((.{0,499})(\.|.$))", req_text) - ] + def tts( + self, + req_text: str = "Amazon Text To Speech", + filename: str = "title.mp3", + random_speaker=False, + censor=False, + ): + if random_speaker: + voice = self.randomvoice() + else: + if not os.getenv("VOICE"): + return ValueError( + "Please set the environment variable VOICE to a valid voice. options are: {}".format( + voices + ) + ) + voice = str(os.getenv("VOICE")).capitalize() + body = {"voice": voice, "text": req_text, "service": "polly"} + response = requests.post(self.url, data=body) + try: + voice_data = requests.get(response.json()["speak_url"]) + with open(filename, "wb") as f: + f.write(voice_data.content) + except (KeyError, JSONDecodeError): + if response.json()["error"] == "Text length is too long!": + chunks = [m.group().strip() for m in re.finditer(r" *((.{0,499})(\.|.$))", req_text)] - audio_clips = [] - cbn = sox.Combiner() + audio_clips = [] + cbn = sox.Combiner() - chunkId = 0 - for chunk in chunks: - body = {"voice": voice, "text": chunk, "service": "polly"} - resp = requests.post(self.url, data=body) - voice_data = requests.get(resp.json()["speak_url"]) - with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: - out.write(voice_data.content) + chunkId = 0 + for chunk in chunks: + body = {"voice": voice, "text": chunk, "service": "polly"} + resp = requests.post(self.url, data=body) + voice_data = requests.get(resp.json()["speak_url"]) + with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: + out.write(voice_data.content) - audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) + audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) - chunkId = chunkId + 1 - try: - if len(audio_clips) > 1: - cbn.convert(samplerate=44100, n_channels=2) - cbn.build(audio_clips, filename, "concatenate") - else: - os.rename(audio_clips[0], filename) - except ( - sox.core.SoxError, - FileNotFoundError, - ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 - for clip in audio_clips: - i = audio_clips.index(clip) # get the index of the clip - audio_clips = ( - audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] - ) # replace the clip with an AudioFileClip - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) - audio_composite.write_audiofile(filename, 44100, 2, 2000, None) + chunkId = chunkId + 1 + try: + if len(audio_clips) > 1: + cbn.convert(samplerate=44100, n_channels=2) + cbn.build(audio_clips, filename, "concatenate") + else: + os.rename(audio_clips[0], filename) + except ( + sox.core.SoxError, + FileNotFoundError, + ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 + for clip in audio_clips: + i = audio_clips.index(clip) # get the index of the clip + audio_clips = ( + audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] + ) # replace the clip with an AudioFileClip + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + audio_composite.write_audiofile(filename, 44100, 2, 2000, None) - def make_readable(self, text): - """ - Amazon Polly fails to read some symbols properly such as '& (and)'. - So we normalize input text before passing it to the service - """ - text = text.replace("&", "and") - return text + def make_readable(self, text): + """ + Amazon Polly fails to read some symbols properly such as '& (and)'. + So we normalize input text before passing it to the service + """ + text = text.replace("&", "and") + return text - def randomvoice(self): - return random.choice(voices) + def randomvoice(self): + return random.choice(voices) diff --git a/TTS/TikTok.py b/TTS/TikTok.py index 5b31c00..662e498 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -15,49 +15,49 @@ from requests.adapters import HTTPAdapter, Retry # https://twitter.com/scanlime/status/1512598559769702406 nonhuman = [ # DISNEY VOICES - "en_us_ghostface", # Ghost Face - "en_us_chewbacca", # Chewbacca - "en_us_c3po", # C3PO - "en_us_stitch", # Stitch - "en_us_stormtrooper", # Stormtrooper - "en_us_rocket", # Rocket - # ENGLISH VOICES + "en_us_ghostface", # Ghost Face + "en_us_chewbacca", # Chewbacca + "en_us_c3po", # C3PO + "en_us_stitch", # Stitch + "en_us_stormtrooper", # Stormtrooper + "en_us_rocket", # Rocket + # ENGLISH VOICES ] human = [ - "en_au_001", # English AU - Female - "en_au_002", # English AU - Male - "en_uk_001", # English UK - Male 1 - "en_uk_003", # English UK - Male 2 - "en_us_001", # English US - Female (Int. 1) - "en_us_002", # English US - Female (Int. 2) - "en_us_006", # English US - Male 1 - "en_us_007", # English US - Male 2 - "en_us_009", # English US - Male 3 - "en_us_010", + "en_au_001", # English AU - Female + "en_au_002", # English AU - Male + "en_uk_001", # English UK - Male 1 + "en_uk_003", # English UK - Male 2 + "en_us_001", # English US - Female (Int. 1) + "en_us_002", # English US - Female (Int. 2) + "en_us_006", # English US - Male 1 + "en_us_007", # English US - Male 2 + "en_us_009", # English US - Male 3 + "en_us_010", ] voices = nonhuman + human noneng = [ - "fr_001", # French - Male 1 - "fr_002", # French - Male 2 - "de_001", # German - Female - "de_002", # German - Male - "es_002", # Spanish - Male - # AMERICA VOICES - "es_mx_002", # Spanish MX - Male - "br_001", # Portuguese BR - Female 1 - "br_003", # Portuguese BR - Female 2 - "br_004", # Portuguese BR - Female 3 - "br_005", # Portuguese BR - Male - # ASIA VOICES - "id_001", # Indonesian - Female - "jp_001", # Japanese - Female 1 - "jp_003", # Japanese - Female 2 - "jp_005", # Japanese - Female 3 - "jp_006", # Japanese - Male - "kr_002", # Korean - Male 1 - "kr_003", # Korean - Female - "kr_004", # Korean - Male 2 + "fr_001", # French - Male 1 + "fr_002", # French - Male 2 + "de_001", # German - Female + "de_002", # German - Male + "es_002", # Spanish - Male + # AMERICA VOICES + "es_mx_002", # Spanish MX - Male + "br_001", # Portuguese BR - Female 1 + "br_003", # Portuguese BR - Female 2 + "br_004", # Portuguese BR - Female 3 + "br_005", # Portuguese BR - Male + # ASIA VOICES + "id_001", # Indonesian - Female + "jp_001", # Japanese - Female 1 + "jp_003", # Japanese - Female 2 + "jp_005", # Japanese - Female 3 + "jp_006", # Japanese - Male + "kr_002", # Korean - Male 1 + "kr_003", # Korean - Female + "kr_004", # Korean - Male 2 ] @@ -66,74 +66,76 @@ noneng = [ class TikTok: # TikTok Text-to-Speech Wrapper - def __init__(self): - self.URI_BASE = "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=" - - def tts( - self, - req_text: str = "TikTok Text To Speech", - filename: str = "title.mp3", - random_speaker: bool = False, - censor=False, - ): - req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and") - if censor: - # req_text = pf.censor(req_text) - pass - voice = ( - self.randomvoice() if random_speaker else (os.getenv("VOICE") or random.choice(human)) - ) - - chunks = [m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text)] - - audio_clips = [] - cbn = sox.Combiner() - # cbn.set_input_format(file_type=["mp3" for _ in chunks]) - - chunkId = 0 - for chunk in chunks: - try: - r = requests.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") - except requests.exceptions.SSLError: - # https://stackoverflow.com/a/47475019/18516611 - session = requests.Session() - retry = Retry(connect=3, backoff_factor=0.5) - adapter = HTTPAdapter(max_retries=retry) - session.mount("http://", adapter) - session.mount("https://", adapter) - r = session.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") - print(r.text) - vstr = [r.json()["data"]["v_str"]][0] - b64d = base64.b64decode(vstr) - - with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: - out.write(b64d) - - audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) - - chunkId = chunkId + 1 - try: - if len(audio_clips) > 1: - cbn.convert(samplerate=44100, n_channels=2) - cbn.build(audio_clips, filename, "concatenate") - else: - os.rename(audio_clips[0], filename) - except ( - sox.core.SoxError, - FileNotFoundError, - ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 - for clip in audio_clips: - i = audio_clips.index(clip) # get the index of the clip - audio_clips = ( - audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] - ) # replace the clip with an AudioFileClip - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) - audio_composite.write_audiofile(filename, 44100, 2, 2000, None) - - @staticmethod - def randomvoice(): - ok_or_good = random.randrange(1, 10) - if ok_or_good == 1: # 1/10 chance of ok voice - return random.choice(voices) - return random.choice(human) # 9/10 chance of good voice + def __init__(self): + self.URI_BASE = ( + "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=" + ) + + def tts( + self, + req_text: str = "TikTok Text To Speech", + filename: str = "title.mp3", + random_speaker: bool = False, + censor=False, + ): + req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and") + if censor: + # req_text = pf.censor(req_text) + pass + voice = ( + self.randomvoice() if random_speaker else (os.getenv("VOICE") or random.choice(human)) + ) + + chunks = [m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text)] + + audio_clips = [] + cbn = sox.Combiner() + # cbn.set_input_format(file_type=["mp3" for _ in chunks]) + + chunkId = 0 + for chunk in chunks: + try: + r = requests.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") + except requests.exceptions.SSLError: + # https://stackoverflow.com/a/47475019/18516611 + session = requests.Session() + retry = Retry(connect=3, backoff_factor=0.5) + adapter = HTTPAdapter(max_retries=retry) + session.mount("http://", adapter) + session.mount("https://", adapter) + r = session.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0") + print(r.text) + vstr = [r.json()["data"]["v_str"]][0] + b64d = base64.b64decode(vstr) + + with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: + out.write(b64d) + + audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) + + chunkId = chunkId + 1 + try: + if len(audio_clips) > 1: + cbn.convert(samplerate=44100, n_channels=2) + cbn.build(audio_clips, filename, "concatenate") + else: + os.rename(audio_clips[0], filename) + except ( + sox.core.SoxError, + FileNotFoundError, + ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 + for clip in audio_clips: + i = audio_clips.index(clip) # get the index of the clip + audio_clips = ( + audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] + ) # replace the clip with an AudioFileClip + audio_concat = concatenate_audioclips(audio_clips) + audio_composite = CompositeAudioClip([audio_concat]) + audio_composite.write_audiofile(filename, 44100, 2, 2000, None) + + @staticmethod + def randomvoice(): + ok_or_good = random.randrange(1, 10) + if ok_or_good == 1: # 1/10 chance of ok voice + return random.choice(voices) + return random.choice(human) # 9/10 chance of good voice diff --git a/TTS/swapper.py b/TTS/swapper.py index 7eebd3e..025275a 100644 --- a/TTS/swapper.py +++ b/TTS/swapper.py @@ -6,16 +6,14 @@ from TTS.GTTS import GTTS from TTS.POLLY import POLLY from TTS.TikTok import TikTok -CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS, 'polly': POLLY} +CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS, "polly": POLLY} class TTS: - def __new__(cls): - load_dotenv() - CHOICE = getenv("TTsChoice").casefold() - valid_keys = [key.lower() for key in CHOICE_DIR.keys()] - if CHOICE not in valid_keys: - raise ValueError( - f"{CHOICE} is not valid. Please use one of these {valid_keys} options" - ) - return CHOICE_DIR.get(CHOICE)() + def __new__(cls): + load_dotenv() + CHOICE = getenv("TTsChoice").casefold() + valid_keys = [key.lower() for key in CHOICE_DIR.keys()] + if CHOICE not in valid_keys: + raise ValueError(f"{CHOICE} is not valid. Please use one of these {valid_keys} options") + return CHOICE_DIR.get(CHOICE)() diff --git a/reddit/subreddit.py b/reddit/subreddit.py index f08cefa..964862f 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -11,85 +11,85 @@ TEXT_WHITELIST = set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234 def textify(text): - return "".join(filter(TEXT_WHITELIST.__contains__, text)) + return "".join(filter(TEXT_WHITELIST.__contains__, text)) def get_subreddit_threads(): - """ - Returns a list of threads from the AskReddit subreddit. - """ - global submission - print_substep("Logging into Reddit.") + """ + Returns a list of threads from the AskReddit subreddit. + """ + global submission + print_substep("Logging into Reddit.") - content = {} - if str(getenv("REDDIT_2FA")).casefold() == "yes": - print("\nEnter your two-factor authentication code from your authenticator app.\n") - code = input("> ") - print() - pw = getenv("REDDIT_PASSWORD") - passkey = f"{pw}:{code}" - else: - passkey = getenv("REDDIT_PASSWORD") - reddit = praw.Reddit( - client_id=getenv("REDDIT_CLIENT_ID"), - client_secret=getenv("REDDIT_CLIENT_SECRET"), - user_agent="Accessing Reddit threads", - username=getenv("REDDIT_USERNAME"), - passkey=passkey, - check_for_async=False, - ) - """ + content = {} + if str(getenv("REDDIT_2FA")).casefold() == "yes": + print("\nEnter your two-factor authentication code from your authenticator app.\n") + code = input("> ") + print() + pw = getenv("REDDIT_PASSWORD") + passkey = f"{pw}:{code}" + else: + passkey = getenv("REDDIT_PASSWORD") + reddit = praw.Reddit( + client_id=getenv("REDDIT_CLIENT_ID"), + client_secret=getenv("REDDIT_CLIENT_SECRET"), + user_agent="Accessing Reddit threads", + username=getenv("REDDIT_USERNAME"), + passkey=passkey, + check_for_async=False, + ) + """ Ask user for subreddit input """ - print_step("Getting subreddit threads...") - if not getenv( - "SUBREDDIT" - ): # note to self. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") - subreddit = reddit.subreddit( - input("What subreddit would you like to pull from? ") - ) # if the env isnt set, ask user - else: - print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config") - subreddit = reddit.subreddit( - getenv("SUBREDDIT") - ) # Allows you to specify in .env. Done for automation purposes. + print_step("Getting subreddit threads...") + if not getenv( + "SUBREDDIT" + ): # note to self. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") + subreddit = reddit.subreddit( + input("What subreddit would you like to pull from? ") + ) # if the env isnt set, ask user + else: + print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config") + subreddit = reddit.subreddit( + getenv("SUBREDDIT") + ) # Allows you to specify in .env. Done for automation purposes. - if getenv("POST_ID"): - submission = reddit.submission(id=getenv("POST_ID")) - else: - threads = subreddit.hot(limit=25) - submission = get_subreddit_undone(threads, subreddit) - submission = check_done(submission) # double checking - if submission is None: - return get_subreddit_threads() # submission already done. rerun - upvotes = submission.score - ratio = submission.upvote_ratio * 100 - num_comments = submission.num_comments + if getenv("POST_ID"): + submission = reddit.submission(id=getenv("POST_ID")) + else: + threads = subreddit.hot(limit=25) + submission = get_subreddit_undone(threads, subreddit) + submission = check_done(submission) # double checking + if submission is None: + return get_subreddit_threads() # submission already done. rerun + upvotes = submission.score + ratio = submission.upvote_ratio * 100 + num_comments = submission.num_comments - print_substep(f"Video will be: {submission.title} :thumbsup:", style="bold green") - print_substep(f"Thread has {upvotes} upvotes", style="bold blue") - print_substep(f"Thread has a upvote ratio of {ratio}%", style="bold blue") - print_substep(f"Thread has {num_comments} comments", style="bold blue") - environ["VIDEO_TITLE"] = str(textify(submission.title)) # todo use global instend of env vars - environ["VIDEO_ID"] = str(textify(submission.id)) + print_substep(f"Video will be: {submission.title} :thumbsup:", style="bold green") + print_substep(f"Thread has {upvotes} upvotes", style="bold blue") + print_substep(f"Thread has a upvote ratio of {ratio}%", style="bold blue") + print_substep(f"Thread has {num_comments} comments", style="bold blue") + environ["VIDEO_TITLE"] = str(textify(submission.title)) # todo use global instend of env vars + environ["VIDEO_ID"] = str(textify(submission.id)) - content["thread_url"] = f"https://reddit.com{submission.permalink}" - content["thread_title"] = submission.title - # content["thread_content"] = submission.content - content["comments"] = [] - for top_level_comment in submission.comments: - if isinstance(top_level_comment, MoreComments): - continue - if top_level_comment.body in ["[removed]", "[deleted]"]: - continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 - if not top_level_comment.stickied: - if len(top_level_comment.body) <= int(environ["MAX_COMMENT_LENGTH"]): - content["comments"].append( - { - "comment_body": top_level_comment.body, - "comment_url": top_level_comment.permalink, - "comment_id": top_level_comment.id, - } - ) - print_substep("Received subreddit threads Successfully.", style="bold green") - return content + content["thread_url"] = f"https://reddit.com{submission.permalink}" + content["thread_title"] = submission.title + # content["thread_content"] = submission.content + content["comments"] = [] + for top_level_comment in submission.comments: + if isinstance(top_level_comment, MoreComments): + continue + if top_level_comment.body in ["[removed]", "[deleted]"]: + continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 + if not top_level_comment.stickied: + if len(top_level_comment.body) <= int(environ["MAX_COMMENT_LENGTH"]): + content["comments"].append( + { + "comment_body": top_level_comment.body, + "comment_url": top_level_comment.permalink, + "comment_id": top_level_comment.id, + } + ) + print_substep("Received subreddit threads Successfully.", style="bold green") + return content diff --git a/setup.py b/setup.py index b000ce8..a8d7f12 100755 --- a/setup.py +++ b/setup.py @@ -15,52 +15,48 @@ console = Console() def handle_input( - message: str = "", - check_type=False, - match: str = "", - err_message: str = "", - nmin=None, - nmax=None, - oob_error="", + message: str = "", + check_type=False, + match: str = "", + err_message: str = "", + nmin=None, + nmax=None, + oob_error="", ): - match = re.compile(match + "$") - while True: - user_input = input(message + "\n> ").strip() - if re.match(match, user_input) is not None: - if check_type is not False: - try: - user_input = check_type(user_input) - if nmin is not None and user_input < nmin: - console.log("[red]" + oob_error) # Input too low failstate - continue - if nmax is not None and user_input > nmax: - console.log("[red]" + oob_error) # Input too high - continue - break # Successful type conversion and number in bounds - except ValueError: - console.log("[red]" + err_message) # Type conversion failed - continue - if ( - nmin is not None and len(user_input) < nmin - ): # Check if string is long enough - console.log("[red]" + oob_error) - continue - if ( - nmax is not None and len(user_input) > nmax - ): # Check if string is not too long - console.log("[red]" + oob_error) - continue - break - console.log("[red]" + err_message) - - return user_input + match = re.compile(match + "$") + while True: + user_input = input(message + "\n> ").strip() + if re.match(match, user_input) is not None: + if check_type is not False: + try: + user_input = check_type(user_input) + if nmin is not None and user_input < nmin: + console.log("[red]" + oob_error) # Input too low failstate + continue + if nmax is not None and user_input > nmax: + console.log("[red]" + oob_error) # Input too high + continue + break # Successful type conversion and number in bounds + except ValueError: + console.log("[red]" + err_message) # Type conversion failed + continue + if nmin is not None and len(user_input) < nmin: # Check if string is long enough + console.log("[red]" + oob_error) + continue + if nmax is not None and len(user_input) > nmax: # Check if string is not too long + console.log("[red]" + oob_error) + continue + break + console.log("[red]" + err_message) + + return user_input if os.path.isfile(".setup-done-before"): - console.log( - "[red]Setup was already completed! Please make sure you have to run this script again. If that is such, delete the file .setup-done-before" - ) - exit() + console.log( + "[red]Setup was already completed! Please make sure you have to run this script again. If that is such, delete the file .setup-done-before" + ) + exit() # These lines ensure the user: # - knows they are in setup mode @@ -68,26 +64,26 @@ if os.path.isfile(".setup-done-before"): print_step("Setup Assistant") print_markdown( - "### You're in the setup wizard. Ensure you're supposed to be here, then type yes to continue. If you're not sure, type no to quit." + "### You're in the setup wizard. Ensure you're supposed to be here, then type yes to continue. If you're not sure, type no to quit." ) # This Input is used to ensure the user is sure they want to continue. if input("Are you sure you want to continue? > ").strip().casefold() != "yes": - console.print("[red]Exiting...") - exit() + console.print("[red]Exiting...") + exit() # This code is inaccessible if the prior check fails, and thus an else statement is unnecessary # Again, let them know they are about to erase all other setup data. console.print( - "[bold red] This will overwrite your current settings. Are you sure you want to continue? [bold green]yes/no" + "[bold red] This will overwrite your current settings. Are you sure you want to continue? [bold green]yes/no" ) if input("Are you sure you want to continue? > ").strip().casefold() != "yes": - console.print("[red]Abort mission! Exiting...") - exit() + console.print("[red]Abort mission! Exiting...") + exit() # This is once again inaccessible if the prior checks fail # Once they confirm, move on with the script. console.print("[bold green]Alright! Let's get started!") @@ -103,18 +99,18 @@ console.log("[bold green]Opacity (range of 0-1, decimals are OK)") console.log("[bold green]Subreddit (without r/ or /r/)") console.log("[bold green]Theme (light or dark)") console.print( - "[green]If you don't have these, please follow the instructions in the README.md file to set them up." + "[green]If you don't have these, please follow the instructions in the README.md file to set them up." ) console.print( - "[green]If you do have these, type yes to continue. If you dont, go ahead and grab those quickly and come back." + "[green]If you do have these, type yes to continue. If you dont, go ahead and grab those quickly and come back." ) print() if input("Are you sure you have the credentials? > ").strip().casefold() != "yes": - console.print("[red]I don't understand that.") - console.print("[red]Exiting...") - exit() + console.print("[red]I don't understand that.") + console.print("[red]Exiting...") + exit() console.print("[bold green]Alright! Let's get started!") @@ -123,69 +119,69 @@ console.print("[bold green]Alright! Let's get started!") console.log("Enter your credentials now.") client_id = handle_input( - "Client ID > ", - False, - "[-a-zA-Z0-9._~+/]+=*", - "That is somehow not a correct ID, try again.", - 12, - 30, - "The ID should be over 12 and under 30 characters, double check your input.", + "Client ID > ", + False, + "[-a-zA-Z0-9._~+/]+=*", + "That is somehow not a correct ID, try again.", + 12, + 30, + "The ID should be over 12 and under 30 characters, double check your input.", ) client_sec = handle_input( - "Client Secret > ", - False, - "[-a-zA-Z0-9._~+/]+=*", - "That is somehow not a correct secret, try again.", - 20, - 40, - "The secret should be over 20 and under 40 characters, double check your input.", + "Client Secret > ", + False, + "[-a-zA-Z0-9._~+/]+=*", + "That is somehow not a correct secret, try again.", + 20, + 40, + "The secret should be over 20 and under 40 characters, double check your input.", ) user = handle_input( - "Username > ", - False, - r"[_0-9a-zA-Z]+", - "That is not a valid user", - 3, - 20, - "A username HAS to be between 3 and 20 characters", + "Username > ", + False, + r"[_0-9a-zA-Z]+", + "That is not a valid user", + 3, + 20, + "A username HAS to be between 3 and 20 characters", ) passw = handle_input("Password > ", False, ".*", "", 8, None, "Password too short") twofactor = handle_input( - "2fa Enabled? (yes/no) > ", - False, - r"(yes)|(no)", - "You need to input either yes or no", + "2fa Enabled? (yes/no) > ", + False, + r"(yes)|(no)", + "You need to input either yes or no", ) opacity = handle_input( - "Opacity? (range of 0-1) > ", - float, - ".*", - "You need to input a number between 0 and 1", - 0, - 1, - "Your number is not between 0 and 1", + "Opacity? (range of 0-1) > ", + float, + ".*", + "You need to input a number between 0 and 1", + 0, + 1, + "Your number is not between 0 and 1", ) subreddit = handle_input( - "Subreddit (without r/) > ", - False, - r"[_0-9a-zA-Z]+", - "This subreddit cannot exist, make sure you typed it in correctly and removed the r/ (or /r/).", - 3, - 20, - "A subreddit name HAS to be between 3 and 20 characters", + "Subreddit (without r/) > ", + False, + r"[_0-9a-zA-Z]+", + "This subreddit cannot exist, make sure you typed it in correctly and removed the r/ (or /r/).", + 3, + 20, + "A subreddit name HAS to be between 3 and 20 characters", ) theme = handle_input( - "Theme? (light or dark) > ", - False, - r"(light)|(dark)", - "You need to input 'light' or 'dark'", + "Theme? (light or dark) > ", + False, + r"(light)|(dark)", + "You need to input 'light' or 'dark'", ) loader = Loader("Attempting to save your credentials...", "Done!").start() # you can also put a while loop here, e.g. while VideoIsBeingMade == True: ... console.log("Writing to the .env file...") with open(".env", "w") as f: - f.write( - f"""REDDIT_CLIENT_ID="{client_id}" + f.write( + f"""REDDIT_CLIENT_ID="{client_id}" REDDIT_CLIENT_SECRET="{client_sec}" REDDIT_USERNAME="{user}" REDDIT_PASSWORD="{passw}" @@ -194,12 +190,12 @@ THEME="{theme}" SUBREDDIT="{subreddit}" OPACITY={opacity} """ - ) + ) with open(".setup-done-before", "w") as f: - f.write( - "This file blocks the setup assistant from running again. Delete this file to run setup again." - ) + f.write( + "This file blocks the setup assistant from running again. Delete this file to run setup again." + ) loader.stop() diff --git a/utils/cleanup.py b/utils/cleanup.py index 320ce49..9490b6d 100644 --- a/utils/cleanup.py +++ b/utils/cleanup.py @@ -3,22 +3,20 @@ from os.path import exists def cleanup() -> int: - if exists("./assets/temp"): - count = 0 - files = [ - f for f in os.listdir(".") if f.endswith(".mp4") and "temp" in f.lower() - ] - count += len(files) - for f in files: - os.remove(f) - try: - for file in os.listdir("./assets/temp/mp4"): - count += 1 - os.remove("./assets/temp/mp4/" + file) - except FileNotFoundError: - pass - for file in os.listdir("./assets/temp/mp3"): - count += 1 - os.remove("./assets/temp/mp3/" + file) - return count - return 0 + if exists("./assets/temp"): + count = 0 + files = [f for f in os.listdir(".") if f.endswith(".mp4") and "temp" in f.lower()] + count += len(files) + for f in files: + os.remove(f) + try: + for file in os.listdir("./assets/temp/mp4"): + count += 1 + os.remove("./assets/temp/mp4/" + file) + except FileNotFoundError: + pass + for file in os.listdir("./assets/temp/mp3"): + count += 1 + os.remove("./assets/temp/mp3/" + file) + return count + return 0 diff --git a/utils/config.py b/utils/config.py index 0216141..000b615 100644 --- a/utils/config.py +++ b/utils/config.py @@ -3,36 +3,36 @@ from dotenv import dotenv_values DEFAULTS = { - "SUBREDDIT": "AskReddit", - "ALLOW_NSFW": "False", - "POST_ID": "", - "THEME": "DARK", - "REDDIT_2FA": "no", - "TIMES_TO_RUN": "", - "MAX_COMMENT_LENGTH": "500", - "OPACITY": "1", - "VOICE": "en_us_001", - "STORYMODE": "False", + "SUBREDDIT": "AskReddit", + "ALLOW_NSFW": "False", + "POST_ID": "", + "THEME": "DARK", + "REDDIT_2FA": "no", + "TIMES_TO_RUN": "", + "MAX_COMMENT_LENGTH": "500", + "OPACITY": "1", + "VOICE": "en_us_001", + "STORYMODE": "False", } class Config: - def __init__(self): - self.raw = dotenv_values("../.env") - self.load_attrs() - - def __getattr__(self, attr): # code completion for attributes fix. - return getattr(self, attr) - - def load_attrs(self): - for key, value in self.raw.items(): - self.add_attr(key, value) - - def add_attr(self, key, value): - if value is None or value == "": - setattr(self, key, DEFAULTS[key]) - else: - setattr(self, key, str(value)) + def __init__(self): + self.raw = dotenv_values("../.env") + self.load_attrs() + + def __getattr__(self, attr): # code completion for attributes fix. + return getattr(self, attr) + + def load_attrs(self): + for key, value in self.raw.items(): + self.add_attr(key, value) + + def add_attr(self, key, value): + if value is None or value == "": + setattr(self, key, DEFAULTS[key]) + else: + setattr(self, key, str(value)) config = Config() diff --git a/utils/console.py b/utils/console.py index 1b4e3cb..11ee429 100644 --- a/utils/console.py +++ b/utils/console.py @@ -9,19 +9,19 @@ console = Console() def print_markdown(text): - """Prints a rich info message. Support Markdown syntax.""" + """Prints a rich info message. Support Markdown syntax.""" - md = Padding(Markdown(text), 2) - console.print(md) + md = Padding(Markdown(text), 2) + console.print(md) def print_step(text): - """Prints a rich info message.""" + """Prints a rich info message.""" - panel = Panel(Text(text, justify="left")) - console.print(panel) + panel = Panel(Text(text, justify="left")) + console.print(panel) def print_substep(text, style=""): - """Prints a rich info message without the panelling.""" - console.print(text, style=style) + """Prints a rich info message without the panelling.""" + console.print(text, style=style) diff --git a/utils/loader.py b/utils/loader.py index ac1c535..b9dc276 100644 --- a/utils/loader.py +++ b/utils/loader.py @@ -9,43 +9,43 @@ from time import sleep class Loader: - def __init__(self, desc="Loading...", end="Done!", timeout=0.1): - """ - A loader-like context manager - - Args: - desc (str, optional): The loader's description. Defaults to "Loading...". - end (str, optional): Final print. Defaults to "Done!". - timeout (float, optional): Sleep time between prints. Defaults to 0.1. - """ - self.desc = desc - self.end = end - self.timeout = timeout - - self._thread = Thread(target=self._animate, daemon=True) - self.steps = ["âĸŋ", "âŖģ", "âŖŊ", "âŖž", "âŖˇ", "âŖ¯", "âŖŸ", "âĄŋ"] - self.done = False - - def start(self): - self._thread.start() - return self - - def _animate(self): - for c in cycle(self.steps): - if self.done: - break - print(f"\r{self.desc} {c}", flush=True, end="") - sleep(self.timeout) - - def __enter__(self): - self.start() - - def stop(self): - self.done = True - cols = get_terminal_size((80, 20)).columns - print("\r" + " " * cols, end="", flush=True) - print(f"\r{self.end}", flush=True) - - def __exit__(self, exc_type, exc_value, tb): - # handle exceptions with those variables ^ - self.stop() + def __init__(self, desc="Loading...", end="Done!", timeout=0.1): + """ + A loader-like context manager + + Args: + desc (str, optional): The loader's description. Defaults to "Loading...". + end (str, optional): Final print. Defaults to "Done!". + timeout (float, optional): Sleep time between prints. Defaults to 0.1. + """ + self.desc = desc + self.end = end + self.timeout = timeout + + self._thread = Thread(target=self._animate, daemon=True) + self.steps = ["âĸŋ", "âŖģ", "âŖŊ", "âŖž", "âŖˇ", "âŖ¯", "âŖŸ", "âĄŋ"] + self.done = False + + def start(self): + self._thread.start() + return self + + def _animate(self): + for c in cycle(self.steps): + if self.done: + break + print(f"\r{self.desc} {c}", flush=True, end="") + sleep(self.timeout) + + def __enter__(self): + self.start() + + def stop(self): + self.done = True + cols = get_terminal_size((80, 20)).columns + print("\r" + " " * cols, end="", flush=True) + print(f"\r{self.end}", flush=True) + + def __exit__(self, exc_type, exc_value, tb): + # handle exceptions with those variables ^ + self.stop() diff --git a/utils/subreddit.py b/utils/subreddit.py index 15185e2..d12228f 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -5,28 +5,28 @@ from utils.console import print_substep def get_subreddit_undone(submissions: List, subreddit): - """ - recursively checks if the top submission in the list was already done. - """ - with open("./video_creation/data/videos.json", "r") as done_vids_raw: - done_videos = json.load(done_vids_raw) - for submission in submissions: - if already_done(done_videos, submission): - continue - if submission.over_18: - if getenv("ALLOW_NSFW").casefold() == "false": - print_substep("NSFW Post Detected. Skipping...") - continue - return submission - print('all submissions have been done going by top submission order') - return get_subreddit_undone( - subreddit.top(time_filter="hour"), subreddit - ) # all of the videos in hot have already been done + """ + recursively checks if the top submission in the list was already done. + """ + with open("./video_creation/data/videos.json", "r") as done_vids_raw: + done_videos = json.load(done_vids_raw) + for submission in submissions: + if already_done(done_videos, submission): + continue + if submission.over_18: + if getenv("ALLOW_NSFW").casefold() == "false": + print_substep("NSFW Post Detected. Skipping...") + continue + return submission + print("all submissions have been done going by top submission order") + return get_subreddit_undone( + subreddit.top(time_filter="hour"), subreddit + ) # all of the videos in hot have already been done def already_done(done_videos: list, submission): - for video in done_videos: - if video["id"] == str(submission): - return True - return False + for video in done_videos: + if video["id"] == str(submission): + return True + return False diff --git a/utils/videos.py b/utils/videos.py index 0c1a27f..51a2704 100755 --- a/utils/videos.py +++ b/utils/videos.py @@ -5,19 +5,19 @@ from utils.console import print_step def check_done( - redditobj, + redditobj, ): # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack - """params: - reddit_object: The Reddit Object you received in askreddit.py""" - with open("./video_creation/data/videos.json", "r") as done_vids_raw: - done_videos = json.load(done_vids_raw) - for video in done_videos: - if video["id"] == str(redditobj): - if getenv("POST_ID"): - print_step( - "You already have done this video but since it was declared specifically in the .env file the program will continue" - ) - return redditobj - print_step("Getting new post as the current one has already been done") - return None - return redditobj + """params: + reddit_object: The Reddit Object you received in askreddit.py""" + with open("./video_creation/data/videos.json", "r") as done_vids_raw: + done_videos = json.load(done_vids_raw) + for video in done_videos: + if video["id"] == str(redditobj): + if getenv("POST_ID"): + print_step( + "You already have done this video but since it was declared specifically in the .env file the program will continue" + ) + return redditobj + print_step("Getting new post as the current one has already been done") + return None + return redditobj diff --git a/utils/voice.py b/utils/voice.py index 32333dc..120ee60 100644 --- a/utils/voice.py +++ b/utils/voice.py @@ -2,21 +2,21 @@ import re def sanitize_text(text): - """ - Sanitizes the text for tts. - What gets removed: - - following characters`^_~@!&;#:-%“”‘"%*/{}[]()\|<>?=+` - - any http or https links - """ + """ + Sanitizes the text for tts. + What gets removed: + - following characters`^_~@!&;#:-%“”‘"%*/{}[]()\|<>?=+` + - any http or https links + """ - # remove any urls from the text - regex_urls = r"((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*" + # remove any urls from the text + regex_urls = r"((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*" - result = re.sub(regex_urls, " ", text) + result = re.sub(regex_urls, " ", text) - # note: not removing apostrophes - regex_expr = r"\s['|’]|['|’]\s|[\^_~@!&;#:\-%“”‘\"%\*/{}\[\]\(\)\\|<>=+]" - result = re.sub(regex_expr, " ", result) + # note: not removing apostrophes + regex_expr = r"\s['|’]|['|’]\s|[\^_~@!&;#:\-%“”‘\"%\*/{}\[\]\(\)\\|<>=+]" + result = re.sub(regex_expr, " ", result) - # remove extra whitespace - return " ".join(result.split()) + # remove extra whitespace + return " ".join(result.split()) diff --git a/video_creation/background.py b/video_creation/background.py index 146a63f..fb300e6 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -9,55 +9,53 @@ from utils.console import print_step, print_substep def get_start_and_end_times(video_length, length_of_clip): - random_time = randrange(180, int(length_of_clip) - int(video_length)) - return random_time, random_time + video_length + random_time = randrange(180, int(length_of_clip) - int(video_length)) + return random_time, random_time + video_length def download_background(): - """Downloads the backgrounds/s video from YouTube.""" - Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True) - background_options = [ # uri , filename , credit - ("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"), - # ( - # "https://www.youtube.com/watch?v=2X9QGY__0II", - # "rocket_league.mp4", - # "Orbital Gameplay", - # ), - ] - # note: make sure the file name doesn't include an - in it - if not len(listdir("./assets/backgrounds")) >= len( - background_options - ): # if there are any background videos not installed - print_step( - "We need to download the backgrounds videos. they are fairly large but it's only done once. 😎" - ) - print_substep("Downloading the backgrounds videos... please be patient 🙏 ") - for uri, filename, credit in background_options: - if Path(f"assets/backgrounds/{credit}-{filename}").is_file(): - continue # adds check to see if file exists before downloading - print_substep(f"Downloading {filename} from {uri}") - YouTube(uri).streams.filter(res="1080p").first().download( - "assets/backgrounds", filename=f"{credit}-{filename}" - ) - - print_substep( - "Background videos downloaded successfully! 🎉", style="bold green" - ) + """Downloads the backgrounds/s video from YouTube.""" + Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True) + background_options = [ # uri , filename , credit + ("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"), + # ( + # "https://www.youtube.com/watch?v=2X9QGY__0II", + # "rocket_league.mp4", + # "Orbital Gameplay", + # ), + ] + # note: make sure the file name doesn't include an - in it + if not len(listdir("./assets/backgrounds")) >= len( + background_options + ): # if there are any background videos not installed + print_step( + "We need to download the backgrounds videos. they are fairly large but it's only done once. 😎" + ) + print_substep("Downloading the backgrounds videos... please be patient 🙏 ") + for uri, filename, credit in background_options: + if Path(f"assets/backgrounds/{credit}-{filename}").is_file(): + continue # adds check to see if file exists before downloading + print_substep(f"Downloading {filename} from {uri}") + YouTube(uri).streams.filter(res="1080p").first().download( + "assets/backgrounds", filename=f"{credit}-{filename}" + ) + + print_substep("Background videos downloaded successfully! 🎉", style="bold green") def chop_background_video(video_length): - print_step("Finding a spot in the backgrounds video to chop...✂ī¸") - choice = random.choice(listdir("assets/backgrounds")) - environ["background_credit"] = choice.split("-")[0] - - background = VideoFileClip(f"assets/backgrounds/{choice}") - - start_time, end_time = get_start_and_end_times(video_length, background.duration) - ffmpeg_extract_subclip( - f"assets/backgrounds/{choice}", - start_time, - end_time, - targetname="assets/temp/background.mp4", - ) - print_substep("Background video chopped successfully!", style="bold green") - return True + print_step("Finding a spot in the backgrounds video to chop...✂ī¸") + choice = random.choice(listdir("assets/backgrounds")) + environ["background_credit"] = choice.split("-")[0] + + background = VideoFileClip(f"assets/backgrounds/{choice}") + + start_time, end_time = get_start_and_end_times(video_length, background.duration) + ffmpeg_extract_subclip( + f"assets/backgrounds/{choice}", + start_time, + end_time, + targetname="assets/temp/background.mp4", + ) + print_substep("Background video chopped successfully!", style="bold green") + return True diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 25241e1..e27a2be 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -16,62 +16,60 @@ storymode = False def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): - """Downloads screenshots of reddit posts as they are seen on the web. - Args: - reddit_object: The Reddit Object you received in askreddit.py - screenshot_num: The number of screenshots you want to download. - """ - print_step("Downloading screenshots of reddit posts...") + """Downloads screenshots of reddit posts as they are seen on the web. + Args: + reddit_object: The Reddit Object you received in askreddit.py + screenshot_num: The number of screenshots you want to download. + """ + print_step("Downloading screenshots of reddit posts...") - # ! Make sure the reddit screenshots folder exists - Path("assets/temp/png").mkdir(parents=True, exist_ok=True) + # ! Make sure the reddit screenshots folder exists + Path("assets/temp/png").mkdir(parents=True, exist_ok=True) - with sync_playwright() as p: - print_substep("Launching Headless Browser...") + with sync_playwright() as p: + print_substep("Launching Headless Browser...") - browser = p.chromium.launch() - context = browser.new_context() + browser = p.chromium.launch() + context = browser.new_context() - if getenv("THEME").upper() == "DARK": - cookie_file = open("./video_creation/data/cookie-dark-mode.json") - else: - cookie_file = open("./video_creation/data/cookie-light-mode.json") - cookies = json.load(cookie_file) - context.add_cookies(cookies) # load preference cookies - # Get the thread screenshot - page = context.new_page() - page.goto(reddit_object["thread_url"]) - page.set_viewport_size(ViewportSize(width=1920, height=1080)) - if page.locator('[data-testid="content-gate"]').is_visible(): - # This means the post is NSFW and requires to click the proceed button. + if getenv("THEME").upper() == "DARK": + cookie_file = open("./video_creation/data/cookie-dark-mode.json") + else: + cookie_file = open("./video_creation/data/cookie-light-mode.json") + cookies = json.load(cookie_file) + context.add_cookies(cookies) # load preference cookies + # Get the thread screenshot + page = context.new_page() + page.goto(reddit_object["thread_url"]) + page.set_viewport_size(ViewportSize(width=1920, height=1080)) + if page.locator('[data-testid="content-gate"]').is_visible(): + # This means the post is NSFW and requires to click the proceed button. - print_substep("Post is NSFW. You are spicy...") - page.locator('[data-testid="content-gate"] button').click() - page.locator( - '[data-click-id="text"] button' - ).click() # Remove "Click to see nsfw" Button in Screenshot + print_substep("Post is NSFW. You are spicy...") + page.locator('[data-testid="content-gate"] button').click() + page.locator( + '[data-click-id="text"] button' + ).click() # Remove "Click to see nsfw" Button in Screenshot - page.locator('[data-test-id="post-content"]').screenshot( - path="assets/temp/png/title.png" - ) - if storymode: - page.locator('[data-click-id="text"]').screenshot( - path="assets/temp/png/story_content.png" - ) - else: - for idx, comment in track( - enumerate(reddit_object["comments"]), "Downloading screenshots..." - ): + page.locator('[data-test-id="post-content"]').screenshot(path="assets/temp/png/title.png") + if storymode: + page.locator('[data-click-id="text"]').screenshot( + path="assets/temp/png/story_content.png" + ) + else: + for idx, comment in track( + enumerate(reddit_object["comments"]), "Downloading screenshots..." + ): - # Stop if we have reached the screenshot_num - if idx >= screenshot_num: - break + # Stop if we have reached the screenshot_num + if idx >= screenshot_num: + break - if page.locator('[data-testid="content-gate"]').is_visible(): - page.locator('[data-testid="content-gate"] button').click() + if page.locator('[data-testid="content-gate"]').is_visible(): + page.locator('[data-testid="content-gate"] button').click() - page.goto(f'https://reddit.com{comment["comment_url"]}') - page.locator(f"#t1_{comment['comment_id']}").screenshot( - path=f"assets/temp/png/comment_{idx}.png" - ) - print_substep("Screenshots downloaded Successfully.", style="bold green") + page.goto(f'https://reddit.com{comment["comment_url"]}') + page.locator(f"#t1_{comment['comment_id']}").screenshot( + path=f"assets/temp/png/comment_{idx}.png" + ) + print_substep("Screenshots downloaded Successfully.", style="bold green") diff --git a/video_creation/voices.py b/video_creation/voices.py index 960b5fa..be7da96 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -20,60 +20,60 @@ VIDEO_LENGTH: int = 40 # secs def save_text_to_mp3(reddit_obj): - """Saves Text to MP3 files. - Args: - reddit_obj : The reddit object you received from the reddit API in the askreddit.py file. - """ - print_step("Saving Text to MP3 files...") - length = 0 + """Saves Text to MP3 files. + Args: + reddit_obj : The reddit object you received from the reddit API in the askreddit.py file. + """ + print_step("Saving Text to MP3 files...") + length = 0 - # Create a folder for the mp3 files. - Path("assets/temp/mp3").mkdir(parents=True, exist_ok=True) - TextToSpeech = TTS() - TextToSpeech.tts( - sanitize_text(reddit_obj["thread_title"]), - filename="assets/temp/mp3/title.mp3", - random_speaker=False, - ) - try: - length += MP3("assets/temp/mp3/title.mp3").info.length - except HeaderNotFoundError: # note to self AudioFileClip - length += sox.file_info.duration("assets/temp/mp3/title.mp3") - if getenv("STORYMODE").casefold() == "true": - TextToSpeech.tts( - sanitize_text(reddit_obj["thread_content"]), - filename="assets/temp/mp3/story_content.mp3", - random_speaker=False, - ) - # 'story_content' - com = 0 - for comment in track((reddit_obj["comments"]), "Saving..."): - # ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer - # but this is just a good_voices starting point - if length > VIDEO_LENGTH: - break + # Create a folder for the mp3 files. + Path("assets/temp/mp3").mkdir(parents=True, exist_ok=True) + TextToSpeech = TTS() + TextToSpeech.tts( + sanitize_text(reddit_obj["thread_title"]), + filename="assets/temp/mp3/title.mp3", + random_speaker=False, + ) + try: + length += MP3("assets/temp/mp3/title.mp3").info.length + except HeaderNotFoundError: # note to self AudioFileClip + length += sox.file_info.duration("assets/temp/mp3/title.mp3") + if getenv("STORYMODE").casefold() == "true": + TextToSpeech.tts( + sanitize_text(reddit_obj["thread_content"]), + filename="assets/temp/mp3/story_content.mp3", + random_speaker=False, + ) + # 'story_content' + com = 0 + for comment in track((reddit_obj["comments"]), "Saving..."): + # ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer + # but this is just a good_voices starting point + if length > VIDEO_LENGTH: + break - TextToSpeech.tts( - sanitize_text(comment["comment_body"]), - filename=f"assets/temp/mp3/{com}.mp3", - random_speaker=False, - ) - try: - length += MP3(f"assets/temp/mp3/{com}.mp3").info.length - com += 1 - except (HeaderNotFoundError, MutagenError, Exception): - try: - length += sox.file_info.duration(f"assets/temp/mp3/{com}.mp3") - com += 1 - except (OSError, IOError): - print( - "would have removed" - f"assets/temp/mp3/{com}.mp3" - f"assets/temp/png/comment_{com}.png" - ) - # remove(f"assets/temp/mp3/{com}.mp3") - # remove(f"assets/temp/png/comment_{com}.png")# todo might cause odd un-syncing + TextToSpeech.tts( + sanitize_text(comment["comment_body"]), + filename=f"assets/temp/mp3/{com}.mp3", + random_speaker=False, + ) + try: + length += MP3(f"assets/temp/mp3/{com}.mp3").info.length + com += 1 + except (HeaderNotFoundError, MutagenError, Exception): + try: + length += sox.file_info.duration(f"assets/temp/mp3/{com}.mp3") + com += 1 + except (OSError, IOError): + print( + "would have removed" + f"assets/temp/mp3/{com}.mp3" + f"assets/temp/png/comment_{com}.png" + ) + # remove(f"assets/temp/mp3/{com}.mp3") + # remove(f"assets/temp/png/comment_{com}.png")# todo might cause odd un-syncing - print_substep("Saved Text to MP3 files Successfully.", style="bold green") - # ! Return the index, so we know how many screenshots of comments we need to make. - return length, com + print_substep("Saved Text to MP3 files Successfully.", style="bold green") + # ! Return the index, so we know how many screenshots of comments we need to make. + return length, com From 09ba69c5bc53a79c0163b07c6260685a0424513f Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:13:19 -0400 Subject: [PATCH 10/20] removed unused .env members --- .env.template | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/.env.template b/.env.template index c94fa6a..2e1ec9e 100644 --- a/.env.template +++ b/.env.template @@ -1,4 +1,3 @@ -# This can be found in the email that reddit sent you when you created the app REDDIT_CLIENT_ID="" REDDIT_CLIENT_SECRET="" @@ -6,16 +5,8 @@ REDDIT_USERNAME="" REDDIT_PASSWORD="" # If no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: "no" -RANDOM_THREAD="" +RANDOM_THREAD="no" -# Filters the comments by range of length (min and max characters) -# Min has to be less or equal to max -# DO NOT INSERT ANY SPACES BETWEEN THE COMMA AND THE VALUES -COMMENT_LENGTH_RANGE = "min,max" - -# The absolute path of the folder where you want to save the final video -# If empty or wrong, the path will be 'results/' -FINAL_VIDEO_PATH="" # Valid options are "yes" and "no" for the variable below REDDIT_2FA="" SUBREDDIT="AskReddit" @@ -25,15 +16,16 @@ ALLOW_NSFW="False" POST_ID="" #set to either LIGHT or DARK THEME="LIGHT" -# used if you want to run multiple times. set to an int e.g. 4 or 29 and leave blank for once +# used if you want to run multiple times. set to an int e.g. 4 or 29 and leave blank for 1 TIMES_TO_RUN="" +# max number of characters a comment can have. MAX_COMMENT_LENGTH="500" # Range is 0 -> 1 recommended around 0.8-0.9 OPACITY="1" -# see TTSwrapper.py for all valid options +# see different voice options: todo: add docs VOICE="Matthew" # e.g. en_us_002 -TTsChoice="polly" # todo add docs +TTsChoice="polly" # IN-PROGRESS - not yet implemented -STORYMODE="False" \ No newline at end of file +STORYMODE="False" From 76bff1a02d7edfceb4336bf0160b55a274753684 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:17:36 -0400 Subject: [PATCH 11/20] fixed #553 --- .devcontainer/devcontainer.json | 19 ---- .gitignore | 87 +++++++++++++++++-- .vscode/launch.json | 13 --- main.py | 2 +- utils/checker.py | 62 +++++++++++++ utils/envUpdate.py | 48 ---------- utils/scripts/FileGrabber.ps1 | 9 ++ .../FileGrabberenv.ps1} | 6 +- video_creation/data/.gitignore | 2 - 9 files changed, 153 insertions(+), 95 deletions(-) delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .vscode/launch.json create mode 100644 utils/checker.py delete mode 100644 utils/envUpdate.py create mode 100644 utils/scripts/FileGrabber.ps1 rename utils/{envUpdateWin.ps1 => scripts/FileGrabberenv.ps1} (77%) delete mode 100644 video_creation/data/.gitignore diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 1f86901..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,19 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.238.0/containers/docker-existing-dockerfile -{ - "name": "Existing Dockerfile", - - // Sets the run context to one level up instead of the .devcontainer folder. - "context": "..", - - // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. - "dockerFile": "../Dockerfile", - - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-python.python" - ], - - // Install OS dependencies - "postCreateCommand": "apt-get update && apt -qq install -y sox && apt-get install -y libsox-fmt-all" -} diff --git a/.gitignore b/.gitignore index 1c28a20..9a8b2b2 100644 --- a/.gitignore +++ b/.gitignore @@ -153,22 +153,91 @@ dmypy.json cython_debug/ # PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -.idea/ +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser assets/ out .DS_Store .setup-done-before -assets/ results/* -.env reddit-bot-351418-5560ebc49cac.json /.idea *.pyc /video_creation/data/videos.json -utils/envUpdate.py.old -envVars.txt \ No newline at end of file +/video_creation/data/envvars.txt diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 0d2e482..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Python: Main.py", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/main.py", - "console": "integratedTerminal", - "justMyCode": true - } - ] -} \ No newline at end of file diff --git a/main.py b/main.py index bfb3ba3..6f478b1 100755 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ from os import getenv, name from reddit.subreddit import get_subreddit_threads from utils.cleanup import cleanup from utils.console import print_markdown, print_step -from utils.envUpdate import envUpdate +from utils.checker import envUpdate from video_creation.background import download_background, chop_background_video from video_creation.final_video import make_final_video from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts diff --git a/utils/checker.py b/utils/checker.py new file mode 100644 index 0000000..0372514 --- /dev/null +++ b/utils/checker.py @@ -0,0 +1,62 @@ +import os +import subprocess +import tempfile +from os import path +from sys import platform + +ACCEPTABLE_TO_BE_LEFT_BLANK = ['RANDOM_THREAD', 'TIMES_TO_RUN'] + + +def envUpdate(): + if path.exists(".env.template"): # if .env.template exists and .env does not exist + if platform == "win32" or platform == "cygwin": + runPS('utils/scripts/FileGrabber.ps1') + with open(".\\video_creation\\data\\envvars.txt", "rb") as f: + envTemplate = f.read() + elif platform == "darwin" or platform == "linux": + envTemplate = subprocess.check_output("awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", + shell=True, ) + else: + raise OSError("Unsupported platform") + elif path.exists(".env"): + if platform == "win32" or platform == "cygwin": + runPS('utils/scripts/FileGrabberenv.ps1') + with open(".\\video_creation\\data\\envvars.txt", "rb") as f: + envTemplate = f.read() + elif platform == "darwin" or platform == "linux": + envTemplate = subprocess.check_output("awk -F '=' 'NF {print $1}' .env | grep --regexp=^[a-zA-Z]", + shell=True, ) + else: + raise OSError("Unsupported platform") + else: + raise FileNotFoundError("No .env or .env.template file found") + tempEnv = tempfile.TemporaryFile() + tempEnv.write(envTemplate) + tempEnv.seek(0) + envVars = tempEnv.readlines() + + + missing = [] + isMissingEnvs = False + for env in envVars: + try: + env = env.decode("utf-8").strip() + except AttributeError: + env = env.strip() + + if env not in os.environ: + if str(env) in ACCEPTABLE_TO_BE_LEFT_BLANK: + continue + isMissingEnvs = True + missing.append(env) + + if isMissingEnvs: + printstr = '' + [printstr + str(var) for var in missing] + print(f"The following environment variables are missing: {printstr}. Please add them to the .env file.") + exit(-1) + + +def runPS(cmd): + completed = subprocess.run(["powershell", "-Command", cmd], capture_output=True) + return completed diff --git a/utils/envUpdate.py b/utils/envUpdate.py deleted file mode 100644 index f225419..0000000 --- a/utils/envUpdate.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -import subprocess -import tempfile -import logging - -from os import path -from sys import platform, stderr - -log = logging.getLogger(__name__) - -def envUpdate(): - if path.exists(".env.template"): - if platform == "win32" or platform == "cygwin": - runPS('utils\envUpdateWin.ps1') - f = open("envVars.txt", "rb") - envTemplate = f.read() - elif platform == "darwin" or platform == "linux": - envTemplate = subprocess.check_output( - "awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", - shell=True, - ) - return - tempEnv = tempfile.TemporaryFile() - tempEnv.write(envTemplate) - tempEnv.seek(0) - envVars = tempEnv.readlines() - - missing = [] - isMissingEnvs = False - for env in envVars: - try: - env = env.decode("utf-8").strip() - except AttributeError: - env = env.strip() - - if env not in os.environ: - isMissingEnvs = True - missing.append(env) - - if isMissingEnvs: - log.error( - f"[ERROR] The following environment variables are missing: {missing}.)" - ) - exit(-1) - -def runPS(cmd): - completed = subprocess.run(["powershell", "-Command", cmd], capture_output=True) - return completed \ No newline at end of file diff --git a/utils/scripts/FileGrabber.ps1 b/utils/scripts/FileGrabber.ps1 new file mode 100644 index 0000000..a820d2e --- /dev/null +++ b/utils/scripts/FileGrabber.ps1 @@ -0,0 +1,9 @@ +$envFile = Get-Content ".\.env.template" + +$envFile -split "=" | Where-Object {$_ -notmatch '\"'} | Set-Content ".\envVarsbefSpl.txt" +Get-Content ".\envVarsbefSpl.txt" | Where-Object {$_ -notmatch '\#'} | Set-Content ".\envVarsN.txt" +Get-Content ".\envVarsN.txt" | Where-Object {$_ -ne ''} | Set-Content ".\video_creation\data\envvars.txt" +Remove-Item ".\envVarsbefSpl.txt" +Remove-Item ".\envVarsN.txt" + +Write-Host $nowSplit diff --git a/utils/envUpdateWin.ps1 b/utils/scripts/FileGrabberenv.ps1 similarity index 77% rename from utils/envUpdateWin.ps1 rename to utils/scripts/FileGrabberenv.ps1 index 1eb8f64..ffb021b 100644 --- a/utils/envUpdateWin.ps1 +++ b/utils/scripts/FileGrabberenv.ps1 @@ -1,9 +1,9 @@ -$envFile = Get-Content ".\.env.template" +$envFile = Get-Content ".\.env" $envFile -split "=" | Where-Object {$_ -notmatch '\"'} | Set-Content ".\envVarsbefSpl.txt" Get-Content ".\envVarsbefSpl.txt" | Where-Object {$_ -notmatch '\#'} | Set-Content ".\envVarsN.txt" -Get-Content ".\envVarsN.txt" | Where-Object {$_ -ne ''} | Set-Content ".\envVars.txt" +Get-Content ".\envVarsN.txt" | Where-Object {$_ -ne ''} | Set-Content ".\video_creation\data\envvars.txt" Remove-Item ".\envVarsbefSpl.txt" Remove-Item ".\envVarsN.txt" -Write-Host $nowSplit \ No newline at end of file +Write-Host $nowSplit diff --git a/video_creation/data/.gitignore b/video_creation/data/.gitignore deleted file mode 100644 index a8731e5..0000000 --- a/video_creation/data/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -videos.json - #todo add videos on github From 99978b1cf1189ae1008772731cffd31f40462631 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:29:59 -0400 Subject: [PATCH 12/20] fixed readme as seen in #556 --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 13248b1..26b5814 100644 --- a/README.md +++ b/README.md @@ -39,21 +39,22 @@ The only original thing being done is the editing and gathering of all materials ## Installation 👩‍đŸ’ģ 1. Clone this repository -2. 2a **Automatic Install**: Run `python3 main.py` and type 'yes' to activate the setup assistant. +2. 2a **Automatic Install**: Run `python main.py` and type 'yes' to activate the setup assistant. 2b **Manual Install**: Rename `.env.template` to `.env` and replace all values with the appropriate fields. To get Reddit keys (**required**), visit [the Reddit Apps page.](https://www.reddit.com/prefs/apps) TL;DR set up an app that is a "script". Copy your keys into the `.env` file, along with whether your account uses two-factor authentication. 3. Install [SoX](https://sourceforge.net/projects/sox/files/sox/) -4. Run `pip3 install -r requirements.txt` +4. Run `pip install -r requirements.txt` -5. Run `playwright install` and `playwright install-deps`. +5. Run `playwright install` and `playwright install-deps`. (if this fails try adding python -m to the front of the command) -6. Run `python3 main.py` (unless you chose automatic install, then the installer will automatically run main.py) +6. Run `python main.py` (unless you chose automatic install, then the installer will automatically run main.py) required\*\*), visit [the Reddit Apps page.](https://www.reddit.com/prefs/apps) TL;DR set up an app that is a "script". Copy your keys into the `.env` file, along with whether your account uses two-factor authentication. 7. Enjoy 😎 +(Note if you got an error installing or running the bot try first rerunning the command with a three after the name e.g. python3 or pip3) ## Video https://user-images.githubusercontent.com/66544866/173453972-6526e4e6-c6ef-41c5-ab40-5d275e724e7c.mp4 From 56ad55933986a115bb3411d45ae55520895c6bc9 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:37:00 -0400 Subject: [PATCH 13/20] added the changes of #549 --- .gitignore | 4 ++-- TTS/swapper.py | 7 ++++++- reddit/subreddit.py | 13 +++++++++---- utils/subreddit.py | 9 ++++++--- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 9a8b2b2..4ee3693 100644 --- a/.gitignore +++ b/.gitignore @@ -239,5 +239,5 @@ results/* reddit-bot-351418-5560ebc49cac.json /.idea *.pyc -/video_creation/data/videos.json -/video_creation/data/envvars.txt +video_creation/data/videos.json +video_creation/data/envvars.txt diff --git a/TTS/swapper.py b/TTS/swapper.py index 025275a..c5f6776 100644 --- a/TTS/swapper.py +++ b/TTS/swapper.py @@ -5,6 +5,7 @@ from dotenv import load_dotenv from TTS.GTTS import GTTS from TTS.POLLY import POLLY from TTS.TikTok import TikTok +from utils.console import print_substep CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS, "polly": POLLY} @@ -12,7 +13,11 @@ CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS, "polly": POLLY} class TTS: def __new__(cls): load_dotenv() - CHOICE = getenv("TTsChoice").casefold() + try: + CHOICE = getenv("TTsChoice").casefold() + except AttributeError: + print_substep("None defined. Defaulting to 'polly.'") + CHOICE = "polly" valid_keys = [key.lower() for key in CHOICE_DIR.keys()] if CHOICE not in valid_keys: raise ValueError(f"{CHOICE} is not valid. Please use one of these {valid_keys} options") diff --git a/reddit/subreddit.py b/reddit/subreddit.py index 964862f..92c6d7a 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -1,3 +1,4 @@ +import re from os import getenv, environ import praw @@ -44,10 +45,14 @@ def get_subreddit_threads(): print_step("Getting subreddit threads...") if not getenv( "SUBREDDIT" - ): # note to self. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") - subreddit = reddit.subreddit( - input("What subreddit would you like to pull from? ") - ) # if the env isnt set, ask user + ): # note to user. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") + try: + subreddit = reddit.subreddit( + re.sub(r"r\/", "", input("What subreddit would you like to pull from? ")) # removes the r/ from the input + ) + except ValueError: + subreddit = reddit.subreddit("askreddit") + print_substep("Subreddit not defined. Using AskReddit.") else: print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config") subreddit = reddit.subreddit( diff --git a/utils/subreddit.py b/utils/subreddit.py index d12228f..e05c136 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -14,9 +14,12 @@ def get_subreddit_undone(submissions: List, subreddit): if already_done(done_videos, submission): continue if submission.over_18: - if getenv("ALLOW_NSFW").casefold() == "false": - print_substep("NSFW Post Detected. Skipping...") - continue + try: + if getenv("ALLOW_NSFW").casefold() == "false": + print_substep("NSFW Post Detected. Skipping...") + continue + except AttributeError: + print_substep("NSFW settings not defined. Skipping NSFW post...") return submission print("all submissions have been done going by top submission order") return get_subreddit_undone( From 894bc306c31fe7e901fc734e71d3a5aca28cfce1 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:38:21 -0400 Subject: [PATCH 14/20] reformatted again --- reddit/subreddit.py | 3 ++- utils/checker.py | 25 +++++++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/reddit/subreddit.py b/reddit/subreddit.py index 92c6d7a..34a8e17 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -48,7 +48,8 @@ def get_subreddit_threads(): ): # note to user. you can have multiple subreddits via reddit.subreddit("redditdev+learnpython") try: subreddit = reddit.subreddit( - re.sub(r"r\/", "", input("What subreddit would you like to pull from? ")) # removes the r/ from the input + re.sub(r"r\/", "", input("What subreddit would you like to pull from? ")) + # removes the r/ from the input ) except ValueError: subreddit = reddit.subreddit("askreddit") diff --git a/utils/checker.py b/utils/checker.py index 0372514..05f1412 100644 --- a/utils/checker.py +++ b/utils/checker.py @@ -4,28 +4,32 @@ import tempfile from os import path from sys import platform -ACCEPTABLE_TO_BE_LEFT_BLANK = ['RANDOM_THREAD', 'TIMES_TO_RUN'] +ACCEPTABLE_TO_BE_LEFT_BLANK = ["RANDOM_THREAD", "TIMES_TO_RUN"] def envUpdate(): if path.exists(".env.template"): # if .env.template exists and .env does not exist if platform == "win32" or platform == "cygwin": - runPS('utils/scripts/FileGrabber.ps1') + runPS("utils/scripts/FileGrabber.ps1") with open(".\\video_creation\\data\\envvars.txt", "rb") as f: envTemplate = f.read() elif platform == "darwin" or platform == "linux": - envTemplate = subprocess.check_output("awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", - shell=True, ) + envTemplate = subprocess.check_output( + "awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", + shell=True, + ) else: raise OSError("Unsupported platform") elif path.exists(".env"): if platform == "win32" or platform == "cygwin": - runPS('utils/scripts/FileGrabberenv.ps1') + runPS("utils/scripts/FileGrabberenv.ps1") with open(".\\video_creation\\data\\envvars.txt", "rb") as f: envTemplate = f.read() elif platform == "darwin" or platform == "linux": - envTemplate = subprocess.check_output("awk -F '=' 'NF {print $1}' .env | grep --regexp=^[a-zA-Z]", - shell=True, ) + envTemplate = subprocess.check_output( + "awk -F '=' 'NF {print $1}' .env | grep --regexp=^[a-zA-Z]", + shell=True, + ) else: raise OSError("Unsupported platform") else: @@ -35,7 +39,6 @@ def envUpdate(): tempEnv.seek(0) envVars = tempEnv.readlines() - missing = [] isMissingEnvs = False for env in envVars: @@ -51,9 +54,11 @@ def envUpdate(): missing.append(env) if isMissingEnvs: - printstr = '' + printstr = "" [printstr + str(var) for var in missing] - print(f"The following environment variables are missing: {printstr}. Please add them to the .env file.") + print( + f"The following environment variables are missing: {printstr}. Please add them to the .env file." + ) exit(-1) From 886b8114a522be1280e80579fd6839f016bc7e24 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:47:40 -0400 Subject: [PATCH 15/20] fixes #557 stops it from ever happening again --- .env.template | 2 +- reddit/subreddit.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.env.template b/.env.template index 2e1ec9e..bcd326d 100644 --- a/.env.template +++ b/.env.template @@ -19,7 +19,7 @@ THEME="LIGHT" # used if you want to run multiple times. set to an int e.g. 4 or 29 and leave blank for 1 TIMES_TO_RUN="" # max number of characters a comment can have. -MAX_COMMENT_LENGTH="500" +MAX_COMMENT_LENGTH="500" # default is 500 # Range is 0 -> 1 recommended around 0.8-0.9 OPACITY="1" diff --git a/reddit/subreddit.py b/reddit/subreddit.py index 34a8e17..7c5db91 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -15,6 +15,13 @@ def textify(text): return "".join(filter(TEXT_WHITELIST.__contains__, text)) +def try_env(param, backup): + try: + return environ[param] + except KeyError: + return backup + + def get_subreddit_threads(): """ Returns a list of threads from the AskReddit subreddit. @@ -89,7 +96,7 @@ def get_subreddit_threads(): if top_level_comment.body in ["[removed]", "[deleted]"]: continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78 if not top_level_comment.stickied: - if len(top_level_comment.body) <= int(environ["MAX_COMMENT_LENGTH"]): + if len(top_level_comment.body) <= int(try_env("MAX_COMMENT_LENGTH", 500)): content["comments"].append( { "comment_body": top_level_comment.body, From a36aca273e497cc14d9c1b48dc2ddc154b411a83 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:51:38 -0400 Subject: [PATCH 16/20] added init files to stop python from whining --- TTS/__init__.py | 0 utils/__init__.py | 0 video_creation/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 TTS/__init__.py create mode 100644 utils/__init__.py create mode 100644 video_creation/__init__.py diff --git a/TTS/__init__.py b/TTS/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/video_creation/__init__.py b/video_creation/__init__.py new file mode 100644 index 0000000..e69de29 From 56635835645a4b34d6605db098576893099bd80e Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jun 2022 19:57:05 -0400 Subject: [PATCH 17/20] added timeout prevention --- video_creation/screenshot_downloader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index e27a2be..6147dff 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -40,7 +40,7 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): context.add_cookies(cookies) # load preference cookies # Get the thread screenshot page = context.new_page() - page.goto(reddit_object["thread_url"]) + page.goto(reddit_object["thread_url"], timeout=0) page.set_viewport_size(ViewportSize(width=1920, height=1080)) if page.locator('[data-testid="content-gate"]').is_visible(): # This means the post is NSFW and requires to click the proceed button. @@ -68,7 +68,7 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num): if page.locator('[data-testid="content-gate"]').is_visible(): page.locator('[data-testid="content-gate"] button').click() - page.goto(f'https://reddit.com{comment["comment_url"]}') + page.goto(f'https://reddit.com{comment["comment_url"]}', timeout=0) page.locator(f"#t1_{comment['comment_id']}").screenshot( path=f"assets/temp/png/comment_{idx}.png" ) From a8b309765185afc8e7f87b8a4812cdf6db2e129e Mon Sep 17 00:00:00 2001 From: Jason <66544866+JasonLovesDoggo@users.noreply.github.com> Date: Sat, 18 Jun 2022 22:05:34 -0400 Subject: [PATCH 18/20] Update main.py Added version tag --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 6f478b1..f62e877 100755 --- a/main.py +++ b/main.py @@ -11,7 +11,7 @@ from video_creation.background import download_background, chop_background_video from video_creation.final_video import make_final_video from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts from video_creation.voices import save_text_to_mp3 - +VERSION = 2.1 print( """ ██████╗ ███████╗██████╗ ██████╗ ██╗████████╗ ██╗ ██╗██╗██████╗ ███████╗ ██████╗ ███╗ ███╗ █████╗ ██╗ ██╗███████╗██████╗ From cd558fcc0294797f99e9c64b9357f2f5c674bac4 Mon Sep 17 00:00:00 2001 From: Jason <66544866+JasonLovesDoggo@users.noreply.github.com> Date: Sun, 19 Jun 2022 11:17:22 -0400 Subject: [PATCH 19/20] Update main.py Remove env update --- main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index f62e877..3ca7738 100755 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ from os import getenv, name from reddit.subreddit import get_subreddit_threads from utils.cleanup import cleanup from utils.console import print_markdown, print_step -from utils.checker import envUpdate +# from utils.checker import envUpdate from video_creation.background import download_background, chop_background_video from video_creation.final_video import make_final_video from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts @@ -38,7 +38,7 @@ reddit2fa = getenv("REDDIT_2FA") def main(): - envUpdate() + #envUpdate() cleanup() def get_obj(): From fc14049dba5706743af79a0eaab568f059dee369 Mon Sep 17 00:00:00 2001 From: Jason <66544866+JasonLovesDoggo@users.noreply.github.com> Date: Sun, 19 Jun 2022 15:32:56 -0400 Subject: [PATCH 20/20] Delete checker.py --- utils/checker.py | 67 ------------------------------------------------ 1 file changed, 67 deletions(-) delete mode 100644 utils/checker.py diff --git a/utils/checker.py b/utils/checker.py deleted file mode 100644 index 05f1412..0000000 --- a/utils/checker.py +++ /dev/null @@ -1,67 +0,0 @@ -import os -import subprocess -import tempfile -from os import path -from sys import platform - -ACCEPTABLE_TO_BE_LEFT_BLANK = ["RANDOM_THREAD", "TIMES_TO_RUN"] - - -def envUpdate(): - if path.exists(".env.template"): # if .env.template exists and .env does not exist - if platform == "win32" or platform == "cygwin": - runPS("utils/scripts/FileGrabber.ps1") - with open(".\\video_creation\\data\\envvars.txt", "rb") as f: - envTemplate = f.read() - elif platform == "darwin" or platform == "linux": - envTemplate = subprocess.check_output( - "awk -F '=' 'NF {print $1}' .env.template | grep --regexp=^[a-zA-Z]", - shell=True, - ) - else: - raise OSError("Unsupported platform") - elif path.exists(".env"): - if platform == "win32" or platform == "cygwin": - runPS("utils/scripts/FileGrabberenv.ps1") - with open(".\\video_creation\\data\\envvars.txt", "rb") as f: - envTemplate = f.read() - elif platform == "darwin" or platform == "linux": - envTemplate = subprocess.check_output( - "awk -F '=' 'NF {print $1}' .env | grep --regexp=^[a-zA-Z]", - shell=True, - ) - else: - raise OSError("Unsupported platform") - else: - raise FileNotFoundError("No .env or .env.template file found") - tempEnv = tempfile.TemporaryFile() - tempEnv.write(envTemplate) - tempEnv.seek(0) - envVars = tempEnv.readlines() - - missing = [] - isMissingEnvs = False - for env in envVars: - try: - env = env.decode("utf-8").strip() - except AttributeError: - env = env.strip() - - if env not in os.environ: - if str(env) in ACCEPTABLE_TO_BE_LEFT_BLANK: - continue - isMissingEnvs = True - missing.append(env) - - if isMissingEnvs: - printstr = "" - [printstr + str(var) for var in missing] - print( - f"The following environment variables are missing: {printstr}. Please add them to the .env file." - ) - exit(-1) - - -def runPS(cmd): - completed = subprocess.run(["powershell", "-Command", cmd], capture_output=True) - return completed