Merge branch 'develop' into elevenlabs

pull/1619/head
Simon 2 years ago committed by GitHub
commit 53ab45bba9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -29,4 +29,4 @@ jobs:
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY
git checkout $GITHUB_HEAD_REF
git commit -am "fixup: Format Python code with Black"
git push
git push origin HEAD:master

@ -1,6 +1,7 @@
FROM python:3.10.9-slim
RUN apt update
RUN apt-get install -y ffmpeg
RUN apt install python3-pip -y
RUN mkdir /app

@ -56,6 +56,13 @@
.tooltip-inner {
max-width: 500px !important;
}
#hard-reload {
cursor: pointer;
color: darkblue;
}
#hard-reload:hover {
color: blue;
}
</style>
</head>
@ -132,11 +139,17 @@
Theme by &copy; Bootstrap. <a
href="https://github.com/elebumm/RedditVideoMakerBot/blob/master/README.md#developers-and-maintainers"
target="_blank">Developers and Maintainers</a></p>
<p class="mb-0">If your data is not refreshing, try to hard reload(Ctrl + F5) and visit your local
<p class="mb-0">If your data is not refreshing, try to hard reload(Ctrl + F5) or click <a id="hard-reload">this</a> and visit your local
<strong>{{ file }}</strong> file.
</p>
</div>
</footer>
<script>
document.getElementById("hard-reload").addEventListener("click", function () {
window.location.reload(true);
});
</script>
</body>
</html>

@ -70,7 +70,7 @@ In its current state, this bot does exactly what it needs to do. However, improv
I have tried to simplify the code so anyone can read it and start contributing at any skill level. Don't be shy :) contribute!
- [ ] Creating better documentation and adding a command line interface.
- [ ] Allowing the user to choose background music for their videos.
- [x] Allowing the user to choose background music for their videos.
- [x] Allowing users to choose a reddit thread instead of being randomized.
- [x] Allowing users to choose a background that is picked instead of the Minecraft one.
- [x] Allowing users to choose between any subreddit.

@ -3,11 +3,8 @@ import re
from pathlib import Path
from typing import Tuple
# import sox
# from mutagen import MutagenError
# from mutagen.mp3 import MP3, HeaderNotFoundError
import numpy as np
import translators as ts
import translators
from moviepy.audio.AudioClip import AudioClip
from moviepy.audio.fx.volumex import volumex
from moviepy.editor import AudioFileClip
@ -55,9 +52,18 @@ class TTSEngine:
self,
): # adds periods to the end of paragraphs (where people often forget to put them) so tts doesn't blend sentences
for comment in self.reddit_object["comments"]:
# remove links
regex_urls = r"((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*"
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"])
if comment["comment_body"][-1] != ".":
comment["comment_body"] += "."
comment["comment_body"] = comment["comment_body"].replace(". . .", ".")
comment["comment_body"] = comment["comment_body"].replace(".. . ", ".")
comment["comment_body"] = comment["comment_body"].replace(". . ", ".")
comment["comment_body"] = re.sub(r'\."\.', '".', comment["comment_body"])
def run(self) -> Tuple[int, int]:
Path(self.path).mkdir(parents=True, exist_ok=True)
@ -141,7 +147,7 @@ class TTSEngine:
print("OSError")
def call_tts(self, filename: str, text: str):
self.tts_module.run(text, filepath=f"{self.path}/{filename}.mp3")
self.tts_module.run(text, filepath=f"{self.path}/{filename}.mp3", random_voice=settings.config["settings"]["tts"]["random_voice"])
# try:
# self.length += MP3(f"{self.path}/{filename}.mp3").info.length
# except (MutagenError, HeaderNotFoundError):
@ -172,6 +178,6 @@ 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 = ts.google(text, to_language=lang)
translated_text = translators.google(text, to_language=lang)
new_text = sanitize_text(translated_text)
return new_text

