Merge branch 'lloydy-changes' into develop

pull/1838/head
Anthony Lloyd 2 years ago
commit 15ff6f6a8c

@ -43,7 +43,7 @@ def index():
@app.route("/backgrounds", methods=["GET"])
def backgrounds():
return render_template("backgrounds.html", file="backgrounds.json")
return render_template("backgrounds.html", file="background_videos.json")
@app.route("/background/add", methods=["POST"])
@ -93,10 +93,10 @@ def videos_json():
return send_from_directory("video_creation/data", "videos.json")
# Make backgrounds.json accessible
@app.route("/backgrounds.json")
# Make background_videos.json accessible
@app.route("/background_videos.json")
def backgrounds_json():
return send_from_directory("utils", "backgrounds.json")
return send_from_directory("utils", "background_videos.json")
# Make videos in results folder accessible

@ -125,7 +125,7 @@
// Show background videos
$(document).ready(function () {
$.getJSON("backgrounds.json",
$.getJSON("background_videos.json",
function (data) {
delete data["__comment"];
var background = '';

@ -200,9 +200,9 @@
</div>
</div>
<div class="row mb-2">
<label for="background_choice" class="col-4">Background Choice</label>
<label for="background_video" class="col-4">Background Video</label>
<div class="col-8">
<select name="background_choice" class="form-select" data-toggle="tooltip"
<select name="background_video" class="form-select" data-toggle="tooltip"
data-original-title='Sets the background of the video'>
<option value=" ">Random Video</option>
{% for background in checks["background_video"]["options"][1:] %}

@ -4,13 +4,15 @@ from gtts import gTTS
from utils import settings
from typing import Optional
class GTTS:
def __init__(self):
self.max_chars = 5000
self.voices = []
def run(self, text, filepath):
def run(self, text, filepath, random_voice: bool = False, voice: Optional[str] = None):
tts = gTTS(
text=text,
lang=settings.config["reddit"]["thread"]["post_lang"] or "en",
@ -18,5 +20,5 @@ class GTTS:
)
tts.save(filepath)
def randomvoice(self):
return random.choice(self.voices)
def random_voice(self):
return random.choice(self.voices)

@ -75,6 +75,8 @@ vocals: Final[tuple] = (
"en_female_ht_f08_wonderful_world", # Dramatic
)
all_voices: Final[tuple] = disney_voices + eng_voices + non_eng_voices + vocals
# comment out a voice to make it unavailable to be randomly selected
class TikTok:
"""TikTok Text-to-Speech Wrapper"""
@ -95,12 +97,13 @@ class TikTok:
# set the headers to the session, so we don't have to do it for every request
self._session.headers = headers
def run(self, text: str, filepath: str, random_voice: bool = False):
if random_voice:
voice = self.random_voice()
else:
# if tiktok_voice is not set in the config file, then use a random voice
voice = settings.config["settings"]["tts"].get("tiktok_voice", None)
def run(self, text: str, filepath: str, random_voice: bool = False, voice: Optional[str] = None):
if not voice:
if random_voice:
voice = self.random_voice()
else:
# if tiktok_voice is not set in the config file, then use a random voice
voice = settings.config["settings"]["tts"].get("tiktok_voice", self.random_voice())
# get the audio from the TikTok API
data = self.get_voices(voice=voice, text=text)

@ -30,13 +30,16 @@ class AWSPolly:
self.max_chars = 3000
self.voices = voices
def run(self, text, filepath, random_voice: bool = False):
def run(self, text, filepath, random_voice: bool = False, voice: str = None):
try:
session = Session(profile_name="polly")
polly = session.client("polly")
if random_voice:
voice = self.randomvoice()
else:
if voice: # If voice is explicitly provided, use it
voice = voice.capitalize()
elif random_voice: # Else if random_voice is set to True, pick a random voice
voice = self.random_voice()
else: # If none of the above, use the voice from the settings
if not settings.config["settings"]["tts"]["aws_polly_voice"]:
raise ValueError(
f"Please set the TOML variable AWS_VOICE to a valid voice. options are: {voices}"
@ -56,11 +59,8 @@ class AWSPolly:
# Access the audio stream from the response
if "AudioStream" in response:
file = open(filepath, "wb")
file.write(response["AudioStream"].read())
file.close()
# print_substep(f"Saved Text {idx} to MP3 files successfully.", style="bold green")
with open(filepath, "wb") as file:
file.write(response["AudioStream"].read())
else:
# The response didn't contain audio data, exit gracefully
print("Could not stream audio")
@ -75,5 +75,5 @@ class AWSPolly:
)
sys.exit(-1)
def randomvoice(self):
def random_voice(self):
return random.choice(self.voices)

@ -22,13 +22,13 @@ class elevenlabs:
self.max_chars = 2500
self.voices = voices
def run(self, text, filepath, random_voice: bool = False):
if random_voice:
voice = self.randomvoice()
else:
voice = str(
settings.config["settings"]["tts"]["elevenlabs_voice_name"]
).capitalize()
def run(self, text, filepath, voice=None, random_voice: bool = False):
if not voice: # If voice is not provided directly
if random_voice:
voice = self.random_voice()
else:
voice = str(settings.config["settings"]["tts"]["elevenlabs_voice_name"]).capitalize()
if settings.config["settings"]["tts"]["elevenlabs_api_key"]:
api_key = settings.config["settings"]["tts"]["elevenlabs_api_key"]
@ -42,5 +42,5 @@ class elevenlabs:
)
save(audio=audio, filename=filepath)
def randomvoice(self):
def random_voice(self):
return random.choice(self.voices)

@ -1,7 +1,7 @@
import os
import re
from pathlib import Path
from typing import Tuple
from typing import Tuple, Optional
import numpy as np
import translators
@ -72,42 +72,38 @@ class TTSEngine:
print_step("Saving Text to MP3 files...")
self.add_periods()
self.call_tts("title", process_text(self.reddit_object["thread_title"]))
# processed_text = ##self.reddit_object["thread_post"] != ""
# Select a voice for the title and thread body if in story mode
voice = self.tts_module.random_voice() if settings.config["settings"]["tts"]["random_voice"] else None
self.call_tts("title", process_text(self.reddit_object["thread_title"]), voice)
idx = 0
if settings.config["settings"]["storymode"]:
if settings.config["settings"]["storymodemethod"] == 0:
if len(self.reddit_object["thread_post"]) > self.tts_module.max_chars:
self.split_post(self.reddit_object["thread_post"], "postaudio")
self.split_post(self.reddit_object["thread_post"], "postaudio", voice)
else:
self.call_tts(
"postaudio", process_text(self.reddit_object["thread_post"])
)
self.call_tts("postaudio", process_text(self.reddit_object["thread_post"]), voice)
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))
self.call_tts(f"postaudio-{idx}", process_text(text), voice)
else:
for idx, comment in track(
enumerate(self.reddit_object["comments"]), "Saving..."
):
for idx, comment in track(enumerate(self.reddit_object["comments"]), "Saving..."):
comment_voice = self.tts_module.random_voice() if settings.config["settings"]["tts"]["random_voice"] else None
# ! 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
idx -= 1
break
if (
len(comment["comment_body"]) > self.tts_module.max_chars
): # Split the comment if it is too long
self.split_post(comment["comment_body"], idx) # Split the comment
if (len(comment["comment_body"]) > self.tts_module.max_chars): # Split the comment if it is too long
self.split_post(comment["comment_body"], idx, comment_voice) # Split the comment
else: # If the comment is not too long, just call the tts engine
self.call_tts(f"{idx}", process_text(comment["comment_body"]))
self.call_tts(f"{idx}", process_text(comment["comment_body"]), comment_voice)
print_substep("Saved Text to MP3 files successfully.", style="bold green")
return self.length, idx
def split_post(self, text: str, idx):
def split_post(self, text: str, idx, voice: Optional[str] = None):
split_files = []
split_text = [
x.group().strip()
@ -126,7 +122,7 @@ class TTSEngine:
print("newtext was blank because sanitized split text resulted in none")
continue
else:
self.call_tts(f"{idx}-{idy}.part", newtext)
self.call_tts(f"{idx}-{idy}.part", newtext, voice)
with open(f"{self.path}/list.txt", "w") as f:
for idz in range(0, len(split_text)):
f.write("file " + f"'{idx}-{idz}.part.mp3'" + "\n")
@ -148,11 +144,12 @@ class TTSEngine:
except OSError:
print("OSError")
def call_tts(self, filename: str, text: str):
def call_tts(self, filename: str, text: str, voice: Optional[str] = None):
self.tts_module.run(
text,
filepath=f"{self.path}/{filename}.mp3",
random_voice=settings.config["settings"]["tts"]["random_voice"],
voice=voice
)
# try:
# self.length += MP3(f"{self.path}/{filename}.mp3").info.length
@ -188,4 +185,4 @@ def process_text(text: str, clean: bool = True):
text, translator="google", to_language=lang
)
new_text = sanitize_text(translated_text)
return new_text
return new_text

@ -4,6 +4,8 @@ import pyttsx3
from utils import settings
from typing import Optional
class pyttsx:
def __init__(self):
@ -15,9 +17,17 @@ class pyttsx:
text: str,
filepath: str,
random_voice=False,
voice: Optional[str] = None,
):
voice_id = settings.config["settings"]["tts"]["python_voice"]
voice_num = settings.config["settings"]["tts"]["py_voice_num"]
if not voice:
if random_voice:
voice = self.random_voice()
else:
# if pyTTS is not set in the config file, then use a random voice
voice = settings.config["settings"]["tts"].get("python_voice", self.random_voice())
if voice_id == "" or voice_num == "":
voice_id = 2
voice_num = 3
@ -33,12 +43,8 @@ class pyttsx:
if random_voice:
voice_id = self.randomvoice()
engine = pyttsx3.init()
voices = engine.getProperty("voices")
engine.setProperty(
"voice", voices[voice_id].id
) # changing index changes voices but ony 0 and 1 are working here
engine.save_to_file(text, f"{filepath}")
engine.runAndWait()
def randomvoice(self):
return random.choice(self.voices)
def random_voice(self):
return random.choice(self.voices)

@ -36,7 +36,7 @@ class StreamlabsPolly:
def run(self, text, filepath, random_voice: bool = False):
if random_voice:
voice = self.randomvoice()
voice = self.random_voice()
else:
if not settings.config["settings"]["tts"]["streamlabs_polly_voice"]:
raise ValueError(
@ -62,5 +62,5 @@ class StreamlabsPolly:
except (KeyError, JSONDecodeError):
print("Error occurred calling Streamlabs Polly")
def randomvoice(self):
def random_voice(self):
return random.choice(self.voices)

@ -1,5 +1,4 @@
import re
import praw
from praw.models import MoreComments
from prawcore.exceptions import ResponseException
@ -14,9 +13,7 @@ from utils.voice import sanitize_text
def get_subreddit_threads(POST_ID: str):
"""
Returns a list of threads from the AskReddit subreddit.
"""
"""Returns a list of threads from the AskReddit subreddit."""
print_substep("Logging into Reddit.")
@ -40,7 +37,7 @@ def get_subreddit_threads(POST_ID: str):
client_secret=settings.config["reddit"]["creds"]["client_secret"],
user_agent="Accessing Reddit threads",
username=username,
passkey=passkey,
password=passkey,
check_for_async=False,
)
except ResponseException as e:
@ -49,7 +46,10 @@ def get_subreddit_threads(POST_ID: str):
except:
print("Something went wrong...")
# Ask user for subreddit input
min_upvotes = settings.config["reddit"]["thread"]["min_upvotes"]
sort_type = settings.config["reddit"]["thread"]["sort_type"]
subreddit_choice = settings.config["reddit"]["thread"]["subreddit"].lstrip('r/')
print_step("Getting subreddit threads...")
similarity_score = 0
if not settings.config["reddit"]["thread"][
@ -99,9 +99,10 @@ def get_subreddit_threads(POST_ID: str):
threads, subreddit, similarity_scores=similarity_scores
)
else:
threads = subreddit.hot(limit=25)
threads = list(getattr(subreddit, str(sort_type))(limit=25))
submission = get_subreddit_undone(threads, subreddit)
# Check submission
if submission is None:
return get_subreddit_threads(POST_ID) # submission already done. rerun
@ -129,45 +130,43 @@ def get_subreddit_threads(POST_ID: str):
f"Thread has a similarity score up to {round(similarity_score * 100)}%",
style="bold blue",
)
if submission.score < min_upvotes:
print_substep(
f"Thread has {submission.score} upvotes which is below the minimum threshold of {min_upvotes}. Skipping.")
return get_subreddit_threads(POST_ID)
content["thread_url"] = threadurl
content["thread_title"] = submission.title
content["thread_id"] = submission.id
content["is_nsfw"] = submission.over_18
content["comments"] = []
content["thread_post"] = "" # Default value
if settings.config["settings"]["storymode"]:
if settings.config["settings"]["storymodemethod"] == 1:
content["thread_post"] = posttextparser(submission.selftext)
else:
content["thread_post"] = submission.selftext
else:
for top_level_comment in submission.comments:
if isinstance(top_level_comment, MoreComments):
# Process comments
min_comment_length = int(str(settings.config["reddit"]["thread"]["min_comment_length"]))
max_comment_length = int(str(settings.config["reddit"]["thread"]["max_comment_length"]))
min_comment_upvotes = int(str(settings.config["reddit"]["thread"]["min_comment_upvotes"]))
for comment in submission.comments:
if isinstance(comment, MoreComments) or comment.body in ["[removed]", "[deleted]"] or comment.stickied:
continue
sanitised = sanitize_text(comment.body)
if not sanitised or sanitised == " ":
continue
if top_level_comment.body in ["[removed]", "[deleted]"]:
continue # # see https://github.com/JasonLovesDoggo/RedditVideoMakerBot/issues/78
if not top_level_comment.stickied:
sanitised = sanitize_text(top_level_comment.body)
if not sanitised or sanitised == " ":
continue
if len(top_level_comment.body) <= int(
settings.config["reddit"]["thread"]["max_comment_length"]
):
if len(top_level_comment.body) >= int(
settings.config["reddit"]["thread"]["min_comment_length"]
):
if (
top_level_comment.author is not None
and sanitize_text(top_level_comment.body) is not None
): # if errors occur with this change to if not.
content["comments"].append(
{
"comment_body": top_level_comment.body,
"comment_url": top_level_comment.permalink,
"comment_id": top_level_comment.id,
}
)
if min_comment_length <= len(comment.body) <= max_comment_length and comment.score >= min_comment_upvotes:
if comment.author and sanitised:
content["comments"].append({
"comment_body": comment.body,
"comment_url": comment.permalink,
"comment_id": comment.id,
})
print_substep("Received subreddit threads Successfully.", style="bold green")
return content

