diff --git a/.env.template b/.env.template deleted file mode 100644 index 173bacf..0000000 --- a/.env.template +++ /dev/null @@ -1,39 +0,0 @@ -# This can be found in the email that reddit sent you when you created the app -REDDIT_CLIENT_ID="" -REDDIT_CLIENT_SECRET="" - -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="" - -# 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" -# True or False -ALLOW_NSFW="False" -# Used if you want to use a specific post. example of one is urdtfx -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 -TIMES_TO_RUN="" -MAX_COMMENT_LENGTH="500" -# Range is 0 -> 1 recommended around 0.8-0.9 -OPACITY="1" - -# see TTSwrapper.py for all valid options -VOICE="Matthew" # e.g. en_us_002 -TTsChoice="polly" # todo add docs - -# IN-PROGRESS - not yet implemented -STORYMODE="False" diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index ba1c6b8..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - 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 deleted file mode 100644 index c04b714..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,72 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -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' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # 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 - - # ℹ️ 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. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml deleted file mode 100644 index eac633f..0000000 --- a/.github/workflows/docker-image.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Docker Image CI - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -jobs: - - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Build the Docker image - run: docker build . --file Dockerfile --tag my-image-name:$(date +%s) diff --git a/README.md b/README.md deleted file mode 100644 index 13248b1..0000000 --- a/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# Reddit Video Maker Bot πŸŽ₯ - -All done WITHOUT video editing or asset compiling. Just pure ✨programming magic✨. - -Created by Lewis Menelaws & [TMRRW](https://tmrrwinc.ca) - - - - - - - - - - -## Video Explainer - -[![lewisthumbnail](https://user-images.githubusercontent.com/6053155/173631669-1d1b14ad-c478-4010-b57d-d79592a789f2.png) -](https://www.youtube.com/watch?v=3gjcY_00U1w) - -## Motivation πŸ€” - -These videos on TikTok, YouTube and Instagram get MILLIONS of views across all platforms and require very little effort. -The only original thing being done is the editing and gathering of all materials... - -... but what if we can automate that process? πŸ€” - -## Disclaimers 🚨 - -- **At the moment**, this repository won't attempt to upload this content through this bot. It will give you a file that - you will then have to upload manually. This is for the sake of avoiding any sort of community guideline issues. - -## Requirements - -- Python 3.6+ -- Playwright (this should install automatically in installation) -- Sox - -## Installation πŸ‘©β€πŸ’» - -1. Clone this repository -2. 2a **Automatic Install**: Run `python3 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` - -5. Run `playwright install` and `playwright install-deps`. - -6. Run `python3 main.py` (unless you chose automatic install, then the installer will automatically run main.py) - required\*\*), visit [the Reddit Apps page.](https://www.reddit.com/prefs/apps) TL;DR set up an app that is a "script". - Copy your keys into the `.env` file, along with whether your account uses two-factor authentication. -7. Enjoy 😎 - -## Video - -https://user-images.githubusercontent.com/66544866/173453972-6526e4e6-c6ef-41c5-ab40-5d275e724e7c.mp4 - -## Contributing & Ways to improve πŸ“ˆ - -In its current state, this bot does exactly what it needs to do. However, lots of improvements can be made. - -I have tried to simplify the code so anyone can read it and start contributing at any skill level. Don't be shy :) contribute! - -- [ ] Creating better documentation and adding a command line interface. -- [x] Allowing users to choose a reddit thread instead of being randomized. -- [x] Allowing users to choose a background that is picked instead of the Minecraft one. -- [x] Allowing users to choose between any subreddit. -- [x] Allowing users to change voice. -- [x] Checks if a video has already been created -- [x] Light and Dark modes -- [x] NSFW post filter - -Please read our [contributing guidelines](CONTRIBUTING.md) for more detailed information. - -## Developers and maintainers. - -Elebumm (Lewis#6305) - https://github.com/elebumm (Founder) - -Jason (JasonLovesDoggo#1904) - https://github.com/JasonLovesDoggo - -CallumIO (c.#6837) - https://github.com/CallumIO - -HarryDaDev (hrvyy#9677) - https://github.com/ImmaHarry - -LukaHietala (Pix.#0001) - https://github.com/LukaHietala - -Freebiell (Freebie#6429) - https://github.com/FreebieII diff --git a/TTS/POLLY.py b/TTS/POLLY.py deleted file mode 100644 index 54cddca..0000000 --- a/TTS/POLLY.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import random -import re - -import requests -import sox -from moviepy.audio.AudioClip import concatenate_audioclips, CompositeAudioClip -from moviepy.audio.io.AudioFileClip import AudioFileClip -from requests.exceptions import JSONDecodeError - -voices = [ - "Brian", - "Emma", - "Russell", - "Joey", - "Matthew", - "Joanna", - "Kimberly", - "Amy", - "Geraint", - "Nicole", - "Justin", - "Ivy", - "Kendra", - "Salli", - "Raveena", -] - - -# valid voices https://lazypy.ro/tts/ - - -class POLLY: - def __init__(self): - self.url = "https://streamlabs.com/polly/speak" - - def tts( - self, - req_text: str = "Amazon Text To Speech", - filename: str = "title.mp3", - random_speaker=False, - censor=False, - ): - if random_speaker: - voice = self.randomvoice() - else: - if not os.getenv("VOICE"): - return ValueError( - "Please set the environment variable VOICE to a valid voice. options are: {}".format( - voices - ) - ) - voice = str(os.getenv("VOICE")).capitalize() - body = {"voice": voice, "text": req_text, "service": "polly"} - response = requests.post(self.url, data=body) - try: - voice_data = requests.get(response.json()["speak_url"]) - with open(filename, "wb") as f: - f.write(voice_data.content) - except (KeyError, JSONDecodeError): - if response.json()["error"] == "Text length is too long!": - chunks = [ - m.group().strip() for m in re.finditer(r" *((.{0,499})(\.|.$))", req_text) - ] - - audio_clips = [] - cbn = sox.Combiner() - - chunkId = 0 - for chunk in chunks: - body = {"voice": voice, "text": chunk, "service": "polly"} - resp = requests.post(self.url, data=body) - voice_data = requests.get(resp.json()["speak_url"]) - with open(filename.replace(".mp3", f"-{chunkId}.mp3"), "wb") as out: - out.write(voice_data.content) - - audio_clips.append(filename.replace(".mp3", f"-{chunkId}.mp3")) - - chunkId = chunkId + 1 - try: - if len(audio_clips) > 1: - cbn.convert(samplerate=44100, n_channels=2) - cbn.build(audio_clips, filename, "concatenate") - else: - os.rename(audio_clips[0], filename) - except ( - sox.core.SoxError, - FileNotFoundError, - ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339 - for clip in audio_clips: - i = audio_clips.index(clip) # get the index of the clip - audio_clips = ( - audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :] - ) # replace the clip with an AudioFileClip - audio_concat = concatenate_audioclips(audio_clips) - audio_composite = CompositeAudioClip([audio_concat]) - audio_composite.write_audiofile(filename, 44100, 2, 2000, None) - - def make_readable(self, text): - """ - Amazon Polly fails to read some symbols properly such as '& (and)'. - So we normalize input text before passing it to the service - """ - text = text.replace("&", "and") - return text - - def randomvoice(self): - return random.choice(voices) diff --git a/TTS/TikTok.py b/TTS/TikTok.py deleted file mode 100644 index b9d1928..0000000 --- a/TTS/TikTok.py +++ /dev/null @@ -1,139 +0,0 @@ -import base64 -import os -import random -import re - -import requests -import sox -from moviepy.audio.AudioClip import concatenate_audioclips, CompositeAudioClip -from moviepy.audio.io.AudioFileClip import AudioFileClip -from requests.adapters import HTTPAdapter, Retry - -# from profanity_filter import ProfanityFilter -# pf = ProfanityFilter() -# Code by @JasonLovesDoggo -# 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 -] -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", -] -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 -] - - -# good_voices = {'good': ['en_us_002', 'en_us_006'], -# 'ok': ['en_au_002', 'en_uk_001']} # less en_us_stormtrooper more less en_us_rocket en_us_ghostface - - -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 diff --git a/TTS/swapper.py b/TTS/swapper.py deleted file mode 100644 index f4717b1..0000000 --- a/TTS/swapper.py +++ /dev/null @@ -1,21 +0,0 @@ -from os import getenv - -from dotenv import load_dotenv - -from TTS.GTTS import GTTS -from TTS.POLLY import POLLY -from TTS.TikTok import TikTok - -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)() diff --git a/main.py b/main.py deleted file mode 100755 index b4e1123..0000000 --- a/main.py +++ /dev/null @@ -1,73 +0,0 @@ -import time - -from subprocess import Popen -from dotenv import load_dotenv -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 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 - -print( - """ -β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— -β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β•šβ•β•β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•— -β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• -β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•— -β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ -β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β• β•šβ•β•β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β•β•šβ•β• β•šβ•β• -""" -) -load_dotenv() -# Modified by JasonLovesDoggo -print_markdown( - "### Thanks for using this tool! [Feel free to contribute to this project on GitHub!](https://lewismenelaws.com) If you have any questions, feel free to reach out to me on Twitter or submit a GitHub issue. You can find solutions to many common problems in the [Documentation](https://luka-hietala.gitbook.io/documentation-for-the-reddit-bot/)" -) - -time.sleep(1) - -client_id = getenv("REDDIT_CLIENT_ID") -client_secret = getenv("REDDIT_CLIENT_SECRET") -username = getenv("REDDIT_USERNAME") -password = getenv("REDDIT_PASSWORD") -reddit2fa = getenv("REDDIT_2FA") - - -def main(): - cleanup() - - def get_obj(): - reddit_obj = get_subreddit_threads() - return reddit_obj - - reddit_object = get_obj() - length, number_of_comments = save_text_to_mp3(reddit_object) - download_screenshots_of_reddit_posts(reddit_object, number_of_comments) - download_background() - chop_background_video(length) - make_final_video(number_of_comments, length) - - -def run_many(times): - for x in range(times): - x = x + 1 - print_step( - f'on the {x}{("st" if x == 1 else ("nd" if x == 2 else ("rd" if x == 3 else "th")))} iteration of {times}' - ) # correct 1st 2nd 3rd 4th 5th.... - main() - Popen("cls" if name == "nt" else "clear", shell=True).wait() - - -if __name__ == "__main__": - try: - if getenv("TIMES_TO_RUN") and isinstance(int(getenv("TIMES_TO_RUN")), int): - run_many(int(getenv("TIMES_TO_RUN"))) - else: - main() - except KeyboardInterrupt: - print_markdown("## Clearing temp files") - cleanup() - exit() diff --git a/reddit/subreddit.py b/reddit/subreddit.py deleted file mode 100644 index dc0d8ae..0000000 --- a/reddit/subreddit.py +++ /dev/null @@ -1,95 +0,0 @@ -from os import getenv, environ - -import praw - -from utils.console import print_step, print_substep -from utils.subreddit import get_subreddit_undone -from utils.videos import check_done -from praw.models import MoreComments - -TEXT_WHITELIST = set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890") - - -def textify(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.") - - 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 - - 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 diff --git a/utils/cleanup.py b/utils/cleanup.py deleted file mode 100644 index f3a6c61..0000000 --- a/utils/cleanup.py +++ /dev/null @@ -1,24 +0,0 @@ -import os -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 diff --git a/utils/loader.py b/utils/loader.py deleted file mode 100644 index f23a716..0000000 --- a/utils/loader.py +++ /dev/null @@ -1,51 +0,0 @@ -# Okay, have to admit. This code is from StackOverflow. It's so efficient, that it's probably the best way to do it. -# Although, it is edited to use less threads. - - -from itertools import cycle -from shutil import get_terminal_size -from threading import Thread -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() diff --git a/utils/subreddit.py b/utils/subreddit.py deleted file mode 100644 index 2a1114a..0000000 --- a/utils/subreddit.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import List -import json -from os import getenv -from utils.console import print_substep - - -def get_subreddit_undone(submissions: List, subreddit): - """ - recursively checks if the top submission in the list was already done. - """ - 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