pull/1607/head
Simon 2 years ago
parent 868fda11f4
commit cab5359e08

@ -16,6 +16,7 @@ voices = [
"Sam",
]
class elevenlabs:
def __init__(self):
self.max_chars = 2500
@ -33,19 +34,13 @@ class elevenlabs:
api_key = settings.config["settings"]["tts"]["elevenlabs_api_key"]
else:
raise ValueError(
"You didn't set an Elevenlabs API key! Please set the config variable ELEVENLABS_API_KEY to a valid API key."
)
"You didn't set an Elevenlabs API key! Please set the config variable ELEVENLABS_API_KEY to a valid API key."
)
audio = generate(
api_key=api_key,
text=text,
voice=voice,
model="eleven_multilingual_v1"
)
save(
audio=audio,
filename=filepath
api_key=api_key, text=text, voice=voice, model="eleven_multilingual_v1"
)
save(audio=audio, filename=filepath)
def randomvoice(self):
return random.choice(self.voices)

@ -56,8 +56,10 @@ class TTSEngine:
regex_urls = r"((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*"
comment["comment_body"] = re.sub(regex_urls, " ", comment["comment_body"])
comment["comment_body"] = comment["comment_body"].replace("\n", ". ")
comment["comment_body"] = re.sub(r'\bAI\b', 'A.I', comment["comment_body"])
comment["comment_body"] = re.sub(r'\bAGI\b', 'A.G.I', comment["comment_body"])
comment["comment_body"] = re.sub(r"\bAI\b", "A.I", comment["comment_body"])
comment["comment_body"] = re.sub(
r"\bAGI\b", "A.G.I", comment["comment_body"]
)
if comment["comment_body"][-1] != ".":
comment["comment_body"] += "."
comment["comment_body"] = comment["comment_body"].replace(". . .", ".")
@ -147,7 +149,11 @@ class TTSEngine:
print("OSError")
def call_tts(self, filename: str, text: str):
self.tts_module.run(text, filepath=f"{self.path}/{filename}.mp3", random_voice=settings.config["settings"]["tts"]["random_voice"])
self.tts_module.run(
text,
filepath=f"{self.path}/{filename}.mp3",
random_voice=settings.config["settings"]["tts"]["random_voice"],
)
# try:
# self.length += MP3(f"{self.path}/{filename}.mp3").info.length
# except (MutagenError, HeaderNotFoundError):

