Fmt using black & isort

pull/2060/head
Jason 5 months ago
parent fe383a794c
commit 9e60d83580

@ -3,14 +3,8 @@ from pathlib import Path
# Used "tomlkit" instead of "toml" because it doesn't change formatting on "dump"
import tomlkit
from flask import (
Flask,
redirect,
render_template,
request,
send_from_directory,
url_for,
)
from flask import (Flask, redirect, render_template, request,
send_from_directory, url_for)
import utils.gui_utils as gui
@ -82,7 +76,9 @@ def settings():
# Change settings
config = gui.modify_settings(data, config_load, checks)
return render_template("settings.html", file="config.toml", data=config, checks=checks)
return render_template(
"settings.html", file="config.toml", data=config, checks=checks
)
# Make videos.json accessible

@ -2,7 +2,7 @@
import base64
import random
import time
from typing import Optional, Final
from typing import Final, Optional
import requests
@ -86,7 +86,9 @@ class TikTok:
"Cookie": f"sessionid={settings.config['settings']['tts']['tiktok_sessionid']}",
}
self.URI_BASE = "https://api16-normal-c-useast1a.tiktokv.com/media/api/text/speech/invoke/"
self.URI_BASE = (
"https://api16-normal-c-useast1a.tiktokv.com/media/api/text/speech/invoke/"
)
self.max_chars = 200
self._session = requests.Session()

@ -41,7 +41,9 @@ class AWSPolly:
raise ValueError(
f"Please set the TOML variable AWS_VOICE to a valid voice. options are: {voices}"
)
voice = str(settings.config["settings"]["tts"]["aws_polly_voice"]).capitalize()
voice = str(
settings.config["settings"]["tts"]["aws_polly_voice"]
).capitalize()
try:
# Request speech synthesis
response = polly.synthesize_speech(

@ -7,32 +7,36 @@ from utils import settings
class elevenlabs:
def __init__(self):
self.max_chars = 2500
self.client: ElevenLabs = None
def run(self, text, filepath, random_voice: bool = False):
if self.client is None:
self.initialize()
if random_voice:
voice = self.randomvoice()
else:
voice = str(settings.config["settings"]["tts"]["elevenlabs_voice_name"]).capitalize()
audio = self.client.generate(text=text, voice=voice, model="eleven_multilingual_v1")
save(audio=audio, filename=filepath)
def initialize(self):
if settings.config["settings"]["tts"]["elevenlabs_api_key"]:
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."
)
self.client = ElevenLabs(api_key=api_key)
def randomvoice(self):
if self.client is None:
self.initialize()
return random.choice(self.client.voices.get_all().voices).voice_name
def __init__(self):
self.max_chars = 2500
self.client: ElevenLabs = None
def run(self, text, filepath, random_voice: bool = False):
if self.client is None:
self.initialize()
if random_voice:
voice = self.randomvoice()
else:
voice = str(
settings.config["settings"]["tts"]["elevenlabs_voice_name"]
).capitalize()
audio = self.client.generate(
text=text, voice=voice, model="eleven_multilingual_v1"
)
save(audio=audio, filename=filepath)
def initialize(self):
if settings.config["settings"]["tts"]["elevenlabs_api_key"]:
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."
)
self.client = ElevenLabs(api_key=api_key)
def randomvoice(self):
if self.client is None:
self.initialize()
return random.choice(self.client.voices.get_all().voices).voice_name

@ -14,9 +14,7 @@ from utils import settings
from utils.console import print_step, print_substep
from utils.voice import sanitize_text
DEFAULT_MAX_LENGTH: int = (
50 # Video length variable, edit this on your own risk. It should work, but it's not supported
)
DEFAULT_MAX_LENGTH: int = 50 # Video length variable, edit this on your own risk. It should work, but it's not supported
class TTSEngine:
@ -58,7 +56,9 @@ class TTSEngine:
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"\bAGI\b", "A.G.I", comment["comment_body"]
)
if comment["comment_body"][-1] != ".":
comment["comment_body"] += "."
comment["comment_body"] = comment["comment_body"].replace(". . .", ".")
@ -80,13 +80,17 @@ class TTSEngine:
if len(self.reddit_object["thread_post"]) > self.tts_module.max_chars:
self.split_post(self.reddit_object["thread_post"], "postaudio")
else:
self.call_tts("postaudio", process_text(self.reddit_object["thread_post"]))
self.call_tts(
"postaudio", process_text(self.reddit_object["thread_post"])
)
elif settings.config["settings"]["storymodemethod"] == 1:
for idx, text in track(enumerate(self.reddit_object["thread_post"])):
self.call_tts(f"postaudio-{idx}", process_text(text))
else:
for idx, comment in track(enumerate(self.reddit_object["comments"]), "Saving..."):
for idx, comment in track(
enumerate(self.reddit_object["comments"]), "Saving..."
):
# ! Stop creating mp3 files if the length is greater than max length.
if self.length > self.max_length and idx > 1:
self.length -= self.last_clip_length
@ -169,7 +173,9 @@ class TTSEngine:
fps=44100,
)
silence = volumex(silence, 0)
silence.write_audiofile(f"{self.path}/silence.mp3", fps=44100, verbose=False, logger=None)
silence.write_audiofile(
f"{self.path}/silence.mp3", fps=44100, verbose=False, logger=None
)
def process_text(text: str, clean: bool = True):
@ -177,6 +183,8 @@ def process_text(text: str, clean: bool = True):
new_text = sanitize_text(text) if clean else text
if lang:
print_substep("Translating Text...")
translated_text = translators.translate_text(text, translator="google", to_language=lang)
translated_text = translators.translate_text(
text, translator="google", to_language=lang
)
new_text = sanitize_text(translated_text)
return new_text