@ -6,6 +6,7 @@ from os import name
from pathlib import Path
from subprocess import Popen
import ffmpeg
from prawcore import ResponseException
from utils.console import print_substep
from reddit.subreddit import get_subreddit_threads
@ -15,8 +16,9 @@ from utils.console import print_markdown, print_step
from utils.id import id
from utils.version import checkversion
from video_creation.background import (
download_background,
chop_background_video,
download_background_video,
download_background_audio,
chop_background,
get_background_config,
)
from video_creation.final_video import make_final_video
@ -50,10 +52,18 @@ def main(POST_ID=None) -> None:
length, number_of_comments = save_text_to_mp3(reddit_object)
length = math.ceil(length)
get_screenshots_of_reddit_posts(reddit_object, number_of_comments)
bg_config = get_background_config()
download_background(bg_config)
chop_background_video(bg_config, length, reddit_object)
make_final_video(number_of_comments, length, reddit_object, bg_config)
bg_config = {
"video": get_background_config("video"),
"audio": get_background_config("audio"),
}
download_background_video(bg_config["video"])
download_background_audio(bg_config["audio"])
chop_background(bg_config, length, reddit_object)
try:
make_final_video(number_of_comments, length, reddit_object, bg_config)
except ffmpeg.Error as e:
print(e.stderr.decode("utf8"))
exit(1)
def run_many(times) -> None:
@ -81,6 +91,7 @@ def shutdown():
if __name__ == "__main__":
if sys.version_info.major != 3 or sys.version_info.minor != 10:
print("Hey! Congratulations, you've made it so far (which is pretty rare with no Python 3.10). Unfortunately, this program only works on Python 3.10. Please install Python 3.10 and try again.")
exit()
ffmpeg_install() # install ffmpeg if not installed
directory = Path().absolute()
config = settings.check_toml(
@ -128,4 +139,4 @@ if __name__ == "__main__":
f"Error: {err} \n"
f'Config: {config["settings"]}'
)
raise err
raise err

@ -5,19 +5,19 @@ moviepy==1.0.3
playwright==1.23.0
praw==7.6.1
prawcore~=2.3.0
pytube==12.1.0
requests==2.28.1
rich==13.3.1
rich==13.3.5
toml==0.10.2
translators==5.3.1
pyttsx3==2.90
Pillow~=9.4.0
tomlkit==0.11.4
Flask==2.2.2
tomlkit==0.11.8
Flask==2.3.2
clean-text==0.6.0
unidecode==1.3.2
spacy==3.4.1
torch==1.12.1
transformers==4.25.1
ffmpeg-python==0.2.0
elevenlabs==0.2.10
elevenlabs==0.2.10
yt-dlp==2023.3.4

