diff --git a/.gitignore b/.gitignore index 5f4115f..41bdd5e 100644 --- a/.gitignore +++ b/.gitignore @@ -244,4 +244,4 @@ video_creation/data/videos.json video_creation/data/envvars.txt config.toml -video_creation/data/videos.json \ No newline at end of file +*.exe \ No newline at end of file diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py index b56269e..90fe45f 100644 --- a/TTS/engine_wrapper.py +++ b/TTS/engine_wrapper.py @@ -14,7 +14,9 @@ from utils import settings from utils.console import print_step, print_substep from utils.voice import sanitize_text -DEFAULT_MAX_LENGTH: int = 50 # video length variable + +DEFAULT_MAX_LENGTH: int = 50 # Video length variable, edit this on your own risk. It should work, but it's not supported + class TTSEngine: @@ -74,7 +76,7 @@ class TTSEngine: self.add_periods() self.call_tts("title", process_text(self.reddit_object["thread_title"])) # processed_text = ##self.reddit_object["thread_post"] != "" - idx = None + idx = 0 if settings.config["settings"]["storymode"]: if settings.config["settings"]["storymodemethod"] == 0: diff --git a/main.py b/main.py index 23dd55f..b8692c1 100755 --- a/main.py +++ b/main.py @@ -1,12 +1,11 @@ #!/usr/bin/env python import math import sys -from logging import error from os import name from pathlib import Path from subprocess import Popen +from typing import NoReturn -import ffmpeg from prawcore import ResponseException from utils.console import print_substep from reddit.subreddit import get_subreddit_threads @@ -59,11 +58,7 @@ def main(POST_ID=None) -> None: 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) + make_final_video(number_of_comments, length, reddit_object, bg_config) def run_many(times) -> None: @@ -75,31 +70,26 @@ def run_many(times) -> None: Popen("cls" if name == "nt" else "clear", shell=True).wait() -def shutdown(): - try: - redditid - except NameError: - print("Exiting...") - exit() - else: +def shutdown() -> NoReturn: + if "redditid" in globals(): print_markdown("## Clearing temp files") cleanup(redditid) - print("Exiting...") - exit() + + print("Exiting...") + sys.exit() 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 + 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.") + sys.exit() + ffmpeg_install() directory = Path().absolute() config = settings.check_toml( - f"{directory}/utils/.config.template.toml", "config.toml" + f"{directory}/utils/.config.template.toml", f"{directory}/config.toml" ) - config is False and exit() + config is False and sys.exit() + if ( not settings.config["settings"]["tts"]["tiktok_sessionid"] or settings.config["settings"]["tts"]["tiktok_sessionid"] == "" @@ -108,7 +98,7 @@ if __name__ == "__main__": "TikTok voice requires a sessionid! Check our documentation on how to obtain one.", "bold red", ) - exit() + sys.exit() try: if config["reddit"]["thread"]["post_id"]: for index, post_id in enumerate( @@ -127,10 +117,8 @@ if __name__ == "__main__": except KeyboardInterrupt: shutdown() except ResponseException: - # error for invalid credentials print_markdown("## Invalid credentials") print_markdown("Please check your credentials in the config.toml file") - shutdown() except Exception as err: config["settings"]["tts"]["tiktok_sessionid"] = "REDACTED" diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..38bbeb7 --- /dev/null +++ b/run.bat @@ -0,0 +1,15 @@ +@echo off +set VENV_DIR=.venv + +if exist "%VENV_DIR%" ( + echo Activating virtual environment... + call "%VENV_DIR%\Scripts\activate.bat" +) + +echo Running Python script... +python main.py + +if errorlevel 1 ( + echo An error occurred. Press any key to exit. + pause >nul +) diff --git a/utils/.config.template.toml b/utils/.config.template.toml index c5ec3ba..90e511f 100644 --- a/utils/.config.template.toml +++ b/utils/.config.template.toml @@ -14,7 +14,6 @@ max_comment_length = { default = 500, optional = false, nmin = 10, nmax = 10000, min_comment_length = { default = 1, optional = true, nmin = 0, nmax = 10000, type = "int", explanation = "min_comment_length number of characters a comment can have. default is 0", example = 50, oob_error = "the max comment length should be between 1 and 100" } post_lang = { default = "", optional = true, explanation = "The language you would like to translate to.", example = "es-cr", options = ['','af', 'ak', 'am', 'ar', 'as', 'ay', 'az', 'be', 'bg', 'bho', 'bm', 'bn', 'bs', 'ca', 'ceb', 'ckb', 'co', 'cs', 'cy', 'da', 'de', 'doi', 'dv', 'ee', 'el', 'en', 'en-US', 'eo', 'es', 'et', 'eu', 'fa', 'fi', 'fr', 'fy', 'ga', 'gd', 'gl', 'gn', 'gom', 'gu', 'ha', 'haw', 'hi', 'hmn', 'hr', 'ht', 'hu', 'hy', 'id', 'ig', 'ilo', 'is', 'it', 'iw', 'ja', 'jw', 'ka', 'kk', 'km', 'kn', 'ko', 'kri', 'ku', 'ky', 'la', 'lb', 'lg', 'ln', 'lo', 'lt', 'lus', 'lv', 'mai', 'mg', 'mi', 'mk', 'ml', 'mn', 'mni-Mtei', 'mr', 'ms', 'mt', 'my', 'ne', 'nl', 'no', 'nso', 'ny', 'om', 'or', 'pa', 'pl', 'ps', 'pt', 'qu', 'ro', 'ru', 'rw', 'sa', 'sd', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'st', 'su', 'sv', 'sw', 'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tr', 'ts', 'tt', 'ug', 'uk', 'ur', 'uz', 'vi', 'xh', 'yi', 'yo', 'zh-CN', 'zh-TW', 'zu'] } min_comments = { default = 20, optional = false, nmin = 10, type = "int", explanation = "The minimum number of comments a post should have to be included. default is 20", example = 29, oob_error = "the minimum number of comments should be between 15 and 999999" } -#post_url = { optional = true, default = "", regex = "^https:\\/\\/www\\.reddit\\.com\\/r\\/[a-zA-Z0-9]+\\/comments\\/[a-zA-Z0-9]+\\/[a-zA-Z0-9_]+\\/$", explanation = "Not working currently Use if you want to use a specific post.", example = "https://www.reddit.com/r/buildapc/comments/yzh07p/have_you_switched_to_windows_11/" } [ai] ai_similarity_enabled = {optional = true, option = [true, false], default = false, type = "bool", explanation = "Threads read from Reddit are sorted based on their similarity to the keywords given below"} @@ -25,7 +24,7 @@ allow_nsfw = { optional = false, type = "bool", default = false, example = false theme = { optional = false, default = "dark", example = "light", options = ["dark", "light", "transparent", ], explanation = "Sets the Reddit theme, either LIGHT or DARK. For story mode you can also use a transparent background." } 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" } +#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, great for subreddits with stories" } 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." } @@ -54,5 +53,5 @@ tiktok_voice = { optional = true, default = "en_us_001", example = "en_us_006", 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" } +#silence_duration = { optional = true, example = "0.1", explanation = "Time in seconds between TTS comments", default = 0.3, type = "float" } no_emojis = { optional = false, type = "bool", default = false, example = false, options = [true, false,], explanation = "Whether to remove emojis from the comments" } \ No newline at end of file diff --git a/utils/cleanup.py b/utils/cleanup.py index e035980..6e00d4c 100644 --- a/utils/cleanup.py +++ b/utils/cleanup.py @@ -1,29 +1,20 @@ import os from os.path import exists +import shutil def _listdir(d): # listdir with full path return [os.path.join(d, f) for f in os.listdir(d)] -def cleanup(id) -> int: +def cleanup(reddit_id) -> int: """Deletes all temporary assets in assets/temp Returns: int: How many files were deleted """ - if exists(f"../assets/temp/{id}/"): - count = 0 - files = [f for f in os.listdir(f"../assets/temp/{id}/") if f.endswith(".mp4")] - count += len(files) - for f in files: - os.remove(f"../assets/temp/{id}/{f}") - REMOVE_DIRS = [f"../assets/temp/{id}/mp3/", f"../assets/temp/{id}/png/"] - for d in REMOVE_DIRS: - if exists(d): - count += len(_listdir(d)) - for f in _listdir(d): - os.remove(f) - os.rmdir(d) - os.rmdir(f"../assets/temp/{id}/") - return count + directory = f"../assets/temp/{reddit_id}/" + if exists(directory): + shutil.rmtree(directory) + + return 1 diff --git a/utils/ffmpeg_install.py b/utils/ffmpeg_install.py index acee91a..1171d96 100644 --- a/utils/ffmpeg_install.py +++ b/utils/ffmpeg_install.py @@ -17,22 +17,16 @@ def ffmpeg_install_windows(): os.rename("ffmpeg-master-latest-win64-gpl", "ffmpeg") # Move the files inside bin to the root for file in os.listdir("ffmpeg/bin"): - os.rename(f"ffmpeg/bin/{file}", f"ffmpeg/{file}") + os.rename(f"ffmpeg/bin/{file}", f"./{file}") os.rmdir("ffmpeg/bin") for file in os.listdir("ffmpeg/doc"): os.remove(f"ffmpeg/doc/{file}") os.rmdir("ffmpeg/doc") - # Add to the path - 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() + os.remove("ffmpeg/LICENSE.txt") + os.rmdir("ffmpeg/") + + print("FFmpeg installed successfully! Please restart your computer and then re-run the program.") + except Exception as e: print( "An error occurred while trying to install FFmpeg. Please try again. Otherwise, please install FFmpeg manually and try again." @@ -79,17 +73,12 @@ def ffmpeg_install_mac(): def ffmpeg_install(): try: # Try to run the FFmpeg command - subprocess.run( - ["ffmpeg", "-version"], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - print( - "FFmpeg is installed on this system! If you are seeing this error for the second time, restart your computer." - ) + subprocess.run(['ffmpeg', '-version'], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except FileNotFoundError as e: - print("FFmpeg is not installed on this system.") + # Check if there's ffmpeg.exe in the current directory + if os.path.exists("./ffmpeg.exe"): + print('FFmpeg is installed on this system! If you are seeing this error for the second time, restart your computer.') + print('FFmpeg is not installed on this system.') resp = input( "We can try to automatically install it for you. Would you like to do that? (y/n): " ) diff --git a/utils/posttextparser.py b/utils/posttextparser.py index 9b1e306..120d4aa 100644 --- a/utils/posttextparser.py +++ b/utils/posttextparser.py @@ -1,4 +1,7 @@ +import os import re +import time +from typing import List import spacy @@ -7,25 +10,25 @@ from utils.voice import sanitize_text # working good -def posttextparser(obj): - text = re.sub("\n", "", obj) - +def posttextparser(obj, *, tried: bool = False) -> List[str]: + text: str = re.sub("\n", " ", obj) try: nlp = spacy.load("en_core_web_sm") - except OSError: + except OSError as e: + if not tried: + os.system("python -m spacy download en_core_web_sm") + time.sleep(5) + return posttextparser(obj, tried=True) print_step( - "The spacy model can't load. You need to install it with the command \npython -m spacy download en_core_web_sm" - ) - exit() + "The spacy model can't load. You need to install it with the command \npython -m spacy download en_core_web_sm ") + raise e doc = nlp(text) newtext: list = [] - # to check for space str for line in doc.sents: if sanitize_text(line.text): newtext.append(line.text) - # print(line) return newtext diff --git a/video_creation/background.py b/video_creation/background.py index 1fb1ffc..68c255a 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -26,8 +26,6 @@ def load_background_options(): 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] diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 34eb449..52ebe04 100644 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -11,11 +11,11 @@ from PIL import Image from rich.console import Console from rich.progress import track -from utils import settings from utils.cleanup import cleanup from utils.console import print_step, print_substep from utils.thumbnail import create_thumbnail from utils.videos import save_data +from utils import settings import tempfile import threading @@ -97,9 +97,9 @@ def prepare_background(reddit_id: str, W: int, H: int) -> str: ) try: output.run(quiet=True) - except Exception as e: - print(e) - exit() + except ffmpeg.Error as e: + print(e.stderr.decode("utf8")) + exit(1) return output_path @@ -352,7 +352,7 @@ def make_final_video( text=text, x=f"(w-text_w)", y=f"(h-text_h)", - fontsize=12, + fontsize=5, fontcolor="White", fontfile=os.path.join("fonts", "Roboto-Regular.ttf"), ) @@ -362,7 +362,7 @@ def make_final_video( pbar = tqdm(total=100, desc="Progress: ", bar_format="{l_bar}{bar}", unit=" %") - def on_update_example(progress): + def on_update_example(progress) -> None: status = round(progress * 100, 2) old_percentage = pbar.n pbar.update(status - old_percentage) @@ -399,25 +399,28 @@ def make_final_video( ) # 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, - ) + try: + 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, + ) + except ffmpeg.Error as e: + print(e.stderr.decode("utf8")) + exit(1) + old_percentage = pbar.n pbar.update(100 - old_percentage) pbar.close() diff --git a/video_creation/screenshot_downloader.py b/video_creation/screenshot_downloader.py index cc4c6fa..f7510f6 100644 --- a/video_creation/screenshot_downloader.py +++ b/video_creation/screenshot_downloader.py @@ -237,7 +237,7 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int): page.goto(f'https://reddit.com{comment["comment_url"]}', timeout=0) - # translate code + # translate code if settings.config["reddit"]["thread"]["post_lang"]: comment_tl = translators.google( @@ -282,3 +282,4 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int): browser.close() print_substep("Screenshots downloaded Successfully.", style="bold green") +