From bfe577643c2134bc383138c2489c2cd2951e5d2e Mon Sep 17 00:00:00 2001 From: John Toniutti Date: Mon, 19 Dec 2022 19:39:03 +0100 Subject: [PATCH] Custom resolution (#1277) Thank you @jotonedev! --- TTS/GTTS.py | 1 - TTS/aws_polly.py | 1 - TTS/engine_wrapper.py | 1 - utils/.config.template.toml | 3 +- utils/console.py | 1 - utils/settings.py | 1 - video_creation/final_video.py | 65 +++++--- video_creation/screenshot_downloader.py | 206 ++++++++++++------------ video_creation/voices.py | 2 - 9 files changed, 147 insertions(+), 134 deletions(-) mode change 100644 => 100755 video_creation/final_video.py diff --git a/TTS/GTTS.py b/TTS/GTTS.py index 3bf8ee3..bff100f 100644 --- a/TTS/GTTS.py +++ b/TTS/GTTS.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import random from gtts import gTTS diff --git a/TTS/aws_polly.py b/TTS/aws_polly.py index fefed92..58323f9 100644 --- a/TTS/aws_polly.py +++ b/TTS/aws_polly.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import random import sys diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py index 6317a1d..6ca63d5 100644 --- a/TTS/engine_wrapper.py +++ b/TTS/engine_wrapper.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import os import re from pathlib import Path diff --git a/utils/.config.template.toml b/utils/.config.template.toml index 1015328..d8cdd25 100644 --- a/utils/.config.template.toml +++ b/utils/.config.template.toml @@ -27,7 +27,8 @@ storymode = { optional = true, type = "bool", default = false, example = false, storymodemethod= { optional = true, default = 1, example = 1, explanation = "Style that's used for the storymode. Set to 0 for single picture display in whole video, set to 1 for fancy looking video ", type = "int", nmin = 0, oob_error = "It's very hard to run something less than once.", options = [0, 1] } 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." } fps = { optional = true, default = 30, example = 30, explanation = "Sets the FPS of the video, 30 is default for best performance. 60 FPS is smoother.", type = "int", nmin = 1, nmax = 60, oob_error = "The FPS HAS to be between 1 and 60" } - +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", "minecraft-2","multiversus","fall-guys","steep", ""], explanation = "Sets the background for the video based on game name" } diff --git a/utils/console.py b/utils/console.py index 2788d63..3419f05 100644 --- a/utils/console.py +++ b/utils/console.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import re from rich.columns import Columns diff --git a/utils/settings.py b/utils/settings.py index 55b8364..221a09c 100755 --- a/utils/settings.py +++ b/utils/settings.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python import re from typing import Tuple, Dict from pathlib import Path diff --git a/video_creation/final_video.py b/video_creation/final_video.py old mode 100644 new mode 100755 index 39b2b10..c258cf9 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -1,8 +1,9 @@ -#!/usr/bin/env python3 -import multiprocessing import os import re +import multiprocessing from os.path import exists +from typing import Tuple, Any, Final +import translators as ts import shutil from typing import Tuple, Any from PIL import Image @@ -26,7 +27,6 @@ from utils import settings from utils.thumbnail import create_thumbnail console = Console() -W, H = 1080, 1920 def name_normalize(name: str) -> str: @@ -36,20 +36,34 @@ def name_normalize(name: str) -> str: name = re.sub(r"(\d+)\s?\/\s?(\d+)", r"\1 of \2", name) name = re.sub(r"(\w+)\s?\/\s?(\w+)", r"\1 or \2", name) name = re.sub(r"\/", r"", name) - name[:30] 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 - else: return name +def prepare_background(reddit_id: str, W: int, H: int) -> VideoFileClip: + clip = ( + VideoFileClip(f"assets/temp/{reddit_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, @@ -63,23 +77,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"]) + + reddit_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(reddit_id, W=W, H=H) # Gather all audio clips if settings.config["settings"]["storymode"]: @@ -112,11 +129,12 @@ def make_final_video( 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") + ImageClip(f"assets/temp/{reddit_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), @@ -238,15 +256,14 @@ 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) - # if + final = Video(final).add_watermark( - text=f"Background credit: {background_config[2]}", opacity=0.4, redditid=reddit_obj + text=f"Background credit: {background_config[2]}", + opacity=0.4, + redditid=reddit_obj, ) - - - final.write_videofile( - f"assets/temp/{id}/temp.mp4", + f"assets/temp/{reddit_id}/temp.mp4", fps=int(settings.config["settings"]["fps"]), audio_codec="aac", audio_bitrate="192k", @@ -254,7 +271,7 @@ def make_final_video( threads=multiprocessing.cpu_count(), ) ffmpeg_extract_subclip( - f"assets/temp/{id}/temp.mp4", + f"assets/temp/{reddit_id}/temp.mp4", 0, length, targetname=f"results/{subreddit}/{filename}.mp4", @@ -265,7 +282,7 @@ def make_final_video( save_data(subreddit, filename+".mp4", title, idx, background_config[2]) print_step("Removing temporary files 🗑") - cleanups = cleanup(id) + cleanups = cleanup(reddit_id) print_substep(f"Removed {cleanups} temporary files 🗑") print_substep("See result in the results folder!") diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index 540f8b3..8e66d21 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -1,7 +1,7 @@ import json import re from pathlib import Path -from typing import Dict +from typing import Dict, Final import translators as ts from playwright.async_api import async_playwright # pylint: disable=unused-import @@ -12,8 +12,8 @@ from utils import settings from utils.console import print_step, print_substep from utils.imagenarator import imagemaker -# do not remove the above line +__all__ = ["download_screenshots_of_reddit_posts"] def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int): """Downloads screenshots of reddit posts as seen on the web. Downloads to assets/temp/png @@ -22,13 +22,16 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int): 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"]) - + reddit_id = re.sub(r"[^\w\s-]", "", reddit_object["thread_id"]) # ! Make sure the reddit screenshots folder exists - Path(f"assets/temp/{id}/png").mkdir(parents=True, exist_ok=True) + Path(f"assets/temp/{reddit_id}/png").mkdir(parents=True, exist_ok=True) def download(cookie_file, num=None): screenshot_num = num @@ -38,111 +41,110 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int): browser = p.chromium.launch() # headless=False #to check for chrome view context = browser.new_context() - cookies = json.load(cookie_file) + # 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.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=1080, height=1920)) - if page.locator('[data-testid="content-gate"]').is_visible(): - # This means the post is NSFW and requires to click the proceed button. + context = browser.new_context( + locale=lang or "en-us", + color_scheme="dark", + viewport=ViewportSize(width=W, height=H), + device_scale_factor=dsf, + ) - print_substep("Post is NSFW. You are spicy...") - page.locator('[data-testid="content-gate"] button').click() - page.wait_for_load_state() # Wait for page to fully load + # 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) + cookie_file.close() - 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 + 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=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. + + print_substep("Post is NSFW. You are spicy...") + page.locator('[data-testid="content-gate"] button').click() + page.wait_for_load_state() # Wait for page to fully load + + 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 - if settings.config["reddit"]["thread"]["post_lang"]: - print_substep("Translating post...") - texts_in_tl = ts.google( - reddit_object["thread_title"], - to_language=settings.config["reddit"]["thread"]["post_lang"], - ) - - page.evaluate( - "tl_content => document.querySelector('[data-test-id=\"post-content\"] > div:nth-child(3) > div > div').textContent = tl_content", - texts_in_tl, - ) - else: - print_substep("Skipping translation...") - postcontentpath = f"assets/temp/{id}/png/title.png" - page.locator('[data-test-id="post-content"]').screenshot( - path=postcontentpath + if lang: + print_substep("Translating post...") + texts_in_tl = ts.google( + reddit_object["thread_title"], + to_language=lang, ) - if settings.config["settings"]["storymode"]: + page.evaluate( + "tl_content => document.querySelector('[data-test-id=\"post-content\"] > div:nth-child(3) > div > div').textContent = tl_content", + texts_in_tl, + ) + else: + print_substep("Skipping translation...") - try: # new change - page.locator('[data-click-id="text"]').first.screenshot( - path=f"assets/temp/{id}/png/story_content.png" + postcontentpath = f"assets/temp/{reddit_id}/png/title.png" + page.locator('[data-test-id="post-content"]').screenshot(path=postcontentpath) + + if storymode: + page.locator('[data-click-id="text"]').screenshot( + path=f"assets/temp/{reddit_id}/png/story_content.png" + ) + else: + 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 + + if page.locator('[data-testid="content-gate"]').is_visible(): + page.locator('[data-testid="content-gate"] button').click() + + page.goto(f'https://reddit.com{comment["comment_url"]}', timeout=0) + + # translate code + + if settings.config["reddit"]["thread"]["post_lang"]: + comment_tl = ts.google( + comment["comment_body"], + to_language=settings.config["reddit"]["thread"]["post_lang"], ) - except: - exit - if not settings.config["settings"]["storymode"]: - for idx, comment in enumerate( - track(reddit_object["comments"], "Downloading screenshots...") - ): - # Stop if we have reached the screenshot_num - if idx >= screenshot_num: - break + page.evaluate( + '([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"]], + ) + try: + 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 + print("TimeoutError: Skipping screenshot...") + continue - if page.locator('[data-testid="content-gate"]').is_visible(): - page.locator('[data-testid="content-gate"] button').click() + # close browser instance when we are done using it + browser.close() - page.goto(f'https://reddit.com{comment["comment_url"]}', timeout=0) - - # translate code - - if settings.config["reddit"]["thread"]["post_lang"]: - comment_tl = ts.google( - comment["comment_body"], - to_language=settings.config["reddit"]["thread"][ - "post_lang" - ], - ) - page.evaluate( - '([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"]], - ) - try: - page.locator(f"#t1_{comment['comment_id']}").screenshot( - path=f"assets/temp/{id}/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") - - # story=False - theme = settings.config["settings"]["theme"] - if theme == "dark": - cookie_file = open( - "./video_creation/data/cookie-dark-mode.json", encoding="utf-8" - ) - bgcolor = (33, 33, 36, 255) - txtcolor = (240, 240, 240) - else: - cookie_file = open( - "./video_creation/data/cookie-light-mode.json", encoding="utf-8" - ) - bgcolor = (255, 255, 255, 255) - txtcolor = (0, 0, 0) - if settings.config["settings"]["storymode"]: - if settings.config["settings"]["storymodemethod"] == 1: - # for idx,item in enumerate(reddit_object["thread_post"]): - imagemaker(theme=bgcolor, reddit_obj=reddit_object, txtclr=txtcolor) - - if ( - settings.config["settings"]["storymodemethod"] == 0 - or not settings.config["settings"]["storymode"] - ): - download(cookie_file, screenshot_num) + print_substep("Screenshots downloaded Successfully.", style="bold green") \ No newline at end of file diff --git a/video_creation/voices.py b/video_creation/voices.py index 68d5085..425f589 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - from typing import Tuple from rich.console import Console