@ -31,11 +31,13 @@ storymodemethod= { optional = true, default = 1, example = 1, explanation = "Sty
storymode_max_length = { optional = true, default = 1000, example = 1000, explanation = "Max length of the storymode video in characters. 200 characters are approximately 50 seconds.", type = "int", nmin = 1, oob_error = "It's very hard to make a video under a second." }
resolution_w = { optional = false, default = 1080, example = 1440, explantation = "Sets the width in pixels of the final video" }
resolution_h = { optional = false, default = 1920, example = 2560, explantation = "Sets the height in pixels of the final video" }
zoom = { optional = true, default = 1, example = 1.1, explanation = "Sets the browser zoom level. Useful if you want the text larger.", type = "float", nmin = 0.1, nmax = 2, oob_error = "The text is really difficult to read at a zoom level higher than 2" }
[settings.background]
background_choice = { optional = true, default = "minecraft", example = "rocket-league", options = ["minecraft", "gta", "rocket-league", "motor-gta", "csgo-surf", "cluster-truck", "minecraft-2","multiversus","fall-guys","steep", ""], explanation = "Sets the background for the video based on game name" }
#background_audio = { optional = true, type = "bool", default = false, example = false, options = [true, false,], explanation = "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" }
background_video = { optional = true, default = "minecraft", example = "rocket-league", options = ["minecraft", "gta", "rocket-league", "motor-gta", "csgo-surf", "cluster-truck", "minecraft-2","multiversus","fall-guys","steep", ""], explanation = "Sets the background for the video based on game name" }
background_audio = { optional = true, default = "lofi", example = "chill-summer", options = ["lofi","lofi-2","chill-summer",""], explanation = "Sets the background audio for the video" }
background_audio_volume = { optional = true, type = "float", nmin = 0, nmax = 1, default = 0.15, example = 0.05, explanation="Sets the volume of the background audio. If you don't want background audio, set it to 0.", oob_error = "The volume HAS to be between 0 and 1", input_error = "The volume HAS to be a float number between 0 and 1"}
enable_extra_audio = { optional = true, type = "bool", default = false, example = false, explanation="Used if you want to render another video without background audio in a separate folder", input_error = "The value HAS to be true or false"}
background_thumbnail = { optional = true, type = "bool", default = false, example = false, options = [true, false,], explanation = "Generate a thumbnail for the video (put a thumbnail.png file in the assets/backgrounds directory.)" }
background_thumbnail_font_family = { optional = true, default = "arial", example = "arial", explanation = "Font family for the thumbnail text" }
background_thumbnail_font_size = { optional = true, type = "int", default = 96, example = 96, explanation = "Font size in pixels for the thumbnail text" }
@ -43,12 +45,13 @@ background_thumbnail_font_color = { optional = true, default = "255,255,255", ex
[settings.tts]
voice_choice = { optional = false, default = "tiktok", options = ["elevenlabs", "streamlabspolly", "tiktok", "googletranslate", "awspolly", "pyttsx", ], example = "tiktok", explanation = "The voice platform used for TTS generation. " }
random_voice = { optional = false, default = true, example = true, options = [true, false,], explanation = "Randomizes the voice used for each comment" }
elevenlabs_voice_name = { optional = false, default = "Bella", example = "Bella", explanation = "The voice used for elevenlabs", options = ["Adam", "Antoni", "Arnold", "Bella", "Domi", "Elli", "Josh", "Rachel", "Sam", ] }
elevenlabs_api_key = { optional = true, example = "21f13f91f54d741e2ae27d2ab1b99d59", explanation = "Elevenlabs API key" }
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 = true, default = "en_us_001", example = "en_us_006", explanation = "The voice used for TikTok TTS" }
tiktok_sessionid = { optional = true, example = "c76bcc3a7625abcc27b508c7db457ff1", explanation = "TikTok sessionid needed for the TTS API request. Check documentation if you don't know how to obtain it." }
tiktok_sessionid = { optional = true, example = "c76bcc3a7625abcc27b508c7db457ff1", explanation = "TikTok sessionid needed if you're using the TikTok TTS. Check documentation if you don't know how to obtain it." }
python_voice = { optional = false, default = "1", example = "1", explanation = "The index of the system tts voices (can be downloaded externally, run ptt.py to find value, start from zero)" }
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" }

@ -0,0 +1,18 @@
{
"__comment": "Supported Backgrounds Audio. Can add/remove background audio here...",
"lofi": [
"https://www.youtube.com/watch?v=LTphVIore3A",
"lofi.mp3",
"Super Lofi World"
],
"lofi-2":[
"https://www.youtube.com/watch?v=BEXL80LS0-I",
"lofi-2.mp3",
"stompsPlaylist"
],
"chill-summer":[
"https://www.youtube.com/watch?v=EZE8JagnBI8",
"chill-summer.mp3",
"Mellow Vibes Radio"
]
}

@ -23,7 +23,7 @@ def ffmpeg_install_windows():
os.remove(f"ffmpeg/doc/{file}")
os.rmdir("ffmpeg/doc")
# Add to the path
subprocess.run("setx /M PATH \"%PATH%;%CD%\\ffmpeg\"", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run("setx PATH \"%PATH%;%CD%\\ffmpeg\"", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print("FFmpeg installed successfully! Please restart your computer and then re-run the program.")
exit()
except Exception as e:
@ -81,4 +81,4 @@ def ffmpeg_install():
except Exception as e:
print("Welcome fellow traveler! You're one of the few who have made it this far. We have no idea how you got at this error, but we're glad you're here. Please report this error to the developer, and we'll try to fix it as soon as possible. Thank you for your patience!")
print(e)
return None
return None

@ -68,9 +68,9 @@ def imagemaker(theme, reddit_obj: dict, txtclr, padding=5, transparent=False) ->
tfont = ImageFont.truetype(os.path.join("fonts", "Roboto-Bold.ttf"), 50)
else:
tfont = ImageFont.truetype(
os.path.join("fonts", "Roboto-Bold.ttf"), 35
os.path.join("fonts", "Roboto-Bold.ttf"), 100
) # for title
font = ImageFont.truetype(os.path.join("fonts", "Roboto-Regular.ttf"), 30)
font = ImageFont.truetype(os.path.join("fonts", "Roboto-Regular.ttf"), 90)
size = (1920, 1080)
image = Image.new("RGBA", size, theme)

@ -3,29 +3,38 @@ import random
import re
from pathlib import Path
from random import randrange
from typing import Any, Tuple
from typing import Any, Tuple,Dict
from moviepy.editor import VideoFileClip
from moviepy.editor import VideoFileClip,AudioFileClip
from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
from pytube import YouTube
from pytube.cli import on_progress
from utils import settings
from utils.console import print_step, print_substep
import yt_dlp
# Load background videos
with open("./utils/backgrounds.json") as json_file:
background_options = json.load(json_file)
def load_background_options():
background_options = {}
# Load background videos
with open("./utils/background_videos.json") as json_file:
background_options["video"] = json.load(json_file)
# Remove "__comment" from backgrounds
background_options.pop("__comment", None)
# Load background audios
with open("./utils/background_audios.json") as json_file:
background_options["audio"] = json.load(json_file)
# Remove "__comment" from backgrounds
del background_options["video"]["__comment"]
del background_options["audio"]["__comment"]
# Add position lambda function
# (https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html#moviepy.video.VideoClip.VideoClip.set_position)
for name in list(background_options["video"].keys()):
pos = background_options["video"][name][3]
# Add position lambda function
# (https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html#moviepy.video.VideoClip.VideoClip.set_position)
for name in list(background_options.keys()):
pos = background_options[name][3]
if pos != "center":
background_options["video"][name][3] = lambda t: ("center", pos + t)
return background_options
if pos != "center":
background_options[name][3] = lambda t: ("center", pos + t)
def get_start_and_end_times(video_length: int, length_of_clip: int) -> Tuple[int, int]:
@ -38,15 +47,22 @@ def get_start_and_end_times(video_length: int, length_of_clip: int) -> Tuple[int
Returns:
tuple[int,int]: Start and end time of the randomized interval
"""
random_time = randrange(180, int(length_of_clip) - int(video_length))
initialValue = 180
# Issue #1649 - Ensures that will be a valid interval in the video
while(int(length_of_clip) <= int(video_length+initialValue)):
if(initialValue == initialValue //2):
raise Exception("Your background is too short for this video length")
else:
initialValue //= 2 #Divides the initial value by 2 until reach 0
random_time = randrange(initialValue, int(length_of_clip) - int(video_length))
return random_time, random_time + video_length
def get_background_config():
def get_background_config(mode: str):
"""Fetch the background/s configuration"""
try:
choice = str(
settings.config["settings"]["background"]["background_choice"]
settings.config["settings"]["background"][f"background_{mode}"]
).casefold()
except AttributeError:
print_substep("No background selected. Picking random background'")
@ -54,57 +70,98 @@ def get_background_config():
# Handle default / not supported background using default option.
# Default : pick random from supported background.
if not choice or choice not in background_options:
choice = random.choice(list(background_options.keys()))
return background_options[choice]
if not choice or choice not in background_options[mode]:
choice = random.choice(list(background_options[mode].keys()))
return background_options[mode][choice]
def download_background(background_config: Tuple[str, str, str, Any]):
def download_background_video(background_config: Tuple[str, str, str, Any]):
"""Downloads the background/s video from YouTube."""
Path("./assets/backgrounds/").mkdir(parents=True, exist_ok=True)
Path("./assets/backgrounds/video/").mkdir(parents=True, exist_ok=True)
# note: make sure the file name doesn't include an - in it
uri, filename, credit, _ = background_config
if Path(f"assets/backgrounds/{credit}-{filename}").is_file():
if Path(f"assets/backgrounds/video/{credit}-{filename}").is_file():
return
print_step(
"We need to download the backgrounds videos. they are fairly large but it's only done once. 😎"
)
print_substep("Downloading the backgrounds videos... please be patient 🙏 ")
print_substep(f"Downloading {filename} from {uri}")
YouTube(uri, on_progress_callback=on_progress).streams.filter(
res="1080p"
).first().download("assets/backgrounds", filename=f"{credit}-{filename}")
ydl_opts = {
'format': "bestvideo[height<=1080][ext=mp4]",
"outtmpl": f"assets/backgrounds/video/{credit}-{filename}",
"retries": 10,
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download(uri)
print_substep("Background video downloaded successfully! 🎉", style="bold green")
def download_background_audio(background_config: Tuple[str, str, str]):
"""Downloads the background/s audio from YouTube."""
Path("./assets/backgrounds/audio/").mkdir(parents=True, exist_ok=True)
# note: make sure the file name doesn't include an - in it
uri, filename, credit = background_config
if Path(f"assets/backgrounds/audio/{credit}-{filename}").is_file():
return
print_step(
"We need to download the backgrounds audio. they are fairly large but it's only done once. 😎"
)
print_substep("Downloading the backgrounds audio... please be patient 🙏 ")
print_substep(f"Downloading {filename} from {uri}")
ydl_opts = {
'outtmpl': f'./assets/backgrounds/audio/{credit}-{filename}',
'format': 'bestaudio/best',
'extract_audio': True,
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([uri])
def chop_background_video(
background_config: Tuple[str, str, str, Any], video_length: int, reddit_object: dict
print_substep("Background audio downloaded successfully! 🎉", style="bold green")
def chop_background(
background_config: Dict[str,Tuple], video_length: int, reddit_object: dict
):
"""Generates the background footage to be used in the video and writes it to assets/temp/background.mp4
"""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:
background_config (Tuple[str, str, str, Any]) : Current background configuration
background_config (Dict[str,Tuple]]) : Current background configuration
video_length (int): Length of the clip where the background footage is to be taken out of
"""
print_step("Finding a spot in the backgrounds video to chop...✂️")
choice = f"{background_config[2]}-{background_config[1]}"
id = re.sub(r"[^\w\s-]", "", reddit_object["thread_id"])
background = VideoFileClip(f"assets/backgrounds/{choice}")
start_time, end_time = get_start_and_end_times(video_length, background.duration)
if(settings.config["settings"]["background"][f"background_audio_volume"] == 0):
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]}"
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)
background_audio = background_audio.subclip(start_time_audio,end_time_audio)
background_audio.write_audiofile(f"assets/temp/{id}/background.mp3")
print_step("Finding a spot in the backgrounds video to chop...✂️")
video_choice = f"{background_config['video'][2]}-{background_config['video'][1]}"
background_video = VideoFileClip(f"assets/backgrounds/video/{video_choice}")
start_time_video, end_time_video = get_start_and_end_times(video_length, background_video.duration)
# Extract video subclip
try:
ffmpeg_extract_subclip(
f"assets/backgrounds/{choice}",
start_time,
end_time,
f"assets/backgrounds/video/{video_choice}",
start_time_video,
end_time_video,
targetname=f"assets/temp/{id}/background.mp4",
)
except (OSError, IOError): # ffmpeg issue see #348
print_substep("FFMPEG issue. Trying again...")
with VideoFileClip(f"assets/backgrounds/{choice}") as video:
new = video.subclip(start_time, end_time)
with VideoFileClip(f"assets/backgrounds/video/{video_choice}") as video:
new = video.subclip(start_time_video, end_time_video)
new.write_videofile(f"assets/temp/{id}/background.mp4")
print_substep("Background video chopped successfully!", style="bold green")
return background_config[2]
return background_config["video"][2]
# Create a tuple for downloads background (background_audio_options, background_video_options)
background_options = load_background_options()

@ -1,13 +1,12 @@
import multiprocessing
import os
import re
import shutil
from os.path import exists # Needs to be imported specifically
from typing import Final
from typing import Tuple, Any
from typing import Tuple, Any, Dict
import ffmpeg
import translators as ts
import translators
from PIL import Image
from rich.console import Console
from rich.progress import track
@ -18,12 +17,12 @@ from utils.console import print_step, print_substep
from utils.thumbnail import create_thumbnail
from utils.videos import save_data
console = Console()
import tempfile
import threading
import time
console = Console()
class ProgressFfmpeg(threading.Thread):
def __init__(self, vid_duration_seconds, progress_update_callback):
@ -73,7 +72,7 @@ def name_normalize(name: str) -> str:
lang = settings.config["reddit"]["thread"]["post_lang"]
if lang:
print_substep("Translating filename...")
translated_name = ts.google(name, to_language=lang)
translated_name = translators.google(name, to_language=lang)
return translated_name
else:
return name
@ -104,11 +103,34 @@ def prepare_background(reddit_id: str, W: int, H: int) -> str:
return output_path
def merge_background_audio(audio: ffmpeg, reddit_id: str):
"""Gather an audio and merge with assets/backgrounds/background.mp3
Args:
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"]
if (background_audio_volume == 0):
return audio # Return the original audio
else:
# sets volume to config
bg_audio = (
ffmpeg.input(f"assets/temp/{reddit_id}/background.mp3")
.filter(
"volume",
background_audio_volume,
)
)
# Merges audio and background_audio
merged_audio = ffmpeg.filter([audio, bg_audio], "amix", duration="longest")
return merged_audio # Return merged audio
def make_final_video(
number_of_clips: int,
length: int,
reddit_obj: dict,
background_config: Tuple[str, str, str, Any],
background_config: Dict[str,Tuple],
):
"""Gathers audio clips, gathers all screenshots, stitches them together and saves the final video to assets/temp
Args:
@ -122,6 +144,10 @@ def make_final_video(
H: Final[int] = int(settings.config["settings"]["resolution_h"])
reddit_id = re.sub(r"[^\w\s-]", "", reddit_obj["thread_id"])
allowOnlyTTSFolder: bool = settings.config["settings"]["background"]["enable_extra_audio"] \
and settings.config["settings"]["background"]["background_audio_volume"] != 0
print_step("Creating the final video 🎥")
background_clip = ffmpeg.input(prepare_background(reddit_id, W=W, H=H))
@ -177,6 +203,7 @@ def make_final_video(
screenshot_width = int((W * 45) // 100)
audio = ffmpeg.input(f"assets/temp/{reddit_id}/audio.mp3")
final_audio = merge_background_audio(audio,reddit_id)
image_clips = list()
@ -258,15 +285,19 @@ def make_final_video(
subreddit = settings.config["reddit"]["thread"]["subreddit"]
if not exists(f"./results/{subreddit}"):
print_substep("The results folder didn't exist so I made it")
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.")
os.makedirs(f"./results/{subreddit}/OnlyTTS")
# create a thumbnail for the video
settingsbackground = settings.config["settings"]["background"]
if settingsbackground["background_thumbnail"]:
if not exists(f"./results/{subreddit}/thumbnails"):
print_substep("The results/thumbnails folder didn't exist so I made it")
print_substep("The 'results/thumbnails' folder could not be found so it was automatically created.")
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(
@ -300,7 +331,7 @@ def make_final_video(
f"Thumbnail - Building Thumbnail in assets/temp/{reddit_id}/thumbnail.png"
)
text = f"Background by {background_config[2]}"
text = f"Background by {background_config['video'][2]}"
background_clip = ffmpeg.drawtext(
background_clip,
text=text,
@ -320,15 +351,14 @@ def make_final_video(
old_percentage = pbar.n
pbar.update(status - old_percentage)
path = f"results/{subreddit}/{filename}"
path = path[:251]
path = path + ".mp4"
defaultPath = f"results/{subreddit}"
with ProgressFfmpeg(length, on_update_example) as progress:
path = defaultPath + f"/{filename}"
path = path[:251] + ".mp4" #Prevent a error by limiting the path length, do not change this.
ffmpeg.output(
background_clip,
audio,
path,
final_audio,
path,
f="mp4",
**{
"c:v": "h264",
@ -342,13 +372,36 @@ def make_final_video(
capture_stdout=False,
capture_stderr=False,
)
old_percentage = pbar.n
pbar.update(100 - old_percentage)
if(allowOnlyTTSFolder):
path = defaultPath + f"/OnlyTTS/{filename}"
path = path[:251] + ".mp4" #Prevent a error by limiting the path length, do not change this.
print_step("Rendering the Only TTS Video 🎥")
with ProgressFfmpeg(length, on_update_example) as progress:
ffmpeg.output(
background_clip,
audio,
path,
f="mp4",
**{
"c:v": "h264",
"b:v": "20M",
"b:a": "192k",
"threads": multiprocessing.cpu_count(),
},
).overwrite_output().global_args("-progress", progress.output_file.name).run(
quiet=True,
overwrite_output=True,
capture_stdout=False,
capture_stderr=False,
)
old_percentage = pbar.n
pbar.update(100 - old_percentage)
pbar.close()
save_data(subreddit, filename + ".mp4", title, idx, background_config[2])
save_data(subreddit, filename + ".mp4", title, idx, background_config['video'][2])
print_step("Removing temporary files 🗑")
cleanups = cleanup(reddit_id)
print_substep(f"Removed {cleanups} temporary files 🗑")
print_step("Done! 🎉 The video is in the results folder 📁")
print_step("Done! 🎉 The video is in the results folder 📁")

@ -3,7 +3,7 @@ import re
from pathlib import Path
from typing import Dict, Final
import translators as ts
import translators
from playwright.async_api import async_playwright # pylint: disable=unused-import
from playwright.sync_api import ViewportSize, sync_playwright
from rich.progress import track
@ -115,6 +115,19 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
)
page.locator("button[class$='m-full-width']").click()
page.wait_for_timeout(5000)
login_error_div = page.locator(".AnimatedForm__errorMessage").first
if login_error_div.is_visible():
login_error_message = login_error_div.inner_text()
if login_error_message.strip() == "":
# The div element is empty, no error
pass
else:
# The div contains an error message
print_substep("Your reddit credentials are incorrect! Please modify them accordingly in the config.toml file.", style="red")
exit()
else:
pass
page.wait_for_load_state()
# Get the thread screenshot
@ -144,7 +157,7 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
if lang:
print_substep("Translating post...")
texts_in_tl = ts.google(
texts_in_tl = translators.google(
reddit_object["thread_title"],
to_language=lang,
)
@ -158,9 +171,20 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
postcontentpath = f"assets/temp/{reddit_id}/png/title.png"
try:
page.locator('[data-test-id="post-content"]').screenshot(
path=postcontentpath
)
if settings.config["settings"]["zoom"] != 1:
# store zoom settings
zoom = settings.config["settings"]["zoom"]
# zoom the body of the page
page.evaluate("document.body.style.zoom="+str(zoom))
# 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))
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")
resp = input(
@ -205,7 +229,7 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
# translate code
if settings.config["reddit"]["thread"]["post_lang"]:
comment_tl = ts.google(
comment_tl = translators.google(
comment["comment_body"],
to_language=settings.config["reddit"]["thread"]["post_lang"],
)
@ -214,9 +238,22 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
[comment_tl, comment["comment_id"]],
)
try:
page.locator(f"#t1_{comment['comment_id']}").screenshot(
path=f"assets/temp/{reddit_id}/png/comment_{idx}.png"
)
if settings.config["settings"]["zoom"] != 1:
# store zoom settings
zoom = settings.config["settings"]["zoom"]
# 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()
# 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()
for i in location:
location[i] = float("{:.2f}".format(location[i]*zoom))
page.screenshot(clip=location, path=f"assets/temp/{reddit_id}/png/comment_{idx}.png")
else:
page.locator(f"#t1_{comment['comment_id']}").screenshot(
path=f"assets/temp/{reddit_id}/png/comment_{idx}.png"
)
except TimeoutError:
del reddit_object["comments"]
screenshot_num += 1

Loading…
Cancel
Save