@ -91,7 +91,8 @@ def shutdown():
if __name__ == "__main__":
if sys.version_info.major != 3 or sys.version_info.minor != 10:
print(
"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.")
"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."
)
exit()
ffmpeg_install() # install ffmpeg if not installed
directory = Path().absolute()
@ -100,8 +101,8 @@ if __name__ == "__main__":
)
config is False and exit()
if (
not settings.config["settings"]["tts"]["tiktok_sessionid"]
or settings.config["settings"]["tts"]["tiktok_sessionid"] == ""
not settings.config["settings"]["tts"]["tiktok_sessionid"]
or settings.config["settings"]["tts"]["tiktok_sessionid"] == ""
) and config["settings"]["tts"]["voice_choice"] == "tiktok":
print_substep(
"TikTok voice requires a sessionid! Check our documentation on how to obtain one.",
@ -111,7 +112,7 @@ if __name__ == "__main__":
try:
if config["reddit"]["thread"]["post_id"]:
for index, post_id in enumerate(
config["reddit"]["thread"]["post_id"].split("+")
config["reddit"]["thread"]["post_id"].split("+")
):
index += 1
print_step(

@ -107,7 +107,10 @@ def get_subreddit_threads(POST_ID: str):
if submission is None:
return get_subreddit_threads(POST_ID) # submission already done. rerun
elif not submission.num_comments and settings.config["settings"]["storymode"] == "false":
elif (
not submission.num_comments
and settings.config["settings"]["storymode"] == "false"
):
print_substep("No comments found. Skipping.")
exit()

@ -23,22 +23,36 @@ def ffmpeg_install_windows():
os.remove(f"ffmpeg/doc/{file}")
os.rmdir("ffmpeg/doc")
# Add to the path
subprocess.run("setx PATH \"%PATH%;%CD%\\ffmpeg\"", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print("FFmpeg installed successfully! Please restart your computer and then re-run the program.")
subprocess.run(
'setx PATH "%PATH%;%CD%\\ffmpeg"',
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
print(
"FFmpeg installed successfully! Please restart your computer and then re-run the program."
)
exit()
except Exception as e:
print(
"An error occurred while trying to install FFmpeg. Please try again. Otherwise, please install FFmpeg manually and try again.")
"An error occurred while trying to install FFmpeg. Please try again. Otherwise, please install FFmpeg manually and try again."
)
print(e)
exit()
def ffmpeg_install_linux():
try:
subprocess.run("sudo apt install ffmpeg", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run(
"sudo apt install ffmpeg",
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except Exception as e:
print(
"An error occurred while trying to install FFmpeg. Please try again. Otherwise, please install FFmpeg manually and try again.")
"An error occurred while trying to install FFmpeg. Please try again. Otherwise, please install FFmpeg manually and try again."
)
print(e)
exit()
print("FFmpeg installed successfully! Please re-run the program.")
@ -47,10 +61,16 @@ def ffmpeg_install_linux():
def ffmpeg_install_mac():
try:
subprocess.run("brew install ffmpeg", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run(
"brew install ffmpeg",
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except FileNotFoundError:
print(
"Homebrew is not installed. Please install it and try again. Otherwise, please install FFmpeg manually and try again.")
"Homebrew is not installed. Please install it and try again. Otherwise, please install FFmpeg manually and try again."
)
exit()
print("FFmpeg installed successfully! Please re-run the program.")
exit()
@ -59,11 +79,20 @@ def ffmpeg_install_mac():
def ffmpeg_install():
try:
# Try to run the FFmpeg command
subprocess.run(['ffmpeg', '-version'], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print('FFmpeg is installed on this system! If you are seeing this error for the second time, restart your computer.')
subprocess.run(
["ffmpeg", "-version"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
print(
"FFmpeg is installed on this system! If you are seeing this error for the second time, restart your computer."
)
except FileNotFoundError as e:
print('FFmpeg is not installed on this system.')
resp = input("We can try to automatically install it for you. Would you like to do that? (y/n): ")
print("FFmpeg is not installed on this system.")
resp = input(
"We can try to automatically install it for you. Would you like to do that? (y/n): "
)
if resp.lower() == "y":
print("Installing FFmpeg...")
if os.name == "nt":
@ -73,12 +102,16 @@ def ffmpeg_install():
elif os.name == "mac":
ffmpeg_install_mac()
else:
print("Your OS is not supported. Please install FFmpeg manually and try again.")
print(
"Your OS is not supported. Please install FFmpeg manually and try again."
)
exit()
else:
print("Please install FFmpeg manually and try again.")
exit()
except Exception as e:
print("Welcome fellow traveler! You're one of the few who have made it this far. We have no idea how you got at this error, but we're glad you're here. Please report this error to the developer, and we'll try to fix it as soon as possible. Thank you for your patience!")
print(
"Welcome fellow traveler! You're one of the few who have made it this far. We have no idea how you got at this error, but we're glad you're here. Please report this error to the developer, and we'll try to fix it as soon as possible. Thank you for your patience!"
)
print(e)
return None

@ -57,9 +57,7 @@ def imagemaker(theme, reddit_obj: dict, txtclr, padding=5, transparent=False) ->
"""
Render Images for video
"""
title = process_text(
reddit_obj["thread_title"], False
)
title = process_text(reddit_obj["thread_title"], False)
texts = reddit_obj["thread_post"]
id = re.sub(r"[^\w\s-]", "", reddit_obj["thread_id"])

@ -59,12 +59,14 @@ def get_subreddit_undone(
continue
if settings.config["settings"]["storymode"]:
if not submission.selftext:
print_substep("You are trying to use story mode on post with no post text")
print_substep(
"You are trying to use story mode on post with no post text"
)
continue
else:
# Check for the length of the post text
if len(submission.selftext) > (
settings.config["settings"]["storymode_max_length"] or 2000
settings.config["settings"]["storymode_max_length"] or 2000
):
print_substep(
f"Post is too long ({len(submission.selftext)}), try with a different post. ({settings.config['settings']['storymode_max_length']} character limit)"

@ -3,14 +3,15 @@ import random
import re
from pathlib import Path
from random import randrange
from typing import Any, Tuple,Dict
from typing import Any, Tuple, Dict
from moviepy.editor import VideoFileClip,AudioFileClip
from moviepy.editor import VideoFileClip, AudioFileClip
from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
from utils import settings
from utils.console import print_step, print_substep
import yt_dlp
def load_background_options():
background_options = {}
# Load background videos
@ -20,11 +21,11 @@ def load_background_options():
# Load background audios
with open("./utils/background_audios.json") as json_file:
background_options["audio"] = json.load(json_file)
# Remove "__comment" from backgrounds
del background_options["video"]["__comment"]
del background_options["audio"]["__comment"]
# Add position lambda function
# (https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html#moviepy.video.VideoClip.VideoClip.set_position)
for name in list(background_options["video"].keys()):
@ -36,7 +37,6 @@ def load_background_options():
return background_options
def get_start_and_end_times(video_length: int, length_of_clip: int) -> Tuple[int, int]:
"""Generates a random interval of time to be used as the background of the video.
@ -49,11 +49,11 @@ def get_start_and_end_times(video_length: int, length_of_clip: int) -> Tuple[int
"""
initialValue = 180
# Issue #1649 - Ensures that will be a valid interval in the video
while(int(length_of_clip) <= int(video_length+initialValue)):
if(initialValue == initialValue //2):
while int(length_of_clip) <= int(video_length + initialValue):
if initialValue == initialValue // 2:
raise Exception("Your background is too short for this video length")
else:
initialValue //= 2 #Divides the initial value by 2 until reach 0
initialValue //= 2 # Divides the initial value by 2 until reach 0
random_time = randrange(initialValue, int(length_of_clip) - int(video_length))
return random_time, random_time + video_length
@ -75,6 +75,7 @@ def get_background_config(mode: str):
return background_options[mode][choice]
def download_background_video(background_config: Tuple[str, str, str, Any]):
"""Downloads the background/s video from YouTube."""
Path("./assets/backgrounds/video/").mkdir(parents=True, exist_ok=True)
@ -88,7 +89,7 @@ def download_background_video(background_config: Tuple[str, str, str, Any]):
print_substep("Downloading the backgrounds videos... please be patient 🙏 ")
print_substep(f"Downloading {filename} from {uri}")
ydl_opts = {
'format': "bestvideo[height<=1080][ext=mp4]",
"format": "bestvideo[height<=1080][ext=mp4]",
"outtmpl": f"assets/backgrounds/video/{credit}-{filename}",
"retries": 10,
}
@ -97,6 +98,7 @@ def download_background_video(background_config: Tuple[str, str, str, Any]):
ydl.download(uri)
print_substep("Background video downloaded successfully! 🎉", style="bold green")
def download_background_audio(background_config: Tuple[str, str, str]):
"""Downloads the background/s audio from YouTube."""
Path("./assets/backgrounds/audio/").mkdir(parents=True, exist_ok=True)
@ -110,9 +112,9 @@ def download_background_audio(background_config: Tuple[str, str, str]):
print_substep("Downloading the backgrounds audio... please be patient 🙏 ")
print_substep(f"Downloading {filename} from {uri}")
ydl_opts = {
'outtmpl': f'./assets/backgrounds/audio/{credit}-{filename}',
'format': 'bestaudio/best',
'extract_audio': True,
"outtmpl": f"./assets/backgrounds/audio/{credit}-{filename}",
"format": "bestaudio/best",
"extract_audio": True,
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
@ -121,9 +123,8 @@ def download_background_audio(background_config: Tuple[str, str, str]):
print_substep("Background audio downloaded successfully! 🎉", style="bold green")
def chop_background(
background_config: Dict[str,Tuple], video_length: int, reddit_object: dict
background_config: Dict[str, Tuple], video_length: int, reddit_object: dict
):
"""Generates the background audio and footage to be used in the video and writes it to assets/temp/background.mp3 and assets/temp/background.mp4
@ -133,20 +134,26 @@ def chop_background(
"""
id = re.sub(r"[^\w\s-]", "", reddit_object["thread_id"])
if(settings.config["settings"]["background"][f"background_audio_volume"] == 0):
if settings.config["settings"]["background"][f"background_audio_volume"] == 0:
print_step("Volume was set to 0. Skipping background audio creation . . .")
else:
print_step("Finding a spot in the backgrounds audio to chop...✂️")
audio_choice = f"{background_config['audio'][2]}-{background_config['audio'][1]}"
audio_choice = (
f"{background_config['audio'][2]}-{background_config['audio'][1]}"
)
background_audio = AudioFileClip(f"assets/backgrounds/audio/{audio_choice}")
start_time_audio, end_time_audio = get_start_and_end_times(video_length, background_audio.duration)
background_audio = background_audio.subclip(start_time_audio,end_time_audio)
start_time_audio, end_time_audio = get_start_and_end_times(
video_length, background_audio.duration
)
background_audio = background_audio.subclip(start_time_audio, end_time_audio)
background_audio.write_audiofile(f"assets/temp/{id}/background.mp3")
print_step("Finding a spot in the backgrounds video to chop...✂️")
video_choice = f"{background_config['video'][2]}-{background_config['video'][1]}"
background_video = VideoFileClip(f"assets/backgrounds/video/{video_choice}")
start_time_video, end_time_video = get_start_and_end_times(video_length, background_video.duration)
start_time_video, end_time_video = get_start_and_end_times(
video_length, background_video.duration
)
# Extract video subclip
try:
ffmpeg_extract_subclip(
@ -163,5 +170,6 @@ def chop_background(
print_substep("Background video chopped successfully!", style="bold green")
return background_config["video"][2]
# Create a tuple for downloads background (background_audio_options, background_video_options)
background_options = load_background_options()

@ -1,7 +1,7 @@
import multiprocessing
import os
import re
from os.path import exists # Needs to be imported specifically
from os.path import exists # Needs to be imported specifically
from typing import Final
from typing import Tuple, Any, Dict
@ -109,28 +109,27 @@ def merge_background_audio(audio: ffmpeg, reddit_id: str):
audio (ffmpeg): The TTS final audio but without background.
reddit_id (str): The ID of subreddit
"""
background_audio_volume = settings.config["settings"]["background"]["background_audio_volume"]
if (background_audio_volume == 0):
return audio # Return the original audio
background_audio_volume = settings.config["settings"]["background"][
"background_audio_volume"
]
if background_audio_volume == 0:
return audio # Return the original audio
else:
# sets volume to config
bg_audio = (
ffmpeg.input(f"assets/temp/{reddit_id}/background.mp3")
.filter(
"volume",
background_audio_volume,
)
bg_audio = ffmpeg.input(f"assets/temp/{reddit_id}/background.mp3").filter(
"volume",
background_audio_volume,
)
# Merges audio and background_audio
merged_audio = ffmpeg.filter([audio, bg_audio], "amix", duration="longest")
return merged_audio # Return merged audio
return merged_audio # Return merged audio
def make_final_video(
number_of_clips: int,
length: int,
reddit_obj: dict,
background_config: Dict[str,Tuple],
background_config: Dict[str, Tuple],
):
"""Gathers audio clips, gathers all screenshots, stitches them together and saves the final video to assets/temp
Args:
@ -147,8 +146,10 @@ def make_final_video(
reddit_id = re.sub(r"[^\w\s-]", "", reddit_obj["thread_id"])
allowOnlyTTSFolder: bool = settings.config["settings"]["background"]["enable_extra_audio"] \
and settings.config["settings"]["background"]["background_audio_volume"] != 0
allowOnlyTTSFolder: bool = (
settings.config["settings"]["background"]["enable_extra_audio"]
and settings.config["settings"]["background"]["background_audio_volume"] != 0
)
print_step("Creating the final video 🎥")
@ -157,7 +158,9 @@ def make_final_video(
# Gather all audio clips
audio_clips = list()
if number_of_clips == 0 and settings.config["settings"]["storymode"] == "false":
print("No audio clips to gather. Please use a different TTS or post.") # This is to fix the TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
print(
"No audio clips to gather. Please use a different TTS or post."
) # This is to fix the TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
exit()
if settings.config["settings"]["storymode"]:
if settings.config["settings"]["storymodemethod"] == 0:
@ -208,7 +211,7 @@ def make_final_video(
screenshot_width = int((W * 45) // 100)
audio = ffmpeg.input(f"assets/temp/{reddit_id}/audio.mp3")
final_audio = merge_background_audio(audio,reddit_id)
final_audio = merge_background_audio(audio, reddit_id)
image_clips = list()
@ -291,11 +294,15 @@ def make_final_video(
subreddit = settings.config["reddit"]["thread"]["subreddit"]
if not exists(f"./results/{subreddit}"):
print_substep("The 'results' folder could not be found so it was automatically created.")
print_substep(
"The 'results' folder could not be found so it was automatically created."
)
os.makedirs(f"./results/{subreddit}")
if not exists(f"./results/{subreddit}/OnlyTTS") and allowOnlyTTSFolder:
print_substep("The 'OnlyTTS' folder could not be found so it was automatically created.")
print_substep(
"The 'OnlyTTS' folder could not be found so it was automatically created."
)
os.makedirs(f"./results/{subreddit}/OnlyTTS")
# create a thumbnail for the video
@ -303,7 +310,9 @@ def make_final_video(
if settingsbackground["background_thumbnail"]:
if not exists(f"./results/{subreddit}/thumbnails"):
print_substep("The 'results/thumbnails' folder could not be found so it was automatically created.")
print_substep(
"The 'results/thumbnails' folder could not be found so it was automatically created."
)
os.makedirs(f"./results/{subreddit}/thumbnails")
# get the first file with the .png extension from assets/backgrounds and use it as a background for the thumbnail
first_image = next(
@ -361,11 +370,13 @@ def make_final_video(
defaultPath = f"results/{subreddit}"
with ProgressFfmpeg(length, on_update_example) as progress:
path = defaultPath + f"/{filename}"
path = path[:251] + ".mp4" #Prevent a error by limiting the path length, do not change this.
path = (
path[:251] + ".mp4"
) # Prevent a error by limiting the path length, do not change this.
ffmpeg.output(
background_clip,
final_audio,
path,
path,
f="mp4",
**{
"c:v": "h264",
@ -383,7 +394,9 @@ def make_final_video(
pbar.update(100 - old_percentage)
if allowOnlyTTSFolder:
path = defaultPath + f"/OnlyTTS/{filename}"
path = path[:251] + ".mp4" # Prevent a error by limiting the path length, do not change this.
path = (
path[:251] + ".mp4"
) # Prevent a error by limiting the path length, do not change this.
print_step("Rendering the Only TTS Video 🎥")
with ProgressFfmpeg(length, on_update_example) as progress:
ffmpeg.output(
@ -397,7 +410,9 @@ def make_final_video(
"b:a": "192k",
"threads": multiprocessing.cpu_count(),
},
).overwrite_output().global_args("-progress", progress.output_file.name).run(
).overwrite_output().global_args(
"-progress", progress.output_file.name
).run(
quiet=True,
overwrite_output=True,
capture_stdout=False,
@ -406,7 +421,7 @@ def make_final_video(
old_percentage = pbar.n
pbar.update(100 - old_percentage)
pbar.close()
save_data(subreddit, filename + ".mp4", title, idx, background_config['video'][2])
save_data(subreddit, filename + ".mp4", title, idx, background_config["video"][2])
print_step("Removing temporary files 🗑")
cleanups = cleanup(reddit_id)
print_substep(f"Removed {cleanups} temporary files 🗑")

@ -116,7 +116,7 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
)
page.locator("button[class$='m-full-width']").click()
page.wait_for_timeout(5000)
login_error_div = page.locator(".AnimatedForm__errorMessage").first
if login_error_div.is_visible():
login_error_message = login_error_div.inner_text()
@ -125,7 +125,10 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
pass
else:
# The div contains an error message
print_substep("Your reddit credentials are incorrect! Please modify them accordingly in the config.toml file.", style="red")
print_substep(
"Your reddit credentials are incorrect! Please modify them accordingly in the config.toml file.",
style="red",
)
exit()
else:
pass
@ -183,11 +186,11 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
# store zoom settings
zoom = settings.config["settings"]["zoom"]
# zoom the body of the page
page.evaluate("document.body.style.zoom="+str(zoom))
page.evaluate("document.body.style.zoom=" + str(zoom))
# as zooming the body doesn't change the properties of the divs, we need to adjust for the zoom
location = page.locator('[data-test-id="post-content"]').bounding_box()
for i in location:
location[i] = float("{:.2f}".format(location[i]*zoom))
location[i] = float("{:.2f}".format(location[i] * zoom))
page.screenshot(clip=location, path=postcontentpath)
else:
page.locator('[data-test-id="post-content"]').screenshot(
@ -250,14 +253,21 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
# store zoom settings
zoom = settings.config["settings"]["zoom"]
# zoom the body of the page
page.evaluate("document.body.style.zoom="+str(zoom))
page.evaluate("document.body.style.zoom=" + str(zoom))
# scroll comment into view
page.locator(f"#t1_{comment['comment_id']}").scroll_into_view_if_needed()
page.locator(
f"#t1_{comment['comment_id']}"
).scroll_into_view_if_needed()
# as zooming the body doesn't change the properties of the divs, we need to adjust for the zoom
location = page.locator(f"#t1_{comment['comment_id']}").bounding_box()
location = page.locator(
f"#t1_{comment['comment_id']}"
).bounding_box()
for i in location:
location[i] = float("{:.2f}".format(location[i]*zoom))
page.screenshot(clip=location, path=f"assets/temp/{reddit_id}/png/comment_{idx}.png")
location[i] = float("{:.2f}".format(location[i] * zoom))
page.screenshot(
clip=location,
path=f"assets/temp/{reddit_id}/png/comment_{idx}.png",
)
else:
page.locator(f"#t1_{comment['comment_id']}").screenshot(
path=f"assets/temp/{reddit_id}/png/comment_{idx}.png"

@ -20,7 +20,7 @@ TTSProviders = {
"StreamlabsPolly": StreamlabsPolly,
"TikTok": TikTok,
"pyttsx": pyttsx,
"ElevenLabs": elevenlabs
"ElevenLabs": elevenlabs,
}

Loading…
Cancel
Save