diff --git a/.config.template.toml b/.config.template.toml index ddfa293..ceb59c2 100644 --- a/.config.template.toml +++ b/.config.template.toml @@ -1,40 +1,31 @@ [reddit.creds] -client_id = { optional = false, nmin = 12, nmax = 30, explanation = "the ID of your Reddit app of SCRIPT type", example = "fFAGRNJru1FTz70BzhT3Zg", regex = "^[-a-zA-Z0-9._~+/]+=*$", input_error = "The client ID can only contain printable characters.", oob_error = "The ID should be over 12 and under 30 characters, double check your input." } -client_secret = { optional = false, nmin = 20, nmax = 40, explanation = "the SECRET of your Reddit app of SCRIPT type", example = "fFAGRNJru1FTz70BzhT3Zg", regex = "^[-a-zA-Z0-9._~+/]+=*$", input_error = "The client ID can only contain printable characters.", oob_error = "The secret should be over 20 and under 40 characters, double check your input." } -username = { optional = false, nmin = 3, nmax = 20, explanation = "the username of your reddit account", example = "JasonLovesDoggo", regex = "^[-_0-9a-zA-Z]+$", oob_error = "A username HAS to be between 3 and 20 characters" } -password = { optional = false, nmin = 8, explanation = "the password of your reddit account", example = "fFAGRNJru1FTz70BzhT3Zg", oob_error = "Password too short" } -2fa = { optional = true, type = "bool", options = [true, - false, -], default = false, explanation = "Whether you have Reddit 2FA enabled, Valid options are True and False", example = true } - +client_id = { optional = false, example = "fFAGRNJru1FTz70BzhT3Zg", explanation = "the ID of your Reddit app of SCRIPT type", default = "", nmin = 12, nmax = 30, regex = "^[-a-zA-Z0-9._~+/]+=*$", oob_error = "The ID should be over 12 and under 30 characters, double check your input.", input_error = "The client ID can only contain printable characters." } +client_secret = { optional = false, example = "fFAGRNJru1FTz70BzhT3Zg", explanation = "the SECRET of your Reddit app of SCRIPT type", default = "", nmin = 20, nmax = 40, regex = "^[-a-zA-Z0-9._~+/]+=*$", oob_error = "The secret should be over 20 and under 40 characters, double check your input.", input_error = "The client ID can only contain printable characters." } +username = { optional = false, example = "JasonLovesDoggo", explanation = "the username of your reddit account", default = "", nmin = 3, nmax = 20,regex = "^[-_0-9a-zA-Z]+$", oob_error = "A username HAS to be between 3 and 20 characters" } +password = { optional = false, example = "fFAGRNJru1FTz70BzhT3Zg", explanation = "the password of your reddit account", default = "", nmin = 8, oob_error = "Password too short" } +2fa = { optional = true, example = "True", explanation = "Whether you have Reddit 2FA enabled, Valid options are True and False", default = false, options = [true, false], type = "bool" } [reddit.thread] -random = { optional = true, options = [true, - false, -], default = false, type = "bool", explanation = "If set to no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: 'False'", example = "True" } -subreddit = { optional = false, regex = "[_0-9a-zA-Z]+$", nmin = 3, nmax = 21, explanation = "what subreddit to pull posts from, the name of the sub, not the URL", example = "AskReddit", oob_error = "A subreddit name HAS to be between 3 and 20 characters" } -post_id = { optional = true, default = "", regex = "^((?!://|://)[+a-zA-Z])*$", explanation = "Used if you want to use a specific post.", example = "urdtfx" } -max_comment_length = { default = 500, optional = false, nmin = 10, nmax = 10000, type = "int", explanation = "max number of characters a comment can have. default is 500", example = 500, oob_error = "the max comment length should be between 10 and 10000" } -post_lang = { default = "", optional = true, explanation = "The language you would like to translate to.", example = "es-cr" } +random = { optional = true, example = "True", explanation = "If set to false, it will ask you a thread link to extract the thread, if true it will randomize it.", default = true, options = [true, false], type = "bool" } +subreddit = { optional = false, example = "AskReddit", explanation = "what subreddit to pull posts from, the name of the sub, not the URL", default = "", nmin = 3, nmax = 21,regex = "[_0-9a-zA-Z]+$", oob_error = "A subreddit name HAS to be between 3 and 20 characters" } +post_id = { optional = true, example = "urdtfx", explanation = "Used if you want to use a specific post.", regex = "^((?!://|://)[+a-zA-Z])*$" } +max_comment_length = { optional = false, example = 500, explanation = "max number of characters a comment can have. default is 500", default = 500, type = "int", nmin = 10, nmax = 10000, oob_error = "the max comment length should be between 10 and 10000" } +post_lang = { optional = true, example = "es-cr", explanation = "The language you would like to translate to.", default = "" } +sort = { optional = true, example = "hot ,top, relevance, new", explanation = "method of sorting threads", default = "top", options = ["hot", "top", "relevance", "new"] } +sort_time = { optional = true, example = "day, hour, week, month, year, all", explanation = "time range to gather threads from", default = "all", options = ["day", "hour", "week", "month", "year", "all"] } [settings] -allow_nsfw = { optional = false, type = "bool", default = false, example = false, options = [true, - false, -], explanation = "Whether to allow NSFW content, True or False" } -theme = { optional = false, default = "dark", example = "light", options = ["dark", - "light", -], explanation = "sets the Reddit theme, either LIGHT or DARK" } -times_to_run = { optional = false, default = 1, example = 2, explanation = "used if you want to run multiple times. set to an int e.g. 4 or 29 or 1", type = "int", nmin = 1, oob_error = "It's very hard to run something less than once." } -opacity = { optional = false, default = 0.9, example = 0.8, explanation = "Sets the opacity of the comments when overlayed over the background", type = "float", nmin = 0, nmax = 1, oob_error = "The opacity HAS to be between 0 and 1", input_error = "The opacity HAS to be a decimal number between 0 and 1" } -storymode = { optional = true, type = "bool", default = false, example = false, options = [true, - false, -], explanation = "not yet implemented" } -background_choice = { optional = true, default = "minecraft", example = "minecraft", options = ["minecraft", "gta", "rocket-league", "motor-gta", ""], explanation = "Sets the background for the video" } -background_audio = { optional = true, type = "bool", default = false, example = false, options = [true, - false, -], explaination="Sets a audio to play in the background (put a background.mp3 file in the assets/backgrounds directory for it to be used.)" } +allow_nsfw = { optional = false, example = "false", explanation = "Whether to allow NSFW content, True or False", default = false, options = [true, false], type = "bool" } +theme = { optional = false, example = "light, dark", explanation = "sets the Reddit theme, either LIGHT or DARK", default = "dark", options = ["dark", "light"] } +times_to_run = { optional = false, example = 2, explanation = "used if you want to run multiple times. set to an int e.g. 4 or 29 or 1", default = 1, type = "int", nmin = 1, oob_error = "It's very hard to run something less than once." } +opacity = { optional = false, example = 0.8, explanation = "Sets the opacity of the comments when overlayed over the background", default = 0.9, type = "float", nmin = 0, nmax = 1, oob_error = "The opacity HAS to be between 0 and 1", input_error = "The opacity HAS to be a decimal number between 0 and 1" } +storymode = { optional = true, example = false, explanation = "not yet implemented", default = false, options = [true, false], type = "bool" } +background_choice = { optional = true, example = "minecraft", explanation = "Sets the background for the video", default = "minecraft", options = ["minecraft", "gta", "rocket-league", "motor-gta",] } +background_audio = { optional = true, example = false, explaination = "Sets a audio to play in the background (put a background.mp3 file in the assets/backgrounds directory for it to be used.)", default = false, options = [true, false], type = "bool" } + [settings.tts] -choice = { optional = false, default = "", options = ["streamlabspolly", "tiktok", "googletranslate", "awspolly", ], example = "streamlabspolly", explanation = "The backend used for TTS generation. This can be left blank and you will be prompted to choose at runtime." } -aws_polly_voice = { optional = false, default = "Matthew", example = "Matthew", explanation = "The voice used for AWS Polly" } -streamlabs_polly_voice = { optional = false, default = "Matthew", example = "Matthew", explanation = "The voice used for Streamlabs Polly" } -tiktok_voice = { optional = false, default = "en_us_006", example = "en_us_006", explanation = "The voice used for TikTok TTS" } +choice = { optional = false, example = "streamlabspolly", explanation = "The backend used for TTS generation. This can be left blank and you will be prompted to choose at runtime.", default = "", options = ["streamlabspolly", "tiktok", "googletranslate", "awspolly", "blank"] } +aws_polly_voice = { optional = false, example = "Matthew", explanation = "The voice used for AWS Polly", default = "Matthew" } +streamlabs_polly_voice = { optional = false, example = "Matthew", explanation = "The voice used for Streamlabs Polly", default = "Matthew" } +tiktok_voice = { optional = false, example = "en_us_006", explanation = "The voice used for TikTok TTS", default = "en_us_006" } +silence_duration = { optional = true, example = "0.1", explanation = "time in seconds between TTS comments", default = 0.3, type = "float" } diff --git a/TTS/TikTok.py b/TTS/TikTok.py index 743118c..8b2e487 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -65,7 +65,7 @@ class TikTok: # TikTok Text-to-Speech Wrapper self.URI_BASE = ( "https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/?text_speaker=" ) - self.max_chars = 300 + self.max_chars = 270 self.voices = {"human": human, "nonhuman": nonhuman, "noneng": noneng} def run(self, text, filepath, random_voice: bool = False): diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py index a171db7..f50e29f 100644 --- a/TTS/engine_wrapper.py +++ b/TTS/engine_wrapper.py @@ -6,6 +6,11 @@ import re # import sox # from mutagen import MutagenError # from mutagen.mp3 import MP3, HeaderNotFoundError +import os + +import numpy as np +from moviepy.audio.AudioClip import AudioClip +from moviepy.audio.fx.volumex import volumex import translators as ts from rich.progress import track from moviepy.editor import AudioFileClip, CompositeAudioClip, concatenate_audioclips @@ -13,7 +18,7 @@ from utils.console import print_step, print_substep from utils.voice import sanitize_text from utils import settings -DEFUALT_MAX_LENGTH: int = 50 # video length variable +DEFAULT_MAX_LENGTH: int = 45 # video length variable class TTSEngine: @@ -35,7 +40,7 @@ class TTSEngine: tts_module, reddit_object: dict, path: str = "assets/temp/mp3", - max_length: int = DEFUALT_MAX_LENGTH, + max_length: int = DEFAULT_MAX_LENGTH, ): self.tts_module = tts_module() self.reddit_object = reddit_object @@ -76,30 +81,37 @@ class TTSEngine: return self.length, idx def split_post(self, text: str, idx: int): - split_files = [] split_text = [ x.group().strip() for x in re.finditer(rf" *((.{{0,{self.tts_module.max_chars}}})(\.|.$))", text) ] + try: + silence_duration = settings.config["settings"]["tts"]["silence_duration"] + except ValueError: + silence_duration = 0.3 + + silence_long = AudioClip(make_frame=lambda t: np.sin(440 * 2 * np.pi * t), duration=silence_duration, fps=44100) + silence_long_new = volumex(silence_long, 0) + silence_long_new.write_audiofile(f"{self.path}/long_silence.mp3", fps=44100, verbose=False, logger=None) idy = None for idy, text_cut in enumerate(split_text): # print(f"{idx}-{idy}: {text_cut}\n") self.call_tts(f"{idx}-{idy}.part", text_cut) - split_files.append(AudioFileClip(f"{self.path}/{idx}-{idy}.part.mp3")) - CompositeAudioClip([concatenate_audioclips(split_files)]).write_audiofile( - f"{self.path}/{idx}.mp3", fps=44100, verbose=False, logger=None - ) - for i in split_files: - name = i.filename - i.close() - Path(name).unlink() + with open(f"{self.path}/list.txt", 'w') as f: + for newy in range(0, len(split_text)): + f.write("file " + f"'{idx}-{newy}.part.mp3'"+"\n") + f.write("file " + f"'long_silence.mp3'"+"\n") + + os.system("ffmpeg -f concat -y -hide_banner -loglevel panic -safe 0 " + + "-i " + f"{self.path}/list.txt " + + "-c copy " + f"{self.path}/{idx}.mp3") - # for i in range(0, idy + 1): - # print(f"Cleaning up {self.path}/{idx}-{i}.part.mp3") - # Path(f"{self.path}/{idx}-{i}.part.mp3").unlink() + for i in range(0, idy + 1): + # print(f"Cleaning up {self.path}/{idx}-{i}.part.mp3") + Path(f"{self.path}/{idx}-{i}.part.mp3").unlink() def call_tts(self, filename: str, text: str): self.tts_module.run(text=process_text(text), filepath=f"{self.path}/{filename}.mp3") diff --git a/reddit/subreddit.py b/reddit/subreddit.py index b64a52a..c807df1 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -62,16 +62,29 @@ def get_subreddit_threads(POST_ID: str): if POST_ID: # would only be called if there are multiple queued posts submission = reddit.submission(id=POST_ID) - elif ( - settings.config["reddit"]["thread"]["post_id"] - and len(settings.config["reddit"]["thread"]["post_id"].split("+")) == 1 - ): + elif (settings.config["reddit"]["thread"]["post_id"] + and len(settings.config["reddit"]["thread"]["post_id"].split("+")) == 1): submission = reddit.submission(id=settings.config["reddit"]["thread"]["post_id"]) - else: - threads = subreddit.hot(limit=25) - submission = get_subreddit_undone(threads, subreddit) + comment_type = settings.config["reddit"]["thread"]["sort"] + + try: + if str(comment_type) == "top": + threads = subreddit.top(limit=25) + elif str(comment_type) == "new": + threads = subreddit.new(limit=25) + elif str(comment_type) == "hot": + threads = subreddit.hot(limit=25) + elif str(comment_type) == "relevance": + threads = subreddit.relevance(limit=25) + else: + threads = subreddit.top(limit=25) + except AttributeError: + threads = subreddit.top(limit=25) + + submission = get_subreddit_undone(threads, subreddit) submission = check_done(submission) # double-checking + if submission is None or not submission.num_comments: return get_subreddit_threads(POST_ID) # submission already done. rerun upvotes = submission.score diff --git a/requirements.txt b/requirements.txt index 8b377c2..a17c9f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ requests==2.28.1 rich==12.4.4 toml==0.10.2 translators==5.3.1 +numpy~=1.23.0 \ No newline at end of file diff --git a/utils/subreddit.py b/utils/subreddit.py index 48dceba..ac3a275 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -34,6 +34,9 @@ def get_subreddit_undone(submissions: list, subreddit): if submission.stickied: print_substep("This post was pinned by moderators. Skipping...") continue + if submission.num_comments == 0: + print_substep("This post has 0 comments. Skipping...") + continue return submission print("all submissions have been done going by top submission order") return get_subreddit_undone( diff --git a/video_creation/final_video.py b/video_creation/final_video.py index f1e1f96..7853014 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -38,6 +38,8 @@ def name_normalize(name: str) -> str: name = re.sub(r"(\w+)\s?\/\s?(\w+)", r"\1 or \2", name) name = re.sub(r"\/", r"", name) + name = name[0:140] + lang = settings.config["reddit"]["thread"]["post_lang"] if lang: print_substep("Translating filename...")