diff --git a/utils/.config.template.toml b/utils/.config.template.toml index 50cf413..7892929 100644 --- a/utils/.config.template.toml +++ b/utils/.config.template.toml @@ -21,8 +21,9 @@ theme = { optional = false, default = "dark", example = "light", options = ["dar 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" } transition = { optional = true, default = 0.2, example = 0.2, explanation = "Sets the transition time (in seconds) between the comments. Set to 0 if you want to disable it.", type = "float", nmin = 0, nmax = 2, oob_error = "The transition HAS to be between 0 and 2", input_error = "The opacity HAS to be a decimal number between 0 and 2" } -storymode = { optional = true, type = "bool", default = false, example = false, options = [true, false, ], explanation = "Only read out title and post content, not yet implemented" } - +storymode = { optional = false, type = "bool", default = false, example = false, options = [true, false, ], explanation = "Only read out title and post content, not yet implemented" } +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" } [settings.background] background_choice = { optional = true, default = "minecraft", example = "rocket-league", options = ["", "minecraft", "gta", "rocket-league", "motor-gta", "csgo-surf", "cluster-truck"], explanation = "Sets the background for the video based on game name" } diff --git a/video_creation/final_video.py b/video_creation/final_video.py index ad675c5..3db4e6d 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -3,7 +3,7 @@ import multiprocessing import os import re from os.path import exists -from typing import Tuple, Any +from typing import Tuple, Any, Final from moviepy.audio.AudioClip import concatenate_audioclips, CompositeAudioClip from moviepy.audio.io.AudioFileClip import AudioFileClip @@ -21,7 +21,6 @@ from utils.video import Video from utils.videos import save_data console = Console() -W, H = 1080, 1920 def name_normalize(name: str) -> str: @@ -45,6 +44,20 @@ def name_normalize(name: str) -> str: return name +def prepare_background(id: str, W: int, H: int) -> VideoFileClip: + clip = VideoFileClip(f"assets/temp/{id}/background.mp4").without_audio().resize(height=H) + + # calculate the center of the background clip + c = clip.w // 2 + + # calculate the coordinates where to crop + half_w = W // 2 + x1 = c - half_w + x2 = c + half_w + + return clip.crop(x1=x1, y1=0, x2=x2, y2=H) + + def make_final_video( number_of_clips: int, length: int, @@ -58,23 +71,26 @@ def make_final_video( reddit_obj (dict): The reddit object that contains the posts to read. background_config (Tuple[str, str, str, Any]): The background config to use. """ + # settings values + W: Final[int] = int(settings.config["settings"]["resolution_w"]) + H: Final[int] = int(settings.config["settings"]["resolution_h"]) + # try: # if it isn't found (i.e you just updated and copied over config.toml) it will throw an error # VOLUME_MULTIPLIER = settings.config["settings"]['background']["background_audio_volume"] # except (TypeError, KeyError): # print('No background audio volume found in config.toml. Using default value of 1.') # VOLUME_MULTIPLIER = 1 + id = re.sub(r"[^\w\s-]", "", reddit_obj["thread_id"]) print_step("Creating the final video 🎥") + VideoFileClip.reW = lambda clip: clip.resize(width=W) VideoFileClip.reH = lambda clip: clip.resize(width=H) + opacity = settings.config["settings"]["opacity"] transition = settings.config["settings"]["transition"] - background_clip = ( - VideoFileClip(f"assets/temp/{id}/background.mp4") - .without_audio() - .resize(height=H) - .crop(x1=1166.6, y1=0, x2=2246.6, y2=1920) - ) + + background_clip = prepare_background(id, W=W, H=H) # Gather all audio clips audio_clips = [AudioFileClip(f"assets/temp/{id}/mp3/{i}.mp3") for i in range(number_of_clips)] @@ -88,11 +104,12 @@ def make_final_video( # Gather all images new_opacity = 1 if opacity is None or float(opacity) >= 1 else float(opacity) new_transition = 0 if transition is None or float(transition) > 2 else float(transition) + screenshow_width = int((W*90)//100) image_clips.insert( 0, ImageClip(f"assets/temp/{id}/png/title.png") .set_duration(audio_clips[0].duration) - .resize(width=W - 100) + .resize(width=screenshow_width) .set_opacity(new_opacity) .crossfadein(new_transition) .crossfadeout(new_transition), @@ -102,7 +119,7 @@ def make_final_video( image_clips.append( ImageClip(f"assets/temp/{id}/png/comment_{i}.png") .set_duration(audio_clips[i + 1].duration) - .resize(width=W - 100) + .resize(width=screenshow_width) .set_opacity(new_opacity) .crossfadein(new_transition) .crossfadeout(new_transition) @@ -118,6 +135,7 @@ def make_final_video( # .set_opacity(float(opacity)), # ) # else: story mode stuff + img_clip_pos = background_config[3] image_concat = concatenate_videoclips(image_clips).set_position(img_clip_pos) # note transition kwarg for delay in imgs image_concat.audio = audio_composite @@ -139,6 +157,7 @@ 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, redditid=reddit_obj) final.write_videofile( f"assets/temp/{id}/temp.mp4", diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index ba7835f..01da49a 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -1,7 +1,7 @@ -import json import re +import json from pathlib import Path -from typing import Dict +from typing import Final import translators as ts from playwright.sync_api import sync_playwright, ViewportSize @@ -10,9 +10,8 @@ from rich.progress import track from utils import settings from utils.console import print_step, print_substep -# do not remove the above line -storymode = False +__all__ = ["download_screenshots_of_reddit_posts"] def download_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int): @@ -22,6 +21,12 @@ def download_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: in reddit_object (Dict): Reddit object received from reddit/subreddit.py screenshot_num (int): Number of screenshots to download """ + # settings values + W: Final[int] = int(settings.config["settings"]["resolution_w"]) + H: Final[int] = int(settings.config["settings"]["resolution_h"]) + lang: Final[str] = settings.config["reddit"]["thread"]["post_lang"] + storymode: Final[bool] = settings.config["settings"]["storymode"] + print_step("Downloading screenshots of reddit posts...") id = re.sub(r"[^\w\s-]", "", reddit_object["thread_id"]) # ! Make sure the reddit screenshots folder exists @@ -30,19 +35,33 @@ def download_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: in with sync_playwright() as p: print_substep("Launching Headless Browser...") - browser = p.chromium.launch(headless=True) # add headless=False for debug - context = browser.new_context() + browser = p.chromium.launch(headless=True) # add headless=False for debug + # Device scale factor (or dsf for short) allows us to increase the resolution of the screenshots + # When the dsf is 1, the width of the screenshot is 600 pixels + # so we need a dsf such that the width of the screenshot is greater than the final resolution of the video + dsf = (W // 600)+1 + + context = browser.new_context( + locale=lang or "en-us", + color_scheme="dark", + viewport=ViewportSize(width=W, height=H), + device_scale_factor=dsf + ) + + # set the theme and disable non-essential cookies if settings.config["settings"]["theme"] == "dark": cookie_file = open("./video_creation/data/cookie-dark-mode.json", encoding="utf-8") else: cookie_file = open("./video_creation/data/cookie-light-mode.json", encoding="utf-8") cookies = json.load(cookie_file) context.add_cookies(cookies) # load preference cookies + # Get the thread screenshot page = context.new_page() page.goto(reddit_object["thread_url"], timeout=0) - page.set_viewport_size(ViewportSize(width=1920, height=1080)) + page.set_viewport_size(ViewportSize(width=W, height=H)) + if page.locator('[data-testid="content-gate"]').is_visible(): # This means the post is NSFW and requires to click the proceed button. @@ -55,11 +74,11 @@ def download_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: in # translate code - if settings.config["reddit"]["thread"]["post_lang"]: + if lang: print_substep("Translating post...") texts_in_tl = ts.google( reddit_object["thread_title"], - to_language=settings.config["reddit"]["thread"]["post_lang"], + to_language=lang, ) page.evaluate( @@ -75,7 +94,7 @@ def download_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: in if storymode: page.locator('[data-click-id="text"]').screenshot(path=f"assets/temp/{id}/png/story_content.png") else: - for idx, comment in enumerate(track(reddit_object["comments"], "Downloading screenshots...")): + for idx, comment in enumerate(track(reddit_object["comments"][:screenshot_num], "Downloading screenshots...")): # Stop if we have reached the screenshot_num if idx >= screenshot_num: break @@ -103,4 +122,8 @@ def download_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: in screenshot_num += 1 print("TimeoutError: Skipping screenshot...") continue - print_substep("Screenshots downloaded Successfully.", style="bold green") + + # close browser instance when we are done using it + browser.close() + + print_substep("Screenshots downloaded Successfully.", style="bold green")