@ -14,6 +14,8 @@ max_comment_length = { default = 500, optional = false, nmin = 10, nmax = 10000,
min_comment_length = { default = 1, optional = true, nmin = 0, nmax = 10000, type = "int", explanation = "min_comment_length number of characters a comment can have. default is 0", example = 50, oob_error = "the max comment length should be between 1 and 100" }
post_lang = { default = "", optional = true, explanation = "The language you would like to translate to.", example = "es-cr", options = ['','af', 'ak', 'am', 'ar', 'as', 'ay', 'az', 'be', 'bg', 'bho', 'bm', 'bn', 'bs', 'ca', 'ceb', 'ckb', 'co', 'cs', 'cy', 'da', 'de', 'doi', 'dv', 'ee', 'el', 'en', 'en-US', 'eo', 'es', 'et', 'eu', 'fa', 'fi', 'fr', 'fy', 'ga', 'gd', 'gl', 'gn', 'gom', 'gu', 'ha', 'haw', 'hi', 'hmn', 'hr', 'ht', 'hu', 'hy', 'id', 'ig', 'ilo', 'is', 'it', 'iw', 'ja', 'jw', 'ka', 'kk', 'km', 'kn', 'ko', 'kri', 'ku', 'ky', 'la', 'lb', 'lg', 'ln', 'lo', 'lt', 'lus', 'lv', 'mai', 'mg', 'mi', 'mk', 'ml', 'mn', 'mni-Mtei', 'mr', 'ms', 'mt', 'my', 'ne', 'nl', 'no', 'nso', 'ny', 'om', 'or', 'pa', 'pl', 'ps', 'pt', 'qu', 'ro', 'ru', 'rw', 'sa', 'sd', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'st', 'su', 'sv', 'sw', 'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tr', 'ts', 'tt', 'ug', 'uk', 'ur', 'uz', 'vi', 'xh', 'yi', 'yo', 'zh-CN', 'zh-TW', 'zu'] }
min_comments = { default = 20, optional = false, nmin = 10, type = "int", explanation = "The minimum number of comments a post should have to be included. default is 20", example = 29, oob_error = "the minimum number of comments should be between 15 and 999999" }
min_upvotes = { default = 20, optional = false, nmin = 1, type = "int", explanation = "The minimum number of upvotes a post should have to be included. default is 20", example = 29, oob_error = "the minimum number of comments should be between 15 and 999999" }
sort_type = { default = "hot", optional = false, options = ["hot", "top", ], explanation = "the way to sort the reddit threads", example = "top" , oob_error = "the sort type needs to be hot, top, etc." }
[ai]
ai_similarity_enabled = {optional = true, option = [true, false], default = false, type = "bool", explanation = "Threads read from Reddit are sorted based on their similarity to the keywords given below"}
@ -55,3 +57,10 @@ python_voice = { optional = false, default = "1", example = "1", explanation = "
py_voice_num = { optional = false, default = "2", example = "2", explanation = "The number of system voices (2 are pre-installed in Windows)" }
silence_duration = { optional = true, example = "0.1", explanation = "Time in seconds between TTS comments", default = 0.3, type = "float" }
no_emojis = { optional = false, type = "bool", default = false, example = false, options = [true, false,], explanation = "Whether to remove emojis from the comments" }
[settings.codecs]
VCODEC = { optional = false, default = "h264", example = "h264_nvenc", explanation = "The codec used to encode the videos. to find a list of avaliable codecs run ffmpeg " }
VCODECPRESET = { optional = false, default = "slow", example = "fast", explanation = "The codec preset used to encode the videos. to find a list of avaliable codecs run ffmpeg " }
VIDEO_BITRATE = { optional = false, default = "20M", example = "12M", explanation = "The bitrate of the video in megabytes per second ie 50M " }
AUDIO_BITRATE = { optional = false, default = "192k", example = "256k", explanation = "The bitrate of the audio in kilobytes per second ie 256k " }
CPUTHREADS = { optional = false, default = "4", example = "8", type = "int", explanation = "The amount of CPU threads to use during encoding " }

@ -3,18 +3,33 @@ import shutil
from os.path import exists
def _listdir(d): # listdir with full path
return [os.path.join(d, f) for f in os.listdir(d)]
def count_items_in_directory(directory):
"""Count all items (files and subdirectories) in a directory."""
return sum([len(files) for _, _, files in os.walk(directory)])
def cleanup(reddit_id) -> int:
"""Deletes all temporary assets in assets/temp
"""Deletes all temporary assets in temp/
Returns:
int: How many files were deleted
"""
directory = f"../assets/temp/{reddit_id}/"
if exists(directory):
shutil.rmtree(directory)
# Check current working directory
cwd = os.getcwd()
print("Current working directory:", cwd)
directory = os.path.join(cwd, "assets", "temp", reddit_id)
print("Target directory:", directory)
return 1
if not exists(directory):
print("Directory does not exist!")
return 0
count_before_delete = count_items_in_directory(directory)
try:
shutil.rmtree(directory)
print(f"Successfully deleted the directory with {count_before_delete} items!")
return count_before_delete
except Exception as e:
print(f"Error encountered while deleting: {e}")
return 0

@ -137,21 +137,21 @@ def modify_settings(data: dict, config_load, checks: dict):
# Delete background video
def delete_background(key):
# Read backgrounds.json
with open("utils/backgrounds.json", "r", encoding="utf-8") as backgrounds:
data = json.load(backgrounds)
# Read background_videos.json
with open("utils/background_videos.json", "r", encoding="utf-8") as background_videos:
data = json.load(background_videos)
# Remove background from backgrounds.json
with open("utils/backgrounds.json", "w", encoding="utf-8") as backgrounds:
# Remove background from background_videos.json
with open("utils/background_videos.json", "w", encoding="utf-8") as background_videos:
if data.pop(key, None):
json.dump(data, backgrounds, ensure_ascii=False, indent=4)
json.dump(data, background_videos, ensure_ascii=False, indent=4)
else:
flash("Couldn't find this background. Try refreshing the page.", "error")
return
# Remove background video from ".config.template.toml"
config = tomlkit.loads(Path("utils/.config.template.toml").read_text())
config["settings"]["background"]["background_choice"]["options"].remove(key)
config["settings"]["background"]["background_video"]["options"].remove(key)
with Path("utils/.config.template.toml").open("w") as toml_file:
toml_file.write(tomlkit.dumps(config))
@ -193,8 +193,8 @@ def add_background(youtube_uri, filename, citation, position):
filename = filename.replace(" ", "_")
# Check if background doesn't already exist
with open("utils/backgrounds.json", "r", encoding="utf-8") as backgrounds:
data = json.load(backgrounds)
with open("utils/background_videos.json", "r", encoding="utf-8") as background_videos:
data = json.load(background_videos)
# Check if key isn't already taken
if filename in list(data.keys()):
@ -207,7 +207,7 @@ def add_background(youtube_uri, filename, citation, position):
return
# Add background video to json file
with open("utils/backgrounds.json", "r+", encoding="utf-8") as backgrounds:
with open("utils/background_videos.json", "r+", encoding="utf-8") as backgrounds:
data = json.load(backgrounds)
data[filename] = [youtube_uri, filename + ".mp4", citation, position]
@ -216,7 +216,7 @@ def add_background(youtube_uri, filename, citation, position):
# Add background video to ".config.template.toml"
config = tomlkit.loads(Path("utils/.config.template.toml").read_text())
config["settings"]["background"]["background_choice"]["options"].append(filename)
config["settings"]["background"]["background_video"]["options"].append(filename)
with Path("utils/.config.template.toml").open("w") as toml_file:
toml_file.write(tomlkit.dumps(config))

@ -65,12 +65,14 @@ class ProgressFfmpeg(threading.Thread):
def name_normalize(name: str) -> str:
name = re.sub(r'[?\\"%*:|<>]', "", name)
name = re.sub(r"( [w,W]\s?\/\s?[o,O,0])", r" without", name)
name = re.sub(r"( [w,W]\s?\/)", r" with", name)
name = re.sub(r"(\d+)\s?\/\s?(\d+)", r"\1 of \2", name)
name = re.sub(r"(\w+)\s?\/\s?(\w+)", r"\1 or \2", name)
name = re.sub(r"\/", r"", name)
name = re.sub(r'[?\"%*:|<>]', "", name)
# Note: I've changed [w,W] to [wW] and [o,O,0] to [oO0]
name = re.sub(r"([wW])\s?/\s?([oO0])", r"without", name)
name = re.sub(r"([wW])\s?/", r"with", name)
name = re.sub(r"(\d+)\s?/\s?(\d+)", r"\1 of \2", name)
# This one handles "this/that" as "this or that". The words won't overlap with the earlier "w/ or w/o"
name = re.sub(r"(\w+)\s?/\s?(\w+)", r"\1 or \2", name)
name = re.sub(r"/", "", name)
lang = settings.config["reddit"]["thread"]["post_lang"]
if lang:
@ -83,7 +85,7 @@ def name_normalize(name: str) -> str:
return name
def prepare_background(reddit_id: str, W: int, H: int) -> str:
def prepare_background(reddit_id: str, W: int, H: int) -> Tuple[str, float]:
output_path = f"assets/temp/{reddit_id}/background_noaudio.mp4"
output = (
ffmpeg.input(f"assets/temp/{reddit_id}/background.mp4")
@ -92,10 +94,15 @@ def prepare_background(reddit_id: str, W: int, H: int) -> str:
output_path,
an=None,
**{
"c:v": "h264",
"b:v": "20M",
"b:a": "192k",
"threads": multiprocessing.cpu_count(),
# these settings improve the encoding a lot and reduce visual errors in the video
# plus its now super easy to configure your codec settings
"c:v": str(settings.config["settings"]["codecs"]["VCODEC"]),
"preset": str(settings.config["settings"]["codecs"]["VCODECPRESET"]),
"b:v": str(settings.config["settings"]["codecs"]["VIDEO_BITRATE"]),
"b:a": str(settings.config["settings"]["codecs"]["AUDIO_BITRATE"]),
"threads": int(str(settings.config["settings"]["codecs"]["CPUTHREADS"])),
"force_key_frames": "expr:gte(t,n_forced*1)",
"g": 250, # GOP size
},
)
.overwrite_output()
@ -144,8 +151,8 @@ def make_final_video(
background_config (Tuple[str, str, str, Any]): The background config to use.
"""
# settings values
W: Final[int] = int(settings.config["settings"]["resolution_w"])
H: Final[int] = int(settings.config["settings"]["resolution_h"])
W: Final[int] = int(str(settings.config["settings"]["resolution_w"]))
H: Final[int] = int(str(settings.config["settings"]["resolution_h"]))
opacity = settings.config["settings"]["opacity"]
@ -162,33 +169,28 @@ 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'
audio_clips_durations = []
storymode = settings.config.get("settings", {}).get("storymode")
storymodemethod = settings.config.get("settings", {}).get("storymodemethod")
if number_of_clips == 0 and not storymode:
print("No audio clips to gather. Please use a different TTS or post.")
exit()
if settings.config["settings"]["storymode"]:
if settings.config["settings"]["storymodemethod"] == 0:
if storymode:
if 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")
)
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..."
)
]
audio_clips.insert(
0, ffmpeg.input(f"assets/temp/{reddit_id}/mp3/title.mp3")
)
audio_clips.insert(1, ffmpeg.input(f"assets/temp/{reddit_id}/mp3/postaudio.mp3"))
elif storymodemethod == 1:
#i find it weird that I have to increase it by one to make it work, it kind of makes sense but not really
number_of_clips += 1
audio_clips.extend(
[ffmpeg.input(f"assets/temp/{reddit_id}/mp3/postaudio-{i}.mp3") for i in track(range(number_of_clips))])
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)
]
audio_clips.extend([ffmpeg.input(f"assets/temp/{reddit_id}/mp3/{i}.mp3") for i in range(number_of_clips + 1)])
audio_clips.insert(0, ffmpeg.input(f"assets/temp/{reddit_id}/mp3/title.mp3"))
audio_clips_durations = [
@ -228,24 +230,19 @@ def make_final_video(
)
current_time = 0
if settings.config["settings"]["storymode"]:
audio_clips_durations = [
float(
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"
]
),
)
#that logic didnt actually work seeing as the mp3 names are way different for each mode
if settings.config["settings"]["storymodemethod"] == 0:
audio_clips_durations = [
float(
ffmpeg.probe(f"assets/temp/{reddit_id}/mp3/postaudio.mp3")["format"]["duration"]
)
]
audio_clips_durations.insert(
0,
float(ffmpeg.probe(f"assets/temp/{reddit_id}/mp3/title.mp3")["format"]["duration"]),
)
image_clips.insert(
1,
ffmpeg.input(f"assets/temp/{reddit_id}/png/story_content.png").filter(
@ -254,15 +251,27 @@ def make_final_video(
)
background_clip = background_clip.overlay(
image_clips[0],
enable=f"between(t,{current_time},{current_time + audio_clips_durations[0]})",
x="(main_w-overlay_w)/2",
y="(main_h-overlay_h)/2",
)
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..."
):
audio_clips_durations = [
float(
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"]),
)
print("the number of clips originally is: " + str(int(number_of_clips)))
for i in track(range(number_of_clips + 1)):
print("appending image: " + str(i) + ".png")
print("at time :" + str(current_time))
image_clips.append(
ffmpeg.input(f"assets/temp/{reddit_id}/png/img{i}.png")["v"].filter(
"scale", screenshot_width, -1
@ -274,6 +283,7 @@ def make_final_video(
x="(main_w-overlay_w)/2",
y="(main_h-overlay_h)/2",
)
print("Total image clips: " + str(len(image_clips)))
current_time += audio_clips_durations[i]
else:
for i in range(0, number_of_clips + 1):
@ -291,6 +301,11 @@ def make_final_video(
)
current_time += audio_clips_durations[i]
#fade and cut video at appropriate time
total_audio_duration = sum(audio_clips_durations)
background_clip = background_clip.filter('tpad', stop_duration=1)
background_clip = background_clip.filter('fade', type='out', start_time=total_audio_duration, duration=1)
title = re.sub(r"[^\w\s-]", "", reddit_obj["thread_title"])
idx = re.sub(r"[^\w\s-]", "", reddit_obj["thread_id"])
title_thumb = reddit_obj["thread_title"]
@ -346,10 +361,8 @@ def make_final_video(
height,
title_thumb,
)
thumbnailSave.save(f"./assets/temp/{reddit_id}/thumbnail.png")
print_substep(
f"Thumbnail - Building Thumbnail in assets/temp/{reddit_id}/thumbnail.png"
)
thumbnailSave.save(f"results/{subreddit}/thumbnails/{filename}.png")
print_substep(f"Thumbnail - Building Thumbnail in results/{subreddit}/thumbnails/{filename}.png")
text = f"Background by {background_config['video'][2]}"
background_clip = ffmpeg.drawtext(
@ -385,10 +398,13 @@ def make_final_video(
path,
f="mp4",
**{
"c:v": "h264",
"b:v": "20M",
"b:a": "192k",
"threads": multiprocessing.cpu_count(),
"c:v": str(settings.config["settings"]["codecs"]["VCODEC"]),
"preset": str(settings.config["settings"]["codecs"]["VCODECPRESET"]),
"b:v": str(settings.config["settings"]["codecs"]["VIDEO_BITRATE"]),
"b:a": str(settings.config["settings"]["codecs"]["AUDIO_BITRATE"]),
"threads": int(settings.config["settings"]["codecs"]["CPUTHREADS"]),
"g": 250, # GOP size
"force_key_frames": "expr:gte(t,n_forced*1)",
},
).overwrite_output().global_args(
"-progress", progress.output_file.name
@ -417,10 +433,13 @@ def make_final_video(
path,
f="mp4",
**{
"c:v": "h264",
"b:v": "20M",
"b:a": "192k",
"threads": multiprocessing.cpu_count(),
"c:v": str(settings.config["settings"]["codecs"]["VCODEC"]),
"preset": str(settings.config["settings"]["codecs"]["VCODECPRESET"]),
"b:v": str(settings.config["settings"]["codecs"]["VIDEO_BITRATE"]),
"b:a": str(settings.config["settings"]["codecs"]["AUDIO_BITRATE"]),
"threads": int(settings.config["settings"]["codecs"]["CPUTHREADS"]),
"g": 250, # GOP size
"force_key_frames": "expr:gte(t,n_forced*1)",
},
).overwrite_output().global_args(
"-progress", progress.output_file.name

@ -16,6 +16,37 @@ from utils.videos import save_data
__all__ = ["download_screenshots_of_reddit_posts"]
def translate_comment_body(page, comment: dict, lang: str):
"""Translate a Reddit comment and update its content on the page.
Args:
page: The Playwright page object.
comment (dict): The Reddit comment data.
lang (str): The target language to which the comment should be translated.
"""
# Check if language setting is present
if not lang:
return
# Translate the comment body
comment_tl = translators.translate_text(
comment["comment_body"],
translator="google",
to_language=lang
)
# Update the content on the page with the translated text
page.evaluate(
'''(tl_content, tl_id) => {
const commentElement = document.querySelector(`#t1_${tl_id} > div:nth-child(2) > div > div[data-testid="comment"] > div`);
if (commentElement) {
commentElement.textContent = tl_content;
}
}''',
comment_tl, comment["comment_id"]
)
def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
"""Downloads screenshots of reddit posts as seen on the web. Downloads to assets/temp/png
@ -26,8 +57,8 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
# settings values
W: Final[int] = int(settings.config["settings"]["resolution_w"])
H: Final[int] = int(settings.config["settings"]["resolution_h"])
lang: Final[str] = settings.config["reddit"]["thread"]["post_lang"]
storymode: Final[bool] = settings.config["settings"]["storymode"]
lang: Final[str] = str(settings.config["reddit"]["thread"]["post_lang"])
storymode: Final[bool] = bool(settings.config["settings"]["storymode"])
print_step("Downloading screenshots of reddit posts...")
reddit_id = re.sub(r"[^\w\s-]", "", reddit_object["thread_id"])
@ -106,12 +137,8 @@ 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('[name="username"]').fill(
settings.config["reddit"]["creds"]["username"]
)
page.locator('[name="password"]').fill(
settings.config["reddit"]["creds"]["password"]
)
page.locator('[name="username"]').fill(str(settings.config["reddit"]["creds"]["username"]))
page.locator('[name="password"]').fill(str(settings.config["reddit"]["creds"]["password"]))
page.locator("button[class$='m-full-width']").click()
page.wait_for_timeout(5000)
@ -189,14 +216,15 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
# 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] * float(zoom)))
page.screenshot(clip=location, path=postcontentpath)
else:
page.locator('[data-test-id="post-content"]').screenshot(
path=postcontentpath
)
except Exception as e:
print_substep("Something went wrong!", style="red")
print_substep("Something went wrong inside screenshot_downloader!", style="red")
print_substep(f"Error: {str(e)}", style="red")
resp = input(
"Something went wrong with making the screenshots! Do you want to skip the post? (y/n) "
)
@ -238,16 +266,8 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
# translate code
if settings.config["reddit"]["thread"]["post_lang"]:
comment_tl = translators.translate_text(
comment["comment_body"],
translator="google",
to_language=settings.config["reddit"]["thread"]["post_lang"],
)
page.evaluate(
'([tl_content, tl_id]) => document.querySelector(`#t1_${tl_id} > div:nth-child(2) > div > div[data-testid="comment"] > div`).textContent = tl_content',
[comment_tl, comment["comment_id"]],
)
translate_comment_body(page, comment, settings.config["reddit"]["thread"]["post_lang"])
try:
if settings.config["settings"]["zoom"] != 1:
# store zoom settings
@ -263,7 +283,7 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
f"#t1_{comment['comment_id']}"
).bounding_box()
for i in location:
location[i] = float("{:.2f}".format(location[i] * zoom))
location[i] = float("{:.2f}".format(location[i] * float(zoom)))
page.screenshot(
clip=location,
path=f"assets/temp/{reddit_id}/png/comment_{idx}.png",

Loading…
Cancel
Save