From c412155720d3cb6544608e0fed87e364b0a6fa37 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 8 Jul 2022 00:50:15 -0400 Subject: [PATCH] background audio implementation --- .config.template.toml | 5 ++ TTS/engine_wrapper.py | 15 +++-- TTS/streamlabs_polly.py | 4 +- main.py | 2 +- reddit/subreddit.py | 10 ++-- utils/voice.py | 2 +- video_creation/background.py | 2 +- video_creation/final_video.py | 110 +++++++++++----------------------- video_creation/voices.py | 2 +- 9 files changed, 62 insertions(+), 90 deletions(-) diff --git a/.config.template.toml b/.config.template.toml index ddfa293..16a6436 100644 --- a/.config.template.toml +++ b/.config.template.toml @@ -29,10 +29,15 @@ opacity = { optional = false, default = 0.9, example = 0.8, explanation = "Sets storymode = { optional = true, type = "bool", default = false, example = false, options = [true, false, ], explanation = "not yet implemented" } + +[settings.background] 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.)" } +background_audio_volume = { optional = true, type = "float", default = 0.3, example = 0.1, explanation="Sets the volume of the background audio. only used if the background_audio is also set to true" } + + [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" } diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py index df90569..4f3cf1c 100644 --- a/TTS/engine_wrapper.py +++ b/TTS/engine_wrapper.py @@ -47,7 +47,7 @@ class TTSEngine: Path(self.path).mkdir(parents=True, exist_ok=True) - # This file needs to be removed in case this post does not use post text, so that it wont appear in the final video + # This file needs to be removed in case this post does not use post text, so that it won't appear in the final video try: Path(f"{self.path}/posttext.mp3").unlink() except OSError: @@ -67,10 +67,12 @@ class TTSEngine: # ! Stop creating mp3 files if the length is greater than max length. if self.length > self.max_length: break - if not self.tts_module.max_chars: + 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 + else: # If the comment is not too long, just call the tts engine self.call_tts(f"{idx}", comment["comment_body"]) - else: - self.split_post(comment["comment_body"], idx) print_substep("Saved Text to MP3 files successfully.", style="bold green") return self.length, idx @@ -84,9 +86,12 @@ class TTSEngine: ) ] - idy = None for idy, text_cut in enumerate(split_text): # print(f"{idx}-{idy}: {text_cut}\n") + if text_cut == "": + print("Empty text cut: tell the devs about this") + continue + 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( diff --git a/TTS/streamlabs_polly.py b/TTS/streamlabs_polly.py index b7365ab..a5fda6a 100644 --- a/TTS/streamlabs_polly.py +++ b/TTS/streamlabs_polly.py @@ -48,10 +48,12 @@ class StreamlabsPolly: else: try: + print(body) + print(response.json()) voice_data = requests.get(response.json()["speak_url"]) with open(filepath, "wb") as f: f.write(voice_data.content) - except (KeyError, JSONDecodeError): + except (KeyError, JSONDecodeError) as e: try: if response.json()["error"] == "No text specified!": raise ValueError("Please specify a text to convert to speech.") diff --git a/main.py b/main.py index 8ce8725..63e4e84 100755 --- a/main.py +++ b/main.py @@ -32,7 +32,7 @@ print( print_markdown( "### Thanks for using this tool! [Feel free to contribute to this project on GitHub!](https://lewismenelaws.com) If you have any questions, feel free to reach out to me on Twitter or submit a GitHub issue. You can find solutions to many common problems in the [Documentation](https://luka-hietala.gitbook.io/documentation-for-the-reddit-bot/)" ) -print_step(f"You are using V{VERSION} of the bot") +print_step(f"You are using v{VERSION} of the bot") def main(POST_ID=None): diff --git a/reddit/subreddit.py b/reddit/subreddit.py index 829a3a8..716a7fa 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -18,7 +18,7 @@ def get_subreddit_threads(POST_ID: str): print_substep("Logging into Reddit.") content = {} - if settings.config["reddit"]["creds"]["2fa"] == True: + if settings.config["reddit"]["creds"]["2fa"]: print("\nEnter your two-factor authentication code from your authenticator app.\n") code = input("> ") print() @@ -27,7 +27,7 @@ def get_subreddit_threads(POST_ID: str): else: passkey = settings.config["reddit"]["creds"]["password"] username = settings.config["reddit"]["creds"]["username"] - if username.casefold().startswith("u/"): + if str(username).casefold().startswith("u/"): username = username[2:] reddit = praw.Reddit( client_id=settings.config["reddit"]["creds"]["client_id"], @@ -55,7 +55,7 @@ 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 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 @@ -65,11 +65,10 @@ def get_subreddit_threads(POST_ID: str): submission = reddit.submission(id=POST_ID) elif ( settings.config["reddit"]["thread"]["post_id"] - and len(settings.config["reddit"]["thread"]["post_id"].split("+")) == 1 + and len(str(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) submission = check_done(submission) # double-checking @@ -104,6 +103,7 @@ def get_subreddit_threads(POST_ID: str): ): 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( { diff --git a/utils/voice.py b/utils/voice.py index 4a77833..a0709fa 100644 --- a/utils/voice.py +++ b/utils/voice.py @@ -36,7 +36,7 @@ def sleep_until(time): # Convert datetime to unix timestamp and adjust for locality if isinstance(time, datetime): - # If we're on Python 3 and the user specified a timezone, convert to UTC and get tje timestamp. + # If we're on Python 3 and the user specified a timezone, convert to UTC and get the timestamp. if sys.version_info[0] >= 3 and time.tzinfo: end = time.astimezone(timezone.utc).timestamp() else: diff --git a/video_creation/background.py b/video_creation/background.py index 7ef4321..a6efc8b 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -64,7 +64,7 @@ def get_start_and_end_times(video_length: int, length_of_clip: int) -> Tuple[int def get_background_config(): """Fetch the background/s configuration""" try: - choice = str(settings.config["settings"]["background_choice"]).casefold() + choice = str(settings.config["settings"]["background"]["background_choice"]).casefold() except AttributeError: print_substep("No background selected. Picking random background'") choice = None diff --git a/video_creation/final_video.py b/video_creation/final_video.py index f1e1f96..bccbd0a 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -3,30 +3,22 @@ import multiprocessing import os import re from os.path import exists -from typing import Dict, Tuple, Any - +from typing import Tuple, Any import translators as ts -from moviepy.editor import ( - VideoFileClip, - AudioFileClip, - ImageClip, - concatenate_videoclips, - concatenate_audioclips, - CompositeAudioClip, - CompositeVideoClip, -) +from moviepy.editor import (VideoFileClip, AudioFileClip, ImageClip, concatenate_videoclips, concatenate_audioclips, + CompositeAudioClip, CompositeVideoClip, ) from moviepy.video.io.ffmpeg_tools import ffmpeg_merge_video_audio, ffmpeg_extract_subclip from rich.console import Console +import moviepy.editor as mpe from utils.cleanup import cleanup from utils.console import print_step, print_substep from utils.videos import save_data from utils import settings - console = Console() - +VOLUME_MULTIPLIER = settings.config["settings"]['background']["background_audio_volume"] W, H = 1080, 1920 @@ -48,9 +40,7 @@ def name_normalize(name: str) -> str: return name -def make_final_video( - number_of_clips: int, length: int, reddit_obj: dict, background_config: Tuple[str, str, str, Any] -): +def make_final_video(number_of_clips: int, length: int, reddit_obj: dict, background_config: Tuple[str, str, str, Any]): """Gathers audio clips, gathers all screenshots, stitches them together and saves the final video to assets/temp Args: number_of_clips (int): Index to end at when going through the screenshots' @@ -63,11 +53,8 @@ def make_final_video( VideoFileClip.reH = lambda clip: clip.resize(width=H) opacity = settings.config["settings"]["opacity"] background_clip = ( - VideoFileClip("assets/temp/background.mp4") - .without_audio() - .resize(height=H) - .crop(x1=1166.6, y1=0, x2=2246.6, y2=1920) - ) + VideoFileClip("assets/temp/background.mp4").without_audio().resize(height=H).crop(x1=1166.6, y1=0, x2=2246.6, + y2=1920)) # Gather all audio clips audio_clips = [AudioFileClip(f"assets/temp/mp3/{i}.mp3") for i in range(number_of_clips)] @@ -80,21 +67,13 @@ def make_final_video( image_clips = [] # Gather all images new_opacity = 1 if opacity is None or float(opacity) >= 1 else float(opacity) - image_clips.insert( - 0, - ImageClip("assets/temp/png/title.png") - .set_duration(audio_clips[0].duration) - .resize(width=W - 100) - .set_opacity(new_opacity), - ) + image_clips.insert(0, ImageClip("assets/temp/png/title.png").set_duration(audio_clips[0].duration).resize( + width=W - 100).set_opacity(new_opacity), ) for i in range(0, number_of_clips): image_clips.append( - ImageClip(f"assets/temp/png/comment_{i}.png") - .set_duration(audio_clips[i + 1].duration) - .resize(width=W - 100) - .set_opacity(new_opacity) - ) + ImageClip(f"assets/temp/png/comment_{i}.png").set_duration(audio_clips[i + 1].duration).resize( + width=W - 100).set_opacity(new_opacity)) # if os.path.exists("assets/mp3/posttext.mp3"): # image_clips.insert( @@ -122,51 +101,32 @@ def make_final_video( print_substep("The results folder didn't exist so I made it") os.makedirs(f"./results/{subreddit}") - final.write_videofile( - "assets/temp/temp.mp4", - fps=30, - audio_codec="aac", - audio_bitrate="192k", - verbose=False, - threads=multiprocessing.cpu_count(), - ) - if settings.config["settings"]["background_audio"]: - print("[bold green] Merging background audio with video") - if not exists(f"assets/backgrounds/background.mp3"): - print_substep( - "Cannot find assets/backgrounds/background.mp3 audio file didn't so skipping." - ) - ffmpeg_extract_subclip( - "assets/temp/temp.mp4", - 0, - final.duration, - targetname=f"results/{subreddit}/{filename}", - ) - else: - ffmpeg_merge_video_audio( - "assets/temp/temp.mp4", - "assets/backgrounds/background.mp3", - "assets/temp/temp_audio.mp4", - ) - ffmpeg_extract_subclip( # check if this gets run - "assets/temp/temp_audio.mp4", - 0, - final.duration, - targetname=f"results/{subreddit}/{filename}", - ) + final.write_videofile("assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k", verbose=False, + threads=multiprocessing.cpu_count(), ) + if settings.config["settings"]['background']["background_audio"] and exists(f"assets/backgrounds/background.mp3"): + if not isinstance(VOLUME_MULTIPLIER, float): + print("No background audio volume set, using default of .3 set it in the config.toml file") + assert VOLUME_MULTIPLIER == float(0.3) + print('Merging background audio with video') + my_clip = mpe.VideoFileClip('assets/temp/temp.mp4') + audio_background = AudioFileClip("assets/backgrounds/background.mp3") + lowered_audio = audio_background.multiply_volume( + VOLUME_MULTIPLIER) # lower volume by background_audio_volume, use with fx + lowered_audio = lowered_audio.subclip(0, my_clip.duration) # trim the audio to the length of the video + lowered_audio.set_duration(my_clip.duration) # set the duration of the audio to the length of the video + final_audio = mpe.CompositeAudioClip([my_clip.audio, lowered_audio]) + final_clip = my_clip.set_audio(final_audio) + + final_clip.write_videofile("assets/temp/temp_audio.mp4", fps=30, audio_codec="aac", audio_bitrate="192k", + verbose=False, threads=multiprocessing.cpu_count()) + ffmpeg_extract_subclip( # check if this gets run + "assets/temp/temp_audio.mp4", 0, final.duration, targetname=f"results/{subreddit}/{filename}", ) else: - print("debug duck") - ffmpeg_extract_subclip( - "assets/temp/temp.mp4", - 0, - final.duration, - targetname=f"results/{subreddit}/{filename}", - ) + ffmpeg_extract_subclip("assets/temp/temp.mp4", 0, final.duration, + targetname=f"results/{subreddit}/{filename}", ) print_step("Removing temporary files 🗑") cleanups = cleanup() print_substep(f"Removed {cleanups} temporary files 🗑") print_substep("See result in the results folder!") - print_step( - f'Reddit title: {reddit_obj["thread_title"]} \n Background Credit: {background_config[2]}' - ) + print_step(f'Reddit title: {reddit_obj["thread_title"]} \n Background Credit: {background_config[2]}') diff --git a/video_creation/voices.py b/video_creation/voices.py index ffc0898..74894eb 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -34,7 +34,7 @@ def save_text_to_mp3(reddit_obj) -> Tuple[int, int]: """ voice = settings.config["settings"]["tts"]["choice"] - if voice.casefold() in map(lambda _: _.casefold(), TTSProviders): + if str(voice).casefold() in map(lambda _: _.casefold(), TTSProviders): text_to_mp3 = TTSEngine(get_case_insensitive_key_value(TTSProviders, voice), reddit_obj) else: while True: