From f4245ec02fbbd97681473fed18f5c6a63fe705fe Mon Sep 17 00:00:00 2001 From: satya ratnam Date: Sat, 4 Jun 2022 16:38:45 +0800 Subject: [PATCH] Added several features to the bot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Features✨ - Change accent - Change background video (random list) - Change video speed (default voice is really slow) - Better naming scheme for output videos in `final` folder --- .env.template | 6 ++- .gitignore | 1 + README.md | 29 +++++++++----- main.py | 32 ++++++++++++--- reddit/subreddit.py | 31 ++++++++++++--- video_creation/background.py | 23 +++++------ video_creation/final_video.py | 73 ++++++++++++++++++++++++++++++++++- video_creation/voices.py | 8 ++-- 8 files changed, 163 insertions(+), 40 deletions(-) diff --git a/.env.template b/.env.template index f00c2ac..d7588e7 100644 --- a/.env.template +++ b/.env.template @@ -3,8 +3,12 @@ REDDIT_CLIENT_SECRET="" REDDIT_USERNAME="" REDDIT_PASSWORD="" +# You can follow the steps here "https://www.wikihow.com/Install-FFmpeg-on-Windows" to install FFmpeg on Windows. +# Alternatively you could download it from here https://ffmpeg.org/download.html +FFMPEG_PATH="" + # Valid options are "yes" and "no" for the variable below -REDDIT_2FA="" +REDDIT_2FA="no" SUBREDDIT="" diff --git a/.gitignore b/.gitignore index b541305..683d69a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ assets/ +final/ .env reddit-bot-351418-5560ebc49cac.json __pycache__ \ No newline at end of file diff --git a/README.md b/README.md index 00e30da..2286b94 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,19 @@ -# Reddit Video Maker Bot 🎥 +# Reddit Video Maker Bot ++🎥 https://user-images.githubusercontent.com/6053155/170525726-2db23ae0-97b8-4bd1-8c95-00da60ce099f.mp4 All done WITHOUT video editing or asset compiling. Just pure ✨programming magic✨. -Created by Lewis Menelaws & [TMRRW](https://tmrrwinc.ca) +Created by Lewis Menelaws & [TMRRW](https://tmrrwinc.ca) modified by [Tya](https://tya.design/) + +This is my first time using python and I tried adding a couple features. + +## New Features✨ + +- Change [accent](https://gtts.readthedocs.io/en/latest/module.html?highlight=accent) +- Change background video (random list) +- Change video speed (default voice is really slow) +- Better naming scheme for output videos in `final` folder [ @@ -32,11 +41,13 @@ These videos on TikTok, YouTube and Instagram get MILLIONS of views across all p 1. Clone this repository 2. Rename `.env.template` to `.env` and replace all values with the appropriate fields. To get Reddit keys (**required**), visit [the Reddit Apps page.](https://www.reddit.com/prefs/apps) TL;DR set up an app that is a "script". Copy your keys into the `.env` file, along with whether your account uses two-factor authentication. -3. Run `pip3 install -r requirements.txt` -4. Run `playwright install` and `playwright install-deps`. -5. Run `python3 main.py` -6. ... -7. Enjoy 😎 +3. Install [FFmpeg](https://ffmpeg.org/download.html) and include the path of the `ffmpeg.exe` into the `.env` file. A guide for installing FFmpeg on windows can be found [here](https://www.wikihow.com/Install-FFmpeg-on-Windows) +4. Run `pip3 install -r requirements.txt` +5. Run `playwright install` and `playwright install-deps`. +6. Make any configurations you want to change in the `main.py` file +7. Run `python3 main.py` +8. ... +9. Enjoy 😎 ## Contributing & Ways to improve 📈 @@ -45,7 +56,7 @@ In its current state, this bot does exactly what it needs to do. However, lots o I have tried to simplify the code so anyone can read it and start contributing at any skill level. Don't be shy :) contribute! - [ ] Allowing users to choose a reddit thread instead of being randomized. -- [ ] Allowing users to choose a background that is picked instead of the Minecraft one. +- [x] Allowing users to choose a background that is picked instead of the Minecraft one. - [x] Allowing users to choose between any subreddit. -- [ ] Allowing users to change voice. +- [x] Allowing users to change voice. - [ ] Creating better documentation and adding a command line interface. diff --git a/main.py b/main.py index a5b64c6..c882fe7 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,6 @@ +from unittest.util import _MAX_LENGTH from utils.console import print_markdown +from utils.console import print_step import time from reddit.subreddit import get_subreddit_threads @@ -6,18 +8,38 @@ from video_creation.background import download_background, chop_background_video from video_creation.voices import save_text_to_mp3 from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts from video_creation.final_video import make_final_video +import random print_markdown( - "### Thanks for using this tool! [Feel free to contribute to this project on GitHub!](https://lewismenelaws.com) If you have any questions, feel free to reach out to me on Twitter or submit a GitHub issue." + "### Thanks for using this tool! [Feel free to contribute to this project on GitHub!](https://lewismenelaws.com) If you have any questions, feel free to reach out to me on Twitter or submit a GitHub issue. \n ### This modified version of this tool was created by [@tya.design](https://tya.design) and is available on [GitHub](https://github.com/Tyaaa-aa/RedditVideoMakerBot)." ) time.sleep(3) +# The maximum length of the video in seconds. (This is the length of the video that will be created, but not 100% accurate) +MAX_LENGTH = 1 + +# Video speed multiplier (1.0 = normal speed, 2.0 = double speed, 0.5 = half speed) +VIDEO_SPEED = 1.4 + +# Voice accent https://gtts.readthedocs.io/en/latest/module.html?highlight=accent +VOICE_ACCENT = "co.uk" + +# Background video to use (video id from youtube) +# Use an array of videos to create a random background video +BACKGROUND_VIDEO_ARRAY = ["5sYdvjXX7YU", "Pt5_GSKIWQM"] +# select random 1 +BACKGROUND_VIDEO = BACKGROUND_VIDEO_ARRAY[random.randrange(0, len(BACKGROUND_VIDEO_ARRAY))] + + reddit_object = get_subreddit_threads() +video_title = reddit_object["thread_title"] +chosen_subreddit = reddit_object["subreddit"] +ffmpeg_exe = reddit_object["ffmpeg_exe"] -length, number_of_comments = save_text_to_mp3(reddit_object) +length, number_of_comments = save_text_to_mp3(reddit_object, MAX_LENGTH, VOICE_ACCENT) download_screenshots_of_reddit_posts(reddit_object, number_of_comments) -download_background() -chop_background_video(length) -final_video = make_final_video(number_of_comments) +download_background(BACKGROUND_VIDEO) +chop_background_video(BACKGROUND_VIDEO, length) +final_video = make_final_video(number_of_comments, chosen_subreddit, video_title, ffmpeg_exe, VIDEO_SPEED) \ No newline at end of file diff --git a/reddit/subreddit.py b/reddit/subreddit.py index 5d020fe..ebea13b 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -3,6 +3,7 @@ import praw import random from dotenv import load_dotenv import os +import sys def get_subreddit_threads(): @@ -13,7 +14,17 @@ def get_subreddit_threads(): load_dotenv() - print_step("Getting AskReddit threads...") + # print_step("Getting AskReddit threads...") + + if os.getenv("FFMPEG_PATH"): + ffmpeg_exe = os.getenv("FFMPEG_PATH") + else: + # ! Prompt the user to enter the path to FFmpeg + try: + ffmpeg_exe = input("What is the path to ffmpeg.exe?") + except ValueError: + print_step("Error with FFmpeg path. Terminating script.", style="bold red") + sys.exit() if os.getenv("REDDIT_2FA").lower() == "yes": print( @@ -38,24 +49,34 @@ def get_subreddit_threads(): if os.getenv("SUBREDDIT"): subreddit = reddit.subreddit(os.getenv("SUBREDDIT")) + subreddit_title = os.getenv("SUBREDDIT") else: # ! Prompt the user to enter a subreddit try: - subreddit = reddit.subreddit( - input("What subreddit would you like to pull from? ") - ) + subreddit_title = input("What subreddit would you like to pull from? ") + subreddit = reddit.subreddit(subreddit_title) except ValueError: subreddit = reddit.subreddit("askreddit") + subreddit_title = "askreddit" print_substep("Subreddit not defined. Using AskReddit.") + + threads = subreddit.hot(limit=25) submission = list(threads)[random.randrange(0, 25)] - print_substep(f"Video will be: {submission.title} :thumbsup:") + + # print_substep(f"Video will be: {submission.title}") + + print_step("Video will be: "+subreddit_title.upper()+" - "+submission.title) + # print_step("Thread Title: " + submission.title) + try: content["thread_url"] = submission.url content["thread_title"] = submission.title content["comments"] = [] + content["subreddit"] = subreddit_title.upper() + content["ffmpeg_exe"] = str(ffmpeg_exe) for top_level_comment in submission.comments: content["comments"].append( diff --git a/video_creation/background.py b/video_creation/background.py index dce46bd..dc86e0e 100644 --- a/video_creation/background.py +++ b/video_creation/background.py @@ -7,43 +7,38 @@ from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip from moviepy.editor import VideoFileClip from utils.console import print_step, print_substep - def get_start_and_end_times(video_length, length_of_clip): random_time = randrange(180, int(length_of_clip) - int(video_length)) return random_time, random_time + video_length -def download_background(): - """Downloads the background video from youtube. - - Shoutout to: bbswitzer (https://www.youtube.com/watch?v=n_Dv4JMiwK8) - """ - - if not Path("assets/mp4/background.mp4").is_file(): +def download_background(youtube_id): + if not Path(f"assets/mp4/background-{youtube_id}.mp4").is_file(): print_step( - "We need to download the Minecraft background video. This is fairly large but it's only done once." + "We need to download the background video. This is fairly large but it's only done once." ) print_substep("Downloading the background video... please be patient.") ydl_opts = { - "outtmpl": "assets/mp4/background.mp4", + "outtmpl": f"assets/mp4/background-{youtube_id}.mp4", "merge_output_format": "mp4", } with YoutubeDL(ydl_opts) as ydl: - ydl.download("https://www.youtube.com/watch?v=n_Dv4JMiwK8") + ydl.download(f"https://www.youtube.com/watch?v={youtube_id}") print_substep("Background video downloaded successfully!", style="bold green") -def chop_background_video(video_length): +def chop_background_video(youtube_id, video_length): print_step("Finding a spot in the background video to chop...") - background = VideoFileClip("assets/mp4/background.mp4") + background_path = f"assets/mp4/background-{youtube_id}.mp4" + background = VideoFileClip(background_path) start_time, end_time = get_start_and_end_times(video_length, background.duration) ffmpeg_extract_subclip( - "assets/mp4/background.mp4", + background_path, start_time, end_time, targetname="assets/mp4/clip.mp4", diff --git a/video_creation/final_video.py b/video_creation/final_video.py index e1f71ff..87a42c8 100644 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -8,12 +8,16 @@ from moviepy.editor import ( CompositeVideoClip, ) from utils.console import print_step +import re +from pathlib import Path +import subprocess +from typing import List W, H = 1080, 1920 -def make_final_video(number_of_clips): +def make_final_video(number_of_clips, chosen_subreddit, video_title, ffmpeg_exe, video_speed): print_step("Creating the final video...") VideoFileClip.reW = lambda clip: clip.resize(width=W) VideoFileClip.reH = lambda clip: clip.resize(width=H) @@ -54,8 +58,73 @@ def make_final_video(number_of_clips): image_concat.audio = audio_composite final = CompositeVideoClip([background_clip, image_concat]) final.write_videofile( - "assets/final_video.mp4", fps=30, audio_codec="aac", audio_bitrate="192k" + "assets/final_video_raw.mp4", fps=30, audio_codec="aac", audio_bitrate="192k" ) + videos = [ + Video(speed=video_speed, path="assets/final_video_raw.mp4"), + ] + + print_step("Creating sped up version of the final video...") + + Path("final/").mkdir(parents=True, exist_ok=True) + final_video_title = urlify(chosen_subreddit)+"-"+urlify(video_title)+".mp4" + concatenate_videos(ffmpeg_exe, + videos=videos, output_file=f"final/"+final_video_title) for i in range(0, number_of_clips): pass + + +class Video(): + def __init__(self, path: str, speed: float = 1.0): + self.path = path + self.speed = speed + + +def concatenate_videos(ffmpeg_exe, videos: List[Video], output_file: str): + + COMMAND_BASE = [ffmpeg_exe] + COMMAND_BASE += ["-n"] # disable file overwriting + + video_count = len(videos) + video_speeds = [float(1/x.speed) for x in videos] + audio_speeds = [float(x.speed) for x in videos] + + cmd_input_files = [] + filters, concat = ("", "") + for i, x in enumerate(videos): + cmd_input_files += ["-i", x.path] + filters += f"[{i}:v] setpts = {video_speeds[i]} * PTS [v{i}];" + filters += f"[{i}:a] atempo = {audio_speeds[i]} [a{i}];" + concat += f"[v{i}][a{i}]" + concat += f"concat = n = {video_count}:v = 1:a = 1 [v_all][a_all]" + + filter_complex = f"{filters}{concat}".replace(" ", "") + + cmd_filter_complex = [ + "-filter_complex", filter_complex, + ] + cmd_map = [ + "-map", "[v_all]", + "-map", "[a_all]", + ] + command = sum([ + COMMAND_BASE, + cmd_input_files, + cmd_filter_complex, + cmd_map, + [output_file], + ], []) + + subprocess.run(command) + + +def urlify(s): + + # Remove all non-word characters (everything except numbers and letters) + s = re.sub(r"[^\w\s]", '', s) + + # Replace all runs of whitespace with a single dash + s = re.sub(r"\s+", '-', s) + + return s diff --git a/video_creation/voices.py b/video_creation/voices.py index e8d8e43..9046ab8 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -5,7 +5,7 @@ from utils.console import print_step, print_substep from rich.progress import track -def save_text_to_mp3(reddit_obj): +def save_text_to_mp3(reddit_obj, maxlength, accent): """Saves Text to MP3 files. Args: @@ -17,15 +17,15 @@ def save_text_to_mp3(reddit_obj): # Create a folder for the mp3 files. Path("assets/mp3").mkdir(parents=True, exist_ok=True) - tts = gTTS(text=reddit_obj["thread_title"], lang="en", slow=False) + tts = gTTS(text=reddit_obj["thread_title"], lang="en", tld=accent, slow=False) tts.save(f"assets/mp3/title.mp3") length += MP3(f"assets/mp3/title.mp3").info.length for idx, comment in track(enumerate(reddit_obj["comments"]), "Saving..."): # ! Stop creating mp3 files if the length is greater than 50 seconds. This can be longer, but this is just a good starting point - if length > 50: + if length > maxlength: break - tts = gTTS(text=comment["comment_body"], lang="en", slow=False) + tts = gTTS(text=comment["comment_body"], lang="en", tld=accent, slow=False) tts.save(f"assets/mp3/{idx}.mp3") length += MP3(f"assets/mp3/{idx}.mp3").info.length