Skip to content

Commit 902ff00

Browse files
JasonLovesDoggoCopilotgithub-actions[bot]RequieMapraffq
authored
chore: release 3.4.0 (#2426)
Co-authored-by: Jason Cameron <git@jasoncameron.dev> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: RequieMa <57754488+RequieMa@users.noreply.github.com> Co-authored-by: prafful <praffulsharma1230@gmail.com> Co-authored-by: M. Sanaullah <thebolly@gmail.com> Co-authored-by: embee <emilien.bev.com@gmail.com> Co-authored-by: Rodrigo <55567123+rodrigodasilv@users.noreply.github.com> Co-authored-by: Emilien Bevierre <44171454+emilienbev@users.noreply.github.com> Co-authored-by: tkhmielnitzky <tkhmielnitzky@gmail.com> Co-authored-by: bnfone <89687390+bnfone@users.noreply.github.com> Co-authored-by: Cyteon <129582290+Cyteon@users.noreply.github.com>
1 parent 64bf647 commit 902ff00

21 files changed

Lines changed: 319 additions & 164 deletions

.dockerignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
Dockerfile
1+
Dockerfile
2+
results

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.10

README.md

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,27 +37,61 @@ The only original thing being done is the editing and gathering of all materials
3737

3838
## Installation 👩‍💻
3939

40-
1. Clone this repository
41-
2. Run `pip install -r requirements.txt`
42-
3. Run `python -m playwright install` and `python -m playwright install-deps`
40+
1. Clone this repository:
41+
```sh
42+
git clone https://github.com/elebumm/RedditVideoMakerBot.git
43+
cd RedditVideoMakerBot
44+
```
45+
46+
2. Create and activate a virtual environment:
47+
- On **Windows**:
48+
```sh
49+
python -m venv ./venv
50+
.\venv\Scripts\activate
51+
```
52+
- On **macOS and Linux**:
53+
```sh
54+
python3 -m venv ./venv
55+
source ./venv/bin/activate
56+
```
57+
58+
3. Install the required dependencies:
59+
```sh
60+
pip install -r requirements.txt
61+
```
62+
63+
4. Install Playwright and its dependencies:
64+
```sh
65+
python -m playwright install
66+
python -m playwright install-deps
67+
```
68+
69+
---
4370

4471
**EXPERIMENTAL!!!!**
4572

46-
On macOS and Linux (debian, arch, fedora and centos, and based on those), you can run an install script that will automatically install steps 1 to 3. (requires bash)
73+
- On macOS and Linux (Debian, Arch, Fedora, CentOS, and based on those), you can run an installation script that will automatically install steps 1 to 3. (requires bash)
74+
- `bash <(curl -sL https://raw.githubusercontent.com/elebumm/RedditVideoMakerBot/master/install.sh)`
75+
- This can also be used to update the installation
4776

48-
`bash <(curl -sL https://raw.githubusercontent.com/elebumm/RedditVideoMakerBot/master/install.sh)`
77+
---
4978

50-
This can also be used to update the installation
79+
5. Run the bot:
80+
```sh
81+
python main.py
82+
```
5183

52-
4. Run `python main.py`
53-
5. Visit [the Reddit Apps page.](https://www.reddit.com/prefs/apps), and set up an app that is a "script". Paste any URL in redirect URL. Ex:`https://jasoncameron.dev`
54-
6. The bot will ask you to fill in your details to connect to the Reddit API, and configure the bot to your liking
55-
7. Enjoy 😎
56-
8. If you need to reconfigure the bot, simply open the `config.toml` file and delete the lines that need to be changed. On the next run of the bot, it will help you reconfigure those options.
84+
6. Visit [the Reddit Apps page](https://www.reddit.com/prefs/apps), and set up an app that is a "script". Paste any URL in the redirect URL field, for example: `https://jasoncameron.dev`.
5785

58-
(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)
86+
7. The bot will prompt you to fill in your details to connect to the Reddit API and configure the bot to your liking.
5987

60-
If you want to read more detailed guide about the bot, please refer to the [documentation](https://reddit-video-maker-bot.netlify.app/)
88+
8. Enjoy 😎
89+
90+
9. If you need to reconfigure the bot, simply open the `config.toml` file and delete the lines that need to be changed. On the next run of the bot, it will help you reconfigure those options.
91+
92+
(Note: If you encounter any errors installing or running the bot, try using `python3` or `pip3` instead of `python` or `pip`.)
93+
94+
For a more detailed guide about the bot, please refer to the [documentation](https://reddit-video-maker-bot.netlify.app/).
6195

6296
## Video
6397

TTS/GTTS.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def __init__(self):
1010
self.max_chars = 5000
1111
self.voices = []
1212

13-
def run(self, text, filepath):
13+
def run(self, text, filepath, random_voice: bool = False):
1414
tts = gTTS(
1515
text=text,
1616
lang=settings.config["reddit"]["thread"]["post_lang"] or "en",

TTS/elevenlabs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,4 @@ def initialize(self):
3535
def randomvoice(self):
3636
if self.client is None:
3737
self.initialize()
38-
return random.choice(self.client.voices.get_all().voices).voice_name
38+
return random.choice(self.client.voices.get_all().voices).name

TTS/engine_wrapper.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
import numpy as np
77
import translators
8+
from moviepy import AudioFileClip
89
from moviepy.audio.AudioClip import AudioClip
9-
from moviepy.audio.fx.volumex import volumex
10-
from moviepy.editor import AudioFileClip
10+
from moviepy.audio.fx import MultiplyVolume
1111
from rich.progress import track
1212

1313
from utils import settings
@@ -112,7 +112,6 @@ def split_post(self, text: str, idx):
112112
]
113113
self.create_silence_mp3()
114114

115-
idy = None
116115
for idy, text_cut in enumerate(split_text):
117116
newtext = process_text(text_cut)
118117
# print(f"{idx}-{idy}: {newtext}\n")
@@ -144,11 +143,18 @@ def split_post(self, text: str, idx):
144143
print("OSError")
145144

146145
def call_tts(self, filename: str, text: str):
147-
self.tts_module.run(
148-
text,
149-
filepath=f"{self.path}/{filename}.mp3",
150-
random_voice=settings.config["settings"]["tts"]["random_voice"],
151-
)
146+
if settings.config["settings"]["tts"]["voice_choice"] == "googletranslate":
147+
# GTTS does not have the argument 'random_voice'
148+
self.tts_module.run(
149+
text,
150+
filepath=f"{self.path}/{filename}.mp3",
151+
)
152+
else:
153+
self.tts_module.run(
154+
text,
155+
filepath=f"{self.path}/{filename}.mp3",
156+
random_voice=settings.config["settings"]["tts"]["random_voice"],
157+
)
152158
# try:
153159
# self.length += MP3(f"{self.path}/{filename}.mp3").info.length
154160
# except (MutagenError, HeaderNotFoundError):
@@ -164,12 +170,12 @@ def call_tts(self, filename: str, text: str):
164170
def create_silence_mp3(self):
165171
silence_duration = settings.config["settings"]["tts"]["silence_duration"]
166172
silence = AudioClip(
167-
make_frame=lambda t: np.sin(440 * 2 * np.pi * t),
173+
frame_function=lambda t: np.sin(440 * 2 * np.pi * t),
168174
duration=silence_duration,
169175
fps=44100,
170176
)
171-
silence = volumex(silence, 0)
172-
silence.write_audiofile(f"{self.path}/silence.mp3", fps=44100, verbose=False, logger=None)
177+
silence = silence.with_effects([MultiplyVolume(0)])
178+
silence.write_audiofile(f"{self.path}/silence.mp3", fps=44100, logger=None)
173179

174180

175181
def process_text(text: str, clean: bool = True):

TTS/openai_tts.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import random
2+
3+
import requests
4+
5+
from utils import settings
6+
7+
8+
class OpenAITTS:
9+
"""
10+
A Text-to-Speech engine that uses an OpenAI-like TTS API endpoint to generate audio from text.
11+
12+
Attributes:
13+
max_chars (int): Maximum number of characters allowed per API call.
14+
api_key (str): API key loaded from settings.
15+
api_url (str): The complete API endpoint URL, built from a base URL provided in the config.
16+
available_voices (list): Static list of supported voices (according to current docs).
17+
"""
18+
19+
def __init__(self):
20+
# Set maximum input size based on API limits (4096 characters per request)
21+
self.max_chars = 4096
22+
self.api_key = settings.config["settings"]["tts"].get("openai_api_key")
23+
if not self.api_key:
24+
raise ValueError(
25+
"No OpenAI API key provided in settings! Please set 'openai_api_key' in your config."
26+
)
27+
28+
# Read the base URL from the configuration (e.g., "https://api.openai.com/v1" or "https://api.openai.com/v1/")
29+
base_url = settings.config["settings"]["tts"].get(
30+
"openai_api_url", "https://api.openai.com/v1"
31+
)
32+
# Remove trailing slash if present
33+
if base_url.endswith("/"):
34+
base_url = base_url[:-1]
35+
# Append the TTS-specific path
36+
self.api_url = base_url + "/audio/speech"
37+
38+
# Set the available voices to a static list as per OpenAI TTS documentation.
39+
self.available_voices = self.get_available_voices()
40+
41+
def get_available_voices(self):
42+
"""
43+
Return a static list of supported voices for the OpenAI TTS API.
44+
45+
According to the documentation, supported voices include:
46+
"alloy", "ash", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer"
47+
"""
48+
return ["alloy", "ash", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer"]
49+
50+
def randomvoice(self):
51+
"""
52+
Select and return a random voice from the available voices.
53+
"""
54+
return random.choice(self.available_voices)
55+
56+
def run(self, text, filepath, random_voice: bool = False):
57+
"""
58+
Convert the provided text to speech and save the resulting audio to the specified filepath.
59+
60+
Args:
61+
text (str): The input text to convert.
62+
filepath (str): The file path where the generated audio will be saved.
63+
random_voice (bool): If True, select a random voice from the available voices.
64+
"""
65+
# Choose voice based on configuration or randomly if requested.
66+
if random_voice:
67+
voice = self.randomvoice()
68+
else:
69+
voice = settings.config["settings"]["tts"].get("openai_voice_name", "alloy")
70+
voice = str(voice).lower() # Ensure lower-case as expected by the API
71+
72+
# Select the model from configuration; default to 'tts-1'
73+
model = settings.config["settings"]["tts"].get("openai_model", "tts-1")
74+
75+
# Create payload for API request
76+
payload = {
77+
"model": model,
78+
"voice": voice,
79+
"input": text,
80+
"response_format": "mp3", # allowed formats: "mp3", "aac", "opus", "flac", "pcm" or "wav"
81+
}
82+
headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
83+
try:
84+
response = requests.post(self.api_url, headers=headers, json=payload)
85+
if response.status_code != 200:
86+
raise RuntimeError(f"Error from TTS API: {response.status_code} {response.text}")
87+
# Write response as binary into file.
88+
with open(filepath, "wb") as f:
89+
f.write(response.content)
90+
except Exception as e:
91+
raise RuntimeError(f"Failed to generate audio with OpenAI TTS API: {str(e)}")

main.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from os import name
55
from pathlib import Path
66
from subprocess import Popen
7-
from typing import NoReturn
7+
from typing import Dict, NoReturn
88

99
from prawcore import ResponseException
1010

@@ -13,7 +13,7 @@
1313
from utils.cleanup import cleanup
1414
from utils.console import print_markdown, print_step, print_substep
1515
from utils.ffmpeg_install import ffmpeg_install
16-
from utils.id import id
16+
from utils.id import extract_id
1717
from utils.version import checkversion
1818
from video_creation.background import (
1919
chop_background,
@@ -25,7 +25,7 @@
2525
from video_creation.screenshot_downloader import get_screenshots_of_reddit_posts
2626
from video_creation.voices import save_text_to_mp3
2727

28-
__VERSION__ = "3.3.0"
28+
__VERSION__ = "3.4.0"
2929

3030
print(
3131
"""
@@ -42,11 +42,15 @@
4242
)
4343
checkversion(__VERSION__)
4444

45+
reddit_id: str
46+
reddit_object: Dict[str, str | list]
47+
4548

4649
def main(POST_ID=None) -> None:
47-
global redditid, reddit_object
50+
global reddit_id, reddit_object
4851
reddit_object = get_subreddit_threads(POST_ID)
49-
redditid = id(reddit_object)
52+
reddit_id = extract_id(reddit_object)
53+
print_substep(f"Thread ID is {reddit_id}", style="bold blue")
5054
length, number_of_comments = save_text_to_mp3(reddit_object)
5155
length = math.ceil(length)
5256
get_screenshots_of_reddit_posts(reddit_object, number_of_comments)
@@ -64,22 +68,22 @@ def run_many(times) -> None:
6468
for x in range(1, times + 1):
6569
print_step(
6670
f'on the {x}{("th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th")[x % 10]} iteration of {times}'
67-
) # correct 1st 2nd 3rd 4th 5th....
71+
)
6872
main()
6973
Popen("cls" if name == "nt" else "clear", shell=True).wait()
7074

7175

7276
def shutdown() -> NoReturn:
73-
if "redditid" in globals():
77+
if "reddit_id" in globals():
7478
print_markdown("## Clearing temp files")
75-
cleanup(redditid)
79+
cleanup(reddit_id)
7680

7781
print("Exiting...")
7882
sys.exit()
7983

8084

8185
if __name__ == "__main__":
82-
if sys.version_info.major != 3 or sys.version_info.minor not in [10, 11]:
86+
if sys.version_info.major != 3 or sys.version_info.minor not in [10, 11, 12]:
8387
print(
8488
"Hey! Congratulations, you've made it so far (which is pretty rare with no Python 3.10). Unfortunately, this program only works on Python 3.10. Please install Python 3.10 and try again."
8589
)
@@ -122,6 +126,7 @@ def shutdown() -> NoReturn:
122126
except Exception as err:
123127
config["settings"]["tts"]["tiktok_sessionid"] = "REDACTED"
124128
config["settings"]["tts"]["elevenlabs_api_key"] = "REDACTED"
129+
config["settings"]["tts"]["openai_api_key"] = "REDACTED"
125130
print_step(
126131
f"Sorry, something went wrong with this version! Try again, and feel free to report this issue at GitHub or the Discord community.\n"
127132
f"Version: {__VERSION__} \n"

requirements.txt

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
1-
boto3==1.34.127
2-
botocore==1.34.127
3-
gTTS==2.5.1
4-
moviepy==1.0.3
5-
playwright==1.44.0
6-
praw==7.7.1
7-
prawcore~=2.3.0
1+
boto3==1.36.8
2+
botocore==1.36.8
3+
gTTS==2.5.4
4+
moviepy==2.2.1
5+
playwright==1.49.1
6+
praw==7.8.1
87
requests==2.32.3
9-
rich==13.7.1
8+
rich==13.9.4
109
toml==0.10.2
11-
translators==5.9.2
12-
pyttsx3==2.90
13-
Pillow==10.3.0
14-
tomlkit==0.12.5
15-
Flask==3.0.3
10+
translators==5.9.9
11+
pyttsx3==2.98
12+
tomlkit==0.13.2
13+
Flask==3.1.1
1614
clean-text==0.6.0
17-
unidecode==1.3.8
18-
spacy==3.7.5
19-
torch==2.3.1
20-
transformers==4.41.2
15+
unidecode==1.4.0
16+
spacy==3.8.7
17+
torch==2.7.0
18+
transformers==4.52.4
2119
ffmpeg-python==0.2.0
22-
elevenlabs==1.3.0
23-
yt-dlp==2024.5.27
24-
numpy==1.26.4
20+
elevenlabs==1.57.0
21+
yt-dlp==2025.10.22

utils/.config.template.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ background_thumbnail_font_size = { optional = true, type = "int", default = 96,
4444
background_thumbnail_font_color = { optional = true, default = "255,255,255", example = "255,255,255", explanation = "Font color in RGB format for the thumbnail text" }
4545

4646
[settings.tts]
47-
voice_choice = { optional = false, default = "tiktok", options = ["elevenlabs", "streamlabspolly", "tiktok", "googletranslate", "awspolly", "pyttsx", ], example = "tiktok", explanation = "The voice platform used for TTS generation. " }
47+
voice_choice = { optional = false, default = "tiktok", options = ["elevenlabs", "streamlabspolly", "tiktok", "googletranslate", "awspolly", "pyttsx", "OpenAI"], example = "tiktok", explanation = "The voice platform used for TTS generation. " }
4848
random_voice = { optional = false, type = "bool", default = true, example = true, options = [true, false,], explanation = "Randomizes the voice used for each comment" }
4949
elevenlabs_voice_name = { optional = false, default = "Bella", example = "Bella", explanation = "The voice used for elevenlabs", options = ["Adam", "Antoni", "Arnold", "Bella", "Domi", "Elli", "Josh", "Rachel", "Sam", ] }
5050
elevenlabs_api_key = { optional = true, example = "21f13f91f54d741e2ae27d2ab1b99d59", explanation = "Elevenlabs API key" }
@@ -56,3 +56,7 @@ python_voice = { optional = false, default = "1", example = "1", explanation = "
5656
py_voice_num = { optional = false, default = "2", example = "2", explanation = "The number of system voices (2 are pre-installed in Windows)" }
5757
silence_duration = { optional = true, example = "0.1", explanation = "Time in seconds between TTS comments", default = 0.3, type = "float" }
5858
no_emojis = { optional = false, type = "bool", default = false, example = false, options = [true, false,], explanation = "Whether to remove emojis from the comments" }
59+
openai_api_url = { optional = true, default = "https://api.openai.com/v1/", example = "https://api.openai.com/v1/", explanation = "The API endpoint URL for OpenAI TTS generation" }
60+
openai_api_key = { optional = true, example = "sk-abc123def456...", explanation = "Your OpenAI API key for TTS generation" }
61+
openai_voice_name = { optional = false, default = "alloy", example = "alloy", explanation = "The voice used for OpenAI TTS generation", options = ["alloy", "ash", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "af_heart"] }
62+
openai_model = { optional = false, default = "tts-1", example = "tts-1", explanation = "The model variant used for OpenAI TTS generation", options = ["tts-1", "tts-1-hd", "gpt-4o-mini-tts"] }

0 commit comments

Comments
 (0)