@ -21,7 +21,9 @@ class pyttsx:
if voice_id == "" or voice_num == "":
voice_id = 2
voice_num = 3
raise ValueError("set pyttsx values to a valid value, switching to defaults")
raise ValueError(
"set pyttsx values to a valid value, switching to defaults"
)
else:
voice_id = int(voice_id)
voice_num = int(voice_num)

@ -42,7 +42,9 @@ class StreamlabsPolly:
raise ValueError(
f"Please set the config variable STREAMLABS_POLLY_VOICE to a valid voice. options are: {voices}"
)
voice = str(settings.config["settings"]["tts"]["streamlabs_polly_voice"]).capitalize()
voice = str(
settings.config["settings"]["tts"]["streamlabs_polly_voice"]
).capitalize()
body = {"voice": voice, "text": text, "service": "polly"}
headers = {"Referer": "https://streamlabs.com/"}

@ -11,19 +11,17 @@ from prawcore import ResponseException
from reddit.subreddit import get_subreddit_threads
from utils import settings
from utils.cleanup import cleanup
from utils.console import print_markdown, print_step
from utils.console import print_substep
from utils.console import print_markdown, print_step, print_substep
from utils.ffmpeg_install import ffmpeg_install
from utils.id import id
from utils.version import checkversion
from video_creation.background import (
download_background_video,
download_background_audio,
chop_background,
get_background_config,
)
from video_creation.background import (chop_background,
download_background_audio,
download_background_video,
get_background_config)
from video_creation.final_video import make_final_video
from video_creation.screenshot_downloader import get_screenshots_of_reddit_posts
from video_creation.screenshot_downloader import \
get_screenshots_of_reddit_posts
from video_creation.voices import save_text_to_mp3
__VERSION__ = "3.2.1"
@ -38,7 +36,6 @@ print(
"""
)
# Modified by JasonLovesDoggo
print_markdown(
"### Thanks for using this tool! Feel free to contribute to this project on GitHub! If you have any questions, feel free to join my Discord server or submit a GitHub issue. You can find solutions to many common problems in the documentation: https://reddit-video-maker-bot.netlify.app/"
)
@ -104,7 +101,9 @@ if __name__ == "__main__":
sys.exit()
try:
if config["reddit"]["thread"]["post_id"]:
for index, post_id in enumerate(config["reddit"]["thread"]["post_id"].split("+")):
for index, post_id in enumerate(
config["reddit"]["thread"]["post_id"].split("+")
):
index += 1
print_step(
f'on the {index}{("st" if index % 10 == 1 else ("nd" if index % 10 == 2 else ("rd" if index % 10 == 3 else "th")))} post of {len(config["reddit"]["thread"]["post_id"].split("+"))}'

@ -22,7 +22,9 @@ def get_subreddit_threads(POST_ID: str):
content = {}
if settings.config["reddit"]["creds"]["2fa"]:
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 = settings.config["reddit"]["creds"]["password"]
@ -55,7 +57,9 @@ def get_subreddit_threads(POST_ID: str):
]: # 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? "))
re.sub(
r"r\/", "", input("What subreddit would you like to pull from? ")
)
# removes the r/ from the input
)
except ValueError:
@ -65,7 +69,9 @@ def get_subreddit_threads(POST_ID: str):
sub = settings.config["reddit"]["thread"]["subreddit"]
print_substep(f"Using subreddit: r/{sub} from TOML config")
subreddit_choice = sub
if str(subreddit_choice).casefold().startswith("r/"): # removes the r/ from the input
if (
str(subreddit_choice).casefold().startswith("r/")
): # removes the r/ from the input
subreddit_choice = subreddit_choice[2:]
subreddit = reddit.subreddit(subreddit_choice)
@ -76,8 +82,12 @@ def get_subreddit_threads(POST_ID: str):
settings.config["reddit"]["thread"]["post_id"]
and len(str(settings.config["reddit"]["thread"]["post_id"]).split("+")) == 1
):
submission = reddit.submission(id=settings.config["reddit"]["thread"]["post_id"])
elif settings.config["ai"]["ai_similarity_enabled"]: # ai sorting based on comparison
submission = reddit.submission(
id=settings.config["reddit"]["thread"]["post_id"]
)
elif settings.config["ai"][
"ai_similarity_enabled"
]: # ai sorting based on comparison
threads = subreddit.hot(limit=50)
keywords = settings.config["ai"]["ai_similarity_keywords"].split(",")
keywords = [keyword.strip() for keyword in keywords]
@ -95,7 +105,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()

@ -1,12 +1,16 @@
import numpy as np
import torch
from transformers import AutoTokenizer, AutoModel
from transformers import AutoModel, AutoTokenizer
# Mean Pooling - Take attention mask into account for correct averaging
def mean_pooling(model_output, attention_mask):
token_embeddings = model_output[0] # First element of model_output contains all token embeddings
input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
token_embeddings = model_output[
0
] # First element of model_output contains all token embeddings
input_mask_expanded = (
attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
)
return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(
input_mask_expanded.sum(1), min=1e-9
)
@ -32,13 +36,19 @@ def sort_by_similarity(thread_objects, keywords):
)
with torch.no_grad():
threads_embeddings = model(**encoded_threads)
threads_embeddings = mean_pooling(threads_embeddings, encoded_threads["attention_mask"])
threads_embeddings = mean_pooling(
threads_embeddings, encoded_threads["attention_mask"]
)
# Keywords inference
encoded_keywords = tokenizer(keywords, padding=True, truncation=True, return_tensors="pt")
encoded_keywords = tokenizer(
keywords, padding=True, truncation=True, return_tensors="pt"
)
with torch.no_grad():
keywords_embeddings = model(**encoded_keywords)
keywords_embeddings = mean_pooling(keywords_embeddings, encoded_keywords["attention_mask"])
keywords_embeddings = mean_pooling(
keywords_embeddings, encoded_keywords["attention_mask"]
)
# Compare every keyword w/ every thread embedding
threads_embeddings_tensor = torch.tensor(threads_embeddings)

@ -49,7 +49,10 @@ def handle_input(
optional=False,
):
if optional:
console.print(message + "\n[green]This is an optional value. Do you want to skip it? (y/n)")
console.print(
message
+ "\n[green]This is an optional value. Do you want to skip it? (y/n)"
)
if input().casefold().startswith("y"):
return default if default is not NotImplemented else ""
if default is not NotImplemented:
@ -83,7 +86,11 @@ def handle_input(
console.print("[red]" + err_message)
continue
elif match != "" and re.match(match, user_input) is None:
console.print("[red]" + err_message + "\nAre you absolutely sure it's correct?(y/n)")
console.print(
"[red]"
+ err_message
+ "\nAre you absolutely sure it's correct?(y/n)"
)
if input().casefold().startswith("y"):
break
continue
@ -116,5 +123,9 @@ def handle_input(
if user_input in options:
return user_input
console.print(
"[red bold]" + err_message + "\nValid options are: " + ", ".join(map(str, options)) + "."
"[red bold]"
+ err_message
+ "\nValid options are: "
+ ", ".join(map(str, options))
+ "."
)

@ -7,9 +7,7 @@ import requests
def ffmpeg_install_windows():
try:
ffmpeg_url = (
"https://github.com/GyanD/codexffmpeg/releases/download/6.0/ffmpeg-6.0-full_build.zip"
)
ffmpeg_url = "https://github.com/GyanD/codexffmpeg/releases/download/6.0/ffmpeg-6.0-full_build.zip"
ffmpeg_zip_filename = "ffmpeg.zip"
ffmpeg_extracted_folder = "ffmpeg"
@ -129,7 +127,9 @@ 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.")

@ -1,12 +1,13 @@
from PIL.ImageFont import ImageFont, FreeTypeFont
from PIL.ImageFont import FreeTypeFont, ImageFont
def getsize(font: ImageFont | FreeTypeFont, text: str):
left, top, right, bottom = font.getbbox(text)
width = right - left
height = bottom - top
return width, height
left, top, right, bottom = font.getbbox(text)
width = right - left
height = bottom - top
return width, height
def getheight(font: ImageFont | FreeTypeFont, text: str):
_, height = getsize(font, text)
return height
_, height = getsize(font, text)
return height

@ -67,7 +67,11 @@ def check(value, checks):
and not hasattr(value, "__iter__")
and (
("nmin" in checks and checks["nmin"] is not None and value < checks["nmin"])
or ("nmax" in checks and checks["nmax"] is not None and value > checks["nmax"])
or (
"nmax" in checks
and checks["nmax"] is not None
and value > checks["nmax"]
)
)
):
incorrect = True
@ -76,8 +80,16 @@ def check(value, checks):
not incorrect
and hasattr(value, "__iter__")
and (
("nmin" in checks and checks["nmin"] is not None and len(value) < checks["nmin"])
or ("nmax" in checks and checks["nmax"] is not None and len(value) > checks["nmax"])
(
"nmin" in checks
and checks["nmin"] is not None
and len(value) < checks["nmin"]
)
or (
"nmax" in checks
and checks["nmax"] is not None
and len(value) > checks["nmax"]
)
)
):
incorrect = True
@ -150,7 +162,9 @@ def delete_background(key):
# Add background video
def add_background(youtube_uri, filename, citation, position):
# Validate YouTube URI
regex = re.compile(r"(?:\/|%3D|v=|vi=)([0-9A-z\-_]{11})(?:[%#?&]|$)").search(youtube_uri)
regex = re.compile(r"(?:\/|%3D|v=|vi=)([0-9A-z\-_]{11})(?:[%#?&]|$)").search(
youtube_uri
)
if not regex:
flash("YouTube URI is invalid!", "error")

@ -6,7 +6,7 @@ from PIL import Image, ImageDraw, ImageFont
from rich.progress import track
from TTS.engine_wrapper import process_text
from utils.fonts import getsize, getheight
from utils.fonts import getheight, getsize
def draw_multiple_line_text(
@ -19,7 +19,9 @@ def draw_multiple_line_text(
font_height = getheight(font, text)
image_width, image_height = image.size
lines = textwrap.wrap(text, width=wrap)
y = (image_height / 2) - (((font_height + (len(lines) * padding) / len(lines)) * len(lines)) / 2)
y = (image_height / 2) - (
((font_height + (len(lines) * padding) / len(lines)) * len(lines)) / 2
)
for line in lines:
line_width, line_height = getsize(font, line)
if transparent:
@ -71,5 +73,7 @@ def imagemaker(theme, reddit_obj: dict, txtclr, padding=5, transparent=False) ->
for idx, text in track(enumerate(texts), "Rendering Image"):
image = Image.new("RGBA", size, theme)
text = process_text(text, False)
draw_multiple_line_text(image, text, font, txtclr, padding, wrap=30, transparent=transparent)
draw_multiple_line_text(
image, text, font, txtclr, padding, wrap=30, transparent=transparent
)
image.save(f"assets/temp/{id}/png/img{idx}.png")

@ -1,5 +1,7 @@
def clear_cookie_by_name(context, cookie_cleared_name):
cookies = context.cookies()
filtered_cookies = [cookie for cookie in cookies if cookie["name"] != cookie_cleared_name]
filtered_cookies = [
cookie for cookie in cookies if cookie["name"] != cookie_cleared_name
]
context.clear_cookies()
context.add_cookies(filtered_cookies)

@ -1,6 +1,6 @@
import re
from pathlib import Path
from typing import Tuple, Dict
from typing import Dict, Tuple
import toml
from rich.console import Console
@ -53,7 +53,11 @@ def check(value, checks, name):
and not hasattr(value, "__iter__")
and (
("nmin" in checks and checks["nmin"] is not None and value < checks["nmin"])
or ("nmax" in checks and checks["nmax"] is not None and value > checks["nmax"])
or (
"nmax" in checks
and checks["nmax"] is not None
and value > checks["nmax"]
)
)
):
incorrect = True
@ -61,8 +65,16 @@ def check(value, checks, name):
not incorrect
and hasattr(value, "__iter__")
and (
("nmin" in checks and checks["nmin"] is not None and len(value) < checks["nmin"])
or ("nmax" in checks and checks["nmax"] is not None and len(value) > checks["nmax"])
(
"nmin" in checks
and checks["nmin"] is not None
and len(value) < checks["nmin"]
)
or (
"nmax" in checks
and checks["nmax"] is not None
and len(value) > checks["nmax"]
)
)
):
incorrect = True
@ -70,9 +82,15 @@ def check(value, checks, name):
if incorrect:
value = handle_input(
message=(
(("[blue]Example: " + str(checks["example"]) + "\n") if "example" in checks else "")
(
("[blue]Example: " + str(checks["example"]) + "\n")
if "example" in checks
else ""
)
+ "[red]"
+ ("Non-optional ", "Optional ")["optional" in checks and checks["optional"] is True]
+ ("Non-optional ", "Optional ")[
"optional" in checks and checks["optional"] is True
]
)
+ "[#C0CAF5 bold]"
+ str(name)
@ -113,7 +131,9 @@ def check_toml(template_file, config_file) -> Tuple[bool, Dict]:
try:
template = toml.load(template_file)
except Exception as error:
console.print(f"[red bold]Encountered error when trying to to load {template_file}: {error}")
console.print(
f"[red bold]Encountered error when trying to to load {template_file}: {error}"
)
return False
try:
config = toml.load(config_file)

@ -6,7 +6,9 @@ from utils.ai_methods import sort_by_similarity
from utils.console import print_substep
def get_subreddit_undone(submissions: list, subreddit, times_checked=0, similarity_scores=None):
def get_subreddit_undone(
submissions: list, subreddit, times_checked=0, similarity_scores=None
):
"""_summary_
Args:
@ -18,7 +20,9 @@ def get_subreddit_undone(submissions: list, subreddit, times_checked=0, similari
"""
# Second try of getting a valid Submission
if times_checked and settings.config["ai"]["ai_similarity_enabled"]:
print("Sorting based on similarity for a different date filter and thread limit..")
print(
"Sorting based on similarity for a different date filter and thread limit.."
)
submissions = sort_by_similarity(
submissions, keywords=settings.config["ai"]["ai_similarity_enabled"]
)
@ -27,7 +31,9 @@ def get_subreddit_undone(submissions: list, subreddit, times_checked=0, similari
if not exists("./video_creation/data/videos.json"):
with open("./video_creation/data/videos.json", "w+") as f:
json.dump([], f)
with open("./video_creation/data/videos.json", "r", encoding="utf-8") as done_vids_raw:
with open(
"./video_creation/data/videos.json", "r", encoding="utf-8"
) as done_vids_raw:
done_videos = json.load(done_vids_raw)
for i, submission in enumerate(submissions):
if already_done(done_videos, submission):
@ -43,7 +49,8 @@ def get_subreddit_undone(submissions: list, subreddit, times_checked=0, similari
print_substep("This post was pinned by moderators. Skipping...")
continue
if (
submission.num_comments <= int(settings.config["reddit"]["thread"]["min_comments"])
submission.num_comments
<= int(settings.config["reddit"]["thread"]["min_comments"])
and not settings.config["settings"]["storymode"]
):
print_substep(
@ -52,7 +59,9 @@ def get_subreddit_undone(submissions: list, subreddit, times_checked=0, similari
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

@ -1,11 +1,15 @@
from PIL import ImageDraw, ImageFont
def create_thumbnail(thumbnail, font_family, font_size, font_color, width, height, title):
def create_thumbnail(
thumbnail, font_family, font_size, font_color, width, height, title
):
font = ImageFont.truetype(font_family + ".ttf", font_size)
Xaxis = width - (width * 0.2) # 20% of the width
sizeLetterXaxis = font_size * 0.5 # 50% of the font size
XaxisLetterQty = round(Xaxis / sizeLetterXaxis) # Quantity of letters that can fit in the X axis
XaxisLetterQty = round(
Xaxis / sizeLetterXaxis
) # Quantity of letters that can fit in the X axis
MarginYaxis = height * 0.12 # 12% of the height
MarginXaxis = width * 0.05 # 5% of the width
# 1.1 rem
@ -30,6 +34,8 @@ def create_thumbnail(thumbnail, font_family, font_size, font_color, width, heigh
# loop for put the title in the thumbnail
for i in range(0, len(arrayTitle)):
# 1.1 rem
draw.text((MarginXaxis, MarginYaxis + (LineHeight * i)), arrayTitle[i], rgb, font=font)
draw.text(
(MarginXaxis, MarginYaxis + (LineHeight * i)), arrayTitle[i], rgb, font=font
)
return thumbnail

@ -19,7 +19,9 @@ def check_done(
Returns:
Submission|None: Reddit object in args
"""
with open("./video_creation/data/videos.json", "r", encoding="utf-8") as done_vids_raw:
with open(
"./video_creation/data/videos.json", "r", encoding="utf-8"
) as done_vids_raw:
done_videos = json.load(done_vids_raw)
for video in done_videos:
if video["id"] == str(redditobj):
@ -33,7 +35,9 @@ def check_done(
return redditobj
def save_data(subreddit: str, filename: str, reddit_title: str, reddit_id: str, credit: str):
def save_data(
subreddit: str, filename: str, reddit_title: str, reddit_id: str, credit: str
):
"""Saves the videos that have already been generated to a JSON file in video_creation/data/videos.json
Args:

@ -43,7 +43,9 @@ def sleep_until(time) -> None:
if sys.version_info[0] >= 3 and time.tzinfo:
end = time.astimezone(timezone.utc).timestamp()
else:
zoneDiff = pytime.time() - (datetime.now() - datetime(1970, 1, 1)).total_seconds()
zoneDiff = (
pytime.time() - (datetime.now() - datetime(1970, 1, 1)).total_seconds()
)
end = (time - datetime(1970, 1, 1)).total_seconds() + zoneDiff
# Type check

@ -3,10 +3,10 @@ import random
import re
from pathlib import Path
from random import randrange
from typing import Any, Tuple, Dict
from typing import Any, Dict, Tuple
import yt_dlp
from moviepy.editor import VideoFileClip, AudioFileClip
from moviepy.editor import AudioFileClip, VideoFileClip
from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
from utils import settings
@ -60,7 +60,9 @@ def get_start_and_end_times(video_length: int, length_of_clip: int) -> Tuple[int
def get_background_config(mode: str):
"""Fetch the background/s configuration"""
try:
choice = str(settings.config["settings"]["background"][f"background_{mode}"]).casefold()
choice = str(
settings.config["settings"]["background"][f"background_{mode}"]
).casefold()
except AttributeError:
print_substep("No background selected. Picking random background'")
choice = None
@ -120,7 +122,9 @@ 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):
def chop_background(
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
Args:
@ -133,7 +137,9 @@ def chop_background(background_config: Dict[str, Tuple], video_length: int, redd
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

@ -2,18 +2,16 @@ import multiprocessing
import os
import re
import tempfile
import threading
import textwrap
import threading
import time
from pathlib import Path
from os.path import exists # Needs to be imported specifically
from typing import Final
from typing import Tuple, Dict
from pathlib import Path
from typing import Dict, Final, Tuple
import ffmpeg
import translators
from PIL import ImageDraw, ImageFont, Image
from PIL import Image, ImageDraw, ImageFont
from rich.console import Console
from rich.progress import track
@ -79,7 +77,9 @@ def name_normalize(name: str) -> str:
lang = settings.config["reddit"]["thread"]["post_lang"]
if lang:
print_substep("Translating filename...")
translated_name = translators.translate_text(name, translator="google", to_language=lang)
translated_name = translators.translate_text(
name, translator="google", to_language=lang
)
return translated_name
else:
return name
@ -118,7 +118,10 @@ def create_fancy_thumbnail(image, text, text_color, padding, wrap=35):
lines = textwrap.wrap(text, width=wrap)
y = (
(image_height / 2)
- (((getheight(font, text) + (len(lines) * padding) / len(lines)) * len(lines)) / 2)
- (
((getheight(font, text) + (len(lines) * padding) / len(lines)) * len(lines))
/ 2
)
+ 30
)
draw = ImageDraw.Draw(image)
@ -135,28 +138,52 @@ def create_fancy_thumbnail(image, text, text_color, padding, wrap=35):
if len(lines) == 3:
lines = textwrap.wrap(text, width=wrap + 10)
font_title_size = 40
font = ImageFont.truetype(os.path.join("fonts", "Roboto-Bold.ttf"), font_title_size)
font = ImageFont.truetype(
os.path.join("fonts", "Roboto-Bold.ttf"), font_title_size
)
y = (
(image_height / 2)
- (((getheight(font, text) + (len(lines) * padding) / len(lines)) * len(lines)) / 2)
- (
(
(getheight(font, text) + (len(lines) * padding) / len(lines))
* len(lines)
)
/ 2
)
+ 35
)
elif len(lines) == 4:
lines = textwrap.wrap(text, width=wrap + 10)
font_title_size = 35
font = ImageFont.truetype(os.path.join("fonts", "Roboto-Bold.ttf"), font_title_size)
font = ImageFont.truetype(
os.path.join("fonts", "Roboto-Bold.ttf"), font_title_size
)
y = (
(image_height / 2)
- (((getheight(font, text) + (len(lines) * padding) / len(lines)) * len(lines)) / 2)
- (
(
(getheight(font, text) + (len(lines) * padding) / len(lines))
* len(lines)
)
/ 2
)
+ 40
)
elif len(lines) > 4:
lines = textwrap.wrap(text, width=wrap + 10)
font_title_size = 30
font = ImageFont.truetype(os.path.join("fonts", "Roboto-Bold.ttf"), font_title_size)
font = ImageFont.truetype(
os.path.join("fonts", "Roboto-Bold.ttf"), font_title_size
)
y = (
(image_height / 2)
- (((getheight(font, text) + (len(lines) * padding) / len(lines)) * len(lines)) / 2)
- (
(
(getheight(font, text) + (len(lines) * padding) / len(lines))
* len(lines)
)
/ 2
)
+ 30
)
@ -173,7 +200,9 @@ 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"]
background_audio_volume = settings.config["settings"]["background"][
"background_audio_volume"
]
if background_audio_volume == 0:
return audio # Return the original audio
else:
@ -227,27 +256,42 @@ def make_final_video(
if settings.config["settings"]["storymode"]:
if settings.config["settings"]["storymodemethod"] == 0:
audio_clips = [ffmpeg.input(f"assets/temp/{reddit_id}/mp3/title.mp3")]
audio_clips.insert(1, ffmpeg.input(f"assets/temp/{reddit_id}/mp3/postaudio.mp3"))
audio_clips.insert(
1, ffmpeg.input(f"assets/temp/{reddit_id}/mp3/postaudio.mp3")
)
elif settings.config["settings"]["storymodemethod"] == 1:
audio_clips = [
ffmpeg.input(f"assets/temp/{reddit_id}/mp3/postaudio-{i}.mp3")
for i in track(range(number_of_clips + 1), "Collecting the audio files...")
for i in track(
range(number_of_clips + 1), "Collecting the audio files..."
)
]
audio_clips.insert(0, ffmpeg.input(f"assets/temp/{reddit_id}/mp3/title.mp3"))
audio_clips.insert(
0, ffmpeg.input(f"assets/temp/{reddit_id}/mp3/title.mp3")
)
else:
audio_clips = [
ffmpeg.input(f"assets/temp/{reddit_id}/mp3/{i}.mp3") for i in range(number_of_clips)
ffmpeg.input(f"assets/temp/{reddit_id}/mp3/{i}.mp3")
for i in range(number_of_clips)
]
audio_clips.insert(0, ffmpeg.input(f"assets/temp/{reddit_id}/mp3/title.mp3"))
audio_clips_durations = [
float(ffmpeg.probe(f"assets/temp/{reddit_id}/mp3/{i}.mp3")["format"]["duration"])
float(
ffmpeg.probe(f"assets/temp/{reddit_id}/mp3/{i}.mp3")["format"][
"duration"
]
)
for i in range(number_of_clips)
]
audio_clips_durations.insert(
0,
float(ffmpeg.probe(f"assets/temp/{reddit_id}/mp3/title.mp3")["format"]["duration"]),
float(
ffmpeg.probe(f"assets/temp/{reddit_id}/mp3/title.mp3")["format"][
"duration"
]
),
)
audio_concat = ffmpeg.concat(*audio_clips, a=1, v=0)
ffmpeg.output(
@ -290,13 +334,19 @@ def make_final_video(
if settings.config["settings"]["storymode"]:
audio_clips_durations = [
float(
ffmpeg.probe(f"assets/temp/{reddit_id}/mp3/postaudio-{i}.mp3")["format"]["duration"]
ffmpeg.probe(f"assets/temp/{reddit_id}/mp3/postaudio-{i}.mp3")[
"format"
]["duration"]
)
for i in range(number_of_clips)
]
audio_clips_durations.insert(
0,
float(ffmpeg.probe(f"assets/temp/{reddit_id}/mp3/title.mp3")["format"]["duration"]),
float(
ffmpeg.probe(f"assets/temp/{reddit_id}/mp3/title.mp3")["format"][
"duration"
]
),
)
if settings.config["settings"]["storymodemethod"] == 0:
image_clips.insert(
@ -313,7 +363,9 @@ def make_final_video(
)
current_time += audio_clips_durations[0]
elif settings.config["settings"]["storymodemethod"] == 1:
for i in track(range(0, number_of_clips + 1), "Collecting the image files..."):
for i in track(
range(0, number_of_clips + 1), "Collecting the image files..."
):
image_clips.append(
ffmpeg.input(f"assets/temp/{reddit_id}/png/img{i}.png")["v"].filter(
"scale", screenshot_width, -1
@ -329,9 +381,9 @@ def make_final_video(
else:
for i in range(0, number_of_clips + 1):
image_clips.append(
ffmpeg.input(f"assets/temp/{reddit_id}/png/comment_{i}.png")["v"].filter(
"scale", screenshot_width, -1
)
ffmpeg.input(f"assets/temp/{reddit_id}/png/comment_{i}.png")[
"v"
].filter("scale", screenshot_width, -1)
)
image_overlay = image_clips[i].filter("colorchannelmixer", aa=opacity)
assert (
@ -353,11 +405,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
@ -371,7 +427,11 @@ def make_final_video(
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(
(file for file in os.listdir("assets/backgrounds") if file.endswith(".png")),
(
file
for file in os.listdir("assets/backgrounds")
if file.endswith(".png")
),
None,
)
if first_image is None:
@ -393,7 +453,9 @@ def make_final_video(
title_thumb,
)
thumbnailSave.save(f"./assets/temp/{reddit_id}/thumbnail.png")
print_substep(f"Thumbnail - Building Thumbnail in assets/temp/{reddit_id}/thumbnail.png")
print_substep(
f"Thumbnail - Building Thumbnail in assets/temp/{reddit_id}/thumbnail.png"
)
text = f"Background by {background_config['video'][2]}"
background_clip = ffmpeg.drawtext(
@ -434,7 +496,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,
@ -464,7 +528,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,

@ -36,7 +36,9 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
# set the theme and disable non-essential cookies
if settings.config["settings"]["theme"] == "dark":
cookie_file = open("./video_creation/data/cookie-dark-mode.json", encoding="utf-8")
cookie_file = open(
"./video_creation/data/cookie-dark-mode.json", encoding="utf-8"
)
bgcolor = (33, 33, 36, 255)
txtcolor = (240, 240, 240)
transparent = False
@ -46,15 +48,21 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
bgcolor = (0, 0, 0, 0)
txtcolor = (255, 255, 255)
transparent = True
cookie_file = open("./video_creation/data/cookie-dark-mode.json", encoding="utf-8")
cookie_file = open(
"./video_creation/data/cookie-dark-mode.json", encoding="utf-8"
)
else:
# Switch to dark theme
cookie_file = open("./video_creation/data/cookie-dark-mode.json", encoding="utf-8")
cookie_file = open(
"./video_creation/data/cookie-dark-mode.json", encoding="utf-8"
)
bgcolor = (33, 33, 36, 255)
txtcolor = (240, 240, 240)
transparent = False
else:
cookie_file = open("./video_creation/data/cookie-light-mode.json", encoding="utf-8")
cookie_file = open(
"./video_creation/data/cookie-light-mode.json", encoding="utf-8"
)
bgcolor = (255, 255, 255, 255)
txtcolor = (0, 0, 0)
transparent = False
@ -99,8 +107,12 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
page.set_viewport_size(ViewportSize(width=1920, height=1080))
page.wait_for_load_state()
page.locator(f'input[name="username"]').fill(settings.config["reddit"]["creds"]["username"])
page.locator(f'input[name="password"]').fill(settings.config["reddit"]["creds"]["password"])
page.locator(f'input[name="username"]').fill(
settings.config["reddit"]["creds"]["username"]
)
page.locator(f'input[name="password"]').fill(
settings.config["reddit"]["creds"]["password"]
)
page.get_by_role("button", name="Log In").click()
page.wait_for_timeout(5000)
@ -181,7 +193,9 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
location[i] = float("{:.2f}".format(location[i] * zoom))
page.screenshot(clip=location, path=postcontentpath)
else:
page.locator('[data-test-id="post-content"]').screenshot(path=postcontentpath)
page.locator('[data-test-id="post-content"]').screenshot(
path=postcontentpath
)
except Exception as e:
print_substep("Something went wrong!", style="red")
resp = input(
@ -195,7 +209,9 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
"green",
)
resp = input("Do you want the error traceback for debugging purposes? (y/n)")
resp = input(
"Do you want the error traceback for debugging purposes? (y/n)"
)
if not resp.casefold().startswith("y"):
exit()
@ -240,9 +256,13 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
# zoom the body of the page
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(

@ -2,15 +2,15 @@ from typing import Tuple
from rich.console import Console
from TTS.GTTS import GTTS
from TTS.TikTok import TikTok
from TTS.aws_polly import AWSPolly
from TTS.elevenlabs import elevenlabs
from TTS.engine_wrapper import TTSEngine
from TTS.GTTS import GTTS
from TTS.pyttsx import pyttsx
from TTS.streamlabs_polly import StreamlabsPolly
from TTS.TikTok import TikTok
from utils import settings
from utils.console import print_table, print_step
from utils.console import print_step, print_table
console = Console()
@ -36,7 +36,9 @@ def save_text_to_mp3(reddit_obj) -> Tuple[int, int]:
voice = settings.config["settings"]["tts"]["voice_choice"]
if str(voice).casefold() in map(lambda _: _.casefold(), TTSProviders):
text_to_mp3 = TTSEngine(get_case_insensitive_key_value(TTSProviders, voice), reddit_obj)
text_to_mp3 = TTSEngine(
get_case_insensitive_key_value(TTSProviders, voice), reddit_obj
)
else:
while True:
print_step("Please choose one of the following TTS providers: ")
@ -45,12 +47,18 @@ def save_text_to_mp3(reddit_obj) -> Tuple[int, int]:
if choice.casefold() in map(lambda _: _.casefold(), TTSProviders):
break
print("Unknown Choice")
text_to_mp3 = TTSEngine(get_case_insensitive_key_value(TTSProviders, choice), reddit_obj)
text_to_mp3 = TTSEngine(
get_case_insensitive_key_value(TTSProviders, choice), reddit_obj
)
return text_to_mp3.run()
def get_case_insensitive_key_value(input_dict, key):
return next(
(value for dict_key, value in input_dict.items() if dict_key.lower() == key.lower()),
(
value
for dict_key, value in input_dict.items()
if dict_key.lower() == key.lower()
),
None,
)

Loading…
Cancel
Save