diff --git a/.env.template b/.env.template
index cb2fa3b..bcd326d 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 lenght (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 'assets/'
-FINAL_VIDEO_PATH=""
# Valid options are "yes" and "no" for the variable below
REDDIT_2FA=""
SUBREDDIT="AskReddit"
@@ -25,14 +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_COMMENT_LENGTH="500"
+# max number of characters a comment can have.
+MAX_COMMENT_LENGTH="500" # default is 500
# Range is 0 -> 1 recommended around 0.8-0.9
OPACITY="1"
-# see TTSwrapper.py for all valid options
-VOICE="en_us_001" # e.g. en_us_002
+# see different voice options: todo: add docs
+VOICE="Matthew" # e.g. en_us_002
+TTsChoice="polly"
# IN-PROGRESS - not yet implemented
STORYMODE="False"
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/.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/.gitignore b/.gitignore
index 2701ca5..4ee3693 100644
--- a/.gitignore
+++ b/.gitignore
@@ -153,20 +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
\ No newline at end of file
+video_creation/data/videos.json
+video_creation/data/envvars.txt
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 71a9aa8..e3e858d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,16 +8,22 @@ All types of contributions are encouraged and valued. See the [Table of Contents
>
> - β Star the project
> - π£ Tweet about it
-> - π² Refer this project in your project's readme
+> - π² Refer this project in your project's readme
## Table of Contents
-- [I Have a Question](#i-have-a-question)
-- [I Want To Contribute](#i-want-to-contribute)
-- [Reporting Bugs](#reporting-bugs)
-- [Suggesting Enhancements](#suggesting-enhancements)
-- [Your First Code Contribution](#your-first-code-contribution)
-- [Improving The Documentation](#improving-the-documentation)
+- [Contributing to Reddit Video Maker Bot π₯](#contributing-to-reddit-video-maker-bot-)
+ - [Table of Contents](#table-of-contents)
+ - [I Have a Question](#i-have-a-question)
+ - [I Want To Contribute](#i-want-to-contribute)
+ - [Reporting Bugs](#reporting-bugs)
+ - [How Do I Submit a Good Bug Report?](#how-do-i-submit-a-good-bug-report)
+ - [Suggesting Enhancements](#suggesting-enhancements)
+ - [How Do I Submit a Good Enhancement Suggestion?](#how-do-i-submit-a-good-enhancement-suggestion)
+ - [Your First Code Contribution](#your-first-code-contribution)
+ - [Your environment](#your-environment)
+ - [Making your first PR](#making-your-first-pr)
+ - [Improving The Documentation](#improving-the-documentation)
## I Have a Question
@@ -38,6 +44,7 @@ Additionally, there is a [Discord Server](https://discord.gg/swqtb7AsNQ) for any
## I Want To Contribute
### Reporting Bugs
+
Before Submitting a Bug Report
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
@@ -51,9 +58,9 @@ A good bug report shouldn't leave others needing to chase you up for more inform
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
- Your input and the output
- - Is the issue reproducable? Does it exist in previous versions?
-
-How Do I Submit a Good Bug Report?
+ - Is the issue reproducible? Does it exist in previous versions?
+
+#### How Do I Submit a Good Bug Report?
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
@@ -65,7 +72,7 @@ We use GitHub issues to track bugs and errors. If you run into an issue with the
Once it's filed:
- The project team will label the issue accordingly.
-- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will try to support you as best as they can, but you may not recieve an instant.
+- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will try to support you as best as they can, but you may not receive an instant.
- If the team discovers that this is an issue it will be marked `bug` or `error`, as well as possibly other tags relating to the nature of the error), and the issue will be left to be [implemented by someone](#your-first-code-contribution).
diff --git a/README.md b/README.md
index 1696c62..1e72b5c 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,19 @@ All done WITHOUT video editing or asset compiling. Just pure β¨programming magi
Created by Lewis Menelaws & [TMRRW](https://tmrrwinc.ca)
+
+
-
+
+
+
+
+
+## Video Explainer
+
+[
+](https://www.youtube.com/watch?v=3gjcY_00U1w)
## Motivation π€
@@ -29,26 +39,27 @@ The only original thing being done is the editing and gathering of all materials
## Installation π©βπ»
1. Clone this repository
-2. Run `pip3 install -r requirements.txt`
-3. Run `playwright install` and `playwright install-deps`.
-4. Install [SoX](https://sourceforge.net/projects/sox/files/sox/)
-**EXPERIMENTAL**: Run this install script to do steps 1-3 automatically (it also install dependencies!). Supports MacOS and Debian, Arch, CentoOS and fedora.
+2. Install [SoX](https://sourceforge.net/projects/sox/files/sox/)
+
+3. Run `pip install -r requirements.txt`
+
+4. Run `playwright install` and `playwright install-deps`. (if this fails try adding python -m to the front of the command)
+
+**EXPERIMENTAL**: Run this install script to do steps 1-4 automatically (it also install dependencies!). Supports MacOS and Debian, Arch, CentoOS and fedora.
To run: `sh <(curl -sL https://raw.githubusercontent.com/micziz/RedditVideoMakerBot/master/install.sh)`
-5.
- 5a **Automatic Install**: Run `python3 main.py` and type 'yes' to activate the setup assistant.
- 5b **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.
-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
-
## Contributing & Ways to improve π
In its current state, this bot does exactly what it needs to do. However, lots of improvements can be made.
@@ -62,7 +73,7 @@ I have tried to simplify the code so anyone can read it and start contributing a
- [x] Allowing users to change voice.
- [x] Checks if a video has already been created
- [x] Light and Dark modes
-- [x] Nsfw post filter
+- [x] NSFW post filter
Please read our [contributing guidelines](CONTRIBUTING.md) for more detailed information.
@@ -72,10 +83,10 @@ Elebumm (Lewis#6305) - https://github.com/elebumm (Founder)
Jason (JasonLovesDoggo#1904) - https://github.com/JasonLovesDoggo
-CallumIO - https://github.com/CallumIO
+CallumIO (c.#6837) - https://github.com/CallumIO
HarryDaDev (hrvyy#9677) - https://github.com/ImmaHarry
LukaHietala (Pix.#0001) - https://github.com/LukaHietala
-Freebiell - https://github.com/FreebieII
+Freebiell (Freebie#6429) - https://github.com/FreebieII
diff --git a/TTS/GTTS.py b/TTS/GTTS.py
new file mode 100644
index 0000000..fcbcb9b
--- /dev/null
+++ b/TTS/GTTS.py
@@ -0,0 +1,13 @@
+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}")
diff --git a/TTS/POLLY.py b/TTS/POLLY.py
new file mode 100644
index 0000000..da1fae0
--- /dev/null
+++ b/TTS/POLLY.py
@@ -0,0 +1,106 @@
+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/video_creation/TTSwrapper.py b/TTS/TikTok.py
similarity index 77%
rename from video_creation/TTSwrapper.py
rename to TTS/TikTok.py
index fc104dc..662e498 100644
--- a/video_creation/TTSwrapper.py
+++ b/TTS/TikTok.py
@@ -65,41 +65,37 @@ noneng = [
# 'ok': ['en_au_002', 'en_uk_001']} # less en_us_stormtrooper more less en_us_rocket en_us_ghostface
-class TTTTSWrapper: # TikTok Text-to-Speech Wrapper
+class TikTok: # TikTok Text-to-Speech Wrapper
def __init__(self):
- self.URI_BASE = "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker="
+ self.URI_BASE = (
+ "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker="
+ )
def tts(
- self,
- req_text: str = "TikTok Text To Speech",
- filename: str = "title.mp3",
- random_speaker: bool = False,
- censer=False,
+ self,
+ req_text: str = "TikTok Text To Speech",
+ filename: str = "title.mp3",
+ random_speaker: bool = False,
+ censor=False,
):
req_text = req_text.replace("+", "plus").replace(" ", "+").replace("&", "and")
- if censer:
+ if censor:
# req_text = pf.censor(req_text)
pass
voice = (
- self.randomvoice()
- if random_speaker
- else (os.getenv("VOICE") or random.choice(human))
+ self.randomvoice() if random_speaker else (os.getenv("VOICE") or random.choice(human))
)
- chunks = [
- m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text)
- ]
+ chunks = [m.group().strip() for m in re.finditer(r" *((.{0,299})(\.|.$))", req_text)]
audio_clips = []
cbn = sox.Combiner()
- #cbn.set_input_format(file_type=["mp3" for _ in chunks])
+ # cbn.set_input_format(file_type=["mp3" for _ in chunks])
chunkId = 0
for chunk in chunks:
try:
- r = requests.post(
- f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0"
- )
+ r = requests.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0")
except requests.exceptions.SSLError:
# https://stackoverflow.com/a/47475019/18516611
session = requests.Session()
@@ -107,9 +103,8 @@ class TTTTSWrapper: # TikTok Text-to-Speech Wrapper
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)
- r = session.post(
- f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0"
- )
+ r = session.post(f"{self.URI_BASE}{voice}&req_text={chunk}&speaker_map_type=0")
+ print(r.text)
vstr = [r.json()["data"]["v_str"]][0]
b64d = base64.b64decode(vstr)
@@ -125,12 +120,14 @@ class TTTTSWrapper: # TikTok Text-to-Speech Wrapper
cbn.build(audio_clips, filename, "concatenate")
else:
os.rename(audio_clips[0], filename)
-
- except sox.core.SoxError: # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339
+ except (
+ sox.core.SoxError,
+ FileNotFoundError,
+ ): # https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/67#issuecomment-1150466339
for clip in audio_clips:
i = audio_clips.index(clip) # get the index of the clip
audio_clips = (
- audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1:]
+ audio_clips[:i] + [AudioFileClip(clip)] + audio_clips[i + 1 :]
) # replace the clip with an AudioFileClip
audio_concat = concatenate_audioclips(audio_clips)
audio_composite = CompositeAudioClip([audio_concat])
diff --git a/TTS/__init__.py b/TTS/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/TTS/swapper.py b/TTS/swapper.py
new file mode 100644
index 0000000..c5f6776
--- /dev/null
+++ b/TTS/swapper.py
@@ -0,0 +1,24 @@
+from os import getenv
+
+from dotenv import load_dotenv
+
+from TTS.GTTS import GTTS
+from TTS.POLLY import POLLY
+from TTS.TikTok import TikTok
+from utils.console import print_substep
+
+CHOICE_DIR = {"tiktok": TikTok, "gtts": GTTS, "polly": POLLY}
+
+
+class TTS:
+ def __new__(cls):
+ load_dotenv()
+ try:
+ CHOICE = getenv("TTsChoice").casefold()
+ except AttributeError:
+ print_substep("None defined. Defaulting to 'polly.'")
+ CHOICE = "polly"
+ valid_keys = [key.lower() for key in CHOICE_DIR.keys()]
+ if CHOICE not in valid_keys:
+ raise ValueError(f"{CHOICE} is not valid. Please use one of these {valid_keys} options")
+ return CHOICE_DIR.get(CHOICE)()
diff --git a/main.py b/main.py
index 4f74a74..f62e877 100755
--- a/main.py
+++ b/main.py
@@ -6,24 +6,26 @@ from os import getenv, name
from reddit.subreddit import get_subreddit_threads
from utils.cleanup import cleanup
from utils.console import print_markdown, print_step
+from utils.checker import envUpdate
from video_creation.background import download_background, chop_background_video
from video_creation.final_video import make_final_video
from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts
from video_creation.voices import save_text_to_mp3
-
-banner = """
-βββββββ βββββββββββββββ βββββββ ββββββββββββ βββ βββββββββββββ ββββββββ βββββββ ββββ ββββ ββββββ βββ ββββββββββββββββββ
+VERSION = 2.1
+print(
+ """
+βββββββ βββββββββββββββ βββββββ ββββββββββββ βββ βββββββββββββ ββββββββ βββββββ ββββ ββββ ββββββ βββ ββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββ βββ βββββββββββββββββββββββββββββββ βββββ ββββββββββββββββ ββββββββββββββββββββ
ββββββββββββββ βββ ββββββ ββββββ βββ βββ βββββββββ βββββββββ βββ βββ ββββββββββββββββββββββββββ ββββββ ββββββββ
ββββββββββββββ βββ ββββββ ββββββ βββ ββββ ββββββββββ βββββββββ βββ βββ ββββββββββββββββββββββββββ ββββββ ββββββββ
βββ ββββββββββββββββββββββββββββββ βββ βββββββ ββββββββββββββββββββββββββββ βββ βββ ββββββ ββββββ ββββββββββββββ βββ
βββ ββββββββββββββββββ βββββββ βββ βββ βββββ ββββββββββ ββββββββ βββββββ βββ ββββββ ββββββ ββββββββββββββ βββ
"""
-print(banner)
+)
load_dotenv()
# Modified by JasonLovesDoggo
print_markdown(
- "### Thanks for using this tool! [Feel free to contribute to this project on GitHub!](https://lewismenelaws.com) If you have any questions, feel free to reach out to me on Twitter or submit a GitHub issue."
+ "### Thanks for using this tool! [Feel free to contribute to this project on GitHub!](https://lewismenelaws.com) If you have any questions, feel free to reach out to me on Twitter or submit a GitHub issue. You can find solutions to many common problems in the [Documentation](https://luka-hietala.gitbook.io/documentation-for-the-reddit-bot/)"
)
time.sleep(1)
@@ -36,6 +38,7 @@ reddit2fa = getenv("REDDIT_2FA")
def main():
+ envUpdate()
cleanup()
def get_obj():
@@ -47,7 +50,7 @@ def main():
download_screenshots_of_reddit_posts(reddit_object, number_of_comments)
download_background()
chop_background_video(length)
- final_video = make_final_video(number_of_comments, length)
+ make_final_video(number_of_comments, length)
def run_many(times):
diff --git a/reddit/subreddit.py b/reddit/subreddit.py
index 273eb27..7c5db91 100644
--- a/reddit/subreddit.py
+++ b/reddit/subreddit.py
@@ -1,3 +1,4 @@
+import re
from os import getenv, environ
import praw
@@ -14,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.
@@ -23,9 +31,7 @@ def get_subreddit_threads():
content = {}
if str(getenv("REDDIT_2FA")).casefold() == "yes":
- print(
- "\nEnter your two-factor authentication code from your authenticator app.\n"
- )
+ print("\nEnter your two-factor authentication code from your authenticator app.\n")
code = input("> ")
print()
pw = getenv("REDDIT_PASSWORD")
@@ -41,19 +47,22 @@ def get_subreddit_threads():
check_for_async=False,
)
"""
- Ask user for subreddit input
- """
+ 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
+ ): # 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"
- )
+ print_substep(f"Using subreddit: r/{getenv('SUBREDDIT')} from environment variable config")
subreddit = reddit.subreddit(
getenv("SUBREDDIT")
) # Allows you to specify in .env. Done for automation purposes.
@@ -71,14 +80,10 @@ def get_subreddit_threads():
num_comments = submission.num_comments
print_substep(f"Video will be: {submission.title} :thumbsup:", style="bold green")
- print_substep(f"Thread has " + str(upvotes) + " upvotes", style="bold blue")
- print_substep(
- f"Thread has a upvote ratio of " + str(ratio) + "%", style="bold blue"
- )
- print_substep(f"Thread has " + str(num_comments) + " comments", style="bold blue")
- environ["VIDEO_TITLE"] = str(
- textify(submission.title)
- ) # todo use global instend of env vars
+ print_substep(f"Thread has {upvotes} upvotes", style="bold blue")
+ print_substep(f"Thread has a upvote ratio of {ratio}%", style="bold blue")
+ print_substep(f"Thread has {num_comments} comments", style="bold blue")
+ environ["VIDEO_TITLE"] = str(textify(submission.title)) # todo use global instend of env vars
environ["VIDEO_ID"] = str(textify(submission.id))
content["thread_url"] = f"https://reddit.com{submission.permalink}"
@@ -91,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,
diff --git a/setup.py b/setup.py
index 640cd26..a8d7f12 100755
--- a/setup.py
+++ b/setup.py
@@ -40,14 +40,10 @@ def handle_input(
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
+ 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
+ if nmax is not None and len(user_input) > nmax: # Check if string is not too long
console.log("[red]" + oob_error)
continue
break
diff --git a/utils/__init__.py b/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/utils/checker.py b/utils/checker.py
new file mode 100644
index 0000000..05f1412
--- /dev/null
+++ b/utils/checker.py
@@ -0,0 +1,67 @@
+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/cleanup.py b/utils/cleanup.py
index f3a6c61..9490b6d 100644
--- a/utils/cleanup.py
+++ b/utils/cleanup.py
@@ -5,9 +5,7 @@ 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()
- ]
+ files = [f for f in os.listdir(".") if f.endswith(".mp4") and "temp" in f.lower()]
count += len(files)
for f in files:
os.remove(f)
diff --git a/utils/config.py b/utils/config.py
new file mode 100644
index 0000000..000b615
--- /dev/null
+++ b/utils/config.py
@@ -0,0 +1,40 @@
+# write a class that takes .env file and parses it into a dictionary
+
+from dotenv import dotenv_values
+
+DEFAULTS = {
+ "SUBREDDIT": "AskReddit",
+ "ALLOW_NSFW": "False",
+ "POST_ID": "",
+ "THEME": "DARK",
+ "REDDIT_2FA": "no",
+ "TIMES_TO_RUN": "",
+ "MAX_COMMENT_LENGTH": "500",
+ "OPACITY": "1",
+ "VOICE": "en_us_001",
+ "STORYMODE": "False",
+}
+
+
+class Config:
+ def __init__(self):
+ self.raw = dotenv_values("../.env")
+ self.load_attrs()
+
+ def __getattr__(self, attr): # code completion for attributes fix.
+ return getattr(self, attr)
+
+ def load_attrs(self):
+ for key, value in self.raw.items():
+ self.add_attr(key, value)
+
+ def add_attr(self, key, value):
+ if value is None or value == "":
+ setattr(self, key, DEFAULTS[key])
+ else:
+ setattr(self, key, str(value))
+
+
+config = Config()
+
+print(config.SUBREDDIT)
diff --git a/utils/loader.py b/utils/loader.py
index 46c40fe..b9dc276 100644
--- a/utils/loader.py
+++ b/utils/loader.py
@@ -1,4 +1,3 @@
-
# Okay, have to admit. This code is from StackOverflow. It's so efficient, that it's probably the best way to do it.
# Although, it is edited to use less threads.
@@ -15,9 +14,9 @@ class Loader:
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.
+ 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
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/scripts/FileGrabberenv.ps1 b/utils/scripts/FileGrabberenv.ps1
new file mode 100644
index 0000000..ffb021b
--- /dev/null
+++ b/utils/scripts/FileGrabberenv.ps1
@@ -0,0 +1,9 @@
+$envFile = Get-Content ".\.env"
+
+$envFile -split "=" | Where-Object {$_ -notmatch '\"'} | Set-Content ".\envVarsbefSpl.txt"
+Get-Content ".\envVarsbefSpl.txt" | Where-Object {$_ -notmatch '\#'} | Set-Content ".\envVarsN.txt"
+Get-Content ".\envVarsN.txt" | Where-Object {$_ -ne ''} | Set-Content ".\video_creation\data\envvars.txt"
+Remove-Item ".\envVarsbefSpl.txt"
+Remove-Item ".\envVarsN.txt"
+
+Write-Host $nowSplit
diff --git a/utils/subreddit.py b/utils/subreddit.py
index 7a7dca0..e05c136 100644
--- a/utils/subreddit.py
+++ b/utils/subreddit.py
@@ -3,6 +3,7 @@ import json
from os import getenv
from utils.console import print_substep
+
def get_subreddit_undone(submissions: List, subreddit):
"""
recursively checks if the top submission in the list was already done.
@@ -13,10 +14,14 @@ 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(
subreddit.top(time_filter="hour"), subreddit
) # all of the videos in hot have already been done
diff --git a/utils/videos.py b/utils/videos.py
index a7b2012..51a2704 100755
--- a/utils/videos.py
+++ b/utils/videos.py
@@ -4,7 +4,9 @@ from os import getenv
from utils.console import print_step
-def check_done(redditobj): # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack
+def check_done(
+ redditobj,
+): # don't set this to be run anyplace that isn't subreddit.py bc of inspect stack
"""params:
reddit_object: The Reddit Object you received in askreddit.py"""
with open("./video_creation/data/videos.json", "r") as done_vids_raw:
diff --git a/video_creation/__init__.py b/video_creation/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/video_creation/background.py b/video_creation/background.py
index 162380e..fb300e6 100644
--- a/video_creation/background.py
+++ b/video_creation/background.py
@@ -7,8 +7,6 @@ from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
from moviepy.editor import VideoFileClip
from utils.console import print_step, print_substep
-import datetime
-
def get_start_and_end_times(video_length, length_of_clip):
random_time = randrange(180, int(length_of_clip) - int(video_length))
@@ -16,18 +14,18 @@ def get_start_and_end_times(video_length, length_of_clip):
def download_background():
- """Downloads the backgrounds/s video from youtube."""
+ """Downloads the backgrounds/s video from YouTube."""
Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True)
background_options = [ # uri , filename , credit
("https://www.youtube.com/watch?v=n_Dv4JMiwK8", "parkour.mp4", "bbswitzer"),
- (
- "https://www.youtube.com/watch?v=2X9QGY__0II",
- "rocket_league.mp4",
- "Orbital Gameplay",
- ),
+ # (
+ # "https://www.youtube.com/watch?v=2X9QGY__0II",
+ # "rocket_league.mp4",
+ # "Orbital Gameplay",
+ # ),
]
- # note: make sure the file name doesn't include a - in it
- if len(listdir("./assets/backgrounds")) != len(
+ # note: make sure the file name doesn't include an - in it
+ if not len(listdir("./assets/backgrounds")) >= len(
background_options
): # if there are any background videos not installed
print_step(
@@ -35,14 +33,14 @@ def download_background():
)
print_substep("Downloading the backgrounds videos... please be patient π ")
for uri, filename, credit in background_options:
+ if Path(f"assets/backgrounds/{credit}-{filename}").is_file():
+ continue # adds check to see if file exists before downloading
print_substep(f"Downloading {filename} from {uri}")
YouTube(uri).streams.filter(res="1080p").first().download(
"assets/backgrounds", filename=f"{credit}-{filename}"
)
- print_substep(
- "Background videos downloaded successfully! π", style="bold green"
- )
+ print_substep("Background videos downloaded successfully! π", style="bold green")
def chop_background_video(video_length):
@@ -60,5 +58,4 @@ def chop_background_video(video_length):
targetname="assets/temp/background.mp4",
)
print_substep("Background video chopped successfully!", style="bold green")
- noerror = True
- return noerror
+ return True
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/.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
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 7fec8d0..37b1ac2 100755
--- a/video_creation/final_video.py
+++ b/video_creation/final_video.py
@@ -13,17 +13,12 @@ from moviepy.editor import (
CompositeAudioClip,
CompositeVideoClip,
)
-import reddit.subreddit
-import re
-from utils.console import print_step, print_substep
-from dotenv import load_dotenv
-import os
from moviepy.video.io import ffmpeg_tools
+from rich.console import Console
from reddit import subreddit
from utils.cleanup import cleanup
from utils.console import print_step, print_substep
-from rich.console import Console
console = Console()
@@ -46,7 +41,7 @@ def make_final_video(number_of_clips, length):
audio_clips = []
for i in range(0, number_of_clips):
audio_clips.append(AudioFileClip(f"assets/temp/mp3/{i}.mp3"))
- audio_clips.insert(0, AudioFileClip(f"assets/temp/mp3/title.mp3"))
+ audio_clips.insert(0, AudioFileClip("assets/temp/mp3/title.mp3"))
audio_concat = concatenate_audioclips(audio_clips)
audio_composite = CompositeAudioClip([audio_concat])
@@ -57,12 +52,29 @@ def make_final_video(number_of_clips, length):
# Output Length
console.log(f"[bold green] Video Will Be: {int_total_length} Seconds Long")
- # Gather all images
+ # add title to video
image_clips = []
+ # Gather all images
+ if opacity is None or float(opacity) >= 1: # opacity not set or is set to one OR MORE
+ image_clips.insert(
+ 0,
+ ImageClip("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
+ 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)
@@ -77,38 +89,18 @@ def make_final_video(number_of_clips, length):
.resize(width=W - 100)
.set_opacity(float(opacity)),
)
- if (
- opacity is None or float(opacity) >= 1
- ): # opacity not set or is set to one OR MORE
- image_clips.insert(
- 0,
- ImageClip(f"assets/temp/png/title.png")
- .set_duration(audio_clips[0].duration)
- .set_position("center")
- .resize(width=W - 100)
- .set_opacity(float(opacity)),
- )
- if os.path.exists("assets/mp3/posttext.mp3"):
- image_clips.insert(
- 0,
- ImageClip("assets/png/title.png")
- .set_duration(audio_clips[0].duration + audio_clips[1].duration)
- .set_position("center")
- .resize(width=W - 100)
- .set_opacity(float(opacity)),
- )
- else:
- image_clips.insert(
- 0,
- ImageClip("assets/temp/png/title.png")
- .set_duration(audio_clips[0].duration)
- .set_position("center")
- .resize(width=W - 100)
- .set_opacity(float(opacity)),
- )
- image_concat = concatenate_videoclips(image_clips).set_position(
- ("center", "center")
- )
+
+ # 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])
@@ -142,9 +134,7 @@ def make_final_video(number_of_clips, length):
print_substep("the results folder didn't exist so I made it")
os.mkdir("./results")
- final.write_videofile(
- "assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k"
- )
+ final.write_videofile("assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k")
ffmpeg_tools.ffmpeg_extract_subclip(
"assets/temp/temp.mp4", 0, length, targetname=f"results/{filename}"
)
@@ -153,7 +143,7 @@ def make_final_video(number_of_clips, length):
print_step("Removing temporary files π")
cleanups = cleanup()
print_substep(f"Removed {cleanups} temporary files π")
- print_substep(f"See result in the results folder!")
+ print_substep("See result in the results folder!")
print_step(
f"Reddit title: {os.getenv('VIDEO_TITLE')} \n Background Credit: {os.getenv('background_credit')}"
diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py
index 4095f72..6147dff 100644
--- a/video_creation/screenshot_downloader.py
+++ b/video_creation/screenshot_downloader.py
@@ -18,8 +18,8 @@ storymode = False
def download_screenshots_of_reddit_posts(reddit_object, screenshot_num):
"""Downloads screenshots of reddit posts as they are seen on the web.
Args:
- reddit_object: The Reddit Object you received in askreddit.py
- screenshot_num: The number of screenshots you want to download.
+ reddit_object: The Reddit Object you received in askreddit.py
+ screenshot_num: The number of screenshots you want to download.
"""
print_step("Downloading screenshots of reddit posts...")
@@ -40,7 +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.
@@ -51,9 +51,7 @@ def download_screenshots_of_reddit_posts(reddit_object, screenshot_num):
'[data-click-id="text"] button'
).click() # Remove "Click to see nsfw" Button in Screenshot
- page.locator('[data-test-id="post-content"]').screenshot(
- path="assets/temp/png/title.png"
- )
+ page.locator('[data-test-id="post-content"]').screenshot(path="assets/temp/png/title.png")
if storymode:
page.locator('[data-click-id="text"]').screenshot(
path="assets/temp/png/story_content.png"
@@ -70,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"
)
diff --git a/video_creation/voices.py b/video_creation/voices.py
index 7912314..be7da96 100644
--- a/video_creation/voices.py
+++ b/video_creation/voices.py
@@ -1,20 +1,20 @@
#!/usr/bin/env python3
-from gtts import gTTS
+from os import getenv
from pathlib import Path
-from os import getenv, name
import sox
from mutagen import MutagenError
from mutagen.mp3 import MP3, HeaderNotFoundError
-from rich.progress import track
from rich.console import Console
+from rich.progress import track
-console = Console()
-import re
+from TTS.swapper import TTS
from utils.console import print_step, print_substep
from utils.voice import sanitize_text
-from video_creation.TTSwrapper import TTTTSWrapper
+
+console = Console()
+
VIDEO_LENGTH: int = 40 # secs
@@ -22,38 +22,38 @@ VIDEO_LENGTH: int = 40 # secs
def save_text_to_mp3(reddit_obj):
"""Saves Text to MP3 files.
Args:
- reddit_obj : The reddit object you received from the reddit API in the askreddit.py file.
+ reddit_obj : The reddit object you received from the reddit API in the askreddit.py file.
"""
print_step("Saving Text to MP3 files...")
length = 0
# Create a folder for the mp3 files.
Path("assets/temp/mp3").mkdir(parents=True, exist_ok=True)
-
- ttttsw = TTTTSWrapper() # tiktok text to speech wrapper
- ttttsw.tts(
+ TextToSpeech = TTS()
+ TextToSpeech.tts(
sanitize_text(reddit_obj["thread_title"]),
- filename=f"assets/temp/mp3/title.mp3",
+ filename="assets/temp/mp3/title.mp3",
random_speaker=False,
)
try:
- length += MP3(f"assets/temp/mp3/title.mp3").info.length
+ length += MP3("assets/temp/mp3/title.mp3").info.length
except HeaderNotFoundError: # note to self AudioFileClip
- length += sox.file_info.duration(f"assets/temp/mp3/title.mp3")
+ length += sox.file_info.duration("assets/temp/mp3/title.mp3")
if getenv("STORYMODE").casefold() == "true":
- ttttsw.tts(
+ TextToSpeech.tts(
sanitize_text(reddit_obj["thread_content"]),
- filename=f"assets/temp/mp3/story_content.mp3",
+ filename="assets/temp/mp3/story_content.mp3",
random_speaker=False,
)
- #'story_content'
+ # 'story_content'
com = 0
for comment in track((reddit_obj["comments"]), "Saving..."):
- # ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer, but this is just a good_voices starting point
+ # ! Stop creating mp3 files if the length is greater than VIDEO_LENGTH seconds. This can be longer
+ # but this is just a good_voices starting point
if length > VIDEO_LENGTH:
break
- ttttsw.tts(
+ TextToSpeech.tts(
sanitize_text(comment["comment_body"]),
filename=f"assets/temp/mp3/{com}.mp3",
random_speaker=False,