diff --git a/README.md b/README.md index 8a9e205..0530b2b 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ https://user-images.githubusercontent.com/66544866/173453972-6526e4e6-c6ef-41c5- ## Contributing & Ways to improve 📈 -In its current state, this bot does exactly what it needs to do. However, lots of improvements can be made. +In its current state, this bot does exactly what it needs to do. However, improvements can always be made! I have tried to simplify the code so anyone can read it and start contributing at any skill level. Don't be shy :) contribute! diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py index c5cc86c..bad4495 100644 --- a/TTS/engine_wrapper.py +++ b/TTS/engine_wrapper.py @@ -13,7 +13,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 = 50 # video length variable class TTSEngine: diff --git a/main.py b/main.py index 10ab3c1..adc478d 100755 --- a/main.py +++ b/main.py @@ -2,6 +2,9 @@ import math from subprocess import Popen from os import name + +from prawcore import ResponseException + from reddit.subreddit import get_subreddit_threads from utils.cleanup import cleanup from utils.console import print_markdown, print_step @@ -16,8 +19,8 @@ from video_creation.final_video import make_final_video from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts from video_creation.voices import save_text_to_mp3 -__VERSION__ = "2.3" -__BRANCH__ = "master" +__VERSION__ = "2.3.1" +__BRANCH__ = "develop" print( """ @@ -51,14 +54,20 @@ def main(POST_ID=None): def run_many(times): for x in range(1, times + 1): print_step( - f'on the {x}{("th", "st", "nd", "rd", "th", "th", "th", "th","th", "th")[x%10]} iteration of {times}' + f'on the {x}{("th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th")[x % 10]} iteration of {times}' ) # correct 1st 2nd 3rd 4th 5th.... main() Popen("cls" if name == "nt" else "clear", shell=True).wait() +def shutdown(): + print_markdown("## Clearing temp files") + cleanup() + exit() + + if __name__ == "__main__": - config = settings.check_toml(".config.template.toml", "config.toml") + config = settings.check_toml("utils/.config.template.toml", "config.toml") config is False and exit() try: if config["settings"]["times_to_run"]: @@ -68,13 +77,19 @@ if __name__ == "__main__": for index, post_id in enumerate(config["reddit"]["thread"]["post_id"].split("+")): index += 1 print_step( - f'on the {index}{("st" if index%10 == 1 else ("nd" if index%10 == 2 else ("rd" if index%10 == 3 else "th")))} post of {len(config["reddit"]["thread"]["post_id"].split("+"))}' + f'on the {index}{("st" if index % 10 == 1 else ("nd" if index % 10 == 2 else ("rd" if index % 10 == 3 else "th")))} post of {len(config["reddit"]["thread"]["post_id"].split("+"))}' ) main(post_id) Popen("cls" if name == "nt" else "clear", shell=True).wait() else: main() except KeyboardInterrupt: - print_markdown("## Clearing temp files") - cleanup() - exit() + shutdown() + except ResponseException: + # error for invalid credentials + print_markdown("## Invalid credentials") + print_markdown("Please check your credentials in the config.toml file") + + shutdown() + + # todo error diff --git a/requirements.txt b/requirements.txt index e57a37d..7bccd0d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,5 @@ requests==2.28.1 rich==12.5.1 toml==0.10.2 translators==5.3.1 + +Pillow~=9.1.1 diff --git a/.config.template.toml b/utils/.config.template.toml similarity index 97% rename from .config.template.toml rename to utils/.config.template.toml index 18f1b00..9fd779e 100644 --- a/.config.template.toml +++ b/utils/.config.template.toml @@ -31,7 +31,7 @@ storymode = { optional = true, type = "bool", default = false, example = 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_choice = { optional = true, default = "minecraft", example = "minecraft", options = ["minecraft", "gta", "rocket-league", "motor-gta", "csgo-surf", "cluster-truck", ""], 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.)" } diff --git a/utils/CONSTANTS.py b/utils/CONSTANTS.py new file mode 100644 index 0000000..e46ebbc --- /dev/null +++ b/utils/CONSTANTS.py @@ -0,0 +1,45 @@ +# Supported Background. Can add/remove background video here.... +# - : key -> used as keyword for TOML file. value -> background configuration +# Format (value): +# 1. Youtube URI +# 2. filename +# 3. Citation (owner of the video) +# 4. Position of image clips in the background. See moviepy reference for more information. (https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html#moviepy.video.VideoClip.VideoClip.set_position) +background_options = { + "motor-gta": ( # Motor-GTA Racing + "https://www.youtube.com/watch?v=vw5L4xCPy9Q", + "bike-parkour-gta.mp4", + "Achy Gaming", + lambda t: ("center", 480 + t), + ), + "rocket-league": ( # Rocket League + "https://www.youtube.com/watch?v=2X9QGY__0II", + "rocket_league.mp4", + "Orbital Gameplay", + lambda t: ("center", 200 + t), + ), + "minecraft": ( # Minecraft parkour + "https://www.youtube.com/watch?v=n_Dv4JMiwK8", + "parkour.mp4", + "bbswitzer", + "center", + ), + "gta": ( # GTA Stunt Race + "https://www.youtube.com/watch?v=qGa9kWREOnE", + "gta-stunt-race.mp4", + "Achy Gaming", + lambda t: ("center", 480 + t), + ), + "csgo-surf": ( # CSGO Surf + "https://www.youtube.com/watch?v=E-8JlyO59Io", + "csgo-surf.mp4", + "Aki", + "center", + ), + "cluster-truck": ( # Cluster Truck Gameplay + "https://www.youtube.com/watch?v=uVKxtdMgJVU", + "cluster_truck.mp4", + "No Copyright Gameplay", + lambda t: ("center", 480 + t), + ), +} diff --git a/utils/cleanup.py b/utils/cleanup.py index ef4fc44..75074b7 100644 --- a/utils/cleanup.py +++ b/utils/cleanup.py @@ -2,6 +2,10 @@ import os 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 cleanup() -> int: """Deletes all temporary assets in assets/temp @@ -14,14 +18,12 @@ def cleanup() -> int: count += len(files) for f in files: os.remove(f) - try: - for file in os.listdir("./assets/temp/mp4"): + REMOVE_DIRS = ["./assets/temp/mp3/", "./assets/temp/png/"] + files_to_remove = list(map(_listdir, REMOVE_DIRS)) + for directory in files_to_remove: + for file in directory: count += 1 - os.remove("./assets/temp/mp4/" + file) - except FileNotFoundError: - pass - for file in os.listdir("./assets/temp/mp3"): - count += 1 - os.remove("./assets/temp/mp3/" + file) + os.remove(file) return count + return 0 diff --git a/utils/settings.py b/utils/settings.py index a36f63e..a9d7726 100755 --- a/utils/settings.py +++ b/utils/settings.py @@ -167,4 +167,4 @@ If you see any prompts, that means that you have unset/incorrectly set variables if __name__ == "__main__": - check_toml(".config.template.toml", "config.toml") + check_toml("utils/.config.template.toml", "config.toml") diff --git a/utils/subreddit.py b/utils/subreddit.py index 4eb0108..c386868 100644 --- a/utils/subreddit.py +++ b/utils/subreddit.py @@ -5,7 +5,7 @@ from utils import settings from utils.console import print_substep -def get_subreddit_undone(submissions: list, subreddit): +def get_subreddit_undone(submissions: list, subreddit, times_checked=0): """_summary_ Args: @@ -41,8 +41,24 @@ def get_subreddit_undone(submissions: list, subreddit): continue return submission print("all submissions have been done going by top submission order") + VALID_TIME_FILTERS = [ + "day", + "hour", + "month", + "week", + "year", + "all", + ] # set doesn't have __getitem__ + index = times_checked + 1 + if index == len(VALID_TIME_FILTERS): + print("all time filters have been checked you absolute madlad ") + return get_subreddit_undone( - subreddit.top(time_filter="hour"), subreddit + subreddit.top( + time_filter=VALID_TIME_FILTERS[index], limit=(50 if int(index) == 0 else index + 1 * 50) + ), + subreddit, + times_checked=index, ) # all the videos in hot have already been done diff --git a/utils/video.py b/utils/video.py new file mode 100644 index 0000000..63dc170 --- /dev/null +++ b/utils/video.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from typing import Tuple + +from PIL import ImageFont, Image, ImageDraw, ImageEnhance +from moviepy.video.VideoClip import VideoClip, ImageClip +from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip + + +class Video: + def __init__(self, video: VideoClip, *args, **kwargs): + self.video: VideoClip = video + self.fps = self.video.fps + self.duration = self.video.duration + + @staticmethod + def _create_watermark(text, fontsize, opacity=0.5): + path = "./assets/temp/png/watermark.png" + width = int(fontsize * len(text)) + height = int(fontsize * len(text) / 2) + white = (255, 255, 255) + transparent = (0, 0, 0, 0) + + font = ImageFont.load_default() + wm = Image.new("RGBA", (width, height), transparent) + im = Image.new("RGBA", (width, height), transparent) # Change this line too. + + draw = ImageDraw.Draw(wm) + w, h = draw.textsize(text, font) + draw.text(((width - w) / 2, (height - h) / 2), text, white, font) + en = ImageEnhance.Brightness(wm) # todo allow it to use the fontsize + mask = en.enhance(1 - opacity) + im.paste(wm, (25, 25), mask) + im.save(path) + return ImageClip(path) + + def add_watermark( + self, text, opacity=0.5, duration: int | float = 5, position: Tuple = (0.7, 0.9), fontsize=15 + ): + compensation = round( + (position[0] / ((len(text) * (fontsize / 5) / 1.5) / 100 + position[0] * position[0])), + ndigits=2, + ) + position = (compensation, position[1]) + # print(f'{compensation=}') + # print(f'{position=}') + img_clip = self._create_watermark(text, opacity=opacity, fontsize=fontsize) + img_clip = img_clip.set_opacity(opacity).set_duration(duration) + img_clip = img_clip.set_position( + position, relative=True + ) # todo get dara from utils/CONSTANTS.py and adapt position accordingly + + # Overlay the img clip on the first video clip + self.video = CompositeVideoClip([self.video, img_clip]) + return self.video diff --git a/video_creation/background.py b/video_creation/background.py index be0f46c..6e656fa 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -10,42 +10,9 @@ from pytube import YouTube from pytube.cli import on_progress from utils import settings +from utils.CONSTANTS import background_options from utils.console import print_step, print_substep -# Supported Background. Can add/remove background video here.... -# - : key -> used as keyword for TOML file. value -> background configuration -# Format (value): -# 1. Youtube URI -# 2. filename -# 3. Citation (owner of the video) -# 4. Position of image clips in the background. See moviepy reference for more information. (https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html#moviepy.video.VideoClip.VideoClip.set_position) -background_options = { - "motor-gta": ( # Motor-GTA Racing - "https://www.youtube.com/watch?v=vw5L4xCPy9Q", - "bike-parkour-gta.mp4", - "Achy Gaming", - lambda t: ("center", 480 + t), - ), - "rocket-league": ( # Rocket League - "https://www.youtube.com/watch?v=2X9QGY__0II", - "rocket_league.mp4", - "Orbital Gameplay", - lambda t: ("center", 200 + t), - ), - "minecraft": ( # Minecraft parkour - "https://www.youtube.com/watch?v=n_Dv4JMiwK8", - "parkour.mp4", - "bbswitzer", - "center", - ), - "gta": ( # GTA Stunt Race - "https://www.youtube.com/watch?v=qGa9kWREOnE", - "gta-stunt-race.mp4", - "Achy Gaming", - lambda t: ("center", 480 + t), - ), -} - def get_start_and_end_times(video_length: int, length_of_clip: int) -> Tuple[int, int]: """Generates a random interval of time to be used as the background of the video. @@ -92,7 +59,7 @@ def download_background(background_config: Tuple[str, str, str, Any]): YouTube(uri, on_progress_callback=on_progress).streams.filter(res="1080p").first().download( "assets/backgrounds", filename=f"{credit}-{filename}" ) - print_substep("Background videos downloaded successfully! 🎉", style="bold green") + print_substep("Background video downloaded successfully! 🎉", style="bold green") def chop_background_video(background_config: Tuple[str, str, str, Any], video_length: int): diff --git a/video_creation/final_video.py b/video_creation/final_video.py index eb42247..8d12afe 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -15,6 +15,7 @@ from rich.console import Console from utils.cleanup import cleanup from utils.console import print_step, print_substep +from utils.video import Video from utils.videos import save_data from utils import settings @@ -34,6 +35,7 @@ def name_normalize(name: str) -> str: lang = settings.config["reddit"]["thread"]["post_lang"] if lang: import translators as ts + print_substep("Translating filename...") translated_name = ts.google(name, to_language=lang) return translated_name @@ -109,7 +111,9 @@ def make_final_video( # ) # else: story mode stuff img_clip_pos = background_config[3] - image_concat = concatenate_videoclips(image_clips).set_position(img_clip_pos) + image_concat = concatenate_videoclips(image_clips).set_position( + img_clip_pos + ) # note transition kwarg for delay in imgs image_concat.audio = audio_composite final = CompositeVideoClip([background_clip, image_concat]) title = re.sub(r"[^\w\s-]", "", reddit_obj["thread_title"]) @@ -129,7 +133,9 @@ def make_final_video( # # lowered_audio = audio_background.multiply_volume( # todo get this to work # # VOLUME_MULTIPLIER) # lower volume by background_audio_volume, use with fx # final.set_audio(final_audio) - + final = Video(final).add_watermark( + text=f"Background credit: {background_config[2]}", opacity=0.4 + ) final.write_videofile( "assets/temp/temp.mp4", fps=30, diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 6fb9ef4..1f28c74 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -49,9 +49,11 @@ def download_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: in print_substep("Post is NSFW. You are spicy...") page.locator('[data-testid="content-gate"] button').click() - page.locator( - '[data-click-id="text"] button' - ).click() # Remove "Click to see nsfw" Button in Screenshot + + if page.locator('[data-click-id="text"] button').is_visible(): + page.locator( + '[data-click-id="text"] button' + ).click() # Remove "Click to see nsfw" Button in Screenshot # translate code @@ -99,9 +101,13 @@ def download_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: in '([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"]], ) - - page.locator(f"#t1_{comment['comment_id']}").screenshot( - path=f"assets/temp/png/comment_{idx}.png" - ) - + try: + page.locator(f"#t1_{comment['comment_id']}").screenshot( + path=f"assets/temp/png/comment_{idx}.png" + ) + except TimeoutError: + del reddit_object["comments"] + screenshot_num += 1 + print("TimeoutError: Skipping screenshot...") + continue print_substep("Screenshots downloaded Successfully.", style="bold green")