background audio implementation

pull/880/head
Jason 3 years ago
parent a4149e1d12
commit c412155720

@ -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, storymode = { optional = true, type = "bool", default = false, example = false, options = [true,
false, false,
], explanation = "not yet implemented" } ], 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_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, background_audio = { optional = true, type = "bool", default = false, example = false, options = [true,
false, 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.)" } ], 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] [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." } 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" } aws_polly_voice = { optional = false, default = "Matthew", example = "Matthew", explanation = "The voice used for AWS Polly" }

@ -47,7 +47,7 @@ class TTSEngine:
Path(self.path).mkdir(parents=True, exist_ok=True) 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: try:
Path(f"{self.path}/posttext.mp3").unlink() Path(f"{self.path}/posttext.mp3").unlink()
except OSError: except OSError:
@ -67,10 +67,12 @@ class TTSEngine:
# ! Stop creating mp3 files if the length is greater than max length. # ! Stop creating mp3 files if the length is greater than max length.
if self.length > self.max_length: if self.length > self.max_length:
break 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"]) 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") print_substep("Saved Text to MP3 files successfully.", style="bold green")
return self.length, idx return self.length, idx
@ -84,9 +86,12 @@ class TTSEngine:
) )
] ]
idy = None
for idy, text_cut in enumerate(split_text): for idy, text_cut in enumerate(split_text):
# print(f"{idx}-{idy}: {text_cut}\n") # 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) self.call_tts(f"{idx}-{idy}.part", text_cut)
split_files.append(AudioFileClip(f"{self.path}/{idx}-{idy}.part.mp3")) split_files.append(AudioFileClip(f"{self.path}/{idx}-{idy}.part.mp3"))
CompositeAudioClip([concatenate_audioclips(split_files)]).write_audiofile( CompositeAudioClip([concatenate_audioclips(split_files)]).write_audiofile(

@ -48,10 +48,12 @@ class StreamlabsPolly:
else: else:
try: try:
print(body)
print(response.json())
voice_data = requests.get(response.json()["speak_url"]) voice_data = requests.get(response.json()["speak_url"])
with open(filepath, "wb") as f: with open(filepath, "wb") as f:
f.write(voice_data.content) f.write(voice_data.content)
except (KeyError, JSONDecodeError): except (KeyError, JSONDecodeError) as e:
try: try:
if response.json()["error"] == "No text specified!": if response.json()["error"] == "No text specified!":
raise ValueError("Please specify a text to convert to speech.") raise ValueError("Please specify a text to convert to speech.")

@ -32,7 +32,7 @@ print(
print_markdown( 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/)" "### 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): def main(POST_ID=None):

@ -18,7 +18,7 @@ def get_subreddit_threads(POST_ID: str):
print_substep("Logging into Reddit.") print_substep("Logging into Reddit.")
content = {} 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") print("\nEnter your two-factor authentication code from your authenticator app.\n")
code = input("> ") code = input("> ")
print() print()
@ -27,7 +27,7 @@ def get_subreddit_threads(POST_ID: str):
else: else:
passkey = settings.config["reddit"]["creds"]["password"] passkey = settings.config["reddit"]["creds"]["password"]
username = settings.config["reddit"]["creds"]["username"] username = settings.config["reddit"]["creds"]["username"]
if username.casefold().startswith("u/"): if str(username).casefold().startswith("u/"):
username = username[2:] username = username[2:]
reddit = praw.Reddit( reddit = praw.Reddit(
client_id=settings.config["reddit"]["creds"]["client_id"], 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"] sub = settings.config["reddit"]["thread"]["subreddit"]
print_substep(f"Using subreddit: r/{sub} from TOML config") print_substep(f"Using subreddit: r/{sub} from TOML config")
subreddit_choice = sub 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_choice = subreddit_choice[2:]
subreddit = reddit.subreddit( subreddit = reddit.subreddit(
subreddit_choice subreddit_choice
@ -65,11 +65,10 @@ def get_subreddit_threads(POST_ID: str):
submission = reddit.submission(id=POST_ID) submission = reddit.submission(id=POST_ID)
elif ( elif (
settings.config["reddit"]["thread"]["post_id"] 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"]) submission = reddit.submission(id=settings.config["reddit"]["thread"]["post_id"])
else: else:
threads = subreddit.hot(limit=25) threads = subreddit.hot(limit=25)
submission = get_subreddit_undone(threads, subreddit) submission = get_subreddit_undone(threads, subreddit)
submission = check_done(submission) # double-checking submission = check_done(submission) # double-checking
@ -104,6 +103,7 @@ def get_subreddit_threads(POST_ID: str):
): ):
if ( if (
top_level_comment.author is not None 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. ): # if errors occur with this change to if not.
content["comments"].append( content["comments"].append(
{ {

@ -36,7 +36,7 @@ def sleep_until(time):
# Convert datetime to unix timestamp and adjust for locality # Convert datetime to unix timestamp and adjust for locality
if isinstance(time, datetime): 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: if sys.version_info[0] >= 3 and time.tzinfo:
end = time.astimezone(timezone.utc).timestamp() end = time.astimezone(timezone.utc).timestamp()
else: else:

@ -64,7 +64,7 @@ def get_start_and_end_times(video_length: int, length_of_clip: int) -> Tuple[int
def get_background_config(): def get_background_config():
"""Fetch the background/s configuration""" """Fetch the background/s configuration"""
try: try:
choice = str(settings.config["settings"]["background_choice"]).casefold() choice = str(settings.config["settings"]["background"]["background_choice"]).casefold()
except AttributeError: except AttributeError:
print_substep("No background selected. Picking random background'") print_substep("No background selected. Picking random background'")
choice = None choice = None

@ -3,30 +3,22 @@ import multiprocessing
import os import os
import re import re
from os.path import exists from os.path import exists
from typing import Dict, Tuple, Any from typing import Tuple, Any
import translators as ts import translators as ts
from moviepy.editor import ( from moviepy.editor import (VideoFileClip, AudioFileClip, ImageClip, concatenate_videoclips, concatenate_audioclips,
VideoFileClip, CompositeAudioClip, CompositeVideoClip, )
AudioFileClip,
ImageClip,
concatenate_videoclips,
concatenate_audioclips,
CompositeAudioClip,
CompositeVideoClip,
)
from moviepy.video.io.ffmpeg_tools import ffmpeg_merge_video_audio, ffmpeg_extract_subclip from moviepy.video.io.ffmpeg_tools import ffmpeg_merge_video_audio, ffmpeg_extract_subclip
from rich.console import Console from rich.console import Console
import moviepy.editor as mpe
from utils.cleanup import cleanup from utils.cleanup import cleanup
from utils.console import print_step, print_substep from utils.console import print_step, print_substep
from utils.videos import save_data from utils.videos import save_data
from utils import settings from utils import settings
console = Console() console = Console()
VOLUME_MULTIPLIER = settings.config["settings"]['background']["background_audio_volume"]
W, H = 1080, 1920 W, H = 1080, 1920
@ -48,9 +40,7 @@ def name_normalize(name: str) -> str:
return name return name
def make_final_video( def make_final_video(number_of_clips: int, length: int, reddit_obj: dict, background_config: Tuple[str, str, str, Any]):
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 """Gathers audio clips, gathers all screenshots, stitches them together and saves the final video to assets/temp
Args: Args:
number_of_clips (int): Index to end at when going through the screenshots' 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) VideoFileClip.reH = lambda clip: clip.resize(width=H)
opacity = settings.config["settings"]["opacity"] opacity = settings.config["settings"]["opacity"]
background_clip = ( background_clip = (
VideoFileClip("assets/temp/background.mp4") VideoFileClip("assets/temp/background.mp4").without_audio().resize(height=H).crop(x1=1166.6, y1=0, x2=2246.6,
.without_audio() y2=1920))
.resize(height=H)
.crop(x1=1166.6, y1=0, x2=2246.6, y2=1920)
)
# Gather all audio clips # Gather all audio clips
audio_clips = [AudioFileClip(f"assets/temp/mp3/{i}.mp3") for i in range(number_of_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 = [] image_clips = []
# Gather all images # Gather all images
new_opacity = 1 if opacity is None or float(opacity) >= 1 else float(opacity) new_opacity = 1 if opacity is None or float(opacity) >= 1 else float(opacity)
image_clips.insert( image_clips.insert(0, ImageClip("assets/temp/png/title.png").set_duration(audio_clips[0].duration).resize(
0, width=W - 100).set_opacity(new_opacity), )
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): for i in range(0, number_of_clips):
image_clips.append( image_clips.append(
ImageClip(f"assets/temp/png/comment_{i}.png") ImageClip(f"assets/temp/png/comment_{i}.png").set_duration(audio_clips[i + 1].duration).resize(
.set_duration(audio_clips[i + 1].duration) width=W - 100).set_opacity(new_opacity))
.resize(width=W - 100)
.set_opacity(new_opacity)
)
# if os.path.exists("assets/mp3/posttext.mp3"): # if os.path.exists("assets/mp3/posttext.mp3"):
# image_clips.insert( # image_clips.insert(
@ -122,51 +101,32 @@ def make_final_video(
print_substep("The results folder didn't exist so I made it") print_substep("The results folder didn't exist so I made it")
os.makedirs(f"./results/{subreddit}") os.makedirs(f"./results/{subreddit}")
final.write_videofile( final.write_videofile("assets/temp/temp.mp4", fps=30, audio_codec="aac", audio_bitrate="192k", verbose=False,
"assets/temp/temp.mp4", threads=multiprocessing.cpu_count(), )
fps=30, if settings.config["settings"]['background']["background_audio"] and exists(f"assets/backgrounds/background.mp3"):
audio_codec="aac", if not isinstance(VOLUME_MULTIPLIER, float):
audio_bitrate="192k", print("No background audio volume set, using default of .3 set it in the config.toml file")
verbose=False, assert VOLUME_MULTIPLIER == float(0.3)
threads=multiprocessing.cpu_count(), print('Merging background audio with video')
) my_clip = mpe.VideoFileClip('assets/temp/temp.mp4')
if settings.config["settings"]["background_audio"]: audio_background = AudioFileClip("assets/backgrounds/background.mp3")
print("[bold green] Merging background audio with video") lowered_audio = audio_background.multiply_volume(
if not exists(f"assets/backgrounds/background.mp3"): VOLUME_MULTIPLIER) # lower volume by background_audio_volume, use with fx
print_substep( lowered_audio = lowered_audio.subclip(0, my_clip.duration) # trim the audio to the length of the video
"Cannot find assets/backgrounds/background.mp3 audio file didn't so skipping." 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])
ffmpeg_extract_subclip( final_clip = my_clip.set_audio(final_audio)
"assets/temp/temp.mp4",
0, final_clip.write_videofile("assets/temp/temp_audio.mp4", fps=30, audio_codec="aac", audio_bitrate="192k",
final.duration, verbose=False, threads=multiprocessing.cpu_count())
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 ffmpeg_extract_subclip( # check if this gets run
"assets/temp/temp_audio.mp4", "assets/temp/temp_audio.mp4", 0, final.duration, targetname=f"results/{subreddit}/{filename}", )
0,
final.duration,
targetname=f"results/{subreddit}/{filename}",
)
else: else:
print("debug duck") ffmpeg_extract_subclip("assets/temp/temp.mp4", 0, final.duration,
ffmpeg_extract_subclip( targetname=f"results/{subreddit}/{filename}", )
"assets/temp/temp.mp4",
0,
final.duration,
targetname=f"results/{subreddit}/{filename}",
)
print_step("Removing temporary files 🗑") print_step("Removing temporary files 🗑")
cleanups = cleanup() cleanups = cleanup()
print_substep(f"Removed {cleanups} temporary files 🗑") print_substep(f"Removed {cleanups} temporary files 🗑")
print_substep("See result in the results folder!") print_substep("See result in the results folder!")
print_step( print_step(f'Reddit title: {reddit_obj["thread_title"]} \n Background Credit: {background_config[2]}')
f'Reddit title: {reddit_obj["thread_title"]} \n Background Credit: {background_config[2]}'
)

@ -34,7 +34,7 @@ def save_text_to_mp3(reddit_obj) -> Tuple[int, int]:
""" """
voice = settings.config["settings"]["tts"]["choice"] 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) text_to_mp3 = TTSEngine(get_case_insensitive_key_value(TTSProviders, voice), reddit_obj)
else: else:
while True: while True:

Loading…
Cancel
Save